PR

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

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
Think IT会員限定特典
  • 「写真から顔を自動認識して、簡単に目隠し加工する」サンプルプログラム

四国の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のWebサイトにログインすることでさまざまな限定特典を入手できるようになります。

Think IT会員サービスの概要とメリットをチェック

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

写真から顔を自動認識して、簡単に目隠し加工する(前編) | Think IT(シンクイット)

Think IT(シンクイット)

サイトに予期せぬエラーが起こりました。しばらくたってから再度お試しください。