
项目来源:第四届 3D 视觉创新应用竞赛 – 朱逸楠 – 华南理工大学 相机型号:Orbbec Gemini 2 计算平台:NVIDIA Jetson Orin Nano 关键执行设备:DJI RoboMaster M3508 无刷电机、STM32F407GTZ6、正交编码轮、陀螺仪、遥控器与多机构电控系统 源代码链接:https://github.com/3DCVdeveloper/RobotV_Volleyball-assisted-training-robot
1. 为什么排球训练需要一台“会跑的机器人”?
传统排球训练里,发球、捡球、布置标志桶这些工作往往依赖人工。问题并不复杂,却非常消耗训练时间:
- 发球质量受人影响,速度、角度、旋转不够稳定;
- 捡球频繁打断训练节奏,训练强度一上来就很耗人;
- 定点训练要反复调整发球位置和目标点,搬运设备麻烦;
- 标志桶摆放、回收属于低价值重复劳动,却会占用教练和队员注意力。
这个项目的切入点很明确:让机器人承担训练中的“重复劳动”和“标准化动作”,把人从捡球、搬球、摆桶中解放出来,让训练者更专注于接发、移动、判断和技术动作本身。
它不是一台固定位置的发球机,而是一套可移动的排球训练平台:能识别排球,快速获取排球,移动到给定地点发射排球,还能拿取、存储、放置标志桶。换句话说,它要在球场上完成从感知、定位、运动、执行到交互的一整条闭环。
2. 项目一句话概括
这是一台基于 Gemini 2 深度视觉、Jetson Orin Nano 边缘计算、ROS2 任务调度、STM32 实时控制和全向移动底盘的排球辅助训练机器人。
它的系统目标可以拆成四个关键词:
| 能力 | 具体表现 | 训练价值 |
|---|---|---|
| 识别 | 通过视觉检测排球,并结合深度信息判断空间位置 | 自动找球、辅助拾球 |
| 获取 | 使用同步带卷入式取球机构快速收球 | 减少人工捡球 |
| 移动 | 全向轮底盘移动到任意给定位置 | 支持多点、多角度训练 |
| 发射与布置 | 三同步带发射排球,三自由度机构夹取/存储/放置标志桶 | 发球训练、标点训练、路线训练 |
3. 系统总览:一台机器人里藏着四套能力
项目可以分为机械、嵌入式硬件、嵌入式软件、算法四个层级。

4. 机械设计:快、稳、能容错,是球场机器人最难的底层功夫
4.1 全向轮底盘:为了在球场上“横着走”
机器人需要快速接近排球,也要能从任意角度调整发射位置。项目在麦克纳姆轮、全向轮、舵轮之间综合比较了解算难度、装配复杂度、成本与效率,最终选择全向轮底盘。
这个选择很务实:全向轮具备平面内前后、左右、旋转的运动能力,可以让机器人在不大幅转身的情况下快速修正位置。对排球训练来说,这一点非常关键,因为球的位置和训练目标点并不总在机器人正前方。
底盘用 30 x 40 铝方管作为框架,连接件采用 3mm 碳纤板;全向轮辊子使用耐磨聚氨酯,内部加入轴承降低摩擦。轮毂直径设置为 150mm,目标是兼顾速度、加速度和结构强度。驱动电机选择 DJI RoboMaster M3508 无刷减速电机,用于满足载荷、速度和加速度需求。
4.2 取球机构:同步带卷入 + 弹性摇臂,解决“球不总是标准球”的问题
排球不是刚性零件。不同温度、充气程度、磨损状态都会导致球径和触感变化,球场地面也可能存在轻微不平整。如果取球机构只按固定间距设计,很容易出现卡球、漏球或压球。
项目采用同步带卷入式方案,并把两根同步带布置在可上下摆动的摇臂上;摇臂由舵机驱动,舵机与摇臂之间加入弹簧连接。这种结构的意义在于:让机构拥有一定“柔顺性”,能主动适应不同球径和地面高度变化。
值得注意的是,技术文档也指出弹性连接会引入较大振动,若继续优化,可以从摇臂谐振频率、阻尼结构和同步带张紧稳定性三个方向深入改进。这是一个很典型的机器人机械问题:越柔顺,越能适应复杂环境;但越柔顺,也越需要处理振动和响应滞后。
4.3 三同步带发射机构:不只控制速度,还能控制自旋
发射机构采用三条平均分布的同步带将球带出,两条在下方,一条在上方,三条同步带分别由电机驱动。通过让三个电机以不同速度、不同方向旋转,机器人可以给排球叠加不同方向的出射速度和自旋。
这部分是项目的训练价值核心。排球不是只要“打出去”就够了,真正有训练意义的是稳定复现不同球路:
- 直线球:速度稳定、落点可控;
- 带旋球:改变飞行轨迹和接球难度;
- 不同角度球:模拟比赛中的多种来球方向;
- 不同距离球:适配接发、移动、防守等训练场景。
由于排球质量较小,发射机构更关注转速而不是扭矩。因此项目将 3508 电机的 1:19 减速箱拆下,使用减速前的高速部分直接驱动同步带,以提高出球初速度。
4.4 标志桶取放机构:训练辅助不止是“发球”
项目还设计了用于标志桶拿取、存储、放置的三自由度机构,包括夹爪、水平摆臂、竖直升降系统和可存储 5 个标志桶的筒仓。
它的典型动作链为:

这个设计让机器人从“发球设备”扩展成“训练布场助手”。在青少年教学、步伐训练、移动路线训练中,标志桶的自动布置能显著减少教练的重复工作。
5. 感知与定位:机器人如何把“看见球”变成“能去捡球”?
这一部分可以按三层理解:图像层识别目标、深度层估算距离、定位层确认机器人自身位姿。只有这三层信息连起来,机器人才能从“画面里有球”走到“底盘该移动到哪里”。

5.1 视觉输入层:Gemini 2 负责 RGB + 深度信息
项目采用 Orbbec Gemini 2 系列深度相机作为主要传感器,通过 OpenCV 读取图像流。在拾球任务中,RGB 图像用于识别排球,深度图用于判断排球距离机器人有多远。
一个简化版的相机取流逻辑如下,对应本地代码中 helloworld.py 的思路:
import cv2
camera = cv2.VideoCapture("/dev/video0")
camera.set(cv2.CAP_PROP_FRAME_WIDTH, 480)
camera.set(cv2.CAP_PROP_FRAME_HEIGHT, 640)
camera.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc("M", "J", "P", "G"))
ok, frame = camera.read()
if ok:
# 后续可接入 YOLOv8 检测,或保存图像用于调试
cv2.imwrite("ball_frame.png", frame)
camera.release()
这段代码本身并不复杂,但它承担了感知链路的第一步:稳定拿到画面。对于机器人项目来说,“能实时取流”比单张图片识别更关键,因为底盘、取球机构和相机之间存在连续的运动关系。
5.2 目标检测层:YOLOv8 找出排球所在区域
PPT 中明确提到,项目为了提高嵌入式平台上的推理效率,使用 YOLOv8 进行目标检测,并自行采集数据集训练。本地 code/detection/weights 下保留了 detect.pt 和 pose.pt 权重文件,说明项目已经具备模型训练或部署产物。
YOLOv8 在这里的作用可以理解为:从一帧图像中快速返回“目标框”。机器人并不直接控制像素点,而是先获得目标框中心,再结合深度信息计算空间位置。
# 简化示意:检测框中心点用于后续深度采样
box = [x1, y1, x2, y2]
u = int((x1 + x2) / 2)
v = int((y1 + y2) / 2)
# u, v 是排球在图像中的中心位置
# depth[v, u] 可作为该目标的近似距离
这里有一个工程细节:实际项目中不建议只读取一个像素的深度值,因为深度图可能有空洞、噪声或边缘误差。更稳妥的做法是在检测框中心附近取一个小 ROI,并使用中位数或均值滤波:
roi = depth[v - 5:v + 5, u - 5:u + 5]
valid_depth = roi[roi > 0]
z = median(valid_depth)
这样可以降低单点深度异常对拾球目标点的影响。
5.3 坐标换算层:从图像坐标到机器人目标点
深度相机给出的信息通常是图像坐标 (u, v) 和深度 z。机器人真正需要的是相对于自身坐标系的目标位置。简化后的针孔相机模型如下:
X_camera = (u - cx) * z / fx
Y_camera = (v - cy) * z / fy
Z_camera = z
其中:
| 参数 | 含义 |
|---|---|
u, v |
排球中心点在图像中的像素坐标 |
z |
深度相机测得的目标距离 |
fx, fy |
相机焦距参数 |
cx, cy |
相机主点坐标 |
X_camera, Y_camera, Z_camera |
排球在相机坐标系下的位置 |
完成相机坐标估计后,还需要结合相机安装位置,把它转换到机器人坐标系,再生成底盘要追踪的目标点。文章中可以把这一步理解为:视觉负责告诉机器人“球在我前方偏左多少、距离多少”,ROS2 再把这个信息变成一个可执行的移动目标。
5.4 场地定位层:正交编码轮 + 陀螺仪确认机器人自身位置
机器人没有采用高成本定位系统,而是选择正交编码轮和陀螺仪:
- 一对相互正交的被动全向轮分别记录
x、y方向位移;
- 陀螺仪记录机器人航向角
yaw;
- 10bit 磁编码器记录全向轮角速度;
- 弹簧和高精度浮动导轨保证编码轮贴地,减少打滑和悬空误差。
它的定位输出可以抽象成:
robot_pose = [x_current_pos, y_current_pos, yaw_current_pos]
这与项目中的自定义消息 SerialReceive.msg 一致:
float64 x_current_pos
float64 y_current_pos
float64 yaw_current_pos
这套方案的优点是成本、功耗和开发复杂度都可控,适合封闭球场中的相对定位。难点在于编码轮会受到地面摩擦、轮径误差、安装正交度和累积误差影响。因此项目使用一体式 3D 打印结构保证两个全向轮正交,并通过浮动机构提高接触稳定性。
6. 软件架构:ROS2 负责“大脑”,STM32 负责“肌肉反射”
项目的软件结构可以按“上位机决策、通信桥接、下位机实时控制”来读。这样比单纯罗列文件更容易理解:Jetson 负责想清楚要去哪里,STM32 负责把电机和机构稳稳地动起来。

