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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -14,11 +14,14 @@

import static org.bytedeco.javacpp.opencv_core.Mat;
import static org.bytedeco.javacpp.opencv_core.MatVector;
import static org.bytedeco.javacpp.opencv_core.Point2f;
import static org.bytedeco.javacpp.opencv_core.Rect;
import static org.bytedeco.javacpp.opencv_core.RotatedRect;
import static org.bytedeco.javacpp.opencv_imgproc.arcLength;
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,14 +31,15 @@
* 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"))
.aliases("Filter Contours")
.build();

private final SocketHint<ContoursReport> contoursHint = new SocketHint.Builder<>(ContoursReport
Expand All @@ -45,9 +49,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<Number> maxPerimeterHint =
SocketHints.Inputs.createNumberSpinnerSocketHint("Max Perimeter", 10000, 0,
Integer.MAX_VALUE);

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

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

Expand Down Expand Up @@ -79,34 +93,40 @@ 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;
private final InputSocket<Number> maxHeightSocket;
private final InputSocket<List<Number>> soliditySocket;
private final InputSocket<Number> minVertexSocket;
private final InputSocket<Number> maxVertexSocket;
private final InputSocket<Number> minRatioSocket;
private final InputSocket<Number> maxRatioSocket;
private final InputSocket<List<Number>> soliditySocket;

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);
this.maxHeightSocket = inputSocketFactory.create(maxHeightHint);
this.soliditySocket = inputSocketFactory.create(solidityHint);
this.minVertexSocket = inputSocketFactory.create(minVertexHint);
this.maxVertexSocket = inputSocketFactory.create(maxVertexHint);
this.minRatioSocket = inputSocketFactory.create(minRatioHint);
this.maxRatioSocket = inputSocketFactory.create(maxRatioHint);
this.soliditySocket = inputSocketFactory.create(solidityHint);

this.outputSocket = outputSocketFactory.create(contoursHint);
}
Expand All @@ -116,16 +136,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 +164,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 +191,43 @@ 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 RotatedRect bb = minAreaRect(contour);
Point2f points = new Point2f(4);
bb.points(points);
final double rotatedWidth = Math.sqrt(Math.pow(points.position(0).x()
- points.position(1).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 +241,7 @@ public void perform() {
continue;
}

final double ratio = (double) bb.width() / (double) bb.height();
final double ratio = width / height;
if (ratio < minRatio || ratio > maxRatio) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
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)
.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 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()));
}
}
33 changes: 27 additions & 6 deletions core/src/test/resources/edu/wpi/grip/projects/testALL.grip
Original file line number Diff line number Diff line change
Expand Up @@ -108,27 +108,48 @@
</grip:Input>
<grip:Output step="6" socket="0" previewed="true"/>
</grip:Step>
<grip:Step name="Filter Contours">
<grip:Step name="Advanced Filter Contours">
<grip:Input step="7" socket="0"/>
<grip:Input step="7" socket="1">
<value>2.0</value>
</grip:Input>
<grip:Input step="7" socket="2">
<value>3.0</value>
<value>10000.0</value>
</grip:Input>
<grip:Input step="7" socket="3">
<value>1.0</value>
<value>3.0</value>
</grip:Input>
<grip:Input step="7" socket="4">
<value>1000.0</value>
</grip:Input>
<grip:Input step="7" socket="5">
<value>3.0</value>
<grip:Input step="7" socket="5">
<value>true</value>
</grip:Input>
<grip:Input step="7" socket="6">
<value>20.0</value>
<value>1.0</value>
</grip:Input>
<grip:Input step="7" socket="7">
<value>10000.0</value>
</grip:Input>
<grip:Input step="7" socket="8">
<value>3.0</value>
</grip:Input>
<grip:Input step="7" socket="9">
<value>10000.0</value>
</grip:Input>
<grip:Input step="7" socket="10">
<value>20.0</value>
</grip:Input>
<grip:Input step="7" socket="11">
<value>20.0</value>
</grip:Input>
<grip:Input step="7" socket="12">
<value>20.0</value>
</grip:Input>
<grip:Input step="7" socket="13">
<value>20.0</value>
</grip:Input>
<grip:Input step="7" socket="14">
<value>
<int>0</int>
<int>100</int>
Expand Down