Контурный анализ — это один из важных и очень полезных методов описания, хранения, распознавания, сравнения и поиска графических образов/объектов.
Контур — это внешние очертания (обвод) предмета/объекта.
При проведении контурного анализа:
* полагается, что контур содержит достаточную информацию о форме объекта;
* внутренние точки объекта во внимание не принимаются.
Вышеприведённые положения, разумеется, накладывают существенные ограничения на область применения контурного анализа, которые, в основном, связаны с проблемами выделения контура на изображениях:
* из-за одинаковой яркости с фоном объект может не иметь чёткой границы, или может быть зашумлён помехами, что приводит к невозможности выделения контура;
* перекрытие объектов или их группировка приводит к тому, что контур выделяется неправильно и не соответствует границе объекта.
Однако, переход к рассмотрению только контуров объектов позволяет уйти от пространства изображения – к пространству контуров, что существенно снижает сложность алгоритмов и вычислений.
Т.о., контурный анализ имеет довольно слабую устойчивость к помехам, и любое пересечение или лишь частичная видимость объекта приводит либо к невозможности детектирования, либо к ложным срабатываниям, но простота и быстродействие контурного анализа, позволяют вполне успешно применять данный подход (при чётко выраженном объекте на контрастном фоне и отсутствии помех).
Итак, мы определились, что контур — это некая граница объекта, которая отделяет его от фона (других объектов). Вспомним, как мы можем получить контуры?
Во всех этих случаях мы получаем бинарное изображение, которое явным образом задаёт нам границы объекта. Вот эта совокупность пикселей, составляющих границу объекта и есть контур объекта.
Чтобы оперировать полученным контуром, его необходимо как-то представить (закодировать).
Например, указывать вершины отрезков, составляющих контур.
Другой известный способ кодирования контура – это цепной код Фримена.
Цепной код Фримена (Фридмана) (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 — тип линии
Обычная последовательность действий при распознавании объектов методом контурного анализа: 1. предварительная обработка изображения (сглаживание, фильтрация помех, увеличение контраста); 2. бинаризация изображения; 3. выделение контуров объектов; 4. первичная фильтрация контуров (по периметру, площади и т.п.); 5. эквализация контуров (приведение к единой длине, сглаживание) — позволяет добиться инвариантности к масштабу; 6. перебор всех найденных контуров и поиск шаблона, максимально похожего на данный контур (или же сортировка контуров по каком-либо признаку, например, площади).
Свойства контуров
OpenCV предоставляет функции для определения таких полезных свойств найденных контуров, как площадь и длина(периметр).
— возвращает площадь контура
contour — контур (последовательность или массив вершин)
slice — начальная и конечные точки контура (по-умолчанию весь контур)
ориентация контура влияет на знак, т.о. функция может вернуть отрицательную величину.
Можно использовать fabs() чтобы получить абсолютное значение площади.
— возвращает периметр контура или длину кривой (части контура)
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
//
// поиск кругов на изображении
// по отношению площади контура к квадрату его длины
//
//
// http://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;
}
Чтобы снизить ложные срабатывания можно ввести нижнее/верхнее ограничения на площадь круга.
Дополнительные полезные функции:
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) — ! ошибка выполнения!
функция возвращает прямоугольник, у которого стороны строго вертикальны и горизонтальны (параллельны сторонам(системе координат) изображения).
— возвращает минимально возможный прямоугольник, которым можно обвести контур
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() для отображения окружности на рисунке.
parameter — параметр метода аппроксимации, в случае CV_POLY_APPROX_DP — это желаемая точность
parameter2 — Если src_seq — последовательность, то параметр определяет должна ли аппроксимироваться только одна последовательность или все последовательности этого уровня и ниже. Если src_seq — массив CvMat* точек, то параметр определяет закрывается ли кривая (parameter2!=0) или нет (parameter2=0).
функция аппроксимирует одну или более кривых и возвращает результат аппроксимации. В случае нескольких кривых(контуров), результирующее дерево имеет ту же структуру, что и входящее.
Из примера-картинки не видно, что да как вообще там образовалось. Может, стоит добавить изображение после поиска границ Канни? (т.е. bin)
Цепной код Фримена, как я понял — это последовательность цифр от 0 до 7ми, показывающая все изгибы контура. Как бы можно было выудить эту последовательность?
dxf — это, по-моему, zip-архив с данными. Можно в нём полазать и поразбираться.
В формат автокада можно перенести через сам автокад (написав к нему плагин), либо воспользоваться сторонними библиотеками (бесплатных не встречал).
Добрый день! решаю следующую задачу распознавания автомобильных номеров, проблема следующая, локализовал номер, затем ищу контуры букв и цифр, нахожу контуры букв и цифр, вырезаю буквы и цифры в цикле и отправляю на распознавание по одной, как сделать чтобы номер был в правильной последовательности, например номер Н080НА55, а на выходе AНА55080, по какому принципу он берет сначала такой контур затем другой…
иерархия контуров есть (см cv::findContours)
однако, она определяет только вхождение одного контура внутрь другого.
поэтому, я бы рекомендовал находить номер, далее внутри номера обнаруживать отдельные символы и потом уже последовательно их классифицировать.
успехов!
Спасибо, символы нахожу опять через контуры, но пока меня результаты не впечатляют, скорость сильно маленькая и изображение необходимо хорошо обработать перед этим, сейчас нашел вот такое описание обнаружения рамки…
но подробно нигде нету данного метода, уже все облазил…
Сканируем изображение построчно и строим функцию
где I(i) значение яркости в соответствующем пикселе. Функция f(x) в области номерной
пластины начнет быстро возрастать. После этого сглаживаем функцию f(x) и
находим ее производную. Места с высокими значениями производной и есть
подозрительные области. Вычислив вторую производную можно определить горизонтальные края пластины (рисунок 16).
Добрый день! Решил поделится реализацией алгоритма локализации автомобильного номера на С++ с использованием OPENCV. Данный алгоритм не идеален и есть над чем работать, очень помог материал с сайта robocraft.ru, за что очень благодарен. К сожалению я еще сильно не окреп) и не могу создать отдельный пост, выставляю сюда…
Выставляю рабочий код с загрузкой видео файла
#include <opencv\cv.h>
#include <opencv\highgui.h>
#include <stdlib.h>
#include <stdio.h>
// находит и показывает рамку на изображении
void findplate(IplImage* _image)
{
assert(_image!=0);
{
//находим соотношения площади к периметру контура
double area = fabs(cvContourArea(contourLow));
double perim = cvContourPerimeter(contourLow);
if ((area/perim)>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
Поэтому можно предположить, что соотношение сохранится для четверти и произвольной части окружности.
Решил развить поиск окружностей, описанный в этой теме. У меня сложности с регистрацией на форуме, по какой-то причине не приходит письмо для активации, поэтому пишу здесь.
Поиск окружностей и их частей на изображении
На шаге 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!=NULL; 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;
}
Комментарии (22)
RSS свернуть / развернутьЦепной код Фримена, как я понял — это последовательность цифр от 0 до 7ми, показывающая все изгибы контура. Как бы можно было выудить эту последовательность?
Zuix
cvMinEnclosingCircle(current, ¢er, &radius);
второй аргумент странный
Zuix
Zuix
Cerebrum
Я работаю с opencv 2.3.1 и она упорно отказывается находить cvFindDominantPoints. В чем может быть проблема?
Altivolus
Теперь проблема другая. При вызове этой функции происходит непонятная ошибка.
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
Код:
Где может быть ошибка?
Altivolus
Stesh
А есть ли возможность в Opencv сохранения полученых контуров в DWG, DXF или других CAD форматах?
Ange5545
noonv
Вот только мне никак не удается найти хотя бы в каком виде храняться данные в DXF(
Ange5545
В формат автокада можно перенести через сам автокад (написав к нему плагин), либо воспользоваться сторонними библиотеками (бесплатных не встречал).
JohnJ
tigrito
zobnin
zobnin
однако, она определяет только вхождение одного контура внутрь другого.
поэтому, я бы рекомендовал находить номер, далее внутри номера обнаруживать отдельные символы и потом уже последовательно их классифицировать.
успехов!
noonv
но подробно нигде нету данного метода, уже все облазил…
Сканируем изображение построчно и строим функцию
где I(i) значение яркости в соответствующем пикселе. Функция f(x) в области номерной
пластины начнет быстро возрастать. После этого сглаживаем функцию f(x) и
находим ее производную. Места с высокими значениями производной и есть
подозрительные области. Вычислив вторую производную можно определить горизонтальные края пластины (рисунок 16).
zobnin
Выставляю рабочий код с загрузкой видео файла
#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, NULL, 1);
// расширение
cvDilate(temp, temp, NULL, 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, NULL); // ищем среди оставшихся прямоугольники
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 для распознавания номера…
zobnin
». Как известно, человек с легкостью распознает окружность, если на изображении осталась часть окружности. Поэтому возникает вопрос, как изменится соотношение, если на изображении видно только половину окружности.
PI*(R/2)^2 / (2*PI*R/2)*(2*PI*R/2) = R^2/4 / (R)*(PI*R) = 1/4*PI
Поэтому можно предположить, что соотношение сохранится для четверти и произвольной части окружности.
tester
tester
tester
Поиск окружностей и их частей на изображении
На шаге 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.
Стоит отметить, что данный метод достаточно трудно реализовать на практике и, видимо, есть более надежные методы поиска.
tester
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.