如何在C++环境下将非摄像头视频流接入Mediapipe手部识别?
MediaPipe C++ 手部识别自定义输入输出流实现方案
官方C++端的计算图本身就支持手动传入输入流、读取输出流,不需要依赖默认的摄像头采集、屏幕渲染组件,仅需少量修改即可实现需求,以下是可直接落地的实现方案:
第一步:修改手部识别计算图配置
只需要修改官方默认的计算图pbtxt配置,删除绑定摄像头、屏幕输出的节点,保留核心识别逻辑,新增对外暴露的输入输出流声明即可:
- 删除默认配置里绑定摄像头的
PacketGenerator组件 - 删除默认配置里负责渲染到屏幕的
OpenCvPreviewSink组件 - 新增自定义输入输出流声明,中间所有手部识别核心节点的配置完全不需要调整
修改后的配置片段示例:
# 对外暴露的输入流,用于接收外部传入的图像 input_stream: "input_frames" # 对外暴露的输出流,用于输出识别结果,可按需添加多个 output_stream: "hand_landmarks" output_stream: "handedness" # 中间保留官方手部识别的所有核心节点配置,直接复用官方原有参数即可 node { calculator: "HandLandmarkCpu" input_stream: "IMAGE:input_frames" output_stream: "LANDMARKS:hand_landmarks" output_stream: "HANDEDNESS:handedness" options: { [mediapipe.HandLandmarkCpuOptions.ext] { num_hands: 2 min_detection_confidence: 0.5 } } }
第二步:C++ 核心代码实现
整体逻辑只有3个环节:初始化计算图、传入自定义输入帧、读取输出结果,示例代码如下:
1. 头文件引入
#include "mediapipe/framework/calculator_graph.h" #include "mediapipe/framework/formats/image_frame.h" #include "mediapipe/framework/formats/image_frame_opencv.h" #include "mediapipe/framework/formats/landmark.pb.h" #include "mediapipe/framework/port/opencv_highgui_inc.h" #include "mediapipe/framework/port/opencv_imgproc_inc.h" #include "mediapipe/framework/port/parse_text_proto.h"
2. 计算图初始化
// 加载修改完成的计算图配置 mediapipe::CalculatorGraphConfig config = mediapipe::ParseTextProtoOrDie<mediapipe::CalculatorGraphConfig>(R"( // 此处粘贴第一步修改完成的完整pbtxt配置 )"); mediapipe::CalculatorGraph graph; MP_ASSERT_OK(graph.Initialize(config)); // 存储输出的手部关键点结果 std::vector<mediapipe::NormalizedLandmarkList> hand_landmark_results; // 注册输出流观测回调,计算完成后会自动触发回调返回结果 MP_ASSERT_OK(graph.ObserveOutputStream("hand_landmarks", [&hand_landmark_results](const mediapipe::Packet& packet) -> mediapipe::Status { hand_landmark_results.push_back(packet.Get<mediapipe::NormalizedLandmarkList>()); return mediapipe::OkStatus(); })); // 启动计算图 MP_ASSERT_OK(graph.StartRun({}));
3. 手动传入自定义输入流
你可以从任意来源(视频文件、RTSP流、内存图像数据等)获取图像,转换为MediaPipe支持的格式传入即可,以下是传入OpenCV Mat格式帧的示例:
cv::Mat frame; // 此处可以替换为任意图像读取逻辑,比如读本地图片、拉流解码后的数据等 cv::VideoCapture cap("your_input_source.mp4"); // 时间戳必须严格单调递增,单位为微秒 int64_t timestamp = 0; while (cap.read(frame)) { // 转换OpenCV BGR格式为MediaPipe要求的RGB格式 auto input_frame = absl::make_unique<mediapipe::ImageFrame>( mediapipe::ImageFormat::SRGB, frame.cols, frame.rows, mediapipe::ImageFrame::kDefaultAlignmentBoundary); cv::cvtColor(frame, mediapipe::formats::MatView(input_frame.get()), cv::COLOR_BGR2RGB); // 将图像包传入计算图输入流 MP_ASSERT_OK(graph.AddPacketToInputStream( "input_frames", mediapipe::Adopt(input_frame.release()) .At(mediapipe::Timestamp(timestamp)) )); // 按实际帧率调整时间戳增量,30fps对应每帧间隔33000微秒 timestamp += 33000; }
4. 停止计算图并获取完整结果
// 关闭输入流,等待所有计算任务完成 MP_ASSERT_OK(graph.CloseInputStream("input_frames")); MP_ASSERT_OK(graph.WaitUntilDone()); // 此时hand_landmark_results中已经存储了所有输入帧的手部关键点结果,可自行处理
注意事项
- 输入流的时间戳必须严格单调递增,不能重复或回退,否则计算图会直接报错
- 如果需要同时获取多类输出(比如手势分类结果、手部掩码),只需要在计算图配置里新增对应的
output_stream,再分别注册观测回调即可 - 切换CPU/GPU运行模式仅需要替换Calculator的后缀(
HandLandmarkCpu改为HandLandmarkGpu),输入输出逻辑完全不需要修改
内容的提问来源于stack exchange,提问作者DirenD
相关产品推荐
相关产品推荐

