0

I searched some sources online but they are not exactly what I am looking for.

So for a set of images. I want to generate a color histogram that is in the form of {color1: p1, color2: p2,..... color100: p100} where colorxxx represent a color from RGB images. and p represent the prob of that color.

Is there a easy way to do such things in python?

Thanks

jack
  • 883
  • 2
  • 9
  • 21
  • Did you not find PIL's function [`histogram`](http://effbot.org/imagingbook/image.htm), or did you but you decided it does not fit your purpose because it does not work for a *set* of images? – Jongware Jul 13 '18 at 21:09
  • It returns a list with length 256*3. But It only counted frequency for each channel and concat them together. It is different from what I needed as what I needed is actually clustering the colors into a set of color and count the frequency of the cluster. In one dimension, the just need to set the bin for the interval and count frequency of each bin, but what about for images with 3 channels? – jack Jul 13 '18 at 21:21
  • @jack if you could enhance your question by specifying *which* online sources you searched and *exactly why* they're not what you're looking for, I think usr256 and others (myself included) would have an easier time answering your question – en_Knight Jul 13 '18 at 21:38
  • You could access the individual RGB pixels of the image with `PIL` and use a `collections.Counter` intance to count how many of each color are present by just sending them to as a `tuple` composed the three color channel values. This will work because these tuples would be valid dictionary keys like (255, 0, 128)`—this is important because `Counter` is a `dict` subclass which requires keys to be hashable (in other words it can only count hashable things). After sending all the pixels, the `Counter` instance _is_ effectively a histogram of the image. – martineau Jul 13 '18 at 23:09

1 Answers1

1

Method 1:

{k:np.sum(a==k) for k in set(a.ravel().tolist())}

or a little more readably

count = lambda A, key : np.sum(A==key)
unique_keys = set(A.ravel().tolist())
return {key : count(A,key) for key in unique_keys}

Walking through it:

{...}

dictionary comprehension to generate the mapping

set(a.ravel().tolist())

a.ravel flattens the image; to list allows it to be cast to a set, which is a container for the unique elements.

np.sum(a==k)

Count how many times the element is in the image. This is not the most efficient way to do this, but puts the histogram into the format you requested


Taken together, if your image is the 3x3

a = np.array([[1,2,3],[1,3,3],[3,3,3]])

then

set(a.ravel().tolist()) # yields set([1, 2, 3])

and the entire expression yields

{1: 2, 2: 1, 3: 6}

Method 2

from PIL.Image import fromarray
b = fromarray(a.astype(np.uint8)) # convert to a PIL image
hist =  {idx:count for idx, count in enumerate(b.histogram()) if count}

This works very similarly (using a dictionary comprehension), but makes use of the PIL histogram functionality, and enumerate to fetch the indices. It may have some domain limitations.

"Binning"

If you want to have "bins" of colors, as you indicated, then the remaining work is just defining your bin structure, which an be done many number of ways. For example, in the previous example, we can create fixed size integer bins by

num_bins = 2
b = fromarray(a.astype(np.uint8)//num_bins) # convert to a PIL image, binned
hist =  {idx*num_bins:count for idx, count in enumerate(b.histogram()) if count}
en_Knight
  • 5,301
  • 2
  • 26
  • 46
  • Sorry for replying so late. Thanks for answer. But here you only generated a dictionary with index from 0 - 784, which is not what I wanted. Because this is only the flattened count for each channels' intensity count. What I want is to group the color space into a smaller subspace, from 256 x 256 x 256 to say 100 color categories. And get a distribution for colors fall into those 100 categories(we may define the color category by a cube in the color space, say with width, height, length 10 by 10 by 10 ). I guess in that sense, it could not be called a color histogram in the usual sense then. – jack Jul 16 '18 at 16:29
  • @jack that's fine, does the edit address your question? – en_Knight Jul 16 '18 at 17:22
  • sorry but I am afraid not..... When you divided by num_bins, I think you only divided each value of the array. That does not make a bin I think... – jack Jul 16 '18 at 17:33
  • @jack hmm I think it does :) Did you try it on the example? If your input array was [1,2,3,4,5] and you divide by 2 using integer division, you get [0,1,1,2,2]; they are now binned by size 2. The algorithm correctly interprets that there is 1 element in bin [0,2), two elements between [2,4), and two elements between [4,6). As I said there are many *possible* ways to do binning, but this is a very common one – en_Knight Jul 16 '18 at 17:37
  • I see. But I am afraid that it still only treat intensity values from different channels independently, didn't it? So if I want to generate random color based on the histogram you gave, I would only be able to sample from each channel independently and concate them together? – jack Jul 16 '18 at 17:53
  • @jack, sorry, but there's a *lot* of ways to accomplish the general thing you're describing and I'm not sure which route your suggesting at. Summarily, that operation would go into the "b = ... " step, where you create the bins. How you create the bins is independent from how you create the histogram, which is what I read from the original question. One way to accomplish channel invariance would be to compress the three channels into their hex format, and sample from that. Would that be sufficient for what you're trying to accomplish? – en_Knight Jul 16 '18 at 18:18