AI落地好场景,用米尔RK3576做无人视力测试仪
2026-06-18
36
来源:米尔电子


01
实现自动视力检测:用户通过简单的手势即可完成左右眼的视力检测 AI手势识别:利用MediaPipe实现精准的OK手势检测,作为启动测试的交互方式 智能化流程控制:自动识别E字方向、判断测试结果、自动切换测试眼别 语音交互:集成语音播报功能,提供清晰的测试指引和结果反馈
02

RK3576开发板: 核心控制器,运行所有业务逻辑 视频播放模块: 使用GStreamer进行硬件解码播放引导视频 手势识别模块: MediaPipe Hands检测OK手势作为启动信号 距离检测模块: 串口读取TOF传感器数据,检测用户距离 语音播报模块: ES8388音频编解码,播报欢迎词和检测结果 UI显示模块: PySide6 Qt开发,HDMI输出到显示屏
摄像头采集图像 → MediaPipe手势识别 → 判断OK手势 TOF传感器检测距离 → 串口通信 → 判断距离是否合适 随机生成E字方向 → 显示在屏幕上 用户手势 → 摄像头采集 → 手势识别 → 对比判断 测试结果 → 语音播报 + 屏幕显示
模块 | 功能描述 |
视频播放模块 | 开机播放引导视频,支持GStreamer硬件解码 |
手势识别模块 | 使用MediaPipe检测OK手势,作为测试启动信号 |
距离检测模块 | 通过串口读取红外测距传感器数据,检测用户距离 |
视力测试模块 | 随机显示E字方向,根据用户手势判断是否正确 |
语音播报模块 | 播放欢迎语音、测试指引、视力结果等 |
UI显示模块 | 使用PySide6显示视频、E字图片、状态信息 |

