Line following robot with OpenCV and contour-based approach

Constantin Toporov
4 min readOct 23, 2018

--

In my previous story, I told about PiTanq — a robot-tank I built. Then a big goal is to make it autonomous. I was inspired by Udacity course for self-driving cars and the first task of this course is to recognize lanes on roads.

As my robot is pretty far from hitting a road I put a white tape on the floor.

The idea was to implement tracking a line based on lanes detection approach from the Udacity course.

A general solution for this task would look like:

Filter image by color

Udacity course task requires to detect both yellow and white lines. For that purpose, they use HSV or HLS conversion. I need to detect only a white line so I decided to use only a grayscale filter.

Next step I picked a threshold to have a binary image:

Detect the line

Then, applying Canny edge detector, I got that picture (inside a region of interest):

Using Hough line detector I found some odd lines:

Making detection rules more restrictive:

Not good in terms of relevance (and reliability as well).

Spent some time with these experiments I was not able to get satisfying results. Then I decided to try another approach. Instead of lines detection, I used contours detection. Assuming, the biggest contour is the line and taking a centerline of the bounding box, I got a plausible direction.

Light problem

The next problem I encountered with, was different light conditions on the line. One side of the line turned out in the shadow of a couch and it was impossible to find a grayscale threshold working along the whole line loop.

The solution was to adjust the threshold individually for each image depending on a ratio of white pixels to the whole area.

def balance_pic(image):
global T
ret = None
direction = 0
for i in range(0, tconf.th_iterations):
rc, gray = cv.threshold(image, T, 255, 0)
crop = Roi.crop_roi(gray)
nwh = cv.countNonZero(crop)
perc = int(100 * nwh / Roi.get_area())
if perc > tconf.white_max:
if T > tconf.threshold_max:
break
if direction == -1:
ret = crop
break
T += 10
direction = 1
elif perc < tconf.white_min:
if T < tconf.threshold_min:
break
if direction == 1:
ret = crop
break
T -= 10
direction = -1
else:
ret = crop
break
return ret

Make driving decisions

Based on computer vision technics we got a direction to move. The real decisions were made depending on the angle of this vector and its shift from the image middle point.

Determine turn actions (if required):

def check_shift_turn(angle, shift):
turn_state = 0
if angle < tconf.turn_angle or angle > 180 - tconf.turn_angle:
turn_state = np.sign(90 - angle)
shift_state = 0
if abs(shift) > tconf.shift_max:
shift_state = np.sign(shift)
return turn_state, shift_state
def get_turn(turn_state, shift_state):
turn_dir = 0
turn_val = 0
if shift_state != 0:
turn_dir = shift_state
turn_val = tconf.shift_step if shift_state != turn_state else tconf.turn_step
elif turn_state != 0:
turn_dir = turn_state
turn_val = tconf.turn_step
return turn_dir, turn_val

Do self-driving:

while(True):
a, shift = get_vector()
if a is None:
# there is some code omitted related to line finding
break
turn_state, shift_state = check_shift_turn(a, shift)
turn_dir, turn_val = get_turn(turn_state, shift_state)
if turn_dir != 0:
turn(turn_dir, turn_val)
else:
time.sleep(tconf.straight_run)

Results

There is a debug visual info:

Algorithm settings

## Picture settings# initial grayscale threshold
threshold = 120
# max grayscale threshold
threshold_max = 180
#min grayscale threshold
threshold_min = 40
# iterations to find balanced threshold
th_iterations = 10
# min % of white in roi
white_min=3
# max % of white in roi
white_max=12
## Driving settings# line angle to make a turn
turn_angle = 45
# line shift to make an adjustment
shift_max = 20
# turning time of shift adjustment
shift_step = 0.125
# turning time of turn
turn_step = 0.25
# time of straight run
straight_run = 0.5
# attempts to find the line if lost
find_turn_attempts = 5
# turn step to find the line if lost
find_turn_step = 0.2
# max N of iterations of the whole tracking
max_steps = 100

Code

The source code is available on Github.

Links

PiTanq site

Story of PiTanq creation

PiTanq code on Github

Sign up to discover human stories that deepen your understanding of the world.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Constantin Toporov
Constantin Toporov

Written by Constantin Toporov

Working on artificial intelligence and computer vision applied to sports.

Responses (2)

Write a response