【实验】树莓派小车

树莓派小车实验报告
1 小车构造及环境配置
1.1 硬件搭建
-
树莓派3B

-
两个PWM电机
-
两个红外传感器
-
一个超声波传感器

-
一个单目摄像头
1.2 系统烧录
- 使用
树莓派镜像烧录器
下载直达 https://downloads.raspberrypi.org/imager/imager_latest.exe
优点:
- 可以直接在设置中配置网络环境
- 可以选择多种操作系统
-
操作步骤:
-
下载镜像https://downloads.raspberrypi.org/raspios_arm64/images/raspios_arm64-2022-09-26/2022-09-22-raspios-bullseye-arm64.img.xz
-
选择自定义镜像

-
设置ssh和wifi
-
1.3 raspberry PI环境搭配
-
首先对树莓派进行换源
* pi@raspberrypi:~ $ sudo nano /etc/apt/sources.list deb https://mirror.nju.edu.cn/debian/ bullseye main contrib non-free deb https://mirror.nju.edu.cn/debian-security/ bullseye-security main contrib non-free deb https://mirror.nju.edu.cn/debian bullseye-updates main contrib non-free * pi@raspberrypi:~ $ sudo nano /etc/apt/sources.list.d/raspi.list deb https://mirror.nju.edu.cn/raspberrypi/debian/ bullseye main -
更新
sudo apt-get update sudo apt-get upgrade -
安装OPENCV
sudo apt-get install python3-opencv -
配置摄像头选项
sudo raspi-config找到Interface Options --> Camera -->打开摄像头YES
(可以在终端中查看device0是否存在)
1.4 PC端配置
- VS code连接Raspberry Pi

配置SSH文件
#Read more about SSH config files: https://linux.die.net/man/5/ssh_config
Host copy # PC名称,随便起
HostName 192.168.43.162 # 树莓派地址,可以使用IP Scanner查看
User pi # 树莓派名称,在烧录SD卡的时候配置
Port 22 # 默认22
几点注意事项:
HostName可以使用IP Scanner,如果是连接安卓手机可以通过手机热点直接查看,苹果手机不管是IP Scanner还是手机热点都看不了
改这个SSH会造成连不上GitHub的情况,如果想要重新连接GitHub,必须将上面注释,该车下面
Host github.com Hostname ssh.github.com Port 443
-
下载VNC Viewer
连接方式也是通过SSH连接到小车地址
1.5 引脚分配

