# Finding number of pixels with certain RGB color in an image

I have the following image:

To determine the number of black pixels (nBlackPixels) I use:

imgData = ImageData[image];
positions = Position[imgData, {0., 0., 0.}];
nBlackPixels = Length[positions];


Can this be done faster?

• Count[Chop[imgData], {0, 0, 0}]? Jun 29 '15 at 14:34
• @Guess I think one needs to specify a levelspec of Infinity for Count. I also wonder if Chopping might actually be indicated here, since the OP is after pure black anyway. Jun 29 '15 at 14:43
• Ah, yes @Marco; I forgot how the colors were embedded there. Thanks. Jun 29 '15 at 14:55

ImageLevels[
ColorConvert[img, "Grayscale"]
][[1, 2]] // RepeatedTiming
(* Out: {0.0029, 144316} *)


Generally, for speed, you want to avoid pattern matching altogether.

• ImageLevels[img, "Byte"][[3, 1, 2]] is shorter and faster.
– M.R.
Jun 29 '15 at 15:18
• Thanks to everybody, but this code is incredibly fast ...
– mrz
Jun 29 '15 at 15:18
• @M.R. I don't think that will work in general, will it? You're counting the number of zeros in the blue color channel - but let's say there is a pixel like {50,150,0} then your method will be wrong. Jun 29 '15 at 15:25
• @Pickett Correct this is a one off ;)
– M.R.
Jun 29 '15 at 15:45

You can do this much faster and more easily on the image level using:

ImageMeasurements[ColorNegate@Binarize[img, 0], "Total"] // RepeatedTiming


{0.0039, 144316.}

Another way:

Length@ImageValuePositions[img, 0] // RepeatedTiming


{0.013, 144316}

• Wow, the first approach is really fast! Interesting! (+1) Jun 29 '15 at 14:46
• I never heard of RepeatedTiming. Thanks for that ;) Jun 29 '15 at 14:50

All times decrease as you proceed through this answer. The final version takes about 70% of the time of Pickett's solution on this sample problem on my hardware.

For comparison, mrz's initial solution:

mrz := Length[Position[ImageData[img], {0., 0., 0.}]]
mrz // RepeatedTiming
(* Out: {0.208, 144316} *)


I found a number of much slower ways to do this. :-)

Something unrelated to the proposed solutions, kind of the first thing I thought of (Can we tell who's been doing a lot of matching lately?):

(Plus @@ Plus @@ (ImageData[img, "byte"] /.
{{0, 0, 0} -> 1, {_, _, _} -> 0})) // RepeatedTiming
(* Out: {0.157, 144316} *)


Pickett's comment to MarcoB's solution is an improvement. I've also compared with Count[]ing a Flatten[]ed list.

MarcoB := Count[ImageData[img], {0., 0., 0.}, Infinity];
MarcoB // RepeatedTiming
(* Out: {0.128, 144316} *)

Count[ImageData[img, "byte"], {0, 0, 0}, Infinity] // RepeatedTiming
(* Out: {0.115, 144316} *)

Count[Flatten[ImageData[img], 1], {0., 0., 0.}] // RepeatedTiming
(* Out: {0.0985, 144316} *)

Count[ImageData[img], {0., 0., 0.}, 2] // RepeatedTiming
(* Out: {0.0907, 144316} *)

Count[Flatten[ImageData[img, "byte"], 1], {0, 0, 0}] // RepeatedTiming
(* Out: {0.0846, 144316} *)

Count[ImageData[img, "byte"], {0, 0, 0}, 2] // RepeatedTiming
(* Out: {0.0760, 144316} *)


Conclusions: Count[] to depth 2 beats Flatten[]ing beats Count[] to depth Infinity. Note that Count[]ing to depth 1 the Flatten[]ed showed an insignificant speedup, so these were suppressed in the above. Also, converting to "byte"s so that Count[] was comparing integers was faster in each variation than leaving the ImageData[] as floating point numbers.

If we are willing to Compile[], we can do a bit better. Note however, that compiling Pickett's solution "bakes in" only one image. That is, one would have to pay the cost of compilation for each image one wanted to count. This is because neither img nor ColorConvert[] are full arrays of numbers so neither can be an argument to a compiled function.

countzeroes = Compile[{{image, _Integer, 3}},
Count[Flatten[image, 1], {0, 0, 0}]
];
countzeroes[ImageData[img, "byte"]] // RepeatedTiming
(* Out: {0.0068, 144316} *)

Pickett := ImageLevels[ColorConvert[img, "Grayscale"]][[1, 2]];
Pickett // RepeatedTiming
(* Out: {0.0054, 144316} *)

(* Compiled Pickett ... *)
(* Note: the image is fixed at compile time -- varying the image requires recompilation *)
countzeroes = Compile[{},
ImageLevels[ColorConvert[img, "Grayscale"]][[1, 2]],
{{ImageLevels[_], _Real, 2}}
];
countzeroes[] // RepeatedTiming
(* Out: {0.0054, 144316.} *)

countzeroes = Compile[{{image, _Integer, 3}},
Count[image, {0, 0, 0}, 2]
];
countzeroes[ImageData[img, "byte"]] // RepeatedTiming
(* Out: {0.0038, 144316} *)


Compiled Pickett is not measurably faster than Pickett (because the only thing the compiler can do anything with is "[[1,2]]", which is too small to matter). We also see compilation speedup in the MarcoB-like solutions. Compiled Count[]ing to depth Infinity is insignificantly slower in this test, so is suppressed. Count[]ing to depth 2 is still faster than Flatten[]ing and Count[]ing. Converting to "byte" is about 3-times faster than forcing compiled Count[] to work with _Reals (0.0105 seconds, not shown above).

I prefer the MarcoB-like solutions because it is immediately clear what they do. It is a happy accident that the compiled, "byte"ed, Count[]ed to depth 2 version is fastest (on this test, on my hardware).

• Thank you for the thorough analysis (+1)! Jun 29 '15 at 20:51
• Great, Thanks Eric ..
– mrz
Jul 1 '15 at 9:15

You can use Count to achieve the same thing. It is slighlty faster on your image, but same order of magnitude execution time:

Clear[data]
data = ImageData[img];

Length@Position[data, {0., 0., 0.}] // RepeatedTiming
Count[data, {0., 0., 0.}, Infinity] // RepeatedTiming

(* Out:
{0.16, 144316}
{0.10, 144316}
*)

• Count is much faster if you tell it to only look up to level 2 instead. Jun 29 '15 at 15:01
• @Pickett that's an excellent idea, thanks for pointing that out. Jun 29 '15 at 20:52