【超全注释】基于Canny边缘检测与霍夫变换的车道线边缘检测学习笔记

一、颜色选择(Color Selection)

1 原理

从下图中可以看出,车道线的颜色信息是可以将它和周围环境区分开来的明显特征。因此可以通过颜色选择对图像上的车道线相关信息进行提取。

设定三个颜色通道的阈值,选出小于阈值的像素并将它们的值置为0(黑色),对大于等于阈值的像素进行保留。

2 代码与结果

#导入依赖库
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np


# 利用matplotlib.image读取图片数据,并打印出图像的数据类型和数据维度
image = mpimg.imread('img.bmp')
print('This image is: ',type(image),            # 将会输出<class 'numpy.ndarray'>
         'with dimensions:', image.shape)       # 将会输出(540, 960, 3)


# 获取图片的高度和宽度
ysize = image.shape[0]      # 高度
xsize = image.shape[1]      # 宽度
# 深拷贝
color_select = np.copy(image)


# 设定颜色阈值
red_threshold = 200
green_threshold = 200
blue_threshold = 200
# 利用上述三个阈值生成rgb_threshold阈值 。
rgb_threshold = [red_threshold, green_threshold, blue_threshold]


# 选出小于阈值的像素并将它们的值置为0(黑色),对大于等于阈值的像素进行保留.
thresholds = (image[:,:,0] < rgb_threshold[0]) \
            | (image[:,:,1] < rgb_threshold[1]) \
            | (image[:,:,2] < rgb_threshold[2])
color_select[thresholds] = [0,0,0]      # to black


# 展示结果
plt.imshow(color_select)
plt.show()


# 保存结果
mpimg.imsave("颜色选择结果.png", color_select)

二、区域筛选(Region Masking)

1 原理

通过上一节简单的颜色选择得到了上图所示的包含车道线的像素信息,下面需要进一步提取出当前车辆所在的车道线信息。暂时假设相机位于车辆正中,且相机视角中心处于车辆正前方,且当前车道为直线车道。因此应该在如图所示的红线区域内提取当前车道线。

下面我们需要将感兴趣区域筛选与颜色选择结合起来,对经过颜色选择得到的像素信息进行区域筛选,并将符合条件的像素置为红色,即可得到当前车道的车道线信息。

2 代码与结果

#导入依赖库
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np


# 利用matplotlib.image读取图片数据,并打印出图像的数据类型和数据维度
image = mpimg.imread('img.bmp')
print('This image is: ',type(image),            # 将会输出<class 'numpy.ndarray'>
         'with dimensions:', image.shape)       # 将会输出(540, 960, 3)


# 获取图片的高度和宽度
ysize = image.shape[0]      # 高度
xsize = image.shape[1]      # 宽度
# 深拷贝
color_select = np.copy(image)
line_image = np.copy(image)


# 设定颜色阈值
red_threshold = 200
green_threshold = 200
blue_threshold = 200
# 利用上述三个阈值生成rgb_threshold阈值 。
rgb_threshold = [red_threshold, green_threshold, blue_threshold]


# 定义三角形区域
# (x=0, y=0)是最左上角的位置
left_bottom = [0, 539]      #三角区域的左下角
right_bottom = [900, 539]   #三角区域的右下角
apex = [475, 320]           #三角区域的顶点


# 利用polyfit函数根据直线的两个端点得到连线,输入参数“1”表示曲线最高阶数为1(直线)
# 得到直线 ax+b 的参数 a和b
fit_left = np.polyfit((left_bottom[0], apex[0]), (left_bottom[1], apex[1]), 1)
fit_right = np.polyfit((right_bottom[0], apex[0]), (right_bottom[1], apex[1]), 1)
fit_bottom = np.polyfit((left_bottom[0], right_bottom[0]), (left_bottom[1], right_bottom[1]), 1)


# 选出低于阈值的像素并将它们的值置为0(黑色),对高于阈值的像素进行保留.
color_thresholds = (image[:,:,0] < rgb_threshold[0]) \
            | (image[:,:,1] < rgb_threshold[1]) \
            | (image[:,:,2] < rgb_threshold[2])
color_select[color_thresholds] = [0,0,0]      # to black


