Core Image workingColorSpace & outputColorSpace

Core Image workingColorSpace & outputColorSpace

我正在使用 Metal Core Image 着色器渲染视频帧。我的要求之一是能够从 CIImage 中选择一种特定的颜色(以及用户选择的附近范围),将该颜色保留在输出中并将其他所有颜色变成黑色和白色(颜色飞溅)。但我对适用于在各种色彩空间(包括 10 位 HDR)中拍摄的视频的正确方法感到困惑:

  1. 第一项工作是在任何给定像素位置从 CIImage 中提取颜色值。据我了解,这可以使用以下 API:

    提取
    func render(_ image: CIImage, 
      toBitmap data: UnsafeMutableRawPointer, 
      rowBytes: Int, 
      bounds: CGRect, 
      format: CIFormat, 
      colorSpace: CGColorSpace?)
    

API 说将 NULL 传递给 colorSpace 将导致输出在 ciContext outputColorSpace 中。考虑到 8 位和 10 位输入图像的可能性,目前尚不清楚如何正确使用此 API 在给定像素位置提取准确的颜色?

  1. 提取值后,下一个问题是如何将值传递给Metal Core Image shader?着色器使用依赖于 ciContext 的 workingcolorSpace 的标准化颜色范围。我是否需要使用应传递给着色器的颜色创建一维纹理,或者有更好的方法吗?

我认为您可以实现这一目标而无需担心颜色 spaces,甚至无需中间渲染步骤(这会大大加快性能)。

您可以简单地将图像裁剪为包含特定颜色的 1x1 像素正方形图像,并使图像在所有方向上几乎无限延伸。 然后您可以将该图像传递到您的下一个内核并在任何地方对其进行采样以检索颜色值(与以前相同的颜色 space)。

let pixelCoordinate: CGPoint // the coordinate of the pixel that contains the color

// crop down to a single pixel
let colorPixel = inputImage.cropped(to: CGRect(origin: pixelCoordinate, size: CGSize(width: 1, height: 1))
// make the pixel extent infinite
let colorImage = colorPixel.clampedToExtent()

// simply pass it to your kernel
myKernel.apply(..., arguments: [colorImage, ...])

在 Metal 内核代码中,您可以通过 sampler(或颜色内核中的 sample_t)简单地访问它并像这样对其进行采样:

// you can sample at any coord since the image contains the single color everywhere
float4 pickedColor = colorImage.sample(colorImage.coord());

根据您的评论,这里有另一种选择:

您可以使用上下文的工作颜色space将像素值读取为浮点数。通过使用浮点值,您可以确保输入的位深度无关紧要,并且可以正确表示扩展的颜色值。 因此,例如,BT.2020 中的 100% 红色会导致扩展的 sRGB 值 (1.2483, -0.3880, -0.1434).

要读取该值,您可以使用我们的小型辅助库 CoreImageExtensions (or check out the implementation 了解如何使用 render 获取浮点值):

let pixelColor = context.readFloat32PixelValue(from: image, at: coordinate, colorSpace: context.workingColorSpace)
// you can convert that to CIVector, which can be passed to a kernel
let vectorValue = CIVector(x: pixelColor.r, y: pixelColor.g, ...)

在您的 Metal 内核中,您可以为该颜色使用 float4 输入参数。

只要您对上下文使用相同的 workingColorSpace,就可以在以后的渲染调用中存储和使用颜色值。

要从 CIImage 读取“原始”颜色值,需要创建用于将像素渲染到位图的 CIContext,同时将 workingColorSpaceoutputColorSpace 设置为 NSNull().那么就不会有颜色space的转换,你也不用担心颜色spaces:

let context = CIContext(options: [.workingColorSpace: NSNull(), .outputColorSpace: NSNull()])

然后,在将像素渲染为位图时,指定最高精度的颜色格式 CIFormat.RGBAf 以确保您没有剪裁任何值。并使用 nil 作为 colorSpace 参数。您将获得每个像素 4 个 Float32 值,这些值可以按照第一个答案的建议传递给 CIVector 中的着色器。

但是你可以做另一件事,借用第二个答案中的裁剪和夹紧想法。

  1. 使用该答案中建议的方法创建仅包含所选颜色的无限图像。
  2. 将该图像裁剪到框架的范围
  3. 使用 CIColorAbsoluteDifference 其中一个输入是原始帧,另一个是这个均匀的彩色图像。
  4. 该过滤器的输出将使所有与所选颜色完全匹配的像素返回,而其他像素的 none 将为黑色,因为此过滤器计算颜色与仅像素之间的绝对差异完全相同的颜色将产生 (0,0,0) 输出。
  5. 将该图像传递给着色器。如果从图像中采样的颜色在其所有颜色分量中恰好为 0(忽略 alpha),则意味着您需要将输入像素原封不动地复制到输出。否则将其设置为您需要的任何设置(黑色或白色或其他)。