2019-09-21 13:30:09 -04:00
|
|
|
package com.chameleonvision.vision.process;
|
|
|
|
|
|
|
|
|
|
import com.chameleonvision.vision.camera.CameraValues;
|
|
|
|
|
import org.apache.commons.math3.util.FastMath;
|
|
|
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
|
import org.opencv.core.*;
|
2019-09-22 02:49:30 -04:00
|
|
|
import org.opencv.imgproc.Imgproc;
|
2019-09-21 13:30:09 -04:00
|
|
|
|
|
|
|
|
import java.util.*;
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("WeakerAccess")
|
|
|
|
|
public class CVProcess {
|
|
|
|
|
|
2019-09-22 23:33:51 +03:00
|
|
|
private final CameraValues CamVals;
|
|
|
|
|
private HashMap<String, Integer> TargetGrouping = new HashMap<>() {{
|
|
|
|
|
put("Single", 1);
|
|
|
|
|
put("Dual", 2);
|
|
|
|
|
put("Triple", 3);
|
|
|
|
|
put("Quadruple", 4);
|
|
|
|
|
put("Quintuple", 5);
|
|
|
|
|
}};
|
|
|
|
|
private Mat Kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5, 5));
|
|
|
|
|
private Mat hsvImage = new Mat();
|
|
|
|
|
private List<MatOfPoint> FoundContours = new ArrayList<>();
|
|
|
|
|
private Mat binaryMat = new Mat();
|
|
|
|
|
private List<MatOfPoint> FilteredContours = new ArrayList<>();
|
|
|
|
|
private Comparator<RotatedRect> SortByCentermostComparator = Comparator.comparingDouble(this::calcDistance);
|
|
|
|
|
private List<RotatedRect> FinalCountours = new ArrayList<>();
|
|
|
|
|
private Mat intersectMatA = new Mat();
|
|
|
|
|
private Mat intersectMatB = new Mat();
|
2019-09-22 02:49:30 -04:00
|
|
|
|
2019-09-22 23:33:51 +03:00
|
|
|
CVProcess(CameraValues camVals) {
|
|
|
|
|
CamVals = camVals;
|
|
|
|
|
}
|
2019-09-22 02:49:30 -04:00
|
|
|
|
2019-09-22 23:33:51 +03:00
|
|
|
void HSVThreshold(Mat srcImage, Mat dst, @NotNull Scalar hsvLower, @NotNull Scalar hsvUpper, boolean shouldErode, boolean shouldDilate) {
|
|
|
|
|
Imgproc.cvtColor(srcImage, hsvImage, Imgproc.COLOR_RGB2HSV, 3);
|
|
|
|
|
Core.inRange(hsvImage, hsvLower, hsvUpper, dst);
|
|
|
|
|
if (shouldErode) {
|
|
|
|
|
Imgproc.erode(dst, dst, Kernel);
|
|
|
|
|
}
|
|
|
|
|
if (shouldDilate) {
|
|
|
|
|
Imgproc.dilate(dst, dst, Kernel);
|
|
|
|
|
}
|
|
|
|
|
hsvImage.release();
|
|
|
|
|
}
|
2019-09-22 02:49:30 -04:00
|
|
|
|
2019-09-22 23:33:51 +03:00
|
|
|
List<MatOfPoint> FindContours(Mat src) {
|
|
|
|
|
src.copyTo(binaryMat);
|
|
|
|
|
FoundContours.clear();
|
|
|
|
|
Imgproc.findContours(binaryMat, FoundContours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_TC89_L1);
|
|
|
|
|
binaryMat.release();
|
|
|
|
|
return FoundContours;
|
|
|
|
|
}
|
2019-09-22 02:49:30 -04:00
|
|
|
|
2019-09-21 13:30:09 -04:00
|
|
|
List<MatOfPoint> FilterContours(List<MatOfPoint> InputContours, List<Integer> area, List<Integer> ratio, List<Integer> extent) {
|
2019-09-22 23:33:51 +03:00
|
|
|
for (MatOfPoint Contour : InputContours) {
|
|
|
|
|
try {
|
2019-09-22 03:40:55 +03:00
|
|
|
var contourArea = Imgproc.contourArea(Contour);//TODO change scaling
|
|
|
|
|
int targetArea = (int) ((((float) contourArea) / CamVals.ImageArea) * 100);
|
2019-09-22 23:33:51 +03:00
|
|
|
if (targetArea < area.get(0) || targetArea > area.get(1)) {
|
2019-09-21 13:30:09 -04:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
var rect = Imgproc.minAreaRect(new MatOfPoint2f(Contour.toArray()));
|
|
|
|
|
var targetFullness = (contourArea / rect.size.area()) * 100;
|
2019-09-22 23:33:51 +03:00
|
|
|
if (targetFullness < extent.get(0) || targetArea > extent.get(1)) {
|
2019-09-21 13:30:09 -04:00
|
|
|
continue;
|
|
|
|
|
}
|
2019-09-22 03:40:55 +03:00
|
|
|
double aspectRatio = rect.size.width / rect.size.height;//TODO i think aspectRatio is inverted
|
2019-09-22 23:33:51 +03:00
|
|
|
if (aspectRatio < ratio.get(0) || aspectRatio > ratio.get(1)) {
|
2019-09-21 13:30:09 -04:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
FilteredContours.add(Contour);
|
2019-09-22 23:33:51 +03:00
|
|
|
} catch (Exception e) {
|
2019-09-22 03:40:55 +03:00
|
|
|
System.err.println("Error while filtering contours");
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
2019-09-21 13:30:09 -04:00
|
|
|
}
|
|
|
|
|
return FilteredContours;
|
|
|
|
|
}
|
2019-09-22 02:49:30 -04:00
|
|
|
|
2019-09-22 23:33:51 +03:00
|
|
|
private double calcDistance(RotatedRect rect) {
|
|
|
|
|
return FastMath.sqrt(FastMath.pow(CamVals.CenterX - rect.center.x, 2) + FastMath.pow(CamVals.CenterY - rect.center.y, 2));
|
|
|
|
|
}
|
2019-09-22 02:49:30 -04:00
|
|
|
|
2019-09-22 23:33:51 +03:00
|
|
|
RotatedRect SortTargetsToOne(List<RotatedRect> inputRects, String sortMode) {
|
|
|
|
|
switch (sortMode) {
|
|
|
|
|
case "Largest":
|
|
|
|
|
return Collections.max(inputRects, Comparator.comparing(rect -> rect.size.area()));
|
|
|
|
|
case "Smallest":
|
|
|
|
|
return Collections.min(inputRects, Comparator.comparing(rect -> rect.size.area()));
|
|
|
|
|
case "Highest":
|
|
|
|
|
return Collections.min(inputRects, Comparator.comparing(rect -> rect.center.y));
|
|
|
|
|
case "Lowest":
|
|
|
|
|
return Collections.max(inputRects, Comparator.comparing(rect -> rect.center.y));
|
|
|
|
|
case "Leftmost":
|
|
|
|
|
return Collections.min(inputRects, Comparator.comparing(rect -> rect.center.x));
|
|
|
|
|
case "Rightmost":
|
|
|
|
|
return Collections.max(inputRects, Comparator.comparing(rect -> rect.center.x));
|
|
|
|
|
case "Centermost":
|
|
|
|
|
return Collections.min(inputRects, SortByCentermostComparator);
|
|
|
|
|
default:
|
|
|
|
|
return inputRects.get(0); // default to whatever the first contour is, but this should never happen
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-22 02:49:30 -04:00
|
|
|
|
2019-09-22 23:33:51 +03:00
|
|
|
List<RotatedRect> GroupTargets(List<MatOfPoint> InputContours, String IntersectionPoint, String TargetGroup) {
|
|
|
|
|
FinalCountours.clear();
|
|
|
|
|
if (!TargetGroup.equals("Single")) {
|
|
|
|
|
for (var i = 0; i < InputContours.size(); i++) {
|
|
|
|
|
List<Point> FinalContourList = new ArrayList<>(InputContours.get(i).toList());
|
|
|
|
|
for (var c = 0; c < (TargetGrouping.get(TargetGroup) - 1); c++) {
|
|
|
|
|
try {
|
|
|
|
|
MatOfPoint firstContour = InputContours.get(i + c);
|
|
|
|
|
MatOfPoint secondContour = InputContours.get(i + c + 1);
|
|
|
|
|
if (IsIntersecting(firstContour, secondContour, IntersectionPoint)) {
|
|
|
|
|
FinalContourList.addAll(secondContour.toList());
|
|
|
|
|
}
|
2019-09-23 12:36:57 -07:00
|
|
|
else{
|
|
|
|
|
FinalContourList.clear();
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-09-22 23:33:51 +03:00
|
|
|
firstContour.release();
|
|
|
|
|
secondContour.release();
|
|
|
|
|
MatOfPoint2f contour = new MatOfPoint2f();
|
|
|
|
|
contour.fromList(FinalContourList);
|
|
|
|
|
if (contour.cols() != 0 && contour.rows() != 0) {
|
|
|
|
|
RotatedRect rect = Imgproc.minAreaRect(contour);
|
|
|
|
|
FinalCountours.add(rect);
|
|
|
|
|
}
|
|
|
|
|
} catch (IndexOutOfBoundsException e) {
|
|
|
|
|
FinalContourList.clear();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-22 02:49:30 -04:00
|
|
|
|
2019-09-22 23:33:51 +03:00
|
|
|
} else {
|
|
|
|
|
for (MatOfPoint inputContour : InputContours) {
|
|
|
|
|
MatOfPoint2f contour = new MatOfPoint2f();
|
|
|
|
|
contour.fromArray(inputContour.toArray());
|
|
|
|
|
if (contour.cols() != 0 && contour.rows() != 0) {
|
|
|
|
|
RotatedRect rect = Imgproc.minAreaRect(contour);
|
|
|
|
|
FinalCountours.add(rect);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return FinalCountours;
|
|
|
|
|
}
|
2019-09-22 02:49:30 -04:00
|
|
|
|
2019-09-22 23:33:51 +03:00
|
|
|
private boolean IsIntersecting(MatOfPoint ContourOne, MatOfPoint ContourTwo, String IntersectionPoint) {
|
|
|
|
|
if (IntersectionPoint.equals("None")) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
Imgproc.fitLine(ContourOne, intersectMatA, Imgproc.CV_DIST_L2, 0, 0.01, 0.01);
|
|
|
|
|
Imgproc.fitLine(ContourTwo, intersectMatB, Imgproc.CV_DIST_L2, 0, 0.01, 0.01);
|
|
|
|
|
double vxA = intersectMatA.get(0, 0)[0];
|
|
|
|
|
double vyA = intersectMatA.get(1, 0)[0];
|
|
|
|
|
double x0A = intersectMatA.get(2, 0)[0];
|
|
|
|
|
double y0A = intersectMatA.get(3, 0)[0];
|
|
|
|
|
double mA = vyA / vxA;
|
|
|
|
|
double vxB = intersectMatB.get(0, 0)[0];
|
|
|
|
|
double vyB = intersectMatB.get(1, 0)[0];
|
|
|
|
|
double x0B = intersectMatB.get(2, 0)[0];
|
|
|
|
|
double y0B = intersectMatB.get(3, 0)[0];
|
|
|
|
|
double mB = vyB / vxB;
|
2019-09-23 12:36:57 -07:00
|
|
|
double bA = y0A - (mA*x0A);
|
|
|
|
|
double bB = y0B - (mB*x0B);
|
2019-09-22 23:33:51 +03:00
|
|
|
double intersectionX = ((mA * x0A) - y0A - (mB * x0B) + y0B )/ (mA - mB);
|
|
|
|
|
double intersectionY = (mA * (intersectionX - x0A)) + y0A;
|
2019-09-23 12:36:57 -07:00
|
|
|
double massX = intersectionX + 1;
|
|
|
|
|
double massY = intersectionY + ((mA + bA + mB +bB) / 2);
|
2019-09-22 23:33:51 +03:00
|
|
|
switch (IntersectionPoint) {
|
|
|
|
|
case "Up": {
|
2019-09-23 12:36:57 -07:00
|
|
|
if (intersectionY < massY) {
|
2019-09-22 23:33:51 +03:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "Down": {
|
2019-09-23 12:36:57 -07:00
|
|
|
if (intersectionY > massY) {
|
2019-09-22 23:33:51 +03:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "Left": {
|
2019-09-23 12:36:57 -07:00
|
|
|
if (intersectionX < massX) {
|
2019-09-22 23:33:51 +03:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "Right": {
|
2019-09-23 12:36:57 -07:00
|
|
|
if (intersectionX > massX) {
|
2019-09-22 23:33:51 +03:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-21 13:30:09 -04:00
|
|
|
}
|