我正在try 使用OpenCV来检测在白色背景下扫描的卡片的轮廓.然而,我遇到了一个问题,它似乎只是将整个图像检测为1轮廓.在这一点上,我完全被我能调整的东西难住了.我try 过ImgProc.RETRLIST,但它返回42K等高线.

Code:

    public void convert() throws FileNotFoundException {
        // Load the image file
        // temp
        File file = ResourceUtils.getFile("classpath:input_image.png");
        // temp change to inputstream
        Mat image = Imgcodecs.imread(file.getAbsolutePath());

        // Convert the image to grayscale
        Mat grayImage = new Mat();
        Imgproc.cvtColor(image, grayImage, Imgproc.COLOR_BGR2GRAY);

        // Apply a threshold to convert the image to black and white
        Mat thresholdImage = new Mat();
        Imgproc.threshold(grayImage, thresholdImage, 242, 255, Imgproc.THRESH_BINARY);
        Imgcodecs.imwrite("thresh.png", thresholdImage);

        // Find contours in the thresholded image
        List<MatOfPoint> contours = new ArrayList<>();
        Mat hierarchy = new Mat();
        Imgproc.findContours(thresholdImage, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

        // Set the amount of empty space to add around the cropped image
        int emptySpace = 20;

        // Loop over the contours
        for (int i = 0; i < contours.size(); i++) {
            MatOfPoint contour = contours.get(i);

            // Find the bounding box of the contour
            Rect boundingRect = Imgproc.boundingRect(contour);

            // Crop the rectangle from the original image using the bounding box coordinates
            Mat rect = new Mat(image, boundingRect);

            // Straighten the rectangle
            RotatedRect rotatedRect = Imgproc.minAreaRect(new MatOfPoint2f(contour.toArray()));
            double angle = rotatedRect.angle;
            if (rotatedRect.size.width > rotatedRect.size.height) {
                angle += 90;
            }
            Mat rotationMatrix = Imgproc.getRotationMatrix2D(rotatedRect.center, angle, 1.0);
            Mat rotatedRectMat = new Mat();
            Imgproc.warpAffine(rect, rotatedRectMat, rotationMatrix, rect.size());

            // Add empty space around the cropped image
            Mat rectWithBorder = new Mat();
            Core.copyMakeBorder(rotatedRectMat, rectWithBorder, emptySpace, emptySpace, emptySpace, emptySpace, Core.BORDER_CONSTANT, new Scalar(255, 255, 255));

            // Save the rectangle with border as a separate image file
            Imgcodecs.imwrite(String.format("output_image_%d.png", i), rectWithBorder);

        }
    }

Input Image:

enter image description here

Generated Threshold:

enter image description here

Output Image:

enter image description here

推荐答案

之所以整个图像被检测为一个轮廓,是因为findContours发现了白色轮廓(而不是黑色轮廓),而阈值之后的整个图像是一个大的白色轮廓.

使用Imgproc.THRESH_BINARY_INV将黑白反转:

Imgproc.threshold(grayImage, thresholdImage, 242, 255, Imgproc.THRESH_BINARY_INV);

其他问题:

  • 我们可以忽略被认为是噪声的小轮廓:

     double contour_area = Imgproc.contourArea(contour);
     if (contour_area < min_contour_area) {
         continue;
     }
    
  • warpAffine之前添加白色填充--这是必需的,因为warpAffine可能会添加黑色边距(例如,考虑旋转45度):

     Mat rect = new Mat(image, boundingRect);
     rect = rect.clone(); //Create a clone of the cropped rectangle (required because copyMakeBorder refers to the large image).
     Mat rectWithBorder = new Mat();
     Core.copyMakeBorder(rect, rectWithBorder, emptySpace*2, emptySpace*2, emptySpace*2, emptySpace*2, Core.BORDER_CONSTANT, new Scalar(255, 255, 255));
    
  • 围绕裁剪的矩形的中心旋转(而不是围绕rotatedRect.center):

     Point rect_center = new Point((rectWithBorder.cols()-1.0)/2, (rectWithBorder.rows()-1.0)/2);
     Mat rotationMatrix = Imgproc.getRotationMatrix2D(rect_center, angle, 1.0);  
     Mat rotatedRectMat = new Mat();
     Imgproc.warpAffine(rectWithBorder, rotatedRectMat, rotationMatrix, rectWithBorder.size());
    
  • 删除warpAffine之后的多余页边距:

     Rect boundingRect2 = new Rect(emptySpace, emptySpace, rotatedRectMat.cols()-emptySpace*2, rotatedRectMat.rows()-emptySpace*2);
     Mat rectWithBorder2 = new Mat(rotatedRectMat, boundingRect2);
    

代码示例:

package myproject;

import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Scalar;
import org.opencv.core.Rect;
import org.opencv.core.Point;
import org.opencv.core.MatOfPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.RotatedRect;
import org.opencv.imgproc.Imgproc;
import org.opencv.imgcodecs.Imgcodecs;
import java.util.List;
import java.util.ArrayList;

class Sample {
    

static { System.loadLibrary(Core.NATIVE_LIBRARY_NAME); }


  
public static void main(String[] args) {
    // Load the image file
    // temp
    //File file = ResourceUtils.getFile("classpath:input_image.png");
    final String image_file_name = "input_image.png";
    
    // temp change to inputstream
    Mat image = Imgcodecs.imread(image_file_name);

    // Convert the image to grayscale
    Mat grayImage = new Mat();
    Imgproc.cvtColor(image, grayImage, Imgproc.COLOR_BGR2GRAY);

    // Apply a threshold to convert the image to black and white
    Mat thresholdImage = new Mat();
    //Imgproc.threshold(grayImage, thresholdImage, 242, 255, Imgproc.THRESH_BINARY);
    Imgproc.threshold(grayImage, thresholdImage, 242, 255, Imgproc.THRESH_BINARY_INV);
    Imgcodecs.imwrite("thresh.png", thresholdImage);

    // Find contours in the thresholded image
    List<MatOfPoint> contours = new ArrayList<>();
    Mat hierarchy = new Mat();
    Imgproc.findContours(thresholdImage, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

    // Set the amount of empty space to add around the cropped image
    int emptySpace = 20;
    
    double min_contour_area = 10000.0;  //Area threshold - ignore small contours with area less than 10000 (assume noise)

    // Loop over the contours
    for (int i = 0; i < contours.size(); i++) {
        MatOfPoint contour = contours.get(i);
        
        double contour_area = Imgproc.contourArea(contour);
        
        if (contour_area < min_contour_area) {
            continue;   //Ignore small contours with area less than min_contour_area (assume noise)
        }

        // Find the bounding box of the contour
        Rect boundingRect = Imgproc.boundingRect(contour);

        // Crop the rectangle from the original image using the bounding box coordinates
        Mat rect = new Mat(image, boundingRect);
        rect = rect.clone(); //Create a clone of the cropped rectangle (required because copyMakeBorder refers to the large image). 
        
        //Imgcodecs.imwrite("rect.png", rect); //Save rect for testing

        // Straighten the rectangle
        RotatedRect rotatedRect = Imgproc.minAreaRect(new MatOfPoint2f(contour.toArray()));
        double angle = rotatedRect.angle;
        if (rotatedRect.size.width > rotatedRect.size.height) {
            angle += 90;
        }
               
        // Add empty space around the rect - add larger padding before rotation because warpAffine may add black margins
        Mat rectWithBorder = new Mat();
        Core.copyMakeBorder(rect, rectWithBorder, emptySpace*2, emptySpace*2, emptySpace*2, emptySpace*2, Core.BORDER_CONSTANT, new Scalar(255, 255, 255));
        
        //Imgcodecs.imwrite("rectWithBorder.png", rectWithBorder); //Save rectWithBorder for testing
        
        //Mat rotationMatrix = Imgproc.getRotationMatrix2D(rotatedRect.center, angle, 1.0);  
        Point rect_center = new Point((rectWithBorder.cols()-1.0)/2, (rectWithBorder.rows()-1.0)/2);
        Mat rotationMatrix = Imgproc.getRotationMatrix2D(rect_center, angle, 1.0);  //Rotate around the center of rect.
        Mat rotatedRectMat = new Mat();
        Imgproc.warpAffine(rectWithBorder, rotatedRectMat, rotationMatrix, rectWithBorder.size());
        
        //Imgcodecs.imwrite("rotatedRectMat.png", rotatedRectMat); //Save rotatedRectMat for testing
        
        //Crop the center of rotatedRectMat (remove the extra emptySpace padding from each side).
        Rect boundingRect2 = new Rect(emptySpace, emptySpace, rotatedRectMat.cols()-emptySpace*2, rotatedRectMat.rows()-emptySpace*2);
        Mat rectWithBorder2 = new Mat(rotatedRectMat, boundingRect2);


        // Save the rectangle with border as a separate image file
        Imgcodecs.imwrite(String.format("output_image_%d.png", i), rectWithBorder2);
    }
}

}

Result:
enter image description here enter image description here enter image description here

enter image description here enter image description here enter image description here

enter image description here enter image description here enter image description here

Java相关问答推荐

使用Apache Poi MQLSlideShow,在XSLFTable表中,我们可以在文本段落后面的每个单元格中包含圆角矩形吗?

OpenJDK、4K显示和文本质量

Java:根据4象限中添加的行数均匀分布行的公式

为什么我们仍然需要实现noArgsConstructor如果Java默认提供一个非参数化的构造函数?''

为什么Java的代码工作(if condition内部的实例)

有关手动创建的包的问题

Java中实现的归并排序算法给出ArrayIndexOutOfBound异常

Bean定义不是从Spring ApplationConext.xml文件加载的

无法使用ApacheSpark依赖项构建JavaFX应用程序

Java中将文本拆分为数字或十进制数字和字符串

在向WebSphere中的文档添加元素时iText挂起

Java17支持哪个MapR版本?

我怎样才能让IntelliJ标记toString()的任何实现?

删除打印语句会影响功能...腐败在起作用?

如何在Record Java中使用isRecord()和RecordComponent[]?

在Java中将对象&转换为&q;HashMap(&Q)

我们可以在方法中声明接口吗?

由于版本不匹配,从Java 8迁移到Java 17和Spring 6 JUnit4失败

在JPanel上使用GridBagLayout并将JButton放在里面时出现问题

对于 Hangman 游戏,索引 0 超出长度 0 的范围