写真から顔を自動認識して、簡単に目隠し加工する(前編)
書き出されるXAMLコードをリスト1のように編集します。
リスト1 編集されたXAMLコード(ImageShow.xaml)
(1)
(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から
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
「写真から顔を自動認識して、簡単に目隠し加工する」サンプルプログラム