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


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

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

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

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

セピア補正のフィルターを適用すると結果はこんな感じです。
実際にプロダクトには色々な画像補正フィルターを掛け合わせて独自フィルターを作り実装しました。

オリジナル セピア補正

※写真はフリー素材を利用:https://www.pakutaso.com/model.html

画像の部分補正処理の流れ

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

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

マスク画像の作成

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

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

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を使用しています。

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
   }
}

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

オリジナル画像 しみ消し用のマスク画像

画像の合成

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

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

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画像

結果

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

オリジナル画像 しみ消し処理後の画像