在 Windows Phone 8 中进行趣味人脸检测
这是一个有趣且简短的教程,将使用 FaceDetectionWP8 库在 Windows Phone 上尝试面部检测。
开始
如果有人正在寻找 Windows Phone 的面部检测库但还没有尝试过这个,那么这个教程就是为他们准备的。这是一个很棒的库,是 Julia Schwarz 的 facedetecionwp7 库的衍生品。你可以用它来做很多很酷的事情,而且非常省事。
让我们玩得开心一点
让我们开始创建一个 Windows Phone 8 项目。我们的任务是尝试使用这个库,在图片中检测人脸,并从中获得一些乐趣,例如用经典的 troll face 和笑声覆盖人脸。:P
现在,无论这听起来如何,让我们尝试一下。我为此设计了一个非常简单的 GUI。如下所示:
如果你想仔细看看 XAML,它看起来会是这样的:
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock Text="Face Detection in Windows Phone"
Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Button x:Name="CaptureImageButton" Content="Capture Image"
HorizontalAlignment="Center" Margin="0" VerticalAlignment="Top"
Width="267" Click="CaptureImageButton_Click"/>
<Image Name="facesPic" HorizontalAlignment="Center"
Height="388" Margin="0,170,0,0"
VerticalAlignment="Top" Width="427"/>
</Grid>
</Grid>
现在,你可以清楚地看到这是一个非常基础的用户界面。我们有一个带有点击事件 `CaptureImageButton_Click` 的 `Button` 和一个名为 `facesPic` 的 `Image` 控件。
现在,在我们开始之前,让我们先添加一个我们需要的库的引用。我们需要来自这里的 WritableBitmapEx。这个库已经在 Nuget 上可用了,所以你也可以从那里获取。
在将它添加到项目引用后,您将需要来自FaceDetectionWP8的一些库才能使其正常工作。你也可以从以下链接获取那里的类。
下载 .zip 文件后,解压缩它并将所有类包含在你的项目中。我将它们添加到了一个名为 *FaceDetector* 的单独文件夹下。
让我们切换到我们的 *MainPage.xaml.cs*。
现在,首先要做的是选择一张图片,为此最好的方法是使用 `PhotoChooserTask`。让我们在 `CaptureImageButton_Click` 事件中添加以下代码片段:
private void CaptureImageButton_Click(object sender, RoutedEventArgs e)
{
PhotoChooserTask photo = new PhotoChooserTask();
photo.Completed += new EventHandler<PhotoResult>(photoChooserTask_Completed);
photo.ShowCamera = true;
photo.Show();
}
接下来需要编写的是 `photoChooserTask_Completed` 事件处理程序。让我们开始编写它。首先要做的是将选择的照片加载到一个 `WritableBitmap` 中,因为我们要对其进行一些更改。
if(e.TaskResult==TaskResult.OK)
{
BitmapImage SourceBitmap = new BitmapImage();
SourceBitmap.SetSource(e.ChosenPhoto);
WriteableBitmap SourceWritableBitmap = new WriteableBitmap(SourceBitmap);
}
现在,我们需要做的下一件事是稍微缩小图像,因为大多数 Windows Phone 设备都配备了功能强大的摄像头,图片经常被过度采样。我个人建议你也缩小图像。我没有在这个演示中这样做,但如果你想要更快的性能,你确实应该尝试一下。我在 `if` 块内添加了以下代码段。
byte[] downsampledImage = new byte[SourceWritableBitmap.PixelWidth /
_downsampleFactor * SourceWritableBitmap.PixelHeight / _downsampleFactor];
Utils.DownSample(SourceWritableBitmap.ToByteArray(), SourceWritableBitmap.PixelWidth, SourceWritableBitmap.PixelHeight, ref downsampledImage, _downsampleFactor);
SourceWritableBitmap = SourceWritableBitmap.FromByteArray(downsampledImage);
这里的代码段现在非常直接。这里的 `Utils` 类来自 `FaceDetectionWP8` 库。这里使用的 `Utils.DownSample` 的第一个参数是 `SourceWritableBitmap` 的字节数组,它使用了我们之前添加的 `WritableBitmapEx`。第二个参数是源位图的宽度,第三个参数是高度。传递的最后一个参数是下采样因子。它在 `MainPage` 类中定义如下:
int _downsampleFactor = 2;
然后 `SourceWritableBitmap` 会被它自己的下采样版本重新初始化。
接下来要做的是获取检测到的人脸,当然了。:D 那么,还在等什么?我为此添加了以下代码片段:
List<FaceDetector.Rectangle> faces = new List<FaceDetector.Rectangle>();
faces = _detector.getFaces(SourceWritableBitmap, 2f, 1.25f, 0.1f, 1, false, true);
我在这里使用了默认值,除了最后一个参数,它决定是否应检测多张人脸,我将其保留为 `true`。如果你想调整你的 Haar 级联检测参数,你可以随意尝试其他参数。
现在是乐趣所在,我们将用 troll 哈哈图片替换图片中的人脸/人脸。:P 我们可以看到它返回人脸为一个 `List
现在,在用 Troll 哈哈替换源图像之前,我们需要将其加载到另一个 `WritableBitmap` 中,以便我们可以将它们混合在一起。
StreamResourceInfo MaskImageSri = Application.GetResourceStream
(new Uri("Images/Troll.png", UriKind.Relative));
BitmapImage MaskImageBitmap = new BitmapImage();
MaskImageBitmap.SetSource(MaskImageSri.Stream);
WriteableBitmap MaskWritableBitmap = new WriteableBitmap(MaskImageBitmap);
既然我们有了 *Troll.png*,为什么不把它粘贴到源位图上呢?
foreach (var r in faces)
{
int x = Convert.ToInt32(r.X);
int y = Convert.ToInt32(r.Y);
int width = Convert.ToInt32(r.Width);
int height = Convert.ToInt32(r.Height);
System.Windows.Rect destRect = new Rect(x, y, width, height);
System.Windows.Rect srcRect = new Rect(0, 0, MaskWritableBitmap.PixelWidth, MaskWritableBitmap.PixelHeight);
SourceWritableBitmap.Blit(destRect, MaskWritableBitmap, srcRect);
}
SourceWritableBitmap.Invalidate();
facesPic.Source = SourceWritableBitmap;
现在,这也很直接。我们所需要的就是遍历人脸,并创建两个单独的矩形 `struct`。一个是来自 `MaskWritableBitmap`,所以我们可以得到我们要粘贴的图像部分的矩形,在这种情况下是完整的图像,而目标矩形是我们要将这个 troll 哈哈粘贴到其上的源图像中的人脸矩形。
接下来要做的是调用 `Blit()` 方法,因为它负责混合这两个 `WritableBitmaps`。它来自我们之前添加的 `WritableBitmapEx` 库。第一个参数是目标矩形,源矩形将被粘贴到那里。在这种情况下,是将被 troll 哈哈图像替换的人脸矩形。第二个参数将是包含要粘贴到源中的矩形的 `WritableBitmap`。这里是 Troll Image,最后一个是 Troll Image 中将被传输到主位图的矩形。
我们在 `SourceWritableBitmap` 上调用它,因为我们想将 troll 哈哈粘贴到上面。在一切完成之后,我们将 `facesPic` 的源设置为 `SourceWritableBitmap`。
现在,我们要做的就是构建和测试应用程序。结果如下:
那么,你还在等什么?测试文章上方的演示项目,玩得开心!