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

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

25 Декабрь 2010

35. Подсчёт объектов (людей), пересекающих линию (People counting)

написано в рубрике: OpenCV, Распознавание образов — Кручинин Александр @ 12:35 ПП

Самый простейший путь при подсчёте объектов – это использование детектирования движений. Например, используя ту же функцию update_mhi из примеров OpenCV. Собственно решение напрашивается само – следить за выделяемыми областями движения, и как только происходит пересечение заданной линии, то увеличивать значение счётчика. Однако функция update_mhi не предназначена для слежения за объектами, поэтому эту часть придётся делать самостоятельно. На рисунке 35.1 показано обнаруженные центра движений (а), и изменения положений центров движений (б).

Рис. 35. 1. Центры движений в кадре (а), изменение движений (b)

Рис. 35. 1. Центры движений в кадре (а), изменение движений (b)

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

Для наблюдения за объектами будет использоваться следующая структура:

struct _OBJECT_

{

int x,y; //Координаты центра объекта

int w,h; //Ширина, высота объекта

int timer; //Задержка, позволяющая следить за объектом после остановки

int num; //Номер объекта

int object;//Соответствие с новым объектом

int inn; //Переменная, показывающая где находится объект относительно линии пересечения

}VObject[MAX_OBJECTS],BVObject[MAX_OBJECTS];

VObject – текущий массив объектов, BVObject – предыдущий массив объектом. При каждом анализе детектируемых зон, необходимо сначала сохранить предыдущий массив, например так:

memcpy(BVObject,VObject,sizeof(_OBJECT_)*MAX_OBJECTS);

Если в предыдущем массиве нет ни одного объекта, то при анализе детектируемых зон можно вызвать следующий листинг:

if (all_object<MAX_OBJECTS){

VObject[all_object].num=end_object;

VObject[all_object].timer=timer_1;

VObject[all_object].x=xx;

VObject[all_object].y=yy;

VObject[all_object].w=comp_rect.width;

VObject[all_object].h=comp_rect.height;

VObject[all_object].object=-1;

VObject[all_object].inn=2;

cur_object=all_object;

all_object++;

end_object++;

}

Здесь, end_object – глобальный счётчик номеров объектов (всегда увеличивается); timer_1 – максимальное время задержки, после которого объект, которому не соответствия движения, уничтожается; xx, yy – текущие координаты центра движения; comp_rect – размер зоны движения.

Если от предыдущего кадра сохранились объекты, то необходимо проверять соответствие со старыми объектами:

min=-1;

for(j=0;j<local_all_object;j++)

if (BVObject[j].object==-1)

{

dd=LengthLine(xx,yy,BVObject[j].x,BVObject[j].y);

if (dd<maxdist && (min==-1 || dd1>dd))

{

min=j;

dd1=dd;

}

}

if (min==-1) {

VObject[all_object].num=end_object;

VObject[all_object].inn=2;

end_object++;

}

else {

VObject[all_object].num=BVObject[min].num;

VObject[all_object].inn=BVObject[min].inn;

BVObject[min].object=0;

}

VObject[all_object].timer=timer_1;

VObject[all_object].x=xx;

VObject[all_object].y=yy;

VObject[all_object].w=comp_rect.width;

VObject[all_object].h=comp_rect.height;

VObject[all_object].object=-1;

cur_object=all_object;

all_object++;

Здесь, local_all_object – общее количество старых объектов. Если указатель ссылки на новый объект (BVObject[j].object) равен -1, то сравнивается его удалённость с текущим объектом, если она минимальна и меньше расстояния R, заданного переменной maxdist, то соответствие найдено. Если соответствие не было найдено, то создаётся новый объект, иначе текущему объекту присваиваются номера и положения относительно граничной линии старого объекта.

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

float ang=MakePolarF(center.x-Center.x,center.y-Center.y);//Из библиотеки ImagePak

byte inn=0;

if ((ang>In_ && ang<In_+PI) || (In_>PI  && ang<PI-In_)) {

inn=1;

}

if (VObject[cur_object].inn==0 && inn==1) All_In++;

