33. OpenCV шаг за шагом. Сравнение контуров через суммарные характеристики — моменты



Оглавление
1. OpenCV шаг за шагом. Введение.
2. Установка.
3. Hello World.
4. Загрузка картинки.

31. Типы данных OpenCV — хранилище памяти, последовательность
32. Нахождение контуров и операции с ними
33. Сравнение контуров через суммарные характеристики — моменты

Сравнение контуров — распространённая задача, возникающая, например, при решении проблемы поиска заданного объекта на изображении (template matching)

template matching — сравнение шаблона — поиск заданного объекта на изображении.

Самый простой вариант сравнения пары контуров — это рассчитать их моменты.
Момент — это суммарная характеристика контура, рассчитанная интегрированием (суммированием) всех пикселей контура.
момент (p,q) определяется формулой:

           n
m(p,q) = Summ I(x,y) x^p y^q
          i=1

, где
p и q — порядок возведения в степень соответствующего параметра при суммировании.
n — число пикселй контура
Исходя их определения, можно сделать вывод, что
момент m00 — равен длине пикселей контура (вернее сказать — площадь, но так как мы рассматриваем не полигон, а границу, то уместно вести речь о длине)

#define cvContourMoments( contour, moments ) \
    cvMoments( contour, moments, 0 )

/* Calculates all spatial and central moments up to the 3rd order */
CVAPI(void) cvMoments( const CvArr* arr, CvMoments* moments, int binary CV_DEFAULT(0));

— рассчёт всех моментов контура

array – растровое изображение (одноканальное, 8-битное 2D-массив элеметов с плавающей точкой) или массив ( 1xN или Nx1 ) 2D-точек (Point или Point2f ).
moments – возвращаемые моменты
binary – если задан, то все ненулевые пиксели исходной картинки интерпретируются, как 1. (только для изображения)

/* Retrieve particular spatial, central or normalized central moments */
CVAPI(double)  cvGetSpatialMoment( CvMoments* moments, int x_order, int y_order );
CVAPI(double)  cvGetCentralMoment( CvMoments* moments, int x_order, int y_order );
CVAPI(double)  cvGetNormalizedCentralMoment( CvMoments* moments,
                                             int x_order, int y_order );

/* Spatial and central moments */
typedef struct CvMoments
{
    double  m00, m10, m01, m20, m11, m02, m30, m21, m12, m03; /* spatial moments */
    double  mu20, mu11, mu02, mu30, mu21, mu12, mu03; /* central moments */
    double  inv_sqrt_m00; /* m00 != 0 ? 1/sqrt(m00) : 0 */
}
CvMoments;

В версии для C++ используется класс cv::Moments():

//! raster image moments
class CV_EXPORTS_W_MAP Moments
{
public:
    //! the default constructor
    Moments();
    //! the full constructor
    Moments(double m00, double m10, double m01, double m20, double m11,
            double m02, double m30, double m21, double m12, double m03 );
    //! the conversion from CvMoments
    Moments( const CvMoments& moments );
    //! the conversion to CvMoments
    operator CvMoments() const;

    //! spatial moments
    CV_PROP_RW double  m00, m10, m01, m20, m11, m02, m30, m21, m12, m03;
    //! central moments
    CV_PROP_RW double  mu20, mu11, mu02, mu30, mu21, mu12, mu03;
    //! central normalized moments
    CV_PROP_RW double  nu20, nu11, nu02, nu30, nu21, nu12, nu03;
};

//! computes moments of the rasterized shape or a vector of points
CV_EXPORTS_W Moments moments( InputArray array, bool binaryImage=false );

typedef const _InputArray& InputArray;

/*!
 Proxy datatype for passing Mat's and vector<>'s as input parameters
 */
class CV_EXPORTS _InputArray

Т.о., сравнение двух контуров можно свести к сравнению их моментов.

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

Поэтому, лучше использовать нормализованные инвариантные моменты.

CVAPI(double)  cvGetCentralMoment( CvMoments* moments, int x_order, int y_order );

— вычисление центральных моментов (central moments):

           n
mu(p,q) = Summ I(x,y) (x-Xc)^p (y-Yc)^q
          i=1

, где Xc, Yc — центр масс:

     m10        m01
Xc = --- , Yc = ---
     m00        m00
CVAPI(double)  cvGetNormalizedCentralMoment( CvMoments* moments,
                                             int x_order, int y_order );

— вычисление нормализованных центральных моментов (normalized central moments):

                mu(p, q)
nu(p, q) = ------------------
               m00 ^ ((p + q)/2+1)
/* Calculates 7 Hu's invariants from precalculated spatial and central moments */
CVAPI(void) cvGetHuMoments( CvMoments*  moments, CvHuMoments*  hu_moments );

— вычисления Hu invariant moments
— это линейная комбинация центральных моментов (идея состоит в том, что комбинируя различные нормализованные центральные моменты возможно создать инвариантное представление контуров, не зависящее от масшаба, вращения и отражения (h1) ):

