add multi pose estimation and multi person holistic tracking
This commit is contained in:
parent
f405c764b9
commit
b72fc70c01
|
@ -35,7 +35,7 @@ fn face_mesh() -> Result<()> {
|
||||||
highgui::imshow(window, &mut flip_frame)?;
|
highgui::imshow(window, &mut flip_frame)?;
|
||||||
|
|
||||||
if !result.is_empty() {
|
if !result.is_empty() {
|
||||||
let landmark = result[0][0];
|
let landmark = result[0].data[0];
|
||||||
println!("LANDMARK: {} {} {}", landmark.x, landmark.y, landmark.z);
|
println!("LANDMARK: {} {} {}", landmark.x, landmark.y, landmark.z);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -35,7 +35,7 @@ pub fn hand_tracking() -> Result<()> {
|
||||||
highgui::imshow(window, &mut flip_frame)?;
|
highgui::imshow(window, &mut flip_frame)?;
|
||||||
|
|
||||||
if !result.is_empty() {
|
if !result.is_empty() {
|
||||||
let landmark = result[0][0];
|
let landmark = result[0].data[0];
|
||||||
println!("LANDMARK: {} {} {}", landmark.x, landmark.y, landmark.z);
|
println!("LANDMARK: {} {} {}", landmark.x, landmark.y, landmark.z);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -31,12 +31,11 @@ fn face_mesh() -> Result<()> {
|
||||||
|
|
||||||
println!("processing");
|
println!("processing");
|
||||||
let result = detector.process(&flip_frame);
|
let result = detector.process(&flip_frame);
|
||||||
println!("received {} types of landmarks", result.len());
|
|
||||||
|
|
||||||
highgui::imshow(window, &mut flip_frame)?;
|
highgui::imshow(window, &mut flip_frame)?;
|
||||||
|
|
||||||
if !result[0].is_empty() {
|
if let Some(pose) = result.pose {
|
||||||
let landmark = result[0][0][0];
|
let landmark = pose.data[0];
|
||||||
println!("LANDMARK: {} {} {}", landmark.x, landmark.y, landmark.z);
|
println!("LANDMARK: {} {} {}", landmark.x, landmark.y, landmark.z);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
57
examples/multi_person_holistic_tracking.rs
Normal file
57
examples/multi_person_holistic_tracking.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use mediapipe::*;
|
||||||
|
use opencv::prelude::*;
|
||||||
|
use opencv::{highgui, imgproc, videoio, Result};
|
||||||
|
|
||||||
|
fn face_mesh() -> Result<()> {
|
||||||
|
let window = "video capture";
|
||||||
|
|
||||||
|
highgui::named_window(window, highgui::WINDOW_AUTOSIZE)?;
|
||||||
|
|
||||||
|
let mut cap = videoio::VideoCapture::new(0, videoio::CAP_ANY)?;
|
||||||
|
if !cap.is_opened()? {
|
||||||
|
panic!("Unable to open default cam")
|
||||||
|
}
|
||||||
|
|
||||||
|
cap.set(videoio::CAP_PROP_FRAME_WIDTH, 640.0)?;
|
||||||
|
cap.set(videoio::CAP_PROP_FRAME_HEIGHT, 480.0)?;
|
||||||
|
cap.set(videoio::CAP_PROP_FPS, 30.0)?;
|
||||||
|
|
||||||
|
let mut detector = holistic::MultiPersonHolisticDetector::default();
|
||||||
|
|
||||||
|
let mut raw_frame = Mat::default();
|
||||||
|
let mut rgb_frame = Mat::default();
|
||||||
|
let mut flip_frame = Mat::default();
|
||||||
|
loop {
|
||||||
|
cap.read(&mut raw_frame)?;
|
||||||
|
|
||||||
|
let size = raw_frame.size()?;
|
||||||
|
if size.width > 0 && !raw_frame.empty() {
|
||||||
|
imgproc::cvt_color(&raw_frame, &mut rgb_frame, imgproc::COLOR_BGR2RGB, 0)?;
|
||||||
|
opencv::core::flip(&rgb_frame, &mut flip_frame, 1)?; // horizontal
|
||||||
|
|
||||||
|
println!("processing");
|
||||||
|
let result = detector.process(&flip_frame);
|
||||||
|
|
||||||
|
highgui::imshow(window, &mut flip_frame)?;
|
||||||
|
|
||||||
|
if !result.is_empty() {
|
||||||
|
if let Some(pose) = &result[0].pose {
|
||||||
|
let landmark = pose.data[0];
|
||||||
|
println!("LANDMARK: {} {} {}", landmark.x, landmark.y, landmark.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("WARN: Skip empty frame");
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = highgui::wait_key(10)?;
|
||||||
|
if key > 0 && key != 255 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
face_mesh().unwrap()
|
||||||
|
}
|
55
examples/multi_pose_estimation.rs
Normal file
55
examples/multi_pose_estimation.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use mediapipe::*;
|
||||||
|
use opencv::prelude::*;
|
||||||
|
use opencv::{highgui, imgproc, videoio, Result};
|
||||||
|
|
||||||
|
pub fn pose_estimation() -> Result<()> {
|
||||||
|
let window = "video capture";
|
||||||
|
|
||||||
|
highgui::named_window(window, highgui::WINDOW_AUTOSIZE)?;
|
||||||
|
|
||||||
|
let mut cap = videoio::VideoCapture::new(0, videoio::CAP_ANY)?;
|
||||||
|
if !cap.is_opened()? {
|
||||||
|
panic!("Unable to open default cam")
|
||||||
|
}
|
||||||
|
|
||||||
|
cap.set(videoio::CAP_PROP_FRAME_WIDTH, 640.0)?;
|
||||||
|
cap.set(videoio::CAP_PROP_FRAME_HEIGHT, 480.0)?;
|
||||||
|
cap.set(videoio::CAP_PROP_FPS, 30.0)?;
|
||||||
|
|
||||||
|
let mut detector = pose::MultiPoseDetector::default();
|
||||||
|
|
||||||
|
let mut raw_frame = Mat::default();
|
||||||
|
let mut rgb_frame = Mat::default();
|
||||||
|
let mut flip_frame = Mat::default();
|
||||||
|
loop {
|
||||||
|
cap.read(&mut raw_frame)?;
|
||||||
|
|
||||||
|
let size = raw_frame.size()?;
|
||||||
|
if size.width > 0 && !raw_frame.empty() {
|
||||||
|
imgproc::cvt_color(&raw_frame, &mut rgb_frame, imgproc::COLOR_BGR2RGB, 0)?;
|
||||||
|
opencv::core::flip(&rgb_frame, &mut flip_frame, 1)?; // horizontal
|
||||||
|
|
||||||
|
println!("processing");
|
||||||
|
let result = detector.process(&rgb_frame);
|
||||||
|
|
||||||
|
highgui::imshow(window, &mut rgb_frame)?;
|
||||||
|
|
||||||
|
if !result.is_empty() {
|
||||||
|
let landmark = result[0].data[0];
|
||||||
|
println!("LANDMARK: {} {} {}", landmark.x, landmark.y, landmark.z);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("WARN: Skip empty frame");
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = highgui::wait_key(10)?;
|
||||||
|
if key > 0 && key != 255 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
pose_estimation().unwrap()
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ impl FaceMeshDetector {
|
||||||
let graph = Detector::new(
|
let graph = Detector::new(
|
||||||
include_str!("graphs/face_mesh_desktop_live.pbtxt"),
|
include_str!("graphs/face_mesh_desktop_live.pbtxt"),
|
||||||
vec![Output {
|
vec![Output {
|
||||||
type_: FeatureType::Face,
|
type_: FeatureType::Faces,
|
||||||
name: "multi_face_landmarks".into(),
|
name: "multi_face_landmarks".into(),
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
|
@ -19,9 +19,17 @@ impl FaceMeshDetector {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the input frame, returns a face mesh if detected.
|
/// Processes the input frame, returns a face mesh if detected.
|
||||||
pub fn process(&mut self, input: &Mat) -> Vec<Vec<Landmark>> {
|
pub fn process(&mut self, input: &Mat) -> Vec<FaceMesh> {
|
||||||
let landmarks = self.graph.process(input);
|
let landmarks = self.graph.process(input);
|
||||||
landmarks[0].clone()
|
let mut faces = vec![];
|
||||||
|
|
||||||
|
for face_landmarks in landmarks[0].iter() {
|
||||||
|
let mut face = FaceMesh::default();
|
||||||
|
face.data.copy_from_slice(&face_landmarks[..]);
|
||||||
|
faces.push(face);
|
||||||
|
}
|
||||||
|
|
||||||
|
faces
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
55
src/graphs/multi_person_holistic_tracking_cpu.pbtxt
Normal file
55
src/graphs/multi_person_holistic_tracking_cpu.pbtxt
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# Tracks pose + hands + face landmarks.
|
||||||
|
|
||||||
|
# CPU image. (ImageFrame)
|
||||||
|
input_stream: "input_video"
|
||||||
|
|
||||||
|
output_stream: "multi_pose_landmarks"
|
||||||
|
|
||||||
|
output_stream: "pose_rois"
|
||||||
|
|
||||||
|
output_stream: "pose_detections"
|
||||||
|
|
||||||
|
output_stream: "multi_left_hand_landmarks"
|
||||||
|
|
||||||
|
output_stream: "multi_right_hand_landmarks"
|
||||||
|
|
||||||
|
# Throttles the images flowing downstream for flow control. It passes through
|
||||||
|
# the very first incoming image unaltered, and waits for downstream nodes
|
||||||
|
# (calculators and subgraphs) in the graph to finish their tasks before it
|
||||||
|
# passes through another image. All images that come in while waiting are
|
||||||
|
# dropped, limiting the number of in-flight images in most part of the graph to
|
||||||
|
# 1. This prevents the downstream nodes from queuing up incoming images and data
|
||||||
|
# excessively, which leads to increased latency and memory usage, unwanted in
|
||||||
|
# real-time mobile applications. It also eliminates unnecessarily computation,
|
||||||
|
# e.g., the output produced by a node may get dropped downstream if the
|
||||||
|
# subsequent nodes are still busy processing previous inputs.
|
||||||
|
node {
|
||||||
|
calculator: "FlowLimiterCalculator"
|
||||||
|
input_stream: "input_video"
|
||||||
|
input_stream: "FINISHED:output_video"
|
||||||
|
input_stream_info: {
|
||||||
|
tag_index: "FINISHED"
|
||||||
|
back_edge: true
|
||||||
|
}
|
||||||
|
output_stream: "throttled_input_video"
|
||||||
|
node_options: {
|
||||||
|
[type.googleapis.com/mediapipe.FlowLimiterCalculatorOptions] {
|
||||||
|
max_in_flight: 1
|
||||||
|
max_in_queue: 1
|
||||||
|
# Timeout is disabled (set to 0) as first frame processing can take more
|
||||||
|
# than 1 second.
|
||||||
|
in_flight_timeout: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node {
|
||||||
|
calculator: "MultiPersonHolisticLandmarkCpu"
|
||||||
|
input_stream: "IMAGE:throttled_input_video"
|
||||||
|
output_stream: "POSE_LANDMARKS:multi_pose_landmarks"
|
||||||
|
output_stream: "POSE_ROI:pose_rois"
|
||||||
|
output_stream: "POSE_DETECTION:pose_detections"
|
||||||
|
output_stream: "FACE_LANDMARKS:multi_face_landmarks"
|
||||||
|
output_stream: "LEFT_HAND_LANDMARKS:multi_left_hand_landmarks"
|
||||||
|
output_stream: "RIGHT_HAND_LANDMARKS:multi_right_hand_landmarks"
|
||||||
|
}
|
53
src/graphs/multi_person_pose_tracking_cpu.pbtxt
Normal file
53
src/graphs/multi_person_pose_tracking_cpu.pbtxt
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# MediaPipe graph that performs pose tracking with TensorFlow Lite on CPU.
|
||||||
|
|
||||||
|
# CPU buffer. (ImageFrame)
|
||||||
|
input_stream: "input_video"
|
||||||
|
|
||||||
|
# Output image with rendered results. (ImageFrame)
|
||||||
|
output_stream: "multi_pose_landmarks"
|
||||||
|
|
||||||
|
output_stream: "pose_detections"
|
||||||
|
|
||||||
|
output_stream: "roi_from_landmarks"
|
||||||
|
|
||||||
|
# Generates side packet to enable segmentation.
|
||||||
|
node {
|
||||||
|
calculator: "ConstantSidePacketCalculator"
|
||||||
|
output_side_packet: "PACKET:enable_segmentation"
|
||||||
|
node_options: {
|
||||||
|
[type.googleapis.com/mediapipe.ConstantSidePacketCalculatorOptions]: {
|
||||||
|
packet { bool_value: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Throttles the images flowing downstream for flow control. It passes through
|
||||||
|
# the very first incoming image unaltered, and waits for downstream nodes
|
||||||
|
# (calculators and subgraphs) in the graph to finish their tasks before it
|
||||||
|
# passes through another image. All images that come in while waiting are
|
||||||
|
# dropped, limiting the number of in-flight images in most part of the graph to
|
||||||
|
# 1. This prevents the downstream nodes from queuing up incoming images and data
|
||||||
|
# excessively, which leads to increased latency and memory usage, unwanted in
|
||||||
|
# real-time mobile applications. It also eliminates unnecessarily computation,
|
||||||
|
# e.g., the output produced by a node may get dropped downstream if the
|
||||||
|
# subsequent nodes are still busy processing previous inputs.
|
||||||
|
node {
|
||||||
|
calculator: "FlowLimiterCalculator"
|
||||||
|
input_stream: "input_video"
|
||||||
|
input_stream: "FINISHED:output_video"
|
||||||
|
input_stream_info: {
|
||||||
|
tag_index: "FINISHED"
|
||||||
|
back_edge: true
|
||||||
|
}
|
||||||
|
output_stream: "throttled_input_video"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Subgraph that detects poses and corresponding landmarks.
|
||||||
|
node {
|
||||||
|
calculator: "MultiPoseLandmarkCpu"
|
||||||
|
input_side_packet: "ENABLE_SEGMENTATION:enable_segmentation"
|
||||||
|
input_stream: "IMAGE:throttled_input_video"
|
||||||
|
output_stream: "LANDMARKS:multi_pose_landmarks"
|
||||||
|
output_stream: "DETECTION:pose_detections"
|
||||||
|
output_stream: "ROI_FROM_LANDMARKS:roi_from_landmarks"
|
||||||
|
}
|
14
src/hands.rs
14
src/hands.rs
|
@ -1,6 +1,8 @@
|
||||||
//! Hand detection utilities.
|
//! Hand detection utilities.
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
pub const NUM_HAND_LANDMARKS: usize = 21;
|
||||||
|
|
||||||
/// Hand landmark indices.
|
/// Hand landmark indices.
|
||||||
pub enum HandLandmark {
|
pub enum HandLandmark {
|
||||||
WRIST = 0,
|
WRIST = 0,
|
||||||
|
@ -44,9 +46,17 @@ impl HandDetector {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the input frame, returns a list of hands
|
/// Processes the input frame, returns a list of hands
|
||||||
pub fn process(&mut self, input: &Mat) -> Vec<Vec<Landmark>> {
|
pub fn process(&mut self, input: &Mat) -> Vec<Hand> {
|
||||||
let result = self.graph.process(input);
|
let result = self.graph.process(input);
|
||||||
result[0].clone()
|
let mut hands = vec![];
|
||||||
|
|
||||||
|
for hand_landmarks in result[0].iter() {
|
||||||
|
let mut hand = Hand::default();
|
||||||
|
hand.data.copy_from_slice(&hand_landmarks[..]);
|
||||||
|
hands.push(hand);
|
||||||
|
}
|
||||||
|
|
||||||
|
hands
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
140
src/holistic.rs
140
src/holistic.rs
|
@ -5,6 +5,14 @@ pub struct HolisticDetector {
|
||||||
graph: Detector,
|
graph: Detector,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct HolisticDetection {
|
||||||
|
pub pose: Option<Pose>,
|
||||||
|
pub face: Option<FaceMesh>,
|
||||||
|
pub left_hand: Option<Hand>,
|
||||||
|
pub right_hand: Option<Hand>,
|
||||||
|
}
|
||||||
|
|
||||||
impl HolisticDetector {
|
impl HolisticDetector {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let outputs = vec![
|
let outputs = vec![
|
||||||
|
@ -32,9 +40,44 @@ impl HolisticDetector {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the input frame, returns landmarks if detected
|
/// Processes the input frame, returns landmarks if detected
|
||||||
pub fn process(&mut self, input: &Mat) -> Vec<Vec<Vec<Landmark>>> {
|
pub fn process(&mut self, input: &Mat) -> HolisticDetection {
|
||||||
let landmarks = self.graph.process(input);
|
let landmarks = self.graph.process(input);
|
||||||
landmarks.clone()
|
|
||||||
|
let mut pose = None;
|
||||||
|
let mut face = None;
|
||||||
|
let mut left_hand = None;
|
||||||
|
let mut right_hand = None;
|
||||||
|
|
||||||
|
if !landmarks[0].is_empty() {
|
||||||
|
let mut p = Pose::default();
|
||||||
|
p.data.copy_from_slice(&landmarks[0][0][..]);
|
||||||
|
pose = Some(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !landmarks[1].is_empty() {
|
||||||
|
let mut f = FaceMesh::default();
|
||||||
|
f.data.copy_from_slice(&landmarks[1][0][..]);
|
||||||
|
face = Some(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !landmarks[2].is_empty() {
|
||||||
|
let mut l = Hand::default();
|
||||||
|
l.data.copy_from_slice(&landmarks[2][0][..]);
|
||||||
|
left_hand = Some(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !landmarks[3].is_empty() {
|
||||||
|
let mut r = Hand::default();
|
||||||
|
r.data.copy_from_slice(&landmarks[3][0][..]);
|
||||||
|
right_hand = Some(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
HolisticDetection {
|
||||||
|
pose,
|
||||||
|
face,
|
||||||
|
left_hand,
|
||||||
|
right_hand,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,3 +86,96 @@ impl Default for HolisticDetector {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct MultiPersonHolisticDetector {
|
||||||
|
graph: Detector,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MultiPersonHolisticDetector {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let outputs = vec![
|
||||||
|
Output {
|
||||||
|
type_: FeatureType::Poses,
|
||||||
|
name: "multi_pose_landmarks".into(),
|
||||||
|
},
|
||||||
|
Output {
|
||||||
|
type_: FeatureType::Faces,
|
||||||
|
name: "multi_face_landmarks".into(),
|
||||||
|
},
|
||||||
|
Output {
|
||||||
|
type_: FeatureType::Hands,
|
||||||
|
name: "multi_left_hand_landmarks".into(),
|
||||||
|
},
|
||||||
|
Output {
|
||||||
|
type_: FeatureType::Hands,
|
||||||
|
name: "multi_right_hand_landmarks".into(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let graph = Detector::new(
|
||||||
|
include_str!("graphs/multi_person_holistic_tracking_cpu.pbtxt"),
|
||||||
|
outputs,
|
||||||
|
);
|
||||||
|
|
||||||
|
Self { graph }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Processes the input frame, returns landmarks if detected
|
||||||
|
pub fn process(&mut self, input: &Mat) -> Vec<HolisticDetection> {
|
||||||
|
let landmarks = self.graph.process(input);
|
||||||
|
|
||||||
|
let max_landmarks = landmarks
|
||||||
|
.iter()
|
||||||
|
.map(|l| l.len())
|
||||||
|
.reduce(|acc, item| acc.max(item))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut detections = vec![];
|
||||||
|
|
||||||
|
for i in 0..max_landmarks {
|
||||||
|
let mut pose = None;
|
||||||
|
let mut face = None;
|
||||||
|
let mut left_hand = None;
|
||||||
|
let mut right_hand = None;
|
||||||
|
|
||||||
|
if landmarks[0].len() > i {
|
||||||
|
let mut p = Pose::default();
|
||||||
|
p.data.copy_from_slice(&landmarks[0][i][..]);
|
||||||
|
pose = Some(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if landmarks[1].len() > i {
|
||||||
|
let mut f = FaceMesh::default();
|
||||||
|
f.data.copy_from_slice(&landmarks[1][i][..]);
|
||||||
|
face = Some(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if landmarks[2].len() > i {
|
||||||
|
let mut l = Hand::default();
|
||||||
|
l.data.copy_from_slice(&landmarks[2][i][..]);
|
||||||
|
left_hand = Some(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
if landmarks[3].len() > i {
|
||||||
|
let mut r = Hand::default();
|
||||||
|
r.data.copy_from_slice(&landmarks[3][i][..]);
|
||||||
|
right_hand = Some(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
detections.push(HolisticDetection {
|
||||||
|
pose,
|
||||||
|
face,
|
||||||
|
left_hand,
|
||||||
|
right_hand,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
detections
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MultiPersonHolisticDetector {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ impl FeatureType {
|
||||||
FeatureType::Face => 478,
|
FeatureType::Face => 478,
|
||||||
FeatureType::Faces => 478,
|
FeatureType::Faces => 478,
|
||||||
FeatureType::Hand => 21,
|
FeatureType::Hand => 21,
|
||||||
FeatureType::Hands => 42,
|
FeatureType::Hands => 21,
|
||||||
FeatureType::Pose => 33,
|
FeatureType::Pose => 33,
|
||||||
FeatureType::Poses => 33,
|
FeatureType::Poses => 33,
|
||||||
}
|
}
|
||||||
|
@ -100,6 +100,7 @@ impl Default for Landmark {
|
||||||
|
|
||||||
/// Represents a detected pose, as 33 landmarks.
|
/// Represents a detected pose, as 33 landmarks.
|
||||||
/// Landmark names are in [pose::PoseLandmark].
|
/// Landmark names are in [pose::PoseLandmark].
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct Pose {
|
pub struct Pose {
|
||||||
pub data: [Landmark; 33],
|
pub data: [Landmark; 33],
|
||||||
}
|
}
|
||||||
|
@ -114,12 +115,13 @@ impl Default for Pose {
|
||||||
|
|
||||||
/// Represents a detected hand, as 21 landmarks.
|
/// Represents a detected hand, as 21 landmarks.
|
||||||
/// Landmark names are in [hands::HandLandmark]
|
/// Landmark names are in [hands::HandLandmark]
|
||||||
#[derive(Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Hand {
|
pub struct Hand {
|
||||||
pub data: [Landmark; 21],
|
pub data: [Landmark; 21],
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a detected face mesh, as 478 landmarks.
|
/// Represents a detected face mesh, as 478 landmarks.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct FaceMesh {
|
pub struct FaceMesh {
|
||||||
pub data: [Landmark; 478],
|
pub data: [Landmark; 478],
|
||||||
}
|
}
|
||||||
|
|
40
src/pose.rs
40
src/pose.rs
|
@ -1,6 +1,8 @@
|
||||||
//! Pose detection utilities.
|
//! Pose detection utilities.
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
pub const NUM_POSE_LANDMARKS: usize = 33;
|
||||||
|
|
||||||
/// Pose landmark indices.
|
/// Pose landmark indices.
|
||||||
pub enum PoseLandmark {
|
pub enum PoseLandmark {
|
||||||
NOSE = 0,
|
NOSE = 0,
|
||||||
|
@ -76,3 +78,41 @@ impl Default for PoseDetector {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct MultiPoseDetector {
|
||||||
|
graph: Detector,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MultiPoseDetector {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let graph = Detector::new(
|
||||||
|
include_str!("graphs/multi_person_pose_tracking_cpu.pbtxt"),
|
||||||
|
vec![Output {
|
||||||
|
type_: FeatureType::Poses,
|
||||||
|
name: "multi_pose_landmarks".into(),
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
|
||||||
|
Self { graph }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Processes the input frame, returns poses if detected.
|
||||||
|
pub fn process(&mut self, input: &Mat) -> Vec<Pose> {
|
||||||
|
let result = self.graph.process(input);
|
||||||
|
let mut poses = vec![];
|
||||||
|
|
||||||
|
for pose_landmarks in result[0].iter() {
|
||||||
|
let mut pose = Pose::default();
|
||||||
|
pose.data.copy_from_slice(&pose_landmarks[..]);
|
||||||
|
poses.push(pose);
|
||||||
|
}
|
||||||
|
|
||||||
|
poses
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MultiPoseDetector {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user