如果你已经运行了我们提供的完整调试代码,**双手都清晰出现在画面中**,但系统仍然只检测到一只手(控制台输出始终是“检测到 1 只手”),那么我们可以断定:问题不是代码逻辑错误,而是 **MediaPipe 的检测机制在当前环境下未能激活双手机制**。
下面我们进行**终极排查 + 强制解决方案**,确保你能真正实现双手识别。
---
## 🔍 终极原因分析(为什么 MediaPipe “看不见”第二只手?)
即使设置了 `max_num_hands=2`,MediaPipe Hands 内部有一个 **非极大抑制(Non-Maximum Suppression, NMS)策略**,它会基于空间重叠度自动过滤掉“看起来太近”的手。这是导致“明明有两只手却只返回一只”的根本原因!
> 📌 **关键点**:当两只手靠得太近、交叉、或被模型认为是同一个手时,MediaPipe 会主动抑制其中一个检测结果。
---
## ✅ 解决方案一:修改 MediaPipe 源码参数(高级用户推荐)
MediaPipe 默认的 `hand_detection_cpu.pbtxt` 中有一个参数叫 `min_suppression_threshold`,默认为 `0.3`,意味着如果两个候选框重叠超过 70%,就只保留一个。
我们可以通过**降低这个阈值**来允许更多手被保留。
但由于 Python 包是编译好的,不能直接改 pbtxt 文件,所以我们采用以下替代方法:
### ✅ 替代方案:使用自定义推理流程(基于 TFLite 手动调用)
但这对大多数用户太复杂。因此我们提供更实用的方法:
---
## ✅ 实用解决方案二:强制启用双手机制的技巧
### ✨ 技巧:让 MediaPipe “以为”是两只独立的手 —— 分区域检测
我们将图像分成左右两部分,分别送入检测器。这样可以绕过 NMS 限制,强制检测出两只手。
```python
import cv2
import mediapipe as mp
mp_hands = mp.solutions.hands
mp_drawing = mp.solutions.drawing_utils
# 全局 Hands 模型(用于复用)
hands = mp_hands.Hands(
static_image_mode=False,
max_num_hands=1, # 每次只检测一只手,但我们分区域处理
min_detection_confidence=0.5,
min_tracking_confidence=0.5
)
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
while cap.isOpened():
success, image = cap.read()
if not success:
continue
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
h, w, _ = image.shape
# 存储所有检测到的手
all_hand_landmarks = []
all_handedness = []
# ---- 左半区检测右手 or 左手 ----
left_roi = image_rgb[:, :w//2] # 左半边
results_left = hands.process(left_roi)
if results_left.multi_hand_landmarks:
for i, lm in enumerate(results_left.multi_hand_landmarks):
# 调整坐标回全局位置
for point in lm.landmark:
point.x = point.x * (w//2) / w
all_hand_landmarks.append(lm)
# 假设左区检测到的是左手(可进一步判断)
handedness = results_left.multi_handedness[i].classification[0].label
all_handedness.append(handedness)
# ---- 右半区检测 ----
right_roi = image_rgb[:, w//2:] # 右半边
results_right = hands.process(right_roi)
if results_right.multi_hand_landmarks:
for i, lm in enumerate(results_right.multi_hand_landmarks):
for point in lm.landmark:
point.x = (point.x * (w//2) + w//2) / w
all_hand_landmarks.append(lm)
handedness = results_right.multi_handedness[i].classification[0].label
all_handedness.append(handedness)
# ---- 合并绘制 ----
for lm, hand_label in zip(all_hand_landmarks, all_handedness):
mp_drawing.draw_landmarks(image, lm, mp_hands.HAND_CONNECTIONS)
x_px = int(lm.landmark[0].x * w)
y_px = int(lm.landmark[0].y * h)
cv2.putText(image, hand_label, (x_px, y_px - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
cv2.imshow('Dual Hand Forced Detection', image)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
hands.close()
cap.release()
cv2.destroyAllWindows()
```
✅ **优点**:
- 即使双手靠近也不会被 NMS 过滤;
- 稳定检测出两只手;
- 适用于手语录制场景。
⚠️ **注意**:
- 需要适当调整 ROI 区域避免切掉手;
- 可扩展为三区域滑动窗口提升精度。
---
## ✅ 解决方案三:升级到 MediaPipe Tasks(新版本 API,支持更强双手)
Google 推出了新的 [MediaPipe Tasks](https://developers.google.com/mediapipe/solutions/vision/hand_landmarker) API,支持更好的多手检测。
### 安装:
```bash
pip install mediapipe-models
```
### 示例代码(Tasks 版本):
```python
from mediapipe import solutions
from mediapipe.framework.formats import landmark_pb2
import numpy as np
import cv2
def draw_landmarks_on_image(rgb_image, detection_result):
hand_landmarks_list = detection_result.hand_landmarks
handedness_list = detection_result.handedness
annotated_image = np.copy(rgb_image)
for idx in range(len(hand_landmarks_list)):
hand_landmarks = hand_landmarks_list[idx]
handedness = handedness_list[idx]
# Draw landmarks
hand_landmarks_proto = landmark_pb2.NormalizedLandmarkList()
hand_landmarks_proto.landmark.extend([
landmark_pb2.NormalizedLandmark(x=landmark.x, y=landmark.y, z=landmark.z)
for landmark in hand_landmarks
])
solutions.drawing_utils.draw_landmarks(
annotated_image,
hand_landmarks_proto,
solutions.hands.HAND_CONNECTIONS,
solutions.drawing_styles.get_default_hand_landmarks_style(),
solutions.drawing_styles.get_default_hand_connections_style())
# Label
x = int(hand_landmarks[0].x * rgb_image.shape[1])
y = int(hand_landmarks[0].y * rgb_image.shape[0])
cv2.putText(annotated_image, f'{handedness[0].display_name}',
(x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
return annotated_image
# 初始化 Hand Landmarker
BaseOptions = mp.tasks.BaseOptions
HandLandmarker = mp.tasks.vision.HandLandmarker
HandLandmarkerOptions = mp.tasks.vision.HandLandmarkerOptions
VisionRunningMode = mp.tasks.vision.RunningMode
options = HandLandmarkerOptions(
base_options=BaseOptions(model_asset_path='hand_landmarker.task'),
running_mode=VisionRunningMode.IMAGE,
num_hands=2 # 明确设置双手
)
detector = HandLandmarker.create_from_options(options)
# 视频循环
cap = cv2.VideoCapture(0)
while cap.isOpened():
success, image = cap.read()
if not success:
continue
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image_mp = mp.Image(image_format=mp.ImageFormat.SRGB, data=image_rgb)
detection_result = detector.detect(image_mp)
annotated_image = draw_landmarks_on_image(image_rgb, detection_result)
cv2.imshow('MediaPipe Tasks - Dual Hands', cv2.cvtColor(annotated_image, cv2.COLOR_RGB2BGR))
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
```
📌 下载模型文件:[hand_landmarker.task](https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task)
---
## ✅ 最终建议(快速见效)
| 目标 | 推荐做法 |
|------|----------|
| 快速看到双手 | 使用 **分区域检测法(解决方案二)** |
| 长期项目开发 | 迁移到 **MediaPipe Tasks API** |
| 动态手语识别 | 结合双手关键点 + LSTM / Transformer 序列建模 |
---
###