美文网首页
Vision框架详细解析(十五) —— 基于Vision的人员分

Vision框架详细解析(十五) —— 基于Vision的人员分

作者: 刀客传奇 | 来源:发表于2022-03-20 16:27 被阅读0次

版本记录

版本号 时间
V1.0 2022.03.20 星期日

前言

iOS 11+macOS 10.13+ 新出了Vision框架,提供了人脸识别、物体检测、物体跟踪等技术,它是基于Core ML的。可以说是人工智能的一部分,接下来几篇我们就详细的解析一下Vision框架。感兴趣的看下面几篇文章。
1. Vision框架详细解析(一) —— 基本概览(一)
2. Vision框架详细解析(二) —— 基于Vision的人脸识别(一)
3. Vision框架详细解析(三) —— 基于Vision的人脸识别(二)
4. Vision框架详细解析(四) —— 在iOS中使用Vision和Metal进行照片堆叠(一)
5. Vision框架详细解析(五) —— 在iOS中使用Vision和Metal进行照片堆叠(二)
6. Vision框架详细解析(六) —— 基于Vision的显著性分析(一)
7. Vision框架详细解析(七) —— 基于Vision的显著性分析(二)
8. Vision框架详细解析(八) —— 基于Vision的QR扫描(一)
9. Vision框架详细解析(九) —— 基于Vision的QR扫描(二)
10. Vision框架详细解析(十) —— 基于Vision的Body Detect和Hand Pose(一)
11. Vision框架详细解析(十一) —— 基于Vision的Body Detect和Hand Pose(二)
12. Vision框架详细解析(十二) —— 基于Vision的Face Detection新特性(一)
13. Vision框架详细解析(十三) —— 基于Vision的Face Detection新特性(二)
14. Vision框架详细解析(十四) —— 基于Vision的人员分割(一)

源码

首先看下工程组织结构

下面就是源码了

1. GreetingProcessor.swift
import UIKit
import Combine
import Vision
import CoreImage.CIFilterBuiltins

class GreetingProcessor {
  static let shared = GreetingProcessor()
  @Published var photoOutput = UIImage()
  let context = CIContext()
  let request = VNGeneratePersonSegmentationRequest()

  func generatePhotoGreeting(greeting: Greeting) {
    guard
      let backgroundImage = greeting.backgroundImage.cgImage,
      let foregroundImage = greeting.foregroundImage.cgImage else {
      print("Missing required images")
      return
    }

    // Create request
    request.qualityLevel = .accurate
    request.revision = VNGeneratePersonSegmentationRequestRevision1
    request.outputPixelFormat = kCVPixelFormatType_OneComponent8

    // Create request handler
    let requestHandler = VNImageRequestHandler(
      cgImage: foregroundImage,
      options: [:])

    do {
      // Process request
      try requestHandler.perform([request])
      guard let mask = request.results?.first else {
        print("Error generating person segmentation mask")
        return
      }

      let foreground = CIImage(cgImage: foregroundImage)
      let maskImage = CIImage(cvPixelBuffer: mask.pixelBuffer)
      let background = CIImage(cgImage: backgroundImage)
      
      guard let output = blendImages(
        background: background,
        foreground: foreground,
        mask: maskImage) else {
          print("Error blending images")
          return
      }
      // Update photoOutput
      if let photoResult = renderAsUIImage(output) {
        self.photoOutput = photoResult
      }
    } catch {
      print("Error processing person segmentation request")
    }
  }

  func blendImages(
    background: CIImage,
    foreground: CIImage,
    mask: CIImage,
    isRedMask: Bool = false
  ) -> CIImage? {
    // scale mask
    let maskScaleX = foreground.extent.width / mask.extent.width
    let maskScaleY = foreground.extent.height / mask.extent.height
    let maskScaled = mask.transformed(by: __CGAffineTransformMake(maskScaleX, 0, 0, maskScaleY, 0, 0))

    // scale background
    let backgroundScaleX = (foreground.extent.width / background.extent.width)
    let backgroundScaleY = (foreground.extent.height / background.extent.height)
    let backgroundScaled = background.transformed(
      by: __CGAffineTransformMake(backgroundScaleX, 0, 0, backgroundScaleY, 0, 0))

    let blendFilter = isRedMask ? CIFilter.blendWithRedMask() : CIFilter.blendWithMask()
    blendFilter.inputImage = foreground
    blendFilter.backgroundImage = backgroundScaled
    blendFilter.maskImage = maskScaled

    return blendFilter.outputImage
  }
  
