iOS 15 新功能:Vision 的人物分割視覺請求


本篇原文(標題:New in iOS 15: Vision Person Segmentation)刊登於作者 Medium,由 Anupam Chugh 所著,並授權翻譯及轉載。

Apple 的 Vision 框架可以為複雜的電腦視覺 (computer vision) 挑戰,提供開箱即用的解決方案。它還會在分類期間對圖像進行前處理 (pre-processing) 來提取 Core ML 請求。

在 iOS 14 中,我們有 6 個新的 Vision 功能,包括輪廓偵測 (Contour Detection)、光流 (Optical Flow)、軌跡偵測 (Trajectory Detection)、離線視頻處理、及手部與身體姿勢估測 (Hand and Body Pose Estimation)。

在今年的 WWDC 2021,Apple 引入了 2 個新的視覺請求 (Vision request):人物分割 (Person Segmentation) 和文件分割 (Document Segmentation)。

在這篇文章中,我會詳細介紹 iOS 15 推出的人物分割視覺請求。

人物分割視覺請求

語義分割 (Semantic Segmentation) 是將圖像中所有像素點進行分類的技術,常用於從背景分割出前景物件的情況。

你可能都接觸過語義分割的用例,像是自動駕駛和視頻通話中的虛擬背景。

DeepLabV3 是一種流行的機器學習模型,用於執行圖像分割 (image segmentation)。如果你想利用 Core ML,可以參考這篇教學來看看如何轉換圖像的背景。

至於在 Vision 框架中,我們可以利用新的 VNGeneratePersonSegmentationRequest 類別進行人物分割,並回傳一個幀 (frame) 中人物的分割遮罩 (segmentation mask)。

讓我們如此設置:

let request = VNGeneratePersonSegmentationRequest()
request.qualityLevel = .accurate
request.outputPixelFormat = kCVPixelFormatType_OneComponent8

我們有 3 個 Quality Level:accuratebalanced、和 fast。我會推薦你在處理影片時使用 fast

在下一個部分中,我們會看看如何在一個 SwiftUI App 中對圖像進行人物分割。然後,我們會看看如何用新的 Core Image 濾鏡來進行同樣的操作。

設置我們的 SwiftUI 視圖

以下是一個簡單的 SwiftUI 界面,在一個垂直堆疊 (Vertical Stack) 中包含了 3 個圖像:

@State var finalOutputImage : UIImage = UIImage(named: "background")!
@State var outputImage : UIImage = UIImage(named: "background")!
@State var inputImage : UIImage = UIImage(named: "sample")!
    
var body: some View {

    VStack{

        Image(uiImage: inputImage)
            .resizable()
            .aspectRatio(contentMode: .fit)

        Image(uiImage: outputImage)
            .resizable()
            .aspectRatio(contentMode: .fit)

        Image(uiImage: finalOutputImage)
            .resizable()
            .aspectRatio(contentMode: .fit)

    }.task {
        runVisionRequest()
    }
}

task 是一個新的 SwiftUI 修飾符,用於執行非同步的操作。我們在當中呼叫了 runVisionRequest 方法。

執行視覺請求

以下的方法會運行人物分割視覺請求,並回傳遮罩的匯出結果:

func runVisionRequest(){
        
    let request = VNGeneratePersonSegmentationRequest()
    request.qualityLevel = .accurate
    request.outputPixelFormat = kCVPixelFormatType_OneComponent8

    let handler = VNImageRequestHandler(cgImage: inputImage.cgImage!, options: [:])

    do {
        try handler.perform([request])

        let mask = request.results!.first!
        let maskBuffer = mask.pixelBuffer
        maskInputImage(maskBuffer)

    }catch {
        print(error)
    }
}

原始圖像和遮罩圖像應該會是這樣:

上面的分割遮罩使用了 accurate Quality Level,讓我們來看看 fastbalanced 的結果:

如果我們想添加新的背景,可以將分割遮罩傳遞給 maskInputImage 函式。

