Миллион домашних фотографий: лица, лица, лица

Итак, все фотографии разложены по папкам и находить фотографии Новых годов или дней рождения стало быстро и удобно. Фотографии из отпусков тоже можно найти относительно быстро, но хотелось большего. А именно, искать по людям и не просто по людям, а по набору людей, например, найти все совместные фотографии детей или фотографии с бабушкой и т.д.

Поэтому я решил немного углубиться в так называемый Face Recognition.

e53fc9a1d3261176739122d4ef17f136

Все просто?

С первого взгляда, когда смотришь на тематические статьи, кажется, что со стороны пользователя все просто: взял по одной фотографии человека, сказал системе, что это, мол, Саша, Алиса, Тима; потом на вход дается набор фотографий и система однозначно (ну ок, не однозначно, а с некоторой большой вероятностью) устанавливает «кто есть кто». Но на практике так работает, если надо отличить Байдена от Обамы на протокольных фотографиях из Белого дома. Не совсем так работает, когда нужно различать лица на домашних фотках (плохой ракурс, освещение, перекрытие лиц предметами и т.д.). Совсем не так работает, когда на фотографиях близкие родственники. И почти никак не работает если фотографии покрывают всю жизнь от младенчества до зрелости (я и сам часто не могу отличить фотографию одного своего ребенка от другого если нет контекста или хотя бы даты снимка).

8006b2e85ce37fd1e274139d96fb335b

Еще, смотря на коммерческие системы, кажется, что стоит дать на вход одну фронтальную фотографию и лицо будет распознано во всех ракурсах и на всех кадрах. Но, как показал опыт, часто многие системы просто пропускают «не очень хорошие» лица: перекрытые, в профиль и т.д, а пользователь даже и не узнает, что на этой фотографии лицо было проигнорировано, да и доступ к алгоритмам коммерческих систем не всегда есть, так что пришлось работать с тем, что нашел в opensource.

В итоге, помимо самой системы распознавания, пришлось делать пользовательский интерфейс проверки качества распознавания, добавления новых шаблонов, повторного распознавания и других мелочей.

Но об этом чуть позже.

Белогривые лошадки?

Вначале, я, как любитель серверов и всего облачного, решил, что, наверное, можно не изобретать велосипед и воспользоваться готовыми облачными решениями. 

Но, посмотрев на цены популярных облачных решений и сравнив со своим объемом информации, понял, что на те деньги которые потребуются, чтобы пропустить через облака всю домашнюю медиатеку, можно и дома поставить не такой уж плохой сервер.

096d1d8e9654227e7a3b90a6ed07ab67

В итоге «белогривые лошадки» помчались без оглядки мимо, а я принялся за реализацию локально-серверного варианта. Тем более, что какой-никакой сервер дома уже стоял и процессор там большую часть времени простаивал.

На случай, если кого интересуют облачные решения, оставлю пару ссылок:

https://azure.microsoft.com/en-us/services/cognitive-services/face/

https://cloud.google.com/vision/docs/face-tutorial

https://aws.amazon.com/rekognition/

Там весьма богатый функционал, не только распознавание лиц. И, для небольших объемов, он даже местами бесплатный.

CPU → GPU

Сначала я, будучи наивным, запустил поиск лиц по алгоритму CNN (см. ниже) на CPU. 

В итоге одна фотография в 1000 пикселей по длинной стороне, обрабатывалась пару минут. Прикинув объем медиатеки, обозначенный в заголовке, и произведя нехитрые расчеты, я довольно сильно приуныл, тратить четыре года чистого процессорного времени в ожидании обработки всех фотографий в мои планы совсем не входило.

Но, как и многие, я знал, что для ускорения некоторых типов вычислений можно применять GPU. Так вот, Face Recognition как из тех типов вычислений. Поэтому, почитав отзывы и подобрав подходящий вариант, я помчался на местную онлайн барахолку и за довольно небольшую сумму наличности купил GeForce GTX 1050 Ti. И, даже на такой скромной карточке, процесс пошел куда шустрее… меньше секунды на одну фотографию! Но, увы, это не происходит по щелчку пальцев. Вначале надо чтобы весь зоопарк библиотек смог с этой видеокарточкой заработать.

