OpenCV - Lane detetion, both yellow and white lanes - python

I have road photos from the racing game.
I want to detect yellow and white lanes.
If I use RGB space,
def select_rgb_white_yellow(image):
# white color mask
lower = np.uint8([123, 116, 116])
upper = np.uint8([186, 172, 160])
white_mask = cv2.inRange(image, lower, upper)
# yellow color mask
lower = np.uint8([134, 121, 100])
upper = np.uint8([206, 155, 87])
yellow_mask = cv2.inRange(image, lower, upper)
# combine the mask
mask = cv2.bitwise_or(white_mask, yellow_mask)
masked = cv2.bitwise_and(image, image, mask = mask)
return masked
I only get white lanes.
So tweaked little bit, using HLS space, by using this code.
def convert_hls(image):
return cv2.cvtColor(image, cv2.COLOR_RGB2HLS)
Then, extracting the yellow and white lane again,
def select_white_yellow(image):
converted = convert_hls(image)
# white color mask
lower = np.uint8([0, 0, 0])
upper = np.uint8([0, 0, 255])
white_mask = cv2.inRange(converted, lower, upper)
# yellow color mask
lower = np.uint8([ 10, 0, 100])
upper = np.uint8([ 40, 255, 255])
yellow_mask = cv2.inRange(converted, lower, upper)
# combine the mask
mask = cv2.bitwise_or(white_mask, yellow_mask)
return cv2.bitwise_and(image, image, mask = mask)
Then it cannot detect white lane anymore. Is there good way to detect both white and yellow lane?
Here are all RGB colour code that I found
Yellow
c2974A (194, 149, 74)
a07444 (160, 116, 68)
b38e55 (179, 142, 85)
867964 (134, 121, 100)
ce9b57 (206, 155, 87)
ce9853 (206, 152, 83)
white
b4a59d (180, 165, 157)
b9a99a (185, 169, 154)
baaca0 (186, 172, 160)
867e79 (134, 126, 121)
7b7474 (123, 116, 116)
827d7c (130, 125, 124)