# 找出函数内的区域(三角区域)
XX, YY = np.meshgrid(np.arange(0, xsize), np.arange(0, ysize))
region_thresholds = (YY > (XX*fit_left[0] + fit_left[1])) & \
                    (YY > (XX*fit_right[0] + fit_right[1])) & \
                    (YY < (XX*fit_bottom[0] + fit_bottom[1]))
# 将原图中大于等于颜色选择阈值且位于感兴趣区域中的像素设置为红色。
line_image[~color_thresholds & region_thresholds] = [255,0,0]


# 结果展示
# plt.imshow(color_select)
plt.imshow(line_image)
plt.show()


# 保存结果
mpimg.imsave("区域筛选结果.png", line_image)

 

三、Canny边缘检测

1 原理

Canndy边缘检测的算法原理:

  1. 将彩色图像转换为灰度图像。
  2. 然后利用高斯滤波器对图像进行高斯模糊去噪。
  3. 对去噪后的灰度图计算梯度(gradient),得到图像中每个像素点对应的强度梯度。梯度值代表了该像素点与周围像素值的变化关系,变化越大则梯度值越大。一般来说,在图像中物体的边缘通常会表现出较大的梯度值。
  4. Canny算法可以从梯度图像中提取出像素梯度大小处于一定范围的边界点集合,并针对较粗的边缘,利用非极大值抑制算法,过滤掉边缘附近的部分像素,从而只保留图像中位于实际边缘的较细的边缘线。

在OpenCV中的Canny算法函数接口定义如下:

edges=cv2.Canny(image, low_threshold, high_threshold)

该函数能够利用Canny算法检测输入图像image中的边缘,并将它们标记在输出图像edges中。

其中2个阈值参数:

  • 低于阈值low_threshold的像素点会被认为不是边缘。
  • 高于阈值high_threshold的像素点会被认为是边缘。
  • 在阈值low_threshold和阈值high_threshold之间的像素点,若与第2步得到的边缘像素点相邻,则被认为是边缘,否则被认为不是边缘。

2 代码与结果

#导入依赖库
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import cv2

# rgb转灰度图
image = mpimg.imread('img.bmp')
gray_img = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)

# 设定高斯平滑的kernel的大小
kernel_size = 5
blur_gray = cv2.GaussianBlur(gray_img,(kernel_size, kernel_size), 0)

# 设定参数
low_threshold = 50
high_threshold = 150
edges = cv2.Canny(blur_gray, low_threshold, high_threshold)

# 展示结果
plt.imshow(edges, cmap='Greys_r')
​​​​​​​
# 保存结果
mpimg.imsave("Canny边缘检测结果.png", edges)

四、霍夫变换

1 原理

霍夫变换的算法流程大致如下:给定一个物件、要辨别的形状的种类,算法会在参数空间(parameter space)中执行投票来决定物体的形状,而这是由累加空间(accumulator space)里的局部最大值(local maximum)来决定。

具体到本次任务中,即通过霍夫变换将Canny算法检测到的边缘点中呈现为直线的车道线找出来。

在图像空间(Image Space)中可以将车道线视为直线,形式为:

y=m_{0}x+b_{0}

而在对应的霍夫空间(Hough Space,也可以简单将其理解为“参数空间”)中,相应的直线可以直接用 m_{0} 和 b_{0} 的组合来表示。于是经过霍夫变换,图像空间中的一条直线将被转换为霍夫空间中的一个点,如下图所示。

同理,有:

图像空间中的点 (x, y) 对应霍夫空间下的直线b=y-xm

霍夫空间下的两条直线的交点对应图像空间中的一条直线,该直线上存在两个点分别与霍夫空间中的两条直线对应。

图像空间中的一条直线的多个点,对应霍夫空间中的多条直线相交于一点,示意图如下。

Canny边缘检测得到的结果是图像中所有被视为边缘的点,这些点也就对应于霍夫空间中的多条直线。要想提取出图像空间中处于同一直线上的点,也就是需要找到霍夫空间中相交于一点的直线。

由于用 y = m x + b 的形式表示直线,会存在平行于 y 轴的直线斜率为无穷的情况,因此我们采用“sine Hough”空间用来避免这一问题。在图像空间中,我们用 ρ 来表示直线到原点的距离,用 θ 表示从 x 轴到直线垂线的角度,具体表示形式如下图所示。即在霍夫空间用参数( θ , ρ ) 来代替参数( m , b )。

基于此,图像空间中的一点将对应“sine Hough”空间下的一条正弦曲线,如下图所示:

在OpenCV中函数HoughLinesP的声明:

lines = cv2.HoughLinesP(masked_edges, rho, theta, threshold, np.array([]),
                                             min_line_length, max_line_gap)

其中,masked_edges是输入的边缘图像(来自Canny算法输出),该函数的输出lines是包含线段起始点信息(x1,y1,x2,y2)的数组。其他参数定义了需要的是什么样的线段。

首先,ρ 和 θ 分别表示霍夫变换算法中栅格的距离和角度分辨率。需要注意的是,在霍夫空间中的( θ , ρ ) 坐标系中定义了栅格,用来近似化求解空间中曲线的交点等信息,ρ 的单位应为像素,最小值为1; θ 的单位应为弧度,初始值通常可设置为1度(pi/180 rad)。

threshold参数指定算法中投票的最小值(即给定栅格中的曲线交点个数),只有当某个栅格中的曲线交点个数大于threshold时,才认为其是一条应该输出的线段。

参数中的 np.array([]) 只是一个 placeholder,使用时无需修改。参数 min_line_length 是一条输出线段的最小长度(单位为像素),参数 max_line_gap 是连接两个独立线段成为一条输出线段的最大线段间隔(单位为像素)。

OpenCV的 cv2.fillPoly() 函数可以用来实现对四边形区域的筛选(该函数其实可以实现对任意复杂的多边形区域进行筛选)。

2 代码与结果

加载图像->灰度转换->高斯模糊->Canny边缘检测->fillPoly目标区域筛选->HoughLinesP直线提取->结果可视化

#导入依赖库
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
import cv2


# rgb转换为灰度图像
image = mpimg.imread('img.bmp')
gray_img = cv2.cvtColor(image,cv2.COLOR_RGB2GRAY)


# 设定 kernel size 并应用于高斯平滑
kernel_size = 5
blur_gray = cv2.GaussianBlur(gray_img,(kernel_size, kernel_size),0)


# 设定Canny边缘检测参数
low_threshold = 50
high_threshold = 150
edges = cv2.Canny(blur_gray, low_threshold, high_threshold)


# 创建掩区
mask = np.zeros_like(edges)   # 与edges一样shape和type的全0数组
ignore_mask_color = 255   


# 设置四边形掩区
imshape = image.shape


# 设置四边形掩区的四个顶点
vertices = np.array([[(0,imshape[0]),(450, 350), (490, 350), (imshape[1],imshape[0])]], dtype=np.int32)
cv2.fillPoly(mask, vertices, ignore_mask_color)
masked_edges = cv2.bitwise_and(edges, mask)  # 取交集


plt.imshow(edges)
plt.show()


plt.imshow(mask)
plt.show()


plt.imshow(masked_edges)
plt.show()

# 设定霍夫曼变换参数
rho = 2 # 霍夫变换算法中栅格的距离
theta = np.pi/180 # 角度分辨率
threshold = 15     # 投票的最小值(即给定栅格中的曲线交点个数)
min_line_length = 30 # 一条输出线段的最小长度(单位为像素)
max_line_gap = 20    # 连接两个独立线段成为一条输出线段的最大线段间隔(单位为像素)。
line_image = np.copy(image)*0 # 创建空白去画线


# lines是包含线段起始点信息(x1,y1,x2,y2)的数组
lines = cv2.HoughLinesP(masked_edges, rho, theta, threshold, np.array([]),
                            min_line_length, max_line_gap)


# 遍历并画出lines
for line in lines:
    for x1,y1,x2,y2 in line:
        # img, pt1, pt2, color, thickness
        cv2.line(line_image,(x1,y1),(x2,y2),(255,0,0),10)


# 二维转三维(因为line_image为RGB图像)
color_edges = np.dstack((edges, edges, edges)) 


# 重叠color_edges和line_image。其中,color_edges不透明度0.8,line_image不透明度1.
lines_edges = cv2.addWeighted(color_edges, 0.8, line_image, 1, 0) 


# 展示结果
plt.imshow(lines_edges)
plt.show()


# 保存结果
mpimg.imsave("霍夫变换结果.png", lines_edges)

五、参考文章

【自动驾驶技术】优达学城无人驾驶工程师学习笔记(七)——计算机视觉基础_尚庆龙的博客-CSDN博客_优达学城 自动驾驶

  • 2
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

人工智能技术小白修炼手册

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值