OpenCV study notes--Getting started with lane line detection

The gitee source code of this note:
https://gitee.com/hongtao-jiang/opencv_lanedetect.git

2023.8.5

1. OpenCV installation

Whether conda manages the virtual environment depends on yourself
pip install opencv

import cv2
print(cv2.__version__)

Check if the library is installed successfully

2. Reading and saving pictures

import cv2

img=cv2.imread('img.jpg',cv2.IMREAD_GRAYSCALE) #此为以灰度图格式读入,彩色读入则shape是3通道
print(type(img)) #<class 'numpy.ndarray'>
print(img.shape) #(368, 640)

cv2.imshow('image',img)#一闪而逝
#cv2.waitKey(0)#延时阻塞 0一直阻直到键入  单位毫秒
#k=cv2.waitKey(0)
#print(k)  #输出键入的ASCII值

cv2.imwrite('img_gray.jpg',img)#灰度图重组到文件里,图片要指定后缀,cv以此确定压缩格式

3. Canny algorithm edge detection

insert image description here
Calculate the surrounding gradient of each pixel, and compare the changes to determine whether it is an edge.
For points B and C, which are not on the edge, there is no obvious change in the gradient, but for A on the edge, the gradient changes will be large, so it is possible It is an edge point
. The above is an ideal example. The gradient is
insert image description here
calculated along the edge normal. In fact, the situation is more complicated. For example: 1 is the left and right edges, and 3 is both up and down. Gradient direction (there are 4 positive and negative),
(1) Apply a Gaussian filter to smooth the image and filter out noise.
Edge detection is susceptible to noise, so smooth the image and reduce noise points
(2) Calculate the gradient size and direction of each pixel of the image.
insert image description here
insert image description here
(3) Non-maximum value suppression to eliminate the adverse effects of edge detection
Traverse all pixels in the image to determine whether the current pixel is the maximum value with the same direction gradient among the surrounding pixels.
insert image description here
Keep the yellow background pixels, and others return to 0

(4) Apply double-threshold detection to determine the real and potential edges.
insert image description here
insert image description hereFirst set the high and low thresholds (generally the high threshold is 2 to 3 times the low threshold), and traverse the entire gray matrix. If the gradient of a certain point is higher than the high threshold , then set 1 in the result. If the gradient value of this point is lower than the low threshold, then set 0 in the result. If the gradient value of this point is between the high and low thresholds, the following judgment needs to be made: check the point (set It is regarded as the 8 neighbor points of the center point), and see if there is a point whose gradient value is higher than the high threshold. If it exists, it means that the center point is connected to the determined edge point, so set 1 in the result, otherwise set 0 .

(5) Suppress isolated weak edges to complete edge detection

Dual-threshold detection identifies true and potential edges

Original image: insert image description here
Edge contour:
insert image description here
Threshold: edge_img=cv2.Canny(img,50,100)
insert image description here
Threshold: edge_img=cv2.Canny(img,70,120)
Compared with the previous picture, the edge line is less

import cv2

img=cv2.imread('img.jpg',cv2.IMREAD_GRAYSCALE)

edge_img=cv2.Canny(img,70,120)

cv2.imshow('edges',edge_img)
cv2.waitKey(0)
cv2.imwrite('edge_img.jpg',edge_img)

Attached is a real-time detection effect code that can dynamically adjust the threshold:

import cv2

cv2.namedWindow('edge_detection')
cv2.createTrackbar('minThreshold','edge_detection',50,1000,lambda x: x)
cv2.createTrackbar('maxThreshold','edge_detection',100,1000,lambda x: x)

img=cv2.imread('img.jpg',cv2.IMREAD_GRAYSCALE)
while True:
    minThreshold=cv2.getTrackbarPos('minThreshold', 'edge_detection')
    maxThreshold = cv2.getTrackbarPos('maxThreshold', 'edge_detection')
    edges = cv2.Canny(img, minThreshold, maxThreshold)
    cv2.imshow('edge_detection', edges)
    cv2.waitKey(10)

The effect is as follows:
insert image description here
used to select the appropriate threshold

4、ROI mask

region of interest area of ​​interest

· Array slicing
· Boolean operation (AND operation)

insert image description here
The original image is stored in the memory in the form of matrix np.array.
zeros_like generates a matrix
fillpoly of all zeros of the same size to fill the mask part, which is reserved for AND operation