9632dcc9e8e049c407e6d8144757810f

И тут начинается веселье: сперва надо поставить драйвера для видеокарточки и CUDO. Потом поставить… библиотеки с поддержкой CUDO? Нет, потом надо ставить сборочное окружение, так как теперь все те библиотеки, что запросто встали из репозиториев без поддержки GPU ускорения придется собирать и ставить руками.

Настройка всего этого зоопарка — тема отдельной статьи и даже не одной (благо на просторах Интернета их немало), но в первый раз все это заняло пару вечеров и немало дискового пространства.

Итак, сервер настроен, библиотеки стоят, вентиляторы охлаждения CPU и GPU работают на малых оборотах в ожидании задач, можно и начать распознавать лица.

Этапы распознавания

Весь этап распознавания лиц на фотографии можно разбить на несколько этапов:

  1. Нахождение лиц на фотографии (face detection)

  2. Поиск элементов лица (landmarks detection)

  3. Кодировка лица (face encoding)

  4. Сравнение лица с шаблонами (face matching)

4bdb086886f06f62ba62af1e224b005b

Нахождение лиц на фотографии можно делать разными способами, но самые популярные это:

  • Гистограмма направленных градиентов (HOG).

  • Алгоритм на базе сверточных нейронных сетей (CNN).

HOG работает быстро, вполне достаточно CPU, но распознает хуже и только фронтальные лица.

CNN целесообразно использовать только на GPU, зато распознает гораздо лучше и во всех возможных позах.

Я использовал реализации этих алгоритмов из библиотеки face_recognition (пришлось немного ее модифицировать, чтобы бы заставить работать с элементами лица полученными в других библиотеках, см. ниже). Эта библиотека является по сути удобной оберткой над dlib.

При первом же прогоне полноформатной фотографии на 8 мегапикселей с цифрового фотоаппарата, видеокарта сказала: «Упс» и выкинула исключение о том, что, мол, милый друг, моих 4 GB видеопамяти для этой задачи маловато. Поэтому волевым решением было решено урезать все входные фотографии до 1000 пикселей по длинной стороне (max_image_size в конфиге, если что), этого размера вполне достаточно, если вас не сильно интересуют лица всяких прохожих далеко на заднем плане (о да, иногда, в процессе отладки, там попадались довольно странные лица)

Поиск элементов лица заключается в поиске опознавательных точек. Необходимый минимум — это углы глаз и нос, причем, довольно часто, соотношение пропорций расстояний между глазами и носом довольно точно позволяет идентифицировать лицо. Но чаще происходит также распознавание рта, овала лица и бровей.

f9f85b8b3d5cb4c3ea198271b8b92f13

Некоторые алгоритмы также позволяют находить элементы лица в трёхмерных координатах, что полезно, например, при поиске направления поворота лица.

Я «игрался» с двумя алгоритмами: первый из упомянутой выше библиотеки face_recognition (она же dlib), второй из библиотеки face-alignment.

Первый работает, в общем-то, неплохо, но увы, только на фронтальных фотографиях. На фотографиях в профиль начинается «веселье». И самое главное «веселье» в том, что алгоритм не определяет фас или профиль, а просто находит фас по мере всех своих «нейроспособностей». В итоге весьма весомая часть распознанных лиц выглядит примерно так:

9464afcce8b3487d5d80d00641df03ca

Это меня немало так опечалило, и я предпринял две попытки это как-то исправить.

Вначале, решил «в лоб» научить нейронную сеть отличать хороший результат от плохого, руками отобрал примерно 10000 фотографий с хорошим/плохим результатом настроил нейронку и получил точность… около 80%, что было совсем недостаточно, так как до этого система примерно на таком же количестве искала точки корректно. Да, немного точность повысилась, но процент брака, а соответственно потенциальной ручной работы, был велик.