if (VObject[cur_object].inn==1 && inn==0) {

All_Out++;

}

VObject[cur_object].inn=inn;

Здесь, center – это центр области движения; Center – центр заданной нами линии; In_ – угол в радианах вектора из центра линии в одну из сторон линии.

Этот метод прост и не лишён недостатков, возможно он не всегда работает, но я не нашёл подходящих видео для тестирования. Если у вас есть видео с камеры, расположенный сверху в коридоре (выходе/входе), через которые проходят много людей, и вам несложно передать эти видео мне для тестирования, то свяжитесь со мной!

24 Декабрь 2010

Подсчёт людей (people counting)

написано в рубрике: Распознавание образов — Кручинин Александр @ 8:43 ПП

Случайно в Интернете наткнулся на ряд фирм (как отечественных, так и зарубежных), занимающихся тем, что предоставляют аппаратно-программные средства подсчёта количества проходящих людей, например, через коридор. Сколько вошло и сколько вышло. Решение данного вопроса не очень сложно с использованием OpenCV (на базе процедуры детектирования движений).

На видео видно, что объекты иногда теряются и находятся, как новые. Это из-за небольшого времени, потраченного на данную разработку. Позднее выложу описание, как это работает.

Пример видео взят с сайта http://homepages.inf.ed.ac.uk/rbf/CAVIARDATA1/

14 Декабрь 2010

34. Создание программы детектирования движения на нескольких камерах с использованием OpenCV. Часть III

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

Текст результирующей программы детектирования движений вы можете скачать здесь:

Multiple Motion Detect http://vidikon.com/download/multimd.zip

На рисунке 34.1 представлена система для тестирования программы с 4 камерами (1 встроенная).

Рис. 34.1 Система для тестирования

Рис. 34.1 Система для тестирования

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

http://blog.vidikon.com/?p=423

http://blog.vidikon.com/?p=157

http://blog.vidikon.com/?p=103

