Автор: Sol Kogan
Так ли сложно сделать аналог FindFace? Разбираемся, как можно спарсить фото из ВК, распознать на них лица и сделать по ним поиск.
Когда в сентябре 2018 года FindFace стал закрытым проектом, аноны со всего интернета грустно вздохнули. Теперь нельзя было найти девушку, сфотографированную в автобусе, или сдеанонить любимую порноактрису. Но в начале 2019, некие аноны запилили годный сервис по адресу searchface.ru, работающий не хуже, а может и лучше продавшегося властям FindFace. Однако счастье длилось недолго - Вконтакте собрались подать на создателей сайта в суд, и серчфейс исключил из выдачи ID Вконтакте найденных аккаунтов. Теперь доступны только фото пользователей, а узнать их имя, или перейти на профиль ВК нельзя.
Энтузиасты предложили определять ID ВКонтакте по URL-ам фоточек, которые выдает сайт. Но как ассоциировать URL фото и ID ВК? Некоторые решили спарсить все URL всех фотографий всех пользователей. Естественно, данная инициатива заняла бы очень много времени, учитывая ограничения API ВК. Проблема была еще и в том, что через API можно было получать только URL-ы фотографий, которые не были удалены, а в выдаче серчфейса было довольно много старых, уже удаленных пользователями фотографий.
Но так ли сложно сделать простую систему распознавания лиц, в масштабах, скажем, небольшого города? Ради эксперимента, один исследователь решил скачать фотографии девушек от 18 до 30 лет, живущих в его городе, и сделать свой, локальный аналог FindFace.
Прежде всего нужно было выкачать фотографии с лицом каждой девушки из данного города. В ВК API есть команда users.search, которая позволяет искать людей ВК по заданным параметрам. В ответ можно получить множество информации о юзере, включая ссылку на фото его аватарки. Единственной проблемой является ограничение данной API команды на количество возвращаемых результатов - не более 1000 людей за запрос. Чтобы выкачать всех девушек небольшого городка пришлось делать вложенные циклы, которые давали запрос на поиск сперва всех девушек, родившихся в январе возрастом 18 лет, потом родившихся в феврале, этого же возраста, и так до тридцатилетних девушек родившихся в декабре. Если бы город был большой (скажем как Челябинск), пришлось бы делать еще один вложенный цикл, и перебирать не только месяца но и дни в них, с целью ограничить результаты каждого ответа сервера количеством девушек не более 1000.
import vk_api
import time, codecs
import os, sys, shutil, requests
import os.path
vk_session = vk_api.VkApi('вашлогин', 'вашпароль')
vk_session.auth()
vk = vk_session.get_api()
vozrast=18
vozrastmax=30
citynumber=906
psex=1
ff=codecs.open('ids.txt', 'w', encoding='utf8')
while(vozrast<=vozrastmax):
mesac=1
while(mesac<=12):
time.sleep(1)
z=vk.users.search(count=1000, fields='id, photo_max_orig, has_photo, first_name, last_name', city=citynumber, sex=psex, age_from=vozrast, age_to=vozrast, birth_month=mesac)
mesac=mesac+1
for x in z['items']:
if(x['has_photo']==1):
s=str(x['id'])+'|'+str(x['photo_max_orig'])+'|'+str(x['first_name'])+' '+str(x['last_name'])+'\n'
ff.write(s)
vozrast=vozrast+1
ff.close()
print('Done!')
В данном кусочке кода мы получили через API ВК следующую информацию - ID ВК девушки, ссылку на фото ее аватарки, имя и фамилию, и записали полученную информацию в обычный текстовый файл с названием ids.txt
Теперь, когда у нас есть ссылки на фотографии аватарок девушек, нужно их скачать. Здесь никаких ограничений нет, скачивать будем через request.
import time, shutil
import random, codecs, os, requests
def loadfile(name,url):
if not(os.path.exists('users/'+str(name)+'.jpg')):
r = requests.get(url, stream=True)
if r.status_code == 200:
with open('users/'+str(name)+'.jpg', 'wb') as f:
r.raw.decode_content = True
shutil.copyfileobj(r.raw, f)
file=codecs.open('ids.txt', 'r', encoding='utf8')
for x in file:
mas=x.split('|')
userid=int(mas[0])
f1=mas[1]
loadfile(str(userid),f1)
ff.close()
Данный кусочек программы пробежится по строкам файла ids.txt, скачает каждую аватарку и сохранит ее в папке users, причем название сохраненного фото будет содержать ID ВК.
Имея на руках папку users с аватарками всех девушек маленького городка, необходимо задействовать код, который позволяет найти на фотографии лицо, и получить его параметры.
Для работы нам понадобятся предварительно обученные модели для выделения лица на фотографии и извлечения дескрипторов из лица. Эти модели можно скачать с сайта dlib:
http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2
http://dlib.net/files/dlib_face_recognition_resnet_model_v1.dat.bz2
Мы приведем здесь пример кода который берет два фото и сравнивает их между собой.
import dlib, os
import numpy as np
from skimage import io
from scipy.spatial import distance
sp = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')
facerec = dlib.face_recognition_model_v1('dlib_face_recognition_resnet_model_v1.dat')
detector = dlib.get_frontal_face_detector()
def getfacedescriptor(filename):
img = io.imread(filename)
win1 = dlib.image_window()
win1.clear_overlay()
win1.set_image(img)
dets = detector(img, 1)
for k, d in enumerate(dets):
shape = sp(img, d)
win1.clear_overlay()
win1.add_overlay(d)
win1.add_overlay(shape)
try:
face_descriptor1 = facerec.compute_face_descriptor(img, shape)
except:
face_descriptor1=None
return(face_descriptor1)
f1=getfacedescriptor('1.jpg')
f2=getfacedescriptor('2.jpg')
a = distance.euclidean(f1,f2)
print('Result: '+str(a)+' (< 0,52 = Win!)')
Чем меньше результат сравнения, тем более вероятно что на двух фото один и тот же человек. Как правило, значение менее 0,52 свидетельствует о совпадении.
Все что нужно у нас уже есть. Мы не будем приводить весь код программы (чтобы не радовать скрипт-киддис), но далее нам просто нужно пробежаться по папке с сохраненными фото, распознать на каждой из них лицо, получить его дескриптор, и сохранить дескрипторы в каком-либо виде, чтобы не распознавать все фото каждый раз, а сравнивать искомое фото с числовыми параметрами заранее высчитанных дескрипторов.
Сохранять дескриптор в файл можно например так:
m = np.array(f1)
np.save(filename, m)
И читать назад в переменную вот так:
f1=np.load(filename)
В имена файлов с дескрипторами также пишем ID ВК. Даже последовательный перебор и сравнение сотни тысяч сохраненных в файлы дескрипторов на среднем ноутбуке занимает примерно две секунды. Можно конечно заморочиться, и упорядочить их, по параметрам, чтобы ускорить поиск, но в рамках данного эксперимента с числом аккаунтов ВК менее 100 тысяч, задержка в 1-2 секунды была несущественна.
Мы просто перебираем сохраненные дескрипторы и с помощью
a = distance.euclidean(f1,f2)
получаем Евклидовое расстояние между f1 - очередным дескриптором из нашей коллекции, и f2 - дескриптором лица искомого фото. Ищем среди таких расстояний наименьшее значение, и получаем файл фото наиболее похожего человека. Как вы помните, в именах файлов с дескрипторами мы храним ID ВКонтакте, поэтому просто получаем имя файла того дескриптора, который имеет с искомым наименьшее значение Евклидова расстояния - и вот, у нас уже есть ID ВК нужного нам человека.
Теперь немного о подводных камнях - при выкачивании аватарок выяснилось, что половина юзеров ставят на аву смешные картинки, котиков, бэтмена, что угодно кроме своего лица. Поэтому, пришлось добавить в код скачивания аватарки детектор лиц, и в случае, если лицо не найдено, скачивать дополнительные фото из альбомов юзера до тех пор, пока не наткнемся на фото с лицом. Для гарантии можно скачать 3-5 фотографий юзера на которых есть лицо, тогда вероятно что на одной из них все же будет он сам.
Получить список фотографий юзера можно опять же через VK API
vk.photos.getAll(owner_id=userid, count=100, no_service_albums=0)
Это сильно замедлит скачивание фото, потому что у ВК есть ограничение API в 3 запроса за секунду. Выходом может быть разделение скачивания фото на части с помощью разных аккаунтов ВК, и прокси серверов.
Как видим, самой большой проблемой является время затраченное на сбор фотографий юзеров. На небольшой городок с населением около сотни тысяч человек, с учетом того, что качались дополнительные фото с аккаунтов без лиц на аватарке ушло около трех дней. Пришлось также учесть размер лица на фотографиях, если оно было слишком мелким, система давала сбои, поэтому прежде чем получать параметры лица, необходимо измерить его размер на фото, и если он недостаточен, брать следующее фото, в поиске крупного плана. Идеально было бы еще как то отличать фото лиц сделанные в анфас, и парсить только их, а также исключать из базы фото с двумя и более лицами на снимке.
Тем не менее, при всех недоработанных моментах, простейшее получение дескрипторов лиц с помощью dlib и сравнение их с параметрами лица искомого человека показало прекрасные результаты, программа находила людей в большинстве случаев. Как вы уже поняли, можно скормить подобной программе не только фотографии из профилей Вконтакте. Другие социальные сети, Whatsapp, сайты знакомств также подходят как источник данных. А если у кого то на черном рынке есть база паспортов граждан, все становится гораздо проще - так как на паспорте обычно имеется четкое фото в анфас, которого в большинстве случаев достаточно для целей распознавания.
Помните, что любое ваше фото в профиле любой соцсети позволяет с помощью подобных технологий найти ваш аккаунт, по фото сделанными к примеру уличной камерой. Сейчас в Москве полиции хотят выдать очки с встроенной видеокамерой. С помощью подобной системы очки будут идентифицировать человека на которого смотрит страж порядка.
Результатом данного исследования явился тот факт, что получение информации из открытых источников с помощью сравнения лиц на фотографиях, не является слишком уж трудной задачей. Уже сейчас с помощью таких вот программ любой прохожий, просто сфотографировав вас сможет узнать ваше имя, сколько вам лет, в каких группах вы состоите, и кому ставите лайки. При условии, что вы оставили подобную информацию о себе в сети. Делайте выводы.