检测图像上的硬币(和适合椭圆)
我目前正在进行一个项目,在这个项目中,我正在试图检测出躺在平坦表面(即桌子)上的几枚硬币。 硬币不重叠,不被其他物体隐藏。 但可能有其他物体可见,照明条件可能不完美…基本上认为自己拍摄你的桌子上有一些硬币。
所以每个点都应该可以看作是一个椭圆。 由于我不知道相机的位置,椭圆的形状可能会有所不同,从圆(俯视图)到平椭圆取决于硬币拍摄的angular度。
我的问题是,我不知道如何提取硬币,并最终适合椭圆形在他们(我正在寻找做进一步的计算)。
现在我刚刚在OpenCV中设置了一个阈值,使用findContours()来获得轮廓线并拟合一个椭圆。 不幸的是,等高线很less给我硬币的形状(reflection,坏的照明…),这种方式也不是首选,因为我不希望用户设置任何阈值。
另一个想法是在该图像上使用椭圆的模板匹配方法,但是由于我不知道相机的angular度和椭圆的大小,我不认为这将工作得很好…
所以我想问问,如果有人能告诉我一个方法,在我的情况下工作…
有没有一种快速的方法从图像中提取三个硬币? 计算应该在移动设备上实时进行,并且方法不应该对于不同的或变化的灯光或背景的颜色太敏感。
如果有人能给我任何提示,哪种方法可以为我工作,将是伟大的… … –
这里有一些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: %dy: %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所build议的,Ohtsu的方法在这里工作得很好。 你似乎担心图像是双方的,但我不认为这会是一个问题。 只要有大量的桌面可见,就可以保证在直方图中有一个峰值。 只要桌面上有几个视觉上可区分的物体,就可以保证您的第二个高峰。
扩大你的二进制图像几次,以消除阈值留下的噪音。 硬币相对较大,所以他们应该幸存下来这种形态的操作。
使用区域生长将白色像素分组为对象 – 只是迭代地连接相邻的前景像素。 在这个操作结束时,你将得到一个不相交对象的列表,并且你将知道每个对象占据了哪个像素。
从这些信息中,您将知道对象的宽度和高度(从上一步开始)。 所以,现在您可以估计围绕对象的椭圆的大小,然后看看这个特定的对象与椭圆的匹配程度。 只是使用宽度与高度的比例可能会更容易一些。
或者,您可以使用时刻以更精确的方式确定对象的形状。
我不知道你的问题最好的方法是什么。 但是,关于设定阈值,可以使用大津的方法,根据图像直方图的分析自动find最佳阈值。 使用OpenCV的阈值方法,参数ThresholdType
等于THRESH_OTSU
。
但是请注意,大津的方法只适用于双峰直方图的图像(例如,在黑暗背景下有明亮物体的图像)。
您可能已经看到了这一点,但是也有一种用于在一组2D点(例如,连接的组件)上拟合椭圆的方法。
编辑 :大津的方法适用于示例图像:
灰度图像:
使用大津方法的结果:
如果任何人在将来像我一样来到这个问题,但使用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]); }