There are a number of issues with your question and assumptions.
You cannot count colours with np.unique(image)
You cannot count the colours in an image with np.unique(im). Let's see why by making a random image with just 4 intensities: 0,1,2 and 3.
import numpy as np
import cv2
# Ensure repeatable, deterministic randomness!
np.random.seed(42)
# Make a random image
im = np.random.randint(0,4,(480,640,3), dtype=np.uint8)
That looks like this where each row is the RGB triplet for one pixel:
array([[[2, 2, 3],
[3, 2, 1],
[2, 2, 0],
...,
[3, 3, 2],
[0, 0, 1],
[1, 1, 1]],
...,
[3, 3, 1],
[2, 3, 0],
[0, 1, 3]]], dtype=uint8)
Now, if you try and get the unique colours like this, it will not work, because each colour is a combination of 3 intensities:
np.unique(im) # prints: array([0, 1, 2, 3], dtype=uint8)
Whereas, if you want the number of unique colours, you need to look for the number of unique combinations of three RGB/BGR values:
np.unique(im.reshape(-1, im.shape[2]), axis=0)
which gives a vector of the unique RGB/BGR triplets in the image - each row is a unique colour combination:
array([[0, 0, 0],
[0, 0, 1],
[0, 0, 2],
[0, 0, 3],
[0, 1, 0],
[0, 1, 1],
[0, 1, 2],
[0, 1, 3],
[0, 2, 0],
[0, 2, 1],
[0, 2, 2],
[0, 2, 3],
[0, 3, 0],
[0, 3, 1],
[0, 3, 2],
[0, 3, 3],
[1, 0, 0],
[1, 0, 1],
[1, 0, 2],
[1, 0, 3],
[1, 1, 0],
[1, 1, 1],
[1, 1, 2],
[1, 1, 3],
[1, 2, 0],
[1, 2, 1],
[1, 2, 2],
[1, 2, 3],
[1, 3, 0],
[1, 3, 1],
[1, 3, 2],
[1, 3, 3],
[2, 0, 0],
[2, 0, 1],
[2, 0, 2],
[2, 0, 3],
[2, 1, 0],
[2, 1, 1],
[2, 1, 2],
[2, 1, 3],
[2, 2, 0],
[2, 2, 1],
[2, 2, 2],
[2, 2, 3],
[2, 3, 0],
[2, 3, 1],
[2, 3, 2],
[2, 3, 3],
[3, 0, 0],
[3, 0, 1],
[3, 0, 2],
[3, 0, 3],
[3, 1, 0],
[3, 1, 1],
[3, 1, 2],
[3, 1, 3],
[3, 2, 0],
[3, 2, 1],
[3, 2, 2],
[3, 2, 3],
[3, 3, 0],
[3, 3, 1],
[3, 3, 2],
[3, 3, 3]], dtype=uint8)
Or, as a simple number of unique colours:
len(np.unique(im.reshape(-1, im.shape[2]), axis=0)) # prints 64
So, for your image:
# Open image
im = cv2.imread('image.png',cv2.IMREAD_UNCHANGED)
# Count unique colours
len(np.unique(im.reshape(-1, im.shape[2]), axis=0) # prints 790
There are more colours than you expect
Why do I have more colours than I expect? The two most common reasons are:
- the image was saved as a JPEG
- there is text or drawn shapes that were anti-aliased
Let's look at how saving as a JPEG messes you up!
# Load image and count colours
im = cv2.imread('image.png',cv2.IMREAD_UNCHANGED)
len(np.unique(im.reshape(-1, im.shape[2]), axis=0)) # prints 790
# Save as JPEG
cv2.imwrite('temp.jpg',im)
# Reload and recount just the same
im = cv2.imread('temp.jpg',cv2.IMREAD_UNCHANGED)
len(np.unique(im.reshape(-1, im.shape[2]), axis=0)) # prints 4666 !!!
How can I palettise an image - (reduce colours to a fixed palette)?
If you want to palettise your image to your own specific palette, firstly you need to specify your palette in BGR order (!) to match OpenCV's ordering:
palette = np.array([
[0,0,0], # Black
[93,136,106], # Green
[208,224,64], # Blue
[85,124,168]], # Brown
dtype=np.uint8)
Then read your image discarding the completely pointless alpha channel:
test = cv2.imread("image.png",cv2.IMREAD_COLOR)
Then calculate the distance to each palette entry from each pixel:
distance = np.linalg.norm(test[:,:,None] - palette[None,None,:], axis=3)
Then choose whichever one of the palette colours is nearest for each pixel:
palettised = np.argmin(distance, axis=2).astype(np.uint8)
Your image is now in the array palettised and stored at each pixel location is the index of the nearest colour in your palette - so, as your palette has 4 entries (0..3), all elements of your image are 0, 1, 2 or 3.
So, now you can multiply by 85 with:
result = palettised * 85
