Compare commits

..
9 Commits
Author SHA1 Message Date
MediaPipe Teamandjqtang 4a20e9909d Project import generated by Copybara.
GitOrigin-RevId: b66251317fbebfbb8e1f2ddc64ea5da84bceb7e5
2022-05-06 17:05:30 -07:00
MediaPipe Teamandschmidt-sebastian 7fb37c80e8 Project import generated by Copybara.
GitOrigin-RevId: 19a829ffd755edb43e54d20c0e7b9348512d5108
2022-05-05 19:57:20 +00:00
MediaPipe Teamandjqtang c6c80c3745 Project import generated by Copybara.
GitOrigin-RevId: 17113e259b160929c49262e7aa78ac22d228f9fc
2022-03-22 17:48:17 -07:00
MediaPipe Teamandjqtang cc6a2f7af6 Project import generated by Copybara.
GitOrigin-RevId: 73d686c40057684f8bfaca285368bf1813f9fc26
2022-03-21 12:12:39 -07:00
MediaPipe Teamandjqtang e6c19885c6 Project import generated by Copybara.
GitOrigin-RevId: bb059a0721c92e8154d33ce8057b3915a25b3d7d
2021-12-13 15:56:02 -08:00
MediaPipe Teamandchuoling cf101e62a9 Project import generated by Copybara.
GitOrigin-RevId: 7e1d382a1788ebd8412c5626581b4c4cf2fe75ea
2021-11-16 14:32:04 -05:00
MediaPipe Teamandchuoling f4e7f6cc48 Project import generated by Copybara.
GitOrigin-RevId: 412b20ea6bc8e49ba5b50798a6114ad6173ff073
2021-11-04 01:53:16 -04:00
MediaPipe Teamandjqtang d4bb35fe5a Project import generated by Copybara.
GitOrigin-RevId: d4a11282d20fe4d2e137f9032cf349750030dcb9
2021-11-03 17:27:30 -07:00
MediaPipe Teamandchuoling 1faeaae7e5 Project import generated by Copybara.
GitOrigin-RevId: bbbbcb4f5174dea33525729ede47c770069157cd
2021-10-18 17:00:29 -04:00
494 changed files with 10574 additions and 3422 deletions
+8
View File
@@ -32,6 +32,9 @@ build:macos --copt=-w
# Sets the default Apple platform to macOS. # Sets the default Apple platform to macOS.
build --apple_platform_type=macos build --apple_platform_type=macos
# Compile ObjC++ files with C++17
build --per_file_copt=.*\.mm\$@-std=c++17
# Allow debugging with XCODE # Allow debugging with XCODE
build --apple_generate_dsym build --apple_generate_dsym
@@ -58,6 +61,7 @@ build:android_arm64 --fat_apk_cpu=arm64-v8a
# iOS configs. # iOS configs.
build:ios --apple_platform_type=ios build:ios --apple_platform_type=ios
build:ios --copt=-fno-aligned-allocation
build:ios_i386 --config=ios build:ios_i386 --config=ios
build:ios_i386 --cpu=ios_i386 build:ios_i386 --cpu=ios_i386
@@ -87,6 +91,10 @@ build:darwin_x86_64 --apple_platform_type=macos
build:darwin_x86_64 --macos_minimum_os=10.12 build:darwin_x86_64 --macos_minimum_os=10.12
build:darwin_x86_64 --cpu=darwin_x86_64 build:darwin_x86_64 --cpu=darwin_x86_64
build:darwin_arm64 --apple_platform_type=macos
build:darwin_arm64 --macos_minimum_os=10.16
build:darwin_arm64 --cpu=darwin_arm64
# This bazelrc file is meant to be written by a setup script. # This bazelrc file is meant to be written by a setup script.
try-import %workspace%/.configure.bazelrc try-import %workspace%/.configure.bazelrc
+1 -1
View File
@@ -1 +1 @@
3.7.2 5.0.0
@@ -10,5 +10,3 @@ For questions on how to work with MediaPipe, or support for problems that are no
If you are reporting a vulnerability, please use the [dedicated reporting process](https://github.com/google/mediapipe/security). If you are reporting a vulnerability, please use the [dedicated reporting process](https://github.com/google/mediapipe/security).
For high-level discussions about MediaPipe, please post to discuss@mediapipe.org, for questions about the development or internal workings of MediaPipe, or if you would like to know how to contribute to MediaPipe, please post to developers@mediapipe.org.
+1 -1
View File
@@ -15,4 +15,4 @@
# A list of assignees # A list of assignees
assignees: assignees:
- sgowroji - sureshdagooglecom
+1 -1
View File
@@ -5,7 +5,7 @@
* Bug fixes * Bug fixes
* Documentation fixes * Documentation fixes
For new feature additions (e.g., new graphs and calculators), we are currently not planning to accept new feature pull requests into the MediaPipe repository. Instead, we like to get contributors to create their own repositories of the new feature and list it at [Awesome MediaPipe](https://mediapipe.org). This will allow contributors to more quickly get their code out to the community. For new feature additions (e.g., new graphs and calculators), we are currently not planning to accept new feature pull requests into the MediaPipe repository. Instead, we like to get contributors to create their own repositories of the new feature and list it at [Awesome MediaPipe](https://mediapipe.page.link/awesome-mediapipe). This will allow contributors to more quickly get their code out to the community.
Before sending your pull requests, make sure you followed this list. Before sending your pull requests, make sure you followed this list.
+1 -1
View File
@@ -56,7 +56,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=3.7.2 ARG BAZEL_VERSION=5.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" && \
+2 -2
View File
@@ -136,8 +136,8 @@ run code search using
## Community ## Community
* [Awesome MediaPipe](https://mediapipe.org) - A curated list of awesome * [Awesome MediaPipe](https://mediapipe.page.link/awesome-mediapipe) - A
MediaPipe related frameworks, libraries and software curated list of awesome MediaPipe related frameworks, libraries and software
* [Slack community](https://mediapipe.page.link/joinslack) for MediaPipe users * [Slack community](https://mediapipe.page.link/joinslack) for MediaPipe users
* [Discuss](https://groups.google.com/forum/#!forum/mediapipe) - General * [Discuss](https://groups.google.com/forum/#!forum/mediapipe) - General
community discussion around MediaPipe community discussion around MediaPipe
+32 -35
View File
@@ -61,11 +61,12 @@ http_archive(
sha256 = "de682ea824bfffba05b4e33b67431c247397d6175962534305136aa06f92e049", sha256 = "de682ea824bfffba05b4e33b67431c247397d6175962534305136aa06f92e049",
) )
# Google Benchmark library. # Google Benchmark library v1.6.1 released on 2022-01-10.
http_archive( http_archive(
name = "com_google_benchmark", name = "com_google_benchmark",
urls = ["https://github.com/google/benchmark/archive/main.zip"], urls = ["https://github.com/google/benchmark/archive/refs/tags/v1.6.1.tar.gz"],
strip_prefix = "benchmark-main", strip_prefix = "benchmark-1.6.1",
sha256 = "6132883bc8c9b0df5375b16ab520fac1a85dc9e4cf5be59480448ece74b278d4",
build_file = "@//third_party:benchmark.BUILD", build_file = "@//third_party:benchmark.BUILD",
) )
@@ -122,16 +123,16 @@ http_archive(
# ...but the Java download is currently broken, so we use the "source" download. # ...but the Java download is currently broken, so we use the "source" download.
http_archive( http_archive(
name = "com_google_protobuf_javalite", name = "com_google_protobuf_javalite",
sha256 = "a79d19dcdf9139fa4b81206e318e33d245c4c9da1ffed21c87288ed4380426f9", sha256 = "87407cd28e7a9c95d9f61a098a53cf031109d451a7763e7dd1253abf8b4df422",
strip_prefix = "protobuf-3.11.4", strip_prefix = "protobuf-3.19.1",
urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.11.4.tar.gz"], urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.19.1.tar.gz"],
) )
http_archive( http_archive(
name = "com_google_protobuf", name = "com_google_protobuf",
sha256 = "a79d19dcdf9139fa4b81206e318e33d245c4c9da1ffed21c87288ed4380426f9", sha256 = "87407cd28e7a9c95d9f61a098a53cf031109d451a7763e7dd1253abf8b4df422",
strip_prefix = "protobuf-3.11.4", strip_prefix = "protobuf-3.19.1",
urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.11.4.tar.gz"], urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.19.1.tar.gz"],
patches = [ patches = [
"@//third_party:com_google_protobuf_fixes.diff" "@//third_party:com_google_protobuf_fixes.diff"
], ],
@@ -154,28 +155,29 @@ http_archive(
sha256 = "75922da3a1bdb417d820398eb03d4e9bd067c4905a4246d35a44c01d62154d91", sha256 = "75922da3a1bdb417d820398eb03d4e9bd067c4905a4246d35a44c01d62154d91",
) )
# Point to the commit that deprecates the usage of Eigen::MappedSparseMatrix.
http_archive( http_archive(
name = "pybind11", name = "pybind11",
urls = [ urls = [
"https://storage.googleapis.com/mirror.tensorflow.org/github.com/pybind/pybind11/archive/v2.7.1.tar.gz", "https://github.com/pybind/pybind11/archive/70a58c577eaf067748c2ec31bfd0b0a614cffba6.zip",
"https://github.com/pybind/pybind11/archive/v2.7.1.tar.gz",
], ],
sha256 = "616d1c42e4cf14fa27b2a4ff759d7d7b33006fdc5ad8fd603bb2c22622f27020", sha256 = "b971842fab1b5b8f3815a2302331782b7d137fef0e06502422bc4bc360f4956c",
strip_prefix = "pybind11-2.7.1", strip_prefix = "pybind11-70a58c577eaf067748c2ec31bfd0b0a614cffba6",
build_file = "@pybind11_bazel//:pybind11.BUILD", build_file = "@pybind11_bazel//:pybind11.BUILD",
) )
# Point to the commit that deprecates the usage of Eigen::MappedSparseMatrix.
http_archive( http_archive(
name = "ceres_solver", name = "ceres_solver",
url = "https://github.com/ceres-solver/ceres-solver/archive/2.0.0.zip", url = "https://github.com/ceres-solver/ceres-solver/archive/123fba61cf2611a3c8bddc9d91416db26b10b558.zip",
patches = [ patches = [
"@//third_party:ceres_solver_compatibility_fixes.diff" "@//third_party:ceres_solver_compatibility_fixes.diff"
], ],
patch_args = [ patch_args = [
"-p1", "-p1",
], ],
strip_prefix = "ceres-solver-2.0.0", strip_prefix = "ceres-solver-123fba61cf2611a3c8bddc9d91416db26b10b558",
sha256 = "db12d37b4cebb26353ae5b7746c7985e00877baa8e7b12dc4d3a1512252fff3b" sha256 = "8b7b16ceb363420e0fd499576daf73fa338adb0b1449f58bea7862766baa1ac7"
) )
http_archive( http_archive(
@@ -200,7 +202,10 @@ 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/local/opt/opencv@3", # For local MacOS builds, the path should point to an opencv@3 installation.
# If you edit the path here, you will also need to update the corresponding
# prefix in "opencv_macos.BUILD".
path = "/usr/local",
) )
new_local_repository( new_local_repository(
@@ -249,21 +254,12 @@ http_archive(
], ],
) )
# You may run setup_android.sh to install Android SDK and NDK.
android_ndk_repository(
name = "androidndk",
)
android_sdk_repository(
name = "androidsdk",
)
# iOS basic build deps. # iOS basic build deps.
http_archive( http_archive(
name = "build_bazel_rules_apple", name = "build_bazel_rules_apple",
sha256 = "7a7afdd4869bb201c9352eed2daf37294d42b093579b70423490c1b4d4f6ce42", sha256 = "77e8bf6fda706f420a55874ae6ee4df0c9d95da6c7838228b26910fc82eea5a2",
url = "https://github.com/bazelbuild/rules_apple/releases/download/0.19.0/rules_apple.0.19.0.tar.gz", url = "https://github.com/bazelbuild/rules_apple/releases/download/0.32.0/rules_apple.0.32.0.tar.gz",
patches = [ patches = [
# Bypass checking ios unit test runner when building MP ios applications. # Bypass checking ios unit test runner when building MP ios applications.
"@//third_party:build_bazel_rules_apple_bypass_test_runner_check.diff" "@//third_party:build_bazel_rules_apple_bypass_test_runner_check.diff"
@@ -289,10 +285,9 @@ swift_rules_dependencies()
http_archive( http_archive(
name = "build_bazel_apple_support", name = "build_bazel_apple_support",
sha256 = "122ebf7fe7d1c8e938af6aeaee0efe788a3a2449ece5a8d6a428cb18d6f88033", sha256 = "741366f79d900c11e11d8efd6cc6c66a31bfb2451178b58e0b5edc6f1db17b35",
urls = [ urls = [
"https://storage.googleapis.com/mirror.tensorflow.org/github.com/bazelbuild/apple_support/releases/download/0.7.1/apple_support.0.7.1.tar.gz", "https://github.com/bazelbuild/apple_support/releases/download/0.10.0/apple_support.0.10.0.tar.gz"
"https://github.com/bazelbuild/apple_support/releases/download/0.7.1/apple_support.0.7.1.tar.gz",
], ],
) )
@@ -350,6 +345,9 @@ maven_install(
"com.google.auto.value:auto-value:1.8.1", "com.google.auto.value:auto-value:1.8.1",
"com.google.auto.value:auto-value-annotations:1.8.1", "com.google.auto.value:auto-value-annotations:1.8.1",
"com.google.code.findbugs:jsr305:latest.release", "com.google.code.findbugs:jsr305:latest.release",
"com.google.android.datatransport:transport-api:3.0.0",
"com.google.android.datatransport:transport-backend-cct:3.1.0",
"com.google.android.datatransport:transport-runtime:3.1.0",
"com.google.flogger:flogger-system-backend:0.6", "com.google.flogger:flogger-system-backend:0.6",
"com.google.flogger:flogger:0.6", "com.google.flogger:flogger:0.6",
"com.google.guava:guava:27.0.1-android", "com.google.guava:guava:27.0.1-android",
@@ -379,9 +377,9 @@ http_archive(
) )
# Tensorflow repo should always go after the other external dependencies. # Tensorflow repo should always go after the other external dependencies.
# 2021-07-29 # 2022-02-15
_TENSORFLOW_GIT_COMMIT = "52a2905cbc21034766c08041933053178c5d10e3" _TENSORFLOW_GIT_COMMIT = "a3419acc751dfc19caf4d34a1594e1f76810ec58"
_TENSORFLOW_SHA256 = "06d4691bcdb700f3275fa0971a1585221c2b9f3dffe867963be565a6643d7f56" _TENSORFLOW_SHA256 = "b95b2a83632d4055742ae1a2dcc96b45da6c12a339462dbc76c8bca505308e3a"
http_archive( http_archive(
name = "org_tensorflow", name = "org_tensorflow",
urls = [ urls = [
@@ -389,7 +387,6 @@ http_archive(
], ],
patches = [ patches = [
"@//third_party:org_tensorflow_compatibility_fixes.diff", "@//third_party:org_tensorflow_compatibility_fixes.diff",
"@//third_party:org_tensorflow_objc_cxx17.diff",
# Diff is generated with a script, don't update it manually. # Diff is generated with a script, don't update it manually.
"@//third_party:org_tensorflow_custom_ops.diff", "@//third_party:org_tensorflow_custom_ops.diff",
], ],
+2 -2
View File
@@ -109,7 +109,7 @@ for app in ${apps}; do
if [[ ${category} != "shoe" ]]; then if [[ ${category} != "shoe" ]]; then
bazel_flags_extended+=(--define ${category}=true) bazel_flags_extended+=(--define ${category}=true)
fi fi
bazel "${bazel_flags_extended[@]}" bazelisk "${bazel_flags_extended[@]}"
cp -f "${bin}" "${apk}" cp -f "${bin}" "${apk}"
fi fi
apks+=(${apk}) apks+=(${apk})
@@ -120,7 +120,7 @@ for app in ${apps}; do
if [[ ${app_name} == "templatematchingcpu" ]]; then if [[ ${app_name} == "templatematchingcpu" ]]; then
switch_to_opencv_4 switch_to_opencv_4
fi fi
bazel "${bazel_flags[@]}" bazelisk "${bazel_flags[@]}"
cp -f "${bin}" "${apk}" cp -f "${bin}" "${apk}"
if [[ ${app_name} == "templatematchingcpu" ]]; then if [[ ${app_name} == "templatematchingcpu" ]]; then
switch_to_opencv_3 switch_to_opencv_3
+1 -1
View File
@@ -83,7 +83,7 @@ for app in ${apps}; do
bazel_flags=("${default_bazel_flags[@]}") bazel_flags=("${default_bazel_flags[@]}")
bazel_flags+=(${target}) bazel_flags+=(${target})
bazel "${bazel_flags[@]}" bazelisk "${bazel_flags[@]}"
cp -f "${bin_dir}/${app}/"*"_cpu" "${out_dir}" cp -f "${bin_dir}/${app}/"*"_cpu" "${out_dir}"
fi fi
if [[ $build_only == false ]]; then if [[ $build_only == false ]]; then
+1 -1
View File
@@ -71,7 +71,7 @@ for app in ${apps}; do
bazel_flags+=(--linkopt=-s) bazel_flags+=(--linkopt=-s)
fi fi
bazel "${bazel_flags[@]}" bazelisk "${bazel_flags[@]}"
cp -f "${bin_dir}/${app}/"*".ipa" "${out_dir}" cp -f "${bin_dir}/${app}/"*".ipa" "${out_dir}"
fi fi
done done
+2 -2
View File
@@ -169,7 +169,7 @@ behavior depending on resource constraints.
[`CalculatorBase`]: https://github.com/google/mediapipe/tree/master/mediapipe/framework/calculator_base.h [`CalculatorBase`]: https://github.com/google/mediapipe/tree/master/mediapipe/framework/calculator_base.h
[`DefaultInputStreamHandler`]: https://github.com/google/mediapipe/tree/master/mediapipe/framework/stream_handler/default_input_stream_handler.h [`DefaultInputStreamHandler`]: https://github.com/google/mediapipe/tree/master/mediapipe/framework/stream_handler/default_input_stream_handler.h
[`SyncSetInputStreamHandler`]: https://github.com/google/mediapipe/tree/master/mediapipe/framework/stream_handler/sync_set_input_stream_handler.h [`SyncSetInputStreamHandler`]: https://github.com/google/mediapipe/tree/master/mediapipe/framework/stream_handler/sync_set_input_stream_handler.cc
[`ImmediateInputStreamHandler`]: https://github.com/google/mediapipe/tree/master/mediapipe/framework/stream_handler/immediate_input_stream_handler.h [`ImmediateInputStreamHandler`]: https://github.com/google/mediapipe/tree/master/mediapipe/framework/stream_handler/immediate_input_stream_handler.cc
[`CalculatorGraphConfig::max_queue_size`]: https://github.com/google/mediapipe/tree/master/mediapipe/framework/calculator.proto [`CalculatorGraphConfig::max_queue_size`]: https://github.com/google/mediapipe/tree/master/mediapipe/framework/calculator.proto
[`FlowLimiterCalculator`]: https://github.com/google/mediapipe/tree/master/mediapipe/calculators/core/flow_limiter_calculator.cc [`FlowLimiterCalculator`]: https://github.com/google/mediapipe/tree/master/mediapipe/calculators/core/flow_limiter_calculator.cc
+10 -1
View File
@@ -29,7 +29,7 @@ APIs (currently in alpha) that are now available in
* Install MediaPipe following these [instructions](./install.md). * Install MediaPipe following these [instructions](./install.md).
* Setup Java Runtime. * Setup Java Runtime.
* Setup Android SDK release 28.0.3 and above. * Setup Android SDK release 30.0.0 and above.
* Setup Android NDK version between 18 and 21. * Setup Android NDK version between 18 and 21.
MediaPipe recommends setting up Android SDK and NDK via Android Studio (and see MediaPipe recommends setting up Android SDK and NDK via Android Studio (and see
@@ -47,6 +47,15 @@ export ANDROID_HOME=<path to the Android SDK>
export ANDROID_NDK_HOME=<path to the Android NDK> export ANDROID_NDK_HOME=<path to the Android NDK>
``` ```
and add android_ndk_repository() and android_sdk_repository() rules into the
[`WORKSPACE`](https://github.com/google/mediapipe/blob/master/WORKSPACE) file as
the following:
```bash
$ echo "android_sdk_repository(name = \"androidsdk\")" >> WORKSPACE
$ echo "android_ndk_repository(name = \"androidndk\", api_level=21)" >> WORKSPACE
```
In order to use MediaPipe on earlier Android versions, MediaPipe needs to switch In order to use MediaPipe on earlier Android versions, MediaPipe needs to switch
to a lower Android API level. You can achieve this by specifying `api_level = to a lower Android API level. You can achieve this by specifying `api_level =
$YOUR_INTENDED_API_LEVEL` in android_ndk_repository() and/or $YOUR_INTENDED_API_LEVEL` in android_ndk_repository() and/or
@@ -48,6 +48,16 @@ each project.
bazel build -c opt --strip=ALWAYS \ bazel build -c opt --strip=ALWAYS \
--host_crosstool_top=@bazel_tools//tools/cpp:toolchain \ --host_crosstool_top=@bazel_tools//tools/cpp:toolchain \
--fat_apk_cpu=arm64-v8a,armeabi-v7a \ --fat_apk_cpu=arm64-v8a,armeabi-v7a \
--legacy_whole_archive=0 \
--features=-legacy_whole_archive \
--copt=-fvisibility=hidden \
--copt=-ffunction-sections \
--copt=-fdata-sections \
--copt=-fstack-protector \
--copt=-Oz \
--copt=-fomit-frame-pointer \
--copt=-DABSL_MIN_LOG_LEVEL=2 \
--linkopt=-Wl,--gc-sections,--strip-all \
//path/to/the/aar/build/file:aar_name.aar //path/to/the/aar/build/file:aar_name.aar
``` ```
@@ -57,6 +67,16 @@ each project.
bazel build -c opt --strip=ALWAYS \ bazel build -c opt --strip=ALWAYS \
--host_crosstool_top=@bazel_tools//tools/cpp:toolchain \ --host_crosstool_top=@bazel_tools//tools/cpp:toolchain \
--fat_apk_cpu=arm64-v8a,armeabi-v7a \ --fat_apk_cpu=arm64-v8a,armeabi-v7a \
--legacy_whole_archive=0 \
--features=-legacy_whole_archive \
--copt=-fvisibility=hidden \
--copt=-ffunction-sections \
--copt=-fdata-sections \
--copt=-fstack-protector \
--copt=-Oz \
--copt=-fomit-frame-pointer \
--copt=-DABSL_MIN_LOG_LEVEL=2 \
--linkopt=-Wl,--gc-sections,--strip-all \
//mediapipe/examples/android/src/java/com/google/mediapipe/apps/aar_example:mediapipe_face_detection.aar //mediapipe/examples/android/src/java/com/google/mediapipe/apps/aar_example:mediapipe_face_detection.aar
# It should print: # It should print:
@@ -117,7 +137,7 @@ each project.
implementation 'com.google.flogger:flogger-system-backend:latest.release' implementation 'com.google.flogger:flogger-system-backend:latest.release'
implementation 'com.google.code.findbugs:jsr305:latest.release' implementation 'com.google.code.findbugs:jsr305:latest.release'
implementation 'com.google.guava:guava:27.0.1-android' implementation 'com.google.guava:guava:27.0.1-android'
implementation 'com.google.protobuf:protobuf-java:3.11.4' implementation 'com.google.protobuf:protobuf-javalite:3.19.1'
// CameraX core library // CameraX core library
def camerax_version = "1.0.0-beta10" def camerax_version = "1.0.0-beta10"
implementation "androidx.camera:camera-core:$camerax_version" implementation "androidx.camera:camera-core:$camerax_version"
+80 -30
View File
@@ -1,28 +1,30 @@
--- ---
layout: default layout: default
title: Android Solutions title: MediaPipe Android Solutions
parent: MediaPipe on Android parent: MediaPipe on Android
grand_parent: Getting Started grand_parent: Getting Started
nav_order: 2 nav_order: 2
--- ---
# Android Solution APIs # MediaPipe Android Solutions
{: .no_toc } {: .no_toc }
1. TOC 1. TOC
{:toc} {:toc}
--- ---
Please follow instructions below to use the MediaPipe Solution APIs in Android MediaPipe Android Solution APIs (currently in alpha) are available in:
Studio projects and build the Android example apps in the supported MediaPipe
[solutions](../solutions/solutions.md).
## Integrate MediaPipe Android Solutions in Android Studio * [MediaPipe Face Detection](../solutions/face_detection#android-solution-api)
* [MediaPipe Face Mesh](../solutions/face_mesh#android-solution-api)
* [MediaPipe Hands](../solutions/hands#android-solution-api)
MediaPipe Android Solution APIs (currently in alpha) are now available in ## Incorporation in Android Studio
Prebuilt packages of Android Solution APIs can be found in
[Google's Maven Repository](https://maven.google.com/web/index.html?#com.google.mediapipe). [Google's Maven Repository](https://maven.google.com/web/index.html?#com.google.mediapipe).
To incorporate MediaPipe Android Solutions into an Android Studio project, add To incorporate them into an Android Studio project, add the following into the
the following into the project's Gradle dependencies: project's Gradle dependencies:
``` ```
dependencies { dependencies {
@@ -34,27 +36,20 @@ dependencies {
implementation 'com.google.mediapipe:facemesh:latest.release' implementation 'com.google.mediapipe:facemesh:latest.release'
// Optional: MediaPipe Hands Solution. // Optional: MediaPipe Hands Solution.
implementation 'com.google.mediapipe:hands:latest.release' implementation 'com.google.mediapipe:hands:latest.release'
// MediaPipe deps
implementation 'com.google.flogger:flogger:0.6'
implementation 'com.google.flogger:flogger-system-backend:0.6'
implementation 'com.google.guava:guava:27.0.1-android'
implementation 'com.google.protobuf:protobuf-java:3.11.4'
// CameraX core library
def camerax_version = "1.0.0-beta10"
implementation "androidx.camera:camera-core:$camerax_version"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-lifecycle:$camerax_version"
} }
``` ```
See the detailed solution APIs usage examples for different use cases in the If you need further customization, instead of using the prebuilt maven packages
solution example apps' consider building a MediaPipe Android Archive library locally from source by
[source code](https://github.com/google/mediapipe/tree/master/mediapipe/examples/android/solutions). following these [instructions](./android_archive_library.md).
If the prebuilt maven packages are not sufficient, building the MediaPipe
Android archive library locally by following these
[instructions](./android_archive_library.md).
## Build solution example apps in Android Studio ## Building solution example apps
Detailed usage examples of the Android Solution APIs can be found in the
[source code](https://github.com/google/mediapipe/tree/master/mediapipe/examples/android/solutions)
of the solution example apps.
To build these apps:
1. Open Android Studio Arctic Fox on Linux, macOS, or Windows. 1. Open Android Studio Arctic Fox on Linux, macOS, or Windows.
@@ -75,7 +70,62 @@ Android archive library locally by following these
6. (Optional) Run solutions on CPU. 6. (Optional) Run solutions on CPU.
MediaPipe solution example apps run the pipeline and the model inference on MediaPipe solution example apps run the pipeline and model inference on GPU
GPU by default. If needed, for example to run the apps on Android Emulator, by default. If needed, for example to run the apps on Android Emulator, set
set the `RUN_ON_GPU` boolean variable to `false` in the app's the `RUN_ON_GPU` boolean variable to `false` in the app's
MainActivity.java to run the pipeline and the model inference on CPU. `MainActivity.java` to run the pipeline and model inference on CPU.
## MediaPipe Solution APIs Terms of Service
Last modified: November 12, 2021
Use of MediaPipe Solution APIs is subject to the
[Google APIs Terms of Service](https://developers.google.com/terms),
[Google API Services User Data Policy](https://developers.google.com/terms/api-services-user-data-policy),
and the terms below. Please check back from time to time as these terms and
policies are occasionally updated.
**Privacy**
When you use MediaPipe Solution APIs, processing of the input data (e.g. images,
video, text) fully happens on-device, and **MediaPipe does not send that input
data to Google servers**. As a result, you can use our APIs for processing data
that should not leave the device.
MediaPipe Android Solution APIs will contact Google servers from time to time in
order to receive things like bug fixes, updated models, and hardware accelerator
compatibility information. MediaPipe Android Solution APIs also send metrics
about the performance and utilization of the APIs in your app to Google. Google
uses this metrics data to measure performance, API usage, debug, maintain and
improve the APIs, and detect misuse or abuse, as further described in our
[Privacy Policy](https://policies.google.com/privacy).
**You are responsible for obtaining informed consent from your app users about
Googles processing of MediaPipe metrics data as required by applicable law.**
Data we collect may include the following, across all MediaPipe Android Solution
APIs:
- Device information (such as manufacturer, model, OS version and build) and
available ML hardware accelerators (GPU and DSP). Used for diagnostics and
usage analytics.
- App identification information (package name / bundle id, app version). Used
for diagnostics and usage analytics.
- API configuration (such as image format, resolution, and MediaPipe version
used). Used for diagnostics and usage analytics.
- Event type (such as initialize, download model, update, run, and detection).
Used for diagnostics and usage analytics.
- Error codes. Used for diagnostics.
- Performance metrics. Used for diagnostics.
- Per-installation identifiers that do not uniquely identify a user or
physical device. Used for operation of remote configuration and usage
analytics.
- Network request sender IP addresses. Used for remote configuration
diagnostics. Collected IP addresses are retained temporarily.
+15
View File
@@ -59,6 +59,21 @@ OpenGL ES profile shading language version string: OpenGL ES GLSL ES 3.20
OpenGL ES profile extensions: OpenGL ES profile extensions:
``` ```
If you have connected to your computer through SSH and find when you probe for
GPU information you see the output:
```bash
glxinfo | grep -i opengl
Error: unable to open display
```
Try re-establishing your SSH connection with the `-X` option and try again. For
example:
```bash
ssh -X <user>@<host>
```
*Notice the ES 3.20 text above.* *Notice the ES 3.20 text above.*
You need to see ES 3.1 or greater printed in order to perform TFLite inference You need to see ES 3.1 or greater printed in order to perform TFLite inference
+1 -1
View File
@@ -131,7 +131,7 @@ Create a `BUILD` file in the `$APPLICATION_PATH` and add the following build
rules: rules:
``` ```
MIN_IOS_VERSION = "10.0" MIN_IOS_VERSION = "11.0"
load( load(
"@build_bazel_rules_apple//apple:ios.bzl", "@build_bazel_rules_apple//apple:ios.bzl",
+3 -3
View File
@@ -569,7 +569,7 @@ next section.
Option 1. Follow Option 1. Follow
[the official Bazel documentation](https://docs.bazel.build/versions/master/install-windows.html) [the official Bazel documentation](https://docs.bazel.build/versions/master/install-windows.html)
to install Bazel 3.7.2 or higher. to install Bazel 5.0.0 or higher.
Option 2. Follow the official Option 2. Follow the official
[Bazel documentation](https://docs.bazel.build/versions/master/install-bazelisk.html) [Bazel documentation](https://docs.bazel.build/versions/master/install-bazelisk.html)
@@ -657,7 +657,7 @@ cameras. Alternatively, you use a video file as input.
Note: Windows' and WSLs adb versions must be the same version, e.g., if WSL Note: Windows' and WSLs adb versions must be the same version, e.g., if WSL
has ADB 1.0.39, you need to download the corresponding Windows ADB from has ADB 1.0.39, you need to download the corresponding Windows ADB from
[here](https://dl.google.com/android/repository/platform-tools_r26.0.1-windows.zip). [here](https://dl.google.com/android/repository/platform-tools_r30.0.3-windows.zip).
3. Launch WSL. 3. Launch WSL.
@@ -796,7 +796,7 @@ This will use a Docker image that will isolate mediapipe's installation from the
```bash ```bash
$ docker run -it --name mediapipe mediapipe:latest $ docker run -it --name mediapipe mediapipe:latest
root@bca08b91ff63:/mediapipe# GLOG_logtostderr=1 bazelisk run --define MEDIAPIPE_DISABLE_GPU=1 mediapipe/examples/desktop/hello_world:hello_world root@bca08b91ff63:/mediapipe# GLOG_logtostderr=1 bazel run --define MEDIAPIPE_DISABLE_GPU=1 mediapipe/examples/desktop/hello_world
# Should print: # Should print:
# Hello World! # Hello World!
+10 -2
View File
@@ -32,9 +32,14 @@ example apps, start from, start from
xcode-select --install xcode-select --install
``` ```
3. Install [Bazel](https://bazel.build/). 3. Install [Bazelisk](https://github.com/bazelbuild/bazelisk)
.
We recommend using [Homebrew](https://brew.sh/) to get the latest version. We recommend using [Homebrew](https://brew.sh/) to get the latest versions.
```bash
brew install bazelisk
```
4. Set Python 3.7 as the default Python version and install the Python "six" 4. Set Python 3.7 as the default Python version and install the Python "six"
library. This is needed for TensorFlow. library. This is needed for TensorFlow.
@@ -187,6 +192,9 @@ Note: When you ask Xcode to run an app, by default it will use the Debug
configuration. Some of our demos are computationally heavy; you may want to use configuration. Some of our demos are computationally heavy; you may want to use
the Release configuration for better performance. the Release configuration for better performance.
Note: Due to an imcoptibility caused by one of our dependencies, MediaPipe
cannot be used for apps running on the iPhone Simulator on Apple Silicon (M1).
Tip: To switch build configuration in Xcode, click on the target menu, choose Tip: To switch build configuration in Xcode, click on the target menu, choose
"Edit Scheme...", select the Run action, and switch the Build Configuration from "Edit Scheme...", select the Run action, and switch the Build Configuration from
Debug to Release. Note that this is set independently for each target. Debug to Release. Note that this is set independently for each target.
+3 -2
View File
@@ -126,6 +126,7 @@ following steps:
} }
return packet.Get<MyType>(); return packet.Get<MyType>();
}); });
}
} // namespace mediapipe } // namespace mediapipe
``` ```
@@ -249,12 +250,12 @@ three stages: initialization and setup, graph run, and graph shutdown.
graph.start_run() graph.start_run()
graph.add_packet_to_input_stream( graph.add_packet_to_input_stream(
'in_stream', mp.packet_creator.create_str('abc').at(0)) 'in_stream', mp.packet_creator.create_string('abc').at(0))
rgb_img = cv2.cvtColor(cv2.imread('/path/to/your/image.png'), cv2.COLOR_BGR2RGB) rgb_img = cv2.cvtColor(cv2.imread('/path/to/your/image.png'), cv2.COLOR_BGR2RGB)
graph.add_packet_to_input_stream( graph.add_packet_to_input_stream(
'in_stream', 'in_stream',
mp.packet_creator.create_image_frame(format=mp.ImageFormat.SRGB, mp.packet_creator.create_image_frame(image_format=mp.ImageFormat.SRGB,
data=rgb_img).at(1)) data=rgb_img).at(1))
``` ```
+2 -2
View File
@@ -108,14 +108,14 @@ ERROR: No matching distribution found for mediapipe
after running `pip install mediapipe` usually indicates that there is no qualified MediaPipe Python for your system. after running `pip install mediapipe` usually indicates that there is no qualified MediaPipe Python for your system.
Please note that MediaPipe Python PyPI officially supports the **64-bit** Please note that MediaPipe Python PyPI officially supports the **64-bit**
version of Python 3.7 and above on the following OS: version of Python 3.7 to 3.10 on the following OS:
- x86_64 Linux - x86_64 Linux
- x86_64 macOS 10.15+ - x86_64 macOS 10.15+
- amd64 Windows - amd64 Windows
If the OS is currently supported and you still see this error, please make sure If the OS is currently supported and you still see this error, please make sure
that both the Python and pip binary are for Python 3.7 and above. Otherwise, that both the Python and pip binary are for Python 3.7 to 3.10. Otherwise,
please consider building the MediaPipe Python package locally by following the please consider building the MediaPipe Python package locally by following the
instructions [here](python.md#building-mediapipe-python-package). instructions [here](python.md#building-mediapipe-python-package).
+2 -2
View File
@@ -136,8 +136,8 @@ run code search using
## Community ## Community
* [Awesome MediaPipe](https://mediapipe.org) - A curated list of awesome * [Awesome MediaPipe](https://mediapipe.page.link/awesome-mediapipe) - A
MediaPipe related frameworks, libraries and software curated list of awesome MediaPipe related frameworks, libraries and software
* [Slack community](https://mediapipe.page.link/joinslack) for MediaPipe users * [Slack community](https://mediapipe.page.link/joinslack) for MediaPipe users
* [Discuss](https://groups.google.com/forum/#!forum/mediapipe) - General * [Discuss](https://groups.google.com/forum/#!forum/mediapipe) - General
community discussion around MediaPipe community discussion around MediaPipe
+36 -16
View File
@@ -26,7 +26,7 @@ MediaPipe Face Detection is an ultrafast face detection solution that comes with
face detector tailored for mobile GPU inference. The detector's super-realtime face detector tailored for mobile GPU inference. The detector's super-realtime
performance enables it to be applied to any live viewfinder experience that performance enables it to be applied to any live viewfinder experience that
requires an accurate facial region of interest as an input for other requires an accurate facial region of interest as an input for other
task-specific models, such as 3D facial keypoint or geometry estimation (e.g., task-specific models, such as 3D facial keypoint estimation (e.g.,
[MediaPipe Face Mesh](./face_mesh.md)), facial features or expression [MediaPipe Face Mesh](./face_mesh.md)), facial features or expression
classification, and face region segmentation. BlazeFace uses a lightweight classification, and face region segmentation. BlazeFace uses a lightweight
feature extraction network inspired by, but distinct from feature extraction network inspired by, but distinct from
@@ -218,14 +218,13 @@ camera.start();
### Android Solution API ### Android Solution API
Please first follow general Please first follow general
[instructions](../getting_started/android_solutions.md#integrate-mediapipe-android-solutions-api) [instructions](../getting_started/android_solutions.md) to add MediaPipe Gradle
to add MediaPipe Gradle dependencies, then try the Face Detection Solution API dependencies and try the Android Solution API in the companion
in the companion [example Android Studio project](https://github.com/google/mediapipe/tree/master/mediapipe/examples/android/solutions/facedetection),
[example Android Studio project](https://github.com/google/mediapipe/tree/master/mediapipe/examples/android/solutions/facedetection)
following
[these instructions](../getting_started/android_solutions.md#build-solution-example-apps-in-android-studio)
and learn more in the usage example below. and learn more in the usage example below.
Supported configuration options:
* [staticImageMode](#static_image_mode) * [staticImageMode](#static_image_mode)
* [modelSelection](#model_selection) * [modelSelection](#model_selection)
@@ -257,8 +256,15 @@ glSurfaceView.setSolutionResultRenderer(new FaceDetectionResultGlRenderer());
glSurfaceView.setRenderInputImage(true); glSurfaceView.setRenderInputImage(true);
faceDetection.setResultListener( faceDetection.setResultListener(
faceDetectionResult -> { faceDetectionResult -> {
if (faceDetectionResult.multiFaceDetections().isEmpty()) {
return;
}
RelativeKeypoint noseTip = RelativeKeypoint noseTip =
FaceDetection.getFaceKeypoint(result, 0, FaceKeypoint.NOSE_TIP); faceDetectionResult
.multiFaceDetections()
.get(0)
.getLocationData()
.getRelativeKeypoints(FaceKeypoint.NOSE_TIP);
Log.i( Log.i(
TAG, TAG,
String.format( String.format(
@@ -297,10 +303,17 @@ FaceDetection faceDetection = new FaceDetection(this, faceDetectionOptions);
FaceDetectionResultImageView imageView = new FaceDetectionResultImageView(this); FaceDetectionResultImageView imageView = new FaceDetectionResultImageView(this);
faceDetection.setResultListener( faceDetection.setResultListener(
faceDetectionResult -> { faceDetectionResult -> {
if (faceDetectionResult.multiFaceDetections().isEmpty()) {
return;
}
int width = faceDetectionResult.inputBitmap().getWidth(); int width = faceDetectionResult.inputBitmap().getWidth();
int height = faceDetectionResult.inputBitmap().getHeight(); int height = faceDetectionResult.inputBitmap().getHeight();
RelativeKeypoint noseTip = RelativeKeypoint noseTip =
FaceDetection.getFaceKeypoint(result, 0, FaceKeypoint.NOSE_TIP); faceDetectionResult
.multiFaceDetections()
.get(0)
.getLocationData()
.getRelativeKeypoints(FaceKeypoint.NOSE_TIP);
Log.i( Log.i(
TAG, TAG,
String.format( String.format(
@@ -334,9 +347,9 @@ ActivityResultLauncher<Intent> imageGetter =
} }
} }
}); });
Intent gallery = new Intent( Intent pickImageIntent = new Intent(Intent.ACTION_PICK);
Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI); pickImageIntent.setDataAndType(MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*");
imageGetter.launch(gallery); imageGetter.launch(pickImageIntent);
``` ```
#### Video Input #### Video Input
@@ -368,8 +381,15 @@ glSurfaceView.setRenderInputImage(true);
faceDetection.setResultListener( faceDetection.setResultListener(
faceDetectionResult -> { faceDetectionResult -> {
if (faceDetectionResult.multiFaceDetections().isEmpty()) {
return;
}
RelativeKeypoint noseTip = RelativeKeypoint noseTip =
FaceDetection.getFaceKeypoint(result, 0, FaceKeypoint.NOSE_TIP); faceDetectionResult
.multiFaceDetections()
.get(0)
.getLocationData()
.getRelativeKeypoints(FaceKeypoint.NOSE_TIP);
Log.i( Log.i(
TAG, TAG,
String.format( String.format(
@@ -398,9 +418,9 @@ ActivityResultLauncher<Intent> videoGetter =
} }
} }
}); });
Intent gallery = Intent pickVideoIntent = new Intent(Intent.ACTION_PICK);
new Intent(Intent.ACTION_PICK, MediaStore.Video.Media.INTERNAL_CONTENT_URI); pickVideoIntent.setDataAndType(MediaStore.Video.Media.INTERNAL_CONTENT_URI, "video/*");
videoGetter.launch(gallery); videoGetter.launch(pickVideoIntent);
``` ```
## Example Apps ## Example Apps
+38 -41
View File
@@ -20,34 +20,34 @@ nav_order: 2
## Overview ## Overview
MediaPipe Face Mesh is a face geometry solution that estimates 468 3D face MediaPipe Face Mesh is a solution that estimates 468 3D face landmarks in
landmarks in real-time even on mobile devices. It employs machine learning (ML) real-time even on mobile devices. It employs machine learning (ML) to infer the
to infer the 3D surface geometry, requiring only a single camera input without 3D facial surface, requiring only a single camera input without the need for a
the need for a dedicated depth sensor. Utilizing lightweight model architectures dedicated depth sensor. Utilizing lightweight model architectures together with
together with GPU acceleration throughout the pipeline, the solution delivers GPU acceleration throughout the pipeline, the solution delivers real-time
real-time performance critical for live experiences. performance critical for live experiences.
Additionally, the solution is bundled with the Face Geometry module that bridges Additionally, the solution is bundled with the Face Transform module that
the gap between the face landmark estimation and useful real-time augmented bridges the gap between the face landmark estimation and useful real-time
reality (AR) applications. It establishes a metric 3D space and uses the face augmented reality (AR) applications. It establishes a metric 3D space and uses
landmark screen positions to estimate face geometry within that space. The face the face landmark screen positions to estimate a face transform within that
geometry data consists of common 3D geometry primitives, including a face pose space. The face transform data consists of common 3D primitives, including a
transformation matrix and a triangular face mesh. Under the hood, a lightweight face pose transformation matrix and a triangular face mesh. Under the hood, a
statistical analysis method called lightweight statistical analysis method called
[Procrustes Analysis](https://en.wikipedia.org/wiki/Procrustes_analysis) is [Procrustes Analysis](https://en.wikipedia.org/wiki/Procrustes_analysis) is
employed to drive a robust, performant and portable logic. The analysis runs on 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. 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) |
:-------------------------------------------------------------: | :-------------------------------------------------------------: |
*Fig 1. AR effects utilizing facial surface geometry.* | *Fig 1. AR effects utilizing the 3D facial surface.* |
## ML Pipeline ## ML Pipeline
Our ML pipeline consists of two real-time deep neural network models that work Our ML pipeline consists of two real-time deep neural network models that work
together: A detector that operates on the full image and computes face locations together: A detector that operates on the full image and computes face locations
and a 3D face landmark model that operates on those locations and predicts the and a 3D face landmark model that operates on those locations and predicts the
approximate surface geometry via regression. Having the face accurately cropped approximate 3D surface via regression. Having the face accurately cropped
drastically reduces the need for common data augmentations like affine drastically reduces the need for common data augmentations like affine
transformations consisting of rotations, translation and scale changes. Instead transformations consisting of rotations, translation and scale changes. Instead
it allows the network to dedicate most of its capacity towards coordinate it allows the network to dedicate most of its capacity towards coordinate
@@ -55,8 +55,8 @@ prediction accuracy. In addition, in our pipeline the crops can also be
generated based on the face landmarks identified in the previous frame, and only generated based on the face landmarks identified in the previous frame, and only
when the landmark model could no longer identify face presence is the face when the landmark model could no longer identify face presence is the face
detector invoked to relocalize the face. This strategy is similar to that detector invoked to relocalize the face. This strategy is similar to that
employed in our [MediaPipe Hands](./hands.md) solution, which uses a palm detector employed in our [MediaPipe Hands](./hands.md) solution, which uses a palm
together with a hand landmark model. detector together with a hand landmark model.
The pipeline is implemented as a MediaPipe The pipeline is implemented as a MediaPipe
[graph](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/face_mesh/face_mesh_mobile.pbtxt) [graph](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/face_mesh/face_mesh_mobile.pbtxt)
@@ -128,7 +128,7 @@ about the model in this [paper](https://arxiv.org/abs/2006.10962).
:---------------------------------------------------------------------------: | :---------------------------------------------------------------------------: |
*Fig 3. Attention Mesh: Overview of model architecture.* | *Fig 3. Attention Mesh: Overview of model architecture.* |
## Face Geometry Module ## Face Transform Module
The [Face Landmark Model](#face-landmark-model) performs a single-camera face landmark 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 detection in the screen coordinate space: the X- and Y- coordinates are
@@ -140,7 +140,7 @@ enable the full spectrum of augmented reality (AR) features like aligning a
virtual 3D object with a detected face. virtual 3D object with a detected face.
The The
[Face Geometry module](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_geometry) [Face Transform 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 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. 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 By design, you'll be able to use a perspective camera to project the final 3D
@@ -151,7 +151,7 @@ landmark positions are not changed.
#### Metric 3D Space #### Metric 3D Space
The **Metric 3D space** established within the Face Geometry module is a The **Metric 3D space** established within the Face Transform module is a
right-handed orthonormal metric 3D coordinate space. Within the space, there is 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 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 negative direction of the Z-axis. In the current pipeline, it is assumed that
@@ -184,11 +184,11 @@ functions:
### Components ### Components
#### Geometry Pipeline #### Transform Pipeline
The **Geometry Pipeline** is a key component, which is responsible for The **Transform Pipeline** is a key component, which is responsible for
estimating face geometry objects within the Metric 3D space. On each frame, the estimating the face transform objects within the Metric 3D space. On each frame,
following steps are executed in the given order: the following steps are executed in the given order:
- Face landmark screen coordinates are converted into the Metric 3D space - Face landmark screen coordinates are converted into the Metric 3D space
coordinates; coordinates;
@@ -199,12 +199,12 @@ following steps are executed in the given order:
positions (XYZ), while both the vertex texture coordinates (UV) and the positions (XYZ), while both the vertex texture coordinates (UV) and the
triangular topology are inherited from the canonical face model. triangular topology are inherited from the canonical face model.
The geometry pipeline is implemented as a MediaPipe The transform pipeline is implemented as a MediaPipe
[calculator](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_geometry/geometry_pipeline_calculator.cc). [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 For your convenience, this calculator is bundled together with corresponding
with corresponding metadata into a unified MediaPipe metadata into a unified MediaPipe
[subgraph](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_geometry/face_geometry_from_landmarks.pbtxt). [subgraph](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_geometry/face_geometry_from_landmarks.pbtxt).
The face geometry format is defined as a Protocol Buffer The face transform format is defined as a Protocol Buffer
[message](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_geometry/protos/face_geometry.proto). [message](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_geometry/protos/face_geometry.proto).
#### Effect Renderer #### Effect Renderer
@@ -227,7 +227,7 @@ The effect renderer is implemented as a MediaPipe
| ![face_geometry_renderer.gif](../images/face_geometry_renderer.gif) | | ![face_geometry_renderer.gif](../images/face_geometry_renderer.gif) |
| :---------------------------------------------------------------------: | | :---------------------------------------------------------------------: |
| *Fig 5. An example of face effects rendered by the Face Geometry Effect Renderer.* | | *Fig 5. An example of face effects rendered by the Face Transform Effect Renderer.* |
## Solution APIs ## Solution APIs
@@ -487,12 +487,9 @@ camera.start();
### Android Solution API ### Android Solution API
Please first follow general Please first follow general
[instructions](../getting_started/android_solutions.md#integrate-mediapipe-android-solutions-api) [instructions](../getting_started/android_solutions.md) to add MediaPipe Gradle
to add MediaPipe Gradle dependencies, then try the Face Mesh Solution API in the dependencies and try the Android Solution API in the companion
companion [example Android Studio project](https://github.com/google/mediapipe/tree/master/mediapipe/examples/android/solutions/facemesh),
[example Android Studio project](https://github.com/google/mediapipe/tree/master/mediapipe/examples/android/solutions/facemesh)
following
[these instructions](../getting_started/android_solutions.md#build-solution-example-apps-in-android-studio)
and learn more in the usage example below. and learn more in the usage example below.
Supported configuration options: Supported configuration options:
@@ -612,9 +609,9 @@ ActivityResultLauncher<Intent> imageGetter =
} }
} }
}); });
Intent gallery = new Intent( Intent pickImageIntent = new Intent(Intent.ACTION_PICK);
Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI); pickImageIntent.setDataAndType(MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*");
imageGetter.launch(gallery); imageGetter.launch(pickImageIntent);
``` ```
#### Video Input #### Video Input
@@ -678,9 +675,9 @@ ActivityResultLauncher<Intent> videoGetter =
} }
} }
}); });
Intent gallery = Intent pickVideoIntent = new Intent(Intent.ACTION_PICK);
new Intent(Intent.ACTION_PICK, MediaStore.Video.Media.INTERNAL_CONTENT_URI); pickVideoIntent.setDataAndType(MediaStore.Video.Media.INTERNAL_CONTENT_URI, "video/*");
videoGetter.launch(gallery); videoGetter.launch(pickVideoIntent);
``` ```
## Example Apps ## Example Apps
+55 -25
View File
@@ -91,8 +91,10 @@ To detect initial hand locations, we designed a
mobile real-time uses in a manner similar to the face detection model in mobile real-time uses in a manner similar to the face detection model in
[MediaPipe Face Mesh](./face_mesh.md). Detecting hands is a decidedly complex [MediaPipe Face Mesh](./face_mesh.md). Detecting hands is a decidedly complex
task: our task: our
[model](https://github.com/google/mediapipe/tree/master/mediapipe/modules/palm_detection/palm_detection.tflite) [lite model](https://github.com/google/mediapipe/tree/master/mediapipe/modules/palm_detection/palm_detection_lite.tflite)
has to work across a variety of hand sizes with a large scale span (~20x) and
[full model](https://github.com/google/mediapipe/tree/master/mediapipe/modules/palm_detection/palm_detection_full.tflite)
have to work across a variety of hand sizes with a large scale span (~20x)
relative to the image frame and be able to detect occluded and self-occluded relative to the image frame and be able to detect occluded and self-occluded
hands. Whereas faces have high contrast patterns, e.g., in the eye and mouth hands. Whereas faces have high contrast patterns, e.g., in the eye and mouth
region, the lack of such features in hands makes it comparatively difficult to region, the lack of such features in hands makes it comparatively difficult to
@@ -120,7 +122,7 @@ just 86.22%.
### Hand Landmark Model ### Hand Landmark Model
After the palm detection over the whole image our subsequent hand landmark After the palm detection over the whole image our subsequent hand landmark
[model](https://github.com/google/mediapipe/tree/master/mediapipe/modules/hand_landmark/hand_landmark.tflite) [model](https://github.com/google/mediapipe/tree/master/mediapipe/modules/hand_landmark/hand_landmark_full.tflite)
performs precise keypoint localization of 21 3D hand-knuckle coordinates inside performs precise keypoint localization of 21 3D hand-knuckle coordinates inside
the detected hand regions via regression, that is direct coordinate prediction. the detected hand regions via regression, that is direct coordinate prediction.
The model learns a consistent internal hand pose representation and is robust The model learns a consistent internal hand pose representation and is robust
@@ -163,6 +165,11 @@ unrelated, images. Default to `false`.
Maximum number of hands to detect. Default to `2`. Maximum number of hands to detect. Default to `2`.
#### model_complexity
Complexity of the hand landmark model: `0` or `1`. Landmark accuracy as well as
inference latency generally go up with the model complexity. Default to `1`.
#### min_detection_confidence #### min_detection_confidence
Minimum confidence value (`[0.0, 1.0]`) from the hand detection model for the Minimum confidence value (`[0.0, 1.0]`) from the hand detection model for the
@@ -190,6 +197,13 @@ of 21 hand landmarks and each landmark is composed of `x`, `y` and `z`. `x` and
and the smaller the value the closer the landmark is to the camera. The and the smaller the value the closer the landmark is to the camera. The
magnitude of `z` uses roughly the same scale as `x`. magnitude of `z` uses roughly the same scale as `x`.
#### multi_hand_world_landmarks
Collection of detected/tracked hands, where each hand is represented as a list
of 21 hand landmarks in world coordinates. Each landmark is composed of `x`, `y`
and `z`: real-world 3D coordinates in meters with the origin at the hand's
approximate geometric center.
#### multi_handedness #### multi_handedness
Collection of handedness of the detected/tracked hands (i.e. is it a left or Collection of handedness of the detected/tracked hands (i.e. is it a left or
@@ -212,6 +226,7 @@ Supported configuration options:
* [static_image_mode](#static_image_mode) * [static_image_mode](#static_image_mode)
* [max_num_hands](#max_num_hands) * [max_num_hands](#max_num_hands)
* [model_complexity](#model_complexity)
* [min_detection_confidence](#min_detection_confidence) * [min_detection_confidence](#min_detection_confidence)
* [min_tracking_confidence](#min_tracking_confidence) * [min_tracking_confidence](#min_tracking_confidence)
@@ -256,10 +271,17 @@ with mp_hands.Hands(
mp_drawing_styles.get_default_hand_connections_style()) mp_drawing_styles.get_default_hand_connections_style())
cv2.imwrite( cv2.imwrite(
'/tmp/annotated_image' + str(idx) + '.png', cv2.flip(annotated_image, 1)) '/tmp/annotated_image' + str(idx) + '.png', cv2.flip(annotated_image, 1))
# Draw hand world landmarks.
if not results.multi_hand_world_landmarks:
continue
for hand_world_landmarks in results.multi_hand_world_landmarks:
mp_drawing.plot_landmarks(
hand_world_landmarks, mp_hands.HAND_CONNECTIONS, azimuth=5)
# For webcam input: # For webcam input:
cap = cv2.VideoCapture(0) cap = cv2.VideoCapture(0)
with mp_hands.Hands( with mp_hands.Hands(
model_complexity=0,
min_detection_confidence=0.5, min_detection_confidence=0.5,
min_tracking_confidence=0.5) as hands: min_tracking_confidence=0.5) as hands:
while cap.isOpened(): while cap.isOpened():
@@ -302,6 +324,7 @@ and a [fun application], and the following usage example.
Supported configuration options: Supported configuration options:
* [maxNumHands](#max_num_hands) * [maxNumHands](#max_num_hands)
* [modelComplexity](#model_complexity)
* [minDetectionConfidence](#min_detection_confidence) * [minDetectionConfidence](#min_detection_confidence)
* [minTrackingConfidence](#min_tracking_confidence) * [minTrackingConfidence](#min_tracking_confidence)
@@ -351,6 +374,7 @@ const hands = new Hands({locateFile: (file) => {
}}); }});
hands.setOptions({ hands.setOptions({
maxNumHands: 2, maxNumHands: 2,
modelComplexity: 1,
minDetectionConfidence: 0.5, minDetectionConfidence: 0.5,
minTrackingConfidence: 0.5 minTrackingConfidence: 0.5
}); });
@@ -370,13 +394,10 @@ camera.start();
### Android Solution API ### Android Solution API
Please first follow general Please first follow general
[instructions](../getting_started/android_solutions.md#integrate-mediapipe-android-solutions-api) [instructions](../getting_started/android_solutions.md) to add MediaPipe Gradle
to add MediaPipe Gradle dependencies, then try the Hands Solution API in the dependencies and try the Android Solution API in the companion
companion [example Android Studio project](https://github.com/google/mediapipe/tree/master/mediapipe/examples/android/solutions/hands),
[example Android Studio project](https://github.com/google/mediapipe/tree/master/mediapipe/examples/android/solutions/hands) and learn more in the usage example below.
following
[these instructions](../getting_started/android_solutions.md#build-solution-example-apps-in-android-studio)
and learn more in usage example below.
Supported configuration options: Supported configuration options:
@@ -391,7 +412,7 @@ Supported configuration options:
HandsOptions handsOptions = HandsOptions handsOptions =
HandsOptions.builder() HandsOptions.builder()
.setStaticImageMode(false) .setStaticImageMode(false)
.setMaxNumHands(1) .setMaxNumHands(2)
.setRunOnGpu(true).build(); .setRunOnGpu(true).build();
Hands hands = new Hands(this, handsOptions); Hands hands = new Hands(this, handsOptions);
hands.setErrorListener( hands.setErrorListener(
@@ -414,8 +435,11 @@ glSurfaceView.setRenderInputImage(true);
hands.setResultListener( hands.setResultListener(
handsResult -> { handsResult -> {
NormalizedLandmark wristLandmark = Hands.getHandLandmark( if (result.multiHandLandmarks().isEmpty()) {
handsResult, 0, HandLandmark.WRIST); return;
}
NormalizedLandmark wristLandmark =
handsResult.multiHandLandmarks().get(0).getLandmarkList().get(HandLandmark.WRIST);
Log.i( Log.i(
TAG, TAG,
String.format( String.format(
@@ -444,7 +468,7 @@ glSurfaceView.post(
HandsOptions handsOptions = HandsOptions handsOptions =
HandsOptions.builder() HandsOptions.builder()
.setStaticImageMode(true) .setStaticImageMode(true)
.setMaxNumHands(1) .setMaxNumHands(2)
.setRunOnGpu(true).build(); .setRunOnGpu(true).build();
Hands hands = new Hands(this, handsOptions); Hands hands = new Hands(this, handsOptions);
@@ -455,10 +479,13 @@ Hands hands = new Hands(this, handsOptions);
HandsResultImageView imageView = new HandsResultImageView(this); HandsResultImageView imageView = new HandsResultImageView(this);
hands.setResultListener( hands.setResultListener(
handsResult -> { handsResult -> {
if (result.multiHandLandmarks().isEmpty()) {
return;
}
int width = handsResult.inputBitmap().getWidth(); int width = handsResult.inputBitmap().getWidth();
int height = handsResult.inputBitmap().getHeight(); int height = handsResult.inputBitmap().getHeight();
NormalizedLandmark wristLandmark = Hands.getHandLandmark( NormalizedLandmark wristLandmark =
handsResult, 0, HandLandmark.WRIST); handsResult.multiHandLandmarks().get(0).getLandmarkList().get(HandLandmark.WRIST);
Log.i( Log.i(
TAG, TAG,
String.format( String.format(
@@ -492,9 +519,9 @@ ActivityResultLauncher<Intent> imageGetter =
} }
} }
}); });
Intent gallery = new Intent( Intent pickImageIntent = new Intent(Intent.ACTION_PICK);
Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI); pickImageIntent.setDataAndType(MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*");
imageGetter.launch(gallery); imageGetter.launch(pickImageIntent);
``` ```
#### Video Input #### Video Input
@@ -504,7 +531,7 @@ imageGetter.launch(gallery);
HandsOptions handsOptions = HandsOptions handsOptions =
HandsOptions.builder() HandsOptions.builder()
.setStaticImageMode(false) .setStaticImageMode(false)
.setMaxNumHands(1) .setMaxNumHands(2)
.setRunOnGpu(true).build(); .setRunOnGpu(true).build();
Hands hands = new Hands(this, handsOptions); Hands hands = new Hands(this, handsOptions);
hands.setErrorListener( hands.setErrorListener(
@@ -527,8 +554,11 @@ glSurfaceView.setRenderInputImage(true);
hands.setResultListener( hands.setResultListener(
handsResult -> { handsResult -> {
NormalizedLandmark wristLandmark = Hands.getHandLandmark( if (result.multiHandLandmarks().isEmpty()) {
handsResult, 0, HandLandmark.WRIST); return;
}
NormalizedLandmark wristLandmark =
handsResult.multiHandLandmarks().get(0).getLandmarkList().get(HandLandmark.WRIST);
Log.i( Log.i(
TAG, TAG,
String.format( String.format(
@@ -557,9 +587,9 @@ ActivityResultLauncher<Intent> videoGetter =
} }
} }
}); });
Intent gallery = Intent pickVideoIntent = new Intent(Intent.ACTION_PICK);
new Intent(Intent.ACTION_PICK, MediaStore.Video.Media.INTERNAL_CONTENT_URI); pickVideoIntent.setDataAndType(MediaStore.Video.Media.INTERNAL_CONTENT_URI, "video/*");
videoGetter.launch(gallery); videoGetter.launch(pickVideoIntent);
``` ```
## Example Apps ## Example Apps
+10 -1
View File
@@ -159,6 +159,11 @@ images to reduce jitter. Ignored if [enable_segmentation](#enable_segmentation)
is `false` or [static_image_mode](#static_image_mode) is `true`. Default to is `false` or [static_image_mode](#static_image_mode) is `true`. Default to
`true`. `true`.
#### refine_face_landmarks
Whether to further refine the landmark coordinates around the eyes and lips, and
output additional landmarks around the irises. Default to `false`.
#### min_detection_confidence #### min_detection_confidence
Minimum confidence value (`[0.0, 1.0]`) from the person-detection model for the Minimum confidence value (`[0.0, 1.0]`) from the person-detection model for the
@@ -241,6 +246,7 @@ Supported configuration options:
* [smooth_landmarks](#smooth_landmarks) * [smooth_landmarks](#smooth_landmarks)
* [enable_segmentation](#enable_segmentation) * [enable_segmentation](#enable_segmentation)
* [smooth_segmentation](#smooth_segmentation) * [smooth_segmentation](#smooth_segmentation)
* [refine_face_landmarks](#refine_face_landmarks)
* [min_detection_confidence](#min_detection_confidence) * [min_detection_confidence](#min_detection_confidence)
* [min_tracking_confidence](#min_tracking_confidence) * [min_tracking_confidence](#min_tracking_confidence)
@@ -256,7 +262,8 @@ IMAGE_FILES = []
with mp_holistic.Holistic( with mp_holistic.Holistic(
static_image_mode=True, static_image_mode=True,
model_complexity=2, model_complexity=2,
enable_segmentation=True) as holistic: enable_segmentation=True,
refine_face_landmarks=True) as holistic:
for idx, file in enumerate(IMAGE_FILES): for idx, file in enumerate(IMAGE_FILES):
image = cv2.imread(file) image = cv2.imread(file)
image_height, image_width, _ = image.shape image_height, image_width, _ = image.shape
@@ -350,6 +357,7 @@ Supported configuration options:
* [smoothLandmarks](#smooth_landmarks) * [smoothLandmarks](#smooth_landmarks)
* [enableSegmentation](#enable_segmentation) * [enableSegmentation](#enable_segmentation)
* [smoothSegmentation](#smooth_segmentation) * [smoothSegmentation](#smooth_segmentation)
* [refineFaceLandmarks](#refineFaceLandmarks)
* [minDetectionConfidence](#min_detection_confidence) * [minDetectionConfidence](#min_detection_confidence)
* [minTrackingConfidence](#min_tracking_confidence) * [minTrackingConfidence](#min_tracking_confidence)
@@ -421,6 +429,7 @@ holistic.setOptions({
smoothLandmarks: true, smoothLandmarks: true,
enableSegmentation: true, enableSegmentation: true,
smoothSegmentation: true, smoothSegmentation: true,
refineFaceLandmarks: true,
minDetectionConfidence: 0.5, minDetectionConfidence: 0.5,
minTrackingConfidence: 0.5 minTrackingConfidence: 0.5
}); });
+5 -4
View File
@@ -55,13 +55,14 @@ one over the other.
### [Hands](https://google.github.io/mediapipe/solutions/hands) ### [Hands](https://google.github.io/mediapipe/solutions/hands)
* Palm detection model: * Palm detection model:
[TFLite model](https://github.com/google/mediapipe/tree/master/mediapipe/modules/palm_detection/palm_detection.tflite), [TFLite model (lite)](https://github.com/google/mediapipe/tree/master/mediapipe/modules/palm_detection/palm_detection_lite.tflite),
[TFLite model (full)](https://github.com/google/mediapipe/tree/master/mediapipe/modules/palm_detection/palm_detection_full.tflite),
[TF.js model](https://tfhub.dev/mediapipe/handdetector/1) [TF.js model](https://tfhub.dev/mediapipe/handdetector/1)
* Hand landmark model: * Hand landmark model:
[TFLite model](https://github.com/google/mediapipe/tree/master/mediapipe/modules/hand_landmark/hand_landmark.tflite), [TFLite model (lite)](https://github.com/google/mediapipe/tree/master/mediapipe/modules/hand_landmark/hand_landmark_lite.tflite),
[TFLite model (sparse)](https://github.com/google/mediapipe/tree/master/mediapipe/modules/hand_landmark/hand_landmark_sparse.tflite), [TFLite model (full)](https://github.com/google/mediapipe/tree/master/mediapipe/modules/hand_landmark/hand_landmark_full.tflite),
[TF.js model](https://tfhub.dev/mediapipe/handskeleton/1) [TF.js model](https://tfhub.dev/mediapipe/handskeleton/1)
* [Model card](https://mediapipe.page.link/handmc), [Model card (sparse)](https://mediapipe.page.link/handmc-sparse) * [Model card](https://mediapipe.page.link/handmc)
### [Pose](https://google.github.io/mediapipe/solutions/pose) ### [Pose](https://google.github.io/mediapipe/solutions/pose)
+1 -1
View File
@@ -116,7 +116,7 @@ on how to build MediaPipe examples.
Note: The following runs TensorFlow inference on CPU. If you would like to Note: The following runs TensorFlow inference on CPU. If you would like to
run inference on GPU (Linux only), please follow run inference on GPU (Linux only), please follow
[TensorFlow CUDA Support and Setup on Linux Desktop](gpu.md#tensorflow-cuda-support-and-setup-on-linux-desktop) [TensorFlow CUDA Support and Setup on Linux Desktop](../getting_started/gpu_support.md#tensorflow-cuda-support-and-setup-on-linux-desktop)
instead. instead.
To build the TensorFlow CPU inference example on desktop, run: To build the TensorFlow CPU inference example on desktop, run:
+1 -1
View File
@@ -384,7 +384,7 @@ Supported configuration options:
<meta charset="utf-8"> <meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/control_utils_3d.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils_3d/control_utils_3d.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/objectron/objectron.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@mediapipe/objectron/objectron.js" crossorigin="anonymous"></script>
</head> </head>
+3 -2
View File
@@ -125,7 +125,7 @@ hip midpoints.
:----------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------: |
*Fig 3. Vitruvian man aligned via two virtual keypoints predicted by BlazePose detector in addition to the face bounding box.* | *Fig 3. Vitruvian man aligned via two virtual keypoints predicted by BlazePose detector in addition to the face bounding box.* |
### Pose Landmark Model (BlazePose GHUM 3D) ### Pose Landmark Model (BlazePose [GHUM](https://github.com/google-research/google-research/tree/master/ghum) 3D)
The landmark model in MediaPipe Pose predicts the location of 33 pose landmarks The landmark model in MediaPipe Pose predicts the location of 33 pose landmarks
(see figure below). (see figure below).
@@ -359,7 +359,7 @@ Supported configuration options:
<meta charset="utf-8"> <meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/control_utils_3d.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils_3d/control_utils_3d.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/pose/pose.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@mediapipe/pose/pose.js" crossorigin="anonymous"></script>
</head> </head>
@@ -486,6 +486,7 @@ on how to build MediaPipe examples.
[BlazePose: On-device Real-time Body Pose Tracking](https://arxiv.org/abs/2006.10204) [BlazePose: On-device Real-time Body Pose Tracking](https://arxiv.org/abs/2006.10204)
([presentation](https://youtu.be/YPpUOTRn5tA)) ([presentation](https://youtu.be/YPpUOTRn5tA))
* [Models and model cards](./models.md#pose) * [Models and model cards](./models.md#pose)
* [GHUM & GHUML: Generative 3D Human Shape and Articulated Pose Models](https://github.com/google-research/google-research/tree/master/ghum)
* [Web demo](https://code.mediapipe.dev/codepen/pose) * [Web demo](https://code.mediapipe.dev/codepen/pose)
* [Python Colab](https://mediapipe.page.link/pose_py_colab) * [Python Colab](https://mediapipe.page.link/pose_py_colab)
+10 -9
View File
@@ -258,13 +258,14 @@ Many of the following settings are advanced and not recommended for general
usage. Consult [Enabling tracing and profiling](#enabling-tracing-and-profiling) usage. Consult [Enabling tracing and profiling](#enabling-tracing-and-profiling)
for a friendlier introduction. for a friendlier introduction.
histogram_interval_size_usec :Specifies the size of the runtimes histogram histogram_interval_size_usec
intervals (in microseconds) to generate the histogram of the Process() time. The : Specifies the size of the runtimes histogram intervals (in microseconds) to
last interval extends to +inf. If not specified, the interval is 1000000 usec = generate the histogram of the `Process()` time. The last interval extends to
1 sec. +inf. If not specified, the interval is 1000000 usec = 1 sec.
num_histogram_intervals :Specifies the number of intervals to generate the num_histogram_intervals
histogram of the `Process()` runtime. If not specified, one interval is used. : Specifies the number of intervals to generate the histogram of the
`Process()` runtime. If not specified, one interval is used.
enable_profiler enable_profiler
: If true, the profiler starts profiling when graph is initialized. : If true, the profiler starts profiling when graph is initialized.
@@ -288,7 +289,7 @@ trace_event_types_disabled
trace_log_path trace_log_path
: The output directory and base-name prefix for trace log files. Log files are : The output directory and base-name prefix for trace log files. Log files are
written to: StrCat(trace_log_path, index, "`.binarypb`") written to: `StrCat(trace_log_path, index, ".binarypb")`
trace_log_count trace_log_count
: The number of trace log files retained. The trace log files are named : The number of trace log files retained. The trace log files are named
@@ -310,8 +311,8 @@ trace_log_instant_events
trace_log_interval_count trace_log_interval_count
: The number of trace log intervals per file. The total log duration is: : The number of trace log intervals per file. The total log duration is:
`trace_log_interval_usec * trace_log_file_count * trace_log_interval_count`. `trace_log_interval_usec * trace_log_count * trace_log_interval_count`. The
The default value specifies 10 intervals per file. default value specifies 10 intervals per file.
trace_log_disabled trace_log_disabled
: An option to turn ON/OFF writing trace files to disk. Saving trace files to : An option to turn ON/OFF writing trace files to disk. Saving trace files to
+10
View File
@@ -75,6 +75,7 @@ alias(
actual = select({ actual = select({
":macos_i386": ":macos_i386", ":macos_i386": ":macos_i386",
":macos_x86_64": ":macos_x86_64", ":macos_x86_64": ":macos_x86_64",
":macos_arm64": ":macos_arm64",
"//conditions:default": ":macos_i386", # Arbitrarily chosen from above. "//conditions:default": ":macos_i386", # Arbitrarily chosen from above.
}), }),
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
@@ -119,6 +120,15 @@ config_setting(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )
config_setting(
name = "macos_arm64",
values = {
"apple_platform_type": "macos",
"cpu": "darwin_arm64",
},
visibility = ["//visibility:public"],
)
[ [
config_setting( config_setting(
name = arch, name = arch,
+50 -11
View File
@@ -117,6 +117,7 @@ mediapipe_proto_library(
"//mediapipe/framework:calculator_options_proto", "//mediapipe/framework:calculator_options_proto",
"//mediapipe/framework:calculator_proto", "//mediapipe/framework:calculator_proto",
"//mediapipe/framework/formats:classification_proto", "//mediapipe/framework/formats:classification_proto",
"//mediapipe/framework/formats:landmark_proto",
], ],
) )
@@ -213,6 +214,7 @@ cc_library(
"//mediapipe/framework:collection_item_id", "//mediapipe/framework:collection_item_id",
"//mediapipe/framework:packet", "//mediapipe/framework:packet",
"//mediapipe/framework/formats:classification_cc_proto", "//mediapipe/framework/formats:classification_cc_proto",
"//mediapipe/framework/formats:detection_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:integral_types",
@@ -309,8 +311,8 @@ cc_library(
) )
cc_library( cc_library(
name = "concatenate_normalized_landmark_list_calculator", name = "concatenate_proto_list_calculator",
srcs = ["concatenate_normalized_landmark_list_calculator.cc"], srcs = ["concatenate_proto_list_calculator.cc"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
":concatenate_vector_calculator_cc_proto", ":concatenate_vector_calculator_cc_proto",
@@ -324,10 +326,10 @@ cc_library(
) )
cc_test( cc_test(
name = "concatenate_normalized_landmark_list_calculator_test", name = "concatenate_proto_list_calculator_test",
srcs = ["concatenate_normalized_landmark_list_calculator_test.cc"], srcs = ["concatenate_proto_list_calculator_test.cc"],
deps = [ deps = [
":concatenate_normalized_landmark_list_calculator", ":concatenate_proto_list_calculator",
":concatenate_vector_calculator_cc_proto", ":concatenate_vector_calculator_cc_proto",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework:calculator_runner", "//mediapipe/framework:calculator_runner",
@@ -531,9 +533,13 @@ cc_test(
":split_vector_calculator", ":split_vector_calculator",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework:calculator_runner", "//mediapipe/framework:calculator_runner",
"//mediapipe/framework/api2:node",
"//mediapipe/framework/api2:port",
"//mediapipe/framework/port:gtest_main", "//mediapipe/framework/port:gtest_main",
"//mediapipe/framework/port:parse_text_proto", "//mediapipe/framework/port:parse_text_proto",
"//mediapipe/framework/port:status", "//mediapipe/framework/port:status",
"@com_google_absl//absl/status",
"@com_google_absl//absl/types:optional",
], ],
) )
@@ -960,8 +966,8 @@ cc_test(
) )
cc_library( cc_library(
name = "split_landmarks_calculator", name = "split_proto_list_calculator",
srcs = ["split_landmarks_calculator.cc"], srcs = ["split_proto_list_calculator.cc"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
":split_vector_calculator_cc_proto", ":split_vector_calculator_cc_proto",
@@ -975,10 +981,10 @@ cc_library(
) )
cc_test( cc_test(
name = "split_landmarks_calculator_test", name = "split_proto_list_calculator_test",
srcs = ["split_landmarks_calculator_test.cc"], srcs = ["split_proto_list_calculator_test.cc"],
deps = [ deps = [
":split_landmarks_calculator", ":split_proto_list_calculator",
":split_vector_calculator_cc_proto", ":split_vector_calculator_cc_proto",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework:calculator_runner", "//mediapipe/framework:calculator_runner",
@@ -1191,6 +1197,7 @@ cc_library(
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework:collection_item_id", "//mediapipe/framework:collection_item_id",
"//mediapipe/framework/formats:classification_cc_proto", "//mediapipe/framework/formats:classification_cc_proto",
"//mediapipe/framework/formats:landmark_cc_proto",
"//mediapipe/framework/port:integral_types", "//mediapipe/framework/port:integral_types",
"//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:ret_check",
"//mediapipe/framework/port:status", "//mediapipe/framework/port:status",
@@ -1238,7 +1245,6 @@ cc_test(
"//mediapipe/framework:calculator_profile_cc_proto", "//mediapipe/framework:calculator_profile_cc_proto",
"//mediapipe/framework:test_calculators", "//mediapipe/framework:test_calculators",
"//mediapipe/framework/deps:clock", "//mediapipe/framework/deps:clock",
"//mediapipe/framework/deps:message_matchers",
"//mediapipe/framework/port:core_proto", "//mediapipe/framework/port:core_proto",
"//mediapipe/framework/port:gtest_main", "//mediapipe/framework/port:gtest_main",
"//mediapipe/framework/port:integral_types", "//mediapipe/framework/port:integral_types",
@@ -1252,3 +1258,36 @@ cc_test(
"@com_google_absl//absl/time", "@com_google_absl//absl/time",
], ],
) )
cc_library(
name = "get_vector_item_calculator",
srcs = ["get_vector_item_calculator.cc"],
hdrs = ["get_vector_item_calculator.h"],
visibility = ["//visibility:public"],
deps = [
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework:packet",
"//mediapipe/framework/api2:node",
"//mediapipe/framework/api2:port",
"//mediapipe/framework/formats:classification_cc_proto",
"//mediapipe/framework/formats:landmark_cc_proto",
"//mediapipe/framework/port:ret_check",
"//mediapipe/framework/port:status",
],
alwayslink = 1,
)
cc_library(
name = "vector_size_calculator",
srcs = ["vector_size_calculator.cc"],
hdrs = ["vector_size_calculator.h"],
visibility = ["//visibility:public"],
deps = [
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework/api2:node",
"//mediapipe/framework/formats:classification_cc_proto",
"//mediapipe/framework/formats:landmark_cc_proto",
"//mediapipe/framework/port:status",
],
alwayslink = 1,
)
@@ -28,6 +28,10 @@ typedef BeginLoopCalculator<std::vector<::mediapipe::NormalizedLandmarkList>>
BeginLoopNormalizedLandmarkListVectorCalculator; BeginLoopNormalizedLandmarkListVectorCalculator;
REGISTER_CALCULATOR(BeginLoopNormalizedLandmarkListVectorCalculator); REGISTER_CALCULATOR(BeginLoopNormalizedLandmarkListVectorCalculator);
// A calculator to process std::vector<int>.
typedef BeginLoopCalculator<std::vector<int>> BeginLoopIntCalculator;
REGISTER_CALCULATOR(BeginLoopIntCalculator);
// A calculator to process std::vector<NormalizedRect>. // A calculator to process std::vector<NormalizedRect>.
typedef BeginLoopCalculator<std::vector<::mediapipe::NormalizedRect>> typedef BeginLoopCalculator<std::vector<::mediapipe::NormalizedRect>>
BeginLoopNormalizedRectCalculator; BeginLoopNormalizedRectCalculator;
@@ -47,4 +51,8 @@ typedef BeginLoopCalculator<std::vector<std::vector<Matrix>>>
BeginLoopMatrixVectorCalculator; BeginLoopMatrixVectorCalculator;
REGISTER_CALCULATOR(BeginLoopMatrixVectorCalculator); REGISTER_CALCULATOR(BeginLoopMatrixVectorCalculator);
// A calculator to process std::vector<uint64_t>.
typedef BeginLoopCalculator<std::vector<uint64_t>> BeginLoopUint64tCalculator;
REGISTER_CALCULATOR(BeginLoopUint64tCalculator);
} // namespace mediapipe } // namespace mediapipe
@@ -1,79 +0,0 @@
// 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/api2/node.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 {
namespace api2 {
// Concatenates several NormalizedLandmarkList protos following stream index
// order. This class assumes that every input stream contains a
// NormalizedLandmarkList proto object.
class ConcatenateNormalizedLandmarkListCalculator : public Node {
public:
static constexpr Input<NormalizedLandmarkList>::Multiple kIn{""};
static constexpr Output<NormalizedLandmarkList> kOut{""};
MEDIAPIPE_NODE_CONTRACT(kIn, kOut);
static absl::Status UpdateContract(CalculatorContract* cc) {
RET_CHECK_GE(kIn(cc).Count(), 1);
return absl::OkStatus();
}
absl::Status Open(CalculatorContext* cc) override {
only_emit_if_all_present_ =
cc->Options<::mediapipe::ConcatenateVectorCalculatorOptions>()
.only_emit_if_all_present();
return absl::OkStatus();
}
absl::Status Process(CalculatorContext* cc) override {
if (only_emit_if_all_present_) {
for (const auto& input : kIn(cc)) {
if (input.IsEmpty()) return absl::OkStatus();
}
}
NormalizedLandmarkList output;
for (const auto& input : kIn(cc)) {
if (input.IsEmpty()) continue;
const NormalizedLandmarkList& list = *input;
for (int j = 0; j < list.landmark_size(); ++j) {
*output.add_landmark() = list.landmark(j);
}
}
kOut(cc).Send(std::move(output));
return absl::OkStatus();
}
private:
bool only_emit_if_all_present_;
};
MEDIAPIPE_REGISTER_NODE(ConcatenateNormalizedLandmarkListCalculator);
} // namespace api2
} // namespace mediapipe
// NOLINTNEXTLINE
#endif // MEDIAPIPE_CALCULATORS_CORE_CONCATENATE_NORMALIZED_LIST_CALCULATOR_H_
@@ -0,0 +1,118 @@
// 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_PROTO_LIST_CALCULATOR_H_ // NOLINT
#define MEDIAPIPE_CALCULATORS_CORE_CONCATENATE_PROTO_LIST_CALCULATOR_H_ // NOLINT
#include "mediapipe/calculators/core/concatenate_vector_calculator.pb.h"
#include "mediapipe/framework/api2/node.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 {
namespace api2 {
// Concatenate several input packets of ListType with a repeated field of
// ItemType into a single output packet of ListType following stream index
// order.
template <typename ItemType, typename ListType>
class ConcatenateListsCalculator : public Node {
public:
static constexpr typename Input<ListType>::Multiple kIn{""};
static constexpr Output<ListType> kOut{""};
MEDIAPIPE_NODE_CONTRACT(kIn, kOut);
static absl::Status UpdateContract(CalculatorContract* cc) {
RET_CHECK_GE(kIn(cc).Count(), 1);
return absl::OkStatus();
}
absl::Status Open(CalculatorContext* cc) override {
only_emit_if_all_present_ =
cc->Options<::mediapipe::ConcatenateVectorCalculatorOptions>()
.only_emit_if_all_present();
return absl::OkStatus();
}
absl::Status Process(CalculatorContext* cc) override {
if (only_emit_if_all_present_) {
for (const auto& input : kIn(cc)) {
if (input.IsEmpty()) return absl::OkStatus();
}
}
ListType output;
for (const auto& input : kIn(cc)) {
if (input.IsEmpty()) continue;
const ListType& list = *input;
for (int j = 0; j < ListSize(list); ++j) {
*AddItem(output) = GetItem(list, j);
}
}
kOut(cc).Send(std::move(output));
return absl::OkStatus();
}
protected:
virtual int ListSize(const ListType& list) const = 0;
virtual const ItemType GetItem(const ListType& list, int idx) const = 0;
virtual ItemType* AddItem(ListType& list) const = 0;
private:
bool only_emit_if_all_present_;
};
// TODO: Move calculators to separate *.cc files
class ConcatenateNormalizedLandmarkListCalculator
: public ConcatenateListsCalculator<NormalizedLandmark,
NormalizedLandmarkList> {
protected:
int ListSize(const NormalizedLandmarkList& list) const override {
return list.landmark_size();
}
const NormalizedLandmark GetItem(const NormalizedLandmarkList& list,
int idx) const override {
return list.landmark(idx);
}
NormalizedLandmark* AddItem(NormalizedLandmarkList& list) const override {
return list.add_landmark();
}
};
MEDIAPIPE_REGISTER_NODE(ConcatenateNormalizedLandmarkListCalculator);
class ConcatenateLandmarkListCalculator
: public ConcatenateListsCalculator<Landmark, LandmarkList> {
protected:
int ListSize(const LandmarkList& list) const override {
return list.landmark_size();
}
const Landmark GetItem(const LandmarkList& list, int idx) const override {
return list.landmark(idx);
}
Landmark* AddItem(LandmarkList& list) const override {
return list.add_landmark();
}
};
MEDIAPIPE_REGISTER_NODE(ConcatenateLandmarkListCalculator);
} // namespace api2
} // namespace mediapipe
// NOLINTNEXTLINE
#endif // MEDIAPIPE_CALCULATORS_CORE_CONCATENATE_PROTO_LIST_CALCULATOR_H_
@@ -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/formats/classification.pb.h" #include "mediapipe/framework/formats/classification.pb.h"
#include "mediapipe/framework/formats/landmark.pb.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/integral_types.h"
#include "mediapipe/framework/port/ret_check.h" #include "mediapipe/framework/port/ret_check.h"
@@ -79,6 +80,8 @@ class ConstantSidePacketCalculator : public CalculatorBase {
packet.Set<uint64>(); packet.Set<uint64>();
} else if (packet_options.has_classification_list_value()) { } else if (packet_options.has_classification_list_value()) {
packet.Set<ClassificationList>(); packet.Set<ClassificationList>();
} else if (packet_options.has_landmark_list_value()) {
packet.Set<LandmarkList>();
} else { } else {
return absl::InvalidArgumentError( return absl::InvalidArgumentError(
"None of supported values were specified in options."); "None of supported values were specified in options.");
@@ -108,6 +111,9 @@ class ConstantSidePacketCalculator : public CalculatorBase {
} else if (packet_options.has_classification_list_value()) { } else if (packet_options.has_classification_list_value()) {
packet.Set(MakePacket<ClassificationList>( packet.Set(MakePacket<ClassificationList>(
packet_options.classification_list_value())); packet_options.classification_list_value()));
} else if (packet_options.has_landmark_list_value()) {
packet.Set(
MakePacket<LandmarkList>(packet_options.landmark_list_value()));
} else { } else {
return absl::InvalidArgumentError( return absl::InvalidArgumentError(
"None of supported values were specified in options."); "None of supported values were specified in options.");
@@ -18,6 +18,7 @@ package mediapipe;
import "mediapipe/framework/calculator.proto"; import "mediapipe/framework/calculator.proto";
import "mediapipe/framework/formats/classification.proto"; import "mediapipe/framework/formats/classification.proto";
import "mediapipe/framework/formats/landmark.proto";
option objc_class_prefix = "MediaPipe"; option objc_class_prefix = "MediaPipe";
@@ -34,6 +35,7 @@ message ConstantSidePacketCalculatorOptions {
string string_value = 4; string string_value = 4;
uint64 uint64_value = 5; uint64 uint64_value = 5;
ClassificationList classification_list_value = 6; ClassificationList classification_list_value = 6;
LandmarkList landmark_list_value = 7;
} }
} }
@@ -17,6 +17,7 @@
#include <vector> #include <vector>
#include "mediapipe/framework/formats/classification.pb.h" #include "mediapipe/framework/formats/classification.pb.h"
#include "mediapipe/framework/formats/detection.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/util/render_data.pb.h" #include "mediapipe/util/render_data.pb.h"
@@ -50,4 +51,8 @@ REGISTER_CALCULATOR(EndLoopClassificationListCalculator);
typedef EndLoopCalculator<std::vector<TfLiteTensor>> EndLoopTensorCalculator; typedef EndLoopCalculator<std::vector<TfLiteTensor>> EndLoopTensorCalculator;
REGISTER_CALCULATOR(EndLoopTensorCalculator); REGISTER_CALCULATOR(EndLoopTensorCalculator);
typedef EndLoopCalculator<std::vector<::mediapipe::Detection>>
EndLoopDetectionCalculator;
REGISTER_CALCULATOR(EndLoopDetectionCalculator);
} // namespace mediapipe } // namespace mediapipe
@@ -0,0 +1,32 @@
// Copyright 2022 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/calculators/core/get_vector_item_calculator.h"
#include "mediapipe/framework/formats/classification.pb.h"
#include "mediapipe/framework/formats/landmark.pb.h"
namespace mediapipe {
namespace api2 {
using GetLandmarkListVectorItemCalculator =
GetVectorItemCalculator<mediapipe::LandmarkList>;
REGISTER_CALCULATOR(GetLandmarkListVectorItemCalculator);
using GetClassificationListVectorItemCalculator =
GetVectorItemCalculator<mediapipe::ClassificationList>;
REGISTER_CALCULATOR(GetClassificationListVectorItemCalculator);
} // namespace api2
} // namespace mediapipe
@@ -0,0 +1,77 @@
// 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_GET_VECTOR_ITEM_CALCULATOR_H_
#define MEDIAPIPE_CALCULATORS_CORE_GET_VECTOR_ITEM_CALCULATOR_H_
#include <optional>
#include "mediapipe/framework/api2/node.h"
#include "mediapipe/framework/api2/port.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/packet.h"
#include "mediapipe/framework/port/ret_check.h"
#include "mediapipe/framework/port/status.h"
namespace mediapipe {
namespace api2 {
// A calcutlator to return an item from the vector by its index.
//
// Inputs:
// VECTOR - std::vector<T>
// Vector to take an item from.
// INDEX - int
// Index of the item to return.
//
// Outputs:
// ITEM - T
// Item from the vector at given index.
//
// Example config:
// node {
// calculator: "Get{SpecificType}VectorItemCalculator"
// input_stream: "VECTOR:vector"
// input_stream: "INDEX:index"
// input_stream: "ITEM:item"
// }
//
template <typename T>
class GetVectorItemCalculator : public Node {
public:
static constexpr Input<std::vector<T>> kIn{"VECTOR"};
static constexpr Input<int> kIdx{"INDEX"};
static constexpr Output<T> kOut{"ITEM"};
MEDIAPIPE_NODE_CONTRACT(kIn, kIdx, kOut);
absl::Status Process(CalculatorContext* cc) final {
if (kIn(cc).IsEmpty() || kIdx(cc).IsEmpty()) {
return absl::OkStatus();
}
const std::vector<T>& items = kIn(cc).Get();
const int idx = kIdx(cc).Get();
RET_CHECK_LT(idx, items.size());
kOut(cc).Send(items[idx]);
return absl::OkStatus();
}
};
} // namespace api2
} // namespace mediapipe
#endif // MEDIAPIPE_CALCULATORS_CORE_GET_VECTOR_ITEM_CALCULATOR_H_
@@ -29,6 +29,11 @@ namespace api2 {
// This calculator periodically copies the GraphProfile from // This calculator periodically copies the GraphProfile from
// mediapipe::GraphProfiler::CaptureProfile to the "PROFILE" output stream. // mediapipe::GraphProfiler::CaptureProfile to the "PROFILE" output stream.
// //
// Similarly to the log files saved by GraphProfiler::WriteProfile when trace
// logging is enabled, the first captured profile contains the full
// canonicalized graph config and, if tracing is enabled, calculator names in
// graph traces. Subsequent profiles omit this information.
//
// Example config: // Example config:
// node { // node {
// calculator: "GraphProfileCalculator" // calculator: "GraphProfileCalculator"
@@ -50,11 +55,14 @@ class GraphProfileCalculator : public Node {
absl::Status Process(CalculatorContext* cc) final { absl::Status Process(CalculatorContext* cc) final {
auto options = cc->Options<::mediapipe::GraphProfileCalculatorOptions>(); auto options = cc->Options<::mediapipe::GraphProfileCalculatorOptions>();
if (prev_profile_ts_ == Timestamp::Unset() || bool first_profile = prev_profile_ts_ == Timestamp::Unset();
if (first_profile ||
cc->InputTimestamp() - prev_profile_ts_ >= options.profile_interval()) { cc->InputTimestamp() - prev_profile_ts_ >= options.profile_interval()) {
prev_profile_ts_ = cc->InputTimestamp(); prev_profile_ts_ = cc->InputTimestamp();
GraphProfile result; GraphProfile result;
MP_RETURN_IF_ERROR(cc->GetProfilingContext()->CaptureProfile(&result)); MP_RETURN_IF_ERROR(cc->GetProfilingContext()->CaptureProfile(
&result, first_profile ? PopulateGraphConfig::kFull
: PopulateGraphConfig::kNo));
kProfileOut(cc).Send(result); kProfileOut(cc).Send(result);
} }
return absl::OkStatus(); return absl::OkStatus();
@@ -23,7 +23,6 @@
#include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/calculator_profile.pb.h" #include "mediapipe/framework/calculator_profile.pb.h"
#include "mediapipe/framework/deps/clock.h" #include "mediapipe/framework/deps/clock.h"
#include "mediapipe/framework/deps/message_matchers.h"
#include "mediapipe/framework/port/gmock.h" #include "mediapipe/framework/port/gmock.h"
#include "mediapipe/framework/port/gtest.h" #include "mediapipe/framework/port/gtest.h"
#include "mediapipe/framework/port/integral_types.h" #include "mediapipe/framework/port/integral_types.h"
@@ -203,6 +202,8 @@ TEST_F(GraphProfileCalculatorTest, GraphProfile) {
} }
})pb"); })pb");
ASSERT_EQ(output_packets.size(), 2);
EXPECT_TRUE(output_packets[0].Get<GraphProfile>().has_config());
EXPECT_THAT(output_packets[1].Get<GraphProfile>(), EXPECT_THAT(output_packets[1].Get<GraphProfile>(),
mediapipe::EqualsProto(expected_profile)); mediapipe::EqualsProto(expected_profile));
} }
@@ -14,7 +14,11 @@
#include <memory> #include <memory>
#include "absl/status/status.h"
#include "absl/types/optional.h"
#include "mediapipe/calculators/core/split_vector_calculator.h" #include "mediapipe/calculators/core/split_vector_calculator.h"
#include "mediapipe/framework/api2/node.h"
#include "mediapipe/framework/api2/port.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"
#include "mediapipe/framework/port/gtest.h" #include "mediapipe/framework/port/gtest.h"
@@ -301,4 +305,99 @@ TEST(MuxCalculatorTest, DiscardSkippedInputs_MuxInputStreamHandler) {
} }
} // namespace } // namespace
class PassThroughAndTsBoundUpdateNode : public mediapipe::api2::Node {
public:
static constexpr mediapipe::api2::Input<int> kInValue{"VALUE"};
static constexpr mediapipe::api2::Output<int> kOutValue{"VALUE"};
static constexpr mediapipe::api2::Output<int> kOutTsBoundUpdate{
"TS_BOUND_UPDATE"};
MEDIAPIPE_NODE_CONTRACT(kInValue, kOutValue, kOutTsBoundUpdate);
absl::Status Process(CalculatorContext* cc) override {
kOutValue(cc).Send(kInValue(cc));
kOutTsBoundUpdate(cc).SetNextTimestampBound(
cc->InputTimestamp().NextAllowedInStream());
return absl::OkStatus();
}
};
MEDIAPIPE_REGISTER_NODE(PassThroughAndTsBoundUpdateNode);
class ToOptionalNode : public mediapipe::api2::Node {
public:
static constexpr mediapipe::api2::Input<int> kTick{"TICK"};
static constexpr mediapipe::api2::Input<int> kInValue{"VALUE"};
static constexpr mediapipe::api2::Output<absl::optional<int>> kOutValue{
"OUTPUT"};
MEDIAPIPE_NODE_CONTRACT(kTick, kInValue, kOutValue);
absl::Status Process(CalculatorContext* cc) override {
if (kInValue(cc).IsEmpty()) {
kOutValue(cc).Send(absl::nullopt);
} else {
kOutValue(cc).Send({kInValue(cc).Get()});
}
return absl::OkStatus();
}
};
MEDIAPIPE_REGISTER_NODE(ToOptionalNode);
namespace {
TEST(MuxCalculatorTest, HandleTimestampBoundUpdates) {
CalculatorGraphConfig config =
mediapipe::ParseTextProtoOrDie<CalculatorGraphConfig>(
R"pb(
input_stream: "select"
node {
calculator: "PassThroughAndTsBoundUpdateNode"
input_stream: "VALUE:select"
output_stream: "VALUE:select_ps"
output_stream: "TS_BOUND_UPDATE:ts_bound_update"
}
node {
calculator: "MuxCalculator"
input_stream: "INPUT:0:select_ps"
input_stream: "INPUT:1:ts_bound_update"
input_stream: "SELECT:select"
output_stream: "OUTPUT:select_or_ts_bound_update"
}
node {
calculator: "ToOptionalNode"
input_stream: "TICK:select"
input_stream: "VALUE:select_or_ts_bound_update"
output_stream: "OUTPUT:output"
}
)pb");
std::vector<Packet> output_packets;
tool::AddVectorSink("output", &config, &output_packets);
CalculatorGraph graph;
MP_ASSERT_OK(graph.Initialize(config));
MP_ASSERT_OK(graph.StartRun({}));
auto send_value_fn = [&](int value, Timestamp ts) -> absl::Status {
MP_RETURN_IF_ERROR(
graph.AddPacketToInputStream("select", MakePacket<int>(value).At(ts)));
return graph.WaitUntilIdle();
};
MP_ASSERT_OK(send_value_fn(0, Timestamp(1)));
ASSERT_EQ(output_packets.size(), 1);
EXPECT_EQ(output_packets[0].Get<absl::optional<int>>(), 0);
MP_ASSERT_OK(send_value_fn(1, Timestamp(2)));
ASSERT_EQ(output_packets.size(), 2);
EXPECT_EQ(output_packets[1].Get<absl::optional<int>>(), absl::nullopt);
MP_ASSERT_OK(send_value_fn(0, Timestamp(3)));
ASSERT_EQ(output_packets.size(), 3);
EXPECT_EQ(output_packets[2].Get<absl::optional<int>>(), 0);
MP_ASSERT_OK(graph.CloseAllInputStreams());
MP_ASSERT_OK(graph.WaitUntilDone());
}
} // namespace
} // namespace mediapipe } // namespace mediapipe
@@ -60,7 +60,10 @@ class PacketClonerCalculator : public CalculatorBase {
const auto calculator_options = const auto calculator_options =
cc->Options<mediapipe::PacketClonerCalculatorOptions>(); cc->Options<mediapipe::PacketClonerCalculatorOptions>();
output_only_when_all_inputs_received_ = output_only_when_all_inputs_received_ =
calculator_options.output_only_when_all_inputs_received(); calculator_options.output_only_when_all_inputs_received() ||
calculator_options.output_packets_only_when_all_inputs_received();
output_empty_packets_before_all_inputs_received_ =
calculator_options.output_packets_only_when_all_inputs_received();
// Parse input streams. // Parse input streams.
tick_signal_index_ = cc->Inputs().NumEntries() - 1; tick_signal_index_ = cc->Inputs().NumEntries() - 1;
@@ -88,6 +91,9 @@ class PacketClonerCalculator : public CalculatorBase {
// Return if one of the input is null. // Return if one of the input is null.
for (int i = 0; i < tick_signal_index_; ++i) { for (int i = 0; i < tick_signal_index_; ++i) {
if (current_[i].IsEmpty()) { if (current_[i].IsEmpty()) {
if (output_empty_packets_before_all_inputs_received_) {
SetAllNextTimestampBounds(cc);
}
return absl::OkStatus(); return absl::OkStatus();
} }
} }
@@ -107,9 +113,17 @@ class PacketClonerCalculator : public CalculatorBase {
} }
private: private:
void SetAllNextTimestampBounds(CalculatorContext* cc) {
for (int j = 0; j < tick_signal_index_; ++j) {
cc->Outputs().Index(j).SetNextTimestampBound(
cc->InputTimestamp().NextAllowedInStream());
}
}
std::vector<Packet> current_; std::vector<Packet> current_;
int tick_signal_index_; int tick_signal_index_;
bool output_only_when_all_inputs_received_; bool output_only_when_all_inputs_received_;
bool output_empty_packets_before_all_inputs_received_;
}; };
REGISTER_CALCULATOR(PacketClonerCalculator); REGISTER_CALCULATOR(PacketClonerCalculator);
@@ -28,4 +28,9 @@ message PacketClonerCalculatorOptions {
// When true, this calculator will drop received TICK packets if any input // When true, this calculator will drop received TICK packets if any input
// stream hasn't received a packet yet. // stream hasn't received a packet yet.
optional bool output_only_when_all_inputs_received = 1 [default = false]; optional bool output_only_when_all_inputs_received = 1 [default = false];
// Similar with above, but also transmit empty packet for all streams before
// all inputs are received.
optional bool output_packets_only_when_all_inputs_received = 2
[default = false];
} }
@@ -23,8 +23,8 @@
#include "mediapipe/framework/port/canonical_errors.h" #include "mediapipe/framework/port/canonical_errors.h"
#include "mediapipe/framework/port/status.h" #include "mediapipe/framework/port/status.h"
// Quantizes a vector of floats to a std::string so that each float becomes a // Quantizes a vector of floats to a string so that each float becomes a byte
// byte in the [0, 255] range. Any value above max_quantized_value or below // in the [0, 255] range. Any value above max_quantized_value or below
// min_quantized_value will be saturated to '/xFF' or '/0'. // min_quantized_value will be saturated to '/xFF' or '/0'.
// //
// Example config: // Example config:
@@ -12,8 +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.
#ifndef MEDIAPIPE_CALCULATORS_CORE_SPLIT_LANDMARKS_CALCULATOR_H_ // NOLINT #ifndef MEDIAPIPE_CALCULATORS_CORE_SPLIT_PROTO_LIST_CALCULATOR_H_ // NOLINT
#define MEDIAPIPE_CALCULATORS_CORE_SPLIT_LANDMARKS_CALCULATOR_H_ // NOLINT #define MEDIAPIPE_CALCULATORS_CORE_SPLIT_PROTO_LIST_CALCULATOR_H_ // NOLINT
#include "mediapipe/calculators/core/split_vector_calculator.pb.h" #include "mediapipe/calculators/core/split_vector_calculator.pb.h"
#include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/calculator_framework.h"
@@ -24,30 +24,30 @@
namespace mediapipe { namespace mediapipe {
// Splits an input packet with LandmarkListType into // Splits an input packet of ListType with a repeated field of ItemType
// multiple LandmarkListType output packets using the [begin, end) ranges // into multiple ListType output packets using the [begin, end) ranges
// specified in SplitVectorCalculatorOptions. If the option "element_only" is // specified in SplitVectorCalculatorOptions. If the option "element_only" is
// set to true, all ranges should be of size 1 and all outputs will be elements // set to true, all ranges should be of size 1 and all outputs will be elements
// of type LandmarkType. If "element_only" is false, ranges can be // of type ItemType. If "element_only" is false, ranges can be
// non-zero in size and all outputs will be of type LandmarkListType. // non-zero in size and all outputs will be of type ListType.
// If the option "combine_outputs" is set to true, only one output stream can be // If the option "combine_outputs" is set to true, only one output stream can be
// specified and all ranges of elements will be combined into one // specified and all ranges of elements will be combined into one
// LandmarkListType. // ListType.
template <typename LandmarkType, typename LandmarkListType> template <typename ItemType, typename ListType>
class SplitLandmarksCalculator : public CalculatorBase { class SplitListsCalculator : public CalculatorBase {
public: public:
static absl::Status GetContract(CalculatorContract* cc) { static absl::Status GetContract(CalculatorContract* cc) {
RET_CHECK(cc->Inputs().NumEntries() == 1); RET_CHECK(cc->Inputs().NumEntries() == 1);
RET_CHECK(cc->Outputs().NumEntries() != 0); RET_CHECK(cc->Outputs().NumEntries() != 0);
cc->Inputs().Index(0).Set<LandmarkListType>(); cc->Inputs().Index(0).Set<ListType>();
const auto& options = const auto& options =
cc->Options<::mediapipe::SplitVectorCalculatorOptions>(); cc->Options<::mediapipe::SplitVectorCalculatorOptions>();
if (options.combine_outputs()) { if (options.combine_outputs()) {
RET_CHECK_EQ(cc->Outputs().NumEntries(), 1); RET_CHECK_EQ(cc->Outputs().NumEntries(), 1);
cc->Outputs().Index(0).Set<LandmarkListType>(); cc->Outputs().Index(0).Set<ListType>();
for (int i = 0; i < options.ranges_size() - 1; ++i) { for (int i = 0; i < options.ranges_size() - 1; ++i) {
for (int j = i + 1; j < options.ranges_size(); ++j) { for (int j = i + 1; j < options.ranges_size(); ++j) {
const auto& range_0 = options.ranges(i); const auto& range_0 = options.ranges(i);
@@ -82,9 +82,9 @@ class SplitLandmarksCalculator : public CalculatorBase {
return absl::InvalidArgumentError( return absl::InvalidArgumentError(
"Since element_only is true, all ranges should be of size 1."); "Since element_only is true, all ranges should be of size 1.");
} }
cc->Outputs().Index(i).Set<LandmarkType>(); cc->Outputs().Index(i).Set<ItemType>();
} else { } else {
cc->Outputs().Index(i).Set<LandmarkListType>(); cc->Outputs().Index(i).Set<ListType>();
} }
} }
} }
@@ -111,39 +111,38 @@ class SplitLandmarksCalculator : public CalculatorBase {
} }
absl::Status Process(CalculatorContext* cc) override { absl::Status Process(CalculatorContext* cc) override {
const LandmarkListType& input = const ListType& input = cc->Inputs().Index(0).Get<ListType>();
cc->Inputs().Index(0).Get<LandmarkListType>(); RET_CHECK_GE(ListSize(input), max_range_end_)
RET_CHECK_GE(input.landmark_size(), max_range_end_) << "Max range end " << max_range_end_ << " exceeds list size "
<< "Max range end " << max_range_end_ << " exceeds landmarks size " << ListSize(input);
<< input.landmark_size();
if (combine_outputs_) { if (combine_outputs_) {
LandmarkListType output; ListType output;
for (int i = 0; i < ranges_.size(); ++i) { for (int i = 0; i < ranges_.size(); ++i) {
for (int j = ranges_[i].first; j < ranges_[i].second; ++j) { for (int j = ranges_[i].first; j < ranges_[i].second; ++j) {
const LandmarkType& input_landmark = input.landmark(j); const ItemType& input_item = GetItem(input, j);
*output.add_landmark() = input_landmark; *AddItem(output) = input_item;
} }
} }
RET_CHECK_EQ(output.landmark_size(), total_elements_); RET_CHECK_EQ(ListSize(output), total_elements_);
cc->Outputs().Index(0).AddPacket( cc->Outputs().Index(0).AddPacket(
MakePacket<LandmarkListType>(output).At(cc->InputTimestamp())); MakePacket<ListType>(output).At(cc->InputTimestamp()));
} else { } else {
if (element_only_) { if (element_only_) {
for (int i = 0; i < ranges_.size(); ++i) { for (int i = 0; i < ranges_.size(); ++i) {
cc->Outputs().Index(i).AddPacket( cc->Outputs().Index(i).AddPacket(
MakePacket<LandmarkType>(input.landmark(ranges_[i].first)) MakePacket<ItemType>(GetItem(input, ranges_[i].first))
.At(cc->InputTimestamp())); .At(cc->InputTimestamp()));
} }
} else { } else {
for (int i = 0; i < ranges_.size(); ++i) { for (int i = 0; i < ranges_.size(); ++i) {
LandmarkListType output; ListType output;
for (int j = ranges_[i].first; j < ranges_[i].second; ++j) { for (int j = ranges_[i].first; j < ranges_[i].second; ++j) {
const LandmarkType& input_landmark = input.landmark(j); const ItemType& input_item = GetItem(input, j);
*output.add_landmark() = input_landmark; *AddItem(output) = input_item;
} }
cc->Outputs().Index(i).AddPacket( cc->Outputs().Index(i).AddPacket(
MakePacket<LandmarkListType>(output).At(cc->InputTimestamp())); MakePacket<ListType>(output).At(cc->InputTimestamp()));
} }
} }
} }
@@ -151,6 +150,11 @@ class SplitLandmarksCalculator : public CalculatorBase {
return absl::OkStatus(); return absl::OkStatus();
} }
protected:
virtual int ListSize(const ListType& list) const = 0;
virtual const ItemType GetItem(const ListType& list, int idx) const = 0;
virtual ItemType* AddItem(ListType& list) const = 0;
private: private:
std::vector<std::pair<int32, int32>> ranges_; std::vector<std::pair<int32, int32>> ranges_;
int32 max_range_end_ = -1; int32 max_range_end_ = -1;
@@ -159,15 +163,40 @@ class SplitLandmarksCalculator : public CalculatorBase {
bool combine_outputs_ = false; bool combine_outputs_ = false;
}; };
typedef SplitLandmarksCalculator<NormalizedLandmark, NormalizedLandmarkList> // TODO: Move calculators to separate *.cc files
SplitNormalizedLandmarkListCalculator;
class SplitNormalizedLandmarkListCalculator
: public SplitListsCalculator<NormalizedLandmark, NormalizedLandmarkList> {
protected:
int ListSize(const NormalizedLandmarkList& list) const override {
return list.landmark_size();
}
const NormalizedLandmark GetItem(const NormalizedLandmarkList& list,
int idx) const override {
return list.landmark(idx);
}
NormalizedLandmark* AddItem(NormalizedLandmarkList& list) const override {
return list.add_landmark();
}
};
REGISTER_CALCULATOR(SplitNormalizedLandmarkListCalculator); REGISTER_CALCULATOR(SplitNormalizedLandmarkListCalculator);
typedef SplitLandmarksCalculator<Landmark, LandmarkList> class SplitLandmarkListCalculator
SplitLandmarkListCalculator; : public SplitListsCalculator<Landmark, LandmarkList> {
protected:
int ListSize(const LandmarkList& list) const override {
return list.landmark_size();
}
const Landmark GetItem(const LandmarkList& list, int idx) const override {
return list.landmark(idx);
}
Landmark* AddItem(LandmarkList& list) const override {
return list.add_landmark();
}
};
REGISTER_CALCULATOR(SplitLandmarkListCalculator); REGISTER_CALCULATOR(SplitLandmarkListCalculator);
} // namespace mediapipe } // namespace mediapipe
// NOLINTNEXTLINE // NOLINTNEXTLINE
#endif // MEDIAPIPE_CALCULATORS_CORE_SPLIT_LANDMARKS_CALCULATOR_H_ #endif // MEDIAPIPE_CALCULATORS_CORE_SPLIT_PROTO_LIST_CALCULATOR_H_
@@ -83,4 +83,7 @@ REGISTER_CALCULATOR(SplitClassificationListVectorCalculator);
typedef SplitVectorCalculator<uint64_t, false> SplitUint64tVectorCalculator; typedef SplitVectorCalculator<uint64_t, false> SplitUint64tVectorCalculator;
REGISTER_CALCULATOR(SplitUint64tVectorCalculator); REGISTER_CALCULATOR(SplitUint64tVectorCalculator);
typedef SplitVectorCalculator<float, false> SplitFloatVectorCalculator;
REGISTER_CALCULATOR(SplitFloatVectorCalculator);
} // namespace mediapipe } // namespace mediapipe
@@ -24,7 +24,7 @@
namespace mediapipe { namespace mediapipe {
// Calculator that converts a std::string into an integer type, or fails if the // Calculator that converts a string into an integer type, or fails if the
// conversion is not possible. // conversion is not possible.
// //
// Example config: // Example config:
@@ -47,7 +47,7 @@ class StringToIntCalculatorTemplate : public CalculatorBase {
if (!absl::SimpleAtoi(cc->InputSidePackets().Index(0).Get<std::string>(), if (!absl::SimpleAtoi(cc->InputSidePackets().Index(0).Get<std::string>(),
&number)) { &number)) {
return absl::InvalidArgumentError( return absl::InvalidArgumentError(
"The std::string could not be parsed as an integer."); "The string could not be parsed as an integer.");
} }
cc->OutputSidePackets().Index(0).Set(MakePacket<IntType>(number)); cc->OutputSidePackets().Index(0).Set(MakePacket<IntType>(number));
return absl::OkStatus(); return absl::OkStatus();
@@ -0,0 +1,32 @@
// Copyright 2022 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/calculators/core/vector_size_calculator.h"
#include "mediapipe/framework/formats/classification.pb.h"
#include "mediapipe/framework/formats/landmark.pb.h"
namespace mediapipe {
namespace api2 {
using LandmarkListVectorSizeCalculator =
VectorSizeCalculator<mediapipe::LandmarkList>;
REGISTER_CALCULATOR(LandmarkListVectorSizeCalculator);
using ClassificationListVectorSizeCalculator =
VectorSizeCalculator<mediapipe::ClassificationList>;
REGISTER_CALCULATOR(ClassificationListVectorSizeCalculator);
} // namespace api2
} // namespace mediapipe
@@ -0,0 +1,64 @@
// Copyright 2022 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_VECTOR_SIZE_CALCULATOR_H_
#define MEDIAPIPE_CALCULATORS_CORE_VECTOR_SIZE_CALCULATOR_H_
#include <optional>
#include "mediapipe/framework/api2/node.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/port/status.h"
namespace mediapipe {
namespace api2 {
// A calcutlator to return vector size.
//
// Inputs:
// VECTOR - std::vector<T>
// Vector which size to return.
//
// Outputs:
// SIZE - int
// Size of the input vector.
//
// Example config:
// node {
// calculator: "{SpecificType}VectorSizeCalculator"
// input_stream: "VECTOR:vector"
// output_stream: "SIZE:vector_size"
// }
//
template <typename T>
class VectorSizeCalculator : public Node {
public:
static constexpr Input<std::vector<T>> kIn{"VECTOR"};
static constexpr Output<int> kOut{"SIZE"};
MEDIAPIPE_NODE_CONTRACT(kIn, kOut);
absl::Status Process(CalculatorContext* cc) final {
if (kIn(cc).IsEmpty()) {
return absl::OkStatus();
}
kOut(cc).Send(kIn(cc).Get().size());
return absl::OkStatus();
}
};
} // namespace api2
} // namespace mediapipe
#endif // MEDIAPIPE_CALCULATORS_CORE_VECTOR_SIZE_CALCULATOR_H_
+3
View File
@@ -239,10 +239,13 @@ cc_library(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
":image_transformation_calculator_cc_proto", ":image_transformation_calculator_cc_proto",
"//mediapipe/framework:packet",
"//mediapipe/framework:timestamp",
"//mediapipe/gpu:scale_mode_cc_proto", "//mediapipe/gpu:scale_mode_cc_proto",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:image_frame", "//mediapipe/framework/formats:image_frame",
"//mediapipe/framework/formats:image_frame_opencv", "//mediapipe/framework/formats:image_frame_opencv",
"//mediapipe/framework/formats:video_stream_header",
"//mediapipe/framework/port:opencv_core", "//mediapipe/framework/port:opencv_core",
"//mediapipe/framework/port:opencv_imgproc", "//mediapipe/framework/port:opencv_imgproc",
"//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:ret_check",
@@ -105,7 +105,7 @@ absl::StatusOr<ImageFileProperties> GetImageFileProperites(
} // namespace } // namespace
// Calculator to extract EXIF information from an image file. The input is // 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 // a string containing raw byte data from a file, and the output is an
// ImageFileProperties proto object with the relevant fields filled in. // 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 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 // the result as a stream or a side packet. The calculator checks that if an
@@ -16,10 +16,13 @@
#include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/formats/image_frame.h" #include "mediapipe/framework/formats/image_frame.h"
#include "mediapipe/framework/formats/image_frame_opencv.h" #include "mediapipe/framework/formats/image_frame_opencv.h"
#include "mediapipe/framework/formats/video_stream_header.h"
#include "mediapipe/framework/packet.h"
#include "mediapipe/framework/port/opencv_core_inc.h" #include "mediapipe/framework/port/opencv_core_inc.h"
#include "mediapipe/framework/port/opencv_imgproc_inc.h" #include "mediapipe/framework/port/opencv_imgproc_inc.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/timestamp.h"
#include "mediapipe/gpu/scale_mode.pb.h" #include "mediapipe/gpu/scale_mode.pb.h"
#if !MEDIAPIPE_DISABLE_GPU #if !MEDIAPIPE_DISABLE_GPU
@@ -45,10 +48,14 @@ namespace mediapipe {
#if !MEDIAPIPE_DISABLE_GPU #if !MEDIAPIPE_DISABLE_GPU
#endif // !MEDIAPIPE_DISABLE_GPU #endif // !MEDIAPIPE_DISABLE_GPU
#if defined(MEDIAPIPE_IOS)
#endif // defined(MEDIAPIPE_IOS)
namespace { namespace {
constexpr char kImageFrameTag[] = "IMAGE"; constexpr char kImageFrameTag[] = "IMAGE";
constexpr char kGpuBufferTag[] = "IMAGE_GPU"; constexpr char kGpuBufferTag[] = "IMAGE_GPU";
constexpr char kVideoPrestreamTag[] = "VIDEO_PRESTREAM";
int RotationModeToDegrees(mediapipe::RotationMode_Mode rotation) { int RotationModeToDegrees(mediapipe::RotationMode_Mode rotation) {
switch (rotation) { switch (rotation) {
@@ -119,6 +126,12 @@ mediapipe::ScaleMode_Mode ParseScaleMode(
// provided, it overrides the FLIP_VERTICALLY input side packet and/or // provided, it overrides the FLIP_VERTICALLY input side packet and/or
// corresponding field in the calculator options. // corresponding field in the calculator options.
// //
// VIDEO_PRESTREAM (optional): VideoHeader for the input ImageFrames, if
// rotating or scaling the frames, the header width and height will be updated
// appropriately. Note the header is updated only based on dimensions and
// rotations specified as side packets or options, input_stream
// transformations will not update the header.
//
// Output: // Output:
// One of the following tags: // One of the following tags:
// IMAGE - ImageFrame representing the output image. // IMAGE - ImageFrame representing the output image.
@@ -239,6 +252,21 @@ absl::Status ImageTransformationCalculator::GetContract(
cc->Inputs().Tag("FLIP_VERTICALLY").Set<bool>(); cc->Inputs().Tag("FLIP_VERTICALLY").Set<bool>();
} }
RET_CHECK(cc->Inputs().HasTag(kVideoPrestreamTag) ==
cc->Outputs().HasTag(kVideoPrestreamTag))
<< "If VIDEO_PRESTREAM is provided, it must be provided both as an "
"inputs and output stream.";
if (cc->Inputs().HasTag(kVideoPrestreamTag)) {
RET_CHECK(!(cc->Inputs().HasTag("OUTPUT_DIMENSIONS") ||
cc->Inputs().HasTag("ROTATION_DEGREES")))
<< "If specifying VIDEO_PRESTREAM, the transformations that affect the "
"dimensions of the frames (OUTPUT_DIMENSIONS and ROTATION_DEGREES) "
"need to be constant for every frame, meaning they can only be "
"provided in the calculator options or side packets.";
cc->Inputs().Tag(kVideoPrestreamTag).Set<mediapipe::VideoHeader>();
cc->Outputs().Tag(kVideoPrestreamTag).Set<mediapipe::VideoHeader>();
}
if (cc->InputSidePackets().HasTag("OUTPUT_DIMENSIONS")) { if (cc->InputSidePackets().HasTag("OUTPUT_DIMENSIONS")) {
cc->InputSidePackets().Tag("OUTPUT_DIMENSIONS").Set<DimensionsPacketType>(); cc->InputSidePackets().Tag("OUTPUT_DIMENSIONS").Set<DimensionsPacketType>();
} }
@@ -323,6 +351,24 @@ absl::Status ImageTransformationCalculator::Open(CalculatorContext* cc) {
} }
absl::Status ImageTransformationCalculator::Process(CalculatorContext* cc) { absl::Status ImageTransformationCalculator::Process(CalculatorContext* cc) {
// First update the video header if it is given, based on the rotation and
// dimensions specified as side packets or options. This will only be done
// once, so streaming transformation changes will not be reflected in
// the header.
if (cc->Inputs().HasTag(kVideoPrestreamTag) &&
!cc->Inputs().Tag(kVideoPrestreamTag).IsEmpty() &&
cc->Outputs().HasTag(kVideoPrestreamTag)) {
mediapipe::VideoHeader header =
cc->Inputs().Tag(kVideoPrestreamTag).Get<mediapipe::VideoHeader>();
// Update the header's width and height if needed.
ComputeOutputDimensions(header.width, header.height, &header.width,
&header.height);
cc->Outputs()
.Tag(kVideoPrestreamTag)
.AddPacket(mediapipe::MakePacket<mediapipe::VideoHeader>(header).At(
mediapipe::Timestamp::PreStream()));
}
// Override values if specified so. // Override values if specified so.
if (cc->Inputs().HasTag("ROTATION_DEGREES") && if (cc->Inputs().HasTag("ROTATION_DEGREES") &&
!cc->Inputs().Tag("ROTATION_DEGREES").IsEmpty()) { !cc->Inputs().Tag("ROTATION_DEGREES").IsEmpty()) {
@@ -22,9 +22,9 @@
namespace mediapipe { namespace mediapipe {
// Takes in an encoded image std::string, decodes it by OpenCV, and converts to // Takes in an encoded image string, decodes it by OpenCV, and converts to an
// an ImageFrame. Note that this calculator only supports grayscale and RGB // ImageFrame. Note that this calculator only supports grayscale and RGB images
// images for now. // for now.
// //
// Example config: // Example config:
// node { // node {
@@ -20,8 +20,8 @@
namespace mediapipe { namespace mediapipe {
// Takes in a std::string, draws the text std::string by cv::putText(), and // Takes in a string, draws the text string by cv::putText(), and outputs an
// outputs an ImageFrame. // ImageFrame.
// //
// Example config: // Example config:
// node { // node {
@@ -421,6 +421,10 @@ absl::Status ScaleImageCalculator::InitializeFromOptions() {
alignment_boundary_ = options_.alignment_boundary(); alignment_boundary_ = options_.alignment_boundary();
} }
if (options_.has_output_format()) {
output_format_ = options_.output_format();
}
downscaler_.reset(new ImageResizer(options_.post_sharpening_coefficient())); downscaler_.reset(new ImageResizer(options_.post_sharpening_coefficient()));
return absl::OkStatus(); return absl::OkStatus();
@@ -433,13 +437,17 @@ absl::Status ScaleImageCalculator::ValidateImageFormats() const {
<< "The output image format was set to UNKNOWN."; << "The output image format was set to UNKNOWN.";
// TODO Remove these conditions. // TODO Remove these conditions.
RET_CHECK(output_format_ == ImageFormat::SRGB || RET_CHECK(output_format_ == ImageFormat::SRGB ||
output_format_ == ImageFormat::SRGBA ||
(input_format_ == output_format_ && (input_format_ == output_format_ &&
output_format_ == ImageFormat::YCBCR420P)) output_format_ == ImageFormat::YCBCR420P))
<< "Outputting YCbCr420P images from SRGB input is not yet supported"; << "Outputting YCbCr420P images from SRGB input is not yet supported";
RET_CHECK(input_format_ == output_format_ || RET_CHECK(input_format_ == output_format_ ||
input_format_ == ImageFormat::YCBCR420P) (input_format_ == ImageFormat::YCBCR420P &&
output_format_ == ImageFormat::SRGB) ||
(input_format_ == ImageFormat::SRGB &&
output_format_ == ImageFormat::SRGBA))
<< "Conversion of the color space (except from " << "Conversion of the color space (except from "
"YCbCr420P to SRGB) is not yet supported."; "YCbCr420P to SRGB or SRGB to SRBGA) is not yet supported.";
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -553,7 +561,6 @@ absl::Status ScaleImageCalculator::Process(CalculatorContext* cc) {
} }
} }
cc->GetCounter("Inputs")->Increment();
const ImageFrame* image_frame; const ImageFrame* image_frame;
ImageFrame converted_image_frame; ImageFrame converted_image_frame;
if (input_format_ == ImageFormat::YCBCR420P) { if (input_format_ == ImageFormat::YCBCR420P) {
@@ -605,6 +612,15 @@ absl::Status ScaleImageCalculator::Process(CalculatorContext* cc) {
.Add(output_image.release(), cc->InputTimestamp()); .Add(output_image.release(), cc->InputTimestamp());
return absl::OkStatus(); return absl::OkStatus();
} }
} else if (input_format_ == ImageFormat::SRGB &&
output_format_ == ImageFormat::SRGBA) {
image_frame = &cc->Inputs().Get(input_data_id_).Get<ImageFrame>();
cv::Mat input_mat = ::mediapipe::formats::MatView(image_frame);
converted_image_frame.Reset(ImageFormat::SRGBA, image_frame->Width(),
image_frame->Height(), alignment_boundary_);
cv::Mat output_mat = ::mediapipe::formats::MatView(&converted_image_frame);
cv::cvtColor(input_mat, output_mat, cv::COLOR_RGB2RGBA, 4);
image_frame = &converted_image_frame;
} else { } else {
image_frame = &cc->Inputs().Get(input_data_id_).Get<ImageFrame>(); image_frame = &cc->Inputs().Get(input_data_id_).Get<ImageFrame>();
MP_RETURN_IF_ERROR(ValidateImageFrame(cc, *image_frame)); MP_RETURN_IF_ERROR(ValidateImageFrame(cc, *image_frame));
@@ -183,22 +183,22 @@ absl::Status SegmentationSmoothingCalculator::Close(CalculatorContext* cc) {
absl::Status SegmentationSmoothingCalculator::RenderCpu(CalculatorContext* cc) { absl::Status SegmentationSmoothingCalculator::RenderCpu(CalculatorContext* cc) {
// Setup source images. // Setup source images.
const auto& current_frame = cc->Inputs().Tag(kCurrentMaskTag).Get<Image>(); const auto& current_frame = cc->Inputs().Tag(kCurrentMaskTag).Get<Image>();
const cv::Mat current_mat = mediapipe::formats::MatView(&current_frame); auto current_mat = mediapipe::formats::MatView(&current_frame);
RET_CHECK_EQ(current_mat.type(), CV_32FC1) RET_CHECK_EQ(current_mat->type(), CV_32FC1)
<< "Only 1-channel float input image is supported."; << "Only 1-channel float input image is supported.";
const auto& previous_frame = cc->Inputs().Tag(kPreviousMaskTag).Get<Image>(); const auto& previous_frame = cc->Inputs().Tag(kPreviousMaskTag).Get<Image>();
const cv::Mat previous_mat = mediapipe::formats::MatView(&previous_frame); auto previous_mat = mediapipe::formats::MatView(&previous_frame);
RET_CHECK_EQ(previous_mat.type(), current_mat.type()) RET_CHECK_EQ(previous_mat->type(), current_mat->type())
<< "Warning: mixing input format types: " << previous_mat.type() << "Warning: mixing input format types: " << previous_mat->type()
<< " != " << previous_mat.type(); << " != " << previous_mat->type();
RET_CHECK_EQ(current_mat.rows, previous_mat.rows); RET_CHECK_EQ(current_mat->rows, previous_mat->rows);
RET_CHECK_EQ(current_mat.cols, previous_mat.cols); RET_CHECK_EQ(current_mat->cols, previous_mat->cols);
// Setup destination image. // Setup destination image.
auto output_frame = std::make_shared<ImageFrame>( auto output_frame = std::make_shared<ImageFrame>(
current_frame.image_format(), current_mat.cols, current_mat.rows); current_frame.image_format(), current_mat->cols, current_mat->rows);
cv::Mat output_mat = mediapipe::formats::MatView(output_frame.get()); cv::Mat output_mat = mediapipe::formats::MatView(output_frame.get());
output_mat.setTo(cv::Scalar(0)); output_mat.setTo(cv::Scalar(0));
@@ -233,8 +233,8 @@ absl::Status SegmentationSmoothingCalculator::RenderCpu(CalculatorContext* cc) {
// Write directly to the first channel of output. // Write directly to the first channel of output.
for (int i = 0; i < output_mat.rows; ++i) { for (int i = 0; i < output_mat.rows; ++i) {
float* out_ptr = output_mat.ptr<float>(i); float* out_ptr = output_mat.ptr<float>(i);
const float* curr_ptr = current_mat.ptr<float>(i); const float* curr_ptr = current_mat->ptr<float>(i);
const float* prev_ptr = previous_mat.ptr<float>(i); const float* prev_ptr = previous_mat->ptr<float>(i);
for (int j = 0; j < output_mat.cols; ++j) { for (int j = 0; j < output_mat.cols; ++j) {
const float new_mask_value = curr_ptr[j]; const float new_mask_value = curr_ptr[j];
const float prev_mask_value = prev_ptr[j]; const float prev_mask_value = prev_ptr[j];
@@ -116,8 +116,8 @@ void RunGraph(Packet curr_packet, Packet prev_packet, bool use_gpu, float ratio,
ASSERT_EQ(1, output_packets.size()); ASSERT_EQ(1, output_packets.size());
Image result_image = output_packets[0].Get<Image>(); Image result_image = output_packets[0].Get<Image>();
cv::Mat result_mat = formats::MatView(&result_image); auto result_mat = formats::MatView(&result_image);
result_mat.copyTo(*result); result_mat->copyTo(*result);
// Fully close graph at end, otherwise calculator+Images are destroyed // Fully close graph at end, otherwise calculator+Images are destroyed
// after calling WaitUntilDone(). // after calling WaitUntilDone().
@@ -135,10 +135,10 @@ void RunTest(bool use_gpu, float mix_ratio, cv::Mat& test_result) {
Packet curr_packet = MakePacket<Image>(std::make_unique<ImageFrame>( Packet curr_packet = MakePacket<Image>(std::make_unique<ImageFrame>(
ImageFormat::VEC32F1, curr_mat.size().width, curr_mat.size().height)); ImageFormat::VEC32F1, curr_mat.size().width, curr_mat.size().height));
curr_mat.copyTo(formats::MatView(&(curr_packet.Get<Image>()))); curr_mat.copyTo(*formats::MatView(&(curr_packet.Get<Image>())));
Packet prev_packet = MakePacket<Image>(std::make_unique<ImageFrame>( Packet prev_packet = MakePacket<Image>(std::make_unique<ImageFrame>(
ImageFormat::VEC32F1, prev_mat.size().width, prev_mat.size().height)); ImageFormat::VEC32F1, prev_mat.size().width, prev_mat.size().height));
prev_mat.copyTo(formats::MatView(&(prev_packet.Get<Image>()))); prev_mat.copyTo(*formats::MatView(&(prev_packet.Get<Image>())));
cv::Mat result; cv::Mat result;
RunGraph(curr_packet, prev_packet, use_gpu, mix_ratio, &result); RunGraph(curr_packet, prev_packet, use_gpu, mix_ratio, &result);
+16 -9
View File
@@ -28,7 +28,9 @@ package(default_visibility = ["//visibility:private"])
exports_files( exports_files(
glob(["testdata/image_to_tensor/*"]), glob(["testdata/image_to_tensor/*"]),
visibility = ["//mediapipe/calculators/image:__subpackages__"], visibility = [
"//mediapipe/calculators/image:__subpackages__",
],
) )
selects.config_setting_group( selects.config_setting_group(
@@ -64,15 +66,16 @@ cc_library(
":inference_calculator_cc_proto", ":inference_calculator_cc_proto",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework/api2:node", "//mediapipe/framework/api2:node",
"//mediapipe/framework/api2:packet",
"//mediapipe/framework/formats:tensor", "//mediapipe/framework/formats:tensor",
"//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:ret_check",
"//mediapipe/framework/stream_handler:fixed_size_input_stream_handler", "//mediapipe/framework/stream_handler:fixed_size_input_stream_handler",
"//mediapipe/framework/tool:subgraph_expansion", "//mediapipe/framework/tool:subgraph_expansion",
"//mediapipe/util/tflite:config",
"//mediapipe/util/tflite:tflite_model_loader", "//mediapipe/util/tflite:tflite_model_loader",
"@com_google_absl//absl/memory", "@com_google_absl//absl/memory",
"@com_google_absl//absl/strings", "@com_google_absl//absl/strings",
"@org_tensorflow//tensorflow/lite:framework", "@org_tensorflow//tensorflow/lite:framework",
"@org_tensorflow//tensorflow/lite/core/api:op_resolver",
"@org_tensorflow//tensorflow/lite/kernels:builtin_ops", "@org_tensorflow//tensorflow/lite/kernels:builtin_ops",
], ],
alwayslink = 1, alwayslink = 1,
@@ -84,14 +87,16 @@ cc_library(
tags = ["nomac"], # config problem with cpuinfo via TF tags = ["nomac"], # config problem with cpuinfo via TF
deps = [ deps = [
"inference_calculator_interface", "inference_calculator_interface",
"//mediapipe/framework/deps:file_path",
"//mediapipe/gpu:gl_calculator_helper", "//mediapipe/gpu:gl_calculator_helper",
"//mediapipe/gpu:gpu_buffer", "//mediapipe/gpu:gpu_buffer",
"//mediapipe/util/tflite:config",
"//mediapipe/util/tflite:tflite_gpu_runner", "//mediapipe/util/tflite:tflite_gpu_runner",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/status",
"@org_tensorflow//tensorflow/lite:framework_stable",
"@org_tensorflow//tensorflow/lite/delegates/gpu:gl_delegate", "@org_tensorflow//tensorflow/lite/delegates/gpu:gl_delegate",
"@org_tensorflow//tensorflow/lite/delegates/gpu/common:shape", "@org_tensorflow//tensorflow/lite/delegates/gpu/common:shape",
"@org_tensorflow//tensorflow/lite/delegates/gpu/gl:gl_buffer",
"@org_tensorflow//tensorflow/lite/delegates/gpu/gl:gl_program",
"@org_tensorflow//tensorflow/lite/delegates/gpu/gl:gl_shader",
], ],
alwayslink = 1, alwayslink = 1,
) )
@@ -141,6 +146,8 @@ cc_library(
":inference_calculator_interface", ":inference_calculator_interface",
"@com_google_absl//absl/memory", "@com_google_absl//absl/memory",
"@org_tensorflow//tensorflow/lite/delegates/xnnpack:xnnpack_delegate", "@org_tensorflow//tensorflow/lite/delegates/xnnpack:xnnpack_delegate",
"@org_tensorflow//tensorflow/lite:framework_stable",
"@org_tensorflow//tensorflow/lite/c:c_api_types",
] + select({ ] + select({
"//conditions:default": [ "//conditions:default": [
"//mediapipe/util:cpu_util", "//mediapipe/util:cpu_util",
@@ -154,7 +161,7 @@ cc_library(
cc_library( cc_library(
name = "inference_calculator_gl_if_compute_shader_available", name = "inference_calculator_gl_if_compute_shader_available",
deps = select({ deps = selects.with_or({
":compute_shader_unavailable": [], ":compute_shader_unavailable": [],
"//conditions:default": [":inference_calculator_gl"], "//conditions:default": [":inference_calculator_gl"],
}), }),
@@ -303,7 +310,7 @@ cc_library(
"//mediapipe/framework/formats:tensor", "//mediapipe/framework/formats:tensor",
"//mediapipe/framework/formats/object_detection:anchor_cc_proto", "//mediapipe/framework/formats/object_detection:anchor_cc_proto",
"//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:ret_check",
] + select({ ] + selects.with_or({
":compute_shader_unavailable": [], ":compute_shader_unavailable": [],
"//conditions:default": [":tensors_to_detections_calculator_gpu_deps"], "//conditions:default": [":tensors_to_detections_calculator_gpu_deps"],
}), }),
@@ -560,7 +567,7 @@ cc_library(
cc_library( cc_library(
name = "image_to_tensor_calculator_gpu_deps", name = "image_to_tensor_calculator_gpu_deps",
deps = select({ deps = selects.with_or({
"//mediapipe:android": [ "//mediapipe:android": [
":image_to_tensor_converter_gl_buffer", ":image_to_tensor_converter_gl_buffer",
"//mediapipe/gpu:gl_calculator_helper", "//mediapipe/gpu:gl_calculator_helper",
@@ -684,7 +691,7 @@ cc_library(
name = "image_to_tensor_converter_gl_buffer", name = "image_to_tensor_converter_gl_buffer",
srcs = ["image_to_tensor_converter_gl_buffer.cc"], srcs = ["image_to_tensor_converter_gl_buffer.cc"],
hdrs = ["image_to_tensor_converter_gl_buffer.h"], hdrs = ["image_to_tensor_converter_gl_buffer.h"],
deps = ["//mediapipe/framework:port"] + select({ deps = ["//mediapipe/framework:port"] + selects.with_or({
"//mediapipe:apple": [], "//mediapipe:apple": [],
"//conditions:default": [ "//conditions:default": [
":image_to_tensor_converter", ":image_to_tensor_converter",
@@ -49,7 +49,6 @@
#include "mediapipe/calculators/tensor/image_to_tensor_converter_gl_texture.h" #include "mediapipe/calculators/tensor/image_to_tensor_converter_gl_texture.h"
#include "mediapipe/gpu/gl_calculator_helper.h" #include "mediapipe/gpu/gl_calculator_helper.h"
#endif // MEDIAPIPE_METAL_ENABLED #endif // MEDIAPIPE_METAL_ENABLED
#endif // !MEDIAPIPE_DISABLE_GPU #endif // !MEDIAPIPE_DISABLE_GPU
namespace mediapipe { namespace mediapipe {
@@ -142,11 +141,37 @@ class ImageToTensorCalculator : public Node {
const auto& options = const auto& options =
cc->Options<mediapipe::ImageToTensorCalculatorOptions>(); cc->Options<mediapipe::ImageToTensorCalculatorOptions>();
RET_CHECK(options.has_output_tensor_float_range()) RET_CHECK(options.has_output_tensor_float_range() ||
options.has_output_tensor_int_range() ||
options.has_output_tensor_uint_range())
<< "Output tensor range is required."; << "Output tensor range is required.";
RET_CHECK_LT(options.output_tensor_float_range().min(), if (options.has_output_tensor_float_range()) {
options.output_tensor_float_range().max()) RET_CHECK_LT(options.output_tensor_float_range().min(),
<< "Valid output tensor range is required."; options.output_tensor_float_range().max())
<< "Valid output float tensor range is required.";
}
if (options.has_output_tensor_uint_range()) {
RET_CHECK_LT(options.output_tensor_uint_range().min(),
options.output_tensor_uint_range().max())
<< "Valid output uint tensor range is required.";
RET_CHECK_GE(options.output_tensor_uint_range().min(), 0)
<< "The minimum of the output uint tensor range must be "
"non-negative.";
RET_CHECK_LE(options.output_tensor_uint_range().max(), 255)
<< "The maximum of the output uint tensor range must be less than or "
"equal to 255.";
}
if (options.has_output_tensor_int_range()) {
RET_CHECK_LT(options.output_tensor_int_range().min(),
options.output_tensor_int_range().max())
<< "Valid output int tensor range is required.";
RET_CHECK_GE(options.output_tensor_int_range().min(), -128)
<< "The minimum of the output int tensor range must be greater than "
"or equal to -128.";
RET_CHECK_LE(options.output_tensor_int_range().max(), 127)
<< "The maximum of the output int tensor range must be less than or "
"equal to 127.";
}
RET_CHECK_GT(options.output_tensor_width(), 0) RET_CHECK_GT(options.output_tensor_width(), 0)
<< "Valid output tensor width is required."; << "Valid output tensor width is required.";
RET_CHECK_GT(options.output_tensor_height(), 0) RET_CHECK_GT(options.output_tensor_height(), 0)
@@ -175,9 +200,19 @@ class ImageToTensorCalculator : public Node {
options_ = cc->Options<mediapipe::ImageToTensorCalculatorOptions>(); options_ = cc->Options<mediapipe::ImageToTensorCalculatorOptions>();
output_width_ = options_.output_tensor_width(); output_width_ = options_.output_tensor_width();
output_height_ = options_.output_tensor_height(); output_height_ = options_.output_tensor_height();
range_min_ = options_.output_tensor_float_range().min(); is_float_output_ = options_.has_output_tensor_float_range();
range_max_ = options_.output_tensor_float_range().max(); if (options_.has_output_tensor_uint_range()) {
range_min_ =
static_cast<float>(options_.output_tensor_uint_range().min());
range_max_ =
static_cast<float>(options_.output_tensor_uint_range().max());
} else if (options_.has_output_tensor_int_range()) {
range_min_ = static_cast<float>(options_.output_tensor_int_range().min());
range_max_ = static_cast<float>(options_.output_tensor_int_range().max());
} else {
range_min_ = options_.output_tensor_float_range().min();
range_max_ = options_.output_tensor_float_range().max();
}
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -225,7 +260,7 @@ class ImageToTensorCalculator : public Node {
} }
// Lazy initialization of the GPU or CPU converter. // Lazy initialization of the GPU or CPU converter.
MP_RETURN_IF_ERROR(InitConverterIfNecessary(cc, image->UsesGpu())); MP_RETURN_IF_ERROR(InitConverterIfNecessary(cc, *image.get()));
ASSIGN_OR_RETURN(Tensor tensor, ASSIGN_OR_RETURN(Tensor tensor,
(image->UsesGpu() ? gpu_converter_ : cpu_converter_) (image->UsesGpu() ? gpu_converter_ : cpu_converter_)
@@ -257,6 +292,17 @@ class ImageToTensorCalculator : public Node {
} }
} }
Tensor::ElementType GetOutputTensorType() {
if (is_float_output_) {
return Tensor::ElementType::kFloat32;
}
if (range_min_ < 0) {
return Tensor::ElementType::kInt8;
} else {
return Tensor::ElementType::kUInt8;
}
}
absl::StatusOr<std::shared_ptr<const mediapipe::Image>> GetInputImage( absl::StatusOr<std::shared_ptr<const mediapipe::Image>> GetInputImage(
CalculatorContext* cc) { CalculatorContext* cc) {
if (kIn(cc).IsConnected()) { if (kIn(cc).IsConnected()) {
@@ -283,9 +329,15 @@ class ImageToTensorCalculator : public Node {
} }
} }
absl::Status InitConverterIfNecessary(CalculatorContext* cc, bool use_gpu) { absl::Status InitConverterIfNecessary(CalculatorContext* cc,
const Image& image) {
// Lazy initialization of the GPU or CPU converter. // Lazy initialization of the GPU or CPU converter.
if (use_gpu) { if (image.UsesGpu()) {
if (!is_float_output_) {
return absl::UnimplementedError(
"ImageToTensorConverter for the input GPU image currently doesn't "
"support quantization.");
}
if (!gpu_converter_) { if (!gpu_converter_) {
#if !MEDIAPIPE_DISABLE_GPU #if !MEDIAPIPE_DISABLE_GPU
#if MEDIAPIPE_METAL_ENABLED #if MEDIAPIPE_METAL_ENABLED
@@ -296,17 +348,26 @@ class ImageToTensorCalculator : public Node {
CreateImageToGlBufferTensorConverter( CreateImageToGlBufferTensorConverter(
cc, DoesGpuInputStartAtBottom(), GetBorderMode())); cc, DoesGpuInputStartAtBottom(), GetBorderMode()));
#else #else
ASSIGN_OR_RETURN(gpu_converter_, // Check whether the underlying storage object is a GL texture.
CreateImageToGlTextureTensorConverter( if (image.GetGpuBuffer()
cc, DoesGpuInputStartAtBottom(), GetBorderMode())); .internal_storage<mediapipe::GlTextureBuffer>()) {
ASSIGN_OR_RETURN(
gpu_converter_,
CreateImageToGlTextureTensorConverter(
cc, DoesGpuInputStartAtBottom(), GetBorderMode()));
} else {
return absl::UnimplementedError(
"ImageToTensorConverter for the input GPU image is unavailable.");
}
#endif // MEDIAPIPE_METAL_ENABLED #endif // MEDIAPIPE_METAL_ENABLED
#endif // !MEDIAPIPE_DISABLE_GPU #endif // !MEDIAPIPE_DISABLE_GPU
} }
} else { } else {
if (!cpu_converter_) { if (!cpu_converter_) {
#if !MEDIAPIPE_DISABLE_OPENCV #if !MEDIAPIPE_DISABLE_OPENCV
ASSIGN_OR_RETURN(cpu_converter_, ASSIGN_OR_RETURN(
CreateOpenCvConverter(cc, GetBorderMode())); cpu_converter_,
CreateOpenCvConverter(cc, GetBorderMode(), GetOutputTensorType()));
#else #else
LOG(FATAL) << "Cannot create image to tensor opencv converter since " LOG(FATAL) << "Cannot create image to tensor opencv converter since "
"MEDIAPIPE_DISABLE_OPENCV is defined."; "MEDIAPIPE_DISABLE_OPENCV is defined.";
@@ -321,6 +382,7 @@ class ImageToTensorCalculator : public Node {
mediapipe::ImageToTensorCalculatorOptions options_; mediapipe::ImageToTensorCalculatorOptions options_;
int output_width_ = 0; int output_width_ = 0;
int output_height_ = 0; int output_height_ = 0;
bool is_float_output_ = false;
float range_min_ = 0.0f; float range_min_ = 0.0f;
float range_max_ = 1.0f; float range_max_ = 1.0f;
}; };
@@ -31,6 +31,22 @@ message ImageToTensorCalculatorOptions {
optional float max = 2; optional float max = 2;
} }
// Range of int values [min, max].
// min, must be strictly less than max.
// Please note that IntRange is supported for CPU tensors only.
message IntRange {
optional int64 min = 1;
optional int64 max = 2;
}
// Range of uint values [min, max].
// min, must be strictly less than max.
// Please note that UIntRange is supported for CPU tensors only.
message UIntRange {
optional uint64 min = 1;
optional uint64 max = 2;
}
// Pixel extrapolation methods. See @border_mode. // Pixel extrapolation methods. See @border_mode.
enum BorderMode { enum BorderMode {
BORDER_UNSPECIFIED = 0; BORDER_UNSPECIFIED = 0;
@@ -49,6 +65,8 @@ message ImageToTensorCalculatorOptions {
// Output tensor element range/type image pixels are converted to. // Output tensor element range/type image pixels are converted to.
oneof range { oneof range {
FloatRange output_tensor_float_range = 4; FloatRange output_tensor_float_range = 4;
IntRange output_tensor_int_range = 7;
UIntRange output_tensor_uint_range = 8;
} }
// For CONVENTIONAL mode for OpenGL, input image starts at bottom and needs // For CONVENTIONAL mode for OpenGL, input image starts at bottom and needs
@@ -61,7 +61,8 @@ void RunTestWithInputImagePacket(const Packet& input_image_packet,
float range_max, int tensor_width, float range_max, int tensor_width,
int tensor_height, bool keep_aspect, int tensor_height, bool keep_aspect,
absl::optional<BorderMode> border_mode, absl::optional<BorderMode> border_mode,
const mediapipe::NormalizedRect& roi) { const mediapipe::NormalizedRect& roi,
bool output_int_tensor) {
std::string border_mode_str; std::string border_mode_str;
if (border_mode) { if (border_mode) {
switch (*border_mode) { switch (*border_mode) {
@@ -73,6 +74,30 @@ void RunTestWithInputImagePacket(const Packet& input_image_packet,
break; break;
} }
} }
std::string output_tensor_range;
if (output_int_tensor) {
if (range_min < 0) {
output_tensor_range = absl::Substitute(R"(output_tensor_int_range {
min: $0
max: $1
})",
static_cast<int>(range_min),
static_cast<int>(range_max));
} else {
output_tensor_range = absl::Substitute(R"(output_tensor_uint_range {
min: $0
max: $1
})",
static_cast<uint>(range_min),
static_cast<uint>(range_max));
}
} else {
output_tensor_range = absl::Substitute(R"(output_tensor_float_range {
min: $0
max: $1
})",
range_min, range_max);
}
auto graph_config = mediapipe::ParseTextProtoOrDie<CalculatorGraphConfig>( auto graph_config = mediapipe::ParseTextProtoOrDie<CalculatorGraphConfig>(
absl::Substitute(R"( absl::Substitute(R"(
input_stream: "input_image" input_stream: "input_image"
@@ -86,22 +111,18 @@ void RunTestWithInputImagePacket(const Packet& input_image_packet,
[mediapipe.ImageToTensorCalculatorOptions.ext] { [mediapipe.ImageToTensorCalculatorOptions.ext] {
output_tensor_width: $0 output_tensor_width: $0
output_tensor_height: $1 output_tensor_height: $1
keep_aspect_ratio: $4 keep_aspect_ratio: $2
output_tensor_float_range { $3 # output range
min: $2 $4 # border mode
max: $3
}
$5 # border mode
} }
} }
} }
)", )",
/*$0=*/tensor_width, /*$0=*/tensor_width,
/*$1=*/tensor_height, /*$1=*/tensor_height,
/*$2=*/range_min, /*$2=*/keep_aspect ? "true" : "false",
/*$3=*/range_max, /*$3=*/output_tensor_range,
/*$4=*/keep_aspect ? "true" : "false", /*$4=*/border_mode_str));
/*$5=*/border_mode_str));
std::vector<Packet> output_packets; std::vector<Packet> output_packets;
tool::AddVectorSink("tensor", &graph_config, &output_packets); tool::AddVectorSink("tensor", &graph_config, &output_packets);
@@ -126,11 +147,24 @@ void RunTestWithInputImagePacket(const Packet& input_image_packet,
ASSERT_THAT(tensor_vec, testing::SizeIs(1)); ASSERT_THAT(tensor_vec, testing::SizeIs(1));
const Tensor& tensor = tensor_vec[0]; const Tensor& tensor = tensor_vec[0];
EXPECT_EQ(tensor.element_type(), Tensor::ElementType::kFloat32);
auto view = tensor.GetCpuReadView(); auto view = tensor.GetCpuReadView();
cv::Mat tensor_mat(tensor_height, tensor_width, CV_32FC3, cv::Mat tensor_mat;
const_cast<float*>(view.buffer<float>())); if (output_int_tensor) {
if (range_min < 0) {
EXPECT_EQ(tensor.element_type(), Tensor::ElementType::kInt8);
tensor_mat = cv::Mat(tensor_height, tensor_width, CV_8SC3,
const_cast<int8*>(view.buffer<int8>()));
} else {
EXPECT_EQ(tensor.element_type(), Tensor::ElementType::kUInt8);
tensor_mat = cv::Mat(tensor_height, tensor_width, CV_8UC3,
const_cast<uint8*>(view.buffer<uint8>()));
}
} else {
EXPECT_EQ(tensor.element_type(), Tensor::ElementType::kFloat32);
tensor_mat = cv::Mat(tensor_height, tensor_width, CV_32FC3,
const_cast<float*>(view.buffer<float>()));
}
cv::Mat result_rgb; cv::Mat result_rgb;
auto transformation = auto transformation =
GetValueRangeTransformation(range_min, range_max, 0.0f, 255.0f).value(); GetValueRangeTransformation(range_min, range_max, 0.0f, 255.0f).value();
@@ -170,16 +204,29 @@ enum class InputType { kImageFrame, kImage };
const std::vector<InputType> kInputTypesToTest = {InputType::kImageFrame, const std::vector<InputType> kInputTypesToTest = {InputType::kImageFrame,
InputType::kImage}; InputType::kImage};
void RunTest(cv::Mat input, cv::Mat expected_result, float range_min, void RunTest(cv::Mat input, cv::Mat expected_result,
float range_max, int tensor_width, int tensor_height, std::vector<std::pair<float, float>> float_ranges,
bool keep_aspect, absl::optional<BorderMode> border_mode, std::vector<std::pair<int, int>> int_ranges, int tensor_width,
int tensor_height, bool keep_aspect,
absl::optional<BorderMode> border_mode,
const mediapipe::NormalizedRect& roi) { const mediapipe::NormalizedRect& roi) {
for (auto input_type : kInputTypesToTest) { for (auto input_type : kInputTypesToTest) {
RunTestWithInputImagePacket( for (auto float_range : float_ranges) {
input_type == InputType::kImageFrame ? MakeImageFramePacket(input) RunTestWithInputImagePacket(
: MakeImagePacket(input), input_type == InputType::kImageFrame ? MakeImageFramePacket(input)
expected_result, range_min, range_max, tensor_width, tensor_height, : MakeImagePacket(input),
keep_aspect, border_mode, roi); expected_result, float_range.first, float_range.second, tensor_width,
tensor_height, keep_aspect, border_mode, roi,
/*output_int_tensor=*/false);
}
for (auto int_range : int_ranges) {
RunTestWithInputImagePacket(
input_type == InputType::kImageFrame ? MakeImageFramePacket(input)
: MakeImagePacket(input),
expected_result, int_range.first, int_range.second, tensor_width,
tensor_height, keep_aspect, border_mode, roi,
/*output_int_tensor=*/true);
}
} }
} }
@@ -195,8 +242,8 @@ TEST(ImageToTensorCalculatorTest, MediumSubRectKeepAspect) {
"tensor/testdata/image_to_tensor/input.jpg"), "tensor/testdata/image_to_tensor/input.jpg"),
GetRgb("/mediapipe/calculators/" GetRgb("/mediapipe/calculators/"
"tensor/testdata/image_to_tensor/medium_sub_rect_keep_aspect.png"), "tensor/testdata/image_to_tensor/medium_sub_rect_keep_aspect.png"),
/*range_min=*/0.0f, /*float_ranges=*/{{0.0f, 1.0f}},
/*range_max=*/1.0f, /*int_ranges=*/{{0, 255}, {-128, 127}},
/*tensor_width=*/256, /*tensor_height=*/256, /*keep_aspect=*/true, /*tensor_width=*/256, /*tensor_height=*/256, /*keep_aspect=*/true,
/*border mode*/ {}, roi); /*border mode*/ {}, roi);
} }
@@ -213,8 +260,8 @@ TEST(ImageToTensorCalculatorTest, MediumSubRectKeepAspectBorderZero) {
GetRgb("/mediapipe/calculators/" GetRgb("/mediapipe/calculators/"
"tensor/testdata/image_to_tensor/" "tensor/testdata/image_to_tensor/"
"medium_sub_rect_keep_aspect_border_zero.png"), "medium_sub_rect_keep_aspect_border_zero.png"),
/*range_min=*/0.0f, /*float_ranges=*/{{0.0f, 1.0f}},
/*range_max=*/1.0f, /*int_ranges=*/{{0, 255}, {-128, 127}},
/*tensor_width=*/256, /*tensor_height=*/256, /*keep_aspect=*/true, /*tensor_width=*/256, /*tensor_height=*/256, /*keep_aspect=*/true,
BorderMode::kZero, roi); BorderMode::kZero, roi);
} }
@@ -231,7 +278,8 @@ TEST(ImageToTensorCalculatorTest, MediumSubRectKeepAspectWithRotation) {
GetRgb("/mediapipe/calculators/" GetRgb("/mediapipe/calculators/"
"tensor/testdata/image_to_tensor/" "tensor/testdata/image_to_tensor/"
"medium_sub_rect_keep_aspect_with_rotation.png"), "medium_sub_rect_keep_aspect_with_rotation.png"),
/*range_min=*/0.0f, /*range_max=*/1.0f, /*float_ranges=*/{{0.0f, 1.0f}},
/*int_ranges=*/{{0, 255}},
/*tensor_width=*/256, /*tensor_height=*/256, /*keep_aspect=*/true, /*tensor_width=*/256, /*tensor_height=*/256, /*keep_aspect=*/true,
BorderMode::kReplicate, roi); BorderMode::kReplicate, roi);
} }
@@ -249,7 +297,8 @@ TEST(ImageToTensorCalculatorTest,
GetRgb("/mediapipe/calculators/" GetRgb("/mediapipe/calculators/"
"tensor/testdata/image_to_tensor/" "tensor/testdata/image_to_tensor/"
"medium_sub_rect_keep_aspect_with_rotation_border_zero.png"), "medium_sub_rect_keep_aspect_with_rotation_border_zero.png"),
/*range_min=*/0.0f, /*range_max=*/1.0f, /*float_ranges=*/{{0.0f, 1.0f}},
/*int_ranges=*/{{0, 255}, {-128, 127}},
/*tensor_width=*/256, /*tensor_height=*/256, /*keep_aspect=*/true, /*tensor_width=*/256, /*tensor_height=*/256, /*keep_aspect=*/true,
BorderMode::kZero, roi); BorderMode::kZero, roi);
} }
@@ -267,8 +316,8 @@ TEST(ImageToTensorCalculatorTest, MediumSubRectWithRotation) {
GetRgb( GetRgb(
"/mediapipe/calculators/" "/mediapipe/calculators/"
"tensor/testdata/image_to_tensor/medium_sub_rect_with_rotation.png"), "tensor/testdata/image_to_tensor/medium_sub_rect_with_rotation.png"),
/*range_min=*/-1.0f, /*float_ranges=*/{{-1.0f, 1.0f}},
/*range_max=*/1.0f, /*int_ranges=*/{{0, 255}, {-128, 127}},
/*tensor_width=*/256, /*tensor_height=*/256, /*keep_aspect=*/false, /*tensor_width=*/256, /*tensor_height=*/256, /*keep_aspect=*/false,
BorderMode::kReplicate, roi); BorderMode::kReplicate, roi);
} }
@@ -285,8 +334,8 @@ TEST(ImageToTensorCalculatorTest, MediumSubRectWithRotationBorderZero) {
GetRgb("/mediapipe/calculators/" GetRgb("/mediapipe/calculators/"
"tensor/testdata/image_to_tensor/" "tensor/testdata/image_to_tensor/"
"medium_sub_rect_with_rotation_border_zero.png"), "medium_sub_rect_with_rotation_border_zero.png"),
/*range_min=*/-1.0f, /*float_ranges=*/{{-1.0f, 1.0f}},
/*range_max=*/1.0f, /*int_ranges=*/{{0, 255}, {-128, 127}},
/*tensor_width=*/256, /*tensor_height=*/256, /*keep_aspect=*/false, /*tensor_width=*/256, /*tensor_height=*/256, /*keep_aspect=*/false,
BorderMode::kZero, roi); BorderMode::kZero, roi);
} }
@@ -302,8 +351,8 @@ TEST(ImageToTensorCalculatorTest, LargeSubRect) {
"tensor/testdata/image_to_tensor/input.jpg"), "tensor/testdata/image_to_tensor/input.jpg"),
GetRgb("/mediapipe/calculators/" GetRgb("/mediapipe/calculators/"
"tensor/testdata/image_to_tensor/large_sub_rect.png"), "tensor/testdata/image_to_tensor/large_sub_rect.png"),
/*range_min=*/0.0f, /*float_ranges=*/{{0.0f, 1.0f}},
/*range_max=*/1.0f, /*int_ranges=*/{{0, 255}},
/*tensor_width=*/128, /*tensor_height=*/128, /*keep_aspect=*/false, /*tensor_width=*/128, /*tensor_height=*/128, /*keep_aspect=*/false,
BorderMode::kReplicate, roi); BorderMode::kReplicate, roi);
} }
@@ -320,8 +369,8 @@ TEST(ImageToTensorCalculatorTest, LargeSubRectBorderZero) {
"tensor/testdata/image_to_tensor/input.jpg"), "tensor/testdata/image_to_tensor/input.jpg"),
GetRgb("/mediapipe/calculators/" GetRgb("/mediapipe/calculators/"
"tensor/testdata/image_to_tensor/large_sub_rect_border_zero.png"), "tensor/testdata/image_to_tensor/large_sub_rect_border_zero.png"),
/*range_min=*/0.0f, /*float_ranges=*/{{0.0f, 1.0f}},
/*range_max=*/1.0f, /*int_ranges=*/{{0, 255}, {-128, 127}},
/*tensor_width=*/128, /*tensor_height=*/128, /*keep_aspect=*/false, /*tensor_width=*/128, /*tensor_height=*/128, /*keep_aspect=*/false,
BorderMode::kZero, roi); BorderMode::kZero, roi);
} }
@@ -338,8 +387,8 @@ TEST(ImageToTensorCalculatorTest, LargeSubRectKeepAspect) {
"tensor/testdata/image_to_tensor/input.jpg"), "tensor/testdata/image_to_tensor/input.jpg"),
GetRgb("/mediapipe/calculators/" GetRgb("/mediapipe/calculators/"
"tensor/testdata/image_to_tensor/large_sub_rect_keep_aspect.png"), "tensor/testdata/image_to_tensor/large_sub_rect_keep_aspect.png"),
/*range_min=*/0.0f, /*float_ranges=*/{{0.0f, 1.0f}},
/*range_max=*/1.0f, /*int_ranges=*/{{0, 255}, {-128, 127}},
/*tensor_width=*/128, /*tensor_height=*/128, /*keep_aspect=*/true, /*tensor_width=*/128, /*tensor_height=*/128, /*keep_aspect=*/true,
BorderMode::kReplicate, roi); BorderMode::kReplicate, roi);
} }
@@ -356,8 +405,8 @@ TEST(ImageToTensorCalculatorTest, LargeSubRectKeepAspectBorderZero) {
GetRgb("/mediapipe/calculators/" GetRgb("/mediapipe/calculators/"
"tensor/testdata/image_to_tensor/" "tensor/testdata/image_to_tensor/"
"large_sub_rect_keep_aspect_border_zero.png"), "large_sub_rect_keep_aspect_border_zero.png"),
/*range_min=*/0.0f, /*float_ranges=*/{{0.0f, 1.0f}},
/*range_max=*/1.0f, /*int_ranges=*/{{0, 255}, {-128, 127}},
/*tensor_width=*/128, /*tensor_height=*/128, /*keep_aspect=*/true, /*tensor_width=*/128, /*tensor_height=*/128, /*keep_aspect=*/true,
BorderMode::kZero, roi); BorderMode::kZero, roi);
} }
@@ -374,8 +423,8 @@ TEST(ImageToTensorCalculatorTest, LargeSubRectKeepAspectWithRotation) {
GetRgb("/mediapipe/calculators/" GetRgb("/mediapipe/calculators/"
"tensor/testdata/image_to_tensor/" "tensor/testdata/image_to_tensor/"
"large_sub_rect_keep_aspect_with_rotation.png"), "large_sub_rect_keep_aspect_with_rotation.png"),
/*range_min=*/0.0f, /*float_ranges=*/{{0.0f, 1.0f}},
/*range_max=*/1.0f, /*int_ranges=*/{{0, 255}, {-128, 127}},
/*tensor_width=*/128, /*tensor_height=*/128, /*keep_aspect=*/true, /*tensor_width=*/128, /*tensor_height=*/128, /*keep_aspect=*/true,
/*border_mode=*/{}, roi); /*border_mode=*/{}, roi);
} }
@@ -393,8 +442,8 @@ TEST(ImageToTensorCalculatorTest,
GetRgb("/mediapipe/calculators/" GetRgb("/mediapipe/calculators/"
"tensor/testdata/image_to_tensor/" "tensor/testdata/image_to_tensor/"
"large_sub_rect_keep_aspect_with_rotation_border_zero.png"), "large_sub_rect_keep_aspect_with_rotation_border_zero.png"),
/*range_min=*/0.0f, /*float_ranges=*/{{0.0f, 1.0f}},
/*range_max=*/1.0f, /*int_ranges=*/{{0, 255}},
/*tensor_width=*/128, /*tensor_height=*/128, /*keep_aspect=*/true, /*tensor_width=*/128, /*tensor_height=*/128, /*keep_aspect=*/true,
/*border_mode=*/BorderMode::kZero, roi); /*border_mode=*/BorderMode::kZero, roi);
} }
@@ -410,8 +459,8 @@ TEST(ImageToTensorCalculatorTest, NoOpExceptRange) {
"tensor/testdata/image_to_tensor/input.jpg"), "tensor/testdata/image_to_tensor/input.jpg"),
GetRgb("/mediapipe/calculators/" GetRgb("/mediapipe/calculators/"
"tensor/testdata/image_to_tensor/noop_except_range.png"), "tensor/testdata/image_to_tensor/noop_except_range.png"),
/*range_min=*/0.0f, /*float_ranges=*/{{0.0f, 1.0f}},
/*range_max=*/1.0f, /*int_ranges=*/{{0, 255}, {-128, 127}},
/*tensor_width=*/64, /*tensor_height=*/128, /*keep_aspect=*/true, /*tensor_width=*/64, /*tensor_height=*/128, /*keep_aspect=*/true,
BorderMode::kReplicate, roi); BorderMode::kReplicate, roi);
} }
@@ -427,8 +476,8 @@ TEST(ImageToTensorCalculatorTest, NoOpExceptRangeBorderZero) {
"tensor/testdata/image_to_tensor/input.jpg"), "tensor/testdata/image_to_tensor/input.jpg"),
GetRgb("/mediapipe/calculators/" GetRgb("/mediapipe/calculators/"
"tensor/testdata/image_to_tensor/noop_except_range.png"), "tensor/testdata/image_to_tensor/noop_except_range.png"),
/*range_min=*/0.0f, /*float_ranges=*/{{0.0f, 1.0f}},
/*range_max=*/1.0f, /*int_ranges=*/{{0, 255}, {-128, 127}},
/*tensor_width=*/64, /*tensor_height=*/128, /*keep_aspect=*/true, /*tensor_width=*/64, /*tensor_height=*/128, /*keep_aspect=*/true,
BorderMode::kZero, roi); BorderMode::kZero, roi);
} }
@@ -268,10 +268,12 @@ class GlProcessor : public ImageToTensorConverter {
const RotatedRect& roi, const RotatedRect& roi,
const Size& output_dims, float range_min, const Size& output_dims, float range_min,
float range_max) override { float range_max) override {
if (input.format() != mediapipe::GpuBufferFormat::kBGRA32) { if (input.format() != mediapipe::GpuBufferFormat::kBGRA32 &&
return InvalidArgumentError( input.format() != mediapipe::GpuBufferFormat::kRGBAHalf64 &&
absl::StrCat("Only BGRA/RGBA textures are supported, passed format: ", input.format() != mediapipe::GpuBufferFormat::kRGBAFloat128) {
static_cast<uint32_t>(input.format()))); return InvalidArgumentError(absl::StrCat(
"Only 4-channel texture input formats are supported, passed format: ",
static_cast<uint32_t>(input.format())));
} }
constexpr int kNumChannels = 3; constexpr int kNumChannels = 3;
@@ -16,7 +16,7 @@
#include "mediapipe/framework/port.h" #include "mediapipe/framework/port.h"
#if MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_20 #if MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_30
#include <array> #include <array>
#include <memory> #include <memory>
@@ -172,10 +172,12 @@ class GlProcessor : public ImageToTensorConverter {
const RotatedRect& roi, const RotatedRect& roi,
const Size& output_dims, float range_min, const Size& output_dims, float range_min,
float range_max) override { float range_max) override {
if (input.format() != mediapipe::GpuBufferFormat::kBGRA32) { if (input.format() != mediapipe::GpuBufferFormat::kBGRA32 &&
return InvalidArgumentError( input.format() != mediapipe::GpuBufferFormat::kRGBAHalf64 &&
absl::StrCat("Only BGRA/RGBA textures are supported, passed format: ", input.format() != mediapipe::GpuBufferFormat::kRGBAFloat128) {
static_cast<uint32_t>(input.format()))); return InvalidArgumentError(absl::StrCat(
"Only 4-channel texture input formats are supported, passed format: ",
static_cast<uint32_t>(input.format())));
} }
constexpr int kNumChannels = 3; constexpr int kNumChannels = 3;
@@ -339,4 +341,4 @@ CreateImageToGlTextureTensorConverter(CalculatorContext* cc,
} // namespace mediapipe } // namespace mediapipe
#endif // MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_20 #endif // MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_30
@@ -17,7 +17,7 @@
#include "mediapipe/framework/port.h" #include "mediapipe/framework/port.h"
#if MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_20 #if MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_30
#include <memory> #include <memory>
@@ -37,6 +37,6 @@ CreateImageToGlTextureTensorConverter(CalculatorContext* cc,
} // namespace mediapipe } // namespace mediapipe
#endif // MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_20 #endif // MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_30
#endif // MEDIAPIPE_CALCULATORS_TENSOR_IMAGE_TO_TENSOR_CONVERTER_GL_TEXTURE_H_ #endif // MEDIAPIPE_CALCULATORS_TENSOR_IMAGE_TO_TENSOR_CONVERTER_GL_TEXTURE_H_
@@ -2,7 +2,7 @@
#include "mediapipe/framework/port.h" #include "mediapipe/framework/port.h"
#if MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_20 #if MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_30
#include <array> #include <array>
#include <memory> #include <memory>
@@ -85,4 +85,4 @@ bool IsGlClampToBorderSupported(const mediapipe::GlContext& gl_context) {
} // namespace mediapipe } // namespace mediapipe
#endif // MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_20 #endif // MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_30
@@ -3,7 +3,7 @@
#include "mediapipe/framework/port.h" #include "mediapipe/framework/port.h"
#if MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_20 #if MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_30
#include <array> #include <array>
#include <memory> #include <memory>
@@ -40,6 +40,6 @@ bool IsGlClampToBorderSupported(const mediapipe::GlContext& gl_context);
} // namespace mediapipe } // namespace mediapipe
#endif // MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_20 #endif // MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_30
#endif // MEDIAPIPE_CALCULATORS_TENSOR_IMAGE_TO_TENSOR_CONVERTER_GL_UTILS_H_ #endif // MEDIAPIPE_CALCULATORS_TENSOR_IMAGE_TO_TENSOR_CONVERTER_GL_UTILS_H_
@@ -1,6 +1,6 @@
#include "mediapipe/framework/port.h" #include "mediapipe/framework/port.h"
#if MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_20 #if MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_30
#include "mediapipe/calculators/tensor/image_to_tensor_converter_gl_utils.h" #include "mediapipe/calculators/tensor/image_to_tensor_converter_gl_utils.h"
#include "mediapipe/framework/port/gtest.h" #include "mediapipe/framework/port/gtest.h"
@@ -46,4 +46,4 @@ TEST(ImageToTensorConverterGlUtilsTest, GlTexParameteriOverrider) {
} // namespace } // namespace
} // namespace mediapipe } // namespace mediapipe
#endif // MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_20 #endif // MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_30
@@ -352,11 +352,12 @@ class MetalProcessor : public ImageToTensorConverter {
const RotatedRect& roi, const RotatedRect& roi,
const Size& output_dims, float range_min, const Size& output_dims, float range_min,
float range_max) override { float range_max) override {
if (input.format() != mediapipe::GpuBufferFormat::kBGRA32) { if (input.format() != mediapipe::GpuBufferFormat::kBGRA32 &&
return InvalidArgumentError( input.format() != mediapipe::GpuBufferFormat::kRGBAHalf64 &&
absl::StrCat("Only BGRA/RGBA textures are supported, passed " input.format() != mediapipe::GpuBufferFormat::kRGBAFloat128) {
"format: ", return InvalidArgumentError(absl::StrCat(
static_cast<uint32_t>(input.format()))); "Only 4-channel texture input formats are supported, passed format: ",
static_cast<uint32_t>(input.format())));
} }
@autoreleasepool { @autoreleasepool {
@@ -35,7 +35,8 @@ namespace {
class OpenCvProcessor : public ImageToTensorConverter { class OpenCvProcessor : public ImageToTensorConverter {
public: public:
OpenCvProcessor(BorderMode border_mode) { OpenCvProcessor(BorderMode border_mode, Tensor::ElementType tensor_type)
: tensor_type_(tensor_type) {
switch (border_mode) { switch (border_mode) {
case BorderMode::kReplicate: case BorderMode::kReplicate:
border_mode_ = cv::BORDER_REPLICATE; border_mode_ = cv::BORDER_REPLICATE;
@@ -44,6 +45,19 @@ class OpenCvProcessor : public ImageToTensorConverter {
border_mode_ = cv::BORDER_CONSTANT; border_mode_ = cv::BORDER_CONSTANT;
break; break;
} }
switch (tensor_type_) {
case Tensor::ElementType::kInt8:
mat_type_ = CV_8SC3;
break;
case Tensor::ElementType::kFloat32:
mat_type_ = CV_32FC3;
break;
case Tensor::ElementType::kUInt8:
mat_type_ = CV_8UC3;
break;
default:
mat_type_ = -1;
}
} }
absl::StatusOr<Tensor> Convert(const mediapipe::Image& input, absl::StatusOr<Tensor> Convert(const mediapipe::Image& input,
@@ -56,15 +70,30 @@ class OpenCvProcessor : public ImageToTensorConverter {
absl::StrCat("Only RGBA/RGB formats are supported, passed format: ", absl::StrCat("Only RGBA/RGB formats are supported, passed format: ",
static_cast<uint32_t>(input.image_format()))); static_cast<uint32_t>(input.image_format())));
} }
cv::Mat src = mediapipe::formats::MatView(&input); auto src = mediapipe::formats::MatView(&input);
constexpr int kNumChannels = 3; constexpr int kNumChannels = 3;
Tensor tensor( Tensor tensor(tensor_type_, Tensor::Shape{1, output_dims.height,
Tensor::ElementType::kFloat32, output_dims.width, kNumChannels});
Tensor::Shape{1, output_dims.height, output_dims.width, kNumChannels});
auto buffer_view = tensor.GetCpuWriteView(); auto buffer_view = tensor.GetCpuWriteView();
cv::Mat dst(output_dims.height, output_dims.width, CV_32FC3, cv::Mat dst;
buffer_view.buffer<float>()); switch (tensor_type_) {
case Tensor::ElementType::kInt8:
dst = cv::Mat(output_dims.height, output_dims.width, mat_type_,
buffer_view.buffer<int8>());
break;
case Tensor::ElementType::kFloat32:
dst = cv::Mat(output_dims.height, output_dims.width, mat_type_,
buffer_view.buffer<float>());
break;
case Tensor::ElementType::kUInt8:
dst = cv::Mat(output_dims.height, output_dims.width, mat_type_,
buffer_view.buffer<uint8>());
break;
default:
return InvalidArgumentError(
absl::StrCat("Unsupported tensor type: ", tensor_type_));
}
const cv::RotatedRect rotated_rect(cv::Point2f(roi.center_x, roi.center_y), const cv::RotatedRect rotated_rect(cv::Point2f(roi.center_x, roi.center_y),
cv::Size2f(roi.width, roi.height), cv::Size2f(roi.width, roi.height),
@@ -85,7 +114,7 @@ class OpenCvProcessor : public ImageToTensorConverter {
cv::Mat projection_matrix = cv::Mat projection_matrix =
cv::getPerspectiveTransform(src_points, dst_points); cv::getPerspectiveTransform(src_points, dst_points);
cv::Mat transformed; cv::Mat transformed;
cv::warpPerspective(src, transformed, projection_matrix, cv::warpPerspective(*src, transformed, projection_matrix,
cv::Size(dst_width, dst_height), cv::Size(dst_width, dst_height),
/*flags=*/cv::INTER_LINEAR, /*flags=*/cv::INTER_LINEAR,
/*borderMode=*/border_mode_); /*borderMode=*/border_mode_);
@@ -102,19 +131,29 @@ class OpenCvProcessor : public ImageToTensorConverter {
auto transform, auto transform,
GetValueRangeTransformation(kInputImageRangeMin, kInputImageRangeMax, GetValueRangeTransformation(kInputImageRangeMin, kInputImageRangeMax,
range_min, range_max)); range_min, range_max));
transformed.convertTo(dst, CV_32FC3, transform.scale, transform.offset); transformed.convertTo(dst, mat_type_, transform.scale, transform.offset);
return tensor; return tensor;
} }
private: private:
enum cv::BorderTypes border_mode_; enum cv::BorderTypes border_mode_;
Tensor::ElementType tensor_type_;
int mat_type_;
}; };
} // namespace } // namespace
absl::StatusOr<std::unique_ptr<ImageToTensorConverter>> CreateOpenCvConverter( absl::StatusOr<std::unique_ptr<ImageToTensorConverter>> CreateOpenCvConverter(
CalculatorContext* cc, BorderMode border_mode) { CalculatorContext* cc, BorderMode border_mode,
return absl::make_unique<OpenCvProcessor>(border_mode); Tensor::ElementType tensor_type) {
if (tensor_type != Tensor::ElementType::kInt8 &&
tensor_type != Tensor::ElementType::kFloat32 &&
tensor_type != Tensor::ElementType::kUInt8) {
return absl::InvalidArgumentError(absl::StrCat(
"Tensor type is currently not supported by OpenCvProcessor, type: ",
tensor_type));
}
return absl::make_unique<OpenCvProcessor>(border_mode, tensor_type);
} }
} // namespace mediapipe } // namespace mediapipe
@@ -25,7 +25,8 @@ namespace mediapipe {
// Creates OpenCV image-to-tensor converter. // Creates OpenCV image-to-tensor converter.
absl::StatusOr<std::unique_ptr<ImageToTensorConverter>> CreateOpenCvConverter( absl::StatusOr<std::unique_ptr<ImageToTensorConverter>> CreateOpenCvConverter(
CalculatorContext* cc, BorderMode border_mode); CalculatorContext* cc, BorderMode border_mode,
Tensor::ElementType tensor_type);
} // namespace mediapipe } // namespace mediapipe
@@ -21,7 +21,9 @@
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
#include "absl/strings/string_view.h" #include "absl/strings/string_view.h"
#include "mediapipe/framework/api2/packet.h"
#include "mediapipe/framework/tool/subgraph_expansion.h" #include "mediapipe/framework/tool/subgraph_expansion.h"
#include "tensorflow/lite/core/api/op_resolver.h"
namespace mediapipe { namespace mediapipe {
namespace api2 { namespace api2 {
@@ -36,6 +38,7 @@ class InferenceCalculatorSelectorImpl
Subgraph::GetOptions<mediapipe::InferenceCalculatorOptions>( Subgraph::GetOptions<mediapipe::InferenceCalculatorOptions>(
subgraph_node); subgraph_node);
std::vector<absl::string_view> impls; std::vector<absl::string_view> impls;
const bool should_use_gpu = const bool should_use_gpu =
!options.has_delegate() || // Use GPU delegate if not specified !options.has_delegate() || // Use GPU delegate if not specified
(options.has_delegate() && options.delegate().has_gpu()); (options.has_delegate() && options.delegate().has_gpu());
@@ -66,5 +69,17 @@ absl::StatusOr<Packet<TfLiteModelPtr>> InferenceCalculator::GetModelAsPacket(
"Must specify TFLite model as path or loaded model."); "Must specify TFLite model as path or loaded model.");
} }
absl::StatusOr<Packet<tflite::OpResolver>>
InferenceCalculator::GetOpResolverAsPacket(CalculatorContext* cc) {
if (kSideInOpResolver(cc).IsConnected()) {
return kSideInOpResolver(cc).As<tflite::OpResolver>();
} else if (kSideInCustomOpResolver(cc).IsConnected()) {
return kSideInCustomOpResolver(cc).As<tflite::OpResolver>();
}
return PacketAdopting<tflite::OpResolver>(
std::make_unique<
tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates>());
}
} // namespace api2 } // namespace api2
} // namespace mediapipe } // namespace mediapipe
@@ -27,6 +27,7 @@
#include "mediapipe/framework/formats/tensor.h" #include "mediapipe/framework/formats/tensor.h"
#include "mediapipe/framework/port/ret_check.h" #include "mediapipe/framework/port/ret_check.h"
#include "mediapipe/util/tflite/tflite_model_loader.h" #include "mediapipe/util/tflite/tflite_model_loader.h"
#include "tensorflow/lite/core/api/op_resolver.h"
#include "tensorflow/lite/error_reporter.h" #include "tensorflow/lite/error_reporter.h"
#include "tensorflow/lite/interpreter.h" #include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/kernels/register.h" #include "tensorflow/lite/kernels/register.h"
@@ -55,8 +56,11 @@ namespace api2 {
// TENSORS - Vector of Tensors // TENSORS - Vector of Tensors
// //
// Input side packet: // Input side packet:
// DEPRECATED: Prefer to use the "OP_RESOLVER" input side packet instead.
// CUSTOM_OP_RESOLVER (optional) - Use a custom op resolver, // CUSTOM_OP_RESOLVER (optional) - Use a custom op resolver,
// instead of the builtin one. // instead of the builtin one.
// OP_RESOLVER (optional) - Use to provide tflite op resolver
// (tflite::OpResolver)
// MODEL (optional) - Use to specify TfLite model // MODEL (optional) - Use to specify TfLite model
// (std::unique_ptr<tflite::FlatBufferModel, // (std::unique_ptr<tflite::FlatBufferModel,
// std::function<void(tflite::FlatBufferModel*)>>) // std::function<void(tflite::FlatBufferModel*)>>)
@@ -95,15 +99,21 @@ namespace api2 {
class InferenceCalculator : public NodeIntf { class InferenceCalculator : public NodeIntf {
public: public:
static constexpr Input<std::vector<Tensor>> kInTensors{"TENSORS"}; static constexpr Input<std::vector<Tensor>> kInTensors{"TENSORS"};
// Deprecated. Prefers to use "OP_RESOLVER" input side packet instead.
// TODO: Removes the "CUSTOM_OP_RESOLVER" side input after the
// migration.
static constexpr SideInput<tflite::ops::builtin::BuiltinOpResolver>::Optional static constexpr SideInput<tflite::ops::builtin::BuiltinOpResolver>::Optional
kSideInCustomOpResolver{"CUSTOM_OP_RESOLVER"}; kSideInCustomOpResolver{"CUSTOM_OP_RESOLVER"};
static constexpr SideInput<tflite::OpResolver>::Optional kSideInOpResolver{
"OP_RESOLVER"};
static constexpr SideInput<TfLiteModelPtr>::Optional kSideInModel{"MODEL"}; static constexpr SideInput<TfLiteModelPtr>::Optional kSideInModel{"MODEL"};
static constexpr Output<std::vector<Tensor>> kOutTensors{"TENSORS"}; static constexpr Output<std::vector<Tensor>> kOutTensors{"TENSORS"};
static constexpr SideInput< static constexpr SideInput<
mediapipe::InferenceCalculatorOptions::Delegate>::Optional kDelegate{ mediapipe::InferenceCalculatorOptions::Delegate>::Optional kDelegate{
"DELEGATE"}; "DELEGATE"};
MEDIAPIPE_NODE_CONTRACT(kInTensors, kSideInCustomOpResolver, kSideInModel, MEDIAPIPE_NODE_CONTRACT(kInTensors, kSideInCustomOpResolver,
kOutTensors, kDelegate); kSideInOpResolver, kSideInModel, kOutTensors,
kDelegate);
protected: protected:
using TfLiteDelegatePtr = using TfLiteDelegatePtr =
@@ -111,6 +121,9 @@ class InferenceCalculator : public NodeIntf {
absl::StatusOr<Packet<TfLiteModelPtr>> GetModelAsPacket( absl::StatusOr<Packet<TfLiteModelPtr>> GetModelAsPacket(
CalculatorContext* cc); CalculatorContext* cc);
absl::StatusOr<Packet<tflite::OpResolver>> GetOpResolverAsPacket(
CalculatorContext* cc);
}; };
struct InferenceCalculatorSelector : public InferenceCalculator { struct InferenceCalculatorSelector : public InferenceCalculator {
@@ -34,7 +34,6 @@ option java_outer_classname = "InferenceCalculatorProto";
// } // }
// } // }
// } // }
//
message InferenceCalculatorOptions { message InferenceCalculatorOptions {
extend mediapipe.CalculatorOptions { extend mediapipe.CalculatorOptions {
optional InferenceCalculatorOptions ext = 336783863; optional InferenceCalculatorOptions ext = 336783863;
@@ -69,8 +68,30 @@ message InferenceCalculatorOptions {
// Load pre-compiled serialized binary cache to accelerate init process. // Load pre-compiled serialized binary cache to accelerate init process.
// Only available for OpenCL delegate on Android. // Only available for OpenCL delegate on Android.
// Kernel caching will only be enabled if this path is set. // Kernel caching will only be enabled if this path is set.
//
// NOTE: binary cache usage may be skipped if valid serialized model,
// specified by "serialized_model_dir", exists.
//
// TODO: update to cached_kernel_dir
optional string cached_kernel_path = 2; optional string cached_kernel_path = 2;
// A dir to load from and save to a pre-compiled serialized model used to
// accelerate init process.
//
// NOTE: available for OpenCL delegate on Android only when
// "use_advanced_gpu_api" is set to true and "model_token" is set
// properly.
//
// NOTE: serialized model takes precedence over binary cache
// specified by "cached_kernel_path", which still can be used if
// serialized model is invalid or missing.
optional string serialized_model_dir = 7;
// Unique token identifying the model. Used in conjunction with
// "serialized_model_dir". It is the caller's responsibility to ensure
// there is no clash of the tokens.
optional string model_token = 8;
// Encapsulated compilation/runtime tradeoffs. // Encapsulated compilation/runtime tradeoffs.
enum InferenceUsage { enum InferenceUsage {
UNSPECIFIED = 0; UNSPECIFIED = 0;
@@ -95,6 +116,9 @@ message InferenceCalculatorOptions {
// to ensure there is no clash of the tokens. If unspecified, NNAPI will // to ensure there is no clash of the tokens. If unspecified, NNAPI will
// not try caching the compilation. // not try caching the compilation.
optional string model_token = 2; optional string model_token = 2;
// The name of an accelerator to be used for NNAPI delegate, e.g.
// "google-edgetpu". When not specified, it will be selected by NNAPI.
optional string accelerator_name = 3;
} }
message Xnnpack { message Xnnpack {
// Number of threads for XNNPACK delegate. (By default, calculator tries // Number of threads for XNNPACK delegate. (By default, calculator tries
@@ -19,7 +19,7 @@
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
#include "mediapipe/calculators/tensor/inference_calculator.h" #include "mediapipe/calculators/tensor/inference_calculator.h"
#include "tensorflow/lite/interpreter_builder.h"
#if defined(MEDIAPIPE_ANDROID) #if defined(MEDIAPIPE_ANDROID)
#include "tensorflow/lite/delegates/nnapi/nnapi_delegate.h" #include "tensorflow/lite/delegates/nnapi/nnapi_delegate.h"
#endif // ANDROID #endif // ANDROID
@@ -28,6 +28,7 @@
#include "mediapipe/util/cpu_util.h" #include "mediapipe/util/cpu_util.h"
#endif // !__EMSCRIPTEN__ || __EMSCRIPTEN_PTHREADS__ #endif // !__EMSCRIPTEN__ || __EMSCRIPTEN_PTHREADS__
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h" #include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
namespace mediapipe { namespace mediapipe {
@@ -61,6 +62,17 @@ int GetXnnpackNumThreads(
return GetXnnpackDefaultNumThreads(); return GetXnnpackDefaultNumThreads();
} }
template <typename T>
void CopyTensorBuffer(const Tensor& input_tensor,
tflite::Interpreter* interpreter,
int input_tensor_index) {
auto input_tensor_view = input_tensor.GetCpuReadView();
auto input_tensor_buffer = input_tensor_view.buffer<T>();
T* local_tensor_buffer =
interpreter->typed_input_tensor<T>(input_tensor_index);
std::memcpy(local_tensor_buffer, input_tensor_buffer, input_tensor.bytes());
}
} // namespace } // namespace
class InferenceCalculatorCpuImpl class InferenceCalculatorCpuImpl
@@ -73,14 +85,16 @@ class InferenceCalculatorCpuImpl
absl::Status Close(CalculatorContext* cc) override; absl::Status Close(CalculatorContext* cc) override;
private: private:
absl::Status LoadModel(CalculatorContext* cc); absl::Status InitInterpreter(CalculatorContext* cc);
absl::Status LoadDelegate(CalculatorContext* cc); absl::Status LoadDelegate(CalculatorContext* cc,
absl::Status LoadDelegateAndAllocateTensors(CalculatorContext* cc); tflite::InterpreterBuilder* interpreter_builder);
absl::Status AllocateTensors();
// TfLite requires us to keep the model alive as long as the interpreter is. // TfLite requires us to keep the model alive as long as the interpreter is.
Packet<TfLiteModelPtr> model_packet_; Packet<TfLiteModelPtr> model_packet_;
std::unique_ptr<tflite::Interpreter> interpreter_; std::unique_ptr<tflite::Interpreter> interpreter_;
TfLiteDelegatePtr delegate_; TfLiteDelegatePtr delegate_;
TfLiteType input_tensor_type_ = TfLiteType::kTfLiteNoType;
}; };
absl::Status InferenceCalculatorCpuImpl::UpdateContract( absl::Status InferenceCalculatorCpuImpl::UpdateContract(
@@ -93,8 +107,7 @@ absl::Status InferenceCalculatorCpuImpl::UpdateContract(
} }
absl::Status InferenceCalculatorCpuImpl::Open(CalculatorContext* cc) { absl::Status InferenceCalculatorCpuImpl::Open(CalculatorContext* cc) {
MP_RETURN_IF_ERROR(LoadModel(cc)); return InitInterpreter(cc);
return LoadDelegateAndAllocateTensors(cc);
} }
absl::Status InferenceCalculatorCpuImpl::Process(CalculatorContext* cc) { absl::Status InferenceCalculatorCpuImpl::Process(CalculatorContext* cc) {
@@ -107,12 +120,24 @@ absl::Status InferenceCalculatorCpuImpl::Process(CalculatorContext* cc) {
// Read CPU input into tensors. // Read CPU input into tensors.
for (int i = 0; i < input_tensors.size(); ++i) { for (int i = 0; i < input_tensors.size(); ++i) {
const Tensor* input_tensor = &input_tensors[i]; switch (input_tensor_type_) {
auto input_tensor_view = input_tensor->GetCpuReadView(); case TfLiteType::kTfLiteFloat16:
auto input_tensor_buffer = input_tensor_view.buffer<float>(); case TfLiteType::kTfLiteFloat32: {
float* local_tensor_buffer = interpreter_->typed_input_tensor<float>(i); CopyTensorBuffer<float>(input_tensors[i], interpreter_.get(), i);
std::memcpy(local_tensor_buffer, input_tensor_buffer, break;
input_tensor->bytes()); }
case TfLiteType::kTfLiteUInt8: {
CopyTensorBuffer<uint8>(input_tensors[i], interpreter_.get(), i);
break;
}
case TfLiteType::kTfLiteInt8: {
CopyTensorBuffer<int8>(input_tensors[i], interpreter_.get(), i);
break;
}
default:
return absl::InvalidArgumentError(
absl::StrCat("Unsupported input tensor type:", input_tensor_type_));
}
} }
// Run inference. // Run inference.
@@ -141,40 +166,34 @@ absl::Status InferenceCalculatorCpuImpl::Close(CalculatorContext* cc) {
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status InferenceCalculatorCpuImpl::LoadModel(CalculatorContext* cc) { absl::Status InferenceCalculatorCpuImpl::InitInterpreter(
CalculatorContext* cc) {
ASSIGN_OR_RETURN(model_packet_, GetModelAsPacket(cc)); ASSIGN_OR_RETURN(model_packet_, GetModelAsPacket(cc));
const auto& model = *model_packet_.Get(); const auto& model = *model_packet_.Get();
tflite::ops::builtin::BuiltinOpResolver op_resolver = ASSIGN_OR_RETURN(auto op_resolver_packet, GetOpResolverAsPacket(cc));
kSideInCustomOpResolver(cc).GetOr( const auto& op_resolver = op_resolver_packet.Get();
tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates()); tflite::InterpreterBuilder interpreter_builder(model, op_resolver);
MP_RETURN_IF_ERROR(LoadDelegate(cc, &interpreter_builder));
tflite::InterpreterBuilder(model, op_resolver)(&interpreter_);
RET_CHECK(interpreter_);
#if defined(__EMSCRIPTEN__) #if defined(__EMSCRIPTEN__)
interpreter_->SetNumThreads(1); interpreter_builder.SetNumThreads(1);
#else #else
interpreter_->SetNumThreads( interpreter_builder.SetNumThreads(
cc->Options<mediapipe::InferenceCalculatorOptions>().cpu_num_thread()); cc->Options<mediapipe::InferenceCalculatorOptions>().cpu_num_thread());
#endif // __EMSCRIPTEN__ #endif // __EMSCRIPTEN__
return absl::OkStatus(); RET_CHECK_EQ(interpreter_builder(&interpreter_), kTfLiteOk);
RET_CHECK(interpreter_);
return AllocateTensors();
} }
absl::Status InferenceCalculatorCpuImpl::LoadDelegateAndAllocateTensors( absl::Status InferenceCalculatorCpuImpl::AllocateTensors() {
CalculatorContext* cc) {
MP_RETURN_IF_ERROR(LoadDelegate(cc));
// AllocateTensors() can be called only after ModifyGraphWithDelegate.
RET_CHECK_EQ(interpreter_->AllocateTensors(), kTfLiteOk); RET_CHECK_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
// TODO: Support quantized tensors. input_tensor_type_ = interpreter_->tensor(interpreter_->inputs()[0])->type;
RET_CHECK_NE(
interpreter_->tensor(interpreter_->inputs()[0])->quantization.type,
kTfLiteAffineQuantization);
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status InferenceCalculatorCpuImpl::LoadDelegate(CalculatorContext* cc) { absl::Status InferenceCalculatorCpuImpl::LoadDelegate(
CalculatorContext* cc, tflite::InterpreterBuilder* interpreter_builder) {
const auto& calculator_opts = const auto& calculator_opts =
cc->Options<mediapipe::InferenceCalculatorOptions>(); cc->Options<mediapipe::InferenceCalculatorOptions>();
auto opts_delegate = calculator_opts.delegate(); auto opts_delegate = calculator_opts.delegate();
@@ -203,18 +222,20 @@ absl::Status InferenceCalculatorCpuImpl::LoadDelegate(CalculatorContext* cc) {
if (nnapi_requested) { if (nnapi_requested) {
// Attempt to use NNAPI. // Attempt to use NNAPI.
// If not supported, the default CPU delegate will be created and used. // If not supported, the default CPU delegate will be created and used.
interpreter_->SetAllowFp16PrecisionForFp32(1);
tflite::StatefulNnApiDelegate::Options options; tflite::StatefulNnApiDelegate::Options options;
const auto& nnapi = opts_delegate.nnapi(); const auto& nnapi = opts_delegate.nnapi();
options.allow_fp16 = true;
// Set up cache_dir and model_token for NNAPI compilation cache. // Set up cache_dir and model_token for NNAPI compilation cache.
options.cache_dir = options.cache_dir =
nnapi.has_cache_dir() ? nnapi.cache_dir().c_str() : nullptr; nnapi.has_cache_dir() ? nnapi.cache_dir().c_str() : nullptr;
options.model_token = options.model_token =
nnapi.has_model_token() ? nnapi.model_token().c_str() : nullptr; nnapi.has_model_token() ? nnapi.model_token().c_str() : nullptr;
options.accelerator_name = nnapi.has_accelerator_name()
? nnapi.accelerator_name().c_str()
: nullptr;
delegate_ = TfLiteDelegatePtr(new tflite::StatefulNnApiDelegate(options), delegate_ = TfLiteDelegatePtr(new tflite::StatefulNnApiDelegate(options),
[](TfLiteDelegate*) {}); [](TfLiteDelegate*) {});
RET_CHECK_EQ(interpreter_->ModifyGraphWithDelegate(delegate_.get()), interpreter_builder->AddDelegate(delegate_.get());
kTfLiteOk);
return absl::OkStatus(); return absl::OkStatus();
} }
#endif // MEDIAPIPE_ANDROID #endif // MEDIAPIPE_ANDROID
@@ -226,13 +247,12 @@ absl::Status InferenceCalculatorCpuImpl::LoadDelegate(CalculatorContext* cc) {
#endif // defined(__EMSCRIPTEN__) #endif // defined(__EMSCRIPTEN__)
if (use_xnnpack) { if (use_xnnpack) {
TfLiteXNNPackDelegateOptions xnnpack_opts{}; auto xnnpack_opts = TfLiteXNNPackDelegateOptionsDefault();
xnnpack_opts.num_threads = xnnpack_opts.num_threads =
GetXnnpackNumThreads(opts_has_delegate, opts_delegate); GetXnnpackNumThreads(opts_has_delegate, opts_delegate);
delegate_ = TfLiteDelegatePtr(TfLiteXNNPackDelegateCreate(&xnnpack_opts), delegate_ = TfLiteDelegatePtr(TfLiteXNNPackDelegateCreate(&xnnpack_opts),
&TfLiteXNNPackDelegateDelete); &TfLiteXNNPackDelegateDelete);
RET_CHECK_EQ(interpreter_->ModifyGraphWithDelegate(delegate_.get()), interpreter_builder->AddDelegate(delegate_.get());
kTfLiteOk);
} }
return absl::OkStatus(); return absl::OkStatus();
@@ -154,8 +154,9 @@ TEST_P(InferenceCalculatorTest, TestFaceDetection) {
detection_packets[0].Get<std::vector<Detection>>(); detection_packets[0].Get<std::vector<Detection>>();
#if !defined(MEDIAPIPE_PROTO_LITE) #if !defined(MEDIAPIPE_PROTO_LITE)
// Approximately is not available with lite protos (b/178137094). // Approximately is not available with lite protos (b/178137094).
EXPECT_THAT(dets, constexpr float kEpison = 0.001;
ElementsAre(Approximately(EqualsProto(expected_detection)))); EXPECT_THAT(dets, ElementsAre(Approximately(EqualsProto(expected_detection),
kEpison)));
#endif #endif
} }
@@ -20,6 +20,7 @@
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
#include "absl/status/status.h" #include "absl/status/status.h"
#include "mediapipe/calculators/tensor/inference_calculator.h" #include "mediapipe/calculators/tensor/inference_calculator.h"
#include "mediapipe/framework/deps/file_path.h"
#include "mediapipe/util/tflite/config.h" #include "mediapipe/util/tflite/config.h"
#if MEDIAPIPE_TFLITE_GL_INFERENCE #if MEDIAPIPE_TFLITE_GL_INFERENCE
@@ -49,8 +50,8 @@ class InferenceCalculatorGlImpl
absl::Status Close(CalculatorContext* cc) override; absl::Status Close(CalculatorContext* cc) override;
private: private:
absl::Status ReadKernelsFromFile(); absl::Status ReadGpuCaches();
absl::Status WriteKernelsToFile(); absl::Status SaveGpuCaches();
absl::Status LoadModel(CalculatorContext* cc); absl::Status LoadModel(CalculatorContext* cc);
absl::Status LoadDelegate(CalculatorContext* cc); absl::Status LoadDelegate(CalculatorContext* cc);
absl::Status LoadDelegateAndAllocateTensors(CalculatorContext* cc); absl::Status LoadDelegateAndAllocateTensors(CalculatorContext* cc);
@@ -58,8 +59,6 @@ class InferenceCalculatorGlImpl
// TfLite requires us to keep the model alive as long as the interpreter is. // TfLite requires us to keep the model alive as long as the interpreter is.
Packet<TfLiteModelPtr> model_packet_; Packet<TfLiteModelPtr> model_packet_;
std::unique_ptr<tflite::Interpreter> interpreter_;
TfLiteDelegatePtr delegate_;
#if MEDIAPIPE_TFLITE_GL_INFERENCE #if MEDIAPIPE_TFLITE_GL_INFERENCE
mediapipe::GlCalculatorHelper gpu_helper_; mediapipe::GlCalculatorHelper gpu_helper_;
@@ -71,6 +70,9 @@ class InferenceCalculatorGlImpl
tflite_gpu_runner_usage_; tflite_gpu_runner_usage_;
#endif // MEDIAPIPE_TFLITE_GL_INFERENCE #endif // MEDIAPIPE_TFLITE_GL_INFERENCE
TfLiteDelegatePtr delegate_;
std::unique_ptr<tflite::Interpreter> interpreter_;
#if MEDIAPIPE_TFLITE_GPU_SUPPORTED #if MEDIAPIPE_TFLITE_GPU_SUPPORTED
std::vector<Tensor::Shape> output_shapes_; std::vector<Tensor::Shape> output_shapes_;
std::vector<std::unique_ptr<Tensor>> gpu_buffers_in_; std::vector<std::unique_ptr<Tensor>> gpu_buffers_in_;
@@ -82,6 +84,8 @@ class InferenceCalculatorGlImpl
bool use_kernel_caching_ = false; bool use_kernel_caching_ = false;
std::string cached_kernel_filename_; std::string cached_kernel_filename_;
bool use_serialized_model_ = false;
std::string serialized_model_path_;
}; };
absl::Status InferenceCalculatorGlImpl::UpdateContract(CalculatorContract* cc) { absl::Status InferenceCalculatorGlImpl::UpdateContract(CalculatorContract* cc) {
@@ -114,6 +118,9 @@ absl::Status InferenceCalculatorGlImpl::Open(CalculatorContext* cc) {
tflite_gpu_runner_usage_ = delegate.gpu().usage(); tflite_gpu_runner_usage_ = delegate.gpu().usage();
use_kernel_caching_ = use_kernel_caching_ =
use_advanced_gpu_api_ && delegate.gpu().has_cached_kernel_path(); use_advanced_gpu_api_ && delegate.gpu().has_cached_kernel_path();
use_serialized_model_ = use_advanced_gpu_api_ &&
delegate.gpu().has_serialized_model_dir() &&
delegate.gpu().has_model_token();
use_gpu_delegate_ = !use_advanced_gpu_api_; use_gpu_delegate_ = !use_advanced_gpu_api_;
if (use_kernel_caching_) { if (use_kernel_caching_) {
@@ -123,6 +130,12 @@ absl::Status InferenceCalculatorGlImpl::Open(CalculatorContext* cc) {
".ker"; ".ker";
#endif // MEDIAPIPE_ANDROID #endif // MEDIAPIPE_ANDROID
} }
if (use_serialized_model_) {
#ifdef MEDIAPIPE_ANDROID
serialized_model_path_ = mediapipe::file::JoinPath(
delegate.gpu().serialized_model_dir(), delegate.gpu().model_token());
#endif // MEDIAPIPE_ANDROID
}
// When use_advanced_gpu_api_, model loading is handled in InitTFLiteGPURunner // When use_advanced_gpu_api_, model loading is handled in InitTFLiteGPURunner
// for everything. // for everything.
@@ -210,7 +223,7 @@ absl::Status InferenceCalculatorGlImpl::Process(CalculatorContext* cc) {
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status InferenceCalculatorGlImpl::WriteKernelsToFile() { absl::Status InferenceCalculatorGlImpl::SaveGpuCaches() {
#ifdef MEDIAPIPE_ANDROID #ifdef MEDIAPIPE_ANDROID
if (use_kernel_caching_) { if (use_kernel_caching_) {
// Save kernel file. // Save kernel file.
@@ -220,36 +233,58 @@ absl::Status InferenceCalculatorGlImpl::WriteKernelsToFile() {
MP_RETURN_IF_ERROR( MP_RETURN_IF_ERROR(
mediapipe::file::SetContents(cached_kernel_filename_, cache_str)); mediapipe::file::SetContents(cached_kernel_filename_, cache_str));
} }
if (use_serialized_model_) {
// Save serialized model file.
ASSIGN_OR_RETURN(std::vector<uint8_t> serialized_model_vec,
tflite_gpu_runner_->GetSerializedModel());
absl::string_view serialized_model(
reinterpret_cast<char*>(serialized_model_vec.data()),
serialized_model_vec.size());
MP_RETURN_IF_ERROR(
mediapipe::file::SetContents(serialized_model_path_, serialized_model));
}
#endif // MEDIAPIPE_ANDROID #endif // MEDIAPIPE_ANDROID
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status InferenceCalculatorGlImpl::Close(CalculatorContext* cc) { absl::Status InferenceCalculatorGlImpl::Close(CalculatorContext* cc) {
MP_RETURN_IF_ERROR(WriteKernelsToFile()); MP_RETURN_IF_ERROR(SaveGpuCaches());
if (use_gpu_delegate_) { if (use_gpu_delegate_) {
MP_RETURN_IF_ERROR(gpu_helper_.RunInGlContext([this]() -> Status { MP_RETURN_IF_ERROR(gpu_helper_.RunInGlContext([this]() -> Status {
gpu_buffers_in_.clear(); gpu_buffers_in_.clear();
gpu_buffers_out_.clear(); gpu_buffers_out_.clear();
// Delegate must outlive the interpreter, hence the order is important.
interpreter_ = nullptr;
delegate_ = nullptr;
return absl::OkStatus(); return absl::OkStatus();
})); }));
} else {
// Delegate must outlive the interpreter, hence the order is important.
interpreter_ = nullptr;
delegate_ = nullptr;
} }
interpreter_ = nullptr;
delegate_ = nullptr;
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status InferenceCalculatorGlImpl::ReadKernelsFromFile() { absl::Status InferenceCalculatorGlImpl::ReadGpuCaches() {
#ifdef MEDIAPIPE_ANDROID #ifdef MEDIAPIPE_ANDROID
if (use_kernel_caching_) { if (use_kernel_caching_ && File::Exists(cached_kernel_filename_)) {
// Load pre-compiled kernel file. // Load pre-compiled kernel file.
if (mediapipe::File::Exists(cached_kernel_filename_)) { std::string cache_str;
std::string cache_str; MP_RETURN_IF_ERROR(
MP_RETURN_IF_ERROR( mediapipe::file::GetContents(cached_kernel_filename_, &cache_str));
mediapipe::file::GetContents(cached_kernel_filename_, &cache_str)); std::vector<uint8_t> cache_vec(cache_str.begin(), cache_str.end());
std::vector<uint8_t> cache_vec(cache_str.begin(), cache_str.end()); tflite_gpu_runner_->SetSerializedBinaryCache(std::move(cache_vec));
tflite_gpu_runner_->SetSerializedBinaryCache(std::move(cache_vec)); }
} if (use_serialized_model_ && File::Exists(serialized_model_path_)) {
// Load serialized model file.
std::string serialized_model_str;
MP_RETURN_IF_ERROR(
file::GetContents(serialized_model_path_, &serialized_model_str));
std::vector<uint8_t> serialized_model_vec(serialized_model_str.begin(),
serialized_model_str.end());
tflite_gpu_runner_->SetSerializedModel(std::move(serialized_model_vec));
} }
#endif // MEDIAPIPE_ANDROID #endif // MEDIAPIPE_ANDROID
return absl::OkStatus(); return absl::OkStatus();
@@ -259,9 +294,6 @@ absl::Status InferenceCalculatorGlImpl::InitTFLiteGPURunner(
CalculatorContext* cc) { CalculatorContext* cc) {
ASSIGN_OR_RETURN(model_packet_, GetModelAsPacket(cc)); ASSIGN_OR_RETURN(model_packet_, GetModelAsPacket(cc));
const auto& model = *model_packet_.Get(); const auto& model = *model_packet_.Get();
tflite::ops::builtin::BuiltinOpResolver op_resolver =
kSideInCustomOpResolver(cc).GetOr(
tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates());
// Create runner // Create runner
tflite::gpu::InferenceOptions options; tflite::gpu::InferenceOptions options;
@@ -300,8 +332,17 @@ absl::Status InferenceCalculatorGlImpl::InitTFLiteGPURunner(
break; break;
} }
} }
MP_RETURN_IF_ERROR(tflite_gpu_runner_->InitializeWithModel( if (kSideInOpResolver(cc).IsConnected()) {
model, op_resolver, /*allow_quant_ops=*/true)); const tflite::OpResolver& op_resolver = kSideInOpResolver(cc).Get();
MP_RETURN_IF_ERROR(tflite_gpu_runner_->InitializeWithModel(
model, op_resolver, /*allow_quant_ops=*/true));
} else {
tflite::ops::builtin::BuiltinOpResolver op_resolver =
kSideInCustomOpResolver(cc).GetOr(
tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates());
MP_RETURN_IF_ERROR(tflite_gpu_runner_->InitializeWithModel(
model, op_resolver, /*allow_quant_ops=*/true));
}
// Create and bind OpenGL buffers for outputs. // Create and bind OpenGL buffers for outputs.
// The buffers are created once and their ids are passed to calculator outputs // The buffers are created once and their ids are passed to calculator outputs
@@ -313,7 +354,7 @@ absl::Status InferenceCalculatorGlImpl::InitTFLiteGPURunner(
tflite_gpu_runner_->GetOutputShapes()[i].c}; tflite_gpu_runner_->GetOutputShapes()[i].c};
} }
MP_RETURN_IF_ERROR(ReadKernelsFromFile()); MP_RETURN_IF_ERROR(ReadGpuCaches());
MP_RETURN_IF_ERROR(tflite_gpu_runner_->Build()); MP_RETURN_IF_ERROR(tflite_gpu_runner_->Build());
@@ -323,11 +364,15 @@ absl::Status InferenceCalculatorGlImpl::InitTFLiteGPURunner(
absl::Status InferenceCalculatorGlImpl::LoadModel(CalculatorContext* cc) { absl::Status InferenceCalculatorGlImpl::LoadModel(CalculatorContext* cc) {
ASSIGN_OR_RETURN(model_packet_, GetModelAsPacket(cc)); ASSIGN_OR_RETURN(model_packet_, GetModelAsPacket(cc));
const auto& model = *model_packet_.Get(); const auto& model = *model_packet_.Get();
tflite::ops::builtin::BuiltinOpResolver op_resolver = if (kSideInOpResolver(cc).IsConnected()) {
kSideInCustomOpResolver(cc).GetOr( const tflite::OpResolver& op_resolver = kSideInOpResolver(cc).Get();
tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates()); tflite::InterpreterBuilder(model, op_resolver)(&interpreter_);
} else {
tflite::InterpreterBuilder(model, op_resolver)(&interpreter_); tflite::ops::builtin::BuiltinOpResolver op_resolver =
kSideInCustomOpResolver(cc).GetOr(
tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates());
tflite::InterpreterBuilder(model, op_resolver)(&interpreter_);
}
RET_CHECK(interpreter_); RET_CHECK(interpreter_);
#if defined(__EMSCRIPTEN__) #if defined(__EMSCRIPTEN__)
@@ -90,9 +90,10 @@ class InferenceCalculatorMetalImpl
absl::Status Close(CalculatorContext* cc) override; absl::Status Close(CalculatorContext* cc) override;
private: private:
absl::Status LoadModel(CalculatorContext* cc); absl::Status InitInterpreter(CalculatorContext* cc);
absl::Status LoadDelegate(CalculatorContext* cc); void AddDelegate(CalculatorContext* cc,
absl::Status LoadDelegateAndAllocateTensors(CalculatorContext* cc); tflite::InterpreterBuilder* interpreter_builder);
absl::Status CreateConverters(CalculatorContext* cc);
// TfLite requires us to keep the model alive as long as the interpreter is. // TfLite requires us to keep the model alive as long as the interpreter is.
Packet<TfLiteModelPtr> model_packet_; Packet<TfLiteModelPtr> model_packet_;
@@ -127,11 +128,9 @@ absl::Status InferenceCalculatorMetalImpl::Open(CalculatorContext* cc) {
const auto& options = cc->Options<::mediapipe::InferenceCalculatorOptions>(); const auto& options = cc->Options<::mediapipe::InferenceCalculatorOptions>();
allow_precision_loss_ = options.delegate().gpu().allow_precision_loss(); allow_precision_loss_ = options.delegate().gpu().allow_precision_loss();
MP_RETURN_IF_ERROR(LoadModel(cc));
gpu_helper_ = [[MPPMetalHelper alloc] initWithCalculatorContext:cc]; gpu_helper_ = [[MPPMetalHelper alloc] initWithCalculatorContext:cc];
RET_CHECK(gpu_helper_); RET_CHECK(gpu_helper_);
return LoadDelegateAndAllocateTensors(cc); return InitInterpreter(cc);
} }
absl::Status InferenceCalculatorMetalImpl::Process(CalculatorContext* cc) { absl::Status InferenceCalculatorMetalImpl::Process(CalculatorContext* cc) {
@@ -199,27 +198,20 @@ absl::Status InferenceCalculatorMetalImpl::Close(CalculatorContext* cc) {
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status InferenceCalculatorMetalImpl::LoadModel(CalculatorContext* cc) { absl::Status InferenceCalculatorMetalImpl::InitInterpreter(
CalculatorContext* cc) {
ASSIGN_OR_RETURN(model_packet_, GetModelAsPacket(cc)); ASSIGN_OR_RETURN(model_packet_, GetModelAsPacket(cc));
const auto& model = *model_packet_.Get(); const auto& model = *model_packet_.Get();
tflite::ops::builtin::BuiltinOpResolver op_resolver = ASSIGN_OR_RETURN(auto op_resolver_packet, GetOpResolverAsPacket(cc));
kSideInCustomOpResolver(cc).GetOr( const auto& op_resolver = op_resolver_packet.Get();
tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates()); tflite::InterpreterBuilder interpreter_builder(model, op_resolver);
AddDelegate(cc, &interpreter_builder);
tflite::InterpreterBuilder(model, op_resolver)(&interpreter_); interpreter_builder.SetNumThreads(
cc->Options<mediapipe::InferenceCalculatorOptions>().cpu_num_thread());
RET_CHECK_EQ(interpreter_builder(&interpreter_), kTfLiteOk);
RET_CHECK(interpreter_); RET_CHECK(interpreter_);
interpreter_->SetNumThreads( MP_RETURN_IF_ERROR(CreateConverters(cc));
cc->Options<mediapipe::InferenceCalculatorOptions>().cpu_num_thread());
return absl::OkStatus();
}
absl::Status InferenceCalculatorMetalImpl::LoadDelegateAndAllocateTensors(
CalculatorContext* cc) {
MP_RETURN_IF_ERROR(LoadDelegate(cc));
// AllocateTensors() can be called only after ModifyGraphWithDelegate.
RET_CHECK_EQ(interpreter_->AllocateTensors(), kTfLiteOk); RET_CHECK_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
// TODO: Support quantized tensors. // TODO: Support quantized tensors.
RET_CHECK_NE( RET_CHECK_NE(
@@ -228,7 +220,8 @@ absl::Status InferenceCalculatorMetalImpl::LoadDelegateAndAllocateTensors(
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status InferenceCalculatorMetalImpl::LoadDelegate(CalculatorContext* cc) { void InferenceCalculatorMetalImpl::AddDelegate(
CalculatorContext* cc, tflite::InterpreterBuilder* interpreter_builder) {
const auto& calculator_opts = const auto& calculator_opts =
cc->Options<mediapipe::InferenceCalculatorOptions>(); cc->Options<mediapipe::InferenceCalculatorOptions>();
@@ -242,9 +235,11 @@ absl::Status InferenceCalculatorMetalImpl::LoadDelegate(CalculatorContext* cc) {
options.wait_type = TFLGpuDelegateWaitType::TFLGpuDelegateWaitTypeDoNotWait; options.wait_type = TFLGpuDelegateWaitType::TFLGpuDelegateWaitTypeDoNotWait;
delegate_ = delegate_ =
TfLiteDelegatePtr(TFLGpuDelegateCreate(&options), &TFLGpuDelegateDelete); TfLiteDelegatePtr(TFLGpuDelegateCreate(&options), &TFLGpuDelegateDelete);
RET_CHECK_EQ(interpreter_->ModifyGraphWithDelegate(delegate_.get()), interpreter_builder->AddDelegate(delegate_.get());
kTfLiteOk); }
absl::Status InferenceCalculatorMetalImpl::CreateConverters(
CalculatorContext* cc) {
id<MTLDevice> device = gpu_helper_.mtlDevice; id<MTLDevice> device = gpu_helper_.mtlDevice;
// Get input image sizes. // Get input image sizes.
@@ -39,8 +39,6 @@
namespace mediapipe { namespace mediapipe {
using ::tflite::Interpreter;
void DoSmokeTest(const std::string& graph_proto) { void DoSmokeTest(const std::string& graph_proto) {
const int width = 8; const int width = 8;
const int height = 8; const int height = 8;
@@ -91,6 +91,40 @@ void ConvertAnchorsToRawValues(const std::vector<Anchor>& anchors,
} }
} }
absl::Status CheckCustomTensorMapping(
const TensorsToDetectionsCalculatorOptions::TensorMapping& tensor_mapping) {
RET_CHECK(tensor_mapping.has_detections_tensor_index() &&
tensor_mapping.has_scores_tensor_index());
int bitmap = 0;
bitmap |= 1 << tensor_mapping.detections_tensor_index();
bitmap |= 1 << tensor_mapping.scores_tensor_index();
if (!tensor_mapping.has_num_detections_tensor_index() &&
!tensor_mapping.has_classes_tensor_index() &&
!tensor_mapping.has_anchors_tensor_index()) {
// Only allows the output tensor index 0 and 1 to be occupied.
RET_CHECK_EQ(3, bitmap) << "The custom output tensor indices should only "
"cover index 0 and 1.";
} else if (tensor_mapping.has_anchors_tensor_index()) {
RET_CHECK(!tensor_mapping.has_classes_tensor_index() &&
!tensor_mapping.has_num_detections_tensor_index());
bitmap |= 1 << tensor_mapping.anchors_tensor_index();
// If the"anchors" tensor will be available, only allows the output tensor
// index 0, 1, 2 to be occupied.
RET_CHECK_EQ(7, bitmap) << "The custom output tensor indices should only "
"cover index 0, 1 and 2.";
} else {
RET_CHECK(tensor_mapping.has_classes_tensor_index() &&
tensor_mapping.has_num_detections_tensor_index());
// If the "classes" and the "number of detections" tensors will be
// available, only allows the output tensor index 0, 1, 2, 3 to be occupied.
bitmap |= 1 << tensor_mapping.classes_tensor_index();
bitmap |= 1 << tensor_mapping.num_detections_tensor_index();
RET_CHECK_EQ(15, bitmap) << "The custom output tensor indices should only "
"cover index 0, 1, 2 and 3.";
}
return absl::OkStatus();
}
} // namespace } // namespace
// Convert result Tensors from object detection models into MediaPipe // Convert result Tensors from object detection models into MediaPipe
@@ -170,13 +204,27 @@ class TensorsToDetectionsCalculator : public Node {
Detection ConvertToDetection(float box_ymin, float box_xmin, float box_ymax, Detection ConvertToDetection(float box_ymin, float box_xmin, float box_ymax,
float box_xmax, float score, int class_id, float box_xmax, float score, int class_id,
bool flip_vertically); bool flip_vertically);
bool IsClassIndexAllowed(int class_index);
int num_classes_ = 0; int num_classes_ = 0;
int num_boxes_ = 0; int num_boxes_ = 0;
int num_coords_ = 0; int num_coords_ = 0;
std::set<int> ignore_classes_; int max_results_ = -1;
::mediapipe::TensorsToDetectionsCalculatorOptions options_; // Set of allowed or ignored class indices.
struct ClassIndexSet {
absl::flat_hash_set<int> values;
bool is_allowlist;
};
// Allowed or ignored class indices based on provided options or side packet.
// These are used to filter out the output detection results.
ClassIndexSet class_index_set_;
TensorsToDetectionsCalculatorOptions options_;
bool scores_tensor_index_is_set_ = false;
TensorsToDetectionsCalculatorOptions::TensorMapping tensor_mapping_;
std::vector<int> box_indices_ = {0, 1, 2, 3};
bool has_custom_box_indices_ = false;
std::vector<Anchor> anchors_; std::vector<Anchor> anchors_;
#ifndef MEDIAPIPE_DISABLE_GL_COMPUTE #ifndef MEDIAPIPE_DISABLE_GL_COMPUTE
@@ -239,6 +287,21 @@ absl::Status TensorsToDetectionsCalculator::Process(CalculatorContext* cc) {
} }
} }
} }
const int num_input_tensors = kInTensors(cc)->size();
if (!scores_tensor_index_is_set_) {
if (num_input_tensors == 2 ||
num_input_tensors == kNumInputTensorsWithAnchors) {
tensor_mapping_.set_scores_tensor_index(1);
} else {
tensor_mapping_.set_scores_tensor_index(2);
}
scores_tensor_index_is_set_ = true;
}
if (gpu_processing || num_input_tensors != 4) {
// Allows custom bounding box indices when receiving 4 cpu tensors.
// Uses the default bbox indices in other cases.
RET_CHECK(!has_custom_box_indices_);
}
if (gpu_processing) { if (gpu_processing) {
if (!gpu_inited_) { if (!gpu_inited_) {
@@ -263,12 +326,15 @@ absl::Status TensorsToDetectionsCalculator::ProcessCPU(
// Postprocessing on CPU for model without postprocessing op. E.g. output // Postprocessing on CPU for model without postprocessing op. E.g. output
// raw score tensor and box tensor. Anchor decoding will be handled below. // raw score tensor and box tensor. Anchor decoding will be handled below.
// TODO: Add flexible input tensor size handling. // TODO: Add flexible input tensor size handling.
auto raw_box_tensor = &input_tensors[0]; auto raw_box_tensor =
&input_tensors[tensor_mapping_.detections_tensor_index()];
RET_CHECK_EQ(raw_box_tensor->shape().dims.size(), 3); RET_CHECK_EQ(raw_box_tensor->shape().dims.size(), 3);
RET_CHECK_EQ(raw_box_tensor->shape().dims[0], 1); RET_CHECK_EQ(raw_box_tensor->shape().dims[0], 1);
RET_CHECK_GT(num_boxes_, 0) << "Please set num_boxes in calculator options";
RET_CHECK_EQ(raw_box_tensor->shape().dims[1], num_boxes_); RET_CHECK_EQ(raw_box_tensor->shape().dims[1], num_boxes_);
RET_CHECK_EQ(raw_box_tensor->shape().dims[2], num_coords_); RET_CHECK_EQ(raw_box_tensor->shape().dims[2], num_coords_);
auto raw_score_tensor = &input_tensors[1]; auto raw_score_tensor =
&input_tensors[tensor_mapping_.scores_tensor_index()];
RET_CHECK_EQ(raw_score_tensor->shape().dims.size(), 3); RET_CHECK_EQ(raw_score_tensor->shape().dims.size(), 3);
RET_CHECK_EQ(raw_score_tensor->shape().dims[0], 1); RET_CHECK_EQ(raw_score_tensor->shape().dims[0], 1);
RET_CHECK_EQ(raw_score_tensor->shape().dims[1], num_boxes_); RET_CHECK_EQ(raw_score_tensor->shape().dims[1], num_boxes_);
@@ -281,7 +347,8 @@ absl::Status TensorsToDetectionsCalculator::ProcessCPU(
// TODO: Support other options to load anchors. // TODO: Support other options to load anchors.
if (!anchors_init_) { if (!anchors_init_) {
if (input_tensors.size() == kNumInputTensorsWithAnchors) { if (input_tensors.size() == kNumInputTensorsWithAnchors) {
auto anchor_tensor = &input_tensors[2]; auto anchor_tensor =
&input_tensors[tensor_mapping_.anchors_tensor_index()];
RET_CHECK_EQ(anchor_tensor->shape().dims.size(), 2); RET_CHECK_EQ(anchor_tensor->shape().dims.size(), 2);
RET_CHECK_EQ(anchor_tensor->shape().dims[0], num_boxes_); RET_CHECK_EQ(anchor_tensor->shape().dims[0], num_boxes_);
RET_CHECK_EQ(anchor_tensor->shape().dims[1], kNumCoordsPerBox); RET_CHECK_EQ(anchor_tensor->shape().dims[1], kNumCoordsPerBox);
@@ -307,7 +374,7 @@ absl::Status TensorsToDetectionsCalculator::ProcessCPU(
float max_score = -std::numeric_limits<float>::max(); float max_score = -std::numeric_limits<float>::max();
// Find the top score for box i. // Find the top score for box i.
for (int score_idx = 0; score_idx < num_classes_; ++score_idx) { for (int score_idx = 0; score_idx < num_classes_; ++score_idx) {
if (ignore_classes_.find(score_idx) == ignore_classes_.end()) { if (IsClassIndexAllowed(score_idx)) {
auto score = raw_scores[i * num_classes_ + score_idx]; auto score = raw_scores[i * num_classes_ + score_idx];
if (options_.sigmoid_score()) { if (options_.sigmoid_score()) {
if (options_.has_score_clipping_thresh()) { if (options_.has_score_clipping_thresh()) {
@@ -337,23 +404,26 @@ absl::Status TensorsToDetectionsCalculator::ProcessCPU(
// Postprocessing on CPU with postprocessing op (e.g. anchor decoding and // Postprocessing on CPU with postprocessing op (e.g. anchor decoding and
// non-maximum suppression) within the model. // non-maximum suppression) within the model.
RET_CHECK_EQ(input_tensors.size(), 4); RET_CHECK_EQ(input_tensors.size(), 4);
auto num_boxes_tensor =
auto num_boxes_tensor = &input_tensors[3]; &input_tensors[tensor_mapping_.num_detections_tensor_index()];
RET_CHECK_EQ(num_boxes_tensor->shape().dims.size(), 1); RET_CHECK_EQ(num_boxes_tensor->shape().dims.size(), 1);
RET_CHECK_EQ(num_boxes_tensor->shape().dims[0], 1); RET_CHECK_EQ(num_boxes_tensor->shape().dims[0], 1);
auto detection_boxes_tensor = &input_tensors[0]; auto detection_boxes_tensor =
&input_tensors[tensor_mapping_.detections_tensor_index()];
RET_CHECK_EQ(detection_boxes_tensor->shape().dims.size(), 3); RET_CHECK_EQ(detection_boxes_tensor->shape().dims.size(), 3);
RET_CHECK_EQ(detection_boxes_tensor->shape().dims[0], 1); RET_CHECK_EQ(detection_boxes_tensor->shape().dims[0], 1);
const int max_detections = detection_boxes_tensor->shape().dims[1]; const int max_detections = detection_boxes_tensor->shape().dims[1];
RET_CHECK_EQ(detection_boxes_tensor->shape().dims[2], num_coords_); RET_CHECK_EQ(detection_boxes_tensor->shape().dims[2], num_coords_);
auto detection_classes_tensor = &input_tensors[1]; auto detection_classes_tensor =
&input_tensors[tensor_mapping_.classes_tensor_index()];
RET_CHECK_EQ(detection_classes_tensor->shape().dims.size(), 2); RET_CHECK_EQ(detection_classes_tensor->shape().dims.size(), 2);
RET_CHECK_EQ(detection_classes_tensor->shape().dims[0], 1); RET_CHECK_EQ(detection_classes_tensor->shape().dims[0], 1);
RET_CHECK_EQ(detection_classes_tensor->shape().dims[1], max_detections); RET_CHECK_EQ(detection_classes_tensor->shape().dims[1], max_detections);
auto detection_scores_tensor = &input_tensors[2]; auto detection_scores_tensor =
&input_tensors[tensor_mapping_.scores_tensor_index()];
RET_CHECK_EQ(detection_scores_tensor->shape().dims.size(), 2); RET_CHECK_EQ(detection_scores_tensor->shape().dims.size(), 2);
RET_CHECK_EQ(detection_scores_tensor->shape().dims[0], 1); RET_CHECK_EQ(detection_scores_tensor->shape().dims[0], 1);
RET_CHECK_EQ(detection_scores_tensor->shape().dims[1], max_detections); RET_CHECK_EQ(detection_scores_tensor->shape().dims[1], max_detections);
@@ -385,6 +455,7 @@ absl::Status TensorsToDetectionsCalculator::ProcessGPU(
CalculatorContext* cc, std::vector<Detection>* output_detections) { CalculatorContext* cc, std::vector<Detection>* output_detections) {
const auto& input_tensors = *kInTensors(cc); const auto& input_tensors = *kInTensors(cc);
RET_CHECK_GE(input_tensors.size(), 2); RET_CHECK_GE(input_tensors.size(), 2);
RET_CHECK_GT(num_boxes_, 0) << "Please set num_boxes in calculator options";
#ifndef MEDIAPIPE_DISABLE_GL_COMPUTE #ifndef MEDIAPIPE_DISABLE_GL_COMPUTE
MP_RETURN_IF_ERROR(gpu_helper_.RunInGlContext([this, &input_tensors, &cc, MP_RETURN_IF_ERROR(gpu_helper_.RunInGlContext([this, &input_tensors, &cc,
@@ -392,12 +463,14 @@ absl::Status TensorsToDetectionsCalculator::ProcessGPU(
-> absl::Status { -> absl::Status {
if (!anchors_init_) { if (!anchors_init_) {
if (input_tensors.size() == kNumInputTensorsWithAnchors) { if (input_tensors.size() == kNumInputTensorsWithAnchors) {
auto read_view = input_tensors[2].GetOpenGlBufferReadView(); auto read_view = input_tensors[tensor_mapping_.anchors_tensor_index()]
.GetOpenGlBufferReadView();
glBindBuffer(GL_COPY_READ_BUFFER, read_view.name()); glBindBuffer(GL_COPY_READ_BUFFER, read_view.name());
auto write_view = raw_anchors_buffer_->GetOpenGlBufferWriteView(); auto write_view = raw_anchors_buffer_->GetOpenGlBufferWriteView();
glBindBuffer(GL_COPY_WRITE_BUFFER, write_view.name()); glBindBuffer(GL_COPY_WRITE_BUFFER, write_view.name());
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, glCopyBufferSubData(
input_tensors[2].bytes()); GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0,
input_tensors[tensor_mapping_.anchors_tensor_index()].bytes());
} else if (!kInAnchors(cc).IsEmpty()) { } else if (!kInAnchors(cc).IsEmpty()) {
const auto& anchors = *kInAnchors(cc); const auto& anchors = *kInAnchors(cc);
auto anchors_view = raw_anchors_buffer_->GetCpuWriteView(); auto anchors_view = raw_anchors_buffer_->GetCpuWriteView();
@@ -416,7 +489,9 @@ absl::Status TensorsToDetectionsCalculator::ProcessGPU(
auto decoded_boxes_view = auto decoded_boxes_view =
decoded_boxes_buffer_->GetOpenGlBufferWriteView(); decoded_boxes_buffer_->GetOpenGlBufferWriteView();
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, decoded_boxes_view.name()); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, decoded_boxes_view.name());
auto input0_view = input_tensors[0].GetOpenGlBufferReadView(); auto input0_view =
input_tensors[tensor_mapping_.detections_tensor_index()]
.GetOpenGlBufferReadView();
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, input0_view.name()); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, input0_view.name());
auto raw_anchors_view = raw_anchors_buffer_->GetOpenGlBufferReadView(); auto raw_anchors_view = raw_anchors_buffer_->GetOpenGlBufferReadView();
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, raw_anchors_view.name()); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, raw_anchors_view.name());
@@ -425,7 +500,8 @@ absl::Status TensorsToDetectionsCalculator::ProcessGPU(
// Score boxes. // Score boxes.
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, scored_boxes_view.name()); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, scored_boxes_view.name());
auto input1_view = input_tensors[1].GetOpenGlBufferReadView(); auto input1_view = input_tensors[tensor_mapping_.scores_tensor_index()]
.GetOpenGlBufferReadView();
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, input1_view.name()); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, input1_view.name());
glUseProgram(score_program_); glUseProgram(score_program_);
glDispatchCompute(num_boxes_, 1, 1); glDispatchCompute(num_boxes_, 1, 1);
@@ -457,7 +533,8 @@ absl::Status TensorsToDetectionsCalculator::ProcessGPU(
if (input_tensors.size() == kNumInputTensorsWithAnchors) { if (input_tensors.size() == kNumInputTensorsWithAnchors) {
RET_CHECK_EQ(input_tensors.size(), kNumInputTensorsWithAnchors); RET_CHECK_EQ(input_tensors.size(), kNumInputTensorsWithAnchors);
auto command_buffer = [gpu_helper_ commandBuffer]; auto command_buffer = [gpu_helper_ commandBuffer];
auto src_buffer = input_tensors[2].GetMtlBufferReadView(command_buffer); auto src_buffer = input_tensors[tensor_mapping_.anchors_tensor_index()]
.GetMtlBufferReadView(command_buffer);
auto dest_buffer = auto dest_buffer =
raw_anchors_buffer_->GetMtlBufferWriteView(command_buffer); raw_anchors_buffer_->GetMtlBufferWriteView(command_buffer);
id<MTLBlitCommandEncoder> blit_command = id<MTLBlitCommandEncoder> blit_command =
@@ -466,7 +543,9 @@ absl::Status TensorsToDetectionsCalculator::ProcessGPU(
sourceOffset:0 sourceOffset:0
toBuffer:dest_buffer.buffer() toBuffer:dest_buffer.buffer()
destinationOffset:0 destinationOffset:0
size:input_tensors[2].bytes()]; size:input_tensors[tensor_mapping_
.anchors_tensor_index()]
.bytes()];
[blit_command endEncoding]; [blit_command endEncoding];
[command_buffer commit]; [command_buffer commit];
} else if (!kInAnchors(cc).IsEmpty()) { } else if (!kInAnchors(cc).IsEmpty()) {
@@ -493,7 +572,8 @@ absl::Status TensorsToDetectionsCalculator::ProcessGPU(
auto decoded_boxes_view = auto decoded_boxes_view =
decoded_boxes_buffer_->GetMtlBufferWriteView(command_buffer); decoded_boxes_buffer_->GetMtlBufferWriteView(command_buffer);
[command_encoder setBuffer:decoded_boxes_view.buffer() offset:0 atIndex:0]; [command_encoder setBuffer:decoded_boxes_view.buffer() offset:0 atIndex:0];
auto input0_view = input_tensors[0].GetMtlBufferReadView(command_buffer); auto input0_view = input_tensors[tensor_mapping_.detections_tensor_index()]
.GetMtlBufferReadView(command_buffer);
[command_encoder setBuffer:input0_view.buffer() offset:0 atIndex:1]; [command_encoder setBuffer:input0_view.buffer() offset:0 atIndex:1];
auto raw_anchors_view = auto raw_anchors_view =
raw_anchors_buffer_->GetMtlBufferReadView(command_buffer); raw_anchors_buffer_->GetMtlBufferReadView(command_buffer);
@@ -505,7 +585,8 @@ absl::Status TensorsToDetectionsCalculator::ProcessGPU(
[command_encoder setComputePipelineState:score_program_]; [command_encoder setComputePipelineState:score_program_];
[command_encoder setBuffer:scored_boxes_view.buffer() offset:0 atIndex:0]; [command_encoder setBuffer:scored_boxes_view.buffer() offset:0 atIndex:0];
auto input1_view = input_tensors[1].GetMtlBufferReadView(command_buffer); auto input1_view = input_tensors[tensor_mapping_.scores_tensor_index()]
.GetMtlBufferReadView(command_buffer);
[command_encoder setBuffer:input1_view.buffer() offset:0 atIndex:1]; [command_encoder setBuffer:input1_view.buffer() offset:0 atIndex:1];
MTLSize score_threads_per_group = MTLSizeMake(1, num_classes_, 1); MTLSize score_threads_per_group = MTLSizeMake(1, num_classes_, 1);
MTLSize score_threadgroups = MTLSizeMake(num_boxes_, 1, 1); MTLSize score_threadgroups = MTLSizeMake(num_boxes_, 1, 1);
@@ -563,12 +644,15 @@ absl::Status TensorsToDetectionsCalculator::LoadOptions(CalculatorContext* cc) {
// Get calculator options specified in the graph. // Get calculator options specified in the graph.
options_ = cc->Options<::mediapipe::TensorsToDetectionsCalculatorOptions>(); options_ = cc->Options<::mediapipe::TensorsToDetectionsCalculatorOptions>();
RET_CHECK(options_.has_num_classes()); RET_CHECK(options_.has_num_classes());
RET_CHECK(options_.has_num_boxes());
RET_CHECK(options_.has_num_coords()); RET_CHECK(options_.has_num_coords());
num_classes_ = options_.num_classes(); num_classes_ = options_.num_classes();
num_boxes_ = options_.num_boxes(); num_boxes_ = options_.num_boxes();
num_coords_ = options_.num_coords(); num_coords_ = options_.num_coords();
CHECK_NE(options_.max_results(), 0)
<< "The maximum number of the top-scored detection results must be "
"non-zero.";
max_results_ = options_.max_results();
// Currently only support 2D when num_values_per_keypoint equals to 2. // Currently only support 2D when num_values_per_keypoint equals to 2.
CHECK_EQ(options_.num_values_per_keypoint(), 2); CHECK_EQ(options_.num_values_per_keypoint(), 2);
@@ -580,15 +664,55 @@ absl::Status TensorsToDetectionsCalculator::LoadOptions(CalculatorContext* cc) {
if (kSideInIgnoreClasses(cc).IsConnected()) { if (kSideInIgnoreClasses(cc).IsConnected()) {
RET_CHECK(!kSideInIgnoreClasses(cc).IsEmpty()); RET_CHECK(!kSideInIgnoreClasses(cc).IsEmpty());
RET_CHECK(options_.allow_classes().empty());
class_index_set_.is_allowlist = false;
for (int ignore_class : *kSideInIgnoreClasses(cc)) { for (int ignore_class : *kSideInIgnoreClasses(cc)) {
ignore_classes_.insert(ignore_class); class_index_set_.values.insert(ignore_class);
}
} else if (!options_.allow_classes().empty()) {
RET_CHECK(options_.ignore_classes().empty());
class_index_set_.is_allowlist = true;
for (int i = 0; i < options_.allow_classes_size(); ++i) {
class_index_set_.values.insert(options_.allow_classes(i));
} }
} else { } else {
class_index_set_.is_allowlist = false;
for (int i = 0; i < options_.ignore_classes_size(); ++i) { for (int i = 0; i < options_.ignore_classes_size(); ++i) {
ignore_classes_.insert(options_.ignore_classes(i)); class_index_set_.values.insert(options_.ignore_classes(i));
} }
} }
if (options_.has_tensor_mapping()) {
RET_CHECK_OK(CheckCustomTensorMapping(options_.tensor_mapping()));
tensor_mapping_ = options_.tensor_mapping();
scores_tensor_index_is_set_ = true;
} else {
// Assigns the default tensor indices.
tensor_mapping_.set_detections_tensor_index(0);
tensor_mapping_.set_classes_tensor_index(1);
tensor_mapping_.set_anchors_tensor_index(2);
tensor_mapping_.set_num_detections_tensor_index(3);
// The scores tensor index needs to be determined based on the number of
// model's output tensors, which will be available in the first invocation
// of the Process() method.
tensor_mapping_.set_scores_tensor_index(-1);
scores_tensor_index_is_set_ = false;
}
if (options_.has_box_boundaries_indices()) {
box_indices_ = {options_.box_boundaries_indices().ymin(),
options_.box_boundaries_indices().xmin(),
options_.box_boundaries_indices().ymax(),
options_.box_boundaries_indices().xmax()};
int bitmap = 0;
for (int i : box_indices_) {
bitmap |= 1 << i;
}
RET_CHECK_EQ(bitmap, 15) << "The custom box boundaries indices should only "
"cover index 0, 1, 2, and 3.";
has_custom_box_indices_ = true;
}
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -660,14 +784,22 @@ absl::Status TensorsToDetectionsCalculator::ConvertToDetections(
const float* detection_boxes, const float* detection_scores, const float* detection_boxes, const float* detection_scores,
const int* detection_classes, std::vector<Detection>* output_detections) { const int* detection_classes, std::vector<Detection>* output_detections) {
for (int i = 0; i < num_boxes_; ++i) { for (int i = 0; i < num_boxes_; ++i) {
if (max_results_ > 0 && output_detections->size() == max_results_) {
break;
}
if (options_.has_min_score_thresh() && if (options_.has_min_score_thresh() &&
detection_scores[i] < options_.min_score_thresh()) { detection_scores[i] < options_.min_score_thresh()) {
continue; continue;
} }
if (!IsClassIndexAllowed(detection_classes[i])) {
continue;
}
const int box_offset = i * num_coords_; const int box_offset = i * num_coords_;
Detection detection = ConvertToDetection( Detection detection = ConvertToDetection(
detection_boxes[box_offset + 0], detection_boxes[box_offset + 1], /*box_ymin=*/detection_boxes[box_offset + box_indices_[0]],
detection_boxes[box_offset + 2], detection_boxes[box_offset + 3], /*box_xmin=*/detection_boxes[box_offset + box_indices_[1]],
/*box_ymax=*/detection_boxes[box_offset + box_indices_[2]],
/*box_xmax=*/detection_boxes[box_offset + box_indices_[3]],
detection_scores[i], detection_classes[i], options_.flip_vertically()); detection_scores[i], detection_classes[i], options_.flip_vertically());
const auto& bbox = detection.location_data().relative_bounding_box(); const auto& bbox = detection.location_data().relative_bounding_box();
if (bbox.width() < 0 || bbox.height() < 0 || std::isnan(bbox.width()) || if (bbox.width() < 0 || bbox.height() < 0 || std::isnan(bbox.width()) ||
@@ -909,7 +1041,7 @@ void main() {
options_.has_score_clipping_thresh() ? 1 : 0, options_.has_score_clipping_thresh() ? 1 : 0,
options_.has_score_clipping_thresh() ? options_.score_clipping_thresh() options_.has_score_clipping_thresh() ? options_.score_clipping_thresh()
: 0, : 0,
!ignore_classes_.empty() ? 1 : 0); !IsClassIndexAllowed(0));
// # filter classes supported is hardware dependent. // # filter classes supported is hardware dependent.
int max_wg_size; // typically <= 1024 int max_wg_size; // typically <= 1024
@@ -918,7 +1050,14 @@ void main() {
CHECK_LT(num_classes_, max_wg_size) CHECK_LT(num_classes_, max_wg_size)
<< "# classes must be < " << max_wg_size; << "# classes must be < " << max_wg_size;
// TODO support better filtering. // TODO support better filtering.
CHECK_LE(ignore_classes_.size(), 1) << "Only ignore class 0 is allowed"; if (class_index_set_.is_allowlist) {
CHECK_EQ(class_index_set_.values.size(),
IsClassIndexAllowed(0) ? num_classes_ : num_classes_ - 1)
<< "Only all classes >= class 0 or >= class 1";
} else {
CHECK_EQ(class_index_set_.values.size(), IsClassIndexAllowed(0) ? 0 : 1)
<< "Only ignore class 0 is allowed";
}
// Shader program // Shader program
{ {
@@ -1125,10 +1264,17 @@ kernel void scoreKernel(
options_.has_score_clipping_thresh() ? 1 : 0, options_.has_score_clipping_thresh() ? 1 : 0,
options_.has_score_clipping_thresh() ? options_.score_clipping_thresh() options_.has_score_clipping_thresh() ? options_.score_clipping_thresh()
: 0, : 0,
ignore_classes_.size() ? 1 : 0); !IsClassIndexAllowed(0));
// TODO support better filtering. // TODO support better filtering.
CHECK_LE(ignore_classes_.size(), 1) << "Only ignore class 0 is allowed"; if (class_index_set_.is_allowlist) {
CHECK_EQ(class_index_set_.values.size(),
IsClassIndexAllowed(0) ? num_classes_ : num_classes_ - 1)
<< "Only all classes >= class 0 or >= class 1";
} else {
CHECK_EQ(class_index_set_.values.size(), IsClassIndexAllowed(0) ? 0 : 1)
<< "Only ignore class 0 is allowed";
}
{ {
// Shader program // Shader program
@@ -1160,5 +1306,16 @@ kernel void scoreKernel(
return absl::OkStatus(); return absl::OkStatus();
} }
bool TensorsToDetectionsCalculator::IsClassIndexAllowed(int class_index) {
if (class_index_set_.values.empty()) {
return true;
}
if (class_index_set_.is_allowlist) {
return class_index_set_.values.contains(class_index);
} else {
return !class_index_set_.values.contains(class_index);
}
}
} // namespace api2 } // namespace api2
} // namespace mediapipe } // namespace mediapipe
@@ -57,7 +57,12 @@ message TensorsToDetectionsCalculatorOptions {
optional bool reverse_output_order = 14 [default = false]; optional bool reverse_output_order = 14 [default = false];
// The ids of classes that should be ignored during decoding the score for // The ids of classes that should be ignored during decoding the score for
// each predicted box. Can be overridden with IGNORE_CLASSES side packet. // each predicted box. Can be overridden with IGNORE_CLASSES side packet.
// `ignore_classes` and `allow_classes` are mutually exclusive.
repeated int32 ignore_classes = 8; repeated int32 ignore_classes = 8;
// The ids of classes that should be allowed during decoding the score for
// each predicted box. `ignore_classes` and `allow_classes` are mutually
// exclusive.
repeated int32 allow_classes = 21 [packed = true];
optional bool sigmoid_score = 15 [default = false]; optional bool sigmoid_score = 15 [default = false];
optional float score_clipping_thresh = 16; optional float score_clipping_thresh = 16;
@@ -71,4 +76,40 @@ message TensorsToDetectionsCalculatorOptions {
// Score threshold for perserving decoded detections. // Score threshold for perserving decoded detections.
optional float min_score_thresh = 19; optional float min_score_thresh = 19;
// The maximum number of the detection results to return. If < 0, all
// available results will be returned.
// For the detection models that have built-in non max suppression op, the
// output detections are the top-scored results. Otherwise, the output
// detections are the first N results that have higher scores than
// `min_score_thresh`.
optional int32 max_results = 20 [default = -1];
// The custom model output tensor mapping.
// The indices of the "detections" tensor and the "scores" tensor are always
// required. If the model outputs an "anchors" tensor, `anchors_tensor_index`
// must be specified. If the model outputs both "classes" tensor and "number
// of detections" tensors, `classes_tensor_index` and
// `num_detections_tensor_index` must be set.
message TensorMapping {
optional int32 detections_tensor_index = 1;
optional int32 classes_tensor_index = 2;
optional int32 scores_tensor_index = 3;
optional int32 num_detections_tensor_index = 4;
optional int32 anchors_tensor_index = 5;
}
optional TensorMapping tensor_mapping = 22;
// Represents the bounding box by using the combination of boundaries,
// {ymin, xmin, ymax, xmax}.
// The default order is {ymin, xmin, ymax, xmax}.
message BoxBoundariesIndices {
optional int32 ymin = 1 [default = 0];
optional int32 xmin = 2 [default = 1];
optional int32 ymax = 3 [default = 2];
optional int32 xmax = 4 [default = 3];
}
oneof box_indices {
BoxBoundariesIndices box_boundaries_indices = 23;
}
} }
@@ -355,9 +355,10 @@ absl::Status TensorsToSegmentationCalculator::ProcessCpu(
std::shared_ptr<ImageFrame> mask_frame = std::make_shared<ImageFrame>( std::shared_ptr<ImageFrame> mask_frame = std::make_shared<ImageFrame>(
ImageFormat::VEC32F1, output_width, output_height); ImageFormat::VEC32F1, output_width, output_height);
std::unique_ptr<Image> output_mask = absl::make_unique<Image>(mask_frame); std::unique_ptr<Image> output_mask = absl::make_unique<Image>(mask_frame);
cv::Mat output_mat = formats::MatView(output_mask.get()); auto output_mat = formats::MatView(output_mask.get());
// Upsample small mask into output. // Upsample small mask into output.
cv::resize(small_mask_mat, output_mat, cv::Size(output_width, output_height)); cv::resize(small_mask_mat, *output_mat,
cv::Size(output_width, output_height));
cc->Outputs().Tag(kMaskTag).Add(output_mask.release(), cc->InputTimestamp()); cc->Outputs().Tag(kMaskTag).Add(output_mask.release(), cc->InputTimestamp());
return absl::OkStatus(); return absl::OkStatus();
+1
View File
@@ -334,6 +334,7 @@ cc_library(
":image_frame_to_tensor_calculator_cc_proto", ":image_frame_to_tensor_calculator_cc_proto",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:image_frame", "//mediapipe/framework/formats:image_frame",
"//mediapipe/framework/port:core_proto",
"//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:ret_check",
"//mediapipe/framework/port:status", "//mediapipe/framework/port:status",
] + select({ ] + select({
@@ -17,6 +17,7 @@
#include "mediapipe/calculators/tensorflow/image_frame_to_tensor_calculator.pb.h" #include "mediapipe/calculators/tensorflow/image_frame_to_tensor_calculator.pb.h"
#include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/formats/image_frame.h" #include "mediapipe/framework/formats/image_frame.h"
#include "mediapipe/framework/port/proto_ns.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"
@@ -32,7 +33,10 @@ namespace {
// Convert the ImageFrame into Tensor with floating point value type. // Convert the ImageFrame into Tensor with floating point value type.
// The value will be normalized based on mean and stddev. // The value will be normalized based on mean and stddev.
std::unique_ptr<tf::Tensor> ImageFrameToNormalizedTensor( std::unique_ptr<tf::Tensor> ImageFrameToNormalizedTensor(
const ImageFrame& image_frame, float mean, float stddev) { // const ImageFrame& image_frame, float mean, float stddev) {
const ImageFrame& image_frame,
const mediapipe::proto_ns::RepeatedField<float>& mean,
const mediapipe::proto_ns::RepeatedField<float>& stddev) {
const int cols = image_frame.Width(); const int cols = image_frame.Width();
const int rows = image_frame.Height(); const int rows = image_frame.Height();
const int channels = image_frame.NumberOfChannels(); const int channels = image_frame.NumberOfChannels();
@@ -45,7 +49,20 @@ std::unique_ptr<tf::Tensor> ImageFrameToNormalizedTensor(
for (int row = 0; row < rows; ++row) { for (int row = 0; row < rows; ++row) {
for (int col = 0; col < cols; ++col) { for (int col = 0; col < cols; ++col) {
for (int channel = 0; channel < channels; ++channel) { for (int channel = 0; channel < channels; ++channel) {
tensor_data(row, col, channel) = (pixel[channel] - mean) / stddev; float mean_value = 0;
if (mean.size() > 1) {
mean_value = mean[channel];
} else if (!mean.empty()) {
mean_value = mean[0];
}
float stddev_value = 1;
if (stddev.size() > 1) {
stddev_value = stddev[channel];
} else if (!stddev.empty()) {
stddev_value = stddev[0];
}
tensor_data(row, col, channel) =
(pixel[channel] - mean_value) / stddev_value;
} }
pixel += channels; pixel += channels;
} }
@@ -126,7 +143,18 @@ absl::Status ImageFrameToTensorCalculator::Process(CalculatorContext* cc) {
const tf::DataType data_type = options_.data_type(); const tf::DataType data_type = options_.data_type();
RET_CHECK_EQ(data_type, tf::DT_FLOAT) RET_CHECK_EQ(data_type, tf::DT_FLOAT)
<< "Unsupported data type " << data_type; << "Unsupported data type " << data_type;
RET_CHECK_GT(options_.stddev(), 0.0f); RET_CHECK_GT(options_.stddev().size(), 0) << "You must set a stddev.";
RET_CHECK_GT(options_.stddev()[0], 0.0f) << "The stddev cannot be zero.";
if (options_.stddev().size() > 1) {
RET_CHECK_EQ(options_.stddev().size(), video_frame.NumberOfChannels())
<< "If specifying multiple stddev normalization values, "
<< "the number must match the number of image channels.";
}
if (options_.mean().size() > 1) {
RET_CHECK_EQ(options_.mean().size(), video_frame.NumberOfChannels())
<< "If specifying multiple mean normalization values, "
<< "the number must match the number of image channels.";
}
tensor = ImageFrameToNormalizedTensor(video_frame, options_.mean(), tensor = ImageFrameToNormalizedTensor(video_frame, options_.mean(),
options_.stddev()); options_.stddev());
} else { } else {
@@ -32,6 +32,6 @@ message ImageFrameToTensorCalculatorOptions {
// If set, the output tensor T is equal to (F - mean * J) / stddev, where F // If set, the output tensor T is equal to (F - mean * J) / stddev, where F
// and J are the input image frame and the all-ones matrix of the same size, // and J are the input image frame and the all-ones matrix of the same size,
// respectively. Otherwise, T is equal to F. // respectively. Otherwise, T is equal to F.
optional float mean = 2; repeated float mean = 2;
optional float stddev = 3; repeated float stddev = 3;
} }
@@ -454,4 +454,32 @@ TEST_F(ImageFrameToTensorCalculatorTest, FixedRGBFrameWithMeanAndStddev) {
EXPECT_EQ(actual[2], 127.0f / 128.0f); // (255 - 128) / 128 EXPECT_EQ(actual[2], 127.0f / 128.0f); // (255 - 128) / 128
} }
TEST_F(ImageFrameToTensorCalculatorTest, FixedRGBFrameWithRepeatMeanAndStddev) {
runner_ = ::absl::make_unique<CalculatorRunner>(
"ImageFrameToTensorCalculator",
"[mediapipe.ImageFrameToTensorCalculatorOptions.ext]"
"{data_type:DT_FLOAT mean:128.0 mean:128.0 mean:128.0 "
" stddev:128.0 stddev:128.0 stddev:128.0}",
1, 1, 0);
// Create a single pixel image of fixed color #0080ff.
auto image_frame = ::absl::make_unique<ImageFrame>(ImageFormat::SRGB, 1, 1);
const uint8 color[] = {0, 128, 255};
SetToColor<uint8>(color, image_frame.get());
runner_->MutableInputs()->Index(0).packets.push_back(
Adopt(image_frame.release()).At(Timestamp(0)));
MP_ASSERT_OK(runner_->Run());
const auto& tensor = runner_->Outputs().Index(0).packets[0].Get<tf::Tensor>();
EXPECT_EQ(tensor.dtype(), tf::DT_FLOAT);
ASSERT_EQ(tensor.dims(), 3);
EXPECT_EQ(tensor.shape().dim_size(0), 1);
EXPECT_EQ(tensor.shape().dim_size(1), 1);
EXPECT_EQ(tensor.shape().dim_size(2), 3);
const float* actual = tensor.flat<float>().data();
EXPECT_EQ(actual[0], -1.0f); // ( 0 - 128) / 128
EXPECT_EQ(actual[1], 0.0f); // (128 - 128) / 128
EXPECT_EQ(actual[2], 127.0f / 128.0f); // (255 - 128) / 128
}
} // namespace mediapipe } // namespace mediapipe
@@ -26,7 +26,6 @@
#include "tensorflow/core/framework/types.h" #include "tensorflow/core/framework/types.h"
namespace mediapipe { namespace mediapipe {
namespace { namespace {
constexpr char kTransposeOptionsString[] = constexpr char kTransposeOptionsString[] =
@@ -70,10 +70,10 @@ const int kNumCoordsPerBox = 4;
// image/understanding/object_detection/export_inference_graph.py // image/understanding/object_detection/export_inference_graph.py
// //
// By default, the output Detections store label ids (integers) for each // By default, the output Detections store label ids (integers) for each
// detection. Optionally, a label map (of the form std::map<int, std::string> // detection. Optionally, a label map (of the form std::map<int, string>
// mapping label ids to label names as strings) can be made available as an // mapping label ids to label names as strings) can be made available as an
// input side packet, in which case the output Detections store // input side packet, in which case the output Detections store
// labels as their associated std::string provided by the label map. // labels as their associated string provided by the label map.
// //
// Usage example: // Usage example:
// node { // node {
@@ -59,7 +59,7 @@ namespace mpms = mediapipe::mediasequence;
// bounding boxes from vector<Detections>, and streams with the // bounding boxes from vector<Detections>, and streams with the
// "FLOAT_FEATURE_${NAME}" pattern, which stores the values from vector<float>'s // "FLOAT_FEATURE_${NAME}" pattern, which stores the values from vector<float>'s
// associated with the name ${NAME}. "KEYPOINTS" stores a map of 2D keypoints // associated with the name ${NAME}. "KEYPOINTS" stores a map of 2D keypoints
// from flat_hash_map<std::string, vector<pair<float, float>>>. "IMAGE_${NAME}", // from flat_hash_map<string, vector<pair<float, float>>>. "IMAGE_${NAME}",
// "BBOX_${NAME}", and "KEYPOINTS_${NAME}" will also store prefixed versions of // "BBOX_${NAME}", and "KEYPOINTS_${NAME}" will also store prefixed versions of
// each stream, which allows for multiple image streams to be included. However, // each stream, which allows for multiple image streams to be included. However,
// the default names are suppored by more tools. // the default names are suppored by more tools.
@@ -28,7 +28,7 @@
// output_side_packet: "SEQUENCE_EXAMPLE:sequence_example" // output_side_packet: "SEQUENCE_EXAMPLE:sequence_example"
// } // }
// //
// Example converting to std::string in Close(): // Example converting to string in Close():
// node { // node {
// calculator: "StringToSequenceExampleCalculator" // calculator: "StringToSequenceExampleCalculator"
// input_side_packet: "SEQUENCE_EXAMPLE:sequence_example" // input_side_packet: "SEQUENCE_EXAMPLE:sequence_example"
@@ -121,8 +121,12 @@ absl::Status TensorToImageFrameCalculator::Process(CalculatorContext* cc) {
if (d > 255) d = 255; if (d > 255) d = 255;
buffer[i] = d; buffer[i] = d;
} }
output = ::absl::make_unique<ImageFrame>(format, width, height, output = ::absl::make_unique<ImageFrame>(
width * depth, buffer.release()); format, width, height, width * depth, buffer.release(),
[total_size](uint8* ptr) {
::operator delete[](ptr, total_size,
std::align_val_t(EIGEN_MAX_ALIGN_BYTES));
});
} else if (input_tensor.dtype() == tensorflow::DT_UINT8) { } else if (input_tensor.dtype() == tensorflow::DT_UINT8) {
if (scale_factor_ != 1.0) { if (scale_factor_ != 1.0) {
return absl::InvalidArgumentError("scale_factor_ given for uint8 tensor"); return absl::InvalidArgumentError("scale_factor_ given for uint8 tensor");
@@ -302,10 +302,9 @@ class TensorFlowInferenceCalculator : public CalculatorBase {
<< "To use recurrent_tag_pairs, batch_size must be 1."; << "To use recurrent_tag_pairs, batch_size must be 1.";
for (const auto& tag_pair : options_.recurrent_tag_pair()) { for (const auto& tag_pair : options_.recurrent_tag_pair()) {
const std::vector<std::string> tags = absl::StrSplit(tag_pair, ':'); const std::vector<std::string> tags = absl::StrSplit(tag_pair, ':');
RET_CHECK_EQ(tags.size(), 2) RET_CHECK_EQ(tags.size(), 2) << "recurrent_tag_pair must be a colon "
<< "recurrent_tag_pair must be a colon " "separated string with two components: "
"separated std::string with two components: " << tag_pair;
<< tag_pair;
RET_CHECK(mediapipe::ContainsKey(tag_to_tensor_map_, tags[0])) RET_CHECK(mediapipe::ContainsKey(tag_to_tensor_map_, tags[0]))
<< "Can't find tag '" << tags[0] << "' in signature " << "Can't find tag '" << tags[0] << "' in signature "
<< options_.signature_name(); << options_.signature_name();
@@ -86,7 +86,7 @@ class TensorFlowSessionFromFrozenGraphCalculator : public CalculatorBase {
cc->InputSidePackets() cc->InputSidePackets()
.Tag(kStringModelFilePathTag) .Tag(kStringModelFilePathTag)
.Set<std::string>( .Set<std::string>(
// Filename of std::string model. // Filename of string model.
); );
} }
cc->OutputSidePackets() cc->OutputSidePackets()
@@ -84,7 +84,7 @@ class TensorFlowSessionFromFrozenGraphGenerator : public PacketGenerator {
} else if (input_side_packets->HasTag(kStringModelFilePathTag)) { } else if (input_side_packets->HasTag(kStringModelFilePathTag)) {
input_side_packets->Tag(kStringModelFilePathTag) input_side_packets->Tag(kStringModelFilePathTag)
.Set<std::string>( .Set<std::string>(
// Filename of std::string model. // Filename of string model.
); );
} }
output_side_packets->Tag(kSessionTag) output_side_packets->Tag(kSessionTag)
@@ -69,6 +69,8 @@ const std::string MaybeConvertSignatureToTag(
[](unsigned char c) { return std::toupper(c); }); [](unsigned char c) { return std::toupper(c); });
output = absl::StrReplaceAll(output, {{"/", "_"}}); output = absl::StrReplaceAll(output, {{"/", "_"}});
output = absl::StrReplaceAll(output, {{"-", "_"}}); output = absl::StrReplaceAll(output, {{"-", "_"}});
output = absl::StrReplaceAll(output, {{".", "_"}});
LOG(INFO) << "Renamed TAG from: " << name << " to " << output;
return output; return output;
} else { } else {
return name; return name;
@@ -71,6 +71,8 @@ const std::string MaybeConvertSignatureToTag(
[](unsigned char c) { return std::toupper(c); }); [](unsigned char c) { return std::toupper(c); });
output = absl::StrReplaceAll(output, {{"/", "_"}}); output = absl::StrReplaceAll(output, {{"/", "_"}});
output = absl::StrReplaceAll(output, {{"-", "_"}}); output = absl::StrReplaceAll(output, {{"-", "_"}});
output = absl::StrReplaceAll(output, {{".", "_"}});
LOG(INFO) << "Renamed TAG from: " << name << " to " << output;
return output; return output;
} else { } else {
return name; return name;

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