写真から顔を自動認識して、簡単に目隠し加工する(前編)

2012年2月27日(月)
PROJECT KySS

書き出されるXAMLコードをリスト1のように編集します。

リスト1 編集されたXAMLコード(ImageShow.xaml)

(1)プロパティ要素内にListBoxTemplateというキー名の 要素を配置し、要素に適用するテンプレートを定義します。要素を配置し、Marginプロパティに10を指定して余白を設けます。この中に子要素として、要素を配置します。Sourceプロパティには「画像名」をバインドします。ここで指定する名称は、VBコード内のクラスで定義するプロパティ名です。
(2)(1)で定義したListBoxTemplateをItemTemplateに指定して参照します。

<phone:PhoneApplicationPage 
  x:Class="WP71_ImageFileUpload.ImageShowPage"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
  xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  FontFamily="{StaticResource PhoneFontFamilyNormal}"
  FontSize="{StaticResource PhoneFontSizeNormal}"
  Foreground="{StaticResource PhoneForegroundBrush}"
  SupportedOrientations="Portrait" Orientation="Portrait"
  mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
  shell:SystemTray.IsVisible="True" Language="ja-JP">
  <phone:PhoneApplicationPage.Resources> ■(1)
    <DataTemplate x:Key="ListBoxTemplate"> ■(1)
      <StackPanel Margin="10"> ■(1)
        <Image Width="384" Height="288" Source="{Binding 画像名}"/> ■(1)
      </StackPanel> ■(1)
    </DataTemplate> ■(1)
  </phone:PhoneApplicationPage.Resources> ■(1)
    <!--LayoutRoot は、すべてのページ コンテンツが配置されるルート グリッドです-->
  <Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <!--TitlePanel は、アプリケーション名とページ タイトルを格納します-->
    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
      <TextBlock x:Name="ApplicationTitle" Text="画像一覧" Style="{StaticResource PhoneTextNormalStyle}"/>
    </StackPanel>
 
    <!--ContentPanel - 追加コンテンツをここに入力します-->
    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
      <ListBox Height="682" HorizontalAlignment="Left" Margin="6,6,0,0" Name="ListBox1" VerticalAlignment="Top" Width="441" ItemTemplate="{StaticResource ListBoxTemplate}"/> ■(2)
    </Grid>
  </Grid>
  ~コード略~
</phone:PhoneApplicationPage>

次に、MainPage.xamlを展開して表示されるMainPage.xaml.vbをダブルクリックして、リスト2のコードを記述します。

ロジックコードを記述する

リスト2 (MainPage.xaml.vb)

Option Strict On

ランチャーやチューザーに関するクラスの含まれる、Microsoft.Phone.Tasks名前空間をインポートします。

Imports Microsoft.Phone.Tasks
Imports System.Windows.Media.Imaging
  Imports Microsoft.Phone
  Imports System.IO

LINQ to XMLでXMLを利用するクラスの含まれる、System.Xml.Linq名前空間をインポートします。

  Imports System.Xml.Linq
  Partial Public Class MainPage
  Inherits PhoneApplicationPage
 ' コンストラクター
  Public Sub New()
  InitializeComponent()
  End Sub

バイト型の配列、imageByteをメンバ変数として宣言します。

  Dim imageByte As Byte()

カメラアプリケーションを起動するCameraCaptureTaskクラス用メンバ変数、myCameraTaskを宣言します。

  Dim myCameraTask As CameraCaptureTask

ユーザーの専用サーバーに配置したASP.NETのDefault.aspxファイル(後ほど作成します)にfileNameを引数として渡すURLを定数変数として宣言します。

  Dim imageFileName As String
  Const UploadUri As String = "ユーザーの専用サーバーURL/ImageFileUpload/Default.aspx?fileName={0}"

ページがアクティブになった時の処理

