单元 7
7. 手势识别与交互
本章使用MediaPipe Hand Landmarker识别手部的21个关键点,根据手指弯曲状态判断手势,并通过ROS2速度话题控制机器人底盘。用户可以先体验基础手势控制,再通过修改Python程序增加原地旋转和点赞识别。
7.0 · 手势识别与交互
课程导读
本章使用MediaPipe Hand Landmarker识别手部的21个关键点,根据手指弯曲状态判断手势,并通过ROS2速度话题控制机器人底盘。用户可以先体验基础手势控制,再通过修改Python程序增加原地旋转和点赞识别。
7.0.1 本章能完成什么
完成本章学习后,用户可以:
- 使用一条ROS2 Launch命令启动底盘、相机和手势控制节点。
- 在画面中查看左右手、21个手部关键点和手势名称。
- 使用
open和close手势控制机器人前进、后退。 - 了解手势识别结果如何映射为ROS2
Twist速度指令。 - 修改基础版程序,为
ok增加原地旋转动作。 - 选择性学习如何增加
thumb_up点赞识别。
7.0.2 实验列表
| 课程内容 | 难度 | 预计时间 | 前置内容 | 说明 |
|---|---|---|---|---|
| 7.1 实验1:基础手势控制 | 入门 | 20~30分钟 | 7.0课程导读 | 使用open前进、close后退。 |
7.2 实验2:增加ok原地旋转 |
入门 | 20~30分钟 | 建议完成7.1 | 修改基础版程序,增加原地旋转。 |
7.3 进阶拓展:增加thumb_up识别 |
进阶/可选 | 20~30分钟 | 建议完成7.1 | 增加带方向限制的点赞识别。 |
7.0.4 通用启动与退出方法
-
启动机器人,并使用NoMachine连接机器人远程桌面。关于连接方法,可参考 1 教程资料/1 机器人快速使用指南/ROSOrin Pro使用手册/7.2直连模式连接步骤。
-
在远程桌面中打开命令行终端。输入命令时需要严格区分大小写,可使用“Tab”键补齐关键词。
-
按照所选实验的操作步骤启动对应Launch文件。基础版、入门版和进阶版的启动命令不同,请勿混用或同时运行。
-
实验结束后,在运行Launch文件的终端中按“Ctrl+C”停止程序。若实验过程中关闭了APP自启服务,请执行以下命令恢复服务:
sudo systemctl restart start_app_node.service
7.0.5 实验学习路径
- 基础体验路线: 7.1 基础手势控制。
- 入门实践路线: 7.1 基础手势控制 → 7.2 增加
ok原地旋转。 - 进阶拓展路线: 7.1 基础手势控制 → 7.3 增加
thumb_up识别。
7.1 · 手势识别与交互
实验1:基础手势控制
| 预计时间 | 难度 | 前置内容 |
|---|---|---|
| 20~30分钟 | 入门 | 7.0 课程导读 |
本实验先体验完整的手势识别与底盘控制流程,并了解程序的基本结构和安全停车机制。
7.1.1 你将完成什么
启动基础版程序后,将手掌完整放入摄像头视野。程序会绘制手部关键点和连线,并在每只手旁显示即时识别结果,在画面右上角显示连续稳定识别后的控制手势。
当程序稳定识别到open时,机器人前进;稳定识别到close时,机器人后退;手离开画面、手势不稳定或出现其他手势时,机器人停止。

