Metalでカメラからの動画入力をリアルタイム処理する

Metalのパフォーマンスがわかりやすく活きる例としては、動画のリアルタイム処理です。動画撮影中に**30fpsなり60fpsなりで渡されてくるカメラからの入力をMetalで画像処理し、Metalで描画する**、ということをやります。


##カメラからの入力を取得する

カメラからの入力をリアルタイムに受け取るようにする実装は、何も新しいことはないのでここでは省略します。AVFoundationの`AVCaptureSession`を用いるおなじみのものです。[iOS-10-Sampler](https://github.com/shu223/iOS-10-Sampler)にもそういう実装があるので必要に応じてご参照ください。

関連：

- [iOSでの動画処理における「回転」「向き」の取り扱いでもう混乱したくない - Qiita](http://qiita.com/shu223/items/057351d41229861251af)
- [AV Foundationで240fpsスローモーション動画撮影を実装する - Qiita](http://qiita.com/shu223/items/51f764101e6fd5c2161b)


`AVCaptureVideoDataOutputSampleBufferDelegate` プロトコルの ` captureOutput:didOutputSampleBuffer:fromConnection:` メソッドで、カメラからの入力が`CMSampleBuffer` として渡されてきます。

```swift
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!)
```

ここから `CMSampleBufferGetImageBuffer()` を用いて、 `CVImageBuffer` (`CVPixelBuffer`)を取り出します。

```swift
let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
```

ここまでは**iOSデバイスでカメラ入力に対して何かやりたいときには必ず通る、おなじみの手順**かと思います。


##CVImageBuffer -> MTLTexture

ここからが本番で、Metalで処理するために、 `CVImageBuffer` (`CVPixelBuffer`) からMTLTextureを生成します。

###1.`CVMetalTextureCache`を生成
 
まず、`CVMetalTextureCache`を用意しておきます。

(Swift)

```swift
var textureCache : CVMetalTextureCache?
```

```swift
CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, device, nil, &textureCache)
```

(ObjC)

```objc
CVMetalTextureCacheRef textureCache;
```

```objc
CVMetalTextureCacheCreate(kCFAllocatorDefault,
                          nil,
                          device,
                          nil,
                          &textureCache);
```


（`device`は`MTLDevice`）

`CVMetalTextureCache`は、**`CVMetalTexture`型のテクスチャを生成・管理**するためのクラスです。以下リファレンスより引用。

> A CoreVideo Metal texture cache creates and manages CVMetalTexture textures. You use a CVMetalTextureCache object to directly read from or write to GPU-based CoreVideo image buffers in rendering or GPU compute tasks that use the Metal framework. 

テクスチャをつくるたびに生成しなおす必要はありません。


###2. CVImageBuffer -> CVMetalTexture

ここからは、カメラからの入力（CMSampleBuffer）を得るたびに行う処理です。

`CVMetalTextureCache`を用いて、 `CVImageBuffer` から、 `CVMetalTexture` を生成します。

```swift:swift
let width = CVPixelBufferGetWidth(imageBuffer)
let height = CVPixelBufferGetHeight(imageBuffer)
    
var imageTexture: CVMetalTexture?
    
let result = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache, imageBuffer, nil, pixelFormat, width, height, 0, &imageTexture)
```

```objc:objc
CVMetalTextureRef imageTexture;
CVReturn result = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                                            textureCache,
                                                            imageBuffer,
                                                            nil,
                                                            MTLPixelFormatBGRA8Unorm,
                                                            width, height, 0,
                                                            &imageTexture);
```

成功すれば、戻り値は `kCVReturnSuccess` となります。

ちなみに、`CVMetalTexture`とはどういうものかというと、「Metalで利用するためのtexture-basedなイメージバッファ」とのことです。

> A texture-based image buffer that supplies source image data for use with the Metal framework.

###3. CVMetalTexture -> MTLTexture

イメージバッファから、`MTLTexture` を取り出します。

```swift:swift
let texture = CVMetalTextureGetTexture(imageTexture)
```

```objc:objc
id<MTLTexture> texture = CVMetalTextureGetTexture(imageTexture);
CFRelease(imageTexture);
```

**Objective-Cの場合は明示的にCVMetalTextureを解放**する必要がある点に注意。

##MTLTexture -> Metalで処理

**Metalのシェーダ**で何かしらの処理を行う話は以下の記事に、

- [Metalシェーダことはじめ - WebGL/GLSLの豊富なサンプルを参考にする - Qiita](http://qiita.com/shu223/items/dd5a53b8c291abe30fc2)
- [MetalシェーダでSceneKitのマテリアルを描画する - Qiita](http://qiita.com/shu223/items/b5729fdf1d95721d07b7)

また、**Metal Performance Shadersでリサイズやブラー**処理を行う話はこちらに、

- [Metalで画像をリサイズする - Qiita](http://qiita.com/shu223/items/19ac718dcd561a891689)
- [Metal逆引きレシピ - Qiita](http://qiita.com/shu223/items/7ed4efd54d03f0babbe0)

MTLTextureに対して**（Metalでシームレスに）Core Imageで画像処理**を行う話は下記記事に書きました。

- [Metalの恩恵は受けつつCore Imageで「手軽に」画像処理 - Qiita](http://qiita.com/shu223/items/4c0f8073477f787e1627)


##MTLTexture -> MTKViewで表示（描画)

MTLTextureをMTKViewで描画する方法については、下記記事にまとめました。

- [MetalでUIImageViewライクに画像を表示する - 最小実装編 - Qiita](http://qiita.com/shu223/items/3301a1e64757c0bd73ef)


##出来上がり

（AnimatedGIFを載せる予定）

##今後の展望

このへんの知見は、Metal Performance ShadersでのCNNを使う際にも活きてくる予定です。

![](https://github.com/shu223/iOS-10-Sampler/blob/master/README_resources/imagerecog.gif?raw=true)

- [iOS 10の新機能のサンプルコード集「iOS 10 Sampler」 - Qiita](http://qiita.com/shu223/items/f371655c2b101f1b0218)

近日中に記事を書く予定です。