代码位置:Github地址
项目是学校项目设计的课程项目,要求使用基于树莓派的小车实现部分功能。
在该实验中实现了 小车的避障、单线巡线、颜色追踪任务,后文会附出已实现功能的实现过程和代码,在参考的资料中有部分有待实现的内容以供参考,如有需要的同学可以参考本博客和参考教程实现树莓派小车各种功能的控制。
学校提供的小车的商家是慧净电子,商家提供了一些使用教程,适合初学,基于C语言,实现了一些简单的红外避障、红外寻迹、超声波避障和摄像头调用。
项目使用Python作为编程语言,本人也建议使用Python实现,尤其是希望实现复杂功能的同学:首先,相较于C++,Python更容易上手,可以更方便实现各种功能;其次,Python有很多集成好的深度学习框架,如Tensorflow、Pytorch,对于一些复杂的功能可以很好地实现。但是同时,Python在运行速度方面存在劣势。
完成该实验需要做好前期准备,在此基础上为代码创建环境,理解代码逻辑,对其中使用的超参数结合实际情况进行修改,运行代码,实现PC端和树莓派端的良好结合。
树莓派3
SD卡
驱动板(L298N)
CSI摄像头
超声波测距传感器
红外避障传感器
小车 + 4个电机
电脑
树莓派自带的系统比较老旧,其中预装的是Python=3.4,对后续Opencv的使用不太方便。更新成*Raspberry Pi OS (bust 10)*系统,后续的环境搭建、依赖安装和系统有关,太新的系统不保证成功;
树莓派系统直接从树莓派官网下载,使用SD卡安装镜像之前记得格式化。
为树莓派开启VNC服务可以有很多种方案,这里做个简单罗列,仅详细介绍实践过的方案。
方案一:树莓派默认热点。树莓派有热点模块,商家预装的树莓派系统默认打开该模块,因此可以通过连接名叫pi3的热点对小车进行控制;
方案二:树莓派设置连接WiFi。重装系统之后小车没有默认开启热点,因此需要对小车进行配置。可以手动在小车的SD卡中写配置文件让其开机之后自动连接WiFi,通过连接相同WiFi之后进行vnc调试;
方案三:显示器手动设置连接WiFi。如果有显示器的同学会更加方便,通过显示器和鼠标、键盘控制调配小车,我们便采用该方案。手动为小车连接WiFi之后,可以使用vnc连接到小车在PC端控制。
无论使用上述的任何一种方案,获取小车的ip地址之后均可以通过下面的教程开启VNC服务。
树莓派开启VNC服务
成功连接VNC之后后续的控制和配置可以很方便进行。当然也可以使用Putty或者远程桌面连接等方式连接到树莓派,这里仅介绍我们使用的方法。
为了后续更新和下包更快捷,建议更改成国内源:
参考的资料是:
更换系统更新源
更换pip源
亲测有效。
安装中文输入法为后续搜索解决方案提供便利;
这里提供用到过的两种解决方案
比较健全的方案:该方案需要对中文输入进行一些设置,因此较为复杂;
凑活能用的方案:该方案比较快捷,单纯想打出中文用这个就可以;
方案一:使用FileZilla传输文件;
方案二:直接使用U盘传输;
直流电机的原理可以参考学长的介绍,电机控制的代码在move.py中,需要注意GPIO端口号的配置。
红外避障相关代码在infrared.py内,InfraredMeasure函数是小车左右的两个红外避障传感器。在本项目中没有使用到底部的传感器。
注意,红外避障传感器传回0表示前方有障碍物,传回1表示前方无障碍物。
超声波测距原理可以参考学长的介绍,ultrasound.py代码中包含了超声波传感器的初始化,超声波测距和对测距进行移动平均。
摄像头在首次使用之前需要进行一些配置,可以参考摄像头配置中提到的方法。
python调用摄像头有两种方式:
方式一:使用picamera
方式二:使用opencv
在巡线功能中我们使用picamera模块,后面颜色检测追踪功能使用了opencv模块。
摄像头相关代码在camera.py中,其中实现了:
- 摄像头初始化;
- 实时图像传输(发送端),注意HOST为PC在此WiFi网络下的IP地址(通过ifconfig查看),PORT设置一个和接收端相同的端口号就可以。PC端使用pc_receiver.py运行来接收树莓派传回的数据。
另外注意,程序终止是一定要关闭摄像机(camera.close()),否则下次无法正常打开。
代码的整体框架参考了MingruiYU学长的博客。
代码的基础模块实现了传感器功能的调用以及电机的控制,包括:
电机控制代码:move.py;
红外线传感器代码:infrared.py;
超声波传感器代码:ultrasound.py;
摄像头调用代码:camera.py;
避障代码:main_obstacle_avoidance.py;
单线巡线代码:main_track.py;
颜色追踪代码:main_tracking_color.py;
主程序模块通过Car类继承了基础模块的各个类,调用基础模块从而编排逻辑实现功能。
后续的实现需要安装Opencv环境,Opencv可理论上可以在线安装,但是因为网络和不知名原因,可能会出现wheel下载不出或者一直安装不上的原因。因此采用了一种离线安装,手动安装依赖的方式。
可以参考:python3使用pip安装opencv
注意:如果直接按照教程安装会安装最新版的Opencv,有部分依赖之间可能存在版本冲突的问题,可能会安装失败,可以手动下载低版本的轮子自行安装。
避障代码采用左右两个红外传感器和一个超声波测距传感器组合实现避障。程序框图如下:
这里红外传感器传回的变量是布尔值,0表示有障碍,1表示没有障碍。
在左侧和右侧均没有障碍时采用超声波传感器判断是否可以直行。注意这里 d i s t < 20 c m dist<20cm dist<20cm时表示正前方不远处有障碍,那么就应该避开障碍,由于倒车可能陷入循环,因此左转避开障碍。
这部分的核心代码为:
if (start_time is None) or (time.time() - start_time > 0.5):
start_time = None
if left_measure == 0 and right_measure == 1:
print("Going right")
car.right(80)
elif left_measure == 1 and right_measure == 0:
print("Going left")
car.left(80)
elif left_measure == 0 and right_measure == 0:
print("Going back")
car.back(50)
else:
if dist_mov_ave < 20:
car.left(80)
print("Going left")
start_time = time.time()
elif dist_mov_ave < 100:
car.forward(dist_mov_ave/2 + 40)
else:
car.forward(90)
else:
pass
单线巡线过程中尝试过多种方案,在这里依次介绍。
两种方案采用了相同的图像处理逻辑,使用picamera模块获得图像后,对图片进行滤波去噪,使用蓝色二值化图像(因为蓝色分量的红色部分较少,可以很好地区分)。针对得到的二值化图像,仅仅检测靠近小车前方的区域来获取道路信息即可,对道路信息进行平均得到道路的中心点横坐标。下面展示经过二值化并保存检测点的图片:
该方案通过逻辑判断小车的前进方向。当道路中心 x m i d x_{mid} xmid在整个屏幕左侧左转,在整个屏幕右侧右转;如果没有检测到道路(意味着小车冲出去了),那么就回退,直到找到道路再重新开始判断。
方案二:线性控制小车的速度;
该方案在方案一的基础上进行了修正,我们有一个基本的假设,如果小车检测的道路越靠右测那么右转的速度差速应该越大。在这样的假设下,我们设计了一个线性的速度控制程序。
左轮的速度为:
v l e f t = v l o w + v h i g h − v l o w w i d t h ∗ x m i d v_{left}=v_{low}+\frac{v_{high}-v_{low}}{width}*x_{mid} vleft=vlow+widthvhigh−vlow∗xmid
右轮速度为:
v r i g h t = v h i g n + v l o w − v h i g h w i d t h ∗ x m i d v_{right}=v_{hign}+\frac{v_{low}-v_{high}}{width}*x_{mid} vright=vhign+widthvlow−vhigh∗xmid
通过线性控制小车可以实现小车更加精细的控制,减少小车出错的的次数。
核心代码:
ForB = 'Forward'
LorR = 'Brake'
camera, rawCapture = car.CameraInit() # Initialize the PiCamera
for raw_frame in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True):
frame_origin = raw_frame.array
################## lane detection #############################
img = cv2.blur(frame_origin, (5, 5)) # denoising
blu_img, _, red_img = cv2.split(img) # extract the blu channel of the RGB image (since the lane in our experiment is red)
_, dst = cv2.threshold(blu_img, 30, 255, cv2.THRESH_BINARY) # binaryzation, the thresold deponds on the light in the environment
height, width = dst.shape
print(height, width)
half_width = int(width/2)
all_line_pos = np.zeros((num_lane_point, 1))
img_out = cv2.cvtColor(dst, cv2.COLOR_GRAY2RGB)
for i in range(num_lane_point): # each detected point on the lane
detect_height = height - 20 * (i+1)
detect_area_all = dst[detect_height, 0: width-1]
line_all = np.where(detect_area_all == 0)
if len(line_all[0]):
all_line_pos[i] = int(np.max(line_all) + np.min(line_all)) / 2
else:
all_line_pos[i] = -1
if all_line_pos[i] >= 0: # draw the lane points on the binary image
img_out = cv2.circle(img_out, (all_line_pos[i], detect_height), 4, (0, 0, 255), thickness=10)
if VideoReturn: # detect the tennis & transmit the frames to PC
car.VideoTransmission(img_out)
############################ decision making #####################################
all_mid = np.median(all_line_pos) # choose the most internal lane point for decision making
if all_mid < 0:
ForB = 'Backward'
else: #if line is on the left, turn left and fo straight
ForB = 'Forward'
############################ motion control #####################################
if ForB is 'Forward':
vleft = speed_low + (speed_high-speed_low)/width * all_mid
vright = speed_high + (speed_low-speed_high)/width * all_mid
car.forward_turn(vleft, vright)
elif ForB is 'Backward':
car.back(25)
由于RGB图像空间对光强更加敏感,H(色调)S(饱和度)V(明度):对光强变化不敏感,因此实现颜色追踪使用HSV颜色空间。整体程序框图如下:
该部分实现参考github视觉项目。
图像处理部分和巡线任务相似。
图像处理结果如图所示:
在运动控制方面,使用超声波和颜色追踪结合使用,没有检测到物体则原地不动;检测到物体后,使用超声波测距,如果较远便追踪,较近便后退;追踪速度通过在巡线线性控制的基础上给定了一个速度不变的区域,减少不必要的决策。该运动方案也是在结合其他实验之后得到的比较健全的方案。
核心代码:
def Move(x, y, dis):
vleft = speed_low + (speed_high-speed_low)/IMAGE_WIDTH * x
vright = speed_high + (speed_low-speed_high)/IMAGE_WIDTH * x
if x == 0 and y == 0:
car.brake()
print("stop")
else:
if dis < 20:
car.back(25)
print("back")
else:
if IMAGE_WIDTH/4 < x < (IMAGE_WIDTH-IMAGE_WIDTH/4):
car.forward(30)
print("forward")
elif x < IMAGE_WIDTH/4:
car.forward_turn(vleft, vright)
print("left")
elif x > (IMAGE_WIDTH-IMAGE_WIDTH/4):
car.forward_turn(vleft, vright)
print("right")
cd code
python3 main_obstacle_avoidance.py
cd code
python3 main_track.py
cd code
python3 main_tracking_color.py
使用PC端接收树莓派处理之后的图片,方便观察和调试结果。
在树莓派端的camera.py中设置IP和端口(IP可以通过ipconfig获得,端口相同即可),在PC端pc_receiver.py代码中设置对应的IP和端口。运行代码便可以接收图像。
MingruiYU学长的博客
github视觉项目
树莓派开启VNC服务
更换系统更新源
更换pip源
python3使用pip安装opencv
树莓派安装中文输入法
树莓派安装中文输入法
摄像头配置