Reputation: 47
I am currently working on an computer vision project with python and openCV.
I need to search in an image with a size of about 620x420 pixels for the green pixel, which is the closest to the bottom of the image. There are multiple green pixels (e.g. 100) in the image from previous contour analysis.
Here is an example image:
I already implemented it with the following code:
# Search for the most bottom contour point
xLowestContour = 0 # x coordinate of the lowest contour point
yLowestContour = 0 # y coordinate of the lowest contour point
for y in range(roi_h): # roi_h is the heigth of the image
for x in range(roi_w): # roi_w is the width of the image
(b, g, r) = roi_copy[y, x] # roi_copy is the actual image
if g == 255 and b == 0 and r == 0:
xLowestContour = x
yLowestContour = y
This code works. But there is a big problem with it. It looks like that this way of searching for a specific pixel in an image is very inefficient. The framerate drops from 25 FPS to 2 FPS with this codesnippet. The CPU utilization is only at 10 % when using this codesnippet.
Is there a more efficient way to do this operation? I would also like to utilize more CPU power and achieve a higher framerate.
Upvotes: 1
Views: 1417
Reputation: 520
There is an obvious problem in the accepted answer because the last coordinates that we can see are [539, 677]. Well, there is neither a green pixel at (539,677) nor at (677,539). The problem here is, that it checks if the condition is somewhere True in the RGB value. If only one value is True, then all the others are True as well.
def argwhere(pic, color):
a = np.argwhere(pic == color)
return np.delete(a, 2, 1)
exa2 = argwhere(pic=img, color=[0, 255, 0])
for e in exa2:
if np.sum(img[e[0], e[1]]) != 255:
print(img[e[0], e[1]])
# ....
# [0 0 0]
# [0 0 0]
# [0 0 0]
# [0 0 0]
# [0 0 0]
# [255 255 255]
# [255 255 255]
# [255 255 255]
# [255 255 255]
# [255 255 255]
# [255 255 255]
# ....
This week, I found out about Numexpr https://github.com/pydata/numexpr (thanks to Stackoverflow), and wrote a little function which is 5x faster than np.argwhere(image == [0,255,0]) and probably a 100 times faster than PIL. And what's even better: it checks all RGB values :)
import numexpr
import numpy as np
def search_colors(pic, colors):
colorstosearch = colors
red = pic[..., 0]
green = pic[..., 1]
blue = pic[..., 2]
wholedict = {"blue": blue, "green": green, "red": red}
wholecommand = ""
for ini, co in enumerate(colorstosearch):
for ini2, col in enumerate(co):
wholedict[f"varall{ini}_{ini2}"] = np.array([col]).astype(np.uint8)
wholecommand += f"((red == varall{ini}_0) & (green == varall{ini}_1) & (blue == varall{ini}_2))|"
wholecommand = wholecommand.strip("|")
expre = numexpr.evaluate(wholecommand, local_dict=wholedict)
exa = np.array(np.where(expre)).T[::-1]
return np.vstack([exa[..., 1], exa[..., 0]]).T
import cv2
path = r"C:\Users\Gamer\Documents\Downloads\jFDSk.png"
img = cv2.imread(path)
exa1 = search_colors(pic=img, colors=[(0, 255, 0)])
coords = exa1[np.where(np.max(exa1[..., 1]))[0]]
print(coords)
#array([[324, 66]], dtype=int64)
It is blazing fast: (1 ms / Intel i5)
%timeit search_colors(pic=img, colors=[(0,255,0)])
1.05 ms ± 7.97 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
%timeit argwhere(pic=img, color=[0, 255, 0])
4.68 ms ± 63.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Upvotes: 0
Reputation: 386
def get_last_pixel(image, rgb):
color_rows, color_cols = np.where((image[:, :, 0] == rgb[0]) & (image[:, :, 1] == rgb[1]) & (
image[:, :, 2] == rgb[2])) # Get coordinates of pixels wit the requested rgb value
if len(color_rows) == 0:
raise Exception("no pixels of the requested color")
row_max = np.argmax(color_rows) # Find the index of the biggest row
pixel_coords = (color_rows[row_max], color_cols[row_max])
return pixel_coords
example_input = cv2.imread("example_input.png")
last_green = get_last_pixel(example_input, (0, 255, 0))
example_output = cv2.circle(
example_input, last_green[::-1], radius=5, color=(0, 0, 255), thickness=1)
cv2.imwrite("example_output.png", example_output)
Upvotes: 0
Reputation: 21203
Avoid loops, use numpy
!
You can use numpy's argwhere()
. The statement returns an array of coordinates where a certain condition is satified.
a = np.argwhere(image == [0,255,0])
# returns array a with three columns,
# since you need only the coordinates, you can delete the third column:
a = np.delete(a, 2, 1) # delete third column in a
a
would return following sample:
array([[ 0, 18],
[ 0, 19],
[ 0, 21],
...,
[539, 675],
[539, 677],
[539, 677]], dtype=int64)
The above returns coordinates where the green pixel values [0, 255, 0]
are present.
Based on the returned coordinates, you can filter the ones closer to the bottom of the image.
Upvotes: 2