# 设置gpio口为BOARD编号规范
gpio.setmode(gpio.BOARD)
# 定义引脚
# 电机
pin1 = 12 # 左正
pin2 = 16 # 左反
pin3 = 18 # 右正
pin4 = 22 # 右反
ENA = 38 # 左边电机使能
ENB = 40 # 右边电机使能
# 超声波
TRIG = 13 # send-pin
ECHO = 15 # receive-pin
# 红外线
GPIO_Infrared_left = 29 # 左边红外线
GPIO_Infrared_right = 31 # 右边红外线
2 八字循迹
1.1 电机控制
def car_forward(): # 定义前进函数
gpio.output(pin1, gpio.HIGH) # 将pin1接口设置为高电压
gpio.output(pin2, gpio.LOW) # 将pin2接口设置为低电压
gpio.output(pin3, gpio.HIGH) # 将pin3接口设置为高电压
gpio.output(pin4, gpio.LOW) # 将pin4接口设置为低电压
def car_back(): # 定义后退函数
gpio.output(pin1, gpio.LOW)
gpio.output(pin2, gpio.HIGH)
gpio.output(pin3, gpio.LOW)
gpio.output(pin4, gpio.HIGH)
def car_left(): # 定义左转函数
gpio.output(pin1, gpio.LOW)
gpio.output(pin2, gpio.HIGH)
gpio.output(pin3, gpio.HIGH)
gpio.output(pin4, gpio.LOW)
def car_right(): # 定义右转函数
gpio.output(pin1, gpio.HIGH)
gpio.output(pin2, gpio.LOW)
gpio.output(pin3, gpio.LOW)
gpio.output(pin4, gpio.HIGH)
def car_stop(): # 定义停止函数
gpio.output(pin1, gpio.LOW)
gpio.output(pin2, gpio.LOW)
gpio.output(pin3, gpio.LOW)
gpio.output(pin4, gpio.LOW)
1.2 图像捕捉与处理
1.2.1 摄像头处理
# 打开摄像头,图像尺寸640*480(长*高)
cap = cv2.VideoCapture(0)
# 设置捕捉到的图像为480*640
cap.set(3, 640)
cap.set(4, 480)
1.2.2 图像处理
import time
import cv2
import numpy as np
cap = cv2.VideoCapture(0)
cap.set(3, 640)
cap.set(4, 480)
while (1):
ret, frame = cap.read()
cv2.imshow('frame', dst) # 展示每一帧
# 转化为灰度图
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.imshow('gray', gray) # 展示灰度图
# 大津法二值化
retval, dst = cv2.threshold(gray, 100, 255, cv2.THRESH_OTSU)
# 膨胀,白区域变大
dst = cv2.dilate(dst, None, iterations=2)
cv2.imshow('dilate_frame', dst) # 展示每一帧
# 腐蚀,白区域变小
dst = cv2.erode(dst, None, iterations=6)
cv2.imshow('erode_frame', dst) # 展示每一帧
# 找到连续白色像素点最宽的那条
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 释放清理
cap.release()
cv2.destroyAllWindows()
代码讲解
ret, frame = cap.read() cv2.imshow('frame', dst)![]()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) cv2.imshow('gray', gray) # 展示灰度图![]()
这里我们先是将图像进行二值化
retval, dst = cv2.threshold(gray, 100, 255, cv2.THRESH_OTSU)这个阈值取决于环境
# 膨胀,白区域变大 dst = cv2.dilate(dst, None, iterations=2) cv2.imshow('dilate_frame', dst) # 展示每一帧![]()
如果只对图像进行膨胀则在边界处提取不够明显!!
# 腐蚀,白区域变小 dst = cv2.erode(dst, None, iterations=6) cv2.imshow('erode_frame', dst) # 展示每一帧![]()
如果只对图像进行腐蚀则图像特征不明显!!!
# 膨胀,白区域变大 dst = cv2.dilate(dst, None, iterations=2) cv2.imshow('dilate_frame', dst) # 展示每一帧 # 腐蚀,白区域变小 dst = cv2.erode(dst, None, iterations=6) cv2.imshow('erode_frame', dst) # 展示每一帧![]()
1.2.3 结果展示
此处推荐先对图像进行高斯滤波

1.3 循迹策略
寻找第400行像素,找到最长的一段白色像素集合作为赛道,寻找赛道中点与320作为比较进行左右判断
# 单看第400行的像素值
# print(dst.shape)
color = dst[400]
# 找到连续白色像素点最宽的那条
road_set = []
road_set_length = []
road = []
for i in range(len(color)):
if (i == 639):
road_set.append(road)
road_set_length.append(len(road))
break
if color[i] == 255 and color[i + 1] == 255:
road.append(i)
elif (len(road) > 20 and color[i + 1] != 255):
road_set.append(road)
road_set_length.append(len(road))
road = []
if (len(road_set_length) == 0):
center = 320
elif (len(road) == 0):
center = 320
else:
index_ = road_set_length.index(max(road_set_length))
road = road_set[index_]
center = (min(road) + max(road)) / 2
# 计算出center与标准中心点的偏移量
# 如果为正,应该左转
direction = center - 320
print(direction)
threshold = 90 #右转阈值
threshold_neg = -40 #左转阈值
if abs(direction) >= threshold:
# 右转
if direction > threshold:
car_forward()
pwm1.ChangeDutyCycle(25 + direction / 18)
pwm2.ChangeDutyCycle(0)
if(direction>120 and direction <150):
time.sleep(0.3)
elif(direction >= 150):
time.sleep(0.5)
# 左转
elif direction < threshold_neg:
car_forward()
pwm1.ChangeDutyCycle(0)
pwm2.ChangeDutyCycle(25 + abs(direction) / 12)
if(abs(direction)>120 and abs(direction)<150):
time.sleep(0.3)
elif(abs(direction)>=150):
time.sleep(0.5)
else:
car_forward()
pwm1.ChangeDutyCycle(20)
pwm2.ChangeDutyCycle(20)
1.4 结果展示
3 乒乓球跟踪
1.1 图像处理
将图像转换成HSV
1.2 图像结果


