32. OpenCV шаг за шагом. Нахождение контуров и операции с ними



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

29. Интегральное изображение
30. Трансформация изображения — аффинные преобразования, гомография
31. Типы данных OpenCV — хранилище памяти, последовательность
32. Нахождение контуров и операции с ними

Контурный анализ — это один из важных и очень полезных методов описания, хранения, распознавания, сравнения и поиска графических образов/объектов.

Контур — это внешние очертания (обвод) предмета/объекта.

При проведении контурного анализа:
* полагается, что контур содержит достаточную информацию о форме объекта;
* внутренние точки объекта во внимание не принимаются.

Вышеприведённые положения, разумеется, накладывают существенные ограничения на область применения контурного анализа, которые, в основном, связаны с проблемами выделения контура на изображениях:
* из-за одинаковой яркости с фоном объект может не иметь чёткой границы, или может быть зашумлён помехами, что приводит к невозможности выделения контура;
* перекрытие объектов или их группировка приводит к тому, что контур выделяется неправильно и не соответствует границе объекта.

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

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

Итак, мы определились, что контур — это некая граница объекта, которая отделяет его от фона (других объектов). Вспомним, как мы можем получить контуры?

Разумеется, первым вспоминается детектор границ Кенни, а затем можно привести любые другие методы получения двоичного изображения:
пороговое преобразование,
выделение объекта по цвету

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

Чтобы оперировать полученным контуром, его необходимо как-то представить (закодировать).
Например, указывать вершины отрезков, составляющих контур.
Другой известный способ кодирования контура – это цепной код Фримена.

Цепной код Фримена (Фридмана) (Freeman Chain Code)

Цепные коды применяются для представления границы в виде последовательности отрезков прямых линий определённой длины и направления. В основе этого представления лежит 4- или 8- связная решётка. Длина каждого отрезка определяется разрешением решётки, а направления задаются выбранным кодом.
(для представления всех направлений в 4-связной решётке достаточно 2-х бит, а для 8-связной решётки цепного кода требуется 3 бита)
цепной код Фримена

Библиотека OpenCV реализует удобные методы для детектирования и манипуляции с контурами изображения.

Для поиска контуров используется функция cvFindContours():

CVAPI(int)  cvFindContours( CvArr* image, CvMemStorage* storage, CvSeq** first_contour,
                            int header_size CV_DEFAULT(sizeof(CvContour)),
                            int mode CV_DEFAULT(CV_RETR_LIST),
                            int method CV_DEFAULT(CV_CHAIN_APPROX_SIMPLE),
                            CvPoint offset CV_DEFAULT(cvPoint(0,0)));

— нахождение контуров на двоичном изображении

image — исходное 8-битное одноканальное изображение (ненулевые пиксели обрабатываются как 1, а нулевые — 0)
Для получения такого изображения из градаций серого можно, например, использовать функции cvThreshold() или cvCanny()
storage — хранилище памяти для хранения данных найденных контуров
first_contour — указатель, который будет указывать на первый элемент последовательности, содержащей данные найденных контуров
header_size — размер заголовка элемента последовательности
mode — режим поиска:

#define CV_RETR_EXTERNAL 0 // найти только крайние внешние контуры
#define CV_RETR_LIST     1 // найти все контуры и разместить их списком
#define CV_RETR_CCOMP    2 // найти все контуры и разместить их в виде 2-уровневой иерархии
#define CV_RETR_TREE     3 // найти все контуры и разместить их в иерархии вложенных контуров

method — метод аппроксимации:

#define CV_CHAIN_CODE               0 // цепной код Фридмана
#define CV_CHAIN_APPROX_NONE        1 // все точки цепного кода переводятся в точки
#define CV_CHAIN_APPROX_SIMPLE      2 // сжимает горизонтальные, вертикальные и диагональные сегменты и оставляет только их конечные точки
#define CV_CHAIN_APPROX_TC89_L1     3 // применяется алгоритм
#define CV_CHAIN_APPROX_TC89_KCOS   4 // аппроксимации Teh-Chin
#define CV_LINK_RUNS                5 // алгоритм только для CV_RETR_LIST

offset — смещение, на которое сдвигать точки контура (полезно, если контуры извлекаются из ROI и затем должны анализироваться в контексте целого изображения)

обратите внимание, что функция cvFindContours() может находить внешние и вложенные контуры и определять их иерархию вложения.

а отобразить найденные контуры можно с помощью функции cvDrawContours():

