Skip to content Skip to sidebar Skip to footer

Opencv Python Pixel-by-pixel Loop Is Slow

I'm trying to compare every pixel's hue value with a threshold given. If the pixel's hue is between the threshold value given, it will draw a small circle to that particular pixel.

Solution 1:

I would suggest making this vectorized where you first find the circles that have a hue between 26 and 35 as well as hues between 36 and 77, then draw them on your image. cv2.circle is unfortunately not vectorized as it is designed to only take a single pair of coordinates but the search for the pixels of interest can be done faster.

Try:

import numpy as np
import cv2

img = cv2.imread("green bottle.jpg")
imgHSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
hue = imgHSV[...,0]
(rows1, cols1) = np.where(np.logical_and(hue >= 26, hue <= 35))
(rows2, cols2) = np.where(np.logical_and(hue >= 36, hue <= 77))

for r, c inzip(rows1, cols1):
    cv2.circle(img, (c, r), 1, (255, 0, 0))

for r, c inzip(rows2, cols2):
    cv2.circle(img, (c, r), 1, (0, 255, 0))

cv2.imshow("Image", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

numpy.where takes in a NumPy array and a condition to check for such that any values that meet this condition, we can obtain the row and column locations. I also use numpy.logical_and to properly incorporate the range search for the hues. I do this twice, once per range of hues which gives me two sets of coordinates. I then loop over each set of coordinates and plot the circles in their respective colours.

Solution 2:

I had a try and came up with something pretty much identical to @rayryeng like this but it was very slow for my image:

# Load image
im = cv2.imread('smooth.png')

# Convert to HSV and extract H
H = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)[..., 0]

# Mask Hues in range [26..35]
rangeA = np.logical_and(H>=26, H<=35)

# Get coordinates of selected pixels and plot circle at each
for y,x in np.argwhere(rangeA):
    cv2.circle(im, (x,y), 1, (255,255,255))

When I timed it, I realised all the time was taken by cv2.circle(). So I looked at the circle of radius 1 and it looks like this:

0 1 0
1 0 1
0 1 0

which is extremely similar to a 3x3 morphological cross:

0 1 0
1 1 1
0 1 0

So, I drew the circles with morphology rather than cv2.circle(), and I got this:

#!/usr/bin/env python3

import cv2
import numpy as np

# Load image
im = cv2.imread('smooth.png')

# Convert to HSV and extract H
H = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)[..., 0]

# Mask Hues in range [26..35]
rangeA = np.logical_and(H>=26, H<=35)
    
# Note that a circle radius 1 is a 3x3 cross, so rather than# draw circles we can convolve with a ring
ring = cv2.getStructuringElement(cv2.MORPH_CROSS, (3,3))
ring[1,1]=0

res = cv2.morphologyEx((rangeA*255).astype(np.uint8), cv2.MORPH_DILATE, ring)

# Save result
cv2.imwrite('result.png', res)

So, if I start with this:

enter image description here

I got this in a fraction of the time:

enter image description here

Timings on my image were 5ms as follows with for loop:

In [133]: %%timeit
     ...: for y,x in np.argwhere(rangeA):
     ...:     cv2.circle(im, (x,y), 1, (255,255,255))
     ...: 
4.98 ms ± 42.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

and 159 microseconds with dilation:

%timeit cv2.morphologyEx((rangeA*255).astype(np.uint8), cv2.MORPH_DILATE, ring)
159 µs ± 4.13 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Post a Comment for "Opencv Python Pixel-by-pixel Loop Is Slow"