hu[0] = nu20 + nu02
hu[1] = (nu20 - nu02)^2 + 4nu11^2
hu[2] = (nu30 - 3nu12)^2 + (3*nu21 - nu03)^2
hu[3] = (nu30 + nu12)^2 + (nu21 + nu03)^2
hu[4] = (nu30 - 3*nu12)*(nu30 + nu12)[(nu30 + nu12)^2 - 3*(nu21 + nu03)^2] + (3*nu21 - nu03)(nu21 + nu03)[3*(nu30 + nu12)^2 - (nu21 + nu03)^2]
hu[5] = (nu20 - nu02)[(nu30 + nu12)^2 - (nu21 + nu03)2] + 4*nu11*(nu30 + nu12)(nu21 + nu03)
hu[6] = (3*nu21 - nu03)(nu21 + nu03)[3*(nu30 + nu12)^2 - (nu21 + nu03)^2] - (nu30 - 3*nu12)(nu21 + nu03)[3*(nu30 + nu12)^2 - (nu21 + nu03)^2]

Используя Hu моменты, можно попробовать сравнить два объекта

//! matches two contours using one of the available algorithms
CV_EXPORTS_W double matchShapes( InputArray contour1, InputArray contour2,
                                 int method, double parameter );

/* Compares two contours by matching their moments */
CVAPI(double)  cvMatchShapes( const void* object1, const void* object2,
                              int method, double parameter CV_DEFAULT(0));

#define cvMatchContours     cvMatchShapes

— сравнение двух контуров по их моментам (Hu)

object1 — первый контур или изображение (градации серого)
object2 — второй контур или изображение (градации серого)
method — метод сравнения:

/* Shape matching methods */
enum
{
    CV_CONTOURS_MATCH_I1  =1,
    CV_CONTOURS_MATCH_I2  =2,
    CV_CONTOURS_MATCH_I3  =3
};

parameter – параметр для метода сравнения (пока не используется)

методы сранения:

//
// пример использование cvMatchShapes()
// сравнение объектов по моментам их контуров 
//
// https://robocraft.ru
//

#include <cv.h>
#include <highgui.h>
#include <stdlib.h>
#include <stdio.h>

// сравнение объектов по моментам их контуров 
void testMatch(IplImage* original, IplImage* templ)
{
    assert(original!=0);
    assert(templ!=0);

    printf("[i] test cvMatchShapes()\n");

    IplImage *src=0, *dst=0;

    src=cvCloneImage(original);

    IplImage* binI = cvCreateImage( cvGetSize(src), 8, 1);
    IplImage* binT = cvCreateImage( cvGetSize(templ), 8, 1);

    // заведём цветные картинки
    IplImage* rgb = cvCreateImage(cvGetSize(original), 8, 3);
    cvConvertImage(src, rgb, CV_GRAY2BGR);
    IplImage* rgbT = cvCreateImage(cvGetSize(templ), 8, 3);
    cvConvertImage(templ, rgbT, CV_GRAY2BGR);

    // получаем границы изображения и шаблона
    cvCanny(src, binI, 50, 200);
    cvCanny(templ, binT, 50, 200);

    // показываем
    cvNamedWindow( "cannyI", 1 );
    cvShowImage( "cannyI", binI);

    cvNamedWindow( "cannyT", 1 );
    cvShowImage( "cannyT", binT);

    // для хранения контуров
    CvMemStorage* storage = cvCreateMemStorage(0);
    CvSeq* contoursI=0, *contoursT=0;

    // находим контуры изображения
    int contoursCont = cvFindContours( binI, storage, &contoursI, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));

    // для отметки контуров
    CvFont font;
    cvInitFont(&font, CV_FONT_HERSHEY_PLAIN, 1.0, 1.0);
    char buf[128];
    int counter=0;

    // нарисуем контуры изображения
    if(contoursI!=0){
        for(CvSeq* seq0 = contoursI;seq0!=0;seq0 = seq0->h_next){
            // рисуем контур
            cvDrawContours(rgb, seq0, CV_RGB(255,216,0), CV_RGB(0,0,250), 0, 1, 8); 
            // выводим его номер
            //CvPoint2D32f point; float rad;
            //cvMinEnclosingCircle(seq0,&point,&rad); // получим окружность содержащую контур
            //cvPutText(rgb, itoa(++counter, buf, 10), cvPointFrom32f(point), &font, CV_RGB(0,255,0));
        }
    }
    // показываем
    cvNamedWindow( "cont", 1 );
    cvShowImage( "cont", rgb );

    cvConvertImage(src, rgb, CV_GRAY2BGR);

    // находим контуры шаблона
    cvFindContours( binT, storage, &contoursT, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));

    CvSeq* seqT=0;
    double perimT = 0;

    if(contoursT!=0){
        // находим самый длинный контур 
        for(CvSeq* seq0 = contoursT;seq0!=0;seq0 = seq0->h_next){
            double perim = cvContourPerimeter(seq0);
            if(perim>perimT){
                perimT = perim;
                seqT = seq0;
            }
            // рисуем
            cvDrawContours(rgbT, seq0, CV_RGB(255,216,0), CV_RGB(0,0,250), 0, 1, 8); // рисуем контур
        }
    }
    // покажем контур шаблона
    cvDrawContours(rgbT, seqT, CV_RGB(52,201,36), CV_RGB(36,201,197), 0, 2, 8); // рисуем контур
    cvNamedWindow( "contT", 1 );
    cvShowImage( "contT", rgbT );


    CvSeq* seqM=0;
    double matchM=1000;
    // обходим контуры изображения 
    counter=0;
    if(contoursI!=0){
        // поиск лучшего совпадения контуров по их моментам 
        for(CvSeq* seq0 = contoursI;seq0!=0;seq0 = seq0->h_next){
            double match0 = cvMatchShapes(seq0, seqT, CV_CONTOURS_MATCH_I3);
            if(match0<matchM){
                matchM = match0;
                seqM = seq0;
            }
            printf("[i] %d match: %.2f\n", ++counter, match0);
        }
    }
    // рисуем найденный контур
    cvDrawContours(rgb, seqM, CV_RGB(52,201,36), CV_RGB(36,201,197), 0, 2, 8); // рисуем контур

    cvNamedWindow( "find", 1 );
    cvShowImage( "find", rgb );

    // ждём нажатия клавиши
    cvWaitKey(0);

    // освобождаем ресурсы
    cvReleaseMemStorage(&storage);

    cvReleaseImage(&src);
    cvReleaseImage(&dst);
    cvReleaseImage(&rgb);
    cvReleaseImage(&rgbT);
    cvReleaseImage(&binI);
    cvReleaseImage(&binT);

    // удаляем окна
    cvDestroyAllWindows();
}

