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

如何用内置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边缘,结合蒙版混合即可实现需求:

步骤说明

  1. 生成Sobel边缘蒙版:用CISobelEdgeDetection处理原图,调整intensity参数匹配自定义Kernel的阈值逻辑(对应sobelLength > 0.9)。
  2. 创建红色图层:生成与原图尺寸一致的纯红色图像。
  3. 蒙版混合:用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滤镜完成红色边缘替换:

步骤说明

  1. 计算Sobel梯度幅值:用MPSImageSobel处理原图,得到单通道梯度幅值图。
  2. 生成阈值蒙版:用MPSImageThresholdBinary将梯度超过阈值的区域设为白色,其余为黑色。
  3. 混合红色与原图:用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 23:50:59