Issue
the problem I'm facing currently is related to my code which is being developed to follow a black line. Currently my code function by creating a contour around the black line and drawing a dot in the centre of that line. I also draw a dot in the centre of the image and calculate the distance between those dots. With this information i can turn the robot accordingly.
What I want to achieve is to segment the contour horizontally as when there are steep corners in the line the code will calculate the average and the dot will be outside the line
This isn't the biggest issue in the world because I can just situate the camera to be closer to the line making the line curve less dramatic however, I don't wan to do this unless it's my last option as it's less efficient and not cool. This is an example of what i want the image to look like:
as you can see the contour is seperated into smaller contours.
This is my code:
import numpy as np
import cv2
import math
#lower and upper values in hsv format for mask
lg = np.array([75, 52, 60])
ug = np.array([106, 255, 255])
lb = np.array([0,32,0])
ub = np.array([179, 255, 93])
#ignore this (for seperate part of line following)
def percentage_calculator(boundaries, colour, image):
for(lower, upper) in boundaries:
lower = np.array(lower)
upper = np.array(upper)
# finds colors in boundaries a applies a mask
mask = cv2.inRange(image, lower, upper)
output = cv2.bitwise_and(image, image, mask = mask)
tot_pixel = image.size
pixel = np.count_nonzero(output)
percentage = round(pixel * 100 / tot_pixel, 2)
print(colour + " pixels: " + str(pixel))
print("Total pixels: " + str(tot_pixel))
print("Percentage of " + colour + " pixels: " + str(percentage) + "%")
green_boundaries = [
([75, 52, 60], [106, 255, 255])
]
cap = cv2.VideoCapture(1)
#centre point of screen
Xmid = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) / 2)
Ymid = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) / 2)
while True:
_, img = cap.read()
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
#cropping and getting the hsv value and converting it to see of the value is there for green
mask = cv2.inRange(hsv, lg, ug)
green_result = cv2.bitwise_and(hsv, hsv, mask=mask)
#cropping and getting the hsv value and converting it to see of the value is there
mask1 = cv2.inRange(hsv, lb, ub)
black_result = cv2.bitwise_and(hsv, hsv, mask=mask1)
#Finding all current contours
contours,_= cv2.findContours(mask1,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
#finding the biggest contour
max_contour = contours[0]
for contour in contours:
if cv2.contourArea(contour)>cv2.contourArea(max_contour):
max_contour = contour
#simplifying the contour so it approximates the lines better
contour = cv2.approxPolyDP(max_contour, 0.05 * cv2.arcLength(contour, True), True)
#calculating the centroid (I think) of the contour
M = cv2.moments(contour)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
#cX and cY are the centre of the contour
cv2.circle(img, (cX, cY), 5, (0, 0, 255), -1)
cv2.circle(img, (Xmid, Ymid), 5, (255, 255, 0), -1)
#drawing a line between the two circles for visualisation
cv2.line(img, (cX, cY), (Xmid, Ymid), (255, 0, 0), 2)
#using distance formula to find the distance between the two coords of the circles
distance = math.sqrt((Xmid - cX) ** 2 + (Ymid - cY) ** 2)
cv2.drawContours(img, [contour], -1, (0, 255, 0), 2)
#cv2.imshow("green", green_result)
cv2.imshow('black', black_result)
#cv2.imshow("hsv", hsv)
cv2.imshow('img', img)
k = cv2.waitKey(30) & 0xff
if k==27:
break
Thanks for the answers!
Solution
There isn't a way to split a contour, however, here is the best solution I found to get the middle of the line. I converted the image to grayscale and blurred the image:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.medianBlur(gray, 9)
I thresholded and inverted the image to get the black line as the only non-zero pixels:
_, thresholded = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
inverted_thresholded = cv2.bitwise_not(thresholded)
I then eroded those pixels with skimage's skeletonization and grabbed all the pixels left:
skeleton, dist = medial_axis(inverted_thresholded, return_distance=True)
dist_on_skel = dist * skeleton
skeleton = (255 * dist_on_skel).astype(np.uint8)
Line = cv2.findNonZero(skeleton)
Then I remove any random noise by only grabbing pixels that are close to each other:
filtered_pixel_coords = []
prev_point = None
for pixel in Line:
x, y = pixel[0]
if prev_point is not None:
prev_x, prev_y = prev_point
distance = math.sqrt((x - prev_x) ** 2 + (y - prev_y) ** 2)
if distance <= outlier_threshold:
#forgets about any pixels on the edge cause sometimes there are a lot of them and that is bad
if 10 < x < w - 10 and 10 < y < h - 10:
filtered_pixel_coords.append([x, y])
else:
filtered_pixel_coords.append([x, y])
prev_point = [x, y]
pixel_Coords = filtered_pixel_coords
I then draw a line between the average of every 50th pixel:
for i, coord in enumerate(pixel_Coords):
avg[0] += coord[0]
avg[1] += coord[1]
count += 1
if count == 50 or i == len(pixel_Coords) - 1:
avg[0] //= count
avg[1] //= count
averaged_coords.append(avg.copy())
avg = [0, 0]
count = 0
for i in range(len(averaged_coords) - 1):
pt1 = (averaged_coords[i][0], averaged_coords[i][1])
pt2 = (averaged_coords[i + 1][0], averaged_coords[i + 1][1])
cv2.line(img, pt1, pt2, (0, 0, 255), 2)
I hope this helps.
Answered By - Noah Robb
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.