LoginSignup
6
1

[iOS17]Visionで写真の前景をトリミング

Posted at

Visionで画像の前景をトリミングする

iOS17からVisionに画像の前景をトリミングするAPIが追加されました
写真アプリで長押しするとハイライトされるアレです!
写真アプリにはiOS16から入っていたのですが、iOS17からVisionにAPIが入ったので、アプリにこの機能を簡単に組み込めるようになりました

Frame 288.png

WWDC23のセッション↓

前景を全て含むマスク画像を作成する

これだけのコードで前景のマスク画像を作成できます

func makeMask(inputImage: CIImage) -> CIImage {
    let request = VNGenerateForegroundInstanceMaskRequest()
    let handler = VNImageRequestHandler(ciImage: inputImage)
    try! handler.perform([request])
    let result: VNInstanceMaskObservation = request.results!.first!

    let mask = try! result.generateScaledMaskForImage(forInstances: result.allInstances, from: handler)
    return CIImage(cvPixelBuffer: mask)
}

こちらが実行結果です
出力されるCIImageのcolor spaceはkCGColorSpaceModelMonochromeで8bitのモノクロ画像のようです
前景っぽいところが255(白)で背景っぽいところが0(黒)となっているようです

Frame 289.png

複数の前景を含む場合に前景を選んでマスク画像を作成する

画像に複数の前景が含まれる場合、前景を選んでマスク画像を作成したいです
VNInstanceMaskObservation.instanceMaskに検出した前景ごとのラベルがついた画像が入っています
512x512の正方形の8bitのモノクロ画像で、背景の領域が0、前景のインスタンスの領域が1, 2, 3...と順番に入っていくようです
切り出したい領域にある前景のインスタンスのラベルを調べて、複数の前景を含む写真から指定した前景のみを含むマスクを作成できます

Frame 290.png

instanceMaskの指定した座標のラベルを調べて、generateScaledMaskForImageにそのラベルを渡します
instanceMaskが入力画像によらず正方形のようなので、入力された座標を正規化し、instanceMaskのサイズに合わせて座標を求めるようにします

func makeMask(inputImage: CIImage, atNormalizedSelectedPoint normalizedSelectedPoint: CGPoint) -> CIImage {
    let request = VNGenerateForegroundInstanceMaskRequest()
    let handler = VNImageRequestHandler(ciImage: inputImage)
    try! handler.perform([request])
    let result: VNInstanceMaskObservation = request.results!.first!

    let i = getInstanceLabel(atNormalizedPoint: normalizedSelectedPoint, instanceMask: result.instanceMask)
    let mask = try! result.generateScaledMaskForImage(forInstances: [Int(i)], from: handler)
    return CIImage(cvPixelBuffer: mask)
}

func getInstanceLabel(atNormalizedPoint normalizedPoint: CGPoint, instanceMask: CVPixelBuffer) -> UInt8 {
    let point = VNImagePointForNormalizedPoint(normalizedPoint,
                                               CVPixelBufferGetWidth(instanceMask) - 1,
                                               CVPixelBufferGetHeight(instanceMask) - 1)

    CVPixelBufferLockBaseAddress(instanceMask, .readOnly)
    defer {
        CVPixelBufferUnlockBaseAddress(instanceMask, .readOnly)
    }

    let pixels = CVPixelBufferGetBaseAddress(instanceMask)!
    let bytesPerRow = CVPixelBufferGetBytesPerRow(instanceMask)
    let instanceLabel = pixels.load(fromByteOffset: Int(point.y) * bytesPerRow + Int(point.x), as: UInt8.self)
    return instanceLabel
}

instanceLabelが2のエリアを指定して出力すると、2のラベルの前景のインスタンスのみのマスク画像が作成されます

Frame 291.png

まとめ

iOS17から新しく入ったVisionの画像の前景をトリミングするAPIを使ってみました
少ないコードで導入可能で、かなり精度も良いように感じました
色々な機能に活用することができそうです

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1