java OpenCV实现扫描仪图片倾斜矫正

首先先去opencv官网下载资源

官网地址:Releases – OpenCV

官网下载慢可以选择在百度网盘下载,版本是455

链接:https://pan.baidu.com/s/1LADtih8l8nStKwJRIde91Q
提取码:wx0h

提示:以下方法只适用于小角度倾斜,90度这种无法矫正!!!

下载之后解压,\opencv\build\java下有个jar包,需要导入到ide中,该目录下还有x64和x86两个文件夹,是opencv的动态库,根据自己电脑来选择使用,opencv的接口都在动态库里,很重要!

导入jar包,引入opencv的动态库

java OpenCV实现扫描仪图片倾斜矫正

java OpenCV实现扫描仪图片倾斜矫正

后续会用到PngEncoder,需要导入包


    com.pngencoder
    pngencoder
    0.9.0

后面就是具体的代码开发,第一个方法是用opencv的霍夫检测算法计算倾斜角度并旋转,但是因为修改后图片比原图大了十几倍,放弃了,知道的大佬可以指导下怎么在不改变图片的分辨率和清晰度缩小图片。

public static void first () {
    try {
        //输入图片路径
        String srcPath = "D:\\TU\\x7.png";
        //输出图片路径
        String rotatePath = "D:\\TU\\rotate.png";
        //加载opencv动态库,必要
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        //输入图片
        Mat src = Imgcodecs.imread(srcPath);
        //灰度化
        Mat gray = new Mat();
        if (src.channels() == 3) {
            Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
            src = gray;
        } else {
            System.out.println("不是RGB图片!");
        }
        //边缘算法检测
        Mat cannyMat = src.clone();
        //表示迟滞过程的第一个阈值
        double threshold1 = 60;
        //表示迟滞过程的第二个阈值,通常把第一个阈值*2或*3
        double threshold2 = threshold1 * 3;
        Imgproc.Canny(src, cannyMat, threshold1, threshold2);
        //计算倾斜角度
        double angle = OpenCVUtil.getAngle(cannyMat);
        // 获取最大矩形
        RotatedRect rect = OpenCVUtil.findMaxRect(cannyMat);
        // 旋转矩形
        Mat CorrectImg = OpenCVUtil.rotation(cannyMat, rect, angle);
        HandleImgUtils.saveImg(CorrectImg, "d:/TU/correct.png");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

第二个方法是用opencv的霍夫检测计算出倾斜角度,通过Graphics2D来旋转图片。

public static void second () {
    try {
        //输入图片路径
        String srcPath = "D:\\TU\\x7.png";
        //输出图片路径
        String rotatePath = "D:\\TU\\rotate.png";
        //加载opencv动态库,必要
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        //输入图片
        Mat src = Imgcodecs.imread(srcPath);
        //灰度化
        Mat gray = new Mat();
        if (src.channels() == 3) {
            Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
            src = gray;
        } else {
            System.out.println("不是RGB图片!");
        }
        //边缘算法检测
        Mat cannyMat = src.clone();
        //表示迟滞过程的第一个阈值
        double threshold1 = 60;
        //表示迟滞过程的第二个阈值,通常把第一个阈值*2或*3
        double threshold2 = threshold1 * 3;
        Imgproc.Canny(src, cannyMat, threshold1, threshold2);
        //计算倾斜角度
        double angle = OpenCVUtil.getAngle(cannyMat);
        //图片旋转
        BufferedImage srcBuff = ImageIO.read(new File(srcPath));
        //图片宽度
        int width = srcBuff.getWidth(null);
        //图片高度
        int height = srcBuff.getHeight(null);
        BufferedImage res = new BufferedImage(width, height,
                BufferedImage.TYPE_INT_RGB);
        Graphics2D g2 = res.createGraphics();
        //设置图片底色
        g2.setBackground(Color.WHITE);
        g2.fillRect(0, 0, width, height);
        g2.rotate(Math.toRadians(angle), width / 2, height / 2);
        g2.drawImage(srcBuff, null, null);
        g2.dispose();
        //ImageIO.write生成png速度特别慢,听说jdk1.9以后有优化(没试过),但是本项目不能改jdk版本,所以选择PngEncoder来生成图片
        //ImageIO.write(res, "png", new File(rotatePath));
        PngEncoder encoder=new PngEncoder();
        encoder.withBufferedImage(res).toFile(rotatePath);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

OpenCVUtil工具类

public class OpenCVUtil {
    /**
     * 寻找轮廓,并按照递增排序
     *
     * @param cannyMat
     * @return
     */
    public static List findContours(Mat cannyMat) {
        List contours = new ArrayList();
        Mat hierarchy = new Mat();

        // 寻找轮廓
        Imgproc.findContours(cannyMat, contours, hierarchy, Imgproc.RETR_LIST,                 Imgproc.CHAIN_APPROX_SIMPLE,
                new Point(0, 0));
        if (contours.size() () {
                @Override
                public int compare(MatOfPoint o1, MatOfPoint o2) {
                    MatOfPoint2f mat1 = new MatOfPoint2f(o1.toArray());
                    RotatedRect rect1 = Imgproc.minAreaRect(mat1);
                    Rect r1 = rect1.boundingRect();

                    MatOfPoint2f mat2 = new MatOfPoint2f(o2.toArray());
                    RotatedRect rect2 = Imgproc.minAreaRect(mat2);
                    Rect r2 = rect2.boundingRect();

                    return (int) (r1.area() - r2.area());
                }
            });
            return contours;
        }
    }

    /**
     * 作用:返回边缘检测之后的最大轮廓
     *
     * @param cannyMat
     *            Canny之后的Mat矩阵
     * @return
     */
    public static MatOfPoint findMaxContour(Mat cannyMat) {
        List contours = findContours(cannyMat);
        return contours.get(contours.size() - 1);
    }

    /**
     * 返回边缘检测之后的最大矩形
     *
     * @param cannyMat
     *            Canny之后的mat矩阵
     * @return
     */
    public static RotatedRect findMaxRect(Mat cannyMat) {
        MatOfPoint maxContour = findMaxContour(cannyMat);

        MatOfPoint2f matOfPoint2f = new MatOfPoint2f(maxContour.toArray());

        RotatedRect rect = Imgproc.minAreaRect(matOfPoint2f);
        Imgproc.boundingRect(cannyMat);

        return rect;
    }

    /**
     * 获取倾斜角度,只能矫正小角度倾斜,大于90度没法判断文字朝向
     * @author lyzhou2
     * @date 2022/3/4 15:05
     * @param cannyMat
     * @return
     */
    public static double getAngle (Mat cannyMat) {
        Mat lines = new Mat();
        //累加器阈值参数,小于设置值不返回
        int threshold = 100;
        //最低线段长度,低于设置值则不返回
        double minLineLength = 200;
        //间距小于该值的线当成同一条线
        double maxLineGap = 10;
        Imgproc.HoughLinesP(cannyMat, lines, 1, Math.PI/180, threshold, minLineLength, maxLineGap);
        //倾斜角度
        double angle = 0;
        //所有线倾斜角度之和
        double totalAngle = 0;
        for (int i = 0; i < lines.rows(); i++) {
            double[] line = lines.get(i, 0);
            //计算每条线弧度
            //这个计算是给Imgproc.getRotationMatrix2D方法用的,但是生成的图片会比原图大十几倍,简直离谱,有知道的大佬也可告知下怎么在不改变图片分辨率和清晰度的情况下缩小
//            double radian = Math.atan(Math.abs(line[3] - line[1]) * (1.0) / (line[2] - line[0]));
            //这个计算是给Graphics2D旋转使用
            double radian = Math.atan((line[3] - line[1]) * (-1.0) / (line[2] - line[0]));
            //计算每条线的倾斜角度
            double lineAngle = 360 * radian / (2 * Math.PI);
            //表格类图片要过滤掉竖线,不然取得角度会有问题
            if (Math.abs(lineAngle) > 45) {
                lineAngle = 90 - lineAngle;
            }
            totalAngle += lineAngle;
        }
        //取角度平均数
        angle = totalAngle / lines.rows();
        return angle;
    }
}

接下来就是测试验证了

第一张是原图,第二张是矫正之后的图片,可以看到很明显的矫正了。

java OpenCV实现扫描仪图片倾斜矫正

如果矫正后还是特别倾斜的,可以通过修改threshold、minLineLength 、maxLineGap 这几个参数来调整。

这只是在main方法里执行成功了,如果放在接口里去调用,会报错:Exception in thread “main” java.lang.UnsatisfiedLinkError: org.opencv.imgcodecs.Imgcodecs.imread_1(Ljava/lang/String;)J

然后你去百度会有说把System.loadLibrary改成System.load,然后又会报另一个错:java.lang.UnsatisfiedLinkError: Expecting an absolute path of the library: opencv_java455

其实就是找不到opencv动态库的路径。

顺便科普下System.loadLibrary参数是相对路径下的dll,System.load参数是绝对路径。如果是用System.load的话,直接写死就行,用System.loadLibrary则需要在启动时加上下面这句,windows下还可以把dll放在系统环境变量的Path任意目录下,因为System.loadLibrary默认是去Path下读取。

例如放到项目同级目录下:

VM options:-Djava.library.path=./

设置好后就可以用postman去调用接口测试了。

最后就是打包了,因为阿里的maven仓库里没有opencv-455.jar这个包,所以打包成jar包的时候会发现opencv-455.jar没有被打包进去,需要在pom.xml设置。

groupId、artifactId、version可以随便写,scope写system,systemPath是指jar路径,${basedir}是指当前项目目录。


    org.opencv
    opencv
    4.5.5
    system
    ${basedir}/lib/opencv-455.jar

【参考资料】

Java基于opencv—矫正图像 – 奇迹迪 – 博客园

Java实现图片无损任意角度旋转_通往精英的成长之路-CSDN博客_java实现图片旋转

JavaCV入门示例及UnsatisfiedLinkError异常踩坑记录_半斤米粉闯天下的博客-CSDN博客

Original: https://blog.csdn.net/lyzhou2/article/details/123323794
Author: lyzhou2
Title: java OpenCV实现扫描仪图片倾斜矫正

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/701977/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球