  private func renderAsUIImage(_ image: CIImage) -> UIImage? {
    guard let cgImage = context.createCGImage(image, from: image.extent) else {
      return nil
    }
    return UIImage(cgImage: cgImage)
  }
  
  func processVideoFrame(
    foreground: CVPixelBuffer,
    background: CGImage
  ) -> CIImage? {
    // Create request handler
    let ciForeground = CIImage(cvPixelBuffer: foreground)
    let personSegmentFilter = CIFilter.personSegmentation()
    personSegmentFilter.inputImage = ciForeground
    if let mask = personSegmentFilter.outputImage {
      guard let output = blendImages(
        background: CIImage(cgImage: background),
        foreground: ciForeground,
        mask: mask,
        isRedMask: true) else {
          print("Error blending images")
          return nil
        }
      return output
    }
    return nil
  }
}
2. CameraViewController.swift
import UIKit
import AVFoundation
import MetalKit

class CameraViewController: UIViewController {
  // AV Foundation
  let captureSession = AVCaptureSession()
  let cameraView = MTKView()
  var background: UIImage?
  var cameraFrameAdded = false

  // swiftlint:disable implicitly_unwrapped_optional
  public var metalDevice: MTLDevice!
  public var metalCommandQueue: MTLCommandQueue!
  public var ciContext: CIContext!
  // swiftlint:enable implicitly_unwrapped_optional

  public var currentCIImage: CIImage? {
    didSet {
      cameraView.draw()
    }
  }

  override func viewDidLoad() {
    super.viewDidLoad()
    setupMetal()
    setupCoreImage()
    setupSession()
  }

  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    if !cameraFrameAdded {
      cameraView.frame = view.frame
      view.addSubview(cameraView)
      cameraFrameAdded.toggle()
    }
    startSession()
  }

  override func viewWillDisappear(_ animated: Bool) {
    stopSession()
    super.viewWillDisappear(animated)
  }

  func setupSession() {
    captureSession.beginConfiguration()
    guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) else {
      return
    }
    do {
      let videoInput = try AVCaptureDeviceInput(device: camera)
      if captureSession.canAddInput(videoInput) {
        captureSession.addInput(videoInput)
      }
    } catch {
      print("Error setting device input: \(error)")
      return
    }
    let output = AVCaptureVideoDataOutput()
    output.alwaysDiscardsLateVideoFrames = true
    output.setSampleBufferDelegate(self, queue: .main)
    captureSession.addOutput(output)
    output.connections.first?.videoOrientation = .portrait
    captureSession.commitConfiguration()
  }

  func setupMetal() {
    metalDevice = MTLCreateSystemDefaultDevice()
    metalCommandQueue = metalDevice.makeCommandQueue()
    cameraView.device = metalDevice
    cameraView.isPaused = true
    cameraView.enableSetNeedsDisplay = false
    cameraView.delegate = self
    cameraView.framebufferOnly = false
  }

  func setupCoreImage() {
    ciContext = CIContext(mtlDevice: metalDevice)
  }

  func startSession() {
    if !captureSession.isRunning {
      DispatchQueue.global(qos: .default).async { [weak self] in
        self?.captureSession.startRunning()
      }
    }
  }

  func stopSession() {
    if captureSession.isRunning {
      DispatchQueue.global(qos: .default).async { [weak self] in
        self?.captureSession.stopRunning()
      }
    }
  }
}

// MARK: - AVCaptureVideoDataOutputSampleBufferDelegate
extension CameraViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
  func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
    // Grab the pixelbuffer frame from the camera output
    guard
      let pixelBuffer = sampleBuffer.imageBuffer,
      let backgroundImage = self.background?.cgImage else {
      return
    }
    DispatchQueue.global().async {
      if let output = GreetingProcessor.shared.processVideoFrame(
        foreground: pixelBuffer,
        background: backgroundImage) {
        DispatchQueue.main.async {
          self.currentCIImage = output
        }
      }
    }
  }
}

// MARK: - MTKViewDelegate
extension CameraViewController: MTKViewDelegate {
  func draw(in view: MTKView) {
    guard
      let commandBuffer = metalCommandQueue.makeCommandBuffer(),
      let ciImage = currentCIImage,
      let currentDrawable = view.currentDrawable else {
      return
    }

    let drawSize = cameraView.drawableSize
    let scaleX = drawSize.width / ciImage.extent.width
    let scaleY = drawSize.height / ciImage.extent.height

    let newImage = ciImage.transformed(by: .init(scaleX: scaleX, y: scaleY))
    // render into the metal texture
    self.ciContext.render(
      newImage,
      to: currentDrawable.texture,
      commandBuffer: commandBuffer,
      bounds: newImage.extent,
      colorSpace: CGColorSpaceCreateDeviceRGB())

    // register drawable to command buffer
    commandBuffer.present(currentDrawable)
    commandBuffer.commit()
  }

