OM222O
OM222O

Reputation: 1006

OpenCV detecting an object and its rotation

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:

enter image description here

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):

enter image description here

Upvotes: 4

Views: 5964

Answers (1)

Max Kaha
Max Kaha

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). enter image description here

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: enter image description here

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: enter image description here

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

Related Questions