はじめに
前回 は、Windows Phone OS 7.1から搭載されたPhotoCameraクラスを使用して静止画撮影の方法についてご紹介しました。
このPhotoCameraクラスには、静止画撮影関係の機能以外にも、プレビュー中のフレーム(画像)をARGB32形式、YCbCr形式、Y成分(輝度情報のみ)の形式で取得することができます。
プレビューフレームを用いることで、リアルタイムでエフェクトをかけながら表示したり、特定のマーカーを探しバーコードを読み取るアプリケーションを開発することができます。
今回は、プレビューフレームを取得する方法と取得したフレームの利用方法についてご紹介いたします。サンプルプロジェクトを2つご用意しましたので、是非ソースコードと一緒に記事を読み進めて頂ければと思います。
ARGB32形式のプレビューフレームを取得してエフェクト処理を行う
冒頭の「CameraPreviewTest_ColorEffect.zip」のほうのサンプルプロジェクトを使用しながらTipsをご紹介します。
Microsoft.Devices名前空間のPhotoCamera.GetPreviewBufferArgb32メソッドで、ARGB32フォーマットでプレビュー中に表示されている画像(プレビューフレーム)を取得します。
Windows Phone 7でもPCでも同じですが、ディスプレイに表示されている画面はピクセルという最小単位で構成されています。各ピクセルはピクセルの不透明度を示すアルファ値を持ちます。ピクセルの構成をイメージで表すと下図の通りです。
上位8ビットにはアルファ値が格納されています。このアルファ値は不透明度を表した値で、アルファ値が最大(255)の場合は不透明に、0の場合は透明となります。
下位24ビットにRGB値が格納されています。RGBとは三原色(R成分、G成分、B色成分)の値が、それぞれ8ビットずつ格納されています。このRGBはRed、Green、Blueの頭文字を取ったものです。
プレビューフレームを取得するのは簡単です。例としてプレビューフレームをPhotoCamera.GetPreviewBufferArgb32メソッドでバッファーへ書き出しを行い、その後ピクセルデータを分離ストレージへ保存する簡単なコードをご紹介します。
double pixelSize = camera . PreviewResolution . Width * camera . PreviewResolution . Height ;
int [] pixelData = new int [( int ) pixelSize ];
camera . GetPreviewBufferArgb32 ( pixelData );
using ( var store = IsolatedStorageFile . GetUserStoreForApplication ())
using ( var strm = store . CreateFile ( "preview.rgb" )) {
foreach ( int pixel in pixelData ) {
var bytes = BitConverter . GetBytes ( pixel );
strm . Write ( bytes , 0 , bytes . Length );
}
}
上記のコードを実行すると、ARGB32データが分離ストレージに書き出されているはずです。
前回紹介しましたIsolated Strage Explorer Toolを使ってデータを取り出してもよいのですが、ファイルヘッダも付いていないピクセルデータしかないファイルですので、Windowsに付属しているプレビューツールでは見ることができません。
これでは、本当にプレビューフレームを取り出すことができているのか、確認することができません。取り込んだフレームに対してエフェクト処理を施し、画面上に表示するようにひとつ工夫してみましょう。
<Grid x:Name = "ContentPanel" Grid . Row = "1" Margin = "12,0,12,0" >
<Rectangle x:Name = "PreviewRectangle" Tap = "PreviewRectangle_Tap" >
<Rectangle.Fill>
<VideoBrush x:Name = "PreviewBrush" />
</Rectangle.Fill>
</Rectangle>
<Image Height = "310" x:Name = "ImageEffect" Stretch = "UniformToFill"
VerticalAlignment = "Bottom" RenderTransformOrigin = "0.5,0.5"
Margin = "20,0,20,60" Width = "401" />
</Grid>
Expression BlendでXAMLを表示すると下図のようになります。中央の枠がImageコントロールですので、ここにエフェクト処理をしたプレビューフレームを表示させます。
あとでエフェクト処理を掛けやすいように、PhotoCamera.GetPreviewBufferArgb32メソッドで取得したARGB32データを格納するクラスを作ります。画像の幅と高さ、それと画像データを格納するバッファがプロパティとしてあるだけの簡単なクラスを作成します。
後ほどエフェクト処理を実装しますので、中身のないEffectSepiaToneメソッドを実装しています。
public class FrameBuffer {
public int [] Buffer { get ; set ; }
public int Width { get ; set ; }
public int Height { get ; set ; }
public FrameBuffer ( int width , int height ) {
Buffer = new int [ width * height ];
Width = width ;
Height = height ;
}
public void EffectSepiaTone () {
}
}
ここで少し簡単にですが、カメラプレビューの仕組みを説明します。
カメラのプレビュー表示の仕組みは、カメラデバイスのレンズから取り込んだ映像をパラパラ漫画のように表示させて、人間の目から見ると滞りなく動いているように見せています。パラパラ漫画のように画像を切り替える速度の単位は、FPS(Frames Per Second)と呼ばれます。1秒間に何枚の画像をパラパラ表示させるのかを基準とした単位です。
大体のWindows Phone端末に搭載されているカメラの入力フレームレートは、約30FPSです。1秒間に30枚の画像を表示させていることになります。1枚の表示に使える時間は約33ミリ秒です。
この速度でバーコードの解析は難しいので、1秒間に10回解析を行うようにタイマーを定義します。
タイマーは、System.Windows.Threading名前空間のDispatcherTimerクラスを使用します。カメラの初期化完了通知ハンドラの処理の中で、DispatcherTimerのインスタンスを生成してタイマーを開始させます。
System . Windows . Threading . DispatcherTimer readTimer = null ;
void camera_Initialized ( object sender , CameraOperationCompletedEventArgs e ) {
if (! e . Succeeded )
return ;
Dispatcher . BeginInvoke (() => {
PreviewBrush . RelativeTransform = new CompositeTransform () {
CenterX = 0.5 , CenterY = 0.5 ,
Rotation = camera . Orientation
};
ImageEffect . RenderTransform = new CompositeTransform () {
CenterX = 0.5 , CenterY = 0.5 ,
Rotation = camera . Orientation
};
readTimer = new System . Windows . Threading . DispatcherTimer ();
readTimer . Tick += new EventHandler ( readTimer_Tick );
readTimer . Interval = TimeSpan . FromMilliseconds ( 100 );
readTimer . Start ();
});
}
100ミリ秒ごとにタイマーが満了し、その都度readTimer_Tickメソッドが呼び出されます。先ほど上で定義したFrameBufferクラスを使ってプレビューフレームを取り出してみましょう。
取り出したプレビューフレームをFrameBufferオブジェクトに取り込み、セピア調にエフェクト処理を行います。WriteableBitmapクラスを経由させてImageコントロールにセットします。
void readTimer_Tick ( object sender , EventArgs e ) {
var frame = new FrameBuffer (
( int ) camera . PreviewResolution . Width ,
( int ) camera . PreviewResolution . Height );
camera . GetPreviewBufferArgb32 ( frame . Buffer );
frame . EffectSepiaTone ();
var wb = new WriteableBitmap ( frame . Width , frame . Height );
frame . Buffer . CopyTo ( wb . Pixels , 0 );
ImageEffect . Source = wb ;
}
以上のコードを実行すると、下図のようにオリジナルのプレビュー(背景)とエフェクト処理後のプレビュー(手前)が表示されます。
EffectSepiaToneメソッドの実装については、上記では特に取り扱いませんでした。セピア調だけでは寂しいので簡単なエフェクトについてご紹介します。readTimer_Tickメソッドにて、セピア調にエフェクト処理を行ったEffectSepiaToneメソッドをこれから紹介するエフェクトメソッドに変更すると更に面白い効果が得られると思います。
簡単に実装できるエフェクト処理のサンプルコードを記載していますので、好みのエフェクト処理を選んで頂ければと思います。
エフェクト処理の紹介:セピア調
昔の写真のように少し色褪せた感じのセピア調になるエフェクト処理です。セピアというのはイカ墨の色のことで、JIS慣用色名ではRGB値0x6B4A2Bで定義されています。RGBのそれぞれの値に直すと(R:107、G:74、B:43)となります。これをR成分を基準(1)として考えると(赤:1、緑:0.7、青:0.4)の比率になります。この比率でグレイスケール化した後に掛けてみましょう。
public void EffectSepiaTone () {
for ( int i = 0 ; i < Buffer . Length ; i ++) {
int pixel = Buffer [ i ];
byte r = Convert . ToByte (( pixel >> 16 ) & 0xff );
byte g = Convert . ToByte (( pixel >> 8 ) & 0xff );
byte b = Convert . ToByte ( pixel & 0xff );
byte avg = ( byte )(( r + g + b ) / 3 );
r = ( byte )( avg * 1 );
g = ( byte )( avg * 0.7 );
b = ( byte )( avg * 0.4 );
Buffer [ i ] = 255 << 24 | r << 16 | g << 8 | b ;
}
}
エフェクト処理の紹介:グレイスケール
グレイスケールは白と黒とその中間色で表現した言わば白黒写真です。白黒写真と言っても色空間やガンマ値を考慮した沢山の方法があります。R成分、G成分、B成分の最大値と最小値を足して2で割る中間値法や、R成分、G成分、B成分のそれぞれの要素に重み付けをして平均を取る加重平均法等です。
今回は一番簡単な単純平均法を使用しましょう。1ピクセルのR成分、G成分、B成分の要素をすべて足し合わせて、3で割り平均値を取っています。
public void EffectGrayscale () {
for ( int i = 0 ; i < Buffer . Length ; i ++) {
int pixel = Buffer [ i ];
byte r = Convert . ToByte (( pixel >> 16 ) & 0xff );
byte g = Convert . ToByte (( pixel >> 8 ) & 0xff );
byte b = Convert . ToByte ( pixel & 0xff );
byte avg = ( byte )(( r + g + b ) / 3 );
Buffer [ i ] = 255 << 24 | avg << 16 | avg << 8 | avg ;
}
}
エフェクト処理の紹介:カラーエフェクト
赤、緑、青の3色によるカラーエフェクト処理です。この3色は計算処理が不要で、それぞれ用のビットマスクを使用するだけでエフェクトを得ることが可能です。
public enum ColorEffectType {
Red ,
Green ,
Blue
}
public void EffectColor ( ColorEffectType effectType ) {
if ( effectType == ColorEffectType . Red ) {
for ( int i = 0 ; i < Buffer . Length ; i ++)
Buffer [ i ] &= 0xff0000 ;
} else if ( effectType == ColorEffectType . Green ) {
for ( int i = 0 ; i < Buffer . Length ; i ++)
Buffer [ i ] &= 0x00ff00 ;
} else if ( effectType == ColorEffectType . Blue ) {
for ( int i = 0 ; i < Buffer . Length ; i ++)
Buffer [ i ] &= 0x0000ff ;
}
}
エフェクト処理の紹介:ネガポジ反転
デジカメの時代となって久しいですが、現像の際に写真屋さんで反転と共に補正を行いやすくするために、写真の明暗や色を反転させたネガフィルムを利用して現像していました。
正しく見えるピクチャーフレームを加工するので、現像とは逆に画像のピクセル情報を反転させるだけです。
public void EffectNegative () {
for ( int i = 0 ; i < Buffer . Length ; i ++) {
int pixel = Buffer [ i ];
Buffer [ i ] = pixel ^ 0x00ffffff ;
}
}
以上で、エフェクト処理についてのご紹介は終わりです。
YCbCr形式のプレビューフレームを取得してバーコード解析を行う
続いて、冒頭の「CameraPreviewTest_Barcode」のほうのサンプルプロジェクトを使用しながらTipsをご紹介します。YCbCr形式のプレビューフレームを取得しバーコード解析を行いたいと思います。
PhotoCamera.GetPreviewBufferYCbCrメソッドを利用すると、YCbCrフォーマットでプレビュー中のフレームを取得することができます。
YCbCrフォーマットは、輝度信号Yと2つの色差信号(Cb、Cr)を使って表現します。「 人間の目は明るさの変化には敏感だが色の変化には鈍感」という性質に基づいた色空間で、明るさ(イメージ的には白黒テレビです)の情報Yと、色を表す情報のCb、Crが分離されているのが特徴です。
バーコードの解析には色の情報は不要ですので、プレビューフレームの輝度信号Yだけを取得するPhotoCamera.GetPreviewBufferYメソッドを使用しましょう。メソッドの定義は以下の通りとなっており、GetPreviewBufferArgb32メソッドと異なる点は引数がbyte[]型になっているところです。
public void GetPreviewBufferY (
byte [] pixelData
)
今回バーコードの解析にライブラリを使用します。コードでの実装例の前に「Windows Phone 7 Silverlight ZXing Barcode Scanning Library」を紹介したいと思います。
Windows Phone 7 Silverlight ZXing Barcode Scanning Libraryを使用する
Google社が公開しているオープンソースのうちのひとつに、ZXingと言われるバーコードのエンコード(生成)とデコード(解析)を行うライブラリがあります。ZXingの名前は、シマウマを表すZebraと横断歩道を意味するCrossingを合わせた“ Zebra Crossing” という語に由来するようです。
このZXingライブラリは、iPhone向けやAndroid向け等たくさんの派生プロジェクトが存在しており、表題の「Windows Phone 7 Silverlight ZXing Barcode Scanning Library」はWindows Phone 7のSilverlightアプリケーション向けライブラリのプロジェクトです。
2011年10月現在、このWindows Phone 7向けのライブラリがサポートしているバーコードのフォーマットは、以下の8つとなっています。
UPC-A and UPC-E
EAN-8 and EAN-13
Code 39
Code 128
QR Code
ITF
Data Matrix (Not tested)
PDF417 (Not tested)
バーコードを含んだ写真から解析を行うというライブラリの性格上、カメラ機能と相性がよく、標準カメラアプリを使って撮影を行うCameraCaptureTaskクラスや、既に撮影済みの写真を選択するPhotoChooserTaskクラスや、カメラデバイスからのプレビューフレームを受け取るPhotoCameraクラスと組み合わせて使用すると、有用なアプリケーションを開発することができそうです。
導入
導入はとても簡単で、Codeplaxからダウンロードして、dllアセンブリを参照するだけです。http://silverlightzxing.codeplex.com/ にアクセスしていただいて、右端の「Download」を選択します。
既にビルド済みのdllアセンブリが含まれていますので、ダウンロードしたzipファイルを任意のフォルダへ解凍して、以下のファイルを自分のプロジェクトへ組み込んでしまいましょう。
Silverlight_ZXing_Core.dll
WP7_Barcode_Library.dll
以上で、プロジェクトへのZXingライブラリの組み込みは完了です。
次は画面を作っていきましょう。
前述した通り、PhotoCamera.GetPreviewBufferYメソッドから取り込んだ映像を矩形PreviewRectangleに表示しつつ、取り込んだ映像をリアルタイムに解析し、バーコードの認識ができればリストボックスBarcodeListBoxへ解析結果を追加していきます。xamlを示します。
<Grid x:Name = "ContentPanel" Grid . Row = "1" Margin = "12,0,12,0" >
<Rectangle x:Name = "PreviewRectangle" Tap = "PreviewRectangle_Tap" >
<Rectangle.Fill>
<VideoBrush x:Name = "PreviewBrush" />
</Rectangle.Fill>
</Rectangle>
<ListBox Name = "BarcodeListBox" FontSize = "29.333" />
</Grid>
先ほどご紹介しましたリアルタイムにエフェクト処理を行うのと同様に、まずカメラの初期化処理が完了したらタイマーを開始し、満了後にreadTimer_Tickメソッドが呼び出されるように設定します。
au IS12TとHTC 7 Mozartの実機で動作確認を行いましたが、プレビュー表示程度ですと10fps(1秒間に10回)でプレビューフレームを解析しても滞りなく処理されていました。
System . Windows . Threading . DispatcherTimer readTimer = null ;
void camera_Initialized ( object sender , CameraOperationCompletedEventArgs e ) {
if (! e . Succeeded )
return ;
Dispatcher . BeginInvoke (() => {
PreviewBrush . RelativeTransform = new CompositeTransform () {
CenterX = 0.5 , CenterY = 0.5 ,
Rotation = camera . Orientation
};
readTimer = new System . Windows . Threading . DispatcherTimer ();
readTimer . Tick += new EventHandler ( readTimer_Tick );
readTimer . Interval = TimeSpan . FromMilliseconds ( 100 );
readTimer . Start ();
});
}
Zxingにはいくつかのバーコード解析クラスが存在しています。今回はそのうちのQRコード用のQRCodeReaderクラスを使用します。このQRCodeReaderクラスは解析対象にBinaryBitmapオブジェクトを作成する必要があります。
LuminanceSourceクラスを継承したPreviewFrameLuminanceSourceクラスを作成し、PhotoCamera.GetPreviewBufferYメソッドで取得した輝度情報を格納します。
public class PreviewFrameLuminanceSource : LuminanceSource {
public byte [] Buffer { get ; set ; }
public PreviewFrameLuminanceSource ( int width , int height ) : base ( width , height ) {
Buffer = new byte [ width * height ];
}
public override sbyte [] Matrix {
get {
return (( Buffer as Array ) as sbyte []);
}
}
public override sbyte [] getRow ( int y , sbyte [] row ) {
if ( row == null || row . Length < Width ) {
row = new sbyte [ Width ];
}
for ( int i = 0 ; i < Height ; i ++) {
row [ i ] = Convert . ToSByte ( Buffer [ i * Width + y ]);
}
return row ;
}
}
輝度情報のバッファを格納したPreviewFrameLuminanceSourceオブジェクトを元に、HybridBinarizerクラスを使ってBinaryBitmapオブジェクトを作ります。
QRCodeReader.decodeメソッドでバーコードの解析(デコード)を行います。解析を行いバーコードが発見できなかった場合、ReaderExceptionが発行されます。バーコードが発見できた場合、result.Textにテキストが含まれてきますので、リストボックスへ追加していきます。
void readTimer_Tick ( object sender , EventArgs e ) {
var luminanceSource = new PreviewFrameLuminanceSource (
( int ) camera . PreviewResolution . Width ,
( int ) camera . PreviewResolution . Height );
camera . GetPreviewBufferY ( luminanceSource . Buffer );
var reader = new QRCodeReader ();
var binarizer = new HybridBinarizer ( luminanceSource );
var binBitmap = new BinaryBitmap ( binarizer );
Result result = null ;
try {
result = reader . decode ( binBitmap );
} catch ( ReaderException ) {
return ;
} catch ( Exception ex ) {
throw ex ;
}
Dispatcher . BeginInvoke (() => {
if (! BarcodeListBox . Items . Contains ( result . Text )) {
BarcodeListBox . Items . Add ( result . Text );
}
});
}
写真の撮り方が悪かったのでかなり見えにくいですが、カメラプレビューの上に認識できたバーコードが表示されているのがわかりますでしょうか。
サンプルアプリケーションの実装としては以上です。ライブラリ組み込みも難易度もそれほど高くなく、簡単にバーコードのデコードが出来るのは魅力的ではないでしょうか。
さいごに
今回はカメラを使ったリアルタイムなエフェクト処理とバーコード解析処理をご紹介させて頂きました。
上記で紹介したように延々と画像の入力を受け付けるアプリケーションのほかにも、有効なバーコードをデコードした時点で、その情報をWebAPIを使って取得するようなアプリケーションを作成するのも面白いかもしれませんね。
エフェクト処理は、カメラプレビューと組み合わせて使わなくても、写真編集アプリケーションにも流用が効きそうです。自身のアプリケーションに組み込む際には、好みの画質に調整して頂ければと思います。
今回は以上で終わりです。ありがとうございました。