3

CIImage我们可以计算/中存在多少透明像素的最快方法是什么UIImage

例如:

在此处输入图像描述

如果我们谈论效率,我的第一个想法是使用Metal Kernelusing any CIColorKernelor so,但我不明白如何使用它来输出“count”。

还有我想到的其他想法:

  1. 使用某种平均颜色来计算它,“越红”越填充像素?也许某种线性计算取决于图像大小(使用CIAreaAverage CIFilter
  2. 一个一个地计算像素并检查RGB值?
  3. 使用 Metal 并行功能,类似于这篇文章:Counting coloured pixels on the GPU - Theory ?
  4. 缩小图像然后计数?或者上面建议的所有其他过程是否只是按比例缩放而不是版本,并且它的倍数取决于计算后的缩小比例?

实现此计数的最快方法是什么?

4

3 回答 3

4

要回答你的问题如何做金属,你会使用device atomic_int.

本质上,您创建一个 IntMTLBuffer并将其传递给您的内核并使用atomic_fetch_add_explicit.

创建缓冲区一次:

var bristleCounter = 0
counterBuffer = device.makeBuffer(bytes: &bristleCounter, length: MemoryLayout<Int>.size, options: [.storageModeShared])

将计数器重置为 0 并绑定计数器缓冲区:

var z = 0
counterBuffer.contents().copyMemory(from: &z, byteCount: MemoryLayout<Int>.size)
kernelEncoder.setBuffer(counterBuffer, offset: 0, index: 0)

核心:

kernel void myKernel (device atomic_int *counter [[buffer(0)]]) {}

内核中的增量计数器(并获取值):

int newCounterValue = atomic_fetch_add_explicit(counter, 1, memory_order_relaxed);

获取 CPU 端的计数器:

kernelEncoder.endEncoding()
kernelBuffer.commit()
kernelBuffer.waitUntilCompleted()
    
//Counter from kernel now in counterBuffer
let bufPointer = counterBuffer.contents().load(as: Int.self)
print("Counter: \(bufPointer)")
于 2021-06-22T02:22:33.497 回答
3

您要执行的是归约操作,由于其大规模并行性质,它不一定非常适合 GPU。我建议不要自己为 GPU 编写缩减操作,而是使用 Apple 提供的一些高度优化的内置 API(如CIAreaAverage或相应的 Metal Performance Shaders)。

最有效的方法在一定程度上取决于您的用例,特别是图像的来源(通过UIImage/CGImage或 Core Image 管道的结果加载?)以及您需要结果计数的位置(在 CPU/Swift 端或作为另一个核心图像过滤器的输入?)。
它还取决于像素是否也可以是半透明的(alpha not0.01.0)。

如果图像在 GPU 上和/或应该在 GPU 上使用计数,我建议使用CIAreaAverage. 结果的 alpha 值应反映透明像素的百分比。请注意,这仅适用于现在有半透明像素的情况。

下一个最佳解决方案可能只是在 CPU 上迭代像素数据。它可能是几百万像素,但操作本身非常快,所以这几乎不需要时间。您甚至可以通过将图像分成块并使用concurrentPerform(...).DispatchQueue

最后一个,但可能是过大的解决方案是使用 Accelerate(这会让@FlexMonkey 高兴):将图像的像素数据加载到 vDSP 缓冲区中,并使用sumoraverage方法使用 CPU 的矢量单位计算百分比。

澄清

当我说归约操作“不一定非常适合 GPU”时,我的意思是说以一种有效的方式实现它相当复杂,而且远不如顺序算法那么简单。

检查一个像素是否透明可以并行进行,当然,但需要将结果收集到一个中,这需要多个 GPU 内核在同一内存中读取和写入值。这通常需要一些同步(从而阻碍并行执行)并由于访问共享或全局内存空间而产生延迟成本。这就是为什么 GPU 的高效收集算法通常遵循基于多步树的方法。我强烈推荐阅读 NVIDIA 关于该主题的出版物(例如此处此处)。这也是我建议尽可能使用内置 API 的原因,因为 Apple 的 Metal 团队知道如何为他们的硬件最好地优化这些算法。

Apple 的Metal Shading Language Specification (pp. 158) 中还有一个示例缩减实现,它使用simd_shuffle内部函数在树中有效地传递中间值。不过,一般原则与上面链接的 NVIDIA 出版物所述相同。

于 2021-06-20T12:33:33.903 回答
0

如果图像包含半透明像素,则可以轻松地对其进行预处理,以使 alpha 低于某个阈值的所有像素完全透明,否则完全不透明。然后CIAreaAverage可以应用,如问题中最初建议的那样,最后可以通过将结果的 alpha 分量乘以图像大小来计算完全不透明像素的近似数量。

对于预处理,我们可以使用一个简单的 CIColorKernel,如下所示:

half4 clampAlpha(coreimage::sample_t color) {
    half4 out = half4(color);
    out.a = step(half(0.99), out.a);
    return  out;
}

(选择您喜欢的任何阈值而不是 0.99)

要从输出中获取 alpha 分量,CIAreaAverage我们可以执行以下操作:

        let context = CIContext(options: [.workingColorSpace: NSNull(), .outputColorSpace: NSNull()])
        var color: [Float] = [0, 0, 0, 0]
        context.render(output,
                       toBitmap: &color,
                       rowBytes: MemoryLayout<Float>.size * 4,
                       bounds: CGRect(origin: .zero, size: CGSize(width: 1, height: 1)),
                       format: .RGBAf,
                       colorSpace: nil)

// color[3] contains alpha component of the result

使用这种方法,一切都在 GPU 上完成,同时利用其固有的并行性。

顺便说一句,请查看此应用程序https://apps.apple.com/us/app/filter-magic/id1594986951。它可以让你使用每一个 CoreImage 过滤器。

于 2021-12-24T15:23:30.340 回答