Контура

Контура

Шокуров Антон В.
shokurov.anton.v@yandex.ru
http://машинноезрение.рф

22 октября 2018 г.

Версия: 0.09

Аннотация

Преобразование растровых изображений в бинарные. Пороговые функции. Связанные компоненты. Их подсчет.

1 Контура

В данной заметке будет показано как выделять контура у объектов на бинарном изображении. Далее показано, как их аппроксимировать аналитическими кривыми: линия, окружность и тому подобное.

1.1 Выделение контуров

Выделение контуров и их иерархию. В целом для их выделения используется функция findContours, но в зависимости от конкретной задачи с разными параметрами.

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

Выделение внешних контуров Напомню, как считать и превратить цветное изображение в бинарное:

1Mat color; 
2color = imread(argv[1], IMREAD_COLOR); 
3 
4Mat gray; 
5cvtColor( color, gray, COLOR_BGR2GRAY ); 
6 
7Mat bin; 
8threshold(gray, bin, 180, 255, THRESH_BINARY_INV );

Далее, для выделения только внешних контуров на изображение используется такой код: что это делается так:

1vector<vector<Point2i>> contours; 
2vector<Vec4i> hierarchy; 
3findContours( bin, contours, hierarchy, 
4    RETR_EXTERNAL, CHAIN_APPROX_SIMPLE );

Наиболее важна для нас переменная contours. Переменная hierarchy будет использована в следующих параграфах.

В contours хранятся выделенные контура объекта. При вызове было задано RETR_EXTERNAL поэтому будут выделены только внешние контура объектов.

Параметр CHAIN_APPROX_SIMPLE указывает на то, что подряд идущие точки по горизонтали и вертикали не следует записывать в контур (для экономии памяти). Если указать значение HAIN_APPROX_NONE , то будут указаны все точки контура (согласно идеологии 8-связанности).

Итого, размер вектора contours указывает на количество связанных компонент.

1cout << "компоненты: " << contours.size() << endl;

Исходя их определения переменной contours следует, что её элементы в свою очередь также являются векторами типа vector<Point2i>, т.е. состоят из точек. Поэтому, contours[i][j] задает координаты jой точки iго контура.

Упражнение. Напиши программу, которая отрисовывает контура (разными цветами).

Отрисовка контуров Отрисовать контура можно и использую, соответствующую, функцию опенсв: drawContours.

1drawContours( color, contours, 1, Scalar( 0, 100, 200), 2 );

Первый аргумент указывает на изображение на котором будет выполнена отрисовка контуров. Второй аргумент задает сами контура. Третий на индекс контура, который нужно отрисовать. В случае, если задать число 1 будут отрисованы все контура. Далее задается цвет и толщина линий (отрезков).

Выделение внутренних контуров Далее рассмотрим как выделять дырки в объектах. Заменем параметр RETR_EXTERNAL на RETR_CCOMP.

1findContours( bin, contours, hierarchy, 
2    RETR_CCOMP, CHAIN_APPROX_SIMPLE );

Последний параметр позволяет создать простейшую иерархию их контуров. Он помимо внешнего контура объекта выделяет и внутренние, т.е. ищет дырки. Данная информация занесена в переменную hierarchy. Рассмотрем её подробнее для понимания того как это происходит.

Каждый из элементов вектора hierarchy (имея тип Vec4i) состоит из 4 целочисленных компонент. Он имеет такой же размер, что и вектор контуров (contours). Фактически в переменной hierarchy для каждого контура хранится его положение в иерархии контуров, т.е. какие у него соседние дыры (братья/сестры), дыры (дети) и родитель. Так, все компоненты являются индексами. Индекс равный 1 означает отсутствие данной сущности. Итого, компоненты указывают на индекс:

В случае использования параметра RETR_CCOMP иерархия устроена просто. Для каждого внешнего контура указаны контура дыр в случае их наличия.

1int cntrs = contours.size(); 
2cout << "контуры: " << cntrs << endl; 
3int i, k = 0; 
4for( i = 0; i < cntrs && i != 1 ; i = hierarchy[i][0], k++ ) 
5
6  const Vec4i &data = hierarchy[i]; 
7  int child = data[2], holes = 0; 
8  int j; 
9  cout << "контур " << k << ": "; 
10  for( j = child; j != 1; j = hierarchy[j][0], holes++ ); 
11  if( holes ) 
12    cout << "дыры: " << holes; 
13  cout << endl; 
14}

Определение иерархии Для полноценного разбора всех контуров необходимо заменить в первоначальном вызове параметр RETR_EXTERNAL на RETR_TREE, т.е. сделать вызов следующим образом:

1findContours( bin, contours, hierarchy, 
2    RETR_TREE, CHAIN_APPROX_SIMPLE );.

При таком вызове в переменной hierarchy хранится полноценная информация об иерархии контуров: в ней для каждой дыры хранится контура которые в ней находятся.

Напишем функцию, которая печатает эту иерархию:

1void print_tree( const vector<Vec4i> &hierarchy, int s) 
2
3  int cntrs = hierarchy.size(); 
4  int i; 
5  cout << "[ "; 
6  for( i = s; i < cntrs && i != 1 ; i = hierarchy[i][0]) 
7  { 
8    if( i != s ) 
9      cout << ","; 
10    cout << " "; 
11    const Vec4i &data = hierarchy[i]; 
12    print_tree( hierarchy, data[2] ); 
13  } 
14  if( i != s ) 
15    cout << " "; 
16  cout << " ]"; 
17}

Вызвать эту функцию следует следующим образом:

1print_tree( hierarchy, 0 );

Упражнение. Напишите программу, которая на изображении выделает все контура, которые содержать две дырки и одну дырку с объектом.

1.2 Характеристики контуров

Можно применить и моменты пройденные ранее.

Длины arcLength

Площадь contourArea

1.3 Аппроксимация кривых

После того как контура выделены их можно аппроксимировать аналитическими кривыми.

approxPolyDP

Линия fitLine

Окружности fitEllipse

Эллипс fitEllipseAMS fitEllipseDirect

1.4 Вокруг множество точек

boundingRect minAreaRect

minEnclosingCircle minEnclosingTriangle