java OpenCV implementation of scanner image tilt correction

First download the opencv resources on the official website

Official website address: Releases - OpenCV

The download on the official website is slow. You can choose to download on Baidu online disk. The version is 455

Link: https://pan.baidu.com/s/1LADtih8l8nStKwJRIde91Q  
Extraction code: wx0h

Tip: the following methods are only applicable to small angle tilt, and 90 degrees cannot be corrected!!!

After downloading and decompressing, there is a jar package under \ opencv\build\java, which needs to be imported into the ide. There are also x64 and x86 folders under this directory. It is the dynamic library of OpenCV. You can choose to use it according to your computer. The interfaces of OpenCV are in the dynamic library, which is very important!

Import the jar package and the dynamic library of opencv

PngEncoder will be used later, and the package needs to be imported

<!-- png Image processing -->
<dependency>
	<groupId>com.pngencoder</groupId>
	<artifactId>pngencoder</artifactId>
	<version>0.9.0</version>
</dependency>

The following is the specific code development. The first method is to use the Hough detection algorithm of opencv to calculate the tilt angle and rotate. However, because the modified image is more than ten times larger than the original image, it is abandoned. The boss who knows can guide how to reduce the image without changing the resolution and clarity of the image.

public static void first () {
    try {
        //Enter picture path
        String srcPath = "D:\\TU\\x7.png";
        //Output picture path
        String rotatePath = "D:\\TU\\rotate.png";
        //Load opencv dynamic library, if necessary
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        //Input picture
        Mat src = Imgcodecs.imread(srcPath);
        //Grayscale
        Mat gray = new Mat();
        if (src.channels() == 3) {
            Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
            src = gray;
        } else {
            System.out.println("no RGB picture!");
        }
        //Edge detection algorithm
        Mat cannyMat = src.clone();
        //Represents the first threshold of the hysteresis process
        double threshold1 = 60;
        //Represents the second threshold of the hysteresis process, usually the first threshold * 2 or * 3
        double threshold2 = threshold1 * 3;
        Imgproc.Canny(src, cannyMat, threshold1, threshold2);
        //Calculate tilt angle
        double angle = OpenCVUtil.getAngle(cannyMat);
        // Gets the maximum rectangle
        RotatedRect rect = OpenCVUtil.findMaxRect(cannyMat);
        // Rotate rectangle
        Mat CorrectImg = OpenCVUtil.rotation(cannyMat, rect, angle);
        HandleImgUtils.saveImg(CorrectImg, "d:/TU/correct.png");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

The second method is to calculate the tilt angle with the Hough detection of opencv, and rotate the picture through Graphics2D.

public static void second () {
    try {
        //Enter picture path
        String srcPath = "D:\\TU\\x7.png";
        //Output picture path
        String rotatePath = "D:\\TU\\rotate.png";
        //Load opencv dynamic library, if necessary
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        //Input picture
        Mat src = Imgcodecs.imread(srcPath);
        //Grayscale
        Mat gray = new Mat();
        if (src.channels() == 3) {
            Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
            src = gray;
        } else {
            System.out.println("no RGB picture!");
        }
        //Edge detection algorithm
        Mat cannyMat = src.clone();
        //Represents the first threshold of the hysteresis process
        double threshold1 = 60;
        //Represents the second threshold of the hysteresis process, usually the first threshold * 2 or * 3
        double threshold2 = threshold1 * 3;
        Imgproc.Canny(src, cannyMat, threshold1, threshold2);
        //Calculate tilt angle
        double angle = OpenCVUtil.getAngle(cannyMat);
        //pictures rotating
        BufferedImage srcBuff = ImageIO.read(new File(srcPath));
        //image width
        int width = srcBuff.getWidth(null);
        //Picture height
        int height = srcBuff.getHeight(null);
        BufferedImage res = new BufferedImage(width, height,
                BufferedImage.TYPE_INT_RGB);
        Graphics2D g2 = res.createGraphics();
        //Set picture background
        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 generates png very slowly. I heard that jdk1 After 9, there will be optimization (not tried), but the JDK version of this project cannot be changed, so PngEncoder is selected to generate pictures
        //ImageIO.write(res, "png", new File(rotatePath));
        PngEncoder encoder=new PngEncoder();
        encoder.withBufferedImage(res).toFile(rotatePath);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

OpenCVUtil tool class

public class OpenCVUtil {
    /**
     * Find contours and sort them incrementally
     *
     * @param cannyMat
     * @return
     */
    public static List<MatOfPoint> findContours(Mat cannyMat) {
        List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
        Mat hierarchy = new Mat();

        // Find contour
        Imgproc.findContours(cannyMat, contours, hierarchy, Imgproc.RETR_LIST,                 Imgproc.CHAIN_APPROX_SIMPLE,
                new Point(0, 0));
        if (contours.size() <= 0) {
            throw new RuntimeException("Image outline not found");
        } else {
            // The contours are sorted in ascending order
            contours.sort(new Comparator<MatOfPoint>() {
                @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;
        }
    }    

    /**
     * Function: returns the maximum contour after edge detection
     *
     * @param cannyMat
     *            Canny Subsequent Mat matrix
     * @return
     */
    public static MatOfPoint findMaxContour(Mat cannyMat) {
        List<MatOfPoint> contours = findContours(cannyMat);
        return contours.get(contours.size() - 1);
    }

    /**
     * Returns the maximum rectangle after edge detection
     *
     * @param cannyMat
     *            Canny mat matrix after
     * @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;
    }
    
    /**
     * To obtain the tilt angle, you can only correct the small angle tilt. If it is greater than 90 degrees, you can't judge the text orientation
     * @author lyzhou2
     * @date 2022/3/4 15:05
     * @param cannyMat
     * @return
     */
    public static double getAngle (Mat cannyMat) {
        Mat lines = new Mat();
        //Accumulator threshold parameter. If it is less than the set value, it will not be returned
        int threshold = 100;
        //The length of the lowest line segment. If it is lower than the set value, it will not be returned
        double minLineLength = 200;
        //Lines with spacing less than this value are treated as the same line
        double maxLineGap = 10;
        Imgproc.HoughLinesP(cannyMat, lines, 1, Math.PI/180, threshold, minLineLength, maxLineGap);
        //Tilt angle
        double angle = 0;
        //Sum of inclination angles of all lines
        double totalAngle = 0;
        for (int i = 0; i < lines.rows(); i++) {
            double[] line = lines.get(i, 0);
            //Calculate the radian of each line
            //This calculation is for imgproc The getrotationmatrix2d method is used, but the generated image will be more than ten times larger than the original image, which is outrageous. Someone who knows can also tell you how to reduce the image without changing the resolution and definition of the image
//            double radian = Math.atan(Math.abs(line[3] - line[1]) * (1.0) / (line[2] - line[0]));
            //This calculation is used for Graphics2D rotation
            double radian = Math.atan((line[3] - line[1]) * (-1.0) / (line[2] - line[0]));
            //Calculate the tilt angle of each line
            double lineAngle = 360 * radian / (2 * Math.PI);
            //Vertical lines should be filtered out for table pictures, otherwise there will be problems in obtaining the angle
            if (Math.abs(lineAngle) > 45) {
                lineAngle = 90 - lineAngle;
            }
            totalAngle += lineAngle;
        }
        //Average angle
        angle = totalAngle / lines.rows();
        return angle;
    }
}

The next step is test verification

The first is the original picture, and the second is the picture after correction. You can see the obvious correction.

If it is still inclined after correction, it can be adjusted by modifying the parameters of threshold, minLineLength and maxLineGap.

This is only successful in the main method. If it is called in the interface, an error will be reported: exception in thread "main" Java lang.UnsatisfiedLinkError: org. opencv. imgcodecs. Imgcodecs. imread_ 1(Ljava/lang/String;) J

Then when you go to Baidu, you will say to put the system Change loadlibrary to system Load, and then another error will be reported: Java lang.UnsatisfiedLinkError: Expecting an absolute path of the library: opencv_ java455

In fact, the path of opencv dynamic library cannot be found.

By the way, system The loadlibrary parameter is the dll under the relative Path, system The load parameter is an absolute Path. If using system If you load, just write it dead. Use system Loadlibrary needs to add the following sentence when starting. Under windows, you can also put the dll in any directory of Path of the system environment variable, because system Loadlibrary is read in Path by default.

For example, put it under the same level directory of the project:

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

After setting, you can use postman to call the interface test.

Finally, it is packed, because there is no opencv-455 in Alibaba's maven warehouse Jar package, so when you package it into a jar package, you will find opencv-455 Jar is not packaged in, it needs to be in POM XML settings.

groupId, artifactId and version can be written freely. scope is written as system. systemPath refers to jar path and ${basedir} refers to the current project directory.

<!-- Image processing OpenCv-->
<dependency>
	<groupId>org.opencv</groupId>
	<artifactId>opencv</artifactId>
	<version>4.5.5</version>
	<scope>system</scope>
	<systemPath>${basedir}/lib/opencv-455.jar</systemPath>
</dependency>

[References]

Java based on opencv - correction image - Miracle di - blog Garden

java implementation of lossless image rotation at any angle_ The growth path to the elite - CSDN blog_ java image rotation

JavaCV introduction example and UnsatisfiedLinkError abnormal pit stepping record_ Half a catty of rice noodles into the World blog - CSDN blog

Keywords: Java

Added by jmrothermel on Mon, 07 Mar 2022 12:00:56 +0200