
Оглавление
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, каша получается.