Затем я решил предпринять попытку настроить нейронку на распознание лиц в фас и в профиль. И… достиг примерно такого же результата. В общем, для таких экспериментов нужно куда большая база образцов, иначе точность будет так себе. На все эти мытарства ушло около месяца, а потом я наткнулся на второй алгоритм, и проблемы исчезли. Да, я немного углубил свои познания в нейронных сетях, но, так сказать, осадочек остался.

Второй алгоритм, кончено тоже не идеален, но, во-первых, процент брака меньше, и, во-вторых, и в главных, брак такой, что его довольно легко распознать алгоритмически. Когда не происходит распознавания черт лица на выходе идет такая абракадабра, что простым анализом, например, линия рта или овала лица не может иметь острых углов или размер глаза не может быть больше трети от лица, легко отсеивается.

13a3c8551c6d41fe51213cc540fb6ae1

Для лучшего распознавания элементов лица некоторым алгоритмам (например из библиотеки deepface) желательно чтобы лицо было выровнено по линии глаз, но некоторые обходятся и без этого (из face_recogintion, dlib).

Кодировка лица заключается в переводе изображения лица (или только координат полученных элементов лица) в то представление, в которым можно осуществлять сравнение двух лиц. Чаще всего это матричный вид. Иногда этот этап совмещен с предыдущим. Особых тонкостей и возможностей тонкой настройки тут нет, просто приходиться использовать вариант из той библиотеки, с помощью которой осуществлен поиск элементов лица.

После кодировки лицо можно сравнить с кодировками шаблонов других лиц и по «близости» к ним судить о принадлежности одному или другому человеку.

Вот как раз с шаблонами и началась самая большая заморочка.

При малом количестве шаблонов (я в начале взял по одной фотографии лица каждого человека которого надо было распознавать, потом по 10) у меня так и не получилось подстроить пороги срабатывания так, чтобы близкие родственники однозначно распознавались, кто есть кто. Поставишь большой порог, система путает братьев/сестер, поставишь маленький, и для многих фотографий соответствия вообще не находится. В итоге, методом многократных итераций, подобрал максимальный порог при котором не было путаницы между отдельными людьми и смирился с тем что шаблонов придется делать много, очень много. Теперь если взять фотографии за тот год из которого нет ни одного шаблона, то лишь где-то две трети лиц будут однозначно распознаны, остальные либо попадут в категорию «вроде бы похож» (weak match) либо вообще не распознаны, такие приходиться посмотреть глазами, добавить в шаблоны и повторить итерацию сравнения.

«Профильно-фронтальные» проблемы

Прогнав через систему фотографии за несколько лет, я понял, что большинство несоответствий идет тогда, когда лицо повернуто в сторону, т. е. алгоритм не проводит соответствие между лицами, принадлежащими одному человеку, но под разным углом к камере.

К сожалению, я не смог найти в открытом доступе готовых алгоритмов которые решают эту задачу и смирился с тем что шаблонов потребуется много. При этом я также понял, что в большинстве случаев для исходных задач (а ну-ка найди мне фотографии Алисы с бабушкой) чаще всего фотографии в профиль не так важны, поэтому добавил в систему возможность отсекать фотографии в профиль, тем самым снижая объем ручной работы. Так что, если кто-то захочет воспользоваться моей системой, но при этом не готов на подвиг в виде бессонных ночей рассматривания сотен и тысяч (пусть даже любимых) лиц, то можно просто использовать конфигурационный файл с именем frontal.cfg и снизить объем последующей ручной работы в несколько раз.

А что насчет видео?

Разобравшись с фотографиями, я решил, а не замахнуться ли нам на Вильяма, понимаете ли, нашего Шекспира распознавание лиц в видео? Эта задача, с практической точки зрения, выглядела даже более полезной чем предыдущая, так как, если на фотографии по иконкам в режиме предпросмотра можно довольно быстро понять кто есть кто, то на видео эта задача уже практически нерешаемая, лицо-то в видео далеко не всегда на первом же кадре. По реализации эта задача не очень сильно отличается от работы с фото, только вместо одного кадра на файл их теперь несколько тысяч.

