利用计算机视觉的自主追球探测车

dooooit_4000007
转载
发布时间: 2025-06-14 16:53:33 | 阅读数 0收藏数 0评论数 0
封面
我设计了一款追球机器人,它可以识别并追逐红球,一旦进入红球的追踪范围,就会尝试利用前方的伺服器推动红球。项目主要依赖于树莓派 (Raspberry Pi),它处理传入的视频,识别球体,并根据球体的位置控制电机的转速来追踪球体。Arduino 拥有完全独立的电子连接,可以测量球体与探测车之间的距离.

准备工作:

材料:

硬件

  1. Raspberry Pi 3+ 或更高版本
  2. Raspberry Pi 电机帽
  3. 机器人汽车底盘
  4. 任何 5V电源(为 Pi/Arduino 供电)
  5. Lipo 电池AA 电池组(为电机供电)
  6. 锂电池充电器
  7. 公/母直流连接器
  8. 5V稳压器
  9. Raspberry Pi 摄像头/USB 网络摄像头
  10. 垫片或支架
  11. 任何 Arduino(最好是 Nano)
  12. 迷你面包板
  13. 超声波测距仪
  14. 2x 9g SG90 伺服器
  15. Neopixel 8 环(或任何其他 Neopixel 环)- 可选
  16. 公对公和公对母杜邦线

工具:

  1. 烙铁和焊料
  2. 绑带
  3. 蓝丁胶
1

Raspberry Pi软件

那么,计算机视觉。计算机视觉有大量复杂的程序和算法来检测猫和狗,但对于这个项目,因为我们需要识别像球一样简单的物体,OpenCV 是一个很棒且易于使用的工具。然而,虽然使用 OpenCV 很容易,但将它安装在你的 Raspberry Pi 上却不那么容易。它可能相当复杂,所以我不会用我自己的话来让你困惑,而是给你链接两个指南来告诉你如何做到这一点。有艰难而痛苦的方法,也有简单的方法。相信我,如果你选择简单的方法,它会为你节省很多时间(我就是这样做的)

安装并测试 OpenCV 以确保其正常工作后,现在是时候转到电机和摄像头软件了。

汽车库

1)通过在 Raspberry Pi 终端中输入以下命令启用IC2 :

sudo raspi-config

转到接口选项(或旧型号的高级选项)

选择IC2并启用它

重启你的 Pi

2)检查你的 Python 版本是否为 3 或更高版本。如果不是,请将 Python 版本升级到 3 或更高版本。

3)要安装该库,请在终端中输入以下命令:

sudo pip3 install adafruit-circuitpython-motorkit

Pi 相机库

1)与 Motor 库类似,您需要启用 Pi Camera:

sudo raspi-config

转至接口选项

选择相机并启用它

重启你的 Pi

2)要将相机连接到 Pi,首先确保 Pi 已关闭

3)找到 Pi 上的摄像头模块端口,然后轻轻向上拉端口塑料夹的边缘

4)插入摄像头模块带状电缆;确保带状电缆底部的连接器朝向照片中所示端口中的触点

4)将塑料夹推回原位

2

5V电源

5V电源

1)将电池连接到电源板

2)将电源板放置在 Pi 下方以节省空间

3)在电源板和 Pi 之间使用提供的或额外的支架或垫片

4)使用 micro-USB 线将 Pi 连接到电源板

3

电机帽

电机帽

1)将接头引脚和接线端子焊接到给定的电机 HAT 中,确保接头引脚指向下方,接线端子引脚位于电机 HAT 的顶部

2) 将接头插针插入 Raspberry Pi 的端子插针,确保 Pi 摄像头电缆穿过电机 HAT 中用于 Pi 摄像头电缆的狭缝

3)在 Motor HAT 和 Raspberry Pi 之间安装垫片或支架,将它们放置在所有 4 个角上,并用胶水/螺母和螺栓固定(这是必要的,以防止 Motor HAT 与 Pi 金属 HDMI 外壳接触,可能导致短路)

4)从上到下 - 电机 HAT、Raspberry Pi、电源板

4

Arduino电子设备

探测车的“摧毁”球体功能将由 Arduino 控制。任何 Arduino 都可以,但 Arduino Nano 更适合,因为它体积小巧。它连接到超声波探测器,用于探测与球体的距离,并将信息传输到 Arduino,Arduino 控制伺服器和 Neopixel 环。启动下面的模拟程序,看看会发生什么:

  1. 将下面 Tinkercad 电路中的 Arduino 代码(按“代码”选项卡)上传到 Arduino
  2. 将 Arduino 放在迷你面包板上,或者将电线焊接到 Arduino 引脚上
  3. 将所有电子设备通过面包板正确连接到 Arduino
  4. 通过将 Arduino 连接到使用 USB 连接为 Pi 供电的同一块电源板来为 Arduino 供电(电源板上有两个 5V 输出 USB 端口)