Когда на камерах нет движения, то частота кадров снижается до минимальной (т.е. максимальная задержка. В противном случае увеличивается. Результаты смотрите в таблице и на рисунке 34.2.

Таблица

Количество камер, на которых обнаружено движение

Процент загруженности CPU

0

4-8%

1

10-16%

2

17-21%

3

22-29%

4

31-34%

Рис. 34.2. Загруженность CPU

Рис. 34.2. Загруженность CPU

Понятно, что если не управлять детектированием движения загрузка будет постоянно максимальной, что для описываемого случая приемлемо, но в ситуации, когда максимальная загрузка колеблется около 100% – это необходимое решение.

12 Декабрь 2010

33. Создание программы детектирования движения на нескольких камерах с использованием OpenCV. Часть II

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

Процесс управления нагрузкой центрального процессора в данном случае является простейшей задачей управления процессом распознавания образов (см. http://blog.vidikon.com/?p=157, http://blog.vidikon.com/?p=103 и др.). Когда в кадре не будет движений, то задержка кадров будет увеличена до максимума (MAXMSEC), в противном случае уменьшена до минимума (MINMSEC). Реализовать это достаточно просто – после получения количества областей движения необходимо вставить следующий код (Листинг 33.1).

Листинг 33.1

if (detect>0) detect–;

if (detect1>0) detect=5;

if (detect>0) waitkey=MINMSEC;

else waitkey=MAXMSEC;

Здесь detect1 показывает количество движений в области, а detect определяет порог, после скольких кадров без движений происходит увеличение задержки.

Для того, чтобы была возможность понаблюдать за видео потоками с камер, реализуем возможность включения и выключения окон (команды winoff и winon), что показано ниже (листинг 33.2).

Листинг 33.2

//Вызов перед циклом обработки кадров

char buf[256];

sprintf(buf,”Camera #%d”,localNumMultiThread);

if (localWindowCapture) cvNamedWindow( buf, 1 );

//Вызов в цикле обработки кадров

if (localWindowCapture!=WindowCapture[localNumMultiThread])

{

if (localWindowCapture) cvDestroyWindow(buf);

else cvNamedWindow( buf, 1 );

localWindowCapture=WindowCapture[localNumMultiThread];

}

if (localWindowCapture) cvShowImage( buf, image);

//Вызов после цикла обработки кадров

if (localWindowCapture) cvDestroyWindow(buf);

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

Листинг 33.3

if (detect>0 && OUTJPG)

{

strcpy(buf1,PATHJPG);

sprintf(buf2,”cam%dn”,localNumMultiThread);

strcat(buf1,buf2);

FILE *f;

strcpy(buf3,buf1);

for(long ii=JPG_CAM+1;ii<MAX_JPG_CAM;ii++)

{

strcpy(buf1,buf3);

sprintf(buf2,”%d.jpg”,ii);

strcat(buf1,buf2);

f=fopen(buf1,”r”);

if (f==NULL) break;

fclose(f);

}

cvSaveImage(buf1,image1);

}

Условие срабатывается только, когда задержка кадров минимальна и включен режим вывод jpg файлов OUTJPG. Затем создаётся имя файла по заданному в настройках пути PATHJPG, которое в цикле сравнивается с существующими на диске файлами, и, если такие есть, то генерируется следующее имя. После чего изображение сохраняется в файл.

Для того, чтобы записывать кадры в видеофайл первоначально необходимо осуществить захват устройства (Листинг 33.4).

Листинг 33.4

//Первоначально определяется перед циклом

CvVideoWriter* cvVideoWriter

//Внутри цикла

if (OUTAVI)

{

if (!cvVideoWriter)

{

strcpy(buf1,PATHAVI);

sprintf(buf2,”cam%d.avi”,localNumMultiThread);

strcat(buf1,buf2);

cvVideoWriter=cvCreateVideoWriter(buf1, CV_FOURCC(CODEC[0], CODEC[1], CODEC[2], CODEC[3]) ,10,

cvSize(image ->width/2,image->height/2));

logpolar_frame = cvCreateImage(cvSize(image ->width/2,image->height/2),IPL_DEPTH_8U,3);

cvZero(logpolar_frame );

logpolar_frame->origin = logpolar_frame->origin;

cvLogPolar( image1, logpolar_frame,cvPoint2D32f(image1->width/2,

image1->height/2),40,

CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS );

}

}

Первоначально создаётся имя и путь до видеофайла, а затем происходит захват устройства (cvCreateVideoWriter), причём наименование кодека будет браться из файла и храниться в массиве CODEC. Для уменьшения размеров видеофайла уменьшим в 2 раза все размеры картинки. А затем идёт некоторая странная вещь, которую я подсмотрел в книге «Learning OpenCV». Это вызов функции cvLogPolar. Сама по себе эта функция нам не нужна, однако она видимо меняет какие-то настройки IplImage, какие я не разобрался – посмотрел в Debug, но все параметры вроде бы не меняются, может быть идёт какое-то выравнивание на границу памяти, но я не стал смотреть. Но без этой функции дальнейшие попытки записи в видеофайл приводят к неудаче. Сама запись довольно проста (листинг 33.5).

Листинг 33.5

if (detect>0){

if (OUTAVI) {

cvResize(image1, logpolar_frame, 2);

cvWriteFrame(cvVideoWriter,logpolar_frame);

}

}

11 Декабрь 2010

32. Создание программы детектирования движения на нескольких камерах с использованием OpenCV. Часть I

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

Пример детектирования движений есть в примерах, поставляемых к самой библиотеке, а также описан здесь: http://blog.vidikon.com/?p=55. Однако кому-то наверняка захочется применить это на нескольких камерах с возможностью сохранения кадров с движением на жестком диске. Сформулируем требования к программе:

- поддержка нескольких камер (например, 1-4);

- детектирование движений на всех камерах;

- сохранение отдельных кадров с движением в файлы jpg;

- сохранение отдельных кадров с движением в видео файлы;

- сохранение информации о времени фиксирования кадров в log-файлы;

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

Программа будет писаться для Windows, но программист Linux может легко переделать её под себя, поменяв функции создания потоков и вызовы Win32 API функций системного времени и тиков процессора. Чтобы не тратить время на графический интерфейс, ограничимся файлом конфигураций и простейшей консолью. Поскольку вопросы описания работы с камерой в OpenCV достаточно широко освещены, не будем на них останавливаться, а сразу перейдём к созданию потоков для нескольких камер. Исходный код, вызываемый после захвата камер, представлен в листинге 32.1.

Листинг 32.1

DWORD lpT;

HANDLE h;

for(i=0;i<all_camera;i++)

{

if (!capture[i]) continue;

NumMultiThread=i;

h=CreateThread(NULL,0,MultiThread,NULL,0,&lpT);

Sleep(100);

CloseHandle(h);

}

Здесь глобальной переменной NumMultiThread присваивается номер потока, чтобы затем в функции MultiThread можно было отличить, какую камеру обрабатываем. Применяя Sleep, засыпаем, чтобы возникло никаких нюансов вроде состояния состязания. Делаем заготовку функции MultiThread (Листинг 32.2).

Листинг 32.2

DWORD WINAPI MultiThread(LPVOID)

{

int localNumMultiThread=NumMultiThread;

//Work with camera

if (capture[localNumMultiThread])

{

IplImage* motion = 0;

IplImage* image1 = 0;

int detect=0;

int waitkey=MINMSEC;

for(;;)

{

if (!capture[localNumMultiThread]) break;

IplImage* image = cvQueryFrame( capture[localNumMultiThread]);

if( !image )

break;

if( !motion )

{

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

cvZero( motion );

motion->origin = image->origin;

}

detect1=update_mhi( image, motion, 30,localNumMultiThread );

cvWaitKey(waitkey);

if (UnloadCapture[localNumMultiThread]==0) break;

}

cvReleaseCapture( &capture[localNumMultiThread] );

}

UnloadCapture[localNumMultiThread]=-1;

return 0;

}

Рассмотрим, какие здесь появились изменения по сравнению с обычным процессом детектирования. Для отличия видеокамер используется переменная localNumMultiThread, detect служит как характеристика количества движений в кадре, waitkey – определяет время ожидания в миллисекундах, поскольку у нас есть задача управления загруженностью, UnloadCapture служит для анализа команд пользователя – когда нам надо прекратить работу. Помимо этого нам необходимо преобразовать функцию update_mhi, добавив параметр, определяющий поток с видеокамеры, и добавив возвращаемое значение. Для обработки различных потоков приходится хранить в памяти массив параметров (Листинг 32.3).

Листинг 32.3

// ring image buffer

IplImage **buf[MAX_CAMERA] ;

int last[MAX_CAMERA];

// temporary images

IplImage *mhi[MAX_CAMERA]; // MHI

IplImage *orient[MAX_CAMERA]; // orientation

IplImage *mask[MAX_CAMERA]; // valid orientation mask

IplImage *segmask[MAX_CAMERA]; // motion segmentation map

CvMemStorage* storage[MAX_CAMERA]; // temporary storage

Видоизменив функцию:

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

внутри необходимо в соответствующие места добавить [camera], при использовании этих параметров. При обработке цикла областей движений (iterate through the motion components) необходимо учитывать количество областей движений больше некоторого минимального размера.

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

Листинг 32.4

char str[21];

int k=0;

Sleep(1000);

do{

printf(”MMDT#”);

scanf(”%20s”, str, 21);

k=0;

if (strcmp(str,”exit”)==0) break;

if (strcmp(str,”winon”)==0) {

for(i=0;i<allcam;i++)

WindowCapture[i]=1;

k=1;

}

if (strcmp(str,”winoff”)==0) {

for(i=0;i<allcam;i++)

WindowCapture[i]=0;

k=1;

}

if (k==0) printf(”Unknown command\n”);

}while(1);

При команде exit необходимо завершить все потоки и выйти из программы (Листинг 32.5). При команде winon происходит отображение графических окон с web-камер, а при winoff – закрытие.

Листинг 32.4

if (all_camera>0)

for(i=0;i<all_camera;i++)

if (capture[i])

{

UnloadCapture[i]=0;

do{

Sleep(100);

}while(UnloadCapture[i]!=-1);

}

return 0;

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

Работает на WordPress