如何从CIAreaHistogram中提取主色?

我正在寻找分析iOS上的UIImage(最像素的颜色)最主要的颜色,我偶然发现了核心图像的基于filter的API,特别是CIAreaHistogram。

似乎这个filter可能会帮助我,但我正在努力了解API。 首先它说filter的输出是一个一维图像,它是您的input框的长度和一个像素的高度。 我如何读取这些数据? 我基本上想要找出频率最高的颜色值,所以我期望数据包含每种颜色的某种频率计数,我不清楚这个一维图像是如何表示的,因为它不是真的解释我在这个一维图像内可以预期的数据。 如果它真的是一个直方图,为什么它不会返回一个像字典一样的数据结构

其次,在API中要求多个分档? 这个input应该是什么? 如果我想要一个精确的分析,inputbin参数是我的图像的色彩空间? 什么使仓值更小,我会想象它通过欧几里德距离近似的颜色接近最近的bin。 如果情况是这样,那么不会产生精确的直方图结果,为什么有人想要这样做?

从API的angular度对上述两个问题的任何意见都会对我有很大的帮助

CIAreaHistogram返回一个图像,其中每个像素的reg,绿色,蓝色和alpha值指示图像中该色调的频率。 您可以将该图像渲染到UInt8数组以查看直方图数据。 还有一个没有logging的outputData值:

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

不过,我发现vImage是使用直方图的更好的框架。 首先,您需要创build一个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实例创build的图像缓冲区vImage工作(可以使用createCGImage方法创build一个图像缓冲区, vImageBuffer_InitWithCGImage将创build一个图像缓冲区:

 var inBuffer: vImage_Buffer = vImage_Buffer() vImageBuffer_InitWithCGImage( &inBuffer, &format, nil, imageRef, UInt32(kvImageNoFlags)) 

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

 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] 

从视觉的angular度来看,直方图不会给你主导的色彩:半黄色的图像{1,1,0}和半黑色的{0,0,0}会得到与半红色的图像相同的结果{1,0,0}并保持绿色{0,1,0}

希望这可以帮助,

西蒙

伊恩·奥尔曼(Ian Ollmann)为色调计算直方图的想法非常简洁,可以用一个简单的颜色核心来完成。 这个内核返回一幅图像色调的单色图像(基于这个原始的工作 )

 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(cb, cg));" + " vec4 q = mix(vec4(p.xyw, cr), vec4(cr, p.yzx), step(px, cr));" + " float d = qx - min(qw, qy);" + " float e = 1.0e-10;" + " vec3 hsv = vec3(abs(qz + (qw - qy) / (6.0 * d + e)), d / (qx + e), qx);" + " return vec4(vec3(hsv.r), 1.0);" + "}" let colorKernel = CIColorKernel(string: shaderString) 

如果我得到蓝天的图像的色调,结果直方图看起来像这样:

蓝天直方图

而温暖的夕阳给出了这样一个直方图:

在这里输入图像说明

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

西蒙

直方图方法的一个问题是你失去了颜色通道之间的相关性。 也就是说,一半的图像可能是洋红色,一半是黄色的。 你会发现一个红色的直方图都在1.0 bin,但是蓝色和绿色的bin会在0.0和1.0之间平均分配,没有任何内容。 尽pipe你可以确定红色是明亮的,但是对于“主色”,蓝色和绿色成分应该是什么,

你可以使用2 **(8 + 8 + 8)箱子的3D直方图,但这是相当大的,你会发现信号是相当稀疏的。 偶然的情况下,三个像素可能会落在一个bin中,而其他地方没有两个像素,尽pipe许多用户可能会告诉您存在一个主要的颜色,并且与该像素无关。

您可以使3D直方图的分辨率降低很多,并且(例如)每个颜色通道只有16个分档。 通过这种方式,箱子更有可能具有统计上有意义的人口数量。 这应该给你一个出发点,find一个当地人口的像素在该斌的意思。 如果每个bin都有一个计数和一个{R,G,B}总和,那么一旦确定了最常用的bin,就可以快速find该bin中像素的平均颜色。 这种方法仍然受到直方图网格的一些影响。 您将更可能识别网格单元中间的颜色比边缘处的颜色。 人口可能跨越多个网格单元格。 类似kmeans可能是另一种方法。

如果你只是想要主色调,那么转换到色彩空间如HSV,然后是色调直方图将起作用。

我不知道vImage,CI或MetalPerformanceShaders中的任何filter为你做这些事情。 您当然可以在CPU或Metal中编写代码,而不会遇到麻烦。