001/* 002 * Copyright (C) Photon Vision. 003 * 004 * This program is free software: you can redistribute it and/or modify 005 * it under the terms of the GNU General Public License as published by 006 * the Free Software Foundation, either version 3 of the License, or 007 * (at your option) any later version. 008 * 009 * This program is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 * GNU General Public License for more details. 013 * 014 * You should have received a copy of the GNU General Public License 015 * along with this program. If not, see <https://www.gnu.org/licenses/>. 016 */ 017 018package org.photonvision.vision.pipe.impl; 019 020import java.awt.*; 021import java.util.List; 022import org.apache.commons.lang3.tuple.Pair; 023import org.opencv.core.*; 024import org.opencv.core.Point; 025import org.opencv.imgproc.Imgproc; 026import org.photonvision.common.logging.LogGroup; 027import org.photonvision.common.logging.Logger; 028import org.photonvision.common.util.ColorHelper; 029import org.photonvision.vision.frame.FrameDivisor; 030import org.photonvision.vision.opencv.CVShape; 031import org.photonvision.vision.opencv.ContourShape; 032import org.photonvision.vision.pipe.MutatingPipe; 033import org.photonvision.vision.target.TrackedTarget; 034 035public class Draw2dTargetsPipe 036 extends MutatingPipe<Pair<Mat, List<TrackedTarget>>, Draw2dTargetsPipe.Draw2dTargetsParams> { 037 MatOfPoint tempMat = new MatOfPoint(); 038 private static final Logger logger = new Logger(Draw2dTargetsPipe.class, LogGroup.General); 039 040 @Override 041 protected Void process(Pair<Mat, List<TrackedTarget>> in) { 042 var imRows = in.getLeft().rows(); 043 var imCols = in.getLeft().cols(); 044 var imageSize = Math.sqrt(imRows * imCols); 045 var textSize = params.kPixelsToText * imageSize; 046 var thickness = params.kPixelsToThickness * imageSize; 047 048 if (!params.shouldDraw) return null; 049 050 if (!in.getRight().isEmpty() 051 && (params.showCentroid 052 || params.showMaximumBox 053 || params.showRotatedBox 054 || params.showShape)) { 055 var centroidColour = ColorHelper.colorToScalar(params.centroidColor); 056 var maximumBoxColour = ColorHelper.colorToScalar(params.maximumBoxColor); 057 var rotatedBoxColour = ColorHelper.colorToScalar(params.rotatedBoxColor); 058 var circleColor = ColorHelper.colorToScalar(params.circleColor); 059 var shapeColour = ColorHelper.colorToScalar(params.shapeOutlineColour); 060 061 for (int i = 0; i < (params.showMultipleTargets ? in.getRight().size() : 1); i++) { 062 Point[] vertices = new Point[4]; 063 MatOfPoint contour = new MatOfPoint(); 064 065 if (i != 0 && !params.showMultipleTargets) { 066 break; 067 } 068 069 TrackedTarget target = in.getRight().get(i); 070 RotatedRect r = target.getMinAreaRect(); 071 072 if (r == null) continue; 073 074 r.points(vertices); 075 dividePointArray(vertices); 076 contour.fromArray(vertices); 077 078 if (params.shouldShowRotatedBox(target.getShape())) { 079 Imgproc.drawContours( 080 in.getLeft(), 081 List.of(contour), 082 0, 083 rotatedBoxColour, 084 (int) Math.ceil(imageSize * params.kPixelsToBoxThickness)); 085 } else if (params.shouldShowCircle(target.getShape())) { 086 Imgproc.circle( 087 in.getLeft(), 088 target.getShape().center, 089 (int) target.getShape().radius, 090 circleColor, 091 (int) Math.ceil(imageSize * params.kPixelsToBoxThickness)); 092 } else { 093 // draw approximate polygon 094 var poly = target.getApproximateBoundingPolygon(); 095 096 // fall back on the shape's approx poly dp 097 if (poly == null && target.getShape() != null) 098 poly = target.getShape().getContour().getApproxPolyDp(); 099 if (poly != null) { 100 var mat = new MatOfPoint(); 101 mat.fromArray(poly.toArray()); 102 divideMat(mat, mat); 103 Imgproc.drawContours( 104 in.getLeft(), 105 List.of(mat), 106 -1, 107 ColorHelper.colorToScalar(params.rotatedBoxColor), 108 2); 109 mat.release(); 110 } 111 } 112 113 if (params.showMaximumBox) { 114 Rect box = Imgproc.boundingRect(contour); 115 Imgproc.rectangle( 116 in.getLeft(), 117 new Point(box.x, box.y), 118 new Point(box.x + box.width, box.y + box.height), 119 maximumBoxColour, 120 (int) Math.ceil(imageSize * params.kPixelsToBoxThickness)); 121 } 122 123 if (params.showShape) { 124 divideMat(target.m_mainContour.mat, tempMat); 125 Imgproc.drawContours( 126 in.getLeft(), 127 List.of(tempMat), 128 -1, 129 shapeColour, 130 (int) Math.ceil(imageSize * params.kPixelsToBoxThickness)); 131 } 132 133 if (params.showContourNumber) { 134 var center = target.m_mainContour.getCenterPoint(); 135 var textPos = 136 new Point( 137 center.x + params.kPixelsToOffset * imageSize, 138 center.y - params.kPixelsToOffset * imageSize); 139 dividePoint(textPos); 140 141 int id = target.getFiducialId(); 142 var contourNumber = String.valueOf(id == -1 ? i : id); 143 144 Imgproc.putText( 145 in.getLeft(), 146 contourNumber, 147 textPos, 148 0, 149 textSize, 150 ColorHelper.colorToScalar(params.textColor), 151 (int) thickness); 152 } 153 154 if (params.showCentroid) { 155 Point centroid = target.getTargetOffsetPoint().clone(); 156 dividePoint(centroid); 157 var crosshairRadius = (int) (imageSize * params.kPixelsToCentroidRadius); 158 var x = centroid.x; 159 var y = centroid.y; 160 Point xMax = new Point(x + crosshairRadius, y); 161 Point xMin = new Point(x - crosshairRadius, y); 162 Point yMax = new Point(x, y + crosshairRadius); 163 Point yMin = new Point(x, y - crosshairRadius); 164 165 Imgproc.line( 166 in.getLeft(), 167 xMax, 168 xMin, 169 centroidColour, 170 (int) Math.ceil(imageSize * params.kPixelsToBoxThickness)); 171 Imgproc.line( 172 in.getLeft(), 173 yMax, 174 yMin, 175 centroidColour, 176 (int) Math.ceil(imageSize * params.kPixelsToBoxThickness)); 177 } 178 } 179 } 180 181 return null; 182 } 183 184 private void divideMat(MatOfPoint src, MatOfPoint dst) { 185 var hull = src.toArray(); 186 for (Point point : hull) { 187 dividePoint(point); 188 } 189 dst.fromArray(hull); 190 } 191 192 private void divideMat(MatOfPoint2f src, MatOfPoint dst) { 193 var hull = src.toArray(); 194 for (Point point : hull) { 195 dividePoint(point); 196 } 197 dst.fromArray(hull); 198 } 199 200 /** Scale a given point list by the current frame divisor. the point list is mutated! */ 201 private void dividePointList(List<Point> points) { 202 for (var p : points) { 203 dividePoint(p); 204 } 205 } 206 207 /** Scale a given point array by the current frame divisor. the point list is mutated! */ 208 private void dividePointArray(Point[] points) { 209 for (var p : points) { 210 dividePoint(p); 211 } 212 } 213 214 private void dividePoint(Point p) { 215 p.x = p.x / (double) params.divisor.value; 216 p.y = p.y / (double) params.divisor.value; 217 } 218 219 public static class Draw2dTargetsParams { 220 public double kPixelsToText = 0.0025; 221 public double kPixelsToThickness = 0.008; 222 public double kPixelsToOffset = 0.04; 223 public double kPixelsToBoxThickness = 0.007; 224 public double kPixelsToCentroidRadius = 0.03; 225 public boolean showCentroid = true; 226 public boolean showRotatedBox = true; 227 public boolean showShape = false; 228 public boolean showMaximumBox = true; 229 public boolean showContourNumber = true; 230 public Color centroidColor = Color.GREEN; // Color.decode("#ff5ebf"); 231 public Color rotatedBoxColor = Color.BLUE; 232 public Color maximumBoxColor = Color.RED; 233 public Color shapeOutlineColour = Color.MAGENTA; 234 public Color textColor = Color.GREEN; 235 public Color circleColor = Color.RED; 236 237 public final boolean showMultipleTargets; 238 public final boolean shouldDraw; 239 240 public final FrameDivisor divisor; 241 242 public boolean shouldShowRotatedBox(CVShape shape) { 243 return showRotatedBox && (shape == null || shape.shape.equals(ContourShape.Quadrilateral)); 244 } 245 246 public boolean shouldShowCircle(CVShape shape) { 247 return shape != null && shape.shape.equals(ContourShape.Circle); 248 } 249 250 public Draw2dTargetsParams( 251 boolean shouldDraw, boolean showMultipleTargets, FrameDivisor divisor) { 252 this.shouldDraw = shouldDraw; 253 this.showMultipleTargets = showMultipleTargets; 254 this.divisor = divisor; 255 } 256 } 257}