I try to detect the yellow line in the following picture but shadows obscured the yellow roads. Do we have any method to deal with that?
We can detect the yellow in this question:About Line detection by using OpenCV and How to delete the other object from figure by using opencv?.
The coding is as follows:
import cv2
import numpy as np
image = cv2.imread('Road.jpg')
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
low_yellow = np.array([18, 94, 140])
up_yellow = np.array([48, 255, 255])
mask = cv2.inRange(hsv, low_yellow, up_yellow)
edges = cv2.Canny(mask, 75, 150)
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 50, maxLineGap=250)
for line in lines:
x1, y1, x2, y2 = line[0]
cv2.line(image, (x1, y1), (x2, y2), (0, 255, 0), 5)
# cv2.imshow('image', img)
cv2.imwrite("result.jpg", edges)
Here is the code to convert to lab and auto-threshold
You'll have to detect the lines using a proper method.
An advanced solution would be training a dataset to do segmentation (neural network Ex : Unet )
import cv2
import numpy as np
img = cv2.imread('YourImagePath.jpg')
cv2.imshow("Original", img)
k = cv2.waitKey(0)
# Convert To lab
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
# display b channel
cv2.imshow("Lab", lab[:, :, 2])
k = cv2.waitKey(0)
# auto threshold using Otsu
ret , mask = cv2.threshold(lab[:, :, 2] , 0 , 255 , cv2.THRESH_BINARY+
cv2.THRESH_OTSU)
#display Binary
cv2.imshow("Binary", mask)
k = cv2.waitKey(0)
cv2.destroyAllWindows()
Output:
Related
I have detected dotted lines using HoughLinesP on some image samples and draw red lines on them. For example, look at the image below:
The code for detecting the lines is below:
import cv2
import numpy as np
import math
img = cv2.imread("tabulapy/image2.png")
img = cv2.resize(img, (0, 0), fx=0.5, fy=0.5)
kernel1 = np.ones((1, 5), np.uint8)
kernel2 = np.ones((9, 9), np.uint8)
imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
imgBW = cv2.threshold(imgGray, 230, 255, cv2.THRESH_BINARY_INV)[1]
img1 = cv2.erode(imgBW, kernel1, iterations=1)
img2 = cv2.dilate(img1, kernel2, iterations=3)
img3 = cv2.bitwise_and(imgBW, img2)
img3 = cv2.bitwise_not(img3)
img4 = cv2.bitwise_and(imgBW, imgBW, mask=img3)
imgLines = cv2.HoughLinesP(img4, 1, np.pi / 180, 10, minLineLength=500, maxLineGap=2)
for i in range(len(imgLines)):
for x1, y1, x2, y2 in imgLines[i]:
cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
I want to crop this image into different parts based on the detected lines(red lines). What I mean, the first image would be from the top to the first detected red line; the second image would be the first detected red line to the second detected red line, and so on...
Does anyone have any idea how it can be done?
I am trying to extract the vertical lines from the fabric image using hough lines in opencv. I applied contrast enhancement to enhance the lines and bilateral filtering to try and remove the other fabric textures. However, on applying the houghlines, the code detects lines all over the image. I tried playing around with the parameters for hough but the results were the same.
Input image after applying histogram equalization and bilateral filter:
Here is the image after applying the hough line, red representing the detected lines.
Output showing hough detections:
What is another approach I can try so that the hough does not start detecting the minute fabric patterns as lines as well?
Here is the code I have:
`
img1= cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img2 = cv2.equalizeHist(img1)
img3 = cv2.equalizeHist(img2)
img4 = cv2.equalizeHist(img3)
img5 = cv2.bilateralFilter(img4, 9, 75,75)
cv2.imshow("threshold",img5)
edges = cv2.Canny(img4,50,127,apertureSize = 3)
lines= cv2.HoughLines(edges, 1, math.pi/180.0, 200, np.array([]), 0, 0)
a,b,c = lines.shape
for i in range(a):
rho = lines[i][0][0]
theta = lines[i][0][1]
a = math.cos(theta)
b = math.sin(theta)
x0, y0 = a*rho, b*rho
pt1 = ( int(x0+1000*(-b)), int(y0+1000*(a)) )
pt2 = ( int(x0-1000*(-b)), int(y0-1000*(a)) )
cv2.line(img, pt1, pt2, (0, 0, 255), 2, cv2.LINE_AA)
cv2.imshow('image1',img)
cv2.waitKey(0)
cv2.destroyAllWindows()`
You need to threshold your equalized image, apply morphology to clean it up before doing the canny edge detection and hough line extraction. Using Python/OpenCV to do the following processing.
Input:
import cv2
import numpy as np
import math
# read image
img = cv2.imread('fabric_equalized.png')
# convert to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# threshold
thresh = cv2.threshold(gray,165,255,cv2.THRESH_BINARY)[1]
# apply close to connect the white areas
kernel = np.ones((15,1), np.uint8)
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
kernel = np.ones((17,3), np.uint8)
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
# apply canny edge detection
edges = cv2.Canny(img, 175, 200)
# get hough lines
result = img.copy()
lines= cv2.HoughLines(edges, 1, math.pi/180.0, 165, np.array([]), 0, 0)
a,b,c = lines.shape
for i in range(a):
rho = lines[i][0][0]
theta = lines[i][0][1]
a = math.cos(theta)
b = math.sin(theta)
x0, y0 = a*rho, b*rho
pt1 = ( int(x0+1000*(-b)), int(y0+1000*(a)) )
pt2 = ( int(x0-1000*(-b)), int(y0-1000*(a)) )
cv2.line(result, pt1, pt2, (0, 0, 255), 2, cv2.LINE_AA)
# save resulting images
cv2.imwrite('fabric_equalized_thresh.jpg',thresh)
cv2.imwrite('fabric_equalized_morph.jpg',morph)
cv2.imwrite('fabric_equalized_edges.jpg',edges)
cv2.imwrite('fabric_equalized_lines.jpg',result)
# show thresh and result
cv2.imshow("thresh", thresh)
cv2.imshow("morph", morph)
cv2.imshow("edges", edges)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Thresholded image:
Morphology cleaned image:
Edge image:
Resulting Hough Lines:
I am trying to crop the live video diagonally. With the help of cv.line, I have mentioned the dimensions and my goal is to display the video of the lower side of the line I have drawn and the upper video should be cropped,
As a beginner, I was just able to draw a line using the following code:
import cv2
cv2.namedWindow("preview")
vc = cv2.VideoCapture(0)
if vc.isOpened(): # try to get the first frame
rval, frame = vc.read()
else:
rval = False
while rval:
cv2.imshow("preview", frame)
rval, frame = vc.read()
key = cv2.waitKey(20)
if key == 27: # exit on ESC
break
else:
cv2.line(img=frame, pt1=(700,5), pt2=(5, 450), color=(255, 0, 0), thickness=1, lineType=8, shift=0)
vc.release()
cv2.destroyWindow("preview")
Output:
Suggestion on this will be very helpful
Here's the code that'll mask out the points above the line. I've added comments here so you can follow what's going on. There are faster ways to do this, but I wanted something that would be easily readable.
import cv2
import matplotlib.pyplot as plt
import numpy as np
path = r"path\to\img"
img = cv2.imread(path)
#plt.imshow(img)
#plt.show()
pt1 = (86, 0) #ensure this point exists within the image
pt2 = (0, 101) #ensure this point exists within the image
cv2.line(img, pt1, pt2, (255, 255, 255))
#plt.imshow(img)
#plt.show()
#slope of line
m = float(pt2[1] - pt1[1])/float(pt2[0] - pt1[0])
c = pt1[1] - m*pt1[0]
#create mask image
mask1 = np.zeros(img.shape, np.uint8)
#for every point in the image
for x in np.arange(0, 87):
for y in np.arange(0, 102):
#test if point exists above the line,
if y > m*x + c:
mask1[y][x] = (255, 255, 255)
#plt.imshow(mask1)
#plt.show()
fin_img = cv2.merge((img[:, :, 0], img[:, :, 1], img[:, :, 2], mask1[:,:, 0]))
#plt.imshow(fin_img)
#plt.show()
cv2.imwrite('output.png', fin_img)
To crop images, I use a mask and cv2.bitwise_and().
Source Image:
The mask:
# Create a mask image with a triangle on it
y,x,_ = img.shape
mask = np.zeros((y,x), np.uint8)
triangle_cnt = np.array( [(x,y), (x,0), (0,y)] )
cv2.drawContours(mask, [triangle_cnt], 0, 255, -1)
The output:
img = cv2.bitwise_and(img, img, mask=mask)
Problem
Using this answer to create a segmentation program, it is counting the objects incorrectly. I noticed that alone objects are being ignored or poor imaging acquisition.
I counted 123 objects and the program returns 117, as can be seen, bellow. The objects circled in red seem to be missing:
Using the following image from a 720p webcam:
Code
import cv2
import numpy as np
import matplotlib.pyplot as plt
from scipy.ndimage import label
import urllib.request
# https://stackoverflow.com/a/14617359/7690982
def segment_on_dt(a, img):
border = cv2.dilate(img, None, iterations=5)
border = border - cv2.erode(border, None)
dt = cv2.distanceTransform(img, cv2.DIST_L2, 3)
plt.imshow(dt)
plt.show()
dt = ((dt - dt.min()) / (dt.max() - dt.min()) * 255).astype(np.uint8)
_, dt = cv2.threshold(dt, 140, 255, cv2.THRESH_BINARY)
lbl, ncc = label(dt)
lbl = lbl * (255 / (ncc + 1))
# Completing the markers now.
lbl[border == 255] = 255
lbl = lbl.astype(np.int32)
cv2.watershed(a, lbl)
print("[INFO] {} unique segments found".format(len(np.unique(lbl)) - 1))
lbl[lbl == -1] = 0
lbl = lbl.astype(np.uint8)
return 255 - lbl
# Open Image
resp = urllib.request.urlopen("https://i.stack.imgur.com/YUgob.jpg")
img = np.asarray(bytearray(resp.read()), dtype="uint8")
img = cv2.imdecode(img, cv2.IMREAD_COLOR)
## Yellow slicer
mask = cv2.inRange(img, (0, 0, 0), (55, 255, 255))
imask = mask > 0
slicer = np.zeros_like(img, np.uint8)
slicer[imask] = img[imask]
# Image Binarization
img_gray = cv2.cvtColor(slicer, cv2.COLOR_BGR2GRAY)
_, img_bin = cv2.threshold(img_gray, 140, 255,
cv2.THRESH_BINARY)
# Morphological Gradient
img_bin = cv2.morphologyEx(img_bin, cv2.MORPH_OPEN,
np.ones((3, 3), dtype=int))
# Segmentation
result = segment_on_dt(img, img_bin)
plt.imshow(np.hstack([result, img_gray]), cmap='Set3')
plt.show()
# Final Picture
result[result != 255] = 0
result = cv2.dilate(result, None)
img[result == 255] = (0, 0, 255)
plt.imshow(result)
plt.show()
Question
How to count the missing objects?
Answering your main question, watershed does not remove single objects. Watershed was functioning fine in your algorithm. It receives the predefined labels and perform segmentation accordingly.
The problem was the threshold you set for the distance transform was too high and it removed the weak signal from the single objects, thus preventing the objects from being labeled and sent to the watershed algorithm.
The reason for the weak distance transform signal was due to the improper segmentation during the color segmentation stage and the difficulty of setting a single threshold to remove noise and extract signal.
To remedy this, we need to perform proper color segmentation and use adaptive threshold instead of the single threshold when segmenting the distance transform signal.
Here is the code i modified. I have incorporated color segmentation method by #user1269942 in the code. Extra explanation is in the code.
import cv2
import numpy as np
import matplotlib.pyplot as plt
from scipy.ndimage import label
import urllib.request
# https://stackoverflow.com/a/14617359/7690982
def segment_on_dt(a, img, img_gray):
# Added several elliptical structuring element for better morphology process
struct_big = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
struct_small = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))
# increase border size
border = cv2.dilate(img, struct_big, iterations=5)
border = border - cv2.erode(img, struct_small)
dt = cv2.distanceTransform(img, cv2.DIST_L2, 3)
dt = ((dt - dt.min()) / (dt.max() - dt.min()) * 255).astype(np.uint8)
# blur the signal lighty to remove noise
dt = cv2.GaussianBlur(dt,(7,7),-1)
# Adaptive threshold to extract local maxima of distance trasnform signal
dt = cv2.adaptiveThreshold(dt, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 21, -9)
#_ , dt = cv2.threshold(dt, 2, 255, cv2.THRESH_BINARY)
# Morphology operation to clean the thresholded signal
dt = cv2.erode(dt,struct_small,iterations = 1)
dt = cv2.dilate(dt,struct_big,iterations = 10)
plt.imshow(dt)
plt.show()
# Labeling
lbl, ncc = label(dt)
lbl = lbl * (255 / (ncc + 1))
# Completing the markers now.
lbl[border == 255] = 255
plt.imshow(lbl)
plt.show()
lbl = lbl.astype(np.int32)
cv2.watershed(a, lbl)
print("[INFO] {} unique segments found".format(len(np.unique(lbl)) - 1))
lbl[lbl == -1] = 0
lbl = lbl.astype(np.uint8)
return 255 - lbl
# Open Image
resp = urllib.request.urlopen("https://i.stack.imgur.com/YUgob.jpg")
img = np.asarray(bytearray(resp.read()), dtype="uint8")
img = cv2.imdecode(img, cv2.IMREAD_COLOR)
## Yellow slicer
# blur to remove noise
img = cv2.blur(img, (9,9))
# proper color segmentation
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, (0, 140, 160), (35, 255, 255))
#mask = cv2.inRange(img, (0, 0, 0), (55, 255, 255))
imask = mask > 0
slicer = np.zeros_like(img, np.uint8)
slicer[imask] = img[imask]
# Image Binarization
img_gray = cv2.cvtColor(slicer, cv2.COLOR_BGR2GRAY)
_, img_bin = cv2.threshold(img_gray, 140, 255,
cv2.THRESH_BINARY)
plt.imshow(img_bin)
plt.show()
# Morphological Gradient
# added
cv2.morphologyEx(img_bin, cv2.MORPH_OPEN,cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3)),img_bin,(-1,-1),10)
cv2.morphologyEx(img_bin, cv2.MORPH_ERODE,cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3)),img_bin,(-1,-1),3)
plt.imshow(img_bin)
plt.show()
# Segmentation
result = segment_on_dt(img, img_bin, img_gray)
plt.imshow(np.hstack([result, img_gray]), cmap='Set3')
plt.show()
# Final Picture
result[result != 255] = 0
result = cv2.dilate(result, None)
img[result == 255] = (0, 0, 255)
plt.imshow(result)
plt.show()
Final results :
124 Unique items found.
An extra item was found because one of the object was divided to 2.
With proper parameter tuning, you might get the exact number you are looking. But i would suggest getting a better camera.
Looking at your code, it is completely reasonable so I'm just going to make one small suggestion and that is to do your "inRange" using HSV color space.
opencv docs on color spaces:
https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_colorspaces/py_colorspaces.html
another SO example using inRange with HSV:
How to detect two different colors using `cv2.inRange` in Python-OpenCV?
and a small code edits for you:
img = cv2.blur(img, (5,5)) #new addition just before "##yellow slicer"
## Yellow slicer
#mask = cv2.inRange(img, (0, 0, 0), (55, 255, 255)) #your line: comment out.
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) #new addition...convert to hsv
mask = cv2.inRange(hsv, (0, 120, 120), (35, 255, 255)) #new addition use hsv for inRange and an adjustment to the values.
Improving Accuracy
Detecting missing objects
im_1, im_2, im_3
I've count 12 missing objects: 2, 7, 8, 11, 65, 77, 78, 84, 92, 95, 96. edit: 85 too
117 found, 12 missing, 6 wrong
1° Attempt: Decrease Mask Sensibility
#mask = cv2.inRange(img, (0, 0, 0), (55, 255, 255)) #Current
mask = cv2.inRange(img, (0, 0, 0), (80, 255, 255)) #1' Attempt
inRange documentaion
im_4, im_5, im_6, im_7
[INFO] 120 unique segments found
120 found, 9 missing, 6 wrong
I have written a python code using opencv and numpy to detect red colour lines in a video and it is working fine as it is able to detect the edges of the red lines. but i want to grab the part of the video in between each two lines as images.how to do that ?? Now I have updated an image. i want to extract the image in between two red lines from the video.
import cv2
import numpy as np
import matplotlib.pyplot as plt
video = cv2.VideoCapture("/home/ksourav/AGS/SampleVideos/Trail1.mp4")
while True:
ret, frame = video.read()
if not ret:
video = cv2.VideoCapture("/home/ksourav/AGS/SampleVideos/Trail1.mp4")
continue
#gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
low_red = np.array ([5, 50, 50])
up_red = np.array([10, 255, 255])
mask = cv2.inRange(hsv, low_red, up_red)
edges= cv2.Canny(mask, 100, 200, apertureSize=7, L2gradient=True)
lines = cv2.HoughLinesP(edges, 9, np.pi/180, 250, maxLineGap=70)
if lines is not None:
for line in lines:
x1, y1, x2, y2 = line[0]
cv2.line(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.imshow("frame", frame)
key = cv2.waitKey(25)
if key == 27:
break
video.release()
cv2.destroyAllWindows()