新しいCameraCaptureTaskクラスのインスタンス、myCameraTaskオブジェクトを作成します。
新しいWebClientのインスタンスmyWebClientオブジェクトを作成します。WebClientクラスは、データの送受信用のメソッドを提供するクラスです。
String またはUriとして指定したリソースをダウンロードするDownloadStringAsyncメソッドで、サーバー上のImageInfo.xmlをダウンロードします。キャッシュから読み込まないように、常に新しいデータを読み込むよう、引数に現在の時間、分、秒を指定しています。
AddHandlerステートメントで、非同期のリソース ダウンロード操作の完了時に発生するDwonloadStringCompletedイベントに、イベントハンドラを追加します。イベントハンドラ内では以下の処理を実行します。
ダウンロードが成功しなかった場合は、警告メッセージを出して処理を抜けます。成功した場合は、XElement.Parseメソッドでダウンロードした結果(resultArgs.Result)を読み込みます。読み込んだ結果はmyDoc変数に格納されます。
読み込んだImageInfo.xmlから要素を取得するクエリを定義します。Countプロパティで要素の個数を取得します。その個数が0より大きい場合、つまり要素が存在する場合は、[データ一覧]ボタンの使用を可能とします。それ以外は[データ一覧]ボタンの使用は不可となります。
AddHandlerステートメントで、チューザータスクが完了した時に発生するCompletedイベントに、イベントハンドラを追加します。イベントハンドラ内では以下の処理が実行されます。
カメラアプリケーションが起動して、写真がきちんと撮れた場合は、写真のデータを含むストリームの、バイトの長さを取得し、バイト配列を作成し変数imageByteに格納します。ChosenPhoto.Readメソッドで、現在のストリームからバイトシーケンスを読み取り、読み取ったバイト数でストリーム内の位置を進めます。
ChosenPhoto.Readの書式は以下の通りです。

ChosenPhoto.Read(バイト配列,0ベースのバイトオフセット(現在のストリームから読み取ったデータの格納を開始します), 現在のストリームから読み取るバイトの最大数)

Seekメソッドで、現在のストリーム内の位置を設定します。書式は以下の通りです。

ChosePhoto.Seek(基点のパラメーターのバイト オフセット, 参照ポイントを示すSeekOrigin型の値)

SeekOriginにはBeginを指定し、ストリームの先頭を指定しています。
WriteableBitmap型の変数imageSourceを宣言し、PictureDecoder.DecodeJpeg(resultArgs.ChosenPhoto)で、撮った写真をJPEGファイルとしてWriteableBitmapオブジェクトにデコードします。PictureDecoder.DecodeJpegメソッドはMicrosof.Phone名前空間に属しています。WriteableBitmapクラスは書き込み更新することのできるBitmapSourceを提供するクラスです。
Image1のSourceプロパティに、デコードされたJPEGファイルを格納しているimageSourceオブジェクトを指定します。これでImage内に撮った写真が表示されます。
カメラを起動している状態で撮影を中止した場合は、saveButtonは使用不可のままで処理を抜けます。

  Protected Overrides Sub OnNavigatedTo(e As System.Windows.Navigation.NavigationEventArgs)
    myCameraTask = New CameraCaptureTask
    Dim myWebClient As New WebClient
 
    AddHandler myWebClient.DownloadStringCompleted, Sub(resultSender As Object, resultArgs As DownloadStringCompletedEventArgs)
        If resultArgs.Error Is Nothing = False Then
            MessageBox.Show("XMLファイルが見つかりません")
            Exit Sub
        Else
            Dim myDoc As XElement = XElement.Parse(resultArgs.Result)
            Dim query = From c In myDoc.Descendants("fileName") Select c
            If query.Count > 0 Then
                ichiranButton.IsEnabled = True
                    Else
                      ichiranButton.IsEnabled = False
                    End If
            End If
        End Sub
    myWebClient.DownloadStringAsync(New Uri(String.Format("ユーザーの専用サーバーのURL/ImageFileUpload/ImageData/ImageInfo.xml?myTime={0}", DateTime.Now.ToLongTimeString), UriKind.Absolute))
 
  AddHandler myCameraTask.Completed, Sub(resultSender As Object, resultArgs As PhotoResult)
        If resultArgs.TaskResult = TaskResult.OK Then
            imageByte = New Byte(CInt(resultArgs.ChosenPhoto.Length)) {}
            resultArgs.ChosenPhoto.Read(imageByte, 0, imageByte.Length)
            resultArgs.ChosenPhoto.Seek(0, IO.SeekOrigin.Begin)
            Dim imageSource As WriteableBitmap = PictureDecoder.DecodeJpeg(resultArgs.ChosenPhoto)
            Image1.Source = imageSource
        Else
            saveButton.IsEnabled = False
            Exit Sub
        End If
      End Sub
    MyBase.OnNavigatedTo(e)
  End Sub

