opencv:在二值图像中拟合最小封闭椭圆
opencv: fit a minimum enclosing ellipse in a binary image
我已经使用 OpenCV 和 grabcut 实现生成了前景的二进制掩码。这表示为 CV_8UC1 的 opencv 矩阵,其中属于前景的像素值为 255,背景为零(即它是二进制掩码)。因此,一张像所附的图片:
我想找到这个蒙版图像的最小封闭椭圆。我在网上找到的例子似乎有点复杂,我无法将其转化为我的需要。我尝试简单地使用
// result is my OpenCV array of
cv::RotatedRect e = cv::fitEllipse(result);
OpenCV Error: Assertion failed (points.checkVector(2) >= 0 &&
(points.depth() == CV_32F || points.depth() == CV_32S)) in fitEllipse,
file /home/luca/Downloads/opencv-2.4.10/modules/imgproc
/src/contours.cpp, line 2019
terminate called after throwing an instance of 'cv::Exception'
what(): /home/luca/Downloads/opencv-2.4.10/modules/imgproc
/src/contours.cpp:2019: error: (-215) points.checkVector(2) >= 0 &&
(points.depth() == CV_32F || points.depth() == CV_32S) in function
fitEllipse
即使我将其转换为 32 位有符号整数,错误仍然存在:
cv::Mat r;
result.convertTo(r, CV_32S);
cv::RotatedRect e = cv::fitEllipse(r);
好的,我明白了。我需要将其转换为轮廓集。代码取自网上例子
cv::vector<cv::vector<cv::Point> > contours;
cv::findContours(result, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);
// Assuming always returns one contour as the input is binary
cv::RotatedRect box = cv::fitEllipse(contours[0]);
// Draw the ellipse
cv::ellipse(image, box, cv::Scalar(255,0,0));
函数fitEllipse takes an array of cv::Point s, not an image. So you want to run your image through findContours先。请注意,findContours
修改了图像,因此您可能需要先复制一份。
std::vector< std::vector<cv::Point> > contours;
cv::Mat tmp = result.clone();
cv::findContours(tmp, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); // edited to change from CV_CHAIN_APPROX_SIMPLE
cv::RotatedRect e = cv::fitEllipse(contours[0]);
以上假设您的图像中只有一个轮廓。你可能想搜索 contours
最大的轮廓(使用大小或面积)以防有任何噪音(并验证你至少得到一个轮廓)。
如果您不确定图像中是否有椭圆,您可以使用另一种方法调用cv::minAreaRect
。
我已经为 3 种不同的方式编写了示例代码:
1. call cv::fitEllipse
2. call cv::minAreaRect
3. call cv::fitEllipse on contour only
代码有点乱,但也许对你有帮助
int main()
{
cv::Mat input = cv::imread("../inputData/fitEllipseMask.jpg");
cv::Mat gray;
cv::cvtColor(input,gray,CV_BGR2GRAY);
cv::Mat mask = gray > 200; // remove jpeg artifacts
std::vector<cv::Point> pts;
for(int j=0; j<mask.rows; ++j)
for(int i=0; i<mask.cols; ++i)
{
if(mask.at<unsigned char>(j,i))
{
pts.push_back(cv::Point(i,j));
}
}
cv::RotatedRect result1 = cv::fitEllipse(pts);
cv::ellipse(input, result1, cv::Scalar(0,255,0) ,3 );
cv::RotatedRect result2 = cv::minAreaRect(pts);
cv::ellipse(input, result2, cv::Scalar(0,0,255) ,3 );
// now a third method to fit an ellipse but only the contour of the mask object
// edges could be extracted with findContours instead which might or might not be better, depending on input images
cv::Mat magX, absmagx;
cv::Sobel(mask, magX, CV_32FC1, 1, 0);
cv::convertScaleAbs( magX, absmagx );
cv::Mat magY, absmagy;
cv::Sobel(mask, magY, CV_32FC1, 0, 1);
cv::convertScaleAbs( magY, absmagy );
cv::Mat mag = absmagx+absmagy;
cv::Mat edgeMask = mag > 0;
cv::imshow("edges",edgeMask);
std::vector<cv::Point> ptsEdges;
for(int j=0; j<edgeMask.rows; ++j)
for(int i=0; i<edgeMask.cols; ++i)
{
if(edgeMask.at<unsigned char>(j,i))
{
ptsEdges.push_back(cv::Point(i,j));
}
}
cv::RotatedRect result3 = cv::fitEllipse(ptsEdges);
cv::ellipse(input, result3, cv::Scalar(255,0,0) , 3 );
cv::namedWindow("input");
cv::imshow("input", input);
//cv::imwrite("../outputData/MainBase.png", input);
cv::waitKey(0);
return 0;
}
1. green: since we try to fit the ellipse to the whole white region, the best found ellipse is something like the mean ellipse within the region
2. red: not as good as 3 but will give better results if there is no ellipse in the image but another object
3. blue: fitting an ellipse to a real ellipse with some outliers/noise is just the best result ;)
我已经使用 OpenCV 和 grabcut 实现生成了前景的二进制掩码。这表示为 CV_8UC1 的 opencv 矩阵,其中属于前景的像素值为 255,背景为零(即它是二进制掩码)。因此,一张像所附的图片:
我想找到这个蒙版图像的最小封闭椭圆。我在网上找到的例子似乎有点复杂,我无法将其转化为我的需要。我尝试简单地使用
// result is my OpenCV array of
cv::RotatedRect e = cv::fitEllipse(result);
OpenCV Error: Assertion failed (points.checkVector(2) >= 0 &&
(points.depth() == CV_32F || points.depth() == CV_32S)) in fitEllipse,
file /home/luca/Downloads/opencv-2.4.10/modules/imgproc
/src/contours.cpp, line 2019
terminate called after throwing an instance of 'cv::Exception'
what(): /home/luca/Downloads/opencv-2.4.10/modules/imgproc
/src/contours.cpp:2019: error: (-215) points.checkVector(2) >= 0 &&
(points.depth() == CV_32F || points.depth() == CV_32S) in function
fitEllipse
即使我将其转换为 32 位有符号整数,错误仍然存在:
cv::Mat r;
result.convertTo(r, CV_32S);
cv::RotatedRect e = cv::fitEllipse(r);
好的,我明白了。我需要将其转换为轮廓集。代码取自网上例子
cv::vector<cv::vector<cv::Point> > contours;
cv::findContours(result, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);
// Assuming always returns one contour as the input is binary
cv::RotatedRect box = cv::fitEllipse(contours[0]);
// Draw the ellipse
cv::ellipse(image, box, cv::Scalar(255,0,0));
函数fitEllipse takes an array of cv::Point s, not an image. So you want to run your image through findContours先。请注意,findContours
修改了图像,因此您可能需要先复制一份。
std::vector< std::vector<cv::Point> > contours;
cv::Mat tmp = result.clone();
cv::findContours(tmp, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); // edited to change from CV_CHAIN_APPROX_SIMPLE
cv::RotatedRect e = cv::fitEllipse(contours[0]);
以上假设您的图像中只有一个轮廓。你可能想搜索 contours
最大的轮廓(使用大小或面积)以防有任何噪音(并验证你至少得到一个轮廓)。
如果您不确定图像中是否有椭圆,您可以使用另一种方法调用cv::minAreaRect
。
我已经为 3 种不同的方式编写了示例代码:
1. call cv::fitEllipse
2. call cv::minAreaRect
3. call cv::fitEllipse on contour only
代码有点乱,但也许对你有帮助
int main()
{
cv::Mat input = cv::imread("../inputData/fitEllipseMask.jpg");
cv::Mat gray;
cv::cvtColor(input,gray,CV_BGR2GRAY);
cv::Mat mask = gray > 200; // remove jpeg artifacts
std::vector<cv::Point> pts;
for(int j=0; j<mask.rows; ++j)
for(int i=0; i<mask.cols; ++i)
{
if(mask.at<unsigned char>(j,i))
{
pts.push_back(cv::Point(i,j));
}
}
cv::RotatedRect result1 = cv::fitEllipse(pts);
cv::ellipse(input, result1, cv::Scalar(0,255,0) ,3 );
cv::RotatedRect result2 = cv::minAreaRect(pts);
cv::ellipse(input, result2, cv::Scalar(0,0,255) ,3 );
// now a third method to fit an ellipse but only the contour of the mask object
// edges could be extracted with findContours instead which might or might not be better, depending on input images
cv::Mat magX, absmagx;
cv::Sobel(mask, magX, CV_32FC1, 1, 0);
cv::convertScaleAbs( magX, absmagx );
cv::Mat magY, absmagy;
cv::Sobel(mask, magY, CV_32FC1, 0, 1);
cv::convertScaleAbs( magY, absmagy );
cv::Mat mag = absmagx+absmagy;
cv::Mat edgeMask = mag > 0;
cv::imshow("edges",edgeMask);
std::vector<cv::Point> ptsEdges;
for(int j=0; j<edgeMask.rows; ++j)
for(int i=0; i<edgeMask.cols; ++i)
{
if(edgeMask.at<unsigned char>(j,i))
{
ptsEdges.push_back(cv::Point(i,j));
}
}
cv::RotatedRect result3 = cv::fitEllipse(ptsEdges);
cv::ellipse(input, result3, cv::Scalar(255,0,0) , 3 );
cv::namedWindow("input");
cv::imshow("input", input);
//cv::imwrite("../outputData/MainBase.png", input);
cv::waitKey(0);
return 0;
}
1. green: since we try to fit the ellipse to the whole white region, the best found ellipse is something like the mean ellipse within the region
2. red: not as good as 3 but will give better results if there is no ellipse in the image but another object
3. blue: fitting an ellipse to a real ellipse with some outliers/noise is just the best result ;)