  func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
    // No-op
  }
}
3. CameraView.swift
import Foundation
import SwiftUI

struct CameraView: UIViewControllerRepresentable {
  typealias UIViewControllerType = CameraViewController
  private let cameraViewController: CameraViewController

  init(videoBackgroundImage: UIImage?) {
    cameraViewController = CameraViewController()
    cameraViewController.background = videoBackgroundImage
  }


  func makeUIViewController(context: Context) -> CameraViewController {
    cameraViewController
  }

  func updateUIViewController(_ uiViewController: CameraViewController, context: Context) {
  }
}
4. ContentView.swift
import SwiftUI

struct ContentView: View {
  var body: some View {
    TabView {
      PhotoGreetingView()
        .tabItem {
          Image(systemName: "photo")
          Text("Photo Greeting")
        }
        .tag(0)
      VideoGreetingView()
        .tabItem {
          Image(systemName: "video")
          Text("Video Greeting")
        }
        .tag(1)
    }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
5. PhotoGreetingView.swift
import SwiftUI

struct PhotoGreetingView: View {
  @State private var displayPhotoGreeting = false
  var greeting: Greeting {
    Greeting(backgroundImage: GreetingContent.photoBackground, foregroundImage: GreetingContent.photoForeground)
  }

  var body: some View {
    ScrollView {
      VStack {
        Image(uiImage: greeting.backgroundImage)
          .resizable()
          .aspectRatio(contentMode: .fit)
          .overlay(OverlayContent(title: "Background"), alignment: .bottom)
        Image(uiImage: greeting.foregroundImage)
          .resizable()
          .aspectRatio(contentMode: .fit)
          .overlay(OverlayContent(title: "Family Pic"), alignment: .bottom)
        Button {
          GreetingProcessor.shared.generatePhotoGreeting(greeting: greeting)
          displayPhotoGreeting.toggle()
        } label: {
          Text("Generate Photo Greeting")
            .font(.headline)
            .foregroundColor(Color.blue)
            .padding(/*@START_MENU_TOKEN@*/.all, 10.0/*@END_MENU_TOKEN@*/)
        }
      }
    }
    .sheet(isPresented: $displayPhotoGreeting) {
      PhotoGreetingOutputView()
    }
    .padding()
  }
}

struct PhotoGreetingView_Previews: PreviewProvider {
  static var previews: some View {
    PhotoGreetingView()
  }
}

struct OverlayContent: View {
  let title: String

  var body: some View {
    Text(title)
      .font(.title)
      .fontWeight(.regular)
      .frame(maxWidth: .infinity)
      .foregroundColor(Color.black)
      .background(Color.white)
      .opacity(0.7)
  }
}

struct PhotoGreetingOutputView: View {
  var greetingProcessor = GreetingProcessor.shared

  var body: some View {
    ScrollView {
      VStack {
        Image(uiImage: greetingProcessor.photoOutput)
          .resizable()
          .aspectRatio(contentMode: .fit)
          .overlay(OverlayContent(title: "Output"), alignment: .bottom)
      }
    }
    .padding()
  }
}
6. VideoGreetingView.swift
import SwiftUI

struct VideoGreetingView: View {
  var cameraView = CameraView(videoBackgroundImage: GreetingContent.videoBackground)
  var body: some View {
    cameraView
  }
}

struct VideoGreetingView_Previews: PreviewProvider {
  static var previews: some View {
    VideoGreetingView()
  }
}
7. Greeting.swift
import UIKit

struct Greeting {
  var backgroundImage: UIImage
  var foregroundImage: UIImage
}
8. GreetingContent.swift
import UIKit

enum GreetingContent {
  // swiftlint:disable implicitly_unwrapped_optional
  static let photoBackground: UIImage! = UIImage(named: "Christmas")
  static let photoForeground: UIImage! = UIImage(named: "Family")
  static let videoBackground: UIImage! = UIImage(named: "Thanksgiving")
  // swiftlint:enable implicitly_unwrapped_optional
}
9. AppMain.swift
import SwiftUI

@main
struct AppMain: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

后记

本篇主要讲述了基于Vision的人员分割,感兴趣的给个赞或者关注~~~

相关文章

网友评论

      本文标题:Vision框架详细解析(十五) —— 基于Vision的人员分

      本文链接:https://www.haomeiwen.com/subject/xwfadrtx.html