【问题标题】:Center of mass computation yields wrong results in OpenCV质心计算在 OpenCV 中产生错误的结果
【发布时间】:2018-03-02 04:38:18
【问题描述】:

我首先要说明我正在慢慢发疯。我正在尝试从图像中提取轮廓并使用 Java 和 OpenCV 计算它们的质心。

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

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

  1. 检查轮廓是否闭合。是的,我查过了。
  2. 在提取轮廓之前使用 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 的实现是否有问题,但您的代码看起来不错。您可以 manually calculate 时刻。但是有一个问题,为什么先蒙版然后再模糊,而不是先模糊再蒙版?非二进制掩码有点用词不当。
  • 是的,这里有点不对劲。我刚刚实现了您的代码的a Python version 或多或少的逐行和got the expected result
  • 你得到一个准确的contourArea吗?
  • 太好了,谢谢。我的代码仍然适用于完整版本。看起来有一个 related issue 特别是在 OpenCV 的 GitHub 上的 Java 实现中,通过 this 简单的拉取请求解决了。本来不应该的东西被投射到int。升级 OpenCV 应该可以解决问题。或者,如果您真的想要,您可以通过简单的修复来编辑您的库文件(实际上只是删除几行上的 int 演员表)。
  • 如果您想查看是否发现了错误,请转到 Github,进入问题,然后您可以搜索问题(通常只搜索函数名称就足够了)并浏览他们中的一些。但请确保删除默认的is:open,以防问题先前已解决。我会将其添加为答案。你没疯!

标签: java opencv centroid


【解决方案1】:

正如评论讨论中提到的,您在 OpenCV 的 GitHub 上的 Java 实现中似乎遇到了 was reported 的问题。最终通过this simple pull request 解决。有一些不必要的int 铸件。

那么可能的解决方案:

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

  2. 您可以使用该修复程序编辑您的库文件(它只是删除了几行上的 (int) 演员表)。

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


如果你无聊想算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 按照您在 cmets 中的说明工作,您可以简单地将其用作M_00。然后还要注意M_10 只是对应于白色像素的x 值和M_01y 值的总和。这些可以很容易地计算出来,您可以使用轮廓定义自己的质心函数。

【讨论】:

  • 我现在可以确认代码在 OpenCV 3.2.0 中可以正常工作。
猜你喜欢
  • 2019-05-12
  • 2015-12-27
  • 2012-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多