Windows Phoneアプリケーション開発入門

第37回Mangoで追加されたカメラ機能を使ってみよう!(3)

はじめに

前回は、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);

// 分離ストレージへARGBデータへ保存する
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
        };

        // 100ミリ秒毎に満了する様にタイマーを開始
        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();

    // 取り込んだ画像をWriteableBitmapへセット
    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)の比率になります。この比率でグレイスケール化した後に掛けてみましょう。

        /// <summary>
        /// 画像バッファをセピア調化する
        /// </summary>
        public void EffectSepiaTone() {
            for (int i = 0; i < Buffer.Length; i++) {
                int pixel = Buffer[i];

                // RGB値を取り出す
                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で割り平均値を取っています。

        /// <summary>
        /// 画像バッファをグレイスケール化する
        /// </summary>
        public void EffectGrayscale() {

            for (int i = 0; i < Buffer.Length; i++) {
                int pixel = Buffer[i];

                // RGB値を取り出す
                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
        }

        /// <summary>
        /// 画像バッファにカラーエフェクトを掛ける
        /// </summary>
        public void EffectColor(ColorEffectType effectType) {
            // 各RGB要素のいずれかの要素だけ有効にする
            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;
            }
        }

エフェクト処理の紹介:ネガポジ反転

画像

デジカメの時代となって久しいですが、現像の際に写真屋さんで反転と共に補正を行いやすくするために、写真の明暗や色を反転させたネガフィルムを利用して現像していました。

正しく見えるピクチャーフレームを加工するので、現像とは逆に画像のピクセル情報を反転させるだけです。

        /// <summary>
        /// 画像バッファをネガポジ反転させる
        /// </summary>
        public void EffectNegative() {
            for (int i = 0; i < Buffer.Length; i++) {
                int pixel = Buffer[i];
                // 各RGB要素のビットを反転させる
                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
        };

        // 100ミリ秒毎に満了する様にタイマーを開始
        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];
}

/// <summary>
/// バッファをsbyte[]型で取得する
/// </summary>
public override sbyte[] Matrix {
    get { 
        // Bufferをbyte[]からsbyte[]へキャスト
        return ((Buffer as Array) as sbyte[]); 
    }
}

/// <summary>
///  1ラインずつデータを取得
/// </summary>
public override sbyte[] getRow(int y, sbyte[] row) {
    // 前回確保した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();

    // バーコード解析用のBitmapを作成
    var binarizer = new HybridBinarizer(luminanceSource);
    var binBitmap = new BinaryBitmap(binarizer);

    Result result = null;
    try {
        // バーコードの解析(デコード)を行う
        result = reader.decode(binBitmap);
    } catch (ReaderException) {
        // プレビューフレーム内にバーコードが見つからなかった場合
        // 読み取り例外のReaderExceptionが発行されてしまう
        return;
    } catch (Exception ex) {
        throw ex;
    }

    // コントロールへのアクセスを行うのでUIスレッドにて非同期で実行
    Dispatcher.BeginInvoke(() => {
        // 既にテキストが含まれていれば追加しない
        if (!BarcodeListBox.Items.Contains(result.Text)) {
            BarcodeListBox.Items.Add(result.Text);
        }
    });
}

写真の撮り方が悪かったのでかなり見えにくいですが、カメラプレビューの上に認識できたバーコードが表示されているのがわかりますでしょうか。

画像

サンプルアプリケーションの実装としては以上です。ライブラリ組み込みも難易度もそれほど高くなく、簡単にバーコードのデコードが出来るのは魅力的ではないでしょうか。

さいごに

今回はカメラを使ったリアルタイムなエフェクト処理とバーコード解析処理をご紹介させて頂きました。

上記で紹介したように延々と画像の入力を受け付けるアプリケーションのほかにも、有効なバーコードをデコードした時点で、その情報をWebAPIを使って取得するようなアプリケーションを作成するのも面白いかもしれませんね。

エフェクト処理は、カメラプレビューと組み合わせて使わなくても、写真編集アプリケーションにも流用が効きそうです。自身のアプリケーションに組み込む際には、好みの画質に調整して頂ければと思います。

今回は以上で終わりです。ありがとうございました。

おすすめ記事

記事・ニュース一覧