To extend the comment: this is an implementation of a 2 stage approach. Take some time to look at the intermediate images/masks to fully understand everything that happens.
Cropping to region of interest
You can automate this, but I cheated a little and did it manually. The cropped sky area will rarely ever have road surface, this is an easy solution that suffices (for now) I think. Similarly, I also cut of the HUD boxes on the right hand side, as they have similar gray colors as the road and were interfering. It's more neat to draw black boxes over them, so they are excluded from processing.
Isolating road
Convert the cropped image to HSV an select only gray-ish values. After some noise removal I improved the mask using findContours to draw the convexhull. If performance is an issue, you could maybe skip it by tweaking the close mask step.
Selecting lines
Using the mask you can create an image of only the road surface. You can use this for color separation without having to worry about also selecting surroundings. My result is not perfect, but I assume you have larger versions of the images which will give better results.
Result:
Code:
import cv2
import numpy as np
# load image
image = cv2.imread('pw12b.jpg')
# crop image
h,w = image.shape[:2]
image = image[200:h-20,20:550]
# create hsv
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# set lower and upper color limits
low_val = (0,0,0)
high_val = (179,45,96)
# Threshold the HSV image
mask = cv2.inRange(hsv, low_val,high_val)
# remove noise
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel=np.ones((8,8),dtype=np.uint8))
# close mask
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel=np.ones((20,20),dtype=np.uint8))
# improve mask by drawing the convexhull
ret, contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
hull = cv2.convexHull(cnt)
cv2.drawContours(mask,[hull],0,(255), -1)
# erode mask a bit to migitate mask bleed of convexhull
mask = cv2.morphologyEx(mask, cv2.MORPH_ERODE, kernel=np.ones((5,5),dtype=np.uint8))
# remove this line, used to show intermediate result of masked road
road = cv2.bitwise_and(image, image,mask=mask)
# apply mask to hsv image
road_hsv = cv2.bitwise_and(hsv, hsv,mask=mask)
# set lower and upper color limits
low_val = (0,0,102)
high_val = (179,255,255)
# Threshold the HSV image
mask2 = cv2.inRange(road_hsv, low_val,high_val)
# apply mask to original image
result = cv2.bitwise_and(image, image,mask=mask2)
#show image
cv2.imshow("Result", result)
cv2.imshow("Road", road)
cv2.imshow("Mask", mask)
cv2.imshow("Image", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Related

Identify red colored parts of an image and extract only their intensity - Python

I am trying to analyze the red wells with different intensities from the attached image.
I want to analyze how the intensity of each red colored well differs from the others.
Does anyone have a solution without using the following code:
import numpy as np
import argparse
import cv2
# Construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", help="path to the image")
args = vars(ap.parse_args())
# Load the image
image = cv2.imread(args["image"])
# Define the list of boundaries
boundaries = [
([17, 15, 100], [50, 56, 200]),
([86, 31, 4], [220, 88, 50]),
([25, 146, 190], [62, 174, 250]),
([103, 86, 65], [145, 133, 128])
]
# Loop over the boundaries
for (lower, upper) in boundaries:
# Create NumPy arrays from the boundaries
lower = np.array(lower, dtype="uint8")
upper = np.array(upper, dtype="uint8")
# Find the colors within the specified boundaries and apply the mask
mask = cv2.inRange(image, lower, upper)
output = cv2.bitwise_and(image, image, mask=mask)
Image I'm trying to process:
Assume we want to automatically find the red wells.
Assume we know that the wells are circular, and that the color is red (reddish).
We also know that the wells are not tiny, but we don't want to overfit the solution too much...
We may use the following stages:
Use cv2.bilateralFilter for reducing noise (while keeping edges fairly sharp).
Find red pixels as described in the following post.
Convert from BGR to HSV, and find the pixels in range that is considered red.
According to the post there are "lower_red" and "upper_red" ranges (we are keeping the ranges without tuning).
Find contours.
Iterate the contours.
Skip small contours (assumed to be noise).
Use cv2.minEnclosingCircle as described here.
Draw a filled circle for each contour, to form a mask, and use cv2.bitwise_and as used in your question.
Code sample:
import numpy as np
import argparse
import cv2
# Construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", help="path to the image")
args = vars(ap.parse_args())
# Load the image
image = cv2.imread(args["image"])
img = cv2.bilateralFilter(image, 11, 75, 75)
# https://stackoverflow.com/questions/30331944/finding-red-color-in-image-using-python-opencv
# Convert from BGR to HSV
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Lower mask (0-10)
lower_red = np.array([0, 50, 50])
upper_red = np.array([10, 255, 255])
mask0 = cv2.inRange(img_hsv, lower_red, upper_red)
# Upper mask (170-180)
lower_red = np.array([170, 50, 50])
upper_red = np.array([180, 255, 255])
mask1 = cv2.inRange(img_hsv, lower_red, upper_red)
# Join the masks
raw_mask = mask0 | mask1
ctns = cv2.findContours(raw_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2] # Find contours
mask = np.zeros_like(raw_mask) # Fill mask with zeros
idx = 0
# Iterate contours
for c in ctns:
area = cv2.contourArea(c) # Find the area of each contours
if (area > 50): # Ignore small contours (assume noise).
cv2.drawContours(mask, [c], 0, 255, -1)
# https://docs.opencv.org/3.4/dd/d49/tutorial_py_contour_features.html
(x, y), radius = cv2.minEnclosingCircle(c)
center = (int(x), int(y))
radius = int(radius)
cv2.circle(mask, center, radius, 255, -1)
tmp_mask = np.zeros_like(mask)
cv2.circle(tmp_mask, center, radius, 255, -1)
output = cv2.bitwise_and(image, image, mask=tmp_mask)
cv2.imshow(f'output{idx}', output) # Show output images for testing
cv2.imwrite(f'output{idx}.png', output) # Save output images for testing
idx += 1
cv2.imshow('image', image)
cv2.imshow('img', img)
cv2.imshow('raw_mask', raw_mask)
cv2.imshow('mask', mask)
cv2.waitKey()
cv2.destroyAllWindows()
Results:
Image after bilateral filter:
raw_mask:
mask:
output0.png:
output1.png:
output2.png:
output3.png:
output4.png:
In case the circles are too large, we can reduce the radius, and get only the center of each well.

How to detect the end of a crop row with opencv?

I am writing a program to detect crop rows in uav imagery and I have successfully managed to detect crop rows in cropped sections of the image using opencv and python, however the issue I am running into now is how to go about detecting the end of a crop row.
The image is of a field(Cant share it publicly) and I am attempting to detect all the rows in that field, I can detect the rows however currently I am simply draw the line along the row from the top to bottom of the image. Instead I would like draw this line from the start to end of the crop row.
i.e. smething like this
https://i.stack.imgur.com/WUkg6.jpg (Not enough rep to post images yet)
But essentially I need to detect the end of the row so I can draw the line to the just the end of the row.
Any Ideas on how this might be done?
Use the Probabilistic Hough Line Transform:
import cv2
import numpy as np
cap = cv2.VideoCapture(0)
frame = cv2.imread("crop.jpg")
# It converts the BGR color space of image to HSV color space
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# Threshold of blue in HSV space
lower_green = np.array([21, 115, 0])
upper_green = np.array([179, 255, 255])
# preparing the mask to overlay
mask = cv2.inRange(hsv, lower_green, upper_green)
mask = cv2.blur(mask, (5,5))
ret, mask = cv2.threshold(mask, 128, 255, cv2.THRESH_BINARY)
edges = cv2.Canny(mask,100,200)
# The black region in the mask has the value of 0,
# so when multiplied with original image removes all non-blue regions
result = cv2.bitwise_and(frame, frame, mask = mask)
cdst = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
linesP = cv2.HoughLinesP(edges, 1, np.pi / 180, 50, None, 100, 30)
if linesP is not None:
for i in range(0, len(linesP)):
l = linesP[i][0]
cv2.line(cdst, (l[0], l[1]), (l[2], l[3]), (0,0,255), 3, cv2.LINE_AA)
cv2.imshow("Detected Lines (in red) - Probabilistic Line Transform", cdst)
cv2.imshow('frame', frame)
cv2.imshow('mask', mask)
cv2.imshow('result', result)
cv2.waitKey(-1)

How to blur red color in image with python opencv so that its not clearly visible?

I want to blur red color in image ("1.png" is attached) so that its not clearly visible. I tried below code where I can change red color to black color but how can I blur it? Please help.
import cv2
import numpy as np
frame = cv2.imread("1.png")
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# define range of red color in HSV
lower = np.array([0,50,50])
upper = np.array([10,255,255])
# define range of blue color in HSV
# lower = np.array([38, 86, 0])
# upper = np.array([121, 255, 255])
# define range of pink color in HSV
# http://www.workwithcolor.com/pink-color-hue-range-01.htm
# lower = np.array([158, 127, 0])
# upper = np.array([179, 255, 255])
# Threshold the HSV image to get only red colors
mask = cv2.inRange(hsv, lower, upper)
color_only = cv2.bitwise_and(frame, frame, mask = mask)
# convert mask to 3-channel image to perform subtract
mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
res = cv2.subtract(frame, mask) #negative values become 0 -> black
cv2.imshow("frame", frame)
# cv2.imshow("mask", mask)
# cv2.imshow("color_only", color_only)
cv2.imshow("res", res)
cv2.waitKey()
cv2.destroyAllWindows()
1.png
You need to blur the segmented image and then use alpha blending to composite the blurred ROI with the background image. This code takes you through all the steps:
Read image and segment the color of interest:
import cv2
import numpy as np
frame = cv2.imread("/home/stephen/Desktop/1.png")
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# define range of red color in HSV
lower = np.array([0,50,50])
upper = np.array([10,255,255])
mask = cv2.inRange(hsv, lower, upper)
color_only = cv2.bitwise_and(frame, frame, mask = mask)
### THE BACKGROUND MUST BE MADE WHITE, NOT BLACK ###
color_only[np.where((color_only==[0,0,0]).all(axis=2))] = [255,255,255]
cv2.imshow("color_only", color_only)
Next, blur the segmented image. Note, I am using a 13,13 kernel to blur the image:
blur = cv2.blur(color_only, (13,13))
cv2.imshow('blur', blur)
Next, blur the mask of the segmented image. We are going to use this to combine the images later. It's easy to combine images using a bitwise function, but that approach will not work here because the blurred image no longer occupies the same space as the segmented image:
maskForAlphaBlending = blur
## BLACK OUT THE WHITE BACKGROUND OF THE ALPHA MASK
maskForAlphaBlending[np.where((maskForAlphaBlending==[255,255,255]).all(axis=2))] = [0,0,0]
maskForAlphaBlending = cv2.cvtColor(maskForAlphaBlending, cv2.COLOR_BGR2GRAY)
cv2.imshow('maskForAlphaBlending', maskForAlphaBlending)
Finally alpha blending can be used to composite the blurred segmented image and the green and white background image:
foreground = blur
background = frame
alpha = cv2.cvtColor(maskForAlphaBlending, cv2.COLOR_GRAY2BGR)
foreground = foreground.astype(float)
background = background.astype(float)
alpha = alpha.astype(float)/100
foreground = cv2.multiply(alpha, foreground)
background = cv2.multiply(1.0 - alpha, background)
outImage = cv2.add(foreground, background)
cv2.imshow("outImg", outImage/255)
Note how the red lines are blurred, but the green and white border is not:

Removing White Text with Black Borders From Image

I am trying to remove text from images that has a black border with white fill. Take the image below as an example.
I have tried a few options utilizing opencv and skimage inpaint
import cv2
from skimage.restoration import inpaint
img = cv2.imread('Documents/test_image.png')
mask = cv2.threshold(img, 210, 255, cv2.THRESH_BINARY)[1][:,:,0]
dst = cv2.inpaint(img, mask, 7, cv2.INPAINT_TELEA)
image_result = inpaint.inpaint_biharmonic(img, mask,
multichannel=True)
cv2.imshow('image',img)
cv2.imshow('mask',mask)
cv2.imshow('dst',dst)
cv2.imshow('image_result',image_result)
cv2.waitKey(0)
It seems like the inpainting is just trying to fill with black as that is what it is identifying as being around the areas of interest. What I would like to do is remove the white text and black borders completely, or secondarily try to fill the white with more information from surrounding colors than just the black.
Here is the best solution I could come up with, still open to others with more experience showing me a better way if anyone has an idea.
mask = cv2.threshold(img, 245, 255, cv2.THRESH_BINARY)[1][:,:,0]
new_mask = cv2.dilate(mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (10,10)))
dst = cv2.inpaint(img, new_mask, 7, cv2.INPAINT_TELEA)
Here are two inpainting methods in Python/OpenCV. Note that I use the saturation channel to create the threshold, since white and black have zero saturation, in principle.
Input:
import cv2
import numpy as np
# read input
img = cv2.imread('white_black_text.png')
# convert to hsv and extract saturation
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
sat = hsv[:,:,1]
# threshold and invert
thresh = cv2.threshold(sat, 10, 255, cv2.THRESH_BINARY)[1]
thresh = 255 - thresh
# apply morphology dilate
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15,15))
thresh = cv2.morphologyEx(thresh, cv2.MORPH_DILATE, kernel)
# do inpainting
result1 = cv2.inpaint(img,thresh,11,cv2.INPAINT_TELEA)
result2 = cv2.inpaint(img,thresh,11,cv2.INPAINT_NS)
# save results
cv2.imwrite('white_black_text_threshold.png', thresh)
cv2.imwrite('white_black_text_inpainted1.png', result1)
cv2.imwrite('white_black_text_inpainted2.png', result1)
# show results
cv2.imshow('thresh',thresh)
cv2.imshow('result1',result1)
cv2.imshow('result2',result2)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold and morphology cleaned result:
Result 1 (Telea):
Result 2 (Navier Stokes):

