vImageで画像処理を行う

vImageとは、iOS5からAccelerate.frameworkに追加された高速画像処理ライブラリです。特徴は何といっても**ハードウェア向けに最適化されていて高速**という点です。WWDC2011のセッション209 "Inside the Accelerate Framework" によると、vImageを使わないで書いたコードよりも14倍高速という結果が出ていました。また同セッションの資料には、消費電力が抑えられるという報告もあります。

ここでは、画像処理の基本演算である畳み込み演算を行う関数`vImageConvolve_ARGB8888`を用いて画像にフィルタをかける方法を紹介します。

##準備
1. Accelerateフレームワークをプロジェクトに追加する
2. Accelerate.hをインポートする

````
#import <Accelerate/Accelerate.h>
````

##入力画像のビットマップデータを取得
`vImageConvolve_ARGB8888`の引数に渡すため、UIImageとして読み込んだ入力画像のビットマップデータを取得します。

````
const size_t width = self.size.width;
const size_t height = self.size.height;
const size_t bytesPerRow = width * 4;

CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst;
CGContextRef bmContext = CGBitmapContextCreate(NULL, width, height, 8,
                                               bytesPerRow, space,
                                               bitmapInfo);
CGColorSpaceRelease(space);

if (!bmContext) {
    return nil;
}

CGRect dstRect = CGRectMake(0, 0, width, height);
CGContextDrawImage(bmContext, dstRect, self.CGImage);

UInt8 *data = (UInt8 *)CGBitmapContextGetData(bmContext);
if (!data) {
    CGContextRelease(bmContext);
    return nil;
}

vImage_Buffer src = {data, height, width, bytesPerRow};
````


##出力画像のメモリ領域を確保
入力画像と同サイズの出力画像用のメモリ領域を確保します。

````
const size_t dstSize = sizeof(UInt8) * width * height * 4;
void *dstData = malloc(dstSize);
vImage_Buffer dst = {dstData, height, width, bytesPerRow};
````


##畳み込み処理を実行
`vImageConvolve_ARGB8888`を実行します。引数が多く複雑に見えますが、どれも畳み込み処理にある程度汎用性を持たせようとすると必要なものばかりです。

下記サンプルコードは、画像全体にガウシアンブラーフィルタをかける場合の例です。

````
vImageConvolve_ARGB8888(&src,   // const vImage_Buffer *src
                        &dst,   // const vImage_Buffer *dest
                        NULL,   // void *tempBuffer
                        0,      // vImagePixelCount srcOffsetToROI_X
                        0,      // vImagePixelCount srcOffsetToROI_Y
                        gaussianblur_kernel,    // const int16_t *kernel
                        5,      // uint32_t kernel_height
                        5,      // uint32_t kernel_width
                        256,    // int32_t divisor
                        NULL,   // Pixel_8888 backgroundColor
                        kvImageCopyInPlace  // vImage_Flags flags
                        );

````

ここでは、このサンプルでポイントとなる引数に絞って補足します。

まず、第1引数と第2引数は、それぞれ入力画像と出力画像のvImage_Buffer構造体へのポインタです。前項と前々項で用意したものを渡しています。

そして重要なのが第6引数"kernel"で、渡している値gaussianblur_kernelは、次のように定義された配列へのポインタです。

````
static int16_t gaussianblur_kernel[25] = {
	1, 4, 6, 4, 1, 
	4, 16, 24, 16, 4,
	6, 24, 36, 24, 6,
	4, 16, 24, 16, 4,
	1, 4, 6, 4, 1
};
````

5x5のガウシアンブラーフィルタのカーネルの値が格納されています。

第7、第8引数はこのカーネルの高さと幅、第9引数"divisor"はこのカーネルの値の合計値です。"divisor"は畳み込み演算の際に値を正規化するために指定します。


##処理結果をUIImageに変換
処理後のビットマップデータから`CGBitmapContextCreateImage`関数を用いてCGImageRefオブジェクトを生成し、そこから`imageWithCGImage:`メソッドを用いてUIImageオブジェクトを生成します。

