Identify all values that are close to zero - python

I have a numpy array and I am minusing a constant value from the array. I want the values to go negative if necessary (and not wrap around or floor to zero). I then need to extract all array values that are around zero and produce a new binary array/image. So the resulting image will show white where in the areas that were close to zero.
I have tried to implement this but its hacky and I'm not sure if its correct. Can you assist what I am trying to do above?
# roi is a numpy array/image in Cielab colour space
swatch_colour = (255, 10, 30) # Cielab colour space
swatch_roi = np.full((roi.shape[0], roi.shape[1], 3), swatch_colour, dtype='int8')
int_roi = roi.astype('int8')
diff = np.subtract(int_roi, swatch_roi)
thresh = diff.copy()
# Get all pixels whose Cielab colour is close to zero
thresh[np.abs(thresh) < (12,6,12)] = 0
# the remaining pixels are greater than/less than the above threshold
thresh[np.abs(thresh) > (0,0,0)] = 255
thresh = thresh.astype('uint8')
# convert from 3 channels to 1 channel
thresh = cv2.cvtColor(thresh, cv2.COLOR_BGR2GRAY)
# Invert the image so that the pixels that were close to zero are white
thresh = cv2.bitwise_not(thresh)

Numpy can index slice logical operators
why can you do something like?
image[image.logical_and( image > -05 , image < 05 )]
https://docs.scipy.org/doc/numpy/reference/generated/numpy.logical_and.html

Related

HSV colour range in openCV

I wrote a program that used trackbars, to find out the appropriate HSV values (range) for segmenting out the white lines from the image.
For a long time this seemed like the best shot:
But its still not very accurate, its leaving out chunks of the line...
After messing around some more, I realised something:
This is very accurate: apart from the fact that the black and white regions are swapped.
Is there any way to invert this colour scheme to swap the black and white regions?
If not, what exactly can I do to not leave out chunks of the line like the first image...I have tried out various HSV combinations and it seems like this is the closest I can get.
code:
import cv2 as cv
import numpy as np
def nothing(x):
pass
img= cv.imread("ti2.jpeg")
cv.namedWindow("image") #create a window that will contain the trackbars for HSV values
cv.createTrackbar('HMin','image',0,179,nothing)
cv.createTrackbar('SMin','image',0,255,nothing)
cv.createTrackbar('VMin','image',0,255,nothing)
cv.createTrackbar('HMax','image',0,179,nothing)
cv.createTrackbar('SMax','image',0,255,nothing)
cv.createTrackbar('VMax','image',0,255,nothing)
cv.setTrackbarPos('HMax', 'image', 179) #setting default trackbar pos for max HSV values at max
cv.setTrackbarPos('SMax', 'image', 255)
cv.setTrackbarPos('VMax', 'image', 255)
while True:
hMin = cv.getTrackbarPos('HMin','image') #get the current slider position
sMin = cv.getTrackbarPos('SMin','image')
vMin = cv.getTrackbarPos('VMin','image')
hMax = cv.getTrackbarPos('HMax','image')
sMax = cv.getTrackbarPos('SMax','image')
vMax = cv.getTrackbarPos('VMax','image')
hsv=cv.cvtColor(img, cv.COLOR_BGR2HSV)
lower=np.array([hMin,sMin,vMin])
upper=np.array([hMax,sMax,vMax])
mask=cv.inRange(hsv,lower,upper)
#result=cv.bitwise_and(frame,frame,mask=mask)
cv.imshow("img",img)
cv.imshow("mask",mask)
#cv.imshow("result",result)
k=cv.waitKey(1)
if k==27 :
break
cv.destroyAllWindows()
Test Image:
To invert the mask
mask = 255-mask # if mask is a uint8 which ranges 0 to 255
mask = 1-mask # if mask is a bool which is either 0 or 1

How to find a specific point on a contour in opencv/python

