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