Swift Vision 物体尺寸测量:iOS矩形检测方案
2025-01-29 23:05:55
Swift Vision 实现物体尺寸测量
如何通过 Swift 和 Vision 框架,在 iOS 应用中检测矩形物体的尺寸,并提供用户友好的交互方式进行测量?这个问题涉及到计算机视觉和用户体验。通过摄像头捕获图像,识别图像中的矩形对象,然后测量其长、宽、高是实现目标的关键。本文探讨此问题,提供切实可行的方案。
基于 Vision 的矩形检测与尺寸提取
使用 Vision 框架的 VNDetectRectanglesRequest
可以在图像中检测矩形,这对于测量盒状物体尤其有用。但是,仅仅检测到矩形还不够,还需将其坐标信息转化为真实的尺寸,并将其展示给用户。以下是一个基本代码示例,解释了如何利用Vision来识别矩形。
步骤:
- 将
UIImage
转换成CGImage
,因为 Vision 框架需要使用CGImage
作为输入。 - 创建
VNDetectRectanglesRequest
。在这个过程中,可调整一些参数来优化检测效果,比如最小和最大纵横比。 - 执行请求。通过
VNImageRequestHandler
处理图像,并传入VNDetectRectanglesRequest
, 图像分析任务将会异步进行。 - 提取检测到的矩形,并将矩形的归一化坐标转换为像素坐标。Vision 框架返回的矩形坐标是 0-1 之间的归一化值。需根据图像实际尺寸换算成像素值。
- 返回表示矩形的
CGRect
。
import UIKit
import Vision
func detectBoxDimensions(in image: UIImage, completion: @escaping (CGRect?) -> Void) {
guard let cgImage = image.cgImage else {
completion(nil)
return
}
let request = VNDetectRectanglesRequest { request, error in
guard let results = request.results as? [VNRectangleObservation],
let box = results.first else {
completion(nil)
return
}
// 将归一化坐标转换成图像像素坐标
let imageWidth = CGFloat(cgImage.width)
let imageHeight = CGFloat(cgImage.height)
// 提取矩形边界框
let boundingBox = box.boundingBox
let boxFrame = CGRect(
x: boundingBox.origin.x * imageWidth,
y: (1 - boundingBox.origin.y - boundingBox.height) * imageHeight,
width: boundingBox.width * imageWidth,
height: boundingBox.height * imageHeight
)
completion(boxFrame)
}
request.minimumAspectRatio = VNAspectRatio(0.5)
request.maximumAspectRatio = VNAspectRatio(1.5)
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
DispatchQueue.global().async {
do {
try handler.perform([request])
} catch {
print("矩形检测错误: \(error)")
completion(nil)
}
}
}
此代码返回的 CGRect
仅表示检测到矩形的 2D 边界,并不能直接给出物体的长宽高。此段代码主要展示了如何使用Vision去获取矩形边框。下一步要讨论如何让使用者获得更精准的尺寸。
基于 SwiftUI 的用户交互与多维度测量
为了完成 3D 测量,仅依靠单次矩形检测是不够的。一个可行方案是引导用户使用手机摄像头围绕物体移动,捕获多帧图像。然后,借助 Vision 框架分析多帧图像中的矩形,从中提取出长宽高。这里主要考虑使用 SwiftUI 创建一个界面来捕获并显示这些数据。
步骤:
- 创建一个摄像头视图: 使用
AVFoundation
的AVCaptureSession
和AVCaptureVideoDataOutput
在 SwiftUI 中展示摄像头预览,并且从相机获取图像帧。 - 捕获帧并处理: 每当捕获到新的图像帧时,调用之前定义的矩形检测方法。
- 利用ARKit进行辅助计算:
ARKit
可以用于提供更准确的深度信息,辅助判断物体的大小和距离,但使用ARKit
会增加应用的复杂度。 对于仅仅是需要测量的应用,只使用Vision
可能足够满足。 - 尺寸展示与用户引导: 在屏幕上展示当前测量的尺寸,并引导用户围绕物体移动摄像头,从而获得三个维度的尺寸。例如可以使用辅助线,以及提示信息告诉用户移动摄像头的方向和姿势。可以使用动画平滑过度帧数之间变化的数据。
import SwiftUI
import AVFoundation
struct CameraView: UIViewControllerRepresentable {
typealias UIViewControllerType = CameraViewController
func makeUIViewController(context: Context) -> CameraViewController {
return CameraViewController()
}
func updateUIViewController(_ uiViewController: CameraViewController, context: Context) {}
}
class CameraViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {
var session: AVCaptureSession?
let previewLayer = AVCaptureVideoPreviewLayer()
override func viewDidLoad() {
super.viewDidLoad()
setupCamera()
// 使用 GCD 定时刷新帧
Timer.scheduledTimer(withTimeInterval: 0.033, repeats: true) { _ in
// 确保视频数据输出可用,并且会话是运行的
if let captureSession = self.session, captureSession.isRunning{
// 执行处理视频数据的功能
// 可以是调用请求更新 UI 或其他数据处理
}
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
session?.stopRunning()
}
private func setupCamera() {
let captureSession = AVCaptureSession()
guard let device = AVCaptureDevice.default(for: .video),
let input = try? AVCaptureDeviceInput(device: device) else {
return
}
if captureSession.canAddInput(input) {
captureSession.addInput(input)
}
let dataOutput = AVCaptureVideoDataOutput()
if captureSession.canAddOutput(dataOutput){
captureSession.addOutput(dataOutput)
dataOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue"))
} else {
return
}
previewLayer.session = captureSession
previewLayer.frame = view.layer.bounds
previewLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer)
session = captureSession
captureSession.startRunning()
}
// 实现代理方法来处理捕获的帧
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
// 此函数会在有新视频帧捕获到时被调用
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
return
}
let ciImage = CIImage(cvPixelBuffer: imageBuffer)
let context = CIContext()
// 从 CIImage 创建一个 CGImage
if let cgImage = context.createCGImage(ciImage, from: ciImage.extent) {
detectBoxDimensions(in:UIImage(cgImage:cgImage), completion:{ rect in
DispatchQueue.main.async {
if let rect = rect {
print("框体在相机预览的位置 \(rect)")
}
}
})
}
}
}
以上的Swift代码为我们提供了了一个SwiftUI页面下的相机视图,以及捕捉视频流,和使用Vision 识别边框的大体逻辑框架。在实际应用中,需处理数据噪声,并设计友好的用户界面。可以对连续的测量值做均值处理来降低误差。 可以增加提示性文案和辅助线指导用户完成测量,也可以利用 CoreMotion 的信息引导用户以特定的轨迹扫描物体,以便取得长宽高等数据。
注意事项
使用相机和 Vision 框架涉及到用户的隐私,需在应用首次使用相机权限时清晰解释用途。同时,由于Vision 框架识别的结果可能存在误差,所以在应用中展示测量数据时应加上免责声明。并且需要针对不同的拍摄角度和光照条件,进行大量的测试和优化,使应用在多种情况下均可稳定可靠运行。