java使用opencv进行图片矫正

目录

前言

一、资源引入

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);
    }


}

四、最终效果