I have a Python script that creates a diff of two images using PIL. That part works fine. Now I need to find an efficient way to count all the non-black pixels (which represent parts of the two images that are different). The diff image is in RGB mode.
My initial cut was something like this:
return sum(x != (0, 0, 0) for x in diffimage.getdata())
Then I realized that the diffs were usually constrained to a portion of the image, so I used
getbbox() to find the actual diff data:
bbox = diffimage.getbbox() return sum(x != (0, 0, 0) for x in diffimage.crop(bbox).getdata()) if bbox else 0
This has the advantage of being VERY fast when the image is all black since
None in that case and no pixel counting need be done.
I still wasn't satisfied, so I decided to try using more of the built-in PIL methods to avoid the generator expression with the Python conditional that needed to be evaluated for each pixel. I came up with:
bbox = diffimage.getbbox() if not bbox: return 0 return sum(diffimage.crop(bbox) .point(lambda x: 255 if x else 0) .convert("L") .point(bool) .getdata())
This is about five times faster than the previous version. The basic steps are:
- Crop to the bounding box to avoid counting black pixels
- Convert all non-zero values in each channel to 255. This way, when we later convert it to grayscale, all non-black pixels are guaranteed to have non-zero values. (Because a pixel might differ in only one channel, and only by a small amount, some pixels that are not actually black might end up as black in grayscale mode, because only a fraction of that channel's value makes its way to grayscale.) BTW, the function isn't evaluated for each pixel but only once for each possible pixel value to make a lookup table, so it's very fast.
- Convert to grayscale.
- Convert all non-zero pixels to 1 using
- Sum all the pixel values.
Can I do better?