Оглавление
1. OpenCV шаг за шагом. Введение.
2. Установка.
3. Hello World.
4. Загрузка картинки.
…
25. Обработка изображения — свёртка
26. Обработка изображения — операторы Собеля и Лапласа
27. Обработка изображения — детектор границ Кенни (Canny)
Края(границы) — это такие кривые на изображении, вдоль которых происходит резкое изменение яркости или других видов неоднородностей.
Проще говоря, край — это резкий переход/изменение яркости.
Причины возникновения краёв:
* изменение освещенности
* изменение цвета
* изменение глубины сцены (ориентации поверхности)
Получается, что края отражают важные особенности изображения и поэтому, целями преобразования изображения в набор кривых являются:
* выделение существенных характеристик изображения
* сокращение объема информации для последующего анализа
Самым популярным методом выделения границ является детектор границ Кенни.
Хотя работа Кенни была проведена на заре компьютерного зрения (1986), детектор границ Кенни до сих пор является одним из лучших детекторов.
Шаги детектора:
— Убрать шум и лишние детали из изображения
— Рассчитать градиент изображения
— Сделать края тонкими (edge thinning)
— Связать края в контура (edge linking)
Детектор использует фильтр на основе первой производной от гауссианы. Так как он восприимчив к шумам, лучше не применять данный метод на необработанных изображения. Сначала, исходные изображения нужно свернуть с гауссовым фильтром.
Границы на изображении могут находиться в различных направлениях, поэтому алгоритм Кенни использует четыре фильтра для выявления горизонтальных, вертикальных и диагональных границ. Воспользовавшись оператором обнаружения границ (например, оператором Собеля) получается значение для первой производной в горизонтальном направлении (Gу) и вертикальном направлении (Gx).
Из этого градиента можно получить угол направления границы:
Q=arctan(Gx/Gy)
Угол направления границы округляется до одной из четырех углов, представляющих вертикаль, горизонталь и две диагонали (например, 0, 45, 90 и 135 градусов).
Затем идет проверка того, достигает ли величина градиента локального максимума в соответствующем направлении.
Например, для сетки 3×3:
* если угол направления градиента равен нулю, точка будет считаться границей, если её интенсивность больше чем у точки выше и ниже рассматриваемой точки,
* если угол направления градиента равен 90 градусам, точка будет считаться границей, если её интенсивность больше чем у точки слева и справа рассматриваемой точки,
* если угол направления градиента равен 135 градусам, точка будет считаться границей, если её интенсивность больше чем у точек находящихся в верхнем левом и нижнем правом углу от рассматриваемой точки
* если угол направления градиента равен 45 градусам, точка будет считаться границей, если её интенсивность больше чем у точек находящихся в верхнем правом и нижнем левом углу от рассматриваемой точки.
Таким образом, получается двоичное изображение, содержащее границы (т.н. «тонкие края»).
В OpenCV, детектор границ Кенни реализуется функцией cvCanny(), которая обрабатывает только одноканальные изображения.
CVAPI(void) cvCanny( const CvArr* image, CvArr* edges, double threshold1, double threshold2, int aperture_size CV_DEFAULT(3) );
— выполнение алгоритма Canny для поиска границ
image — одноканальное изображение для обработки (градации серого)
edges — одноканальное изображение для хранения границ, найденных функцией
threshold1 — порог минимума
threshold2 — порог максимума
aperture_size — размер для оператора Собеля
// // пример работы детектора границ Кенни - cvCanny() // // robocraft.ru // #include <cv.h> #include <highgui.h> #include <stdlib.h> #include <stdio.h> IplImage* image = 0; IplImage* gray = 0; IplImage* dst = 0; int main(int argc, char* argv[]) { // имя картинки задаётся первым параметром char* filename = argc == 2 ? argv[1] : "Image0.jpg"; // получаем картинку image = cvLoadImage(filename,1); printf("[i] image: %s\n", filename); assert( image != 0 ); // создаём одноканальные картинки gray = cvCreateImage( cvGetSize(image), IPL_DEPTH_8U, 1 ); dst = cvCreateImage( cvGetSize(image), IPL_DEPTH_8U, 1 ); // окно для отображения картинки cvNamedWindow("original",CV_WINDOW_AUTOSIZE); cvNamedWindow("gray",CV_WINDOW_AUTOSIZE); cvNamedWindow("cvCanny",CV_WINDOW_AUTOSIZE); // преобразуем в градации серого cvCvtColor(image, gray, CV_RGB2GRAY); // получаем границы cvCanny(gray, dst, 10, 100, 3); // показываем картинки cvShowImage("original",image); cvShowImage("gray",gray); cvShowImage("cvCanny", dst ); // ждём нажатия клавиши cvWaitKey(0); // освобождаем ресурсы cvReleaseImage(&image); cvReleaseImage(&gray); cvReleaseImage(&dst); // удаляем окна cvDestroyAllWindows(); return 0; }
скачать иcходник (27-cvCanny.cpp)
Обратите внимание, как меняется картина, если увеличить размер оператора Собеля:
вот результат работы функции
cvCanny(gray, dst, 10, 100, 5);
И в качестве бонуса 🙂
Вот какой прикольный эффект можно получить, если найденные контуры вычесть из изображения.
Выглядит, как комикс 🙂
Для вычитания используем функцию cvSub():
cvSub — поэлементрная разница между двумя массивами
cvSubS — разница между элементами массива и скаляром
cvSubRS — разница между скаляром и элементами массива
CVAPI(void) cvSub( const CvArr* src1, const CvArr* src2, CvArr* dst, const CvArr* mask CV_DEFAULT(NULL));
— поэлементрная разница между двумя массивами:
dst(mask) = src1(mask) — src2(mask)
src1 — первый исходный массив
src2 — второй исходный массив
dst — целевой массив
mask — маска (8-битный однаканальный массив, указывающий какие элементы целефого массива могут быть изменены)
функция вычитает один массив из другого по формуле:
dst(I)=src1(I)-src2(I) if mask(I)!=0
массивы должны быть одного типа (кроме маски) и одинакового размера (или ROI).
// // Прикольный эффект с использованием детектора Кенни: // находятся контуры и вычитаются из изображения // // robocraft.ru // #include <cv.h> #include <highgui.h> #include <stdlib.h> #include <stdio.h> int main(int argc, char* argv[]) { IplImage *src=0, *dst=0, *dst2=0; // имя картинки задаётся первым параметром char* filename = argc >= 2 ? argv[1] : "Image0.jpg"; // получаем картинку в градациях серого src = cvLoadImage(filename, 0); printf("[i] image: %s\n", filename); assert( src != 0 ); // покажем изображение cvNamedWindow( "original", 1 ); cvShowImage( "original", src ); // получим бинарное изображение dst2 = cvCreateImage( cvSize(src->width, src->height), IPL_DEPTH_8U, 1); cvCanny(src, dst2, 50, 200); cvNamedWindow( "bin", 1 ); cvShowImage( "bin", dst2); //cvScale(src, dst); cvSub(src, dst2, dst2); cvNamedWindow( "sub", 1 ); cvShowImage( "sub", dst2); // ждём нажатия клавиши cvWaitKey(0); // освобождаем ресурсы cvReleaseImage(&src); cvReleaseImage(&dst); cvReleaseImage(&dst2); // удаляем окна cvDestroyAllWindows(); return 0; }
Ещё вариации функции вычитания cvSubS и cvSubRS:
/* dst(mask) = src(mask) - value = src(mask) + (-value) */ CV_INLINE void cvSubS( const CvArr* src, CvScalar value, CvArr* dst, const CvArr* mask CV_DEFAULT(NULL)) { cvAddS( src, cvScalar( -value.val[0], -value.val[1], -value.val[2], -value.val[3]), dst, mask ); }
— разница между элементами массива и скаляром
/* dst(mask) = value - src(mask) */ CVAPI(void) cvSubRS( const CvArr* src, CvScalar value, CvArr* dst, const CvArr* mask CV_DEFAULT(NULL));
— разница между скаляром и элементами массива
src — первый исходный массив
value — скаляр из которого производится вычитание
dst — целевой массив
mask — маска (8-битный однаканальный массив, указывающий какие элементы целефого массива могут быть изменены)
функция вычитает каждый элемент массива из скаляра:
dst(I)=value-src(I) if mask(I)!=0
массивы должны быть одного типа (кроме маски) и одинакового размера (или ROI).
Получающиеся картинки мне очень понравились и я на скорую руку набросал сервис Генератора комиксов 🙂
примеры его работы можно посмотреть здесь:
например, вот пример работы Генератора:
Далее: 28. Преобразование Хафа
Ссылки
http://en.wikipedia.org/wiki/Canny_edge_detector
http://ru.wikipedia.org/wiki/Выделение_границ
Оригинальная статья: JOHN CANNY, A Computational Approach to Edge Detection
И.М.Журавель «Краткий курс теории обработки изображений»: Границы изображений: Края и их обнаружение
Дополнительно:
Deriche edge detector
28 комментариев на «“27. OpenCV шаг за шагом. Обработка изображения — детектор границ Кенни (Canny)”»
чтобы получить цветное изображение, я так понимаю нужно разбить исходное на три ч/б, выполнить поиск границ, затем вычитание, а затем опять слить их в одно да?
точно так 🙂
ок, надо попробовать в реальном времени, наверно прикольно получится
сделал, но получилось не совсем так, какие значения для cvCanny использовали???
и у меня контуры цветные а не чёрные получились
экспериментируйте 🙂 но если вы хотите повторитьГенератор комиксов , то там ещё применена сегментация 😉
ок, ща ползунки прикручу
9 глава, Mean-shift segmentation, да???
угу — cvPyrMeanShiftFiltering(), но обратите внимание, что в версии 2.1 в реализации этой функции есть ошибка. Впрочем, у меня она нормально работала в Release-версиях программы.
Можете погуглить и найти, как пофиксить эту ошибку, а затем пересобрать библиотеку, или же использовать версию 2.2, где эта ошибка исправлена.
ошибка заключается в исключении, которое происходит непонятно почему?
поставил 2.2, теперь даже не компилится, cvCanny ваще нет, Canny принимает не IplImage а Mat, как перейти на 2.2, теперь вместо IplImage Mat писать чтоли???
у меня таких проблем не возникало 🙂
нашёл! она в legacy лежит, только теперь камера на ноуте не работает, а на компе всё норм вроде
всё, работает! только ужасно медленно, примерно 1 кадр в 5-10 секунд, нельзя ли как-нить ускорить сегментацию?
если сможете это сделать — расскажите :))
??
применил вместо cvPyrMeanShiftFiltering cvPyrSegmentation, результат неплохой, примерно кадр в секунду
только всёравно линии цветные а не чёрные как у вас, может вы ещё чего добавили?
И меня интересуют линии эти черные) хоть ты тресни, но разноцветными получаются. Колдунство какое-то)
у меня нормально получилось как в статье. Правда я на c# пишу. Предоставляю код, может он подскажет вам как изменить ваш алгоритм:
Автор noonv, добрый день. Я только знакомлюсь с OpenCV и у меня стоит задание распознать сколько на рисунке кругов и сколько прямоугольников. Хочу написать эту программу в VisualStudio2017 с использованием библиотеки OpenCv 2014 или 2015. Буду очень благодарна, если Вы подскажите, какие настройки нужно сделать, чтобы все Ваши программы на стирание контуров и т. д. заработали у меня в Вижуал.
Привет! Думаю, сейчас стоит использовать С++ интерфейс библиотеки OpenCV.
Пример: Finding contours in your image
Спасибо. А как Вы порекомендуете дальше писать эту программу? Если у Вас есть идеи готова заплатить.
OpenCV shape detection
Спасибо за ссылку. Но мне нужно посчитать сколько на рисунке четырехугольников и сколько кругов. https://docs.google.com/document/d/1K1iiHKvLjuecAPoV9d0wrVAdIjfk6tNTG8Y7bktIgqQ/edit Может, у Вас получится что-то подобрать к этому заданию? Буду очень очень благодарна!)))
почему вот это не работет:
сразу же вылетает «Access violation», а по старому либо ваще не работает либо чёрный экран
У меня переделанный пример с cvQueryFrame выдает ошибку:
Необработанное исключение в «0x766eb727» в «cv1.exe»: Исключение Microsoft C++: cv::Exception по адресу 0x0015e71c…
А с cvLoadImage без проблем. Как сделать сделать правильно манипуляции с захваченным видео на лету?
Спасибо за статью. Если определять контуры по исходному изображению, то видно много шумовых контуров. Сначала наеобходимо убрать шум из мелких деталей, те немного размыть изображение и определить контуры, потом еще немного размыть. Предлагаю готовый код. Что вам мешает найти контуры на изображениии комиксов?
Еще лучше размывать изображение и увеличивать контрастность в несколько итераций.
Лучший результат позволяет получить следующий код: