4. 基于OpenCV的实时手势识别
任务目标
在嵌入式设备(如树莓派4B)上,使用OpenCV调用轻量级深度学习模型(MobileNetV3-SSD),实现摄像头实时画面中的手势识别(0-9数字手势),并能在复杂背景(非纯色、光照变化、轻微遮挡)下保持可用精度。
环境与依赖
- 硬件:树莓派4B(2GB+),USB摄像头或CSI摄像头
- 系统:Raspberry Pi OS (64-bit)
- Python 3.9+,OpenCV 4.5+(含dnn模块),NumPy
- 模型文件:
hand_model.pbtxt(网络结构)与hand_model.pb(权重),基于SSD MobileNetV3,输入尺寸320×320,输出为7个坐标点(食指到小指共4个指尖+4个指根+手腕,实际取指尖与指根共8点用于计数)
模型文件需预先下载并放置于models/目录。若使用其他手势检测模型,需对应调整输出层解析逻辑。
核心实现
以下代码实现完整闭环:读取摄像头帧 → 模型推理 → 解析关键点 → 计算伸展手指数量 → 显示识别结果。针对复杂背景,采用以下策略:
- 使用深度学习模型而非肤色分割,天然抵抗复杂背景干扰
- 对关键点做平滑滤波(指数移动平均),减少抖动
- 手指计数基于几何关系(指尖与指根距离/角度),不依赖背景分割
import cv2
import numpy as np
# 模型路径
PROTO = "models/hand_model.pbtxt"
WEIGHTS = "models/hand_model.pb"
CONF_THRESH = 0.5
# 加载网络
net = cv2.dnn.readNetFromTensorflow(WEIGHTS, PROTO)
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
# 关键点索引(根据模型输出定义)
# 输出为 [x1,y1,...,x21,y21] 共21点,此处取:4-指尖(8,12,16,20),指根(5,9,13,17)
TIP_IDS = [8, 12, 16, 20]
BASE_IDS = [5, 9, 13, 17]
WRIST_ID = 0
# 平滑滤波参数
alpha = 0.4
smoothed_pts = None
def count_fingers(pts):
"""根据指尖与指根距离判断手指是否伸展"""
wrist = pts[WRIST_ID]
count = 0
for tip_id, base_id in zip(TIP_IDS, BASE_IDS):
tip = pts[tip_id]
base = pts[base_id]
# 指尖到指根距离
d_tb = np.linalg.norm(tip - base)
# 指根到手腕距离
d_bw = np.linalg.norm(base - wrist)
# 指尖到手腕距离
d_tw = np.linalg.norm(tip - wrist)
# 若指尖明显远离手腕且指尖-指根距离大于指根-手腕距离的0.6倍,判定为伸展
if d_tw > d_bw * 1.2 and d_tb > d_bw * 0.6:
count += 1
return count
def process_frame(frame):
global smoothed_pts
h, w = frame.shape[:2]
# 模型输入
blob = cv2.dnn.blobFromImage(frame, 1.0/255.0, (320, 320), (0,0,0), swapRB=True, crop=False)
net.setInput(blob)
output = net.forward() # shape: [1, 1, N, 7] 或 [1, N, 7]
# 解析输出
if output.ndim == 4:
detections = output[0, 0]
else:
detections = output[0]
best_conf = 0
best_pts = None
for i in range(detections.shape[0]):
conf = detections[i, 2]
if conf > best_conf:
best_conf = conf
# 21点坐标从第3列开始,每点2个值
raw = detections[i, 3:]
pts = []
for j in range(0, len(raw), 2):
x = raw[j] * w
y = raw[j+1] * h
pts.append([x, y])
best_pts = np.array(pts, dtype=np.float32)
if best_pts is None or best_conf < CONF_THRESH:
return frame, None
# 平滑处理
if smoothed_pts is None:
smoothed_pts = best_pts
else:
smoothed_pts = alpha * best_pts + (1 - alpha) * smoothed_pts
# 绘制关键点
for pt in smoothed_pts:
cv2.circle(frame, (int(pt[0]), int(pt[1])), 4, (0, 255, 0), -1)
# 手指计数
fingers = count_fingers(smoothed_pts)
cv2.putText(frame, f"Fingers: {fingers}", (10, 40),
cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 255), 2)
return frame, fingers
# 主循环
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
while True:
ret, frame = cap.read()
if not ret:
break
result, fingers = process_frame(frame)
cv2.imshow("Hand Gesture Recognition", result)
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
运行与验证
- 将上述代码保存为
gesture_recognition.py,模型文件置于models/目录 - 运行:
python3 gesture_recognition.py - 摄像头自动开启,窗口实时显示画面
验证方法:
- 在摄像头前分别伸出1~5根手指,观察窗口左上角数字是否与预期一致
- 在复杂背景(如室内杂物、窗外光线变化)下测试,若检测到的手部关键点稳定跟随且计数正确率≥80%,即判定通过
- 按
q键退出程序
常见问题
- 检测不到手:确认模型文件路径正确,光照充足,手距摄像头30~80cm
- 计数不稳定:平滑系数alpha可调小(如0.2)增加稳定性,但响应会变慢
- 帧率低:降低输入分辨率至320×240,或改用CSI摄像头