Распознавание образов для программистов

ВНИМАНИЕ! БЛОГ ПЕРЕЕХАЛ ПО АДРЕСУ
RECOG.RU

20 Декабрь 2009

9. Удаление мелких контуров

написано в рубрике: OpenCV — Кручинин Александр @ 2:59 ДП

При распознавании образов вам могут помешать мелкие контуры. Чтобы удалить их, необходимо пройтись по всей цепочке контуров, проверяя размеры каждого и смещая циклы. Для этого в листинге 8.1 сменить в функции cvFindContours() значение CV_RETR_TREE на CV_RETR_LIST (будет просматриваться последовательный список). И после функции cvFindContours() вставить блок кода, представленный в листинге 9.1.

 

Листинг 9.1

CvSeq* h_next=0;

      for( CvSeq* c=contours; c!=NULL; c=c->h_next )

      {

            if (c!=contours)

            {

                  if (c->total<=100) //размер удаляемых контуров

                  {

                        //удаляем мелкие контуры

                        h_next->h_next=h_next->h_next->h_next;

                        continue;

                  }

            }

            h_next=c;

      }

      if (contours->total<=100) contours=contours->h_next;

 

Результат работы представлен на рисунке 9.1.

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

19 Декабрь 2009

8. Использование контуров

написано в рубрике: OpenCV — Кручинин Александр @ 6:56 ДП

Одним из важнейших методов распознавания графических образов является контурный анализ. Библиотека OpenCV предоставляет возможность разработчикам легко детектировать контуры изображения и манипулировать ими. Для поиска контуров предлагается использовать функцию cvFindContours().

 

cvFindContours

 

int cvFindContours(

CvArr* image,

CvMemStorage* storage,

CvSeq** first_contour,

int header_size=sizeof(CvContour),

int mode=CV_RETR_LIST,

int method=CV_CHAIN_APPROX_SIMPLE,

CvPoint offset=cvPoint(0,0)

);

 

Параметры:

image

Исходное 8-битное изображение. Отличные от нуля пикселы обрабатываются как 1, нулевые пикселы остаются 0 – т.е. изображение является монохромным. Чтобы получить такое изображение, можно использовать cvThreshold, cvAdaptiveThreshold или cvCanny. Функция изменяет исходное содержание изображения.

storage

Контейнер найденных контуров.

first_contour

Указатель на первый найденный контур.

header_size

Размер заголовка последовательности, > = sizeof (CvChain) если method=CV_CHAIN_CODE, и > =sizeof (CvContour) в противном случае.

mode

CV_RETR_EXTERNAL – находятся только критические внешние контуры;

CV_RETR_LIST – находятся все контуры, и помещает их в список

CV_RETR_CCOMP – находятся все контуры, и записывают их в иерархию с двумя уровнями: верхний уровень – внешние границы компонентов, второй уровень – границы отверстий

CV_RETR_TREE – находятся все контуры, и записывается полная иерархия вложенных контуров.

method

Метод аппроксимации (для всех режимов, кроме CV_RETR_RUNS, который использует встроенную аппроксимацию).

CV_CHAIN_CODE – на выходе очерчивает контур в цепном коде Фримена [1]. Все другие методы выводят многоугольники;

CV_CHAIN_APPROX_NONE – переводит все точки с цепного кода в точки;

CV_CHAIN_APPROX_SIMPLE – сжимает горизонтальные, вертикальные, и диагональные доли;

CV_CHAIN_APPROX_TC89_L1, CV_CHAIN_APPROX_TC89_KCOS – применяет одну из разновидностей алгоритма апроксимации цепочки Teh-Chin.

CV_LINK_RUNS – использует полностью различный алгоритм поиска контура через соединение горизонтальных долей. Только CV_RETR_LIST режим поиска может использоваться с этим методом.

offset

Смещение, с которым каждая точка контура сдвинута. Это полезно, если контуры извлечены из изображения ROI, и затем они должны быть проанализированы в целом контексте изображения.

 

