MediaPipe无法在实时流绘制手部关键点,遇NoneType错误求助
问题排查与修复:MediaPipe实时手部关键点绘制失败及NoneType错误
核心问题分析
- LIVE_STREAM模式异步逻辑误用:使用
RunningMode.LIVE_STREAM时,detect_async()是异步调用,不会直接返回检测结果——结果仅通过你定义的result_callback函数返回。原代码试图直接获取detect_async()的返回值,必然得到None,这就是触发NoneType错误的根源。 - 图像格式不匹配:OpenCV读取的帧是BGR格式,而MediaPipe要求输入为RGB(对应
mp.ImageFormat.SRGB),直接传入会导致颜色异常,甚至影响检测精度。 - 冗余代码:存在重复导入
mediapipe、numpy的情况,无意义且易混淆。
方案一:改用IMAGE模式(推荐,逻辑更简单)
这种模式下每帧同步检测,直接获取结果,无需回调,完全适配实时摄像头场景:
import mediapipe as mp import cv2 import numpy as np from mediapipe import solutions from mediapipe.framework.formats import landmark_pb2 # 配置参数 MARGIN = 10 # 像素 FONT_SIZE = 1 FONT_THICKNESS = 1 HANDEDNESS_TEXT_COLOR = (88, 205, 54) # 亮绿色 # 初始化MediaPipe组件 BaseOptions = mp.tasks.BaseOptions HandLandmarker = mp.tasks.vision.HandLandmarker HandLandmarkerOptions = mp.tasks.vision.HandLandmarkerOptions VisionRunningMode = mp.tasks.vision.RunningMode # 创建HandLandmarker实例(使用IMAGE同步模式) options = HandLandmarkerOptions( base_options=BaseOptions(model_asset_path='hand_landmarker.task'), running_mode=VisionRunningMode.IMAGE) with HandLandmarker.create_from_options(options) as landmarker: cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() if not ret: break # 将OpenCV的BGR格式转为MediaPipe所需的RGB格式 frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=frame_rgb) # 同步检测手部关键点 result = landmarker.detect(mp_image) # 绘制关键点及连接 if result.hand_landmarks: for idx, hand_landmarks in enumerate(result.hand_landmarks): # 转换为MediaPipe要求的NormalizedLandmarkList格式 hand_landmarks_proto = landmark_pb2.NormalizedLandmarkList() hand_landmarks_proto.landmark.extend([ landmark_pb2.NormalizedLandmark(x=lm.x, y=lm.y, z=lm.z) for lm in hand_landmarks ]) # 在原BGR帧上绘制(适配OpenCV显示格式) solutions.drawing_utils.draw_landmarks( frame, hand_landmarks_proto, solutions.hands.HAND_CONNECTIONS, solutions.drawing_styles.get_default_hand_landmarks_style(), solutions.drawing_styles.get_default_hand_connections_style()) # 可选:绘制左右手标签 handedness = result.handedness[idx][0].category_name cv2.putText(frame, handedness, (int(hand_landmarks[0].x * frame.shape[1]) - MARGIN, int(hand_landmarks[0].y * frame.shape[0]) - MARGIN), cv2.FONT_HERSHEY_SIMPLEX, FONT_SIZE, HANDEDNESS_TEXT_COLOR, FONT_THICKNESS) # 显示画面 cv2.imshow('Hand Landmarks', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
方案二:正确使用LIVE_STREAM模式(异步回调)
如果必须使用异步流模式,需通过线程安全队列传递结果(回调函数在后台线程执行,不能直接调用OpenCV窗口操作):
import mediapipe as mp import cv2 import time import numpy as np from mediapipe import solutions from mediapipe.framework.formats import landmark_pb2 from queue import Queue # 配置参数 MARGIN = 10 # 像素 FONT_SIZE = 1 FONT_THICKNESS = 1 HANDEDNESS_TEXT_COLOR = (88, 205, 54) # 亮绿色 # 线程安全队列,用于传递检测结果到主线程 result_queue = Queue(maxsize=1) # 回调函数:将结果存入队列 def store_result(result: mp.tasks.vision.HandLandmarkerResult, output_image: mp.Image, timestamp_ms: int): if not result_queue.full(): result_queue.put((result, output_image)) # 初始化MediaPipe组件 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.LIVE_STREAM, result_callback=store_result) with HandLandmarker.create_from_options(options) as landmarker: cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() if not ret: break # BGR转RGB frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=frame_rgb) timestamp = int(round(time.time() * 1000)) # 异步提交帧进行检测 landmarker.detect_async(mp_image, timestamp) # 从队列获取结果(非阻塞) if not result_queue.empty(): result, output_image = result_queue.get() if result.hand_landmarks: # 转回BGR格式用于显示 display_frame = cv2.cvtColor(output_image.numpy_view(), cv2.COLOR_RGB2BGR) for idx, hand_landmarks in enumerate(result.hand_landmarks): hand_landmarks_proto = landmark_pb2.NormalizedLandmarkList() hand_landmarks_proto.landmark.extend([ landmark_pb2.NormalizedLandmark(x=lm.x, y=lm.y, z=lm.z) for lm in hand_landmarks ]) solutions.drawing_utils.draw_landmarks( display_frame, hand_landmarks_proto, solutions.hands.HAND_CONNECTIONS, solutions.drawing_styles.get_default_hand_landmarks_style(), solutions.drawing_styles.get_default_hand_connections_style()) # 绘制左右手标签 handedness = result.handedness[idx][0].category_name cv2.putText(display_frame, handedness, (int(hand_landmarks[0].x * display_frame.shape[1]) - MARGIN, int(hand_landmarks[0].y * display_frame.shape[0]) - MARGIN), cv2.FONT_HERSHEY_SIMPLEX, FONT_SIZE, HANDEDNESS_TEXT_COLOR, FONT_THICKNESS) cv2.imshow('Hand Landmarks', display_frame) else: # 无结果时显示原帧 cv2.imshow('Hand Landmarks', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
注意事项
- 确保
hand_landmarker.task文件存在于当前工作目录,或提供完整文件路径。 - 方案一的同步模式逻辑简单易维护,适合大多数实时场景;方案二的异步模式适合高性能设备,处理更流畅。
- OpenCV窗口操作必须在主线程执行,LIVE_STREAM模式的回调属于后台线程,不能直接在回调中调用
cv2.imshow(),否则会导致程序崩溃或无响应。
内容的提问来源于stack exchange,提问作者RohitSanjay00
相关产品推荐
相关产品推荐