int main(int argc, char* argv[])
{
    IplImage *original=0, *templ=0;

    // имя картинки задаётся первым параметром
    char* filename = argc >= 2 ? argv[1] : "Image0.jpg";
    // получаем картинку
    original = cvLoadImage(filename, 0);

    printf("[i] image: %s\n", filename);
    assert( original != 0 );

    // имя шаблона задаётся вторым параметром
    char* filename2 = argc >= 3 ? argv[2] : "templ.bmp";
    // получаем картинку
    templ = cvLoadImage(filename2, 0);

    printf("[i] template: %s\n", filename2);
    assert( templ != 0 );

    // покажем изображения
    cvNamedWindow( "original", 1 );
    cvShowImage( "original", original );
    cvNamedWindow( "template", 1 );
    cvShowImage( "template", templ );

    // сравнение
    testMatch(original, templ);

    // освобождаем ресурсы
    cvReleaseImage(&original);
    cvReleaseImage(&templ);
    // удаляем окна
    cvDestroyAllWindows();
    return 0;
}

скачать иcходник (33-cvMatchShapes.cpp)

далее: Детектирование объектов — поиск объекта по шаблону (Template matching)

Ссылки
http://en.wikipedia.org/wiki/Template_matching
http://en.wikipedia.org/wiki/Image_moment
Edge Based Template Matching


0 комментариев на «“33. OpenCV шаг за шагом. Сравнение контуров через суммарные характеристики — моменты”»

  1. Очень интересный материал! Хорошо, что освещается именно математический вопрос в сравнении контуров изображений.

    Хотелось бы узнать, что конкретно подразумевается под контурами здесь, это действительно набор необязательно связанных точек (которые переведены в бинарный вид с помощью детектора границ Канни, например), или замкнутая последовательность пикселей, или незамкнутые отрезки, или все попадает под определение?

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

Добавить комментарий

Arduino

Что такое Arduino?
Зачем мне Arduino?
Начало работы с Arduino
Для начинающих ардуинщиков
Радиодетали (точка входа для начинающих ардуинщиков)
Первые шаги с Arduino

Разделы

  1. Преимуществ нет, за исключением читабельности: тип bool обычно имеет размер 1 байт, как и uint8_t. Думаю, компилятор в обоих случаях…

  2. Добрый день! Я недавно начал изучать программирование под STM32 и ваши уроки просто бесценны! Хотел узнать зачем использовать переменную типа…

3D-печать AI Arduino Bluetooth CraftDuino DIY Google IDE iRobot Kinect LEGO OpenCV Open Source Python Raspberry Pi RoboCraft ROS swarm ИК автоматизация андроид балансировать бионика версия видео военный датчик дрон интерфейс камера кибервесна манипулятор машинное обучение наше нейронная сеть подводный пылесос работа распознавание робот робототехника светодиод сервомашинка собака управление ходить шаг за шагом шаговый двигатель шилд юмор

OpenCV
Робототехника
Будущее за бионическими роботами?
Нейронная сеть - введение