返回

Swift Vision 物体尺寸测量:iOS矩形检测方案

IOS

Swift Vision 实现物体尺寸测量

如何通过 Swift 和 Vision 框架,在 iOS 应用中检测矩形物体的尺寸,并提供用户友好的交互方式进行测量?这个问题涉及到计算机视觉和用户体验。通过摄像头捕获图像,识别图像中的矩形对象,然后测量其长、宽、高是实现目标的关键。本文探讨此问题,提供切实可行的方案。

基于 Vision 的矩形检测与尺寸提取

使用 Vision 框架的 VNDetectRectanglesRequest 可以在图像中检测矩形,这对于测量盒状物体尤其有用。但是,仅仅检测到矩形还不够,还需将其坐标信息转化为真实的尺寸,并将其展示给用户。以下是一个基本代码示例,解释了如何利用Vision来识别矩形。

步骤:

  1. UIImage 转换成 CGImage,因为 Vision 框架需要使用 CGImage 作为输入。
  2. 创建 VNDetectRectanglesRequest。在这个过程中,可调整一些参数来优化检测效果,比如最小和最大纵横比。
  3. 执行请求。通过 VNImageRequestHandler 处理图像,并传入 VNDetectRectanglesRequest, 图像分析任务将会异步进行。
  4. 提取检测到的矩形,并将矩形的归一化坐标转换为像素坐标。Vision 框架返回的矩形坐标是 0-1 之间的归一化值。需根据图像实际尺寸换算成像素值。
  5. 返回表示矩形的 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 创建一个界面来捕获并显示这些数据。

步骤:

  1. 创建一个摄像头视图: 使用 AVFoundationAVCaptureSessionAVCaptureVideoDataOutput 在 SwiftUI 中展示摄像头预览,并且从相机获取图像帧。
  2. 捕获帧并处理: 每当捕获到新的图像帧时,调用之前定义的矩形检测方法。
  3. 利用ARKit进行辅助计算: ARKit 可以用于提供更准确的深度信息,辅助判断物体的大小和距离,但使用 ARKit 会增加应用的复杂度。 对于仅仅是需要测量的应用,只使用 Vision 可能足够满足。
  4. 尺寸展示与用户引导: 在屏幕上展示当前测量的尺寸,并引导用户围绕物体移动摄像头,从而获得三个维度的尺寸。例如可以使用辅助线,以及提示信息告诉用户移动摄像头的方向和姿势。可以使用动画平滑过度帧数之间变化的数据。
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 框架识别的结果可能存在误差,所以在应用中展示测量数据时应加上免责声明。并且需要针对不同的拍摄角度和光照条件,进行大量的测试和优化,使应用在多种情况下均可稳定可靠运行。