系统初始化 -启动摄像头、手势识别、距离传感器、音频系统 播放引导视频 -开机自动播放intro_guide.mp4介绍使用方法 等待OK手势 -视频结束后显示提示,用户做OK手势启动测试 右眼测试 -随机显示E字方向,用户用手势回答,3次测试后判断结果 左眼测试 -自动切换到左手,重复右眼测试流程 显示结果 -播报双侧视力结果,显示在屏幕上 等待重新测试 -用户可再次做OK手势重新开始测试
03
设备 | 型号/规格 | 用途 |
开发板 | 米尔RK3576 | 核心控制器,运行AI推理和业务逻辑 |
摄像头 | Intel RealSense D435 | 采集RGB图像用于手势识别 |
距离传感器 | TOF激光测距模块 | 检测用户与设备的距离(2cm10m) |
音频编解码 | ES8388 | 音频输出(通过ALSA驱动) |
显示器 | HDMI显示屏 | 显示UI和视力表 |
双流输出 同时支持RGB彩色流和深度流 本项目使用RGB流(640x480)进行手势识别 深度流可用于未来扩展(如手势分割) 高质量图像 RGB分辨率:1920x1080 @ 30fps 采集分辨率:640x480 @ 15fps(用于手势识别) 内置自动白平衡、自动曝光功能 适用于各种光照条件 即插即用 通过USB 3.0接口连接 提供跨平台的librealsense SDK (pyrealsense2) 支持Linux、Windows等操作系统 稳定性强 工业级品质,稳定性好 自动曝光和对焦,适应能力强 在复杂环境下仍能准确识别手势 易于集成 Python绑定支持,易于开发 帧同步机制,确保数据一致性 实时性好,满足手势识别需求
超宽测距范围 测距范围:2cm ~ 10m 覆盖从近到远的各种使用场景 高精度测量 测量精度:±1cm 分辨率高,可检测微小距离变化 快速响应 响应时间:<100ms 实时检测用户距离,适用于动态场景 抗干扰能力强 不受光照变化影响 不受被测物体颜色和材质影响 低功耗 功耗低,发热小 适合长时间运行
RK3576开发板 │ ├── USB3.0接口 ────> Intel RealSense D435 深度摄像头 │ ├── 串口(UART) ─────> TOF激光测距模块 │ ├── I2S/PCM ────────> 音频编解码 (ES8388) ──> 喇叭 │ └── HDMI ──────────> 显示器 (HDMI OUT)
操作系统: Buildroot Linux (ARM64) Python版本: 3.10+ AI框架: MediaPipe (Google) 视觉库: Intel RealSense SDK (librealsense) GUI框架: PySide6 多媒体: GStreamer + ALSA 音频格式: WAV (44.1kHz, 16bit, stereo)
04
模型: MediaPipe Hands (预训练模型) 输入: RGB图像 (640x480) 输出: 21个关键点坐标、手势分类 性能: 实时推理 (30fps)
8 12 16 20 │ │ │ │ ▼ ▼ ▼ ▼ ○─────────────○───────────○───────────○ ← 食指、中指、无名指、小指 │ │ │ │ 7 11 15 19 │ │ │ │ ○─────────────○───────────○───────────○ │ │ │ │ 6 10 14 18 │ │ │ │ ○─────────────○───────────○───────────○ │ │ │ │ 5 9 13 17 │ │ │ │ ○─────────────○───────────○───────────○ 4 1 0 9 └──────────────────────────────────────┘ 手腕
0: 手腕 1-4: 拇指 (掌骨→近节指骨→远节指骨→指尖) 5-8: 食指 9-12: 中指 13-16: 无名指 17-20: 小指
# 手势识别关键代码 (hand_gesture_task.py) import mediapipe as mp import numpy as np import cv2 mp_hands = mp.solutions.hands hands = mp_hands.Hands( static_image_mode=False, max_num_hands=2, min_detection_confidence=0.7, min_tracking_confidence=0.5, ) def calculate_distance(p1, p2): """计算两个 landmark 之间的欧氏距离(归一化坐标)""" return ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5 def recognize_gesture(landmarks): """识别 OK、NO(拳头)手势。""" thumb_tip = landmarks[4] index_tip = landmarks[8] middle_tip = landmarks[12] ring_tip = landmarks[16] pinky_tip = landmarks[20] wrist = landmarks[0] thumb_index_dist = calculate_distance(thumb_tip, index_tip) if thumb_index_dist < 0.05: if calculate_distance(middle_tip, wrist) > 0.3: return "OK" tips = [index_tip, middle_tip, ring_tip, pinky_tip] all_close = True for tip in tips: if calculate_distance(tip, wrist) > 0.15: all_close = False break if all_close: return "NO" return None def get_finger_direction(landmarks, h, w): """获取食指指向:上、下、左、右或静止。""" x5, y5 = int(landmarks[5].x * w), int(landmarks[5].y * h) x8, y8 = int(landmarks[8].x * w), int(landmarks[8].y * h) dx, dy = x8 - x5, y8 - y5 threshold = 25 if abs(dx) < threshold and abs(dy) < threshold: return "静止" if abs(dx) > abs(dy): return "右" if dx > 0 else "左" return "下" if dy > 0 else "上" def image_to_user_direction(img_dir): """将图像坐标系转换为用户视角方向。""" if img_dir == "左": return "右" if img_dir == "右": return "左" return img_dir
def hand_gesture_worker():
"""手势识别工作线程。"""
global current_hand_info, gesture_running
pipeline = rs.pipeline()
config = rs.config()
config.enable_stream(
rs.stream.color, 640, 480, rs.format.bgr8, 15
)
pipeline.start(config)
while gesture_running:
frames = pipeline.wait_for_frames(timeout_ms=1000)
color_frame = frames.get_color_frame()
if not color_frame:
continue
color_image = np.asanyarray(color_frame.get_data())
h, w = color_image.shape[:2]
rgb = cv2.cvtColor(color_image, cv2.COLOR_BGR2RGB)
results = hands.process(rgb)
lines = []
if results.multi_hand_landmarks and results.multi_handedness:
for hand_landmarks, handedness in zip(
results.multi_hand_landmarks,
results.multi_handedness,
):
mp_label = handedness.classification[0].label
user_hand = "右手" if mp_label == "Left" else "左手"
gesture = recognize_gesture(hand_landmarks.landmark)
if gesture:
lines.append(f"[{user_hand}]{gesture}")
else:
direction = get_finger_direction(
hand_landmarks.landmark, h, w
)
user_direction = image_to_user_direction(direction)
lines.append(f"[{user_hand}]{user_direction}")
current_hand_info = "n".join(lines) if lines else "未检测到手"
time.sleep(0.1)
pipeline.stop()
hands.close()OK手势启动测试 用户做OK手势表示准备开始测试 系统检测到OK手势后自动启动 方向手势回答 测试过程中,用户通过伸手指方向来回答E字方向 系统检测食指方向,判断用户回答是否正确 左右手切换 右眼测试用右手,左眼测试用左手 通过multi_handedness判断用户使用的是哪只手
# GStreamer 视频解码管道 (intro_video_player.py)
pipeline_str = (
f"filesrc location={video_path} ! "
"qtdemux ! "
"h264parse ! mppvideodec ! " # 瑞芯微硬件解码
"videoconvert ! video/x-raw,format=BGR ! "
"appsink name=sink emit-signals=true"
)使用mppvideodec调用RK3576硬件解码器 通过AppSink将视频帧传递给Qt UI显示 音频使用独立管道通过ALSA播放
class TestState: # 系统状态 SYSTEM_STATE_IDLE = 0 # 系统空闲 SYSTEM_STATE_TESTING = 1 # 正在测试 # 应用状态 APP_STATE_WAITING_OK = 0 # 等待 OK 手势 APP_STATE_WAITING_HAND_OK = 1 # 等待举手 APP_STATE_TESTING = 2 # 正在测试 APP_STATE_SHOWING_RESULT = 3 # 显示结果
# 串口读取距离数据 (main.py)
def _read_distance(self):
try:
if self.serial_port and self.serial_port.in_waiting:
data = self.serial_port.readline()
if data.startswith(b"D:"):
distance = int(data[2:].strip())
return distance
except Exception as e:
print(f"读取距离失败: {e}")
return -105
问题 | 原因分析 | 解决方案 |
视频无法播放 | mp4v编码不支持 | 使用H.264编码重新转换视频 |
音频无声音 | 采样率不匹配(24kHz mono) | 转换为44.1kHz stereo |
手势识别不稳定 | 光照条件影响 | 调整检测阈值,增加置信度 |
距离传感器读数为0 | 串口波特率不匹配 | 改为115200波特率 |
测试结果不准确 | 左右手方向判断错误 | 修正手势方向与E字方向的映射关系 |
视频画面无法清除 | 停止时回调递归 | 添加stopping标志防止重复调用 |
E字符不显示 | 停止视频后未显示char_label | 在_start_vision_test中添加显示逻辑 |
06
端侧AI推理: 利用RK3576的NPU实现实时手势识别,无需云端支持 多模态交互: 结合视频、语音、手势等多种交互方式 硬件加速: 使用GStreamer硬件解码播放视频,效率高
模块化设计: 清晰的分层架构,易于维护和扩展 状态机管理: 完善的测试流程状态控制 容错处理: 丰富的异常处理和恢复机制 用户友好: 语音引导+视频演示,操作简单
可部署于社区健康站、学校、商场等场所 无需专业人员辅助,用户可自主完成检测 检测结果实时语音播报,方便快捷 成本可控,易于推广普及
07
参数 | 数值 |
代码总量 | 3092行 |
视频解码分辨率 | 640x480 |
手势识别帧率 | 30fps |
距离检测精度 | ±10mm |
语音采样率 | 44.1kHz |
测试时间 | 约2-3分钟/次 |
08
RK3576-Pro/ ├── main.py # 主程序入口(942 行) ├── ui_vision_test.py # UI 界面(241 行) ├── intro_video_player.py # 视频播放器(618 行) ├── hand_gesture_task.py # 手势识别(187 行) ├── generate_custom_audio.py # 音频生成脚本(95 行) ├── pic/ │ ├── up.png │ ├── down.png │ ├── left.png │ └── right.png ├── custom_audio/ │ ├── welcome.wav │ ├── switch_right_hand.wav │ ├── result_*.wav │ └── ... └── videos/ └── intro_guide.mp4
09
# 安装 Python 依赖 pip install pyside6 mediapipe pygame numpy opencv-python # 安装系统依赖 apt-get install gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good alsa-utils
连接硬件设备(摄像头、传感器、音频设备) 启动开发板 运行主程序:
python3 main.py
按照屏幕提示和语音指引进行测试
10

效果视频:
增加更多视力测试模式 支持云端数据存储和分析 添加更多健康检测功能 优化AI模型提升识别准确率
STM32MP25x开发板Bring Up培训课程(下)
STM32MP25x开发板Bring Up培训课程(中)
STM32MP25x开发板Bring Up培训课程(上)
嵌入式Linux入门级板卡的神经网络框架ncnn移植与测试-米尔i.MX6UL开发板
FPGA+MPU+MCU三芯合一!米尔全自动血细胞分析仪解决方案
基于Zynq-7000高速数据采集解决方案—米尔MYD-C7Z010/20-V2开发板
开发环境篇:Linux C按键控制LED--米尔MYD-YT507H开发板
第一视角体验国产处理器全志T507-H开发板
米尔的国产T507-H开发板怎么玩?macOS如何将Ubuntu系统烧录到eMMC的完全调教指南!
如何实现异构处理器间相互通讯——米尔带您玩转i.MX 8M Plus开发板