Project import generated by Copybara.
GitOrigin-RevId: 9295f8ea2339edb71073695ed4fb3fded2f48c60
This commit is contained in:
@@ -9,4 +9,5 @@ Each module (represented as a subfolder) provides subgraphs and corresponding re
|
||||
| [`face_detection`](face_detection/README.md) | Subgraphs to detect faces. |
|
||||
| [`face_landmark`](face_landmark/README.md) | Subgraphs to detect and track face landmarks. |
|
||||
| [`iris_landmark`](iris_landmark/README.md) | Subgraphs to detect iris landmarks. |
|
||||
|
||||
| [`pose_detection`](pose_detection/README.md) | Subgraphs to detect poses. |
|
||||
| [`pose_landmark`](pose_landmark/README.md) | Subgraphs to detect and track pose landmarks. |
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# Copyright 2019 The MediaPipe Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
load(
|
||||
"//mediapipe/framework/tool:mediapipe_graph.bzl",
|
||||
"mediapipe_simple_subgraph",
|
||||
)
|
||||
|
||||
licenses(["notice"]) # Apache 2.0
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
mediapipe_simple_subgraph(
|
||||
name = "pose_detection_cpu",
|
||||
graph = "pose_detection_cpu.pbtxt",
|
||||
register_as = "PoseDetectionCpu",
|
||||
deps = [
|
||||
"//mediapipe/calculators/image:image_transformation_calculator",
|
||||
"//mediapipe/calculators/tflite:ssd_anchors_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_converter_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_inference_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_tensors_to_detections_calculator",
|
||||
"//mediapipe/calculators/util:detection_letterbox_removal_calculator",
|
||||
"//mediapipe/calculators/util:non_max_suppression_calculator",
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_simple_subgraph(
|
||||
name = "pose_detection_gpu",
|
||||
graph = "pose_detection_gpu.pbtxt",
|
||||
register_as = "PoseDetectionGpu",
|
||||
deps = [
|
||||
"//mediapipe/calculators/image:image_transformation_calculator",
|
||||
"//mediapipe/calculators/tflite:ssd_anchors_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_converter_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_inference_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_tensors_to_detections_calculator",
|
||||
"//mediapipe/calculators/util:detection_letterbox_removal_calculator",
|
||||
"//mediapipe/calculators/util:non_max_suppression_calculator",
|
||||
],
|
||||
)
|
||||
|
||||
exports_files(
|
||||
srcs = [
|
||||
"pose_detection.tflite",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
# pose_detection
|
||||
|
||||
Subgraphs|Details
|
||||
:--- | :---
|
||||
[`PoseDetectionCpu`](https://github.com/google/mediapipe/tree/master/mediapipe/modules/pose_detection/pose_detection_cpu.pbtxt)| Detects poses. (CPU input, and inference is executed on CPU.)
|
||||
[`PoseDetectionGpu`](https://github.com/google/mediapipe/tree/master/mediapipe/modules/pose_detection/pose_detection_gpu.pbtxt)| Detects poses. (GPU input, and inference is executed on GPU.)
|
||||
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,155 @@
|
||||
# MediaPipe graph to detect poses. (CPU input, and inference is executed on
|
||||
# CPU.)
|
||||
#
|
||||
# It is required that "pose_detection.tflite" is available at
|
||||
# "mediapipe/modules/pose_detection/pose_detection.tflite"
|
||||
# path during execution.
|
||||
#
|
||||
# EXAMPLE:
|
||||
# node {
|
||||
# calculator: "PoseDetectionCpu"
|
||||
# input_stream: "IMAGE:image"
|
||||
# output_stream: "DETECTIONS:pose_detections"
|
||||
# }
|
||||
|
||||
type: "PoseDetectionCpu"
|
||||
|
||||
# CPU image. (ImageFrame)
|
||||
input_stream: "IMAGE:image"
|
||||
|
||||
# Detected poses. (std::vector<Detection>)
|
||||
# Bounding box in each pose detection is currently set to the bounding box of
|
||||
# the detected face. However, 4 additional key points are available in each
|
||||
# detection, which are used to further calculate a (rotated) bounding box that
|
||||
# encloses the body region of interest. Among the 4 key points, the first two
|
||||
# are for identifying the full-body region, and the second two for upper body
|
||||
# only:
|
||||
#
|
||||
# Key point 0 - mid hip center
|
||||
# Key point 1 - point that encodes size & rotation (for full body)
|
||||
# Key point 2 - mid shoulder center
|
||||
# Key point 3 - point that encodes size & rotation (for upper body)
|
||||
#
|
||||
# NOTE: there will not be an output packet in the DETECTIONS stream for this
|
||||
# particular timestamp if none of poses detected. However, the MediaPipe
|
||||
# framework will internally inform the downstream calculators of the absence of
|
||||
# this packet so that they don't wait for it unnecessarily.
|
||||
output_stream: "DETECTIONS:detections"
|
||||
|
||||
# Transforms the input image on CPU to a 128x128 image. To scale the input
|
||||
# image, the scale_mode option is set to FIT to preserve the aspect ratio,
|
||||
# resulting in potential letterboxing in the transformed image.
|
||||
node: {
|
||||
calculator: "ImageTransformationCalculator"
|
||||
input_stream: "IMAGE:image"
|
||||
output_stream: "IMAGE:transformed_image"
|
||||
output_stream: "LETTERBOX_PADDING:letterbox_padding"
|
||||
options: {
|
||||
[mediapipe.ImageTransformationCalculatorOptions.ext] {
|
||||
output_width: 128
|
||||
output_height: 128
|
||||
scale_mode: FIT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Converts the transformed input image on CPU into an image tensor stored as a
|
||||
# TfLiteTensor.
|
||||
node {
|
||||
calculator: "TfLiteConverterCalculator"
|
||||
input_stream: "IMAGE:transformed_image"
|
||||
output_stream: "TENSORS:input_tensors"
|
||||
}
|
||||
|
||||
# Runs a TensorFlow Lite model on CPU that takes an image tensor and outputs a
|
||||
# vector of tensors representing, for instance, detection boxes/keypoints and
|
||||
# scores.
|
||||
node {
|
||||
calculator: "TfLiteInferenceCalculator"
|
||||
input_stream: "TENSORS:input_tensors"
|
||||
output_stream: "TENSORS:detection_tensors"
|
||||
options: {
|
||||
[mediapipe.TfLiteInferenceCalculatorOptions.ext] {
|
||||
model_path: "mediapipe/modules/pose_detection/pose_detection.tflite"
|
||||
delegate { xnnpack {} }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Generates a single side packet containing a vector of SSD anchors based on
|
||||
# the specification in the options.
|
||||
node {
|
||||
calculator: "SsdAnchorsCalculator"
|
||||
output_side_packet: "anchors"
|
||||
options: {
|
||||
[mediapipe.SsdAnchorsCalculatorOptions.ext] {
|
||||
num_layers: 4
|
||||
min_scale: 0.1484375
|
||||
max_scale: 0.75
|
||||
input_size_height: 128
|
||||
input_size_width: 128
|
||||
anchor_offset_x: 0.5
|
||||
anchor_offset_y: 0.5
|
||||
strides: 8
|
||||
strides: 16
|
||||
strides: 16
|
||||
strides: 16
|
||||
aspect_ratios: 1.0
|
||||
fixed_anchor_size: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Decodes the detection tensors generated by the TensorFlow Lite model, based on
|
||||
# the SSD anchors and the specification in the options, into a vector of
|
||||
# detections. Each detection describes a detected object.
|
||||
node {
|
||||
calculator: "TfLiteTensorsToDetectionsCalculator"
|
||||
input_stream: "TENSORS:detection_tensors"
|
||||
input_side_packet: "ANCHORS:anchors"
|
||||
output_stream: "DETECTIONS:unfiltered_detections"
|
||||
options: {
|
||||
[mediapipe.TfLiteTensorsToDetectionsCalculatorOptions.ext] {
|
||||
num_classes: 1
|
||||
num_boxes: 896
|
||||
num_coords: 12
|
||||
box_coord_offset: 0
|
||||
keypoint_coord_offset: 4
|
||||
num_keypoints: 4
|
||||
num_values_per_keypoint: 2
|
||||
sigmoid_score: true
|
||||
score_clipping_thresh: 100.0
|
||||
reverse_output_order: true
|
||||
x_scale: 128.0
|
||||
y_scale: 128.0
|
||||
h_scale: 128.0
|
||||
w_scale: 128.0
|
||||
min_score_thresh: 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Performs non-max suppression to remove excessive detections.
|
||||
node {
|
||||
calculator: "NonMaxSuppressionCalculator"
|
||||
input_stream: "unfiltered_detections"
|
||||
output_stream: "filtered_detections"
|
||||
options: {
|
||||
[mediapipe.NonMaxSuppressionCalculatorOptions.ext] {
|
||||
min_suppression_threshold: 0.3
|
||||
overlap_type: INTERSECTION_OVER_UNION
|
||||
algorithm: WEIGHTED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Adjusts detection locations (already normalized to [0.f, 1.f]) on the
|
||||
# letterboxed image (after image transformation with the FIT scale mode) to the
|
||||
# corresponding locations on the same image with the letterbox removed (the
|
||||
# input image to the graph before image transformation).
|
||||
node {
|
||||
calculator: "DetectionLetterboxRemovalCalculator"
|
||||
input_stream: "DETECTIONS:filtered_detections"
|
||||
input_stream: "LETTERBOX_PADDING:letterbox_padding"
|
||||
output_stream: "DETECTIONS:detections"
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
# MediaPipe graph to detect poses. (GPU input, and inference is executed on
|
||||
# GPU.)
|
||||
#
|
||||
# It is required that "pose_detection.tflite" is available at
|
||||
# "mediapipe/modules/pose_detection/pose_detection.tflite"
|
||||
# path during execution.
|
||||
#
|
||||
# EXAMPLE:
|
||||
# node {
|
||||
# calculator: "PoseDetectionGpu"
|
||||
# input_stream: "IMAGE:image"
|
||||
# output_stream: "DETECTIONS:pose_detections"
|
||||
# }
|
||||
|
||||
type: "PoseDetectionGpu"
|
||||
|
||||
# GPU image. (GpuBuffer)
|
||||
input_stream: "IMAGE:image"
|
||||
|
||||
# Detected poses. (std::vector<Detection>)
|
||||
# Bounding box in each pose detection is currently set to the bounding box of
|
||||
# the detected face. However, 4 additional key points are available in each
|
||||
# detection, which are used to further calculate a (rotated) bounding box that
|
||||
# encloses the body region of interest. Among the 4 key points, the first two
|
||||
# are for identifying the full-body region, and the second two for upper body
|
||||
# only:
|
||||
#
|
||||
# Key point 0 - mid hip center
|
||||
# Key point 1 - point that encodes size & rotation (for full body)
|
||||
# Key point 2 - mid shoulder center
|
||||
# Key point 3 - point that encodes size & rotation (for upper body)
|
||||
#
|
||||
# NOTE: there will not be an output packet in the DETECTIONS stream for this
|
||||
# particular timestamp if none of poses detected. However, the MediaPipe
|
||||
# framework will internally inform the downstream calculators of the absence of
|
||||
# this packet so that they don't wait for it unnecessarily.
|
||||
output_stream: "DETECTIONS:detections"
|
||||
|
||||
# Transforms the input image on GPU to a 128x128 image. To scale the input
|
||||
# image, the scale_mode option is set to FIT to preserve the aspect ratio,
|
||||
# resulting in potential letterboxing in the transformed image.
|
||||
node: {
|
||||
calculator: "ImageTransformationCalculator"
|
||||
input_stream: "IMAGE_GPU:image"
|
||||
output_stream: "IMAGE_GPU:transformed_image"
|
||||
output_stream: "LETTERBOX_PADDING:letterbox_padding"
|
||||
options: {
|
||||
[mediapipe.ImageTransformationCalculatorOptions.ext] {
|
||||
output_width: 128
|
||||
output_height: 128
|
||||
scale_mode: FIT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Converts the transformed input image on GPU into an image tensor stored as a
|
||||
# TfLiteTensor.
|
||||
node {
|
||||
calculator: "TfLiteConverterCalculator"
|
||||
input_stream: "IMAGE_GPU:transformed_image"
|
||||
output_stream: "TENSORS_GPU:input_tensors"
|
||||
}
|
||||
|
||||
# Runs a TensorFlow Lite model on GPU that takes an image tensor and outputs a
|
||||
# vector of tensors representing, for instance, detection boxes/keypoints and
|
||||
# scores.
|
||||
node {
|
||||
calculator: "TfLiteInferenceCalculator"
|
||||
input_stream: "TENSORS_GPU:input_tensors"
|
||||
# TODO: we can use TENSORS_GPU here and in the downstream calculator
|
||||
output_stream: "TENSORS:detection_tensors"
|
||||
options: {
|
||||
[mediapipe.TfLiteInferenceCalculatorOptions.ext] {
|
||||
model_path: "mediapipe/modules/pose_detection/pose_detection.tflite"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Generates a single side packet containing a vector of SSD anchors based on
|
||||
# the specification in the options.
|
||||
node {
|
||||
calculator: "SsdAnchorsCalculator"
|
||||
output_side_packet: "anchors"
|
||||
options: {
|
||||
[mediapipe.SsdAnchorsCalculatorOptions.ext] {
|
||||
num_layers: 4
|
||||
min_scale: 0.1484375
|
||||
max_scale: 0.75
|
||||
input_size_height: 128
|
||||
input_size_width: 128
|
||||
anchor_offset_x: 0.5
|
||||
anchor_offset_y: 0.5
|
||||
strides: 8
|
||||
strides: 16
|
||||
strides: 16
|
||||
strides: 16
|
||||
aspect_ratios: 1.0
|
||||
fixed_anchor_size: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Decodes the detection tensors generated by the TensorFlow Lite model, based on
|
||||
# the SSD anchors and the specification in the options, into a vector of
|
||||
# detections. Each detection describes a detected object.
|
||||
node {
|
||||
calculator: "TfLiteTensorsToDetectionsCalculator"
|
||||
input_stream: "TENSORS:detection_tensors"
|
||||
input_side_packet: "ANCHORS:anchors"
|
||||
output_stream: "DETECTIONS:unfiltered_detections"
|
||||
options: {
|
||||
[mediapipe.TfLiteTensorsToDetectionsCalculatorOptions.ext] {
|
||||
num_classes: 1
|
||||
num_boxes: 896
|
||||
num_coords: 12
|
||||
box_coord_offset: 0
|
||||
keypoint_coord_offset: 4
|
||||
num_keypoints: 4
|
||||
num_values_per_keypoint: 2
|
||||
sigmoid_score: true
|
||||
score_clipping_thresh: 100.0
|
||||
reverse_output_order: true
|
||||
x_scale: 128.0
|
||||
y_scale: 128.0
|
||||
h_scale: 128.0
|
||||
w_scale: 128.0
|
||||
min_score_thresh: 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Performs non-max suppression to remove excessive detections.
|
||||
node {
|
||||
calculator: "NonMaxSuppressionCalculator"
|
||||
input_stream: "unfiltered_detections"
|
||||
output_stream: "filtered_detections"
|
||||
options: {
|
||||
[mediapipe.NonMaxSuppressionCalculatorOptions.ext] {
|
||||
min_suppression_threshold: 0.3
|
||||
overlap_type: INTERSECTION_OVER_UNION
|
||||
algorithm: WEIGHTED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Adjusts detection locations (already normalized to [0.f, 1.f]) on the
|
||||
# letterboxed image (after image transformation with the FIT scale mode) to the
|
||||
# corresponding locations on the same image with the letterbox removed (the
|
||||
# input image to the graph before image transformation).
|
||||
node {
|
||||
calculator: "DetectionLetterboxRemovalCalculator"
|
||||
input_stream: "DETECTIONS:filtered_detections"
|
||||
input_stream: "LETTERBOX_PADDING:letterbox_padding"
|
||||
output_stream: "DETECTIONS:detections"
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
# Copyright 2020 The MediaPipe Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
load(
|
||||
"//mediapipe/framework/tool:mediapipe_graph.bzl",
|
||||
"mediapipe_simple_subgraph",
|
||||
)
|
||||
|
||||
licenses(["notice"]) # Apache 2.0
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
mediapipe_simple_subgraph(
|
||||
name = "pose_landmark_upper_body_by_roi_gpu",
|
||||
graph = "pose_landmark_upper_body_by_roi_gpu.pbtxt",
|
||||
register_as = "PoseLandmarkUpperBodyByRoiGpu",
|
||||
deps = [
|
||||
"//mediapipe/calculators/core:gate_calculator",
|
||||
"//mediapipe/calculators/core:split_vector_calculator",
|
||||
"//mediapipe/calculators/image:image_cropping_calculator",
|
||||
"//mediapipe/calculators/image:image_transformation_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_converter_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_inference_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_tensors_to_floats_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_tensors_to_landmarks_calculator",
|
||||
"//mediapipe/calculators/util:landmark_letterbox_removal_calculator",
|
||||
"//mediapipe/calculators/util:landmark_projection_calculator",
|
||||
"//mediapipe/calculators/util:thresholding_calculator",
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_simple_subgraph(
|
||||
name = "pose_landmark_upper_body_by_roi_cpu",
|
||||
graph = "pose_landmark_upper_body_by_roi_cpu.pbtxt",
|
||||
register_as = "PoseLandmarkUpperBodyByRoiCpu",
|
||||
deps = [
|
||||
"//mediapipe/calculators/core:gate_calculator",
|
||||
"//mediapipe/calculators/core:split_vector_calculator",
|
||||
"//mediapipe/calculators/image:image_cropping_calculator",
|
||||
"//mediapipe/calculators/image:image_transformation_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_converter_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_inference_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_tensors_to_floats_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_tensors_to_landmarks_calculator",
|
||||
"//mediapipe/calculators/util:landmark_letterbox_removal_calculator",
|
||||
"//mediapipe/calculators/util:landmark_projection_calculator",
|
||||
"//mediapipe/calculators/util:thresholding_calculator",
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_simple_subgraph(
|
||||
name = "pose_landmark_upper_body_gpu",
|
||||
graph = "pose_landmark_upper_body_gpu.pbtxt",
|
||||
register_as = "PoseLandmarkUpperBodyGpu",
|
||||
deps = [
|
||||
":pose_detection_to_roi",
|
||||
":pose_landmark_upper_body_by_roi_gpu",
|
||||
":pose_landmark_upper_body_landmarks_to_roi",
|
||||
"//mediapipe/calculators/core:gate_calculator",
|
||||
"//mediapipe/calculators/core:merge_calculator",
|
||||
"//mediapipe/calculators/core:packet_presence_calculator",
|
||||
"//mediapipe/calculators/core:previous_loopback_calculator",
|
||||
"//mediapipe/calculators/core:split_vector_calculator",
|
||||
"//mediapipe/calculators/image:image_properties_calculator",
|
||||
"//mediapipe/modules/pose_detection:pose_detection_gpu",
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_simple_subgraph(
|
||||
name = "pose_landmark_upper_body_cpu",
|
||||
graph = "pose_landmark_upper_body_cpu.pbtxt",
|
||||
register_as = "PoseLandmarkUpperBodyCpu",
|
||||
deps = [
|
||||
":pose_detection_to_roi",
|
||||
":pose_landmark_upper_body_by_roi_cpu",
|
||||
":pose_landmark_upper_body_landmarks_to_roi",
|
||||
"//mediapipe/calculators/core:gate_calculator",
|
||||
"//mediapipe/calculators/core:merge_calculator",
|
||||
"//mediapipe/calculators/core:packet_presence_calculator",
|
||||
"//mediapipe/calculators/core:previous_loopback_calculator",
|
||||
"//mediapipe/calculators/core:split_vector_calculator",
|
||||
"//mediapipe/calculators/image:image_properties_calculator",
|
||||
"//mediapipe/modules/pose_detection:pose_detection_cpu",
|
||||
],
|
||||
)
|
||||
|
||||
exports_files(
|
||||
srcs = [
|
||||
"pose_landmark_upper_body.tflite",
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_simple_subgraph(
|
||||
name = "pose_detection_to_roi",
|
||||
graph = "pose_detection_to_roi.pbtxt",
|
||||
register_as = "PoseDetectionToRoi",
|
||||
deps = [
|
||||
"//mediapipe/calculators/util:alignment_points_to_rects_calculator",
|
||||
"//mediapipe/calculators/util:rect_transformation_calculator",
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_simple_subgraph(
|
||||
name = "pose_landmark_upper_body_landmarks_to_roi",
|
||||
graph = "pose_landmark_upper_body_landmarks_to_roi.pbtxt",
|
||||
register_as = "PoseLandmarkUpperBodyLandmarksToRoi",
|
||||
deps = [
|
||||
"//mediapipe/calculators/core:split_vector_calculator",
|
||||
"//mediapipe/calculators/util:alignment_points_to_rects_calculator",
|
||||
"//mediapipe/calculators/util:landmarks_to_detection_calculator",
|
||||
"//mediapipe/calculators/util:rect_transformation_calculator",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
# pose_landmark
|
||||
|
||||
Subgraphs|Details
|
||||
:--- | :---
|
||||
[`PoseLandmarkUpperBodyByRoiCpu`](https://github.com/google/mediapipe/tree/master/mediapipe/modules/pose_landmark/pose_landmark_upper_body_by_roi_cpu.pbtxt)| Detects landmarks of a single pose. See landmarks (key points) [scheme](https://github.com/google/mediapipe/tree/master/mediapipe/modules/pose_landmark/pose_landmark_upper_body_topology.svg). (CPU input, and inference is executed on CPU.)
|
||||
[`PoseLandmarkUpperBodyByRoiGpu`](https://github.com/google/mediapipe/tree/master/mediapipe/modules/pose_landmark/pose_landmark_upper_body_gpu.pbtxt)| Detects landmarks of a single pose. See landmarks (key points) [scheme](https://github.com/google/mediapipe/tree/master/mediapipe/modules/pose_landmark/pose_landmark_upper_body_topology.svg). (GPU input, and inference is executed on GPU)
|
||||
[`PoseLandmarkUpperBodyCpu`](https://github.com/google/mediapipe/tree/master/mediapipe/modules/pose_landmark/pose_landmark_upper_body_cpu.pbtxt)| Detects and tracks landmarks of a single pose. See landmarks (key points) [scheme](https://github.com/google/mediapipe/tree/master/mediapipe/modules/pose_landmark/pose_landmark_upper_body_topology.svg). (CPU input, and inference is executed on CPU)
|
||||
[`PoseLandmarkUpperBodyGpu`](https://github.com/google/mediapipe/tree/master/mediapipe/modules/pose_landmark/pose_landmark_upper_body_gpu.pbtxt)| Detects and tracks landmarks of a single pose. See landmarks (key points) [scheme](https://github.com/google/mediapipe/tree/master/mediapipe/modules/pose_landmark/pose_landmark_upper_body_topology.svg). (GPU input, and inference is executed on GPU.)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# MediaPipe graph to calculate pose region of interest (ROI) from a detection
|
||||
# provided by "PoseDetectionCpu" or "PoseDetectionGpu"
|
||||
#
|
||||
# NOTE: this graph is subject to change and should not be used directly.
|
||||
|
||||
type: "PoseDetectionToRoi"
|
||||
|
||||
# Pose detection. (Detection)
|
||||
input_stream: "DETECTION:detection"
|
||||
# Frame size (width and height). (std::pair<int, int>)
|
||||
input_stream: "IMAGE_SIZE:image_size"
|
||||
# ROI according to the first detection of input detections. (NormalizedRect)
|
||||
output_stream: "ROI:roi"
|
||||
|
||||
# Converts pose detection into a rectangle based on center and scale alignment
|
||||
# points. Pose detection contains four key points: first two for full-body pose
|
||||
# and two more for upper-body pose.
|
||||
node {
|
||||
calculator: "AlignmentPointsRectsCalculator"
|
||||
input_stream: "DETECTION:detection"
|
||||
input_stream: "IMAGE_SIZE:image_size"
|
||||
output_stream: "NORM_RECT:raw_roi"
|
||||
options: {
|
||||
[mediapipe.DetectionsToRectsCalculatorOptions.ext] {
|
||||
rotation_vector_start_keypoint_index: 2
|
||||
rotation_vector_end_keypoint_index: 3
|
||||
rotation_vector_target_angle_degrees: 90
|
||||
output_zero_rect_for_empty_detections: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Expands pose rect with marging used during training.
|
||||
node {
|
||||
calculator: "RectTransformationCalculator"
|
||||
input_stream: "NORM_RECT:raw_roi"
|
||||
input_stream: "IMAGE_SIZE:image_size"
|
||||
output_stream: "roi"
|
||||
options: {
|
||||
[mediapipe.RectTransformationCalculatorOptions.ext] {
|
||||
scale_x: 1.5
|
||||
scale_y: 1.5
|
||||
square_long: true
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,192 @@
|
||||
# MediaPipe graph to detect/predict upper-body pose landmarks. (CPU input, and
|
||||
# inference is executed on CPU.)
|
||||
#
|
||||
# It is required that "pose_landmark_upper_body.tflite" is available at
|
||||
# "mediapipe/modules/pose_landmark/pose_landmark_upper_body.tflite"
|
||||
# path during execution.
|
||||
#
|
||||
# EXAMPLE:
|
||||
# node {
|
||||
# calculator: "PoseLandmarkUpperBodyByRoiCpu"
|
||||
# input_stream: "IMAGE:image"
|
||||
# input_stream: "ROI:roi"
|
||||
# output_stream: "LANDMARKS:landmarks"
|
||||
# }
|
||||
|
||||
type: "PoseLandmarkUpperBodyByRoiCpu"
|
||||
|
||||
# CPU image. (ImageFrame)
|
||||
input_stream: "IMAGE:image"
|
||||
# ROI (region of interest) within the given image where a pose is located.
|
||||
# (NormalizedRect)
|
||||
input_stream: "ROI:roi"
|
||||
|
||||
# Pose landmarks within the given ROI. (NormalizedLandmarkList)
|
||||
# We have 25 (upper-body) landmarks
|
||||
# (see pose_landmark_upper_body_topology.svg), and there are other auxiliary key
|
||||
# points.
|
||||
# 0 - nose
|
||||
# 1 - right eye (inner)
|
||||
# 2 - right eye
|
||||
# 3 - right eye (outer)
|
||||
# 4 - left eye (inner)
|
||||
# 5 - left eye
|
||||
# 6 - left eye (outer)
|
||||
# 7 - right ear
|
||||
# 8 - left ear
|
||||
# 9 - mouth (right)
|
||||
# 10 - mouth (left)
|
||||
# 11 - right shoulder
|
||||
# 12 - left shoulder
|
||||
# 13 - right elbow
|
||||
# 14 - left elbow
|
||||
# 15 - right wrist
|
||||
# 16 - left wrist
|
||||
# 17 - right pinky
|
||||
# 18 - left pinky
|
||||
# 19 - right index
|
||||
# 20 - left index
|
||||
# 21 - right thumb
|
||||
# 22 - left thumb
|
||||
# 23 - right hip
|
||||
# 24 - left hip
|
||||
#
|
||||
# NOTE: if a pose is not present within the given ROI, for this particular
|
||||
# timestamp there will not be an output packet in the LANDMARKS stream. However,
|
||||
# the MediaPipe framework will internally inform the downstream calculators of
|
||||
# the absence of this packet so that they don't wait for it unnecessarily.
|
||||
output_stream: "LANDMARKS:landmarks"
|
||||
|
||||
# Crops the rectangle that contains a pose from the input image.
|
||||
node {
|
||||
calculator: "ImageCroppingCalculator"
|
||||
input_stream: "IMAGE:image"
|
||||
input_stream: "NORM_RECT:roi"
|
||||
output_stream: "IMAGE:pose_region"
|
||||
options: {
|
||||
[mediapipe.ImageCroppingCalculatorOptions.ext] {
|
||||
border_mode: BORDER_REPLICATE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Transforms the input image on CPU to a 256x256 image. To scale the input
|
||||
# image, the scale_mode option is set to FIT to preserve the aspect ratio,
|
||||
# resulting in potential letterboxing in the transformed image.
|
||||
node: {
|
||||
calculator: "ImageTransformationCalculator"
|
||||
input_stream: "IMAGE:pose_region"
|
||||
output_stream: "IMAGE:transformed_pose_region"
|
||||
output_stream: "LETTERBOX_PADDING:letterbox_padding"
|
||||
options: {
|
||||
[mediapipe.ImageTransformationCalculatorOptions.ext] {
|
||||
output_width: 256
|
||||
output_height: 256
|
||||
scale_mode: FIT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Converts the transformed input image on CPU into a tensor.
|
||||
node {
|
||||
calculator: "TfLiteConverterCalculator"
|
||||
input_stream: "IMAGE:transformed_pose_region"
|
||||
output_stream: "TENSORS:input_tensors"
|
||||
options: {
|
||||
[mediapipe.TfLiteConverterCalculatorOptions.ext] {
|
||||
zero_center: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Runs a TensorFlow Lite model inference on CPU.
|
||||
node {
|
||||
calculator: "TfLiteInferenceCalculator"
|
||||
input_stream: "TENSORS:input_tensors"
|
||||
output_stream: "TENSORS:output_tensors"
|
||||
options: {
|
||||
[mediapipe.TfLiteInferenceCalculatorOptions.ext] {
|
||||
model_path: "mediapipe/modules/pose_landmark/pose_landmark_upper_body.tflite"
|
||||
delegate { xnnpack {} }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Splits a vector of TFLite tensors to multiple vectors according to the ranges
|
||||
# specified in option.
|
||||
node {
|
||||
calculator: "SplitTfLiteTensorVectorCalculator"
|
||||
input_stream: "output_tensors"
|
||||
output_stream: "landmark_tensors"
|
||||
output_stream: "pose_flag_tensor"
|
||||
options: {
|
||||
[mediapipe.SplitVectorCalculatorOptions.ext] {
|
||||
ranges: { begin: 0 end: 1 }
|
||||
ranges: { begin: 1 end: 2 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Converts the pose-flag tensor into a float that represents the confidence
|
||||
# score of pose presence.
|
||||
node {
|
||||
calculator: "TfLiteTensorsToFloatsCalculator"
|
||||
input_stream: "TENSORS:pose_flag_tensor"
|
||||
output_stream: "FLOAT:pose_presence_score"
|
||||
}
|
||||
|
||||
# Applies a threshold to the confidence score to determine whether a pose is
|
||||
# present.
|
||||
node {
|
||||
calculator: "ThresholdingCalculator"
|
||||
input_stream: "FLOAT:pose_presence_score"
|
||||
output_stream: "FLAG:pose_presence"
|
||||
options: {
|
||||
[mediapipe.ThresholdingCalculatorOptions.ext] {
|
||||
threshold: 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Drop landmarks tensors if pose is not present.
|
||||
node {
|
||||
calculator: "GateCalculator"
|
||||
input_stream: "landmark_tensors"
|
||||
input_stream: "ALLOW:pose_presence"
|
||||
output_stream: "ensured_landmark_tensors"
|
||||
}
|
||||
|
||||
# Decodes the landmark tensors into a vector of lanmarks, where the landmark
|
||||
# coordinates are normalized by the size of the input image to the model.
|
||||
node {
|
||||
calculator: "TfLiteTensorsToLandmarksCalculator"
|
||||
input_stream: "TENSORS:ensured_landmark_tensors"
|
||||
output_stream: "NORM_LANDMARKS:raw_landmarks"
|
||||
options: {
|
||||
[mediapipe.TfLiteTensorsToLandmarksCalculatorOptions.ext] {
|
||||
num_landmarks: 31
|
||||
input_image_width: 256
|
||||
input_image_height: 256
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Adjusts landmarks (already normalized to [0.f, 1.f]) on the letterboxed pose
|
||||
# image (after image transformation with the FIT scale mode) to the
|
||||
# corresponding locations on the same image with the letterbox removed (pose
|
||||
# image before image transformation).
|
||||
node {
|
||||
calculator: "LandmarkLetterboxRemovalCalculator"
|
||||
input_stream: "LANDMARKS:raw_landmarks"
|
||||
input_stream: "LETTERBOX_PADDING:letterbox_padding"
|
||||
output_stream: "LANDMARKS:adjusted_landmarks"
|
||||
}
|
||||
|
||||
# Projects the landmarks from the cropped pose image to the corresponding
|
||||
# locations on the full image before cropping (input to the graph).
|
||||
node {
|
||||
calculator: "LandmarkProjectionCalculator"
|
||||
input_stream: "NORM_LANDMARKS:adjusted_landmarks"
|
||||
input_stream: "NORM_RECT:roi"
|
||||
output_stream: "NORM_LANDMARKS:landmarks"
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
# MediaPipe graph to detect/predict upper-body pose landmarks. (GPU input, and
|
||||
# inference is executed on GPU.)
|
||||
#
|
||||
# It is required that "pose_landmark_upper_body.tflite" is available at
|
||||
# "mediapipe/modules/pose_landmark/pose_landmark_upper_body.tflite"
|
||||
# path during execution.
|
||||
#
|
||||
# EXAMPLE:
|
||||
# node {
|
||||
# calculator: "PoseLandmarkUpperBodyByRoiGpu"
|
||||
# input_stream: "IMAGE:image"
|
||||
# input_stream: "ROI:roi"
|
||||
# output_stream: "LANDMARKS:landmarks"
|
||||
# }
|
||||
|
||||
type: "PoseLandmarkUpperBodyByRoiGpu"
|
||||
|
||||
# GPU image. (GpuBuffer)
|
||||
input_stream: "IMAGE:image"
|
||||
# ROI (region of interest) within the given image where a pose is located.
|
||||
# (NormalizedRect)
|
||||
input_stream: "ROI:roi"
|
||||
|
||||
# Pose landmarks within the given ROI. (NormalizedLandmarkList)
|
||||
# We have 25 (upper-body) landmarks
|
||||
# (see pose_landmark_upper_body_topology.svg), and there are other auxiliary key
|
||||
# points.
|
||||
# 0 - nose
|
||||
# 1 - right eye (inner)
|
||||
# 2 - right eye
|
||||
# 3 - right eye (outer)
|
||||
# 4 - left eye (inner)
|
||||
# 5 - left eye
|
||||
# 6 - left eye (outer)
|
||||
# 7 - right ear
|
||||
# 8 - left ear
|
||||
# 9 - mouth (right)
|
||||
# 10 - mouth (left)
|
||||
# 11 - right shoulder
|
||||
# 12 - left shoulder
|
||||
# 13 - right elbow
|
||||
# 14 - left elbow
|
||||
# 15 - right wrist
|
||||
# 16 - left wrist
|
||||
# 17 - right pinky
|
||||
# 18 - left pinky
|
||||
# 19 - right index
|
||||
# 20 - left index
|
||||
# 21 - right thumb
|
||||
# 22 - left thumb
|
||||
# 23 - right hip
|
||||
# 24 - left hip
|
||||
#
|
||||
# NOTE: if a pose is not present within the given ROI, for this particular
|
||||
# timestamp there will not be an output packet in the LANDMARKS stream. However,
|
||||
# the MediaPipe framework will internally inform the downstream calculators of
|
||||
# the absence of this packet so that they don't wait for it unnecessarily.
|
||||
output_stream: "LANDMARKS:landmarks"
|
||||
|
||||
# Crops the rectangle that contains a pose from the input image.
|
||||
node {
|
||||
calculator: "ImageCroppingCalculator"
|
||||
input_stream: "IMAGE_GPU:image"
|
||||
input_stream: "NORM_RECT:roi"
|
||||
output_stream: "IMAGE_GPU:pose_region"
|
||||
options: {
|
||||
[mediapipe.ImageCroppingCalculatorOptions.ext] {
|
||||
border_mode: BORDER_REPLICATE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Transforms the input image on GPU to a 256x256 image. To scale the input
|
||||
# image, the scale_mode option is set to FIT to preserve the aspect ratio,
|
||||
# resulting in potential letterboxing in the transformed image.
|
||||
node: {
|
||||
calculator: "ImageTransformationCalculator"
|
||||
input_stream: "IMAGE_GPU:pose_region"
|
||||
output_stream: "IMAGE_GPU:transformed_pose_region"
|
||||
output_stream: "LETTERBOX_PADDING:letterbox_padding"
|
||||
options: {
|
||||
[mediapipe.ImageTransformationCalculatorOptions.ext] {
|
||||
output_width: 256
|
||||
output_height: 256
|
||||
scale_mode: FIT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Converts the transformed input image on GPU into a tensor.
|
||||
node {
|
||||
calculator: "TfLiteConverterCalculator"
|
||||
input_stream: "IMAGE_GPU:transformed_pose_region"
|
||||
output_stream: "TENSORS_GPU:input_tensors"
|
||||
options: {
|
||||
[mediapipe.TfLiteConverterCalculatorOptions.ext] {
|
||||
zero_center: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Runs a TensorFlow Lite model inference on GPU.
|
||||
node {
|
||||
calculator: "TfLiteInferenceCalculator"
|
||||
input_stream: "TENSORS_GPU:input_tensors"
|
||||
output_stream: "TENSORS:output_tensors"
|
||||
options: {
|
||||
[mediapipe.TfLiteInferenceCalculatorOptions.ext] {
|
||||
model_path: "mediapipe/modules/pose_landmark/pose_landmark_upper_body.tflite"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Splits a vector of TFLite tensors to multiple vectors according to the ranges
|
||||
# specified in option.
|
||||
node {
|
||||
calculator: "SplitTfLiteTensorVectorCalculator"
|
||||
input_stream: "output_tensors"
|
||||
output_stream: "landmark_tensors"
|
||||
output_stream: "pose_flag_tensor"
|
||||
options: {
|
||||
[mediapipe.SplitVectorCalculatorOptions.ext] {
|
||||
ranges: { begin: 0 end: 1 }
|
||||
ranges: { begin: 1 end: 2 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Converts the pose-flag tensor into a float that represents the confidence
|
||||
# score of pose presence.
|
||||
node {
|
||||
calculator: "TfLiteTensorsToFloatsCalculator"
|
||||
input_stream: "TENSORS:pose_flag_tensor"
|
||||
output_stream: "FLOAT:pose_presence_score"
|
||||
}
|
||||
|
||||
# Applies a threshold to the confidence score to determine whether a pose is
|
||||
# present.
|
||||
node {
|
||||
calculator: "ThresholdingCalculator"
|
||||
input_stream: "FLOAT:pose_presence_score"
|
||||
output_stream: "FLAG:pose_presence"
|
||||
options: {
|
||||
[mediapipe.ThresholdingCalculatorOptions.ext] {
|
||||
threshold: 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Drop landmarks tensors if pose is not present.
|
||||
node {
|
||||
calculator: "GateCalculator"
|
||||
input_stream: "landmark_tensors"
|
||||
input_stream: "ALLOW:pose_presence"
|
||||
output_stream: "ensured_landmark_tensors"
|
||||
}
|
||||
|
||||
# Decodes the landmark tensors into a vector of lanmarks, where the landmark
|
||||
# coordinates are normalized by the size of the input image to the model.
|
||||
node {
|
||||
calculator: "TfLiteTensorsToLandmarksCalculator"
|
||||
input_stream: "TENSORS:ensured_landmark_tensors"
|
||||
output_stream: "NORM_LANDMARKS:raw_landmarks"
|
||||
options: {
|
||||
[mediapipe.TfLiteTensorsToLandmarksCalculatorOptions.ext] {
|
||||
num_landmarks: 31
|
||||
input_image_width: 256
|
||||
input_image_height: 256
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Adjusts landmarks (already normalized to [0.f, 1.f]) on the letterboxed pose
|
||||
# image (after image transformation with the FIT scale mode) to the
|
||||
# corresponding locations on the same image with the letterbox removed (pose
|
||||
# image before image transformation).
|
||||
node {
|
||||
calculator: "LandmarkLetterboxRemovalCalculator"
|
||||
input_stream: "LANDMARKS:raw_landmarks"
|
||||
input_stream: "LETTERBOX_PADDING:letterbox_padding"
|
||||
output_stream: "LANDMARKS:adjusted_landmarks"
|
||||
}
|
||||
|
||||
# Projects the landmarks from the cropped pose image to the corresponding
|
||||
# locations on the full image before cropping (input to the graph).
|
||||
node {
|
||||
calculator: "LandmarkProjectionCalculator"
|
||||
input_stream: "NORM_LANDMARKS:adjusted_landmarks"
|
||||
input_stream: "NORM_RECT:roi"
|
||||
output_stream: "NORM_LANDMARKS:landmarks"
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
# MediaPipe graph to detect/predict pose landmarks. (CPU input, and inference is
|
||||
# executed on CPU.) This graph tries to skip pose detection as much as possible
|
||||
# by using previously detected/predicted landmarks for new images.
|
||||
#
|
||||
# It is required that "pose_detection.tflite" is available at
|
||||
# "mediapipe/modules/pose_detection/pose_detection.tflite"
|
||||
# path during execution.
|
||||
#
|
||||
# It is required that "pose_landmark_upper_body.tflite" is available at
|
||||
# "mediapipe/modules/pose_landmark/pose_landmark_upper_body.tflite"
|
||||
# path during execution.
|
||||
#
|
||||
# EXAMPLE:
|
||||
# node {
|
||||
# calculator: "PoseLandmarkUpperBodyCpu"
|
||||
# input_stream: "IMAGE:image"
|
||||
# output_stream: "LANDMARKS:pose_landmarks"
|
||||
# }
|
||||
|
||||
type: "PoseLandmarkUpperBodyCpu"
|
||||
|
||||
# CPU image. (ImageFrame)
|
||||
input_stream: "IMAGE:image"
|
||||
|
||||
# Pose landmarks within the given ROI. (NormalizedLandmarkList)
|
||||
# We have 25 (upper-body) landmarks
|
||||
# (see pose_landmark_upper_body_topology.svg), and there are other auxiliary key
|
||||
# points.
|
||||
# 0 - nose
|
||||
# 1 - right eye (inner)
|
||||
# 2 - right eye
|
||||
# 3 - right eye (outer)
|
||||
# 4 - left eye (inner)
|
||||
# 5 - left eye
|
||||
# 6 - left eye (outer)
|
||||
# 7 - right ear
|
||||
# 8 - left ear
|
||||
# 9 - mouth (right)
|
||||
# 10 - mouth (left)
|
||||
# 11 - right shoulder
|
||||
# 12 - left shoulder
|
||||
# 13 - right elbow
|
||||
# 14 - left elbow
|
||||
# 15 - right wrist
|
||||
# 16 - left wrist
|
||||
# 17 - right pinky
|
||||
# 18 - left pinky
|
||||
# 19 - right index
|
||||
# 20 - left index
|
||||
# 21 - right thumb
|
||||
# 22 - left thumb
|
||||
# 23 - right hip
|
||||
# 24 - left hip
|
||||
#
|
||||
# NOTE: if a pose is not present within the given ROI, for this particular
|
||||
# timestamp there will not be an output packet in the LANDMARKS stream. However,
|
||||
# the MediaPipe framework will internally inform the downstream calculators of
|
||||
# the absence of this packet so that they don't wait for it unnecessarily.
|
||||
output_stream: "LANDMARKS:pose_landmarks"
|
||||
|
||||
# Extra outputs (for debugging, for instance).
|
||||
# Detected poses. (Detection)
|
||||
output_stream: "DETECTION:pose_detection"
|
||||
# Regions of interest calculated based on landmarks. (NormalizedRect)
|
||||
output_stream: "ROI_FROM_LANDMARKS:pose_rect_from_landmarks"
|
||||
# Regions of interest calculated based on pose detections. (NormalizedRect)
|
||||
output_stream: "ROI_FROM_DETECTION:pose_rect_from_detection"
|
||||
|
||||
# Caches pose rects calculated from landmarks, and upon the arrival of the next
|
||||
# input image, sends out the cached rects with timestamps replaced by that of
|
||||
# the input image, essentially generating a packet that carries the previous
|
||||
# pose rects. Note that upon the arrival of the very first input image, a
|
||||
# timestamp bound update occurs to jump start the feedback loop.
|
||||
node {
|
||||
calculator: "PreviousLoopbackCalculator"
|
||||
input_stream: "MAIN:image"
|
||||
input_stream: "LOOP:pose_rect_from_landmarks"
|
||||
input_stream_info: {
|
||||
tag_index: "LOOP"
|
||||
back_edge: true
|
||||
}
|
||||
output_stream: "PREV_LOOP:prev_pose_rect_from_landmarks"
|
||||
}
|
||||
|
||||
# Checks if there's previous pose rect calculatoed from landmarks.
|
||||
node: {
|
||||
calculator: "PacketPresenceCalculator"
|
||||
input_stream: "PACKET:prev_pose_rect_from_landmarks"
|
||||
output_stream: "PRESENCE:prev_pose_rect_from_landmarks_is_present"
|
||||
}
|
||||
|
||||
# Calculates size of the image.
|
||||
node {
|
||||
calculator: "ImagePropertiesCalculator"
|
||||
input_stream: "IMAGE:image"
|
||||
output_stream: "SIZE:image_size"
|
||||
}
|
||||
|
||||
# Drops the incoming image if PoseLandmarkUpperBodyByRoiCpu was able to identify
|
||||
# pose presence in the previous image. Otherwise, passes the incoming image
|
||||
# through to trigger a new round of pose detection in PoseDetectionCpu.
|
||||
node {
|
||||
calculator: "GateCalculator"
|
||||
input_stream: "image"
|
||||
input_stream: "image_size"
|
||||
input_stream: "DISALLOW:prev_pose_rect_from_landmarks_is_present"
|
||||
output_stream: "image_for_pose_detection"
|
||||
output_stream: "image_size_for_pose_detection"
|
||||
options: {
|
||||
[mediapipe.GateCalculatorOptions.ext] {
|
||||
empty_packets_as_allow: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Detects poses.
|
||||
node {
|
||||
calculator: "PoseDetectionCpu"
|
||||
input_stream: "IMAGE:image_for_pose_detection"
|
||||
output_stream: "DETECTIONS:pose_detections"
|
||||
}
|
||||
|
||||
# Gets the very first detection from "pose_detections" vector.
|
||||
node {
|
||||
calculator: "SplitDetectionVectorCalculator"
|
||||
input_stream: "pose_detections"
|
||||
output_stream: "pose_detection"
|
||||
options: {
|
||||
[mediapipe.SplitVectorCalculatorOptions.ext] {
|
||||
ranges: { begin: 0 end: 1 }
|
||||
element_only: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Calculates region of interest based on pose detection, so that can be used
|
||||
# to detect landmarks.
|
||||
node {
|
||||
calculator: "PoseDetectionToRoi"
|
||||
input_stream: "DETECTION:pose_detection"
|
||||
input_stream: "IMAGE_SIZE:image_size_for_pose_detection"
|
||||
output_stream: "ROI:pose_rect_from_detection"
|
||||
}
|
||||
|
||||
# Selects either pose rect (or ROI) calculated from detection or from previously
|
||||
# detected landmarks if available (in this case, calculation of pose rect from
|
||||
# detection is skipped).
|
||||
node {
|
||||
calculator: "MergeCalculator"
|
||||
input_stream: "pose_rect_from_detection"
|
||||
input_stream: "prev_pose_rect_from_landmarks"
|
||||
output_stream: "pose_rect"
|
||||
}
|
||||
|
||||
# Detects pose landmarks within specified region of interest of the image.
|
||||
node {
|
||||
calculator: "PoseLandmarkUpperBodyByRoiCpu"
|
||||
input_stream: "IMAGE:image"
|
||||
input_stream: "ROI:pose_rect"
|
||||
output_stream: "LANDMARKS:pose_landmarks"
|
||||
}
|
||||
|
||||
# Calculates region of interest based on pose landmarks, so that can be reused
|
||||
# for subsequent image.
|
||||
node {
|
||||
calculator: "PoseLandmarkUpperBodyLandmarksToRoi"
|
||||
input_stream: "LANDMARKS:pose_landmarks"
|
||||
input_stream: "IMAGE_SIZE:image_size"
|
||||
output_stream: "ROI:pose_rect_from_landmarks"
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
# MediaPipe graph to detect/predict pose landmarks. (GPU input, and inference is
|
||||
# executed on GPU.) This graph tries to skip pose detection as much as possible
|
||||
# by using previously detected/predicted landmarks for new images.
|
||||
#
|
||||
# It is required that "pose_detection.tflite" is available at
|
||||
# "mediapipe/modules/pose_detection/pose_detection.tflite"
|
||||
# path during execution.
|
||||
#
|
||||
# It is required that "pose_landmark_upper_body.tflite" is available at
|
||||
# "mediapipe/modules/pose_landmark/pose_landmark_upper_body.tflite"
|
||||
# path during execution.
|
||||
#
|
||||
# EXAMPLE:
|
||||
# node {
|
||||
# calculator: "PoseLandmarkUpperBodyGpu"
|
||||
# input_stream: "IMAGE:image"
|
||||
# output_stream: "LANDMARKS:pose_landmarks"
|
||||
# }
|
||||
|
||||
type: "PoseLandmarkUpperBodyGpu"
|
||||
|
||||
# GPU image. (GpuBuffer)
|
||||
input_stream: "IMAGE:image"
|
||||
|
||||
# Pose landmarks within the given ROI. (NormalizedLandmarkList)
|
||||
# We have 25 (upper-body) landmarks
|
||||
# (see pose_landmark_upper_body_topology.svg), and there are other auxiliary key
|
||||
# points.
|
||||
# 0 - nose
|
||||
# 1 - right eye (inner)
|
||||
# 2 - right eye
|
||||
# 3 - right eye (outer)
|
||||
# 4 - left eye (inner)
|
||||
# 5 - left eye
|
||||
# 6 - left eye (outer)
|
||||
# 7 - right ear
|
||||
# 8 - left ear
|
||||
# 9 - mouth (right)
|
||||
# 10 - mouth (left)
|
||||
# 11 - right shoulder
|
||||
# 12 - left shoulder
|
||||
# 13 - right elbow
|
||||
# 14 - left elbow
|
||||
# 15 - right wrist
|
||||
# 16 - left wrist
|
||||
# 17 - right pinky
|
||||
# 18 - left pinky
|
||||
# 19 - right index
|
||||
# 20 - left index
|
||||
# 21 - right thumb
|
||||
# 22 - left thumb
|
||||
# 23 - right hip
|
||||
# 24 - left hip
|
||||
#
|
||||
# NOTE: if a pose is not present within the given ROI, for this particular
|
||||
# timestamp there will not be an output packet in the LANDMARKS stream. However,
|
||||
# the MediaPipe framework will internally inform the downstream calculators of
|
||||
# the absence of this packet so that they don't wait for it unnecessarily.
|
||||
output_stream: "LANDMARKS:pose_landmarks"
|
||||
|
||||
# Extra outputs (for debugging, for instance).
|
||||
# Detected poses. (Detection)
|
||||
output_stream: "DETECTION:pose_detection"
|
||||
# Regions of interest calculated based on landmarks. (NormalizedRect)
|
||||
output_stream: "ROI_FROM_LANDMARKS:pose_rect_from_landmarks"
|
||||
# Regions of interest calculated based on pose detections. (NormalizedRect)
|
||||
output_stream: "ROI_FROM_DETECTION:pose_rect_from_detection"
|
||||
|
||||
# Caches pose rects calculated from landmarks, and upon the arrival of the next
|
||||
# input image, sends out the cached rects with timestamps replaced by that of
|
||||
# the input image, essentially generating a packet that carries the previous
|
||||
# pose rects. Note that upon the arrival of the very first input image, a
|
||||
# timestamp bound update occurs to jump start the feedback loop.
|
||||
node {
|
||||
calculator: "PreviousLoopbackCalculator"
|
||||
input_stream: "MAIN:image"
|
||||
input_stream: "LOOP:pose_rect_from_landmarks"
|
||||
input_stream_info: {
|
||||
tag_index: "LOOP"
|
||||
back_edge: true
|
||||
}
|
||||
output_stream: "PREV_LOOP:prev_pose_rect_from_landmarks"
|
||||
}
|
||||
|
||||
# Checks if there's previous pose rect calculatoed from landmarks.
|
||||
node: {
|
||||
calculator: "PacketPresenceCalculator"
|
||||
input_stream: "PACKET:prev_pose_rect_from_landmarks"
|
||||
output_stream: "PRESENCE:prev_pose_rect_from_landmarks_is_present"
|
||||
}
|
||||
|
||||
# Calculates size of the image.
|
||||
node {
|
||||
calculator: "ImagePropertiesCalculator"
|
||||
input_stream: "IMAGE_GPU:image"
|
||||
output_stream: "SIZE:image_size"
|
||||
}
|
||||
|
||||
# Drops the incoming image if PoseLandmarkUpperBodyByRoiGpu was able to identify
|
||||
# pose presence in the previous image. Otherwise, passes the incoming image
|
||||
# through to trigger a new round of pose detection in PoseDetectionGpu.
|
||||
node {
|
||||
calculator: "GateCalculator"
|
||||
input_stream: "image"
|
||||
input_stream: "image_size"
|
||||
input_stream: "DISALLOW:prev_pose_rect_from_landmarks_is_present"
|
||||
output_stream: "image_for_pose_detection"
|
||||
output_stream: "image_size_for_pose_detection"
|
||||
options: {
|
||||
[mediapipe.GateCalculatorOptions.ext] {
|
||||
empty_packets_as_allow: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Detects poses.
|
||||
node {
|
||||
calculator: "PoseDetectionGpu"
|
||||
input_stream: "IMAGE:image_for_pose_detection"
|
||||
output_stream: "DETECTIONS:pose_detections"
|
||||
}
|
||||
|
||||
# Gets the very first detection from "pose_detections" vector.
|
||||
node {
|
||||
calculator: "SplitDetectionVectorCalculator"
|
||||
input_stream: "pose_detections"
|
||||
output_stream: "pose_detection"
|
||||
options: {
|
||||
[mediapipe.SplitVectorCalculatorOptions.ext] {
|
||||
ranges: { begin: 0 end: 1 }
|
||||
element_only: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Calculates region of interest based on pose detection, so that can be used
|
||||
# to detect landmarks.
|
||||
node {
|
||||
calculator: "PoseDetectionToRoi"
|
||||
input_stream: "DETECTION:pose_detection"
|
||||
input_stream: "IMAGE_SIZE:image_size_for_pose_detection"
|
||||
output_stream: "ROI:pose_rect_from_detection"
|
||||
}
|
||||
|
||||
# Selects either pose rect (or ROI) calculated from detection or from previously
|
||||
# detected landmarks if available (in this case, calculation of pose rect from
|
||||
# detection is skipped).
|
||||
node {
|
||||
calculator: "MergeCalculator"
|
||||
input_stream: "pose_rect_from_detection"
|
||||
input_stream: "prev_pose_rect_from_landmarks"
|
||||
output_stream: "pose_rect"
|
||||
}
|
||||
|
||||
# Detects pose landmarks within specified region of interest of the image.
|
||||
node {
|
||||
calculator: "PoseLandmarkUpperBodyByRoiGpu"
|
||||
input_stream: "IMAGE:image"
|
||||
input_stream: "ROI:pose_rect"
|
||||
output_stream: "LANDMARKS:pose_landmarks"
|
||||
}
|
||||
|
||||
# Calculates region of interest based on pose landmarks, so that can be reused
|
||||
# for subsequent image.
|
||||
node {
|
||||
calculator: "PoseLandmarkUpperBodyLandmarksToRoi"
|
||||
input_stream: "LANDMARKS:pose_landmarks"
|
||||
input_stream: "IMAGE_SIZE:image_size"
|
||||
output_stream: "ROI:pose_rect_from_landmarks"
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
# MediaPipe graph to calculate pose region of interest (ROI) from landmarks
|
||||
# detected by "PoseLandmarkUpperBodyByRoiCpu" or
|
||||
# "PoseLandmarkUpperBodyByRoiGpu".
|
||||
#
|
||||
# NOTE: this graph is subject to change and should not be used directly.
|
||||
|
||||
type: "PoseLandmarkUpperBodyLandmarksToRoi"
|
||||
|
||||
# Normalized landmarks. (NormalizedLandmarkList)
|
||||
input_stream: "LANDMARKS:landmarks"
|
||||
# Frame size (width & height). (std::pair<int, int>)
|
||||
input_stream: "IMAGE_SIZE:image_size"
|
||||
# ROI according to landmarks. (NormalizedRect)
|
||||
output_stream: "ROI:roi"
|
||||
|
||||
node {
|
||||
calculator: "SplitNormalizedLandmarkListCalculator"
|
||||
input_stream: "landmarks"
|
||||
output_stream: "alignment_landmarks"
|
||||
options: {
|
||||
[mediapipe.SplitVectorCalculatorOptions.ext] {
|
||||
ranges: { begin: 25 end: 27 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Converts landmarks to a detection that tightly encloses all landmarks.
|
||||
node {
|
||||
calculator: "LandmarksToDetectionCalculator"
|
||||
input_stream: "NORM_LANDMARKS:alignment_landmarks"
|
||||
output_stream: "DETECTION:detection"
|
||||
}
|
||||
|
||||
# Converts detection into a rectangle based on center and scale alignment
|
||||
# points.
|
||||
node {
|
||||
calculator: "AlignmentPointsRectsCalculator"
|
||||
input_stream: "DETECTION:detection"
|
||||
input_stream: "IMAGE_SIZE:image_size"
|
||||
output_stream: "NORM_RECT:raw_roi"
|
||||
options: {
|
||||
[mediapipe.DetectionsToRectsCalculatorOptions.ext] {
|
||||
rotation_vector_start_keypoint_index: 0
|
||||
rotation_vector_end_keypoint_index: 1
|
||||
rotation_vector_target_angle_degrees: 90
|
||||
output_zero_rect_for_empty_detections: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Expands pose rect with marging used during training.
|
||||
node {
|
||||
calculator: "RectTransformationCalculator"
|
||||
input_stream: "NORM_RECT:raw_roi"
|
||||
input_stream: "IMAGE_SIZE:image_size"
|
||||
output_stream: "roi"
|
||||
options: {
|
||||
[mediapipe.RectTransformationCalculatorOptions.ext] {
|
||||
scale_x: 1.5
|
||||
scale_y: 1.5
|
||||
square_long: true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,514 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
sodipodi:docname="upper_body_pose_key_points.svg"
|
||||
viewBox="0 0 1000 1000"
|
||||
height="1000"
|
||||
width="1000"
|
||||
id="svg901"
|
||||
version="1.1">
|
||||
<defs
|
||||
id="defs905">
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
id="DotL"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto">
|
||||
<path
|
||||
transform="matrix(0.8,0,0,0.8,5.92,0.8)"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
|
||||
d="m -2.5,-1 c 0,2.76 -2.24,5 -5,5 -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 z"
|
||||
id="path1015" />
|
||||
</marker>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
showgrid="false"
|
||||
id="namedview903"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff" />
|
||||
<g
|
||||
transform="translate(-4)"
|
||||
id="layer4">
|
||||
<ellipse
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.96674;stroke-miterlimit:4;stroke-dasharray:3.9335, 3.9335;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
|
||||
id="path1424"
|
||||
cx="505.18814"
|
||||
cy="305.81415"
|
||||
rx="111.30894"
|
||||
ry="126.60618" />
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-4)"
|
||||
style="display:inline"
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:9.07087;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 89.266999,515.88093 31.432341,-69.15117 50.29176,67.05568 z"
|
||||
id="path915" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccccccc"
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:9.07087;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 175.95642,465.32495 -4.96532,48.46049 92.20155,41.9098 96.39253,-92.20155 46.10078,259.84073 H 600.5665 L 648.76282,463.49369 H 359.58518"
|
||||
id="path919" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:9.07087;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 648.76282,463.49369 94.29696,96.39252 94.29708,-44.00528 -2.09544,-52.38724"
|
||||
id="path921" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:9.07087;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 887.64862,446.72976 -50.29176,69.15117 77.53308,-4.19098 z"
|
||||
id="path923" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccccc"
|
||||
id="path950"
|
||||
d="m 399.14743,295.40655 53.09262,-24.12475 51.926,49.7624 51.92599,-49.7624 54.59156,24.12475"
|
||||
style="fill:none;stroke:#000000;stroke-width:9.07087;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
|
||||
<path
|
||||
id="path952"
|
||||
d="m 473.87588,362.15229 h 58.41674"
|
||||
style="fill:none;stroke:#000000;stroke-width:9.07087;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-4)"
|
||||
style="display:inline"
|
||||
id="layer2">
|
||||
<circle
|
||||
cy="294.37057"
|
||||
cx="404.98865"
|
||||
id="path1237"
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
id="ellipse1275"
|
||||
cx="431.4061"
|
||||
cy="269.62979"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
cy="269.62979"
|
||||
cx="452.83154"
|
||||
id="ellipse1277"
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
id="ellipse1279"
|
||||
cx="473.70761"
|
||||
cy="270.17917"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
cy="271.27792"
|
||||
cx="535.23712"
|
||||
id="ellipse1281"
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
id="ellipse1283"
|
||||
cx="556.6626"
|
||||
cy="271.27792"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
cy="271.82727"
|
||||
cx="577.53864"
|
||||
id="ellipse1285"
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
id="ellipse1287"
|
||||
cx="503.92303"
|
||||
cy="320.92383"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
cy="297.1174"
|
||||
cx="610.31799"
|
||||
id="ellipse1289"
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
id="ellipse1291"
|
||||
cx="534.13843"
|
||||
cy="362.67599"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
cy="362.67599"
|
||||
cx="475.90512"
|
||||
id="ellipse1293"
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
id="ellipse1295"
|
||||
cx="359.83167"
|
||||
cy="462.78937"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
cy="462.78937"
|
||||
cx="648.56439"
|
||||
id="ellipse1297"
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
id="ellipse1299"
|
||||
cx="742.87408"
|
||||
cy="560.00092"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
cy="516.47339"
|
||||
cx="837.18384"
|
||||
id="ellipse1301"
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
id="ellipse1303"
|
||||
cx="835.73291"
|
||||
cy="470.04398"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
cy="446.82925"
|
||||
cx="887.96594"
|
||||
id="ellipse1305"
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
id="ellipse1307"
|
||||
cx="914.08246"
|
||||
cy="510.66968"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
cy="516.47339"
|
||||
cx="91.41188"
|
||||
id="ellipse1309"
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
id="ellipse1311"
|
||||
cx="118.97932"
|
||||
cy="448.28018"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
cy="468.59305"
|
||||
cx="175.51056"
|
||||
id="ellipse1313"
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
id="ellipse1315"
|
||||
cx="168.31055"
|
||||
cy="510.66965"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
cy="554.1972"
|
||||
cx="261.16931"
|
||||
id="ellipse1317"
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
id="ellipse1319"
|
||||
cx="403.35931"
|
||||
cy="722.50366"
|
||||
r="8.8288517" />
|
||||
<circle
|
||||
cy="722.50366"
|
||||
cx="600.68414"
|
||||
id="ellipse1321"
|
||||
style="fill:#de2b00;fill-opacity:1;stroke:#000000;stroke-width:1.5423;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
r="8.8288517" />
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-4)"
|
||||
id="layer3">
|
||||
<text
|
||||
id="text1326"
|
||||
y="255.16025"
|
||||
x="419.60342"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:32px;stroke-width:1.2"
|
||||
y="255.16025"
|
||||
x="419.60342"
|
||||
id="tspan1324"
|
||||
sodipodi:role="line">3</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
x="442.68222"
|
||||
y="255.16025"
|
||||
id="text1330"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1328"
|
||||
x="442.68222"
|
||||
y="255.16025"
|
||||
style="font-size:32px;stroke-width:1.2">2</tspan></text>
|
||||
<text
|
||||
id="text1334"
|
||||
y="255.16025"
|
||||
x="464.07236"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:32px;stroke-width:1.2"
|
||||
y="255.16025"
|
||||
x="464.07236"
|
||||
id="tspan1332"
|
||||
sodipodi:role="line">1</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
x="524.30243"
|
||||
y="255.16025"
|
||||
id="text1338"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1336"
|
||||
x="524.30243"
|
||||
y="255.16025"
|
||||
style="font-size:32px;stroke-width:1.2">4</tspan></text>
|
||||
<text
|
||||
id="text1342"
|
||||
y="255.16025"
|
||||
x="546.92578"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:32px;stroke-width:1.2"
|
||||
y="255.16025"
|
||||
x="546.92578"
|
||||
id="tspan1340"
|
||||
sodipodi:role="line">5</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
x="568.77136"
|
||||
y="255.16025"
|
||||
id="text1346"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1344"
|
||||
x="568.77136"
|
||||
y="255.16025"
|
||||
style="font-size:32px;stroke-width:1.2">6</tspan></text>
|
||||
<text
|
||||
id="text1350"
|
||||
y="303.00659"
|
||||
x="493.90598"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:32px;stroke-width:1.2"
|
||||
y="303.00659"
|
||||
x="493.90598"
|
||||
id="tspan1348"
|
||||
sodipodi:role="line">0</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
x="368.57889"
|
||||
y="309.91464"
|
||||
id="text1354"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1352"
|
||||
x="368.57889"
|
||||
y="309.91464"
|
||||
style="font-size:32px;stroke-width:1.2">7</tspan></text>
|
||||
<text
|
||||
id="text1358"
|
||||
y="310.21472"
|
||||
x="623.38293"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:32px;stroke-width:1.2"
|
||||
y="310.21472"
|
||||
x="623.38293"
|
||||
id="tspan1356"
|
||||
sodipodi:role="line">8</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
x="439.60062"
|
||||
y="374.48364"
|
||||
id="text1362"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1360"
|
||||
x="439.60062"
|
||||
y="374.48364"
|
||||
style="font-size:32px;stroke-width:1.2">9</tspan></text>
|
||||
<text
|
||||
id="text1366"
|
||||
y="375.58969"
|
||||
x="544.67633"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:32px;stroke-width:1.2"
|
||||
y="375.58969"
|
||||
x="544.67633"
|
||||
id="tspan1364"
|
||||
sodipodi:role="line">10</tspan></text>
|
||||
<text
|
||||
id="text1370"
|
||||
y="445.61142"
|
||||
x="338.9014"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:32px;stroke-width:1.2"
|
||||
y="445.61142"
|
||||
x="338.9014"
|
||||
id="tspan1368"
|
||||
sodipodi:role="line">11</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
x="630.0719"
|
||||
y="445.61142"
|
||||
id="text1374"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1372"
|
||||
x="630.0719"
|
||||
y="445.61142"
|
||||
style="font-size:32px;stroke-width:1.2">12</tspan></text>
|
||||
<text
|
||||
id="text1378"
|
||||
y="445.61142"
|
||||
x="817.3042"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:32px;stroke-width:1.2"
|
||||
y="445.61142"
|
||||
x="817.3042"
|
||||
id="tspan1376"
|
||||
sodipodi:role="line">22</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
x="161.83781"
|
||||
y="447.44272"
|
||||
id="text1382"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1380"
|
||||
x="161.83781"
|
||||
y="447.44272"
|
||||
style="font-size:32px;stroke-width:1.2">21</tspan></text>
|
||||
<text
|
||||
id="text1386"
|
||||
y="427.29883"
|
||||
x="97.174942"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:32px;stroke-width:1.2"
|
||||
y="427.29883"
|
||||
x="97.174942"
|
||||
id="tspan1384"
|
||||
sodipodi:role="line">19</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
x="62.824665"
|
||||
y="557.31836"
|
||||
id="text1390"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1388"
|
||||
x="62.824665"
|
||||
y="557.31836"
|
||||
style="font-size:32px;stroke-width:1.2">17</tspan></text>
|
||||
<text
|
||||
id="text1394"
|
||||
y="559.1496"
|
||||
x="145.23141"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:32px;stroke-width:1.2"
|
||||
y="559.1496"
|
||||
x="145.23141"
|
||||
id="tspan1392"
|
||||
sodipodi:role="line">15</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
x="238.75079"
|
||||
y="531.68073"
|
||||
id="text1398"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1396"
|
||||
x="238.75079"
|
||||
y="531.68073"
|
||||
style="font-size:32px;stroke-width:1.2">13</tspan></text>
|
||||
<text
|
||||
id="text1402"
|
||||
y="535.3432"
|
||||
x="736.7287"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:32px;stroke-width:1.2"
|
||||
y="535.3432"
|
||||
x="736.7287"
|
||||
id="tspan1400"
|
||||
sodipodi:role="line">4</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
x="826.46051"
|
||||
y="560.9809"
|
||||
id="text1406"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1404"
|
||||
x="826.46051"
|
||||
y="560.9809"
|
||||
style="font-size:32px;stroke-width:1.2">16</tspan></text>
|
||||
<text
|
||||
id="text1410"
|
||||
y="551.94958"
|
||||
x="913.7923"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:32px;stroke-width:1.2"
|
||||
y="551.94958"
|
||||
x="913.7923"
|
||||
id="tspan1408"
|
||||
sodipodi:role="line">18</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
x="896.0484"
|
||||
y="432.79257"
|
||||
id="text1414"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1412"
|
||||
x="896.0484"
|
||||
y="432.79257"
|
||||
style="font-size:32px;stroke-width:1.2">20</tspan></text>
|
||||
<text
|
||||
id="text1418"
|
||||
y="735.64441"
|
||||
x="615.86548"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:32px;stroke-width:1.2"
|
||||
y="735.64441"
|
||||
x="615.86548"
|
||||
id="tspan1416"
|
||||
sodipodi:role="line">24</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:48px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.2"
|
||||
x="346.6701"
|
||||
y="736.78186"
|
||||
id="text1422"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1420"
|
||||
x="346.6701"
|
||||
y="736.78186"
|
||||
style="font-size:32px;stroke-width:1.2">23</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 21 KiB |
Reference in New Issue
Block a user