Привет! Передо мной встала задача реализовать распознавание дорожных знаков с видео потока. Так как с задачами подобного рода я раньше не сталкивался, то процесс реализации само собой предполагает предварительное долгое «курение» форумов и безжалостные издевательства над чужими примерами. Поэтому решил собрать всё прочитанное в одном месте для будущих поколений, а так же, в ходе повествования, задать Хабру несколько вопросов.
Прелюдии.
Итак, после изучения всех средств, которые возможно использовать для реализации поставленной задачи, я остановился на среде разработки Microsoft Visual Studio© 2010, с использованием чудесной библиотеки OpenCV.
Сам процесс работы с OpenCV предполагает предварительные танцы с бубном, о которых есть достаточно подробных описаний:
в том числе и на Хабре:
Настройка проекта под 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-камеру, вы увидите всплывающее окно со знаком.
Добавлю еще настройки среды, с которыми работает проект:
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 с подробным описанием этих параметров.
Код с чистого листа.
Окончательно отказавшись от старого метода, код был переписан для подстановки обученных каскадов.
#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 показывает создаваемые позитивные картинки, но они, в отличие от указанных в других статьях
картинок, получается вот такая
Т.е утилита обрезает 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