🧑🏻‍🦰

iOSで「髪」「肌」「歯」「空」をセグメンテーションする

2023/11/09に公開

"Matte"は、背景合成等において前景と背景を分離するために使用する、マスク専用の画像を意味します。

iOS 13以降で「Semantic Segmentation Matte」というものを取得できるようになったのですが、これがどういうものかを知るために、まずはiOS 12からある「Portrait Effect Matte」をおさらいしましょう。

おさらい: Portrait Effect Matte

Portrait Effect Matte(以下PEM)は、iOS 12から取得できるようになった、「人間の全身」のセグメンテーション[1]に特化したマスク画像です。

デュアルカメラやTrueDepthカメラでの撮影時に取得できるデプス(深度)情報だけでなく、機械学習も使用しており、高精細であることが特長です。[2]


髪の毛の部分の再現度も高い

Semantic Segmentation Matteの種類

そしてiOS 13から取得できるようになったSemantic Segmentation Matte(以下SSM)は、人の「髪」「肌」「歯」といった部位をセマンティックセグメンテーションするためのマスクです。


左から、Hair / Skin / Teeth

これらを使って、たとえば髪の色を変える、肌の色を変える、歯を白くする、といったことができます。


SSMを使用して肌の色を変える

Semantic Segmentation Matteの取得方法

PEMの場合は以下のように取得できました。

let info = CGImageSourceCopyAuxiliaryDataInfoAtIndex(
   source, 
   0, 
   kCGImageAuxiliaryDataTypePortraitEffectsMatte
) as? [String : AnyObject]

CGImageSourceオブジェクトから、CGImageSourceCopyAuxiliaryDataInfoAtIndex関数を利用して、kCGImageAuxiliaryDataTypePortraitEffectsMatteを指定してPEMを取得しています。[3]

SSMもほぼPEMと同様の方法でCGImageSourceオブジェクトから取得します。それぞれ次のようなAuxiliaryDataTypeが定義されており、

kCGImageAuxiliaryDataTypeSemanticSegmentationHairMatte
kCGImageAuxiliaryDataTypeSemanticSegmentationSkinMatte
kCGImageAuxiliaryDataTypeSemanticSegmentationTeethMatte

CGImageSourceCopyAuxiliaryDataInfoAtIndexの第3引数でこれらを指定するだけです。

let info = CGImageSourceCopyAuxiliaryDataInfoAtIndex(
   source, 
   0, 
   kCGImageAuxiliaryDataTypeSemanticSegmentationHairMatte   // Hair
) as? [String : AnyObject]

AVSemanticSegmentationMatte

AVFoundationでPEMを扱うクラスとして、AVPortraitEffectsMatteが用意されており、次のように(上述のCGImageSourceCopyAuxiliaryDataInfoAtIndex関数から取得した)Dictionaryを渡して初期化することができました。

let matte = AVPortraitEffectsMatte(fromDictionaryRepresentation: info)

iOS 13ではSSMを扱うクラスとしてAVSemanticSegmentationMatteが追加されました。イニシャライザは次のように定義されています。

convenience init(
    fromImageSourceAuxiliaryDataType imageSourceAuxiliaryDataType: CFString, 
    dictionaryRepresentation imageSourceAuxiliaryDataInfoDictionary: 
        [AnyHashable : Any]) throws

AVPortraitEffectsMatteと同様に、[AnyHashable : Any]型のDictionaryを渡して初期化できるようになっています。

第1引数にimageSourceAuxiliaryDataTypeを渡す点だけが違っていて、kCGImageAuxiliaryDataTypeSemanticSegmentationHairMatte,
〜SkinMatte,
〜TeethMatteのいずれかを渡します。

let skinMatte = AVSemanticSegmentationMatte(
    fromImageSourceAuxiliaryDataType: 
        kCGImageAuxiliaryDataTypeSemanticSegmentationSkinMatte, 
    dictionaryRepresentation: info)

AVSemanticSegmentationMatteAVPortraitEffectsMatteと同様にmattingImageというPEMのピクセルデータを保持するプロパティを持ち、

var mattingImage: CVPixelBuffer { get }

これがCVPixelBuffer型なので、Core ImageやMetalといった既存のフレームワークを利用してこのピクセルバッファにある画像データを処理することができます。

SSMをCIImage経由で取得する

前述のCGImageSourceからSSMを取得する方法以外に、CIImageとして取得する方法もあります。

CIImageの各種イニシャライザはCIImageOptionを渡せるようになっているので、

init?(contentsOf url: URL, 
options: [CIImageOption : Any]? = nil)

次のCIImageOptionのいずれかをキーに、

static let auxiliarySemanticSegmentationHairMatte: CIImageOption
static let auxiliarySemanticSegmentationSkinMatte: CIImageOption
static let auxiliarySemanticSegmentationTeethMatte: CIImageOption

値にtrueをセットすることでCIImageオブジェクトとしてSSMを取得できます。

「空」のSSM

iOS 14.1で、kCGImageAuxiliaryDataTypeSemanticSegmentationSkyMatte が追加されました。

名前の通り、「空」をセグメンテーションするためのマスク画像が得られます。

guard let info = CGImageSourceCopyAuxiliaryDataInfoAtIndex(self, 0, kCGImageAuxiliaryDataTypeSemanticSegmentationSkyMatte) as? [String : AnyObject] else { return nil }
let skyMatte = try? AVSemanticSegmentationMatte(fromImageSourceAuxiliaryDataType: kCGImageAuxiliaryDataTypeSemanticSegmentationSkyMatte, dictionaryRepresentation: info)

関連:

https://note.com/shu223/n/nd6da31803df4

https://note.com/shu223/n/nfdfda237a3df

脚注
  1. 画像の領域を分割する処理。 ↩︎

  2. 取得のための実装方法等の詳細は、拙著「Depth in Depth - iOSデプス詳解」(https://booth.pm/ja/items/1313752)をご参照ください。 ↩︎

  3. "Auxiliary"は「補助」という意味。デプス等のデータはメインの画像ではなく、補助的な画像データであるという意味でこう命名されているのでしょう。 ↩︎

Discussion