Q:给定图片,如何识别车道线?

test.jpg

可以从颜色、形状、方向、图像中的位置 几个角度来确定车道线。

颜色:

利用颜色来判断车道线(图中的车道线是白色的)

RGB图片有三个颜色通道R、G、B,每个通道中的每一个像素都是0到255范围内的值。
其中0是最暗值,255是最亮值。
因此RGB图像中,纯白色是255,255,255

尝试过滤白色外的其他颜色:

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
print('start')

# 读取图片,展示原图
image=mpimg.imread('test.jpg')
plt.imshow(image)
plt.show()

# 备份图片,不修改原图
cp_image=np.copy(image)

# 定义筛选阈值,白色是255,255,255,因此我们选比255稍小的值即可,这里选择200
r_threshold=200
g_threshold=200
b_threshold=200
rgb_thresholds=[r_threshold,g_threshold,b_threshold]

# 筛选器,筛选出低于阈值的像素
thresholds=(image[:,:,0]<rgb_thresholds[0]) | (image[:,:,1]<rgb_thresholds[1]) | (image[:,:,2]<rgb_thresholds[2])

# 将不满足条件的值设为0,0,0,即黑色
cp_image[thresholds]=[0,0,0]

# 展示图片
plt.imshow(cp_image)
plt.show()

# 保存图片
mpimg.imsave('after_color.jpg',cp_image)

执行结果:
image.png

区域:

发现只靠颜色无法准确检测出车道线,因为其他物体也有白色。

现在我们假设车道线一定是在车辆前端的固定区域内:
image.png

考虑只对该区域进行颜色处理。

首先我们要能够选出一个三角形区域:

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np

print('start')

# 读取图片,展示
image=mpimg.imread('test.jpg')
plt.imshow(image)
plt.show()

# 显示图片大小
print(image.shape)  # 结果:(540,960,3)

# 备份图片,不修改原图
cp_image=np.copy(image)

# 指定区域点,这里是三角形区域,构造三角形的三个点
# 需要注意的是,x轴是在上面的,y轴是从上往下的,和平常的坐标轴不太一样
p_left=[0,550]
p_right=[900,550]
p_mid=[400,200]

# 构造三角形的三条边
# np.polyfit似乎是给定一组点,拟合出一个多项式的函数
# 我们这里用来构造直线方程
# 末尾的参数1表示构造一次方程
line_left=np.polyfit((p_left[0],p_mid[0]),(p_left[1],p_mid[1]),1)
line_right=np.polyfit((p_mid[0],p_right[0]),(p_mid[1],p_right[1]),1)
line_bottom=np.polyfit((p_left[0],p_right[0]),(p_left[1],p_right[1]),1)

# 筛选器,用于筛选区域内的像素
# 首先要构造位置矩阵
# np.meshgrid传入X可选值域和Y值域,返回所有可选的坐标
# np.arange(0,xsize,step)是构造[0,xsize)中步长为step的等差数列,默认步长为1
# np.arange()和np.linespace()的区别在于,arange传入的是步长,linespace传入的是个数
ysize=cp_image.shape[0] # 注意:行是y
xsize=cp_image.shape[1] # 注意:列是x
X,Y=np.meshgrid(np.arange(0,xsize),np.arange(0,ysize))
region_threshold=((X*line_left[0]+line_left[1])<Y) \
                & ((X*line_right[0]+line_right[1])<Y) \
                & ((X*line_bottom[0]+line_bottom[1])>Y)

# 绘制区域,将区域内的部分涂成红色
cp_image[region_threshold]=[255,0,0]

# 展示区域
plt.imshow(cp_image)
plt.show()

运行结果:
image.png

结合颜色和区域:

只在特定区域内进行颜色处理:

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np

print('start')

image = mpimg.imread('test.jpg')
plt.imshow(image)
plt.show()

# 构造颜色筛选器
r_threshold=200
g_threshold=200
b_threshold=200
rgb_thresholds=[r_threshold,g_threshold,b_threshold]

color_thresholds=(image[:,:,0]<rgb_thresholds[0]) \
                | (image[:,:,1]<rgb_thresholds[1]) \
                | (image[:,:,2]<rgb_thresholds[2])

# 构造区域筛选器
p_left=[0,540]
p_right=[900,540]
p_mid=[500,300]
line_left=np.polyfit((p_left[0],p_mid[0]),(p_left[1],p_mid[1]),1)
line_right=np.polyfit((p_mid[0],p_right[0]),(p_mid[1],p_right[1]),1)
line_bottom=np.polyfit((p_left[0],p_right[0]),(p_left[1],p_right[1]),1)

ysize=image.shape[0] # 注意:行是y
xsize=image.shape[1] # 注意:列是x
X,Y=np.meshgrid(np.arange(0,xsize),np.arange(0,ysize))
region_threshold=((X*line_left[0]+line_left[1])<Y) \
                & ((X*line_right[0]+line_right[1])<Y) \
                & ((X*line_bottom[0]+line_bottom[1])>Y)