CVAPI(void)  cvDrawContours( CvArr *img, CvSeq* contour,
                             CvScalar external_color, CvScalar hole_color,
                             int max_level, int thickness CV_DEFAULT(1),
                             int line_type CV_DEFAULT(8),
                             CvPoint offset CV_DEFAULT(cvPoint(0,0)));

— нарисовать заданные контуры

img — изображение на котором будут нарисованы контуры
contour — указатель на первый контур
external_color — цвет внешних контуров
hole_color — цвет внутренних контуров(отверстие)
max_level — максимальный уровень для отображения контуров (0 — только данный контур, 1 — данный и все следующие на данном уровне, 2 — все следующие контуры и все контуры на следующем уровне и т.д. ) Если величина отрицательная, то будут нарисованы контуры на предыдущем уровне перед contour.
thickness — толщина линии для отображения контуров (если величина отрицательная, то область, ограниченная контуром заливается выбранным цветом )
line_type — тип линии

//
// пример нахождения контуров с помощью функции cvFindContours()
//
// https://robocraft.ru
//

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

IplImage* image = 0;
IplImage* gray = 0;
IplImage* bin = 0;
IplImage* dst = 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 );

	// создаём одноканальные картинки
	gray = cvCreateImage( cvGetSize(image), IPL_DEPTH_8U, 1 );
	bin = cvCreateImage( cvGetSize(image), IPL_DEPTH_8U, 1 );
	// клонируем
	dst = cvCloneImage(image);
	// окно для отображения картинки
	cvNamedWindow("original",CV_WINDOW_AUTOSIZE);
	cvNamedWindow("binary",CV_WINDOW_AUTOSIZE);
	cvNamedWindow("contours",CV_WINDOW_AUTOSIZE);

	// преобразуем в градации серого
	cvCvtColor(image, gray, CV_RGB2GRAY);

	// преобразуем в двоичное
	cvInRangeS(gray, cvScalar(40), cvScalar(150), bin); // atoi(argv[2])

	CvMemStorage* storage = cvCreateMemStorage(0);
	CvSeq* contours=0;

	// находим контуры
	int contoursCont = cvFindContours( bin, storage,&contours,sizeof(CvContour),CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE,cvPoint(0,0));

	// нарисуем контуры
	for(CvSeq* seq0 = contours;seq0!=0;seq0 = seq0->h_next){
		cvDrawContours(dst, seq0, CV_RGB(255,216,0), CV_RGB(0,0,250), 0, 1, 8); // рисуем контур
	}

	// показываем картинки
	cvShowImage("original",image);
	cvShowImage("binary", bin);
	cvShowImage("contours", dst);

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

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

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

Если задать параметром функции метод CV_CHAIN_CODE, то cvFindContours() вернёт цепной код Фримана, работать с которым можно через методы:

CVAPI(void) cvStartReadChainPoints( CvChain* chain, CvChainPtReader* reader );

— инициализация считывателя точек

CVAPI(CvPoint) cvReadChainPoint( CvChainPtReader* reader );

— считывает следующую точку цепного кода

Обычная последовательность действий при распознавании объектов методом контурного анализа:
1. предварительная обработка изображения (сглаживание, фильтрация помех, увеличение контраста);
2. бинаризация изображения;
3. выделение контуров объектов;
4. первичная фильтрация контуров (по периметру, площади и т.п.);
5. эквализация контуров (приведение к единой длине, сглаживание) — позволяет добиться инвариантности к масштабу;
6. перебор всех найденных контуров и поиск шаблона, максимально похожего на данный контур (или же сортировка контуров по каком-либо признаку, например, площади).

Свойства контуров

OpenCV предоставляет функции для определения таких полезных свойств найденных контуров, как площадь и длина(периметр).

CVAPI(double)  cvContourArea( const CvArr* contour,
                              CvSlice slice CV_DEFAULT(CV_WHOLE_SEQ));

— возвращает площадь контура
contour — контур (последовательность или массив вершин)
slice — начальная и конечные точки контура (по-умолчанию весь контур)

ориентация контура влияет на знак, т.о. функция может вернуть отрицательную величину.
Можно использовать fabs() чтобы получить абсолютное значение площади.

CVAPI(double)  cvArcLength( const void* curve,
                            CvSlice slice CV_DEFAULT(CV_WHOLE_SEQ),
                            int is_closed CV_DEFAULT(-1));
#define cvContourPerimeter( contour ) cvArcLength( contour, CV_WHOLE_SEQ, 1 )

