iOSで人物きりぬきPersonSegmentation（バーチャル背景、背景ぼかし、合成）

AppleのVisionで**PersonSegmentation**ができるようになった。
iOSで人物切り抜きができる。

**【セグメンテーション・マット】**
<img src="https://user-images.githubusercontent.com/23278992/145121119-ec1b4b0e-0e0b-46a1-bb9f-907129faa80f.png", width=200>
**GitHubサンプルコード：　
[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/4b34909f-f685-da0c-ed3b-4174feec6afe.png", width=100>
PersonSegmentationSampler](https://github.com/john-rocky/PersonSegmentationSampler)**
VisionのPersonSegmentationで画像の背景合成（ぼかし/画像合成）ができるアプリサンプル。

###既存のセグメンテーションとの違い

今までAVFoundationや、ARKitでもPersonSegmentationの機能があったが、いつでもセグメンテーションマットが得られるわけではなかった。
AVFoundationでは撮影時にPortrait Matteを設定した写真のみだし、
ARKitもマット画像が得られるわけではなかった。
その意味するところの一つは、AVFoundationやARKitでは、
**既存の画像や動画の切り抜きができなかった**ということだ。
そうするためには、DeepLabなどの機械学習モデルをCoreMLで使う必要があった。
（[僕が作った人物の背景をぼかしたり合成するアプリ](https://apps.apple.com/jp/app/blur%E8%87%AA%E5%8B%95%E3%81%BC%E3%81%8B%E3%81%97/id1501780798)ではそうしていた）

https://apps.apple.com/jp/app/blur%E8%87%AA%E5%8B%95%E3%81%BC%E3%81%8B%E3%81%97/id1501780798

2021年に追加されたVisionのPersonSegmentationで、既存の画像や動画の人物きりぬきもできるようになった。



**【ぼかし/透過】**


**【合成】**




#PersonSegmentationの使用手順
いつものVisionのやり方。
Requestを作成し、Handlerで実行、結果はPixelBufferで返されるので、すぐに白黒マット画像が得られる。

###Request作成

```swift
lazy var personSegmentationRequest: VNGeneratePersonSegmentationRequest = {
    request = VNGeneratePersonSegmentationRequest()
    request.qualityLevel = .balanced
    request.outputPixelFormat = kCVPixelFormatType_OneComponent8
    return request
}()
```

###qualityLevel
セグメンテーションの精度は３種類選択可能で、実行速度と消費リソースは精度とトレードオフの関係にある。

|qualityLevel|ユースケース|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/0da2e7ff-df9b-d82b-c6ef-7e0df93ebfb3.jpeg" width=400>|
|---|---|---|
|.fast|ストリーミング|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/2e299661-88c9-bafb-5441-b66a1507f473.png" width=400>|
|.balanced|動画|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/6b5b5a56-9cc9-15fd-b951-0225eb9d0411.png" width=400>|
|.accurate|画像|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/b801c15b-e785-aac0-573f-f301cc4cbcae.png" width=400>|

outputPixelFormatは出力のタイプ。

|outputPixelFormat|タイプ|出力値幅|
|---|---|---|
|kCVPixelFormatType_OneComponent8|8 bit uint|[0;255]|
|kCVPixelFormatType_OneComponent16Half|16 bit float|[0.0;1.0]|
|kCVPixelFormatType_OneComponent32Float|32 bit float|[0.0;1.0]|

###Handler実行
単一画像で解析を行う場合

```swift
let requestHandler = VNImageRequestHandler((cvPixelBuffer: pixelBuffer, orientation: .right, options: [:])
try? requestHandler.perform([personSegmentationRequest])
guard let resultPixelBuffer = personSegmentationRequest.results?.first?.pixelBuffer else { return }
```

動画シーケンス・フレームで解析を行う場合

```swift
let sequenceRequestHandler = VNSequenceRequestHandler()
```

```swift
try? sequenceRequestHandler.perform([personSegmentationRequest],
                            on: pixelBuffer,
                            orientation: .right)
        
guard let resultPixelBuffer = personSegmentationRequest.results?.first?.pixelBuffer else { return }
```

これまでのVision Requestと違い、VNGeneratePersonSegmentationRequestは状態保持（Stateful）リクエストで、.accurate（正確性重視）モードで実行した時に、フレームのシーケンス間でマット画像を滑らかにする。

###背景合成

```swift
let originalImage = CIImage(cvPixelBuffer: pixelBuffer)
var maskImage = CIImage(cvPixelBuffer: resultPixelBuffer)

// 結果のマスクイメージはサイズが変わっているので、オリジナルか背景のサイズに合わせる
var originalImage = CIImage(cvPixelBuffer: framePixelBuffer).oriented(.right)
let scaleX = backgroundImage.extent.width / originalImage.extent.width
let scaleY = backgroundImage.extent.height / originalImage.extent.height
originalImage = originalImage.transformed(by: .init(scaleX: scaleX, y: scaleY))

var maskImage = CIImage(cvPixelBuffer: maskPixelBuffer)
        
let scaleXForMask = backgroundImage.extent.width / maskImage.extent.width
let scaleYForMask = backgroundImage.extent.height / maskImage.extent.height
maskImage = maskImage.transformed(by: .init(scaleX: scaleXForMask, y: scaleYForMask))

let filter = CIFilter(name: "CIBlendWithMask", parameters: [
            kCIInputImageKey: originalImage,
            kCIInputBackgroundImageKey: backgroundImage,
            kCIInputMaskImageKey: maskImage])
let compositeImage = filter?.outputImage
```

VisionのPersonSegmentationで画像の背景合成（ぼかし/画像合成）ができるサンプルを作りました。
GitHubサンプルコード：　
**[PersonSegmentationSampler](https://github.com/john-rocky/PersonSegmentationSampler)**


🐣
****
フリーランスエンジニアです。
お仕事のご相談こちらまで
rockyshikoku@gmail.com
開発したい内容を簡単に書いて、お気軽にメールしてください。

Core MLやARKitを使ったアプリを作っています。
機械学習／AR関連の情報を発信しています。

[Twitter](https://twitter.com/JackdeS11)
[Medium](https://medium.com/@rockyshikoku)