【问题标题】:Detection of coins (and fit ellipses) on an image检测图像上的硬币(和拟合椭圆)
【发布时间】:2011-06-14 16:47:25
【问题描述】:

我目前正在从事一个项目,我试图检测一些放在平坦表面(即桌子)上的硬币。硬币不会重叠,也不会被其他物体隐藏。但是可能还有其他可见的物体,并且照明条件可能并不完美......基本上,考虑一下自己拍摄你的桌子上有一些硬币。

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

我的问题是我不确定如何提取硬币并最终在它们上拟合椭圆(我正在寻找进行进一步计算)。

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

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

所以我想问是否有人可以告诉我一种适用于我的情况的方法。

有没有一种快速的方法可以从图像中提取三枚硬币?计算应在移动设备上实时进行,该方法不应对不同或变化的灯光或背景颜色过于敏感。

如果有人能给我关于哪种方法适合我的任何提示,那就太好了。

【问题讨论】:

  • 为什么要“拟合椭圆”? “找到省略号”还不够吗?

标签: image image-processing opencv image-recognition


【解决方案1】:

我不知道解决您的问题的最佳方法是什么。但是,具体而言,关于阈值化,您可以使用 Otsu 方法,该方法会根据图像直方图的分析自动找到最佳阈值。使用OpenCV的threshold方法,参数ThresholdType等于THRESH_OTSU

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

您可能已经看过这个,但还有一种方法可以让fitting an ellipse 围绕一组 2D 点(例如,连接的组件)。

编辑:Otsu 的方法应用于示例图像:

灰度图像:

应用大津方法的结果:

【讨论】:

  • 对了,我听说过大津的方法。这看起来不错,但是就像您说的那样,它仅适用于二分直方图,并且在大多数情况下,我不会拥有它,因为大多数情况下其他对象都是可见的。我已经在这张照片上尝试了一个分水岭算法,效果很好,但对位点将是跟踪硬币。我可以让用户在开始时选择硬币和桌子,但它应该会自动检测硬币。因此,使用卡尔曼滤波器或粒子滤波器跟踪标记点可能不起作用,因为运动可能不稳定。您还有其他想法或建议吗?
  • 您之前没有提到过跟踪。您需要跟踪它们是因为它们在移动还是因为相机在移动?我猜是后者。试试 OpenCV 附带的 blobtrack_sample.cpp。我在跟踪移动的车辆时得到了很好的结果,但在这种情况下,有很多好的特征(角)要跟踪,用硬币可能会更难。
  • 如果我使用分水岭算法,跟踪只是一个想法。用户首先选择硬币,然后对其进行跟踪,以便我可以使用分水岭算法对每个时间步的硬币进行分割。但由于相机移动不稳定,我怀疑它是否能很好地用于跟踪。如果它运作良好,我仍然更喜欢没有跟踪的方法......
【解决方案2】:

这是一些实现传统方法的 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 以更精确的方式确定对象的形状。

【讨论】:

  • 这听起来很不错!我将尝试第一种方法,看看它的效果如何......如果结果不够,我将尝试另一种方法......所以通过模板匹配,你的意思是垂直缩放图像并希望椭圆变成我可以尝试模板匹配的圈子,甚至可以尝试对圈子进行霍夫变换......到目前为止,谢谢!
  • 感谢您的代码...您发布的示例看起来很棒...我会这样尝试...只有在相机保持笔直时轴才水平/垂直...但是也许我可以从手机传感器中获取...谢谢!
  • 不客气。我删除了解释中的图像金字塔部分,因为我认为轮廓检测会更好。但是,是的,一般的想法是希望将椭圆变成圆形,然后进行模板匹配。不确定是否使用 Hough 变换——它可能需要比它的价值更多的时间。
  • 绝妙的答案!计算机视觉的一大优点是我们可以轻松地将结果可视化,如您的输出图像所示。一件事:我同意显然椭圆的方向总是大致相同的,因为它们在同一个平面上。但是,根据相机和硬币之间的距离,透视变形可能很严重。
  • 是的,它们大部分应该对齐...因为硬币彼此之间的距离不太远,而且相机也不是我认为透视失真可能是可以接受的。目前唯一的对立点是相机倾斜时,但我可能会通过使用手机传感器来纠正这一点。谢谢大家!
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-27
    • 1970-01-01
    • 2014-12-23
    • 2018-12-18
    • 2015-08-06
    • 1970-01-01
    • 2021-11-18
    • 1970-01-01
    相关资源
    最近更新 更多