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



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

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

Детектирование объекта по шаблону может пригодиться во множестве случаев. Самый простой пример — поиск заранее заданного объекта.
В OpenCV для этого есть замечательная функция cvMatchTemplate()

C++: void matchTemplate(InputArray image, InputArray temp, OutputArray result, int method)
Python: cv2.matchTemplate(image, templ, method[, result ]) !result
C: void cvMatchTemplate(const CvArr* image, const CvArr* templ, CvArr* result, int method)
CVAPI(void)  cvMatchTemplate( const CvArr* image, const CvArr* templ,
                              CvArr* result, int method );

— сравнение шаблона и перекрывающего окна на исходном изображении
image — изображение для поиска (8-битное или 32F)
templ — шаблон для поиска (не должен превышать исходное изображение и иметь тот же тип)
result — карта результата сравнения (32FС1) если image WxH и templ wxh, то result = (W-w+1)x(H-h+1)
method — метод сравнения областей изображения:

/* Template matching methods */
enum
{
    CV_TM_SQDIFF        =0,
    CV_TM_SQDIFF_NORMED =1,
    CV_TM_CCORR         =2,
    CV_TM_CCORR_NORMED  =3,
    CV_TM_CCOEFF        =4,
    CV_TM_CCOEFF_NORMED =5
};

( раньше были дефайны )

#define  CV_TM_SQDIFF        0
#define  CV_TM_SQDIFF_NORMED 1
#define  CV_TM_CCORR         2
#define  CV_TM_CCORR_NORMED  3
#define  CV_TM_CCOEFF        4
#define  CV_TM_CCOEFF_NORMED 5

Функцию поиска можно приблизительно представить следующим образом:
изображение шаблона templ последовательно накладывается на исходное изображение image и между ними вычисляется корреляция, результат которой заносится в результирующее изображение result.

Очевидно, что корреляцию между двумя изображениями можно считать разными способами.
Эти методы собраны выше в перечислении и просто меняют формулу рассчёта корреляции:

Посмотрим результаты работы поиска шаблона с разными методами.
Для этого используем следующий код:

//
// Несколько модифицированный пример Example 7-5. Сравнение шаблона
//
// использование: prog шаблон изображение
//
// из книги:
//   Learning OpenCV: Computer Vision with the OpenCV Library
//     by Gary Bradski and Adrian Kaehler
//     Published by O'Reilly Media, October 3, 2008

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