Функция cvFindContours отыскивает контуры от монохромного изображения и возвращает число найденных контуров. Указатель first_contour заполняется функцией. Он будет содержать указатель на первый наиболее внешний контур или ПУСТОЙ УКАЗАТЕЛЬ, если никакие контуры не обнаружены (если изображение полностью черно). Другие контуры могут быть достигнуты от first_contour, используя h_next и связей v_next. Последовательность контуров описана интересной структурой CvSeq, которая предоставляет интерфейс для всех динамических структур в OpenCV.

 

CvSeq

 

#define CV_SEQUENCE_FIELDS() \

int flags; /* разные флажки*/ \

int header_size; /* размер заголовка последовательности*/ \

struct CvSeq* h_prev; /* предыдущая последовательность*/ \

struct CvSeq* h_next; /* следующая последовательность*/ \

struct CvSeq* v_prev; /* 2-ая предыдущая последовательность*/ \

struct CvSeq* v_next; /* 2-ая следующая последовательность */ \

int total; /* общее количество элементов */ \

int elem_size;/* размер элемента последовательности в байтах */ \

char* block_max;/* maximal bound of the last block */ \

char* ptr; /* текущий указатель записи */ \

int delta_elems; /* сколько дополнительных элементов для роста последовательности */ \

CvMemStorage* storage; /* где сохранена последовательность*/ \

CvSeqBlock* free_blocks; /* свободные блоки в списке*/ \

CvSeqBlock* first; /* указатель на первую последовательность */

 

typedef struct CvSeq

{

CV_SEQUENCE_FIELDS()

} CvSeq;

 

После того, как контуры обнаружены – их можно вывести в изображению с помощью функции cvDrawContours().

 

cvDrawContours

 

void cvDrawContours(

CvArr *img,

CvSeq* contour,

CvScalar external_color,

CvScalar hole_color,

int max_level,

int thickness=1,

int line_type=8,

CvPoint offset=cvPoint(0,0)

);

 

Параметры:

img

Изображение, в которое будут выводиться контуры.

contour

Указатель на первый контур.

external_color

Цвет внешних контуров.

hole_color

Цвет внутренних контуров.

max_level 

Максимальный уровень для отображаемых контуров. Если 0, только один контур. Если 1, этот контур и все контуры на том же самом уровне. Если 2, все контуры одинакового уровня и все контуры на один уровень ниже контуров отображаются, и т.д. Если значение отрицательно, функция рисует только дочерние контуры контура до abs(max_level)-1 уровень.

thickness

Толщина контура.

line_type 

Тип линии (смотрите описание в функции cvLine).

offset

Сдвигает все координаты точек на указанное значение. Полезно в случае, если, если контуры, найденные в некотором изображении ROI должны быть приняты во внимание для предоставления в изображении.

 

Функция cvDrawContours рисует контуры в изображении если толщина > =0 или заполняет область, ограниченную контурами если толщина < 0.

Пример использования функции представлен в листинге 8.1.

 

Листинг 8.1.

IplImage* image = cvLoadImage( “image.jpg”, 1 );

 

      //Создаем изображение в градациях серого

      IplImage* img_gray= cvCreateImage( cvSize(image->width,image->height), 8, 1);

 

      CvSeq* contours = 0;   

      CvMemStorage* storage = cvCreateMemStorage(0);

 

      cvCvtColor( image, img_gray, CV_BGR2GRAY );

      cvThreshold( img_gray, img_gray, 128, 255, CV_THRESH_BINARY );

 

      //cvAdaptiveThreshold(img_gray, img_gray, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C ,

      //          CV_THRESH_BINARY, 21, 7);

 

      cvFindContours( img_gray, storage, &contours, sizeof(CvContour),

                    CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0) );

 

      cvDrawContours( image, contours, CV_RGB(255,0,0), CV_RGB(0,255,0),2, 1, CV_AA, cvPoint(0,0) );

 

      cvSaveImage(”img2.jpg”,image );

 

      cvReleaseImage( &image );

 

