На прошлом уроке мы научились превращать набор особых точек, найденных специальным детектором особых точек, в граф. Там же я объяснил, зачем это вообще надо. Сегодня мы будем изучать такую область науки о компьютерном зрении, как нахождение областей интереса на изображении. Как правило, это вторая часть этапа обработки изображений (см. первый урок). И так, предположим, нам надо найти на изображении дорожный знак. Пусть мы пока ограничимся только поиском знаков «кирпич». Вот наша рабочая картинка:
![](https://habrastorage.org/getpro/habr/upload_files/13a/8bb/bb5/13a8bbbb52f60bad2637a2fb6fd3c725.png)
Как мы можем сузить место поиска, чтобы найти знак «кирпич». Бросается в глаза, что он ярко красный. Может, попробуем выделить красный канал (ниже вы увидите, почему так делать не надо):
import cv2
my_photo = cv2.imread('bricks\\dsc_0263.jpg')
red_channel = my_photo[:,:,2]
cv2.imshow('MyPhoto', red_channel)
cv2.waitKey(0)
cv2.destroyAllWindows()
Вот такую картинку мы получили:
![](https://habrastorage.org/getpro/habr/upload_files/452/783/cf3/452783cf3eb4e1d8f78b00bb968b5e37.png)
Что делаем дальше? Давайте попробуем применить порог:
import cv2
import numpy as np
my_photo = cv2.imread('bricks\\dsc_0263.jpg')
red_channel = my_photo[:,:,2]
bin_img = np.zeros(my_photo.shape)
bin_img[red_channel > 200] = [0, 0, 255]
cv2.imshow('MyPhoto', bin_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
И что же у нас получилось? А вот что:
![](https://habrastorage.org/getpro/habr/upload_files/8cb/014/f55/8cb014f55d76ce55c68254bc3c61d771.png)
Казалось бы, ура, мы нашли знак. Как локализовать скопление ярких точек, другой вопрос. Тут проблема в другом. Сработает ли наш метод так же хорошо на другой картинке, например, вот на такой:
![](https://habrastorage.org/getpro/habr/upload_files/cc2/a6a/679/cc2a6a679de23ddb153a686bcde33671.png)
Применяем ту же программу, с тем же эмпирически найденным порогом:
![](https://habrastorage.org/getpro/habr/upload_files/843/8b4/811/8438b4811b04315cb74d1029514ea93b.png)
Вот это подстава, да? Программа среагировала на шум, но не «увидела» знак.
А если попробовать преобразовать в формат HSV и выделить именно красный цвет? Напомню, что значит этот формат. Канал H обозначает цвет. В зависимости от числа меняется оттенок. Канал S – насыщенность, при минимальном значении это белый цвет, при максимальном – цвет, соответствующий значению канала H. Канал V – это яркость. Минимальное значение – черный, максимальное – цвет, соответствующий комбинации H и S.
Попробуем выделить канал H:
import cv2
import numpy as np
my_photo = cv2.imread('bricks\\White1.jpg')
img = cv2.cvtColor(my_photo, cv2.COLOR_BGR2HSV)
h_channel = my_photo[:,:,0]
bin_img = np.zeros(my_photo.shape)
bin_img[(h_channel < 40) * (h_channel > 20)] = [0, 0, 255]
cv2.imshow('h_channel', h_channel)
cv2.imshow('result', bin_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Вот что у нас получилось:
![](https://habrastorage.org/getpro/habr/upload_files/e6d/9e7/f3b/e6d9e7f3b192207e2392861284b1412f.png)
У нас попали в наш диапазон черные стволы деревьев, видимо, они имеют чуть заметный красноватый оттенок. Можно попробовать выделить по яркости:
import cv2
import numpy as np
#my_photo = cv2.imread('bricks\\dsc_0263.jpg')
my_photo = cv2.imread('bricks\\White1.jpg')
#my_photo = cv2.imread('bricks\\videlenka(24).jpg')
img = cv2.cvtColor(my_photo, cv2.COLOR_BGR2HSV)
h_channel = my_photo[:,:,0]
v_channel = my_photo[:,:,2]
bin_img = np.zeros(my_photo.shape)
bin_img[(h_channel < 70) * (h_channel > 20) * (v_channel>100)] = [0, 0, 255]
cv2.imshow('v_channel', v_channel)
cv2.imshow('result', bin_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Вот что у нас получилось:
![](https://habrastorage.org/getpro/habr/upload_files/34b/d73/83a/34bd7383aaed1885c64a8ec876c19831.png)
Относительно неплохо. «Дыры» можно заделать морфологическими операциями (операция закрытие):
kernel = np.ones((5,5),np.uint8)
closing = cv2.morphologyEx(bin_img, cv2.MORPH_CLOSE, kernel)
И вот что у нас получится в итоге:
![](https://habrastorage.org/getpro/habr/upload_files/e69/731/aba/e69731aba32f1fed7eb774e409039a70.png)
У нас остался шум. Тоже можно убрать методом морфологии, на это раз применим операции открытие:
opening = cv2.morphologyEx(bin_img, cv2.MORPH_OPEN, kernel)
Если сначала применить открытие, а потом закрытие, то получим вот что:
![](https://habrastorage.org/getpro/habr/upload_files/47f/187/3ec/47f1873ec62193db126f1a9d59d4ffc1.png)
Шум исчез, но получилось не очень красиво. А если наоборот:
![](https://habrastorage.org/getpro/habr/upload_files/790/4b6/2c5/7904b62c5051278f3feef6e63b3cc5af.png)
Тут не получилось убрать шум.
Ладно, шум, если что, можно будет убрать другим способом. Вот полный текст программы:
import cv2
import numpy as np
my_photo = cv2.imread('bricks\\1.jpg')
img = cv2.cvtColor(my_photo, cv2.COLOR_BGR2HSV)
h_channel = my_photo[:,:,0]
v_channel = my_photo[:,:,2]
bin_img = np.zeros(my_photo.shape)
bin_img[(h_channel < 70) * (h_channel > 20) * (v_channel>100)] = [0, 0, 255]
cv2.imshow('h_channel', h_channel)
cv2.imshow('v_channel', v_channel)
cv2.imshow('my_photo', my_photo)
kernel = np.ones((5,5),np.uint8)
opening = cv2.morphologyEx(bin_img, cv2.MORPH_OPEN, kernel)
closing = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel)
cv2.imshow('result', closing)
cv2.waitKey(0)
cv2.destroyAllWindows()
А сейчас проверим, а сработает ли наш метод на другой картинке:
![](https://habrastorage.org/getpro/habr/upload_files/419/555/c4e/419555c4ea48ecd6861c611ee1d165b2.png)
Как видим, сработал. Еще одна картинка:
![](https://habrastorage.org/getpro/habr/upload_files/78e/ce5/183/78ece5183be7b674d1eb57aa88095f86.png)
А тут не увидел. Зато среагировал на знак «Остановка запрещена». Кстати, а как быть, если мы хотим не «Кирпич» детектировать, а другой знак? Может, попробовать как-то реагировать на форму? Давайте попробуем поискать круглые объекты.
Для начала, вспомним, как выделять контур:
import cv2
import numpy as np
my_photo = cv2.imread('bricks\\White1.jpg')
img_grey = cv2.cvtColor(my_photo,cv2.COLOR_BGR2GRAY)
#set a thresh
thresh = 100
#get threshold image
ret,thresh_img = cv2.threshold(img_grey, thresh, 255, cv2.THRESH_BINARY)
#find contours
contours, hierarchy = cv2.findContours(thresh_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
#create an empty image for contours
img_contours = np.zeros(my_photo.shape)
# draw the contours on the empty image
cv2.drawContours(img_contours, contours, -1, (255,255,255), 1)
cv2.imshow('origin', my_photo) # выводим итоговое изображение в окно
cv2.imshow('contours', img_contours) # выводим итоговое изображение в окно
cv2.waitKey()
cv2.destroyAllWindows()
Посмотрим, как будет выделяться контур на разных картинках:
![](https://habrastorage.org/getpro/habr/upload_files/252/3ea/9db/2523ea9dbe5ad91ecf0d71e1938d236b.png)
![](https://habrastorage.org/getpro/habr/upload_files/7ad/ea6/49f/7adea649fa58ae975f661f5cbee7e621.png)
В обоих случаях окружность видна, но много шума. Как отделить от шума? В OpenCV есть замечательная функция HoughCircles (в нее, судя по всему, уже встроен детектор контуров, так как она работает с самим изображением):
import cv2
import numpy as np
my_photo = cv2.imread('bricks\\1.jpg')
#my_photo = cv2.imread('bricks\\White1.jpg')
img_grey = cv2.cvtColor(my_photo,cv2.COLOR_BGR2GRAY)
rows = img_grey.shape[0]
circles = cv2.HoughCircles(img_grey, cv2.HOUGH_GRADIENT, 1, rows / 8,
param1=100, param2=30,
minRadius=1, maxRadius=100)
res = np.zeros(my_photo.shape)
if circles is not None:
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
center = (i[0], i[1])
# circle center
cv2.circle(res, center, 1, (0, 100, 100), 3)
# circle outline
radius = i[2]
cv2.circle(res, center, radius, (255, 0, 255), 3)
cv2.imshow('origin', my_photo) # выводим итоговое изображение в окно
cv2.imshow('res', res) # выводим итоговое изображение в окно
cv2.waitKey()
cv2.destroyAllWindows()
Данная программа нашла довольно много окружностей:
![](https://habrastorage.org/getpro/habr/upload_files/1ee/e9e/4b9/1eee9e4b989ba756fed76893d7e44992.png)
Это можно отрегулировать порогами, задающиеся параметрами param1 и param2:
circles = cv2.HoughCircles(img_grey, cv2.HOUGH_GRADIENT, 1, rows / 8,
param1=150, param2=50,
minRadius=1, maxRadius=100)
![](https://habrastorage.org/getpro/habr/upload_files/d55/8dd/00a/d558dd00a04c931a68fc22e014b6feee.png)
Более наглядно будет, если отобразить найденные окружности на исходном изображении:
res = np.zeros(my_photo.shape)
if circles is not None:
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
center = (i[0], i[1])
# circle center
cv2.circle(my_photo, center, 1, (0, 100, 100), 3)
# circle outline
radius = i[2]
cv2.circle(my_photo, center, radius, (255, 0, 255), 3)
cv2.imshow('origin', my_photo) # выводим итоговое изображение в окно
![](https://habrastorage.org/getpro/habr/upload_files/9f1/8c0/4b4/9f18c04b4ef9b63612bf29a2d34b74ca.png)
А как сработает на другой картинке?
Смотрим:
![](https://habrastorage.org/getpro/habr/upload_files/abc/04f/9ea/abc04f9ea3aaac3e0a3e70760c9e978e.png)
Здесь, как видим, шума слишком много. Но один из кругов описывает искомый знак.
К сожалению, метод поиска круга срабатывает тоже не всегда. Вот на этой фотографии программа нашла только шум:
![](https://habrastorage.org/getpro/habr/upload_files/428/567/5a4/4285675a45490f74d80e8cadae4588a1.png)
Как можно помочь в данной ситуации?
Первое, можно попробовать «подсунуть» функции HoughCircles не целую картинку, а готовый контур. Только контур следует нарисовать в матрице типа uint8, вот так:
#create an empty image for contours
img_contours = np.uint8(np.zeros((my_photo.shape[0],my_photo.shape[1])))
И что мы получим? Вот что (знаки нашел, но шум остался):
![](https://habrastorage.org/getpro/habr/upload_files/cbc/ca1/c9d/cbcca1c9d23b280bc1beb8553b17615b.png)
Другой вариант – предварительная фильтрация (кто там в комментах возмущался, зачем я пишу про фильтры?):
filterd_image = cv2.medianBlur(my_photo,7)
В данном случае была применена медианная фильтрация (но функции HoughCircles была «скормлена» картинка) и вот что получилось:
![](https://habrastorage.org/getpro/habr/upload_files/4ac/544/5f9/4ac5445f966ff6e0c1781b6923a5bd23.png)
На этом урок закончен, но будет еще. До новых встреч.