如何用内置CIFilter或MPS着色器替代自定义Metal CIKernel实现红色边缘绘制?
问题描述
我实现了一个自定义Metal CIKernel,通过卷积计算Sobel梯度,将图像边缘以红色显示,其余区域保留原图像颜色。我认为可通过内置CIFilter或MPS着色器实现相同效果,但具体该如何操作?
自定义Kernel代码:
extern "C" half4 drawEdges (coreimage::sampler inputImage, coreimage::destination dest) { float2 destCoord = dest.coord(); float2 srcCoord = inputImage.coord(); half4 color = half4(inputImage.sample(srcCoord)); float2 m11Coord = inputImage.transform(destCoord + float2(-1.0,+1.0)); half3 m11 = half3(inputImage.sample(m11Coord).rgb); float2 m12Coord = inputImage.transform(destCoord + float2(0.0,+1.0)); half3 m12 = half3(inputImage.sample(m12Coord).rgb); float2 m13Coord = inputImage.transform(destCoord + float2(+1.0,+1.0)); half3 m13 = half3(inputImage.sample(m13Coord).rgb); float2 m21Coord = inputImage.transform(destCoord + float2(-1.0,0.0)); half3 m21 = half3(inputImage.sample(m21Coord).rgb); float2 m23Coord = inputImage.transform(destCoord + float2(+1.0,0.0)); half3 m23 = half3(inputImage.sample(m23Coord).rgb); float2 m31Coord = inputImage.transform(destCoord + float2(-1.0,-1.0)); half3 m31 = half3(inputImage.sample(m31Coord).rgb); float2 m32Coord = inputImage.transform(destCoord + float2(0.0,-1.0)); half3 m32 = half3(inputImage.sample(m32Coord).rgb); float2 m33Coord = inputImage.transform(destCoord + float2(+1.0,+1.0)); half3 m33 = half3(inputImage.sample(m33Coord).rgb); half3 m31m13 = m31 - m13; half3 m11m33 = m33 - m11; half3 m32m12 = m32 - m12; half3 m21m23 = m21 - m23; half3 H = m32m12 + m32m12 + m11m33 + m31m13; half3 V = m21m23 + m21m23 - m11m33 + m31m13; half3 sobel = sqrt(H*H+V*V); half sobelLength = length(sobel); if (sobelLength > 0.9) { half4 redColor = half4(0.5h, 0.h, 0.0, 1.0); color = redColor; } return half4(color); }
解决方案
一、使用内置CIFilter实现
Core Image提供了CISobelEdgeDetection滤镜直接计算Sobel边缘,结合蒙版混合即可实现需求:
步骤说明
- 生成Sobel边缘蒙版:用
CISobelEdgeDetection处理原图,调整intensity参数匹配自定义Kernel的阈值逻辑(对应sobelLength > 0.9)。 - 创建红色图层:生成与原图尺寸一致的纯红色图像。
- 蒙版混合:用
CIBlendWithMask将红色图层与原图混合,边缘区域显示红色,其余保留原图。
Swift代码示例
import CoreImage import CoreImage.CIFilterBuiltins func applyRedEdgesWithCIFilter(inputImage: CIImage) -> CIImage? { // 1. 生成Sobel边缘蒙版 let sobelFilter = CIFilter.sobelEdgeDetection() sobelFilter.inputImage = inputImage sobelFilter.intensity = 0.9 // 匹配自定义Kernel的阈值,可调整 // 2. 创建纯红色图像 let redColor = CIColor(red: 0.5, green: 0, blue: 0, alpha: 1.0) let redGenerator = CIFilter.colorGenerator() redGenerator.color = redColor guard let redImage = redGenerator.outputImage?.cropped(to: inputImage.extent) else { return nil } // 3. 用边缘蒙版混合红色和原图 let blendFilter = CIFilter.blendWithMask() blendFilter.inputImage = inputImage blendFilter.backgroundImage = redImage blendFilter.maskImage = sobelFilter.outputImage return blendFilter.outputImage }
二、使用MPS着色器实现
Metal Performance Shaders(MPS)的MPSImageSobel可高效计算Sobel梯度,搭配其他MPS滤镜完成红色边缘替换:
步骤说明
- 计算Sobel梯度幅值:用
MPSImageSobel处理原图,得到单通道梯度幅值图。 - 生成阈值蒙版:用
MPSImageThresholdBinary将梯度超过阈值的区域设为白色,其余为黑色。 - 混合红色与原图:用
MPSImageBlendWithMask通过蒙版混合红色图层和原图。
Swift代码示例
import MetalPerformanceShaders import Metal func applyRedEdgesWithMPS(inputImage: CIImage, device: MTLDevice) -> CIImage? { // 转换CIImage为MTLTexture let context = CIContext(mtlDevice: device) guard let inputTexture = context.createTexture(from: inputImage, options: [.colorSpace: inputImage.colorSpace ?? CGColorSpaceCreateDeviceRGB()]) else { return nil } // 1. 创建MPSImageSobel计算梯度幅值 let sobel = MPSImageSobel(device: device) let gradientTexture = device.makeTexture(descriptor: inputTexture.descriptor)! sobel.encode(commandBuffer: nil, sourceTexture: inputTexture, destinationTexture: gradientTexture) // 2. 生成阈值蒙版:梯度>0.9的区域设为白色,其余黑色 let threshold = MPSImageThresholdBinary(device: device, thresholdValue: 0.9, maximumValue: 1.0, linearGrayColorTransform: nil) let maskTexture = device.makeTexture(descriptor: inputTexture.descriptor)! threshold.encode(commandBuffer: nil, sourceTexture: gradientTexture, destinationTexture: maskTexture) // 3. 创建纯红色纹理 let redDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba16Float, width: inputTexture.width, height: inputTexture.height, mipmapped: false) let redTexture = device.makeTexture(descriptor: redDescriptor)! let redColor = SIMD4<Float>(0.5, 0, 0, 1.0) redTexture.fill(with: redColor) // 4. 用蒙版混合红色和原图 let blend = MPSImageBlendWithMask(device: device, blendKernel: .normal) let outputTexture = device.makeTexture(descriptor: inputTexture.descriptor)! blend.encode(commandBuffer: nil, sourceTexture: inputTexture, destinationTexture: redTexture, maskTexture: maskTexture, resultTexture: outputTexture) // 转换回CIImage return CIImage(mtlTexture: outputTexture, options: [.colorSpace: inputImage.colorSpace]) } // 扩展:填充纹理颜色 extension MTLTexture { func fill(with color: SIMD4<Float>) { let bytes = [color] replace(region: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0, withBytes: bytes, bytesPerRow: MemoryLayout<SIMD4<Float>>.stride * width) } }
注意事项
- 内置CIFilter方式更简洁,无需处理Metal设备和纹理,适合快速开发;
- MPS方式性能更高,适合实时处理或大尺寸图像场景;
- 调整阈值参数(CIFilter的
intensity、MPS的thresholdValue)可匹配自定义Kernel的边缘检测灵敏度。
内容的提问来源于stack exchange,提问作者Deepak Sharma
相关产品推荐
相关产品推荐