5

电机电源

为了给电机供电,我们使用了不同的电源。本例中,使用的是锂聚合物电池,可以是2芯或3芯电池。由于电池本身的电压对于所用的电机来说过高,因此需要一个电压调节器来降低电机能够运行的电压(5-6V)。电源连接器采用公母接口,因此可以通过将公连接器从母连接器上拔下来随时切断电机供电。

  1. 给电池充电
  2. 使用电池上的 JST 接口,将正极和负极电线连接到图中所示的正确接口。用胶带将电线固定到 JST 接口上。
  3. 使用螺丝刀将电线的另一侧固定到公直流电源连接器的端子上
  4. 将一对正极线和负极线分别固定到母直流电源连接器上
  5. 将电线的另一端连接到电压调节器上的端子,确保输入电压线连接到调节器上的输入端子并且极性正确(您可以在调节器背面检查极性和输入/输出端子)
  6. 将另一对正极和负极电线连接到电压调节器的输出端子
  7. 将电线的另一端连接到电机 HAT 上的输入电压接线端子,确保电线的极性始终正确
  8. 要为电机 HAT 供电,请将公头和母头直流电源连接器连接在一起
  9. 要查看调节器的输出电压,请按下调节器上的按钮,直到出现输出电压读数,由板上的输出电压 LED 指示
  10. 要从调节器获得 5-6V 的输出电压,请使用螺丝刀逆时针旋转电位器,直到电压降至 5-6V
6

组装探测车-电机

  1. 将给定的电线焊接到电机连接处
  2. 使用侧面支撑和提供的螺丝,将电机固定到位
  3. 使用给定的螺母将螺丝固定到位
  4. 将轮子滑到电机轴上
  5. 对其他 3 个电机重复此操作
7

组装流动站-下层

  1. 使用扎带将超声波测距仪固定到下层地板的前面
  2. 在超声波探测器的两侧固定两个伺服器,但不要太近,以免伺服器与测距仪发生冲突
  3. 在测距仪和伺服器后面,用扎带固定 Arduino Nano,并将前两个电机之间的地板上的孔固定住
  4. 在 Arduino 后面,以同样的方式将电压调节器固定在两个后电机之间
  5. 在电压调节器后面,将 Lipo 电池固定在水平方向
8

组装探测车——垫片和顶板

  1. 使用给定的垫片和螺丝,将顶层和底层连接在一起
  2. 使用扎带将 Pi/Arduino 电池固定在顶层后部的 Lipo 电池上方
  3. 将电源板、Pi 和 Motor HAT 堆叠放置在顶层的中间
  4. 使用 micro-USB 线缆将电源板的 USB 端口连接到 Pi 的 micro-USB 端口,为 Pi 供电
  5. 使用顶板上的侧孔将电机线穿过,将电机线拧入电机 HAT 上的电机端子销中
  6. 将前左电机连接至 M1,前右电机连接至 M3,后左电机连接至 M2,后右电机连接至 M4
9

组装探测车——电线和摄像头

  1. Pi 摄像头线应穿过电机 HAT 中的缝隙,到达顶层前部的摄像头
  2. 使用 Blu-tack 或类似物将相机固定到位
  3. 将 Neopixel 环固定在相机前面的同一块 Blu-tack 胶上,确保环不会遮挡相机的视线
  4. Neopixel 环形线应该连接到 Arduino 所在的底层
  5. 使用 USB 2.0 电缆,将同一电源板的 USB 端口连接到 Arduino 的端口,并将电缆穿过地板的中央孔,为 Arduino 供电。
  6. 将这根线穿过 Pi 相机后面的大圆孔,连接到下面的 Arduino
  7. 如果电线很长并且有多余的长度,请使用扎带捆绑多余的电线,以免妨碍车轮。
10

Raspberry Pi代码

整理好电子设备并组装好火星车后,剩下要做的就是对火星车进行编程:

1) 使用 Thonny 或其他 Python IDE,将以下代码复制到文件中,并以“BallChasingRover”类名称保存。