6.1 ROS2 包结构:把复杂机器人拆成可通信的模块
本地代码中的 ROS2 工程主要位于 code/ros2_path/src,包含三个核心包:
| 包名 | 作用 | 读者可理解成 |
|---|---|---|
py_r1_pkg |
Python 版本任务调度、目标点发送、轨迹发布、串口服务客户端 | 任务层 |
show_path |
C++ 串口通信、路径显示、手动目标服务等 | 通信与显示层 |
show_path_interfaces |
自定义 action、service、message 接口定义 | 模块之间的“协议说明书” |
其中最关键的是 show_path_interfaces。它把机器人动作拆成几类接口,而不是让所有逻辑写死在一个脚本里。
6.2 Action:适合“发一个目标,并持续等待结果”的任务
项目中的 GoalCmd.action 用于发送目标位姿:
float64 x_pos_goal
float64 y_pos_goal
float64 yaw_goal
---
bool flag
---
bool flag
这三个区域分别对应 ROS2 action 的 Goal / Result / Feedback:
| 区域 | 在项目中的含义 |
|---|---|
| Goal | 要机器人去的目标点:x_pos_goal, y_pos_goal, yaw_goal |
| Result | 目标是否被成功接收或更新 |
| Feedback | 执行过程中返回状态 |
它适合机器人导航这类任务,因为导航不是瞬间完成的:发出目标后,机器人需要移动、反馈、判断是否到达,再执行下一步。
一个简化版目标发送逻辑如下:
goal_msg = GoalCmd.Goal()
goal_msg.x_pos_goal = goal_path_list[0]
goal_msg.y_pos_goal = goal_path_list[1]
goal_msg.yaw_goal = goal_path_list[2]
client.send_goal_async(goal_msg, feedback_callback=feedback_callback)
6.3 Service:适合“问一次、答一次”的手动目标更新
项目中的 ManualTarget.srv 更像一个查询或手动更新接口:
float64 x_now
float64 y_now
float64 yaw_now
---
float64 x_next
float64 y_next
float64 yaw_next
它适合调试场景:手动给一个当前位置或目标值,服务端返回下一步目标。相比 action,service 更短促,不强调持续反馈。
6.4 Message:适合连续发布状态
SerialReceive.msg 用来描述当前机器人位姿:
float64 x_current_pos
float64 y_current_pos
float64 yaw_current_pos
这类消息可以被路径显示、导航判断、监控界面同时订阅。它的意义是把“当前在哪里”变成整个系统都能读到的公共状态。
6.5 串口协议:ROS2 与 STM32 之间的关键桥梁
代码中的 serial_handel_all.py 展示了核心通信逻辑:系统打开 /dev/ttyUSB0 和 /dev/ttyUSB1 两个串口,以 115200 波特率通信。上位机一边读取定位数据 x_pos / y_pos / yaw,一边将当前位置与目标位置打包后发送给下位机控制系统。
数据包采用包头与包尾设计:
header = 0x0F
footer = 0xAA
payload = 当前 x/y/yaw + 目标 x/y/yaw
对应的打包逻辑可以抽象成:
def float_to_hex(value):
value_i16 = int(value)
low = value_i16 & 0xFF
high = (value_i16 >> 8) & 0xFF
return low, high
x_low, x_high = float_to_hex(x_pos)
gx_low, gx_high = float_to_hex(x_pos_goal)
packet = bytes([
0x0F,
x_low, x_high,
gx_low, gx_high,
0xAA
])
serial_port.write(packet)
真实代码中还会同时传输 y、yaw 以及目标点的 y_goal、yaw_goal。包头用于快速定位一帧数据的开始,包尾用于确认一帧数据结束,中间载荷传递运动控制所需变量。它比纯字符串通信更紧凑,也更适合连续实时传输。
6.6 下位机控制:为什么要把实时任务交给 STM32?
视觉识别和路径决策需要较强算力,适合放在 Jetson;电机闭环、CAN 通信、机构执行需要稳定实时性,适合放在 STM32。项目使用 FreeRTOS 把底盘、取球、发射、夹爪、升降等任务分配到不同实时任务中,避免一个机构动作阻塞整台机器人。
可以把下位机任务结构理解为:
// 伪代码:FreeRTOS 任务分工
task_chassis_control(); // 底盘速度与位置控制
task_ball_intake(); // 同步带取球
task_ball_launcher(); // 三同步带发射
task_cone_gripper(); // 标志桶夹取与释放
task_serial_bridge(); // 与 Jetson 通信
task_safety_monitor(); // 遥控急停与状态保护
这种分工让机器人在“移动、检测、取球、发射、监控”之间保持并行响应,而不是等待某一个动作完全结束后才处理下一个事件。
7. 典型任务流程:机器人如何完成一次训练循环?
这一节可以按“状态机”来理解。机器人并不是简单地从头跑到尾,而是在不同状态之间切换:找球、去球、取球、去发射点、发球、布置标志桶、返回待命。

7.1 路径点:把训练动作抽象成x y yaw
本地 goal_path.txt 和 goal_path_buff.txt 中保存了若干目标路径点,格式为:
x y yaw
例如:
0 0 0
0 350 0
0 350 180
1500 350 180
这代表机器人可以按一组中间点移动,并在特定坐标附近触发视觉或机构任务。对球场训练来说,这种设计非常实用:预设路线、定点发球、区域转移和标志桶布置都可以统一抽象成目标位姿序列。
7.2 到点判断:不是“差不多到了”,而是有误差阈值
代码中的 reach_goal 函数体现了典型的机器人到点判断方式:只要当前位置与目标位置的误差小于阈值,就认为到达。
def reach_goal(goal, real, error_x=50, error_y=50, error_yaw=5):
return (
abs(goal[0] - real[0]) < error_x and
abs(goal[1] - real[1]) < error_y and
abs(goal[2] - real[2]) < error_yaw
)
这里的 50 可以理解为位置误差容忍范围,5 是角度误差容忍范围。机器人系统通常不会追求数学意义上的完全重合,因为传感器噪声、轮胎打滑、机械间隙都会造成微小误差。合理的阈值能让系统更稳定,不会在目标点附近反复抖动。
7.3 主任务循环:到达一个点,再派发下一个点
自动导航逻辑可以简化成下面的形式:
goal_list = load_goal_path("goal_path.txt")
real_pose = [0.0, 0.0, 0.0]
while robot_is_running:
current_goal = goal_list[0]
if reach_goal(current_goal, real_pose):
goal_list.pop(0)
next_goal = goal_list[0]
send_goal_to_ros2(next_goal)
real_pose = read_pose_feedback()
本地代码中的 goal_cmd_auto.py 还预留了在特定位置触发视觉任务的逻辑。例如到达某个起始拾球点后,调用 OpenCV 相关函数获取画面,再根据结果修正下一目标点。
7.4 发射任务:速度、角度、自旋共同决定训练球路
三同步带发射机构的重点在于“多电机差速”。如果三条带速度一致,排球主要获得前向速度;如果上下或左右方向存在速度差,排球会获得自旋,进而改变飞行轨迹。
可以用一个简化逻辑理解:
straight_ball:
belt_top = high_speed
belt_left = high_speed
belt_right = high_speed
spin_ball:
belt_top = high_speed
belt_left = medium_speed
belt_right = high_speed
真实系统还需要结合排球压缩量、同步带摩擦、发射口角度和电机转速曲线做标定。也就是说,发射机构不是简单“给电机一个速度”,而是要建立“电机转速 → 球速/自旋 → 落点”的训练映射。
7.5 监控与可视化:RViz 让路径调试可见
项目中使用 nav_msgs/Path 发布真实路径和目标路径,并通过 RViz 显示轨迹。这样调试人员可以直观看到:
- 机器人真实轨迹是否偏离目标路径;
- 目标点是否按预期更新;
- 机器人是否在某个点附近反复震荡;
- 串口反馈的姿态是否异常跳变。
这类可视化对机器人调试很重要,因为很多问题只看日志很难发现,而轨迹一画出来就很明显。
8. 项目的关键亮点
亮点一:把“发球机”升级成“移动训练助手”
市面上常见发球设备多为定点发射,仍需要人工捡球、搬运、调整位置。本项目把自主移动、视觉识别、自动取球、定点发射和标志桶取放整合到同一平台,训练流程更加连续。
亮点二:机械机构围绕真实球场环境设计
项目没有只追求理想状态下的结构,而是考虑了排球尺寸变化、场地不平、频繁加减速、机构振动、标志桶存储等实际问题。弹性卷球摇臂、悬挂底盘、浮动编码轮都体现了“真实环境容错”的设计思路。
亮点三:软件架构具备工程化拆分
ROS2 action/service/message 的引入,让目标点、当前位置、监控反馈和路径显示可以解耦运行。多线程执行器用于支持 action server、路径发布和监控并发执行,符合机器人系统中“感知、决策、控制并行”的实际需求。
亮点四:上位机与下位机分工清晰
Jetson Orin Nano 负责视觉和 ROS2 逻辑,STM32 负责实时电机控制,FreeRTOS 管理机构任务,CAN 驱动电机,串口连接两侧。这种架构既保留边缘 AI 的灵活性,也保障底层执行的实时性。
9. 应用场景:它能在哪些训练里真正派上用场?

