一、获取左右车道线的原始位置
导入模块
import cv2
import numpy as np
from matplotlib import pyplot as plt
输入图像
img = cv2.imread('img_road.png')
获取图像的高和宽
height, width, _ = img.shape
对图像的高进行求和
num_ax0 = np.sum(img, axis=0)
显示图像求和图
plt.plot(num_ax0)
plt.show()
得到直方图左右两侧最高点的位置
img_left_argmax=np.argmax(num_ax0[:width//2])
img_right_argmax=np.argmax(num_ax0[width//2:])+width//2
二、绘制小窗口
获取图像中所有非零像素的x和y的位置
nonzeroy,nonzerox=np.array(img.nonzero())
定义一些小窗口的概念参数
# 定义小窗口的个数
windows_num=10
#小窗口的宽度和高度
windows_height=height//windows_num
windows_width=30
#小窗口白色个数阈值
min_pix=40
#初始化当前窗口的位置
left_current=img_left_argmax
right_current=img_right_argmax
创建空列表接收左侧和右侧车道线像素的索引
left_lane_inds=[]
right_lane_inds=[]
绘制小窗口一
for window in range(windows_num):
# 计算当前窗口的上边界的y坐标
win_y_high=height-windows_height*(window+1)
# 计算当前窗口的下边界的y坐标
win_y_low = height - windows_height * window
# 计算左边窗口左右边界的x坐标
win_x_left_left=left_current - windows_width
win_x_left_right=left_current + windows_width
# 计算右边窗口左右边界的x坐标
win_x_right_left=right_current - windows_width
win_x_right_right=right_current + windows_width
cv2.rectangle(out_img, (win_x_left_left, win_y_high), (win_x_left_right, win_y_low), (0, 255, 0), 2)
cv2.rectangle(out_img, (win_x_right_left, win_y_high), (win_x_right_right, win_y_low), (0, 255, 0), 2)
通过上述代码绘画出的小窗口没有随路线的弯曲程度而弯曲,需要该进
改进小窗口,让窗口随路线的走势而走势
for window in range(windows_num):
# 计算当前窗口的上边界的y坐标
win_y_high=height-windows_height*(window+1)
# 计算当前窗口的下边界的y坐标
win_y_low = height - windows_height * window
# 计算左边窗口左右边界的x坐标
win_x_left_left=left_current - windows_width
win_x_left_right=left_current + windows_width
# 计算右边窗口左右边界的x坐标
win_x_right_left=right_current - windows_width
win_x_right_right=right_current + windows_width
cv2.rectangle(out_img, (win_x_left_left, win_y_high), (win_x_left_right, win_y_low), (0, 255, 0), 2)
cv2.rectangle(out_img, (win_x_right_left, win_y_high), (win_x_right_right, win_y_low), (0, 255, 0), 2)
good_left_index=((nonzeroy >= win_y_high) & (nonzeroy < win_y_low)
& (nonzerox >= win_x_left_left) & (nonzerox < win_x_left_right)).nonzero()[0]
good_right_index=((nonzeroy >= win_y_high) & (nonzeroy < win_y_low)
& (nonzerox >= win_x_right_left) & (nonzerox < win_x_right_right)).nonzero()[0]
left_lane_inds.append(good_left_index)
right_lane_inds.append(good_right_index)
if len(good_left_index)>min_pix:
left_current = int(np.mean(nonzerox[good_left_index]))
else:
if len(good_right_index)>min_pix:
offset=int(np.mean(nonzerox[good_right_index]))-right_pre
left_current=left_current+offset
if len(good_right_index)>min_pix:
right_current = int(np.mean(nonzerox[good_right_index]))
else:
if len(good_left_index)>min_pix:
offset=int(np.mean(nonzerox[good_left_index]))-left_pre
right_current=right_current+offset
left_pre=left_current
right_pre=right_current
三、拟合车道线
#连接索引的列表,为了后续更方便的提取出这些像素点的和y的坐标,以便进行车道线的拟合
left_lane_inds=np.concatenate(left_lane_inds)
right_lane_inds=np.concatenate(right_lane_inds)
# 提取左侧和右侧车道线像素的位置
# left_lane_inds 是一个一维数组,它包含了左侧车道线在滑动窗口中找到的白色像素点的x坐标的索引
# 通过将这些索引作为索引器应用到 nonzerox数组上,就可以得到相应的左侧车道线的x坐标
# leftx 包含了左侧车道线自色像素点的华标
leftx=nonzerox[left_lane_inds]
lefty=nonzeroy[left_lane_inds]
rightx=nonzerox[right_lane_inds]
righty=nonzeroy[right_lane_inds]
# 有了坐标之后,就要去对左侧和右侧车道线进行多项式拟合,从而得到拟合的车道线# np.polyfit()是numpy中用于进行多项式拟合的函数
# 他接受三个参数:xy 和deg
# x:自变量数组,y:因变量数组
# deg:多项式的次数,如果是y=ax^2+b^x+C
# left_fit里存放的就是 a、b、c的参数,
left_fit=np.polyfit(lefty,leftx,2)
right_fit=np.polyfit(righty, rightx, 2)
# 使用np.linspace 生成一组均匀分布的数值,用于表示竖直方向上的像素坐标,方便后续的车道线的绘制
# 使用多项式拟合来估计左侧和右侧车道线的x坐标
# left_fitx 就是左侧拟合出来的车道线
ploty=np.linspace(0,height-1,height)
left_fitx=left_fit[0]*ploty**2+left_fit[1]*ploty+left_fit[2]
right_fitx = right_fit[0] * ploty ** 2 + right_fit[1] * ploty + right_fit[2]
# 计算中间车道线的位置
middle_fitx=(left_fitx+right_fitx)//2
# 使用不同颜色将车道线标出来
out_img[lefty,leftx]=[255,0,0]
out_img[righty,rightx]=[0,0,255]
四、完整代码
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread('img_road.png')
# 将图像复制到三个通道,以便于处理
out_img = np.dstack((img, img, img))
# 计算每列像素的总和,以便找到车道线的最高点
num_ax0 = np.sum(img, axis=0)
# 获取左右两侧最高点的位置
img_left_argmax = np.argmax(num_ax0[:width // 2]) # 左侧最高点
img_right_argmax = np.argmax(num_ax0[width // 2:]) + width // 2 # 右侧最高点
# 获取图像中所有非零像素的x和y的位置
nonzeroy, nonzerox = np.array(img.nonzero())
# 定义小窗口的个数
windows_num = 10
# 小窗口的高度和宽度
windows_height = height // windows_num
windows_width = 30
# 小窗口白色像素的个数阈值
min_pix = 400
# 初始化当前窗口的位置
left_current = img_left_argmax
right_current = img_right_argmax
# 记录上一个窗口的位置
left_pre = left_current
right_pre = right_current
# 创建空列表以接收左侧和右侧车道线像素的索引
left_lane_inds = []
right_lane_inds = []
# 遍历每个窗口
for window in range(windows_num):
# 计算当前窗口的上边界y坐标
win_y_high = height - windows_height * (window + 1)
# 计算当前窗口的下边界y坐标
win_y_low = height - windows_height * window
# 计算左侧窗口的左右边界x坐标
win_x_left_left = left_current - windows_width
win_x_left_right = left_current + windows_width
# 计算右侧窗口的左右边界x坐标
win_x_right_left = right_current - windows_width
win_x_right_right = right_current + windows_width
# 在图像上绘制窗口
cv2.rectangle(out_img, (win_x_left_left, win_y_high), (win_x_left_right, win_y_low), (0, 255, 0), 2)
cv2.rectangle(out_img, (win_x_right_left, win_y_high), (win_x_right_right, win_y_low), (0, 255, 0), 2)
# 找到在当前窗口内的好像素索引
good_left_index = ((nonzeroy >= win_y_high) & (nonzeroy < win_y_low)
& (nonzerox >= win_x_left_left) & (nonzerox < win_x_left_right)).nonzero()[0]
good_right_index = ((nonzeroy >= win_y_high) & (nonzeroy < win_y_low)
& (nonzerox >= win_x_right_left) & (nonzerox < win_x_right_right)).nonzero()[0]
# 将找到的索引添加到列表中
left_lane_inds.append(good_left_index)
right_lane_inds.append(good_right_index)
# 更新当前窗口位置
if len(good_left_index) > min_pix:
left_current = int(np.mean(nonzerox[good_left_index])) # 更新左侧窗口位置
else:
if len(good_right_index) > min_pix:
offset = int(np.mean(nonzerox[good_right_index])) - right_pre # 计算偏移量
left_current = left_current + offset # 更新左侧窗口位置
if len(good_right_index) > min_pix:
right_current = int(np.mean(nonzerox[good_right_index])) # 更新右侧窗口位置
else:
if len(good_left_index) > min_pix:
offset = int(np.mean(nonzerox[good_left_index])) - left_pre # 计算偏移量
right_current = right_current + offset # 更新右侧窗口位置
# 更新上一个窗口位置
left_pre = left_current
right_pre = right_current
# 将找到的索引合并为一个数组
left_lane_inds = np.concatenate(left_lane_inds)
right_lane_inds = np.concatenate(right_lane_inds)
# 获取左右车道线的x和y坐标
leftx = nonzerox[left_lane_inds]
lefty = nonzeroy[left_lane_inds]
rightx = nonzerox[right_lane_inds]
righty = nonzeroy[right_lane_inds]
# 使用多项式拟合左侧和右侧车道线
# 有了坐标之后,就要去对左侧和右侧车道线进行多项式拟合,从而得到拟合的车道线# np.polyfit()是numpy中用于进行多项式拟合的函数
# 他接受三个参数:xy 和deg
# x:自变量数组,y:因变量数组
# deg:多项式的次数,如果是2y=ax^2+b^x+C
# left_fit里存放的就是 a、b、c的参数,
left_fit = np.polyfit(lefty, leftx, 2) # 左侧拟合
right_fit = np.polyfit(righty, rightx, 2) # 右侧拟合
# 创建y坐标的线性空间
ploty = np.linspace(0, height - 1, height)
# 根据拟合的多项式计算x坐标
left_fitx = left_fit[0] * ploty ** 2 + left_fit[1] * ploty + left_fit[2]
right_fitx = right_fit[0] * ploty ** 2 + right_fit[1] * ploty + right_fit[2]
# 计算中间车道线的x坐标
middle_fitx = (left_fitx + right_fitx) // 2
# 在输出图像上标记左侧和右侧车道线像素
out_img[lefty, leftx] = [255, 0, 0] # 左侧车道线为红色
out_img[righty, rightx] = [0, 0, 255] # 右侧车道线为蓝色
# 显示结果图像
cv2.imshow('out_img', out_img)
cv2.waitKey(0)
- 图像读取和准备:读取图像并为处理做好准备。
- 直方图计算:计算像素值的总和,以找到车道的位置。
- 滑动窗口:使用滑动窗口技术识别车道像素。
- 车道像素索引:收集左侧和右侧车道的好像素索引。
- 多项式拟合:对识别出的车道点进行多项式拟合,以生成平滑的车道线。
- 可视化:在输出图像上绘制检测到的车道线并展示。
五、库函数
5.1、nonzero()
返回数组中非零元素的索引
numpy.nonzero(a)
用途:它通常用来查找非零元素的位置,常用于图像处理中筛选出特定的像素。
返回值:函数返回一个元组,每个元素是一个数组,表示非零元素在每个维度上的索引。例如,对于二维数组 A
,A.nonzero()
返回两个数组,第一个数组表示行索引,第二个数组表示列索引。
import numpy as np
arr=np.array(
[
[0,1,2,3,4,5,6],
[1,2,3,4,5,6,7],
[0,0,0,0,0,0,0]
]
)
print(arr.nonzero())
#(array([0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]), array([1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6]))
5.2、polyfit()
用于计算数据点的多项式拟合
numpy.polyfit(x, y, deg, rcond=None, full=False, w=None, cov=False)
方法 | 描述 |
---|---|
x | M 个采样点的 x 坐标。(x[i], y[i]) |
y | 采样点的 y 坐标。样本的多个数据集 共享相同 x 坐标的点可以通过以下方式一次拟合 传入一个 2D 数组,该数组每列包含一个数据集。 |
deg | 拟合多项式的次数 |
rcond | 拟合的相对条件编号。小于 this 相对于最大奇异值将被忽略。这 默认值为 len(x)*eps,其中 eps 是 float 类型,大多数情况下约为 2E-16。 |
full | Switch 确定返回值的性质。当为 False 时( default) 仅返回系数,当 True diagnostic 还会返回来自奇异值分解的信息。 |
w | 权重。如果不是 None,则权重适用于未平方的 残差 。理想情况下,权重为 选中,这样产品的误差都具有 相同的方差。使用逆方差加权时,请使用 。默认值为 None。w[i] y[i] - y_hat[i] x[i] w[i]*y[i] w[i] = 1/sigma(y[i]) |
cov | 如果给定且非 False,则不仅返回估计值,还返回其 协方差矩阵。默认情况下,协方差的缩放比例为 chi2/dof,其中 dof = M - (度 + 1),即假定权重 不可靠,除非在相对意义上,并且一切都是缩放的 使得还原的 Chi2 是 Unity。如果 ,则省略此缩放,因为与权重 w = 1/sigma,其中已知 sigma 是 不确定性。cov='unscaled' |
import numpy as np
x = np.array([1, 2, 3, 4])
y = np.array([2, 3, 5, 7])
# ax**2+bx+c
a,b,c = np.polyfit(x, y, 2) # 进行二次多项式拟合
print(a,b,c)
#[0.25 0.45 1.25]
5.3、linspace()
用于生成指定范围内的等间距数字
numpy.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0, *, device=None)
方法 | 描述 |
---|---|
start | 序列的起始值。 |
stop | 序列的结束值,除非 endpoint 设置为 False。 在这种情况下,序列由除最后一个均匀分布的样本之外的所有样本组成,因此不包括 stop。请注意,步骤 当 endpoint 为 False 时,size 会发生变化。num + 1 |
num | 要生成的样本数。默认值为 50。必须为非负数。 |
endpoint | 如果为 True,则 stop 是最后一个样本。否则,不包括在内。 默认值为 True。 |
retstep | 如果为 True,则返回 (samples, step),其中 step 是间距 样本之间。 |
dtype | 输出数组的类型。如果未给出,则数据类型 是从 start 和 stop 推断出来的。推断的 dtype 永远不会是 整数;float 的 整数数组。 |
axis | 结果中用于存储样本的轴。仅在启动时相关 或 stop 是类似数组的。默认情况下 (0),样本将沿着 在开头插入新轴。使用 -1 在末尾获取一个轴。 |
device | 要放置创建的阵列的设备。默认值:None。 仅对于 Array-API 互作性,如果通过,则必须这样做。 |
import numpy as np
samples = np.linspace(0, 1, 5) # 生成 0 到 1 之间的 5 个等间距点
print(samples)
# [0. 0.25 0.5 0.75 1. ]
5.4、dstack()
用于沿着深度(第三个轴)拼接数组。
numpy.dstack(tup)
方法 | 描述 |
---|---|
tup | 数组沿除第三个轴之外的所有轴必须具有相同的形状。 一维或二维数组必须具有相同的形状。 |
import numpy as np
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
c = np.dstack((a, b)) # 沿着深度轴拼接,结果形状为 (2, 2, 2)
print(c)
'''
[[[1 5]
[2 6]]
[[3 7]
[4 8]]]
'''