Недавно мы сделали простой проект, в котором научили скрипт находить и подсвечивать лица при работе с веб-камерой. Сегодня продолжим тему — сделаем так, чтобы скрипт мог определять пол и возраст того, кто смотрит в камеру.
Вот основные мысли про распознавания лиц, которые нам сегодня пригодятся:
- Для распознавания лица компьютер должен получить изображение — через камеру или готовый файл.
- Компьютер использует особый алгоритм, который разбивает изображение на прямоугольники. Так он пытается найти на картинке знакомые ему переходы между светлыми и тёмными областями.
- Если в одном месте программа находит много таких совпадений, то, скорее всего, это лицо человека.
- Чтобы программистам каждый раз не писать свой код распознавания с нуля, сделали специальную библиотеку компьютерного зрения — cv2. Если в неё загрузить заранее подготовленные параметры лиц, она сможет распознавать их намного точнее.
- С помощью этой библиотеки мы можем получить координаты областей, которые нейросеть распознала как лица, чтобы нарисовать рамки вокруг них.
- То, что внутри этих рамок, и будет лицом (по мнению нейронки).
- Если повернуться вполоборота или сильно наклонить лицо вниз — может и не распознать.
Что делаем
Дорабатываем скрипт так, чтобы он мог определять пол и возраст всех, кого он сможет найти на изображении с камеры. Для этого мы подключим к скрипту две новые модели: одна будет определять пол, вторая — возраст.
Чтобы результат был виден сразу и наглядно, добавим вывод пола и возраста сразу над рамкой. Единственный момент: библиотека cv2 плохо работает с русской кодировкой и вместо русских букв выводит странные символы, поэтому надписи будут на английском:
Вот код из прошлого проекта, его и будем дорабатывать.
# подключаем библиотеку компьютерного зрения
import cv2
# функция определения лиц
def highlightFace(net, frame, conf_threshold=0.7):
# делаем копию текущего кадра
frameOpencvDnn=frame.copy()
# высота и ширина кадра
frameHeight=frameOpencvDnn.shape[0]
frameWidth=frameOpencvDnn.shape[1]
# преобразуем картинку в двоичный пиксельный объект
blob=cv2.dnn.blobFromImage(frameOpencvDnn, 1.0, (300, 300), [104, 117, 123], True, False)
# устанавливаем этот объект как входной параметр для нейросети
net.setInput(blob)
# выполняем прямой проход для распознавания лиц
detections=net.forward()
# переменная для рамок вокруг лица
faceBoxes=[]
# перебираем все блоки после распознавания
for i in range(detections.shape[2]):
# получаем результат вычислений для очередного элемента
confidence=detections[0,0,i,2]
# если результат превышает порог срабатывания — это лицо
if confidence>conf_threshold:
# формируем координаты рамки
x1=int(detections[0,0,i,3]*frameWidth)
y1=int(detections[0,0,i,4]*frameHeight)
x2=int(detections[0,0,i,5]*frameWidth)
y2=int(detections[0,0,i,6]*frameHeight)
# добавляем их в общую переменную
faceBoxes.append([x1,y1,x2,y2])
# рисуем рамку на кадре
cv2.rectangle(frameOpencvDnn, (x1,y1), (x2,y2), (0,255,0), int(round(frameHeight/150)), 8)
# вовзращаем кадр с рамками
return frameOpencvDnn,faceBoxes
# загружаем веса для распознавания лиц
faceProto="opencv_face_detector.pbtxt"
# и конфигурацию самой нейросети — слои и связи нейронов
faceModel="opencv_face_detector_uint8.pb"
# запускаем нейросеть по распознаванию лиц
faceNet=cv2.dnn.readNet(faceModel,faceProto)
# получаем видео с камеры
video=cv2.VideoCapture(0)
# пока не нажата любая клавиша — выполняем цикл
while cv2.waitKey(1)<0:
# получаем очередной кадр с камеры
hasFrame,frame=video.read()
# если кадра нет
if not hasFrame:
# останавливаемся и выходим из цикла
cv2.waitKey()
break
# выводим картинку с камеры
cv2.imshow("Face detection", frame)
Что понадобится
Чтобы не писать и не обучать новые нейросети с нуля, поступим как настоящие программисты — возьмём готовые и встроим в свой проект. Мы будем использовать модели, подготовленные Талом Хасснером и Гилом Леви.
С полом всё просто — мужской или женский, а вот прогнозируемый возраст может быть одним из следующих диапазонов:
- 0–2 года,
- 4–6,
- 8–12,
- 15–20,
- 25–32,
- 38–43,
- 48–53,
- 60–100.
Может показаться странным, что в возрастных вариантах есть пробелы. На самом деле очень сложно точно определить точный возраст по одному изображению, потому что тут сильно влияет макияж, освещение, помехи, выражения лица и даже длина волос :-) Поэтому нейросеть специально настроена так, чтобы выдавать один из восьми результатов, к которому она ближе всего.
Качаем веса и параметры нейросети и кладём их в ту же папку, что и рабочий скрипт:
Загружаем новые модели в скрипт
Чтобы подключить новые нейросети к скрипту, используем тот же приём, что и с библиотекой cv2: загрузим файлы в DNN-модуль библиотеки cv2 и получим обученные нейронки, которые сразу готовы к работе.
Добавляем код после загрузки файлов cv2:
genderProto="gender_deploy.prototxt"
genderModel="gender_net.caffemodel"
ageProto="age_deploy.prototxt"
ageModel="age_net.caffemodel"
Теперь нам нужно подготовить специальные параметры для работы с изображениями, чтобы новые нейросети могли работать с найденными лицами. Дело в том, что нам нужно будет передать параметры лиц из одной нейронки в другие. Для этого выровняем освещение и сразу укажем итоговые параметры работы каждой новой нейросети:
# настраиваем свет
MODEL_MEAN_VALUES=(78.4263377603, 87.7689143744, 114.895847746)
# итоговые результаты работы нейросетей для пола и возраста
genderList=['Male ','Female']
ageList=['(0-2)', '(4-6)', '(8-12)', '(15-20)', '(25-32)', '(38-43)', '(48-53)', '(60-100)']
Загружаем файлы в память:
# запускаем нейросети по определению пола и возраста
genderNet=cv2.dnn.readNet(genderModel,genderProto)
ageNet=cv2.dnn.readNet(ageModel,ageProto)
Добавляем вывод новых данных о поле и возрасте
В первый раз мы просто выводили картинку с рамкой, но сейчас этого недостаточно — нам нужно возле каждого лица подписать пол и возраст. Для этого сделаем так:
- Найдём все лица в кадре.
- Для каждого лица на основе рамок вокруг сформируем новый пиксельный объект.
- Отправим этот объект в нейросеть для определения пола.
- Получим результат работы этой нейросети.
- На основании результата выберем один из двух вариантов пола.
- То же самое сделаем для возраста.
- Полученные результаты добавим в виде текста поверх кадра возле рамки.
Всё это нужно добавить в итоговый цикл вместо простого вывода картинки:
# определяем лица в кадре
resultImg,faceBoxes=highlightFace(faceNet,frame)
# перебираем все найденные лица в кадре
for faceBox in faceBoxes:
# получаем изображение лица на основе рамки
face=frame[max(0,faceBox[1]):
min(faceBox[3],frame.shape[0]-1),max(0,faceBox[0])
:min(faceBox[2], frame.shape[1]-1)]
# получаем на этой основе новый бинарный пиксельный объект
blob=cv2.dnn.blobFromImage(face, 1.0, (227,227), MODEL_MEAN_VALUES, swapRB=False)
# отправляем его в нейросеть для определения пола
genderNet.setInput(blob)
# получаем результат работы нейросети
genderPreds=genderNet.forward()
# выбираем пол на основе этого результата
gender=genderList[genderPreds[0].argmax()]
# отправляем результат в переменную с полом
print(f'Gender: {gender}')
# делаем то же самое для возраста
ageNet.setInput(blob)
agePreds=ageNet.forward()
age=ageList[agePreds[0].argmax()]
print(f'Age: {age[1:-1]} years')
# добавляем текст возле каждой рамки в кадре
cv2.putText(resultImg, f'{gender}, {age}', (faceBox[0], faceBox[1]-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,255,255), 2, cv2.LINE_AA)
# выводим итоговую картинку
cv2.imshow("Detecting age and gender", resultImg)
Запускаем скрипт и смотрим, как нейросеть делает нас моложе:
Но если у вас длинные волосы, то для нейросети это сразу весомый признак при определении пола:
Что дальше
В заключительной части проекта мы научим скрипт распознавать лица на фотографиях и посмотрим, как указывать свои параметры при запуске. А пока делитесь в комментариях своими скриншотами — заодно посмотрим, насколько точно скрипт справляется со своей работой.
# подключаем библиотеку компьютерного зрения
import cv2
# функция определения лиц
def highlightFace(net, frame, conf_threshold=0.7):
# делаем копию текущего кадра
frameOpencvDnn=frame.copy()
# высота и ширина кадра
frameHeight=frameOpencvDnn.shape[0]
frameWidth=frameOpencvDnn.shape[1]
# преобразуем картинку в двоичный пиксельный объект
blob=cv2.dnn.blobFromImage(frameOpencvDnn, 1.0, (300, 300), [104, 117, 123], True, False)
# устанавливаем этот объект как входной параметр для нейросети
net.setInput(blob)
# выполняем прямой проход для распознавания лиц
detections=net.forward()
# переменная для рамок вокруг лица
faceBoxes=[]
# перебираем все блоки после распознавания
for i in range(detections.shape[2]):
# получаем результат вычислений для очередного элемента
confidence=detections[0,0,i,2]
# если результат превышает порог срабатывания — это лицо
if confidence>conf_threshold:
# формируем координаты рамки
x1=int(detections[0,0,i,3]*frameWidth)
y1=int(detections[0,0,i,4]*frameHeight)
x2=int(detections[0,0,i,5]*frameWidth)
y2=int(detections[0,0,i,6]*frameHeight)
# добавляем их в общую переменную
faceBoxes.append([x1,y1,x2,y2])
# рисуем рамку на кадре
cv2.rectangle(frameOpencvDnn, (x1,y1), (x2,y2), (0,255,0), int(round(frameHeight/150)), 8)
# вовзращаем кадр с рамками
return frameOpencvDnn,faceBoxes
# загружаем веса для распознавания лиц
faceProto="opencv_face_detector.pbtxt"
# и конфигурацию самой нейросети — слои и связи нейронов
faceModel="opencv_face_detector_uint8.pb"
# точно так же загружаем модели для определения пола и возраста
genderProto="gender_deploy.prototxt"
genderModel="gender_net.caffemodel"
ageProto="age_deploy.prototxt"
ageModel="age_net.caffemodel"
# настраиваем свет
MODEL_MEAN_VALUES=(78.4263377603, 87.7689143744, 114.895847746)
# итоговые результаты работы нейросетей для пола и возраста
genderList=['Male ','Female']
ageList=['(0-2)', '(4-6)', '(8-12)', '(15-20)', '(25-32)', '(38-43)', '(48-53)', '(60-100)']
# запускаем нейросеть по распознаванию лиц
faceNet=cv2.dnn.readNet(faceModel,faceProto)
# и запускаем нейросети по определению пола и возраста
genderNet=cv2.dnn.readNet(genderModel,genderProto)
ageNet=cv2.dnn.readNet(ageModel,ageProto)
# получаем видео с камеры
video=cv2.VideoCapture(0)
# пока не нажата любая клавиша — выполняем цикл
while cv2.waitKey(1)<0:
# получаем очередной кадр с камеры
hasFrame,frame=video.read()
# если кадра нет
if not hasFrame:
# останавливаемся и выходим из цикла
cv2.waitKey()
break
# определяем лица в кадре
resultImg,faceBoxes=highlightFace(faceNet,frame)
# перебираем все найденные лица в кадре
for faceBox in faceBoxes:
# получаем изображение лица на основе рамки
face=frame[max(0,faceBox[1]):
min(faceBox[3],frame.shape[0]-1),max(0,faceBox[0])
:min(faceBox[2], frame.shape[1]-1)]
# получаем на этой основе новый бинарный пиксельный объект
blob=cv2.dnn.blobFromImage(face, 1.0, (227,227), MODEL_MEAN_VALUES, swapRB=False)
# отправляем его в нейросеть для определения пола
genderNet.setInput(blob)
# получаем результат работы нейросети
genderPreds=genderNet.forward()
# выбираем пол на основе этого результата
gender=genderList[genderPreds[0].argmax()]
# отправляем результат в переменную с полом
print(f'Gender: {gender}')
# делаем то же самое для возраста
ageNet.setInput(blob)
agePreds=ageNet.forward()
age=ageList[agePreds[0].argmax()]
print(f'Age: {age[1:-1]} years')
# добавляем текст возле каждой рамки в кадре
cv2.putText(resultImg, f'{gender}, {age}', (faceBox[0], faceBox[1]-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,255,255), 2, cv2.LINE_AA)
# выводим итоговую картинку
cv2.imshow("Detecting age and gender", resultImg)