质心计算在 OpenCV 中产生错误的结果
Center of mass computation yields wrong results in OpenCV
首先我要声明我正在慢慢发疯。我正在尝试从图像中提取轮廓并使用 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
质心靠近左上角绘制,在轮廓之外。
正如评论讨论中提到的,您遇到的这个问题似乎 was reported specifically in the Java implementation on OpenCV's GitHub. It was eventually solved with 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
值的总和。这些可以很容易地计算出来,你可以用等值线定义你自己的质心函数。
首先我要声明我正在慢慢发疯。我正在尝试从图像中提取轮廓并使用 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
质心靠近左上角绘制,在轮廓之外。
正如评论讨论中提到的,您遇到的这个问题似乎 was reported specifically in the Java implementation on OpenCV's GitHub. It was eventually solved with 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
值的总和。这些可以很容易地计算出来,你可以用等值线定义你自己的质心函数。