Skip to content

Commit

Permalink
Factored OpenCvUtils.createFootprintTemplate() out for use outside of…
Browse files Browse the repository at this point in the history
… stages.
  • Loading branch information
markmaker committed May 15, 2020
1 parent 02bcfa5 commit ba10f8c
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 59 deletions.
95 changes: 94 additions & 1 deletion src/main/java/org/openpnp/util/OpenCvUtils.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package org.openpnp.util;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
Expand All @@ -11,14 +17,14 @@

import javax.imageio.ImageIO;

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import org.openpnp.model.Configuration;
import org.openpnp.model.Footprint;
import org.openpnp.model.Length;
import org.openpnp.model.Location;
import org.openpnp.spi.Camera;
Expand Down Expand Up @@ -256,6 +262,93 @@ private enum MinMaxState {
AFTER_INFLECTION
}

/**
* Draws a template image of the given footprint.
*
* @param camera The camera to get the pixel scale from.
* @param footprint Footprint to be drawn.
* @param topView Whether the body is drawn over the pads rather than vice versa.
* @param padsColor Color of the pads. Pads are not drawn if null.
* @param bodyColor Color of the body. Body is not drawn if null.
* @param backgroundColor
* @param marginFactor The margin around the Footprint, relative to its bounding rectangle.
* @param minimumMarginSize
* @return
* @throws Exception
*/
public static BufferedImage createFootprintTemplate(Camera camera, Footprint footprint, double rotation,
boolean topView, Color padsColor, Color bodyColor, Color backgroundColor, double marginFactor, int minimumMarginSize)
throws Exception {
Location unitsPerPixel = camera.getUnitsPerPixel();

Shape shape = footprint.getShape();
Shape bodyShape = footprint.getBodyShape();
Shape padsShape = footprint.getPadsShape();

if (shape == null) {
throw new Exception(
"Invalid footprint found, unable to create template for part match. See https://github.com/openpnp/openpnp/wiki/Fiducials.");
}

// Determine the scaling factor to go from Outline units to
// Camera units.
Length l = new Length(1, footprint.getUnits());
l = l.convertToUnits(unitsPerPixel.getUnits());
double unitScale = l.getValue();

// Create a transform to scale the Shape by
AffineTransform tx = new AffineTransform();

// First we scale by units to convert the units and then we scale
// by the camera X and Y units per pixels to get pixel locations.
tx.scale(unitScale, unitScale);
tx.scale(1.0 / unitsPerPixel.getX(), 1.0 / unitsPerPixel.getY());
tx.rotate(Math.toRadians(-rotation));

// Transform the Shape and draw it out.
shape = tx.createTransformedShape(shape);
bodyShape = tx.createTransformedShape(bodyShape);
padsShape = tx.createTransformedShape(padsShape);

Rectangle2D bounds = shape.getBounds2D();

if (bounds.getWidth() == 0 || bounds.getHeight() == 0) {
throw new Exception(
"Invalid footprint found, unable to create template for part match. Width and height of pads must be greater than 0. See https://github.com/openpnp/openpnp/wiki/Fiducials.");
}

// Make the image bigger than the shape. This gives better
// recognition performance because it allows some border around the edges.
double width = Math.max(bounds.getWidth() * marginFactor, bounds.getWidth()+2*minimumMarginSize);
double height = Math.max(bounds.getHeight() * marginFactor, bounds.getHeight()+2*minimumMarginSize);
BufferedImage template =
new BufferedImage((int) width, (int) height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = (Graphics2D) template.getGraphics();
if (backgroundColor != null) {
g2d.setColor(backgroundColor);
g2d.fillRect(0, 0, (int) width, (int) height);
}

//g2d.setStroke(new BasicStroke(1f));
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// center the drawing
g2d.translate(width / 2.0, height / 2.0);
if (!topView && bodyColor != null) {
g2d.setColor(bodyColor);
g2d.fill(bodyShape);
}
if (padsColor != null) {
g2d.setColor(padsColor);
g2d.fill(padsShape);
}
if (topView && bodyColor != null) {
g2d.setColor(bodyColor);
g2d.fill(bodyShape);
}
g2d.dispose();
return template;
}

/**
* Ported from the C++ version in FireSight by Karl Lew, which is licensed under the
* MIT license.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,86 +1,84 @@
package org.openpnp.vision.pipeline.stages;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;

import org.openpnp.model.Footprint;
import org.openpnp.model.Length;
import org.openpnp.model.Location;
import org.openpnp.spi.Camera;
import org.openpnp.util.OpenCvUtils;
import org.openpnp.vision.pipeline.CvPipeline;
import org.openpnp.vision.pipeline.CvStage;
import org.openpnp.vision.pipeline.Property;
import org.openpnp.vision.pipeline.Stage;
import org.openpnp.vision.pipeline.stages.convert.ColorConverter;
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.Element;
import org.simpleframework.xml.convert.Convert;

@Stage(description="Creates a template from the specified footprint and camera properties. The template is scaled to the camera's units.")
public class CreateFootprintTemplateImage extends CvStage {

public enum FootprintView {
Fiducial,
TopView,
BottomView
}
@Attribute(required=false)
@Property(description = "Determines, how the footprint is drawn. Fiducial: only draws the pads, TopView: draws body over pads, "
+ "BottomView: draws pads over body.")
private FootprintView footprintView = FootprintView.Fiducial;

@Element(required=false)
@Convert(ColorConverter.class)
@Property(description = "Color of the pads.")
private Color padsColor = Color.white;

@Element(required=false)
@Convert(ColorConverter.class)
@Property(description = "Color of the body.")
private Color bodyColor = Color.black;

public FootprintView getFootprintView() {
return footprintView;
}

public void setFootprintView(FootprintView footprintView) {
this.footprintView = footprintView;
}

public Color getPadsColor() {
return padsColor;
}

public void setPadsColor(Color padsColor) {
this.padsColor = padsColor;
}

public Color getBodyColor() {
return bodyColor;
}

public void setBodyColor(Color bodyColor) {
this.bodyColor = bodyColor;
}

@Override
public Result process(CvPipeline pipeline) throws Exception {
Camera camera = (Camera) pipeline.getProperty("camera");
Footprint footprint = (Footprint) pipeline.getProperty("footprint");

if (camera == null) {
throw new Exception("Property \"camera\" is required.");
}
if (footprint == null) {
throw new Exception("Property \"footprint\" is required.");
}

Location unitsPerPixel = camera.getUnitsPerPixel();

Shape shape = footprint.getShape();

if (shape == null) {
throw new Exception(
"Invalid footprint found, unable to create template for fiducial match. See https://github.com/openpnp/openpnp/wiki/Fiducials.");
}

// Determine the scaling factor to go from Outline units to
// Camera units.
Length l = new Length(1, footprint.getUnits());
l = l.convertToUnits(unitsPerPixel.getUnits());
double unitScale = l.getValue();

// Create a transform to scale the Shape by
AffineTransform tx = new AffineTransform();

// First we scale by units to convert the units and then we scale
// by the camera X and Y units per pixels to get pixel locations.
tx.scale(unitScale, unitScale);
tx.scale(1.0 / unitsPerPixel.getX(), 1.0 / unitsPerPixel.getY());

// Transform the Shape and draw it out.
shape = tx.createTransformedShape(shape);

Rectangle2D bounds = shape.getBounds2D();

if (bounds.getWidth() == 0 || bounds.getHeight() == 0) {
throw new Exception(
"Invalid footprint found, unable to create template for fiducial match. Width and height of pads must be greater than 0. See https://github.com/openpnp/openpnp/wiki/Fiducials.");
}

// Make the image 50% bigger than the shape. This gives better
// recognition performance because it allows some border around the edges.
double width = bounds.getWidth() * 1.5;
double height = bounds.getHeight() * 1.5;
BufferedImage template =
new BufferedImage((int) width, (int) height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = (Graphics2D) template.getGraphics();

g2d.setStroke(new BasicStroke(1f));
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.white);
// center the drawing
g2d.translate(width / 2, height / 2);
g2d.fill(shape);

g2d.dispose();
BufferedImage template = OpenCvUtils.createFootprintTemplate(camera, footprint, 0.0,
footprintView == FootprintView.TopView,
padsColor,
footprintView == FootprintView.Fiducial ? null : bodyColor,
null, 1.5, 3);

return new Result(OpenCvUtils.toMat(template));
}
Expand Down

0 comments on commit ba10f8c

Please sign in to comment.