/*
* Copyright (c) 2011-2016, Peter Abeles. All Rights Reserved.
*
* This file is part of BoofCV (http://boofcv.org).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package boofcv.demonstrations.shapes;
import boofcv.abst.filter.binary.InputToBinary;
import boofcv.alg.filter.binary.BinaryImageOps;
import boofcv.alg.filter.binary.Contour;
import boofcv.alg.shapes.FitData;
import boofcv.alg.shapes.ShapeFittingOps;
import boofcv.factory.filter.binary.ConfigThreshold;
import boofcv.factory.filter.binary.FactoryThresholdBinary;
import boofcv.gui.DemonstrationBase;
import boofcv.gui.binary.VisualizeBinaryData;
import boofcv.gui.feature.VisualizeFeatures;
import boofcv.gui.feature.VisualizeShapes;
import boofcv.gui.image.ImageZoomPanel;
import boofcv.gui.image.ShowImages;
import boofcv.struct.ConnectRule;
import boofcv.struct.PointIndex_I32;
import boofcv.struct.image.GrayU8;
import boofcv.struct.image.ImageType;
import georegression.struct.point.Point2D_I32;
import georegression.struct.shapes.EllipseRotated_F64;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Fits shapes to contours from binary images
*
* @author Peter Abeles
*/
public class ShapeFitContourApp
extends DemonstrationBase
implements ThresholdControlPanel.Listener
{
// displays intensity image
VisualizePanel gui = new VisualizePanel();
// converted input image
GrayU8 inputPrev = new GrayU8(1,1);
GrayU8 binary = new GrayU8(1,1);
GrayU8 filtered = new GrayU8(1,1);
// if it has processed an image or not
boolean processImage = false;
InputToBinary inputToBinary;
// Found contours
List contours;
BufferedImage original;
BufferedImage work = new BufferedImage(1,1,BufferedImage.TYPE_INT_RGB);
ShapeFitContourPanel controlPanel;
public ShapeFitContourApp(List examples ) {
super(examples, ImageType.single(GrayU8.class));
controlPanel = new ShapeFitContourPanel(this);
add(BorderLayout.WEST, controlPanel);
add(BorderLayout.CENTER, gui);
ConfigThreshold config = controlPanel.getThreshold().createConfig();
inputToBinary = FactoryThresholdBinary.threshold(config,GrayU8.class);
}
@Override
public synchronized void processImage(final BufferedImage buffered, GrayU8 input ) {
if( buffered != null ) {
original = conditionalDeclare(buffered,original);
work = conditionalDeclare(buffered,work);
this.original.createGraphics().drawImage(buffered,0,0,null);
binary.reshape(input.getWidth(), input.getHeight());
filtered.reshape(input.getWidth(),input.getHeight());
inputPrev.setTo(input);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
Dimension d = gui.getPreferredSize();
if( d.getWidth() < buffered.getWidth() || d.getHeight() < buffered.getHeight() ) {
gui.setPreferredSize(new Dimension(buffered.getWidth(), buffered.getHeight()));
}
}});
} else {
input = inputPrev;
}
process(input);
}
public synchronized void viewUpdated() {
if( contours == null )
return;
int view = controlPanel.getSelectedView();
Graphics2D g2 = work.createGraphics();
if( view == 0 ) {
g2.drawImage(original, 0, 0, null);
} else if( view == 1 ){
VisualizeBinaryData.renderBinary(binary,false,work);
} else {
g2.setColor(Color.BLACK);
g2.fillRect(0,0,work.getWidth(),work.getHeight());
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
gui.setScale(controlPanel.getZoom());
gui.setBufferedImage(work);
}
});
}
public void process( final GrayU8 input ) {
// threshold the input image
inputToBinary.process(input,binary);
// reduce noise with some filtering
BinaryImageOps.erode8(binary, 1, filtered);
BinaryImageOps.dilate8(filtered, 1, binary);
// Find the contour around the shapes
contours = BinaryImageOps.contour(binary, ConnectRule.EIGHT,null);
processImage = true;
viewUpdated();
}
@Override
public void imageThresholdUpdated() {
ConfigThreshold config = controlPanel.getThreshold().createConfig();
inputToBinary = FactoryThresholdBinary.threshold(config,GrayU8.class);
processImageThread(null,null);
}
protected void renderVisuals( Graphics2D g2 , double scale ) {
int activeAlg = controlPanel.getSelectedAlgorithm();
g2.setStroke(new BasicStroke(3));
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if( controlPanel.contoursVisible ) {
g2.setStroke(new BasicStroke(1));
g2.setColor(new Color(0, 100, 0));
VisualizeBinaryData.renderExternal(contours, false,true, scale, g2);
}
if( activeAlg == 0 ) {
double splitFraction = controlPanel.getSplitFraction();
double minimumSplitFraction = controlPanel.getMinimumSplitFraction();
for( Contour c : contours ) {
List vertexes = ShapeFittingOps.fitPolygon(
c.external, true, splitFraction, minimumSplitFraction, 100);
g2.setColor(Color.RED);
visualizePolygon(g2, scale, vertexes);
for( List internal : c.internal ) {
vertexes = ShapeFittingOps.fitPolygon(internal, true, splitFraction, minimumSplitFraction, 100);
g2.setColor(Color.GREEN);
visualizePolygon(g2, scale, vertexes);
}
}
} else if( activeAlg == 1 ) {
// Filter small contours since they can generate really wacky ellipses
for( Contour c : contours ) {
if( c.external.size() > 10) {
FitData ellipse = ShapeFittingOps.fitEllipse_I32(c.external,0,false,null);
g2.setColor(Color.RED);
g2.setStroke(new BasicStroke(2.5f));
VisualizeShapes.drawEllipse(ellipse.shape,scale, g2);
}
for( List internal : c.internal ) {
if( internal.size() <= 10 )
continue;
FitData ellipse = ShapeFittingOps.fitEllipse_I32(internal,0,false,null);
g2.setColor(Color.GREEN);
g2.setStroke(new BasicStroke(2.5f));
VisualizeShapes.drawEllipse(ellipse.shape,scale, g2);
}
}
}
}
private void visualizePolygon(Graphics2D g2, double scale, List vertexes) {
g2.setStroke(new BasicStroke(2));
VisualizeShapes.drawPolygon(vertexes, true,scale, g2);
if( controlPanel.isCornersVisible() ) {
g2.setColor(Color.BLUE);
g2.setStroke(new BasicStroke(2f));
for (PointIndex_I32 p : vertexes) {
VisualizeFeatures.drawCircle(g2, scale * (p.x+0.5), scale * (p.y+0.5), 5);
}
}
}
class VisualizePanel extends ImageZoomPanel {
@Override
protected void paintInPanel(AffineTransform tran, Graphics2D g2) {
synchronized ( ShapeFitContourApp.this ) {
renderVisuals(g2,scale);
}
}
}
public static void main( String args[] ) {
List examples = new ArrayList();
examples.add("particles01.jpg");
examples.add("shapes/shapes02.png");
examples.add("shapes/line_text_test_image.png");
ShapeFitContourApp app = new ShapeFitContourApp(examples);
app.openFile(new File(examples.get(0)));
app.waitUntilDoneProcessing();
ShowImages.showWindow(app,"Contour Shape Fitting",true);
}
}
Ads help maintain this website.