图:程序检测手部关键点并识别手势。
7.1.2 实验目标
- 正确启动基础版手势控制程序。
- 使用
open和close手势控制机器人前进、后退。 - 观察手边即时识别结果与右上角稳定控制手势之间的区别。
- 理解无手势、图像中断和程序退出时的停车保护。
- 找到入门版中新增
ok原地旋转功能的代码位置。
7.1.3 实验准备
开始实验前,请确认:
- 机器人已正常启动,深度相机连接正常。
- 电脑已安装NoMachine,并能够连接机器人远程桌面。
- ROS2工作空间和本节实验程序已安装。
- 摄像头前方光线充足,手掌无遮挡且能够完整进入画面。
- 机器人周围具有足够的前进、后退和旋转空间。
- 旧的指尖轨迹程序
hand_gesture_control_node.launch.py没有运行。
7.1.4 开始实验
本小节先运行基础版。基础版用于体验完整的手势检测和底盘控制流程,不包含ok原地旋转和thumb_up点赞识别。
-
启动机器人,并使用NoMachine连接机器人远程桌面。关于连接方法,可参考 1 教程资料/1 机器人快速使用指南/ROSOrin Pro使用手册/7.2直连模式连接步骤。
-
点击系统桌面的图标
,打开命令行终端。 -
输入以下命令,停止APP自启服务:
sudo systemctl stop start_app_node.service
-
确认机器人驱动轮已架空,或机器人周围没有人员和障碍物。
-
输入以下命令,启动基础版程序:
ros2 launch example hand_gesture_drive.launch.py
该Launch文件会自动完成以下操作:
- 启动底盘控制节点。
- 启动深度相机节点。
- 等待3秒后启动手势控制节点
hand_pose_drive。
-
等待标题为
hand_gesture_control的图像窗口打开,将手掌完整放入摄像头视野。 -
依次测试下表中的手势:
| 手势名称 | 手部姿态 | 机器人动作 |
|---|---|---|
open |
五指张开 | 以0.15 m/s前进 |
close |
握拳 | 以0.15 m/s后退 |
one |
伸出食指 | 只识别,机器人停止 |
two |
伸出食指和中指 | 只识别,机器人停止 |
rock |
伸出拇指、食指和小指 | 只识别,机器人停止 |
ok |
收起拇指和食指,伸出中指、无名指和小指 | 只识别,机器人停止 |
none |
未检测到稳定手势 | 机器人停止 |
7.1.5 查看实验结果
程序正常运行后,检查以下现象:
- 图像窗口能够显示摄像头实时画面。
- 每只手上能够显示21个关键点和关键点连线。
- 手边文字能够显示当前即时识别结果。
- 右上角深色背景区域能够显示
Control: open、Control: close或Control: none。 open或close连续稳定出现5帧后,机器人执行对应动作。- 松开手、改变为其他手势或将手移出画面后,机器人立即停止。
- 相机图像中断超过0.5秒后,程序发布停车指令。

