Распознавание образов с OpenCV: Контуры против Haartraining

в 10:36, , рубрики: computer vision, opencv, Алгоритмы, классификация, Песочница, распознавание изображений, распознавание образов, распознавание объектов, метки: , , , , , ,

Привет! Передо мной встала задача реализовать распознавание дорожных знаков с видео потока. Так как с задачами подобного рода я раньше не сталкивался, то процесс реализации само собой предполагает предварительное долгое «курение» форумов и безжалостные издевательства над чужими примерами. Поэтому решил собрать всё прочитанное в одном месте для будущих поколений, а так же, в ходе повествования, задать Хабру несколько вопросов.

Прелюдии.

Итак, после изучения всех средств, которые возможно использовать для реализации поставленной задачи, я остановился на среде разработки Microsoft Visual Studio© 2010, с использованием чудесной библиотеки OpenCV.

Сам процесс работы с OpenCV предполагает предварительные танцы с бубном, о которых есть достаточно подробных описаний:

Установка OpenCV 2.3.1

в том числе и на Хабре:

Настройка проекта под OpenCV от пользователя skynoname, за которые ему отдельное спасибо!

Первый раз я настраивал среду по первой ссылке, второй раз пришлось перенастроить, используя мануал с Хабра. Советую пользоваться второй ссылкой, потому как только после танцев с бубном, описанных в ней, мне удалось запустить createsampes и haartraining, о которых читайте ниже. Так же советую устанавливать именно OpenCV 2.3.1 потому как мне показалась она более стабильной, нежели 2.4.4.

Проба пера.

