Обнаружение монет (и подгонки эллипсов) на изображении - PullRequest
44 голосов
/ 24 января 2011

В настоящее время я работаю над проектом, в котором я пытаюсь обнаружить несколько монет, лежащих на плоской поверхности (то есть столе). Монеты не перекрываются и не скрыты другими предметами. Но могут быть видны другие объекты, и условия освещения могут быть не идеальными ... По сути, подумайте, снимаете ли вы свой стол, на котором есть монеты.

Таким образом, каждая точка должна быть видна как эллипс. Поскольку я не знаю положение камеры, форма эллипсов может варьироваться от круга (вид сверху) до плоских эллипсов в зависимости от угла, с которого снимаются монеты.

Моя проблема в том, что я не уверен, как извлечь монеты и, наконец, наложить на них эллипсы (которые я ищу для дальнейших вычислений).

На данный момент я только что сделал первую попытку, установив пороговое значение в OpenCV, используя findContours (), чтобы получить контурные линии и подогнать эллипс. К сожалению, контурные линии редко дают мне форму монет (отражения, плохое освещение, ...), и этот способ также не является предпочтительным, поскольку я не хочу, чтобы пользователь устанавливал какие-либо пороги.

Другая идея состояла в том, чтобы использовать метод сопоставления шаблонов эллипса на этом изображении, но, поскольку я не знаю ни угла камеры, ни размера эллипсов, я не думаю, что это сработает хорошо ...

Поэтому я хотел спросить, может ли кто-нибудь сказать мне метод, который будет работать в моем случае.

Есть ли быстрый способ извлечь три монеты из изображения? Расчеты должны выполняться в режиме реального времени на мобильных устройствах, и метод не должен быть слишком чувствительным для различных или меняющихся источников света или цвета фона.

Было бы замечательно, если бы кто-нибудь мог дать мне какие-нибудь советы о том, какой метод мог бы работать для меня.

Ответы [ 3 ]

49 голосов
/ 25 января 2011

Вот некоторый источник 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)

Вероятно, ваш лучший способ отличить монеты от других объектовбудет по форме.Я не могу думать о каких-либо других низкоуровневых функциях изображения (цвет явно отсутствует).Итак, я могу придумать два подхода:

Традиционное обнаружение объектов

Ваша первая задача - отделить объекты (монеты и не монеты) от фона.Метод Оцу, предложенный Карньери, будет хорошо работать здесь.Вы, кажется, беспокоитесь о том, что изображения двудольные , но я не думаю, что это будет проблемой.До тех пор, пока видимое количество рабочих мест, вы гарантированно будете иметь один пик в гистограмме.И пока на столе есть пара визуально различимых объектов, вам гарантирован ваш второй пик.

Расширьте ваше двоичное изображение пару раз, чтобы избавиться от шума, оставленногопороговая.Монеты относительно большие, поэтому они должны пережить эту морфологическую операцию.

Сгруппируйте белые пиксели в объекты, используя увеличение области - просто итеративно соедините смежные пиксели переднего плана.В конце этой операции у вас будет список непересекающихся объектов, и вы будете знать, какие пиксели занимает каждый объект.

Из этой информации вы узнаете ширину и высоту объекта (из предыдущегошаг).Итак, теперь вы можете оценить размер эллипса, который будет окружать объект, а затем посмотреть, насколько хорошо этот конкретный объект соответствует эллипсу.Возможно, будет проще использовать соотношение ширины и высоты.

В качестве альтернативы вы можете использовать моменты , чтобы более точно определить форму объекта.

6 голосов
/ 26 мая 2014

Если кто-нибудь в будущем столкнется с этой проблемой, как я, но с использованием C ++:

Как только вы использовали findContours для нахождения контуров (как в ответе Миши выше), вы можете легко подогнать эллипсы, используя 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]);
    }
6 голосов
/ 25 января 2011

Я не знаю, как лучше всего решить вашу проблему.Что касается порогового значения, в частности, однако, вы можете использовать метод Оцу, который автоматически находит оптимальное пороговое значение на основе анализа гистограммы изображения.Используйте метод OpenCV threshold с параметром ThresholdType, равным THRESH_OTSU.

Однако имейте в виду, что метод Оцу хорошо работает только на изображениях с бимодальными гистограммами (например, изображения сяркие объекты на темном фоне).

Вы, наверное, видели это, но есть также метод для подгонки эллипса вокруг набора 2D точек (например, подключенного компонента).

РЕДАКТИРОВАНИЕ : метод Оцу, примененный к образцу изображения:

Изображение в градациях серого: grayscale image

Результат применения метода Оцу: Otsu image

...