CoreML模型图片预测异常、视频预测正常的问题排查求助
你的问题核心确实出在CVPixelBuffer和CIImage两种输入类型的预处理差异上——虽然你设置了相同的centerCrop选项,但Vision框架对这两种图像类型的处理逻辑、颜色空间转换、像素采样方式都存在细微但关键的区别,最终导致模型接收到的输入和训练数据分布不匹配,引发预测错误。
具体原因拆解
颜色空间与像素格式不匹配
视频帧的CVPixelBuffer通常是YUV格式(比如kCVPixelFormatType_420YpCbCr8BiPlanarFullRange),这是iOS相机采集的原生格式;而UIImage转CIImage后默认是RGB格式。如果你的模型是基于YUV格式的训练数据训练的,那么RGB格式的输入会直接破坏特征分布,导致类别预测和边界框全错。图像裁剪/缩放的实际效果差异
即使都设置了VNImageCropAndScaleOption.centerCrop,Vision处理CIImage和CVPixelBuffer时的像素对齐、采样逻辑不同。比如CIImage可能会因为浮点精度问题导致裁剪后的图像尺寸与模型输入要求存在微小偏差,而模型对输入尺寸的一致性非常敏感。图像方向的隐性差异
视频场景中你硬编码了EXIFOrientation.rightTop,但单图场景用的是image.imageOrientation转换的方向。如果单图的原始方向(比如相册照片的方向)和视频帧方向不一致,会导致图像被错误旋转,模型自然无法正确检测目标。
针对性解决方案
方案1:统一输入格式为CVPixelBuffer(最直接有效)
把单图场景的UIImage转换成和视频场景一致的CVPixelBuffer,确保预处理逻辑完全对齐。
修改单图处理代码:
lazy var classificationRequest: VNCoreMLRequest = { let model = try VNCoreMLModel(for: MODEL_TF2keras_OutConv12().model) let request = VNCoreMLRequest(model: model, completionHandler: { [weak self] request, error in self?.processClassifications(for: request, error: error) }) request.imageCropAndScaleOption = VNImageCropAndScaleOption.centerCrop return request }() func updateClassifications(for image: UIImage) { // 将UIImage转为与视频帧一致的CVPixelBuffer guard let pixelBuffer = image.convertToCVPixelBuffer() else { print("Failed to convert UIImage to CVPixelBuffer") return } // 复用视频场景的方向设置,确保一致 let orientation = CGImagePropertyOrientation(rawValue: UInt32(EXIFOrientation.rightTop.rawValue))! let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, orientation: orientation) do { try handler.perform([self.classificationRequest]) } catch { print("Request failed: \(error)") } } // 扩展UIImage转换方法,确保像素格式与视频帧匹配 extension UIImage { func convertToCVPixelBuffer() -> CVPixelBuffer? { let attributes = [ kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue ] as CFDictionary var pixelBuffer: CVPixelBuffer? let status = CVPixelBufferCreate( kCFAllocatorDefault, Int(self.size.width), Int(self.size.height), kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, // 与视频帧格式一致 attributes, &pixelBuffer ) guard status == kCVReturnSuccess, let buffer = pixelBuffer else { return nil } CVPixelBufferLockBaseAddress(buffer, .readOnly) defer { CVPixelBufferUnlockBaseAddress(buffer, .readOnly) } guard let context = CGContext( data: CVPixelBufferGetBaseAddress(buffer), width: Int(self.size.width), height: Int(self.size.height), bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(buffer), space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue ) else { return nil } context.draw(self.cgImage!, in: CGRect(origin: .zero, size: self.size)) return buffer } }
方案2:验证并对齐模型输入要求
检查你的CoreML模型输入特征:
- 打开
.mlmodel文件,查看输入的Image类型要求:颜色空间是RGB还是YUV?像素格式是什么? - 确保单图场景的图像预处理完全匹配模型要求,比如如果模型期望YUV,就不要用RGB格式输入。
方案3:调试输入图像
在processClassifications方法中,把两个场景的输入图像保存下来对比:
// 在processClassifications中添加调试代码 if let pixelBuffer = request.imageBuffer { let debugImage = UIImage(ciImage: CIImage(cvPixelBuffer: pixelBuffer)) // 保存到相册或打印尺寸、格式信息 UIImageWriteToSavedPhotosAlbum(debugImage, nil, nil, nil) }
对比视频帧和单图处理后的图像,确认尺寸、方向、颜色是否一致,快速定位预处理差异。
内容的提问来源于stack exchange,提问作者ScorpionMania

