💭

Visionフレームワークで画像の背景を削除する

2024/07/29に公開

はじめに

WWDC2024で、Visionフレームワークが更新されbeta版で公開されました。iOSだと、18.0betaから利用できます。その中で、画像の背景削除を行うAPIを利用する機会があったのでまとめました。

公式ドキュメントを見てみる

早速コードにと思ったのですが、まずVisionフレームワークの公式のドキュメントを軽くみてみましょう。
下記の画像だけでも、かなりの部分がbeta版で新しくなっていることがわかるかと思います。もし、この変更について詳しく知りたい!以前のAPIの差し替え方法を知りたい!となった方は、WWDC2024のこの動画を見るとよいかと思います。以前の方法からの差し替え方法まで解説しています。

実際のコード

さて、実際のコードです。

import SwiftUI
import Vision

    
func startSession(imageName: String) async -> UIImage{

    // ローカルアセットからCIImageを取り込む
    let uiImage = UIImage(named: imageName)!
    let image = CIImage(image: uiImage)!
    
    // ImageRequestHandlerの生成
    let imageRequestHandler: ImageRequestHandler = .init(
        image)
    
    // GenerateForegroundInstanceMaskRequestの生成
    let request = GenerateForegroundInstanceMaskRequest()
    
    // セッション開始
    if let result = try? await request.perform(on: image)
    {
        // IndexSetをresult.allInstancesにすると背景以外が表示される。
        // IndexSetを[1]にすると、BackGroundの次に検出されたものだけ表示される
       if let buffer =  try? result.generateMaskedImage(for: result.allInstances, imageFrom: imageRequestHandler,croppedToInstancesExtent: false)
        {
               // bufferをCVPixelBufferをUIImageに
               if let resultimage =  UIImage(pixelBuffer: buffer){
                   return resultimage
               }
       }
    }
    // if letで弾かれたらデフォルト返す
    return UIImage()
}
    

出力結果

このコードを利用して、iphoneに出力してみるとのこのように、犬だけ切り取られた画像が表示されているのがわかるかと思います。

ちょっとした解説

ローカルの画像アセットの名前を渡して、その名前から画像を取得し、最終的UIImageとして出力するメソッドになります。GenerateForegroundInstanceMaskRequestが背景画像のマスクを生成するAPIになっており、そのレスポンスであるInstanceMaskObservationを利用して、背景をマクスした画像を生成しています。

generateMaskedImageのindexSet

generateMaskedImageにはindexSetを渡す必要があります。一度、Swiftで背景処理を行ったことがある方は、この処理の引数が何を指しているかわかるかと思います。しかし、初めて利用した場合IndexSetの意味がわからないと思います。(僕もそうでした)

func generateMaskedImage(
    for instances: IndexSet,
    imageFrom requestHandler: ImageRequestHandler,
    croppedToInstancesExtent: Bool = false
) throws -> CVPixelBuffer

これは、『WWDC23のアプリ内の画像からの被写体の切り抜き』を見ると、理解できます。
10:45からが該当の部分です。ofInstancesという引数があると思います。Visionフレームワークが更新されているのでプロパティ名は異なりますが、同じものです。IndexSetは、背景とコンテンツを分解した上でどれを表示するかを決定する引数で、このIndexSetに[0]を入れると何もない背景画像が出力されます。result.allInstancesをすると背景以外を出力してくれます。

CVPixelBufferからUIImageの生成

CVPixelBufferからUIImageの生成は、こちらの方のExtentionを参考にさせていただきました。

シミュレーターだとエラーになる

解説というより、詰まったところですが。上のコードをシミュレーターで回すとCode=9 "Could not create inference context"というようなエラーになります。これは、シミュレーターではCoreMLが利用できないためエラーになるようで、実機で試してみると問題なく動作します。

https://stackoverflow.com/questions/75863781/code-9-could-not-create-inference-context-coreml-ios

最後に

以前のAPIを利用したことがないので、どこまで便利になっているか体感はできないのですがかなり少ないコード実現できてるのはとても嬉しいです。また、他のVisionフレームワークのAPIと利用フローが統一されているのも使いやすいなーと思いました。他のAPIについても時間があれば理用意してみたいと思います。

参考になった記事

https://developer.apple.com/documentation/vision

https://developer.apple.com/jp/videos/play/wwdc2023/10176/

https://developer.apple.com/jp/videos/play/wwdc2024/10163/

https://qiita.com/tommy19970714/items/bc50d49fcb378c196d4f

https://zenn.dev/uhucream/articles/af8e595a2858f2

Discussion