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

时间:2017-09-21 00:20:40

标签: java opencv centroid

我将首先说明我会慢慢疯狂。我试图从图像中提取轮廓并使用Java和OpenCV计算它们的质心。

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

其他人遇到了这个问题,建议是:

  1. 检查轮廓是否关闭。是的,我查了一下。
  2. 使用Canny在提取轮廓之前检测边缘。我不明白为什么这是必要的,但是我尝试了它,结果就是它弄乱了树的层次结构,因为它为每条边创建了两个轮廓,这不是我想要的。
  3. 输入图像非常大(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);
        }
    }
    

    输入:(完整图片hereenter image description here 输出: enter image description here

    STDOUT:

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

    质心绘制在靠近左上角的位置,在轮廓之外。

1 个答案:

答案 0 :(得分:1)

正如评论讨论中所提到的,看起来这个问题你在OpenCV的GitHub的Java实现中特别具有was reported。它最终用this simple pull request解决了。有一些不必要的int铸件。

然后可能的解决方案:

  1. 升级OpenCV应该可以解决问题。

  2. 您可以使用修补程序编辑库文件(只需在几行上删除(int)强制转换)。

  3. 定义自己的函数来计算质心。

  4. 如果你感到无聊并且想要弄清楚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的总和。这些可以很容易地计算出来,你可以用轮廓定义你自己的质心函数。