В начале сделал «в лоб»: распознавать все кадры и все лица, но после прогона первых нескольких дней было решено сделать несколько оптимизаций, так как объем ручной работы предстоял немалый, да и ждать обработки всех видео файлов пришлось бы не один месяц.

Во-первых, распознавать только первые несколько минут видео (max_video_frames в конфиге) так как, чаще всего, все главные действующие лица уже появились на видео. Во-вторых, распознавать не каждый кадр, а с некоторым прореживанием (video_frames_step в конфиге) так как главные действующие лица не мелькают в кадре лишь на мгновение, а держатся долго. Ну и в довершение, лицо должно попасться не в одном кадре, а в нескольких (min_video_face_count в конфиге) по той же причине, если лицо только мигнуло, то для исходной задачи оно не так уж и важно.

Впоследствии я также попытался применить наработанную базу к распознанию лиц с камер видеонаблюдения (с целью подключить к умному дому), но это уже другая история. Если кратко, все это довольно просто делается с помощью написания буквально пары десятков строк. Но, увы, те камеры, что сейчас у меня стоят и под тем углом, что они стоят, не обеспечивают достаточного разрешения, чтобы уверенно работать в нужном режиме, так что, пока, от этой идеи отказался.

Если кому-то захотелось воспользоваться моими наработками, все исходники и краткая инструкция тут.

Под катом я приведу

несколько скриншотов и сценариев использования системы

Представим, что вы из тех людей, что читают README файлы и систему уже поставили и даже запустили.

Перед вами главное окно. Запустим распознавание папки:

«Recognition» → «Add new files…»

d214fba5d826c9b86853bc957c0b6683

(первый запуск будет довольно долгий, так как библиотеки подгружают необходимые модели из Интернета)

959dd751879520910b8e590ab17f5850

Получим кучу нераспознанных лиц:

639dfdb9202afd11fca93eea03e97497

Добавим (кликом на нужные или выделением с последующим кликом на одну из выделенных) их в шаблоны, введем новое имя для прекрасной незнакомки:

715b9af6b82059ad293ddd1f6d0cefd1

По мере добавления новых людей, список будет расширяться и вводить имена будет не нужно, достаточно выбрать из уже введённых ранее (заглавную фотографию человека можно заменить подложив в папку с шаблонами файл 0_face.jpg). 

0a770ab0f00d5e07f570e72085a3257b

Если в списке очень много людей, то можно начать набирать имя с клавиатуры, при наборе, имя будет подсвечено. Если лицо на фотографии вас не интересует, либо вообще произошла ошибка распознавания, то следует отправить его в корзину (trash).

Если просочилась ошибка поиска элементов лица, до данную фотографию опасно добавлять в шаблоны, так как совершенно непредсказуемо, что именно потом с ним совпадет. Для этого есть галочка «Bad encoding», текущая фотография будет отнесена к данному человеку, но в сравнениях с другими она участвовать не будет.

Повторим сравнение: «Match» → «Rematch folder…».

После этого часть лиц будет однозначно распознана, а часть попадет в категорию «weak», т.е. похож, но не совсем. По этой категории следует пройтись отдельно и также добавить в шаблоны. Можно не бояться добавить в шаблоны много, это лишь улучшит качество распознания, выделять группу лиц можно с помощью Shift и Ctrl. 

Пока лица находятся в категории «weak» или «unknown» они не будут синхронизироваться и по ним не будет осуществляться поиск.

3e57cc44dcaae154fb7864a6b05afe98

Если для понимания «кто есть кто» нужно больше контекста, есть две подсказки, во-первых, на фотографии отображается год (предполагается что фотографии именованы с помощью системы из прошлой статьи), во-вторых, по клику на иконку

4bfc8c9082702d430a48d3b8402e1da4

откроется оригинал фотографии.

6df416e1a3edf0d22ae97817ea7f313c

А если хочется узнать с каким именно шаблоном было совпадение можно кликнуть на иконку

5d2585a12236a77efe5f08a2d4236dbb

