/** * An {@link Operation} that, given a binary image, produces a list of contours of all of the shapes * in the image */ public class FindContoursOperation implements Operation { private final SocketHint<Mat> inputHint = new SocketHint.Builder<>(Mat.class).identifier("Input").build(); private final SocketHint<Boolean> externalHint = SocketHints.createBooleanSocketHint("External Only", false); private final SocketHint<ContoursReport> contoursHint = new SocketHint.Builder<>(ContoursReport.class) .identifier("Contours") .initialValueSupplier(ContoursReport::new) .build(); @Override public String getName() { return "Find Contours"; } @Override public String getDescription() { return "Detect contours in a binary image."; } @Override public Category getCategory() { return Category.FEATURE_DETECTION; } @Override public Optional<InputStream> getIcon() { return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/find-contours.png")); } @Override public InputSocket<?>[] createInputSockets(EventBus eventBus) { return new InputSocket<?>[] { new InputSocket<>(eventBus, inputHint), new InputSocket<>(eventBus, externalHint), }; } @Override public OutputSocket<?>[] createOutputSockets(EventBus eventBus) { return new OutputSocket<?>[] {new OutputSocket<>(eventBus, contoursHint)}; } @Override public Optional<?> createData() { return Optional.of(new Mat()); } @Override @SuppressWarnings("unchecked") public void perform(InputSocket<?>[] inputs, OutputSocket<?>[] outputs, Optional<?> data) { final Mat input = ((InputSocket<Mat>) inputs[0]).getValue().get(); final Mat tmp = ((Optional<Mat>) data).get(); final boolean externalOnly = ((InputSocket<Boolean>) inputs[1]).getValue().get(); if (input.empty()) { return; } // findContours modifies its input, so we pass it a temporary copy of the input image input.copyTo(tmp); // OpenCV has a few different things it can return from findContours, but for now we only use // EXTERNAL and LIST. // The other ones involve hierarchies of contours, which might be useful in some situations, but // probably only // when processing the contours manually in code (so, not in a graphical pipeline). MatVector contours = new MatVector(); findContours( tmp, contours, externalOnly ? CV_RETR_EXTERNAL : CV_RETR_LIST, CV_CHAIN_APPROX_TC89_KCOS); final OutputSocket<ContoursReport> contoursSocket = (OutputSocket<ContoursReport>) outputs[0]; contoursSocket.setValue(new ContoursReport(contours, input.rows(), input.cols())); } }