如何将CIFaceFeature坐标转换为适配全屏视频的CGRect?
解决CIFaceFeature坐标与视图坐标系不匹配的问题
我完全懂你现在的头疼——在全屏视频流里识别人脸后,叠加的矩形总是歪到离谱,核心问题其实是CIFaceFeature的坐标系和UIKit视图坐标系的差异,加上视频流本身的方向、分辨率适配细节没处理到位。我帮你拆解问题,再给出修正后的代码:
问题根源拆解
- 坐标系方向相反:CIImage的原点在左下角,而UIKit视图的原点在左上角,y轴方向完全颠倒;
- 视频方向未同步:摄像头采集的视频方向(比如竖屏拍摄)和屏幕显示方向可能不一致,CIDetector需要明确这个方向才能返回正确的人脸位置;
- 分辨率适配逻辑有误:视频帧的分辨率和屏幕尺寸通常不匹配,直接缩放会导致位置偏移。
修正后的代码实现
我把你的代码重新调整了,重点补上了视频方向的处理,以及更精准的坐标转换逻辑:
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } // 1. 把视频方向转换成CIDetector能识别的参数 let videoOrientation = connection.videoOrientation let detectorOrientation: Int32 switch videoOrientation { case .portrait: detectorOrientation = 6 // 对应kCGImagePropertyOrientationRight case .portraitUpsideDown: detectorOrientation = 8 // 对应kCGImagePropertyOrientationLeft case .landscapeLeft: detectorOrientation = 3 // 对应kCGImagePropertyOrientationDown case .landscapeRight: detectorOrientation = 1 // 对应kCGImagePropertyOrientationUp @unknown default: detectorOrientation = 1 } // 2. 初始化CIImage(建议把faceDetector提到类初始化里,不要每次回调都创建) let pixelBuffer = imageBuffer let sourceImage = CIImage(cvPixelBuffer: pixelBuffer) let options: [String: Any] = [ CIDetectorAccuracy: CIDetectorAccuracyHigh, CIDetectorImageOrientation: detectorOrientation ] guard let faces = faceDetector?.features(in: sourceImage, options: options) as? [CIFaceFeature] else { return } // 3. 获取视频帧尺寸和视图全屏尺寸 let videoSize = CGSize(width: CVPixelBufferGetWidth(pixelBuffer), height: CVPixelBufferGetHeight(pixelBuffer)) let imageViewSize = self.view.bounds.size for feature in faces { var faceBounds = feature.bounds // 第一步:把CIImage坐标系(左下原点)转成UIKit坐标系(左上原点) let flipTransform = CGAffineTransform(scaleX: 1, y: -1) .translatedBy(x: 0, y: -videoSize.height) faceBounds = faceBounds.applying(flipTransform) // 第二步:根据视频方向交换宽高坐标(横屏时视频宽高和视图宽高是互换的) switch videoOrientation { case .landscapeLeft, .landscapeRight: faceBounds = CGRect(x: faceBounds.origin.y, y: faceBounds.origin.x, width: faceBounds.height, height: faceBounds.width) default: break } // 第三步:计算等比缩放比例,适配视图尺寸 let scale = min(imageViewSize.width / videoSize.width, imageViewSize.height / videoSize.height) // 第四步:计算偏移量,让视频居中显示,人脸坐标同步偏移 let dx = (imageViewSize.width - videoSize.width * scale) / 2 let dy = (imageViewSize.height - videoSize.height * scale) / 2 // 最终得到和视图匹配的人脸矩形 let realFaceRect = CGRect( x: faceBounds.origin.x * scale + dx, y: faceBounds.origin.y * scale + dy, width: faceBounds.width * scale, height: faceBounds.height * scale ) // 注意:UI操作必须切到主线程 DispatchQueue.main.async { self.updateFaceOverlay(rect: realFaceRect) } } } // 示例:更新人脸叠加层的方法 func updateFaceOverlay(rect: CGRect) { // 先移除之前的叠加层(避免重复绘制) self.view.layer.sublayers?.filter { $0.name == "faceOverlay" }.forEach { $0.removeFromSuperlayer() } let faceLayer = CAShapeLayer() faceLayer.name = "faceOverlay" faceLayer.frame = rect faceLayer.borderColor = UIColor.red.cgColor faceLayer.borderWidth = 2.0 self.view.layer.addSublayer(faceLayer) }
额外提醒
- 人脸检测器复用:不要在
captureOutput回调里重复创建CIDetector,建议在类的init方法里初始化一次,能大幅提升性能; - 视频方向同步:记得在屏幕旋转时,同步更新
AVCaptureConnection的videoOrientation,否则横屏/竖屏切换后人脸位置会出错; - 主线程操作:所有UI相关的绘制必须在主线程执行,否则会出现崩溃或界面不更新的问题。
这样调整后,应该就能得到和人脸位置完全匹配的叠加矩形了!
内容的提问来源于stack exchange,提问作者Anters Bear
相关产品推荐
相关产品推荐

