Ich sage euch: man muß noch Chaos in sich haben, um einen tanzenden Stern gebären zu können (“Also sprach Zarathustra”, Friedrich Nietzsche)
Поиск лиц на изображении с использованием OpenCV в Python
Обнаружение объектов — это компьютерная технология, связанная с компьютерным зрением и обработкой изображений, которая занимается обнаружением экземпляров семантических объектов определенного класса, например, человеческие лица, автомобили, фрукты и т. д., в цифровых изображениях и видео.
В этом уроке мы будем создавать простой скрипт Python, который занимается обнаружением человеческих лиц на изображении, мы будем использовать два метода из библиотеки OpenCV. Во-первых, мы собираемся использовать каскадные классификаторы Хаара, что является простым (и не очень точным), но наиболее удобным способом для новичков.
После этого мы погрузимся в использование детекторов Single Shot Multibox (или коротко SSD), которые представляют собой метод обнаружения объектов на изображениях с использованием одной глубокой нейронной сети.
Примечание: стоит упомянуть, что вам нужно различать обнаружение объекта и классификацию объекта, обнаружение объекта — это обнаружение объекта и его расположение на изображении, а классификация объектов — это распознавание того, к какому классу принадлежит объект. Если вас интересует классификация изображений, перейдите к этому уроку.
Распознавание лиц с помощью каскадов Хаара
Каскадные классификаторы Хаара на основе функций — это подход, основанный на машинном обучении, при котором каскадная функция обучается на основе большого количества положительных и отрицательных изображений. Затем он используется для обнаружения объектов на других изображениях. Преимущество каскадных классификаторов Хаара в том, что вы можете создать классификатор любого объекта, который захотите, OpenCV уже предоставил вам некоторые параметры классификатора, поэтому вам не нужно собирать какие-либо данные для обучения.
Для начала установите нужные нам пакеты:
pip3 install opencv-python numpy
Хорошо, создайте новый файл Python и продолжайте, для начала, импортируя OpenCV:
import cv2
Вам понадобится образец изображения для тестирования, убедитесь, что на нем есть четкие лицевые стороны. Я буду использовать стоковое изображение, которое содержит двух очень милых детишек:
# Загрузка изображения
image = cv2.imread("kids.jpg")
Функция imread() загружает изображение из указанного файла и возвращает его как N-мерный массив numpy.
Прежде чем мы обнаружим лица на изображении, нужно преобразовать изображение в оттенки серого, потому что функция, которую мы собираемся использовать для обнаружения лиц, ожидает изображение в оттенках серого:
# преобразуем изображение к оттенкам серого
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
Функция cvtColor() преобразует входное изображение из одного цветового пространства в другое, мы указали код cv2.COLOR_BGR2GRAY, что означает преобразование из BGR (Blue Green Red) в оттенки серого.
Поскольку это руководство посвящено обнаружению человеческих лиц, загрузите каскад Хаара для обнаружения этих лиц из списка. Точнее файл haarcascade_frontalface_default.xml. Поместим его в папку с названием cascades, а затем загрузим:
# инициализировать распознаватель лиц (каскад Хаара по умолчанию)
face_cascade = cv2.CascadeClassifier("cascades/haarcascade_fontalface_default.xml")
Давайте теперь обнаружим все лица на изображении:
# обнаружение всех лиц на изображении
faces = face_cascade.detectMultiScale(image_gray)
# печатать количество найденных лиц
print(f"{len(faces)} лиц обнаружено на изображении.")
Функция detectMultiScale() принимает изображение в качестве параметра и обнаруживает объекты разных размеров в виде списка прямоугольников, давайте нарисуем эти прямоугольники на изображении:
# для всех обнаруженных лиц рисуем синий квадрат
for x, y, width, height in faces:
cv2.rectangle(image, (x, y), (x + width, y + height), color=(255, 0, 0), thickness=2)
Наконец, сохраним новое изображение:
# сохраним изображение с обнаруженными лицами
cv2.imwrite("kids_detected.jpg", image)
Вот что у меня получилось:
Довольно круто, не правда ли? Не стесняйтесь использовать другие классификаторы объектов, другие изображения и, что еще интереснее, используйте свою веб-камеру! Вот код для этого:
import cv2
# создать новый объект камеру
cap = cv2.VideoCapture(0)
# инициализировать поиск лица (по умолчанию каскад Хаара)
face_cascade = cv2.CascadeClassifier("cascades/haarcascade_fontalface_default.xml")
while True:
# чтение изображения с камеры
_, image = cap.read()
# преобразование к оттенкам серого
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# обнаружение лиц на фотографии
faces = face_cascade.detectMultiScale(image_gray, 1.3, 5)
# для каждого обнаруженного лица нарисовать синий квадрат
for x, y, width, height in faces:
cv2.rectangle(image, (x, y), (x + width, y + height), color=(255, 0, 0), thickness=2)
cv2.imshow("image", image)
if cv2.waitKey(1) == ord("q"):
break
cap.release()
cv2.destroyAllWindows()
Как только вы загрузите этот скрипт (если, конечно, у вас есть веб-камера), он подключит вашу веб-камеру и начнет рисовать синие прямоугольники вокруг всех лицевых сторон изображения. Код не такой уж сложный, все, что я изменил: вместо чтения изображения из файла я создал объект VideoCapture, который каждый раз в цикле while читает камеру, но как только вы нажмете кнопку q, основной цикл завершится.
Распознавание лиц с помощью SSD
Как видите, предыдущий метод не так уж и сложен. К сожалению, он устарел и сегодня, в реальном мире, редко когда используется. Однако, нейронные сети всегда приходят на помощь, и, к счастью для нас, OpenCV содержит замечательный для нас модуль dnn в пакете cv2, который позволяет находить лица, используя предварительно обученные модели глубокого обучения.
Теперь, чтобы загрузить реальную модель, нам нужно использовать метод readNetFromCaffe(), который принимает в качестве аргументов архитектуру модели и веса:
# загрузим модель Caffe
model = cv2.dnn.readNetFromCaffe(prototxt_path, model_path)
Мы будем использовать то же изображение:
# читаем изображение
image = cv2.imread("kids.jpg")
# получаем ширину и высоту изображения
h, w = image.shape[:2]
Чтобы передать загруженное изображение в нейронную сеть, его нужно предварительно подготовить. В частности, нам нужно изменить размер изображения до размеров (300, 300) и выполнить вычитание среднего, поскольку сеть так обучена:
# предварительная обработка: изменение размера и вычитание среднего
blob = cv2.dnn.blobFromImage(image, 1.0, (300, 300), (104.0, 177.0, 123.0))
Будем использовать объект blob в качестве входа в сеть и выполнить прямую связь, чтобы получить обнаруженные лица:
# устанавливаем на вход нейронной сети изображение
model.setInput(blob)
# выполняем логический вывод и получаем результат
output = np.squeeze(model.forward())
Теперь выходной объект содержит все обнаруженные объекты (в данном случае лица), давайте переберем этот массив и нарисуем все лица на изображении с достоверностью более 50%:
font_scale = 1.0
for i in range(0, output.shape[0]):
# получить уверенность
confidence = output[i, 2]
# если достоверность выше 50%, то нарисуйте окружающий прямоугольник
if confidence > 0.5:
# получить координаты окружающего блока и масштабировать их до исходного изображения
box = output[i, 3:7] * np.array([w, h, w, h])
# преобразовать в целые числа
start_x, start_y, end_x, end_y = box.astype(np.int)
# рисуем прямоугольник вокруг лица
cv2.rectangle(image, (start_x, start_y), (end_x, end_y), color=(255, 0, 0), thickness=2)
# также нарисуем текст
cv2.putText(image, f"{confidence*100:.2f}%", (start_x, start_y-5), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (255, 0, 0), 2)
После того, как мы убедились в достоверности модели обнаруженного объекта, получаем охватывающий прямоугольник и умножаем его на ширину и высоту исходного изображения для вычисления правильных координат прямоугольника, потому что, как вы помните, ранее изменялся размер изображения до (300, 300), поэтому и на выходе также должно быть значение от 0 до 300.
В этом случае мы не только нарисовали окружающие прямоугольники, но и написали текст с указанием достоверности в процентах, давайте покажем и сохраним новое изображение:
# show the image
cv2.imshow("image", image)
cv2.waitKey(0)
# save the image with rectangles
cv2.imwrite("kids_detected_dnn.jpg", image)
Вот получившееся изображение:
Замечательно, этот метод намного лучше и точнее, но он может быть хуже с точки зрения FPS (Кадровая частотаએ), если вы прогнозируете лица в реальном времени, поскольку он не так быстр, как каскадный метод Хаара.
Хорошо! Всё для этого урока, все учебные материалы (включая тестовое изображение, параметры каскада хаара, веса моделей SSD и полный код) можно посмотреть и скачать здесь.