Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split Filter Contours #627

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
import edu.wpi.grip.core.FileManager;
import edu.wpi.grip.core.OperationMetaData;
import edu.wpi.grip.core.events.OperationAddedEvent;
import edu.wpi.grip.core.operations.composite.AdvancedFilterContoursOperation;
import edu.wpi.grip.core.operations.composite.BlobsReport;
import edu.wpi.grip.core.operations.composite.BlurOperation;
import edu.wpi.grip.core.operations.composite.ContoursReport;
import edu.wpi.grip.core.operations.composite.ConvexHullsOperation;
import edu.wpi.grip.core.operations.composite.DesaturateOperation;
import edu.wpi.grip.core.operations.composite.DistanceTransformOperation;
import edu.wpi.grip.core.operations.composite.FilterContoursOperation;
import edu.wpi.grip.core.operations.composite.FilterLinesOperation;
import edu.wpi.grip.core.operations.composite.FindBlobsOperation;
import edu.wpi.grip.core.operations.composite.FindContoursOperation;
Expand All @@ -23,6 +23,7 @@
import edu.wpi.grip.core.operations.composite.RGBThresholdOperation;
import edu.wpi.grip.core.operations.composite.ResizeOperation;
import edu.wpi.grip.core.operations.composite.SaveImageOperation;
import edu.wpi.grip.core.operations.composite.SimpleFilterContoursOperation;
import edu.wpi.grip.core.operations.composite.SwitchOperation;
import edu.wpi.grip.core.operations.composite.ThresholdMoving;
import edu.wpi.grip.core.operations.composite.ValveOperation;
Expand Down Expand Up @@ -85,8 +86,10 @@ public class Operations {
() -> new DesaturateOperation(isf, osf)),
new OperationMetaData(DistanceTransformOperation.DESCRIPTION,
() -> new DistanceTransformOperation(isf, osf)),
new OperationMetaData(FilterContoursOperation.DESCRIPTION,
() -> new FilterContoursOperation(isf, osf)),
new OperationMetaData(SimpleFilterContoursOperation.DESCRIPTION,
() -> new SimpleFilterContoursOperation(isf, osf)),
new OperationMetaData(AdvancedFilterContoursOperation.DESCRIPTION,
() -> new AdvancedFilterContoursOperation(isf, osf)),
new OperationMetaData(FilterLinesOperation.DESCRIPTION,
() -> new FilterLinesOperation(isf, osf)),
new OperationMetaData(FindBlobsOperation.DESCRIPTION,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

import com.google.common.collect.ImmutableList;

import org.bytedeco.javacpp.opencv_core;

import java.util.List;

import static org.bytedeco.javacpp.opencv_core.Mat;
Expand All @@ -19,6 +21,7 @@
import static org.bytedeco.javacpp.opencv_imgproc.boundingRect;
import static org.bytedeco.javacpp.opencv_imgproc.contourArea;
import static org.bytedeco.javacpp.opencv_imgproc.convexHull;
import static org.bytedeco.javacpp.opencv_imgproc.minAreaRect;

/**
* An {@link Operation} that takes in a list of contours and outputs a list of any contours in the
Expand All @@ -28,11 +31,11 @@
* small objects, as well as contours that do not meet the expected characteristics of the feature
* we're actually looking for. So, this operation can help narrow them down.
*/
public class FilterContoursOperation implements Operation {
public class AdvancedFilterContoursOperation implements Operation {

public static final OperationDescription DESCRIPTION =
OperationDescription.builder()
.name("Filter Contours")
.name("Advanced Filter Contours")
.summary("Find contours matching certain criteria")
.category(OperationDescription.Category.FEATURE_DETECTION)
.icon(Icon.iconStream("find-contours"))
Expand All @@ -45,9 +48,19 @@ public class FilterContoursOperation implements Operation {
private final SocketHint<Number> minAreaHint =
SocketHints.Inputs.createNumberSpinnerSocketHint("Min Area", 0, 0, Integer.MAX_VALUE);

private final SocketHint<Number> maxAreaHint =
SocketHints.Inputs.createNumberSpinnerSocketHint("Max Area", 10000, 0, Integer.MAX_VALUE);

private final SocketHint<Number> minPerimeterHint =
SocketHints.Inputs.createNumberSpinnerSocketHint("Min Perimeter", 0, 0, Integer.MAX_VALUE);

private final SocketHint<Boolean> rotatedRectHint =
SocketHints.createBooleanSocketHint("Rotated Rectangles", false);

private final SocketHint<Number> maxPerimeterHint =
SocketHints.Inputs.createNumberSpinnerSocketHint("Max Perimeter", 10000, 0,
Integer.MAX_VALUE);

private final SocketHint<Number> minWidthHint =
SocketHints.Inputs.createNumberSpinnerSocketHint("Min Width", 0, 0, Integer.MAX_VALUE);

Expand Down Expand Up @@ -79,7 +92,10 @@ public class FilterContoursOperation implements Operation {

private final InputSocket<ContoursReport> contoursSocket;
private final InputSocket<Number> minAreaSocket;
private final InputSocket<Number> maxAreaSocket;
private final InputSocket<Number> minPerimeterSocket;
private final InputSocket<Number> maxPerimeterSocket;
private final InputSocket<Boolean> rotatedRectSocket;
private final InputSocket<Number> minWidthSocket;
private final InputSocket<Number> maxWidthSocket;
private final InputSocket<Number> minHeightSocket;
Expand All @@ -93,11 +109,14 @@ public class FilterContoursOperation implements Operation {
private final OutputSocket<ContoursReport> outputSocket;

@SuppressWarnings("JavadocMethod")
public FilterContoursOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
public AdvancedFilterContoursOperation(InputSocket.Factory inputSocketFactory,
OutputSocket.Factory outputSocketFactory) {
this.contoursSocket = inputSocketFactory.create(contoursHint);
this.minAreaSocket = inputSocketFactory.create(minAreaHint);
this.maxAreaSocket = inputSocketFactory.create(maxAreaHint);
this.minPerimeterSocket = inputSocketFactory.create(minPerimeterHint);
this.maxPerimeterSocket = inputSocketFactory.create(maxPerimeterHint);
this.rotatedRectSocket = inputSocketFactory.create(rotatedRectHint);
this.minWidthSocket = inputSocketFactory.create(minWidthHint);
this.maxWidthSocket = inputSocketFactory.create(maxWidthHint);
this.minHeightSocket = inputSocketFactory.create(minHeightHint);
Expand All @@ -116,16 +135,19 @@ public List<InputSocket> getInputSockets() {
return ImmutableList.of(
contoursSocket,
minAreaSocket,
maxAreaSocket,
minPerimeterSocket,
maxPerimeterSocket,
rotatedRectSocket,
minWidthSocket,
maxWidthSocket,
minHeightSocket,
maxHeightSocket,
soliditySocket,
maxVertexSocket,
minVertexSocket,
maxVertexSocket,
minRatioSocket,
maxRatioSocket
maxRatioSocket,
soliditySocket
);
}

Expand All @@ -141,7 +163,10 @@ public List<OutputSocket> getOutputSockets() {
public void perform() {
final InputSocket<ContoursReport> inputSocket = contoursSocket;
final double minArea = minAreaSocket.getValue().get().doubleValue();
final double maxArea = maxAreaSocket.getValue().get().doubleValue();
final double minPerimeter = minPerimeterSocket.getValue().get().doubleValue();
final double maxPerimeter = maxPerimeterSocket.getValue().get().doubleValue();
final boolean rotatedRect = rotatedRectSocket.getValue().get().booleanValue();
final double minWidth = minWidthSocket.getValue().get().doubleValue();
final double maxWidth = maxWidthSocket.getValue().get().doubleValue();
final double minHeight = minHeightSocket.getValue().get().doubleValue();
Expand All @@ -165,19 +190,41 @@ public void perform() {
for (int i = 0; i < inputContours.size(); i++) {
final Mat contour = inputContours.get(i);

final Rect bb = boundingRect(contour);
if (bb.width() < minWidth || bb.width() > maxWidth) {
double width;
double height;
if (rotatedRect) {
final opencv_core.RotatedRect bb = minAreaRect(contour);
opencv_core.Point2f points = new opencv_core.Point2f(4);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RotatedRect and Point2f should be imported directly from org.bytedeco.javacpp.opencv_core to avoid the opencv_core.RotatedRect crap

bb.points(points);
final double rotatedWidth = Math.sqrt( Math.pow(points.position(0).x() - points.position(1)
Copy link
Member

@SamCarlberg SamCarlberg Jul 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get rid of the space in Math.sqrt( Math.pow.... Should also use a sensical scheme of line breaks, right now it's hard to keep track of what's going on. I'd put the Math.pow statements each on their own line.

.x(), 2) + Math.pow(points.position(0).y() - points.position(1).y(), 2));
final double rotatedHeight = Math.sqrt( Math.pow(points.position(1).x() - points.position(2)
.x(), 2) + Math.pow(points.position(1).y() - points.position(2).y(), 2));
if (Math.abs(bb.angle()) >= 45) {
width = rotatedWidth;
height = rotatedHeight;
} else {
width = rotatedHeight;
height = rotatedWidth;
}
} else {
final Rect normbb = boundingRect(contour);
width = normbb.width();
height = normbb.height();
}

if (width < minWidth || width > maxWidth) {
continue;
}
if (bb.height() < minHeight || bb.height() > maxHeight) {
if (width < minHeight || width > maxHeight) {
continue;
}

final double area = contourArea(contour);
if (area < minArea) {
if (area < minArea || area > maxArea) {
continue;
}
if (arcLength(contour, true) < minPerimeter) {
if (arcLength(contour, true) < minPerimeter || arcLength(contour, true) > maxPerimeter) {
continue;
}

Expand All @@ -191,7 +238,7 @@ public void perform() {
continue;
}

final double ratio = bb.width() / bb.height();
final double ratio = width / height;
if (ratio < minRatio || ratio > maxRatio) {
continue;
}
Expand All @@ -204,4 +251,4 @@ public void perform() {
outputSocket.setValue(new ContoursReport(outputContours,
inputSocket.getValue().get().getRows(), inputSocket.getValue().get().getCols()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package edu.wpi.grip.core.operations.composite;

import edu.wpi.grip.core.Operation;
import edu.wpi.grip.core.OperationDescription;
import edu.wpi.grip.core.sockets.InputSocket;
import edu.wpi.grip.core.sockets.OutputSocket;
import edu.wpi.grip.core.sockets.SocketHint;
import edu.wpi.grip.core.sockets.SocketHints;
import edu.wpi.grip.core.util.Icon;

import com.google.common.collect.ImmutableList;

import java.util.List;

import static org.bytedeco.javacpp.opencv_core.Mat;
import static org.bytedeco.javacpp.opencv_core.MatVector;
import static org.bytedeco.javacpp.opencv_imgproc.arcLength;
import static org.bytedeco.javacpp.opencv_imgproc.contourArea;

/**
* An {@link Operation} that takes in a list of contours and outputs a list of any contours in the
* input that match all of several criteria. The user can specify a minimum area and perimeter.
* This is useful because running a FindContours on a real-life image typically leads to many small
* undesirable contours from noise and small objects, as well as contours that do not meet the
* expected characteristics of the feature we're actually looking for. So, this operation can
* help narrow them down.
*/
public class SimpleFilterContoursOperation implements Operation {

public static final OperationDescription DESCRIPTION =
OperationDescription.builder()
.name("Simple Filter Contours")
.summary("Find contours matching certain criteria")
.category(OperationDescription.Category.FEATURE_DETECTION)
.icon(Icon.iconStream("find-contours"))
.build();

private final SocketHint<ContoursReport> contoursHint = new SocketHint.Builder<>(ContoursReport
.class)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Funky formatting

.identifier("Contours").initialValueSupplier(ContoursReport::new).build();

private final SocketHint<Number> minAreaHint =
SocketHints.Inputs.createNumberSpinnerSocketHint("Min Area", 0, 0, Integer.MAX_VALUE);

private final SocketHint<Number> minPerimeterHint =
SocketHints.Inputs.createNumberSpinnerSocketHint("Min Perimeter", 0, 0, Integer.MAX_VALUE);


private final InputSocket<ContoursReport> contoursSocket;
private final InputSocket<Number> minAreaSocket;
private final InputSocket<Number> minPerimeterSocket;

private final OutputSocket<ContoursReport> outputSocket;

@SuppressWarnings("JavadocMethod")
public SimpleFilterContoursOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put parameters on separate lines to avoid a newline inside a parameter definition

outputSocketFactory) {
this.contoursSocket = inputSocketFactory.create(contoursHint);
this.minAreaSocket = inputSocketFactory.create(minAreaHint);
this.minPerimeterSocket = inputSocketFactory.create(minPerimeterHint);

this.outputSocket = outputSocketFactory.create(contoursHint);
}

@Override
public List<InputSocket> getInputSockets() {
return ImmutableList.of(
contoursSocket,
minAreaSocket,
minPerimeterSocket
);
}

@Override
public List<OutputSocket> getOutputSockets() {
return ImmutableList.of(
outputSocket
);
}

@Override
@SuppressWarnings("unchecked")
public void perform() {
final InputSocket<ContoursReport> inputSocket = contoursSocket;
final double minArea = minAreaSocket.getValue().get().doubleValue();
final double minPerimeter = minPerimeterSocket.getValue().get().doubleValue();


final MatVector inputContours = inputSocket.getValue().get().getContours();
final MatVector outputContours = new MatVector(inputContours.size());

// Add contours from the input vector to the output vector only if they pass all of the
// criteria (minimum area, minimum perimeter)
int filteredContourCount = 0;
for (int i = 0; i < inputContours.size(); i++) {
final Mat contour = inputContours.get(i);

final double area = contourArea(contour);
if (area < minArea) {
continue;
}
if (arcLength(contour, true) < minPerimeter) {
continue;
}
outputContours.put(filteredContourCount++, contour);
}

outputContours.resize(filteredContourCount);

outputSocket.setValue(new ContoursReport(outputContours,
inputSocket.getValue().get().getRows(), inputSocket.getValue().get().getCols()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arguments should all be on one line (if it fits), or each should be on its own line

}
}