OverView
感知手的形状和运动的能力可能是改善跨各种技术领域和平台的用户体验的重要组成部分。例如,它可以构成手语理解和手势控制的基础,还可以在增强现实中将数字内容和信息叠加在物理世界之上。虽然对人们来说很自然,但强大的实时手部感知是一项极具挑战性的计算机视觉任务,因为手经常会遮挡自己或彼此(例如手指/手掌遮挡和握手)并且缺乏高对比度模式。
MediaPipe Hands 是一种高保真手和手指跟踪解决方案。它使用机器学习 (ML) 从单帧中推断出一只手的 21 个 3D 地标。尽管当前最先进的方法主要依赖于强大的桌面环境进行推理,但我们的方法在手机上实现了实时性能,甚至可以扩展到多手。我们希望向更广泛的研究和开发社区提供这种手部感知功能将导致创造性用例的出现,激发新的应用程序和新的研究途径。
实现过程
-
环境配置参考
-
编译HandTracking
- iris_tracking_cpu
bazel build -c opt --define MEDIAPIPE_DISABLE_GPU=1 --action_env PYTHON_BIN_PATH="C://xxx//python.exe" mediapipe/examples/desktop/hand_tracking:hand_tracking_cpu --verbose_failures
bazel-bin\mediapipe\examples\desktop\hand_tracking\hand_tracking_cpu --calculator_graph_config_file=mediapipe\graphs\hand_tracking\hand_tracking_desktop_live.pbtxt
- 程序运行后,会开启摄像头,则会看到手势识别的效果。
- iris_tracking_cpu
-
Demo ( 编写代码,获取手势结果等信息)
-
修改mediapipe\examples\desktop\hand_tracking 中BUILD文件,增加 hand_tracking_sample.(后续使用bazel编译程序用)
-
cc_binary( name = "hand_tracking_sample", srcs = ["main.cpp","hand_detect.h","hand_detect.cpp"], deps = [ "//mediapipe/graphs/hand_tracking:desktop_tflite_calculators", ], )
-
接下来写测试代码,由于代码太多了,我就不全贴出来,下来讲下我是如何获取手的相关信息。
-
获取手部21个坐标点
- mediapipe主要通过outputstream来获取相关信息,那么我们只要在model中查找相应流的名称即可。比如 手部的坐标点, 则为 landmarks。
-
// 创建OutputStreamPoller, 我理解相当一个轮训器 // 手部21个特征点 const char* kOutputLandmarks = "landmarks"; // 手 mediapipe::StatusOrPoller handLandmark = graph_.AddOutputStreamPoller(kOutputLandmarks); assert(handLandmark.ok()); pPollerLandmarks_ = std::make_unique<mediapipe::OutputStreamPoller>(std::move(handLandmark.value()));
-
void HandDectect::drawHandLandmarks(cv::Mat& image) { mediapipe::Packet packet_landmarks; if (pPollerLandmarks_->QueueSize() == 0 || !pPollerLandmarks_->Next(&packet_landmarks)) return; auto& output_landmarks = packet_landmarks.Get<std::vector<mediapipe::NormalizedLandmarkList>>(); for (int i = 0; i < output_landmarks.size(); ++i) { mediapipe::NormalizedLandmarkList landmarkList = output_landmarks[i]; for (int j = 0; j < landmarkList.landmark_size(); ++j) { const mediapipe::NormalizedLandmark landmark = landmarkList.landmark(j); float x = landmark.x() * image.cols; float y = landmark.y() * image.rows; cv::circle(image, cv::Point(x, y), 2, cv::Scalar(0, 255, 0)); } } }
-
获取手部特性 (左手、右手 + 置信度)
- 创建轮训器
-
mediapipe::StatusOrPoller handedness = graph_.AddOutputStreamPoller(kOutputHandedness); assert(handedness.ok()); pPollerHandedness_ = std::make_unique<mediapipe::OutputStreamPoller>(std::move(handedness.value()));
- 获取手信息
-
void HandDectect::drawHandeness(cv::Mat& image) { mediapipe::Packet packet_landmarks; if (pPollerHandedness_->QueueSize() == 0 || !pPollerHandedness_->Next(&packet_landmarks)) return; // 多少只手 (最多检出手得个数 model 可配置) auto& output_handeness = packet_landmarks.Get<std::vector<mediapipe::ClassificationList>>(); std::cout << "handeness size: " << output_handeness.size() << std::endl; for (int i = 0; i < output_handeness.size(); ++i) { mediapipe::ClassificationList c = output_handeness[i]; if (c.classification_size() > 0) { const mediapipe::Classification handeness = c.classification(0); if (handeness.has_index()) { int idx = handeness.index(); // 0:左手 1:右手 std::cout << "--> index: " << idx << std::endl; } if (handeness.has_label()) { const std::string label = handeness.label(); // Left or Right std::cout << "---> label: " << label.c_str() << std::endl; } if (handeness.has_score()) { float score = handeness.score(); std::cout << "--> score: " << score << std::endl; } } } }
-
获取手掌信息 (手掌7个坐标点 + 手掌边界框)
- 轮训器
-
const char* kOutputMultiPalmDetections = "multi_palm_detections";// 手掌 // 检测手掌 + 手掌特征点 mediapipe::StatusOrPoller multiPalmDetections = graph_.AddOutputStreamPoller(kOutputMultiPalmDetections); assert(multiPalmDetections.ok()); pPollerMultiPalmDetections_ = std::make_unique<mediapipe::OutputStreamPoller> (std::move(multiPalmDetections.value()));
- 手掌信息 + 外接框
-
void HandDectect::drawMultiPalmDetections(cv::Mat& image) { mediapipe::Packet packet_landmarks; if (pPollerMultiPalmDetections_->QueueSize() == 0 || !pPollerMultiPalmDetections_->Next(&packet_landmarks)) return; auto& output_palms = packet_landmarks.Get<std::vector<mediapipe::Detection>>(); for (size_t i = 0; i < output_palms.size(); i++) { // 手掌边界框 mediapipe::Rect rect; if (detectionToRect(output_palms[i], image.cols, image.rows, &rect) > 0) cv::rectangle(image, cv::Rect((rect.x_center() - rect.width() / 2), (rect.y_center() - rect.height() / 2), rect.width(), rect.height()), cv::Scalar(255, 0, 0), 1, cv::LINE_8, 0); // 手掌特征点 mediapipe::NormalizedLandmarkList landmarks; if (convertDetectionToLandmarks(output_palms[i], &landmarks) > 0) { for (int j = 0; j < landmarks.landmark_size(); ++j) { const mediapipe::NormalizedLandmark landmark = landmarks.landmark(j); float x = landmark.x() * image.cols; float y = landmark.y() * image.rows; cv::circle(image, cv::Point(x, y), 2, cv::Scalar(0, 255, 0)); } } } }
-
获取手的边界框
- 轮训器
-
const char* kOutputMultiHandRects = "multi_hand_rects"; // 手边界 mediapipe::StatusOrPoller multiHandRects = graph_.AddOutputStreamPoller(kOutputMultiHandRects); assert(multiHandRects.ok()); pPollerMultiHandRects_ = std::make_unique<mediapipe::OutputStreamPoller>(std::move(multiHandRects.value()));
- 获取边界框
-
void HandDectect::drawMultiHandRects(cv::Mat& image) { mediapipe::Packet packet_landmarks; if (pPollerMultiHandRects_->QueueSize() == 0 || !pPollerMultiHandRects_->Next(&packet_landmarks)) return; auto& output_hands = packet_landmarks.Get<std::vector<mediapipe::NormalizedRect>>(); for (size_t i = 0; i < output_hands.size(); i++) { mediapipe::NormalizedRect rect = output_hands[i]; float x = rect.x_center() * image.cols; float y = rect.y_center() * image.rows; int w = rect.width() * image.cols; int h = rect.height() * image.rows; cv::rectangle(image, cv::Rect((x - w / 2), (y - h / 2), w, h), cv::Scalar(255, 0, 0), 1, cv::LINE_8, 0); } }
-
完整过程可参考前几篇文章,基本逻辑都差不多。
-
最大检出手的个数可以通过 “hand_tracking_desktop_live.pbtxt” 配置。
-
node { calculator: "ConstantSidePacketCalculator" output_side_packet: "PACKET:num_hands" node_options: { [type.googleapis.com/mediapipe.ConstantSidePacketCalculatorOptions]: { packet { int_value: 4 } // 最大检出手的个数 } } }
-
-
编译
bazel build -c opt --define MEDIAPIPE_DISABLE_GPU=1 --action_env PYTHON_BIN_PATH=“C://xxx//python.exe” mediapipe/examples/desktop/hand_tracking:hand_tracking_sample --verbose_failures
bazel-bin\mediapipe\examples\desktop\hand_tracking\hand_tracking_sample "mediapipe\graphs\hand_tracking\hand_tracking_desktop_live.pbtxt"
-
MediaPipe 其他实现
- IRIS: https://blog.csdn.net/haiyangyunbao813/article/details/122225445?spm=1001.2014.3001.5502
- Pose:https://blog.csdn.net/haiyangyunbao813/article/details/119192951?spm=1001.2014.3001.5502
- 其他待续 …
Demo地址
- HandsTrackingDemo
- 不差积分的童鞋欢迎下载,积分不够的童鞋下面留言。
END
- 以上基本是我实现关于HandsTracking的全部过程了,方便记忆,在此记录下 ,然后有不对的地方欢迎指正。
- 文章纯手工输出,感觉有用得小伙伴,点个赞啥的。