Результат выделения контуров при использовании функции cvThreshold() представлен на рисунке 8.1, а при использовании «умного» построения монохромного изображения cvAdaptiveThreshold(), представлен на рисунке 8.2.

Рис.8.1. Выделение контуров после приведения изображения к монохромному с помощью функции cvThreshold
Рис.8.1. Выделение контуров после приведения изображения к монохромному с помощью функции cvThreshold
Рис.8.2. Выделение контуров после приведения изображения к монохромному с помощью функции cvAdaptiveThreshold
Рис.8.2. Выделение контуров после приведения изображения к монохромному с помощью функции cvAdaptiveThreshold

 

Для сглаживания и получения более аккуратных контуров можно воспользоваться функцией cvApproxPoly().

 

cvApproxPoly

CvSeq* cvApproxPoly(

const void* src_seq,

int header_size,

CvMemStorage* storage,

int method,

double parameter,

int parameter2=0

);

 

Параметры:

src_seq

Последовательность контуров.

header_size

Размер заголовка аппроксимирующей кривой

storage

Хранилище аппроксимированных контуров.

method

Метод аппроксимации; только CV_POLY_APPROX_DP поддержан, который соответствует алгоритму Дугласа – Пейкера.

parameter

Определенный методом параметр; в случае CV_POLY_APPROX_DP это – желательная точность приближения.

parameter2

Если src_seq – последовательность, это означает, должна ли одиночная последовательность быть аппроксимирована или все последовательности на том же самом уровне или ниже src_seq (см. cvFindContours для описания иерархических структур контура).

 

Функция cvApproxPoly() аппроксимирует одну или большее количество кривых и возвращает результат приближения. Так если в листинг 8.1. ниже функции cvFindContours() вставить следующую строку:

contours = cvApproxPoly( contours, sizeof(CvContour), storage, CV_POLY_APPROX_DP, 3, 1 );

результат представлен на рисунке 8.3.

Рис.8.3. Выделение контуров после приведения изображения к монохромному с помощью функции cvAdaptiveThreshold и использованию функции cvApproxPoly
Рис.8.3. Выделение контуров после приведения изображения к монохромному с помощью функции cvAdaptiveThreshold и использованию функции cvApproxPoly

 

1. H. Freeman. On the encoding of arbitrary geometric configurations, IRE Transactions on Electronic Computers EC- 10(1961) 260-268.

13 Декабрь 2009

7. Детектирование движений на основе OpenCV

написано в рубрике: OpenCV — Кручинин Александр @ 10:24 ДП

OpenCV предоставляет возможность детектирования движений на видео-потоке (видео-камера или открытый видео-файл). Рассмотрим пример, идущий в комплекте с OpenCV (Листинг 7.1).

Листинг 7.1.

#ifdef _CH_

#pragma package <opencv>

#endif

 

#ifndef _EiC

// motion templates sample code

#include “cv.h”

#include “highgui.h”

#include <time.h>

#include <math.h>

#include <ctype.h>

#include <stdio.h>

#endif

 

// various tracking parameters (in seconds)

const double MHI_DURATION = 1;

const double MAX_TIME_DELTA = 0.5;

const double MIN_TIME_DELTA = 0.05;

// number of cyclic frame buffer used for motion detection

// (should, probably, depend on FPS)

const int N = 4;

 

// ring image buffer

IplImage **buf = 0;

int last = 0;

 

// temporary images

IplImage *mhi = 0; // MHI

IplImage *orient = 0; // orientation

IplImage *mask = 0; // valid orientation mask

IplImage *segmask = 0; // motion segmentation map

CvMemStorage* storage = 0; // temporary storage

 

// parameters:

//  img – input video frame

//  dst – resultant motion picture

//  args – optional parameters

void  update_mhi( IplImage* img, IplImage* dst, int diff_threshold )

