首页 文章

检测图像上的硬币(和拟合椭圆)

提问于
浏览
44

我目前正在开展一个项目,我正试图检测一些平躺在平面上的硬币(即 table ) . 硬币不重叠,不会被其他物体隐藏 . 但可能有其他物体可见,照明条件可能不完美...基本上考虑自己拍摄你的 table 上有一些硬币 .

所以每个点都应该是椭圆形 . 由于我不知道相机的位置,椭圆的形状可能会有所不同,从圆圈(从顶部看)到扁平椭圆,取决于硬币拍摄的角度 .

我的问题是,我不知道如何提取硬币,最后在它们上面放置椭圆(我正在寻找进一步的计算) .

现在,我刚刚通过在OpenCV中设置阈值进行了第一次尝试,使用findContours()获取轮廓线并拟合椭圆 . 不幸的是,轮廓线很少给我硬币的形状(反射,光线不好......),这种方式也不是首选,因为我不希望用户设置任何阈值 .

另一个想法是在该图像上使用椭圆的模板匹配方法,但由于我不知道相机的角度和椭圆的大小,我认为这不会很好...

所以我想问一下是否有人能告诉我一种方法可以解决我的问题......

Is there a fast way to extract the three coins from the image? The calculations should be made in realtime on mobile devices and the method should not be too sensitive for different or changing lights or the color of the background.

如果有人能给我任何关于哪种方法对我有用的提示,那会很棒...

3 回答

  • 6

    以下是一些实现传统方法的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
    

    这是输出图像:

    coins

    您可以改进的内容:

    • 处理不同的椭圆方向(目前,我假设轴是垂直/水平的) . 使用图像时刻这并不难 .

    • 检查物体凸度(看看 cvConvexityDefects

    区分硬币与其他物体的最佳方式可能是形状 . 我想不出任何其他低级图像功能(颜色明显不合适) . 所以,我可以想到两种方法:

    传统物体检测

    您的第一个任务是将对象(硬币和非硬币)与背景分开 . 按照Carnieri的建议,Ohtsu的方法在这里会很好用 . 你似乎担心图像是二分的,但我不能保证你的直方图中有一个峰值 . 只要桌面上有几个视觉上可辨别的物体,您就可以保证第二个高峰 .

    Dilate你的二进制图像几次摆脱阈值留下的噪音 . 硬币相对较大,因此他们应该在这种形态运作中存活下来 .

    使用区域增长将白色像素分组为对象 - 只需迭代连接相邻的前景像素 . 在此操作结束时,您将拥有一个不相交的对象列表,您将知道每个对象占用哪些像素 .

    根据此信息,您将知道对象的宽度和高度(从上一步开始) . 因此,现在您可以估计围绕对象的椭圆的大小,然后查看此特定对象与椭圆的匹配程度 . 使用宽高比可能更容易 .

    或者,您可以使用moments以更精确的方式确定对象的形状 .

  • 6

    我没有't know what the best method for your problem is. About thresholding specifically, however, you can use Otsu'方法,它根据图像直方图的分析自动找到最佳阈值 . 使用OpenCV的threshold方法,参数 ThresholdType 等于 THRESH_OTSU .

    但请注意,Otsu的方法仅适用于具有双峰直方图的图像(例如,在深色背景上具有明亮物体的图像) .

    您可能已经看过这个,但是还有一个围绕一组2D点的方法(例如,连接的组件) .

    EDIT :Otsu的方法应用于样本图像:

    灰度图像:
    grayscale image

    应用Otsu方法的结果:
    Otsu image

  • 49

    如果其他人像我一样在将来遇到这个问题,但使用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]);
        }
    

相关问题