Is there a more efficient way to search an image for a specific pixel?

Question:

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 620×420 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:

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.

Asked By: michael

||

Answers:

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.

Answered By: Jeru Luke
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)

Example input:
enter image description here

Example output:
enter image description here

Answered By: elbashmubarmeg

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:UsersGamerDocumentsDownloadsjFDSk.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)
Answered By: Hans