{

    double timestamp = (double)clock()/CLOCKS_PER_SEC; // get current time in seconds

    CvSize size = cvSize(img->width,img->height); // get current frame size

    int i, idx1 = last, idx2;

    IplImage* silh;

    CvSeq* seq;

    CvRect comp_rect;

    double count;

    double angle;

    CvPoint center;

    double magnitude;         

    CvScalar color;

 

    // allocate images at the beginning or

    // reallocate them if the frame size is changed

    if( !mhi || mhi->width != size.width || mhi->height != size.height ) {

        if( buf == 0 ) {

            buf = (IplImage**)malloc(N*sizeof(buf[0]));

            memset( buf, 0, N*sizeof(buf[0]));

        }

       

        for( i = 0; i < N; i++ ) {

            cvReleaseImage( &buf[i] );

            buf[i] = cvCreateImage( size, IPL_DEPTH_8U, 1 );

            cvZero( buf[i] );

        }

        cvReleaseImage( &mhi );

        cvReleaseImage( &orient );

        cvReleaseImage( &segmask );

        cvReleaseImage( &mask );

       

        mhi = cvCreateImage( size, IPL_DEPTH_32F, 1 );

        cvZero( mhi ); // clear MHI at the beginning

        orient = cvCreateImage( size, IPL_DEPTH_32F, 1 );

        segmask = cvCreateImage( size, IPL_DEPTH_32F, 1 );

        mask = cvCreateImage( size, IPL_DEPTH_8U, 1 );

    }

 

    cvCvtColor( img, buf[last], CV_BGR2GRAY ); // convert frame to grayscale

 

    idx2 = (last + 1) % N; // index of (last – (N-1))th frame

    last = idx2;

 

    silh = buf[idx2];

    cvAbsDiff( buf[idx1], buf[idx2], silh ); // get difference between frames

   

    cvThreshold( silh, silh, diff_threshold, 1, CV_THRESH_BINARY ); // and threshold it

    cvUpdateMotionHistory( silh, mhi, timestamp, MHI_DURATION ); // update MHI

 

    // convert MHI to blue 8u image

    cvCvtScale( mhi, mask, 255./MHI_DURATION,

                (MHI_DURATION – timestamp)*255./MHI_DURATION );

    cvZero( dst );

    cvCvtPlaneToPix( mask, 0, 0, 0, dst );

 

    // calculate motion gradient orientation and valid orientation mask

    cvCalcMotionGradient( mhi, mask, orient, MAX_TIME_DELTA, MIN_TIME_DELTA, 3 );

   

    if( !storage )

        storage = cvCreateMemStorage(0);

    else

        cvClearMemStorage(storage);

   

    // segment motion: get sequence of motion components

    // segmask is marked motion components map. It is not used further

    seq = cvSegmentMotion( mhi, segmask, storage, timestamp, MAX_TIME_DELTA );

 

    // iterate through the motion components,

    // One more iteration (i == -1) corresponds to the whole image (global motion)

    for( i = -1; i < seq->total; i++ ) {

 

        if( i < 0 ) { // case of the whole image

            comp_rect = cvRect( 0, 0, size.width, size.height );

            color = CV_RGB(255,255,255);

            magnitude = 100;

        }

        else { // i-th motion component

            comp_rect = ((CvConnectedComp*)cvGetSeqElem( seq, i ))->rect;

            if( comp_rect.width + comp_rect.height < 100 ) // reject very small components

                continue;

            color = CV_RGB(255,0,0);

            magnitude = 30;

        }

 

        // select component ROI

        cvSetImageROI( silh, comp_rect );

        cvSetImageROI( mhi, comp_rect );

        cvSetImageROI( orient, comp_rect );

        cvSetImageROI( mask, comp_rect );

 

        // calculate orientation

        angle = cvCalcGlobalOrientation( orient, mask, mhi, timestamp, MHI_DURATION);

        angle = 360.0 – angle;  // adjust for images with top-left origin

 

        count = cvNorm( silh, 0, CV_L1, 0 ); // calculate number of points within silhouette ROI

 

        cvResetImageROI( mhi );

        cvResetImageROI( orient );

        cvResetImageROI( mask );

        cvResetImageROI( silh );

 

        // check for the case of little motion

        if( count < comp_rect.width*comp_rect.height * 0.05 )

            continue;

 

        // draw a clock with arrow indicating the direction

        center = cvPoint( (comp_rect.x + comp_rect.width/2),

                          (comp_rect.y + comp_rect.height/2) );

 

        cvCircle( dst, center, cvRound(magnitude*1.2), color, 3, CV_AA, 0 );

        cvLine( dst, center, cvPoint( cvRound( center.x + magnitude*cos(angle*CV_PI/180)),

                cvRound( center.y – magnitude*sin(angle*CV_PI/180))), color, 3, CV_AA, 0 );

    }

}

 

 

