CoreImageフレームワークを使ってSwiftでしみ消し、くま消し機能を実装した話

バイトル履歴書アプリに画像の補正機能を追加するエンハンスを担当したのでやったことまとめておきます。

今回のエンハンス内容としましては画像補正機能として撮影した写真全体に対して独自で定義したフィルター処理を行うのと、目の下のくまの部分や頬のあたりのしみの部分を部分的に画像補正処理を行う機能をCoreImageフレームワークのみを使い実装しました。

写真全体に対してフィルター処理を行うケースはそんなに難しくないのでサンプルコードだけ載せておきます。

```Swift
import CoreImage
...

// フィルター処理をかけたいSampleイメージを準備
var image = UIImage(named: "sample.jpg")

// UIImageからCIImageへの変換
let ciImage: CIImage? = CIImage(image: image)

// Sepiaフィルターの準備
var filter = CIFilter(name: "CISepiaTone")

// フィルターに画像を渡す
filter.setValue(ciImage, forKey: kCIInputImageKey)

// Filterをかけた画像を取得する
var filteredImage: CIImage? = filter.outputImage
```
セピア補正のフィルターを適用すると結果はこんな感じです。
実際にプロダクトには色々な画像補正フィルターを掛け合わせて独自フィルターを作り実装しました。

|オリジナル|セピア補正|
|:---:|:---:|
|![filter-sample-original.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126555/2d0b5ba5-589c-c2b5-bee7-f5d67c24f46f.png)|![filter-sample-sepia.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126555/d50a7b88-c9d3-dd26-69e7-89869ebfea23.png)|
※写真はフリー素材を利用：https://www.pakutaso.com/model.html

# 画像の部分補正処理の流れ
続いて、画像の部分補正をする場合の処理の流れをまとめていきます。

![0aac97d9-f553-44cf-97ea-c622e28b864d-1920x3172r.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126555/23c0fac0-3439-a0df-bbe5-8c5cb70d27ff.png)

しみ消し機能にしろ、くま消し機能にしろ画像を部分的に補正するという点においては同じなのでどちらも上記の処理フローとなります。

## マスク画像の作成 
マスク画像の作成にはCoreImageのCIDetectionを使い顔のパーツの位置関係を取得することから始めます。顔検出において取得できる情報ですが、口の位置や目の位置、目が開いているかどうかや顔の輪郭を取得でき、それらの情報を元にマスク画像の作成をしていきます。

顔検出もCoreImageがサポートしていて下記のコードより取得できます。

```Swift
let options = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
var faces: [CIFeature] = []
if let image = CIImage(image: faceImage), let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: options) {
     faces = faceDetector.features(in: image)
}
```

マスク画像の作成には検出した顔のパーツの位置情報を使い、数値モデル化したしみの位置を計算して`CIRadialGradient`というCIFilterを使います。こちらのFilterですが、ガウシアンを位置情報を入力することで生成できるフィルターとなっており、しみ消し用のマスクに関しては計6点のガウシアン波形を重ね合わせマスク画像を作成しております。ガウシアン波形の合成には`CISourceOverCompositing`と呼ばれるCIFilterを使用しています。

```Swift
let maskCircles: [MaskCircle] = [
   /// ガウシアンの位置情報など...
]
for (index, circle) in maskCircles.enumerated() {
   guard let gaussian = CIFilter(name: "CIRadialGradient") else { return nil }
   gaussian.setValue(CIVector(cgPoint: circle.center), forKey: kCIInputCenterKey)
   gaussian.setValue(circle.innerRadius, forKey: "inputRadius0")
   gaussian.setValue(circle.outerRadius, forKey: "inputRadius1")
   gaussian.setValue(CIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0), forKey: "inputColor0")
   gaussian.setValue(CIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.0), forKey: "inputColor1")

   if index == 0 {
      prev = gaussian
   } else {
      shimi = CIFilter(name: "CISourceOverCompositing")
      if let prevMask = prev?.outputImage, let gaussianMask = gaussian.outputImage {
         shimi?.setValue(gaussianMask, forKey: kCIInputImageKey)
         shimi?.setValue(prevMask, forKey: kCIInputBackgroundImageKey)
      }
      prev = shimi
   }
}

```

下記の画像がオリジナル画像とマスク画像をオリジナル画像に重ね合わせている図です。顔のしみがある部分を狙ってガウス波形の重ね合わせでマスク画像を作成しています。

|オリジナル画像|しみ消し用のマスク画像|
|:---:|:---:|
|![スクリーンショット 2020-12-22 18.09.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126555/f9b95689-b006-7c25-ee3c-c4832f28f659.png)|![スクリーンショット 2020-12-22 18.09.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126555/1ab70966-9d18-4d1e-e987-b51dd5303d7c.png)|

## 画像の合成
続きまして作成したしみ消し用のマスク画像を元に画像合成をしてしみ消し機能を実装していきます。マスク画像を使った部分的な合成には`CIBlendWithMask`フィルターを使います。

`CIBlendWithMask`フィルターではマスク画像とオリジナル画像とマスク部分だけ適応したい画像補正をオリジナル画像にかけた画像を用意して合成処理を行います。

```Swift
let blend = CIFilter(name: "CIBlendWithMask") else { return nil }

/// ブラー効果をかけた画像の設定
blend.setValue(blurImage, forKey: kCIInputImageKey)
/// オリジナル画像の設定
blend.setValue(originalImage, forKey: kCIInputBackgroundImageKey)
/// マスク画像の設定
blend.setValue(mask, forKey: kCIInputMaskImageKey)

/// 合成した画像の取得
blend.outputImage
```

|オリジナル画像|しみ消し用のマスク画像|Blur画像|
|:---:|:---:|:---:|
|![スクリーンショット 2020-12-22 18.09.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126555/c5b0defd-320c-3dee-c0c0-87b877fa4d85.png)|![スクリーンショット 2020-12-22 18.09.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126555/28095960-ce77-1f93-88aa-a59f5d6d0a60.png)|![スクリーンショット 2020-12-22 18.10.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126555/54877666-601c-9475-b72d-69402d12d8c4.png)|

## 結果
部分的に画像補正を行ってしみ消し機能を実装してみた結果はこんな感じです！しみの部分だけ部分的にぼかし効果が適用されてしみが目立たなくなっているのがわかるかと思います。

|オリジナル画像|しみ消し処理後の画像|
|:---:|:---:|
|![スクリーンショット 2020-12-22 18.32.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126555/5a1f5062-3260-43f1-47bd-00b734e15570.png)|![スクリーンショット 2020-12-22 18.32.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126555/761f07de-fc12-b30b-4642-015fc6093be0.png)|