0
votes

The code below takes an opencv image, converts it to rgb, and steps through the pixels converting them to hex using a list comprehension, and counts the number of pixels there are of each colour.

How can I reduce this code using list comprehension, and solve the TypeError at the bottom ?

import cv2
bgr_img = cv2.imread(img_input)
rgb_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB) # Hex starts with Red

The file is of the shape (300, 700, 3) that is [[[ 90, 150, 140 ], [90 150 140], [90 150 140] ....

palette = dict()
for i in img:
    for j in i:
        colour_hex = [f"#{a:02x}{b:02x}{c:02}" for a,b,c in j]
        if colour_hex in palette:
            palette[colour_hex] +=1
        else:
            palette[colour_hex] = 1

So then the dictionary contains value key pairs of the hex colour and how many of each pixel in the image is that hex colour.

The error message reads

TypeError: cannot unpack non-iterable numpy.uint8 object

Thanks to response below, shorter code that works is like this:-

import cv2
bgr_img = cv2.imread(img_input)
rgb_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB) # Hex starts with Red
palette = dict()
for i in img:
    for j in i:
        colour_hex = f"#{:02x}{:02x}{:02x}".format(*j)
        if colour_hex in palette:
            palette[colour_hex] +=1
        else:
            palette[colour_hex] = 1

can this be made any shorter ? / more Pythonic using a list comprehension ?

1

1 Answers

1
votes

You're running a loop over j to create a list of colour_hex but you really just want the colour_hex for j:

Replace this:

colour_hex = [f"#{a:02x}{b:02x}{c:02}" for a,b,c in j]

With this:

colour_hex = "#{:02x}{:02x}{:02}".format(*j)

If you don't need the bgr_img for anything else and expect many different colours, I'd prefer this for a shorter, perhaps more Pythonic solution:

from cv2
from collections import defaultdict

rgb_img = cv2.cvtColor(cv2.imread(img_input), cv2.COLOR_BGR2RGB)
palette = defaultdict(int)
for i in rgb_img:
    for j in i:
        palette["#{:02x}{:02x}{:02x}".format(*j)] += 1

On the other hand, if you don't need the rgb_img, you can also just use the bgr_img directly, preventing the call to cvtColor:

from cv2
from collections import defaultdict

bgr_img = cv2.imread(img_input)
palette = defaultdict(int)
for i in bgr_img :
    for j in i:
        palette["#{2:02x}{1:02x}{0:02x}".format(*j)] += 1