Lane recognition in automatic driving with OpenCV

Posted by breath18 on Mon, 13 Dec 2021 07:17:27 +0100

Lane recognition is essential in automatic driving, and there are more than one implementation method.

Basic concept of Lane

"Driveway" is explained in Wikipedia or Baidu Encyclopedia. However, as we say every day, it is used to refer to "motorway". Therefore, the terms "lane recognition" and "automatic driving" are also for motor vehicles.

★ the "automatic driving" of bicycle is also worth exploring. In fact, there are already, for example: https://www.designboom.com/technology/self-driving-bicycle-huawei-engineers-operate-unmanned-06-14-2021/ ”

Problem description

Like the real-time lane recognition demonstrated in the previous video, it can be realized in a variety of ways. For example, a learning based approach can be used, that is, to train a deep learning model.

This paper does not use this method. This paper will introduce a simpler method: lane recognition with OpenCV.

As shown in the figure above, the important task of lane recognition is to identify the lane separation lines on both sides of the lane, which is the key to the problem.

So, how to identify the lane separation of these lanes?

When driving out from the perspective of motor vehicles, there are many other objects in the scene, such as vehicles, roadside obstacles, street lights, etc. Through the previous videos and life attempts, it is easy to know that every frame in the scene is changing. This is the driving situation in real life - a little complicated.

Therefore, to solve the problem of lane recognition, we must first find a way to ignore the objects that should not be seen in the scene - only the lane separation line. That is, as shown in the figure below, there are no other objects except the lane separation line. With the driving of motor vehicles, Lane separation lines are only presented in this scene.

In the following content, we will demonstrate how to select the specified area from the video, and introduce the necessary image preprocessing techniques.

Image mask

Image mask: use the selected image, figure or object to block the image to be processed (local or all) to control the area or process of image processing. In image processing, there are many requirements for image mask.

The essence of image mask is Numpy array, as shown in the figure below. Changing the pixel value of the selected area in the figure, for example, to 0, realizes the masking effect shown in the figure.

This is a very simple and effective way to remove unwanted objects.

Basic ideas

The first is to convert the picture to "black and white", that is, set a conversion threshold, so that you can get the effect on the right in the figure below.

The next problem to be solved is how to make the machine "see" the lane separation line. Hough Transformation is usually used, which is a feature extraction technology. For its mathematical principle, please refer to: http://math.itdiffer.com Relevant contents in.

Lane recognition with OpenCV

Let's start writing the code to realize lane recognition. The code of this article is released to AiStudio, and the relevant videos can also be downloaded in this project.

★ address of the project: https://aistudio.baidu.com/aistudio/projectdetail/3215224?contributionType=1 . ”

First introduce the following modules and libraries. If they are not installed locally, please install them yourself.

import os
import re
import cv2
import numpy as np
from tqdm import tqdm_notebook
import matplotlib.pyplot as plt

Then read in the video. This video file has been put into this project and can be downloaded by yourself.

prepare

Load the video file and extract several frames from the video as pictures for subsequent applications.

# Convert video frames into pictures at the specified frame rate
image_path = 'frames'

def get_frame(video, image_path, frame_rate=0.5):
    vidcap = cv2.VideoCapture(video)
    sec = 0
    count = 1
    image_lst = []
    while 1:
        vidcap.set(cv2.CAP_PROP_POS_MSEC, sec*1000)
        has_frames, image = vidcap.read()
        if has_frames:
            image_name = f"imge{count}.jpg"
            cv2.imwrite(f"{image_path}/{image_name}", image)
            image_lst.append(image_name)
        else:
            break
        count += 1
        sec += frame_rate
        sec = round(sec, 2)
    return image_lst

images = get_frame("road.mp4", 'frames', frame_rate=5)   # Here, a frame is taken every 5 seconds. In real business, it takes a long time.
len(images)    # A total of 167 pictures were obtained

Pay special attention to the frame set above_ Rate = 5 is obviously rough, which is set for demonstration.

