目录
前言
一、资源引入
1.1引入资源
1.2加载动态库
二、基本步骤
三、代码实现
四、最终效果
前言
主要需求就是实现发票上传时小幅度的偏移可以自动矫正,对此我使用了opencv来实现,主要原理就是识别出照片中最大四边形轮廓,然后计算这个四边形的旋转角度,这个方法也可以用来做身份证、车票、人民币等。
一、资源引入
1.1引入资源
window环境:
把opencv-455.jar包、opencv_java455.dll文件放入项目
下载地址:
https://pan.baidu.com/s/15NdIxM2BM7Zrj6l1arZ6fw?pwd=ufmo
https://pan.baidu.com/s/1iqbXE4mxuOFa84-Lqcs1ug?pwd=fyo7
liunx环境:
需要把opencv_java455.dll换成libopencv_java455.so这个文件,这个文件根据linux版本不一样内容也不一样,需要自己生成,生成方式看我另一篇文章。
1.2加载动态库
static { //加载opencv动态库,必要 if (SystemUtils.IS_OS_WINDOWS) { path = "C:/data/inv/"; URL url = ClassLoader.getSystemResource("lib/opencv/window/opencv_java455.dll"); System.out.println("windos加载"); System.load(url.getPath()); } else if (SystemUtils.IS_OS_LINUX) { path = "/inv/"; System.out.println("linux加载"); System.load("/data/libopencv_java455.so"); } }
二、基本步骤
1、灰度化
2、二值化
3、高斯模糊降噪,避免环境中的花纹影像
4、膨胀
5、腐蚀
6、边缘检测
7、找出最大轮廓
找到所有轮廓,然后找面积最大并且是四边形的轮廓
8、计算旋转角度并旋转
通过这个轮廓的4的顶点计算一个中心点,通过这个中心点旋转
三、代码实现
public class OpenCVUtil { //本地新建 public static String path; public static void main(String[] args) throws IOException { autoRectify("1.jpg", "2.jpg"); } static { //加载opencv动态库,必要 if (SystemUtils.IS_OS_WINDOWS) { path = "C:/data/inv/"; URL url = ClassLoader.getSystemResource("lib/opencv/window/opencv_java455.dll"); System.out.println("windos加载"); System.load(url.getPath()); } else if (SystemUtils.IS_OS_LINUX) { path = "/inv/"; System.out.println("linux加载"); System.load("/data/libopencv_java455.so"); } System.out.println(path); } public static boolean autoRectify(String srcPath, String rotatePath) throws IOException { // 输入图片 Mat src = Imgcodecs.imread(path + srcPath); // 找到最大矩形 RotatedRect rect = findMaxRect(src); if (rect != null) { Point[] rectPoint = new Point[4]; rect.points(rectPoint); saveImg("画出集合逼近的点", src, rectPoint, null); // 得到旋转角度 double angle = rect.angle; if (angle > 45) { angle = 90 - angle; } else { angle = -angle; } if (angle == 90) { angle = 0.0; } // 矫正图片 src = Imgcodecs.imread(path + srcPath); // 定义中心 Point center = new Point(); center.x = src.cols() / 2.0; center.y = src.rows() / 2.0; Mat matrix = Imgproc.getRotationMatrix2D(center, -angle, 1); Imgproc.warpAffine(src, src, matrix, src.size(), 1, 0, new Scalar(255, 255, 255)); } else { src = Imgcodecs.imread(path + srcPath); } Imgcodecs.imwrite(path + rotatePath, src); return true; } //得到图片参数 public static Map<String, Integer> getPictureParam(String srcPath) { // 输入图片 Mat src = Imgcodecs.imread(srcPath); // 找到最大矩形 RotatedRect rect = findMaxRect(src.clone()); Map<String, Integer> map = new HashMap<>(16); if (rect != null) { //Point[] rectPoint = findFourPoint(srcPath); Point[] rectPoint = new Point[4]; rect.points(rectPoint); saveImg("画出集合逼近的点", src, rectPoint, null); // 得到对应参数 int maxX = 0, minX = (int) rectPoint[0].x, maxY = 0, minY = (int) rectPoint[0].y; for (Point point : rectPoint) { if (point.x > 0) { if (point.x > maxX) { maxX = (int) point.x; } if (point.x < minX) { minX = (int) point.x; } } else { minX = 0; } if (point.y > 0) { if (point.y > maxY) { maxY = (int) point.y; } if (point.y < minY) { minY = (int) point.y; } } else { minY = 0; } } if (src.width() < maxX) { maxX = src.width(); } if (src.height() < maxY) { maxY = src.height(); } if (0 > minX) { minX = 0; } if (0 > minY) { minY = 0; } map.put("x", minX); map.put("y", minY); map.put("w", maxX - minX); map.put("h", maxY - minY); map.put("width", src.width()); map.put("height", src.height()); //查看效果(测试) saveImg("裁剪", src, null, map); } return map; } //裁切图片 public static boolean rect(int x, int y, int w, int h, String source, String target) { Mat mat = Imgcodecs.imread(OpenCVUtil.path + source);//原始图片 Mat m = new Mat(mat, new Rect(x, y, w, h));//剪辑之后的图片 Imgcodecs.imwrite(OpenCVUtil.path + target, m); return true; } //图片压缩 public static void reduce(String source, double size) { File srcFile = new File(OpenCVUtil.path + source); while (srcFile.length() > (long) (1024 * size)) { try { Thumbnails.of(srcFile).scale(0.5f).toFile(srcFile); } catch (IOException e) { throw new ClientCustomException("图片缩略图压缩失败"); } srcFile = new File(OpenCVUtil.path + source); } } /*显示图片 测试专用*/ public static void saveImg(String rotatePath, Mat src, Point[] rectPoint, Map<String, Integer> map) { if(rectPoint!=null){ // 画点 Mat approxPolyMat2 = src.clone(); Scalar color=new Scalar(0,0,255);//设置线的颜色 Imgproc.line(src,rectPoint[0],rectPoint[1],color); Imgproc.line(src,rectPoint[1],rectPoint[2],color); Imgproc.line(src,rectPoint[2],rectPoint[3],color); Imgproc.line(src,rectPoint[3],rectPoint[0],color); for( int i = 0; i < rectPoint.length ; i++) { setPixel(approxPolyMat2, (int)rectPoint[i].y, (int) rectPoint[i].x, 255); } HighGui.imshow(rotatePath, approxPolyMat2); HighGui.waitKey(); }else if(map!=null){ // 裁剪 src=src.submat(new Rect(map.get("x"),map.get("y"),map.get("w"),map.get("h"))); HighGui.imshow(rotatePath, src); HighGui.waitKey(); }else{ HighGui.imshow(rotatePath, src); HighGui.waitKey(); } } //画点 public static void setPixel(Mat areaMat, int y, int x, int color) { Imgproc.circle(areaMat, new Point(x, y), 4, new Scalar(color, 100, 100), 1, -1); } /** * 判断是否是大于一定面积四边形 */ public static boolean isQuadrangle(MatOfPoint contour) { boolean flag = false; MatOfInt hull = new MatOfInt(); MatOfPoint2f approx = new MatOfPoint2f(); approx.convertTo(approx, CvType.CV_32F); // 边框的凸包 Imgproc.convexHull(contour, hull); // 用凸包计算出新的轮廓点 Point[] contourPoints = contour.toArray(); int[] indices = hull.toArray(); List<Point> newPoints = new ArrayList<>(); for (int ind : indices) { newPoints.add(contourPoints[ind]); } MatOfPoint2f contourHull = new MatOfPoint2f(); contourHull.fromList(newPoints); // 多边形拟合凸包边框(此时的拟合的精度较低) Imgproc.approxPolyDP(contourHull, approx, Imgproc.arcLength(contourHull, true) * 0.02, true); MatOfPoint approxf1 = new MatOfPoint(); approx.convertTo(approxf1, CvType.CV_32S); // 四边形的各个角度都接近直角的凸四边形 int row = approx.rows(); if (row == 4) { if (Imgproc.isContourConvex(approxf1)) { Point[] point = approxf1.toArray(); int num = 0; for (int j = 0; j < row; j++) { double cosine = getAngle(point[j % row], point[(j + 2) % row], point[(j + 3) % row]); cosine = Math.abs(cosine); // 角度大概72度 if (cosine < 0.3) { num++; } } //3个角在72到90度 if (num >= 2) { flag = true; } } } return flag; } /** * 找最大轮廓 */ public static RotatedRect findMaxRect(Mat src) { //1.灰度化 Imgproc.cvtColor(src, src, Imgproc.COLOR_BGR2GRAY); saveImg("灰度化", src, null, null); //2.二值化处理 Imgproc.threshold(src, src, 0, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);//灰度图像二值化,效果不是很好,阴影部分大面积全黑 saveImg("二值化处理", src, null, null); //3..高斯模糊降噪,避免环境中的花纹影响边缘检测 Imgproc.GaussianBlur(src, src, new Size(5, 5), 1.0); saveImg("高斯模糊降噪", src, null, null); double num = src.width() * 0.001; Mat structImage = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size((int) (num*3>1?num*3:1) , (int) (num*2>1?num*2:1))); //4.膨胀 Imgproc.dilate(src, src, structImage, new Point(-1, -1),(int) (num*4>1?num*4:1) ); saveImg("膨胀",src,null,null); //5.腐蚀 Imgproc.erode(src, src, structImage, new Point(-1,-1), (int) (num*4>1?num*4:1) ); saveImg("腐蚀",src,null,null); //6.边缘检测 Mat cannyMat = new Mat(); Imgproc.Canny(src, cannyMat, num * 50, num * 100); saveImg("边缘检测", cannyMat, null, null); List<MatOfPoint> contours = new ArrayList<MatOfPoint>(); Mat hierarchy = new Mat(); //7.寻找轮廓 Imgproc.findContours(cannyMat, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE, new Point(0, 0)); //8.找出匹配到的最大轮廓 int areaMax = src.width() * src.height(); double areaMin = areaMax * 0.05; double area = 0; int index = -1; for (int i = 0; i < contours.size(); i++) { double tempArea = Imgproc.boundingRect(contours.get(i)).area(); // 筛选出面积大于某一阈值的,且四边形的各个角度都接近直角的凸四边形 if (tempArea > areaMin && isQuadrangle(contours.get(i))) { if (tempArea > area) { area = tempArea; index = i; } } else { continue; } } //6.获取轮廓内最大的矩形 RotatedRect rect = null; if (index != -1) { MatOfPoint2f matOfPoint2f = new MatOfPoint2f(contours.get(index).toArray()); rect = Imgproc.minAreaRect(matOfPoint2f); } return rect; } // 根据三个点计算中间那个点的夹角 pt1 pt0 pt2 cos值 private static double getAngle(Point pt1, Point pt2, Point pt0) { double dx1 = pt1.x - pt0.x; double dy1 = pt1.y - pt0.y; double dx2 = pt2.x - pt0.x; double dy2 = pt2.y - pt0.y; return (dx1 * dx2 + dy1 * dy2) / Math.sqrt((dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2) + 1e-10); } }