1.3 结果分析
我们最终没有选择使用霍夫检测,因为霍夫检测虽然在不太明亮的环境下检测效果好,但是当检测物体运动之后,检测效果很差,而只用色差的方法寻找边缘,能够很快的反应,它的缺点只有受光线影响,光照越强,检测效果越好。
4 红外避障
太简单了,就只放代码
import RPi.GPIO as gpio
import time
# 定义引脚
pin1 = 12 # 左正
pin2 = 16 # 左反
pin3 = 18 # 右正
pin4 = 22 # 右反
ENA = 38
ENB = 40
GPIO_Infrared_left = 29
GPIO_Infrared_right = 31
gpio.setwarnings(False)
# 设置gpio口为BOARD编号规范
gpio.setmode(gpio.BOARD)
# 设置gpio口为输出
gpio.setup(pin1, gpio.OUT)
gpio.setup(pin2, gpio.OUT)
gpio.setup(pin3, gpio.OUT)
gpio.setup(pin4, gpio.OUT)
gpio.setup(ENA, gpio.OUT)
gpio.setup(ENB, gpio.OUT)
gpio.setup(GPIO_Infrared_left, gpio.IN)
gpio.setup(GPIO_Infrared_right, gpio.IN)
# 设置PWM波,频率为50Hz
pwm1 = gpio.PWM(ENA, 50)
pwm2 = gpio.PWM(ENB, 50)
pwm1.start(0)
pwm2.start(0)
def car_forward(): # 定义前进函数
gpio.output(pin1, gpio.HIGH) # 将pin1接口设置为高电压
gpio.output(pin2, gpio.LOW) # 将pin2接口设置为低电压
gpio.output(pin3, gpio.HIGH) # 将pin3接口设置为高电压
gpio.output(pin4, gpio.LOW) # 将pin4接口设置为低电压
def car_back(): # 定义后退函数
gpio.output(pin1, gpio.LOW)
gpio.output(pin2, gpio.HIGH)
gpio.output(pin3, gpio.LOW)
gpio.output(pin4, gpio.HIGH)
def car_left(): # 定义左转函数
gpio.output(pin1, gpio.LOW)
gpio.output(pin2, gpio.HIGH)
gpio.output(pin3, gpio.HIGH)
gpio.output(pin4, gpio.LOW)
def car_right(): # 定义右转函数
gpio.output(pin1, gpio.HIGH)
gpio.output(pin2, gpio.LOW)
gpio.output(pin3, gpio.LOW)
gpio.output(pin4, gpio.HIGH)
def car_stop(): # 定义停止函数
gpio.output(pin1, gpio.LOW)
gpio.output(pin2, gpio.LOW)
gpio.output(pin3, gpio.LOW)
gpio.output(pin4, gpio.LOW)
def InfraredMeasure():
left_measure = gpio.input(GPIO_Infrared_left) # if there is an obstacle, GPIO will become 0; else, GPIO_input = 1;
right_measure = gpio.input(GPIO_Infrared_right)
return [left_measure, right_measure]
def avoidance(left,right):
if(left==0 and right !=0 ):
car_back()
pwm1.ChangeDutyCycle(25)
pwm2.ChangeDutyCycle(25)
time.sleep(1)
car_right()
pwm1.ChangeDutyCycle(40)
pwm2.ChangeDutyCycle(40)
print("detect obstacles in the left!")
time.sleep(1)
elif(left==0 and right ==0):
car_back()
pwm1.ChangeDutyCycle(40)
pwm2.ChangeDutyCycle(40)
print("detect obstacles in the left and right!")
time.sleep(1)
elif(left == 1 and right ==0):
car_back()
pwm1.ChangeDutyCycle(25)
pwm2.ChangeDutyCycle(25)
time.sleep(2)
car_left()
pwm1.ChangeDutyCycle(40)
pwm2.ChangeDutyCycle(40)
print("detect obstacles in the right!")
time.sleep(1)
else:
car_forward()
pwm1.ChangeDutyCycle(30)
pwm2.ChangeDutyCycle(30)
def loop():
while True:
car_forward()
pwm1.ChangeDutyCycle(30)
pwm2.ChangeDutyCycle(30)
[left, right] = InfraredMeasure()
print(left,right)
avoidance(left,right)
def destroy():
gpio.cleanup()
try:
loop()
except KeyboardInterrupt:
destroy()
5 超声波避障
import RPi.GPIO as gpio
import time
# 定义引脚
pin1 = 12 # 左正
pin2 = 16 # 左反
pin3 = 18 # 右正
pin4 = 22 # 右反
ENA = 38
ENB = 40
TRIG = 13 # send-pin
ECHO = 15 # receive-pin
# 设置gpio口为BOARD编号规范
gpio.setmode(gpio.BOARD)
# 设置gpio口为输出
gpio.setup(pin1, gpio.OUT)
gpio.setup(pin2, gpio.OUT)
gpio.setup(pin3, gpio.OUT)
gpio.setup(pin4, gpio.OUT)
gpio.setup(ENA, gpio.OUT)
gpio.setup(ENB, gpio.OUT)
# 设置PWM波,频率为50Hz
pwm1 = gpio.PWM(ENA, 50)
pwm2 = gpio.PWM(ENB, 50)
pwm1.start(0)
pwm2.start(0)
def car_forward(): # 定义前进函数
gpio.output(pin1, gpio.HIGH) # 将pin1接口设置为高电压
gpio.output(pin2, gpio.LOW) # 将pin2接口设置为低电压
gpio.output(pin3, gpio.HIGH) # 将pin3接口设置为高电压
gpio.output(pin4, gpio.LOW) # 将pin4接口设置为低电压
def car_back(): # 定义后退函数
gpio.output(pin1, gpio.LOW)
gpio.output(pin2, gpio.HIGH)
gpio.output(pin3, gpio.LOW)
gpio.output(pin4, gpio.HIGH)
def car_left(): # 定义左转函数
gpio.output(pin1, gpio.LOW)
gpio.output(pin2, gpio.HIGH)
gpio.output(pin3, gpio.HIGH)
gpio.output(pin4, gpio.LOW)
def car_right(): # 定义右转函数
gpio.output(pin1, gpio.HIGH)
gpio.output(pin2, gpio.LOW)
gpio.output(pin3, gpio.LOW)
gpio.output(pin4, gpio.HIGH)
def car_stop(): # 定义停止函数
gpio.output(pin1, gpio.LOW)
gpio.output(pin2, gpio.LOW)
gpio.output(pin3, gpio.LOW)
gpio.output(pin4, gpio.LOW)
def setup():
gpio.setup(TRIG, gpio.OUT, initial=gpio.LOW)
gpio.setup(ECHO, gpio.IN)
gpio.setwarnings(False) # 关闭警告
def distance():
gpio.output(TRIG, 1) # 给Trig一个10US以上的高电平
time.sleep(0.00001)
gpio.output(TRIG, 0)
# 等待低电平结束,然后记录时间
while gpio.input(ECHO) == 0: # 捕捉 echo 端输出上升沿
pass
time1 = time.time()
# 等待高电平结束,然后记录时间
while gpio.input(ECHO) == 1: # 捕捉 echo 端输出下降沿
pass
time2 = time.time()
during = time2 - time1
# ECHO高电平时刻时间减去低电平时刻时间,所得时间为超声波传播时间
return during * 340 / 2 * 100
# 超声波传播速度为340m/s,最后单位米换算为厘米,所以乘以100
def loop():
while True:
dis = distance()
print(dis, "cm\n")
# print dis, 'cm'
# print ''
time.sleep(0.3)
car_forward()
pwm1.ChangeDutyCycle(30)
pwm2.ChangeDutyCycle(30)
if (dis < 30):
car_back()
pwm1.ChangeDutyCycle(40)
pwm2.ChangeDutyCycle(40)
time.sleep(1)
car_forward()
pwm1.ChangeDutyCycle(40)
pwm2.ChangeDutyCycle(0)
time.sleep(1)
def destroy():
gpio.cleanup()
if __name__ == "__main__":
setup()
try:
loop()
except KeyboardInterrupt:
destroy()