— возвращает периметр контура или длину кривой (части контура)
curve — последовательность или массив точек кривой (контур)
slice — начальная и конечные точки контура (по-умолчанию весь контур)
is_closed — определяет закрыта кривая или нет:
is_closed = 0 — кривая полагается открытой
is_closed > 0 — кривая полагается закрытой
is_closed < 0 — если кривая — последовательность, флаг CV_SEQ_FLAG_CLOSED из ((CvSeq*)curve)->flags проверяется для определения закрыта кривая или нет, в противном случае (кривая представлена массивом (CvMat*) точек) она полагается открытой.

функция считает длину кривой, как сумму длин сегментов между последовательностью точек.

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

PI*R^2 / (2*PI*R)*(2*PI*R) = 1/4*PI ~ 0.079577

Отлично! Теперь используя значение отношения площади контура к квадрату длины контура и сравнивая его с заданным значением 1/4*PI можно находить окружности!

Фактически — площадь — это количество пикселей области, а периметр — количество пикселей на границе области.
Отношение квадрата периметра к площади называется компактность.
Наиболее компактная фигура — это круг: 4*PI

//
// поиск кругов на изображении
// по отношению площади контура к квадрату его длины
//
//
// https://robocraft.ru
//

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

//  находит и показывает круги на изображении
void findCircles(IplImage* _image)
{
	assert(_image!=0);

	IplImage* bin = cvCreateImage( cvGetSize(_image), IPL_DEPTH_8U, 1);

	// конвертируем в градации серого
	cvConvertImage(_image, bin, CV_BGR2GRAY);
	// находим границы
	cvCanny(bin, bin, 50, 200);

	cvNamedWindow( "bin", 1 );
	cvShowImage("bin", bin);

	// хранилище памяти для контуров
	CvMemStorage* storage = cvCreateMemStorage(0);
	CvSeq* contours=0;

	// находим контуры
	int contoursCont = cvFindContours( bin, storage,&contours,sizeof(CvContour),CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE,cvPoint(0,0));

	assert(contours!=0);

	// обходим все контуры
	for( CvSeq* current = contours; current != NULL; current = current->h_next ){
		// вычисляем площадь и периметр контура
		double area = fabs(cvContourArea(current));
		double perim = cvContourPerimeter(current);

		// 1/4*CV_PI = 0,079577
		if ( area / (perim * perim) > 0.07 && area / (perim * perim)< 0.087 ){ // в 10% интервале
			// нарисуем контур
			cvDrawContours(_image, current, cvScalar(0, 0, 255), cvScalar(0, 255, 0), -1, 1, 8);
		}
	}

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

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

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

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

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

	dst = cvCloneImage(src);

	// находим круги на изображении
	findCircles(dst);

	cvNamedWindow( "circles", 1 );
	cvShowImage( "circles", dst);

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

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

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

Чтобы снизить ложные срабатывания можно ввести нижнее/верхнее ограничения на площадь круга.

Дополнительные полезные функции:

CVAPI(CvRect)  cvBoundingRect( CvArr* points, int update CV_DEFAULT(0) );

— возвращает прямоугольник, которым можно обвести контур
points — набор 2D-точек — последовательность или вектор (CvMat) точек
update — флаг обновления:
0 (CvContour) — прямоугольник не рассчитывается, а берётся из поля rect заголовка контура
1 (CvContour) — прямоугольник рассчитывается и записывается в поле rect заголовка контура
0 (CvSeq или CvMat) — прямоугольник рассчитывается и возвращается
1 (CvSeq или CvMat) — ! ошибка выполнения !

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

CVAPI(CvBox2D)  cvMinAreaRect2( const CvArr* points,
                                CvMemStorage* storage CV_DEFAULT(NULL));

— возвращает минимально возможный прямоугольник, которым можно обвести контур
points — последовательность или массив точек
storage — опционально — временное хранилище памяти

отличие функции cvMinAreaRect2 от cvBoundingRect в типе возвращаемой структуры. cvMinAreaRect2 возвращает CvBox2D, которая описывает прямоугольник, который может быть повёрнут относительно системы координат изображения на угол angle.

typedef struct CvBox2D
{
    CvPoint2D32f center;  /* Center of the box.                          */
    CvSize2D32f  size;    /* Box width and length.                       */
    float angle;          /* Angle between the horizontal axis           */
                          /* and the first side (i.e. length) in degrees */
}
CvBox2D;
CVAPI(int)  cvMinEnclosingCircle( const CvArr* points,
                                  CvPoint2D32f* center, float* radius );

— находит окружность минимальной площади, которая содержит данный набор 2D-точек.
points — последовательность или ассив 2D-точек
center — возвращаемое значение — центр окружности
radius — возвращаемое значение — радиус окружности

пример, демонстрирующий работу cvMinEnclosingCircle()
указатель изображения передаётся функции EnclosingCircle(), которая переводит изображение в градации серого, затем использует детектор Кенни для нахождения границ. cvFindContours() находит все контуры границ, и затем для всех контуров по-очереди применяется функция cvMinEnclosingCircle(). Найденные параметры окружности передаются функции cvCircle() для отображения окружности на рисунке.

//
// демонстрация cvMinEnclosingCircle()
//
//
// https://robocraft.ru
//

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

void EnclosingCircle(IplImage* _image)
{
	assert(_image!=0);

	IplImage* bin = cvCreateImage( cvGetSize(_image), IPL_DEPTH_8U, 1);

	// конвертируем в градации серого
	cvConvertImage(_image, bin, CV_BGR2GRAY);
	// находим границы
	cvCanny(bin, bin, 50, 200);

	cvNamedWindow( "bin", 1 );
	cvShowImage("bin", bin);

	// хранилище памяти для контуров
	CvMemStorage* storage = cvCreateMemStorage(0);
	CvSeq* contours=0;

	// находим контуры
	int contoursCont = cvFindContours( bin, storage,&contours,sizeof(CvContour),CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE,cvPoint(0,0));

	assert(contours!=0);

	// обходим все контуры
	for( CvSeq* current = contours; current != NULL; current = current->h_next ){
		CvPoint2D32f center;
		float radius=0;
		// находим параметры окружности
		cvMinEnclosingCircle(current, & center, &radius);
		// рисуем
		cvCircle(_image, cvPointFrom32f(center), radius, CV_RGB(255, 0, 0), 1, 8);
	}

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

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

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

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

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

	dst = cvCloneImage(src);

	// показываем
	EnclosingCircle(dst);

	cvNamedWindow( "circles", 1 );
	cvShowImage( "circles", dst);

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

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

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

использована функция для конвертации CvPoint2D32f в CvPoint:

CV_INLINE  CvPoint  cvPointFrom32f( CvPoint2D32f point )
{
    CvPoint ipt;
    ipt.x = cvRound(point.x);
    ipt.y = cvRound(point.y);

    return ipt;
}
CVAPI(CvSeq*)  cvApproxPoly( const void* src_seq,
                             int header_size, CvMemStorage* storage,
                             int method, double parameter,
                             int parameter2 CV_DEFAULT(0));

— аппроксимация контура(кривой) полигонами
src_seq — исходная последовательность или массив точек
header_size — размер заголовка кривой(контура)
storage — хранилище контуров. Если NULL, то используется хранилище входной последовательности
method — метод аппроксимации:

#define CV_POLY_APPROX_DP 0 // Douglas-Peucker algorithm

parameter — параметр метода аппроксимации, в случае CV_POLY_APPROX_DP — это желаемая точность
parameter2 — Если src_seq — последовательность, то параметр определяет должна ли аппроксимироваться только одна последовательность или все последовательности этого уровня и ниже. Если src_seq — массив CvMat* точек, то параметр определяет закрывается ли кривая (parameter2!=0) или нет (parameter2=0).

функция аппроксимирует одну или более кривых и возвращает результат аппроксимации. В случае нескольких кривых(контуров), результирующее дерево имеет ту же структуру, что и входящее.

	// находим контуры
	int contoursCont = cvFindContours( bin, storage, &contours, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));

	if(contours!=0){
		for(CvSeq* seq0 = contours;seq0!=0;seq0 = seq0->h_next){

			// аппроксимация контура полигонами
			CvSeq* result = cvApproxPoly( seq0, sizeof(CvContour), storage, CV_POLY_APPROX_DP, 0.1, 0 );
			printf("[d] %d %d\n", seq0->total, result->total);
		}
	}
CVAPI(CvSeq*) cvFindDominantPoints( CvSeq* contour, CvMemStorage* storage,
                                   int method CV_DEFAULT(CV_DOMINANT_IPAN),
                                   double parameter1 CV_DEFAULT(0),
                                   double parameter2 CV_DEFAULT(0),
                                   double parameter3 CV_DEFAULT(0),
                                   double parameter4 CV_DEFAULT(0));

— поиск ключевых точек контура (последовательности точек)

contour — исходная последовательность
storage — хранилище контуров.
method — метод:

#define CV_DOMINANT_IPAN 1 // алгоритм IPAN - Image and Pattern Analysis Group

parameter1 — минимальная дистанция
parameter2 — максимальная дистанция
parameter3 — дистанция сближения
parameter4 — максимальный угол отклонения (в градусах)

типичные значения 7, 9, 9, 150

далее: 33. Сравнение контуров через суммарные характеристики — моменты

Ссылки
http://en.wikipedia.org/wiki/Chain_code
Контурный анализ


23 комментария на «“32. OpenCV шаг за шагом. Нахождение контуров и операции с ними”»

  1. Из примера-картинки не видно, что да как вообще там образовалось. Может, стоит добавить изображение после поиска границ Канни? (т.е. bin)

    Цепной код Фримена, как я понял — это последовательность цифр от 0 до 7ми, показывающая все изгибы контура. Как бы можно было выудить эту последовательность?

    • Нашел-таки библиотеку, в которой CvFindDominantPoints.
      Теперь проблема другая. При вызове этой функции происходит непонятная ошибка.

      OpenCV Error: Assertion failed ((icvFindDominantpointsIPAN( contour, storage, &corners, dmin*dmin, dmax*dmax, dneigh*dneigh, (float)amax )) >=0) in unknown function, file ..\..\src\opencv\modules\legacy\src\dominants.cpp, line 392

      Код:

              CvMemStorage* storage_ct = cvCreateMemStorage(0);
      	CvMemStorage* storage_dp = cvCreateMemStorage(0);
      	IplImage* img = cvLoadImage("image11.jpg", CV_LOAD_IMAGE_GRAYSCALE);
      	IplImage* can = cvCreateImage(cvGetSize(img),8,1);
      	cvCanny(img,can,50,200,3);
      	int contoursCont;
      	contoursCont = cvFindContours( can, storage_ct, &contours, sizeof(CvContour),
      		CV_RETR_LIST, 2 );
      	if (contoursCont==0) return 0;
      	contours = cvApproxPoly( contours, sizeof(CvContour), storage_ct, CV_POLY_APPROX_DP, 3, 1 );
      	cvDrawContours(img, contours, cvScalarAll(200), cvScalarAll(200), 100,1 );
      	cvNamedWindow( "image",0 );
      	cvShowImage( "image", img );
      	dps = cvFindDominantPoints( contours, storage_ct);//, CV_DOMINANT_IPAN, 7, 20, 9, 150 );

      Где может быть ошибка?

    • родного — нет, но, мне кажется, ничего не мешает реализовать такое сохранение самостоятельно 😉

    • dxf — это, по-моему, zip-архив с данными. Можно в нём полазать и поразбираться.
      В формат автокада можно перенести через сам автокад (написав к нему плагин), либо воспользоваться сторонними библиотеками (бесплатных не встречал).

  2. Добрый день! решаю следующую задачу распознавания автомобильных номеров, проблема следующая, локализовал номер, затем ищу контуры букв и цифр, нахожу контуры букв и цифр, вырезаю буквы и цифры в цикле и отправляю на распознавание по одной, как сделать чтобы номер был в правильной последовательности, например номер Н080НА55, а на выходе AНА55080, по какому принципу он берет сначала такой контур затем другой…

    • может есть пример сортировки контуров

    • иерархия контуров есть (см cv::findContours)
      однако, она определяет только вхождение одного контура внутрь другого.
      поэтому, я бы рекомендовал находить номер, далее внутри номера обнаруживать отдельные символы и потом уже последовательно их классифицировать.
      успехов!

    • Спасибо, символы нахожу опять через контуры, но пока меня результаты не впечатляют, скорость сильно маленькая и изображение необходимо хорошо обработать перед этим, сейчас нашел вот такое описание обнаружения рамки…
      но подробно нигде нету данного метода, уже все облазил…
      Сканируем изображение построчно и строим функцию
      где I(i) значение яркости в соответствующем пикселе. Функция f(x) в области номерной
      пластины начнет быстро возрастать. После этого сглаживаем функцию f(x) и
      находим ее производную. Места с высокими значениями производной и есть
      подозрительные области. Вычислив вторую производную можно определить горизонтальные края пластины (рисунок 16).

  3. Наткнулся на этот сайт. Отличный мануал, подобные комментарии в коде, отличный разбор идей в комментах. Как бы мне связаться с автором мануала? Очень нужна помощь в интересном проекте, а знаний не хватает

  4. Здесь приводится метод детектирования окружностей на изображении по условию «PI*R^2 / (2*PI*R)*(2*PI*R) = 1/4*PI
    ». Как известно, человек с легкостью распознает окружность, если на изображении осталась часть окружности. Поэтому возникает вопрос, как изменится соотношение, если на изображении видно только половину окружности.
    PI*(R/2)^2 / (2*PI*R/2)*(2*PI*R/2) = R^2/4 / (R)*(PI*R) = 1/4*PI
    Поэтому можно предположить, что соотношение сохранится для четверти и произвольной части окружности.

    • Правильная запись: PI*R^2 / ((2*PI*R)*(2*PI*R)) = 1/4*PI или PI*R^2 / (2*PI*R)/(2*PI*R) = 1/4*PI

    • Метод нерабочий. Надо отсекать отсекающюю грань, кроме того она может быть неровной. PI*R^2/2 / (2*PI*R/2)/(2*PI*R/2) = R^2/2 / (R)*(PI*R) = 1/2*PI

  5. Внесу что-то свое) Нахождение и рисование контуров на Python 2.7:

    import cv,numpy   #подключение OpenCV
    
    #загрузка изображения
    image = cv.LoadImage('OpenCV.jpg',1)
    
    height = image.height
    width  = image.width
    print height,width
    
    
    #создание изображений
    gray 	= cv.CreateImage(cv.GetSize(image),cv.IPL_DEPTH_8U, 1 )
    binary  = cv.CreateImage(cv.GetSize(image),cv.IPL_DEPTH_8U, 1 )
    dst1 	= cv.CreateImage(cv.GetSize(image),cv.IPL_DEPTH_8U, 1 )
    
    #конвертируем изображение из RGB в gray
    cv.CvtColor(image,gray,cv.CV_RGB2GRAY)
    #преобразуем одноканальное изображение в бинарное
    cv.InRangeS(gray,cv.Scalar(30),cv.Scalar(200),binary)
    
    #создаем хранилище памяти
    storage = cv.CreateMemStorage(0)
    #находим контуры бинарного изображения
    contours = cv.FindContours(binary,storage,
    cv.CV_RETR_TREE,cv.CV_CHAIN_APPROX_SIMPLE ,(0,0))
    
    #рисуем контуры
    while contours!= None:
        cv.DrawContours(dst1,contours,cv.CV_RGB(250,0,0), cv.CV_RGB(0,0,250),2,1,8)
        contours = contours.h_next()
    
    #создаем матрицу из изображения dst1
    mat = cv.GetMat(dst1,0)
    
    
    #сохраняем полученную матрицу в xml-файл
    cv.Save('matrix.xml',mat)
    
    #Создаем окна и показываем в них изображения
    cv.NamedWindow('original',cv.CV_WINDOW_AUTOSIZE)
    cv.NamedWindow('gray',cv.CV_WINDOW_AUTOSIZE)
    cv.NamedWindow('Contours',cv.CV_WINDOW_AUTOSIZE)
    cv.NamedWindow('Binary',cv.CV_WINDOW_AUTOSIZE)
    cv.ShowImage('original',image)
    cv.ShowImage('gray',gray)
    cv.ShowImage('Contours',dst1)
    cv.ShowImage('Binary',binary)
    cv.WaitKey(0) #ожидание
  6. Добрый день! Решил поделится реализацией алгоритма локализации автомобильного номера на С++ с использованием OPENCV. Данный алгоритм не идеален и есть над чем работать, очень помог материал с сайта robocraft.ru, за что очень благодарен. К сожалению я еще сильно не окреп) и не могу создать отдельный пост, выставляю сюда…

    Выставляю рабочий код с загрузкой видео файла

    #include <opencv\cv.h>
    #include <opencv\highgui.h>
    #include <stdlib.h>
    #include <stdio.h>
    
    // находит и показывает рамку на изображении
    void findplate(IplImage *_image)
    {
        assert(_image != 0);
    
        IplImage *temp = cvCreateImage(cvGetSize(_image), IPL_DEPTH_8U, 1);
        // конвертируем в градации серого
        cvConvertImage(_image, temp, CV_BGR2GRAY);
        // смотрим что получилось
        //cvNamedWindow( «CV_BGR2GRAY», 1 );
        //cvShowImage(«CV_BGR2GRAY», temp);
        // делаем гауссовское сглаживание
        cvSmooth(temp, temp, CV_GAUSSIAN, 3, 0, 0, 0);
        // Эрозию
        cvErode(temp, temp, 0, 1);
        // расширение
        cvDilate(temp, temp, 0, 1);
    
        // находим границы
        cvCanny(temp, temp, 100, 50, 3);
    
        //cvNamedWindow( «temp», 1 );
        //cvShowImage(«temp», temp);
    
        // хранилище памяти для контуров
        CvMemStorage *storage = cvCreateMemStorage(0);
        CvSeq *contour = 0;
        CvSeq *contourLow = 0;
    
        assert(contours != 0);
    
        cvFindContours(temp, storage, &contour, sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, cvPoint(0, 0));
    
        //оптимизируем контуры
        contourLow = cvApproxPoly(contour, sizeof(CvContour), storage, CV_POLY_APPROX_DP, 0.5, 12);
        // бегаем по контурам
        for (; contourLow != 0; contourLow = contourLow->h_next)
    
        {
            //находим соотношения площади к периметру контура
            double area = fabs(cvContourArea(contourLow));
            double perim = cvContourPerimeter(contourLow);
            if ((area / perim) > 4)
    
            {
    
                CvRect rect;
                CvPoint pt1, pt2;
                rect = cvBoundingRect(contourLow, 0); // ищем среди оставшихся прямоугольники
                pt1.x = rect.x;
                pt2.x = (rect.x + rect.width);
                pt1.y = rect.y;
                pt2.y = (rect.y + rect.height);
                double ratio = rect.width / rect.height;
    
                if ((2.0 < fabs(ratio) && fabs(ratio) < 8.0))
                {
                    //Show result.
                    cvRectangle(_image, pt1, pt2, cvScalar(0, 0, 255), 1, 8, 0);
                }
            }
        }
        // освобождаем ресурсы
        cvReleaseMemStorage(&storage);
        cvReleaseImage(&temp);
    }
    
    IplImage *frame = 0;
    
    int main(int argc, char *argv[])
    {
        // имя файла задаётся первым параметром
        char *filename = argc == 2 ? argv[1] : «VIDEO0003.mp4»;
    
        // printf("[i] file: %s\n", filename);
    
        // окно для отображения картинки
        cvNamedWindow(«original», CV_WINDOW_AUTOSIZE);
    
        // получаем информацию о видео-файле
        CvCapture *capture = cvCreateFileCapture(filename);
    
        while (1)
        {
            // получаем следующий кадр
            frame = cvQueryFrame(capture);
            if (!frame)
            {
                break;
            }
    
            // здесь можно вставить
            // процедуру обработки
            findplate(frame);
            // показываем кадр
            cvShowImage( «original», frame);
    
            char c = cvWaitKey(33);
            if (c == 27)
            { // если нажата ESC — выходим
                break;
            }
        }
    
        // освобождаем ресурсы
        cvReleaseCapture(&capture);
        // удаляем окно
        cvDestroyWindow(«original»);
        return 0;
    }

    В следующем материале покажу как прикрутить tesseract-ocr для распознавания номера…

  7. Решил развить поиск окружностей, описанный в этой теме. У меня сложности с регистрацией на форуме, по какой-то причине не приходит письмо для активации, поэтому пишу здесь.
    Поиск окружностей и их частей на изображении
    На шаге 32 из цикла уроков «OpenCV шаг за шагом» на robocraft.ru приводится способ определения окружности на изображения. Площадь окружности делится на квадрат ее периметра. Данное отношение постоянно для контура окружности любого диаметра и всегда равно 1/(4*pi) или 1/4*CV_PI = 0,079577 как в уроке (исправлено 21.11.16).
    . Данный метод позволяет распознать только полностью видимые окружности с контурами, близкими к правильным окружностям. А как осуществить поиск окружности, частично видимой на изображении?
    Начнем с простого. Возьмем идеальный контур окружности. Для начала выделим ее с помощью прямоугольной области минимального размера. Размеры области можно получить, оперируя с моментами контура окружности. Для окружности область выделения совпадает с описанным квадратом, те получим окружность, вписанную в квадрат. Площадь этого квадрата равна квадрату диаметра или 4 квадратам радиуса, Sкв=4*r^2.
    Возьмем отношение площади рассматриваемого контура, т.е окружности к площади описанного квадрата. Его значение равно pi/4 и не зависит от размера окружности. Площадь контура можно поискать в моментах.
    Теперь рассмотрим случай, когда на изображении видно только половину окружности.
    Я сделал расчеты и получил то же самое отношение — pi/4. См. рисунок. Соотношение сохраняется для случая, когда окружность перекрывается другим изображением по диагонали описанного квадрата, но математические выкладки носят чисто теоретический характер, т.к. на практике достаточно трудно точно определить границы выделяющего треугольника. Возможно, что потребуется использование законов симметрии и отзеркалить видимую часть по диаметру для получения полного изображения окружности. Однако на изображении может остаться только часть дуги окружности и восстановление симметрии позволит получить фигуру, далекую от окружности.
    В итоге я сделал предположение, что отношение площади любой видимой области окружности к выделяющей области будет постоянным и равным pi/4.
    Стоит отметить, что данный метод достаточно трудно реализовать на практике и, видимо, есть более надежные методы поиска.

    #include "stdafx.h"
    
    
    #include <cv.h>
    #include <highgui.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <math.h>
    
    using namespace std;
    using namespace cv;
    
    int main(int argc, char* argv[])
    {
    
      // Создаёи 8-битную, 1-канальную картинку
      IplImage *image1 = cvCreateImage( cvSize(200,200), 8,1);
      // заливаем картинку чёрным цветом
      cvSet(image1,cvScalar(0,0,0));
      // Рисуем окружность с радиусом 65, с центром в 100,100
      cvCircle(image1,cvPoint(100,100),65,cvScalar(255,255,255),1,8);
    
      // создаем хранилище
      CvMemStorage* storage1 = cvCreateMemStorage(0);
      CvSeq* contours = 0;
    
      cvFindContours( image1, storage1, &contours, sizeof(CvContour),
    			   CV_RETR_TREE ,CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0) );
    //делаем CV...TREE т.к. нам нужно получить внешние и внутренние контуры
    
    
    CvRect Rect;
    double perimeter;
    double s;
    
    for( CvSeq* c=contours; c!=0; c=c->h_next)
    {
    		// как правило, четкие замкнутые контуры можно получить на цветном изображении
    	    // высокого качества
            Rect = cvBoundingRect( c ); // Поиск ограничивающего прямоугольника минимального размера
    		perimeter = cvArcLength(c); // Получаем длину контура
    		s = cvContourArea(c);		// Получаем площадь контура
            if ( Rect.width < 30 ) continue; // Маленькие контуры меньше 30 пикселей не нужны
    		//printf("Rect.width=%u \tRect.height=%u\n",Rect.width,Rect.height);
    		// рисуем прясоугольник
            cvRectangle( image1, cvPoint( Rect.x, Rect.y ), cvPoint( Rect.x + Rect.width, Rect.y + Rect.height ), CV_RGB(255,0,0), 2 );
    		double Skv = Rect.width*Rect.height;
    		printf("Rect.width=%u \tRect.height=%u\n",Rect.width,Rect.height);
    		printf("s=%f\t\tperimetr=%f\n", s,perimeter);
    		printf("Skv=%f\tpi=4*s/Skv=%f\ns/perimeter^2=1/(4*pi)=%f \n\n", Skv, 4*s/Skv,(float)(s/perimeter/perimeter));
    }
    
             // покажем изображение
            cvNamedWindow( "original", 1 );
            cvShowImage( "original", image1 );
    
    		//считаем моменты здесь, для всего изображения
    		//(типа, отрисовали выбранный контур в отдельном окне для расчета моментов)
    		CvMoments moments;
    		CvHuMoments HuMoments;
    
    		cvMoments(image1,&moments);
    		cvGetHuMoments(&moments,&HuMoments);
    
    		printf("Moments:\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t\n",moments.m00,moments.m01,moments.m02,
    		moments.m03,moments.m10,moments.m11,moments.m12,moments.m20,moments.m21,moments.m30);
    
    		printf("HuMoments:\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t\n",HuMoments.hu1,HuMoments.hu2,HuMoments.hu3
    		,HuMoments.hu4,HuMoments.hu5,HuMoments.hu6,HuMoments.hu7);
    
    		// центр окружности через моменты
    		double xc = moments.m10/moments.m00;
    		double yc = moments.m01/moments.m00;
    		printf("xc=%f \tyc=%f\n", xc,yc);
    		// ждём нажатия клавиши
    		cvWaitKey(0);
    		// освобождаем ресурсы
    		cvReleaseMemStorage( &storage1);
    		cvDestroyWindow("original");
    		return 0;
    }

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

Arduino

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

Разделы

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

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

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

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