Рассматриваются две задачи компьютерного зрения – поиск лиц на изображении и контуров имеющихся на изображении объектов. Обе задачи решаются средствами библиотеки OpenCV. Вдобавок поиск лиц выполняется нейронной сетью mtcnn.
Многие алгоритмы детектирования объектов основаны на предложенном в [1] подходе, предполагающем:
В каскадной модели отдельные классификаторы позволяют последовательно уточнять расположение окон, охватывающих, например, лица.
Повышение качества детектирования лиц может быть достигнуто за счет расширения набора примитивов Хаара [4], использования иных функций для извлечения признаков, а также в результате модификации слабых классификаторов.
Известны различные библиотечные реализации детекторов фронтальных лиц [5].
Ниже приводятся Python-версии программ, выполняющих поиск фронтальных лиц, использующих детекторы библиотек OpenCV (open source computer vision library) и mtcnn (multi-task cascaded convolutional neural network).
Найденные на фотографии лица, обводятся прямоугольной рамкой заданного цвета (рис. 1).
Рис. 1. Лица найдены
Вдобавок, работая с mtcnn, можно вывести ключевые точки (рис. 2).
Рис. 2. Вывод ключевых точек
Найденные лица могут быть переданы программе, выполняющей их распознавание [6].
Установка библиотек OpenCV и mtcnn:
pip install opencv-python
pip install mtcnn
Информацию о библиотеках предоставят следующие команды:
import cv2
print(cv2.__version__)
pip show mtcnn
import mtcnn
print(mtcnn.__version__)
OpenCV использует каскадный классификатор П. Виолы и М. Джонса [1], выделяющий значимые признаки с помощью алгоритма AdaBoost [2, 3].
Для поиска лиц с OpenCV предварительно нужно загрузить ранее обученную модель, сохраненную в файле haarcascade_frontalface_default.xml [7]. После этого выделение лиц обеспечит следующий код:
from cv2 import imread, imshow, waitKey, destroyAllWindows, CascadeClassifier, rectangle
##filename = 'TheBeatles.jpg'
filename = '1_Handshaking_Handshaking_1_801.jpg'
photo = pyplot.imread(filename) # Загрузка изображения из файла
classifier = CascadeClassifier('haarcascade_frontalface_default.xml') # Загрузка классификатора
# Находим лица
# Коэффициент масштабирования scaleFactor = 1.05
# scaleFactor показывает, на сколько размер изображения изменяется при каждом масштабировании
# По умолчанию scaleFactor = 1.1
# minNeighbors = 5; задает число соседей прямоугольника-кандидата, необходимое для его сохранения
# По умолчанию minNeighbors = 3
boxes = classifier.detectMultiScale(photo, 1.05, 5) # photo, scaleFactor, minNeighbors
for box in boxes:
## print(box)
# Координаты правого верхнего угла рамки, ее ширина и высота
x, y, width, height = box
x2, y2 = x + width, y + height
# Вывод прямоугольников (рамок), обрамляющих лица;
# цвет рамки желтый; ширина линии рамки 2 пикселя
rectangle(photo, (x, y), (x2, y2), (0, 255, 255), 2)
# Показываем изображение с найденными лицами
imshow('Faces', photo)
##waitKey(0) # Окно графического вывода будет закрыто после нажатия на клавишу
##destroyAllWindows() # Закрываем окно
Многозадачная каскадная сверточная нейронная сеть позволяет находить лица и ключевые точки на них (см. рис. 2).
Пример структуры каскадной сверточной нейронной сети приведен на рис. 3 [8].
Рис. 3. Пример структуры каскадной сверточной нейронной сети
На рис. 3:
Сеть обучают на доступных наборах данных, например, AFW [9]. AFW – содержит jpg-файлы с изображениями лиц (пример см. на рис. 4) – обучающая, оценочная и проверочная выборки.
Рис. 4. Пример из оценочной выборки
Для первых двух выборок имеются описания рамок, охватывающих лица. Фрагмент файла с таким описанием для изображения, приведенного на рис. 4:
1--Handshaking/1_Handshaking_Handshaking_1_801.jpg
6
869 27 98 133 0 0 0 0 0 0
645 44 54 70 0 0 0 0 0 0
556 79 49 67 0 0 0 0 0 0
410 23 58 69 0 0 0 0 0 0
243 66 58 68 0 0 0 0 0 0
69 60 62 69 0 0 0 0 0 0
Первая строка файла – это имя рисунка, вторая – число рамок на рисунке, последующие – описания рамок.
При задании рамок (прямоугольников) используется физическая система координат: ее начало находится в верхнем левом углу окна вывода. Оси X и Y соответственно направлены влево и вниз.
Описание рамки содержит следующие данные: x1, y1, w, h, blur, expression, illumination, invalid, occlusion, pose, где
x1, y1, w, h - соответственно координаты верхнего левого угла рамки, ее ширина и высота;
blur – размытость: 0 – нет; 1 – нормальная; 2 – сильная;
expression – выражение лица: 0 – обычное; 1 – преувеличенное;
illumination – освещение: 0 – номальное; 1 – чрезмерное;
invalid – флаг плохого рисунка: 0 – хороший; 1 – плохой;
occlusion – окклюзия (деформация лица): 0 – нет; 1 – частичная; 2 – высокая;
pose – поза: 0 – обычная; 1 – необычная.
Для вывода рисунка библиотеки AFW и заданных для него рамок (рис. 5) можно применить следующий код:
import numpy as np
from matplotlib import pyplot
from matplotlib.patches import Rectangle
boxes_filename = '801.txt'
# Состав файла 801.txt:
# 1--Handshaking/1_Handshaking_Handshaking_1_801.jpg
# 6
# 869 27 98 133 0 0 0 0 0 0
# 645 44 54 70 0 0 0 0 0 0
# 556 79 49 67 0 0 0 0 0 0
# 410 23 58 69 0 0 0 0 0 0
# 243 66 58 68 0 0 0 0 0 0
# 69 60 62 69 0 0 0 0 0 0
# Используется физическая система координат.
# Ее начало в верхнем левом угду рисунка. Ось Y направлена вниз
# В файле после первых двух строчек следующие данные:
# x1, y1, w, h, blur, expression, illumination, invalid, occlusion, pose
# x1, y1, w, h - соответственно координаты верхнего левого угла рамки, ее ширина и высота
# blur - размытость: 0 - нет; 1 - нормальная; 2 - сильная
# expression - выражение лица: 0 - обычное; 1 - преувеличенное
# illumination - освещение: 0 - номальное; 1 - чрезмерное
# invalid - плохой рисунок: 0 - хороший; 1 - плохой
# occlusion - окклюзия (деформация лица): 0 - нет; 1 - частичная; 2 - высокая
# pose - поза: 0 - обычная; 1 - необычная
f = open(boxes_filename, 'r')
file_list = []
for line in f: file_list.append(line)
f.close()
filename = '1_Handshaking_Handshaking_1_801.jpg'
photo = pyplot.imread(filename)
pyplot.imshow(photo)
clr = 'red'
ax = pyplot.gca() # Контекст для выводы рамок (прямоугольников)
for k in range(2, 8):
arr_b = np.asarray(file_list[k].split()).astype('int')
w = arr_b[2]
h = arr_b[3]
rect = Rectangle(arr_b[0:2], w, h, fill = False, color = clr, linewidth = 2)
ax.add_patch(rect)
pyplot.show()
Рис. 5. Изображение AFW с заданными для него рамками
Вывод лиц (рис. 6), найденных mtcnn с заданными по умолчанию параметрами, обеспечит следующий код:
from matplotlib import pyplot
from matplotlib.patches import Rectangle
from matplotlib.patches import Circle
from mtcnn.mtcnn import MTCNN
#
# Выводит изображение, обрамляя найденные лица прямоугольниками
def show_image_with_boxes(photo, faces, with_dots):
clr = 'yellow'
pyplot.imshow(photo)
ax = pyplot.gca() # Контекст для выводы прямоугольников
# Вывод обрамляющих рамок
for face in faces: # Для каждого найденного прямоугольника
x, y, width, height = face['box']
# Обрамляющий прямоугольник и его вывод
rect = Rectangle((x, y), width, height, fill = False, color = clr, linewidth = 2)
ax.add_patch(rect)
# Вывод точек
if with_dots:
for key, value in face['keypoints'].items():
# Создание и вывод точек
dot = Circle(value, radius = 2, color = clr)
ax.add_patch(dot)
pyplot.show()
##filename = 'TheBeatles.jpg'
filename = '1_Handshaking_Handshaking_1_801.jpg'
photo = pyplot.imread(filename) # Загрузка изображения из файла
detector = MTCNN() # Создаем детектор с заданными по умолчанию параметрами
faces = detector.detect_faces(photo) # Находим лица
show_image_with_boxes(photo, faces, False) # Показываем найденные лица (рис. 6)
Рис. 6. Лица, найденные mtcnn
С заданными по умолчанию параметрами имеем одну ошибку.
Для вывода ключевых точек нужно изменить вызов процедуры show_image_with_boxes:
show_image_with_boxes(photo, faces, True)
Отдельно можно вывести обрамленные области (рис. 7):
# Выводим обрамленные части изображения
from matplotlib import pyplot
from matplotlib.patches import Rectangle
from matplotlib.patches import Circle
from mtcnn.mtcnn import MTCNN
def show_boxes(photo, faces):
k = 0
nFaces = len(faces)
for face in faces:
k += 1
x1, y1, w, h = face['box']
x2, y2 = x1 + w, y1 + h
pyplot.subplot(1, nFaces, k)
pyplot.axis('off')
pyplot.imshow(photo[y1:y2, x1:x2]) # Вывод лица
pyplot.show()
##filename = 'TheBeatles.jpg'
filename = '1_Handshaking_Handshaking_1_801.jpg'
photo = pyplot.imread(filename)
detector = MTCNN()
faces = detector.detect_faces(photo)
show_boxes(photo, faces)
Рис. 7. Обрамленные части изображения
Заметим, что детектор OpenCV с заданными по умолчанию параметрами не найдет 2 лица и ошибется в одном случае (рис. 8).
Рис. 8. Результат работы детектора OpenCV с заданными по умолчанию параметрами
Выполняется следующей программой:
only_contours = True # True False
import numpy as np, cv2
fn = '3d.jpg' # Путь к файлу с изображением
img = cv2.imread(fn)
# Параметры цветового фильтра. Подбираются для каждого изображения
hsv_min = np.array((12, 16, 120), np.uint8)
hsv_max = np.array((120, 120, 255), np.uint8)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # Меняем цветовую модель с BGR на HSV
thresh = cv2.inRange(hsv, hsv_min, hsv_max) # Применяем цветовой фильтр
# Ищем контуры; они хранятся переменной contours
_, contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
n_contours = len(contours)
### Отображаем контуры
##cv2.drawContours(img, contours, -1, (255, 0, 0), 2, cv2.LINE_AA, hierarchy, 1)
##cv2.imshow('contours', img) # Показываем изображение с найденными контурами
#
index = 0
def update():
if only_contours:
dst = np.zeros(img.shape, np.uint8) + 255
else:
dst = img.copy()
cv2.drawContours(dst, contours, index, (0, 0, 0), 2)
cv2.imshow('contours', dst)
def update_index(v):
global index
index = v - 1
update()
update_index(0)
cv2.createTrackbar('contour', 'contours', 0, n_contours, update_index)
Если only_contours = True, то будут отображены только найденные контуры, в противном случае – объекты с контурами (рис. 9).
Рис. 9. Исходное изображения и два варианта вывода найденных контуров
Теорию вопроса можно посмотреть на станичке Фортран-реализация поиска контуров изображения
Задача поиска лиц на изображении является частным случаем более общей задачи поиска (детектирования) объектов, заключающейся в обнаружении всех объектов указанных классов и определении охватывающей рамки для каждого из них. Родственными задачами являются следующие [10]:
Рис. 10. Сегментация: а – семантическая; б – по объектам