图:程序识别手部关键点和手势。
7.1.6 结束实验
- 返回运行Launch文件的命令行终端,按“Ctrl+C”停止程序。
7.1.7 完成标准
满足以下条件即表示完成本节实验:
- 能够使用一条Launch命令启动基础版程序。
- 能够区分手边即时手势和右上角稳定控制手势。
- 使用
open时机器人前进,使用close时机器人后退。 - 使用其他手势或移开手后,机器人能够停止。
- 能够按“Ctrl+C”结束实验。
7.1.8 基础版程序简要分析
本节课程程序统一位于:
~/ros2_ws/src/example/example/hand_gesture_course
目录结构如下:
hand_gesture_course/
├── __init__.py
├── fps.py
├── hand_gesture_control.py
├── hand_gesture_ok_control.py
├── hand_gesture_thumb_up.py
├── hand_gesture_drive.launch.py
├── hand_gesture_ok_drive.launch.py
├── hand_gesture_thumb_up_drive.launch.py
└── model/
└── hand_landmarker.task
三个版本相互独立,学习某个版本时可以直接打开对应Python文件进行查看,不需要同时运行其他版本。
7.1.8.1 基础版启动关系
基础版的启动关系如下:
hand_gesture_drive.launch.py
↓
ROS2可执行入口 hand_pose_drive
↓
hand_gesture_control.py中的main()
Launch文件同时包含底盘和相机节点,并使用TimerAction延迟3秒启动手势控制节点,避免底盘和相机尚未完成初始化时就开始处理手势。
7.1.8.2 初始化Hand Landmarker
程序使用MediaPipe Tasks的HandLandmarker,以视频模式最多检测两只手:
current_dir = os.path.dirname(os.path.realpath(__file__))
model_path = os.path.join(current_dir, 'model', 'hand_landmarker.task')
options = vision.HandLandmarkerOptions(
base_options=python.BaseOptions(model_asset_path=model_path),
running_mode=vision.RunningMode.VIDEO,
num_hands=2,
min_hand_detection_confidence=0.75,
min_hand_presence_confidence=0.75,
min_tracking_confidence=0.75,
)
self.detector = vision.HandLandmarker.create_from_options(options)
VIDEO模式会利用连续帧之间的跟踪结果。调用detect_for_video()时,程序为每一帧提供单调递增的毫秒时间戳。
7.1.8.3 从关键点判断手势
程序先把21个归一化关键点转换为像素坐标,再计算五根手指的弯曲角度:
pixels = np.asarray([
(lm.x * width, lm.y * height) for lm in landmarks
], dtype=np.float32)
gesture = classify_gesture(hand_angle(pixels), pixels)
classify_gesture()根据手指弯曲角度返回close、one、two、rock、ok、open或none。
左右手标签只用于画面显示。连续帧缓存使用hand_0和hand_1作为键,避免模型的左右手结果短暂波动时重置稳定帧计数:
hand_key = 'hand_%d' % index
stable = self.update_stable_gesture(hand_key, gesture)
只有同一个手势连续稳定出现5帧后,才会进入底盘控制逻辑。
7.1.8.4 基础版动作映射
基础版动作映射位于:
hand_gesture_course/hand_gesture_control.py
handle_gesture()只允许open和close控制底盘:
def handle_gesture(self, gesture):
if gesture != self.last_control_gesture:
self.get_logger().info('Stable gesture: %s' % gesture)
self.last_control_gesture = gesture
if gesture == 'open':
desired_speed = MOVE_SPEED
elif gesture == 'close':
desired_speed = -MOVE_SPEED
else:
desired_speed = 0.0
self.set_linear_speed(desired_speed)
其中:
MOVE_SPEED = 0.15
Twist.linear.x为正值时前进,为负值时后退,为0时停止。
7.1.8.5 安全停车
程序除了在其他手势和无手势时停车,还通过定时器检查图像是否持续更新:
IMAGE_TIMEOUT_SECONDS = 0.5
def motion_watchdog(self):
if self.active_linear_speed == 0.0:
return
if (self.last_processed_image_time is None or
time.monotonic() - self.last_processed_image_time
> IMAGE_TIMEOUT_SECONDS):
self.get_logger().warn('Image timeout: stopping chassis')
self.stop_chassis()
如果相机图像中断超过0.5秒,程序会主动发布零速度。按“Ctrl+C”退出程序时,也会在销毁节点前调用stop_chassis()。
7.2 · 手势识别与交互
实验2:增加ok原地旋转
| 预计时间 | 难度 | 前置内容 |
|---|---|---|
| 20~30分钟 | 入门 | 建议完成7.1 |
完成基础版实验后,本小节将在基础版程序上增加ok原地旋转。请先了解目标效果,再按照步骤修改程序;完成修改和测试后,最后再运行课程提供的参考答案程序。
7.2.1 要实现的效果
| 手势 | 机器人动作 | 功能来源 |
|---|---|---|
open |
前进 | 基础版原有 |
close |
后退 | 基础版原有 |
ok |
原地旋转 | 本实践新增 |
| 其他手势或无手势 | 停止 | 基础版原有 |
保持ok手势时,机器人以0.5 rad/s的角速度原地旋转;松开手、换成其他手势或识别结果不稳定时,机器人停止。
ok原地旋转不需要修改MediaPipe模型。基础版已经能够输出ok,本实践只需要在手势结果与ROS2 Twist速度指令之间增加一条动作映射。
7.2.2 备份并打开基础版程序
-
确认基础版Launch已经停止。如果程序仍在运行,请在对应终端按“Ctrl+C”。
-
输入以下命令进入课程程序目录:
cd ~/ros2_ws/src/example/example/hand_gesture_course
- 修改前先备份基础版程序:
cp hand_gesture_control.py hand_gesture_control_backup.py
- 打开基础版程序:
gedit hand_gesture_control.py
本实践需要修改的文件为:
~/ros2_ws/src/example/example/hand_gesture_course/hand_gesture_control.py
7.2.3 参考修改过程
1. 增加转向速度
找到MOVE_SPEED,在其下方增加TURN_SPEED:
MOVE_SPEED = 0.15
TURN_SPEED = 0.5
TURN_SPEED对应Twist.angular.z,单位为rad/s。
2. 记录当前角速度
在HandGestureNode.__init__()中找到self.active_linear_speed,在其下方增加角速度状态:
self.active_linear_speed = 0.0
self.active_angular_speed = 0.0
程序同时记录线速度和角速度,避免重复发布相同指令,并使图像断流保护能够判断机器人是否正在旋转。
3. 修改动作映射
找到handle_gesture(),将该函数整体替换为:
def handle_gesture(self, gesture):
if gesture != self.last_control_gesture:
self.get_logger().info('Stable gesture: %s' % gesture)
self.last_control_gesture = gesture
if gesture == 'open':
linear_speed = MOVE_SPEED
angular_speed = 0.0
elif gesture == 'close':
linear_speed = -MOVE_SPEED
angular_speed = 0.0
elif gesture == 'ok':
linear_speed = 0.0
angular_speed = TURN_SPEED
else:
linear_speed = 0.0
angular_speed = 0.0
self.set_velocity(linear_speed, angular_speed)
其中,open和close只设置线速度,ok只设置角速度,其他情况同时将两个速度设置为0。
4. 发布线速度和角速度
找到基础版的set_linear_speed(),将该函数整体替换为:
def set_velocity(self, linear_speed, angular_speed, force=False):
linear_speed = float(linear_speed)
angular_speed = float(angular_speed)
if (not force and linear_speed == self.active_linear_speed and
angular_speed == self.active_angular_speed):
return
message = Twist()
message.linear.x = linear_speed
message.angular.z = angular_speed
self.cmd_vel_pub.publish(message)
self.active_linear_speed = linear_speed
self.active_angular_speed = angular_speed
接着找到stop_chassis(),将其修改为同时清零线速度和角速度:
def stop_chassis(self):
self.set_velocity(0.0, 0.0, force=True)
5. 更新图像断流保护
找到motion_watchdog()开头的运动状态判断,将只检查线速度修改为同时检查线速度和角速度:
def motion_watchdog(self):
if self.active_linear_speed == 0.0 and self.active_angular_speed == 0.0:
return
if (self.last_processed_image_time is None or
time.monotonic() - self.last_processed_image_time
> IMAGE_TIMEOUT_SECONDS):
self.get_logger().warn('Image timeout: stopping chassis')
self.stop_chassis()
如果机器人正在原地旋转时相机图像中断,更新后的看门狗也能够发布停车指令。
7.2.4 保存并测试修改结果
-
保存
hand_gesture_control.py并关闭编辑器。 -
返回终端,直接运行修改后的基础版:
ros2 launch example hand_gesture_drive.launch.py
- 依次检查以下效果:
- 使用
open时,机器人前进。 - 使用
close时,机器人后退。 - 使用
ok时,机器人原地旋转。 - 换成其他手势或将手移出画面时,机器人停止。
- 原地旋转期间中断相机图像时,机器人能够自动停止。
测试结束后,在Launch终端按“Ctrl+C”。
7.2.5 完成标准
满足以下条件即表示完成本节实验:
open和close仍能正常控制机器人前进、后退。ok连续稳定出现5帧后,机器人能够原地旋转。- 改变为其他手势、移开手或相机图像中断时,机器人能够停止。
- 能够找到并说明
TURN_SPEED、handle_gesture()和set_velocity()的作用。 - 能够使用备份文件恢复修改前的基础版程序。
7.2.6 参考实现程序
点击展开参考实现
完成上述修改后,可以对照课程提供的入门版参考实现:
~/ros2_ws/src/example/example/hand_gesture_course/hand_gesture_ok_control.py
参考实现使用独立的ROS2可执行入口和Launch文件。运行前,请确认修改后的基础版已经停止,然后输入:
ros2 launch example hand_gesture_ok_drive.launch.py
参考实现不会覆盖用户修改的hand_gesture_control.py。可以同时打开两个Python文件,对比TURN_SPEED、handle_gesture()、set_velocity()、stop_chassis()和motion_watchdog()的差异。
若需要恢复修改前的基础版,可在所有手势程序停止后输入:
cd ~/ros2_ws/src/example/example/hand_gesture_course
cp hand_gesture_control_backup.py hand_gesture_control.py
7.3 · 手势识别与交互
进阶拓展(可选):增加thumb_up识别
| 预计时间 | 难度 | 前置内容 |
|---|---|---|
| 20~30分钟 | 进阶/可选 | 建议完成7.1 |
本小节以基础版为起点增加thumb_up点赞识别。请先了解目标效果,再按照步骤修改程序;完成修改和测试后,最后再展开课程提供的参考实现。
7.3.1 要实现的效果
| 手势 | 机器人动作 | 功能来源 |
|---|---|---|
open |
前进 | 基础版原有 |
close |
后退 | 基础版原有 |
thumb_up |
只识别和显示,机器人停止 | 本拓展新增 |
| 其他手势或无手势 | 停止 | 基础版原有 |
正确的thumb_up姿态需要满足:拇指伸直并明显朝画面上方,食指、中指、无名指和小指保持弯曲。手势连续稳定出现5帧后,画面右上角显示Control: thumb_up,终端输出Stable gesture: thumb_up。
本拓展只增加识别结果,不为thumb_up绑定底盘动作,因此识别到点赞时机器人保持停止。
7.3.2 备份并打开基础版程序
-
确认所有手势控制Launch已经停止。如果程序仍在运行,请在对应终端按“Ctrl+C”。
-
输入以下命令进入课程程序目录:
cd ~/ros2_ws/src/example/example/hand_gesture_course
- 本拓展应以基础版为起点。如果刚刚完成了
7.2的ok原地旋转实践,请先使用备份恢复基础版:
cp hand_gesture_control_backup.py hand_gesture_control.py
如果没有进行过7.2实践,则无需执行上面的恢复命令。
- 确认当前文件为基础版后,重新备份:
cp hand_gesture_control.py hand_gesture_control_backup.py
- 打开基础版程序:
gedit hand_gesture_control.py
本拓展需要修改的文件为:
~/ros2_ws/src/example/example/hand_gesture_course/hand_gesture_control.py
7.3.3 参考修改过程
1. 增加手势优先级和最大倾角
在文件顶部的手势配置区域,将thumb_up加入优先级,并增加最大倾角:
GESTURE_PRIORITY = (
'thumb_up', 'rock', 'one', 'two', 'ok', 'open', 'close')
THUMB_UP_MAX_TILT_DEGREES = 25.0
最大倾角表示拇指相对画面竖直向上的允许偏差:
0°表示垂直向上。25°允许少量倾斜。- 接近
90°表示拇指水平,不应识别为点赞。
若正常点赞不容易识别,可放宽到30.0;若斜放拇指仍会误识别,可收紧到20.0。
2. 增加点赞方向判断函数
在is_thumb_extended()之后、classify_gesture()之前增加:
def is_thumb_up(landmarks):
"""Return True when an extended thumb points clearly upward."""
thumb_tip = landmarks[4]
thumb_ip = landmarks[3]
thumb_mcp = landmarks[2]
palm_size = np.linalg.norm(landmarks[0] - landmarks[9])
vertical_extension = thumb_mcp[1] - thumb_tip[1]
horizontal_offset = abs(thumb_tip[0] - thumb_mcp[0])
tilt_degrees = np.degrees(np.arctan2(
horizontal_offset, max(vertical_extension, 1e-6)))
return (
is_thumb_extended(landmarks)
and thumb_tip[1] < thumb_ip[1] < thumb_mcp[1]
and vertical_extension > max(8.0, palm_size * 0.35)
and tilt_degrees <= THUMB_UP_MAX_TILT_DEGREES
)
该函数依次检查:
- 拇指两个主要关节是否伸直。
- 拇指指尖、指间关节和掌指关节是否从上到下排列。
- 拇指向上的长度是否相对于手掌足够明显。
- 拇指相对竖直方向的倾角是否不超过25°。
3. 在close之前增加thumb_up分类
在classify_gesture()中,完成无效角度检查后、判断close之前增加:
if (is_thumb_up(landmarks) and
a[1] > bent and
a[2] > bent and
a[3] > bent and
a[4] > bent):
return 'thumb_up'
该判断要求拇指伸直向上,同时食指、中指、无名指和小指保持弯曲。
thumb_up必须放在close之前判断。点赞时其他四指处于弯曲状态,如果先执行握拳判断,点赞可能被提前分类为close,从而错误触发后退动作。
4. 保持基础版动作映射不变
本拓展不需要修改handle_gesture()。基础版只为open和close设置速度,thumb_up会进入else分支并将速度设置为0:
if gesture == 'open':
desired_speed = MOVE_SPEED
elif gesture == 'close':
desired_speed = -MOVE_SPEED
else:
desired_speed = 0.0
7.3.4 保存并测试修改结果
-
保存
hand_gesture_control.py并关闭编辑器。 -
返回终端,直接运行修改后的基础版:
ros2 launch example hand_gesture_drive.launch.py
- 依次检查以下效果:
- 使用
open时,机器人前进。 - 使用
close时,机器人后退。 - 拇指竖直向上、其余四指收起时,程序识别为
thumb_up,机器人保持停止。 - 拇指接近水平时,不应识别为
thumb_up。 - 换成其他手势或将手移出画面时,机器人停止。
测试结束后,在Launch终端按“Ctrl+C”。
7.3.5 完成标准
满足以下条件即表示完成本节拓展:
open和close仍能正常控制机器人前进、后退。- 拇指竖直向上且其余四指弯曲时,程序能够稳定识别
thumb_up。 - 拇指接近水平时,不会被识别为
thumb_up。 - 识别到
thumb_up时机器人保持停止,不会误触发close后退动作。 - 能够说明
THUMB_UP_MAX_TILT_DEGREES对识别严格程度的影响。
7.3.6 参考实现程序
点击展开参考实现
完成上述修改后,可以对照课程提供的进阶版参考实现:
~/ros2_ws/src/example/example/hand_gesture_course/hand_gesture_thumb_up.py
参考实现使用独立的ROS2可执行入口和Launch文件。运行前,请确认修改后的基础版已经停止,然后输入:
ros2 launch example hand_gesture_thumb_up_drive.launch.py
参考实现不会覆盖用户修改的hand_gesture_control.py。可以同时打开两个Python文件,对比GESTURE_PRIORITY、THUMB_UP_MAX_TILT_DEGREES、is_thumb_up()和classify_gesture()的差异。
若需要恢复修改前的基础版,可在所有手势程序停止后输入:
cd ~/ros2_ws/src/example/example/hand_gesture_course
cp hand_gesture_control_backup.py hand_gesture_control.py
7.4 · 手势识别与交互
常见问题
遇到问题?点击展开排查方法
7.4.1 画面有手部关键点,但机器人不运动
- 查看右上角是否稳定显示
Control: open或Control: close。 - 手边文字是即时识别结果,右上角结果连续稳定5帧后才用于控制。
- 确认没有同时运行其他会向
/controller/cmd_vel发布速度的程序。 - 使用以下命令检查速度话题是否同时具有发布者和订阅者:
ros2 topic info /controller/cmd_vel -v
7.4.2 手势始终显示none
- 将完整手掌放入摄像头视野,不要裁切手腕或指尖。
- 保持手掌正对摄像头,并减少手部旋转。
- 调整手掌与摄像头之间的距离。
- 增加环境光线,避免阴影和强烈逆光。
7.4.3 ok无法识别
本课程的ok不是传统OK手势。应收起拇指和食指,伸出中指、无名指和小指,并保持至少5帧。
7.4.4 水平拇指被识别为thumb_up
进阶版通过THUMB_UP_MAX_TILT_DEGREES限制点赞方向。可将默认值25.0调小为20.0,使判断更加严格。
7.4.5 左右手标签相反
程序在检测前对图像进行镜像翻转,并在显示时交换MediaPipe返回的Left和Right。若修改了图像翻转流程,应同时检查左右手标签交换逻辑。