Changing colours of an area in an image using opencv in python

I have a picture were I want to change all white-ish pixels to grey, but only for a certain area of the image. Example picture, I just want to change the picture outside of the red rectangle, without changing the image within the red rectangle:
I already have the general code, which was part of someone elses Stackoverflow question, that changes the colour of every white pixel instead of only just the one outside of an area.
image = cv.imread("meme 2.jpg")
hsv = cv.cvtColor(image, cv.COLOR_BGR2HSV)
# Define lower and uppper limits of what we call "white-ish"
sensitivity = 19
lower_white = np.array([0, 0, 255 - sensitivity])
upper_white = np.array([255, sensitivity, 255])
# Mask image to only select white
mask = cv.inRange(hsv, lower_white, upper_white)
# Change image to grey where we found brown
image[mask > 0] = (170, 170, 170)
cv.imwrite(file, image)
Here is one way to do that in Python/OpenCV.
Read the input
Convert to HSV color space
Threshold on desired color to make a mask
Use the mask to change the color of all corresponding pixels in the image
Draw a new rectangular mask for the region where you do not want to change
Invert the new mask for the region where you do want to change
Apply the new mask to the original image
Apply the inverted new mask to the color changed image
Add the two results together to form the final image
Save the results
Input:
import cv2
import numpy as np
# Read image
image = cv2.imread('4animals.jpg')
# Convert to HSV
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# Define lower and uppper limits of what we call "white-ish"
sensitivity = 19
lower_white = np.array([0, 0, 255 - sensitivity])
upper_white = np.array([255, sensitivity, 255])
# Create mask to only select white
mask = cv2.inRange(hsv, lower_white, upper_white)
# Change image to grey where we found white
image2 = image.copy()
image2[mask > 0] = (170, 170, 170)
# Create new rectangular mask that is white on black background
x,y,w,h = 33,100,430,550
mask2 = np.zeros_like(image)
cv2.rectangle(mask2, (x,y), (x+w,y+h), (255, 255, 255), -1)
# invert mask
mask2_inv = 255 - mask2
# apply mask to image
image_masked = cv2.bitwise_and(image, mask2)
# apply inverted mask to image2
image2_masked = cv2.bitwise_and(image2, mask2_inv)
# add together
result = cv2.add(image_masked, image2_masked)
# save results
cv2.imwrite('4animals_mask.jpg', mask)
cv2.imwrite('4animals_modified.png', image2)
cv2.imwrite('4animals_mask2.jpg', mask2)
cv2.imwrite('4animals_mask2_inv.jpg', mask2_inv)
cv2.imwrite('4animals_masked.jpg', image_masked)
cv2.imwrite('4animals_modified_masked.jpg', image2_masked)
cv2.imwrite('4animals_result.jpg', result)
cv2.imshow('mask', mask)
cv2.imshow('image2', image2)
cv2.imshow('mask2', mask2 )
cv2.imshow('mask2_inv', mask2_inv)
cv2.imshow('image_masked', image_masked)
cv2.imshow('image2_masked', image2_masked)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Color mask:
Rectangle mask:
Inverted rectangle mask:
Color changed image:
Masked input:
Masked color changed image:
Result:
Here is another simpler method in Python/OpenCV. My previous answer was overly complicated.
Read the input
Convert to HSV color space
Create a mask image by color thresholding
Draw a black rectangle on the previous mask for where you do not want to change the color
Apply the new combined mask to the image to change the color in the desired region
Save the result
Input:
import cv2
import numpy as np
# Read image
image = cv2.imread('4animals.jpg')
# Convert to HSV
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# Define lower and uppper limits of what we call "white-ish"
sensitivity = 19
lower_white = np.array([0, 0, 255 - sensitivity])
upper_white = np.array([255, sensitivity, 255])
# Create mask to only select white
mask = cv2.inRange(hsv, lower_white, upper_white)
# Draw new rectangular mask on old mask that is black inside the rectangle and white outside the rectangle
x,y,w,h = 33,100,430,550
mask2 = mask.copy()
cv2.rectangle(mask2, (x,y), (x+w,y+h), 0, -1)
# Change image to grey where we found white for combined mask
result = image.copy()
result[mask2 > 0] = (170, 170, 170)
# save results
cv2.imwrite('4animals_mask.jpg', mask)
cv2.imwrite('4animals_mask2.jpg', mask2)
cv2.imwrite('4animals_result.jpg', result)
cv2.imshow('mask', mask)
cv2.imshow('mask2', mask2 )
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Mask from color thresholding:
Modified mask with rectangle drawn over it:
Result:

Categories

Resources