简体   繁体   English

如何从CIAreaHistogram中提取主色?

[英]How to extract dominant color from CIAreaHistogram?

I am looking to analyze the most dominant color in a UIImage on iOS (color present in the most pixels) and I stumbled upon Core Image's filter based API, particularly CIAreaHistogram. 我想分析iOS上UIImage中最主要的颜色(大多数像素中存在的颜色),我偶然发现了Core Image的基于过滤器的API,特别是CIAreaHistogram。

It seems like this filter could probably help me but I am struggling to understand the API. 看起来这个过滤器可能对我有帮助,但我很难理解API。 Firstly it says the output of the filter is a one-dimensional image which is the length of your input-bins and one pixel in height. 首先,它说过滤器的输出是一维图像,它是输入箱的长度和高度的一个像素。 How do I read this data? 我如何阅读这些数据? I basically want to figure out the color-value with the highest frequency so I am expecting the data to contain some kind of frequency count for each color, its not clear to me how this one-dimensional image would represent that because it does not really explain the data I can expect inside this 1-d image. 我基本上想要找出具有最高频率的颜色值,所以我期望数据包含每种颜色的某种频率计数,我不清楚这个一维图像如何表示,因为它不是真的解释我在这张1-d图像中可以预期的数据。 And if its truly a histogram why would it not return a data-structure representing that like a dictionary 如果它真的是直方图,为什么它不会返回表示像字典一样的数据结构

Second, in the API it asks for a number of bins? 其次,在API中它要求一些垃圾箱? What should that input be? 那输入应该是什么? If I want an exact analysis would the input bin parameter be the color-space of my image? 如果我想要一个精确的分析,输入bin参数是我的图像的颜色空间? What does making the bin value smaller do, I would imagine it just approximates nearby colors via Euclidean distance to the nearest bin. 什么使bin值更小,我想它只是通过欧几里德距离到最近的bin来近似附近的颜色。 If this is the case will that not yield exact histogram results, why would anyone want to do that? 如果是这种情况将不会产生精确的直方图结果,为什么有人想这样做?

Any input on the above two questions from an API perspective would help me greatly 从API角度对上述两个问题的任何输入都会对我有很大帮助

Ian Ollmann's idea of calculating the histogram just for the hue is really neat and can be done with a simple color kernel. Ian Ollmann只为色调计算直方图的想法非常简洁,可以使用简单的颜色内核完成。 This kernel returns a monochrome image of just the hue of an image (based on 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)

If I get the hue of an image of a blue sky, the resulting histogram looks like this: 如果我得到蓝天图像的色调,结果直方图如下所示:

蓝天直方图

...while a warm sunset gives a histogram like this: ...而温暖的夕阳给出了这样的直方图:

在此输入图像描述

So, that looks like a good technique to get the dominant hue of an image. 所以,这看起来是一种很好的技术,可以获得图像的主导色调。

Simon 西蒙

CIAreaHistogram returns an image where the reg, green, blue and alpha values of each of the pixels indicates the frequency of that tone in the image. CIAreaHistogram返回一个图像,其中每个像素的reg,green,blue和alpha值表示图像中该色调的频率。 You can render that image to an array of UInt8 to look at the histogram data. 您可以将该图像渲染到UInt8数组以查看直方图数据。 There's also an undocumented outputData value: 还有一个未记录的outputData值:

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

However, I've found vImage to be a better framework for working with histograms. 但是,我发现vImage是一个更好的直方图框架。 First off, you need to create a vImage image format: 首先,您需要创建一个vImage图像格式:

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

