もう何も怖くないvImage

どうも、吉村です。
最近画像を大変良く扱うiOSアプリ制作に従事しておりました。
そこでvImageが大変訳に立ちましたので、ちょっとそのノウハウをまとめたいと思います。

1、vImageとは何か?
簡単に言えば、iOS, Macプラットフォームで動く、ハードウェアを超絶活かした爆速画像処理API郡のことです。
うまく使うと、消費電力の削減や、処理の大幅高速化につながります。
得意なことは、低レベルな画像処理です。
APIをざっと眺めると何となく得意なことが分かるとおもいます。

Image Reference Collection

色々ありますが、私が最近よく使ったのは、
画像の回転・拡大縮小・反転、画像処理カーネルの畳み込み演算等です。
こういった処理を非常に高品質かつ超高速に実行できるvImageは、
非常に便利でもあり、頼もしく安心感があります。

ただし、APIに少し癖があり、少々使いにくい側面もあります。

2、基盤
まずはvImageで使う画像のデータ構造を紹介します。

vImage_Buffer

こいつです。
これはvImageで画像を扱うための構造体であり、
vImage_Types.hヘッダーを参照すると、以下の様に定義されています。

これを見る限り、以下の事が分かります。

・dataが画像の生データへのポインタ。左上から右下へ並んでいる。
・width, heightのピクセル数
・rowBytesが1行のバイト数
・メモリの管理機構を持たない

UIImageやCGImageRef等と比べると、ずいぶんと割り切ってシンプルな構造になっています。

この説明には、
void *dataのデータ型が明記されていませんが、
rowBytesの存在と、vImageのAPI自体にフォーマットの指定があるためです。

また余談ですが、vImageのヘッダーは、予想以上に丁寧なコメントが多く、
困ったらヘッダーを読む、というスタイルがオススメです。

さて、vImageに処理をさせる為には、
汎用画像フォーマットと、vImage_Buffer構造体を相互に自由に変換できなければなりません。
一応、iOS7からはvImageのAPIに変換用の関数が追加されたのですが、
あまり使い勝手が良くないのと、さほど高速でもなかったので、現段階では自身でラッパーを書いています。

少し長いので、gistにします。

ヘッダー

https://gist.github.com/Ushio/c672442388fb54665f5a

実装

https://gist.github.com/Ushio/5a184e11a1302fb8fd2d

これを使えば、

のようにvImage_Bufferを作成し、

のように簡単にCGImageRefを作成する事が出来ます。
非同期でないのにblocksになっている理由は、CGImageRefの所有権問題を低減させるためです。

これで自由にvImage_Buffer構造体を構築することが出来る様になりました。

3、API
分かりやすい最初のAPIとして

vImageRotate90_ARGB8888

を題材にして、vImageAPIの基礎を紹介します。

vImageRotate90_ARGB8888は画像を90度回転させるAPIです。

宣言はこうなっています。

さて、細かく見てみましょう。

まずは名前に着目しましょう。
vImageの関数は基本的に、

vImage [処理の名前] _ [vImage_Bufferのフォーマット]

の形式で統一されています。
なので、名前こそ長いものの、恐れることは何もありません。むしろ分かりやすいと言えます。

次に引数です。
src, destは入力と出力なので良いですね、どちらもvImage_Bufferです。
constの存在からsrcには変更がない、ということが分かります(当然かもしれませんが)。

次のrotationConstantは、回転数です。名前から分かります。

backColorは、サイズが合わないときの色ですね、
大抵はサイズを合わせるので、適当に、
uint8_t backcolor[] = {255, 255, 255, 255};
等としておきましょう。

最後にvImage_Flagsです。
これは、kvImageNoFlagsや、kvImageHighQualityResampling等色々あるのですが、
それぞれのAPIごと有効なフラグが違います。
これはそれぞれのAPIリファレンスを参照しましょう。
APIごとにオプションを定義すると、定数がいたずらに増えてしまうので、
ある程度共通でオプションを定義している、ということですね。

vImageの関数の引数の構造は、命名規則とおなじく、ほとんどの関数で、以下の様になっています。

[入力画像 vImage_Buffer] [出力画像 vImage_Buffer], [関数固有パラメータ], [vImage_Flags]

vImageの関数には引数が多くて嫌になりそうですが、
このように整理して考えると、さほどややこしくないことが分かります。
APIの構造を統一することの大切さを実感できる、良いお手本でもありますね。

戻り値は、vImage_Errorで、エラーコードが返ってくるので、
面倒でもハンドリングしておくと良いでしょう。

もうここまで理解がすすめば、
あとはもうリファレンスを自由に飛び回り、APIを呼びまくって見るのが良いでしょう。

4、最後に
いくつか良く使ったvImage APIと、Tipsを紹介して終わろうとおもいます。

vImageConvolve_ARGB8888
 こちらは畳み込み演算を実行する関数です。N x Nのカーネルを使えます。
 ガウシアンブラーなんかは、N x 1のカーネルと、1 x Nのカーネルをそれぞれ1回ずつ、合計2回呼び出せば、実現できますね。

vImageAffineWarp_ARGB8888
 こちらは行列を使って、画像をトランスフォームします。
 vImage_AffineTransformが肝になりますが、
 こちらはCGAffineTransformと基本同じ構成なので、CoreGraphicsのAPIで行列を作ることをオススメします。
 また、この関数は浮動小数点演算を使うため、
 90度回転等シンプルな処理の場合は、vImageRotate90_ARGB8888等の方が圧倒的に高速です。
 kvImageHighQualityResamplingを状況に応じてつけると良いでしょう。

vImageRotate_ARGB8888
 これは回転のみに特化した関数です。vImageAffineWarp_ARGB8888と同様、vImageRotate90_ARGB8888でまかなえる場合はこちらを使いましょう。

vImageLookupTable_Planar8toPlanarF
こちらはテーブルを使ったフォーマット変換です。数値計算で変換するよりも高速です。

vImageScale_ARGB8888
 拡大縮小をします。倍率は、destのサイズで決まります。
 kvImageHighQualityResamplingを状況に応じてつけると良いでしょう。

vImageVerticalReflect_ARGB8888
 上下反転します。
 状況によっては以下のコードで済みますので、この関数を使う必要がない場合もあります。

vImageHorizontalReflect_ARGB8888
 左右反転します。

vImageHistogramCalculation_ARGB8888
 ヒストグラムを計算します。

いかがだったでしょうか。
vImageは適切な場面で活用すれば、非常に協力に我々をサポートしてくれます。
是非みなさんも触ってみてはいかがでしょうか。