(если она подсвечена зеленым, то значит именно эта фотография и была использована как шаблон).

Ну распознали мы все, а что дальше?

Как дальше искать людей? Теперь в базе данных есть информация по каждой фотографии, кто на ней изображен. Отлично! Но как смотреть теперь? Поскольку у меня основной системой просмотра фотографий сейчас является Plex, то я не придумал ничего лучше, чем экспортировать данные о людях на фото в эту его базу в виде тегов. К сожалению, открытого API у них нет, но, к счастью, его база просто хранится в sqlite файле и имеет не очень сложный формат. Поэтому я просто пишу туда теги на прямую. (Не буду грузить статью деталями реализации базы данных Plex, но, если кому-то они интересны, могут посмотреть в исходниках в файле plexdb.py).

Для синхронизации же непосредственно тегов я сделал отдельный скрипт. На веб не выносил, так как его проще регулярно запускать по крону.

face-rec-plexsync -a set_tags

Немного подождать и вуаля! Теперь можно искать!

eb7aac72db00d6b0d6904b33b7d4976c

И все было замечательно до тех пор, пока некоторое время назад кто-то особо талантливый в команде разработки Plex не поломал расширенный поиск. Так что искать по отдельным лицам можно, а вот уже по сложным критериям (несколько лиц, лицо и год и т.д. теперь нельзя). Баг у них на форуме висит уже больше года, народ жалуется, но воз и ныне там. Поэтому я добавил возможность поиска в консоль и если консоль такой же ваш друг, как и мой, то можно обойтись и ей. (Up: в момент когда статья была уже на финальной стадии, баг все-таки починили и в следующем релизе все должно работать)

Например, с помощью вот такого запроса, можно найти все фотографии с Тимой и Алисой за 2020 год

face-rec-db -a find_files_by_names -f 2020 -n Тима,Алиса

Понятно, что смотреть фотографии в консоли не очень интересно, но если добавить после команды что-то вроде

| xargs -I{} ln -s {} /mnt/multimedia/query/ 

то в нужной папке появятся символические ссылки на искомые файлы, а дальше можно просматривать их любой удобной программы, как обычную папку с фотографиями.

Так же, если вы для просмотра используете какую-либо другую систему работы с фотографиями, а слово «питон» для вас означает больше чем животное из отряда чешуйчатых, то, на базе модуля plexsync.py, вам не составит труда написать синхронизацию с вашей системой.

Разумеется это не полное описание всех возможностей, помимо описанного сценария, есть возможность, например, кластеризации, чтобы сгруппировать нераспознанные или плохо распознанные лица. Еще есть возможность поиска по фотографии (был ли вообще такой человек в домашнем архиве?). Еще можно запускать распознавание в консольном режиме или синхронизировать набор фотографий с базой Plex. Есть различные инструменты для работы с шаблонами и т.д.

Заключение

Вот такой вот небольшой пример наведения порядка в домашних фотографиях.

Многое уже сделано, стало гораздо удобнее, но еще есть масса «хотелок», надеюсь когда-нибудь и до них дойдут руки:

  • Распознать голос в видео и аудио записях. Есть десятки тысяч аудио заметок и искать по ним вообще невозможно, это даже хуже, чем видео.

  • Кроме лиц добавить распознание других объектов: животные, предметы.

  • Как фантазия на будущее, распознавать условия съемки и окружение: горы, море, в помещении и т.д.

  • Тоже, скорее из фантазий: попробовать научить нейронку отличать плохие фото от хороших на базе большой выборки, но боюсь, что будет точность как отличием фотографий фас и профиль, нужна очень большая выборка, пока такой нет.

Иногда мне кажется, оборачиваясь назад и оценивая затраченное время, что было бы проще оплатить какую-нибудь облачную систему, типа гугл-фото, которая предоставляет в каком-то виде подобный функционал, но, во-первых, локальное хранение (с бекапом, само собой) надежнее и быстрее, а, во-вторых, я получил неоценимый опыт и это главное!

Спасибо за внимание!

© Habrahabr.ru