场景一:中小学排球教学
机器人可以稳定发球,帮助学生练习垫球、接发球和移动判断。教练不用频繁捡球和摆桶,可以把注意力放在动作纠正和训练组织上。
场景二:个人无同伴训练
个人排球爱好者在没有陪练的情况下,也能通过机器人进行连续接球练习。机器人可按预设点位移动并发球,提高训练密度。
场景三:专业队专项训练
通过调整发射速度、角度、自旋和落点,机器人可模拟不同类型来球,用于接发、防守移动、反应速度等专项训练。
场景四:标点与路线训练
标志桶取放机构让机器人能参与布置训练场地,例如移动路线、反应区域、落点目标等,提升训练组织效率。

10. 可继续优化的方向
这个项目已经完成了从机械设计、嵌入式控制到 ROS2 软件链路的完整探索。如果继续迭代,可以重点关注以下方向:
– 视觉定位闭环增强:将 YOLOv8 检测框、Gemini 2 深度图、相机外参和机器人坐标系做更稳定的融合;
– 取球机构减振:对弹性摇臂进行频率分析,加入阻尼或优化弹簧刚度;
– 发射轨迹建模:建立速度、角度、自旋与落点之间的映射模型,实现更精确的球路控制;
– 定位误差修正:在正交编码轮与陀螺仪基础上,引入视觉标记、场地线识别或滤波融合;
– 训练模式产品化:把发球类型、目标区域、标志桶布局、训练轮次做成可视化交互界面。
11. 结语:真正好的训练机器人,不是替代教练,而是让训练更连续
这台排球辅助训练机器人的价值,不只是“能发球”。它更像一个球场上的自动化助手:能看见球、能靠近球、能把球收进来、能移动到指定位置、能按要求发球,还能参与训练场地布置。
从技术上看,它把 Gemini 2 深度视觉、YOLOv8 目标检测、ROS2 任务调度、串口实时通信、STM32 + FreeRTOS 控制、多机构机械执行整合到同一个系统里。难点不在某一个单独模块,而在于这些模块必须在真实球场中稳定配合。
也正因为如此,它比传统发球机更接近未来训练设备的形态:不是一个固定机器,而是一台能理解场地、移动执行、连续服务训练流程的智能机器人。