如何从CIAreaHistogram中提取主色?

时间:2016-05-26 20:56:37

标签: ios image-processing uiimage core-image ciimage

我希望分析iOS上UIImage中最主要的颜色(大多数像素中存在的颜色),我偶然发现了Core Image基于过滤器的API,特别是CIAreaHistogram。

似乎这个过滤器可能对我有帮助,但我很难理解API。首先,它说过滤器的输出是一维图像,它是输入箱的长度和高度的一个像素。我如何阅读这些数据?我基本上想要找出具有最高频率的颜色值,所以我期望数据包含每种颜色的某种频率计数,我不清楚这个一维图像将如何表示,因为它不是真的解释我在这张1-d图像中可以预期的数据。如果它真的是直方图,为什么它不会返回表示像字典

的数据结构

其次,在API中它要求一些垃圾箱?那输入应该是什么?如果我想要一个精确的分析,输入bin参数是我的图像的颜色空间?什么使bin值更小,我想它只是通过欧几里德距离到最近的bin来近似附近的颜色。如果是这种情况将不会产生精确的直方图结果,为什么有人想要这样做?

从API角度对上述两个问题的任何输入都会对我有所帮助

3 个答案:

答案 0 :(得分:2)

CIAreaHistogram返回一个图像,其中每个像素的reg,green,blue和alpha值表示图像中该色调的频率。您可以将该图像渲染为UInt8数组以查看直方图数据。还有一个未记录的outputData值:

let filter = CIFilter(
    name: "CIAreaHistogram",
    withInputParameters: [kCIInputImageKey: image])!
let histogramData = filter.valueForKey("outputData")

但是,我发现vImage是使用直方图的更好框架。首先,您需要创建vImage图片格式:

var format = vImage_CGImageFormat(
    bitsPerComponent: 8,
    bitsPerPixel: 32,
    colorSpace: nil,
    bitmapInfo: CGBitmapInfo(
        rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue),
    version: 0,
    decode: nil,
    renderingIntent: .RenderingIntentDefault)

vImage适用于可以从CGImage而不是CIImage实例创建的图片缓冲区(您可以使用createCGImage CIContext方法创建一个。 vImageBuffer_InitWithCGImage将创建一个图像缓冲区:

var inBuffer: vImage_Buffer = vImage_Buffer()

vImageBuffer_InitWithCGImage(
    &inBuffer,
    &format,
    nil,
    imageRef,
    UInt32(kvImageNoFlags))

现在创建Uint的数组,它将保存四个通道的直方图值:

let red = [UInt](count: 256, repeatedValue: 0)
let green = [UInt](count: 256, repeatedValue: 0)
let blue = [UInt](count: 256, repeatedValue: 0)
let alpha = [UInt](count: 256, repeatedValue: 0)

let redPtr = UnsafeMutablePointer<vImagePixelCount>(red)
let greenPtr = UnsafeMutablePointer<vImagePixelCount>(green)
let bluePtr = UnsafeMutablePointer<vImagePixelCount>(blue)
let alphaPtr = UnsafeMutablePointer<vImagePixelCount>(alpha)

let rgba = [redPtr, greenPtr, bluePtr, alphaPtr]

let histogram = UnsafeMutablePointer<UnsafeMutablePointer<vImagePixelCount>>(rgba)

最后一步是执行计算,它将填充四个数组,并释放缓冲区的数据:

vImageHistogramCalculation_ARGB8888(&inBuffer, histogram, UInt32(kvImageNoFlags))

free(inBuffer.data)

快速检查不透明图像的alpha数组应该产生255个零,其最终值对应于图像中的像素数:

print(alpha) // [0, 0, 0, 0, 0 ... 409600]

直方图不会从视觉角度为您提供主色:半黄{1,1,0}和半黑{0,0,0}的图像会产生与半红的图像相同的结果{ {1}}并保持绿色{1,0,0}

希望这有帮助,

西蒙

答案 1 :(得分:2)

伊恩·奥尔曼(Ian Ollmann)为色调计算直方图的想法非常简洁,可以使用简单的颜色内核来完成。该内核返回仅仅是图像色调的单色图像(基于this original work

let shaderString = "kernel vec4 kernelFunc(__sample c)" +
"{" +
"    vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);" +
"    vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));" +
"    vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));" +

"    float d = q.x - min(q.w, q.y);" +
"    float e = 1.0e-10;" +
"    vec3 hsv = vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);" +
"    return vec4(vec3(hsv.r), 1.0);" +
"}"

let colorKernel = CIColorKernel(string: shaderString)

如果我得到蓝天图像的色调,结果直方图如下所示:

blue sky histogram

......温暖的夕阳给出了这样的直方图:

enter image description here

所以,这看起来是一种很好的技术,可以获得图像的主导色调。

西蒙

答案 2 :(得分:0)

直方图方法的一个问题是您失去了颜色通道之间的相关性。也就是说,你的一半图像可能是洋红色和半黄色。你会发现一个红色直方图都在1.0 bin中,但是蓝色和绿色的bin会在0.0和1.0之间平均分配,两者之间没有任何内容。尽管你可以非常肯定红色是明亮的,但是你无法说出蓝色和绿色成分对于&#34;主要颜色应该是什么&#34;

您可以使用带有2 **(8 + 8 + 8)个分档的3D直方图,但这个很大,您会发现信号非常稀疏。通过偶然事件,三个像素可能落在一个箱子中并且在其他地方没有两个像素,即使许多用户可能会告诉您存在主要颜色并且它与该像素无关。

您可以使3D直方图的分辨率降低很多,并且(例如)每个颜色通道只有16个分档。通过这种方式,垃圾箱更有可能具有统计上有意义的人口数量。这应该为您提供一个起点,以找到该区域中当地像素数的平均值。如果每个bin都有一个计数和一个{R,G,B}总和,那么一旦你确定了最流行的bin,你就可以快速找到该bin中像素的平均颜色。该方法仍然受到直方图网格的一些影响。您将更有可能识别网格单元格中间的颜色而不是边缘处的颜色。人口可能跨越多个网格单元。像kmeans这样的东西可能是另一种方法。

如果你只想要主色调,那么转换到HSV这样的色彩空间,然后是色调的直方图就可以了。

我不知道vImage,CI或MetalPerformanceShaders中有任何过滤器可以为您执行这些操作。你当然可以在CPU或Metal中编写代码,而不会有太多麻烦。