[画像を保存]ボタンがタップされた時の処理

現在の「年月日時間分秒.jpg」ファイルを作成し、変数imageFileNameに格納しておきます。
変数myUploadUriを宣言し、定数変数UploadUriに指定した専用サーバーに、引数としてjpg画像のファイル名を持たし、myUploadUriに格納しておきます。
新しいWebClientのインスタンスmyWebClientオブジェクトを作成します。WebClientクラスは、データの送受信用のメソッドを提供するクラスです。
OpenWriteAsyncメソッドに、専用サーバーのUriとjpg画像のファイル名で初期化された新しいUriのオブジェクト、myUriを指定します。OpenWriteAsyncメソッドは、指定したリソースにデータを書き込むためのストリームを開くメソッドです。HeadersのContentLengthには、写真のデータを含む、ストリームのバイトの長さを取得して、バイト配列を作成している変数imageByteの長さを文字列に変換して指定しています。ContentTypeにはimage/jpegと指定します。
AddHandlerステートメントで、リソースにデータを書き込むためにストリームを開く非同期操作の完了時に発生する、OpenWriteCompletedイベントに、イベントハンドラを指定します。イベントハンドラ内では以下の処理を実行します。
非同期処理が正常に行われた場合は、データをサーバーに送信するために使用される、書き込み可能なストリームを取得し、Stream型の変数outputStreamで参照します。Writeメソッドで、写真のデータを含むストリームを書き込みます。ストリームを閉じ、保存した旨のメッセージを表示します。[データ一覧]ボタンの使用を可能にします。

  Private Sub saveButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles saveButton.Click
 
    Dim myUploadUri As String = String.Empty
    imageFileName = DateTime.Now.ToString("yyyyMMddHHmmss") & ".jpg"
    myUploadUri = String.Format(UploadUri, imageFileName)
    Dim myWebClient As New WebClient
    myWebClient.Headers(HttpRequestHeader.ContentLength) = CStr(imageByte.Length)
    myWebClient.Headers(HttpRequestHeader.ContentType) = "image/jpeg"
 
    AddHandler myWebClient.OpenWriteCompleted, Sub(mySender As Object, myArgs As OpenWriteCompletedEventArgs)
        If myArgs.Error Is Nothing = True Then
            Dim outputStream As Stream = myArgs.Result
            outputStream.Write(imageByte, 0, imageByte.Length)
                         outputStream.Close()
                         MessageBox.Show(imageFileName & "を保存しました。")
                         ichiranButton.IsEnabled = True
                        End If
                    End Sub
    Dim myUri As Uri = New Uri(myUploadUri, UriKind.Absolute)
    myWebClient.OpenWriteAsync(myUri)
  End Sub

[カメラ]ボタンがタップされた時の処理

Showメソッドでカメラを起動します。[画像を保存]ボタンの使用を可能にします。

  Private Sub cameraButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles cameraButton.Click
    myCameraTask.Show()
    saveButton.IsEnabled = True
  End Sub

[データ一覧]ボタンがタップされた時の処理

ImageShowPage.xamlに遷移します。

  Private Sub ichiranButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles ichiranButton.Click
    NavigationService.Navigate(New Uri("/ImageShowPage.xaml", UriKind.Relative))
  End Sub
End Class
  • 「写真から顔を自動認識して、簡単に目隠し加工する」サンプルプログラム

四国のSOHO。薬師寺国安(VBプログラマ)と、薬師寺聖(デザイナ、エンジニア)によるコラボレーション・ユニット。1997年6月、Dynamic HTMLとDirectAnimationの普及を目的として結成。共同開発やユニット名義での執筆活動を行う。XMLおよび.NETに関する著書や連載多数。最新刊は「Silverlight実践プログラミング」両名とも、Microsoft MVP for Development Platforms - Client App Dev (Oct 2003-Sep 2012)。http://www.PROJECTKySS.NET/

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています