Reputation: 16462
I am trying to cluster colors on an image to a predefined classes (black, white, blue, green, red). I'm using the following code:
import numpy as np
import cv2
src = cv2.imread('objects.png')
colors = np.array([[0x00, 0x00, 0x00],
[0xff, 0xff, 0xff],
[0xff, 0x00, 0x00],
[0x00, 0xff, 0x00],
[0x00, 0x00, 0xff]], dtype=np.float32)
classes = np.array([[0], [1], [2], [3], [4]], np.float32)
dst = np.zeros(src.shape, np.float32)
knn = cv2.KNearest()
knn.train(colors, classes)
# This loop is very inefficient!
for i in range(0, src.shape[0]):
for j in range(0, src.shape[1]):
sample = np.reshape(src[i,j], (-1,3)).astype(np.float32)
retval, result, neighbors, dist = knn.find_nearest(sample, 1)
dst[i,j] = colors[result[0,0]]
cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
The code works well and result is shown below. The image on the left is the input and the image on the right is the output.
However the loop above is very inefficient and make the conversion slow. What is the most efficient Numpy operation to replace the loop above?
Upvotes: 2
Views: 1166
Reputation: 16462
I managed to remove the loop using the code below. The code runs very fast, almost similar with the C++ version.
import numpy as np
import cv2
src = cv2.imread('objects.png')
src_flatten = np.reshape(np.ravel(src, 'C'), (-1, 3))
dst = np.zeros(src.shape, np.float32)
colors = np.array([[0x00, 0x00, 0x00],
[0xff, 0xff, 0xff],
[0xff, 0x00, 0x00],
[0x00, 0xff, 0x00],
[0x00, 0x00, 0xff]], dtype=np.float32)
classes = np.array([[0], [1], [2], [3], [4]], np.float32)
knn = cv2.KNearest()
knn.train(colors, classes)
retval, result, neighbors, dist = knn.find_nearest(src_flatten.astype(np.float32), 1)
dst = colors[np.ravel(result, 'C').astype(np.uint8)]
dst = dst.reshape(src.shape).astype(np.uint8)
cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
The code generated the correct result as before, with faster execution time.
Upvotes: 0
Reputation: 613
You can build a look up table. So that you know corresponding class for each color. It doesn't have to be 256x256x256 you can reduce number of bins.
Upvotes: 0
Reputation: 28683
If you want a simple squared difference measure ("which is the euclidian nearest number), this will work.
Calculate differences
diff = ((src[:,:,:,None] - colors.T)**2).sum(axis=2)
(assuming src
is y,x,3 in shape)
Pick closest colour index:
index = diff.argmin(axis=2)
New image:
out = colors[index]
If your colours are really to have component values of 0 or 0xff, you can use something like
out = np.where(src>0x88, 0xff, 0)
Upvotes: 2