The fastest way around this, if the image is stored in "chunky" format, i.e. the color planes dimension is the last, and this last dimension is contiguous, is to take a np.void view of every 24bits pixel, then run the result through np.unique and np.bincount:
>>> arr = np.random.randint(256, size=(10, 10, 3)).astype(np.uint8)
>>> dt = np.dtype((np.void, arr.shape[-1]*arr.dtype.itemsize))
>>> if arr.strides[-1] != arr.dtype.itemsize:
... arr = np.ascontiguousarray(arr)
...
>>> arr_view = arr.view(dt)
The contents of arr_view look like garbage:
>>> arr_view [0, 0]
array([Â],
dtype='|V3')
But it's not us that have to understand the content:
>>> unq, _ = np.unique(arr_view, return_inverse=True)
>>> unq_cnts = np.bincount(_)
>>> unq = unq.view(arr.dtype).reshape(-1, arr.shape[-1])
And now you have the unique pixels and their counts in those two arrays:
>>> unq[:5]
array([[ 0, 82, 78],
[ 6, 221, 188],
[ 9, 209, 85],
[ 14, 210, 24],
[ 14, 254, 88]], dtype=uint8)
>>> unq_cnts[:5]
array([1, 1, 1, 1, 1], dtype=int64)
np.uniquegive me how often each color appears in the array?np.unique/np.bincountcombo on it. You'll need to view the return ofnp.uniqueas 3uint8s and reshape it to(-1, 3)to make sense of the data, but it will be much faster, as nothing is done aside from viewing the exact same memory in a different way.