Swift Vision API人像分割异常:背景图完全替换自拍图求助
问题排查与修复方案
你的代码出现背景完全覆盖自拍图的核心原因是人像分割蒙版的使用逻辑与滤镜预期相反,同时存在几处API使用不规范的问题,以下是具体问题和修复步骤:
1. 滤镜混合逻辑反转
CIFilter.blendWithRedMask的混合规则是:
输出像素 = 前景图(inputImage) × (1 - 蒙版红色通道值) + 背景图(backgroundImage) × 蒙版红色通道值
而VNGeneratePersonSegmentationRequest输出的蒙版中,白色(255)区域对应人像,黑色(0)区域对应背景。按照当前代码的写法,人像区域会被完全替换为背景图,背景区域保留原图,这和你的需求完全相反。
修复:反转蒙版
在缩放蒙版后添加反转滤镜,将人像区域变为黑色,背景区域变为白色:
let maskScaled = mask.transformed(by: CGAffineTransform(scaleX: maskScaleX, y: maskScaleY)) // 添加蒙版反转 let invertedMask = maskScaled.applyingFilter("CIColorInvert")
然后将blendFilter.maskImage设置为反转后的蒙版:
blendFilter.maskImage = invertedMask
2. 使用私有API存在风险
代码中使用了私有API__CGAffineTransformMake,这可能导致App Store审核被拒,应该替换为公开的CGAffineTransform(scaleX:y:)构造方法:
// 替换前 let maskScaled = mask.transformed(by: __CGAffineTransformMake(maskScaleX, 0, 0, maskScaleY, 0, 0)) // 替换后 let maskScaled = mask.transformed(by: CGAffineTransform(scaleX: maskScaleX, y: maskScaleY))
背景图的缩放也做同样替换。
3. 强制解包存在崩溃隐患
代码中多处使用!强制解包可选值(比如backgroundImageView.image!.cgImage!),如果图片加载失败会直接崩溃,建议添加可选绑定处理:
func applyingMask(buffer: CVPixelBuffer) { guard let inputCGImage = backgroundImageView.image?.cgImage, let backgroundImage = UIImage(named: "starfield")?.ciImage else { print("图片加载失败") return } let input = CIImage(cgImage: inputCGImage) let mask = CIImage(cvPixelBuffer: buffer) let background = backgroundImage // ... 后续逻辑 }
4. 坐标系对齐问题(可选优化)
自拍图通常是镜像的,Vision生成的蒙版是基于原图坐标系,可能和CIImage的坐标系存在翻转差异,导致蒙版和人像错位。可以添加翻转处理:
// 翻转蒙版以匹配自拍图的镜像 let flippedMask = invertedMask.transformed(by: CGAffineTransform(scaleX: 1, y: -1)).transformed(by: CGAffineTransform(translationX: 0, y: input.extent.height))
然后将blendFilter.maskImage设置为flippedMask。
修复后的完整applyingMask方法
func applyingMask(buffer: CVPixelBuffer) { guard let inputCGImage = backgroundImageView.image?.cgImage, let backgroundImage = UIImage(named: "starfield")?.ciImage else { print("图片加载失败") return } let input = CIImage(cgImage: inputCGImage) let mask = CIImage(cvPixelBuffer: buffer) let background = backgroundImage let maskScaleX = input.extent.width / mask.extent.width let maskScaleY = input.extent.height / mask.extent.height let maskScaled = mask.transformed(by: CGAffineTransform(scaleX: maskScaleX, y: maskScaleY)) // 反转蒙版 let invertedMask = maskScaled.applyingFilter("CIColorInvert") // 可选:翻转蒙版匹配自拍镜像 let flippedMask = invertedMask.transformed(by: CGAffineTransform(scaleX: 1, y: -1)).transformed(by: CGAffineTransform(translationX: 0, y: input.extent.height)) let backgroundScaleX = input.extent.width / background.extent.width let backgroundScaleY = input.extent.height / background.extent.height let backgroundScaled = background.transformed(by: CGAffineTransform(scaleX: backgroundScaleX, y: backgroundScaleY)) let blendFilter = CIFilter.blendWithRedMask() blendFilter.inputImage = input blendFilter.backgroundImage = backgroundScaled blendFilter.maskImage = flippedMask // 使用处理后的蒙版 guard let blendedImage = blendFilter.outputImage else { print("混合滤镜输出失败") return } // 将CIImage转换为UIImage时处理坐标系 let context = CIContext(options: [.useSoftwareRenderer: false]) guard let outputCGImage = context.createCGImage(blendedImage, from: blendedImage.extent) else { print("CGImage转换失败") return } backgroundImageView.image = UIImage(cgImage: outputCGImage) }
额外说明
- 确保
VNGeneratePersonSegmentationRequest的outputPixelFormat设置为kCVPixelFormatType_OneComponent8(你当前代码已经设置正确),这会生成单通道的灰度蒙版,适合后续处理。 - 使用
CIContext转换CIImage到UIImage可以避免坐标系显示异常的问题。
内容的提问来源于stack exchange,提问作者Kenny Ho
相关产品推荐
相关产品推荐