# 显示区域
# plt.ploy()是画直线用的
# plt.ploy()的第三个参数中,b表示颜色blue,--表示虚线,b--即蓝色虚线
cp_image=np.copy(image)
x=[p_left[0],p_mid[0],p_right[0],p_left[0]]
y=[p_left[1],p_mid[1],p_right[1],p_left[1]]
plt.plot(x,y,'b--',lw=5)
plt.imshow(cp_image)
plt.show()

# 对区域内进行颜色处理
# cp_image[~region_threshold]=[0,0,0]
cp_image[region_threshold&~color_thresholds]=[255,0,0]
plt.imshow(cp_image)
plt.show()

执行结果:
image.png

image.png

这样就实现了仅在特定区域内筛选颜色

其他颜色的车道线怎么办:canny边缘检测算法

然而,车道线不仅仅都是白色,有可能出现其他颜色。
我们甚至无法预先知道车道线的颜色,这时候怎么办呢?
有没有办法能够处理任何颜色的线条?

图像是x - y的数学函数,因此也可以对他进行数学运算,例如求导。
图像是二维的,因此对于x和y同时求导是有意义的,这称为梯度。
我们测量像素点在每个位置上的变化程度,以及哪个方向变化最快,

通过梯度计算,能够获得较粗的边缘线,
利用canny算法,我们通过仅保留梯度最大的像素点,将边缘细化,
然后,再通过包含一些梯度强度更低一些的像素点,再次扩展高强度边缘的宽度。
梯度强度低的像素点阈值是我们调用canny函数时可以自己定的。

利用canny算法检测边缘线:

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import cv2

image = mpimg.imread('exit-ramp.jpg')
plt.imshow(image)
plt.show()

# 转化为灰度图片,灰度的目的'应该'是更好的检测梯度变化,避免颜色干扰
gray_image=cv2.cvtColor(image,cv2.COLOR_RGB2GRAY)
plt.imshow(gray_image,cmap='gray')
plt.show()

# 先进行高斯模糊,高斯模糊本质上是抑制噪声和伪梯度平均的一种方法
# 实际上cv2.Canny()内部自带高斯平滑,但再做一次可以进一步平滑
kernel_size=5   # 可以选择任意奇数,越大越平滑
blur_gray_image=cv2.GaussianBlur(gray_image,(kernel_size,kernel_size),0)

# 算法首先会检测出>high_threshold的强像素,并拒绝<low_threshold的强像素
# 接着,在low_threshold和high_threshold之间的像素,和强像素联通的保留
# 输出的edges在边缘位置是白色的,非边缘位置是黑色的
# 参数取值:由于像素值是256的,因此阈值可以选成十上百
# 官方建议low与high的比值为1:2或1:3
low_threshold=50
high_threshold=150
edges=cv2.Canny(blur_gray_image,low_threshold,high_threshold)

plt.imshow(edges,cmap='Greys_r')
plt.show()

运行结果:

image.png

image.png

如何将边缘点连成直线:霍夫变换

霍夫空间Hough Space:
    图像空间中的直线y=mx+b中的斜率m和截距b是未知参数,
        由这两个未知参数构造的空间就是对应的霍夫空间。
    图像空间的直线是霍夫空间中的点:
        y=mx+b,映射到霍夫空间就是(m,b)
    图像空间的点是霍夫空间中的线:
        y=mx+b,先转为b=y-mx。
        由于k对应霍夫空间的横坐标,b对应霍夫空间的纵坐标
        所以点对应霍夫空间中的直线b=-xm+y,此处x和y变为了斜率和截距
Q:霍夫空间中相交的线,如何在图像空间中表示他们霍夫空间中的交点?
A:霍夫空间中点在图像空间中是线,
    交点(m,b)中的m对应图像空间的斜率,b对应图像空间中的截距。
    由于霍夫空间是图像空间位置参数构造的空间,
        图像空间的点是霍夫空间中的线,
            线上的点对应图像空间中过点的所有直线
        因此对于霍夫空间的交点,在图像空间中是过图像空间两个点
在图像空间中找过许多点的直线,可以转化为在霍夫空间中找许多直线的交点
    过霍夫空间中交点的所有直线,就对应图像空间中一条直线的点集
为了实现这一点,我们将霍夫空间划分为网格,将同时穿过一个网格的所有线定义为交叉线,
    在同一个格子中就看作交点(用网格有一定的容错率,带拟合效果)
但是这样有一个问题:
    图像空间中的垂直线,斜率无穷大,无法在霍夫空间中用参数m-b表示。
    正弦霍夫空间可以解决这个问题。
正弦霍夫空间:
    在极坐标中用(p,st)定义直线:
        p表示直线与原点之间的垂直距离
        st表示直线与水平方向之间的夹角
    这样,图像空间中的点就对应正弦霍夫空间中的一条正弦曲线
    图像空间中的直线点集,就对应正弦霍夫空间中的许多正弦曲线
    正弦曲线的交点就是这条直线在霍夫空间中的参数

应用霍夫变换绘制直线:

# Do relevant imports
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
import cv2

