质量计算中心在OpenCV中产生错误结果

2020年11月9日 9点热度 0条评论

首先,我要说我正在疯狂。我正在尝试从图像中提取轮廓,并使用Java和OpenCV计算其重心。

对于所有内部轮廓,结果都是正确的,但是对于外部(最大)轮廓,质心相距很远。输入图像,代码和输出结果均在下面。 OpenCV版本是3.1。

其他人有这个问题,建议是:

  • 检查轮廓是否闭合。是的,我检查了。
  • 使用Canny在提取轮廓之前检测边缘。我不明白为什么这样做是必要的,但是我尝试了一下,结果是它弄乱了树的层次结构,因为它为每个边缘生成两个轮廓,这不是我想要的。
  • 输入的图像非常大(27MB),奇怪的是,当我将其调整为1000x800的大小时,突然正确地计算了质心,但是,我需要能够以原始分辨率处理图像。

    /*
         * To change this license header, choose License Headers in Project Properties.
         * To change this template file, choose Tools | Templates
         * and open the template in the editor.
     */
    package com.philrovision.dxfvision.matching;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import org.opencv.core.Core;
    import org.opencv.core.CvType;
    import org.opencv.core.Mat;
    import org.opencv.core.MatOfPoint;
    import org.opencv.core.Point;
    import org.opencv.core.Rect;
    import org.opencv.core.Scalar;
    import org.opencv.imgcodecs.Imgcodecs;
    import org.opencv.imgproc.Imgproc;
    import org.opencv.imgproc.Moments;
    import org.testng.annotations.Test;
    
    /**
     *
     * @author rhobincu
     */
    public class MomentsNGTest {
    
        @Test
        public void testOpenCvMoments() {
            Mat image = Imgcodecs.imread("moments_fail.png");
            Mat channel = new Mat();
            Core.extractChannel(image, channel, 1);
            Mat mask = new Mat();
            Imgproc.threshold(channel, mask, 191, 255, Imgproc.THRESH_BINARY);
    
            Mat filteredMask = new Mat();
            Imgproc.medianBlur(mask, filteredMask, 5);
    
            List<MatOfPoint> allContours = new ArrayList<>();
            Mat hierarchy = new Mat();
    
            Imgproc.findContours(filteredMask, allContours, hierarchy, Imgproc.RETR_TREE,
                    Imgproc.CHAIN_APPROX_SIMPLE, new Point(0, 0));
    
            MatOfPoint largestContour = allContours.stream().max((c1, c2) -> {
                double area1 = Imgproc.contourArea(c1);
                double area2 = Imgproc.contourArea(c2);
                if (area1 < area2) {
                    return -1;
                } else if (area1 > area2) {
                    return 1;
                }
                return 0;
            }).get();
    
            Mat debugCanvas = new Mat(image.size(), CvType.CV_8UC3);
            Imgproc.drawContours(debugCanvas, Arrays.asList(largestContour), -1, new Scalar(255, 255, 255), 3);
            Imgproc.drawMarker(debugCanvas, getCenterOfMass(largestContour),
                    new Scalar(255, 255, 255));
            Rect boundingBox = Imgproc.boundingRect(largestContour);
            Imgproc.rectangle(debugCanvas, boundingBox.br(), boundingBox.tl(), new Scalar(0, 255, 0), 3);
            System.out.printf("Bounding box area is: %f and contour area is: %f", boundingBox.area(), Imgproc.contourArea(
                    largestContour));
            Imgcodecs.imwrite("output.png", debugCanvas);
    
        }
    
        private static Point getCenterOfMass(MatOfPoint contour) {
            Moments moments = Imgproc.moments(contour);
            return new Point(moments.m10 / moments.m00, moments.m01 / moments.m00);
        }
    }
    

    输入:(全图
    here)




    输出:


    标准输出:

    Bounding box area is: 6460729,000000 and contour area is: 5963212,000000
    

    重心在轮廓外部靠近左上角绘制。

    解决方案如下:

    正如评论讨论中提到的那样,您似乎在特别是OpenCV的GitHub上的Java实现中遇到了was reported这个问题。最终用this simple pull request解决了。有一些不必要的int强制转换。

    然后可能的解决方案:

  • 升级OpenCV应该可以解决问题。
  • 您可以使用该修复程序编辑库文件(只需删除在几行上投射的(int))。
  • 定义您自己的函数以计算质心。
  • 如果您很无聊并且想找出3,则计算起来实际上并不困难:

    轮廓的质心通常由
    image moments计算。如该页面所示,可以在图像上将一刻
    M_ij定义为:

    M_ij = sum_x sum_y (x^i * y^j * I(x, y))
    

    二进制形状的质心是

    (x_c, y_c) = (M_10/M_00, M_01/M_00)
    

    请注意
    M_00 = sum_x sum_y (I(x, y)),在二进制0和1图像中,仅是白色像素的数量。如果您的
    contourArea如注释中所述正常工作,则可以简单地将其用作
    M_00。然后还要注意
    M_10只是与白色像素相对应的
    x值和
    M_01
    y值对应的总和。这些可以很容易地计算出来,您可以用轮廓定义自己的质心函数。