Оглавление
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 шаг за шагом. Сравнение контуров через суммарные характеристики — моменты”»
А будет продолжение?
будет )
Очень интересный материал! Хорошо, что освещается именно математический вопрос в сравнении контуров изображений.
Хотелось бы узнать, что конкретно подразумевается под контурами здесь, это действительно набор необязательно связанных точек (которые переведены в бинарный вид с помощью детектора границ Канни, например), или замкнутая последовательность пикселей, или незамкнутые отрезки, или все попадает под определение?
Связанная последовательность пикселей бинарного изображения (см.Нахождение контуров и операции с ними — cvFindContours()).
Нужна формула для аналогичного подхода, но обладающая чувствительностью к масштабированию.
Использую всякие варианты домножения на периметр, площадь и прочие параметры контура, но они меня не устраивают, хочется более устойчивое решение.
Пробовал использовать mu вместо nu, каша получается.