I have used opencv to create some contours, and I need to identify a specific point on a contour, which is usually the innermost point of a 'V' shape. In the attached image, the point I want to identify is shown by the green arrows.
On the left is an easy case, where identification can be done (for example) by computing a convex hull of the contour, and then finding the point furthest from the hull.
However, on the right of the attached image is a much more difficult case, where instead of 1 contour, I get several, and the nice 'V' shape is not present, making it impossible to identify the innermost point of the 'V'. As shown by the red dotted line, one solution might be to extrapolate the higher contour until it intersects with the lower one. Does anyone know how I might go about this? Or have a better solution?
For the record I have tried:
dilation/erosion (works when multiple contours are close together, otherwise not)
hough transform p (tends to mislocate the target point)
Any pointers would be hugely appreciated.
This solution will work for the two images that you provided. This should also be a good solution for all other images that have a similar coloration and a 'v' shape (or at least a partial 'v' shape) that points to the right.
Let's take a look at the easier image first. I started by segmenting the image using color spaces.
# Convert frame to hsv color space
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Define range of pink color in HSV
(b,r,g,b1,r1,g1) = 0,0,0,110,255,255
lower = np.array([b,r,g])
upper = np.array([b1,r1,g1])
# Threshold the HSV image to get only pink colors
mask = cv2.inRange(hsv, lower, upper)
Next, I found the mid_point where there was an equal amount of white above and below that row.
# Calculate the mid point
mid_point = 1
top, bottom = 0, 1
while top < bottom:
top = sum(sum(mask[:mid_point, :]))
bottom = sum(sum(mask[mid_point:, :]))
mid_point += 1
Then, I floodfilled the image starting at the midpoint:
bg = np.zeros((h+2, w+2), np.uint8)
kernel = np.ones((k_size, k_size),np.uint8)
cv2.floodFill(mask, bg, (0, mid_point), 123)
Now that I have the floodfilled image, I know the point that I am looking for is the gray pixel that is the closest to the right side of the image.
# Find the gray pixel that is furthest to the right
idx = 0
while True:
column = mask_temp[:,idx:idx+1]
element_id, gray_px, found = 0, [], False
for element in column:
if element == 123:
v_point = idx, element_id
found = True
element_id += 1
# If no gray pixel is found, break out of the loop
if not found: break
idx += 1
The result:
Now for the harder image. In the image on the right, the 'v' does not fully connect:
To close the 'v', I iteratively dilated the mask checked if it connected:
# Flood fill and dilate loop
k_size, iters = 1, 1
while True:
bg = np.zeros((h+2, w+2), np.uint8)
mask_temp = mask.copy()
kernel = np.ones((k_size, k_size),np.uint8)
mask_temp = cv2.dilate(mask_temp,kernel,iterations = iters)
cv2.floodFill(mask_temp, bg, (0, mid_point), 123)
cv2.imshow('mask', mask_temp)
cv2.waitKey()
k_size += 1
iters += 1
# Break out of the loop of the right side of the image is black
if mask_temp[h-1,w-1]==0 and mask_temp[1, w-1]==0: break
This is the resulting output:

HSL range for yellow lane lines

