【iOS ARKit】人形提取

为解决人形分离和深度估计问题,ARKit 新增加了 Segmentation Buffer(人体分隔缓冲区)和Estimated Depth Data Buffer(深度估计缓冲区)两个缓冲区。人体分隔缓冲区作用类似于图形渲染管线中的 Stencil Buffer(模板缓冲区),用于区分人形区域与背景区域,它是一个像素级的缓冲区,用于精确地描述人形区域。

人体分隔缓冲区用于标识人形区域,所以可以使用非常简单的结构,如使用1标识该像素是人形区域,而用。标识该像素为背景区。人体分隔缓冲区每帧都更新,所以可以动态地追踪摄像头采集的人形变化。

既然人体分隔缓冲区标识了人形区域,我们也就可以利用该缓冲区提取出场景中的人形以便后续应用,如将人形图像通过网络传输到其他AR设备中,实现类似虚拟会议的效果;或者将人形图像放入虚拟世界中,营造更绚酷的体验;或者对提取的人形图像进行模糊和打马赛克等处理,实现以往只能使用绿幕才能实现的实时人形捕捉效果。

为简单起见,本节我们直接获取人体分隔缓冲区数据并将其保存为图像,关键代码如代码如下所示。

Swift 复制代码
//
//  HumanExtraction.swift
//  ARKitDeamo
//
//  Created by zhaoquan du on 2024/2/4.
//

import SwiftUI
import ARKit
import RealityKit
import Combine
import VideoToolbox
import AVFoundation

struct HumanExtraction: View {
    
    var viewModel = HumanExtractionViewModel()
    
    var arView: ARView {
        let arView = ARView(frame: .zero)
        
        return arView
    }
    
    var body: some View {
        HumanExtractionContainer(viewModel: viewModel)
            .overlay(
            VStack{
                Spacer()
                Button(action:{viewModel.catchHuman()}) {
                    Text("截取人形")
                        .frame(width:120,height:40)
                        .font(.body)
                        .foregroundColor(.black)
                        .background(Color.white)
                        .opacity(0.6)
                }
                .offset(y:-30)
                .padding(.bottom, 30)
            }
    )
        .edgesIgnoringSafeArea(.all)
    }
}

struct HumanExtractionContainer : UIViewRepresentable{
   
    var viewModel: HumanExtractionViewModel
    
    
    func makeUIView(context: Context) -> some ARView {
        let arView = ARView(frame: .zero)
        
      
        
        return arView
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {
        guard ARWorldTrackingConfiguration.supportsFrameSemantics(.personSegmentation) else {
            return
        }
        
        let config = ARWorldTrackingConfiguration()
        config.frameSemantics = .personSegmentation
        uiView.session.delegate = viewModel
        uiView.session.run(config)
    }
    
    
    
}

class HumanExtractionViewModel: NSObject,ARSessionDelegate {
    var arFrame: ARFrame? = nil
    func session(_ session: ARSession, didUpdate frame: ARFrame) {
        arFrame = frame
    }
    func catchHuman(){
        if let segmentationBuffer = arFrame?.segmentationBuffer {
            
            if let uiImage = UIImage(pixelBuffer: segmentationBuffer)?.rotate(radians: .pi / 2) {
                UIImageWriteToSavedPhotosAlbum(uiImage, self, #selector(imageSaveHandler(image:didFinishSavingWithError:contextInfo:)), nil)
            }
        }
    }
    @objc func imageSaveHandler(image:UIImage,didFinishSavingWithError error:NSError?,contextInfo:AnyObject) {
        if error != nil {
            print("保存图片出错")
        } else {
            print("保存图片成功")
        }
    }
    
}



extension UIImage {
    public convenience init?(pixelBuffer:CVPixelBuffer) {
        var cgimage: CGImage?
        
        VTCreateCGImageFromCVPixelBuffer(pixelBuffer, options: nil, imageOut: &cgimage)
        
        if let cgimage = cgimage{
            
            self.init(cgImage: cgimage)
            
        }else{
            return nil
        }
    }
    
    func rotate(radians: CGFloat) -> UIImage {
        let rotatedSize = CGRect(origin: .zero, size: size).applying(CGAffineTransform(rotationAngle: CGFloat(radians))).integral.size
        UIGraphicsBeginImageContext(rotatedSize)
        if let context = UIGraphicsGetCurrentContext() {
            let origin = CGPoint(x: rotatedSize.width / 2.0, y: rotatedSize.height / 2.0)
            context.translateBy(x: origin.x, y: origin.y)
            context.rotate(by: radians)
            
            draw(in: CGRect(x: -origin.y, y: -origin.x, width: size.width, height: size.height))
            
            let rotateImage = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            
            return rotateImage ?? self
            
        }
        
        return self
    }
}

在代码 中,人体分隔缓冲区数据每帧都会更新,所以我们需要从 ARFrame 中实时获取值,然后将缓冲区中的数据转换成图像,由于缓冲区中的数据是直接对应硬件摄像头采集的图像数据,为与屏幕显示保持一致,需要对图像进行90°旋转,保存的图像如下右图所示。

进行人形提取时,只是提取屏幕空间中的人形图像,无须使用深度信息,因此无须使用personSegmentation WithDepth 语义,只使用 personSegmentation 语义有助于提高应用性能。

具体代码地址:GitHub - duzhaoquan/ARkitDemo

相关推荐
HarderCoder9 小时前
iOS 知识积累第一弹:从 struct 到 APP 生命周期的全景复盘
ios
叽哥20 小时前
Flutter Riverpod上手指南
android·flutter·ios
用户092 天前
SwiftUI Charts 函数绘图完全指南
ios·swiftui·swift
YungFan2 天前
iOS26适配指南之UIColor
ios·swift
权咚3 天前
阿权的开发经验小集
git·ios·xcode
用户093 天前
TipKit与CloudKit同步完全指南
ios·swift
法的空间3 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
2501_915918413 天前
iOS 上架全流程指南 iOS 应用发布步骤、App Store 上架流程、uni-app 打包上传 ipa 与审核实战经验分享
android·ios·小程序·uni-app·cocoa·iphone·webview
00后程序员张3 天前
iOS App 混淆与加固对比 源码混淆与ipa文件混淆的区别、iOS代码保护与应用安全场景最佳实践
android·安全·ios·小程序·uni-app·iphone·webview
Magnetic_h3 天前
【iOS】设计模式复习
笔记·学习·ios·设计模式·objective-c·cocoa