Установив среду я приступил, наконец, к созданию своих первых HelloWord`ов. Советую пробежаться по OpenCV шаг за шагом. Я перепробовал массу англоязычных и русскоязычых статей и форумов. Этот сборник статей наиболее понятно описывает функционал библиотеки OpenCV. В первых статьях этого мануала описывается довольно простой алгоритм захвата видео с web-камеры, а в кульминации этих статей я нашел, наконец, как выделять и сравнивать контуры.

Первые плоды.

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

Код
 #include "StdAfx.h"

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

int main(int argc, char* argv[])
{
	    char* ustupi = argc >= 2 ? argv[1] : "ustupi.jpg";
		IplImage  *ustupit= cvLoadImage(ustupi);
 // для хранения контуров
			   CvMemStorage* storage = cvCreateMemStorage(0);
			   CvMemStorage* storage_ustupi = cvCreateMemStorage(0);
			   CvSeq* contoursF=0, *contours_ustupi=0;			
        CvCapture* capture = cvCreateCameraCapture(CV_CAP_ANY); 
        assert( capture );
        double width = cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH);
        double height = cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT);
        printf("[i] %.0f x %.0fn", width, height );
        IplImage* frame=0;
		cvNamedWindow("reference",CV_WINDOW_AUTOSIZE);
        printf("[i] press Enter for capture image and Esc for quit!nn");
		IplImage* grayust = cvCreateImage(cvGetSize(ustupit),IPL_DEPTH_8U, 1);
		IplImage* binust = cvCreateImage(cvGetSize(ustupit),IPL_DEPTH_8U, 1);

		// находим контуры Шаблона
				cvCvtColor(ustupit, grayust, CV_RGB2GRAY);
				cvCanny(grayust, binust, 500,100, 3);
				int contoursContT = cvFindContours( binust, storage_ustupi, &contours_ustupi, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));
				CvSeq* seq_ustupi = contours_ustupi;
				if(contours_ustupi!=0){
                for(CvSeq* seq_ustupi = contours_ustupi;	seq_ustupi!=0;seq_ustupi = seq_ustupi->h_next){
                        // рисуем контур шаблона
             //           cvDrawContours(ustupit, seq_ustupi, CV_RGB(255,216,0), CV_RGB(0,0,250), 0, 2, 8); (убрать комментарий, чтобы увидеть контур)
                        }
		}		
        while(true){
                frame = cvQueryFrame( capture );
                char c = cvWaitKey(33);
                if (c == 27) {break;}
				IplImage* grayf = cvCreateImage(cvGetSize(frame),IPL_DEPTH_8U, 1);
				IplImage* binF = cvCreateImage(cvGetSize(frame),IPL_DEPTH_8U, 1);
				cvCvtColor(frame, grayf, CV_RGB2GRAY);
				
				        // получаем границы изображения и шаблона
			   cvCanny(grayf, binF, 500,100, 3);
			   
			   			    // находим контуры изображения
				int contoursContF = cvFindContours( binF, storage, &contoursF, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));
				
				        // для отметки контуров
					CvFont font;
					cvInitFont(&font, CV_FONT_HERSHEY_PLAIN, 1.0, 1.0);
					char buf[128];
					int counter=0;
					IplImage* bin = cvCreateImage(cvGetSize(frame),IPL_DEPTH_8U, 1);
        if(contoursF!=0){
                for(CvSeq* seq0 = contoursF;	seq0!=0;seq0 = seq0->h_next){
                        // рисуем контур
            //            cvDrawContours(frame, seq0, CV_RGB(255,216,0), CV_RGB(0,0,250), 0, 2, 8); (убрать комментарий, чтобы увидеть контур)
                        }
		}
        CvSeq* seqM=0, *seqK=0;
        double matchM=1000, matchP=1000;
        // обходим контуры изображения 
        counter=0;

		 if(contoursF!=0){
                // поиск лучшего совпадения контуров по их моментам 
                for(CvSeq* seq0 = contoursF;seq0!=0;seq0 = seq0->h_next){
                        double match0 = cvMatchShapes(seq0, seq_ustupi, CV_CONTOURS_MATCH_I3);
                        if(match0<matchM){
                                matchM = match0;
                                seqM = seq0;
                        }
						if(match0<0.05 && match0>0.01){cvShowImage("template", ustupit);} // чем меньше параметр match, тем больше схожи контуры
						if(match0>500){cvDestroyWindow("template");}
				}
        }

				cvShowImage("reference",frame);
        }

		// освобождаем ресурсы
        cvReleaseCapture( &capture );
		cvDestroyWindow("reference");
		cvDestroyWindow("frame");
			
        return 0;

}

Показав эту картинку в web-камеру, вы увидите всплывающее окно со знаком.

Знак уступи дорогу

image

Добавлю еще настройки среды, с которыми работает проект:

Настройки

VC++Directories>Library Directories > C:OpenCV2.4.4opencvbuildx86vc10lib;
C/C++>AdditionalIncludeDirectories>C:OpenCV2.4.4opencvbuildincludeopencv;C:OpenCV2.4.4opencvbuildinclude
Linker > General > Enable Incremental Linking > No (/INCREMENTAL:NO)
Linker > Input >
opencv_video244d.lib;
opencv_ml244d.lib;
opencv_legacy244d.lib;
opencv_imgproc244d.lib;
opencv_highgui244d.lib;
opencv_core244d.lib;
opencv_objdetect244d.lib;

Муки творчества.

Добившись обнаружения одного знака, приступил к добавлению распознавания второго знака. И тут начались ложные срабатывания, программа путала знаки и работа крайне нестабильно. Добавлял другие знаки просто копируя часть кода от текущего знака, и меняя переменные. Хотел сделать процедуру, но как то руки не дошли, потому что понял, что этот метод мне не интересен. Прибегать к сравнению по количеству пикселей в контуре и подобным методам не стал.

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

Второй акт танцев с бубном.

В итоге повернул в сторону тренировки каскадов. «Покурив» в этом направлении понял что мне нужны два инструмента createsampes и haartraining. Но их exe`шники у меня отсутствовали, а компилироваться отказывались. На тот момент версия OpenCV у меня была 2.4.4, настроенная по первой статье, во второй же статье я впервые прочитал про использование Cmake при установке. В итоге решил скачать версию 2.3.1 и переустановить библиотеку. После чего мне удалось запустить нужные инструменты через командную строку и встал вопрос как с ними работать. Все точки над «и» расставили статьи, в которых показаны параметры с которыми нужно запускать createsampes и haartraining с подробным описанием этих параметров.

