Рассказывает Деван Сабаратнам, разработчик с 30-летним стажем
В минувшие выходные, пролистывая Amazon Web Services, я заметил новый сервис под названием «Rekognition». Я предположил, что это опечатка (recognition — англ. распознавание), но она привлекла мое внимание. Я заинтересовался: что это за сервис? Amazon привык добавлять новые сервисы в свою платформу с пугающей регулярностью, и этот я пропустил.
Я узнал, что в конце 2016 года Amazon выпустила свой собственный сервис для распознавания изображений на базе глубокого обучения на их платформе. Он может распознавать не только лица, но и объекты на фото. Так как сервис довольно новый, подробностей о нём было немного, но мне захотелось немедленно попробовать его. В общем, в течение часа я написал пример веб-страницы, которая может получать фотографии с моей веб-камеры и выполнять элементарное распознавания лица на нем.
Прим. перев. Чтобы не теряться в многочисленных сервисах Amazon, советуем вам прочитать нашу шпаргалку по AWS.Раньше я занимался технологией распознавания лиц, используя сторонние библиотеки, а также Microsoft Face API, но все попытки создания подобного приложения не увенчались успехом. Но, просматривая документацию «Rekognition», я понял, что AWS API на самом деле очень прост в использовании. Я немедленно принялся за работу.
Цель
Мне была нужна простая веб-страница, которая позволила бы делать фотографию с помощью камеры моего iMac и выполнять распознавание на фотографии. В частности, я хотел бы определять пользователя, сидящего перед компьютером.
Сервис Amazon Rekognition позволяет создавать одну или несколько коллекций. Коллекция — это набор лицевых векторов для фотографий, которые вы хотите сохранить.
Примечание: Сервис сохраняет не фотографии, а их JSON-представление.
После создания коллекции вы можете сфотографировать предмет, сравнить его свойства c сохранёнными и вернуть ближайшее соответствие. Звучит просто, не так ли? По правде говоря, разработка фронтенда веб-страницы для получения данных с камеры заняла больше времени, чем написание бэкенда для распознавания.
В общем, веб-страница позволяет создавать или удалять коллекцию лицевых данных на Amazon, загружать новые данные, полученные из фотографии, в свою коллекцию и сравнивать новые фотографии с существующей коллекцией, чтобы найти совпадение. А в качестве дополнительной фичи я также добавил в эту демку службу Amazon Polly, чтобы после распознавания фотографии страница приветствовала пользователя.
Фронтенд
Я не знал, какую библиотеку использовать для захвата изображения с помощью камеры iMac. В итоге я нашел на GitHub библиотеку JPEG Camera, которая позволяет использовать HTML5 Canvas или Flash для съёмки фото. Я решил использовать её и настроил под себя написанный на JavaScript образец.
Бэкенд
Для бэкенда я использовал Ruby-библиотеку Sinatra, которая может выполнять всю тяжёлую работу с помощью AWS. Я часто использовал Sinatra (на самом деле Padrino) в своих проектах и настоятельно рекомендую эту платформу.
Примечание: Amazon Rekognition предлагает сначала загружать исходные фотографии, которые использует его API, в Amazon S3, а затем обрабатывать их. Я хотел избежать этого ненужного шага и вместо этого отправлять изображения непосредственно в API, что мне в итоге удалось сделать.
Я сумел сделать то же самое с их приветствием Polly . Вместо того, чтобы сохранять аудио в MP3-файл и проигрывать его, у меня получилось закодировать данные MP3 непосредственно в тег <audio>
на странице и воспроизвести их оттуда.
Код
Я разместил весь код этого проекта на моей странице на GitHub. Не стесняйтесь использовать и изменять его. Ниже я постараюсь объяснить код более подробно.
Пишем приложение
Прежде всего, вам понадобится учетная запись Amazon AWS. Я не буду вдаваться в подробности, поскольку это несложно, а в случае затруднений можно легко найти информацию в Интернете.
Создание пользователя AWS IAM
Как только вы создали учетную запись AWS, первое, что нам нужно сделать, — создать пользователя Amazon IAM (Identity & Access Management), который имеет права на использование службы Rekognition. Мы также зададим права для Amazon Polly.
В консоли Amazon нажмите на кнопку «Сервисы» в верхнем левом углу, затем выберите «IAM». В меню слева выберите «Пользователи». Вы должны увидеть список существующих пользователей IAM, которых вы создали в консоли, если вы делали это в прошлом.
Нажмите кнопку «Добавить пользователя», расположенную в верхней части списка, чтобы добавить нового пользователя IAM.
Дайте пользователю имя и убедитесь, что вы отметили пункт «Programmatic Access», так как вы будете использовать этот IAM в вызове API.
Далее приведены настройки разрешений. Убедитесь, что вы щелкаете по третьему квадратику на экране с надписью «Attach existing policies directly». Затем в поле поиска «Filter: Policy Type» введите «rekognition». Выберите «AmazonRekognitionFullAccess» из списка, поставив рядом с ним галочку.
Затем измените фильтр поиска на «polly» и поместите галочку рядом с «AmazonPollyFullAccess».
Теперь у этого IAM есть права, достаточные для Amazon Rekognition и Amazon Polly. Нажмите «Next: Review» в правом нижнем углу.
На странице просмотра вы должны увидеть 2 политики использования, дающие вам полный доступ к Rekognition и Polly. Если вы их не видите, вернитесь и повторите предыдущий шаг. Затем нажмите кнопку «Создать пользователя» в правом нижнем углу.
Эта страница важна. Запишите ключи AWS Key и AWS Secret, которые вы указали на этой странице, поскольку нам необходимо включить их в наше приложение ниже.
Это единственный раз, когда вам будут показаны эти ключи, поэтому сохраните их и файл CSV с этой страницы в надёжном месте.
Загрузка кода
Теперь загрузите пример кода с моей страницы GitHub, чтобы вы могли изменить его по мере необходимости. Загрузите код в виде ZIP-файла, либо склонируйте его в вашу рабочую папку.
Первое, что вам нужно сделать, — это создать файл с именем .env
в рабочей папке и ввести эти две строки, заменив в них ключи Amazon IAM на свои:
export AWS_KEY=A1B2C3D4E5J6K7L10
export AWS_SECRET=T/9rt344Ur+ln89we3552H5uKp901
Теперь, если у вас установлен Ruby (не Ruby on Rails), то для установки зависимостей запустите команду:
bundle install
Для запуска приложения введите эту команду:
ruby faceapp.rb
Она должна запустить веб-браузер на порте 4567, чтобы вы могли увидеть веб-страницу и начать тестирование:
http://localhost:4567
Использование приложения
Сама веб-страница довольно проста. Вы должны увидеть потоковое изображение в верхней части экрана, которое представляет собой канал с вашей веб-камеры.
Сперва создайте коллекцию, щелкнув по ссылке в самом нижнем левом углу страницы. Это создаст пустую коллекцию на серверах Amazon для хранения ваших изображений. Обратите внимание: имя по умолчанию для этой коллекции — faceapp_test
, но вы можете изменить его в коде faceapp.rb
(строка 17).
Затем, чтобы начать добавление лиц в свою коллекцию, попросите нескольких людей сесть перед вашим компьютером или телефоном и убедиться, что их лицо находится только в рамке для фотографий (несколько лиц сделают сканирование неудачным). Когда всё будет готово, введите их имя в текстовое поле и нажмите кнопку «Добавить в коллекцию». Должно появиться сообщение о том, что данные о лице были добавлены в базу данных.
После того, как вы создали несколько лиц в своей базе данных, вы можете попросить случайного человека сесть перед камерой и нажать «Сравнить изображение». Если этот человек уже добавлен в коллекцию, на экране должно появиться его имя.
Обратите внимание, что обычный способ работы Amazon Rekognition заключается в том, чтобы загружать фотографию в Amazon S3 Bucket, а затем обрабатывать его оттуда, но я хотел обойти этот шаг и фактически отправить данные фотографии непосредственно в Rekognition как поток байтов, закодированный в Base64. К счастью, aws-sdk для Ruby позволяет использовать оба метода.
Разбор кода
Прежде всего, давайте взглянем на HTML-страницу:
<html>
<head>
<title>Face Recognition Test</title>
<link href="css/camera.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h1>Face Recognition Test</h1>
<div id='camera'>
<div id='placeholder'>
<p>Your browser does not support a camera!</p>
</div>
</div>
<br>
<p>
<button id='compare_image'>Compare Image</button>
</p>
<br>
<input id='photo_id' placeholder='Enter name for photo' type='text'>
<button id='add_to_collection'>Add To Collection</button>
<br>
<div id='upload_status'></div>
<div id='upload_result'></div>
<br>
<img height='100' id='loading_img' src='images/loading.gif' width='100'>
<audio id='audio_speech' src='#'></audio>
<br>
<br>
<p>
<a href="/collection/create">Create Collection</a> | <a href="/collection/delete">Delete Collection</a>
</p>
<script src='https://code.jquery.com/jquery-3.1.1.min.js'></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment.min.js"></script>
<script src="js/jpeg_camera/jpeg_camera_with_dependencies.min.js" type="text/javascript"></script>
<script src="js/faceapp.js" type="text/javascript"></script>
</body>
</html>
Структура страницы проста: всего несколько блоков, кнопок и ссылок. Обратите внимание, что мы используем jQuery, а также Moment.js для настраиваемого приветствия. Следует обратить внимание на код faceapp.js
, который выполняет все сложные задачи и ссылки на библиотеку камер JPEG.
Вы также можете заметить теги <audio>
в нижней части файла — это аудио-приветствие, которое мы отправляем пользователю.
Теперь разберем JS-файл приложения:
$(document).ready(function() {
if (window.JpegCamera) {
var camera;
// Добавить фотографии к текущей коллекции Rekognition для последующего сравнения
var add_to_collection = function() {
var photo_id = $("#photo_id").val();
if (!photo_id.length) {
$("#upload_status").html("Please provide name for the upload");
return;
}
var snapshot = camera.capture();
var api_url = "/upload/" + photo_id;
$("#loading_img").show();
snapshot.upload({api_url: api_url}).done(function(response) {
$("#upload_result").html(response);
$("#loading_img").hide();
this.discard();
}).fail(function(status_code, error_message, response) {
$("#upload_status").html("Upload failed with status " + status_code + " (" + error_message + ")");
$("#upload_result").html(response);
$("#loading_img").hide();
});
};
// Сравнить фотографию с текущей коллекцией Rekognition
var compare_image = function() {
var snapshot = camera.capture();
var api_url = "/compare";
$("#loading_img").show();
snapshot.upload({api_url: api_url}).done(function(response) {
var data = JSON.parse(response);
if (data.id !== undefined) {
$("#upload_result").html(data.message + ": " + data.id + ", Confidence: " + data.confidence);
// создание звукового отчета
$.post("/speech", {tosay: "Good " + greetingTime(moment()) + " " + data.id}, function(response) {
$("#audio_speech").attr("src", "data:audio/mpeg;base64," + response);
$("#audio_speech")[0].play();
});
} else {
$("#upload_result").html(data.message);
}
$("#loading_img").hide();
this.discard();
}).fail(function(status_code, error_message, response) {
$("#upload_status").html("Upload failed with status " + status_code + " (" + error_message + ")");
$("#upload_result").html(response);
$("#loading_img").hide();
});
};
var greetingTime = function(moment) {
var greet = null;
if(!moment || !moment.isValid()) { return; } // Если мы не можем найти действительный момент, мы возвращаем пустое значение
var split_afternoon = 12 // Определение дня
var split_evening = 17 // Определение вечера
var currentHour = parseFloat(moment.format("HH"));
if(currentHour >= split_afternoon && currentHour <= split_evening) {
greet = "afternoon";
} else if(currentHour >= split_evening) {
greet = "evening";
} else {
greet = "morning";
}
return greet;
}
// Определение кнопок
$("#add_to_collection").click(add_to_collection);
$("#compare_image").click(compare_image);
// Инициализация виджета камеры на экране
var options = {
shutter_ogg_url: "js/jpeg_camera/shutter.ogg",
shutter_mp3_url: "js/jpeg_camera/shutter.mp3",
swf_url: "js/jpeg_camera/jpeg_camera.swf"
}
camera = new JpegCamera("#camera", options).ready(function(info) {
$("#loading_img").hide();
});
}
});
Это настраивает библиотеку JPEG Camera, чтобы отображать канал на экране и обрабатывать загрузку изображений.
Функция add_to_collection()
захватывает изображение с камеры, а затем выполняет запись в конечную точку /upload
вместе с именем пользователя в качестве параметра. Функция проверяет, действительно ли вы ввели имя, которое нужно в качестве уникального идентификатора этих данных.
Функция загрузки проверяет вызов и выводит сообщение об успешном завершении или ошибке.
Функция compare_image()
вызывается, когда вы нажимаете кнопку «Сравнить изображение». Она захватывает кадр из камеры и передает POST-данные в /compare
. Эта конечная точка вернет либо ошибку, либо структуру JSON, содержащую id
(имя) найденного лица, а также процент схожести.
Если лицо совпадёт с данными из коллекции, функция отправит имя найденного лица в /speech
. Эта конечная точка вызывает службу Amazon Polly, чтобы преобразовать приветствие в файл MP3, который можно воспроизвести пользователю.
Служба Amazon Polly возвращает приветствие в виде бинарного потока MP3, поэтому мы берем этот поток ввода-вывода, шифруем его в формате Base64 и помещаем в качестве закодированной исходной ссылки в теги <audio>
на нашей веб-странице. Затем мы можем вызвать .play()
для воспроизведения MP3 через динамики пользователя с помощью HTML5 Web Audio API.
Наконец, в JS-файле приложения есть функция greetingTime()
. Она решает, стоит ли говорить «доброе утро / день / вечер» в зависимости от времени суток пользователя.
Теперь взглянем на код Ruby:
# faceapp.rb
require 'rubygems'
require 'bundler'
Bundler.require
require 'sinatra'
Dotenv.load
# Настройка проверки подлинности aws в этом приложении
Aws.config.update({
:region => 'us-east-1',
:credentials => Aws::Credentials.new(ENV['AWS_KEY'],ENV['AWS_SECRET'])
})
# Название коллекции по умолчанию
FACE_COLLECTION = "faceapp_test"
# Путь
get '/' do
# Показать на главной странице
erb :faceapp
end
post '/upload/:photoid' do
client = Aws::Rekognition::Client.new()
response = client.index_faces({
collection_id: FACE_COLLECTION,
external_image_id: params[:photoid],
image: {
bytes: request.body.read.to_s
}
})
"Image uploaded safely!"
end
post '/compare' do
content_type :json
client = Aws::Rekognition::Client.new()
response = client.search_faces_by_image({
collection_id: FACE_COLLECTION,
max_faces: 1,
face_match_threshold: 95,
image: {
bytes: request.body.read.to_s
}
})
if response.face_matches.count > 1
{:message => "Too many faces found"}.to_json
elsif response.face_matches.count == 0
{:message => "No face detected!"}.to_json
else
# "Сравнение закончил - обнаружил #{ response.face_matches[0].face.external_image_id } with #{ response.face_matches[0].face.confidence } accuracy."
{:id => response.face_matches[0].face.external_image_id, :confidence => response.face_matches[0].face.confidence, :message => "Face found!"}.to_json
end
end
post '/speech' do
client = Aws::Polly::Client.new()
response = client.synthesize_speech({
output_format: "mp3",
voice_id: "Joanna",
text: params[:tosay]
})
Base64.encode64(response.audio_stream.string)
end
get '/collection/:action' do
client = Aws::Rekognition::Client.new()
collections = client.list_collections({}).collection_ids
case params[:action]
when 'create'
if !(collections.include? FACE_COLLECTION)
response = client.create_collection({ collection_id: FACE_COLLECTION })
end
when 'delete'
if (collections.include? FACE_COLLECTION)
response = client.delete_collection({ collection_id: FACE_COLLECTION })
end
end
redirect '/'
end
Здесь задан блок настройки конфигурации аутентификации AWS и имени коллекции по умолчанию, которое мы будем использовать (вы можете его свободно менять).
Остальная часть кода — это конечные точки, которые Sinatra будет слушать. Он прослушивает GET в /
, чтобы отобразить фактическую веб-страницу конечному пользователю, а также слушает вызовы POST к /upload
, /compare
и /speech
, которым JS-файл отправляет данные. Только 3–4 строки кода для каждой из этих конечных точек фактически выполняют задачи распознавания лиц и речи. Подробнее о них можно узнать из документации AWS SDK.
Перевод статьи «Building a face recognition web app in under an hour»