cv2.fillPoly(mask,np.array([[[0,368],[240,210],[300,210],[640,368]]]),color=255)

This part is a trapezoidal fill that encloses four vertices, 255 is the gray value, pure white

import cv2
import numpy as np

edge_img=cv2.imread('edge_img.jpg',cv2.IMREAD_GRAYSCALE)

mask=np.zeros_like(edge_img)
cv2.fillPoly(mask,np.array([[[0,368],[240,210],[300,210],[640,368]]]),color=255)
cv2.imshow('mask',mask)
cv2.waitKey(0)

insert image description here

import cv2
import numpy as np

edge_img=cv2.imread('edge_img.jpg',cv2.IMREAD_GRAYSCALE)

mask=np.zeros_like(edge_img)
mask=cv2.fillPoly(mask,np.array([[[0,368],[240,210],[300,210],[640,368]]]),color=255)
#cv2.imshow('mask',mask)
#cv2.waitKey(0)
mask_edge_img=cv2.bitwise_and(edge_img,mask)
cv2.imshow('mask_edge_img.jpg',mask_edge_img)
#cv2.imwrite('mask_edge_img.jpg',mask_edge_img)
cv2.waitKey(0)

insert image description here
A bit biased, more noise, move to the right

np.array([[[0, 368], [300, 210], [340, 210], [640, 368]]]),
                    color=255)

insert image description here

5. Hough transform

To find a straight line, use
insert image description here
a line in the Cartesian coordinate system, which is a point in the Hough space;
a line in the Hough space represents all the straight lines passing through a certain point in the Cartesian system;

insert image description here

insert image description here
! ! !
Therefore, after such a corresponding transformation idea, a point in the Hough space can be used as a parameter to determine a straight line determined by most points in the Cartesian space. The right figure
insert image description here
insert image description here
is the intersection diagram of the polar coordinate system corresponding to the Hough space

insert image description here
insert image description here

As shown in the figure, two straight lines and two points in the right picture indicate that there are many intersections. According to the coordinate parameters of these two points, determine the straight line

api:
The return value is a list, which represents the coordinates of the start point and end point of the line segment.
insert image description here
In opencv, the Hough transform must be performed in the grayscale image. When reading the image, you must pay attention to the format
insert image description here

Enter line by line in the console

import cv2

import numpy as np

img=cv2.imread('lines.jpg',cv2.IMREAD_GRAYSCALE)

lines = cv2.HoughLinesP(img, 1, np.pi / 180, 15, minLineLength=40,
                        maxLineGap=20)

lines
len(lines)

We intuitively feel the 2 lines, and found that there will be 32 straight lines in the output.
insert image description here
insert image description here
In fact, it is because of the original image we are dealing with. The straight line has a width and is thicker.
Looking at the parameters, there are many lines whose starting and ending coordinates are not much different, which is just a line segment.

In order to obtain two a priori, we have to classify and divide into two groups according to the slope

import cv2
import numpy as np


def calculate_slope(line):
    """
    计算线段line的斜率
    :param line: np.array([[x_1, y_1, x_2, y_2]])
    :return:
    """
    x_1, y_1, x_2, y_2 = line[0]
    return (y_2 - y_1) / (x_2 - x_1)


edge_img = cv2.imread('mask_edge_img.jpg', cv2.IMREAD_GRAYSCALE)
# 获取所有线段
lines = cv2.HoughLinesP(edge_img, 1, np.pi / 180, 15, minLineLength=40,
                        maxLineGap=20)
# 按照斜率正负分成车道线
left_lines = [line for line in lines if calculate_slope(line) > 0]
right_lines = [line for line in lines if calculate_slope(line) < 0]

Note that in opencv, the coordinate origin is in the upper left corner

insert image description here
insert image description here

6. Outlier filtering

The previous section is divided into two groups of lines according to the slope. However, due to errors and noises, they will be misidentified as lane lines. We need to further remove some of them. We know that the lines that fall on the lane line account for the majority, and the slope difference is large. we filter out

import cv2
import numpy as np

def calculate_slope(line):
    """
    计算线段line的斜率
    :param line: np.array([[x_1, y_1, x_2, y_2]])
    :return:
    """
    x_1, y_1, x_2, y_2 = line[0]
    return (y_2 - y_1) / (x_2 - x_1)