Код с чистого листа.

Окончательно отказавшись от старого метода, код был переписан для подстановки обученных каскадов.

Код 2.0

#include "stdafx.h"
#include <stdlib.h>
#include <opencv/cv.h>
#include <opencv/highgui.h>

using namespace cv;

int main(int argc, char** argv) {

    Mat frame, gray;
    string object_cascade = "haarustupi.xml";
    CascadeClassifier haar(object_cascade);
    VideoCapture cap(0);
    namedWindow("Video", 1);
    vector<Rect> objects;

    while (true) {
        cap >> frame;
        cvtColor(frame, gray, CV_BGR2GRAY);

        haar.detectMultiScale(gray, objects, 1.9, 10, 0,Size(50, 50) );
        for (vector<Rect>::const_iterator r = objects.begin(); r != objects.end(); r++)
            rectangle(frame, r->tl(), r->br(), Scalar(0, 0, 255));

        imshow("Video", frame);

        if (waitKey(33) >= 0) break;
    }

    return (EXIT_SUCCESS);
}

Среду настраиваем точно так же как и в прошлом проекте.

ПовторениЯ — отцы учения.

Дело за «малым» обучить каскады.)
Тут начинается самое интересное. После чего я решил писать о всех этих мытарствах на хабр и просить совета.
Я заготовил 500 изображений размером 1600х1200. и одно изображение со знаком уступи дорогу размером 80х80. Одного изображения будет достаточно, потому что мы детектируем определенный объект, а не огромное разнообразие лиц.

Итак, заготовив картинки и создав файл neg.dat со структурой

negative/n (1).jpg
negative/n (2).jpg
negative/n (3).jpg
negative/n (4).jpg
...
negative/n (500).jpg

запускаем файл opencv_createsamples.exe через CMD со следующими параметрами

C:OpenCV2.3.1buildcommonx86opencv_createsamples.exe -vec C:OpenCV2.3.1buildcommonx86positive.vect -bg C:OpenCV2.3.1buildcommonx86neg.dat -img C:OpenCV2.3.1buildcommonx86ustupi.jpg  -num 500 -w 50 -h 50 -bgcolor 0 -bgthresh 0 -show

параметр -show показывает создаваемые позитивные картинки, но они, в отличие от указанных в других статьях
картинок, получается вот такая

маленькая

Распознавание образов с OpenCV: Контуры против Haartraining

Т.е утилита обрезает bg-картинку под размер позитивной картинки. Изменение параметров -w и -h результата не дают и заднего фона все равно почти не видно. Если вдруг кто знает в чем тут дело, поделитесь соображениями. Размер негативных изображений уменьшал до 800х600 — результат тот же.

Ну далее необходимо запустить opencv_haartraining.exe с параметрами

C:OpenCV2.3.1buildcommonx86opencv_haartraining.exe -data C:OpenCV2.3.1buildcommonx86haarustupi -vec C:OpenCV2.3.1buildcommonx86positive.vect -bg C:OpenCV2.3.1buildcommonx86neg.dat -npos 500 -nneg 500 -nstages 6 -nsplits 2 -w 20 -h 24 -mem 1536 -mode ALL -nonsym -minhitrate 0.999 -maxfalsealarm 0.5

после чего вы получите долгожданный xml-файл, который можно подгружать в исходный код программы.
В итоге каскад слегка обучается и, с большим количеством ложных срабатываний, реагирует на, полюбившуюся мне, картинку знака уступи дорогу.
Но я не могу добиться точных срабатываний, как мне кажется, из-за того что обрезается задний фон в позитивных изображениях. И никак не получаются картинки как в мануалах. Но остается еще вариант увеличить количество этапов обучения и, нагрузив свой компьютер на весь день, дождаться пока каскад будет более «образованным». Чем я и планирую заняться до появления других идей.

Эпилог

Вот такая получилась первая HelloHabr-статья у меня. Жду ваших замечаний о стиле изложения материала. Ну и конечно советов по теме.
Надеюсь после полученных советов будет чем продолжить повествование.

Автор: Guzzle

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js