1

So I have a predefined color palette which I would like to color the picture with. I was researching how to do this and found this so far.

import scipy.spatial as sp
import matplotlib.pyplot as plt
import cv2
import numpy

avg = image.open("sourcefile.png")

#Color palette I would ike to use

main_colors = [(100, 100, 100),
               (204, 0, 0),
               (0, 174, 0),
               (102, 51, 153),
               (255, 102, 0),
               (0, 0, 155)]

h, w, bpp = numpy.shape(avg)

# Change colors of each pixel
for py in range(0, h) :
    for px in range(0, w):
        input_color = avg[py][px][0], avg[py][px][1], avg[py][px][2]
        tree = sp.KDTree(main_colors)
        distance, result = tree.query(input_color)
        nearest_color = main_colors[result]

        avg[py][px][0] = nearest_color[0]
        avg[py][px][1] = nearest_color[1]
        avg[py][px][2] = nearest_color[2]

# show image
plt.figure()
plt.axis("off")
plt.imshow(avg)

I believe, I am really close to the solution, however, I can't seem to it. Can someone help me debug this? I keep getting this and I don't know how to solve this

 File "/Average Pictures.py", line 22, in <module>
    input_color = avg[py][px][0], avg[py][px][1], avg[py][px][2]
TypeError: 'Image' object is not subscriptable

After Looking at @Quang Hongs comment I changed the code too:

import scipy.spatial as sp
import matplotlib.pyplot as plt
import cv2
import numpy 

...
...
...


# Blending loaded Images
avg = Image.open(imlist[0])
for i in range(1, N):
    img = Image.open(imlist[i])
    avg = Image.blend(avg, img, 1.0 / float(i + 1))

avg1 = Image.new("RGB", avg.size)
avg1 = asarray(avg1)

main_colors =numpy.array([(100, 100, 100), 
               (204, 0, 0),  
               (0, 174, 0),  
               (102, 51, 153),
               (255, 102, 0), 
               (0, 0, 155),
               ])

main_colors = numpy.array(main_colors)
dist_mat = sp.distance_matrix(avg1.reshape(-1,3), main_colors)
color_idx = dist_mat.argmax(axis=1)

nearest_colors = main_colors[color_idx].reshape(avg1.shape)

fig, axes = plt.subplots(1, 2)
axes[0].imshow(avg)  # original image
axes[1].imshow(nearest_colors)  # nearest_color
plt.show()

The problem now however ist, that it doesn't output a correct image.

Output

Can someone help ? Sorry for being an absolute noob.

Jolanthan
  • 81
  • 7
  • Have a look here... https://stackoverflow.com/a/57204807/2836621 – Mark Setchell May 18 '20 at 20:16
  • dont you need to perform an image.load ? https://stackoverflow.com/questions/1109422/getting-list-of-pixel-values-from-pil – silicontrip May 18 '20 at 20:17
  • Try replacing `avg = image.open("sourcefile.png")` with `avg = cv2.imread("sourcefile.png",mode='RGB')`. If did not work plz `print(type(avg))` here to be able to help better. – Ehsan May 18 '20 at 20:25

1 Answers1

1

I would use distance_matrix to compute the distance across the pixels/reference-colors and then argmin to extract the colors:

# sample image
np.random.seed(1)
avg = np.random.randint(0,255, (10,10,3), dtype=np.uint8)

# in your code, make sure `avg` is an np array
# you can use `cv2.imread` for that purpose, not the BGR color space
# for example
# avg = cv2.imread('image.png')
# agv = cv2.cvtColor(avg, cv2.BGR2RGB) # convert to RGB

# convert main_colors to np array for indexing
main_colors = np.array(main_colors)

# compute the distance matrix
dist_mat = distance_matrix(avg.reshape(-1,3), main_colors)

# extract the nearest color by index
color_idx = dist_mat.argmax(axis=1)

# build the nearest color image with indexing
nearest_colors = main_colors[color_idx].reshape(avg.shape)

# plot
fig, axes = plt.subplots(1,2)
axes[0].imshow(avg)             # original image
axes[1].imshow(nearest_colors)  # nearest_color
plt.show()

Output:

enter image description here

Quang Hoang
  • 146,074
  • 10
  • 56
  • 74
  • After using these lines of code, the ouput isn't correct. Ihave updated my code. Could you take another look at it? – Jolanthan May 18 '20 at 23:34
  • 1
    `avg1 = Image.new("RGB", avg.size)` why are you doing this? `avg1` is just a black single-color image, of course you would get a single-color nearest image. – Quang Hoang May 18 '20 at 23:41
  • Oh yeah, that is right. I was trying to convert the picture into an RGB picture first and then convert it into an array. How should I do it otherwise? – Jolanthan May 19 '20 at 00:02
  • I'm not familiar with `Image`. Did you try the `opencv` code? – Quang Hoang May 19 '20 at 01:42
  • 1
    I solved the problem with the RGB stuff. And the problem I was having was quite simple. Instead of ```argmin``` I used ```argmax``` in the first iteration of the code. Therefore the output was a picture that was least similar to it. Changing that to ```argmax``` to ```argmin``` gave me the correct picture. – Jolanthan May 20 '20 at 06:10
  • @Jolanthan haha, sorry, it was my mistake as well. Not sure why I put `argmax` there either :). Thanks for pointing that out. – Quang Hoang May 20 '20 at 14:55