I am currently working on simple lane detection and I have some trouble finding the range/input values for yellow colored lane lines.
def color_filter(image):
#convert to HLS to mask based on HLS
hls = cv2.cvtColor(image, cv2.COLOR_BGR2HLS)
lower = np.array([0,190,0])
upper = np.array([255,255,255])
yellower = np.array([40,70,60]) #NOT SURE WHAT TO PUT
yelupper = np.array([50,90,65]) #NOT SURE WHAT TO PUT
yellowmask = cv2.inRange(hls, yellower, yelupper)
whitemask = cv2.inRange(hls, lower, upper)
mask = cv2.bitwise_or(yellowmask, whitemask)
masked = cv2.bitwise_and(image, image, mask = mask)
return masked
Here is the image that I've filtered (only white lanes are showing):
http://prntscr.com/ng2cgp
Here's the original image:
http://prntscr.com/ng2cx6
I suggest, you have a further reading on how the HSL/HSV color space works, maybe starting at the Wikipedia article? Furthermore, to easily get some initial values to work on, you can use a HSL calculator, e.g. this one.
To detect white-ish parts in the image, the hue (H) value might by arbitrary, as long as the lightness (L) value is high enough (we want bright colors), and the saturation (S) value is low enough (we want low saturated colors).
In general, H values are within [0 ... 360], whereas S and L values are within [0.0 ... 1.0]. The OpenCV documentation on color conversions tells you, that these values are mapped to H within [0 ... 180], and S and L within [0 ... 255] (for 8-bit images).
Now, to detect yellow-ish parts in the image, appropriate H, S, and L values can be taken from the afore-mentioned HSL calculator by "playing around", what might fit to the colors to be found in the image.
I prepared the following example code, please have a look:
import cv2
import numpy as np
# Load input image
input = cv2.imread('images/input.png', cv2.IMREAD_COLOR)
# Convert to HLS color space
hls = cv2.cvtColor(input, cv2.COLOR_BGR2HLS)
# White-ish areas in image
# H value can be arbitrary, thus within [0 ... 360] (OpenCV: [0 ... 180])
# L value must be relatively high (we want high brightness), e.g. within [0.7 ... 1.0] (OpenCV: [0 ... 255])
# S value must be relatively low (we want low saturation), e.g. within [0.0 ... 0.3] (OpenCV: [0 ... 255])
white_lower = np.array([np.round( 0 / 2), np.round(0.75 * 255), np.round(0.00 * 255)])
white_upper = np.array([np.round(360 / 2), np.round(1.00 * 255), np.round(0.30 * 255)])
white_mask = cv2.inRange(hls, white_lower, white_upper)
# Yellow-ish areas in image
# H value must be appropriate (see HSL color space), e.g. within [40 ... 60]
# L value can be arbitrary (we want everything between bright and dark yellow), e.g. within [0.0 ... 1.0]
# S value must be above some threshold (we want at least some saturation), e.g. within [0.35 ... 1.0]
yellow_lower = np.array([np.round( 40 / 2), np.round(0.00 * 255), np.round(0.35 * 255)])
yellow_upper = np.array([np.round( 60 / 2), np.round(1.00 * 255), np.round(1.00 * 255)])
yellow_mask = cv2.inRange(hls, yellow_lower, yellow_upper)
# Calculate combined mask, and masked image
mask = cv2.bitwise_or(yellow_mask, white_mask)
masked = cv2.bitwise_and(input, input, mask = mask)
# Write output images
cv2.imwrite('images/white_mask.png', white_mask)
cv2.imwrite('images/yellow_mask.png', yellow_mask)
cv2.imwrite('images/masked.png', masked)
The white-ish mask looks like this:
The yellow-ish mask looks like this:
The masked image from your code looks like this:
As you can see, fine-tuning the parameters must be done. But I hope, you now get the general idea, and can continue on your own.

Python 3: I am trying to find find all green pixels in an image by traversing all pixels using an np.array, but can't get around index error