````
memcpy(data, dstData, dstSize);
free(dstData);

CGImageRef blurredImageRef = CGBitmapContextCreateImage(bmContext);
UIImage* blurred = [UIImage imageWithCGImage:blurredImageRef];

CGImageRelease(blurredImageRef);
CGContextRelease(bmContext);
````

以上が、入力画像のUIImageから、vImageの`vImageConvolve_ARGB8888`関数を用いて畳み込み処理を行った出力画像をUIImageとして取得するまでの手順です。

![ブラーサンプル](http://f.cl.ly/items/400a1U3z3v0q3e0E1B03/vImage_blur.png)

##他のフィルタ処理を行う
同じ`vImageConvolve_ARGB8888`関数を用いて、第6引数"kernel"の配列をいろいろと変えることで、様々なフィルタ効果を得ることができます。

以下ではカーネルの配列と、`vImageConvolve_ARGB8888`をコールする部分のみサンプルを示します。カーネルの配列に合わせて`vImageConvolve_ARGB8888`の第7〜第9引数を変えている点にご注目ください。

**エンボス**

![エンボスサンプル](http://f.cl.ly/items/281o1H1g2w403D051u44/vImage_emboss.png)

````
static int16_t emboss_kernel[9] = {
	-2, 0, 0, 
	0, 1, 0, 
	0, 0, 2
};
````

````
vImageConvolve_ARGB8888(&src,
                        &dst,
                        NULL,
                        0,
                        0,
                        emboss_kernel,
                        3,
                        3,
                        1,
                        NULL,
                        kvImageCopyInPlace);
````

**先鋭化**

![先鋭化サンプル](http://f.cl.ly/items/1s191B3z0e2h3C3t171S/vImage_sharpen.png)

````
static int16_t sharpen_kernel[9] = {
	-1, -1, -1, 
	-1, 9, -1, 
	-1, -1, -1
};
````

````
vImageConvolve_ARGB8888(&src,
                        &dst,
                        NULL,
                        0,
                        0,
                        sharpen_kernel,
                        3,
                        3,
                        1,
                        NULL,
                        kvImageCopyInPlace);

````

**エッジ検出**

(後ほど画像を貼付けます)

````
static int16_t edgedetect_kernel[9] = {
 -1, -1, -1, 
 -1, 8, -1, 
 -1, -1, -1
 };
````

````
vImageConvolve_ARGB8888(&src,
                        &dst,
                        NULL,
                        0,
                        0,
                        edgedetect_kernel,
                        3,
                        3,
                        1,
                        backgroundColorBlack,
                        kvImageCopyInPlace);
````

※処理結果を視認しやすくするために、第10引数で背景色を指定しています。


##サンプルコード
vImageCategoryという、vImageによる画像処理を行えるクラスと、そのデモプロジェクトを下記リポジトリにて公開しています。

https://github.com/shu223/vImageCategory

UIImage のカテゴリとして作ってあってAPIも下記のようにシンプルにしてあります。

````
// Convolution Oprations
- (UIImage *)gaussianBlur;
- (UIImage *)edgeDetection;
- (UIImage *)emboss;
- (UIImage *)sharpen;
- (UIImage *)unsharpen;

// Geometric Operations
- (UIImage *)rotateInRadians:(float)radians;

// Morphological Operations
- (UIImage *)dilate;
- (UIImage *)erode;
- (UIImage *)dilateWithIterations:(int)iterations;
- (UIImage *)erodeWithIterations:(int)iterations;
- (UIImage *)gradientWithIterations:(int)iterations;
- (UIImage *)tophatWithIterations:(int)iterations;
- (UIImage *)blackhatWithIterations:(int)iterations;

// Histogram Operations
- (UIImage *)equalization;
````

ここでは紹介していない、**回転や膨張、収縮**の機能も入っているので、それらを使用する際のサンプルとしてもご参照ください。

![処理結果サンプル](http://f.cl.ly/items/0K3c3f1024342m0X1t3o/vImage_demo_r1_c1.png)