Reputation: 1006
I'm working on a robotics project where we need to implement some form of image recognition to find the right path to follow. There is an spinning disk which shows the direction like below:
I have written the code below which successfully captures a video stream using a webcam and tries find the image of the disk from the provided template:
import cv2
IMGn = cv2.imread("North.png",0)
webcam = cv2.VideoCapture(0)
grayScale = True
key = 0
def transformation(frame,template):
w, h = template.shape[::-1]
res = cv2.matchTemplate(frame,template,cv2.TM_SQDIFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
top_left = min_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
cv2.rectangle(frame,top_left, bottom_right, 255, 2)
return frame
while (key!=ord('q')):
check, frame = webcam.read()
if(grayScale):
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
frame = transformation(frame,IMGn)
cv2.imshow("Capturing", frame)
key = cv2.waitKey(1)
webcam.release()
cv2.destroyAllWindows()
this doesn't work super well, but at least finds the general outline of the compass. However I'm not sure how to find the rotation of the circle at all! also the size seems to be an issue (when held back too far or too close the tracking is messed up). It's the first time I'm doing anything with image recognition in general which doesn't help, so please try to simplify your answers. Thanks.
I had an issue with cv2.findContours. It seems to return 3 values, not 2. Other than that the code successfully detects and crops the image, but doesn't manage to find the lines in the final step. There is also the issue where if the picture is rotated more than 180 degrees, it will give the wrong results since the line has rotated more than 180 degrees. Using the small white square inside the black square should fix that and add a 180 degrees offset to the image, depending on that, but I'm not sure how to do that either.
import cv2
webcam = cv2.VideoCapture(0)
def find_disk(frame,template):
w, h = template.shape[::-1]
res = cv2.matchTemplate(frame,template,cv2.TM_SQDIFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
top_left = min_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
frame = frame[top_left[1]:bottom_right[1],top_left[0]:bottom_right[0]]
return frame
def thresh_img(frame):
frame = cv2.GaussianBlur(frame, (5, 5), 0)
ret, thresh = cv2.threshold(frame, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
return thresh
def crop_disk(frame):
_, contours, hierarchy = cv2.findContours(frame, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
areas = []
for cnt in contours:
area = cv2.contourArea(cnt)
areas.append((area, cnt))
areas.sort(key=lambda x: x[0], reverse=True)
areas.pop(0) # remove biggest contour
if (len(areas)>0):
x, y, w, h = cv2.boundingRect(areas[0][1]) # get bounding rectangle around biggest contour to crop to
crop = frame[y:y+h, x:x+w]
else:
crop = frame
return crop
def find_lines(frame):
edges = cv2.Canny(frame, 50, 150, apertureSize=3)
lines = cv2.HoughLines(edges, 1, np.pi/180, 200)
if (lines!=None):
print(lines)
img = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR) # Convert cropped black and white image to color to draw the red line
for rho, theta in lines[0]:
a = np.cos(theta)
b = np.sin(theta)
x0 = a*rho
y0 = b*rho
x1 = int(x0 + 1000*(-b))
y1 = int(y0 + 1000*(a))
x2 = int(x0 - 1000*(-b))
y2 = int(y0 - 1000*(a))
return cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
else:
return frame
key = 0
while (key!=ord('q')):
check, frame = webcam.read()
if(grayScale):
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
frame = find_lines(crop_disk(thresh_img(find_disk(frame,IMGn))))
cv2.imshow("Capturing", frame)
key = cv2.waitKey(1)
#key = ord('q')
webcam.release()
cv2.destroyAllWindows()
here is a picture of a sample output (I got this by having a picture of the disk on my phone and rotating it in front of the camera):
Upvotes: 4
Views: 5964
Reputation: 922
First you might want to put a threshold on the picture so it turns all gray elements to either white or black for easier detection.
img = cv2.imread(r"C:\Users\Max\Desktop\North_rotated_2.png")
img = cv2.resize(img, None, fx=3, fy=3)
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(imgray, (5, 5), 0)
ret, thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
Output would look like this (I rotated your initial picture manually to get an angle on it).
Then we can detect the second biggest contour in the image which should be our black half circle (biggest contour would be an outline alonside the borders of the entire image). This is done with the findContours() function:
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
areas = []
for cnt in contours:
area = cv2.contourArea(cnt)
areas.append((area, cnt))
areas.sort(key=lambda x: x[0], reverse=True)
areas.pop(0) # remove biggest contour
x, y, w, h = cv2.boundingRect(areas[0][1]) # get bounding rectangle around biggest contour to crop to
img = cv2.rectangle(img, (x, y), (x+w, y+h), (255,0,0), 2)
crop = thresh[y:y+h, x:x+w] # crop to size
After we crop to the detected contours we have this image:
Finally you can use HoughLines to find the longest line in your image which should be the edge of your half circle. Here you get back the angles that describe it rho and theta which is likely what you want to know. If we take those angles to get x,y points and draw it onto the image like so:
edges = cv2.Canny(crop, 50, 150, apertureSize=3)
lines = cv2.HoughLines(edges, 1, np.pi/180, 200) # Find lines in image
img = cv2.cvtColor(crop, cv2.COLOR_GRAY2BGR) # Convert cropped black and white image to color to draw the red line
for rho, theta in lines[0]:
a = np.cos(theta)
b = np.sin(theta)
x0 = a*rho
y0 = b*rho
x1 = int(x0 + 1000*(-b))
y1 = int(y0 + 1000*(a))
x2 = int(x0 - 1000*(-b))
y2 = int(y0 - 1000*(a))
cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2) # draw line
Then we can make sure the right line was detected, which in this case seems fine:
Hope this helps point you in the right direction, atleast for manually rotating the image in a few positions it worked fine for me. The angles in lines[0] should be what you are looking for here.
Upvotes: 6