vImage works with image buffers that can be created from CGImage rather than CIImage instances (you can create one with the createCGImage method of CIContext . vImageBuffer_InitWithCGImage will create an image buffer: vImage可与可从创建图像缓冲区CGImage而非CIImage实例(您可以创建一个与createCGImage的方法CIContextvImageBuffer_InitWithCGImage将创建一个图像缓存:

var inBuffer: vImage_Buffer = vImage_Buffer()

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

Now to create arrays of Uint which will hold the histogram values for the four channels: 现在创建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)

The final step is to perform the calculation, which will populate the four arrays, and free the buffer's data: 最后一步是执行计算,它将填充四个数组,并释放缓冲区的数据:

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

free(inBuffer.data)

A quick check of the alpha array of an opaque image should yield 255 zeros with the final value corresponding to the number of pixels in the image: 快速检查不透明图像的alpha数组应该产生255个零,其最终值对应于图像中的像素数:

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

A histogram won't give you the dominant color from a visual perspective: an image which is half yellow {1,1,0} and half black {0,0,0} will give the same results as an image which is half red {1,0,0} and held green {0,1,0} . 直方图不会从视觉角度给出主色:半黄色{1,1,0}和半黑色{0,0,0}的图像将产生与半红色图像相同的结果{1,0,0}并举行绿色{0,1,0}

Hope this helps, 希望这可以帮助,

Simon 西蒙

One problem with the histogram approach is that you lose correlation between the color channels. 直方图方法的一个问题是您失去了颜色通道之间的相关性。 That is, half your image could be magenta and half yellow. 也就是说,你的一半图像可能是洋红色和半黄色。 You will find a red histogram that is all in the 1.0 bin, but the blue and green bins would be evenly split between 0.0 and 1.0 with nothing in between. 你会发现一个红色直方图都在1.0 bin中,但是蓝色和绿色的bin会在0.0和1.0之间平均分配,两者之间没有任何内容。 Even though you can be quite sure that red is bright, you won't be able to say much about what the blue and green component should be for the "predominant color" 即使你可以确定红色是明亮的,你也无法说出蓝色和绿色成分应该是什么“主色”

You could use a 3D histogram with 2**(8+8+8) bins, but this is quite large and you will find the signal is quite sparse. 您可以使用带有2 **(8 + 8 + 8)个箱的3D直方图,但这个非常大,您会发现信号非常稀疏。 By happenstance three pixels might land in one bin and have no two the same elsewhere, even though many users could tell you that there is a predominant color and it has nothing to do with that pixel. 通过偶然事件,三个像素可能落在一个箱子中并且在其他地方没有两个像素,即使许多用户可以告诉您存在主要颜色并且它与该像素无关。

You could make the 3D histogram a lot lower resolution and have (for example) just 16 bins per color channel. 您可以使3D直方图的分辨率降低很多,并且(例如)每个颜色通道只有16个分档。 It is much more likely that bins will have a statistically meaningful population count this way. 通过这种方式,垃圾箱更有可能具有统计上有意义的人口数量。 This should give you a starting point to find a mean for a local population of pixels in that bin. 这应该为您提供一个起点,以找到该区域中当地像素数的平均值。 If each bin had a count and a {R,G,B} sum, then you could quickly find the mean color for pixels in that bin once you had identified the most popular bins. 如果每个bin都有一个计数和一个{R,G,B}总和,那么一旦你确定了最流行的bin,你就可以快速找到该bin中像素的平均颜色。 This method is still subject to some influence from the histogram grid. 该方法仍然受到直方图网格的一些影响。 You will be more likely to identify colors in the middle of a grid cell than at the edges. 您将更有可能识别网格单元格中间的颜色而不是边缘处的颜色。 Populations may span multiple grid cells. 人口可能跨越多个网格单元。 Something like kmeans might be another method. 像kmeans这样的东西可能是另一种方法。

If you just want predominant hue, then conversion to a color space like HSV followed by a histogram of hue would work. 如果你只想要主色调,那么转换到像HSV这样的颜色空间,然后是色调的直方图就行了。

I'm not aware of any filters in vImage, CI or MetalPerformanceShaders to do these things for you. 我不知道vImage,CI或MetalPerformanceShaders中的任何过滤器都可以为您做这些事情。 You can certainly write code in either the CPU or Metal to do it without a lot of trouble. 您当然可以在CPU或Metal中编写代码,而不会遇到很多麻烦。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM