# Analyze pixel color distribution by sampling image over a defined area size (or grid?)

Blockquote

I have an image of different colored particles (red, green, and yellow) that are all mixed up against a black background. I am interested in knowing what color each particle has for its neighbor (if any). Because some particles severely overlap and merge into large blobs, I cannot easily define particles with segmentation analysis. But this is okay, I am more interested in looking at neighbors by color, not by individual particle. Specifically, I want to ask what is the distribution of colors that any given color is touching (i.e. not separated by black pixels).

For analysis, rather than defining specific areas of interest, I was thinking about using a grid approach that would sample the whole image. First, overlay a grid onto the image, with each unit of the grid approximating the size of a particle. Then I would like to have a way so that each grid can report what % of color is in the area of each grid unit. In the end, I would get a distribution for what colors are in each grid unit. Each grid could then be compiled for analysis.

So far, I have separated the channels, binarized the channel, and then pseudo colored back the original color so that everything is equal in terms of intensity. But I need some help to go from here. Any help would be greatly appreciated.

Unfortunately due to unexpected server maintenance my stored images are not accessible at this moment. I've mocked up an image that should suffice. [![mock up test image][1]][1]

ImageParition[image,{20}]//Grid


This will partition the image into the approximate size of one particle.

Perhaps the pixel colors for each square's border can be defined into a matrix that will produce a quantitative distribution for the image?

----update jun 7----

After Wjx's helpful responses and trying out more things, I would like to rephrase and simplify my question.

After the ImagePartition step, I would like to assess each square to know how many colors are in each individual square. As Wjx mentioned earlier, the DominantColors function could be used. For example, taking one square from my mock up partition that has red, green, and yellow (with no black background) and applying the DominantColors function yields Red, Green, and Yellow. Doing the same for one square with just green and black background yields Green and Black.

I have decided that this type of information is sufficient for my needs. So, instead of the neighbor-correlation approach, what I'd like to do is have each square of the partitioned image be analyzed with the DominantColors function.

Then, I'd like to classify each individual square based on its DominantColor result so that each square can be binned into a category. The final result for the image would then be # of squares in each category so that a distribution could be produced.

The Categories would be as follows:

1. "Negatives" - All black
2. "Red" - All red (as well as any that are red AND black)
3. "Green" - All green (as well as any that are green AND black)
4. "Mixed" - any combination of the 3 colors (i.e. R+G, R+Y, G+Y, R+G+Y+, and again, of course black is allowed in any of the combinations also since it is the background).

Hopefully this is a clearer question and should be straightforward code wise. But I still need help putting all the code together. thank you!

-------Update Jun 9--------

@shrx - here is my code for my "pre-processing" of the colors.

Raw image:

[![Raw_Image][3]][3]

Code:

i= image
imgR = ColorSeparate[i, "R"];
imgG = ColorSeparate[i, "G"];

imgRB = Binarize[imgR];
imgRc = ColorReplace[imgRB, White -> Red];

imgGB = Binarize[imgG];
imgGc = ColorReplace[imgGB, White -> Green];



final output image (cannot post because I am at limit for links since I am new)

• How about using ImagePartition and compare the average intensity value in the same small picture, but in different color channel. The color with greatest average intensity could be defined as the 'color of this grid unit'. Do you think this method can help? If it do help, I'll write the code and post it as an full answer – Wjx Jun 7 '16 at 1:01
• Thanks for your reply. I think that could help, but I suppose it depends on what size the grid unit is, but I expected that I'd need to play around with that to figure out what would work best anyways. My main concern is being able to tell which colors are "touching" within any given square. Is there a way to use ImageParition to determine the color of each small square's neighbor post partitioning? thank you! – Nathur Jun 7 '16 at 2:10
• I suppose using a lot of Rotate*** will do this work. It's not a hard work to associate one point in a matrix with its neighbours. – Wjx Jun 7 '16 at 2:16
• Also, there's a way to single out each particle directly by first skrinking all the particles by list manipulation then enlarge them again. The skrinking process can seperate different particles while the enlarging process restore them to their original size while keeping each particle seperated. then by checking the cross area of each pair of particles we can find all the neighbour in a more precise way. Do you think this method will help you better and yield better results? This method can deal with particles sticking together. Can you post the processing image on? – Wjx Jun 7 '16 at 2:20
• That sounds like it could work better. Could the shrinking/particle separation work even with images that have been color separated and then binarized? I will try to post a good image tomorrow. – Nathur Jun 7 '16 at 2:53

Here's my approach which returns each color component's percentage in a given bin.

First, some preprocessing. This quantizes the colors using the Nearest function because by default Mathematica's ColorQuantize fails to properly quantize the image.

i = Import["http://i.stack.imgur.com/otAao.jpg"];
ncolors = 4;
dc = DominantColors[i, ncolors];
dcl = dc /. RGBColor -> List;
ndcl = Nearest[dcl];
iqdn = Map[ndcl, ImageData[i], {2}][[All, All, 1]];


Then, we calculate the percentage of each color in each bin with the desired bin size.

bin = 4;
comppart = ArrayComponents[#, 3, Thread[dcl -> Range[ncolors]]] & /@
Partition[iqdn, {bin, bin}, bin];
tally = Map[Table[{n, Count[Flatten[#], n]/bin^2.}, {n, ncolors}] &, comppart, {2}];
percentages = tally[[All, All, #, 2]] & /@ Range[ncolors];


The percentages are returned as arrays. You can visualize them as corresponding intensity masks:

masks = Image[#, ImageSize -> ImageDimensions[i]] & /@ percentages;
Grid[{dc, Show[#, ImageSize -> 150] & /@ masks}]


Update

As per your updated question, here's how to calculate the distribution of color categories (I have also added the "Yellow" category):

categories = # /. {
{{1, 1.}, __} -> "Negative",
{_, _, {3, 0.}, {4, 0.}} -> "Red",
{_, {2, 0.}, _, {4, 0.}} -> "Green",
{_, {2, 0.}, {3, 0.}, _} -> "Yellow",
_ -> "Mixed"
} & /@ Flatten[tally, 1];
counts = Reverse@Sort@N@Counts[categories]/Times @@ Dimensions[tally][[;; 2]]

<|"Negative" -> 0.892626, "Red" -> 0.047619, "Green" -> 0.036268,
"Mixed" -> 0.0158269, "Yellow" -> 0.00765965|>


Ignoring the "Negatives":

countsNoBG = KeyDrop[counts, "Negative"]/(1 - counts["Negative"])

<|"Red" -> 0.443489, "Green" -> 0.337774, "Mixed" -> 0.1474, "Yellow" -> 0.0713365|>


Compare to the sums of percentages (which does not know about the "Mixed" category):

(Total[Flatten[#, 1]] & /@ percentages)/Times @@ Dimensions[tally][[;; 2]]

{0.910086, 0.0436594, 0.0356883, 0.0105666}

• this looks great! I will play around with it and see if I have any questions. thank you – Nathur Jun 8 '16 at 14:24
• This looks very promising. thank you! I tried it out with more mock images to see how it responded to images with vary amount of mixing. I have a few questions. (1) What area size does it sample the image? It doesn't look like you used an image partition and I cannot figure out which part of the code is instructing this. (2) I would like to leave out the "negative" category in the percentage response. So that only area with colors gets reported in the percent distribute. – Nathur Jun 8 '16 at 15:15
• (con'td) (3) In trying different mock up tests, your code detected higher levels of mixing when particles were really mixed up, but not consistent with lower levels. For example, if I have no mixing (all separate Red and Green) it still reported mixing at 0.02. If I had some mixing (imaging a bunch of red that form a continuos boundary with green) it should be slightly higher mixing because any area samples along the border will have some red and green. But the mixing was not increased (even when normalized to % negative) – Nathur Jun 8 '16 at 15:16
• (cont'd) (4) if there is no yellow, it still reports a positive non-zero number for yellow. this is also true if everything is green or red, it will report high level of the other color but nothing for that color. (5) if there is no mixing (i.e. no non-like colors are touching) it still reports a positive non-zero for mixing. – Nathur Jun 8 '16 at 15:18
• 1) It uses 4x4 bin sizes. You change the bin size by modifying the bin variable. 4) This code assumes there are 4 dominant colors (R, G, Y and B). If there are more or less color blobs, it should be changed accordingly. I will edit the code so you can specify the number of colors. – shrx Jun 8 '16 at 16:34

There's a few steps in solving this problem:

1. Separate colors completely
2. Partition the image
3. Compute the correlation between neighbouring picture segments.

To solve the first problem, I use ColorQuantize first in order to make similar colors the same to avoid noise. Then by using LinearSolve, I can change the three colors to R/G/B so that I can use ColorSeparation conveniently.

Then, I use ImagePartition to split this picture into parts.

Finally, I use RotateRight and Thread to make connection between neighbouring image segments.

The final, and the most important, step is defining and calculating the 'connection index'. So if a image segment with 70% of color A and 30% of color B is adjacent with another with 30% of A and 50% of C, We will assume that there're a 70%*50%=35% connection between A and C for this two adjacent segment. Obviously, if you put a image segment purely in color A and another purely in color C together, this index will be 100%, indicating that one A particle is adjacent to C, sounds reasonable.

The solution will come out after you sum up all the indexes.

The code is as follows:(Maybe not that elegantly written, but still fulfills the goal)

img = ColorQuantize[(*put your image here*), 10];
colors = Rest@DominantColors[img, 4];
restore[{r_, g_, b_}] = LinearSolve[Transpose[List @@@ colors]
, {r, g, b}];

arr = (Clip[
Map[Mean@*Flatten@*ImageData /@ ColorSeparate[#] &,
ImagePartition[Image@Map[restore, ImageData@img, {2}],
20], {2}]*2 - 1] + 1)/2;

mat = Total@
Flatten[Apply[Outer[Times, #1, #2] &,
RotateRight[arr, {1, 0}] + RotateRight[arr, {0, 1}]}], {2}],
1];

Prepend[Transpose@Prepend[mat + Transpose@mat, colors], {""}~Join~
colors] // MatrixForm


I hope this post can solve your problem~:)

Some further explanation: The method I gave in the comment area can yield better result when dealing with this form of pictures: Usually this method will apply better in 3D situations and when the spacing between particles are larger because it will need the gap between particles to distinguish different particles. So it seems that in your situation, the result of this method will not be satisfying. sorry~

• thank you! I will try this and see how it works. Do you also know of a way that could just report the colors in each grid? Not dominant color, but all of the colors? – Nathur Jun 7 '16 at 16:05
• So this works very well for the example! Just to be clear, the matrix reports the summed percent of the image of all the individual squares that have a color and neighbor another color in each direction, correct? It tends to not work as well when you try more complicated mock ups. I'm thinking that it would be safer to try an approach such as my comment above. – Nathur Jun 7 '16 at 16:54
• Fortunately, the thing you need is all included in the matrix arr~:) As you can see, at level 2 of arr, each element is in the form {r,g,b}. so for example, {0.3,0.4,0} just symbolize 30% of this image is covered by color 1, 40% are covered by color 2 and there's no color 3. But the drawback of this solution is that it can only deal with picture with 3 dominant color(black is not included~) – Wjx Jun 8 '16 at 0:37
• but the total area covered does not indicate how they are arranged. in other words, one image could have 0.5 red and 0.5 green that has all red on one half and all green on the other half. A different image could have 0.5 red and 0.5 red but be completely mixed up randomly. This would be different particle behaviors but have the same values. that's why i'm focusing on the squares. Unless I am misunderstanding your last comment. – Nathur Jun 8 '16 at 1:23
• yeah, take a view at arr. It can give you this distribution in each small image segment – Wjx Jun 8 '16 at 1:25