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()
// сравнение объектов по моментам их контуров 
//
// http://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)

Ссылки:
en.wikipedia.org/wiki/Template_matching
en.wikipedia.org/wiki/Image_moment
Edge Based Template Matching
  • +1
  • 7 апреля 2012, 07:33
  • noonv

Комментарии (6)

RSS свернуть / развернуть
+
0
А будет продолжение?
avatar

Astrgan

  • 24 апреля 2013, 19:09
+
0
будет )
avatar

noonv

  • 24 апреля 2013, 21:45
+
0
Очень интересный материал! Хорошо, что освещается именно математический вопрос в сравнении контуров изображений.

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

grafity3

  • 4 августа 2015, 14:06
+
0
Связанная последовательность пикселей бинарного изображения (см. Нахождение контуров и операции с нимиcvFindContours()).
avatar

noonv

  • 4 августа 2015, 14:14
+
0
Нужна формула для аналогичного подхода, но обладающая чувствительностью к масштабированию.
Использую всякие варианты домножения на периметр, площадь и прочие параметры контура, но они меня не устраивают, хочется более устойчивое решение.
avatar

ursoft2004

  • 8 декабря 2015, 13:37
+
0
Пробовал использовать mu вместо nu, каша получается.
avatar

ursoft2004

  • 8 декабря 2015, 13:38

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.