为什么 OpenCV 中的 drawContour() 会生成这个奇怪的 Mask?

Why is the drawContour() in OpenCV generating this strange Mask?

我是从读这篇 Mat 开始的。

然后我将它转换为灰度并对其应用 Imgproc.canny(),得到以下遮罩。

然后我用Imgproc.findContours()找到轮廓,Imgproc.drawContours(),用Core.putText()用数字标记轮廓:

然后我做了 Rect boundingRect = Imgproc.boundingRect(contours.get(0)); Mat submatrix = new Mat(); submatrix = originalMat.submat(boundingRect); 获得关注 submatrix:

到目前为止一切顺利。问题从以下开始:

现在我需要 submatrix 的面具。所以我决定使用Imgproc.drawContours()来获取面具:

Mat mask = new Mat(submatrix.rows(), submatrix.cols(), CvType.CV_8UC1);
        List<MatOfPoint> contourList = new ArrayList<>();
        contourList.add(contours.get(0));
        Imgproc.drawContours(mask, contourList, 0, new Scalar(255), -1);

我得到了以下面具:

我期待的是黑色背景上的实心(白色)菱形。

为什么我会得到这个意想不到的结果?


编辑:

  1. 当我将 Mat mask = new Mat(submatrix.rows(), submatrix.cols(), CvType.CV_8UC1); 替换为 Mat mask = Mat.zeros(submatrix.rows(), submatrix.cols(), CvType.CV_8UC1); 时, 最后一个带有白色 garbage 的面具被替换为空的 黑色面具,上面没有任何白色。我得到了以下子垫 和面具:

  2. 我得到了等高线列表中的第一个等高线(名为 contours) 通过 contours.get(0),并使用第一个轮廓来 计算 Imgproc.boundingRect() 以及 contourList.add(contours.get(0)); 之后(其中 contourList 是 将在最后使用的只有一个轮廓的列表 drawContours()).

    然后我继续将 contours.get(0) 更改为 contours.get(1)Imgproc.boundingRect()contourList.add(); 中(就在 Imgproc.drawContours() 之前)。那 导致这个子垫和掩码:

  3. 然后我改回了contours.get(0) Imgproc.boundingRect();然后让 contourList.add(contours.get(1)); 在那里。得到以下 底板和遮罩:

现在我完全无法理解这里发生的事情。

我不确定在 JAVA 中是如何处理的(我通常在 C++ 或 python 中使用 OpenCV),但是你的代码中有一个错误...

contours列表将有一个点列表。这一点将参考原始图像。所以,这意味着如果图中的一个是 x=300, y= 300, width= 100, height=100 那么当你得到你的子矩阵时,它会尝试在较小的图像中绘制这些点......所以当它尝试绘制点 (300,300) 在 100 x 100 的图像中,它会简单地失败...可能会引发错误或根本不绘制任何东西...

对此的一个解决方案是,执行一个 for 循环并从轮廓的每个点减去边界矩形的初始点(在我的示例中 (300,300))。

因为,为什么绘制了一些垃圾...好吧,您从未初始化矩阵。在 JAVA 中不确定,但在 C++ 中你必须将它们设置为 0。 我认为应该是这样的:

Mat mask = new Mat(submatrix.rows(), submatrix.cols(), CvType.CV_8UC1, new Scalar(0));

希望对您有所帮助:)

编辑

我想我之前没有解释清楚。

您的轮廓是一组点 (x,y)。这些是代表 原始图像 中每个轮廓的点的坐标。该图像有尺寸,而您的子矩阵尺寸较小。这些点在这个小图像边界之外....

你应该这样做来修复它:

for (int j = 0; j < contours[0].length; j++) {
    contours[0][j].x -= boundingrect.x;
    contours[0][j].y -= boundingrect.y;
}

然后你可以绘制轮廓,因为它们将位于子垫的边界内。

我觉得在java中直接减去opencv的分也是可以的:

for (int j = 0; j < contours[0].length; j++) {
    contours[0][j] -= boundingrect.tl();
}

但在这种情况下我不确定,因为我只在 C++ 中尝试过

boundingrect.tl() -> 给你矩形的左上角