int main( int argc, char* argv[] )
{
	IplImage *src, *templ,*ftmp[6]; //ftmp is what to display on
	int i;

	char* filename = argc >= 2 ? argv[1] : "Image0.jpg";
	char* filename0 = argc >= 3 ? argv[2] : "templ.bmp";


	// загрузка изображения
	if((src=cvLoadImage(filename, 1))== 0) {
		printf("Error on reading src image %s\n",argv[1]);
		return(-1);
	}

	// загрузка шаблона
	if((templ=cvLoadImage(filename0, 1))== 0) {
		printf("Error on reading template %s\n",argv[2]);
		return(-1);
	}

	int patchx = templ->width;
	int patchy = templ->height;
	int iwidth = src->width - patchx + 1;
	int iheight = src->height - patchy + 1;
	for(i=0; i<6; ++i){
		ftmp[i] = cvCreateImage( cvSize(iwidth,iheight),32,1);
	}

	// сравнение шаблона с изображением
	for(i=0; i<6; ++i){
		cvMatchTemplate( src, templ, ftmp[i], i);
		//		double min,max;
		//		cvMinMaxLoc(ftmp,&min,&max);
		cvNormalize(ftmp[i],ftmp[i],1,0,CV_MINMAX);
	}
	// показываем
	cvNamedWindow( "Template");
	cvShowImage(   "Template", templ );
	cvNamedWindow( "Image");
	cvShowImage(   "Image", src );

	cvNamedWindow( "SQDIFF" );
	cvShowImage( "SQDIFF", ftmp[0] );

	cvNamedWindow( "SQDIFF_NORMED" );
	cvShowImage(   "SQDIFF_NORMED", ftmp[1] );

	cvNamedWindow( "CCORR" );
	cvShowImage(   "CCORR", ftmp[2] );

	cvNamedWindow( "CCORR_NORMED" );
	cvShowImage(   "CCORR_NORMED", ftmp[3] );

	cvNamedWindow( "CCOEFF" );
	cvShowImage(   "CCOEFF", ftmp[4] );

	cvNamedWindow( "CCOEFF_NORMED" );
	cvShowImage(   "CCOEFF_NORMED", ftmp[5] );

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

cvMatchTemplate.cpp

В качестве исходного изображения возьмём изображение Чеширского Кота, а в качестве шаблона - скопированный из этой же картинки глаз:

Результат:

Следует обратить внимание, что для приведения результата корреляции в "видимый" формат (ведь некоторые методы могут возвращать отрицательные значения) используется нормировка изображения при помощи функции cvNormalize():

C++: double norm(InputArray src1, int normType=NORM_L2, InputArray mask=noArray())
C++: double norm(InputArray src1, InputArray src2, int normType, InputArray mask=noArray())
C++: double norm(const SparseMat& src, int normType)
Python: cv2.norm(src1[, normType[, mask ]]) !retval
Python: cv2.norm(src1, src2[, normType[, mask ]]) !retval
C: double cvNorm(const CvArr* arr1, const CvArr* arr2=NULL, int normType=CV_L2, const CvArr*
mask=NULL)
CVAPI(double)  cvNorm( const CvArr* arr1, const CvArr* arr2 CV_DEFAULT(NULL),
                       int norm_type CV_DEFAULT(CV_L2),
                       const CvArr* mask CV_DEFAULT(NULL) );

CVAPI(void)  cvNormalize( const CvArr* src, CvArr* dst,
                          double a CV_DEFAULT(1.), double b CV_DEFAULT(0.),
                          int norm_type CV_DEFAULT(CV_L2),
                          const CvArr* mask CV_DEFAULT(NULL) );

- нормировка массива

arr1 - первое исходное изображение
arr2 - второе исходное изображение (если NULL, то абсолютная нормировка arr1, иначе абсолютная или относительная arr1-arr2)
norm_type - тип нормировки:

#define CV_C            1
#define CV_L1           2
#define CV_L2           4
#define CV_NORM_MASK    7
#define CV_RELATIVE     8
#define CV_DIFF         16
#define CV_MINMAX       32

#define CV_DIFF_C       (CV_DIFF | CV_C)
#define CV_DIFF_L1      (CV_DIFF | CV_L1)
#define CV_DIFF_L2      (CV_DIFF | CV_L2)
#define CV_RELATIVE_C   (CV_RELATIVE | CV_C)
#define CV_RELATIVE_L1  (CV_RELATIVE | CV_L1)
#define CV_RELATIVE_L2  (CV_RELATIVE | CV_L2)

mask - маска

Без нормализации мы бы увидели следующее:

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

Для этого, очень удобно использовать OpenCV-ную функцию cvMinMaxLoc():

C++: void minMaxLoc(InputArray src, double* minVal, double* maxVal=0, Point* minLoc=0, Point*
maxLoc=0, InputArray mask=noArray())
C++: void minMaxLoc(const SparseMat& src, double* minVal, double* maxVal, int* minIdx=0, int*
maxIdx=0)
Python: cv2.minMaxLoc(src[, mask ]) !minVal, maxVal, minLoc, maxLoc
C: void cvMinMaxLoc(const CvArr* arr, double* minVal, double* maxVal, CvPoint* minLoc=NULL, Cv-
Point* maxLoc=NULL, const CvArr* mask=NULL)
CVAPI(void)  cvMinMaxLoc( const CvArr* arr, double* min_val, double* max_val, CvPoint* min_loc CV_DEFAULT(NULL), CvPoint* max_loc CV_DEFAULT(NULL), const CvArr* mask CV_DEFAULT(NULL) );

- определяет минимальное и максимальное значения массива, а так же их местоположение
arr - массив(изображение) для поиска (одноканальный или многоканальный с установленным COI)
min_val - указатель на переменную для сохранения минимального значения
max_val - указатель на переменную для сохранения максимального значения
min_loc - указатель на точку местоположения минимума
max_loc - указатель на точку местоположения максимума
mask - маска для выбора подмассива

Пример как можно определять местоположение шаблона - поиск максимума-минимума с помощью cvMinMaxLoc():

//
// пример cvMatchTemplate()
// сравнение изображение с шаблоном
//

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

IplImage* image = 0;
IplImage* templ = 0;

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

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

	// шаблон
	char* filename2 = argc >= 3 ? argv[2] : "eye.jpg";
	printf("[i] template: %s\n", filename2);

	templ = cvLoadImage(filename2,1);
	assert( templ != 0 );

	cvNamedWindow("origianl", CV_WINDOW_AUTOSIZE);
	cvNamedWindow("template", CV_WINDOW_AUTOSIZE);
	cvNamedWindow("Match", CV_WINDOW_AUTOSIZE);
	cvNamedWindow("res", CV_WINDOW_AUTOSIZE);

	// размер шаблона
	int width = templ->width;
	int height = templ->height;

	// оригинал и шаблон
	cvShowImage( "origianl", image);
	cvShowImage( "template", templ);

	// изображение для хранения результата сравнения
	// размер результата: если image WxH и templ wxh, то result = (W-w+1)x(H-h+1)
	IplImage *res = cvCreateImage( cvSize( (image->width-templ->width+1), (image->height-templ->height+1)), IPL_DEPTH_32F, 1 );

	// сравнение изображения с шаблоном
	cvMatchTemplate(image, templ, res, CV_TM_SQDIFF);

	// покажем что получили
	cvShowImage( "res", res);

	// определение лучшее положение для сравнения
	// (поиск минимумов и максимумов на изображении)
	double    minval, maxval;
	CvPoint    minloc, maxloc;
	cvMinMaxLoc(res, &minval, &maxval, &minloc, &maxloc, 0);

	// нормализуем
	cvNormalize(res,res,1,0,CV_MINMAX);
	cvNamedWindow("res norm", CV_WINDOW_AUTOSIZE);
	cvShowImage( "res norm", res);

	// выделим область прямоугольником
	cvRectangle(image, cvPoint(minloc.x, minloc.y), cvPoint(minloc.x+templ->width-1, minloc.y+templ->height-1), CV_RGB(255, 0, 0), 1, 8);

	// показываем изображение
	cvShowImage("Match", image);

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

	// освобождаем ресурсы
	cvReleaseImage( &image );
	cvReleaseImage( &templ );
	cvReleaseImage( &res );
	cvDestroyAllWindows();
	return 0;
}