My code currently consists of loading the image, which is successful and I don't believe has any connection to the problem.
Then I go on to transform the color image into a np.array named rgb
# convert image into array
rgb = np.array(img)
red = rgb[:,:,0]
green = rgb[:,:,1]
blue = rgb[:,:,2]
To double check my understanding of this array, in case that may be the root of the issue, it is an array such that rgb[x-coordinate, y-coordinate, color band] which holds the value between 0-255 of either red, green or blue.
Then, my idea was to make a nested for loop to traverse all pixels of my image (620px,400px) and sort them based on the ratio of green to blue and red in an attempt to single out the greener pixels and set all others to black or 0.
for i in range(xsize):
for j in range(ysize):
color = rgb[i,j] <-- Index error occurs here
if(color[0] > 128):
if(color[1] < 128):
if(color[2] > 128):
rgb[i,j] = [0,0,0]
The error I am receiving when trying to run this is as follows:
IndexError: index 400 is out of bounds for axis 0 with size 400
I thought it may have something to do with the bounds I was giving i and j so I tried only sorting through a small inner portion of the image but still got the same error. At this point I am lost as to what is even the root of the error let alone even the solution.
In direct answer to your question, the y axis is given first in numpy arrays, followed by the x axis, so interchange your indices.
Less directly, you will find that for loops are very slow in Python and you are generally better off using numpy vectorised operations instead. Also, you will often find it easier to find shades of green in HSV colourspace.
Let's start with an HSL colour wheel:
and assume you want to make all the greens into black. So, from that Wikipedia page, the Hue corresponding to Green is 120 degrees, which means you could do this:
#!/usr/local/bin/python3
import numpy as np
from PIL import Image
# Open image and make RGB and HSV versions
RGBim = Image.open("image.png").convert('RGB')
HSVim = RGBim.convert('HSV')
# Make numpy versions
RGBna = np.array(RGBim)
HSVna = np.array(HSVim)
# Extract Hue
H = HSVna[:,:,0]
# Find all green pixels, i.e. where 100 < Hue < 140
lo,hi = 100,140
# Rescale to 0-255, rather than 0-360 because we are using uint8
lo = int((lo * 255) / 360)
hi = int((hi * 255) / 360)
green = np.where((H>lo) & (H<hi))
# Make all green pixels black in original image
RGBna[green] = [0,0,0]
count = green[0].size
print("Pixels matched: {}".format(count))
Image.fromarray(RGBna).save('result.png')
Which gives:
Here is a slightly improved version that retains the alpha/transparency, and matches red pixels for extra fun:
#!/usr/local/bin/python3
import numpy as np
from PIL import Image
# Open image and make RGB and HSV versions
im = Image.open("image.png")
# Save Alpha if present, then remove
if 'A' in im.getbands():
savedAlpha = im.getchannel('A')
im = im.convert('RGB')
# Make HSV version
HSVim = im.convert('HSV')
# Make numpy versions
RGBna = np.array(im)
HSVna = np.array(HSVim)
# Extract Hue
H = HSVna[:,:,0]
# Find all red pixels, i.e. where 340 < Hue < 20
lo,hi = 340,20
# Rescale to 0-255, rather than 0-360 because we are using uint8
lo = int((lo * 255) / 360)
hi = int((hi * 255) / 360)
red = np.where((H>lo) | (H<hi))
# Make all red pixels black in original image
RGBna[red] = [0,0,0]
count = red[0].size
print("Pixels matched: {}".format(count))
result=Image.fromarray(RGBna)
# Replace Alpha if originally present
if savedAlpha is not None:
result.putalpha(savedAlpha)
result.save('result.png')
Keywords: Image processing, PIL, Pillow, Hue Saturation Value, HSV, HSL, color ranges, colour ranges, range, prime.

OpenCV maximum and minimum RGB value using mask

I need to get the minimum RGB value of a circle. How can I do like the average RGB value method (cv2.mean) appliying a mask? To get the average RGB value of a circle I'm doing:
circle_img = np.zeros((circle_img.shape[0],circle_img.shape[1]), np.uint8)
cv2.circle(circle_img,(x_center,y_center),radio,(255,255,255),-1)
datos_rgb = cv2.mean(color_img, mask=circle_img)
Where color_img is the original image.
To get the minimum RGB value I'm doing:
masked_data = cv2.bitwise_and(color_img, color_img, mask=circle_img)
rgb_min = masked_data.reshape((masked_data.shape[0]*masked_data.shape[1], 3)).min(axis=0)
Where masked_data is the second image (masked circle).
But I'm getting all time the value [0,0,0] because of the background I think... I need to do like the average (cv2.mean) apliying the mask to ignore the black background. There is no pure black in the original image, so it is not possible to get the value [0,0,0]
To get the maximum RGB value it works perfectly doing:
masked_data = cv2.bitwise_and(color_img, color_img, mask=circle_img)
rgb_max = masked_data.reshape((masked_data.shape[0]*masked_data.shape[1], 3)).max(axis=0)
Because the black color [0,0,0] it does not affect here.
This is the original image.
This is the masked circle.
You may try using only numpy methods to get the results for all required calculations, rather than using OpenCV for some and numpy for others, and in some cases numpy can out-perform OpenCV in terms of execution time. You may use numpys' min, max and mean as:
import cv2
import numpy as np
img = cv2.imread("./assets/11yeJ.jpg")
mask = np.zeros((img.shape[0],img.shape[1]), np.uint8)
cv2.circle(mask, (493, 338), 30, (255, 255, 255), -1)
# Get the indices of mask where value == 255, which may be later used to slice the array.
img_mask = img[np.where(mask == 255)]
img_avg = np.mean(img_mask, axis=0)
img_min = np.min(img_mask, axis=0)
img_max = np.max(img_mask, axis=0)

Categories

Resources