7
$\begingroup$

I have the following image:

enter image description here

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?

$\endgroup$
3
  • 1
    $\begingroup$ Count[Chop[imgData], {0, 0, 0}]? $\endgroup$ Commented Jun 29, 2015 at 14:34
  • $\begingroup$ @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. $\endgroup$
    – MarcoB
    Commented Jun 29, 2015 at 14:43
  • $\begingroup$ Ah, yes @Marco; I forgot how the colors were embedded there. Thanks. $\endgroup$ Commented Jun 29, 2015 at 14:55

4 Answers 4

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

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

$\endgroup$
4
  • 3
    $\begingroup$ ImageLevels[img, "Byte"][[3, 1, 2]] is shorter and faster. $\endgroup$
    – M.R.
    Commented Jun 29, 2015 at 15:18
  • $\begingroup$ Thanks to everybody, but this code is incredibly fast ... $\endgroup$
    – mrz
    Commented Jun 29, 2015 at 15:18
  • $\begingroup$ @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. $\endgroup$
    – C. E.
    Commented Jun 29, 2015 at 15:25
  • $\begingroup$ @Pickett Correct this is a one off ;) $\endgroup$
    – M.R.
    Commented Jun 29, 2015 at 15:45
7
$\begingroup$

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}

$\endgroup$
2
  • $\begingroup$ Wow, the first approach is really fast! Interesting! (+1) $\endgroup$
    – MarcoB
    Commented Jun 29, 2015 at 14:46
  • $\begingroup$ I never heard of RepeatedTiming. Thanks for that ;) $\endgroup$
    – g3kk0
    Commented Jun 29, 2015 at 14:50
5
$\begingroup$

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).

$\endgroup$
2
  • 1
    $\begingroup$ Thank you for the thorough analysis (+1)! $\endgroup$
    – MarcoB
    Commented Jun 29, 2015 at 20:51
  • $\begingroup$ Great, Thanks Eric .. $\endgroup$
    – mrz
    Commented Jul 1, 2015 at 9:15
3
$\begingroup$

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}
*)
$\endgroup$
2
  • 2
    $\begingroup$ Count is much faster if you tell it to only look up to level 2 instead. $\endgroup$
    – C. E.
    Commented Jun 29, 2015 at 15:01
  • 1
    $\begingroup$ @Pickett that's an excellent idea, thanks for pointing that out. $\endgroup$
    – MarcoB
    Commented Jun 29, 2015 at 20:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.