Last active
June 29, 2020 12:37
-
-
Save sureshbabuinfo/f398a758dbbec64eee2d7c8221b0c970 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.a.imgproc.sureshbabu; | |
import com.a.imgproc.HoughLines; | |
import nu.pattern.OpenCV; | |
import org.opencv.core.*; | |
import org.opencv.imgcodecs.Imgcodecs; | |
import org.opencv.imgproc.Imgproc; | |
import java.io.File; | |
import java.util.*; | |
import static java.lang.Math.pow; | |
public class DocuScan { | |
public final static double DEFAULT_RATIO = 0.25; | |
public final static double DEFAULT_AREA = 1000; | |
public void scan(String file, double contourRatio, double contourArea) { | |
OpenCV.loadShared(); | |
final File image = new File(file); | |
Mat srcImg = Imgcodecs.imread(image.getAbsolutePath()); | |
Mat orginalImg = srcImg.clone(); | |
Size orgSize = orginalImg.size(); | |
double ratio = findRatio(srcImg); | |
// Find contours | |
Contours contours = findContours(orginalImg); | |
Mat result = crop(srcImg, contours, ratio, contourRatio, contourArea); | |
writeImage(image.getParent() + File.separator + image.getName().replace(".", "-scanned."), result); | |
result.release(); | |
} | |
public double findRatio(Mat src) { | |
return src.size().height / 500; | |
} | |
public Contours findContours(Mat src) { | |
Mat image = src.clone(); | |
src.release(); | |
Mat grayImage; | |
Mat cannedImage; | |
Mat resizedImage; | |
double ratio = image.size().height / 500; | |
int height = Double.valueOf(image.size().height / ratio).intValue(); | |
int width = Double.valueOf(image.size().width / ratio).intValue(); | |
Size size = new Size(width, height); | |
resizedImage = new Mat(size, CvType.CV_8UC4); | |
grayImage = new Mat(size, CvType.CV_8UC4); | |
cannedImage = new Mat(size, CvType.CV_8UC1); | |
Imgproc.resize(image, resizedImage, size); | |
Imgproc.cvtColor(resizedImage, grayImage, Imgproc.COLOR_RGBA2GRAY); | |
Imgproc.GaussianBlur(grayImage, grayImage, new Size(5, 5), 0); | |
Imgproc.Canny(grayImage, cannedImage, 75, 200); | |
writeImage("/tmp/canned.jpg", cannedImage); | |
System.out.println("1. Completed canny detection"); | |
// Find contours | |
ArrayList<MatOfPoint> contours = new ArrayList<MatOfPoint>(); | |
Mat hierarchy = new Mat(); | |
Imgproc.findContours(cannedImage, contours, hierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE); | |
hierarchy.release(); | |
// Sort | |
Collections.sort(contours, (MatOfPoint lhs, MatOfPoint rhs) -> | |
Double.valueOf(Imgproc.contourArea(rhs)).compareTo(Imgproc.contourArea(lhs))); | |
System.out.println("2. Found Contours"); | |
image.release(); | |
return new Contours(contours, size); | |
} | |
public Mat crop(Mat src, Contours contours, double ratio, double definedRatio, double definedArea) { | |
Mat originalImg = src.clone(); | |
src.release(); | |
Mat result = null; | |
// Find the biggest contour | |
for (MatOfPoint c : contours.contours) { | |
MatOfPoint2f c2f = new MatOfPoint2f(c.toArray()); | |
double peri = Imgproc.arcLength(c2f, true); | |
MatOfPoint2f approx = new MatOfPoint2f(); | |
Imgproc.approxPolyDP(c2f, approx, 0.02 * peri, true); | |
Point[] points = approx.toArray(); | |
// Select biggest 4 angles | |
if (points.length == 4) { | |
System.out.println(Imgproc.contourArea(c)); | |
Point[] foundPoints = sortPoints(points); | |
if ((isWithin(foundPoints, contours.size) && isLargeEnough(foundPoints, contours.size, definedRatio)) || Imgproc.contourArea(c) > definedArea) { | |
System.out.println("Found four points"); | |
originalImg = fourPointTransform(originalImg, foundPoints, ratio); | |
applyThreshold(originalImg); | |
result = originalImg; | |
break; | |
} else { | |
System.out.println("Trying to transform biggest contour. Adjust ratio less than 0.25 or contour area " + | |
"less than 1000 if the image captured is too small"); | |
System.out.println("Verify whether the Canny edge is detected properly"); | |
result = originalImg; | |
break; | |
} | |
} | |
} | |
System.out.println("3. Completed Scanning"); | |
return result; | |
} | |
private void writeImage(String file, Mat src) { | |
Imgcodecs.imwrite(file, src); | |
System.out.println("4. Scanned image written to :: " + file); | |
} | |
private boolean isWithin(Point[] points, Size size){ | |
int width = Double.valueOf(size.width).intValue(); | |
int height = Double.valueOf(size.height).intValue(); | |
return (points[0].x >= 0 && points[0].y >= 0 | |
&& points[1].x <= width && points[1].y >= 0 | |
&& points[2].x <= width && points[2].y <= height | |
&& points[3].x >= 0 && points[3].y <= height); | |
} | |
public Point[] sortPoints(Point[] src ) { | |
ArrayList<Point> srcPoints = new ArrayList(Arrays.asList(src)); | |
Point[] result = { null , null , null , null }; | |
Comparator<Point> sumComparator = (Point lhs, Point rhs) -> Double.valueOf(lhs.y + lhs.x).compareTo(rhs.y + rhs.x); | |
Comparator<Point> diffComparator = (Point lhs, Point rhs) -> Double.valueOf(lhs.y - lhs.x).compareTo(rhs.y - rhs.x); | |
result[0] = Collections.min(srcPoints, sumComparator); | |
result[2] = Collections.max(srcPoints, sumComparator); | |
result[1] = Collections.min(srcPoints, diffComparator); | |
result[3] = Collections.max(srcPoints, diffComparator); | |
return result; | |
} | |
public boolean isLargeEnough(Point[] points, Size size, double ratio){ | |
double contentWidth = Math.max(new Edge(points[0], points[1]).length(), new Edge(points[3], points[2]).length()); | |
double contentHeight = Math.max(new Edge(points[0], points[3]).length(), new Edge(points[1], points[2]).length()); | |
double widthRatio = contentWidth/size.width; | |
double heightRatio = contentHeight/size.height; | |
return widthRatio >= ratio && heightRatio >= ratio; | |
} | |
public Mat fourPointTransform(Mat src, Point[] pts, double ratio) { | |
Point ul = pts[0]; | |
Point ur = pts[1]; | |
Point lr = pts[2]; | |
Point ll = pts[3]; | |
double widthA = Math.sqrt(pow(lr.x - ll.x, 2) + pow(lr.y - ll.y, 2)); | |
double widthB = Math.sqrt(pow(ur.x - ul.x, 2) + pow(ur.y - ul.y, 2)); | |
double maxWidth = Math.max(widthA, widthB) * ratio; | |
double heightA = Math.sqrt(pow(ur.x - lr.x, 2) + pow(ur.y - lr.y, 2)); | |
double heightB = Math.sqrt(pow(ul.x - ll.x, 2) + pow(ul.y - ll.y, 2)); | |
double maxHeight = Math.max(heightA, heightB) * ratio; | |
Mat resultMat = new Mat(Double.valueOf(maxHeight).intValue(), Double.valueOf(maxWidth).intValue(), CvType.CV_8UC4); | |
Mat srcMat = new Mat(4, 1, CvType.CV_32FC2); | |
Mat dstMat = new Mat(4, 1, CvType.CV_32FC2); | |
srcMat.put(0, 0, ul.x * ratio, ul.y * ratio, ur.x * ratio, ur.y * ratio, lr.x * ratio, lr.y * ratio, ll.x * ratio, ll.y * ratio); | |
dstMat.put(0, 0, 0.0, 0.0, maxWidth, 0.0, maxWidth, maxHeight, 0.0, maxHeight); | |
Mat M = Imgproc.getPerspectiveTransform(srcMat, dstMat); | |
Imgproc.warpPerspective(src, resultMat, M, resultMat.size()); | |
srcMat.release(); | |
dstMat.release(); | |
M.release(); | |
return resultMat; | |
} | |
public void applyThreshold(Mat src) { | |
Mat destination = new Mat(src.rows(), src.cols(), src.type()); | |
Imgproc.GaussianBlur(src, destination, new Size(0, 0), 10); | |
Core.addWeighted(src, 1.5, destination, -0.5, 0, destination); | |
src = destination.clone(); | |
destination.release(); | |
} | |
class Contours { | |
public ArrayList<MatOfPoint> contours; | |
public Size size; | |
public Contours(ArrayList<MatOfPoint> contours, Size size) { | |
this.contours = contours; | |
this.size = size; | |
} | |
} | |
class Edge { | |
private Point begin, end; | |
public Edge(Point begin, Point end){ | |
assert begin != null && end != null; | |
this.begin = begin; | |
this.end = end; | |
} | |
public double length(){ | |
return Math.sqrt(Math.pow(begin.x - end.x, 2) + Math.pow(begin.y - end.y, 2)); | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment