[学习笔记]计算机视觉基础知识

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

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

颜色:

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

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)

执行结果:

区域:

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

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

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

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

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()

运行结果:

结合颜色和区域:

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

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()

执行结果:

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

其他颜色的车道线怎么办: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()

运行结果:

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

霍夫空间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)

运行结果:

区域+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)

运行结果:


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

作者:qy原文地址:https://segmentfault.com/a/1190000041892669

%s 个评论

要回复文章请先登录注册