int main(int argc, char** argv)

{

    IplImage* motion = 0;

    CvCapture* capture = 0;

   

    if( argc == 1 || (argc == 2 && strlen(argv[1]) == 1 && isdigit(argv[1][0])))

        capture = cvCaptureFromCAM( argc == 2 ? argv[1][0] – ‘0′ : 0 );

    else if( argc == 2 )

        capture = cvCaptureFromFile( argv[1] );

 

    if( capture )

    {

        cvNamedWindow( “Motion”, 1 );

       

        for(;;)

        {

            IplImage* image;

            if( !cvGrabFrame( capture ))

                break;

            image = cvRetrieveFrame( capture );

 

            if( image )

            {

                if( !motion )

                {

                    motion = cvCreateImage( cvSize(image->width,image->height), 8, 3 );

                    cvZero( motion );

                    motion->origin = image->origin;

                }

            }

 

            update_mhi( image, motion, 30 );

            cvShowImage( “Motion”, motion );

 

            if( cvWaitKey(10) >= 0 )

                break;

        }

        cvReleaseCapture( &capture );

        cvDestroyWindow( “Motion” );

    }

 

    return 0;

}

                               

#ifdef _EiC

main(1,”motempl.c”);

#endif

 

Функцию update_mhi() с незначительными модификациями вы может использовать в вашей программе для детектирования движений. А теперь поподробнее. Видео-поток можно захватывать с камеры cvCaptureFromCAM() или из файла cvCaptureFromFile(). После чего из потока последовательно выбираются кадр за кадром и передаётся в функцию update_mhi(). Параметрами которой являются – исходное изображение, изображение с детектированным движением, параметры для определения монохромности (необходимо для детектирования).

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

С помощью функции cvCvtColor() всё переводится в градации серого. Функция cvAbsDiff() вычисляет абсолютное различие между двумя массивами.

 

AbsDiff

 

void cvAbsDiff(

const CvArr* src1,

const CvArr* src2,

CvArr* dst

);

 

Параметры:

src1 – первый исходный массив данных,

src2 – второй исходный массив данных,

dst – выходной массив.

Абсолютное различие между двумя массивами вычисляется по формуле:

dst(I) = abs(src1(I) – src2(I)).

Все массивы должны иметь тот же самый тип данных и тот же самый размер (или ROI размер).

 

По выходному массиву с помощью функции cvThreshold() строится монохромное изображение, пороговое значение в котором задаются в параметрах функции (см. выше). Функция cvUpdateMotionHistory() обновляет изображение истории движения. Далее области движения на картинке преобразуются в выходной синий цвет, а после этого вызывается функция cvCalcMotionGradient(), которая вычисляет ориентацию движения. Функция cvSegmentMotion() находит все части движения и отмечает их, оценивая каждый (1,2, …). Всё, детектирование окончено. То, что находится дальше – предназначено для отброса мелких (по площади) движений и вывода на экран областей движений.

Размер и положение области движения вы найдёте в comp_rect, а направление движения в angle.

Работает на WordPress