edge_img = cv2.imread('mask_edge_img.jpg', cv2.IMREAD_GRAYSCALE)
# 获取所有线段
lines = cv2.HoughLinesP(edge_img, 1, np.pi / 180, 15, minLineLength=40, maxLineGap=20)
# 按照斜率分成车道线
left_lines = [line for line in lines if calculate_slope(line) > 0]
right_lines = [line for line in lines if calculate_slope(line) < 0]

def reject_abnormal_lines(lines, threshold):
    """
    剔除斜率不一致的线段
    :param lines: 线段集合, [np.array([[x_1, y_1, x_2, y_2]]),np.array([[x_1, y_1, x_2, y_2]]),...,np.array([[x_1, y_1, x_2, y_2]])]
    """
    slopes = [calculate_slope(line) for line in lines]#建个斜率列表
    while len(lines) > 0:
        mean = np.mean(slopes)#计算斜率平均值
        diff = [abs(s - mean) for s in slopes]#计算每个斜率和平均值的差值
        idx = np.argmax(diff)# 获取array中数值最大的 找差值最大线段的索引
        if diff[idx] > threshold:#和阈值比一下,差得多不,超标就滚蛋
            slopes.pop(idx)#斜率列表中删掉
            lines.pop(idx)#从线段列表中删掉
        else:
            break
    return lines


print('before filter:')
print('left lines number=')
print(len(left_lines))
print('right lines number=')
print(len(right_lines))

reject_abnormal_lines(left_lines, threshold=0.2)
reject_abnormal_lines(right_lines, threshold=0.2)


print('after filter:')
print('left lines number=')
print(len(left_lines))
print('right lines number=')
print(len(right_lines))

insert image description here

7. Least squares fitting

Fit all lines considered to be left or right lanes into a single

np.ravel: Pull high-dimensional arrays into one-dimensional arrays
np.polyfit: polynomial fitting
insert image description here

np.polyval: polynomial evaluation, the first parameter is the polynomial coefficient, the second parameter is any X

def least_squares_fit(lines):
    """
    将lines中的线段拟合成一条线段
    :param lines: 线段集合, [np.array([[x_1, y_1, x_2, y_2]]),np.array([[x_1, y_1, x_2, y_2]]),...,np.array([[x_1, y_1, x_2, y_2]])]
    :return: 线段上的两点,np.array([[xmin, ymin], [xmax, ymax]])
    """
    # 1. 取出所有坐标点
    x_coords = np.ravel([[line[0][0], line[0][2]] for line in lines])
    y_coords = np.ravel([[line[0][1], line[0][3]] for line in lines])
    # 2. 进行直线拟合.得到多项式系数
    poly = np.polyfit(x_coords, y_coords, deg=1)
    # 3. 根据多项式系数,计算两个直线上的点,用于唯一确定这条直线
    point_min = (np.min(x_coords), np.polyval(poly, np.min(x_coords)))
    point_max = (np.max(x_coords), np.polyval(poly, np.max(x_coords)))
    return np.array([point_min, point_max], dtype=int)


print("left lane")
print(least_squares_fit(left_lines))
print("right lane")
print(least_squares_fit(right_lines))

Add this section of processing after the code in the previous section.
insert image description here
Two points are practiced as a line.

8. Draw a straight line

cv2.line
add the following code

left_line = least_squares_fit(left_lines)
right_line = least_squares_fit(right_lines)

img = cv2.imread('img.jpg', cv2.IMREAD_COLOR)
cv2.line(img, tuple(left_line[0]), tuple(left_line[1]), color=(0, 255, 255), thickness=5)
cv2.line(img, tuple(right_line[0]), tuple(right_line[1]), color=(0, 255, 255), thickness=5)

cv2.imshow('lane', img)
cv2.waitKey(0)

insert image description here
Why is there a large deviation of the left lane line visible to the naked eye?
Because the Canny edge detection threshold is adjusted, and the mask area at the front ROI mask is not set properly, as shown in the figure below, there is an edge of the vehicle at the left lane line, I did not carefully adjust the threshold, and the rear mask area is not covered
insert image description here
. Move to get a new picture and take part in the drawing:
insert image description here
so the adjustment depends on the situation

9. Video stream reading and writing

insert image description here

capture = cv2.VideoCapture('video.mp4')
while True:
    ret, frame = capture.read()#ret视频流状况,是否关闭
    frame = show_lane(frame)
    cv2.imshow('frame', frame)
    cv2.waitKey(100)

Guess you like

Origin blog.csdn.net/qq_44114055/article/details/132102522