One of the pictures is shown below.

image_path = 'frames/'

# Show one of the pictures
idx = 2    # Specify an index
image_example = image_path + os.listdir("frames")[idx]
img = cv2.imread(image_example) 
# display picture
plt.figure(figsize=(10,10))
plt.imshow(img, cmap= "gray")
plt.show()

Create mask

Obviously, the region we are interested in is a polygonal range, and other regions should be masked. Therefore, first specify the coordinates of the polygon, and then create a mask from it.

# Create 0 array
stencil = np.zeros_like(img[:,:,0])

# Determines the coordinates of the polygon
polygon = np.array([[80,370], [300,250], [450,250], [580,370]])

# Fill polygon with 1
cv2.fillConvexPoly(stencil, polygon, 1)

# Show polygon effect
plt.figure(figsize=(10,10))
plt.imshow(stencil, cmap= "gray")
plt.show()

Use the polygon as a mask on one of the pictures.

mask_img = cv2.bitwise_and(img[:,:,0], 
                      img[:,:,0], 
                      mask=stencil)

# Display effect
plt.figure(figsize=(10,10))
plt.imshow(mask_img, cmap= "gray")
plt.show()

Image preprocessing

In order to identify the lane in each frame of video, all pictures must be preprocessed, mainly in the two aspects mentioned above: threshold and Hough transform

  1. threshold
ret, thresh = cv2.threshold(img, 130, 145, cv2.THRESH_BINARY)

# plot image
plt.figure(figsize=(10,10))
plt.imshow(thresh, cmap= "gray")
plt.show()
  1. Hough transform
lines = cv2.HoughLinesP(thresh, 1, np.pi/180, 30, maxLineGap=200)

# Copy frame picture
dmy = img[:,:,0].copy()

# Draw the Hough line
for line in lines:
    x1, y1, x2, y2 = line[0]
    cv2.line(dmy, (x1, y1), (x2, y2), (255, 0, 0), 3)

# Drawing display
plt.figure(figsize=(10,10))
plt.imshow(dmy, cmap= "gray")
plt.show()

Now, use the above operation for each frame.

cnt = 0

for image in images:
    print(image)
    img = cv2.imread(f'frames/{image}') 
    # Frame mask
    masked = cv2.bitwise_and(img[:,:,0], img[:,:,0], mask=stencil)
  
    # threshold
    ret, thresh = cv2.threshold(masked, 130, 145, cv2.THRESH_BINARY)

    # Hough transform
    lines = cv2.HoughLinesP(thresh, 1, np.pi/180, 30, maxLineGap=200)
    dmy = img[:,:,0].copy()
  
   # Recognized line
    try:
        for line in lines:
            x1, y1, x2, y2 = line[0]
            cv2.line(dmy, (x1, y1), (x2, y2), (255, 0, 0), 3)
  
        cv2.imwrite('detected/'+str(cnt)+'.png',dmy)
  
    except TypeError: 
        cv2.imwrite('detected/'+str(cnt)+'.png',img)

    cnt+= 1

For video

# enter path
pathIn= 'detected/'

# Output video
pathOut = 'roads_v2.mp4'

# Sets the number of frames per second
fps = 30.0

from os.path import isfile, join

# read file
files = [f for f in os.listdir(pathIn) if isfile(join(pathIn, f))]
files.sort(key=lambda f: int(re.sub('\D', '', f)))

Save all detected Lane frames to the list.

frame_list = []

for i in range(len(files)):
    filename=pathIn + files[i]
    #reading each files
    img = cv2.imread(filename)
    height, width, layers = img.shape
    size = (width,height)
    
    #inserting the frames into an image array
    frame_list.append(img)

Finally, all frames are merged into video.

# write the video
out = cv2.VideoWriter(pathOut,cv2.VideoWriter_fourcc(*'DIVX'), 
                      fps, size)

for i in range(len(frame_list)):
    # writing to a image array
    out.write(frame_list[i])

out.release()