ноябрь 2016
Том 31 Номер 11
Современные приложения - Добавление средств распознавания лиц в ваше приложение
На конференции Build 2016 компания Microsoft объявила о выпуске Cognitive Services API. Среди множества доступных API есть несколько сервисов машинного зрения. Эти сервисы могут анализировать возраст и пол лиц на входном изображении. Есть даже API для распознавания эмоций индивидуума на основе выражения его лица. Для рекламы этой технологии по всей территории были расставлены многочисленные киоски, демонстрирующие различные области ее применения. Cognitive Services API создан на основе знаний и усилий Microsoft в области машинного обучения. Тысячи размеченных изображений пропускались через нейронную сеть. Лучше всего то, что вы можете использовать эти сервисы, ничего не зная о машинном обучении или искусственном интеллекте. Вы просто вызываете некий веб-сервис из своего приложения. Интервью с одним из членом группы, участвовавшим в этом проекте, можно посмотреть по ссылке bit.ly/1TGi1QK.
С помощью Cognitive Services API можно добавить базовые средства распознавания лиц в приложение, не вызывая никакие API. Пространство имен Windows.Media.FaceAnalysis содержит функциональность для распознавания лиц на изображениях или в видео. Это базовый набор, где нет богатых возможностей Cognitive Services. По сути, он очень похож на распознавание лиц во многих цифровых камерах. Хотя эти средства являются базовыми, у них есть два преимущества: они работают без подключения к сети и, поскольку вы не обращаетесь к какому-либо API, не влекут за собой никаких издержек. В качестве стратегии оптимизации ваше приложение может локально распознавать наличие лица до вызова Cognitive Services API. Тем самым приложение не будет отправлять изображения, заведомо не содержащие лиц, в Cognitive Services API. Это может дать вам значительную экономию затрат и сократить использование полосы пропускания для ваших пользователей. Локальное распознавание лиц может быть полезным дополнением при применении интеллектуальных облачных сервисов вроде Cognitive Services.
Подготовка проекта
В Visual Studio 2015 создайте новый проект Universal Windows Platform (UWP) App, выберите шаблон Blank и присвойте проекту имя FaceDetection. Поскольку это приложение будет использовать веб-камеру, вы должны добавить эту возможность в приложение. В Solution Explorer дважды щелкните файл Package.appxmanifest. На вкладке Capabilities установите флажки рядом с Microphone и Webcam, как показано на рис. 1. Сохраните файл.
Рис. 1. Добавление возможностей Webcam и Microphone в приложение
Теперь добавьте следующий XAML в файл MainPage.xaml для создания UI:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="320*"/>
<RowDefinition Height="389*"/>
</Grid.RowDefinitions>
<CaptureElement Name="cePreview" Stretch="Uniform" Grid.Row="0" />
<Canvas x:Name="cvsFaceOverlay" Grid.Row="0" ></Canvas>
<StackPanel Grid.Row="1" HorizontalAlignment="Center" Margin="5">
<Button x:Name="btnCamera" Click="btnCamera_Click" >Turn on Camera</Button>
<Button x:Name="btnDetectFaces" Click="btnDetectFaces_Click" >Detect
Faces</Button>
</StackPanel>
</Grid>
Возможно, вы не знакомы с элементом управления CaptureElement. Он выполняет рендеринг потока данных (stream) от подключенного устройства захвата, обычно от камеры или веб-камеры. В отделенном коде (codebehind) вы будете использовать MediaCapture API для его подключения к потоку данных от веб-камеры.
Предварительный просмотр видео с камеры
В файл MainPage.xaml.cs добавьте следующие пространства имен:
using Windows.Media.Capture;
using Windows.Media.Core;
using Windows.Media.FaceAnalysis;
Затем добавьте следующие два члены в класс MainPage:
private FaceDetectionEffect _faceDetectionEffect;
private MediaCapture _mediaCapture;
private IMediaEncodingProperties _previewProperties;
Теперь добавьте следующий обработчик событий для кнопки Start Camera:
private async void btnCamera_Click(object sender, RoutedEventArgs e)
{
_mediaCapture = new MediaCapture();
await _mediaCapture.InitializeAsync();
cePreview.Source = _mediaCapture;
await _mediaCapture.StartPreviewAsync();
}
Запустите проект и щелкните кнопку Start Camera. Теперь вы должны увидеть вывод вашей веб-камеры в приложении. Если веб-камера не подключена к вашей системе, будет сгенерировано исключение.
Отслеживание лиц
Располагая элементом управления CaptureElement, успешно получающим потоковое видео от веб-камеры, теперь можно приступить к отслеживанию лиц. Это требует создания объекта FaceDetectionDefinition, задания некоторых его свойств и подключения к объекту _mediaCapture, который передает потоковое видео созданному CaptureElement.
В обработчики событий для кнопки Detect Faces добавьте следующий код:
private async void btnDetectFaces_Click(object sender, RoutedEventArgs e)
{
var faceDetectionDefinition = new FaceDetectionEffectDefinition();
faceDetectionDefinition.DetectionMode = FaceDetectionMode.HighPerformance;
faceDetectionDefinition.SynchronousDetectionEnabled = false;
_faceDetectionEffect = (FaceDetectionEffect) await
_mediaCapture.AddVideoEffectAsync(faceDetectionDefinition,
MediaStreamType.VideoPreview);
_faceDetectionEffect.FaceDetected += FaceDetectionEffect_FaceDetected;
_faceDetectionEffect.DesiredDetectionInterval = TimeSpan.FromMilliseconds(33);
_faceDetectionEffect.Enabled = true;
}
Этот код создает объект FaceDetectionDefinition, оптимизированный для большей производительности. Это можно увидеть в строке кода, где DetectionMode устанавливается в HighPerformance. В перечислении FaceDetectionMode три члена: HighPerformance (приоритет скорости над точностью), HighQuality (приоритет точности над скоростью) и Balanced (компромиссный вариант между точностью и скоростью). Следующая строка кода не задерживает входящие видеокадры, выполняя алгоритмы распознавания лиц. Благодаря этому предварительный просмотр видео осуществляется плавно, без рывков.
Затем FaceDetectionDefinition добавляется в объект MediaCapture наряду с перечислением, указывающим тип медиа в потоке данных. После этого возвращается объект FaceDetectionEffect. У этого объекта есть событие FaceDetected, срабатывающее после распознавания лица, свойство DesiredDetectionInterval, задающее частоту распознавания лиц, и свойство Enabled, которое разрешает или запрещает распознавание.
Рисование прямоугольников вокруг лиц
Теперь, когда FaceDetectionEffect добавлен в объект MediaCapture и разрешен, нужно добавить код в обработчик события FaceDetection:
private async void FaceDetectionEffect_FaceDetected(
FaceDetectionEffect sender, FaceDetectedEventArgs args)
{
var detectedFaces = args.ResultFrame.DetectedFaces;
await Dispatcher
.RunAsync(CoreDispatcherPriority.Normal,
() => DrawFaceBoxes(detectedFaces));
}
Поскольку это событие генерируется и обрабатывается в другом потоке, вы должны использовать Dispatcher для передачи изменений в UI-поток. Следующая строка кода перебирает IReadOnlyList распознанных лиц. Каждое распознанное лицо обводится прямоугольником, указывающим, где именно оно было распознано. На основе этих данных вы создаете объект Rectangle и добавляете его к холсту наложения лиц (faces overlay canvas), как показано на рис. 2.
Рис. 2. Добавление объекта Rectangle к холсту наложения лиц
private void DrawFaceBoxes(IReadOnlyList<DetectedFace> detectedFaces)
{
cvsFaceOverlay.Children.Clear();
for (int i = 0; i < detectedFaces.Count; i++)
{
var face = detectedFaces[i];
var faceBounds = face.FaceBox;
Rectangle faceHighlightRectangle= new Rectangle()
{
Height = faceBounds.Height,
Width = faceBounds.Width
};
Canvas.SetLeft(faceHighlightRectangle, faceBounds.X);
Canvas.SetTop(faceHighlightRectangle, faceBounds.Y);
faceHighlightRectangle.StrokeThickness = 2;
faceHighlightRectangle.Stroke = new SolidColorBrush(Colors.Red);
cvsFaceOverlay.Children.Add(faceHighlightRectangle);
}
}
Теперь запустите решение и вы увидите, что с прямоугольниками что-то неладно (рис. 3).
Рис. 3. Лицо распознано, но расположение прямоугольника в UI неправильное
Нахождение корректного смещения
Причина, по которой прямоугольники смещаются, в том, что пиксельная сетка, используемая алгоритмом распознавания лиц, начинается от верхнего левого угла медиапотока, а не представления, отображаемого в UI. Вы также должны учитывать, что разрешение видеопотока от камеры может отличаться от разрешения в UI. Чтобы поместить прямоугольник в правильное место, следует принимать во внимание различия позиции и масштаба в UI и в видеопотоке. Для этого добавьте две функции, которые возьмут на себя эту работу: MapRectangleToDetectedFace и LocatePreviewStreamCoordinates.
Первый шаг — получить информацию о потоке предварительного просмотра (preview stream). Для этого вы приводите _previewProperties уровня класса к объекту VideoEncodingProperties. VideoEncodingProperties описывает формат видеопотока. В основном, вам нужно узнать высоту и ширину изображения в видеопотоке. Эта информация позволяет определить соотношение сторон в медиапотоке и тот факт, отличается ли оно от такового в элементе управления CaptureElement.
Метод LocatePreviewStreamCoordinates сравнивает ширину и высоту изображения в медиапотоке с таковыми в элементе управления CaptureElement. В зависимости от разницы в соотношении сторон возможен один из трех случаев. Соотношение сторон одинаково, и подгонки не требуется. Если соотношение сторон отличается, будут добавлены черные полосы сверху и снизу (letterboxes). Если соотношение сторон в CaptureElement больше, чем в медиапотоке, черные полосы добавляются по бокам.
Если черные полосы добавляются по бокам, нужно подстроить X-координату прямоугольника лица. Если же черные полосы добавляются сверху и снизу, требуется подстройка Y-координаты прямоугольника лица.
Приняв во внимание размещение черных полос, вы должны теперь определить разницу в масштабировании между медиапотоком и элементом управления CaptureElement в UI. С помощью Rectangle задаются четыре элемента: Top, Left, Width и Height. Top и Left являются свойствами зависимостей (dependency properties). Хороший обзор Dependency Properties см. в статье по ссылке bit.ly/2bqvsVY.
Снова запустите решение (рис. 4) и вы должны увидеть более точное размещение прямоугольника, выделяющего лицо, как показано на рис. 5.
Рис. 4. Код для вычисления правильного смещения
private Rectangle MapRectangleToDetectedFace(BitmapBounds detectedfaceBoxCoordinates)
{
var faceRectangle = new Rectangle();
var previewStreamPropterties =
_previewProperties as VideoEncodingProperties;
double mediaStreamWidth = previewStreamPropterties.Width;
double mediaStreamHeight = previewStreamPropterties.Height;
var faceHighlightRect = LocatePreviewStreamCoordinates(previewStreamPropterties,
this.cePreview);
faceRectangle.Width = (detectedfaceBoxCoordinates.Width / mediaStreamWidth) *
faceHighlightRect.Width;
faceRectangle.Height = (detectedfaceBoxCoordinates.Height / mediaStreamHeight) *
faceHighlightRect.Height;
var x = (detectedfaceBoxCoordinates.X / mediaStreamWidth) *
faceHighlightRect.Width;
var y = (detectedfaceBoxCoordinates.Y / mediaStreamHeight) *
faceHighlightRect.Height;
Canvas.SetLeft(faceRectangle, x);
Canvas.SetTop(faceRectangle, y);
return faceRectangle;
}
public Rect LocatePreviewStreamCoordinates(
VideoEncodingProperties previewResolution,
CaptureElement previewControl)
{
var uiRectangle = new Rect();
var mediaStreamWidth = previewResolution.Width;
var mediaStreamHeight = previewResolution.Height;
uiRectangle.Width = previewControl.ActualWidth;
uiRectangle.Height = previewControl.ActualHeight;
var uiRatio = previewControl.ActualWidth / previewControl.ActualHeight;
var mediaStreamRatio = mediaStreamWidth / mediaStreamHeight;
if (uiRatio > mediaStreamRatio)
{
var scaleFactor = previewControl.ActualHeight / mediaStreamHeight;
var scaledWidth = mediaStreamWidth * scaleFactor;
uiRectangle.X = (previewControl.ActualWidth - scaledWidth) / 2.0;
uiRectangle.Width = scaledWidth;
}
else
{
var scaleFactor = previewControl.ActualWidth / mediaStreamWidth;
var scaledHeight = mediaStreamHeight * scaleFactor;
uiRectangle.Y = (previewControl.ActualHeight - scaledHeight) / 2.0;
uiRectangle.Height = scaledHeight;
}
return uiRectangle;
}
Рис. 5. Более точное размещение прямоугольника, выделяющего лицо
Выключение распознавания лиц
Распознавание лиц потребляет процессорные ресурсы и в мобильных устройствах может сильно сократить время работы аккумулятора. При включении распознавания лиц вы, очевидно, захотите предоставить пользователям возможность его выключения.
К счастью, выключение распознавания лиц осуществляется довольно просто. Сначала добавьте кнопку в StackPanel в файле MainPage.xaml:
<Button x:Name="btnStopDetection" Click="btnStopDetection_Click">Stop
Detecting Faces</Button>
Теперь добавьте следующий код в обработчик события для кнопки Stop Detecting Faces:
private async void btnStopDetection_Click(object sender, RoutedEventArgs e)
{
_faceDetectionEffect.Enabled = false;
_faceDetectionEffect.FaceDetected -= FaceDetectionEffect_FaceDetected;
await _mediaCapture.ClearEffectsAsync(MediaStreamType.VideoPreview);
_faceDetectionEffect = null;
}
Этот код фактически отменяет процесс настройки. Эффект распознавания лиц отключается, обработчик удаляет подписку на событие FaceDetected, и эффект очищается в объекте захвата медиапотока. Наконец, _faceDetectionEffect присваивается null для освобождения занимаемой им памяти.
Теперь запустите проект. Щелкните кнопку Start Camera, затем Detect Faces и, наконец, Stop Detecting Faces. Вы должны заметить, что, хотя приложение больше не распознает лица, в месте, где было последнее распознавание лица, по-прежнему находится прямоугольник. Давайте исправим это.
Остановите приложение, вернитесь в обработчик события btnStopDetection_Click и добавьте следующую строку кода для очистки содержимого холста cvsFacesOverlay:
this.cvsFaceOverlay.Children.Clear();
Снова запустите приложение и повторите все операции. Теперь, кода распознавание лиц выключается, прямоугольника больше нет.
Заключение
Cognitive Services API предоставляют простой доступ к мощным алгоритмам машинного зрения. Однако, как и все облачные сервисы, они требуют для работы доступа к Интернету. В некоторых случаях, где необходима автономная работа, вы все равно можете выполнять базовое распознавание лиц, используя Face Detection API, встроенные прямо в UWP.
Сценарии автономной работы могут включать размещение веб-камеры, соединенной с Raspberry Pi 2 под управлением Windows IoT Core в удаленном месте. Затем приложение локально сохраняло бы изображения, в которых были распознаны лица. При сборе данных с устройства изображения можно было бы загружать в Cognitive Services для более глубокого анализа.
Локальное распознавание лиц также оптимизирует передачу по сети и использование облачных сервисов, позволяя разработчикам загружать только изображения с лицами. Короче говоря, локальное распознавание лиц может дополнять онлайновые сценарии и обеспечивать автономную работу.
Для более глубокого погружения в данную тематику обязательно изучите CameraFaceDetection в приложениях-примерах UWP на GitHub (bit.ly/2b27gLk).
Фрэнк Ла-Вине (Frank La Vigne) — идеолог технологий в группе Microsoft Technology and Civic Engagement, где он помогает пользователям применять технологии и формировать соответствующие сообщества. Регулярно ведет блог на FranksWorld.com и имеет канал на YouTube под названием «Frank’s World TV» (youtube.com/FranksWorldTV).
Выражаю благодарность за рецензирование статьи эксперту Рэчел Аппель (Rachel Appel).