我目前正在开展一个项目,我正试图检测一些平躺在平面上的硬币(即桌子)。硬币不重叠,不会被其他物体隐藏。但可能有其他物体可见,照明条件可能不完美...基本上,考虑自己拍摄你的桌子上有一些硬币。
因此每个点应该作为椭圆可见。由于我不知道相机的位置,椭圆的形状可能会有所不同,从圆圈(从顶部看)到扁椭圆,取决于硬币的拍摄角度。
我的问题是我不知道如何提取硬币并最终将椭圆放在它们上面(我正在寻找进一步的计算)。
目前,我刚刚通过在OpenCV中设置阈值进行了第一次尝试,使用findContours()来获取轮廓线并拟合椭圆。不幸的是,轮廓线很少给我硬币的形状(反射,光线不好......),这种方式也不是首选,因为我不希望用户设置任何阈值。
另一个想法是在该图像上使用椭圆的模板匹配方法,但由于我不知道相机的角度和椭圆的大小,我认为这不会很好... < / p>
所以我想问一下是否有人可以告诉我一个适用于我的方法。
有没有快速的方法从图像中提取三个硬币?计算应该在移动设备上实时进行,并且该方法对于不同或变化的灯光或背景颜色不应过于敏感。
如果有人能给我任何关于哪种方法对我有用的提示,那会很棒。
答案 0 :(得分:49)
以下是一些实施传统方法的C99源代码(基于OpenCV doco):
#include "cv.h"
#include "highgui.h"
#include <stdio.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
//
// We need this to be high enough to get rid of things that are too small too
// have a definite shape. Otherwise, they will end up as ellipse false positives.
//
#define MIN_AREA 100.00
//
// One way to tell if an object is an ellipse is to look at the relationship
// of its area to its dimensions. If its actual occupied area can be estimated
// using the well-known area formula Area = PI*A*B, then it has a good chance of
// being an ellipse.
//
// This value is the maximum permissible error between actual and estimated area.
//
#define MAX_TOL 100.00
int main( int argc, char** argv )
{
IplImage* src;
// the first command line parameter must be file name of binary (black-n-white) image
if( argc == 2 && (src=cvLoadImage(argv[1], 0))!= 0)
{
IplImage* dst = cvCreateImage( cvGetSize(src), 8, 3 );
CvMemStorage* storage = cvCreateMemStorage(0);
CvSeq* contour = 0;
cvThreshold( src, src, 1, 255, CV_THRESH_BINARY );
//
// Invert the image such that white is foreground, black is background.
// Dilate to get rid of noise.
//
cvXorS(src, cvScalar(255, 0, 0, 0), src, NULL);
cvDilate(src, src, NULL, 2);
cvFindContours( src, storage, &contour, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));
cvZero( dst );
for( ; contour != 0; contour = contour->h_next )
{
double actual_area = fabs(cvContourArea(contour, CV_WHOLE_SEQ, 0));
if (actual_area < MIN_AREA)
continue;
//
// FIXME:
// Assuming the axes of the ellipse are vertical/perpendicular.
//
CvRect rect = ((CvContour *)contour)->rect;
int A = rect.width / 2;
int B = rect.height / 2;
double estimated_area = M_PI * A * B;
double error = fabs(actual_area - estimated_area);
if (error > MAX_TOL)
continue;
printf
(
"center x: %d y: %d A: %d B: %d\n",
rect.x + A,
rect.y + B,
A,
B
);
CvScalar color = CV_RGB( rand() % 255, rand() % 255, rand() % 255 );
cvDrawContours( dst, contour, color, color, -1, CV_FILLED, 8, cvPoint(0,0));
}
cvSaveImage("coins.png", dst, 0);
}
}
鉴于Carnieri提供的二进制图像,这是输出:
./opencv-contour.out coin-ohtsu.pbm
center x: 291 y: 328 A: 54 B: 42
center x: 286 y: 225 A: 46 B: 32
center x: 471 y: 221 A: 48 B: 33
center x: 140 y: 210 A: 42 B: 28
center x: 419 y: 116 A: 32 B: 19
这是输出图像:
你可以改进:
cvConvexityDefects
)区分硬币与其他物体的最佳方式可能是形状。我想不出任何其他低级图像功能(颜色明显不合适)。所以,我可以想到两种方法:
您的首要任务是将对象(硬币和非硬币)与背景分开。按照Carnieri的建议,Ohtsu的方法在这里会很好用。您似乎担心图像是 bipartite ,但我不认为这会是一个问题。只要有大量桌面可见,您就可以保证直方图中有一个峰值。只要桌面上有几个视觉上可辨别的物体,就可以保证你的第二个高峰。
Dilate你的二进制图像几次摆脱阈值所留下的噪音。硬币相对较大,因此他们应该在这种形态运作中存活下来。
使用区域增长将白色像素分组为对象 - 只需迭代连接相邻的前景像素。在此操作结束时,您将获得一个不相交的对象列表,您将知道每个对象占用的像素。
根据这些信息,您将知道对象的宽度和高度(从上一步开始)。因此,现在您可以估计围绕对象的椭圆的大小,然后查看此特定对象与椭圆的匹配程度。使用宽高比可能更容易。
或者,您可以使用moments以更精确的方式确定对象的形状。
答案 1 :(得分:6)
我不知道你问题的最佳方法是什么。但是,关于阈值处理,您可以使用Otsu的方法,该方法基于图像直方图的分析自动找到最佳阈值。使用OpenCV的threshold方法,参数ThresholdType
等于THRESH_OTSU
。
请注意,Otsu的方法仅适用于具有双峰直方图的图像(例如,在深色背景上具有明亮物体的图像)。
您可能已经看过这个,但是在{2}点周围还有一个fitting an ellipse方法(例如,连接的组件)。
编辑:Otsu的方法应用于示例图片:
灰度图像:
应用Otsu方法的结果:
答案 2 :(得分:6)
如果其他人像我一样在未来遇到此问题,但使用C ++:
使用findContours
查找轮廓后(如上面Misha的回答),您可以使用fitEllipse
轻松拟合椭圆,例如
vector<vector<Point> > contours;
findContours(img, contours, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0,0));
RotatedRect rotRecs[contours.size()];
for (int i = 0; i < contours.size(); i++) {
rotRecs[i] = fitEllipse(contours[i]);
}