func maskInputImage(_ buffer: CVPixelBuffer){
        
        let bgImage = UIImage(named: "background")!
        
        let input = CIImage(cgImage: inputImage.cgImage!)
        let mask = CIImage(cvPixelBuffer: buffer)
        let background = CIImage(cgImage: bgImage.cgImage!)
        
        let maskScaleX = input.extent.width / mask.extent.width
        let maskScaleY = input.extent.height / mask.extent.height
        
        let maskScaled =  mask.transformed(by: __CGAffineTransformMake(maskScaleX, 0, 0, maskScaleY, 0, 0))
        
        let backgroundScaleX = input.extent.width / background.extent.width
        let backgroundScaleY = input.extent.height / background.extent.height
        
        let backgroundScaled =  background.transformed(by: __CGAffineTransformMake(backgroundScaleX, 0, 0, backgroundScaleY, 0, 0))
        
        
        let blendFilter = CIFilter.blendWithMask()
        
        blendFilter.inputImage = input
        blendFilter.backgroundImage = backgroundScaled
        blendFilter.maskImage = maskScaled

        if let blendedImage = blendFilter.outputImage{
        
            let ciContext = CIContext(options: nil)
            let filteredImageRef = ciContext.createCGImage(blendedImage, from: blendedImage.extent)
            let maskDisplayRef = ciContext.createCGImage(maskScaled, from: maskScaled.extent)
            
            self.outputImage = UIImage(cgImage: maskDisplayRef!)
            self.finalOutputImage = UIImage(cgImage: filteredImageRef!)
            
        }
}

這個函式使用一個 CoreImage 的融合濾鏡 (blend filter),來在原始圖像上裁剪出遮罩圖像,然後將遮罩圖像與新的背景圖像融合。

它會回傳以下結果:

執行 Core Image 濾鏡

Core Image 是 Apple 的圖像處理框架,常用於簡單的電腦視覺任務。

在 iOS 15 中,Core Image 有一個新的濾鏡:

CIFilter.personSegmentation()

我們可以在前文的 SwiftUI 視圖中應用這個濾鏡:

func runCoreImage(){
  let filter = CIFilter.personSegmentation()
  let input = CIImage(cgImage: inputImage.cgImage!)
  filter.inputImage = CIImage(cgImage: inputImage.cgImage!)

  if let maskImage = filter.outputImage{

      let ciContext = CIContext(options: nil)

      let maskScaleX = input.extent.width / maskImage.extent.width
      let maskScaleY = input.extent.height / maskImage.extent.height

      let maskScaled =  maskImage.transformed(by: __CGAffineTransformMake(maskScaleX, 0, 0, maskScaleY, 0, 0))

      let maskRef = ciContext.createCGImage(maskScaled, from: maskScaled.extent)
      self.outputImage = UIImage(cgImage: maskRef!)

  }
}

以下是模擬器上的結果:

如果要將以上的分割融合到新背景中,我們就需要將紅色轉換為白色,融合濾鏡才可以如常操作。

總結

這篇文章總結了 Vision 新的人物分割請求。如果在實時影片播放器上執行,效果一定會更有趣。

你可以在 GitHub 程式庫上下載完整的程式碼。

我們還可以運行一連串的 Vision 請求。例如,我們可以結合上述人物分割與面部姿勢請求,來構建一個 AI App。Philipp Gehrke 就在這篇文章說明了如何實作身體姿勢估測,我們可以以此為起點,來集成上述人物分割請求,並更準確地偵測人體姿勢。

這篇文章到此為止,謝謝你的閱讀。

參考資料

本篇原文(標題:New in iOS 15: Vision Person Segmentation)刊登於作者 Medium,由 Anupam Chugh 所著,並授權翻譯及轉載。

作者簡介:Anupam Chugh,深入探索 ML 及 AR 的 iOS Developer。喜愛撰寫關於想法、科技、與程式碼的文章。歡迎到我的 Blog 閱讀更多文章,或在 LinkedIn 上關注我。

譯者簡介:Kelly Chan-AppCoda 編輯小姐。


此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。

blog comments powered by Disqus
Shares
Share This