cvMatchTemplate_2.cpp

Результат:

Обратите внимание, что т.к. размер результирующей картинки по ширине и высоте меньше исходного изображения на соответствующий размер шаблона +1 ( result = (W-w+1)x(H-h+1) ), то для выделения объекта на исходном изображении нужно, соответственно, прибавить эти величины.

Ссылки
http://en.wikipedia.org/wiki/Template_matching


0 комментариев на «“Детектирование объектов — поиск объекта по шаблону (Template matching)”»

  1. Спасибо за статью.
    Такой вопрос, искомое изображение на картинке и оно же в виде шаблона в пропорциях 1 к 1?
    Или всё-таки шаблон глаза больше/меньше, чем глаз на целой картинке?

  2. Очень интересно. Мне вообще нравится ваша серия статей OpenCV, легко читается.

    Можно ли проводить сравнение, если изображения повернуты на угол? Для саможельного расстановочного PnP станка.
    Нужно сравнить заранее отснятую фотографию микросхемы/smd детальки и определить угол/ориентацию (0,90,180 градусов) текущей детали. Съемка одной камерой, масштаб постоянный 1 к 1, яркость одна. Подскажите пожалуйста какие функции и алгоритмы можно использовать для сравнения повернутых изображений.
    Другое решение ( как вижу):
    — выравнивание угла до 0,90,180 положения (детали прямоугольные) -> угол на который нужно довернуть
    — сравнение перебором с отснятыми картинками, для выяснения что за деталька и где у нее центр -> центр и 1я ножка детальки
    Цель, программа запускаемая с командной строки с параметрами, результат выводится в echo.
    Сейчас на самодельных станках все делается вслепую. Распознавания нет.
    Возможно есть готовые утилиты сравнения изображений, но я ничего подобного не нашел. Да и не поэкспериментируешь…

    • Спасибо!
      Думаю, для расстановочного станка лучше применять классические алгоритмы технического зрения (для надёжности лучше организовать для камеры постоянную подсветку). Т.е. искать контур, по геометрическим параметрам находить деталь и определять её ориентацию.
      Если возникнут проблемы — пишите на форум, разберёмся 🙂

    • Фотографирование в красном свете дает картинку с четкими контурами выводов. Если искать по геометрии, придется для программы описывать каждый элемент. Что несколько сложнее, чем подготовленная фотография. А вообще практика покажет.

    • в википедии есть псевдокод, но обратите внимание, что тестировать его следует на очень маленьких картинках 😉
      для ускорения процесса, обычно, корреляция считается через преобразование Фурье.

  3. Доброго времени суток!
    Спасибо за вашу статью, очень познавательно.

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

    • Если повторение на объекте, а не на сфотографированном «полигоне» в виде одной его стороны, то не обойтись без поиска точки схождения линий перспективы. От этой точки нужно отталкиваться, делая преобразования с тем чтобы на четверти и на 3/4 картинки изображение смещалось на 1, 2, 3 и так далее пикселей. Все полученные изображения нужно поочереди вычесть из оригинала, затем сложить полученные разности. Затем сделать суммирование x_i_j = x_i_j + x_(i-1)_j + x_i_(j-1), нужно либо сначала проделать операцию с первым столбцом, затем последовательно пройти строки начиная с верхней, либо верхнюю строку, затем столбцы начиная с левого. Потом искать значения меньше пороговых для разности между пикселями, которые перебираются по остаткам левых диагоналей, в переборе по всем значениям у координаты, в переборе по всем левым диагоналям. Самая длинная цепочка пикселей даст самый большой экземпляр повторяющегося объекта. Его берём в качестве шаблона и ищем этим алгоритмом по всей картинке (не забываем уменьшать сэмпл сообразно перспективе, помним про точку схождения). И всё. Если сфотографировать дом так что будут видны две стены с окнами, то шаблоном станет самое ближайшее окно на стене с бОльшим числом окон, а мэтч выявит все окна на этой стене, но плохо будет видеть окна на второй стене (если вообще заметит). У алгоритма есть очевидные оптимизации, но первый раз лучше делать поэтапно и с размахом.
      Можно выводить промежуточные изображения и подумать над другими признаками определения доминируещего шаблона. Это уже под конкретную задачу распознавания.

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

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
Робототехника
Будущее за бионическими роботами?
Нейронная сеть - введение