package resa.evaluation.topology.tomVLD; import org.bytedeco.javacpp.opencv_core; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; /** * This class provides static geometric methods for refining the results given by RANSAC algorithm * {@link resa.evaluation.topology.tomVLD.RobustMatcher#matchImages(opencv_core.Mat, opencv_core.Mat, * org.bytedeco.javacpp.opencv_features2d.KeyPoint, opencv_core.Mat, opencv_core.Mat, * org.bytedeco.javacpp.opencv_features2d.KeyPoint, opencv_core.Rect)} */ public class Util { /** Draws rectangle on given image matrix with color given as a scalar */ public static void drawRectOnMat(opencv_core.Rect r, opencv_core.Mat finalImage, opencv_core.CvScalar scalar) { opencv_core.Scalar color = new opencv_core.Scalar(scalar); opencv_core.Point A = new opencv_core.Point(r.x(), r.y()), B = new opencv_core.Point(r.x() + r.width(), r.y()), C = new opencv_core.Point(r.x() + r.width(), r.y()+ r.height()), D = new opencv_core.Point(r.x(), r.y() + r.height()); opencv_core.line(finalImage, A, B, color, 4, 4, 0); opencv_core.line(finalImage, B, C, color, 4, 4, 0); opencv_core.line(finalImage, C, D, color, 4, 4, 0); opencv_core.line(finalImage, D, A, color, 4, 4, 0); } /** * Draws quadrilateral on given image matrix with color given as a scalar * @param Q 4x2 array with coordinates of the quadrilateral * @param finalImage image matrix on which to draw an image * @param scalar the color */ public static void drawQonMat(double [][] Q, opencv_core.Mat finalImage, opencv_core.CvScalar scalar) { opencv_core.Scalar color = new opencv_core.Scalar(scalar); for (int i = 0; i < 4; i++) { opencv_core.line(finalImage, new opencv_core.Point((int) Q[i][0], (int) Q[i][1]), new opencv_core.Point((int) Q[(i + 1) % 4][0], (int) Q[(i + 1) % 4][1]), color, 4, 4, 0); } } /** epsilon for checking whether doubles are positive or negative {@link #CCW(double[], double[], double[])} */ final static double eps = 1e-2; /** * Is the quadrilateral convex? * @param corners - the 4x2 array of doubles - the coordinates of corners given in counter clockwise order * @return true if argument is a convex quadrilateral and false otherwise. */ public static boolean isConvex(double [][] corners) { assert corners.length == 4; for (int i = 0 ; i < 4 ; i ++) assert corners[i].length == 2; for (int i = 0 ; i < 4 ; i ++) { // vprev * vnext > 0 int prev = (i - 1 + 4) % 4, next = ( i + 1 ) % 4; if (CCW(corners[prev], corners[i], corners[next])) return false; } return true; } /** * Are points a, b and c are in clockwise order? * @param a * @param b * @param c * @return true, if a, b, c are in CCW order */ public static boolean CCW(double[] a, double[] b, double[] c) { double x1 = a[0] - b[0], y1 = a[1] - b[1]; double x2 = c[0] - b[0], y2 = c[1] - b[1]; return x1 * y2 - x2 * y1 > eps; } /** * Returns the area of quadrilateral. * @param p corners of quadrilateral given in CCW order * @return area */ public static double area(double [][] p) { double res = 0.0; for (int i = 0 ; i < 4 ; i ++) { res += p[i][0] * p[(i+1)%4][1] - p[(i+1)%4][0] * p[i][1]; } return Math.abs(res); } /** * Performs testing of quadrilateral obtained by transformation of the corners of logo template onto frame, after * the logo has been detected. * 1. Checks if it's not smaller than 10x10 pixel square <p> * 2. Checks if it's not too large and does fall outside the patch <p> * 3. Checks if it's not too flattened <p> * 4. Checks that it is a convex quadrilateral. * @param scene_corners corners of quadrilateral * @param roi the patch coordinates where this logo * @return true if quadrilateral satisfies all requirements, false otherwise. */ public static boolean checkQuadrilateral(double [][] scene_corners, opencv_core.Rect roi) { double xMax = 0.0, xMin = 1e100; double yMax = 0.0, yMin = 1e100; for (int i = 0 ; i < 4 ; i ++) { xMax = Math.max(xMax, scene_corners[i][0]); xMin = Math.min(xMin, scene_corners[i][0]); yMax = Math.max(yMax, scene_corners[i][1]); yMin = Math.min(yMin, scene_corners[i][1]); } // TODO: update 07/29. If opposite sides are too far from being a rectangle /* double [] sides = new double [4]; for (int i = 0 ; i < 4 ; i ++) { sides[i] = (scene_corners[i][0] - scene_corners[(i+1)%4][0])*(scene_corners[i][0] - scene_corners[(i+1)%4][0]); } for (int i = 0 ; i < 2 ; i ++) if (sides[i] / sides[i + 2] > 3.0 || sides[i] / sides[i + 2] < 1.0 / 3) { System.out.println("Too squeezed"); return false; } */ // TODO: adjust these constants. if (xMax - xMin < 10 || yMax - yMin < 10) { if (Debug.logoDetectionDebugOutput) System.out.println("Of too small resolution"); return false; } double wx = xMax - xMin, hy = yMax - yMin; // TODO: update 07/21. If the quadrilateral is too large with respect to roi, it is counted as bad if (wx > 2 * roi.width() || hy > 2 * roi.height()) { if (Debug.logoDetectionDebugOutput) System.out.println("Too large"); return false; } // TODO: update 07/21 If quadrilateral is too 'flattened' return false (Area < C * Bounding_box_area), C = 1/2 if (Util.area(scene_corners) < .5 * (xMax - xMin) * (yMax - yMin)) { if (Debug.logoDetectionDebugOutput) System.out.println("Of too small area"); return false; } if (!Util.isConvex(scene_corners)) { if (Debug.logoDetectionDebugOutput) System.out.println("not a convex"); return false; } return true; } /** * Given list of points find the rectangle of minimal area which includes at least accuracy*100% points. O(n^3) * @param list - list of 2D points * @param accuracy - the ratio * @return the rectangle enclosing points. */ public static opencv_core.Rect bestBoundingBoxFast(ArrayList<opencv_core.Point2f> list, double accuracy) { if (accuracy < 0.0 || accuracy > 1.0) { System.err.println("Not valid accuracy [0.0, 1.0]"); accuracy = .9; } int N = list.size(), n = (int)Math.ceil(N * accuracy); opencv_core.Rect best = new opencv_core.Rect(0, 0, 1 << 15, 1 << 15); // 'infinite' rectangle ArrayList<opencv_core.Point2f> xSorted = (ArrayList<opencv_core.Point2f>)list.clone(); ArrayList<opencv_core.Point2f> ySorted = (ArrayList<opencv_core.Point2f>)list.clone(); Collections.sort(xSorted, new Comparator<opencv_core.Point2f>() { public int compare(opencv_core.Point2f o1, opencv_core.Point2f o2) { if (o1.x() < o2.x()) return -1; if (o1.x() > o2.x()) return 1; if (o1.y() < o2.y()) return -1; if (o1.y() > o2.y()) return 1; return 0; } }); Collections.sort(ySorted, new Comparator<opencv_core.Point2f>() { public int compare(opencv_core.Point2f o1, opencv_core.Point2f o2) { if (o1.y() < o2.y()) return -1; if (o1.y() > o2.y()) return 1; if (o1.x() > o2.x()) return -1; if (o1.x() < o2.x()) return 1; return 0; } }); for (int i = 0 ; i < N ; i ++) { for (int j = 0 ; j < N ; j ++) { /* int cur = 0, pup = N - 1, pright = i; while (cur < n && pright < N) { if (xSorted.get(pright).y() >= ySorted.get(j).y()) cur ++; pright ++; } if (cur < n) break;// ???? double xMin = xSorted.get(i).x(), xMax = xSorted.get(pright).x(), yMin = ySorted.get(j).y(), yMax = ySorted.get(pup).y(); */ int cur = 0; double xMin = xSorted.get(i).x(), yMin = ySorted.get(j).y(); for (int pup = N - 1, pright = i - 1; pup >= j ; ) { while ( pright + 1 < N && (cur < n || (pright < 0 || xSorted.get(pright).x() == xSorted.get(pright + 1).x()) ) ) { if (xSorted.get(pright + 1).y() >= ySorted.get(j).y() && xSorted.get(pright + 1).y() <= ySorted.get(pup).y()) cur ++; pright = pright + 1; } if (cur < n) break;// ???? double xMax = xSorted.get(pright).x(); double yMax = ySorted.get(pup).y(); if ((xMax - xMin) * (yMax - yMin) < best.area()) { best = new opencv_core.Rect((int)xMin, (int)yMin, (int)(xMax - xMin), (int)(yMax - yMin)); } if ( ySorted.get(pup).x() >= xMin && ySorted.get(pup).x() <= xMax) { cur --; } pup --; } } } return best; } }