You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SwiftUI中实现相机测箱体三维尺寸及Vision矩形检测

在SwiftUI中实现箱体尺寸测量功能

一、SwiftUI中封装相机预览界面

SwiftUI没有原生相机组件,我们可以通过UIViewRepresentable封装AVFoundation的相机预览,实现实时取景和拍照功能:

import SwiftUI
import AVFoundation

struct CameraView: UIViewRepresentable {
    @Binding var capturedImage: UIImage?
    @Binding var isCameraAuthorized: Bool
    
    class Coordinator: NSObject, AVCapturePhotoCaptureDelegate {
        let parent: CameraView
        
        init(parent: CameraView) {
            self.parent = parent
        }
        
        func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
            guard let imageData = photo.fileDataRepresentation(), let image = UIImage(data: imageData) else { return }
            parent.capturedImage = image
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
    
    func makeUIView(context: Context) -> UIView {
        let view = UIView(frame: UIScreen.main.bounds)
        let captureSession = AVCaptureSession()
        captureSession.sessionPreset = .photo
        
        guard let backCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
              let input = try? AVCaptureDeviceInput(device: backCamera) else {
            isCameraAuthorized = false
            return view
        }
        
        if captureSession.canAddInput(input) {
            captureSession.addInput(input)
        }
        
        let photoOutput = AVCapturePhotoOutput()
        if captureSession.canAddOutput(photoOutput) {
            captureSession.addOutput(photoOutput)
            photoOutput.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])], completionHandler: nil)
        }
        
        let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        previewLayer.frame = view.bounds
        previewLayer.videoGravity = .resizeAspectFill
        view.layer.addSublayer(previewLayer)
        
        captureSession.startRunning()
        isCameraAuthorized = true
        photoOutput.setDelegate(context.coordinator, queue: DispatchQueue.main)
        
        return view
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {}
    
    // 暴露拍照触发方法
    func takePhoto(in session: AVCaptureSession) {
        guard let photoOutput = session.outputs.first as? AVCapturePhotoOutput else { return }
        let settings = AVCapturePhotoSettings()
        photoOutput.capturePhoto(with: settings, delegate: context.coordinator)
    }
}

二、修正Vision框架的矩形检测代码

你提供的代码存在两个核心问题:一是用boundingBox只能得到矩形的包围盒,无法反映箱体表面的实际尺寸;二是坐标转换未考虑图像方向。以下是修正后的代码,会提取矩形的四个顶点并计算真实边长:

import UIKit
import Vision

func detectBoxDimensions(in image: UIImage, completion: @escaping (CGFloat?, CGFloat?) -> Void) {
    guard let cgImage = image.cgImage else {
        completion(nil, nil)
        return
    }
    
    let request = VNDetectRectanglesRequest { request, error in
        guard let results = request.results as? [VNRectangleObservation],
              let box = results.first else {
            completion(nil, nil)
            return
        }
        
        // 将Vision的归一化坐标转换为图像实际坐标
        let imageSize = CGSize(width: CGFloat(cgImage.width), height: CGFloat(cgImage.height))
        let transform = CGAffineTransform(scaleX: imageSize.width, y: imageSize.height)
            .scaledBy(x: 1, y: -1)
            .translatedBy(x: 0, y: -imageSize.height)
        
        // 获取矩形四个顶点的实际坐标
        let topLeft = box.topLeft.applying(transform)
        let topRight = box.topRight.applying(transform)
        let bottomLeft = box.bottomLeft.applying(transform)
        
        // 计算宽和高的像素长度
        let width = hypot(topRight.x - topLeft.x, topRight.y - topLeft.y)
        let height = hypot(bottomLeft.x - topLeft.x, bottomLeft.y - topLeft.y)
        
        completion(width, height)
    }
    
    // 设置矩形检测参数,适配箱体的长宽比范围
    request.minimumAspectRatio = VNAspectRatio(0.3)
    request.maximumAspectRatio = VNAspectRatio(3.0)
    request.minimumConfidence = 0.8
    request.maximumObservations = 1
    
    let handler = VNImageRequestHandler(cgImage: cgImage, orientation: .init(image.imageOrientation), options: [:])
    
    DispatchQueue.global().async {
        do {
            try handler.perform([request])
        } catch {
            print("矩形检测失败: \(error)")
            completion(nil, nil)
        }
    }
}

三、实现长宽高三个维度的测量

单张照片只能获取平面的两个维度,要得到箱体的完整三维尺寸,推荐两种方案:

方案1:多面拍照测量

  • 引导用户依次拍摄箱体的正面、侧面、顶面三个相邻面
  • 对每张照片用Vision检测矩形,获取对应的两组边长
  • 通过匹配公共边(比如正面的高度=侧面的高度),整合得到长、宽、高三个维度

方案2:ARKit 3D测量(推荐,支持LiDAR的设备精度更高)

如果用户设备支持LiDAR,可以用ARKit直接在3D空间中测量箱体的三个维度,无需多步拍照:

import ARKit

class ARMeasurementController: UIViewController, ARSessionDelegate {
    let session = ARSession()
    let sceneView = ARSCNView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        sceneView.session = session
        sceneView.delegate = self
        
        let config = ARWorldTrackingConfiguration()
        config.planeDetection = .horizontal
        session.run(config)
    }
    
    func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
        guard let planeAnchor = anchors.first as? ARPlaneAnchor else { return }
        // 提取平面的宽度和高度(单位:米)
        let width = planeAnchor.extent.x
        let height = planeAnchor.extent.z
        // 结合其他平面数据计算第三个维度
    }
}

四、SwiftUI主视图整合示例

将相机预览、拍照、尺寸检测整合到SwiftUI界面:

struct MeasurementView: View {
    @State private var capturedImage: UIImage?
    @State private var isCameraAuthorized = false
    @State private var currentDimensions: (width: CGFloat?, height: CGFloat?) = (nil, nil)
    @State private var measuredValues: [CGFloat] = []
    
    var body: some View {
        VStack {
            if isCameraAuthorized {
                CameraView(capturedImage: $capturedImage, isCameraAuthorized: $isCameraAuthorized)
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                
                Button("拍摄当前面") {
                    guard let image = capturedImage else { return }
                    detectBoxDimensions(in: image) { width, height in
                        DispatchQueue.main.async {
                            self.currentDimensions = (width, height)
                            if let w = width, let h = height {
                                measuredValues.append(contentsOf: [w, h])
                            }
                        }
                    }
                }
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(8)
            } else {
                Text("请授予相机权限")
                    .onAppear {
                        AVCaptureDevice.requestAccess(for: .video) { granted in
                            DispatchQueue.main.async {
                                self.isCameraAuthorized = granted
                            }
                        }
                    }
            }
            
            if let width = currentDimensions.width, let height = currentDimensions.height {
                Text("当前面尺寸:宽\(String(format: "%.2f", width))px,高\(String(format: "%.2f", height))px")
                    .padding()
            }
            
            if measuredValues.count >= 3 {
                Text("箱体三维尺寸:长\(String(format: "%.2f", measuredValues[0]))px,宽\(String(format: "%.2f", measuredValues[1]))px,高\(String(format: "%.2f", measuredValues[2]))px")
                    .padding()
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者Ahmed Zaidan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 01:54:55