from picamera.array import PiRGBArrayfrom picamera import PiCameraimport timeimport cv2import numpy as npfrom adafruit_motorkit import MotorKitkit = MotorKit()motors = [kit.motor3, kit.motor4, kit.motor1, kit.motor2]motorSpeed = [0,0,0,0]speedDef = 0.5leftSpeed = speedDefrightSpeed = speedDefdefTime = 0.01driveTime = defTimemaxDiff = 0.2kp = 1.0ki = 1.0kd = 1.0ballX = 0.0ballY = 0.0x = {'axis':'X', 'lastTime':int(round(time.time()*1000)), 'lastError': 0.0, 'error': 0.0, 'duration': 0.0, 'sumError': 0.0, 'dError': 0.0, 'PID': 0.0}y = {'axis':'Y', 'lastTime':int(round(time.time()*1000)), 'lastError': 0.0, 'error': 0.0, 'duration': 0.0, 'sumError': 0.0, 'dError': 0.0, 'PID': 0.0}def driveMotors(leftChnl = speedDef, rightChnl = speedDef, duration = defTime): motorSpeed[0] = leftChnl motorSpeed[1] = leftChnl motorSpeed[2] = rightChnl motorSpeed[3] = rightChnl motors[0].throttle = motorSpeed[0] motors[1].throttle = motorSpeed[1] motors[2].throttle = motorSpeed[2] motors[3].throttle = motorSpeed[3] def PID(axis): lastTime = axis['lastTime'] lastError = axis['lastError'] now = int(round(time.time()*1000)) duration = now - lastTime axis['sumError'] += axis['error'] duration axis['dError'] = (axis['error'] - lastError)/duration if axis['sumError'] > 1: axis['sumError'] = 1 if axis['sumError'] < -1: axis['sumError'] = -1 axis['PID'] = kp axis['error'] + ki axis['sumError'] + kd axis['dError'] axis['lastError'] = axis['error'] axis['lastTime'] = now return axisdef killMotors(): motors[0].throttle = 0.0 motors[1].throttle = 0.0 motors[2].throttle = 0.0 motors[3].throttle = 0.0params = cv2.SimpleBlobDetector_Params()params.filterByColor = Falseparams.filterByArea = Trueparams.minArea = 2500params.maxArea = 30000params.filterByInertia = Falseparams.filterByConvexity = Falseparams.filterByCircularity = Trueparams.minCircularity = 0.4params.maxCircularity = 1 det = cv2.SimpleBlobDetector_create(params) #set red filter rangelower_red = np.array([160, 60, 20]) #80 for blueupper_red = np.array([180, 255, 255]) #130 for bluecamera = PiCamera()camera.resolution = (640, 480)camera.framerate = 32rawCapture = PiRGBArray(camera, size=(640, 480))time.sleep(0.1)for frame in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True): image = frame.array height, width, chan = np.shape(image) xMid = width/2 1.0 yMid = height/2 1.0 imgHSV = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) redMask = cv2.inRange(imgHSV, lower_red, upper_red) blur = cv2.blur(redMask, (10,10)) res = cv2.bitwise_and(image, image, mask=blur) keypoints = det.detect(blur) try: ballX = int(keypoints[0].pt[0]) ballY = int(keypoints[0].pt[1]) except: pass #not necessary if you dont need to see camera input via VNC cv2.drawKeypoints(image, keypoints, image, (0,255,0), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) xVariance = (ballX - xMid) / xMid yVariance = (ballY - yMid) / yMid x['error'] = xVariance/xMid y['error'] = yVariance/yMid x = PID(x) y = PID(y) leftSpeed = (speedDef y['PID']) + (maxDiff x['PID']) rightSpeed = (speedDef y['PID']) - (maxDiff x['PID']) if leftSpeed > (speedDef + maxDiff): leftSpeed = (speedDef + maxDiff) if leftSpeed < -(speedDef + maxDiff): leftSpeed = -(speedDef + maxDiff) if rightSpeed > (speedDef + maxDiff): rightSpeed = (speedDef + maxDiff) if rightSpeed < -(speedDef + maxDiff): rightSpeed = -(speedDef + maxDiff) driveMotors(leftSpeed, rightSpeed, driveTime) #also not necessary cv2.imshow("Frame", image) #cv2.imshow("Mask", blur) <- shows the mask frame key = cv2.waitKey(1) & 0xFF rawCapture.truncate(0) if key == ord("q"): breakkillMotors()cv2.destroyAllWindows()

2)OpenCV 不能直接从 IDE 运行,因此要运行代码,请打开终端

3)如果你将 OpenCV 安装在虚拟环境中(我已经安装了),请在终端中输入以下内容(如果没有,请忽略):

workon cv

4)导航到保存文件的位置(我的文件保存在“Documents”文件夹中):

cd Documents/

5)进入保存文件的文件夹后,键入以下命令来运行该程序:

python 'YourFileName'.py

11

成功了吗?

按下回车键后,你的机器人就会开始移动。如果在它的视野范围内有一个红球,它就会转向并跟随红球。一旦它靠近,伺服器就会启动。你需要根据你所在区域的光照情况以及红球的大小来修改 Python 代码中的一些设置,以确保代码能够正确识别红球。要检查你的代码是否正确识别了红球,请添加以“cv2.imshow”开头的代码行,以获取摄像头所看到的内容以及它认为是红球的内容的实时反馈。

阅读记录0
点赞0
收藏0
禁止 本文未经作者允许授权,禁止转载
猜你喜欢
评论/提问(已发布 0 条)
评论 评论
收藏 收藏
分享 分享
pdf下载 下载