# 读取图片
image=mpimg.imread('exit-ramp.jpg')

# 转为灰度图片,便于边缘检测
gray=cv2.cvtColor(image,cv2.COLOR_RGB2GRAY)

# 高斯模糊
kernel_size=5
blur_gray=cv2.GaussianBlur(gray,(kernel_size, kernel_size),0)

# 筛选图像边缘
low_threshold=50
high_threshold=150
masked_edges=cv2.Canny(blur_gray, low_threshold, high_threshold)
plt.imshow(masked_edges,cmap='gray')
plt.show()

# Define the Hough transform parameters
# Make a blank the same size as our image to draw on
# 霍夫变换
rho=1 # 网格的距离
theta=np.pi/180   # 网格的角分辨率(?)//TODO
threshold=1   # 最小投票数,网格中穿过至少threshold条直线时视为交点
min_line_length=10    # 输出中接收的线的最小长度,太小的不输出,单位:像素
max_line_gap=1    # 允许连接成线的线段最大距离,超过这个距离不会连接,单位:像素
lines=cv2.HoughLinesP(masked_edges, rho, theta, threshold,
                        np.array([]),min_line_length, 
                        max_line_gap)  # np.array([])只是占位符

# 绘制图像,将线段画在空白背景上
line_image=np.copy(image)*0 #黑色背景图,用于将直线画在上面
for line in lines:
    for x1,y1,x2,y2 in line:
        # 画红色线,粗度为10
        cv2.line(line_image,(x1,y1),(x2,y2),(255,0,0),10)   
plt.imshow(line_image)
plt.show()

# 查看边缘图和直线图的属性
print('shape of edges:',masked_edges.shape)
print('shape of lines:',line_image.shape)

# 将边缘图从单通道图片重叠为三通道图片
# 因为后续的图片合并,要求两张图片尺寸和通道数量相同
color_edges=np.dstack((masked_edges, masked_edges, masked_edges)) 

# 合并两张尺寸相同的图片
# 每张图片有权值
# 最后面的参数是gamma修正系数,0表示不修正
combo=cv2.addWeighted(color_edges, 0.8, line_image, 1, 0) 
plt.imshow(combo)

运行结果:
image.png

image.png

区域+canny边缘检测+霍夫变换:

# Do relevant imports
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
import cv2

# 读取图片
image=mpimg.imread('exit-ramp.jpg')

# 转为灰度图片,便于边缘检测
gray=cv2.cvtColor(image,cv2.COLOR_RGB2GRAY)

# 高斯模糊
kernel_size=5
blur_gray=cv2.GaussianBlur(gray,(kernel_size, kernel_size),0)

# 筛选图像边缘
low_threshold=50
high_threshold=150
edges=cv2.Canny(blur_gray, low_threshold, high_threshold)
plt.imshow(edges,cmap='gray')
plt.show()

# 构造一个四边形区域,划分车道线:
# dtype可以指定数据类型,这里指定为int32
vertices=np.array([[(0,imshape[0]),(450, 290), (490, 290), (imshape[1],imshape[0])]], dtype=np.int32)
mask=np.zeros_like(masked_edges) # 构造一个尺寸和edges相同的空图片
cv2.fillPoly(mask,vertices,255)  # 根据多边形填充图片,多边形内填充白色
masked_edges=cv2.bitwise_and(mask,edges)    # &一下,只留下区域内的边缘点
plt.imshow(masked_edges)
plt.show()

# 霍夫变换,将边缘点变成线
rho=1 # 网格的距离
theta=np.pi/180   # 网格的角分辨率(?)//TODO
threshold=1   # 最小投票数,网格中穿过至少threshold条直线时视为交点
min_line_length=10    # 输出中接收的线的最小长度,太小的不输出,单位:像素
max_line_gap=1    # 允许连接成线的线段最大距离,超过这个距离不会连接,单位:像素
lines=cv2.HoughLinesP(masked_edges, rho, theta, threshold,
                        np.array([]),min_line_length, 
                        max_line_gap)  # np.array([])只是占位符

# 绘制图像,将线段画在空白背景上
line_image=np.copy(image)*0 #黑色背景图,用于将直线画在上面
for line in lines:
    for x1,y1,x2,y2 in line:
        # 画红色线,粗度为10
        cv2.line(line_image,(x1,y1),(x2,y2),(255,0,0),10)   
plt.imshow(line_image)
plt.show()

# 查看边缘图和直线图的属性
print('shape of edges:',masked_edges.shape)
print('shape of lines:',line_image.shape)

# 将边缘图从单通道图片重叠为三通道图片
# 因为后续的图片合并,要求两张图片尺寸和通道数量相同
color_edges=np.dstack((edges, edges, edges)) 

# 合并两张尺寸相同的图片
# 每张图片有权值
# 最后面的参数是gamma修正系数,0表示不修正
combo=cv2.addWeighted(color_edges, 0.8, line_image, 1, 0) 
plt.imshow(combo)

运行结果:
image.png
image.png
image.png

至此,我们就成功的初步筛选出车道线了。


qy
1 声望1 粉丝