Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@sureshbabuinfo
Last active June 29, 2020 12:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sureshbabuinfo/f398a758dbbec64eee2d7c8221b0c970 to your computer and use it in GitHub Desktop.
Save sureshbabuinfo/f398a758dbbec64eee2d7c8221b0c970 to your computer and use it in GitHub Desktop.
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