我希望分析iOS上UIImage中最主要的颜色(大多数像素中存在的颜色),我偶然发现了Core Image基于过滤器的API,特别是CIAreaHistogram。
似乎这个过滤器可能对我有帮助,但我很难理解API。首先,它说过滤器的输出是一维图像,它是输入箱的长度和高度的一个像素。我如何阅读这些数据?我基本上想要找出具有最高频率的颜色值,所以我期望数据包含每种颜色的某种频率计数,我不清楚这个一维图像将如何表示,因为它不是真的解释我在这张1-d图像中可以预期的数据。如果它真的是直方图,为什么它不会返回表示像字典
的数据结构其次,在API中它要求一些垃圾箱?那输入应该是什么?如果我想要一个精确的分析,输入bin参数是我的图像的颜色空间?什么使bin值更小,我想它只是通过欧几里德距离到最近的bin来近似附近的颜色。如果是这种情况将不会产生精确的直方图结果,为什么有人想要这样做?
从API角度对上述两个问题的任何输入都会对我有所帮助
答案 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)
如果我得到蓝天图像的色调,结果直方图如下所示:
......温暖的夕阳给出了这样的直方图:
所以,这看起来是一种很好的技术,可以获得图像的主导色调。
西蒙
答案 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中编写代码,而不会有太多麻烦。