Compare commits

..
10 Commits
Author SHA1 Message Date
MediaPipe Teamandchuoling 8f69af91fe Project import generated by Copybara.
GitOrigin-RevId: 947096e4fc99d6b974e9f50d360d7c0a75072c5d
2020-09-17 11:52:51 -04:00
MediaPipe Teamandchuoling a908d668c7 Project import generated by Copybara.
GitOrigin-RevId: 4cee4a2c2317fb190680c17e31ebbb03bb73b71c
2020-09-17 11:09:17 -04:00
MediaPipe Teamandjqtang 1db91b550a Project import generated by Copybara.
GitOrigin-RevId: c27bb068d24890a4e3b7d2d536267e08d7e4abbd
2020-08-31 16:03:43 -07:00
MediaPipe Teamandchuoling c0124fb83c Project import generated by Copybara.
GitOrigin-RevId: 612e50bb8db2ec3dc1c30049372d87a80c3848db
2020-08-30 19:52:55 -04:00
MediaPipe Teamandchuoling a7225b938a Project import generated by Copybara.
GitOrigin-RevId: aaca5c37abcf8b7a6c3c28804739afdbad46e704
2020-08-13 15:10:54 -04:00
MediaPipe Teamandchuoling 73f4475c17 Project import generated by Copybara.
GitOrigin-RevId: 5cd50ad1abca6bd5d52832975bb60e6c0fc60a27
2020-08-13 04:32:11 -04:00
MediaPipe Teamandchuoling f57ff46845 Project import generated by Copybara.
GitOrigin-RevId: 933c6691064eac6ce3f2df6908611aefbed1d194
2020-08-13 02:18:32 -04:00
MediaPipe Teamandchuoling d7c287c4e9 Project import generated by Copybara.
GitOrigin-RevId: 9295f8ea2339edb71073695ed4fb3fded2f48c60
2020-08-13 01:32:08 -04:00
MediaPipe Teamandchuoling 6b0ab0e012 Project import generated by Copybara.
GitOrigin-RevId: e9b30181bd6a75481835643d6f48c1c05730ff87
2020-08-05 22:14:25 -04:00
MediaPipe Teamandchuoling 2f86a459b6 Project import generated by Copybara.
GitOrigin-RevId: 5b23708185311ae39a8605b0c2eff721e7b4939f
2020-08-05 20:27:31 -04:00
577 changed files with 60471 additions and 6698 deletions
+1 -1
View File
@@ -12,6 +12,6 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
licenses(["notice"]) # Apache 2.0 licenses(["notice"])
exports_files(["LICENSE"]) exports_files(["LICENSE"])
+1 -1
View File
@@ -54,7 +54,7 @@ RUN pip3 install tf_slim
RUN ln -s /usr/bin/python3 /usr/bin/python RUN ln -s /usr/bin/python3 /usr/bin/python
# Install bazel # Install bazel
ARG BAZEL_VERSION=2.0.0 ARG BAZEL_VERSION=3.0.0
RUN mkdir /bazel && \ RUN mkdir /bazel && \
wget --no-check-certificate -O /bazel/installer.sh "https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/b\ wget --no-check-certificate -O /bazel/installer.sh "https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/b\
azel-${BAZEL_VERSION}-installer-linux-x86_64.sh" && \ azel-${BAZEL_VERSION}-installer-linux-x86_64.sh" && \
+11
View File
@@ -0,0 +1,11 @@
global-exclude .git*
global-exclude *_test.py
include CONTRIBUTING.md
include LICENSE
include MANIFEST.in
include README.md
include requirements.txt
recursive-include mediapipe/modules *.tflite *.txt
recursive-include mediapipe/graphs *.binarypb
+37 -19
View File
@@ -22,30 +22,37 @@ desktop/cloud, web and IoT devices.
## ML solutions in MediaPipe ## ML solutions in MediaPipe
Face Detection | Face Mesh | Hands | Hair Segmentation Face Detection | Face Mesh | Iris | Hands | Pose | Hair Segmentation
:----------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------: | :---------------: :----------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------: | :---------------:
[![face_detection](docs/images/mobile/face_detection_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/face_detection) | [![face_mesh](docs/images/mobile/face_mesh_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/face_mesh) | [![hand](docs/images/mobile/hand_tracking_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/hands) | [![hair_segmentation](docs/images/mobile/hair_segmentation_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/hair_segmentation) [![face_detection](docs/images/mobile/face_detection_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/face_detection) | [![face_mesh](docs/images/mobile/face_mesh_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/face_mesh) | [![iris](docs/images/mobile/iris_tracking_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/iris) | [![hand](docs/images/mobile/hand_tracking_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/hands) | [![pose](docs/images/mobile/pose_tracking_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/pose) | [![hair_segmentation](docs/images/mobile/hair_segmentation_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/hair_segmentation)
Object Detection | Box Tracking | Objectron | KNIFT Object Detection | Box Tracking | Instant Motion Tracking | Objectron | KNIFT
:----------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------: | :---: :----------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------: | :---:
[![object_detection](docs/images/mobile/object_detection_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/object_detection) | [![box_tracking](docs/images/mobile/object_tracking_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/box_tracking) | [![objectron](docs/images/mobile/objectron_chair_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/objectron) | [![knift](docs/images/mobile/template_matching_android_cpu_small.gif)](https://google.github.io/mediapipe/solutions/knift) [![object_detection](docs/images/mobile/object_detection_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/object_detection) | [![box_tracking](docs/images/mobile/object_tracking_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/box_tracking) | [![instant_motion_tracking](docs/images/mobile/instant_motion_tracking_android_small.gif)](https://google.github.io/mediapipe/solutions/instant_motion_tracking) | [![objectron](docs/images/mobile/objectron_chair_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/objectron) | [![knift](docs/images/mobile/template_matching_android_cpu_small.gif)](https://google.github.io/mediapipe/solutions/knift)
<!-- []() in the first cell is needed to preserve table formatting in GitHub Pages. --> <!-- []() in the first cell is needed to preserve table formatting in GitHub Pages. -->
<!-- Whenever this table is updated, paste a copy to solutions/solutions.md. --> <!-- Whenever this table is updated, paste a copy to solutions/solutions.md. -->
[]() | Android | iOS | Desktop | Web | Coral []() | Android | iOS | Desktop | Python | Web | Coral
:---------------------------------------------------------------------------- | :-----: | :-: | :-----: | :-: | :---: :---------------------------------------------------------------------------------------- | :-----: | :-: | :-----: | :----: | :-: | :---:
[Face Detection](https://google.github.io/mediapipe/solutions/face_detection) | ✅ | ✅ | ✅ | ✅ | ✅ [Face Detection](https://google.github.io/mediapipe/solutions/face_detection) | ✅ | ✅ | ✅ | | ✅ | ✅
[Face Mesh](https://google.github.io/mediapipe/solutions/face_mesh) | ✅ | ✅ | ✅ | | [Face Mesh](https://google.github.io/mediapipe/solutions/face_mesh) | ✅ | ✅ | ✅ | | |
[Hands](https://google.github.io/mediapipe/solutions/hands) | ✅ | ✅ | ✅ | ✅ | [Iris](https://google.github.io/mediapipe/solutions/iris) | ✅ | ✅ | ✅ | | ✅ |
[Hair Segmentation](https://google.github.io/mediapipe/solutions/hair_segmentation) | ✅ | | ✅ | ✅ | [Hands](https://google.github.io/mediapipe/solutions/hands) | ✅ | | ✅ | | ✅ |
[Object Detection](https://google.github.io/mediapipe/solutions/object_detection) | ✅ | ✅ | ✅ | | ✅ [Pose](https://google.github.io/mediapipe/solutions/pose) | ✅ | ✅ | ✅ | | ✅ |
[Box Tracking](https://google.github.io/mediapipe/solutions/box_tracking) | ✅ | | ✅ | | [Hair Segmentation](https://google.github.io/mediapipe/solutions/hair_segmentation) | ✅ | | ✅ | | ✅ |
[Objectron](https://google.github.io/mediapipe/solutions/objectron) | ✅ | | | | [Object Detection](https://google.github.io/mediapipe/solutions/object_detection) | ✅ | ✅ | ✅ | | |
[KNIFT](https://google.github.io/mediapipe/solutions/knift) | ✅ | | | | [Box Tracking](https://google.github.io/mediapipe/solutions/box_tracking) | ✅ | ✅ | ✅ | | |
[AutoFlip](https://google.github.io/mediapipe/solutions/autoflip) | | | ✅ | | [Instant Motion Tracking](https://google.github.io/mediapipe/solutions/instant_motion_tracking) | ✅ | | | | |
[MediaSequence](https://google.github.io/mediapipe/solutions/media_sequence) | | | | | [Objectron](https://google.github.io/mediapipe/solutions/objectron) | ✅ | | | | |
[YouTube 8M](https://google.github.io/mediapipe/solutions/youtube_8m) | | | | | [KNIFT](https://google.github.io/mediapipe/solutions/knift) | | | | | |
[AutoFlip](https://google.github.io/mediapipe/solutions/autoflip) | | | ✅ | | |
[MediaSequence](https://google.github.io/mediapipe/solutions/media_sequence) | | | ✅ | | |
[YouTube 8M](https://google.github.io/mediapipe/solutions/youtube_8m) | | | ✅ | | |
See also
[MediaPipe Models and Model Cards](https://google.github.io/mediapipe/solutions/models)
for ML models released in MediaPipe.
## MediaPipe on the Web ## MediaPipe on the Web
@@ -63,8 +70,11 @@ never leaves your device.
![visualizer_runner](docs/images/visualizer_runner.png) ![visualizer_runner](docs/images/visualizer_runner.png)
* [MediaPipe Face Detection](https://viz.mediapipe.dev/demo/face_detection) * [MediaPipe Face Detection](https://viz.mediapipe.dev/demo/face_detection)
* [MediaPipe Iris](https://viz.mediapipe.dev/demo/iris_tracking)
* [MediaPipe Iris: Depth-from-Iris](https://viz.mediapipe.dev/demo/iris_depth)
* [MediaPipe Hands](https://viz.mediapipe.dev/demo/hand_tracking) * [MediaPipe Hands](https://viz.mediapipe.dev/demo/hand_tracking)
* [MediaPipe Hands (palm/hand detection only)](https://viz.mediapipe.dev/demo/hand_detection) * [MediaPipe Hands (palm/hand detection only)](https://viz.mediapipe.dev/demo/hand_detection)
* [MediaPipe Pose](https://viz.mediapipe.dev/demo/pose_tracking)
* [MediaPipe Hair Segmentation](https://viz.mediapipe.dev/demo/hair_segmentation) * [MediaPipe Hair Segmentation](https://viz.mediapipe.dev/demo/hair_segmentation)
## Getting started ## Getting started
@@ -83,6 +93,14 @@ run code search using
## Publications ## Publications
* [MediaPipe 3D Face Transform](https://mediapipe.page.link/face-geometry-blog)
in Google Developers Blog
* [Instant Motion Tracking With MediaPipe](https://developers.googleblog.com/2020/08/instant-motion-tracking-with-mediapipe.html)
in Google Developers Blog
* [BlazePose - On-device Real-time Body Pose Tracking](https://ai.googleblog.com/2020/08/on-device-real-time-body-pose-tracking.html)
in Google AI Blog
* [MediaPipe Iris: Real-time Eye Tracking and Depth Estimation](https://ai.googleblog.com/2020/08/mediapipe-iris-real-time-iris-tracking.html)
in Google AI Blog
* [MediaPipe KNIFT: Template-based feature matching](https://developers.googleblog.com/2020/04/mediapipe-knift-template-based-feature-matching.html) * [MediaPipe KNIFT: Template-based feature matching](https://developers.googleblog.com/2020/04/mediapipe-knift-template-based-feature-matching.html)
in Google Developers Blog in Google Developers Blog
* [Alfred Camera: Smart camera features using MediaPipe](https://developers.googleblog.com/2020/03/alfred-camera-smart-camera-features-using-mediapipe.html) * [Alfred Camera: Smart camera features using MediaPipe](https://developers.googleblog.com/2020/03/alfred-camera-smart-camera-features-using-mediapipe.html)
+64 -26
View File
@@ -36,6 +36,19 @@ http_archive(
urls = ["https://github.com/bazelbuild/rules_cc/archive/master.zip"], urls = ["https://github.com/bazelbuild/rules_cc/archive/master.zip"],
) )
http_archive(
name = "rules_foreign_cc",
strip_prefix = "rules_foreign_cc-master",
url = "https://github.com/bazelbuild/rules_foreign_cc/archive/master.zip",
)
load("@rules_foreign_cc//:workspace_definitions.bzl", "rules_foreign_cc_dependencies")
rules_foreign_cc_dependencies()
# This is used to select all contents of the archives for CMake-based packages to give CMake access to them.
all_content = """filegroup(name = "all", srcs = glob(["**"]), visibility = ["//visibility:public"])"""
# GoogleTest/GoogleMock framework. Used by most unit-tests. # GoogleTest/GoogleMock framework. Used by most unit-tests.
# Last updated 2020-06-30. # Last updated 2020-06-30.
http_archive( http_archive(
@@ -68,14 +81,23 @@ http_archive(
url = "https://github.com/gflags/gflags/archive/v2.2.2.zip", url = "https://github.com/gflags/gflags/archive/v2.2.2.zip",
) )
# glog v0.3.5 # 2020-08-21
# TODO: Migrate MediaPipe to use com_github_glog_glog on all platforms.
http_archive( http_archive(
name = "com_github_glog_glog_v_0_3_5", name = "com_github_glog_glog",
url = "https://github.com/google/glog/archive/v0.3.5.zip", strip_prefix = "glog-0a2e5931bd5ff22fd3bf8999eb8ce776f159cda6",
sha256 = "267103f8a1e9578978aa1dc256001e6529ef593e5aea38193d31c2872ee025e8", sha256 = "58c9b3b6aaa4dd8b836c0fd8f65d0f941441fb95e27212c5eeb9979cfd3592ab",
strip_prefix = "glog-0.3.5", urls = [
build_file = "@//third_party:glog.BUILD", "https://github.com/google/glog/archive/0a2e5931bd5ff22fd3bf8999eb8ce776f159cda6.zip",
],
)
http_archive(
name = "com_github_glog_glog_no_gflags",
strip_prefix = "glog-0a2e5931bd5ff22fd3bf8999eb8ce776f159cda6",
sha256 = "58c9b3b6aaa4dd8b836c0fd8f65d0f941441fb95e27212c5eeb9979cfd3592ab",
build_file = "@//third_party:glog_no_gflags.BUILD",
urls = [
"https://github.com/google/glog/archive/0a2e5931bd5ff22fd3bf8999eb8ce776f159cda6.zip",
],
patches = [ patches = [
"@//third_party:com_github_glog_glog_9779e5ea6ef59562b030248947f787d1256132ae.diff" "@//third_party:com_github_glog_glog_9779e5ea6ef59562b030248947f787d1256132ae.diff"
], ],
@@ -84,16 +106,6 @@ http_archive(
], ],
) )
# 2020-02-16
http_archive(
name = "com_github_glog_glog",
strip_prefix = "glog-3ba8976592274bc1f907c402ce22558011d6fc5e",
sha256 = "feca3c7e29a693cab7887409756d89d342d4a992d54d7c5599bebeae8f7b50be",
urls = [
"https://github.com/google/glog/archive/3ba8976592274bc1f907c402ce22558011d6fc5e.zip",
],
)
# easyexif # easyexif
http_archive( http_archive(
name = "easyexif", name = "easyexif",
@@ -137,6 +149,25 @@ http_archive(
urls = ["https://github.com/google/multichannel-audio-tools/archive/master.zip"], urls = ["https://github.com/google/multichannel-audio-tools/archive/master.zip"],
) )
# 2020-07-09
http_archive(
name = "pybind11_bazel",
strip_prefix = "pybind11_bazel-203508e14aab7309892a1c5f7dd05debda22d9a5",
urls = ["https://github.com/pybind/pybind11_bazel/archive/203508e14aab7309892a1c5f7dd05debda22d9a5.zip"],
sha256 = "75922da3a1bdb417d820398eb03d4e9bd067c4905a4246d35a44c01d62154d91",
)
http_archive(
name = "pybind11",
urls = [
"https://storage.googleapis.com/mirror.tensorflow.org/github.com/pybind/pybind11/archive/v2.4.3.tar.gz",
"https://github.com/pybind/pybind11/archive/v2.4.3.tar.gz",
],
sha256 = "1eed57bc6863190e35637290f97a20c81cfe4d9090ac0a24f3bbf08f265eb71d",
strip_prefix = "pybind11-2.4.3",
build_file = "@pybind11_bazel//:pybind11.BUILD",
)
http_archive( http_archive(
name = "ceres_solver", name = "ceres_solver",
url = "https://github.com/ceres-solver/ceres-solver/archive/1.14.0.zip", url = "https://github.com/ceres-solver/ceres-solver/archive/1.14.0.zip",
@@ -150,6 +181,13 @@ http_archive(
sha256 = "5ba6d0db4e784621fda44a50c58bb23b0892684692f0c623e2063f9c19f192f1" sha256 = "5ba6d0db4e784621fda44a50c58bb23b0892684692f0c623e2063f9c19f192f1"
) )
http_archive(
name = "opencv",
build_file_content = all_content,
strip_prefix = "opencv-3.4.10",
urls = ["https://github.com/opencv/opencv/archive/3.4.10.tar.gz"],
)
new_local_repository( new_local_repository(
name = "linux_opencv", name = "linux_opencv",
build_file = "@//third_party:opencv_linux.BUILD", build_file = "@//third_party:opencv_linux.BUILD",
@@ -165,13 +203,13 @@ new_local_repository(
new_local_repository( new_local_repository(
name = "macos_opencv", name = "macos_opencv",
build_file = "@//third_party:opencv_macos.BUILD", build_file = "@//third_party:opencv_macos.BUILD",
path = "/usr", path = "/usr/local/opt/opencv@3",
) )
new_local_repository( new_local_repository(
name = "macos_ffmpeg", name = "macos_ffmpeg",
build_file = "@//third_party:ffmpeg_macos.BUILD", build_file = "@//third_party:ffmpeg_macos.BUILD",
path = "/usr", path = "/usr/local/opt/ffmpeg",
) )
new_local_repository( new_local_repository(
@@ -282,9 +320,6 @@ load("@rules_jvm_external//:defs.bzl", "maven_install")
maven_install( maven_install(
name = "maven", name = "maven",
artifacts = [ artifacts = [
"junit:junit:4.12",
"androidx.test.espresso:espresso-core:3.1.1",
"org.hamcrest:hamcrest-library:1.3",
"androidx.concurrent:concurrent-futures:1.0.0-alpha03", "androidx.concurrent:concurrent-futures:1.0.0-alpha03",
"androidx.lifecycle:lifecycle-common:2.2.0", "androidx.lifecycle:lifecycle-common:2.2.0",
"androidx.annotation:annotation:aar:1.1.0", "androidx.annotation:annotation:aar:1.1.0",
@@ -295,11 +330,15 @@ maven_install(
"androidx.core:core:aar:1.1.0-rc03", "androidx.core:core:aar:1.1.0-rc03",
"androidx.legacy:legacy-support-v4:aar:1.0.0", "androidx.legacy:legacy-support-v4:aar:1.0.0",
"androidx.recyclerview:recyclerview:aar:1.1.0-beta02", "androidx.recyclerview:recyclerview:aar:1.1.0-beta02",
"androidx.test.espresso:espresso-core:3.1.1",
"com.github.bumptech.glide:glide:4.11.0",
"com.google.android.material:material:aar:1.0.0-rc01", "com.google.android.material:material:aar:1.0.0-rc01",
"com.google.code.findbugs:jsr305:3.0.2", "com.google.code.findbugs:jsr305:3.0.2",
"com.google.flogger:flogger-system-backend:0.3.1", "com.google.flogger:flogger-system-backend:0.3.1",
"com.google.flogger:flogger:0.3.1", "com.google.flogger:flogger:0.3.1",
"com.google.guava:guava:27.0.1-android", "com.google.guava:guava:27.0.1-android",
"junit:junit:4.12",
"org.hamcrest:hamcrest-library:1.3",
], ],
repositories = [ repositories = [
"https://jcenter.bintray.com", "https://jcenter.bintray.com",
@@ -323,13 +362,12 @@ http_archive(
) )
#Tensorflow repo should always go after the other external dependencies. #Tensorflow repo should always go after the other external dependencies.
# 2020-05-11 # 2020-08-30
_TENSORFLOW_GIT_COMMIT = "7c09d15f9fcc14343343c247ebf5b8e0afe3e4aa" _TENSORFLOW_GIT_COMMIT = "57b009e31e59bd1a7ae85ef8c0232ed86c9b71db"
_TENSORFLOW_SHA256= "673d00cbd2676ae43df1993e0d28c10b5ffbe96d9e2ab29f88a77b43c0211299" _TENSORFLOW_SHA256= "de7f5f06204e057383028c7e53f3b352cdf85b3a40981b1a770c9a415a792c0e"
http_archive( http_archive(
name = "org_tensorflow", name = "org_tensorflow",
urls = [ urls = [
"https://mirror.bazel.build/github.com/tensorflow/tensorflow/archive/%s.tar.gz" % _TENSORFLOW_GIT_COMMIT,
"https://github.com/tensorflow/tensorflow/archive/%s.tar.gz" % _TENSORFLOW_GIT_COMMIT, "https://github.com/tensorflow/tensorflow/archive/%s.tar.gz" % _TENSORFLOW_GIT_COMMIT,
], ],
patches = [ patches = [
+108
View File
@@ -0,0 +1,108 @@
#!/bin/bash
# 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.
# =========================================================================
#
# Script to build/run all MediaPipe desktop example apps (with webcam input).
#
# To build and run all apps and store them in out_dir:
# $ ./build_ios_examples.sh -d out_dir
# Omitting -d and the associated directory saves all generated apps in the
# current directory.
# To build all apps and store them in out_dir:
# $ ./build_ios_examples.sh -d out_dir -b
# Omitting -d and the associated directory saves all generated apps in the
# current directory.
# To run all apps already stored in out_dir:
# $ ./build_ios_examples.sh -d out_dir -r
# Omitting -d and the associated directory assumes all apps are in the current
# directory.
set -e
out_dir="."
build_only=false
run_only=false
app_dir="mediapipe/examples/desktop"
bin_dir="bazel-bin"
declare -a default_bazel_flags=(build -c opt --define MEDIAPIPE_DISABLE_GPU=1)
while [[ -n $1 ]]; do
case $1 in
-d)
shift
out_dir=$1
;;
-b)
build_only=true
;;
-r)
run_only=true
;;
*)
echo "Unsupported input argument $1."
exit 1
;;
esac
shift
done
echo "app_dir: $app_dir"
echo "out_dir: $out_dir"
declare -a bazel_flags
apps="${app_dir}/*"
for app in ${apps}; do
if [[ -d "${app}" ]]; then
target_name=${app##*/}
if [[ "${target_name}" == "autoflip" ||
"${target_name}" == "hello_world" ||
"${target_name}" == "media_sequence" ||
"${target_name}" == "template_matching" ||
"${target_name}" == "youtube8m" ]]; then
continue
fi
target="${app}:${target_name}_cpu"
echo "=== Target: ${target}"
if [[ $run_only == false ]]; then
bazel_flags=("${default_bazel_flags[@]}")
bazel_flags+=(${target})
bazel "${bazel_flags[@]}"
cp -f "${bin_dir}/${app}/"*"_cpu" "${out_dir}"
fi
if [[ $build_only == false ]]; then
if [[ ${target_name} == "multi_hand_tracking" ]]; then
graph_name="hand_tracking/multi_hand_tracking"
elif [[ ${target_name} == "object_tracking" ]]; then
graph_name="tracking/object_detection_tracking"
elif [[ ${target_name} == "upper_body_pose_tracking" ]]; then
graph_name="pose_tracking/upper_body_pose_tracking"
else
graph_name="${target_name}/${target_name}"
fi
if [[ ${target_name} == "iris_tracking" ||
${target_name} == "upper_body_pose_tracking" ]]; then
graph_suffix="cpu"
else
graph_suffix="desktop_live"
fi
GLOG_logtostderr=1 "${out_dir}/${target_name}_cpu" \
--calculator_graph_config_file=mediapipe/graphs/"${graph_name}_${graph_suffix}.pbtxt"
fi
fi
done
+3
View File
@@ -58,6 +58,9 @@ apps="${app_dir}/*"
for app in ${apps}; do for app in ${apps}; do
if [[ -d "${app}" ]]; then if [[ -d "${app}" ]]; then
target_name=${app##*/} target_name=${app##*/}
if [[ "${target_name}" == "common" ]]; then
continue
fi
target="${app}:${target_name}" target="${app}:${target_name}"
echo "=== Target: ${target}" echo "=== Target: ${target}"
+2 -2
View File
@@ -22,8 +22,8 @@ aux_links:
# Footer content appears at the bottom of every page's main content # Footer content appears at the bottom of every page's main content
footer_content: "&copy; 2020 GOOGLE LLC | <a href=\"https://policies.google.com/privacy\">PRIVACY POLICY</a> | <a href=\"https://policies.google.com/terms\">TERMS OF SERVICE</a>" footer_content: "&copy; 2020 GOOGLE LLC | <a href=\"https://policies.google.com/privacy\">PRIVACY POLICY</a> | <a href=\"https://policies.google.com/terms\">TERMS OF SERVICE</a>"
# Color scheme currently only supports "dark" or nil (default) # Color scheme currently only supports "dark", "light"/nil (default), or a custom scheme that you define
color_scheme: nil color_scheme: mediapipe
# Google Analytics Tracking (optional) # Google Analytics Tracking (optional)
ga_tracking: UA-140696581-2 ga_tracking: UA-140696581-2
+1
View File
@@ -0,0 +1 @@
$link-color: #0097A7;
+3 -8
View File
@@ -402,11 +402,6 @@ node {
The diagram below shows how the `PacketClonerCalculator` defines its output The diagram below shows how the `PacketClonerCalculator` defines its output
packets (bottom) based on its series of input packets (top). packets (bottom) based on its series of input packets (top).
| ![Graph using | | ![Graph using PacketClonerCalculator](../images/packet_cloner_calculator.png) |
: PacketClonerCalculator](../images/packet_cloner_calculator.png) : | :---------------------------------------------------------------------------: |
| :--------------------------------------------------------------------------: | | *Each time it receives a packet on its TICK input stream, the PacketClonerCalculator outputs the most recent packet from each of its input streams. The sequence of output packets (bottom) is determined by the sequence of input packets (top) and their timestamps. The timestamps are shown along the right side of the diagram.* |
| *Each time it receives a packet on its TICK input stream, the |
: PacketClonerCalculator outputs the most recent packet from each of its input :
: streams. The sequence of output packets (bottom) is determined by the :
: sequence of input packets (top) and their timestamps. The timestamps are :
: shown along the right side of the diagram.* :
+1 -1
View File
@@ -20,7 +20,7 @@ Packets are generally created with `MediaPipe::Adopt()` (from packet.h).
```c++ ```c++
// Create some data. // Create some data.
auto data = gtl::MakeUnique<MyDataClass>("constructor_argument"); auto data = absl::make_unique<MyDataClass>("constructor_argument");
// Create a packet to own the data. // Create a packet to own the data.
Packet p = Adopt(data.release()); Packet p = Adopt(data.release());
// Make a new packet with the same data and a different timestamp. // Make a new packet with the same data and a different timestamp.
+93 -10
View File
@@ -280,16 +280,16 @@ are two options:
2. In the project navigator in the left sidebar, select the "Mediapipe" 2. In the project navigator in the left sidebar, select the "Mediapipe"
project. project.
3. Select the "Signing & Capabilities" tab. 3. Select one of the application targets, e.g. HandTrackingGpuApp.
4. Select one of the application targets, e.g. HandTrackingGpuApp. 4. Select the "Signing & Capabilities" tab.
5. Check "Automatically manage signing", and confirm the dialog box. 5. Check "Automatically manage signing", and confirm the dialog box.
6. Select "_Your Name_ (Personal Team)" in the Team pop-up menu. 6. Select "_Your Name_ (Personal Team)" in the Team pop-up menu.
7. This set-up needs to be done once for each application you want to install. 7. This set-up needs to be done once for each application you want to install.
Repeat steps 4-6 as needed. Repeat steps 3-6 as needed.
This generates provisioning profiles for each app you have selected. Now we need This generates provisioning profiles for each app you have selected. Now we need
to tell Bazel to use them. We have provided a script to make this easier. to tell Bazel to use them. We have provided a script to make this easier.
@@ -390,9 +390,6 @@ developer (yourself) is trusted.
bazel build -c opt --define MEDIAPIPE_DISABLE_GPU=1 mediapipe/examples/desktop/hand_tracking:hand_tracking_cpu bazel build -c opt --define MEDIAPIPE_DISABLE_GPU=1 mediapipe/examples/desktop/hand_tracking:hand_tracking_cpu
``` ```
This will open up your webcam as long as it is connected and on. Any errors
is likely due to your webcam being not accessible.
2. To run the application: 2. To run the application:
```bash ```bash
@@ -400,6 +397,9 @@ developer (yourself) is trusted.
--calculator_graph_config_file=mediapipe/graphs/hand_tracking/hand_tracking_desktop_live.pbtxt --calculator_graph_config_file=mediapipe/graphs/hand_tracking/hand_tracking_desktop_live.pbtxt
``` ```
This will open up your webcam as long as it is connected and on. Any errors
is likely due to your webcam being not accessible.
### Option 2: Running on GPU ### Option 2: Running on GPU
Note: This currently works only on Linux, and please first follow Note: This currently works only on Linux, and please first follow
@@ -412,13 +412,96 @@ Note: This currently works only on Linux, and please first follow
mediapipe/examples/desktop/hand_tracking:hand_tracking_gpu mediapipe/examples/desktop/hand_tracking:hand_tracking_gpu
``` ```
This will open up your webcam as long as it is connected and on. Any errors
is likely due to your webcam being not accessible, or GPU drivers not setup
properly.
2. To run the application: 2. To run the application:
```bash ```bash
GLOG_logtostderr=1 bazel-bin/mediapipe/examples/desktop/hand_tracking/hand_tracking_gpu \ GLOG_logtostderr=1 bazel-bin/mediapipe/examples/desktop/hand_tracking/hand_tracking_gpu \
--calculator_graph_config_file=mediapipe/graphs/hand_tracking/hand_tracking_mobile.pbtxt --calculator_graph_config_file=mediapipe/graphs/hand_tracking/hand_tracking_mobile.pbtxt
``` ```
This will open up your webcam as long as it is connected and on. Any errors
is likely due to your webcam being not accessible, or GPU drivers not setup
properly.
## Python
MediaPipe Python package is available on
[PyPI](https://pypi.org/project/mediapipe/), and can be installed simply by `pip
install mediapipe` on Linux and macOS, as described below in
[Run in python interpreter](#run-in-python-interpreter) and in this
[colab](https://mediapipe.page.link/mp-py-colab).
### Run in Python interpreter
Using [MediaPipe Pose](../solutions/pose.md) as an example:
```bash
# Activate a Python virtual environment.
$ python3 -m venv mp_env && source mp_env/bin/activate
# Install MediaPipe Python package
(mp_env)$ pip install mediapipe
# Run in Python interpreter
(mp_env)$ python3
>>> import mediapipe as mp
>>> pose_tracker = mp.examples.UpperBodyPoseTracker()
# For image input
>>> pose_landmarks, _ = pose_tracker.run(input_file='/path/to/input/file', output_file='/path/to/output/file')
>>> pose_landmarks, annotated_image = pose_tracker.run(input_file='/path/to/file')
# For live camera input
# (Press Esc within the output image window to stop the run or let it self terminate after 30 seconds.)
>>> pose_tracker.run_live()
# Close the tracker.
>>> pose_tracker.close()
```
Tip: Use command `deactivate` to exit the Python virtual environment.
### Building Python package from source
Follow these steps only if you have local changes and need to build the Python
package from source. Otherwise, we strongly encourage our users to simply run
`pip install mediapipe`, more convenient and much faster.
1. Make sure that Bazel and OpenCV are correctly installed and configured for
MediaPipe. Please see [Installation](./install.md) for how to setup Bazel
and OpenCV for MediaPipe on Linux and macOS.
2. Install the following dependencies.
```bash
# Debian or Ubuntu
$ sudo apt install python3-dev
$ sudo apt install python3-venv
$ sudo apt install -y protobuf-compiler
```
```bash
# macOS
$ brew install protobuf
```
3. Activate a Python virtual environment.
```bash
$ python3 -m venv mp_env && source mp_env/bin/activate
```
4. In the virtual environment, go to the MediaPipe repo directory.
5. Install the required Python packages.
```bash
(mp_env)mediapipe$ pip3 install -r requirements.txt
```
6. Generate and install MediaPipe package.
```bash
(mp_env)mediapipe$ python3 setup.py gen_protos
(mp_env)mediapipe$ python3 setup.py install --link-opencv
```
+35 -34
View File
@@ -18,8 +18,8 @@ This codelab uses MediaPipe on an iOS device.
### What you will learn ### What you will learn
How to develop an iOS application that uses MediaPipe and run a MediaPipe How to develop an iOS application that uses MediaPipe and run a MediaPipe graph
graph on iOS. on iOS.
### What you will build ### What you will build
@@ -42,8 +42,8 @@ We will be using the following graph, [`edge_detection_mobile_gpu.pbtxt`]:
``` ```
# MediaPipe graph that performs GPU Sobel edge detection on a live video stream. # MediaPipe graph that performs GPU Sobel edge detection on a live video stream.
# Used in the examples # Used in the examples
# mediapipe/examples/android/src/java/com/mediapipe/apps/edgedetectiongpu. # mediapipe/examples/android/src/java/com/google/mediapipe/apps/basic:helloworld
# mediapipe/examples/ios/edgedetectiongpu. # and mediapipe/examples/ios/helloworld.
# Images coming into and out of the graph. # Images coming into and out of the graph.
input_stream: "input_video" input_stream: "input_video"
@@ -89,21 +89,21 @@ to build it.
First, create an XCode project via File > New > Single View App. First, create an XCode project via File > New > Single View App.
Set the product name to "EdgeDetectionGpu", and use an appropriate organization Set the product name to "HelloWorld", and use an appropriate organization
identifier, such as `com.google.mediapipe`. The organization identifier identifier, such as `com.google.mediapipe`. The organization identifier
alongwith the product name will be the `bundle_id` for the application, such as alongwith the product name will be the `bundle_id` for the application, such as
`com.google.mediapipe.EdgeDetectionGpu`. `com.google.mediapipe.HelloWorld`.
Set the language to Objective-C. Set the language to Objective-C.
Save the project to an appropriate location. Let's call this Save the project to an appropriate location. Let's call this
`$PROJECT_TEMPLATE_LOC`. So your project will be in the `$PROJECT_TEMPLATE_LOC`. So your project will be in the
`$PROJECT_TEMPLATE_LOC/EdgeDetectionGpu` directory. This directory will contain `$PROJECT_TEMPLATE_LOC/HelloWorld` directory. This directory will contain
another directory named `EdgeDetectionGpu` and an `EdgeDetectionGpu.xcodeproj` file. another directory named `HelloWorld` and an `HelloWorld.xcodeproj` file.
The `EdgeDetectionGpu.xcodeproj` will not be useful for this tutorial, as we will The `HelloWorld.xcodeproj` will not be useful for this tutorial, as we will use
use bazel to build the iOS application. The content of the bazel to build the iOS application. The content of the
`$PROJECT_TEMPLATE_LOC/EdgeDetectionGpu/EdgeDetectionGpu` directory is listed below: `$PROJECT_TEMPLATE_LOC/HelloWorld/HelloWorld` directory is listed below:
1. `AppDelegate.h` and `AppDelegate.m` 1. `AppDelegate.h` and `AppDelegate.m`
2. `ViewController.h` and `ViewController.m` 2. `ViewController.h` and `ViewController.m`
@@ -112,10 +112,10 @@ use bazel to build the iOS application. The content of the
5. `Main.storyboard` and `Launch.storyboard` 5. `Main.storyboard` and `Launch.storyboard`
6. `Assets.xcassets` directory. 6. `Assets.xcassets` directory.
Copy these files to a directory named `EdgeDetectionGpu` to a location that can Copy these files to a directory named `HelloWorld` to a location that can access
access the MediaPipe source code. For example, the source code of the the MediaPipe source code. For example, the source code of the application that
application that we will build in this tutorial is located in we will build in this tutorial is located in
`mediapipe/examples/ios/EdgeDetectionGpu`. We will refer to this path as the `mediapipe/examples/ios/HelloWorld`. We will refer to this path as the
`$APPLICATION_PATH` throughout the codelab. `$APPLICATION_PATH` throughout the codelab.
Note: MediaPipe provides Objective-C bindings for iOS. The edge detection Note: MediaPipe provides Objective-C bindings for iOS. The edge detection
@@ -134,8 +134,8 @@ load(
) )
ios_application( ios_application(
name = "EdgeDetectionGpuApp", name = "HelloWorldApp",
bundle_id = "com.google.mediapipe.EdgeDetectionGpu", bundle_id = "com.google.mediapipe.HelloWorld",
families = [ families = [
"iphone", "iphone",
"ipad", "ipad",
@@ -143,11 +143,11 @@ ios_application(
infoplists = ["Info.plist"], infoplists = ["Info.plist"],
minimum_os_version = MIN_IOS_VERSION, minimum_os_version = MIN_IOS_VERSION,
provisioning_profile = "//mediapipe/examples/ios:developer_provisioning_profile", provisioning_profile = "//mediapipe/examples/ios:developer_provisioning_profile",
deps = [":EdgeDetectionGpuAppLibrary"], deps = [":HelloWorldAppLibrary"],
) )
objc_library( objc_library(
name = "EdgeDetectionGpuAppLibrary", name = "HelloWorldAppLibrary",
srcs = [ srcs = [
"AppDelegate.m", "AppDelegate.m",
"ViewController.m", "ViewController.m",
@@ -172,9 +172,8 @@ The `objc_library` rule adds dependencies for the `AppDelegate` and
`ViewController` classes, `main.m` and the application storyboards. The `ViewController` classes, `main.m` and the application storyboards. The
templated app depends only on the `UIKit` SDK. templated app depends only on the `UIKit` SDK.
The `ios_application` rule uses the `EdgeDetectionGpuAppLibrary` Objective-C The `ios_application` rule uses the `HelloWorldAppLibrary` Objective-C library
library generated to build an iOS application for installation on your iOS generated to build an iOS application for installation on your iOS device.
device.
Note: You need to point to your own iOS developer provisioning profile to be Note: You need to point to your own iOS developer provisioning profile to be
able to run the application on your iOS device. able to run the application on your iOS device.
@@ -182,21 +181,20 @@ able to run the application on your iOS device.
To build the app, use the following command in a terminal: To build the app, use the following command in a terminal:
``` ```
bazel build -c opt --config=ios_arm64 <$APPLICATION_PATH>:EdgeDetectionGpuApp' bazel build -c opt --config=ios_arm64 <$APPLICATION_PATH>:HelloWorldApp'
``` ```
For example, to build the `EdgeDetectionGpuApp` application in For example, to build the `HelloWorldApp` application in
`mediapipe/examples/ios/edgedetectiongpu`, use the following `mediapipe/examples/ios/helloworld`, use the following command:
command:
``` ```
bazel build -c opt --config=ios_arm64 mediapipe/examples/ios/edgedetectiongpu:EdgeDetectionGpuApp bazel build -c opt --config=ios_arm64 mediapipe/examples/ios/helloworld:HelloWorldApp
``` ```
Then, go back to XCode, open Window > Devices and Simulators, select your Then, go back to XCode, open Window > Devices and Simulators, select your
device, and add the `.ipa` file generated by the command above to your device. device, and add the `.ipa` file generated by the command above to your device.
Here is the document on [setting up and compiling](./building_examples.md#ios) iOS Here is the document on [setting up and compiling](./building_examples.md#ios)
MediaPipe apps. iOS MediaPipe apps.
Open the application on your device. Since it is empty, it should display a Open the application on your device. Since it is empty, it should display a
blank white screen. blank white screen.
@@ -502,8 +500,8 @@ in our app:
}]; }];
``` ```
Note: It is important to start the graph before starting the camera, so that Note: It is important to start the graph before starting the camera, so that the
the graph is ready to process frames as soon as the camera starts sending them. graph is ready to process frames as soon as the camera starts sending them.
Earlier, when we received frames from the camera in the `processVideoFrame` Earlier, when we received frames from the camera in the `processVideoFrame`
function, we displayed them in the `_liveView` using the `_renderer`. Now, we function, we displayed them in the `_liveView` using the `_renderer`. Now, we
@@ -552,9 +550,12 @@ results of running the edge detection graph on a live video feed. Congrats!
![edge_detection_ios_gpu_gif](../images/mobile/edge_detection_ios_gpu.gif) ![edge_detection_ios_gpu_gif](../images/mobile/edge_detection_ios_gpu.gif)
If you ran into any issues, please see the full code of the tutorial Please note that the iOS examples now use a [common] template app. The code in
[here](https://github.com/google/mediapipe/tree/master/mediapipe/examples/ios/edgedetectiongpu). this tutorial is used in the [common] template app. The [helloworld] app has the
appropriate `BUILD` file dependencies for the edge detection graph.
[Bazel]:https://bazel.build/ [Bazel]:https://bazel.build/
[`edge_detection_mobile_gpu.pbtxt`]:https://github.com/google/mediapipe/tree/master/mediapipe/graphs/object_detection/object_detection_mobile_gpu.pbtxt [`edge_detection_mobile_gpu.pbtxt`]:https://github.com/google/mediapipe/tree/master/mediapipe/graphs/edge_detection/edge_detection_mobile_gpu.pbtxt
[MediaPipe installation guide]:./install.md [MediaPipe installation guide]:./install.md
[common]:(https://github.com/google/mediapipe/tree/master/mediapipe/examples/ios/common)
[helloworld]:(https://github.com/google/mediapipe/tree/master/mediapipe/examples/ios/helloworld)
+4 -3
View File
@@ -27,13 +27,14 @@ Repository command failed
usually indicates that Bazel fails to find the local Python binary. To solve usually indicates that Bazel fails to find the local Python binary. To solve
this issue, please first find where the python binary is and then add this issue, please first find where the python binary is and then add
`--action_env PYTHON_BIN_PATH=<path to python binary>` to the Bazel command like `--action_env PYTHON_BIN_PATH=<path to python binary>` to the Bazel command. For
the following: example, you can switch to use the system default python3 binary by the
following command:
``` ```
bazel build -c opt \ bazel build -c opt \
--define MEDIAPIPE_DISABLE_GPU=1 \ --define MEDIAPIPE_DISABLE_GPU=1 \
--action_env PYTHON_BIN_PATH="/path/to/python" \ --action_env PYTHON_BIN_PATH=$(which python3) \
mediapipe/examples/desktop/hello_world mediapipe/examples/desktop/hello_world
``` ```
Binary file not shown.

After

Width:  |  Height:  |  Size: 524 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 MiB

After

Width:  |  Height:  |  Size: 6.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 925 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1019 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

+37 -19
View File
@@ -22,30 +22,37 @@ desktop/cloud, web and IoT devices.
## ML solutions in MediaPipe ## ML solutions in MediaPipe
Face Detection | Face Mesh | Hands | Hair Segmentation Face Detection | Face Mesh | Iris | Hands | Pose | Hair Segmentation
:----------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------: | :---------------: :----------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------: | :---------------:
[![face_detection](images/mobile/face_detection_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/face_detection) | [![face_mesh](images/mobile/face_mesh_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/face_mesh) | [![hand](images/mobile/hand_tracking_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/hands) | [![hair_segmentation](images/mobile/hair_segmentation_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/hair_segmentation) [![face_detection](images/mobile/face_detection_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/face_detection) | [![face_mesh](images/mobile/face_mesh_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/face_mesh) | [![iris](images/mobile/iris_tracking_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/iris) | [![hand](images/mobile/hand_tracking_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/hands) | [![pose](images/mobile/pose_tracking_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/pose) | [![hair_segmentation](images/mobile/hair_segmentation_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/hair_segmentation)
Object Detection | Box Tracking | Objectron | KNIFT Object Detection | Box Tracking | Instant Motion Tracking | Objectron | KNIFT
:----------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------: | :---: :----------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------: | :---:
[![object_detection](images/mobile/object_detection_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/object_detection) | [![box_tracking](images/mobile/object_tracking_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/box_tracking) | [![objectron](images/mobile/objectron_chair_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/objectron) | [![knift](images/mobile/template_matching_android_cpu_small.gif)](https://google.github.io/mediapipe/solutions/knift) [![object_detection](images/mobile/object_detection_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/object_detection) | [![box_tracking](images/mobile/object_tracking_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/box_tracking) | [![instant_motion_tracking](images/mobile/instant_motion_tracking_android_small.gif)](https://google.github.io/mediapipe/solutions/instant_motion_tracking) | [![objectron](images/mobile/objectron_chair_android_gpu_small.gif)](https://google.github.io/mediapipe/solutions/objectron) | [![knift](images/mobile/template_matching_android_cpu_small.gif)](https://google.github.io/mediapipe/solutions/knift)
<!-- []() in the first cell is needed to preserve table formatting in GitHub Pages. --> <!-- []() in the first cell is needed to preserve table formatting in GitHub Pages. -->
<!-- Whenever this table is updated, paste a copy to solutions/solutions.md. --> <!-- Whenever this table is updated, paste a copy to solutions/solutions.md. -->
[]() | Android | iOS | Desktop | Web | Coral []() | Android | iOS | Desktop | Python | Web | Coral
:---------------------------------------------------------------------------- | :-----: | :-: | :-----: | :-: | :---: :---------------------------------------------------------------------------------------- | :-----: | :-: | :-----: | :----: | :-: | :---:
[Face Detection](https://google.github.io/mediapipe/solutions/face_detection) | ✅ | ✅ | ✅ | ✅ | ✅ [Face Detection](https://google.github.io/mediapipe/solutions/face_detection) | ✅ | ✅ | ✅ | | ✅ | ✅
[Face Mesh](https://google.github.io/mediapipe/solutions/face_mesh) | ✅ | ✅ | ✅ | | [Face Mesh](https://google.github.io/mediapipe/solutions/face_mesh) | ✅ | ✅ | ✅ | | |
[Hands](https://google.github.io/mediapipe/solutions/hands) | ✅ | ✅ | ✅ | ✅ | [Iris](https://google.github.io/mediapipe/solutions/iris) | ✅ | ✅ | ✅ | | ✅ |
[Hair Segmentation](https://google.github.io/mediapipe/solutions/hair_segmentation) | ✅ | | ✅ | ✅ | [Hands](https://google.github.io/mediapipe/solutions/hands) | ✅ | | ✅ | | ✅ |
[Object Detection](https://google.github.io/mediapipe/solutions/object_detection) | ✅ | ✅ | ✅ | | ✅ [Pose](https://google.github.io/mediapipe/solutions/pose) | ✅ | ✅ | ✅ | | ✅ |
[Box Tracking](https://google.github.io/mediapipe/solutions/box_tracking) | ✅ | | ✅ | | [Hair Segmentation](https://google.github.io/mediapipe/solutions/hair_segmentation) | ✅ | | ✅ | | ✅ |
[Objectron](https://google.github.io/mediapipe/solutions/objectron) | ✅ | | | | [Object Detection](https://google.github.io/mediapipe/solutions/object_detection) | ✅ | ✅ | ✅ | | |
[KNIFT](https://google.github.io/mediapipe/solutions/knift) | ✅ | | | | [Box Tracking](https://google.github.io/mediapipe/solutions/box_tracking) | ✅ | ✅ | ✅ | | |
[AutoFlip](https://google.github.io/mediapipe/solutions/autoflip) | | | ✅ | | [Instant Motion Tracking](https://google.github.io/mediapipe/solutions/instant_motion_tracking) | ✅ | | | | |
[MediaSequence](https://google.github.io/mediapipe/solutions/media_sequence) | | | | | [Objectron](https://google.github.io/mediapipe/solutions/objectron) | ✅ | | | | |
[YouTube 8M](https://google.github.io/mediapipe/solutions/youtube_8m) | | | | | [KNIFT](https://google.github.io/mediapipe/solutions/knift) | | | | | |
[AutoFlip](https://google.github.io/mediapipe/solutions/autoflip) | | | ✅ | | |
[MediaSequence](https://google.github.io/mediapipe/solutions/media_sequence) | | | ✅ | | |
[YouTube 8M](https://google.github.io/mediapipe/solutions/youtube_8m) | | | ✅ | | |
See also
[MediaPipe Models and Model Cards](https://google.github.io/mediapipe/solutions/models)
for ML models released in MediaPipe.
## MediaPipe on the Web ## MediaPipe on the Web
@@ -63,8 +70,11 @@ never leaves your device.
![visualizer_runner](images/visualizer_runner.png) ![visualizer_runner](images/visualizer_runner.png)
* [MediaPipe Face Detection](https://viz.mediapipe.dev/demo/face_detection) * [MediaPipe Face Detection](https://viz.mediapipe.dev/demo/face_detection)
* [MediaPipe Iris](https://viz.mediapipe.dev/demo/iris_tracking)
* [MediaPipe Iris: Depth-from-Iris](https://viz.mediapipe.dev/demo/iris_depth)
* [MediaPipe Hands](https://viz.mediapipe.dev/demo/hand_tracking) * [MediaPipe Hands](https://viz.mediapipe.dev/demo/hand_tracking)
* [MediaPipe Hands (palm/hand detection only)](https://viz.mediapipe.dev/demo/hand_detection) * [MediaPipe Hands (palm/hand detection only)](https://viz.mediapipe.dev/demo/hand_detection)
* [MediaPipe Pose](https://viz.mediapipe.dev/demo/pose_tracking)
* [MediaPipe Hair Segmentation](https://viz.mediapipe.dev/demo/hair_segmentation) * [MediaPipe Hair Segmentation](https://viz.mediapipe.dev/demo/hair_segmentation)
## Getting started ## Getting started
@@ -83,6 +93,14 @@ run code search using
## Publications ## Publications
* [MediaPipe 3D Face Transform](https://mediapipe.page.link/face-geometry-blog)
in Google Developers Blog
* [Instant Motion Tracking With MediaPipe](https://developers.googleblog.com/2020/08/instant-motion-tracking-with-mediapipe.html)
in Google Developers Blog
* [BlazePose - On-device Real-time Body Pose Tracking](https://ai.googleblog.com/2020/08/on-device-real-time-body-pose-tracking.html)
in Google AI Blog
* [MediaPipe Iris: Real-time Eye Tracking and Depth Estimation](https://ai.googleblog.com/2020/08/mediapipe-iris-real-time-iris-tracking.html)
in Google AI Blog
* [MediaPipe KNIFT: Template-based feature matching](https://developers.googleblog.com/2020/04/mediapipe-knift-template-based-feature-matching.html) * [MediaPipe KNIFT: Template-based feature matching](https://developers.googleblog.com/2020/04/mediapipe-knift-template-based-feature-matching.html)
in Google Developers Blog in Google Developers Blog
* [Alfred Camera: Smart camera features using MediaPipe](https://developers.googleblog.com/2020/03/alfred-camera-smart-camera-features-using-mediapipe.html) * [Alfred Camera: Smart camera features using MediaPipe](https://developers.googleblog.com/2020/03/alfred-camera-smart-camera-features-using-mediapipe.html)
+1 -1
View File
@@ -2,7 +2,7 @@
layout: default layout: default
title: AutoFlip (Saliency-aware Video Cropping) title: AutoFlip (Saliency-aware Video Cropping)
parent: Solutions parent: Solutions
nav_order: 9 nav_order: 12
--- ---
# AutoFlip: Saliency-aware Video Cropping # AutoFlip: Saliency-aware Video Cropping
+1 -1
View File
@@ -2,7 +2,7 @@
layout: default layout: default
title: Box Tracking title: Box Tracking
parent: Solutions parent: Solutions
nav_order: 6 nav_order: 8
--- ---
# MediaPipe Box Tracking # MediaPipe Box Tracking
+1 -6
View File
@@ -102,9 +102,4 @@ to cross-compile and run MediaPipe examples on the
[BlazeFace: Sub-millisecond Neural Face Detection on Mobile GPUs](https://arxiv.org/abs/1907.05047) [BlazeFace: Sub-millisecond Neural Face Detection on Mobile GPUs](https://arxiv.org/abs/1907.05047)
([presentation](https://docs.google.com/presentation/d/1YCtASfnYyZtH-41QvnW5iZxELFnf0MF-pPWSLGj8yjQ/present?slide=id.g5bc8aeffdd_1_0)) ([presentation](https://docs.google.com/presentation/d/1YCtASfnYyZtH-41QvnW5iZxELFnf0MF-pPWSLGj8yjQ/present?slide=id.g5bc8aeffdd_1_0))
([poster](https://drive.google.com/file/d/1u6aB6wxDY7X2TmeUUKgFydulNtXkb3pu/view)) ([poster](https://drive.google.com/file/d/1u6aB6wxDY7X2TmeUUKgFydulNtXkb3pu/view))
* For front-facing/selfie camera: * [Models and model cards](./models.md#face_detection)
[TFLite model](https://github.com/google/mediapipe/tree/master/mediapipe/models/face_detection_front.tflite),
[TFLite model quantized for EdgeTPU/Coral](https://github.com/google/mediapipe/tree/master/mediapipe/examples/coral/models/face-detector-quantized_edgetpu.tflite)
* For back-facing camera:
[TFLite model ](https://github.com/google/mediapipe/tree/master/mediapipe/models/face_detection_back.tflite)
* [Model card](https://drive.google.com/file/d/1f39lSzU5Oq-j_OXgS67KfN5wNsoeAZ4V/view)
+148 -20
View File
@@ -19,13 +19,18 @@ landmarks in real-time even on mobile devices. It employs machine learning (ML)
to infer the 3D surface geometry, requiring only a single camera input without to infer the 3D surface geometry, requiring only a single camera input without
the need for a dedicated depth sensor. Utilizing lightweight model architectures the need for a dedicated depth sensor. Utilizing lightweight model architectures
together with GPU acceleration throughout the pipeline, the solution delivers together with GPU acceleration throughout the pipeline, the solution delivers
real-time performance critical for live experiences. The core of the solution is real-time performance critical for live experiences.
the same as what powers
[YouTube Stories](https://youtube-creators.googleblog.com/2018/11/introducing-more-ways-to-share-your.html)' Additionally, the solution is bundled with the Face Geometry module that bridges
creator effects, the the gap between the face landmark estimation and useful real-time augmented
[Augmented Faces API in ARCore](https://developers.google.com/ar/develop/java/augmented-faces/) reality (AR) applications. It establishes a metric 3D space and uses the face
and the landmark screen positions to estimate face geometry within that space. The face
[ML Kit Face Contour Detection API](https://firebase.google.com/docs/ml-kit/face-detection-concepts#contours). geometry data consists of common 3D geometry primitives, including a face pose
transformation matrix and a triangular face mesh. Under the hood, a lightweight
statistical analysis method called
[Procrustes Analysis](https://en.wikipedia.org/wiki/Procrustes_analysis) is
employed to drive a robust, performant and portable logic. The analysis runs on
CPU and has a minimal speed/memory footprint on top of the ML model inference.
![face_mesh_ar_effects.gif](../images/face_mesh_ar_effects.gif) | ![face_mesh_ar_effects.gif](../images/face_mesh_ar_effects.gif) |
:-------------------------------------------------------------: | :-------------------------------------------------------------: |
@@ -67,15 +72,15 @@ Note: To visualize a graph, copy the graph and paste it into
to visualize its associated subgraphs, please see to visualize its associated subgraphs, please see
[visualizer documentation](../tools/visualizer.md). [visualizer documentation](../tools/visualizer.md).
## Models ### Models
### Face Detection Model #### Face Detection Model
The face detector is the same [BlazeFace](https://arxiv.org/abs/1907.05047) The face detector is the same [BlazeFace](https://arxiv.org/abs/1907.05047)
model used in [MediaPipe Face Detection](./face_detection.md). Please refer to model used in [MediaPipe Face Detection](./face_detection.md). Please refer to
[MediaPipe Face Detection](./face_detection.md) for details. [MediaPipe Face Detection](./face_detection.md) for details.
### Face Landmark Model #### Face Landmark Model
For 3D face landmarks we employed transfer learning and trained a network with For 3D face landmarks we employed transfer learning and trained a network with
several objectives: the network simultaneously predicts 3D landmark coordinates several objectives: the network simultaneously predicts 3D landmark coordinates
@@ -98,7 +103,108 @@ You can find more information about the face landmark model in this
![face_mesh_android_gpu.gif](../images/mobile/face_mesh_android_gpu.gif) | ![face_mesh_android_gpu.gif](../images/mobile/face_mesh_android_gpu.gif) |
:------------------------------------------------------------------------: | :------------------------------------------------------------------------: |
*Fig 2. Output of MediaPipe Face Mesh: the red box indicates the cropped area as input to the landmark model, the red dots represent the 468 landmarks in 3D, and the green lines connecting landmarks illustrate the contours around the eyes, eyebrows, lips and the entire face.* | *Fig 2. Face landmarks: the red box indicates the cropped area as input to the landmark model, the red dots represent the 468 landmarks in 3D, and the green lines connecting landmarks illustrate the contours around the eyes, eyebrows, lips and the entire face.* |
## Face Geometry Module
The [Face Landmark Model](#face-landmark-model) performs a single-camera face landmark
detection in the screen coordinate space: the X- and Y- coordinates are
normalized screen coordinates, while the Z coordinate is relative and is scaled
as the X coodinate under the
[weak perspective projection camera model](https://en.wikipedia.org/wiki/3D_projection#Weak_perspective_projection).
This format is well-suited for some applications, however it does not directly
enable the full spectrum of augmented reality (AR) features like aligning a
virtual 3D object with a detected face.
The
[Face Geometry module](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_geometry)
moves away from the screen coordinate space towards a metric 3D space and
provides necessary primitives to handle a detected face as a regular 3D object.
By design, you'll be able to use a perspective camera to project the final 3D
scene back into the screen coordinate space with a guarantee that the face
landmark positions are not changed.
### Key Concepts
#### Metric 3D Space
The **Metric 3D space** established within the Face Geometry module is a
right-handed orthonormal metric 3D coordinate space. Within the space, there is
a **virtual perspective camera** located at the space origin and pointed in the
negative direction of the Z-axis. In the current pipeline, it is assumed that
the input camera frames are observed by exactly this virtual camera and
therefore its parameters are later used to convert the screen landmark
coordinates back into the Metric 3D space. The *virtual camera parameters* can
be set freely, however for better results it is advised to set them as close to
the *real physical camera parameters* as possible.
![face_geometry_metric_3d_space.gif](../images/face_geometry_metric_3d_space.gif) |
:----------------------------------------------------------------------------: |
*Fig 3. A visualization of multiple key elements in the Metric 3D space.* |
#### Canonical Face Model
The **Canonical Face Model** is a static 3D model of a human face, which follows
the 468 3D face landmark topology of the
[Face Landmark Model](#face-landmark-model). The model bears two important
functions:
- **Defines metric units**: the scale of the canonical face model defines the
metric units of the Metric 3D space. A metric unit used by the
[default canonical face model](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_geometry/data/canonical_face_model.fbx)
is a centimeter;
- **Bridges static and runtime spaces**: the face pose transformation matrix
is - in fact - a linear map from the canonical face model into the runtime
face landmark set estimated on each frame. This way, virtual 3D assets
modeled around the canonical face model can be aligned with a tracked face
by applying the face pose transformation matrix to them.
### Components
#### Geometry Pipeline
The **Geometry Pipeline** is a key component, which is responsible for
estimating face geometry objects within the Metric 3D space. On each frame, the
following steps are executed in the given order:
- Face landmark screen coordinates are converted into the Metric 3D space
coordinates;
- Face pose transformation matrix is estimated as a rigid linear mapping from
the canonical face metric landmark set into the runtime face metric landmark
set in a way that minimizes a difference between the two;
- A face mesh is created using the runtime face metric landmarks as the vertex
positions (XYZ), while both the vertex texture coordinates (UV) and the
triangular topology are inherited from the canonical face model.
The geometry pipeline is implemented as a MediaPipe
[calculator](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_geometry/geometry_pipeline_calculator.cc).
For your convenience, the face geometry pipeline calculator is bundled together
with the face landmark module into a unified MediaPipe
[subgraph](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_geometry/face_geometry_front_gpu.pbtxt).
The face geometry format is defined as a Protocol Buffer
[message](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_geometry/protos/face_geometry.proto).
#### Effect Renderer
The **Effect Renderer** is a component, which serves as a working example of a
face effect renderer. It targets the *OpenGL ES 2.0* API to enable a real-time
performance on mobile devices and supports the following rendering modes:
- **3D object rendering mode**: a virtual object is aligned with a detected
face to emulate an object attached to the face (example: glasses);
- **Face mesh rendering mode**: a texture is stretched on top of the face mesh
surface to emulate a face painting technique.
In both rendering modes, the face mesh is first rendered as an occluder straight
into the depth buffer. This step helps to create a more believable effect via
hiding invisible elements behind the face surface.
The effect renderer is implemented as a MediaPipe
[calculator](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_geometry/effect_renderer_calculator.cc).
| ![face_geometry_renderer.gif](../images/face_geometry_renderer.gif) |
| :---------------------------------------------------------------------: |
| *Fig 4. An example of face effects rendered by the Face Geometry Effect Renderer.* |
## Example Apps ## Example Apps
@@ -111,7 +217,12 @@ Note: To visualize a graph, copy the graph and paste it into
to visualize its associated subgraphs, please see to visualize its associated subgraphs, please see
[visualizer documentation](../tools/visualizer.md). [visualizer documentation](../tools/visualizer.md).
### Mobile ### Face Landmark Example
Face landmark example showcases real-time, cross-platform face landmark
detection. For visual reference, please refer to *Fig. 2*.
#### Mobile
* Graph: * Graph:
[`mediapipe/graphs/face_mesh/face_mesh_mobile.pbtxt`](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/face_mesh/face_mesh_mobile.pbtxt) [`mediapipe/graphs/face_mesh/face_mesh_mobile.pbtxt`](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/face_mesh/face_mesh_mobile.pbtxt)
@@ -125,9 +236,9 @@ Tip: Maximum number of faces to detect/process is set to 1 by default. To change
it, for Android modify `NUM_FACES` in it, for Android modify `NUM_FACES` in
[MainActivity.java](https://github.com/google/mediapipe/tree/master/mediapipe/examples/android/src/java/com/google/mediapipe/apps/facemeshgpu/MainActivity.java), [MainActivity.java](https://github.com/google/mediapipe/tree/master/mediapipe/examples/android/src/java/com/google/mediapipe/apps/facemeshgpu/MainActivity.java),
and for iOS modify `kNumFaces` in and for iOS modify `kNumFaces` in
[ViewController.mm](https://github.com/google/mediapipe/tree/master/mediapipe/examples/ios/facemeshgpu/ViewController.mm). [FaceMeshGpuViewController.mm](https://github.com/google/mediapipe/tree/master/mediapipe/examples/ios/facemeshgpu/FaceMeshGpuViewController.mm).
### Desktop #### Desktop
* Running on CPU * Running on CPU
* Graph: * Graph:
@@ -143,18 +254,35 @@ and for iOS modify `kNumFaces` in
Tip: Maximum number of faces to detect/process is set to 1 by default. To change Tip: Maximum number of faces to detect/process is set to 1 by default. To change
it, in the graph file modify the option of `ConstantSidePacketCalculator`. it, in the graph file modify the option of `ConstantSidePacketCalculator`.
### Face Effect Example
Face effect example showcases real-time mobile face effect application use case
for the Face Mesh solution. To enable a better user experience, this example
only works for a single face. For visual reference, please refer to *Fig. 4*.
#### Mobile
* Graph:
[`mediapipe/graphs/face_effect/face_effect_gpu.pbtxt`](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/face_effect/face_effect_gpu.pbtxt)
* Android target:
[(or download prebuilt ARM64 APK)](https://drive.google.com/file/d/1ccnaDnffEuIXriBZr2SK_Eu4FpO7K44s)
[`mediapipe/examples/android/src/java/com/google/mediapipe/apps/faceeffect`](https://github.com/google/mediapipe/tree/master/mediapipe/examples/android/src/java/com/google/mediapipe/apps/faceeffect/BUILD)
* iOS target:
[`mediapipe/examples/ios/faceeffect`](http:/mediapipe/examples/ios/faceeffect/BUILD)
## Resources ## Resources
* Google AI Blog: * Google AI Blog:
[Real-Time AR Self-Expression with Machine Learning](https://ai.googleblog.com/2019/03/real-time-ar-self-expression-with.html) [Real-Time AR Self-Expression with Machine Learning](https://ai.googleblog.com/2019/03/real-time-ar-self-expression-with.html)
* TensorFlow Blog: * TensorFlow Blog:
[Face and hand tracking in the browser with MediaPipe and TensorFlow.js](https://blog.tensorflow.org/2020/03/face-and-hand-tracking-in-browser-with-mediapipe-and-tensorflowjs.html) [Face and hand tracking in the browser with MediaPipe and TensorFlow.js](https://blog.tensorflow.org/2020/03/face-and-hand-tracking-in-browser-with-mediapipe-and-tensorflowjs.html)
* Google Developers Blog:
[MediaPipe 3D Face Transform](https://mediapipe.page.link/face-geometry-blog)
* Paper: * Paper:
[Real-time Facial Surface Geometry from Monocular Video on Mobile GPUs](https://arxiv.org/abs/1907.06724) [Real-time Facial Surface Geometry from Monocular Video on Mobile GPUs](https://arxiv.org/abs/1907.06724)
([poster](https://docs.google.com/presentation/d/1-LWwOMO9TzEVdrZ1CS1ndJzciRHfYDJfbSxH_ke_JRg/present?slide=id.g5986dd4b4c_4_212)) ([poster](https://docs.google.com/presentation/d/1-LWwOMO9TzEVdrZ1CS1ndJzciRHfYDJfbSxH_ke_JRg/present?slide=id.g5986dd4b4c_4_212))
* Face detection model: * Canonical face model:
[TFLite model](https://github.com/google/mediapipe/tree/master/mediapipe/models/face_detection_front.tflite) [FBX](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_geometry/data/canonical_face_model.fbx),
* Face landmark mode: [OBJ](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_geometry/data/canonical_face_model.obj),
[TFLite model](https://github.com/google/mediapipe/tree/master/mediapipe/models/face_landmark.tflite), [UV visualization](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_geometry/data/canonical_face_model_uv_visualization.png)
[TF.js model](https://tfhub.dev/mediapipe/facemesh/1) * [Models and model cards](./models.md#face_mesh)
* [Model card](https://drive.google.com/file/d/1VFC_wIpw4O7xBOiTgUldl79d9LA-LsnA/view)
+2 -3
View File
@@ -2,7 +2,7 @@
layout: default layout: default
title: Hair Segmentation title: Hair Segmentation
parent: Solutions parent: Solutions
nav_order: 4 nav_order: 6
--- ---
# MediaPipe Hair Segmentation # MediaPipe Hair Segmentation
@@ -54,5 +54,4 @@ Please refer to [these instructions](../index.md#mediapipe-on-the-web).
[Real-time Hair segmentation and recoloring on Mobile GPUs](https://arxiv.org/abs/1907.06740) [Real-time Hair segmentation and recoloring on Mobile GPUs](https://arxiv.org/abs/1907.06740)
([presentation](https://drive.google.com/file/d/1C8WYlWdDRNtU1_pYBvkkG5Z5wqYqf0yj/view)) ([presentation](https://drive.google.com/file/d/1C8WYlWdDRNtU1_pYBvkkG5Z5wqYqf0yj/view))
([supplementary video](https://drive.google.com/file/d/1LPtM99Ch2ogyXYbDNpEqnUfhFq0TfLuf/view)) ([supplementary video](https://drive.google.com/file/d/1LPtM99Ch2ogyXYbDNpEqnUfhFq0TfLuf/view))
* [TFLite model](https://github.com/google/mediapipe/tree/master/mediapipe/models/hair_segmentation.tflite) * [Models and model cards](./models.md#hair_segmentation)
* [Model card](https://drive.google.com/file/d/1lPwJ8BD_-3UUor4LayQ0xpa_RIC_hoRh/view)
+2 -8
View File
@@ -2,7 +2,7 @@
layout: default layout: default
title: Hands title: Hands
parent: Solutions parent: Solutions
nav_order: 3 nav_order: 4
--- ---
# MediaPipe Hands # MediaPipe Hands
@@ -226,10 +226,4 @@ Please refer to [these instructions](../index.md#mediapipe-on-the-web).
* Paper: * Paper:
[MediaPipe Hands: On-device Real-time Hand Tracking](https://arxiv.org/abs/2006.10214) [MediaPipe Hands: On-device Real-time Hand Tracking](https://arxiv.org/abs/2006.10214)
([presentation](https://www.youtube.com/watch?v=I-UOrvxxXEk)) ([presentation](https://www.youtube.com/watch?v=I-UOrvxxXEk))
* Palm detection model: * [Models and model cards](./models.md#hands)
[TFLite model](https://github.com/google/mediapipe/tree/master/mediapipe/models/palm_detection.tflite),
[TF.js model](https://tfhub.dev/mediapipe/handdetector/1)
* Hand landmark model:
[TFLite model](https://github.com/google/mediapipe/tree/master/mediapipe/models/hand_landmark.tflite),
[TF.js model](https://tfhub.dev/mediapipe/handskeleton/1)
* [Model card](https://mediapipe.page.link/handmc)
+122
View File
@@ -0,0 +1,122 @@
---
layout: default
title: Instant Motion Tracking
parent: Solutions
nav_order: 9
---
# MediaPipe Instant Motion Tracking
{: .no_toc }
1. TOC
{:toc}
---
## Overview
Augmented Reality (AR) technology creates fun, engaging, and immersive user
experiences. The ability to perform AR tracking across devices and platforms,
without initialization, remains important to power AR applications at scale.
MediaPipe Instant Motion Tracking provides AR tracking across devices and
platforms without initialization or calibration. It is built upon the
[MediaPipe Box Tracking](./box_tracking.md) solution. With Instant Motion
Tracking, you can easily place virtual 2D and 3D content on static or moving
surfaces, allowing them to seamlessly interact with the real-world environment.
![instant_motion_tracking_android_small](../images/mobile/instant_motion_tracking_android_small.gif) |
:-----------------------------------------------------------------------: |
*Fig 1. Instant Motion Tracking is used to augment the world with a 3D sticker.* |
## Pipeline
The Instant Motion Tracking pipeline is implemented as a MediaPipe
[graph](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/instant_motion_tracking/instant_motion_tracking.pbtxt),
which internally utilizes a
[RegionTrackingSubgraph](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/instant_motion_tracking/subgraphs/region_tracking.pbtxt)
in order to perform anchor tracking for each individual 3D sticker.
We first use a
[StickerManagerCalculator](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/instant_motion_tracking/calculators/sticker_manager_calculator.cc)
to prepare the individual sticker data for the rest of the application. This
information is then sent to the
[RegionTrackingSubgraph](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/instant_motion_tracking/subgraphs/region_tracking.pbtxt)
that performs 3D region tracking for sticker placement and rendering. Once
acquired, our tracked sticker regions are sent with user transformations (i.e.
gestures from the user to rotate and zoom the sticker) and IMU data to the
[MatricesManagerCalculator](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/instant_motion_tracking/calculators/matrices_manager_calculator.cc),
which turns all our sticker transformation data into a set of model matrices.
This data is handled directly by our
[GlAnimationOverlayCalculator](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/object_detection_3d/calculators/gl_animation_overlay_calculator.cc)
as an input stream, which will render the provided texture and object file using
our matrix specifications. The output of
[GlAnimationOverlayCalculator](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/object_detection_3d/calculators/gl_animation_overlay_calculator.cc)
is a video stream depicting the virtual 3D content rendered on top of the real
world, creating immersive AR experiences for users.
## Using Instant Motion Tracking
With the Instant Motion Tracking MediaPipe [graph](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/instant_motion_tracking/instant_motion_tracking.pbtxt),
an application can create an interactive and realistic AR experience by
specifying the required input streams, side packets, and output streams.
The input streams are the following:
* Input Video (GpuBuffer): Video frames to render augmented stickers onto.
* Rotation Matrix (9-element Float Array): The 3x3 row-major rotation
matrix from the device IMU to determine proper orientation of the device.
* Sticker Proto String (String): A string representing the
serialized [sticker buffer protobuf message](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/instant_motion_tracking/calculators/sticker_buffer.proto),
containing a list of all stickers and their attributes.
* Each sticker in the Protobuffer has a unique ID to find associated
anchors and transforms, an initial anchor placement in a normalized [0.0, 1.0]
3D space, a user rotation and user scaling transform on the sticker,
and an integer indicating which type of objects to render for the
sticker (e.g. 3D asset or GIF).
* Sticker Sentinel (Integer): When an anchor must be initially placed or
repositioned, this value must be changed to the ID of the anchor to reset from
the sticker buffer protobuf message. If no valid ID is provided, the system
will simply maintain tracking.
Side packets are also an integral part of the Instant Motion Tracking solution
to provide device-specific information for the rendering system:
* Field of View (Float): The field of view of the camera in radians.
* Aspect Ratio (Float): The aspect ratio (width / height) of the camera frames
(this ratio corresponds to the image frames themselves, not necessarily the
screen bounds).
* Object Asset (String): The
[GlAnimationOverlayCalculator](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/object_detection_3d/calculators/gl_animation_overlay_calculator.cc)
must be provided with an associated asset file name pointing to the 3D model
to render in the viewfinder.
* (Optional) Texture (ImageFrame on Android, GpuBuffer on iOS): Textures for
the
[GlAnimationOverlayCalculator](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/object_detection_3d/calculators/gl_animation_overlay_calculator.cc)
can be provided either via an input stream (dynamic texturing) or as a side
packet (unchanging texture).
The rendering system for the Instant Motion Tracking is powered by OpenGL. For
more information regarding the structure of model matrices and OpenGL rendering,
please visit [OpenGL Wiki](https://www.khronos.org/opengl/wiki/). With the
specifications above, the Instant Motion Tracking capabilities can be adapted to
any device that is able to run the MediaPipe framework with a working IMU system
and connected camera.
## Example Apps
Please first see general instructions for
[Android](../getting_started/building_examples.md#android) on how to build
MediaPipe examples.
* Graph: [mediapipe/graphs/instant_motion_tracking/instant_motion_tracking.pbtxt](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/instant_motion_tracking/instant_motion_tracking.pbtxt)
* Android target (or download prebuilt [ARM64 APK](https://drive.google.com/file/d/1KnaBBoKpCHR73nOBJ4fL_YdWVTAcwe6L/view?usp=sharing)):
[`mediapipe/examples/android/src/java/com/google/mediapipe/apps/instantmotiontracking:instantmotiontracking`](https://github.com/google/mediapipe/tree/master/mediapipe/examples/android/src/java/com/google/mediapipe/apps/instantmotiontracking/BUILD)
## Resources
* Google Developers Blog:
[Instant Motion Tracking With MediaPipe](https://developers.googleblog.com/2020/08/instant-motion-tracking-with-mediapipe.html)
* Google AI Blog:
[The Instant Motion Tracking Behind Motion Stills AR](https://ai.googleblog.com/2018/02/the-instant-motion-tracking-behind.html)
* Paper:
[Instant Motion Tracking and Its Applications to Augmented Reality](https://arxiv.org/abs/1907.06796)
+202
View File
@@ -0,0 +1,202 @@
---
layout: default
title: Iris
parent: Solutions
nav_order: 3
---
# MediaPipe Iris
{: .no_toc }
1. TOC
{:toc}
---
## Overview
A wide range of real-world applications, including computational photography
(glint reflection) and augmented reality effects (virtual avatars) rely on
accurately tracking the iris within an eye. This is a challenging task to solve
on mobile devices, due to the limited computing resources, variable light
conditions and the presence of occlusions, such as hair or people squinting.
Iris tracking can also be utilized to determine the metric distance of the
camera to the user. This can improve a variety of use cases, ranging from
virtual try-on of properly sized glasses and hats to accessibility features that
adopt the font size depending on the viewers distance. Often, sophisticated
specialized hardware is employed to compute the metric distance, limiting the
range of devices on which the solution could be applied.
MediaPipe Iris is a ML solution for accurate iris estimation, able to track
landmarks involving the iris, pupil and the eye contours using a single RGB
camera, in real-time, without the need for specialized hardware. Through use of
iris landmarks, the solution is also able to determine the metric distance
between the subject and the camera with relative error less than 10%. Note that
iris tracking does not infer the location at which people are looking, nor does
it provide any form of identity recognition. With the cross-platfrom capability
of the MediaPipe framework, MediaPipe Iris can run on most modern
[mobile phones](#mobile), [desktops/laptops](#desktop) and even on the
[web](#web).
![iris_tracking_example.gif](../images/mobile/iris_tracking_example.gif) |
:------------------------------------------------------------------------: |
*Fig 1. Example of MediaPipe Iris: eyelid (red) and iris (blue) contours.* |
## ML Pipeline
The first step in the pipeline leverages [MediaPipe Face Mesh](./face_mesh.md),
which generates a mesh of the approximate face geometry. From this mesh, we
isolate the eye region in the original image for use in the subsequent iris
tracking step.
The pipeline is implemented as a MediaPipe
[graph](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/iris_tracking/iris_tracking_gpu.pbtxt)
that uses a
[face landmark subgraph](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_landmark/face_landmark_front_gpu.pbtxt)
from the
[face landmark module](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_landmark),
an
[iris landmark subgraph](https://github.com/google/mediapipe/tree/master/mediapipe/modules/iris_landmark/iris_landmark_left_and_right_gpu.pbtxt)
from the
[iris landmark module](https://github.com/google/mediapipe/tree/master/mediapipe/modules/iris_landmark),
and renders using a dedicated
[iris-and-depth renderer subgraph](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/iris_tracking/subgraphs/iris_and_depth_renderer_gpu.pbtxt).
The
[face landmark subgraph](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_landmark/face_landmark_front_gpu.pbtxt)
internally uses a
[face detection subgraph](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_detection/face_detection_front_gpu.pbtxt)
from the
[face detection module](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_detection).
Note: To visualize a graph, copy the graph and paste it into
[MediaPipe Visualizer](https://viz.mediapipe.dev/). For more information on how
to visualize its associated subgraphs, please see
[visualizer documentation](../tools/visualizer.md).
The output of the pipeline is a set of 478 3D landmarks, including 468 face
landmarks from [MediaPipe Face Mesh](./face_mesh.md), with those around the eyes
further refined (see Fig 2), and 10 additional iris landmarks appended at the
end (5 for each eye, and see Fig 2 also).
## Models
### Face Detection Model
The face detector is the same [BlazeFace](https://arxiv.org/abs/1907.05047)
model used in [MediaPipe Face Detection](./face_detection.md).
### Face Landmark Model
The face landmark model is the same as in [MediaPipe Face Mesh](./face_mesh.md).
You can also find more details in this
[paper](https://arxiv.org/abs/1907.06724).
### Iris Landmark Model
The iris model takes an image patch of the eye region and estimates both the eye
landmarks (along the eyelid) and iris landmarks (along ths iris contour). You
can find more details in this [paper](https://arxiv.org/abs/2006.11341).
![iris_tracking_eye_and_iris_landmarks.png](../images/mobile/iris_tracking_eye_and_iris_landmarks.png) |
:----------------------------------------------------------------------------------------------------: |
*Fig 2. Eye landmarks (red) and iris landmarks (green).* |
## Depth-from-Iris
MediaPipe Iris is able to determine the metric distance of a subject to the
camera with less than 10% error, without requiring any specialized hardware.
This is done by relying on the fact that the horizontal iris diameter of the
human eye remains roughly constant at 11.7±0.5 mm across a wide population,
along with some simple geometric arguments. For more details please refer to our
[Google AI Blog post](https://ai.googleblog.com/2020/08/mediapipe-iris-real-time-iris-tracking.html).
![iris_tracking_depth_from_iris.gif](../images/mobile/iris_tracking_depth_from_iris.gif) |
:--------------------------------------------------------------------------------------------: |
*Fig 3. (Left) MediaPipe Iris predicting metric distance in cm on a Pixel 2 from iris tracking without use of a depth sensor. (Right) Ground-truth depth.* |
## Example Apps
Please first see general instructions for
[Android](../getting_started/building_examples.md#android),
[iOS](../getting_started/building_examples.md#ios) and
[desktop](../getting_started/building_examples.md#desktop) on how to build
MediaPipe examples.
Note: To visualize a graph, copy the graph and paste it into
[MediaPipe Visualizer](https://viz.mediapipe.dev/). For more information on how
to visualize its associated subgraphs, please see
[visualizer documentation](../tools/visualizer.md).
### Mobile
* Graph:
[`mediapipe/graphs/iris_tracking/iris_tracking_gpu.pbtxt`](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/iris_tracking/iris_tracking_gpu.pbtxt)
* Android target:
[(or download prebuilt ARM64 APK)](https://drive.google.com/file/d/1cywcNtqk764TlZf1lvSTV4F3NGB2aL1R/view?usp=sharing)
[`mediapipe/examples/android/src/java/com/google/mediapipe/apps/iristrackinggpu:iristrackinggpu`](https://github.com/google/mediapipe/tree/master/mediapipe/examples/android/src/java/com/google/mediapipe/apps/iristrackinggpu/BUILD)
* iOS target:
[`mediapipe/examples/ios/iristrackinggpu:IrisTrackingGpuApp`](http:/mediapipe/examples/ios/iristrackinggpu/BUILD)
### Desktop
#### Live Camera Input
Please first see general instructions for
[desktop](../getting_started/building_examples.md#desktop) on how to build
MediaPipe examples.
* Running on CPU
* Graph:
[`mediapipe/graphs/iris_tracking/iris_tracking_cpu.pbtxt`](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/iris_tracking/iris_tracking_cpu.pbtxt)
* Target:
[`mediapipe/examples/desktop/iris_tracking:iris_tracking_cpu`](https://github.com/google/mediapipe/tree/master/mediapipe/examples/desktop/iris_tracking/BUILD)
* Running on GPU
* Graph:
[`mediapipe/graphs/iris_tracking/iris_tracking_gpu.pbtxt`](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/iris_tracking/iris_tracking_gpu.pbtxt)
* Target:
[`mediapipe/examples/desktop/iris_tracking:iris_tracking_gpu`](https://github.com/google/mediapipe/tree/master/mediapipe/examples/desktop/iris_tracking/BUILD)
#### Video File Input
1. To build the application, run:
```bash
bazel build -c opt --define MEDIAPIPE_DISABLE_GPU=1 mediapipe/examples/desktop/iris_tracking:iris_tracking_cpu_video_input
```
2. To run the application, replace `<input video path>` and `<output video
path>` in the command below with your own paths:
```
bazel-bin/mediapipe/examples/desktop/iris_tracking/iris_tracking_cpu_video_input \
--calculator_graph_config_file=mediapipe/graphs/iris_tracking/iris_tracking_cpu_video_input.pbtxt \
--input_side_packets=input_video_path=<input video path>,output_video_path=<output video path>
```
#### Single-image Depth Estimation
1. To build the application, run:
```bash
bazel build -c opt --define MEDIAPIPE_DISABLE_GPU=1 mediapipe/examples/desktop/iris_tracking:iris_depth_from_image_desktop
```
2. To run the application, replace `<input image path>` and `<output image
path>` in the command below with your own paths:
```bash
GLOG_logtostderr=1 bazel-bin/mediapipe/examples/desktop/iris_tracking/iris_depth_from_image_desktop \
--input_image_path=<input image path> --output_image_path=<output image path>
```
### Web
Please refer to [these instructions](../index.md#mediapipe-on-the-web).
## Resources
* Google AI Blog:
[MediaPipe Iris: Real-time Eye Tracking and Depth Estimation](https://ai.googleblog.com/2020/08/mediapipe-iris-real-time-iris-tracking.html)
* Paper:
[Real-time Pupil Tracking from Monocular Video for Digital Puppetry](https://arxiv.org/abs/2006.11341)
([presentation](https://youtu.be/cIhXkiiapQI))
* [Models and model cards](./models.md#iris)
+2 -5
View File
@@ -2,7 +2,7 @@
layout: default layout: default
title: KNIFT (Template-based Feature Matching) title: KNIFT (Template-based Feature Matching)
parent: Solutions parent: Solutions
nav_order: 8 nav_order: 11
--- ---
# MediaPipe KNIFT # MediaPipe KNIFT
@@ -139,7 +139,4 @@ to run regular TFLite inference.
* Google Developers Blog: * Google Developers Blog:
[MediaPipe KNIFT: Template-based feature matching](https://developers.googleblog.com/2020/04/mediapipe-knift-template-based-feature-matching.html) [MediaPipe KNIFT: Template-based feature matching](https://developers.googleblog.com/2020/04/mediapipe-knift-template-based-feature-matching.html)
* [TFLite model for up to 200 keypoints](https://github.com/google/mediapipe/tree/master/mediapipe/models/knift_float.tflite) * [Models and model cards](./models.md#knift)
* [TFLite model for up to 400 keypoints](https://github.com/google/mediapipe/tree/master/mediapipe/models/knift_float_400.tflite)
* [TFLite model for up to 1000 keypoints](https://github.com/google/mediapipe/tree/master/mediapipe/models/knift_float_1k.tflite)
* [Model card](https://mediapipe.page.link/knift-mc)
+1 -1
View File
@@ -2,7 +2,7 @@
layout: default layout: default
title: Dataset Preparation with MediaSequence title: Dataset Preparation with MediaSequence
parent: Solutions parent: Solutions
nav_order: 10 nav_order: 13
--- ---
# Dataset Preparation with MediaSequence # Dataset Preparation with MediaSequence
+77
View File
@@ -0,0 +1,77 @@
---
layout: default
title: Models and Model Cards
parent: Solutions
nav_order: 30
---
# MediaPipe Models and Model Cards
{: .no_toc }
1. TOC
{:toc}
---
### [Face Detection](https://google.github.io/mediapipe/solutions/face_detection)
* Face detection model for front-facing/selfie camera:
[TFLite model](https://github.com/google/mediapipe/tree/master/mediapipe/models/face_detection_front.tflite),
[TFLite model quantized for EdgeTPU/Coral](https://github.com/google/mediapipe/tree/master/mediapipe/examples/coral/models/face-detector-quantized_edgetpu.tflite)
* Face detection model for back-facing camera:
[TFLite model ](https://github.com/google/mediapipe/tree/master/mediapipe/models/face_detection_back.tflite)
* [Model card](https://mediapipe.page.link/blazeface-mc)
### [Face Mesh](https://google.github.io/mediapipe/solutions/face_mesh)
* Face landmark model:
[TFLite model](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_landmark/face_landmark.tflite),
[TF.js model](https://tfhub.dev/mediapipe/facemesh/1)
* [Model card](https://mediapipe.page.link/facemesh-mc)
### [Iris](https://google.github.io/mediapipe/solutions/iris)
* Iris landmark model:
[TFLite model](https://github.com/google/mediapipe/tree/master/mediapipe/modules/iris_landmark/iris_landmark.tflite)
* [Model card](https://mediapipe.page.link/iris-mc)
### [Hands](https://google.github.io/mediapipe/solutions/hands)
* Palm detection model:
[TFLite model](https://github.com/google/mediapipe/tree/master/mediapipe/models/palm_detection.tflite),
[TF.js model](https://tfhub.dev/mediapipe/handdetector/1)
* Hand landmark model:
[TFLite model](https://github.com/google/mediapipe/tree/master/mediapipe/models/hand_landmark.tflite),
[TF.js model](https://tfhub.dev/mediapipe/handskeleton/1)
* [Model card](https://mediapipe.page.link/handmc)
### [Pose](https://google.github.io/mediapipe/solutions/pose)
* Pose detection model:
[TFLite model](https://github.com/google/mediapipe/tree/master/mediapipe/modules/pose_detection/pose_detection.tflite)
* Upper-body pose landmark model:
[TFLite model](https://github.com/google/mediapipe/tree/master/mediapipe/modules/pose_landmark/pose_landmark_upper_body.tflite)
* [Model card](https://mediapipe.page.link/blazepose-mc)
### [Hair Segmentation](https://google.github.io/mediapipe/solutions/hair_segmentation)
* [TFLite model](https://github.com/google/mediapipe/tree/master/mediapipe/models/hair_segmentation.tflite)
* [Model card](https://mediapipe.page.link/hairsegmentation-mc)
### [Object Detection](https://google.github.io/mediapipe/solutions/object_detection)
* [TFLite model](https://github.com/google/mediapipe/tree/master/mediapipe/models/ssdlite_object_detection.tflite)
* [TFLite model quantized for EdgeTPU/Coral](https://github.com/google/mediapipe/tree/master/mediapipe/examples/coral/models/object-detector-quantized_edgetpu.tflite)
* [TensorFlow model](https://github.com/google/mediapipe/tree/master/mediapipe/models/object_detection_saved_model)
* [Model information](https://github.com/google/mediapipe/tree/master/mediapipe/models/object_detection_saved_model/README.md)
### [Objectron](https://google.github.io/mediapipe/solutions/objectron)
* [TFLite model for shoes](https://github.com/google/mediapipe/tree/master/mediapipe/models/object_detection_3d_sneakers.tflite)
* [TFLite model for chairs](https://github.com/google/mediapipe/tree/master/mediapipe/models/object_detection_3d_chair.tflite)
### [KNIFT](https://google.github.io/mediapipe/solutions/knift)
* [TFLite model for up to 200 keypoints](https://github.com/google/mediapipe/tree/master/mediapipe/models/knift_float.tflite)
* [TFLite model for up to 400 keypoints](https://github.com/google/mediapipe/tree/master/mediapipe/models/knift_float_400.tflite)
* [TFLite model for up to 1000 keypoints](https://github.com/google/mediapipe/tree/master/mediapipe/models/knift_float_1k.tflite)
* [Model card](https://mediapipe.page.link/knift-mc)
+6 -9
View File
@@ -2,7 +2,7 @@
layout: default layout: default
title: Object Detection title: Object Detection
parent: Solutions parent: Solutions
nav_order: 5 nav_order: 7
--- ---
# MediaPipe Object Detection # MediaPipe Object Detection
@@ -95,8 +95,8 @@ Please first see general instructions for
``` ```
GLOG_logtostderr=1 bazel-bin/mediapipe/examples/desktop/object_detection/object_detection_tflite \ GLOG_logtostderr=1 bazel-bin/mediapipe/examples/desktop/object_detection/object_detection_tflite \
--calculator_graph_config_file=mediapipe/graphs/object_detection/object_detection_desktop_tflite_graph.pbtxt \ --calculator_graph_config_file=mediapipe/graphs/object_detection/object_detection_desktop_tflite_graph.pbtxt \
--input_side_packets=input_video_path=<input video path>,output_video_path=<output video path> --input_side_packets=input_video_path=<input video path>,output_video_path=<output video path>
``` ```
* With a TensorFlow Model * With a TensorFlow Model
@@ -131,8 +131,8 @@ Please first see general instructions for
```bash ```bash
GLOG_logtostderr=1 bazel-bin/mediapipe/examples/desktop/object_detection/object_detection_tflite \ GLOG_logtostderr=1 bazel-bin/mediapipe/examples/desktop/object_detection/object_detection_tflite \
--calculator_graph_config_file=mediapipe/graphs/object_detection/object_detection_desktop_tensorflow_graph.pbtxt \ --calculator_graph_config_file=mediapipe/graphs/object_detection/object_detection_desktop_tensorflow_graph.pbtxt \
--input_side_packets=input_video_path=<input video path>,output_video_path=<output video path> --input_side_packets=input_video_path=<input video path>,output_video_path=<output video path>
``` ```
### Coral ### Coral
@@ -144,7 +144,4 @@ to cross-compile and run MediaPipe examples on the
## Resources ## Resources
* [TFLite model](https://github.com/google/mediapipe/tree/master/mediapipe/models/ssdlite_object_detection.tflite) * [Models and model cards](./models.md#object_detection)
* [TFLite model quantized for EdgeTPU/Coral](https://github.com/google/mediapipe/tree/master/mediapipe/examples/coral/models/object-detector-quantized_edgetpu.tflite)
* [TensorFlow model](https://github.com/google/mediapipe/tree/master/mediapipe/models/object_detection_saved_model)
* [Model information](https://github.com/google/mediapipe/tree/master/mediapipe/models/object_detection_saved_model/README.md)
+3 -4
View File
@@ -2,7 +2,7 @@
layout: default layout: default
title: Objectron (3D Object Detection) title: Objectron (3D Object Detection)
parent: Solutions parent: Solutions
nav_order: 7 nav_order: 10
--- ---
# MediaPipe Objectron # MediaPipe Objectron
@@ -161,7 +161,7 @@ to visualize its associated subgraphs, please see
### Objectron for Shoes ### Objectron for Shoes
* Graph: * Graph:
[`mediapipe/graphs/hair_segmentation/hair_segmentation_mobile_gpu.pbtxt`](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/object_detection_3d/shoe_classic_occlusion_tracking.pbtxt) [`mediapipe/graphs/object_detection_3d/shoe_classic_occlusion_tracking.pbtxt`](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/object_detection_3d/shoe_classic_occlusion_tracking.pbtxt)
* Android target: * Android target:
[(or download prebuilt ARM64 APK)](https://drive.google.com/open?id=1S0K4hbWt3o31FfQ4QU3Rz7IHrvOUMx1d) [(or download prebuilt ARM64 APK)](https://drive.google.com/open?id=1S0K4hbWt3o31FfQ4QU3Rz7IHrvOUMx1d)
[`mediapipe/examples/android/src/java/com/google/mediapipe/apps/objectdetection3d:objectdetection3d`](https://github.com/google/mediapipe/tree/master/mediapipe/examples/android/src/java/com/google/mediapipe/apps/objectdetection3d/BUILD) [`mediapipe/examples/android/src/java/com/google/mediapipe/apps/objectdetection3d:objectdetection3d`](https://github.com/google/mediapipe/tree/master/mediapipe/examples/android/src/java/com/google/mediapipe/apps/objectdetection3d/BUILD)
@@ -191,5 +191,4 @@ to visualize its associated subgraphs, please see
* Paper: * Paper:
[Instant 3D Object Tracking with Applications in Augmented Reality](https://drive.google.com/open?id=1O_zHmlgXIzAdKljp20U_JUkEHOGG52R8) [Instant 3D Object Tracking with Applications in Augmented Reality](https://drive.google.com/open?id=1O_zHmlgXIzAdKljp20U_JUkEHOGG52R8)
([presentation](https://www.youtube.com/watch?v=9ndF1AIo7h0)) ([presentation](https://www.youtube.com/watch?v=9ndF1AIo7h0))
* [TFLite model for shoes](https://github.com/google/mediapipe/tree/master/mediapipe/models/object_detection_3d_sneakers.tflite) * [Models and model cards](./models.md#objectron)
* [TFLite model for chairs](https://github.com/google/mediapipe/tree/master/mediapipe/models/object_detection_3d_chair.tflite)
+194
View File
@@ -0,0 +1,194 @@
---
layout: default
title: Pose
parent: Solutions
nav_order: 5
---
# MediaPipe BlazePose
{: .no_toc }
1. TOC
{:toc}
---
## Overview
Human pose estimation from video plays a critical role in various applications
such as quantifying physical exercises, sign language recognition, and full-body
gesture control. For example, it can form the basis for yoga, dance, and fitness
applications. It can also enable the overlay of digital content and information
on top of the physical world in augmented reality.
MediaPipe Pose is a ML solution for high-fidelity upper-body pose tracking,
inferring 25 2D upper-body landmarks from RGB video frames utilizing our
[BlazePose](https://ai.googleblog.com/2020/08/on-device-real-time-body-pose-tracking.html)
research. Current state-of-the-art approaches rely primarily on powerful desktop
environments for inference, whereas our method achieves real-time performance on
most modern [mobile phones](#mobile), [desktops/laptops](#desktop), in
[python](#python) and even on the [web](#web). A variant of MediaPipe Pose that
performs full-body pose tracking on mobile phones will be included in an
upcoming release of
[ML Kit](https://developers.google.com/ml-kit/early-access/pose-detection).
![pose_tracking_upper_body_example.gif](../images/mobile/pose_tracking_upper_body_example.gif) |
:--------------------------------------------------------------------------------------------: |
*Fig 1. Example of MediaPipe Pose for upper-body pose tracking.* |
## ML Pipeline
The solution utilizes a two-step detector-tracker ML pipeline, proven to be
effective in our [MediaPipe Hands](./hands.md) and
[MediaPipe Face Mesh](./face_mesh.md) solutions. Using a detector, the pipeline
first locates the pose region-of-interest (ROI) within the frame. The tracker
subsequently predicts the pose landmarks within the ROI using the ROI-cropped
frame as input. Note that for video use cases the detector is invoked only as
needed, i.e., for the very first frame and when the tracker could no longer
identify body pose presence in the previous frame. For other frames the pipeline
simply derives the ROI from the previous frames pose landmarks.
The pipeline is implemented as a MediaPipe
[graph](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/pose_tracking/upper_body_pose_tracking_gpu.pbtxt)
that uses a
[pose landmark subgraph](https://github.com/google/mediapipe/tree/master/mediapipe/modules/pose_landmark/pose_landmark_upper_body_gpu.pbtxt)
from the
[pose landmark module](https://github.com/google/mediapipe/tree/master/mediapipe/modules/pose_landmark)
and renders using a dedicated
[upper-body pose renderer subgraph](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/pose_tracking/subgraphs/upper_body_pose_renderer_gpu.pbtxt).
The
[pose landmark subgraph](https://github.com/google/mediapipe/tree/master/mediapipe/modules/pose_landmark/pose_landmark_upper_body_gpu.pbtxt)
internally uses a
[pose detection subgraph](https://github.com/google/mediapipe/tree/master/mediapipe/modules/pose_detection/pose_detection_gpu.pbtxt)
from the
[pose detection module](https://github.com/google/mediapipe/tree/master/mediapipe/modules/pose_detection).
Note: To visualize a graph, copy the graph and paste it into
[MediaPipe Visualizer](https://viz.mediapipe.dev/). For more information on how
to visualize its associated subgraphs, please see
[visualizer documentation](../tools/visualizer.md).
## Models
### Pose Detection Model (BlazePose Detector)
The detector is inspired by our own lightweight
[BlazeFace](https://arxiv.org/abs/1907.05047) model, used in
[MediaPipe Face Detection](./face_detection.md), as a proxy for a person
detector. It explicitly predicts two additional virtual keypoints that firmly
describe the human body center, rotation and scale as a circle. Inspired by
[Leonardos Vitruvian man](https://en.wikipedia.org/wiki/Vitruvian_Man), we
predict the midpoint of a person's hips, the radius of a circle circumscribing
the whole person, and the incline angle of the line connecting the shoulder and
hip midpoints.
![pose_tracking_detector_vitruvian_man.png](../images/mobile/pose_tracking_detector_vitruvian_man.png) |
:----------------------------------------------------------------------------------------------------: |
*Fig 2. Vitruvian man aligned via two virtual keypoints predicted by BlazePose detector in addition to the face bounding box.* |
### Pose Landmark Model (BlazePose Tracker)
The landmark model currently included in MediaPipe Pose predicts the location of
25 upper-body landmarks (see figure below), each with `(x, y, z, visibility)`,
plus two virtual alignment keypoints. Note that the `z` value should be
discarded as the model is currently not fully trained to predict depth, but this
is something we have on the roadmap. The model shares the same architecture as
the full-body version that predicts 33 landmarks, described in more detail in
the
[BlazePose Google AI Blog](https://ai.googleblog.com/2020/08/on-device-real-time-body-pose-tracking.html)
and in this [paper](https://arxiv.org/abs/2006.10204).
![pose_tracking_upper_body_landmarks.png](../images/mobile/pose_tracking_upper_body_landmarks.png) |
:------------------------------------------------------------------------------------------------: |
*Fig 3. 25 upper-body pose landmarks.* |
## Example Apps
Please first see general instructions for
[Android](../getting_started/building_examples.md#android),
[iOS](../getting_started/building_examples.md#ios),
[desktop](../getting_started/building_examples.md#desktop) and
[Python](../getting_started/building_examples.md#python) on how to build
MediaPipe examples.
Note: To visualize a graph, copy the graph and paste it into
[MediaPipe Visualizer](https://viz.mediapipe.dev/). For more information on how
to visualize its associated subgraphs, please see
[visualizer documentation](../tools/visualizer.md).
### Mobile
* Graph:
[`mediapipe/graphs/pose_tracking/upper_body_pose_tracking_gpu.pbtxt`](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/pose_tracking/upper_body_pose_tracking_gpu.pbtxt)
* Android target:
[(or download prebuilt ARM64 APK)](https://drive.google.com/file/d/1uKc6T7KSuA0Mlq2URi5YookHu0U3yoh_/view?usp=sharing)
[`mediapipe/examples/android/src/java/com/google/mediapipe/apps/upperbodyposetrackinggpu:upperbodyposetrackinggpu`](https://github.com/google/mediapipe/tree/master/mediapipe/examples/android/src/java/com/google/mediapipe/apps/upperbodyposetrackinggpu/BUILD)
* iOS target:
[`mediapipe/examples/ios/upperbodyposetrackinggpu:UpperBodyPoseTrackingGpuApp`](http:/mediapipe/examples/ios/upperbodyposetrackinggpu/BUILD)
### Desktop
Please first see general instructions for
[desktop](../getting_started/building_examples.md#desktop) on how to build
MediaPipe examples.
* Running on CPU
* Graph:
[`mediapipe/graphs/pose_tracking/upper_body_pose_tracking_cpu.pbtxt`](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/pose_tracking/upper_body_pose_tracking_cpu.pbtxt)
* Target:
[`mediapipe/examples/desktop/upper_body_pose_tracking:upper_body_pose_tracking_cpu`](https://github.com/google/mediapipe/tree/master/mediapipe/examples/desktop/upper_body_pose_tracking/BUILD)
* Running on GPU
* Graph:
[`mediapipe/graphs/pose_tracking/upper_body_pose_tracking_gpu.pbtxt`](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/pose_tracking/upper_body_pose_tracking_gpu.pbtxt)
* Target:
[`mediapipe/examples/desktop/upper_body_pose_tracking:upper_body_pose_tracking_gpu`](https://github.com/google/mediapipe/tree/master/mediapipe/examples/desktop/upper_body_pose_tracking/BUILD)
### Python
MediaPipe Python package is available on
[PyPI](https://pypi.org/project/mediapipe/), and can be installed simply by `pip
install mediapipe` on Linux and macOS, as described below and in this
[colab](https://mediapipe.page.link/mp-py-colab). If you do need to build the
Python package from source, see
[additional instructions](../getting_started/building_examples.md#python).
```bash
# Activate a Python virtual environment.
$ python3 -m venv mp_env && source mp_env/bin/activate
# Install MediaPipe Python package
(mp_env)$ pip install mediapipe
# Run in Python interpreter
(mp_env)$ python3
>>> import mediapipe as mp
>>> pose_tracker = mp.examples.UpperBodyPoseTracker()
# For image input
>>> pose_landmarks, _ = pose_tracker.run(input_file='/path/to/input/file', output_file='/path/to/output/file')
>>> pose_landmarks, annotated_image = pose_tracker.run(input_file='/path/to/file')
# To print out the pose landmarks, you can simply do "print(pose_landmarks)".
# However, the data points can be more accessible with the following approach.
>>> [print('x is', data_point.x, 'y is', data_point.y, 'z is', data_point.z, 'visibility is', data_point.visibility) for data_point in pose_landmarks.landmark]
# For live camera input
# (Press Esc within the output image window to stop the run or let it self terminate after 30 seconds.)
>>> pose_tracker.run_live()
# Close the tracker.
>>> pose_tracker.close()
```
Tip: Use command `deactivate` to exit the Python virtual environment.
### Web
Please refer to [these instructions](../index.md#mediapipe-on-the-web).
## Resources
* Google AI Blog:
[BlazePose - On-device Real-time Body Pose Tracking](https://ai.googleblog.com/2020/08/on-device-real-time-body-pose-tracking.html)
* Paper:
[BlazePose: On-device Real-time Body Pose Tracking](https://arxiv.org/abs/2006.10204)
([presentation](https://youtu.be/YPpUOTRn5tA))
* [Models and model cards](./models.md#pose)
+21 -14
View File
@@ -14,18 +14,25 @@ has_toc: false
--- ---
<!-- []() in the first cell is needed to preserve table formatting in GitHub Pages. --> <!-- []() in the first cell is needed to preserve table formatting in GitHub Pages. -->
<!-- Whenever this table is updated, paste a copy to ../index.md. --> <!-- Whenever this table is updated, paste a copy to ../external_index.md. -->
[]() | Android | iOS | Desktop | Web | Coral []() | Android | iOS | Desktop | Python | Web | Coral
:---------------------------------------------------------------------------- | :-----: | :-: | :-----: | :-: | :---: :---------------------------------------------------------------------------------------- | :-----: | :-: | :-----: | :----: | :-: | :---:
[Face Detection](https://google.github.io/mediapipe/solutions/face_detection) | ✅ | ✅ | ✅ | ✅ | ✅ [Face Detection](https://google.github.io/mediapipe/solutions/face_detection) | ✅ | ✅ | ✅ | | ✅ | ✅
[Face Mesh](https://google.github.io/mediapipe/solutions/face_mesh) | ✅ | ✅ | ✅ | | [Face Mesh](https://google.github.io/mediapipe/solutions/face_mesh) | ✅ | ✅ | ✅ | | |
[Hands](https://google.github.io/mediapipe/solutions/hands) | ✅ | ✅ | ✅ | ✅ | [Iris](https://google.github.io/mediapipe/solutions/iris) | ✅ | ✅ | ✅ | | ✅ |
[Hair Segmentation](https://google.github.io/mediapipe/solutions/hair_segmentation) | ✅ | | ✅ | ✅ | [Hands](https://google.github.io/mediapipe/solutions/hands) | ✅ | | ✅ | | ✅ |
[Object Detection](https://google.github.io/mediapipe/solutions/object_detection) | ✅ | ✅ | ✅ | | ✅ [Pose](https://google.github.io/mediapipe/solutions/pose) | ✅ | ✅ | ✅ | | ✅ |
[Box Tracking](https://google.github.io/mediapipe/solutions/box_tracking) | ✅ | | ✅ | | [Hair Segmentation](https://google.github.io/mediapipe/solutions/hair_segmentation) | ✅ | | ✅ | | ✅ |
[Objectron](https://google.github.io/mediapipe/solutions/objectron) | ✅ | | | | [Object Detection](https://google.github.io/mediapipe/solutions/object_detection) | ✅ | ✅ | ✅ | | |
[KNIFT](https://google.github.io/mediapipe/solutions/knift) | ✅ | | | | [Box Tracking](https://google.github.io/mediapipe/solutions/box_tracking) | ✅ | ✅ | ✅ | | |
[AutoFlip](https://google.github.io/mediapipe/solutions/autoflip) | | | ✅ | | [Instant Motion Tracking](https://google.github.io/mediapipe/solutions/instant_motion_tracking) | ✅ | | | | |
[MediaSequence](https://google.github.io/mediapipe/solutions/media_sequence) | | | | | [Objectron](https://google.github.io/mediapipe/solutions/objectron) | ✅ | | | | |
[YouTube 8M](https://google.github.io/mediapipe/solutions/youtube_8m) | | | | | [KNIFT](https://google.github.io/mediapipe/solutions/knift) | | | | | |
[AutoFlip](https://google.github.io/mediapipe/solutions/autoflip) | | | ✅ | | |
[MediaSequence](https://google.github.io/mediapipe/solutions/media_sequence) | | | ✅ | | |
[YouTube 8M](https://google.github.io/mediapipe/solutions/youtube_8m) | | | ✅ | | |
See also
[MediaPipe Models and Model Cards](https://google.github.io/mediapipe/solutions/models)
for ML models released in MediaPipe.
+1 -1
View File
@@ -2,7 +2,7 @@
layout: default layout: default
title: YouTube-8M Feature Extraction and Model Inference title: YouTube-8M Feature Extraction and Model Inference
parent: Solutions parent: Solutions
nav_order: 11 nav_order: 14
--- ---
# YouTube-8M Feature Extraction and Model Inference # YouTube-8M Feature Extraction and Model Inference
+8 -2
View File
@@ -71,6 +71,9 @@ MediaPipe will emit data into a pre-specified directory:
You can open the Download Container. Logs will be located in `application You can open the Download Container. Logs will be located in `application
container/.xcappdata/AppData/Documents/` container/.xcappdata/AppData/Documents/`
If XCode shows empty content for the downloaded container file, you can
right click and select 'Show Package Contents' in Finder. Logs
will be located in 'AppData/Documents/'
![Windows Download Container](../images/visualizer/ios_download_container.png) ![Windows Download Container](../images/visualizer/ios_download_container.png)
@@ -144,10 +147,13 @@ we record ten intervals of half a second each. This can be overridden by adding
```bash ```bash
profiler_config { profiler_config {
trace_enabled: true trace_enabled: true
trace_log_path: "/sdcard/profiles" trace_log_path: "/sdcard/profiles/"
} }
``` ```
Note: The forward slash at the end of the `trace_log_path` is necessary for
indicating that `profiles` is a directory (that *should* exist).
* Download the trace files from the device. * Download the trace files from the device.
```bash ```bash
@@ -294,7 +300,7 @@ trace_log_margin_usec
in trace log output. This margin allows time for events to be appended to in trace log output. This margin allows time for events to be appended to
the TraceBuffer. the TraceBuffer.
trace_log_duration_events trace_log_instant_events
: False specifies an event for each calculator invocation. True specifies a : False specifies an event for each calculator invocation. True specifies a
separate event for each start and finish time. separate event for each start and finish time.
@@ -3,27 +3,36 @@
"/BUILD", "/BUILD",
"mediapipe/BUILD", "mediapipe/BUILD",
"mediapipe/objc/BUILD", "mediapipe/objc/BUILD",
"mediapipe/examples/ios/BUILD", "mediapipe/framework/BUILD",
"mediapipe/examples/ios/edgedetectiongpu/BUILD", "mediapipe/gpu/BUILD",
"mediapipe/objc/testing/app/BUILD",
"mediapipe/examples/ios/common/BUILD",
"mediapipe/examples/ios/helloworld/BUILD",
"mediapipe/examples/ios/facedetectioncpu/BUILD", "mediapipe/examples/ios/facedetectioncpu/BUILD",
"mediapipe/examples/ios/facedetectiongpu/BUILD", "mediapipe/examples/ios/facedetectiongpu/BUILD",
"mediapipe/examples/ios/faceeffect/BUILD",
"mediapipe/examples/ios/facemeshgpu/BUILD", "mediapipe/examples/ios/facemeshgpu/BUILD",
"mediapipe/examples/ios/handdetectiongpu/BUILD", "mediapipe/examples/ios/handdetectiongpu/BUILD",
"mediapipe/examples/ios/handtrackinggpu/BUILD", "mediapipe/examples/ios/handtrackinggpu/BUILD",
"mediapipe/examples/ios/iristrackinggpu/BUILD",
"mediapipe/examples/ios/multihandtrackinggpu/BUILD", "mediapipe/examples/ios/multihandtrackinggpu/BUILD",
"mediapipe/examples/ios/objectdetectioncpu/BUILD", "mediapipe/examples/ios/objectdetectioncpu/BUILD",
"mediapipe/examples/ios/objectdetectiongpu/BUILD" "mediapipe/examples/ios/objectdetectiongpu/BUILD",
"mediapipe/examples/ios/upperbodyposetrackinggpu/BUILD"
], ],
"buildTargets" : [ "buildTargets" : [
"//mediapipe/examples/ios/edgedetectiongpu:EdgeDetectionGpuApp", "//mediapipe/examples/ios/helloworld:HelloWorldApp",
"//mediapipe/examples/ios/facedetectioncpu:FaceDetectionCpuApp", "//mediapipe/examples/ios/facedetectioncpu:FaceDetectionCpuApp",
"//mediapipe/examples/ios/facedetectiongpu:FaceDetectionGpuApp", "//mediapipe/examples/ios/facedetectiongpu:FaceDetectionGpuApp",
"//mediapipe/examples/ios/faceeffect:FaceEffectApp",
"//mediapipe/examples/ios/facemeshgpu:FaceMeshGpuApp", "//mediapipe/examples/ios/facemeshgpu:FaceMeshGpuApp",
"//mediapipe/examples/ios/handdetectiongpu:HandDetectionGpuApp", "//mediapipe/examples/ios/handdetectiongpu:HandDetectionGpuApp",
"//mediapipe/examples/ios/handtrackinggpu:HandTrackingGpuApp", "//mediapipe/examples/ios/handtrackinggpu:HandTrackingGpuApp",
"//mediapipe/examples/ios/iristrackinggpu:IrisTrackingGpuApp",
"//mediapipe/examples/ios/multihandtrackinggpu:MultiHandTrackingGpuApp", "//mediapipe/examples/ios/multihandtrackinggpu:MultiHandTrackingGpuApp",
"//mediapipe/examples/ios/objectdetectioncpu:ObjectDetectionCpuApp", "//mediapipe/examples/ios/objectdetectioncpu:ObjectDetectionCpuApp",
"//mediapipe/examples/ios/objectdetectiongpu:ObjectDetectionGpuApp", "//mediapipe/examples/ios/objectdetectiongpu:ObjectDetectionGpuApp",
"//mediapipe/examples/ios/upperbodyposetrackinggpu:UpperBodyPoseTrackingGpuApp",
"//mediapipe/objc:mediapipe_framework_ios" "//mediapipe/objc:mediapipe_framework_ios"
], ],
"optionSet" : { "optionSet" : {
@@ -78,22 +87,20 @@
"mediapipe/calculators/util", "mediapipe/calculators/util",
"mediapipe/examples", "mediapipe/examples",
"mediapipe/examples/ios", "mediapipe/examples/ios",
"mediapipe/examples/ios/edgedetectiongpu", "mediapipe/examples/ios/common",
"mediapipe/examples/ios/edgedetectiongpu/Base.lproj", "mediapipe/examples/ios/common/Base.lproj",
"mediapipe/examples/ios/helloworld",
"mediapipe/examples/ios/facedetectioncpu", "mediapipe/examples/ios/facedetectioncpu",
"mediapipe/examples/ios/facedetectioncpu/Base.lproj",
"mediapipe/examples/ios/facedetectiongpu", "mediapipe/examples/ios/facedetectiongpu",
"mediapipe/examples/ios/facedetectiongpu/Base.lproj", "mediapipe/examples/ios/faceeffect",
"mediapipe/examples/ios/faceeffect/Base.lproj",
"mediapipe/examples/ios/handdetectiongpu", "mediapipe/examples/ios/handdetectiongpu",
"mediapipe/examples/ios/handdetectiongpu/Base.lproj",
"mediapipe/examples/ios/handtrackinggpu", "mediapipe/examples/ios/handtrackinggpu",
"mediapipe/examples/ios/handtrackinggpu/Base.lproj", "mediapipe/examples/ios/iristrackinggpu",
"mediapipe/examples/ios/multihandtrackinggpu", "mediapipe/examples/ios/multihandtrackinggpu",
"mediapipe/examples/ios/multihandtrackinggpu/Base.lproj",
"mediapipe/examples/ios/objectdetectioncpu", "mediapipe/examples/ios/objectdetectioncpu",
"mediapipe/examples/ios/objectdetectioncpu/Base.lproj",
"mediapipe/examples/ios/objectdetectiongpu", "mediapipe/examples/ios/objectdetectiongpu",
"mediapipe/examples/ios/objectdetectiongpu/Base.lproj", "mediapipe/examples/ios/upperbodyposetrackinggpu",
"mediapipe/framework", "mediapipe/framework",
"mediapipe/framework/deps", "mediapipe/framework/deps",
"mediapipe/framework/formats", "mediapipe/framework/formats",
@@ -107,9 +114,12 @@
"mediapipe/graphs", "mediapipe/graphs",
"mediapipe/graphs/edge_detection", "mediapipe/graphs/edge_detection",
"mediapipe/graphs/face_detection", "mediapipe/graphs/face_detection",
"mediapipe/graphs/face_geometry",
"mediapipe/graphs/hand_tracking", "mediapipe/graphs/hand_tracking",
"mediapipe/graphs/object_detection", "mediapipe/graphs/object_detection",
"mediapipe/graphs/pose_tracking",
"mediapipe/models", "mediapipe/models",
"mediapipe/modules",
"mediapipe/objc", "mediapipe/objc",
"mediapipe/util", "mediapipe/util",
"mediapipe/util/android", "mediapipe/util/android",
@@ -11,15 +11,17 @@
"mediapipe", "mediapipe",
"mediapipe/objc", "mediapipe/objc",
"mediapipe/examples/ios", "mediapipe/examples/ios",
"mediapipe/examples/ios/edgedetectiongpu",
"mediapipe/examples/ios/facedetectioncpu", "mediapipe/examples/ios/facedetectioncpu",
"mediapipe/examples/ios/facedetectiongpu", "mediapipe/examples/ios/facedetectiongpu",
"mediapipe/examples/ios/faceeffect",
"mediapipe/examples/ios/facemeshgpu", "mediapipe/examples/ios/facemeshgpu",
"mediapipe/examples/ios/handdetectiongpu", "mediapipe/examples/ios/handdetectiongpu",
"mediapipe/examples/ios/handtrackinggpu", "mediapipe/examples/ios/handtrackinggpu",
"mediapipe/examples/ios/iristrackinggpu",
"mediapipe/examples/ios/multihandtrackinggpu", "mediapipe/examples/ios/multihandtrackinggpu",
"mediapipe/examples/ios/objectdetectioncpu", "mediapipe/examples/ios/objectdetectioncpu",
"mediapipe/examples/ios/objectdetectiongpu" "mediapipe/examples/ios/objectdetectiongpu",
"mediapipe/examples/ios/upperbodyposetrackinggpu"
], ],
"projectName" : "Mediapipe", "projectName" : "Mediapipe",
"workspaceRoot" : "../.." "workspaceRoot" : "../.."
+1 -1
View File
@@ -1,4 +1,4 @@
"""Copyright 2019 The MediaPipe Authors. """Copyright 2019 - 2020 The MediaPipe Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
+1 -1
View File
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
licenses(["notice"]) # Apache 2.0 licenses(["notice"])
package(default_visibility = ["//visibility:private"]) package(default_visibility = ["//visibility:private"])
+1 -1
View File
@@ -13,7 +13,7 @@
# limitations under the License. # limitations under the License.
# #
licenses(["notice"]) # Apache 2.0 licenses(["notice"])
filegroup( filegroup(
name = "test_audios", name = "test_audios",
+64 -1
View File
@@ -15,7 +15,7 @@
load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library") load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library")
licenses(["notice"]) # Apache 2.0 licenses(["notice"])
package(default_visibility = ["//visibility:private"]) package(default_visibility = ["//visibility:private"])
@@ -290,7 +290,9 @@ cc_library(
deps = [ deps = [
":concatenate_vector_calculator_cc_proto", ":concatenate_vector_calculator_cc_proto",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:classification_cc_proto",
"//mediapipe/framework/formats:landmark_cc_proto", "//mediapipe/framework/formats:landmark_cc_proto",
"//mediapipe/framework/port:integral_types",
"//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:ret_check",
"//mediapipe/framework/port:status", "//mediapipe/framework/port:status",
"@org_tensorflow//tensorflow/lite:framework", "@org_tensorflow//tensorflow/lite:framework",
@@ -316,6 +318,37 @@ cc_library(
alwayslink = 1, alwayslink = 1,
) )
cc_library(
name = "concatenate_normalized_landmark_list_calculator",
srcs = ["concatenate_normalized_landmark_list_calculator.cc"],
visibility = ["//visibility:public"],
deps = [
":concatenate_vector_calculator_cc_proto",
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:landmark_cc_proto",
"//mediapipe/framework/port:ret_check",
"//mediapipe/framework/port:status",
],
alwayslink = 1,
)
cc_test(
name = "concatenate_normalized_landmark_list_calculator_test",
srcs = ["concatenate_normalized_landmark_list_calculator_test.cc"],
deps = [
":concatenate_normalized_landmark_list_calculator",
":concatenate_vector_calculator_cc_proto",
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework:calculator_runner",
"//mediapipe/framework:timestamp",
"//mediapipe/framework/formats:landmark_cc_proto",
"//mediapipe/framework/port:gtest_main",
"//mediapipe/framework/port:parse_text_proto",
"//mediapipe/framework/port:status",
"@com_google_absl//absl/strings",
],
)
cc_test( cc_test(
name = "concatenate_vector_calculator_test", name = "concatenate_vector_calculator_test",
srcs = ["concatenate_vector_calculator_test.cc"], srcs = ["concatenate_vector_calculator_test.cc"],
@@ -575,6 +608,35 @@ cc_library(
alwayslink = 1, alwayslink = 1,
) )
cc_library(
name = "packet_presence_calculator",
srcs = ["packet_presence_calculator.cc"],
visibility = ["//visibility:public"],
deps = [
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework:packet",
"//mediapipe/framework:timestamp",
"//mediapipe/framework/port:status",
],
alwayslink = 1,
)
cc_test(
name = "packet_presence_calculator_test",
srcs = ["packet_presence_calculator_test.cc"],
deps = [
":gate_calculator",
":packet_presence_calculator",
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework:calculator_runner",
"//mediapipe/framework:timestamp",
"//mediapipe/framework/port:gtest_main",
"//mediapipe/framework/port:parse_text_proto",
"//mediapipe/framework/port:status",
"//mediapipe/framework/tool:sink",
],
)
cc_library( cc_library(
name = "previous_loopback_calculator", name = "previous_loopback_calculator",
srcs = ["previous_loopback_calculator.cc"], srcs = ["previous_loopback_calculator.cc"],
@@ -1059,6 +1121,7 @@ cc_library(
":constant_side_packet_calculator_cc_proto", ":constant_side_packet_calculator_cc_proto",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework:collection_item_id", "//mediapipe/framework:collection_item_id",
"//mediapipe/framework/port:integral_types",
"//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:ret_check",
"//mediapipe/framework/port:status", "//mediapipe/framework/port:status",
], ],
@@ -0,0 +1,84 @@
// 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.
#ifndef MEDIAPIPE_CALCULATORS_CORE_CONCATENATE_NORMALIZED_LIST_CALCULATOR_H_ // NOLINT
#define MEDIAPIPE_CALCULATORS_CORE_CONCATENATE_NORMALIZED_LIST_CALCULATOR_H_ // NOLINT
#include "mediapipe/calculators/core/concatenate_vector_calculator.pb.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/formats/landmark.pb.h"
#include "mediapipe/framework/port/canonical_errors.h"
#include "mediapipe/framework/port/ret_check.h"
#include "mediapipe/framework/port/status.h"
namespace mediapipe {
// Concatenates several NormalizedLandmarkList protos following stream index
// order. This class assumes that every input stream contains a
// NormalizedLandmarkList proto object.
class ConcatenateNormalizedLandmarkListCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
RET_CHECK(cc->Inputs().NumEntries() != 0);
RET_CHECK(cc->Outputs().NumEntries() == 1);
for (int i = 0; i < cc->Inputs().NumEntries(); ++i) {
cc->Inputs().Index(i).Set<NormalizedLandmarkList>();
}
cc->Outputs().Index(0).Set<NormalizedLandmarkList>();
return ::mediapipe::OkStatus();
}
::mediapipe::Status Open(CalculatorContext* cc) override {
cc->SetOffset(TimestampDiff(0));
only_emit_if_all_present_ =
cc->Options<::mediapipe::ConcatenateVectorCalculatorOptions>()
.only_emit_if_all_present();
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) override {
if (only_emit_if_all_present_) {
for (int i = 0; i < cc->Inputs().NumEntries(); ++i) {
if (cc->Inputs().Index(i).IsEmpty()) return ::mediapipe::OkStatus();
}
}
NormalizedLandmarkList output;
for (int i = 0; i < cc->Inputs().NumEntries(); ++i) {
if (cc->Inputs().Index(i).IsEmpty()) continue;
const NormalizedLandmarkList& input =
cc->Inputs().Index(i).Get<NormalizedLandmarkList>();
for (int j = 0; j < input.landmark_size(); ++j) {
const NormalizedLandmark& input_landmark = input.landmark(j);
*output.add_landmark() = input_landmark;
}
}
cc->Outputs().Index(0).AddPacket(
MakePacket<NormalizedLandmarkList>(output).At(cc->InputTimestamp()));
return ::mediapipe::OkStatus();
}
private:
bool only_emit_if_all_present_;
};
REGISTER_CALCULATOR(ConcatenateNormalizedLandmarkListCalculator);
} // namespace mediapipe
// NOLINTNEXTLINE
#endif // MEDIAPIPE_CALCULATORS_CORE_CONCATENATE_NORMALIZED_LIST_CALCULATOR_H_
@@ -0,0 +1,184 @@
// 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.
#include <memory>
#include <string>
#include <vector>
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/calculator_runner.h"
#include "mediapipe/framework/formats/landmark.pb.h"
#include "mediapipe/framework/port/gmock.h"
#include "mediapipe/framework/port/gtest.h"
#include "mediapipe/framework/port/parse_text_proto.h"
#include "mediapipe/framework/port/status_matchers.h" // NOLINT
namespace mediapipe {
constexpr float kLocationValue = 3;
NormalizedLandmarkList GenerateLandmarks(int landmarks_size,
int value_multiplier) {
NormalizedLandmarkList landmarks;
for (int i = 0; i < landmarks_size; ++i) {
NormalizedLandmark* landmark = landmarks.add_landmark();
landmark->set_x(value_multiplier * kLocationValue);
landmark->set_y(value_multiplier * kLocationValue);
landmark->set_z(value_multiplier * kLocationValue);
}
return landmarks;
}
void ValidateCombinedLandmarks(
const std::vector<NormalizedLandmarkList>& inputs,
const NormalizedLandmarkList& result) {
int element_id = 0;
int expected_size = 0;
for (int i = 0; i < inputs.size(); ++i) {
const NormalizedLandmarkList& landmarks_i = inputs[i];
expected_size += landmarks_i.landmark_size();
for (int j = 0; j < landmarks_i.landmark_size(); ++j) {
const NormalizedLandmark& expected = landmarks_i.landmark(j);
const NormalizedLandmark& got = result.landmark(element_id);
EXPECT_FLOAT_EQ(expected.x(), got.x());
EXPECT_FLOAT_EQ(expected.y(), got.y());
EXPECT_FLOAT_EQ(expected.z(), got.z());
++element_id;
}
}
EXPECT_EQ(expected_size, result.landmark_size());
}
void AddInputLandmarkLists(
const std::vector<NormalizedLandmarkList>& input_landmarks_vec,
int64 timestamp, CalculatorRunner* runner) {
for (int i = 0; i < input_landmarks_vec.size(); ++i) {
runner->MutableInputs()->Index(i).packets.push_back(
MakePacket<NormalizedLandmarkList>(input_landmarks_vec[i])
.At(Timestamp(timestamp)));
}
}
TEST(ConcatenateNormalizedLandmarkListCalculatorTest, EmptyVectorInputs) {
CalculatorRunner runner("ConcatenateNormalizedLandmarkListCalculator",
/*options_string=*/"", /*num_inputs=*/3,
/*num_outputs=*/1, /*num_side_packets=*/0);
NormalizedLandmarkList empty_list;
std::vector<NormalizedLandmarkList> inputs = {empty_list, empty_list,
empty_list};
AddInputLandmarkLists(inputs, /*timestamp=*/1, &runner);
MP_ASSERT_OK(runner.Run());
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
EXPECT_EQ(1, outputs.size());
EXPECT_EQ(0, outputs[0].Get<NormalizedLandmarkList>().landmark_size());
EXPECT_EQ(Timestamp(1), outputs[0].Timestamp());
}
TEST(ConcatenateNormalizedLandmarkListCalculatorTest, OneTimestamp) {
CalculatorRunner runner("ConcatenateNormalizedLandmarkListCalculator",
/*options_string=*/"", /*num_inputs=*/3,
/*num_outputs=*/1, /*num_side_packets=*/0);
NormalizedLandmarkList input_0 =
GenerateLandmarks(/*landmarks_size=*/3, /*value_multiplier=*/0);
NormalizedLandmarkList input_1 =
GenerateLandmarks(/*landmarks_size=*/1, /*value_multiplier=*/1);
NormalizedLandmarkList input_2 =
GenerateLandmarks(/*landmarks_size=*/2, /*value_multiplier=*/2);
std::vector<NormalizedLandmarkList> inputs = {input_0, input_1, input_2};
AddInputLandmarkLists(inputs, /*timestamp=*/1, &runner);
MP_ASSERT_OK(runner.Run());
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
EXPECT_EQ(1, outputs.size());
EXPECT_EQ(Timestamp(1), outputs[0].Timestamp());
const NormalizedLandmarkList& result =
outputs[0].Get<NormalizedLandmarkList>();
ValidateCombinedLandmarks(inputs, result);
}
TEST(ConcatenateNormalizedLandmarkListCalculatorTest,
TwoInputsAtTwoTimestamps) {
CalculatorRunner runner("ConcatenateNormalizedLandmarkListCalculator",
/*options_string=*/"", /*num_inputs=*/3,
/*num_outputs=*/1, /*num_side_packets=*/0);
NormalizedLandmarkList input_0 =
GenerateLandmarks(/*landmarks_size=*/3, /*value_multiplier=*/0);
NormalizedLandmarkList input_1 =
GenerateLandmarks(/*landmarks_size=*/1, /*value_multiplier=*/1);
NormalizedLandmarkList input_2 =
GenerateLandmarks(/*landmarks_size=*/2, /*value_multiplier=*/2);
std::vector<NormalizedLandmarkList> inputs = {input_0, input_1, input_2};
{ AddInputLandmarkLists(inputs, /*timestamp=*/1, &runner); }
{ AddInputLandmarkLists(inputs, /*timestamp=*/2, &runner); }
MP_ASSERT_OK(runner.Run());
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
EXPECT_EQ(2, outputs.size());
{
EXPECT_EQ(Timestamp(1), outputs[0].Timestamp());
const NormalizedLandmarkList& result =
outputs[0].Get<NormalizedLandmarkList>();
ValidateCombinedLandmarks(inputs, result);
}
{
EXPECT_EQ(Timestamp(2), outputs[1].Timestamp());
const NormalizedLandmarkList& result =
outputs[1].Get<NormalizedLandmarkList>();
ValidateCombinedLandmarks(inputs, result);
}
}
TEST(ConcatenateNormalizedLandmarkListCalculatorTest,
OneEmptyStreamStillOutput) {
CalculatorRunner runner("ConcatenateNormalizedLandmarkListCalculator",
/*options_string=*/"", /*num_inputs=*/2,
/*num_outputs=*/1, /*num_side_packets=*/0);
NormalizedLandmarkList input_0 =
GenerateLandmarks(/*landmarks_size=*/3, /*value_multiplier=*/0);
std::vector<NormalizedLandmarkList> inputs = {input_0};
AddInputLandmarkLists(inputs, /*timestamp=*/1, &runner);
MP_ASSERT_OK(runner.Run());
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
EXPECT_EQ(1, outputs.size());
EXPECT_EQ(Timestamp(1), outputs[0].Timestamp());
const NormalizedLandmarkList& result =
outputs[0].Get<NormalizedLandmarkList>();
ValidateCombinedLandmarks(inputs, result);
}
TEST(ConcatenateNormalizedLandmarkListCalculatorTest, OneEmptyStreamNoOutput) {
CalculatorRunner runner("ConcatenateNormalizedLandmarkListCalculator",
/*options_string=*/
"[mediapipe.ConcatenateVectorCalculatorOptions.ext]: "
"{only_emit_if_all_present: true}",
/*num_inputs=*/2,
/*num_outputs=*/1, /*num_side_packets=*/0);
NormalizedLandmarkList input_0 =
GenerateLandmarks(/*landmarks_size=*/3, /*value_multiplier=*/0);
std::vector<NormalizedLandmarkList> inputs = {input_0};
AddInputLandmarkLists(inputs, /*timestamp=*/1, &runner);
MP_ASSERT_OK(runner.Run());
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
EXPECT_EQ(0, outputs.size());
}
} // namespace mediapipe
@@ -16,7 +16,9 @@
#include <vector> #include <vector>
#include "mediapipe/framework/formats/classification.pb.h"
#include "mediapipe/framework/formats/landmark.pb.h" #include "mediapipe/framework/formats/landmark.pb.h"
#include "mediapipe/framework/port/integral_types.h"
#include "tensorflow/lite/interpreter.h" #include "tensorflow/lite/interpreter.h"
#if !defined(MEDIAPIPE_DISABLE_GL_COMPUTE) #if !defined(MEDIAPIPE_DISABLE_GL_COMPUTE)
@@ -45,6 +47,12 @@ REGISTER_CALCULATOR(ConcatenateFloatVectorCalculator);
typedef ConcatenateVectorCalculator<int32> ConcatenateInt32VectorCalculator; typedef ConcatenateVectorCalculator<int32> ConcatenateInt32VectorCalculator;
REGISTER_CALCULATOR(ConcatenateInt32VectorCalculator); REGISTER_CALCULATOR(ConcatenateInt32VectorCalculator);
typedef ConcatenateVectorCalculator<uint64> ConcatenateUInt64VectorCalculator;
REGISTER_CALCULATOR(ConcatenateUInt64VectorCalculator);
typedef ConcatenateVectorCalculator<bool> ConcatenateBoolVectorCalculator;
REGISTER_CALCULATOR(ConcatenateBoolVectorCalculator);
// Example config: // Example config:
// node { // node {
// calculator: "ConcatenateTfLiteTensorVectorCalculator" // calculator: "ConcatenateTfLiteTensorVectorCalculator"
@@ -60,6 +68,14 @@ typedef ConcatenateVectorCalculator<::mediapipe::NormalizedLandmark>
ConcatenateLandmarkVectorCalculator; ConcatenateLandmarkVectorCalculator;
REGISTER_CALCULATOR(ConcatenateLandmarkVectorCalculator); REGISTER_CALCULATOR(ConcatenateLandmarkVectorCalculator);
typedef ConcatenateVectorCalculator<::mediapipe::NormalizedLandmarkList>
ConcatenateLandmarListVectorCalculator;
REGISTER_CALCULATOR(ConcatenateLandmarListVectorCalculator);
typedef ConcatenateVectorCalculator<mediapipe::ClassificationList>
ConcatenateClassificationListVectorCalculator;
REGISTER_CALCULATOR(ConcatenateClassificationListVectorCalculator);
#if !defined(MEDIAPIPE_DISABLE_GL_COMPUTE) #if !defined(MEDIAPIPE_DISABLE_GL_COMPUTE)
typedef ConcatenateVectorCalculator<::tflite::gpu::gl::GlBuffer> typedef ConcatenateVectorCalculator<::tflite::gpu::gl::GlBuffer>
ConcatenateGlBufferVectorCalculator; ConcatenateGlBufferVectorCalculator;
@@ -15,6 +15,7 @@
#ifndef MEDIAPIPE_CALCULATORS_CORE_CONCATENATE_VECTOR_CALCULATOR_H_ #ifndef MEDIAPIPE_CALCULATORS_CORE_CONCATENATE_VECTOR_CALCULATOR_H_
#define MEDIAPIPE_CALCULATORS_CORE_CONCATENATE_VECTOR_CALCULATOR_H_ #define MEDIAPIPE_CALCULATORS_CORE_CONCATENATE_VECTOR_CALCULATOR_H_
#include <string>
#include <type_traits> #include <type_traits>
#include <vector> #include <vector>
@@ -26,10 +27,10 @@
namespace mediapipe { namespace mediapipe {
// Concatenates several std::vector<T> following stream index order. This class // Concatenates several objects of type T or std::vector<T> following stream
// assumes that every input stream contains the vector<T> type. To use this // index order. This class assumes that every input stream contains either T or
// class for a particular type T, regisiter a calculator using // vector<T> type. To use this class for a particular type T, regisiter a
// ConcatenateVectorCalculator<T>. // calculator using ConcatenateVectorCalculator<T>.
template <typename T> template <typename T>
class ConcatenateVectorCalculator : public CalculatorBase { class ConcatenateVectorCalculator : public CalculatorBase {
public: public:
@@ -38,7 +39,8 @@ class ConcatenateVectorCalculator : public CalculatorBase {
RET_CHECK(cc->Outputs().NumEntries() == 1); RET_CHECK(cc->Outputs().NumEntries() == 1);
for (int i = 0; i < cc->Inputs().NumEntries(); ++i) { for (int i = 0; i < cc->Inputs().NumEntries(); ++i) {
cc->Inputs().Index(i).Set<std::vector<T>>(); // Actual type T or vector<T> will be validated in Process().
cc->Inputs().Index(i).SetAny();
} }
cc->Outputs().Index(0).Set<std::vector<T>>(); cc->Outputs().Index(0).Set<std::vector<T>>();
@@ -69,9 +71,19 @@ class ConcatenateVectorCalculator : public CalculatorBase {
CalculatorContext* cc) { CalculatorContext* cc) {
auto output = absl::make_unique<std::vector<U>>(); auto output = absl::make_unique<std::vector<U>>();
for (int i = 0; i < cc->Inputs().NumEntries(); ++i) { for (int i = 0; i < cc->Inputs().NumEntries(); ++i) {
if (cc->Inputs().Index(i).IsEmpty()) continue; auto& input = cc->Inputs().Index(i);
const std::vector<U>& input = cc->Inputs().Index(i).Get<std::vector<U>>();
output->insert(output->end(), input.begin(), input.end()); if (input.IsEmpty()) continue;
if (input.Value().ValidateAsType<U>().ok()) {
const U& value = input.Get<U>();
output->push_back(value);
} else if (input.Value().ValidateAsType<std::vector<U>>().ok()) {
const std::vector<U>& value = input.Get<std::vector<U>>();
output->insert(output->end(), value.begin(), value.end());
} else {
return ::mediapipe::InvalidArgumentError("Invalid input stream type.");
}
} }
cc->Outputs().Index(0).Add(output.release(), cc->InputTimestamp()); cc->Outputs().Index(0).Add(output.release(), cc->InputTimestamp());
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
@@ -88,17 +100,32 @@ class ConcatenateVectorCalculator : public CalculatorBase {
CalculatorContext* cc) { CalculatorContext* cc) {
auto output = absl::make_unique<std::vector<U>>(); auto output = absl::make_unique<std::vector<U>>();
for (int i = 0; i < cc->Inputs().NumEntries(); ++i) { for (int i = 0; i < cc->Inputs().NumEntries(); ++i) {
if (cc->Inputs().Index(i).IsEmpty()) continue; auto& input = cc->Inputs().Index(i);
::mediapipe::StatusOr<std::unique_ptr<std::vector<U>>> input_status =
cc->Inputs().Index(i).Value().Consume<std::vector<U>>(); if (input.IsEmpty()) continue;
if (input_status.ok()) {
std::unique_ptr<std::vector<U>> input_vector = if (input.Value().ValidateAsType<U>().ok()) {
std::move(input_status).ValueOrDie(); ::mediapipe::StatusOr<std::unique_ptr<U>> value_status =
output->insert(output->end(), input.Value().Consume<U>();
std::make_move_iterator(input_vector->begin()), if (value_status.ok()) {
std::make_move_iterator(input_vector->end())); std::unique_ptr<U> value = std::move(value_status).ValueOrDie();
output->push_back(std::move(*value));
} else {
return value_status.status();
}
} else if (input.Value().ValidateAsType<std::vector<U>>().ok()) {
::mediapipe::StatusOr<std::unique_ptr<std::vector<U>>> value_status =
input.Value().Consume<std::vector<U>>();
if (value_status.ok()) {
std::unique_ptr<std::vector<U>> value =
std::move(value_status).ValueOrDie();
output->insert(output->end(), std::make_move_iterator(value->begin()),
std::make_move_iterator(value->end()));
} else {
return value_status.status();
}
} else { } else {
return input_status.status(); return ::mediapipe::InvalidArgumentError("Invalid input stream type.");
} }
} }
cc->Outputs().Index(0).Add(output.release(), cc->InputTimestamp()); cc->Outputs().Index(0).Add(output.release(), cc->InputTimestamp());
@@ -109,7 +136,7 @@ class ConcatenateVectorCalculator : public CalculatorBase {
::mediapipe::Status ConsumeAndConcatenateVectors(std::false_type, ::mediapipe::Status ConsumeAndConcatenateVectors(std::false_type,
CalculatorContext* cc) { CalculatorContext* cc) {
return ::mediapipe::InternalError( return ::mediapipe::InternalError(
"Cannot copy or move input vectors to concatenate them"); "Cannot copy or move inputs to concatenate them");
} }
private: private:
@@ -30,11 +30,29 @@ namespace mediapipe {
typedef ConcatenateVectorCalculator<int> TestConcatenateIntVectorCalculator; typedef ConcatenateVectorCalculator<int> TestConcatenateIntVectorCalculator;
REGISTER_CALCULATOR(TestConcatenateIntVectorCalculator); REGISTER_CALCULATOR(TestConcatenateIntVectorCalculator);
void AddInputVector(int index, const std::vector<int>& input, int64 timestamp,
CalculatorRunner* runner) {
runner->MutableInputs()->Index(index).packets.push_back(
MakePacket<std::vector<int>>(input).At(Timestamp(timestamp)));
}
void AddInputVectors(const std::vector<std::vector<int>>& inputs, void AddInputVectors(const std::vector<std::vector<int>>& inputs,
int64 timestamp, CalculatorRunner* runner) { int64 timestamp, CalculatorRunner* runner) {
for (int i = 0; i < inputs.size(); ++i) { for (int i = 0; i < inputs.size(); ++i) {
runner->MutableInputs()->Index(i).packets.push_back( AddInputVector(i, inputs[i], timestamp, runner);
MakePacket<std::vector<int>>(inputs[i]).At(Timestamp(timestamp))); }
}
void AddInputItem(int index, int input, int64 timestamp,
CalculatorRunner* runner) {
runner->MutableInputs()->Index(index).packets.push_back(
MakePacket<int>(input).At(Timestamp(timestamp)));
}
void AddInputItems(const std::vector<int>& inputs, int64 timestamp,
CalculatorRunner* runner) {
for (int i = 0; i < inputs.size(); ++i) {
AddInputItem(i, inputs[i], timestamp, runner);
} }
} }
@@ -131,6 +149,135 @@ TEST(TestConcatenateIntVectorCalculatorTest, OneEmptyStreamNoOutput) {
EXPECT_EQ(0, outputs.size()); EXPECT_EQ(0, outputs.size());
} }
TEST(TestConcatenateIntVectorCalculatorTest, ItemsOneTimestamp) {
CalculatorRunner runner("TestConcatenateIntVectorCalculator",
/*options_string=*/"", /*num_inputs=*/3,
/*num_outputs=*/1, /*num_side_packets=*/0);
std::vector<int> inputs = {1, 2, 3};
AddInputItems(inputs, /*timestamp=*/1, &runner);
MP_ASSERT_OK(runner.Run());
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
EXPECT_EQ(1, outputs.size());
EXPECT_EQ(Timestamp(1), outputs[0].Timestamp());
std::vector<int> expected_vector = {1, 2, 3};
EXPECT_EQ(expected_vector, outputs[0].Get<std::vector<int>>());
}
TEST(TestConcatenateIntVectorCalculatorTest, ItemsTwoInputsAtTwoTimestamps) {
CalculatorRunner runner("TestConcatenateIntVectorCalculator",
/*options_string=*/"", /*num_inputs=*/3,
/*num_outputs=*/1, /*num_side_packets=*/0);
{
std::vector<int> inputs = {1, 2, 3};
AddInputItems(inputs, /*timestamp=*/1, &runner);
}
{
std::vector<int> inputs = {4, 5, 6};
AddInputItems(inputs, /*timestamp=*/2, &runner);
}
MP_ASSERT_OK(runner.Run());
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
EXPECT_EQ(2, outputs.size());
{
EXPECT_EQ(3, outputs[0].Get<std::vector<int>>().size());
EXPECT_EQ(Timestamp(1), outputs[0].Timestamp());
std::vector<int> expected_vector = {1, 2, 3};
EXPECT_EQ(expected_vector, outputs[0].Get<std::vector<int>>());
}
{
EXPECT_EQ(3, outputs[1].Get<std::vector<int>>().size());
EXPECT_EQ(Timestamp(2), outputs[1].Timestamp());
std::vector<int> expected_vector = {4, 5, 6};
EXPECT_EQ(expected_vector, outputs[1].Get<std::vector<int>>());
}
}
TEST(TestConcatenateIntVectorCalculatorTest, ItemsOneEmptyStreamStillOutput) {
CalculatorRunner runner("TestConcatenateIntVectorCalculator",
/*options_string=*/"", /*num_inputs=*/3,
/*num_outputs=*/1, /*num_side_packets=*/0);
// No third input item.
std::vector<int> inputs = {1, 2};
AddInputItems(inputs, /*timestamp=*/1, &runner);
MP_ASSERT_OK(runner.Run());
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
EXPECT_EQ(1, outputs.size());
EXPECT_EQ(Timestamp(1), outputs[0].Timestamp());
std::vector<int> expected_vector = {1, 2};
EXPECT_EQ(expected_vector, outputs[0].Get<std::vector<int>>());
}
TEST(TestConcatenateIntVectorCalculatorTest, ItemsOneEmptyStreamNoOutput) {
CalculatorRunner runner("TestConcatenateIntVectorCalculator",
/*options_string=*/
"[mediapipe.ConcatenateVectorCalculatorOptions.ext]: "
"{only_emit_if_all_present: true}",
/*num_inputs=*/3,
/*num_outputs=*/1, /*num_side_packets=*/0);
// No third input item.
std::vector<int> inputs = {1, 2};
AddInputItems(inputs, /*timestamp=*/1, &runner);
MP_ASSERT_OK(runner.Run());
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
EXPECT_EQ(0, outputs.size());
}
TEST(TestConcatenateIntVectorCalculatorTest, MixedVectorsAndItems) {
CalculatorRunner runner("TestConcatenateIntVectorCalculator",
/*options_string=*/"", /*num_inputs=*/4,
/*num_outputs=*/1, /*num_side_packets=*/0);
std::vector<int> vector_0 = {1, 2};
std::vector<int> vector_1 = {3, 4, 5};
int item_0 = 6;
int item_1 = 7;
AddInputVector(/*index*/ 0, vector_0, /*timestamp=*/1, &runner);
AddInputVector(/*index*/ 1, vector_1, /*timestamp=*/1, &runner);
AddInputItem(/*index*/ 2, item_0, /*timestamp=*/1, &runner);
AddInputItem(/*index*/ 3, item_1, /*timestamp=*/1, &runner);
MP_ASSERT_OK(runner.Run());
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
EXPECT_EQ(1, outputs.size());
EXPECT_EQ(Timestamp(1), outputs[0].Timestamp());
std::vector<int> expected_vector = {1, 2, 3, 4, 5, 6, 7};
EXPECT_EQ(expected_vector, outputs[0].Get<std::vector<int>>());
}
TEST(TestConcatenateIntVectorCalculatorTest, MixedVectorsAndItemsAnother) {
CalculatorRunner runner("TestConcatenateIntVectorCalculator",
/*options_string=*/"", /*num_inputs=*/4,
/*num_outputs=*/1, /*num_side_packets=*/0);
int item_0 = 1;
std::vector<int> vector_0 = {2, 3};
std::vector<int> vector_1 = {4, 5, 6};
int item_1 = 7;
AddInputItem(/*index*/ 0, item_0, /*timestamp=*/1, &runner);
AddInputVector(/*index*/ 1, vector_0, /*timestamp=*/1, &runner);
AddInputVector(/*index*/ 2, vector_1, /*timestamp=*/1, &runner);
AddInputItem(/*index*/ 3, item_1, /*timestamp=*/1, &runner);
MP_ASSERT_OK(runner.Run());
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
EXPECT_EQ(1, outputs.size());
EXPECT_EQ(Timestamp(1), outputs[0].Timestamp());
std::vector<int> expected_vector = {1, 2, 3, 4, 5, 6, 7};
EXPECT_EQ(expected_vector, outputs[0].Get<std::vector<int>>());
}
void AddInputVectors(const std::vector<std::vector<float>>& inputs, void AddInputVectors(const std::vector<std::vector<float>>& inputs,
int64 timestamp, CalculatorRunner* runner) { int64 timestamp, CalculatorRunner* runner) {
for (int i = 0; i < inputs.size(); ++i) { for (int i = 0; i < inputs.size(); ++i) {
@@ -18,6 +18,7 @@
#include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/collection_item_id.h" #include "mediapipe/framework/collection_item_id.h"
#include "mediapipe/framework/port/canonical_errors.h" #include "mediapipe/framework/port/canonical_errors.h"
#include "mediapipe/framework/port/integral_types.h"
#include "mediapipe/framework/port/ret_check.h" #include "mediapipe/framework/port/ret_check.h"
#include "mediapipe/framework/port/status.h" #include "mediapipe/framework/port/status.h"
@@ -71,6 +72,8 @@ class ConstantSidePacketCalculator : public CalculatorBase {
packet.Set<bool>(); packet.Set<bool>();
} else if (packet_options.has_string_value()) { } else if (packet_options.has_string_value()) {
packet.Set<std::string>(); packet.Set<std::string>();
} else if (packet_options.has_uint64_value()) {
packet.Set<uint64>();
} else { } else {
return ::mediapipe::InvalidArgumentError( return ::mediapipe::InvalidArgumentError(
"None of supported values were specified in options."); "None of supported values were specified in options.");
@@ -95,6 +98,8 @@ class ConstantSidePacketCalculator : public CalculatorBase {
packet.Set(MakePacket<bool>(packet_options.bool_value())); packet.Set(MakePacket<bool>(packet_options.bool_value()));
} else if (packet_options.has_string_value()) { } else if (packet_options.has_string_value()) {
packet.Set(MakePacket<std::string>(packet_options.string_value())); packet.Set(MakePacket<std::string>(packet_options.string_value()));
} else if (packet_options.has_uint64_value()) {
packet.Set(MakePacket<uint64>(packet_options.uint64_value()));
} else { } else {
return ::mediapipe::InvalidArgumentError( return ::mediapipe::InvalidArgumentError(
"None of supported values were specified in options."); "None of supported values were specified in options.");
@@ -29,6 +29,7 @@ message ConstantSidePacketCalculatorOptions {
float float_value = 2; float float_value = 2;
bool bool_value = 3; bool bool_value = 3;
string string_value = 4; string string_value = 4;
uint64 uint64_value = 5;
} }
} }
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include <memory>
#include "mediapipe/calculators/core/split_vector_calculator.h" #include "mediapipe/calculators/core/split_vector_calculator.h"
#include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/calculator_runner.h" #include "mediapipe/framework/calculator_runner.h"
@@ -233,5 +235,71 @@ TEST(MuxCalculatorTest, InputStreamSelector_MuxInputStreamHandler) {
kOutputName, output_fn); kOutputName, output_fn);
EXPECT_EQ(output, input_packets); EXPECT_EQ(output, input_packets);
} }
constexpr char kDualInputGraphConfig[] = R"proto(
input_stream: "input_0"
input_stream: "input_1"
input_stream: "input_select"
output_stream: "test_output"
node {
calculator: "MuxCalculator"
input_stream: "INPUT:0:input_0"
input_stream: "INPUT:1:input_1"
input_stream: "SELECT:input_select"
output_stream: "OUTPUT:test_output"
}
)proto";
TEST(MuxCalculatorTest, DiscardSkippedInputs_MuxInputStreamHandler) {
CalculatorGraphConfig config =
::mediapipe::ParseTextProtoOrDie<CalculatorGraphConfig>(
kDualInputGraphConfig);
CalculatorGraph graph;
MP_ASSERT_OK(graph.Initialize(config));
std::shared_ptr<int> output;
MP_ASSERT_OK(
graph.ObserveOutputStream("test_output", [&output](const Packet& p) {
output = p.Get<std::shared_ptr<int>>();
return ::mediapipe::OkStatus();
}));
MP_ASSERT_OK(graph.StartRun({}));
auto one = std::make_shared<int>(1);
auto two = std::make_shared<int>(2);
auto three = std::make_shared<int>(3);
std::weak_ptr<int> one_weak = one;
std::weak_ptr<int> two_weak = two;
MP_ASSERT_OK(graph.AddPacketToInputStream(
"input_0",
MakePacket<std::shared_ptr<int>>(std::move(one)).At(Timestamp(0))));
MP_ASSERT_OK(graph.AddPacketToInputStream(
"input_1",
MakePacket<std::shared_ptr<int>>(std::move(two)).At(Timestamp(0))));
MP_ASSERT_OK(graph.AddPacketToInputStream(
"input_1",
MakePacket<std::shared_ptr<int>>(std::move(three)).At(Timestamp(1))));
EXPECT_EQ(one, nullptr);
EXPECT_EQ(two, nullptr);
EXPECT_EQ(three, nullptr);
MP_ASSERT_OK(graph.AddPacketToInputStream(
"input_select", MakePacket<int>(0).At(Timestamp(0))));
MP_ASSERT_OK(graph.WaitUntilIdle());
EXPECT_EQ(*output, 1);
EXPECT_NE(one_weak.lock(), nullptr);
EXPECT_EQ(two_weak.lock(), nullptr);
MP_ASSERT_OK(graph.AddPacketToInputStream(
"input_select", MakePacket<int>(1).At(Timestamp(1))));
MP_ASSERT_OK(graph.WaitUntilIdle());
EXPECT_EQ(*output, 3);
MP_ASSERT_OK(graph.CloseAllInputStreams());
MP_ASSERT_OK(graph.WaitUntilDone());
}
} // namespace } // namespace
} // namespace mediapipe } // namespace mediapipe
@@ -0,0 +1,84 @@
// 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.
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/port/status.h"
namespace mediapipe {
// For each non empty input packet, emits a single output packet containing a
// boolean value "true", "false" in response to empty packets (a.k.a. timestamp
// bound updates) This can be used to "flag" the presence of an arbitrary packet
// type as input into a downstream calculator.
//
// Inputs:
// PACKET - any type.
//
// Outputs:
// PRESENCE - bool.
// "true" if packet is not empty, "false" if there's timestamp bound update
// instead.
//
// Examples:
// node: {
// calculator: "PacketPresenceCalculator"
// input_stream: "PACKET:packet"
// output_stream: "PRESENCE:presence"
// }
//
// This calculator can be used in conjuction with GateCalculator in order to
// allow/disallow processing. For instance:
// node: {
// calculator: "PacketPresenceCalculator"
// input_stream: "PACKET:value"
// output_stream: "PRESENCE:disallow_if_present"
// }
// node {
// calculator: "GateCalculator"
// input_stream: "image"
// input_stream: "DISALLOW:disallow_if_present"
// output_stream: "image_for_processing"
// options: {
// [mediapipe.GateCalculatorOptions.ext] {
// empty_packets_as_allow: true
// }
// }
// }
class PacketPresenceCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
cc->Inputs().Tag("PACKET").SetAny();
cc->Outputs().Tag("PRESENCE").Set<bool>();
// Process() function is invoked in response to input stream timestamp
// bound updates.
cc->SetProcessTimestampBounds(true);
return ::mediapipe::OkStatus();
}
::mediapipe::Status Open(CalculatorContext* cc) override {
cc->SetOffset(TimestampDiff(0));
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) final {
cc->Outputs()
.Tag("PRESENCE")
.AddPacket(MakePacket<bool>(!cc->Inputs().Tag("PACKET").IsEmpty())
.At(cc->InputTimestamp()));
return ::mediapipe::OkStatus();
}
};
REGISTER_CALCULATOR(PacketPresenceCalculator);
} // namespace mediapipe
@@ -0,0 +1,85 @@
// 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.
#include <functional>
#include <string>
#include <vector>
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/calculator_runner.h"
#include "mediapipe/framework/port/gmock.h"
#include "mediapipe/framework/port/gtest.h"
#include "mediapipe/framework/port/parse_text_proto.h"
#include "mediapipe/framework/port/status.h"
#include "mediapipe/framework/port/status_matchers.h"
#include "mediapipe/framework/timestamp.h"
#include "mediapipe/framework/tool/sink.h"
namespace mediapipe {
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Value;
namespace {
MATCHER_P2(BoolPacket, value, timestamp, "") {
return Value(arg.template Get<bool>(), Eq(value)) &&
Value(arg.Timestamp(), Eq(timestamp));
}
TEST(PreviousLoopbackCalculator, CorrectTimestamps) {
std::vector<Packet> output_packets;
CalculatorGraphConfig graph_config =
ParseTextProtoOrDie<CalculatorGraphConfig>(R"(
input_stream: 'allow'
input_stream: 'value'
node {
calculator: "GateCalculator"
input_stream: 'value'
input_stream: 'ALLOW:allow'
output_stream: 'gated_value'
}
node {
calculator: 'PacketPresenceCalculator'
input_stream: 'PACKET:gated_value'
output_stream: 'PRESENCE:presence'
}
)");
tool::AddVectorSink("presence", &graph_config, &output_packets);
CalculatorGraph graph;
MP_ASSERT_OK(graph.Initialize(graph_config, {}));
MP_ASSERT_OK(graph.StartRun({}));
auto send_packet = [&graph](int value, bool allow, Timestamp timestamp) {
MP_ASSERT_OK(graph.AddPacketToInputStream(
"value", MakePacket<int>(value).At(timestamp)));
MP_ASSERT_OK(graph.AddPacketToInputStream(
"allow", MakePacket<bool>(allow).At(timestamp)));
};
send_packet(10, false, Timestamp(10));
MP_EXPECT_OK(graph.WaitUntilIdle());
EXPECT_THAT(output_packets, ElementsAre(BoolPacket(false, Timestamp(10))));
output_packets.clear();
send_packet(20, true, Timestamp(11));
MP_EXPECT_OK(graph.WaitUntilIdle());
EXPECT_THAT(output_packets, ElementsAre(BoolPacket(true, Timestamp(11))));
MP_EXPECT_OK(graph.CloseAllInputStreams());
MP_EXPECT_OK(graph.WaitUntilDone());
}
} // namespace
} // namespace mediapipe
+32 -1
View File
@@ -14,7 +14,7 @@
load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library") load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library")
licenses(["notice"]) # Apache 2.0 licenses(["notice"])
package(default_visibility = ["//visibility:private"]) package(default_visibility = ["//visibility:private"])
@@ -630,3 +630,34 @@ cc_library(
], ],
alwayslink = 1, alwayslink = 1,
) )
cc_library(
name = "image_file_properties_calculator",
srcs = ["image_file_properties_calculator.cc"],
visibility = ["//visibility:public"],
deps = [
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:image_file_properties_cc_proto",
"//mediapipe/framework/port:ret_check",
"//mediapipe/framework/port:status",
"@easyexif",
],
alwayslink = 1,
)
cc_test(
name = "image_file_properties_calculator_test",
srcs = ["image_file_properties_calculator_test.cc"],
data = ["//mediapipe/calculators/image/testdata:test_images"],
deps = [
":image_file_properties_calculator",
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework:calculator_runner",
"//mediapipe/framework/deps:file_path",
"//mediapipe/framework/formats:image_file_properties_cc_proto",
"//mediapipe/framework/port:file_helpers",
"//mediapipe/framework/port:gtest_main",
"//mediapipe/framework/port:parse_text_proto",
"//mediapipe/framework/port:status",
],
)
@@ -127,6 +127,10 @@ REGISTER_CALCULATOR(ImageCroppingCalculator);
} }
options_ = cc->Options<mediapipe::ImageCroppingCalculatorOptions>(); options_ = cc->Options<mediapipe::ImageCroppingCalculatorOptions>();
output_max_width_ =
options_.has_output_max_width() ? options_.output_max_width() : FLT_MAX;
output_max_height_ =
options_.has_output_max_height() ? options_.output_max_height() : FLT_MAX;
if (use_gpu_) { if (use_gpu_) {
#if !defined(MEDIAPIPE_DISABLE_GPU) #if !defined(MEDIAPIPE_DISABLE_GPU)
@@ -234,20 +238,27 @@ REGISTER_CALCULATOR(ImageCroppingCalculator);
cv::Mat src_points; cv::Mat src_points;
cv::boxPoints(min_rect, src_points); cv::boxPoints(min_rect, src_points);
float output_width = min_rect.size.width;
float output_height = min_rect.size.height;
float scale = std::min({1.0f, output_max_width_ / output_width,
output_max_height_ / output_height});
output_width *= scale;
output_height *= scale;
float dst_corners[8] = {0, float dst_corners[8] = {0,
min_rect.size.height - 1, output_height - 1,
0, 0,
0, 0,
min_rect.size.width - 1, output_width - 1,
0, 0,
min_rect.size.width - 1, output_width - 1,
min_rect.size.height - 1}; output_height - 1};
cv::Mat dst_points = cv::Mat(4, 2, CV_32F, dst_corners); cv::Mat dst_points = cv::Mat(4, 2, CV_32F, dst_corners);
cv::Mat projection_matrix = cv::Mat projection_matrix =
cv::getPerspectiveTransform(src_points, dst_points); cv::getPerspectiveTransform(src_points, dst_points);
cv::Mat cropped_image; cv::Mat cropped_image;
cv::warpPerspective(input_mat, cropped_image, projection_matrix, cv::warpPerspective(input_mat, cropped_image, projection_matrix,
cv::Size(min_rect.size.width, min_rect.size.height), cv::Size(output_width, output_height),
/* flags = */ 0, /* flags = */ 0,
/* borderMode = */ border_mode); /* borderMode = */ border_mode);
@@ -439,6 +450,12 @@ void ImageCroppingCalculator::GetOutputDimensions(CalculatorContext* cc,
int width = static_cast<int>(std::round((col_max - col_min) * src_width)); int width = static_cast<int>(std::round((col_max - col_min) * src_width));
int height = static_cast<int>(std::round((row_max - row_min) * src_height)); int height = static_cast<int>(std::round((row_max - row_min) * src_height));
float scale =
std::min({1.0f, output_max_width_ / width, output_max_height_ / height});
width *= scale;
height *= scale;
// Minimum output dimension 1x1 prevents creation of textures with 0x0. // Minimum output dimension 1x1 prevents creation of textures with 0x0.
*dst_width = std::max(1, width); *dst_width = std::max(1, width);
*dst_height = std::max(1, height); *dst_height = std::max(1, height);
@@ -1,6 +1,8 @@
#ifndef MEDIAPIPE_CALCULATORS_IMAGE_IMAGE_CROPPING_CALCULATOR_H_ #ifndef MEDIAPIPE_CALCULATORS_IMAGE_IMAGE_CROPPING_CALCULATOR_H_
#define MEDIAPIPE_CALCULATORS_IMAGE_IMAGE_CROPPING_CALCULATOR_H_ #define MEDIAPIPE_CALCULATORS_IMAGE_IMAGE_CROPPING_CALCULATOR_H_
#include <float.h>
#include "mediapipe/calculators/image/image_cropping_calculator.pb.h" #include "mediapipe/calculators/image/image_cropping_calculator.pb.h"
#include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/calculator_framework.h"
@@ -80,6 +82,8 @@ class ImageCroppingCalculator : public CalculatorBase {
bool use_gpu_ = false; bool use_gpu_ = false;
// Output texture corners (4) after transoformation in normalized coordinates. // Output texture corners (4) after transoformation in normalized coordinates.
float transformed_points_[8]; float transformed_points_[8];
float output_max_width_ = FLT_MAX;
float output_max_height_ = FLT_MAX;
#if !defined(MEDIAPIPE_DISABLE_GPU) #if !defined(MEDIAPIPE_DISABLE_GPU)
bool gpu_initialized_ = false; bool gpu_initialized_ = false;
mediapipe::GlCalculatorHelper gpu_helper_; mediapipe::GlCalculatorHelper gpu_helper_;
@@ -51,4 +51,10 @@ message ImageCroppingCalculatorOptions {
// Specifies behaviour for crops that go beyond image borders. // Specifies behaviour for crops that go beyond image borders.
optional BorderMode border_mode = 8 [default = BORDER_ZERO]; optional BorderMode border_mode = 8 [default = BORDER_ZERO];
// Specifies limits for the size of the output image. It will be scaled down,
// preserving ratio, to fit within. These do not change which area of the
// input is selected for cropping.
optional int32 output_max_width = 9;
optional int32 output_max_height = 10;
} }
@@ -0,0 +1,195 @@
// 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.
#include <memory>
#include "exif.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/formats/image_file_properties.pb.h"
#include "mediapipe/framework/port/canonical_errors.h"
#include "mediapipe/framework/port/status.h"
namespace mediapipe {
namespace {
// 35 MM sensor has dimensions 36 mm x 24 mm, so diagonal length is
// sqrt(36^2 + 24^2).
static const double SENSOR_DIAGONAL_35MM = std::sqrt(1872.0);
::mediapipe::StatusOr<double> ComputeFocalLengthInPixels(
int image_width, int image_height, double focal_length_35mm,
double focal_length_mm) {
// TODO: Allow returning image file properties even when focal length
// computation is not possible.
if (image_width == 0 || image_height == 0) {
return ::mediapipe::InternalError(
"Image dimensions should be non-zero to compute focal length in "
"pixels.");
}
if (focal_length_mm == 0) {
return ::mediapipe::InternalError(
"Focal length in mm should be non-zero to compute focal length in "
"pixels.");
}
if (focal_length_35mm == 0) {
return ::mediapipe::InternalError(
"Focal length in 35 mm should be non-zero to compute focal length in "
"pixels.");
}
// Derived from
// https://en.wikipedia.org/wiki/35_mm_equivalent_focal_length#Calculation.
/// Using focal_length_35mm = focal_length_mm * SENSOR_DIAGONAL_35MM /
/// sensor_diagonal_mm, we can calculate the diagonal length of the sensor in
/// millimeters i.e. sensor_diagonal_mm.
double sensor_diagonal_mm =
SENSOR_DIAGONAL_35MM / focal_length_35mm * focal_length_mm;
// Note that for the following computations, the longer dimension is treated
// as image width and the shorter dimension is treated as image height.
int width = image_width;
int height = image_height;
if (image_height > image_width) {
width = image_height;
height = image_width;
}
double inv_aspect_ratio = (double)height / width;
// Compute sensor width.
/// Using Pythagoras theorem, sensor_width^2 + sensor_height^2 =
/// sensor_diagonal_mm^2. We can substitute sensor_width / sensor_height with
/// the aspect ratio calculated in pixels to compute the sensor width.
double sensor_width = std::sqrt((sensor_diagonal_mm * sensor_diagonal_mm) /
(1.0 + inv_aspect_ratio * inv_aspect_ratio));
// Compute focal length in pixels.
double focal_length_pixels = width * focal_length_mm / sensor_width;
return focal_length_pixels;
}
::mediapipe::StatusOr<ImageFileProperties> GetImageFileProperites(
const std::string& image_bytes) {
easyexif::EXIFInfo result;
int code = result.parseFrom(image_bytes);
if (code) {
return ::mediapipe::InternalError("Error parsing EXIF, code: " +
std::to_string(code));
}
ImageFileProperties properties;
properties.set_image_width(result.ImageWidth);
properties.set_image_height(result.ImageHeight);
properties.set_focal_length_mm(result.FocalLength);
properties.set_focal_length_35mm(result.FocalLengthIn35mm);
ASSIGN_OR_RETURN(auto focal_length_pixels,
ComputeFocalLengthInPixels(properties.image_width(),
properties.image_height(),
properties.focal_length_35mm(),
properties.focal_length_mm()));
properties.set_focal_length_pixels(focal_length_pixels);
return properties;
}
} // namespace
// Calculator to extract EXIF information from an image file. The input is
// a std::string containing raw byte data from a file, and the output is an
// ImageFileProperties proto object with the relevant fields filled in.
// The calculator accepts the input as a stream or a side packet, and can output
// the result as a stream or a side packet. The calculator checks that if an
// output stream is present, it outputs to that stream, and if not, it checks if
// it can output to a side packet.
//
// Example config with input and output streams:
// node {
// calculator: "ImageFilePropertiesCalculator"
// input_stream: "image_bytes"
// output_stream: "image_properties"
// }
// Example config with input and output side packets:
// node {
// calculator: "ImageFilePropertiesCalculator"
// input_side_packet: "image_bytes"
// output_side_packet: "image_properties"
// }
class ImageFilePropertiesCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
if (cc->Inputs().NumEntries() != 0) {
RET_CHECK(cc->Inputs().NumEntries() == 1);
cc->Inputs().Index(0).Set<std::string>();
} else {
RET_CHECK(cc->InputSidePackets().NumEntries() == 1);
cc->InputSidePackets().Index(0).Set<std::string>();
}
if (cc->Outputs().NumEntries() != 0) {
RET_CHECK(cc->Outputs().NumEntries() == 1);
cc->Outputs().Index(0).Set<::mediapipe::ImageFileProperties>();
} else {
RET_CHECK(cc->OutputSidePackets().NumEntries() == 1);
cc->OutputSidePackets().Index(0).Set<::mediapipe::ImageFileProperties>();
}
return ::mediapipe::OkStatus();
}
::mediapipe::Status Open(CalculatorContext* cc) override {
cc->SetOffset(TimestampDiff(0));
if (cc->InputSidePackets().NumEntries() == 1) {
const std::string& image_bytes =
cc->InputSidePackets().Index(0).Get<std::string>();
ASSIGN_OR_RETURN(properties_, GetImageFileProperites(image_bytes));
read_properties_ = true;
}
if (read_properties_ && cc->OutputSidePackets().NumEntries() == 1) {
cc->OutputSidePackets().Index(0).Set(
MakePacket<ImageFileProperties>(properties_));
}
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) override {
if (cc->Inputs().NumEntries() == 1) {
if (cc->Inputs().Index(0).IsEmpty()) {
return ::mediapipe::OkStatus();
}
const std::string& image_bytes = cc->Inputs().Index(0).Get<std::string>();
ASSIGN_OR_RETURN(properties_, GetImageFileProperites(image_bytes));
read_properties_ = true;
}
if (read_properties_) {
if (cc->Outputs().NumEntries() == 1) {
cc->Outputs().Index(0).AddPacket(
MakePacket<ImageFileProperties>(properties_)
.At(cc->InputTimestamp()));
} else {
cc->OutputSidePackets().Index(0).Set(
MakePacket<ImageFileProperties>(properties_)
.At(::mediapipe::Timestamp::Unset()));
}
}
return ::mediapipe::OkStatus();
}
private:
ImageFileProperties properties_;
bool read_properties_ = false;
};
REGISTER_CALCULATOR(ImageFilePropertiesCalculator);
} // namespace mediapipe
@@ -0,0 +1,134 @@
// Copyright 2018 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.
#include <math.h>
#include <cmath>
#include <limits>
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/calculator_runner.h"
#include "mediapipe/framework/deps/file_path.h"
#include "mediapipe/framework/formats/image_file_properties.pb.h"
#include "mediapipe/framework/port/file_helpers.h"
#include "mediapipe/framework/port/gmock.h"
#include "mediapipe/framework/port/gtest.h"
#include "mediapipe/framework/port/parse_text_proto.h"
#include "mediapipe/framework/port/status_matchers.h"
namespace mediapipe {
namespace {
constexpr char kImageFilePath[] =
"/mediapipe/calculators/image/testdata/"
"front_camera_pixel2.jpg";
constexpr int kExpectedWidth = 2448;
constexpr int kExpectedHeight = 3264;
constexpr double kExpectedFocalLengthMm = 3.38;
constexpr double kExpectedFocalLengthIn35Mm = 25;
constexpr double kExpectedFocalLengthPixels = 2357.48;
double RoundToNDecimals(double value, int n) {
return std::round(value * pow(10.0, n)) / pow(10.0, n);
}
TEST(ImageFilePropertiesCalculatorTest, ReadsFocalLengthFromJpegInStreams) {
std::string image_filepath = file::JoinPath("./", kImageFilePath);
std::string image_contents;
MP_ASSERT_OK(file::GetContents(image_filepath, &image_contents));
CalculatorGraphConfig::Node node_config =
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"(
calculator: "ImageFilePropertiesCalculator"
input_stream: "image_bytes"
output_stream: "properties"
)");
CalculatorRunner runner(node_config);
runner.MutableInputs()->Index(0).packets.push_back(
MakePacket<std::string>(image_contents).At(Timestamp(0)));
MP_ASSERT_OK(runner.Run());
const auto& outputs = runner.Outputs();
ASSERT_EQ(1, outputs.NumEntries());
const std::vector<Packet>& packets = outputs.Index(0).packets;
ASSERT_EQ(1, packets.size());
const auto& result = packets[0].Get<::mediapipe::ImageFileProperties>();
EXPECT_EQ(kExpectedWidth, result.image_width());
EXPECT_EQ(kExpectedHeight, result.image_height());
EXPECT_DOUBLE_EQ(kExpectedFocalLengthMm, result.focal_length_mm());
EXPECT_DOUBLE_EQ(kExpectedFocalLengthIn35Mm, result.focal_length_35mm());
EXPECT_DOUBLE_EQ(kExpectedFocalLengthPixels,
RoundToNDecimals(result.focal_length_pixels(), /*n=*/2));
}
TEST(ImageFilePropertiesCalculatorTest, ReadsFocalLengthFromJpegInSidePackets) {
std::string image_filepath = file::JoinPath("./", kImageFilePath);
std::string image_contents;
MP_ASSERT_OK(file::GetContents(image_filepath, &image_contents));
CalculatorGraphConfig::Node node_config =
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"(
calculator: "ImageFilePropertiesCalculator"
input_side_packet: "image_bytes"
output_side_packet: "properties"
)");
CalculatorRunner runner(node_config);
runner.MutableSidePackets()->Index(0) =
MakePacket<std::string>(image_contents).At(Timestamp(0));
MP_ASSERT_OK(runner.Run());
const auto& outputs = runner.OutputSidePackets();
EXPECT_EQ(1, outputs.NumEntries());
const auto& packet = outputs.Index(0);
const auto& result = packet.Get<::mediapipe::ImageFileProperties>();
EXPECT_EQ(kExpectedWidth, result.image_width());
EXPECT_EQ(kExpectedHeight, result.image_height());
EXPECT_DOUBLE_EQ(kExpectedFocalLengthMm, result.focal_length_mm());
EXPECT_DOUBLE_EQ(kExpectedFocalLengthIn35Mm, result.focal_length_35mm());
EXPECT_DOUBLE_EQ(kExpectedFocalLengthPixels,
RoundToNDecimals(result.focal_length_pixels(), /*n=*/2));
}
TEST(ImageFilePropertiesCalculatorTest,
ReadsFocalLengthFromJpegStreamToSidePacket) {
std::string image_filepath = file::JoinPath("./", kImageFilePath);
std::string image_contents;
MP_ASSERT_OK(file::GetContents(image_filepath, &image_contents));
CalculatorGraphConfig::Node node_config =
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"(
calculator: "ImageFilePropertiesCalculator"
input_stream: "image_bytes"
output_side_packet: "properties"
)");
CalculatorRunner runner(node_config);
runner.MutableInputs()->Index(0).packets.push_back(
MakePacket<std::string>(image_contents).At(Timestamp(0)));
MP_ASSERT_OK(runner.Run());
const auto& outputs = runner.OutputSidePackets();
EXPECT_EQ(1, outputs.NumEntries());
const auto& packet = outputs.Index(0);
const auto& result = packet.Get<::mediapipe::ImageFileProperties>();
EXPECT_EQ(kExpectedWidth, result.image_width());
EXPECT_EQ(kExpectedHeight, result.image_height());
EXPECT_DOUBLE_EQ(kExpectedFocalLengthMm, result.focal_length_mm());
EXPECT_DOUBLE_EQ(kExpectedFocalLengthIn35Mm, result.focal_length_35mm());
EXPECT_DOUBLE_EQ(kExpectedFocalLengthPixels,
RoundToNDecimals(result.focal_length_pixels(), /*n=*/2));
}
} // namespace
} // namespace mediapipe
+1 -1
View File
@@ -13,7 +13,7 @@
# limitations under the License. # limitations under the License.
# #
licenses(["notice"]) # Apache 2.0 licenses(["notice"])
filegroup( filegroup(
name = "test_images", name = "test_images",
+1 -1
View File
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
licenses(["notice"]) # Apache 2.0 licenses(["notice"])
load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library") load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library")
+7 -1
View File
@@ -15,7 +15,7 @@
load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library") load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library")
licenses(["notice"]) # Apache 2.0 licenses(["notice"])
package(default_visibility = ["//visibility:private"]) package(default_visibility = ["//visibility:private"])
@@ -427,6 +427,10 @@ cc_library(
deps = [ deps = [
":tensorflow_session", ":tensorflow_session",
":tensorflow_inference_calculator_cc_proto", ":tensorflow_inference_calculator_cc_proto",
"//mediapipe/framework:timestamp",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/memory",
"//mediapipe/framework:calculator_context",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework/tool:status_util", "//mediapipe/framework/tool:status_util",
"@com_google_absl//absl/strings", "@com_google_absl//absl/strings",
@@ -434,6 +438,8 @@ cc_library(
"//mediapipe/framework/deps:clock", "//mediapipe/framework/deps:clock",
"//mediapipe/framework/port:status", "//mediapipe/framework/port:status",
"//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:ret_check",
"//mediapipe/framework/port:map_util",
"//mediapipe/framework:packet",
] + select({ ] + select({
"//conditions:default": [ "//conditions:default": [
"@org_tensorflow//tensorflow/core:framework", "@org_tensorflow//tensorflow/core:framework",
@@ -42,16 +42,16 @@ namespace tf = tensorflow;
// a flag controls whether a new first dimension is inserted before // a flag controls whether a new first dimension is inserted before
// concatenation. // concatenation.
// //
// Currently, the number of tensors output will be buffer_size less than the // The number of tensors output will be buffer_size less than the
// number of input tensors because no padding is implemented and only full // number of input tensors unless padding is set to a non-zero value in the
// buffers are output. // options proto.
// //
// The timestamp of the output batch will match the timestamp of the first // The timestamp of the output batch will match the timestamp of the first
// tensor in that batch by default. (e.g. when buffer_size frames are added, the // tensor in that batch by default. (e.g. when buffer_size frames are added, the
// output tensor will have the timestamp of the first input.). This behavior can // output tensor will have the timestamp of the first input.). This behavior can
// be adjusted by the timestamp_offset option. // be adjusted by the timestamp_offset option.
// //
// Example config: // Example config without padding:
// node { // node {
// calculator: "LappedTensorBufferCalculator" // calculator: "LappedTensorBufferCalculator"
// input_stream: "input_tensor" // input_stream: "input_tensor"
@@ -64,26 +64,50 @@ namespace tf = tensorflow;
// } // }
// } // }
// } // }
//
// Example config with padding and timestamp output:
// node {
// calculator: "LappedTensorBufferCalculator"
// input_stream: "input_tensor"
// output_stream: "output_tensor"
// output_stream: "output_timestamp"
// options {
// [mediapipe.LappedTensorBufferCalculatorOptions.ext] {
// buffer_size: 100
// overlap: 50
// add_batch_dim_to_tensors: true
// timestamp_offset: 25
// padding: 25
// }
// }
// }
class LappedTensorBufferCalculator : public CalculatorBase { class LappedTensorBufferCalculator : public CalculatorBase {
public: public:
static ::mediapipe::Status GetContract(CalculatorContract* cc); static ::mediapipe::Status GetContract(CalculatorContract* cc);
::mediapipe::Status Open(CalculatorContext* cc) override; ::mediapipe::Status Open(CalculatorContext* cc) override;
::mediapipe::Status Process(CalculatorContext* cc) override; ::mediapipe::Status Process(CalculatorContext* cc) override;
::mediapipe::Status Close(CalculatorContext* cc) override;
private: private:
// Adds a batch dimension to the input tensor if specified in the calculator // Adds a batch dimension to the input tensor if specified in the
// options. // calculator options.
::mediapipe::Status AddBatchDimension(tf::Tensor* input_tensor); ::mediapipe::Status AddBatchDimension(tf::Tensor* input_tensor);
// Sends the current buffer downstream.
::mediapipe::Status ProcessBuffer(CalculatorContext* cc);
int steps_until_output_; int steps_until_output_;
int buffer_size_; int buffer_size_;
int overlap_; int overlap_;
int timestamp_offset_; int timestamp_offset_;
int initialized_;
std::unique_ptr<CircularBuffer<Timestamp>> timestamp_buffer_; std::unique_ptr<CircularBuffer<Timestamp>> timestamp_buffer_;
std::unique_ptr<CircularBuffer<tf::Tensor>> buffer_; std::unique_ptr<CircularBuffer<tf::Tensor>> buffer_;
LappedTensorBufferCalculatorOptions options_; LappedTensorBufferCalculatorOptions options_;
}; };
REGISTER_CALCULATOR(LappedTensorBufferCalculator); REGISTER_CALCULATOR(LappedTensorBufferCalculator);
::mediapipe::Status LappedTensorBufferCalculator::GetContract( ::mediapipe::Status LappedTensorBufferCalculator::GetContract(
@@ -93,8 +117,8 @@ REGISTER_CALCULATOR(LappedTensorBufferCalculator);
cc->Inputs().Index(0).Set<tf::Tensor>( cc->Inputs().Index(0).Set<tf::Tensor>(
// tensorflow::Tensor stream. // tensorflow::Tensor stream.
); );
RET_CHECK_EQ(cc->Inputs().NumEntries(), 1) RET_CHECK_LE(cc->Outputs().NumEntries(), 2)
<< "Only one output stream is supported."; << "Only one or two output stream(s) is/are supported.";
if (cc->InputSidePackets().HasTag(kBufferSize)) { if (cc->InputSidePackets().HasTag(kBufferSize)) {
cc->InputSidePackets().Tag(kBufferSize).Set<int>(); cc->InputSidePackets().Tag(kBufferSize).Set<int>();
@@ -108,11 +132,15 @@ REGISTER_CALCULATOR(LappedTensorBufferCalculator);
if (cc->InputSidePackets().HasTag(kCalculatorOptions)) { if (cc->InputSidePackets().HasTag(kCalculatorOptions)) {
cc->InputSidePackets() cc->InputSidePackets()
.Tag(kCalculatorOptions) .Tag(kCalculatorOptions)
.Set<LappedTensorBufferCalculatorOptions>(); .Set<LappedTensorBufferCalculator>();
} }
cc->Outputs().Index(0).Set<tf::Tensor>( cc->Outputs().Index(0).Set<tf::Tensor>(
// Output tensorflow::Tensor stream with possibly overlapping steps. // Output tensorflow::Tensor stream with possibly overlapping steps.
); );
// Output timestamp stream with possibly overlapping steps.
if (cc->Outputs().NumEntries() > 1) {
cc->Outputs().Index(1).Set<std::vector<Timestamp>>();
}
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
@@ -141,10 +169,13 @@ REGISTER_CALCULATOR(LappedTensorBufferCalculator);
<< "Negative timestamp_offset is not allowed."; << "Negative timestamp_offset is not allowed.";
RET_CHECK_LT(timestamp_offset_, buffer_size_) RET_CHECK_LT(timestamp_offset_, buffer_size_)
<< "output_frame_num_offset has to be less than buffer_size."; << "output_frame_num_offset has to be less than buffer_size.";
RET_CHECK_LT(options_.padding(), buffer_size_)
<< "padding option must be smaller than buffer size.";
timestamp_buffer_ = timestamp_buffer_ =
absl::make_unique<CircularBuffer<Timestamp>>(buffer_size_); absl::make_unique<CircularBuffer<Timestamp>>(buffer_size_);
buffer_ = absl::make_unique<CircularBuffer<tf::Tensor>>(buffer_size_); buffer_ = absl::make_unique<CircularBuffer<tf::Tensor>>(buffer_size_);
steps_until_output_ = buffer_size_; steps_until_output_ = buffer_size_ - options_.padding();
initialized_ = false;
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
@@ -156,23 +187,36 @@ REGISTER_CALCULATOR(LappedTensorBufferCalculator);
if (options_.add_batch_dim_to_tensors()) { if (options_.add_batch_dim_to_tensors()) {
RET_CHECK_OK(AddBatchDimension(&input_tensor)); RET_CHECK_OK(AddBatchDimension(&input_tensor));
} }
// Pad frames at the beginning with the first frame.
if (!initialized_) {
for (int i = 0; i < options_.padding(); ++i) {
buffer_->push_back(input_tensor);
timestamp_buffer_->push_back(cc->InputTimestamp());
}
initialized_ = true;
}
buffer_->push_back(input_tensor); buffer_->push_back(input_tensor);
timestamp_buffer_->push_back(cc->InputTimestamp()); timestamp_buffer_->push_back(cc->InputTimestamp());
--steps_until_output_; --steps_until_output_;
if (steps_until_output_ <= 0) { if (steps_until_output_ <= 0) {
auto concatenated = ::absl::make_unique<tf::Tensor>(); MP_RETURN_IF_ERROR(ProcessBuffer(cc));
const tf::Status concat_status = tf::tensor::Concat(
std::vector<tf::Tensor>(buffer_->begin(), buffer_->end()),
concatenated.get());
RET_CHECK(concat_status.ok()) << concat_status.ToString();
cc->Outputs().Index(0).Add(concatenated.release(),
timestamp_buffer_->Get(timestamp_offset_));
steps_until_output_ = buffer_size_ - overlap_;
} }
return ::mediapipe::OkStatus();
}
::mediapipe::Status LappedTensorBufferCalculator::Close(CalculatorContext* cc) {
if (!initialized_ || options_.padding() == 0) {
return ::mediapipe::OkStatus();
}
int last_frame = buffer_size_ - steps_until_output_ - 1;
const auto& pad_frame = buffer_->Get(last_frame);
for (int i = 0; i < steps_until_output_ + options_.padding(); ++i) {
buffer_->push_back(pad_frame);
timestamp_buffer_->push_back(cc->InputTimestamp());
}
MP_RETURN_IF_ERROR(ProcessBuffer(cc));
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
@@ -190,4 +234,29 @@ REGISTER_CALCULATOR(LappedTensorBufferCalculator);
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
// Process buffer
::mediapipe::Status LappedTensorBufferCalculator::ProcessBuffer(
CalculatorContext* cc) {
auto concatenated = ::absl::make_unique<tf::Tensor>();
const tf::Status concat_status = tf::tensor::Concat(
std::vector<tf::Tensor>(buffer_->begin(), buffer_->end()),
concatenated.get());
RET_CHECK(concat_status.ok()) << concat_status.ToString();
// Output cancatenated tensor.
cc->Outputs().Index(0).Add(concatenated.release(),
timestamp_buffer_->Get(timestamp_offset_));
if (cc->Outputs().NumEntries() > 1) {
auto output_timestamp = ::absl::make_unique<std::vector<Timestamp>>();
// Output timestamp vector.
*output_timestamp = std::vector<Timestamp>(timestamp_buffer_->begin(),
timestamp_buffer_->end());
RET_CHECK_EQ(output_timestamp->size(), buffer_size_)
<< "Output timestamp size is not correct.";
cc->Outputs().Index(1).Add(output_timestamp.release(),
timestamp_buffer_->Get(timestamp_offset_));
}
steps_until_output_ = buffer_size_ - overlap_;
return ::mediapipe::OkStatus();
}
} // namespace mediapipe } // namespace mediapipe
@@ -45,4 +45,8 @@ message LappedTensorBufferCalculatorOptions {
// This is useful for aligning the timestamp to be centered on the input // This is useful for aligning the timestamp to be centered on the input
// range. // range.
optional int32 timestamp_offset = 4 [default = 0]; optional int32 timestamp_offset = 4 [default = 0];
// Amount of padding (repeating of first/last value) to add to the beginning
// and end of the input stream.
optional int32 padding = 5;
} }
@@ -31,11 +31,15 @@ namespace tf = ::tensorflow;
class LappedTensorBufferCalculatorTest : public ::testing::Test { class LappedTensorBufferCalculatorTest : public ::testing::Test {
protected: protected:
void SetUpCalculator(int buffer_size, int overlap, bool add_dim, void SetUpCalculator(int buffer_size, int overlap, bool add_dim,
int timestamp_offset) { int timestamp_offset, int padding,
bool timestamp_output) {
CalculatorGraphConfig::Node config; CalculatorGraphConfig::Node config;
config.set_calculator("LappedTensorBufferCalculator"); config.set_calculator("LappedTensorBufferCalculator");
config.add_input_stream("input_tensor"); config.add_input_stream("input_tensor");
config.add_output_stream("output_tensor"); config.add_output_stream("output_tensor");
if (timestamp_output) {
config.add_output_stream("output_timestamp");
}
auto options = config.mutable_options()->MutableExtension( auto options = config.mutable_options()->MutableExtension(
LappedTensorBufferCalculatorOptions::ext); LappedTensorBufferCalculatorOptions::ext);
options->set_buffer_size(buffer_size); options->set_buffer_size(buffer_size);
@@ -44,13 +48,14 @@ class LappedTensorBufferCalculatorTest : public ::testing::Test {
options->set_add_batch_dim_to_tensors(true); options->set_add_batch_dim_to_tensors(true);
} }
options->set_timestamp_offset(timestamp_offset); options->set_timestamp_offset(timestamp_offset);
options->set_padding(padding);
runner_ = ::absl::make_unique<CalculatorRunner>(config); runner_ = ::absl::make_unique<CalculatorRunner>(config);
} }
std::unique_ptr<CalculatorRunner> runner_; std::unique_ptr<CalculatorRunner> runner_;
}; };
TEST_F(LappedTensorBufferCalculatorTest, OneToOne) { TEST_F(LappedTensorBufferCalculatorTest, OneToOne) {
SetUpCalculator(1, 0, false, 0); SetUpCalculator(1, 0, false, 0, 0, false);
int num_timesteps = 3; int num_timesteps = 3;
for (int i = 0; i < num_timesteps; ++i) { for (int i = 0; i < num_timesteps; ++i) {
auto input = ::absl::make_unique<tensorflow::Tensor>( auto input = ::absl::make_unique<tensorflow::Tensor>(
@@ -74,7 +79,7 @@ TEST_F(LappedTensorBufferCalculatorTest, OneToTwo) {
int buffer_size = 2; int buffer_size = 2;
int overlap = 1; int overlap = 1;
bool add_dim = false; bool add_dim = false;
SetUpCalculator(buffer_size, overlap, add_dim, 0); SetUpCalculator(buffer_size, overlap, add_dim, 0, 0, false);
int num_timesteps = 3; int num_timesteps = 3;
for (int i = 0; i < num_timesteps; ++i) { for (int i = 0; i < num_timesteps; ++i) {
auto input = ::absl::make_unique<tensorflow::Tensor>( auto input = ::absl::make_unique<tensorflow::Tensor>(
@@ -100,7 +105,7 @@ TEST_F(LappedTensorBufferCalculatorTest, OneToThree) {
int buffer_size = 3; int buffer_size = 3;
int overlap = 2; int overlap = 2;
bool add_dim = false; bool add_dim = false;
SetUpCalculator(buffer_size, overlap, add_dim, 0); SetUpCalculator(buffer_size, overlap, add_dim, 0, 0, false);
int num_timesteps = 3; int num_timesteps = 3;
for (int i = 0; i < num_timesteps; ++i) { for (int i = 0; i < num_timesteps; ++i) {
auto input = ::absl::make_unique<tensorflow::Tensor>( auto input = ::absl::make_unique<tensorflow::Tensor>(
@@ -126,7 +131,7 @@ TEST_F(LappedTensorBufferCalculatorTest, OneToThreeSkip) {
int buffer_size = 3; int buffer_size = 3;
int overlap = 1; int overlap = 1;
bool add_dim = false; bool add_dim = false;
SetUpCalculator(buffer_size, overlap, add_dim, 0); SetUpCalculator(buffer_size, overlap, add_dim, 0, 0, false);
int num_timesteps = 3; int num_timesteps = 3;
for (int i = 0; i < num_timesteps; ++i) { for (int i = 0; i < num_timesteps; ++i) {
auto input = ::absl::make_unique<tensorflow::Tensor>( auto input = ::absl::make_unique<tensorflow::Tensor>(
@@ -152,7 +157,7 @@ TEST_F(LappedTensorBufferCalculatorTest, OneToThreeBatch) {
int buffer_size = 3; int buffer_size = 3;
int overlap = 2; int overlap = 2;
bool add_dim = true; bool add_dim = true;
SetUpCalculator(buffer_size, overlap, add_dim, 0); SetUpCalculator(buffer_size, overlap, add_dim, 0, 0, false);
int num_timesteps = 3; int num_timesteps = 3;
for (int i = 0; i < num_timesteps; ++i) { for (int i = 0; i < num_timesteps; ++i) {
auto input = ::absl::make_unique<tensorflow::Tensor>( auto input = ::absl::make_unique<tensorflow::Tensor>(
@@ -180,7 +185,7 @@ TEST_F(LappedTensorBufferCalculatorTest, NegativeTimestampOffsetFails) {
int overlap = 15; int overlap = 15;
bool add_dim = true; bool add_dim = true;
int timestamp_offset = -7; int timestamp_offset = -7;
SetUpCalculator(buffer_size, overlap, add_dim, timestamp_offset); SetUpCalculator(buffer_size, overlap, add_dim, timestamp_offset, 0, false);
int num_timesteps = 20; int num_timesteps = 20;
for (int i = 0; i < num_timesteps; ++i) { for (int i = 0; i < num_timesteps; ++i) {
auto input = ::absl::make_unique<tensorflow::Tensor>( auto input = ::absl::make_unique<tensorflow::Tensor>(
@@ -197,7 +202,7 @@ TEST_F(LappedTensorBufferCalculatorTest, OutOfRangeTimestampOffsetFails) {
int overlap = 15; int overlap = 15;
bool add_dim = true; bool add_dim = true;
int timestamp_offset = buffer_size; int timestamp_offset = buffer_size;
SetUpCalculator(buffer_size, overlap, add_dim, timestamp_offset); SetUpCalculator(buffer_size, overlap, add_dim, timestamp_offset, 0, false);
int num_timesteps = 20; int num_timesteps = 20;
for (int i = 0; i < num_timesteps; ++i) { for (int i = 0; i < num_timesteps; ++i) {
auto input = ::absl::make_unique<tensorflow::Tensor>( auto input = ::absl::make_unique<tensorflow::Tensor>(
@@ -214,7 +219,7 @@ TEST_F(LappedTensorBufferCalculatorTest, OneToThreeBatchTimestampOffset) {
int overlap = 15; int overlap = 15;
bool add_dim = true; bool add_dim = true;
int timestamp_offset = 7; int timestamp_offset = 7;
SetUpCalculator(buffer_size, overlap, add_dim, timestamp_offset); SetUpCalculator(buffer_size, overlap, add_dim, timestamp_offset, 0, false);
int num_timesteps = 20; int num_timesteps = 20;
for (int i = 0; i < num_timesteps; ++i) { for (int i = 0; i < num_timesteps; ++i) {
auto input = ::absl::make_unique<tensorflow::Tensor>( auto input = ::absl::make_unique<tensorflow::Tensor>(
@@ -236,5 +241,37 @@ TEST_F(LappedTensorBufferCalculatorTest, OneToThreeBatchTimestampOffset) {
} }
} }
TEST_F(LappedTensorBufferCalculatorTest,
OneToThreeBatchTimestampOffsetPadding) {
int buffer_size = 12;
int overlap = 6;
bool add_dim = true;
int timestamp_offset = 3;
int padding = 0;
SetUpCalculator(buffer_size, overlap, add_dim, timestamp_offset, padding,
true);
int num_timesteps = 20;
for (int i = 0; i < num_timesteps; ++i) {
auto input = ::absl::make_unique<tensorflow::Tensor>(
tensorflow::DT_FLOAT, tensorflow::TensorShape({1}));
input->tensor<float, 1>()(0) = i;
runner_->MutableInputs()->Index(0).packets.push_back(
Adopt(input.release()).At(Timestamp(i)));
}
ASSERT_TRUE(runner_->Run().ok());
const int output_size = num_timesteps / buffer_size + 1;
const std::vector<Packet>& output_packets =
runner_->Outputs().Index(0).packets;
ASSERT_EQ(output_size, output_packets.size());
for (int i = 0; i < output_size; ++i) {
int64 value = output_packets[i].Timestamp().Value();
ASSERT_EQ(i * overlap + timestamp_offset, value);
}
const std::vector<Packet>& output_timestamps =
runner_->Outputs().Index(1).packets;
ASSERT_EQ(output_size, output_timestamps.size());
}
} // namespace } // namespace
} // namespace mediapipe } // namespace mediapipe
@@ -19,16 +19,22 @@
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
#include "absl/base/thread_annotations.h"
#include "absl/memory/memory.h"
#include "absl/strings/str_split.h" #include "absl/strings/str_split.h"
#include "absl/synchronization/mutex.h" #include "absl/synchronization/mutex.h"
#include "mediapipe/calculators/tensorflow/tensorflow_inference_calculator.pb.h" #include "mediapipe/calculators/tensorflow/tensorflow_inference_calculator.pb.h"
#include "mediapipe/calculators/tensorflow/tensorflow_session.h" #include "mediapipe/calculators/tensorflow/tensorflow_session.h"
#include "mediapipe/framework/calculator_context.h"
#include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/deps/clock.h" #include "mediapipe/framework/deps/clock.h"
#include "mediapipe/framework/deps/monotonic_clock.h" #include "mediapipe/framework/deps/monotonic_clock.h"
#include "mediapipe/framework/packet.h"
#include "mediapipe/framework/port/map_util.h"
#include "mediapipe/framework/port/ret_check.h" #include "mediapipe/framework/port/ret_check.h"
#include "mediapipe/framework/port/status.h" #include "mediapipe/framework/port/status.h"
#include "mediapipe/framework/port/status_macros.h" #include "mediapipe/framework/port/status_macros.h"
#include "mediapipe/framework/timestamp.h"
#include "mediapipe/framework/tool/status_util.h" #include "mediapipe/framework/tool/status_util.h"
#include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/framework/tensor_shape.h" #include "tensorflow/core/framework/tensor_shape.h"
@@ -77,6 +83,17 @@ class SimpleSemaphore {
absl::Mutex mutex_; absl::Mutex mutex_;
absl::CondVar cond_; absl::CondVar cond_;
}; };
class InferenceState {
public:
InferenceState() : input_tensor_batches_(), batch_timestamps_() {}
// A mapping between stream tags and the tensors we are collecting as a
// batch.
std::map<std::string, std::vector<tf::Tensor>> input_tensor_batches_;
// The timestamps that go into a batch.
std::vector<Timestamp> batch_timestamps_;
};
} // namespace } // namespace
// This calculator performs inference on a trained TensorFlow model. // This calculator performs inference on a trained TensorFlow model.
@@ -218,11 +235,16 @@ class TensorFlowInferenceCalculator : public CalculatorBase {
} }
static ::mediapipe::Status GetContract(CalculatorContract* cc) { static ::mediapipe::Status GetContract(CalculatorContract* cc) {
const auto& options = cc->Options<TensorFlowInferenceCalculatorOptions>();
RET_CHECK(!cc->Inputs().GetTags().empty()); RET_CHECK(!cc->Inputs().GetTags().empty());
for (const std::string& tag : cc->Inputs().GetTags()) { for (const std::string& tag : cc->Inputs().GetTags()) {
// The tensorflow::Tensor with the tag equal to the graph node. May // The tensorflow::Tensor with the tag equal to the graph node. May
// have a TimeSeriesHeader if all present TimeSeriesHeaders match. // have a TimeSeriesHeader if all present TimeSeriesHeaders match.
cc->Inputs().Tag(tag).Set<tf::Tensor>(); if (!options.batched_input()) {
cc->Inputs().Tag(tag).Set<tf::Tensor>();
} else {
cc->Inputs().Tag(tag).Set<std::vector<mediapipe::Packet>>();
}
} }
RET_CHECK(!cc->Outputs().GetTags().empty()); RET_CHECK(!cc->Outputs().GetTags().empty());
for (const std::string& tag : cc->Outputs().GetTags()) { for (const std::string& tag : cc->Outputs().GetTags()) {
@@ -242,6 +264,22 @@ class TensorFlowInferenceCalculator : public CalculatorBase {
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
std::unique_ptr<InferenceState> CreateInferenceState(CalculatorContext* cc)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
std::unique_ptr<InferenceState> inference_state =
absl::make_unique<InferenceState>();
if (cc->InputSidePackets().HasTag("RECURRENT_INIT_TENSORS") &&
!cc->InputSidePackets().Tag("RECURRENT_INIT_TENSORS").IsEmpty()) {
std::map<std::string, tf::Tensor>* init_tensor_map;
init_tensor_map = GetFromUniquePtr<std::map<std::string, tf::Tensor>>(
cc->InputSidePackets().Tag("RECURRENT_INIT_TENSORS"));
for (const auto& p : *init_tensor_map) {
inference_state->input_tensor_batches_[p.first].emplace_back(p.second);
}
}
return inference_state;
}
::mediapipe::Status Open(CalculatorContext* cc) override { ::mediapipe::Status Open(CalculatorContext* cc) override {
options_ = cc->Options<TensorFlowInferenceCalculatorOptions>(); options_ = cc->Options<TensorFlowInferenceCalculatorOptions>();
@@ -275,15 +313,6 @@ class TensorFlowInferenceCalculator : public CalculatorBase {
recurrent_feed_tags_.insert(tags[0]); recurrent_feed_tags_.insert(tags[0]);
recurrent_fetch_tags_to_feed_tags_[tags[1]] = tags[0]; recurrent_fetch_tags_to_feed_tags_[tags[1]] = tags[0];
} }
if (cc->InputSidePackets().HasTag("RECURRENT_INIT_TENSORS") &&
!cc->InputSidePackets().Tag("RECURRENT_INIT_TENSORS").IsEmpty()) {
std::map<std::string, tf::Tensor>* init_tensor_map;
init_tensor_map = GetFromUniquePtr<std::map<std::string, tf::Tensor>>(
cc->InputSidePackets().Tag("RECURRENT_INIT_TENSORS"));
for (const auto& p : *init_tensor_map) {
input_tensor_batches_[p.first].emplace_back(p.second);
}
}
// Check that all tags are present in this signature bound to tensors. // Check that all tags are present in this signature bound to tensors.
for (const std::string& tag : cc->Inputs().GetTags()) { for (const std::string& tag : cc->Inputs().GetTags()) {
@@ -297,9 +326,15 @@ class TensorFlowInferenceCalculator : public CalculatorBase {
<< options_.signature_name(); << options_.signature_name();
} }
if (options_.batch_size() == 1) { {
absl::WriterMutexLock l(&mutex_);
inference_state_ = std::unique_ptr<InferenceState>();
}
if (options_.batch_size() == 1 || options_.batched_input()) {
cc->SetOffset(0); cc->SetOffset(0);
} }
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
@@ -316,6 +351,24 @@ class TensorFlowInferenceCalculator : public CalculatorBase {
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
::mediapipe::Status AggregateTensorPacket(
const std::string& tag_name, const Packet& packet,
std::map<Timestamp, std::map<std::string, tf::Tensor>>*
input_tensors_by_tag_by_timestamp,
InferenceState* inference_state) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
tf::Tensor input_tensor(packet.Get<tf::Tensor>());
RET_CHECK_OK(AddBatchDimension(&input_tensor));
if (::mediapipe::ContainsKey(recurrent_feed_tags_, tag_name)) {
// If we receive an input on a recurrent tag, override the state.
// It's OK to override the global state because there is just one
// input stream allowed for recurrent tensors.
inference_state_->input_tensor_batches_[tag_name].clear();
}
(*input_tensors_by_tag_by_timestamp)[packet.Timestamp()].insert(
std::make_pair(tag_name, input_tensor));
return ::mediapipe::OkStatus();
}
// Removes the batch dimension of the output tensor if specified in the // Removes the batch dimension of the output tensor if specified in the
// calculator options. // calculator options.
::mediapipe::Status RemoveBatchDimension(tf::Tensor* output_tensor) { ::mediapipe::Status RemoveBatchDimension(tf::Tensor* output_tensor) {
@@ -331,48 +384,85 @@ class TensorFlowInferenceCalculator : public CalculatorBase {
} }
::mediapipe::Status Process(CalculatorContext* cc) override { ::mediapipe::Status Process(CalculatorContext* cc) override {
std::map<std::string, tf::Tensor> input_tensors_by_tag; std::unique_ptr<InferenceState> inference_state_to_process;
for (const std::string& tag_as_node_name : cc->Inputs().GetTags()) { {
if (cc->Inputs().Tag(tag_as_node_name).IsEmpty()) { absl::WriterMutexLock l(&mutex_);
// Recurrent tensors can be empty. if (inference_state_ == nullptr) {
if (!::mediapipe::ContainsKey(recurrent_feed_tags_, tag_as_node_name)) { inference_state_ = CreateInferenceState(cc);
if (options_.skip_on_missing_features()) { }
return ::mediapipe::OkStatus(); std::map<Timestamp, std::map<std::string, tf::Tensor>>
} else { input_tensors_by_tag_by_timestamp;
return ::mediapipe::InvalidArgumentError(absl::StrCat( for (const std::string& tag_as_node_name : cc->Inputs().GetTags()) {
"Tag ", tag_as_node_name, if (cc->Inputs().Tag(tag_as_node_name).IsEmpty()) {
" not present at timestamp: ", cc->InputTimestamp().Value())); // Recurrent tensors can be empty.
if (!::mediapipe::ContainsKey(recurrent_feed_tags_,
tag_as_node_name)) {
if (options_.skip_on_missing_features()) {
return ::mediapipe::OkStatus();
} else {
return ::mediapipe::InvalidArgumentError(absl::StrCat(
"Tag ", tag_as_node_name,
" not present at timestamp: ", cc->InputTimestamp().Value()));
}
} }
} else if (options_.batched_input()) {
const auto& tensor_packets =
cc->Inputs().Tag(tag_as_node_name).Get<std::vector<Packet>>();
if (tensor_packets.size() > options_.batch_size()) {
return ::mediapipe::InvalidArgumentError(absl::StrCat(
"Batch for tag ", tag_as_node_name,
" has more packets than batch capacity. batch_size: ",
options_.batch_size(), " packets: ", tensor_packets.size()));
}
for (const auto& packet : tensor_packets) {
RET_CHECK_OK(AggregateTensorPacket(
tag_as_node_name, packet, &input_tensors_by_tag_by_timestamp,
inference_state_.get()));
}
} else {
RET_CHECK_OK(AggregateTensorPacket(
tag_as_node_name, cc->Inputs().Tag(tag_as_node_name).Value(),
&input_tensors_by_tag_by_timestamp, inference_state_.get()));
} }
} else { }
tf::Tensor input_tensor( for (const auto& timestamp_and_input_tensors_by_tag :
cc->Inputs().Tag(tag_as_node_name).Get<tf::Tensor>()); input_tensors_by_tag_by_timestamp) {
RET_CHECK_OK(AddBatchDimension(&input_tensor)); inference_state_->batch_timestamps_.emplace_back(
if (::mediapipe::ContainsKey(recurrent_feed_tags_, tag_as_node_name)) { timestamp_and_input_tensors_by_tag.first);
// If we receive an input on a recurrent tag, override the state. for (const auto& input_tensor_and_tag :
// It's OK to override the global state because there is just one timestamp_and_input_tensors_by_tag.second) {
// input stream allowed for recurrent tensors. inference_state_->input_tensor_batches_[input_tensor_and_tag.first]
input_tensor_batches_[tag_as_node_name].clear(); .emplace_back(input_tensor_and_tag.second);
} }
input_tensors_by_tag.insert( }
std::make_pair(tag_as_node_name, input_tensor)); if (inference_state_->batch_timestamps_.size() == options_.batch_size() ||
options_.batched_input()) {
inference_state_to_process = std::move(inference_state_);
inference_state_ = std::unique_ptr<InferenceState>();
} }
} }
batch_timestamps_.emplace_back(cc->InputTimestamp());
for (const auto& input_tensor_and_tag : input_tensors_by_tag) { if (inference_state_to_process) {
input_tensor_batches_[input_tensor_and_tag.first].emplace_back( MP_RETURN_IF_ERROR(
input_tensor_and_tag.second); OutputBatch(cc, std::move(inference_state_to_process)));
} }
if (batch_timestamps_.size() == options_.batch_size()) {
MP_RETURN_IF_ERROR(OutputBatch(cc));
}
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
::mediapipe::Status Close(CalculatorContext* cc) override { ::mediapipe::Status Close(CalculatorContext* cc) override {
if (!batch_timestamps_.empty()) { std::unique_ptr<InferenceState> inference_state_to_process = nullptr;
MP_RETURN_IF_ERROR(OutputBatch(cc)); {
absl::WriterMutexLock l(&mutex_);
if (cc->GraphStatus().ok() && inference_state_ != nullptr &&
!inference_state_->batch_timestamps_.empty()) {
inference_state_to_process = std::move(inference_state_);
inference_state_ = std::unique_ptr<InferenceState>();
}
}
if (inference_state_to_process) {
MP_RETURN_IF_ERROR(
OutputBatch(cc, std::move(inference_state_to_process)));
} }
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
@@ -385,10 +475,12 @@ class TensorFlowInferenceCalculator : public CalculatorBase {
// memory buffer. Therefore, copies are cheap and should not cause the memory // memory buffer. Therefore, copies are cheap and should not cause the memory
// buffer to fall out of scope. In contrast, concat is only used where // buffer to fall out of scope. In contrast, concat is only used where
// necessary. // necessary.
::mediapipe::Status OutputBatch(CalculatorContext* cc) { ::mediapipe::Status OutputBatch(
CalculatorContext* cc, std::unique_ptr<InferenceState> inference_state) {
const int64 start_time = absl::ToUnixMicros(clock_->TimeNow()); const int64 start_time = absl::ToUnixMicros(clock_->TimeNow());
std::vector<std::pair<mediapipe::ProtoString, tf::Tensor>> input_tensors; std::vector<std::pair<mediapipe::ProtoString, tf::Tensor>> input_tensors;
for (auto& keyed_tensors : input_tensor_batches_) {
for (auto& keyed_tensors : inference_state->input_tensor_batches_) {
if (options_.batch_size() == 1) { if (options_.batch_size() == 1) {
// Short circuit to avoid the cost of deep copying tensors in concat. // Short circuit to avoid the cost of deep copying tensors in concat.
if (!keyed_tensors.second.empty()) { if (!keyed_tensors.second.empty()) {
@@ -404,7 +496,8 @@ class TensorFlowInferenceCalculator : public CalculatorBase {
} else { } else {
// Pad by replicating the first tens or, then ignore the values. // Pad by replicating the first tens or, then ignore the values.
keyed_tensors.second.resize(options_.batch_size()); keyed_tensors.second.resize(options_.batch_size());
std::fill(keyed_tensors.second.begin() + batch_timestamps_.size(), std::fill(keyed_tensors.second.begin() +
inference_state->batch_timestamps_.size(),
keyed_tensors.second.end(), keyed_tensors.second[0]); keyed_tensors.second.end(), keyed_tensors.second[0]);
tf::Tensor concated; tf::Tensor concated;
const tf::Status concat_status = const tf::Status concat_status =
@@ -414,7 +507,7 @@ class TensorFlowInferenceCalculator : public CalculatorBase {
concated); concated);
} }
} }
input_tensor_batches_.clear(); inference_state->input_tensor_batches_.clear();
std::vector<mediapipe::ProtoString> output_tensor_names; std::vector<mediapipe::ProtoString> output_tensor_names;
std::vector<std::string> output_name_in_signature; std::vector<std::string> output_name_in_signature;
for (const std::string& tag : cc->Outputs().GetTags()) { for (const std::string& tag : cc->Outputs().GetTags()) {
@@ -466,9 +559,11 @@ class TensorFlowInferenceCalculator : public CalculatorBase {
int pos = std::find(output_name_in_signature.begin(), int pos = std::find(output_name_in_signature.begin(),
output_name_in_signature.end(), tag_pair.first) - output_name_in_signature.end(), tag_pair.first) -
output_name_in_signature.begin(); output_name_in_signature.begin();
input_tensor_batches_[tag_pair.second].emplace_back(outputs[pos]); inference_state->input_tensor_batches_[tag_pair.second].emplace_back(
outputs[pos]);
} }
absl::WriterMutexLock l(&mutex_);
// Set that we want to split on each index of the 0th dimension. // Set that we want to split on each index of the 0th dimension.
std::vector<tf::int64> split_vector(options_.batch_size(), 1); std::vector<tf::int64> split_vector(options_.batch_size(), 1);
for (int i = 0; i < output_tensor_names.size(); ++i) { for (int i = 0; i < output_tensor_names.size(); ++i) {
@@ -478,7 +573,8 @@ class TensorFlowInferenceCalculator : public CalculatorBase {
RET_CHECK_OK(RemoveBatchDimension(&output_tensor)); RET_CHECK_OK(RemoveBatchDimension(&output_tensor));
cc->Outputs() cc->Outputs()
.Tag(output_name_in_signature[i]) .Tag(output_name_in_signature[i])
.Add(new tf::Tensor(output_tensor), batch_timestamps_[0]); .Add(new tf::Tensor(output_tensor),
inference_state->batch_timestamps_[0]);
} }
} else { } else {
std::vector<tf::Tensor> split_tensors; std::vector<tf::Tensor> split_tensors;
@@ -486,22 +582,30 @@ class TensorFlowInferenceCalculator : public CalculatorBase {
tf::tensor::Split(outputs[i], split_vector, &split_tensors); tf::tensor::Split(outputs[i], split_vector, &split_tensors);
CHECK(split_status.ok()) << split_status.ToString(); CHECK(split_status.ok()) << split_status.ToString();
// Loop over timestamps so that we don't copy the padding. // Loop over timestamps so that we don't copy the padding.
for (int j = 0; j < batch_timestamps_.size(); ++j) { for (int j = 0; j < inference_state->batch_timestamps_.size(); ++j) {
tf::Tensor output_tensor(split_tensors[j]); tf::Tensor output_tensor(split_tensors[j]);
RET_CHECK_OK(RemoveBatchDimension(&output_tensor)); RET_CHECK_OK(RemoveBatchDimension(&output_tensor));
cc->Outputs() cc->Outputs()
.Tag(output_name_in_signature[i]) .Tag(output_name_in_signature[i])
.Add(new tf::Tensor(output_tensor), batch_timestamps_[j]); .Add(new tf::Tensor(output_tensor),
inference_state->batch_timestamps_[j]);
} }
} }
} }
// Get end time and report. // Get end time and report.
const int64 end_time = absl::ToUnixMicros(clock_->TimeNow()); const int64 end_time = absl::ToUnixMicros(clock_->TimeNow());
cc->GetCounter(kTotalUsecsCounterSuffix) cc->GetCounter(kTotalUsecsCounterSuffix)
->IncrementBy(end_time - start_time); ->IncrementBy(end_time - start_time);
cc->GetCounter(kTotalProcessedTimestampsCounterSuffix) cc->GetCounter(kTotalProcessedTimestampsCounterSuffix)
->IncrementBy(batch_timestamps_.size()); ->IncrementBy(inference_state->batch_timestamps_.size());
batch_timestamps_.clear();
// Make sure we hold on to the recursive state.
if (!options_.recurrent_tag_pair().empty()) {
inference_state_ = std::move(inference_state);
inference_state_->batch_timestamps_.clear();
}
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
@@ -514,11 +618,8 @@ class TensorFlowInferenceCalculator : public CalculatorBase {
// A mapping between stream tags and the tensor names they are bound to. // A mapping between stream tags and the tensor names they are bound to.
std::map<std::string, std::string> tag_to_tensor_map_; std::map<std::string, std::string> tag_to_tensor_map_;
// A mapping between stream tags and the tensors we are collecting as a batch. absl::Mutex mutex_;
std::map<std::string, std::vector<tf::Tensor>> input_tensor_batches_; std::unique_ptr<InferenceState> inference_state_ ABSL_GUARDED_BY(mutex_);
// The timestamps that go into a batch.
std::vector<Timestamp> batch_timestamps_;
// The options for the calculator. // The options for the calculator.
TensorFlowInferenceCalculatorOptions options_; TensorFlowInferenceCalculatorOptions options_;
@@ -76,4 +76,13 @@ message TensorFlowInferenceCalculatorOptions {
// only works in the local process, not "globally" across multiple processes // only works in the local process, not "globally" across multiple processes
// or replicas (if any). Default to 0, i.e. no limit. // or replicas (if any). Default to 0, i.e. no limit.
optional int32 max_concurrent_session_runs = 6 [default = 0]; optional int32 max_concurrent_session_runs = 6 [default = 0];
// If turned on, the Calculator expects a vector of batched packages as input.
// This will make sure that you can turn on max_in_flight for batch_size
// greater than 1. Otherwise it results in problems of none-monotonically
// increasing timestamps.
// Use BatchSequentialCalculator to create the batches. The batch_size
// should agree for both calculators. All the data in a batch is processed
// together. The BatchSequentialCalculator can't run with max_in_flight.
optional bool batched_input = 7;
} }
@@ -89,17 +89,31 @@ class TensorflowInferenceCalculatorTest : public ::testing::Test {
output_side_packets.Tag("SESSION"); output_side_packets.Tag("SESSION");
} }
// Create tensor from Vector and add as a Packet to the provided tag as input. Packet CreateTensorPacket(const std::vector<int32>& input, int64 time) {
void AddVectorToInputsAsTensor(const std::vector<int32>& input,
const std::string& tag, int64 time) {
tf::TensorShape tensor_shape; tf::TensorShape tensor_shape;
tensor_shape.AddDim(input.size()); tensor_shape.AddDim(input.size());
auto tensor = absl::make_unique<tf::Tensor>(tf::DT_INT32, tensor_shape); auto tensor = absl::make_unique<tf::Tensor>(tf::DT_INT32, tensor_shape);
for (int i = 0; i < input.size(); ++i) { for (int i = 0; i < input.size(); ++i) {
tensor->vec<int32>()(i) = input[i]; tensor->vec<int32>()(i) = input[i];
} }
return Adopt(tensor.release()).At(Timestamp(time));
}
// Create tensor from Vector and add as a Packet to the provided tag as input.
void AddVectorToInputsAsTensor(const std::vector<int32>& input,
const std::string& tag, int64 time) {
runner_->MutableInputs()->Tag(tag).packets.push_back( runner_->MutableInputs()->Tag(tag).packets.push_back(
Adopt(tensor.release()).At(Timestamp(time))); CreateTensorPacket(input, time));
}
// Create tensor from Vector and add as a Packet to the provided tag as input.
void AddVectorToInputsAsPacket(const std::vector<Packet>& packets,
const std::string& tag) {
CHECK(!packets.empty())
<< "Please specify at least some data in the packet";
auto packets_ptr = absl::make_unique<std::vector<Packet>>(packets);
runner_->MutableInputs()->Tag(tag).packets.push_back(
Adopt(packets_ptr.release()).At(packets.begin()->Timestamp()));
} }
std::unique_ptr<CalculatorRunner> runner_; std::unique_ptr<CalculatorRunner> runner_;
@@ -183,6 +197,45 @@ TEST_F(TensorflowInferenceCalculatorTest, GetComputed) {
EXPECT_THAT(run_status.ToString(), testing::HasSubstr("Tag B")); EXPECT_THAT(run_status.ToString(), testing::HasSubstr("Tag B"));
} }
TEST_F(TensorflowInferenceCalculatorTest, GetComputed_MaxInFlight) {
CalculatorGraphConfig::Node config;
config.set_calculator("TensorFlowInferenceCalculator");
config.add_input_stream("A:tensor_a");
config.add_input_stream("B:tensor_b");
config.add_output_stream("MULTIPLIED:tensor_o1");
config.add_input_side_packet("SESSION:session");
config.set_max_in_flight(2);
CalculatorOptions options;
options.MutableExtension(TensorFlowInferenceCalculatorOptions::ext)
->set_batch_size(1);
options.MutableExtension(TensorFlowInferenceCalculatorOptions::ext)
->set_add_batch_dim_to_tensors(false);
*config.mutable_options() = options;
runner_ = absl::make_unique<CalculatorRunner>(config);
AddSessionInputSidePacket();
AddVectorToInputsAsTensor({2, 2, 2}, "A", 0);
AddVectorToInputsAsTensor({3, 4, 5}, "B", 0);
MP_ASSERT_OK(runner_->Run());
const std::vector<Packet>& output_packets_mult =
runner_->Outputs().Tag("MULTIPLIED").packets;
ASSERT_EQ(1, output_packets_mult.size());
const tf::Tensor& tensor_mult = output_packets_mult[0].Get<tf::Tensor>();
tf::TensorShape expected_shape({3});
auto expected_tensor = tf::test::AsTensor<int32>({6, 8, 10}, expected_shape);
tf::test::ExpectTensorEqual<int32>(expected_tensor, tensor_mult);
// Add only one of the two expected tensors at the next timestamp, expect
// useful failure message.
AddVectorToInputsAsTensor({1, 2, 3}, "A", 1);
auto run_status = runner_->Run();
ASSERT_FALSE(run_status.ok());
EXPECT_THAT(run_status.ToString(),
testing::HasSubstr("TensorFlowInferenceCalculator"));
EXPECT_THAT(run_status.ToString(), testing::HasSubstr("Tag B"));
}
TEST_F(TensorflowInferenceCalculatorTest, BadTag) { TEST_F(TensorflowInferenceCalculatorTest, BadTag) {
CalculatorGraphConfig::Node config; CalculatorGraphConfig::Node config;
config.set_calculator("TensorFlowInferenceCalculator"); config.set_calculator("TensorFlowInferenceCalculator");
@@ -235,6 +288,86 @@ TEST_F(TensorflowInferenceCalculatorTest, GetMultiBatchComputed) {
->Get()); ->Get());
} }
TEST_F(TensorflowInferenceCalculatorTest, GetMultiBatchComputed_MaxInFlight) {
CalculatorGraphConfig::Node config;
config.set_calculator("TensorFlowInferenceCalculator");
config.add_input_stream("A:tensor_a");
config.add_input_stream("B:tensor_b");
config.add_output_stream("MULTIPLIED:tensor_o1");
config.add_input_side_packet("SESSION:session");
config.set_max_in_flight(2);
CalculatorOptions options;
options.MutableExtension(TensorFlowInferenceCalculatorOptions::ext)
->set_batch_size(1);
*config.mutable_options() = options;
runner_ = absl::make_unique<CalculatorRunner>(config);
AddSessionInputSidePacket();
AddVectorToInputsAsTensor({2, 2, 2}, "A", 0);
AddVectorToInputsAsTensor({3, 4, 5}, "B", 0);
AddVectorToInputsAsTensor({3, 3, 3}, "A", 1);
AddVectorToInputsAsTensor({3, 4, 5}, "B", 1);
MP_ASSERT_OK(runner_->Run());
const std::vector<Packet>& output_packets_mult =
runner_->Outputs().Tag("MULTIPLIED").packets;
ASSERT_EQ(2, output_packets_mult.size());
const tf::Tensor& tensor_mult = output_packets_mult[0].Get<tf::Tensor>();
auto expected_tensor = tf::test::AsTensor<int32>({6, 8, 10});
tf::test::ExpectTensorEqual<int32>(tensor_mult, expected_tensor);
const tf::Tensor& tensor_mult1 = output_packets_mult[1].Get<tf::Tensor>();
auto expected_tensor1 = tf::test::AsTensor<int32>({9, 12, 15});
tf::test::ExpectTensorEqual<int32>(tensor_mult1, expected_tensor1);
EXPECT_EQ(2, runner_
->GetCounter(
"TensorFlowInferenceCalculator-TotalProcessedTimestamps")
->Get());
}
TEST_F(TensorflowInferenceCalculatorTest,
GetMultiBatchComputed_MoreThanMaxInFlight) {
CalculatorGraphConfig::Node config;
config.set_calculator("TensorFlowInferenceCalculator");
config.add_input_stream("A:tensor_a");
config.add_input_stream("B:tensor_b");
config.add_output_stream("MULTIPLIED:tensor_o1");
config.add_input_side_packet("SESSION:session");
config.set_max_in_flight(2);
CalculatorOptions options;
options.MutableExtension(TensorFlowInferenceCalculatorOptions::ext)
->set_batch_size(1);
*config.mutable_options() = options;
runner_ = absl::make_unique<CalculatorRunner>(config);
AddSessionInputSidePacket();
AddVectorToInputsAsTensor({2, 2, 2}, "A", 0);
AddVectorToInputsAsTensor({3, 4, 5}, "B", 0);
AddVectorToInputsAsTensor({3, 3, 3}, "A", 1);
AddVectorToInputsAsTensor({3, 4, 5}, "B", 1);
AddVectorToInputsAsTensor({4, 4, 4}, "A", 2);
AddVectorToInputsAsTensor({3, 4, 5}, "B", 2);
MP_ASSERT_OK(runner_->Run());
const std::vector<Packet>& output_packets_mult =
runner_->Outputs().Tag("MULTIPLIED").packets;
ASSERT_EQ(3, output_packets_mult.size());
const tf::Tensor& tensor_mult = output_packets_mult[0].Get<tf::Tensor>();
auto expected_tensor = tf::test::AsTensor<int32>({6, 8, 10});
tf::test::ExpectTensorEqual<int32>(tensor_mult, expected_tensor);
const tf::Tensor& tensor_mult1 = output_packets_mult[1].Get<tf::Tensor>();
auto expected_tensor1 = tf::test::AsTensor<int32>({9, 12, 15});
tf::test::ExpectTensorEqual<int32>(tensor_mult1, expected_tensor1);
const tf::Tensor& tensor_mult2 = output_packets_mult[2].Get<tf::Tensor>();
auto expected_tensor2 = tf::test::AsTensor<int32>({12, 16, 20});
tf::test::ExpectTensorEqual<int32>(tensor_mult2, expected_tensor2);
EXPECT_EQ(3, runner_
->GetCounter(
"TensorFlowInferenceCalculator-TotalProcessedTimestamps")
->Get());
}
TEST_F(TensorflowInferenceCalculatorTest, GetSingleBatchComputed) { TEST_F(TensorflowInferenceCalculatorTest, GetSingleBatchComputed) {
CalculatorGraphConfig::Node config; CalculatorGraphConfig::Node config;
config.set_calculator("TensorFlowInferenceCalculator"); config.set_calculator("TensorFlowInferenceCalculator");
@@ -311,6 +444,66 @@ TEST_F(TensorflowInferenceCalculatorTest, GetCloseBatchComputed) {
->Get()); ->Get());
} }
TEST_F(TensorflowInferenceCalculatorTest, GetBatchComputed_MaxInFlight) {
CalculatorGraphConfig::Node config;
config.set_calculator("TensorFlowInferenceCalculator");
config.add_input_stream("A:tensor_a");
config.add_input_stream("B:tensor_b");
config.add_output_stream("MULTIPLIED:tensor_o1");
config.add_input_side_packet("SESSION:session");
config.set_max_in_flight(2);
CalculatorOptions options;
options.MutableExtension(TensorFlowInferenceCalculatorOptions::ext)
->set_batch_size(2);
options.MutableExtension(TensorFlowInferenceCalculatorOptions::ext)
->set_add_batch_dim_to_tensors(true);
options.MutableExtension(TensorFlowInferenceCalculatorOptions::ext)
->set_batched_input(true);
*config.mutable_options() = options;
runner_ = absl::make_unique<CalculatorRunner>(config);
AddSessionInputSidePacket();
AddVectorToInputsAsPacket(
{CreateTensorPacket({2, 2, 2}, 0), CreateTensorPacket({3, 3, 3}, 1)},
"A");
AddVectorToInputsAsPacket(
{CreateTensorPacket({3, 4, 5}, 0), CreateTensorPacket({3, 4, 5}, 1)},
"B");
AddVectorToInputsAsPacket(
{CreateTensorPacket({4, 4, 4}, 2), CreateTensorPacket({5, 5, 5}, 3)},
"A");
AddVectorToInputsAsPacket(
{CreateTensorPacket({3, 4, 5}, 2), CreateTensorPacket({3, 4, 5}, 3)},
"B");
AddVectorToInputsAsPacket({CreateTensorPacket({6, 6, 6}, 4)}, "A");
AddVectorToInputsAsPacket({CreateTensorPacket({3, 4, 5}, 4)}, "B");
MP_ASSERT_OK(runner_->Run());
const std::vector<Packet>& output_packets_mult =
runner_->Outputs().Tag("MULTIPLIED").packets;
ASSERT_EQ(5, output_packets_mult.size());
const tf::Tensor& tensor_mult = output_packets_mult[0].Get<tf::Tensor>();
auto expected_tensor = tf::test::AsTensor<int32>({6, 8, 10});
tf::test::ExpectTensorEqual<int32>(tensor_mult, expected_tensor);
const tf::Tensor& tensor_mult1 = output_packets_mult[1].Get<tf::Tensor>();
auto expected_tensor1 = tf::test::AsTensor<int32>({9, 12, 15});
tf::test::ExpectTensorEqual<int32>(tensor_mult1, expected_tensor1);
const tf::Tensor& tensor_mult2 = output_packets_mult[2].Get<tf::Tensor>();
auto expected_tensor2 = tf::test::AsTensor<int32>({12, 16, 20});
tf::test::ExpectTensorEqual<int32>(tensor_mult2, expected_tensor2);
const tf::Tensor& tensor_mult3 = output_packets_mult[3].Get<tf::Tensor>();
auto expected_tensor3 = tf::test::AsTensor<int32>({15, 20, 25});
tf::test::ExpectTensorEqual<int32>(tensor_mult3, expected_tensor3);
const tf::Tensor& tensor_mult4 = output_packets_mult[4].Get<tf::Tensor>();
auto expected_tensor4 = tf::test::AsTensor<int32>({18, 24, 30});
tf::test::ExpectTensorEqual<int32>(tensor_mult4, expected_tensor4);
EXPECT_EQ(5, runner_
->GetCounter(
"TensorFlowInferenceCalculator-TotalProcessedTimestamps")
->Get());
}
TEST_F(TensorflowInferenceCalculatorTest, TestRecurrentStates) { TEST_F(TensorflowInferenceCalculatorTest, TestRecurrentStates) {
CalculatorGraphConfig::Node config; CalculatorGraphConfig::Node config;
config.set_calculator("TensorFlowInferenceCalculator"); config.set_calculator("TensorFlowInferenceCalculator");
@@ -509,4 +702,40 @@ TEST_F(TensorflowInferenceCalculatorTest,
->Get()); ->Get());
} }
TEST_F(TensorflowInferenceCalculatorTest, BatchedInputTooBigBatch) {
CalculatorGraphConfig::Node config;
config.set_calculator("TensorFlowInferenceCalculator");
config.add_input_stream("A:tensor_a");
config.add_input_stream("B:tensor_b");
config.add_output_stream("MULTIPLIED:tensor_o1");
config.add_input_side_packet("SESSION:session");
config.set_max_in_flight(2);
CalculatorOptions options;
options.MutableExtension(TensorFlowInferenceCalculatorOptions::ext)
->set_batch_size(2);
options.MutableExtension(TensorFlowInferenceCalculatorOptions::ext)
->set_add_batch_dim_to_tensors(true);
options.MutableExtension(TensorFlowInferenceCalculatorOptions::ext)
->set_batched_input(true);
*config.mutable_options() = options;
runner_ = absl::make_unique<CalculatorRunner>(config);
AddSessionInputSidePacket();
AddVectorToInputsAsPacket(
{CreateTensorPacket({2, 2, 2}, 0), CreateTensorPacket({3, 3, 3}, 1),
CreateTensorPacket({4, 4, 4}, 2)},
"A");
AddVectorToInputsAsPacket(
{CreateTensorPacket({3, 4, 5}, 0), CreateTensorPacket({3, 4, 5}, 1),
CreateTensorPacket({3, 4, 5}, 2)},
"B");
auto status = runner_->Run();
ASSERT_FALSE(status.ok());
EXPECT_THAT(
status.message(),
::testing::HasSubstr(
"has more packets than batch capacity. batch_size: 2 packets: 3"));
}
} // namespace mediapipe } // namespace mediapipe
@@ -51,7 +51,7 @@ namespace {
void SetPreferredDevice(tf::GraphDef* graph_def, absl::string_view device_id) { void SetPreferredDevice(tf::GraphDef* graph_def, absl::string_view device_id) {
for (auto& node : *graph_def->mutable_node()) { for (auto& node : *graph_def->mutable_node()) {
if (node.device().empty()) { if (node.device().empty()) {
node.set_device(device_id); node.set_device(std::string(device_id));
} }
} }
} }
@@ -47,7 +47,7 @@ namespace {
void SetPreferredDevice(tf::GraphDef* graph_def, absl::string_view device_id) { void SetPreferredDevice(tf::GraphDef* graph_def, absl::string_view device_id) {
for (auto& node : *graph_def->mutable_node()) { for (auto& node : *graph_def->mutable_node()) {
if (node.device().empty()) { if (node.device().empty()) {
node.set_device(device_id); node.set_device(std::string(device_id));
} }
} }
} }
@@ -29,6 +29,7 @@ namespace mediapipe {
// Streams: // Streams:
const char kBBoxTag[] = "BBOX"; const char kBBoxTag[] = "BBOX";
const char kImageTag[] = "IMAGE"; const char kImageTag[] = "IMAGE";
const char kKeypointsTag[] = "KEYPOINTS";
const char kFloatFeaturePrefixTag[] = "FLOAT_FEATURE_"; const char kFloatFeaturePrefixTag[] = "FLOAT_FEATURE_";
const char kForwardFlowImageTag[] = "FORWARD_FLOW_ENCODED"; const char kForwardFlowImageTag[] = "FORWARD_FLOW_ENCODED";
@@ -150,7 +151,6 @@ class UnpackMediaSequenceCalculator : public CalculatorBase {
<< "or" << kAudioDecoderOptions; << "or" << kAudioDecoderOptions;
} }
// Optional streams.
if (cc->Outputs().HasTag(kForwardFlowImageTag)) { if (cc->Outputs().HasTag(kForwardFlowImageTag)) {
cc->Outputs().Tag(kForwardFlowImageTag).Set<std::string>(); cc->Outputs().Tag(kForwardFlowImageTag).Set<std::string>();
} }
@@ -244,6 +244,10 @@ class UnpackMediaSequenceCalculator : public CalculatorBase {
const auto& sequence = cc->InputSidePackets() const auto& sequence = cc->InputSidePackets()
.Tag(kSequenceExampleTag) .Tag(kSequenceExampleTag)
.Get<tensorflow::SequenceExample>(); .Get<tensorflow::SequenceExample>();
if (cc->Outputs().HasTag(kKeypointsTag)) {
keypoint_names_ = absl::StrSplit(options.keypoint_names(), ',');
default_keypoint_location_ = options.default_keypoint_location();
}
if (cc->OutputSidePackets().HasTag(kDataPath)) { if (cc->OutputSidePackets().HasTag(kDataPath)) {
std::string root_directory = ""; std::string root_directory = "";
if (cc->InputSidePackets().HasTag(kDatasetRootDirTag)) { if (cc->InputSidePackets().HasTag(kDatasetRootDirTag)) {
@@ -357,7 +361,6 @@ class UnpackMediaSequenceCalculator : public CalculatorBase {
end_timestamp = end_timestamp =
timestamps_[last_timestamp_key_][current_timestamp_index_ + 1]; timestamps_[last_timestamp_key_][current_timestamp_index_ + 1];
} }
for (const auto& map_kv : timestamps_) { for (const auto& map_kv : timestamps_) {
for (int i = 0; i < map_kv.second.size(); ++i) { for (int i = 0; i < map_kv.second.size(); ++i) {
if (map_kv.second[i] >= start_timestamp && if (map_kv.second[i] >= start_timestamp &&
@@ -454,6 +457,10 @@ class UnpackMediaSequenceCalculator : public CalculatorBase {
int current_timestamp_index_; int current_timestamp_index_;
// Store the very first timestamp, so we output everything on the first frame. // Store the very first timestamp, so we output everything on the first frame.
int64 first_timestamp_seen_; int64 first_timestamp_seen_;
// List of keypoint names.
std::vector<std::string> keypoint_names_;
// Default keypoint location when missing.
float default_keypoint_location_;
}; };
REGISTER_CALCULATOR(UnpackMediaSequenceCalculator); REGISTER_CALCULATOR(UnpackMediaSequenceCalculator);
} // namespace mediapipe } // namespace mediapipe
@@ -56,4 +56,11 @@ message UnpackMediaSequenceCalculatorOptions {
// the clip start and end times and outputs these for the // the clip start and end times and outputs these for the
// AudioDecoderCalculator to consume. // AudioDecoderCalculator to consume.
optional AudioDecoderOptions base_audio_decoder_options = 9; optional AudioDecoderOptions base_audio_decoder_options = 9;
optional string keypoint_names = 10 [
default =
"NOSE,LEFT_EAR,RIGHT_EAR,LEFT_SHOULDER,RIGHT_SHOULDER,LEFT_FORE_PAW,RIGHT_FORE_PAW,LEFT_HIP,RIGHT_HIP,LEFT_HIND_PAW,RIGHT_HIND_PAW,ROOT_TAIL"
];
// When the keypoint doesn't exists, output this default value.
optional float default_keypoint_location = 11 [default = -1.0];
} }
+2 -1
View File
@@ -16,7 +16,7 @@
load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library") load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library")
load("@bazel_skylib//lib:selects.bzl", "selects") load("@bazel_skylib//lib:selects.bzl", "selects")
licenses(["notice"]) # Apache 2.0 licenses(["notice"])
package(default_visibility = ["//visibility:private"]) package(default_visibility = ["//visibility:private"])
@@ -257,6 +257,7 @@ cc_library(
}) + select({ }) + select({
"//conditions:default": [], "//conditions:default": [],
"//mediapipe:android": [ "//mediapipe:android": [
"//mediapipe/util/android/file/base",
"@org_tensorflow//tensorflow/lite/delegates/nnapi:nnapi_delegate", "@org_tensorflow//tensorflow/lite/delegates/nnapi:nnapi_delegate",
], ],
}) + select({ }) + select({
@@ -33,6 +33,12 @@
#include "tensorflow/lite/kernels/register.h" #include "tensorflow/lite/kernels/register.h"
#include "tensorflow/lite/model.h" #include "tensorflow/lite/model.h"
#if defined(MEDIAPIPE_ANDROID)
#include "mediapipe/util/android/file/base/file.h"
#include "mediapipe/util/android/file/base/filesystem.h"
#include "mediapipe/util/android/file/base/helpers.h"
#endif // ANDROID
#if MEDIAPIPE_TFLITE_GL_INFERENCE #if MEDIAPIPE_TFLITE_GL_INFERENCE
#include "mediapipe/gpu/gl_calculator_helper.h" #include "mediapipe/gpu/gl_calculator_helper.h"
#include "mediapipe/gpu/gpu_buffer.h" #include "mediapipe/gpu/gpu_buffer.h"
@@ -201,7 +207,7 @@ int GetXnnpackNumThreads(
// Input tensors are assumed to be of the correct size and already normalized. // Input tensors are assumed to be of the correct size and already normalized.
// All output TfLiteTensors will be destroyed when the graph closes, // All output TfLiteTensors will be destroyed when the graph closes,
// (i.e. after calling graph.WaitUntilDone()). // (i.e. after calling graph.WaitUntilDone()).
// GPU tensors are currently only supported on Android and iOS. // GPU tensor support rquires OpenGL ES 3.1+.
// This calculator uses FixedSizeInputStreamHandler by default. // This calculator uses FixedSizeInputStreamHandler by default.
// //
class TfLiteInferenceCalculator : public CalculatorBase { class TfLiteInferenceCalculator : public CalculatorBase {
@@ -219,6 +225,8 @@ class TfLiteInferenceCalculator : public CalculatorBase {
::mediapipe::Status Close(CalculatorContext* cc) override; ::mediapipe::Status Close(CalculatorContext* cc) override;
private: private:
::mediapipe::Status ReadKernelsFromFile();
::mediapipe::Status WriteKernelsToFile();
::mediapipe::Status LoadModel(CalculatorContext* cc); ::mediapipe::Status LoadModel(CalculatorContext* cc);
::mediapipe::StatusOr<Packet> GetModelAsPacket(const CalculatorContext& cc); ::mediapipe::StatusOr<Packet> GetModelAsPacket(const CalculatorContext& cc);
::mediapipe::Status LoadDelegate(CalculatorContext* cc); ::mediapipe::Status LoadDelegate(CalculatorContext* cc);
@@ -273,6 +281,9 @@ class TfLiteInferenceCalculator : public CalculatorBase {
bool use_quantized_tensors_ = false; bool use_quantized_tensors_ = false;
bool use_advanced_gpu_api_ = false; bool use_advanced_gpu_api_ = false;
bool use_kernel_caching_ = false;
std::string cached_kernel_filename_;
}; };
REGISTER_CALCULATOR(TfLiteInferenceCalculator); REGISTER_CALCULATOR(TfLiteInferenceCalculator);
@@ -354,6 +365,17 @@ bool ShouldUseGpu(CC* cc) {
options.has_delegate() && options.has_delegate() &&
options.delegate().has_gpu() && options.delegate().has_gpu() &&
options.delegate().gpu().use_advanced_gpu_api(); options.delegate().gpu().use_advanced_gpu_api();
use_kernel_caching_ =
use_advanced_gpu_api_ && options.delegate().gpu().use_kernel_caching();
if (use_kernel_caching_) {
#if MEDIAPIPE_TFLITE_GL_INFERENCE && defined(MEDIAPIPE_ANDROID)
cached_kernel_filename_ =
"/sdcard/" + mediapipe::File::Basename(options.model_path()) + ".ker";
#endif // MEDIAPIPE_TFLITE_GL_INFERENCE && MEDIAPIPE_ANDROID
}
if (use_advanced_gpu_api_ && !gpu_input_) { if (use_advanced_gpu_api_ && !gpu_input_) {
LOG(WARNING) << "Cannot use advanced GPU APIs, input must be GPU buffers." LOG(WARNING) << "Cannot use advanced GPU APIs, input must be GPU buffers."
"Falling back to the default TFLite API."; "Falling back to the default TFLite API.";
@@ -423,7 +445,23 @@ bool ShouldUseGpu(CC* cc) {
}); });
} }
::mediapipe::Status TfLiteInferenceCalculator::WriteKernelsToFile() {
#if MEDIAPIPE_TFLITE_GL_INFERENCE && defined(MEDIAPIPE_ANDROID)
if (use_kernel_caching_) {
// Save kernel file.
auto kernel_cache = absl::make_unique<std::vector<uint8_t>>(
tflite_gpu_runner_->GetSerializedBinaryCache());
std::string cache_str(kernel_cache->begin(), kernel_cache->end());
MP_RETURN_IF_ERROR(
mediapipe::file::SetContents(cached_kernel_filename_, cache_str));
}
#endif // MEDIAPIPE_TFLITE_GL_INFERENCE && MEDIAPIPE_ANDROID
return ::mediapipe::OkStatus();
}
::mediapipe::Status TfLiteInferenceCalculator::Close(CalculatorContext* cc) { ::mediapipe::Status TfLiteInferenceCalculator::Close(CalculatorContext* cc) {
MP_RETURN_IF_ERROR(WriteKernelsToFile());
return RunInContextIfNeeded([this]() -> ::mediapipe::Status { return RunInContextIfNeeded([this]() -> ::mediapipe::Status {
if (delegate_) { if (delegate_) {
interpreter_ = nullptr; interpreter_ = nullptr;
@@ -635,6 +673,22 @@ bool ShouldUseGpu(CC* cc) {
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
::mediapipe::Status TfLiteInferenceCalculator::ReadKernelsFromFile() {
#if MEDIAPIPE_TFLITE_GL_INFERENCE && defined(MEDIAPIPE_ANDROID)
if (use_kernel_caching_) {
// Load pre-compiled kernel file.
if (mediapipe::File::Exists(cached_kernel_filename_)) {
std::string cache_str;
MP_RETURN_IF_ERROR(
mediapipe::file::GetContents(cached_kernel_filename_, &cache_str));
std::vector<uint8_t> cache_vec(cache_str.begin(), cache_str.end());
tflite_gpu_runner_->SetSerializedBinaryCache(std::move(cache_vec));
}
}
#endif // MEDIAPIPE_TFLITE_GL_INFERENCE && MEDIAPIPE_ANDROID
return ::mediapipe::OkStatus();
}
::mediapipe::Status TfLiteInferenceCalculator::InitTFLiteGPURunner( ::mediapipe::Status TfLiteInferenceCalculator::InitTFLiteGPURunner(
CalculatorContext* cc) { CalculatorContext* cc) {
#if MEDIAPIPE_TFLITE_GL_INFERENCE #if MEDIAPIPE_TFLITE_GL_INFERENCE
@@ -692,6 +746,9 @@ bool ShouldUseGpu(CC* cc) {
::tflite::gpu::gl::CreateReadWriteShaderStorageBuffer<float>( ::tflite::gpu::gl::CreateReadWriteShaderStorageBuffer<float>(
gpu_data_out_[i]->elements, &gpu_data_out_[i]->buffer)); gpu_data_out_[i]->elements, &gpu_data_out_[i]->buffer));
} }
MP_RETURN_IF_ERROR(ReadKernelsFromFile());
MP_RETURN_IF_ERROR(tflite_gpu_runner_->Build()); MP_RETURN_IF_ERROR(tflite_gpu_runner_->Build());
#endif // MEDIAPIPE_TFLITE_GL_INFERENCE #endif // MEDIAPIPE_TFLITE_GL_INFERENCE
@@ -48,6 +48,10 @@ message TfLiteInferenceCalculatorOptions {
// example: // example:
// delegate: { gpu { use_advanced_gpu_api: true } } // delegate: { gpu { use_advanced_gpu_api: true } }
optional bool use_advanced_gpu_api = 1 [default = false]; optional bool use_advanced_gpu_api = 1 [default = false];
// Load pre-compiled serialized binary cache to accelerate init process.
// Only available for OpenCL delegate on Android.
optional bool use_kernel_caching = 2 [default = false];
} }
// Android only. // Android only.
message Nnapi {} message Nnapi {}
+78 -3
View File
@@ -12,14 +12,32 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library") load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library", "mediapipe_proto_library")
licenses(["notice"]) # Apache 2.0 licenses(["notice"])
package(default_visibility = ["//visibility:public"]) package(default_visibility = ["//visibility:public"])
exports_files(["LICENSE"]) exports_files(["LICENSE"])
cc_library(
name = "alignment_points_to_rects_calculator",
srcs = ["alignment_points_to_rects_calculator.cc"],
visibility = ["//visibility:public"],
deps = [
"//mediapipe/calculators/util:detections_to_rects_calculator",
"//mediapipe/calculators/util:detections_to_rects_calculator_cc_proto",
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework:calculator_options_cc_proto",
"//mediapipe/framework/formats:detection_cc_proto",
"//mediapipe/framework/formats:location_data_cc_proto",
"//mediapipe/framework/formats:rect_cc_proto",
"//mediapipe/framework/port:ret_check",
"//mediapipe/framework/port:status",
],
alwayslink = 1,
)
proto_library( proto_library(
name = "annotation_overlay_calculator_proto", name = "annotation_overlay_calculator_proto",
srcs = ["annotation_overlay_calculator.proto"], srcs = ["annotation_overlay_calculator.proto"],
@@ -586,6 +604,15 @@ proto_library(
], ],
) )
proto_library(
name = "rect_to_render_scale_calculator_proto",
srcs = ["rect_to_render_scale_calculator.proto"],
visibility = ["//visibility:public"],
deps = [
"//mediapipe/framework:calculator_proto",
],
)
proto_library( proto_library(
name = "detections_to_render_data_calculator_proto", name = "detections_to_render_data_calculator_proto",
srcs = ["detections_to_render_data_calculator.proto"], srcs = ["detections_to_render_data_calculator.proto"],
@@ -700,7 +727,15 @@ mediapipe_cc_proto_library(
deps = [":rect_to_render_data_calculator_proto"], deps = [":rect_to_render_data_calculator_proto"],
) )
# TODO: What is that one for? mediapipe_cc_proto_library(
name = "rect_to_render_scale_calculator_cc_proto",
srcs = ["rect_to_render_scale_calculator.proto"],
cc_deps = [
"//mediapipe/framework:calculator_cc_proto",
],
visibility = ["//visibility:public"],
deps = [":rect_to_render_scale_calculator_proto"],
)
mediapipe_cc_proto_library( mediapipe_cc_proto_library(
name = "detections_to_render_data_calculator_cc_proto", name = "detections_to_render_data_calculator_cc_proto",
@@ -748,6 +783,7 @@ mediapipe_cc_proto_library(
cc_library( cc_library(
name = "landmarks_to_render_data_calculator", name = "landmarks_to_render_data_calculator",
srcs = ["landmarks_to_render_data_calculator.cc"], srcs = ["landmarks_to_render_data_calculator.cc"],
hdrs = ["landmarks_to_render_data_calculator.h"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
":landmarks_to_render_data_calculator_cc_proto", ":landmarks_to_render_data_calculator_cc_proto",
@@ -830,6 +866,19 @@ cc_library(
alwayslink = 1, alwayslink = 1,
) )
cc_library(
name = "rect_to_render_scale_calculator",
srcs = ["rect_to_render_scale_calculator.cc"],
visibility = ["//visibility:public"],
deps = [
":rect_to_render_scale_calculator_cc_proto",
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:rect_cc_proto",
"//mediapipe/framework/port:ret_check",
],
alwayslink = 1,
)
cc_test( cc_test(
name = "detections_to_render_data_calculator_test", name = "detections_to_render_data_calculator_test",
size = "small", size = "small",
@@ -896,6 +945,31 @@ cc_library(
alwayslink = 1, alwayslink = 1,
) )
mediapipe_proto_library(
name = "landmarks_smoothing_calculator_proto",
srcs = ["landmarks_smoothing_calculator.proto"],
visibility = ["//visibility:public"],
deps = [
"//mediapipe/framework:calculator_options_proto",
],
)
cc_library(
name = "landmarks_smoothing_calculator",
srcs = ["landmarks_smoothing_calculator.cc"],
visibility = ["//visibility:public"],
deps = [
":landmarks_smoothing_calculator_cc_proto",
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework:timestamp",
"//mediapipe/framework/formats:landmark_cc_proto",
"//mediapipe/framework/port:ret_check",
"//mediapipe/util/filtering:relative_velocity_filter",
"@com_google_absl//absl/algorithm:container",
],
alwayslink = 1,
)
cc_library( cc_library(
name = "landmarks_to_floats_calculator", name = "landmarks_to_floats_calculator",
srcs = ["landmarks_to_floats_calculator.cc"], srcs = ["landmarks_to_floats_calculator.cc"],
@@ -1054,6 +1128,7 @@ cc_library(
"//mediapipe/framework/formats:classification_cc_proto", "//mediapipe/framework/formats:classification_cc_proto",
"//mediapipe/framework/formats:landmark_cc_proto", "//mediapipe/framework/formats:landmark_cc_proto",
"//mediapipe/framework/formats:rect_cc_proto", "//mediapipe/framework/formats:rect_cc_proto",
"//mediapipe/framework/port:integral_types",
"//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:ret_check",
"//mediapipe/framework/port:status", "//mediapipe/framework/port:status",
"@com_google_absl//absl/strings", "@com_google_absl//absl/strings",
@@ -0,0 +1,102 @@
#include <cmath>
#include "mediapipe/calculators/util/detections_to_rects_calculator.h"
#include "mediapipe/calculators/util/detections_to_rects_calculator.pb.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/calculator_options.pb.h"
#include "mediapipe/framework/formats/detection.pb.h"
#include "mediapipe/framework/formats/location_data.pb.h"
#include "mediapipe/framework/formats/rect.pb.h"
#include "mediapipe/framework/port/ret_check.h"
#include "mediapipe/framework/port/status.h"
namespace mediapipe {
namespace {} // namespace
// A calculator that converts Detection with two alignment points to Rect.
//
// Detection should contain two points:
// * Center point - center of the crop
// * Scale point - vector from center to scale point defines size and rotation
// of the Rect. Not that Y coordinate of this vector is flipped before
// computing the rotation (it is caused by the fact that Y axis is
// directed downwards). So define target rotation vector accordingly.
//
// Example config:
// node {
// calculator: "AlignmentPointsRectsCalculator"
// input_stream: "DETECTIONS:detections"
// input_stream: "IMAGE_SIZE:image_size"
// output_stream: "NORM_RECT:rect"
// 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
// }
// }
// }
class AlignmentPointsRectsCalculator : public DetectionsToRectsCalculator {
public:
::mediapipe::Status Open(CalculatorContext* cc) override {
RET_CHECK_OK(DetectionsToRectsCalculator::Open(cc));
// Make sure that start and end keypoints are provided.
// They are required for the rect size calculation and will also force base
// calculator to compute rotation.
options_ = cc->Options<DetectionsToRectsCalculatorOptions>();
RET_CHECK(options_.has_rotation_vector_start_keypoint_index())
<< "Start keypoint is required to calculate rect size and rotation";
RET_CHECK(options_.has_rotation_vector_end_keypoint_index())
<< "End keypoint is required to calculate rect size and rotation";
return ::mediapipe::OkStatus();
}
private:
::mediapipe::Status DetectionToNormalizedRect(
const ::mediapipe::Detection& detection,
const DetectionSpec& detection_spec,
::mediapipe::NormalizedRect* rect) override;
};
REGISTER_CALCULATOR(AlignmentPointsRectsCalculator);
::mediapipe::Status AlignmentPointsRectsCalculator::DetectionToNormalizedRect(
const Detection& detection, const DetectionSpec& detection_spec,
NormalizedRect* rect) {
const auto& location_data = detection.location_data();
const auto& image_size = detection_spec.image_size;
RET_CHECK(image_size) << "Image size is required to calculate the rect";
const float x_center =
location_data.relative_keypoints(start_keypoint_index_).x() *
image_size->first;
const float y_center =
location_data.relative_keypoints(start_keypoint_index_).y() *
image_size->second;
const float x_scale =
location_data.relative_keypoints(end_keypoint_index_).x() *
image_size->first;
const float y_scale =
location_data.relative_keypoints(end_keypoint_index_).y() *
image_size->second;
// Bounding box size as double distance from center to scale point.
const float box_size =
std::sqrt((x_scale - x_center) * (x_scale - x_center) +
(y_scale - y_center) * (y_scale - y_center)) *
2.0;
// Set resulting bounding box.
rect->set_x_center(x_center / image_size->first);
rect->set_y_center(y_center / image_size->second);
rect->set_width(box_size / image_size->first);
rect->set_height(box_size / image_size->second);
return ::mediapipe::OkStatus();
}
} // namespace mediapipe
@@ -160,8 +160,8 @@ class AnnotationOverlayCalculator : public CalculatorBase {
GLuint image_mat_tex_ = 0; // Overlay drawing image for GPU. GLuint image_mat_tex_ = 0; // Overlay drawing image for GPU.
int width_ = 0; int width_ = 0;
int height_ = 0; int height_ = 0;
int width_gpu_ = 0; // Size of overlay drawing texture. int width_canvas_ = 0; // Size of overlay drawing texture canvas.
int height_gpu_ = 0; int height_canvas_ = 0;
#endif // MEDIAPIPE_DISABLE_GPU #endif // MEDIAPIPE_DISABLE_GPU
}; };
REGISTER_CALCULATOR(AnnotationOverlayCalculator); REGISTER_CALCULATOR(AnnotationOverlayCalculator);
@@ -250,6 +250,7 @@ REGISTER_CALCULATOR(AnnotationOverlayCalculator);
// Initialize the helper renderer library. // Initialize the helper renderer library.
renderer_ = absl::make_unique<AnnotationRenderer>(); renderer_ = absl::make_unique<AnnotationRenderer>();
renderer_->SetFlipTextVertically(options_.flip_text_vertically()); renderer_->SetFlipTextVertically(options_.flip_text_vertically());
if (use_gpu_) renderer_->SetScaleFactor(options_.gpu_scale_factor());
// Set the output header based on the input header (if present). // Set the output header based on the input header (if present).
const char* input_tag = use_gpu_ ? kInputFrameTagGpu : kInputFrameTag; const char* input_tag = use_gpu_ ? kInputFrameTagGpu : kInputFrameTag;
@@ -388,11 +389,9 @@ REGISTER_CALCULATOR(AnnotationOverlayCalculator);
// Upload render target to GPU. // Upload render target to GPU.
{ {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindTexture(GL_TEXTURE_2D, image_mat_tex_); glBindTexture(GL_TEXTURE_2D, image_mat_tex_);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_gpu_, height_gpu_, GL_RGB, glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_canvas_, height_canvas_,
GL_UNSIGNED_BYTE, overlay_image); GL_RGB, GL_UNSIGNED_BYTE, overlay_image);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
} }
@@ -494,12 +493,13 @@ REGISTER_CALCULATOR(AnnotationOverlayCalculator);
if (format != mediapipe::ImageFormat::SRGBA && if (format != mediapipe::ImageFormat::SRGBA &&
format != mediapipe::ImageFormat::SRGB) format != mediapipe::ImageFormat::SRGB)
RET_CHECK_FAIL() << "Unsupported GPU input format: " << format; RET_CHECK_FAIL() << "Unsupported GPU input format: " << format;
image_mat = absl::make_unique<cv::Mat>(height_gpu_, width_gpu_, CV_8UC3); image_mat =
absl::make_unique<cv::Mat>(height_canvas_, width_canvas_, CV_8UC3);
memset(image_mat->data, kAnnotationBackgroundColor, memset(image_mat->data, kAnnotationBackgroundColor,
height_gpu_ * width_gpu_ * image_mat->elemSize()); height_canvas_ * width_canvas_ * image_mat->elemSize());
} else { } else {
image_mat = absl::make_unique<cv::Mat>( image_mat = absl::make_unique<cv::Mat>(
height_gpu_, width_gpu_, CV_8UC3, height_canvas_, width_canvas_, CV_8UC3,
cv::Scalar(options_.canvas_color().r(), options_.canvas_color().g(), cv::Scalar(options_.canvas_color().r(), options_.canvas_color().g(),
options_.canvas_color().b())); options_.canvas_color().b()));
} }
@@ -646,8 +646,8 @@ REGISTER_CALCULATOR(AnnotationOverlayCalculator);
width_ = RoundUp(options_.canvas_width_px(), alignment); width_ = RoundUp(options_.canvas_width_px(), alignment);
height_ = RoundUp(options_.canvas_height_px(), alignment); height_ = RoundUp(options_.canvas_height_px(), alignment);
} }
width_gpu_ = RoundUp(width_ * scale_factor, alignment); width_canvas_ = RoundUp(width_ * scale_factor, alignment);
height_gpu_ = RoundUp(height_ * scale_factor, alignment); height_canvas_ = RoundUp(height_ * scale_factor, alignment);
// Init texture for opencv rendered frame. // Init texture for opencv rendered frame.
{ {
@@ -655,8 +655,8 @@ REGISTER_CALCULATOR(AnnotationOverlayCalculator);
glBindTexture(GL_TEXTURE_2D, image_mat_tex_); glBindTexture(GL_TEXTURE_2D, image_mat_tex_);
// TODO // TODO
// OpenCV only renders to RGB images, not RGBA. Ideally this should be RGBA. // OpenCV only renders to RGB images, not RGBA. Ideally this should be RGBA.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width_gpu_, height_gpu_, 0, GL_RGB, glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width_canvas_, height_canvas_, 0,
GL_UNSIGNED_BYTE, nullptr); GL_RGB, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
@@ -50,7 +50,5 @@ message AnnotationOverlayCalculatorOptions {
// This can be used to speed up annotation by drawing the annotation on an // This can be used to speed up annotation by drawing the annotation on an
// intermediate image with a reduced scale, e.g. 0.5 (of the input image width // intermediate image with a reduced scale, e.g. 0.5 (of the input image width
// and height), before resizing and overlaying it on top of the input image. // and height), before resizing and overlaying it on top of the input image.
// Should only be used if *all* render data uses normalized coordinates
// (or absolute coordinates are updated to scale accordingly).
optional float gpu_scale_factor = 7 [default = 1.0]; optional float gpu_scale_factor = 7 [default = 1.0];
} }
@@ -20,9 +20,14 @@
#include "mediapipe/framework/formats/classification.pb.h" #include "mediapipe/framework/formats/classification.pb.h"
#include "mediapipe/framework/formats/landmark.pb.h" #include "mediapipe/framework/formats/landmark.pb.h"
#include "mediapipe/framework/formats/rect.pb.h" #include "mediapipe/framework/formats/rect.pb.h"
#include "mediapipe/framework/port/integral_types.h"
namespace mediapipe { namespace mediapipe {
typedef FilterCollectionCalculator<std::vector<uint64>>
FilterUInt64CollectionCalculator;
REGISTER_CALCULATOR(FilterUInt64CollectionCalculator);
typedef FilterCollectionCalculator<std::vector<::mediapipe::NormalizedRect>> typedef FilterCollectionCalculator<std::vector<::mediapipe::NormalizedRect>>
FilterNormalizedRectCollectionCalculator; FilterNormalizedRectCollectionCalculator;
REGISTER_CALCULATOR(FilterNormalizedRectCollectionCalculator); REGISTER_CALCULATOR(FilterNormalizedRectCollectionCalculator);
@@ -0,0 +1,273 @@
// 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.
#include "absl/algorithm/container.h"
#include "mediapipe/calculators/util/landmarks_smoothing_calculator.pb.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/formats/landmark.pb.h"
#include "mediapipe/framework/port/ret_check.h"
#include "mediapipe/framework/timestamp.h"
#include "mediapipe/util/filtering/relative_velocity_filter.h"
namespace mediapipe {
namespace {
constexpr char kNormalizedLandmarksTag[] = "NORM_LANDMARKS";
constexpr char kImageSizeTag[] = "IMAGE_SIZE";
constexpr char kNormalizedFilteredLandmarksTag[] = "NORM_FILTERED_LANDMARKS";
using ::mediapipe::RelativeVelocityFilter;
// Estimate object scale to use its inverse value as velocity scale for
// RelativeVelocityFilter. If value will be too small (less than
// `options_.min_allowed_object_scale`) smoothing will be disabled and
// landmarks will be returned as is.
// Object scale is calculated as average between bounding box width and height
// with sides parallel to axis.
float GetObjectScale(const NormalizedLandmarkList& landmarks, int image_width,
int image_height) {
const auto& lm_minmax_x = absl::c_minmax_element(
landmarks.landmark(),
[](const auto& a, const auto& b) { return a.x() < b.x(); });
const float x_min = lm_minmax_x.first->x();
const float x_max = lm_minmax_x.second->x();
const auto& lm_minmax_y = absl::c_minmax_element(
landmarks.landmark(),
[](const auto& a, const auto& b) { return a.y() < b.y(); });
const float y_min = lm_minmax_y.first->y();
const float y_max = lm_minmax_y.second->y();
const float object_width = (x_max - x_min) * image_width;
const float object_height = (y_max - y_min) * image_height;
return (object_width + object_height) / 2.0f;
}
// Abstract class for various landmarks filters.
class LandmarksFilter {
public:
virtual ~LandmarksFilter() = default;
virtual ::mediapipe::Status Reset() { return ::mediapipe::OkStatus(); }
virtual ::mediapipe::Status Apply(const NormalizedLandmarkList& in_landmarks,
const std::pair<int, int>& image_size,
const absl::Duration& timestamp,
NormalizedLandmarkList* out_landmarks) = 0;
};
// Returns landmarks as is without smoothing.
class NoFilter : public LandmarksFilter {
public:
::mediapipe::Status Apply(const NormalizedLandmarkList& in_landmarks,
const std::pair<int, int>& image_size,
const absl::Duration& timestamp,
NormalizedLandmarkList* out_landmarks) override {
*out_landmarks = in_landmarks;
return ::mediapipe::OkStatus();
}
};
// Please check RelativeVelocityFilter documentation for details.
class VelocityFilter : public LandmarksFilter {
public:
VelocityFilter(int window_size, float velocity_scale,
float min_allowed_object_scale)
: window_size_(window_size),
velocity_scale_(velocity_scale),
min_allowed_object_scale_(min_allowed_object_scale) {}
::mediapipe::Status Reset() override {
x_filters_.clear();
y_filters_.clear();
z_filters_.clear();
return ::mediapipe::OkStatus();
}
::mediapipe::Status Apply(const NormalizedLandmarkList& in_landmarks,
const std::pair<int, int>& image_size,
const absl::Duration& timestamp,
NormalizedLandmarkList* out_landmarks) override {
// Get image size.
int image_width;
int image_height;
std::tie(image_width, image_height) = image_size;
// Get value scale as inverse value of the object scale.
// If value is too small smoothing will be disabled and landmarks will be
// returned as is.
const float object_scale =
GetObjectScale(in_landmarks, image_width, image_height);
if (object_scale < min_allowed_object_scale_) {
*out_landmarks = in_landmarks;
return ::mediapipe::OkStatus();
}
const float value_scale = 1.0f / object_scale;
// Initialize filters once.
MP_RETURN_IF_ERROR(InitializeFiltersIfEmpty(in_landmarks.landmark_size()));
// Filter landmarks. Every axis of every landmark is filtered separately.
for (int i = 0; i < in_landmarks.landmark_size(); ++i) {
const NormalizedLandmark& in_landmark = in_landmarks.landmark(i);
NormalizedLandmark* out_landmark = out_landmarks->add_landmark();
out_landmark->set_x(x_filters_[i].Apply(timestamp, value_scale,
in_landmark.x() * image_width) /
image_width);
out_landmark->set_y(y_filters_[i].Apply(timestamp, value_scale,
in_landmark.y() * image_height) /
image_height);
// Scale Z the save was as X (using image width).
out_landmark->set_z(z_filters_[i].Apply(timestamp, value_scale,
in_landmark.z() * image_width) /
image_width);
// Keep visibility as is.
out_landmark->set_visibility(in_landmark.visibility());
}
return ::mediapipe::OkStatus();
}
private:
// Initializes filters for the first time or after Reset. If initialized then
// check the size.
::mediapipe::Status InitializeFiltersIfEmpty(const int n_landmarks) {
if (!x_filters_.empty()) {
RET_CHECK_EQ(x_filters_.size(), n_landmarks);
RET_CHECK_EQ(y_filters_.size(), n_landmarks);
RET_CHECK_EQ(z_filters_.size(), n_landmarks);
return ::mediapipe::OkStatus();
}
x_filters_.resize(n_landmarks,
RelativeVelocityFilter(window_size_, velocity_scale_));
y_filters_.resize(n_landmarks,
RelativeVelocityFilter(window_size_, velocity_scale_));
z_filters_.resize(n_landmarks,
RelativeVelocityFilter(window_size_, velocity_scale_));
return ::mediapipe::OkStatus();
}
int window_size_;
float velocity_scale_;
float min_allowed_object_scale_;
std::vector<RelativeVelocityFilter> x_filters_;
std::vector<RelativeVelocityFilter> y_filters_;
std::vector<RelativeVelocityFilter> z_filters_;
};
} // namespace
// A calculator to smooth landmarks over time.
//
// Inputs:
// NORM_LANDMARKS: A NormalizedLandmarkList of landmarks you want to smooth.
// IMAGE_SIZE: A std::pair<int, int> represention of image width and height.
// Required to perform all computations in absolute coordinates to avoid any
// influence of normalized values.
//
// Outputs:
// NORM_FILTERED_LANDMARKS: A NormalizedLandmarkList of smoothed landmarks.
//
// Example config:
// node {
// calculator: "LandmarksSmoothingCalculator"
// input_stream: "NORM_LANDMARKS:pose_landmarks"
// input_stream: "IMAGE_SIZE:image_size"
// output_stream: "NORM_FILTERED_LANDMARKS:pose_landmarks_filtered"
// options: {
// [mediapipe.LandmarksSmoothingCalculatorOptions.ext] {
// velocity_filter: {
// window_size: 5
// velocity_scale: 10.0
// }
// }
// }
// }
//
class LandmarksSmoothingCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc);
::mediapipe::Status Open(CalculatorContext* cc) override;
::mediapipe::Status Process(CalculatorContext* cc) override;
private:
LandmarksFilter* landmarks_filter_;
};
REGISTER_CALCULATOR(LandmarksSmoothingCalculator);
::mediapipe::Status LandmarksSmoothingCalculator::GetContract(
CalculatorContract* cc) {
cc->Inputs().Tag(kNormalizedLandmarksTag).Set<NormalizedLandmarkList>();
cc->Inputs().Tag(kImageSizeTag).Set<std::pair<int, int>>();
cc->Outputs()
.Tag(kNormalizedFilteredLandmarksTag)
.Set<NormalizedLandmarkList>();
return ::mediapipe::OkStatus();
}
::mediapipe::Status LandmarksSmoothingCalculator::Open(CalculatorContext* cc) {
cc->SetOffset(TimestampDiff(0));
// Pick landmarks filter.
const auto& options = cc->Options<LandmarksSmoothingCalculatorOptions>();
if (options.has_no_filter()) {
landmarks_filter_ = new NoFilter();
} else if (options.has_velocity_filter()) {
landmarks_filter_ = new VelocityFilter(
options.velocity_filter().window_size(),
options.velocity_filter().velocity_scale(),
options.velocity_filter().min_allowed_object_scale());
} else {
RET_CHECK_FAIL()
<< "Landmarks filter is either not specified or not supported";
}
return ::mediapipe::OkStatus();
}
::mediapipe::Status LandmarksSmoothingCalculator::Process(
CalculatorContext* cc) {
// Check that landmarks are not empty and reset the filter if so.
// Don't emit an empty packet for this timestamp.
if (cc->Inputs().Tag(kNormalizedLandmarksTag).IsEmpty()) {
MP_RETURN_IF_ERROR(landmarks_filter_->Reset());
return ::mediapipe::OkStatus();
}
const auto& in_landmarks =
cc->Inputs().Tag(kNormalizedLandmarksTag).Get<NormalizedLandmarkList>();
const auto& image_size =
cc->Inputs().Tag(kImageSizeTag).Get<std::pair<int, int>>();
const auto& timestamp =
absl::Microseconds(cc->InputTimestamp().Microseconds());
auto out_landmarks = absl::make_unique<NormalizedLandmarkList>();
MP_RETURN_IF_ERROR(landmarks_filter_->Apply(in_landmarks, image_size,
timestamp, out_landmarks.get()));
cc->Outputs()
.Tag(kNormalizedFilteredLandmarksTag)
.Add(out_landmarks.release(), cc->InputTimestamp());
return ::mediapipe::OkStatus();
}
} // namespace mediapipe
@@ -0,0 +1,48 @@
// 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.
syntax = "proto2";
package mediapipe;
import "mediapipe/framework/calculator_options.proto";
message LandmarksSmoothingCalculatorOptions {
extend CalculatorOptions {
optional LandmarksSmoothingCalculatorOptions ext = 325671429;
}
// Default behaviour and fast way to disable smoothing.
message NoFilter {}
message VelocityFilter {
// Number of value changes to keep over time.
// Higher value adds to lag and to stability.
optional int32 window_size = 1 [default = 5];
// Scale to apply to the velocity calculated over the given window. With
// higher velocity `low pass filter` weights new values higher.
// Lower value adds to lag and to stability.
optional float velocity_scale = 2 [default = 10.0];
// If calculated object scale is less than given value smoothing will be
// disabled and landmarks will be returned as is.
optional float min_allowed_object_scale = 3 [default = 1e-6];
}
oneof filter_options {
NoFilter no_filter = 1;
VelocityFilter velocity_filter = 2;
}
}
@@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include "mediapipe/calculators/util/landmarks_to_render_data_calculator.h"
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
@@ -34,8 +35,6 @@ constexpr char kRenderDataTag[] = "RENDER_DATA";
constexpr char kLandmarkLabel[] = "KEYPOINT"; constexpr char kLandmarkLabel[] = "KEYPOINT";
constexpr int kMaxLandmarkThickness = 18; constexpr int kMaxLandmarkThickness = 18;
using ::mediapipe::RenderAnnotation_Point;
inline void SetColor(RenderAnnotation* annotation, const Color& color) { inline void SetColor(RenderAnnotation* annotation, const Color& color) {
annotation->mutable_color()->set_r(color.r()); annotation->mutable_color()->set_r(color.r());
annotation->mutable_color()->set_g(color.g()); annotation->mutable_color()->set_g(color.g());
@@ -104,8 +103,8 @@ void AddConnectionsWithDepth(const LandmarkListType& landmarks,
for (int i = 0; i < landmark_connections.size(); i += 2) { for (int i = 0; i < landmark_connections.size(); i += 2) {
const auto& ld0 = landmarks.landmark(landmark_connections[i]); const auto& ld0 = landmarks.landmark(landmark_connections[i]);
const auto& ld1 = landmarks.landmark(landmark_connections[i + 1]); const auto& ld1 = landmarks.landmark(landmark_connections[i + 1]);
if (visibility_threshold && (ld0.visibility() < visibility_threshold || if (utilize_visibility && (ld0.visibility() < visibility_threshold ||
ld1.visibility() < visibility_threshold)) { ld1.visibility() < visibility_threshold)) {
continue; continue;
} }
const int gray_val1 = const int gray_val1 =
@@ -142,8 +141,8 @@ void AddConnections(const LandmarkListType& landmarks,
for (int i = 0; i < landmark_connections.size(); i += 2) { for (int i = 0; i < landmark_connections.size(); i += 2) {
const auto& ld0 = landmarks.landmark(landmark_connections[i]); const auto& ld0 = landmarks.landmark(landmark_connections[i]);
const auto& ld1 = landmarks.landmark(landmark_connections[i + 1]); const auto& ld1 = landmarks.landmark(landmark_connections[i + 1]);
if (visibility_threshold && (ld0.visibility() < visibility_threshold || if (utilize_visibility && (ld0.visibility() < visibility_threshold ||
ld1.visibility() < visibility_threshold)) { ld1.visibility() < visibility_threshold)) {
continue; continue;
} }
AddConnectionToRenderData<LandmarkType>(ld0, ld1, connection_color, AddConnectionToRenderData<LandmarkType>(ld0, ld1, connection_color,
@@ -162,45 +161,6 @@ RenderAnnotation* AddPointRenderData(const Color& landmark_color,
} // namespace } // namespace
// A calculator that converts Landmark proto to RenderData proto for
// visualization. The input should be LandmarkList proto. It is also possible
// to specify the connections between landmarks.
//
// Example config:
// node {
// calculator: "LandmarksToRenderDataCalculator"
// input_stream: "NORM_LANDMARKS:landmarks"
// output_stream: "RENDER_DATA:render_data"
// options {
// [LandmarksToRenderDataCalculatorOptions.ext] {
// landmark_connections: [0, 1, 1, 2]
// landmark_color { r: 0 g: 255 b: 0 }
// connection_color { r: 0 g: 255 b: 0 }
// thickness: 4.0
// }
// }
// }
class LandmarksToRenderDataCalculator : public CalculatorBase {
public:
LandmarksToRenderDataCalculator() {}
~LandmarksToRenderDataCalculator() override {}
LandmarksToRenderDataCalculator(const LandmarksToRenderDataCalculator&) =
delete;
LandmarksToRenderDataCalculator& operator=(
const LandmarksToRenderDataCalculator&) = delete;
static ::mediapipe::Status GetContract(CalculatorContract* cc);
::mediapipe::Status Open(CalculatorContext* cc) override;
::mediapipe::Status Process(CalculatorContext* cc) override;
private:
LandmarksToRenderDataCalculatorOptions options_;
std::vector<int> landmark_connections_;
};
REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
::mediapipe::Status LandmarksToRenderDataCalculator::GetContract( ::mediapipe::Status LandmarksToRenderDataCalculator::GetContract(
CalculatorContract* cc) { CalculatorContract* cc) {
RET_CHECK(cc->Inputs().HasTag(kLandmarksTag) || RET_CHECK(cc->Inputs().HasTag(kLandmarksTag) ||
@@ -354,4 +314,5 @@ REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
} // namespace mediapipe } // namespace mediapipe
@@ -0,0 +1,69 @@
// 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.
#ifndef MEDIAPIPE_CALCULATORS_UTIL_LANDMARKS_TO_RENDER_DATA_CALCULATOR_H_
#define MEDIAPIPE_CALCULATORS_UTIL_LANDMARKS_TO_RENDER_DATA_CALCULATOR_H_
#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "mediapipe/calculators/util/landmarks_to_render_data_calculator.pb.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/calculator_options.pb.h"
#include "mediapipe/framework/formats/landmark.pb.h"
#include "mediapipe/framework/formats/location_data.pb.h"
#include "mediapipe/framework/port/ret_check.h"
#include "mediapipe/util/color.pb.h"
#include "mediapipe/util/render_data.pb.h"
namespace mediapipe {
// A calculator that converts Landmark proto to RenderData proto for
// visualization. The input should be LandmarkList proto. It is also possible
// to specify the connections between landmarks.
//
// Example config:
// node {
// calculator: "LandmarksToRenderDataCalculator"
// input_stream: "NORM_LANDMARKS:landmarks"
// output_stream: "RENDER_DATA:render_data"
// options {
// [LandmarksToRenderDataCalculatorOptions.ext] {
// landmark_connections: [0, 1, 1, 2]
// landmark_color { r: 0 g: 255 b: 0 }
// connection_color { r: 0 g: 255 b: 0 }
// thickness: 4.0
// }
// }
// }
class LandmarksToRenderDataCalculator : public CalculatorBase {
public:
LandmarksToRenderDataCalculator() {}
~LandmarksToRenderDataCalculator() override {}
LandmarksToRenderDataCalculator(const LandmarksToRenderDataCalculator&) =
delete;
LandmarksToRenderDataCalculator& operator=(
const LandmarksToRenderDataCalculator&) = delete;
static ::mediapipe::Status GetContract(CalculatorContract* cc);
::mediapipe::Status Open(CalculatorContext* cc) override;
::mediapipe::Status Process(CalculatorContext* cc) override;
protected:
::mediapipe::LandmarksToRenderDataCalculatorOptions options_;
std::vector<int> landmark_connections_;
};
} // namespace mediapipe
#endif // MEDIAPIPE_CALCULATORS_UTIL_LANDMARKS_TO_RENDER_DATA_CALCULATOR_H_
@@ -0,0 +1,111 @@
#include "mediapipe/calculators/util/rect_to_render_scale_calculator.pb.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/formats/rect.pb.h"
namespace mediapipe {
namespace {
constexpr char kNormRectTag[] = "NORM_RECT";
constexpr char kImageSizeTag[] = "IMAGE_SIZE";
constexpr char kRenderScaleTag[] = "RENDER_SCALE";
} // namespace
// A calculator to get scale for RenderData primitives.
//
// This calculator allows you to make RenderData primitives size (configured via
// `thickness`) to depend on actual size of the object they should highlight
// (e.g. pose, hand or face). It will give you bigger rendered primitives for
// bigger/closer objects and smaller primitives for smaller/far objects.
//
// IMPORTANT NOTE: RenderData primitives are rendered via OpenCV, which accepts
// only integer thickness. So when object goes further/closer you'll see 1 pixel
// jumps.
//
// Check `mediapipe/util/render_data.proto` for details on
// RenderData primitives and `thickness` parameter.
//
// Inputs:
// NORM_RECT: Normalized rectangle to compute object size from as maximum of
// width and height.
// IMAGE_SIZE: A std::pair<int, int> represention of image width and height to
// transform normalized object width and height to absolute pixel
// coordinates.
//
// Outputs:
// RENDER_SCALE: Float value that should be used to scale RenderData
// primitives calculated as `rect_size * multiplier`.
//
// Example config:
// node {
// calculator: "RectToRenderScaleCalculator"
// input_stream: "NORM_RECT:pose_landmarks_rect"
// input_stream: "IMAGE_SIZE:image_size"
// output_stream: "RENDER_SCALE:render_scale"
// options: {
// [mediapipe.RectToRenderScaleCalculatorOptions.ext] {
// multiplier: 0.001
// }
// }
// }
class RectToRenderScaleCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc);
::mediapipe::Status Open(CalculatorContext* cc) override;
::mediapipe::Status Process(CalculatorContext* cc) override;
private:
RectToRenderScaleCalculatorOptions options_;
};
REGISTER_CALCULATOR(RectToRenderScaleCalculator);
::mediapipe::Status RectToRenderScaleCalculator::GetContract(
CalculatorContract* cc) {
cc->Inputs().Tag(kNormRectTag).Set<NormalizedRect>();
cc->Inputs().Tag(kImageSizeTag).Set<std::pair<int, int>>();
cc->Outputs().Tag(kRenderScaleTag).Set<float>();
return ::mediapipe::OkStatus();
}
::mediapipe::Status RectToRenderScaleCalculator::Open(CalculatorContext* cc) {
cc->SetOffset(TimestampDiff(0));
options_ = cc->Options<RectToRenderScaleCalculatorOptions>();
return ::mediapipe::OkStatus();
}
::mediapipe::Status RectToRenderScaleCalculator::Process(
CalculatorContext* cc) {
if (cc->Inputs().Tag(kNormRectTag).IsEmpty()) {
cc->Outputs()
.Tag(kRenderScaleTag)
.AddPacket(
MakePacket<float>(options_.multiplier()).At(cc->InputTimestamp()));
return ::mediapipe::OkStatus();
}
// Get image size.
int image_width;
int image_height;
std::tie(image_width, image_height) =
cc->Inputs().Tag(kImageSizeTag).Get<std::pair<int, int>>();
// Get rect size in absolute pixel coordinates.
const auto& rect = cc->Inputs().Tag(kNormRectTag).Get<NormalizedRect>();
const float rect_width = rect.width() * image_width;
const float rect_height = rect.height() * image_height;
// Calculate render scale.
const float rect_size = std::max(rect_width, rect_height);
const float render_scale = rect_size * options_.multiplier();
cc->Outputs()
.Tag(kRenderScaleTag)
.AddPacket(MakePacket<float>(render_scale).At(cc->InputTimestamp()));
return ::mediapipe::OkStatus();
}
} // namespace mediapipe
@@ -0,0 +1,18 @@
syntax = "proto2";
package mediapipe;
import "mediapipe/framework/calculator.proto";
message RectToRenderScaleCalculatorOptions {
extend CalculatorOptions {
optional RectToRenderScaleCalculatorOptions ext = 299463409;
}
// Multiplier to apply to the rect size.
// If one defined `thickness` for RenderData primitives for object (e.g. pose,
// hand or face) of size `A` then multiplier should be `1/A`. It means that
// when actual object size on the image will be `B`, than all RenderData
// primitives will be scaled with factor `B/A`.
optional float multiplier = 1 [default = 0.01];
}
+2 -1
View File
@@ -19,7 +19,7 @@ load(
"mediapipe_binary_graph", "mediapipe_binary_graph",
) )
licenses(["notice"]) # Apache 2.0 licenses(["notice"])
package(default_visibility = ["//visibility:private"]) package(default_visibility = ["//visibility:private"])
@@ -316,6 +316,7 @@ cc_library(
"//mediapipe/util/tracking", "//mediapipe/util/tracking",
"//mediapipe/util/tracking:box_tracker", "//mediapipe/util/tracking:box_tracker",
"//mediapipe/util/tracking:tracking_visualization_utilities", "//mediapipe/util/tracking:tracking_visualization_utilities",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/container:node_hash_set", "@com_google_absl//absl/container:node_hash_set",
"@com_google_absl//absl/strings", "@com_google_absl//absl/strings",
], ],
@@ -18,6 +18,7 @@
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include "absl/container/flat_hash_set.h"
#include "absl/container/node_hash_set.h" #include "absl/container/node_hash_set.h"
#include "absl/strings/numbers.h" #include "absl/strings/numbers.h"
#include "mediapipe/calculators/video/box_tracker_calculator.pb.h" #include "mediapipe/calculators/video/box_tracker_calculator.pb.h"
@@ -238,6 +239,11 @@ class BoxTrackerCalculator : public CalculatorBase {
// Queued track time requests. // Queued track time requests.
std::vector<Timestamp> queued_track_requests_; std::vector<Timestamp> queued_track_requests_;
// Stores the tracked ids that have been discarded actively, from continuous
// tracking data. It may accumulate across multiple frames. Once consumed, it
// should be cleared immediately.
absl::flat_hash_set<int> actively_discarded_tracked_ids_;
// Add smooth transition between re-acquisition and previous tracked boxes. // Add smooth transition between re-acquisition and previous tracked boxes.
// `result_box` is the tracking result of one specific timestamp. The smoothed // `result_box` is the tracking result of one specific timestamp. The smoothed
// result will be updated in place. // result will be updated in place.
@@ -1144,9 +1150,16 @@ void BoxTrackerCalculator::StreamTrack(const TrackingData& data,
CHECK(box_map); CHECK(box_map);
CHECK(failed_ids); CHECK(failed_ids);
// Cache the actively discarded tracked ids from the new tracking data.
for (const int discarded_id :
data.motion_data().actively_discarded_tracked_ids()) {
actively_discarded_tracked_ids_.insert(discarded_id);
}
// Track all existing boxes by one frame. // Track all existing boxes by one frame.
MotionVectorFrame mvf; // Holds motion from current to previous frame. MotionVectorFrame mvf; // Holds motion from current to previous frame.
MotionVectorFrameFromTrackingData(data, &mvf); MotionVectorFrameFromTrackingData(data, &mvf);
mvf.actively_discarded_tracked_ids = &actively_discarded_tracked_ids_;
if (forward) { if (forward) {
MotionVectorFrame mvf_inverted; MotionVectorFrame mvf_inverted;
+1 -1
View File
@@ -15,7 +15,7 @@
load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library") load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library")
licenses(["notice"]) # Apache 2.0 licenses(["notice"])
package(default_visibility = ["//mediapipe/calculators/video:__subpackages__"]) package(default_visibility = ["//mediapipe/calculators/video:__subpackages__"])
@@ -197,90 +197,88 @@ REGISTER_CALCULATOR(TrackedDetectionManagerCalculator);
::mediapipe::Status TrackedDetectionManagerCalculator::Process( ::mediapipe::Status TrackedDetectionManagerCalculator::Process(
CalculatorContext* cc) { CalculatorContext* cc) {
if (cc->Inputs().HasTag("TRACKING_BOXES")) { if (cc->Inputs().HasTag(kTrackingBoxesTag) &&
if (!cc->Inputs().Tag("TRACKING_BOXES").IsEmpty()) { !cc->Inputs().Tag(kTrackingBoxesTag).IsEmpty()) {
const TimedBoxProtoList& tracked_boxes = const TimedBoxProtoList& tracked_boxes =
cc->Inputs().Tag("TRACKING_BOXES").Get<TimedBoxProtoList>(); cc->Inputs().Tag(kTrackingBoxesTag).Get<TimedBoxProtoList>();
// Collect all detections that are removed. // Collect all detections that are removed.
auto removed_detection_ids = absl::make_unique<std::vector<int>>(); auto removed_detection_ids = absl::make_unique<std::vector<int>>();
for (const TimedBoxProto& tracked_box : tracked_boxes.box()) { for (const TimedBoxProto& tracked_box : tracked_boxes.box()) {
NormalizedRect bounding_box; NormalizedRect bounding_box;
bounding_box.set_x_center((tracked_box.left() + tracked_box.right()) / bounding_box.set_x_center((tracked_box.left() + tracked_box.right()) /
2.f); 2.f);
bounding_box.set_y_center((tracked_box.bottom() + tracked_box.top()) / bounding_box.set_y_center((tracked_box.bottom() + tracked_box.top()) /
2.f); 2.f);
bounding_box.set_height(tracked_box.bottom() - tracked_box.top()); bounding_box.set_height(tracked_box.bottom() - tracked_box.top());
bounding_box.set_width(tracked_box.right() - tracked_box.left()); bounding_box.set_width(tracked_box.right() - tracked_box.left());
bounding_box.set_rotation(tracked_box.rotation()); bounding_box.set_rotation(tracked_box.rotation());
// First check if this box updates a detection that's waiting for // First check if this box updates a detection that's waiting for
// update from the tracker. // update from the tracker.
auto waiting_for_update_detectoin_ptr = auto waiting_for_update_detectoin_ptr =
waiting_for_update_detections_.find(tracked_box.id()); waiting_for_update_detections_.find(tracked_box.id());
if (waiting_for_update_detectoin_ptr != if (waiting_for_update_detectoin_ptr !=
waiting_for_update_detections_.end()) { waiting_for_update_detections_.end()) {
// Add the detection and remove duplicated detections. // Add the detection and remove duplicated detections.
auto removed_ids = tracked_detection_manager_.AddDetection( auto removed_ids = tracked_detection_manager_.AddDetection(
std::move(waiting_for_update_detectoin_ptr->second)); std::move(waiting_for_update_detectoin_ptr->second));
MoveIds(removed_detection_ids.get(), std::move(removed_ids));
waiting_for_update_detections_.erase(
waiting_for_update_detectoin_ptr);
}
auto removed_ids = tracked_detection_manager_.UpdateDetectionLocation(
tracked_box.id(), bounding_box, tracked_box.time_msec());
MoveIds(removed_detection_ids.get(), std::move(removed_ids)); MoveIds(removed_detection_ids.get(), std::move(removed_ids));
waiting_for_update_detections_.erase(waiting_for_update_detectoin_ptr);
} }
// TODO: Should be handled automatically in detection manager. auto removed_ids = tracked_detection_manager_.UpdateDetectionLocation(
auto removed_ids = tracked_detection_manager_.RemoveObsoleteDetections( tracked_box.id(), bounding_box, tracked_box.time_msec());
GetInputTimestampMs(cc) - kDetectionUpdateTimeOutMS);
MoveIds(removed_detection_ids.get(), std::move(removed_ids)); MoveIds(removed_detection_ids.get(), std::move(removed_ids));
}
// TODO: Should be handled automatically in detection manager.
auto removed_ids = tracked_detection_manager_.RemoveObsoleteDetections(
GetInputTimestampMs(cc) - kDetectionUpdateTimeOutMS);
MoveIds(removed_detection_ids.get(), std::move(removed_ids));
// TODO: Should be handled automatically in detection manager. // TODO: Should be handled automatically in detection manager.
removed_ids = tracked_detection_manager_.RemoveOutOfViewDetections(); removed_ids = tracked_detection_manager_.RemoveOutOfViewDetections();
MoveIds(removed_detection_ids.get(), std::move(removed_ids)); MoveIds(removed_detection_ids.get(), std::move(removed_ids));
if (!removed_detection_ids->empty() && if (!removed_detection_ids->empty() &&
cc->Outputs().HasTag(kCancelObjectIdTag)) { cc->Outputs().HasTag(kCancelObjectIdTag)) {
auto timestamp = cc->InputTimestamp(); auto timestamp = cc->InputTimestamp();
for (int box_id : *removed_detection_ids) { for (int box_id : *removed_detection_ids) {
// The timestamp is incremented (by 1 us) because currently the box // The timestamp is incremented (by 1 us) because currently the box
// tracker calculator only accepts one cancel object ID for any given // tracker calculator only accepts one cancel object ID for any given
// timestamp. // timestamp.
cc->Outputs()
.Tag(kCancelObjectIdTag)
.AddPacket(mediapipe::MakePacket<int>(box_id).At(timestamp++));
}
}
// Output detections and corresponding bounding boxes.
const auto& all_detections =
tracked_detection_manager_.GetAllTrackedDetections();
auto output_detections = absl::make_unique<std::vector<Detection>>();
auto output_boxes = absl::make_unique<std::vector<NormalizedRect>>();
for (const auto& detection_ptr : all_detections) {
const auto& detection = *detection_ptr.second;
// Only output detections that are synced.
if (detection.last_updated_timestamp() <
cc->InputTimestamp().Microseconds() / 1000) {
continue;
}
output_detections->emplace_back(
GetAxisAlignedDetectionFromTrackedDetection(detection));
output_boxes->emplace_back(detection.bounding_box());
}
if (cc->Outputs().HasTag(kDetectionsTag)) {
cc->Outputs() cc->Outputs()
.Tag(kDetectionsTag) .Tag(kCancelObjectIdTag)
.Add(output_detections.release(), cc->InputTimestamp()); .AddPacket(mediapipe::MakePacket<int>(box_id).At(timestamp++));
} }
}
if (cc->Outputs().HasTag(kDetectionBoxesTag)) { // Output detections and corresponding bounding boxes.
cc->Outputs() const auto& all_detections =
.Tag(kDetectionBoxesTag) tracked_detection_manager_.GetAllTrackedDetections();
.Add(output_boxes.release(), cc->InputTimestamp()); auto output_detections = absl::make_unique<std::vector<Detection>>();
auto output_boxes = absl::make_unique<std::vector<NormalizedRect>>();
for (const auto& detection_ptr : all_detections) {
const auto& detection = *detection_ptr.second;
// Only output detections that are synced.
if (detection.last_updated_timestamp() <
cc->InputTimestamp().Microseconds() / 1000) {
continue;
} }
output_detections->emplace_back(
GetAxisAlignedDetectionFromTrackedDetection(detection));
output_boxes->emplace_back(detection.bounding_box());
}
if (cc->Outputs().HasTag(kDetectionsTag)) {
cc->Outputs()
.Tag(kDetectionsTag)
.Add(output_detections.release(), cc->InputTimestamp());
}
if (cc->Outputs().HasTag(kDetectionBoxesTag)) {
cc->Outputs()
.Tag(kDetectionBoxesTag)
.Add(output_boxes.release(), cc->InputTimestamp());
} }
} }
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
licenses(["notice"]) # Apache 2.0 licenses(["notice"])
# Basic library common across example apps. # Basic library common across example apps.
android_library( android_library(

Some files were not shown because too many files have changed in this diff Show More