ByteArrayで画像処理入門

前回は画像ヘッダ(ppmフォーマット)について扱いました。
これで前準備が終わり、実際の画像処理に取りかかることができます。
ただその前に、画像に関する情報はまとめて管理しておくと便利かと思います。
ByteArrayに画像ヘッダ以外のデータ、つまり画像データ本体を格納することになるんですが、その画像データにアクセスする際にいくつかの画像に関する情報が必要です。なのでそれらの情報をどういう風にまとめようかと考えたんですが、ここではOpenCVで利用されているIplImage構造体に倣います。

・IplImage構造体 (OpenCV/cxcore/include/cxtypes.h 内に定義されている)

これを、あまり使わないメンバを除いてActionScriptで書くと、

実際に使う情報はこれくらいで、この中のimageDataに生のピクセルデータが入ることになります。
元が構造体なのでメンバも全てpublic、メソッドは最小限の数で留めておきます。
loadImage()でrawまたはppm画像をデコードするんですが、Flashで使う場合はBitmapData経由でByteArrayを利用したいユーザーが多いと思うので、今回はそっちのことについても少し調べました。
どうやらBitmapData.getPixels()というメソッドで、ピクセルデータが全て入ったByteArrayを返してくれるようです。
ただし、ARGB各8ビット整数のデータが返ってくるらしく自由度は高くありません。
OpenCVで言うなら、IPL_DEPTH_8U固定ということです。

入力をBitmapDataにするとコンストラクタは、

となり、チャンネル数とビット深度は固定されます。
ということでBitmapData経由前提ならnChannelsとdepthメンバは必要なく、埋め込みでOKです。
getPixel()とgetPixel32()で分かれているのに、getPixels32()がないのはなぜだろうAdobe?

ここで、getPixels()後のpositionはデータの終端を指していることに注意します。
(IplImageの場合はimageSizeと同じ値になる、つまり画像データのバイトサイズ)
取得できるデータの末尾よりも後の部分を読み取ろうとした場合はEOFErrorとなります。
Cのポインタを扱っている感覚に近いですね。範囲外参照にはしっかり注意しておきます。

ここで改めてピクセルデータへのアクセス方法を整理します。
(座標(x,y)の各チャンネルデータにアクセスする場合)

・IplImage (C版:OpenCV) の場合

・IplImage (AS3.0版:imageDataにByteArrayを利用) の場合

ARGB固定なのは納得いかないですが、OpenCVのIplImageとほぼ同じ書式でアクセスできます。
(Cの場合、IplImageはポインタで宣言するのでアロー演算子を使うことになる)

ここで、ピクセルデータへの直接アクセス IplImage(opencv.jpのOpenCVサンプルコード)をASで書いてみると、
・R(赤)チャンネル値を0に,B(青)チャンネル値を約0.7倍する

・出力結果 (左:入力、右:出力)

とりあえず同じ結果が得られました。
(1)の所のコードを見比べるとわかりますが、本家にかなり近い書き方で処理できていることがわかります。

ここで、ByteArrayにはread~()というデータ読み込み用メソッドがたくさん用意されていますが、総じて速度が遅く、事前にposition操作も必要なので、直にインデックスでアクセスするより1ステップ手間が増えてしまいます。
なので、
・1バイトずつ読み取る場合、readByte()を使うのは避けてインデックスアクセス。
・一気に4バイト以上読み取る場合はreadInt()やreadDouble()等を使う。
という感じで使い分けるといいんじゃないかなと思います。
ちなみに、readUnsignedInt()は符号なし32ビット整数を読み取るのでBitmapData.getPixel32()と同じです。
つまり、

この3つは同じ値になります。
そしてどうやらreadUnsignedInt()はgetPixel32()より遅いみたいです。
(50万回以上ループを回して50ms程度の差ですが)

ByteArrayを使うメリットはバイトレベルでデータを操作できること。
つまり、画像処理の場合はチャンネルレベルの操作をする時です。

・R(赤)チャンネルの値を取得する

この場合はByteArrayの方が20~25%ほど速いようです。
また、いったんByteArrayにデータを格納した後はARGB固定である必要はありません。
ピクセルデータをHSV色空間に変換して、

のようにそれぞれの成分を単独で取得できるようにしておけば利用しやすいです。

ByteArrayを使ったピクセルデータへのアクセスは、確かにBitmapDataのそれよりは複雑かもしれません。
ただ、ここでByteArray版getPixel()などのメソッドを安易に作ってしまわないようにしたい所。
ピクセルデータへのアクセスは何度も繰り返すものなので、関数呼び出しのオーバーヘッドが無視できません。
OpenCVでもcvGet2D()というgetPixel()と同じ関数がありますが、使うのは非推奨とされていますね。

最近、C言語しか知らない周りの人達にFlashでデモを作って見せると、「へ?Flashってそんなことまで出来るんですねぇ。」みたいに興味を持つ人がちらほら。加えてAlchemyのおかげでC/C++開発者のFlash界参入が期待できそうですね。ストイックな人が多いでしょうから、高度なFlashをどんどん量産していきそうで少し怖いですけど;

関連記事:
ByteArrayで画像処理入門以前 2
ByteArrayで画像処理入門以前

あわせて読む:

3 Thoughts

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です