如何在CIImage中找到最亮的点(也许在Metal中)?

时间:2019-06-12 21:00:05

标签: ios metal core-image metal-performance-shaders

我在Metal中创建了一个自定义CIKernel。这很有用,因为它接近实时。我避免了可能实时滞后的任何cgcontext或cicontext。我的内核实质上进行了霍夫变换,但是我似乎无法弄清楚如何从图像缓冲区读取白点。

这是kernel.metal:

#include <CoreImage/CoreImage.h>

extern "C" {
    namespace coreimage {

        float4 hough(sampler src) {

            // Math

            // More Math

            // eventually:

            if (luminance > 0.8) {
                uint2 position = src.coord()
                // Somehow add this to an array because I need to know the x,y pair
            }

            return float4(luminance, luminance, luminance, 1.0);
        }
    }
}

如果可以将这部分提取到其他内核或函数中,我很好。需要注意的是CIKernel,它的返回类型是一个float4,代表一个像素的新颜色。理想情况下,我想要一种image -> image的交易,而不是image -> array过滤器。例如。减少而不是地图。我的预感很差,这需要我渲染它并在CPU上处理它。

最终我想在我的swift函数中检索到合格坐标(每个图像可以有多个坐标)。

最终解决方案编辑:

根据答案的建议,我正在GPU上进行大量的每像素计算,并在CPU上进行一些数学运算。我设计了2个额外的内核,它们的作用类似于内置的减少内核。一个内核返回每列最高值的1像素高图像,另一个内核返回最高值的归一化y坐标的1像素高图像:

    /// Returns the maximum value in each column.
    ///
    /// - Parameter src: a sampler for the input texture
    /// - Returns: maximum value in for column
    float4 maxValueForColumn(sampler src) {

        const float2 size = float2(src.extent().z, src.extent().w);

        /// Destination pixel coordinate, normalized
        const float2 pos = src.coord();

        float maxV = 0;

        for (float y = 0; y < size.y; y++) {
            float v = src.sample(float2(pos.x, y / size.y)).x;
            if (v > maxV) {
                maxV = v;
            }
        }

        return float4(maxV, maxV, maxV, 1.0);
    }

    /// Returns the normalized coordinate of the maximum value in each column.
    ///
    /// - Parameter src: a sampler for the input texture
    /// - Returns: normalized y-coordinate of the maximum value in for column
    float4 maxCoordForColumn(sampler src) {

        const float2 size = float2(src.extent().z, src.extent().w);

        /// Destination pixel coordinate, normalized
        const float2 pos = src.coord();

        float maxV = 0;
        float maxY = 0;

        for (float y = 0; y < size.y; y++) {
            float v = src.sample(float2(pos.x, y / size.y)).x;
            if (v > maxV) {
                maxY = y / size.y;
                maxV = v;
            }
        }

        return float4(maxY, maxY, maxY, 1.0);
    }

这不会提供亮度大于0.8的每个像素,但出于我的目的,它会返回足够的值:每列中的最大值及其位置。

专业版:仅将(2 *图像宽度)字节复制到CPU而不是每个像素可节省大量时间(几毫秒)。

缺点:如果您在同一列中有两个主要的白点,您将永远不会知道。如果适合您的用例,您可能必须更改此设置并按行而不是按列进行计算。

跟进:

呈现输出中似乎存在问题。以金属返回的Float值与我迅速得到的UInt8值无关。

This unanswered question描述了问题。

编辑: This answered question提供了非常方便的金属功能。调用金属值(例如0.5 )并将其返回时,将在CPU上获得正确的值(例如128 )。

1 个答案:

答案 0 :(得分:1)

检出CICategoryReduction中的过滤器(如CIAreaAverage)。它们返回的图像只有几像素高,其中包含缩小结果。但是您仍然必须渲染它们才能读取Swift函数中的值。

使用此方法解决问题的问题是您不知道要预先返回的坐标数。但是,Core Image在调用内核时需要知道输出的扩展。您可以只是假设一个最大的静态坐标,但这听起来很乏味。

我认为您最好使用Accelerate APIs来迭代CPU上图像的像素(并行,超级高效)以找到相应的坐标。

您可以执行一种混合方法,即使用Core Image在GPU上进行每像素重运算,然后使用Accelerate在CPU上进行分析。您甚至可以使用CIImageProcessorKernel将CPU部件集成到Core Image管道中。