|
14 | 14 | import android.view.Display; |
15 | 15 | import android.view.Surface; |
16 | 16 |
|
| 17 | +import com.adityaarora.liveedgedetection.constants.ScanConstants; |
17 | 18 | import com.adityaarora.liveedgedetection.view.Quadrilateral; |
18 | 19 |
|
19 | 20 | import org.opencv.android.Utils; |
| 21 | +import org.opencv.core.Core; |
20 | 22 | import org.opencv.core.CvType; |
21 | 23 | import org.opencv.core.Mat; |
| 24 | +import org.opencv.core.MatOfInt; |
22 | 25 | import org.opencv.core.MatOfPoint; |
23 | 26 | import org.opencv.core.MatOfPoint2f; |
24 | 27 | import org.opencv.core.Point; |
| 28 | +import org.opencv.core.Scalar; |
25 | 29 | import org.opencv.core.Size; |
26 | 30 | import org.opencv.imgproc.Imgproc; |
27 | 31 | import org.opencv.utils.Converters; |
@@ -276,19 +280,7 @@ public static int configureCameraAngle(Activity activity) { |
276 | 280 | return angle; |
277 | 281 | } |
278 | 282 |
|
279 | | - public static Quadrilateral detectLargestQuadrilateral(Mat mat) { |
280 | | - Mat mGrayMat = new Mat(mat.rows(), mat.cols(), CV_8UC1); |
281 | | - Imgproc.cvtColor(mat, mGrayMat, Imgproc.COLOR_BGR2GRAY, 4); |
282 | | - Imgproc.threshold(mGrayMat, mGrayMat, 150, 255, THRESH_BINARY + THRESH_OTSU); |
283 | 283 |
|
284 | | - List<MatOfPoint> largestContour = findLargestContour(mGrayMat); |
285 | | - if (null != largestContour) { |
286 | | - Quadrilateral mLargestRect = findQuadrilateral(largestContour); |
287 | | - if (mLargestRect != null) |
288 | | - return mLargestRect; |
289 | | - } |
290 | | - return null; |
291 | | - } |
292 | 284 |
|
293 | 285 | public static double getMaxCosine(double maxCosine, Point[] approxPoints) { |
294 | 286 | Log.i(TAG, "ANGLES ARE:"); |
@@ -339,24 +331,86 @@ public int compare(Point lhs, Point rhs) { |
339 | 331 | return result; |
340 | 332 | } |
341 | 333 |
|
342 | | - private static List<MatOfPoint> findLargestContour(Mat inputMat) { |
| 334 | + private static Mat morph_kernel = new Mat(new Size(ScanConstants.KSIZE_CLOSE, ScanConstants.KSIZE_CLOSE), CvType.CV_8UC1, new Scalar(255)); |
| 335 | + |
| 336 | + public static Quadrilateral detectLargestQuadrilateral(Mat originalMat) { |
| 337 | + Imgproc.cvtColor(originalMat, originalMat, Imgproc.COLOR_BGR2GRAY, 4); |
| 338 | + |
| 339 | + // Just OTSU/Binary thresholding is not enough. |
| 340 | + //Imgproc.threshold(mGrayMat, mGrayMat, 150, 255, THRESH_BINARY + THRESH_OTSU); |
| 341 | + |
| 342 | + /* |
| 343 | + * 1. We shall first blur and normalize the image for uniformity, |
| 344 | + * 2. Truncate light-gray to white and normalize, |
| 345 | + * 3. Apply canny edge detection, |
| 346 | + * 4. Cutoff weak edges, |
| 347 | + * 5. Apply closing(morphology), then proceed to finding contours. |
| 348 | + */ |
| 349 | + |
| 350 | + // step 1. |
| 351 | + Imgproc.blur(originalMat, originalMat, new Size(ScanConstants.KSIZE_BLUR, ScanConstants.KSIZE_BLUR)); |
| 352 | + Core.normalize(originalMat, originalMat, 0, 255, Core.NORM_MINMAX); |
| 353 | + // step 2. |
| 354 | + // As most papers are bright in color, we can use truncation to make it uniformly bright. |
| 355 | + Imgproc.threshold(originalMat,originalMat, ScanConstants.TRUNC_THRESH,255,Imgproc.THRESH_TRUNC); |
| 356 | + Core.normalize(originalMat, originalMat, 0, 255, Core.NORM_MINMAX); |
| 357 | + // step 3. |
| 358 | + // After above preprocessing, canny edge detection can now work much better. |
| 359 | + Imgproc.Canny(originalMat, originalMat, ScanConstants.CANNY_THRESH_U, ScanConstants.CANNY_THRESH_L); |
| 360 | + // step 4. |
| 361 | + // Cutoff the remaining weak edges |
| 362 | + Imgproc.threshold(originalMat,originalMat,ScanConstants.CUTOFF_THRESH,255,Imgproc.THRESH_TOZERO); |
| 363 | + // step 5. |
| 364 | + // Closing - closes small gaps. Completes the edges on canny image; AND also reduces stringy lines near edge of paper. |
| 365 | + Imgproc.morphologyEx(originalMat, originalMat, Imgproc.MORPH_CLOSE, morph_kernel, new Point(-1,-1),1); |
| 366 | + |
| 367 | + // Get only the 10 largest contours (each approximated to their convex hulls) |
| 368 | + List<MatOfPoint> largestContour = findLargestContours(originalMat, 10); |
| 369 | + if (null != largestContour) { |
| 370 | + Quadrilateral mLargestRect = findQuadrilateral(largestContour); |
| 371 | + if (mLargestRect != null) |
| 372 | + return mLargestRect; |
| 373 | + } |
| 374 | + return null; |
| 375 | + } |
| 376 | + private static MatOfPoint hull2Points(MatOfInt hull, MatOfPoint contour) { |
| 377 | + List<Integer> indexes = hull.toList(); |
| 378 | + List<Point> points = new ArrayList<>(); |
| 379 | + List<Point> ctrList = contour.toList(); |
| 380 | + for(Integer index:indexes) { |
| 381 | + points.add(ctrList.get(index)); |
| 382 | + } |
| 383 | + MatOfPoint point= new MatOfPoint(); |
| 384 | + point.fromList(points); |
| 385 | + return point; |
| 386 | + } |
| 387 | + private static List<MatOfPoint> findLargestContours(Mat inputMat, int NUM_TOP_CONTOURS) { |
343 | 388 | Mat mHierarchy = new Mat(); |
344 | 389 | List<MatOfPoint> mContourList = new ArrayList<>(); |
345 | | - //finding contours |
346 | | - Imgproc.findContours(inputMat, mContourList, mHierarchy, Imgproc.RETR_EXTERNAL, |
347 | | - Imgproc.CHAIN_APPROX_SIMPLE); |
348 | | - |
349 | | - Mat mContoursMat = new Mat(); |
350 | | - mContoursMat.create(inputMat.rows(), inputMat.cols(), CvType.CV_8U); |
351 | | - |
352 | | - if (mContourList.size() != 0) { |
353 | | - Collections.sort(mContourList, new Comparator<MatOfPoint>() { |
| 390 | + //finding contours - as we are sorting by area anyway, we can use RETR_LIST - faster than RETR_EXTERNAL. |
| 391 | + Imgproc.findContours(inputMat, mContourList, mHierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE); |
| 392 | + |
| 393 | + // Convert the contours to their Convex Hulls i.e. removes minor nuances in the contour |
| 394 | + List<MatOfPoint> mHullList = new ArrayList<>(); |
| 395 | + MatOfInt tempHullIndices = new MatOfInt(); |
| 396 | + for (int i = 0; i < mContourList.size(); i++) { |
| 397 | + Imgproc.convexHull(mContourList.get(i), tempHullIndices); |
| 398 | + mHullList.add(hull2Points(tempHullIndices, mContourList.get(i))); |
| 399 | + } |
| 400 | + // Release mContourList as its job is done |
| 401 | + for (MatOfPoint c : mContourList) |
| 402 | + c.release(); |
| 403 | + tempHullIndices.release(); |
| 404 | + mHierarchy.release(); |
| 405 | + |
| 406 | + if (mHullList.size() != 0) { |
| 407 | + Collections.sort(mHullList, new Comparator<MatOfPoint>() { |
354 | 408 | @Override |
355 | 409 | public int compare(MatOfPoint lhs, MatOfPoint rhs) { |
356 | | - return Double.valueOf(Imgproc.contourArea(rhs)).compareTo(Imgproc.contourArea(lhs)); |
| 410 | + return Double.compare(Imgproc.contourArea(rhs),Imgproc.contourArea(lhs)); |
357 | 411 | } |
358 | 412 | }); |
359 | | - return mContourList; |
| 413 | + return mHullList.subList(0, Math.min(mHullList.size(), NUM_TOP_CONTOURS)); |
360 | 414 | } |
361 | 415 | return null; |
362 | 416 | } |
|
0 commit comments