Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a20e9909d | ||
|
|
7fb37c80e8 | ||
|
|
c6c80c3745 | ||
|
|
cc6a2f7af6 |
@@ -32,6 +32,9 @@ build:macos --copt=-w
|
||||
# Sets the default Apple platform to macOS.
|
||||
build --apple_platform_type=macos
|
||||
|
||||
# Compile ObjC++ files with C++17
|
||||
build --per_file_copt=.*\.mm\$@-std=c++17
|
||||
|
||||
# Allow debugging with XCODE
|
||||
build --apple_generate_dsym
|
||||
|
||||
@@ -88,6 +91,10 @@ build:darwin_x86_64 --apple_platform_type=macos
|
||||
build:darwin_x86_64 --macos_minimum_os=10.12
|
||||
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.
|
||||
try-import %workspace%/.configure.bazelrc
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
4.2.1
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -15,4 +15,4 @@
|
||||
|
||||
# A list of assignees
|
||||
assignees:
|
||||
- sgowroji
|
||||
- sureshdagooglecom
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ RUN pip3 install tf_slim
|
||||
RUN ln -s /usr/bin/python3 /usr/bin/python
|
||||
|
||||
# Install bazel
|
||||
ARG BAZEL_VERSION=4.2.1
|
||||
ARG BAZEL_VERSION=5.0.0
|
||||
RUN mkdir /bazel && \
|
||||
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" && \
|
||||
|
||||
@@ -136,8 +136,8 @@ run code search using
|
||||
|
||||
## Community
|
||||
|
||||
* [Awesome MediaPipe](https://mediapipe.org) - A curated list of awesome
|
||||
MediaPipe related frameworks, libraries and software
|
||||
* [Awesome MediaPipe](https://mediapipe.page.link/awesome-mediapipe) - A
|
||||
curated list of awesome MediaPipe related frameworks, libraries and software
|
||||
* [Slack community](https://mediapipe.page.link/joinslack) for MediaPipe users
|
||||
* [Discuss](https://groups.google.com/forum/#!forum/mediapipe) - General
|
||||
community discussion around MediaPipe
|
||||
|
||||
@@ -61,11 +61,12 @@ http_archive(
|
||||
sha256 = "de682ea824bfffba05b4e33b67431c247397d6175962534305136aa06f92e049",
|
||||
)
|
||||
|
||||
# Google Benchmark library.
|
||||
# Google Benchmark library v1.6.1 released on 2022-01-10.
|
||||
http_archive(
|
||||
name = "com_google_benchmark",
|
||||
urls = ["https://github.com/google/benchmark/archive/main.zip"],
|
||||
strip_prefix = "benchmark-main",
|
||||
urls = ["https://github.com/google/benchmark/archive/refs/tags/v1.6.1.tar.gz"],
|
||||
strip_prefix = "benchmark-1.6.1",
|
||||
sha256 = "6132883bc8c9b0df5375b16ab520fac1a85dc9e4cf5be59480448ece74b278d4",
|
||||
build_file = "@//third_party:benchmark.BUILD",
|
||||
)
|
||||
|
||||
@@ -201,7 +202,10 @@ new_local_repository(
|
||||
new_local_repository(
|
||||
name = "macos_opencv",
|
||||
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(
|
||||
@@ -373,9 +377,9 @@ http_archive(
|
||||
)
|
||||
|
||||
# Tensorflow repo should always go after the other external dependencies.
|
||||
# 2021-12-02
|
||||
_TENSORFLOW_GIT_COMMIT = "18a1dc0ba806dc023808531f0373d9ec068e64bf"
|
||||
_TENSORFLOW_SHA256 = "85b90416f7a11339327777bccd634de00ca0de2cf334f5f0727edcb11ff9289a"
|
||||
# 2022-02-15
|
||||
_TENSORFLOW_GIT_COMMIT = "a3419acc751dfc19caf4d34a1594e1f76810ec58"
|
||||
_TENSORFLOW_SHA256 = "b95b2a83632d4055742ae1a2dcc96b45da6c12a339462dbc76c8bca505308e3a"
|
||||
http_archive(
|
||||
name = "org_tensorflow",
|
||||
urls = [
|
||||
@@ -383,7 +387,6 @@ http_archive(
|
||||
],
|
||||
patches = [
|
||||
"@//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.
|
||||
"@//third_party:org_tensorflow_custom_ops.diff",
|
||||
],
|
||||
|
||||
@@ -109,7 +109,7 @@ for app in ${apps}; do
|
||||
if [[ ${category} != "shoe" ]]; then
|
||||
bazel_flags_extended+=(--define ${category}=true)
|
||||
fi
|
||||
bazel "${bazel_flags_extended[@]}"
|
||||
bazelisk "${bazel_flags_extended[@]}"
|
||||
cp -f "${bin}" "${apk}"
|
||||
fi
|
||||
apks+=(${apk})
|
||||
@@ -120,7 +120,7 @@ for app in ${apps}; do
|
||||
if [[ ${app_name} == "templatematchingcpu" ]]; then
|
||||
switch_to_opencv_4
|
||||
fi
|
||||
bazel "${bazel_flags[@]}"
|
||||
bazelisk "${bazel_flags[@]}"
|
||||
cp -f "${bin}" "${apk}"
|
||||
if [[ ${app_name} == "templatematchingcpu" ]]; then
|
||||
switch_to_opencv_3
|
||||
|
||||
@@ -83,7 +83,7 @@ for app in ${apps}; do
|
||||
bazel_flags=("${default_bazel_flags[@]}")
|
||||
bazel_flags+=(${target})
|
||||
|
||||
bazel "${bazel_flags[@]}"
|
||||
bazelisk "${bazel_flags[@]}"
|
||||
cp -f "${bin_dir}/${app}/"*"_cpu" "${out_dir}"
|
||||
fi
|
||||
if [[ $build_only == false ]]; then
|
||||
|
||||
@@ -71,7 +71,7 @@ for app in ${apps}; do
|
||||
bazel_flags+=(--linkopt=-s)
|
||||
fi
|
||||
|
||||
bazel "${bazel_flags[@]}"
|
||||
bazelisk "${bazel_flags[@]}"
|
||||
cp -f "${bin_dir}/${app}/"*".ipa" "${out_dir}"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -169,7 +169,7 @@ behavior depending on resource constraints.
|
||||
|
||||
[`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
|
||||
[`SyncSetInputStreamHandler`]: https://github.com/google/mediapipe/tree/master/mediapipe/framework/stream_handler/sync_set_input_stream_handler.h
|
||||
[`ImmediateInputStreamHandler`]: https://github.com/google/mediapipe/tree/master/mediapipe/framework/stream_handler/immediate_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.cc
|
||||
[`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
|
||||
|
||||
@@ -30,7 +30,7 @@ APIs (currently in alpha) that are now available in
|
||||
* Install MediaPipe following these [instructions](./install.md).
|
||||
* Setup Java Runtime.
|
||||
* Setup Android SDK release 30.0.0 and above.
|
||||
* Setup Android NDK version 18 and above.
|
||||
* Setup Android NDK version between 18 and 21.
|
||||
|
||||
MediaPipe recommends setting up Android SDK and NDK via Android Studio (and see
|
||||
below for Android Studio setup). However, if you prefer using MediaPipe without
|
||||
@@ -53,7 +53,7 @@ the following:
|
||||
|
||||
```bash
|
||||
$ echo "android_sdk_repository(name = \"androidsdk\")" >> WORKSPACE
|
||||
$ echo "android_ndk_repository(name = \"androidndk\")" >> WORKSPACE
|
||||
$ echo "android_ndk_repository(name = \"androidndk\", api_level=21)" >> WORKSPACE
|
||||
```
|
||||
|
||||
In order to use MediaPipe on earlier Android versions, MediaPipe needs to switch
|
||||
|
||||
@@ -48,6 +48,16 @@ each project.
|
||||
bazel build -c opt --strip=ALWAYS \
|
||||
--host_crosstool_top=@bazel_tools//tools/cpp:toolchain \
|
||||
--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
|
||||
```
|
||||
|
||||
@@ -57,6 +67,16 @@ each project.
|
||||
bazel build -c opt --strip=ALWAYS \
|
||||
--host_crosstool_top=@bazel_tools//tools/cpp:toolchain \
|
||||
--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
|
||||
|
||||
# It should print:
|
||||
|
||||
@@ -59,6 +59,21 @@ OpenGL ES profile shading language version string: OpenGL ES GLSL ES 3.20
|
||||
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.*
|
||||
|
||||
You need to see ES 3.1 or greater printed in order to perform TFLite inference
|
||||
|
||||
@@ -131,7 +131,7 @@ Create a `BUILD` file in the `$APPLICATION_PATH` and add the following build
|
||||
rules:
|
||||
|
||||
```
|
||||
MIN_IOS_VERSION = "10.0"
|
||||
MIN_IOS_VERSION = "11.0"
|
||||
|
||||
load(
|
||||
"@build_bazel_rules_apple//apple:ios.bzl",
|
||||
|
||||
@@ -569,7 +569,7 @@ next section.
|
||||
|
||||
Option 1. Follow
|
||||
[the official Bazel documentation](https://docs.bazel.build/versions/master/install-windows.html)
|
||||
to install Bazel 4.2.1 or higher.
|
||||
to install Bazel 5.0.0 or higher.
|
||||
|
||||
Option 2. Follow the official
|
||||
[Bazel documentation](https://docs.bazel.build/versions/master/install-bazelisk.html)
|
||||
|
||||
@@ -32,9 +32,14 @@ example apps, start from, start from
|
||||
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"
|
||||
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
|
||||
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
|
||||
"Edit Scheme...", select the Run action, and switch the Build Configuration from
|
||||
Debug to Release. Note that this is set independently for each target.
|
||||
|
||||
@@ -126,6 +126,7 @@ following steps:
|
||||
}
|
||||
return packet.Get<MyType>();
|
||||
});
|
||||
}
|
||||
} // namespace mediapipe
|
||||
```
|
||||
|
||||
|
||||
+2
-2
@@ -136,8 +136,8 @@ run code search using
|
||||
|
||||
## Community
|
||||
|
||||
* [Awesome MediaPipe](https://mediapipe.org) - A curated list of awesome
|
||||
MediaPipe related frameworks, libraries and software
|
||||
* [Awesome MediaPipe](https://mediapipe.page.link/awesome-mediapipe) - A
|
||||
curated list of awesome MediaPipe related frameworks, libraries and software
|
||||
* [Slack community](https://mediapipe.page.link/joinslack) for MediaPipe users
|
||||
* [Discuss](https://groups.google.com/forum/#!forum/mediapipe) - General
|
||||
community discussion around MediaPipe
|
||||
|
||||
@@ -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
|
||||
performance enables it to be applied to any live viewfinder experience that
|
||||
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
|
||||
classification, and face region segmentation. BlazeFace uses a lightweight
|
||||
feature extraction network inspired by, but distinct from
|
||||
|
||||
+29
-29
@@ -20,34 +20,34 @@ nav_order: 2
|
||||
|
||||
## Overview
|
||||
|
||||
MediaPipe Face Mesh is a face geometry solution that estimates 468 3D face
|
||||
landmarks in real-time even on mobile devices. It employs machine learning (ML)
|
||||
to infer the 3D surface geometry, requiring only a single camera input without
|
||||
the need for a dedicated depth sensor. Utilizing lightweight model architectures
|
||||
together with GPU acceleration throughout the pipeline, the solution delivers
|
||||
real-time performance critical for live experiences.
|
||||
MediaPipe Face Mesh is a solution that estimates 468 3D face landmarks in
|
||||
real-time even on mobile devices. It employs machine learning (ML) to infer the
|
||||
3D facial surface, requiring only a single camera input without the need for a
|
||||
dedicated depth sensor. Utilizing lightweight model architectures together with
|
||||
GPU acceleration throughout the pipeline, the solution delivers real-time
|
||||
performance critical for live experiences.
|
||||
|
||||
Additionally, the solution is bundled with the Face Geometry module that bridges
|
||||
the gap between the face landmark estimation and useful real-time augmented
|
||||
reality (AR) applications. It establishes a metric 3D space and uses the face
|
||||
landmark screen positions to estimate face geometry within that space. The face
|
||||
geometry data consists of common 3D geometry primitives, including a face pose
|
||||
transformation matrix and a triangular face mesh. Under the hood, a lightweight
|
||||
statistical analysis method called
|
||||
Additionally, the solution is bundled with the Face Transform module that
|
||||
bridges the gap between the face landmark estimation and useful real-time
|
||||
augmented reality (AR) applications. It establishes a metric 3D space and uses
|
||||
the face landmark screen positions to estimate a face transform within that
|
||||
space. The face transform data consists of common 3D primitives, including a
|
||||
face pose transformation matrix and a triangular face mesh. Under the hood, a
|
||||
lightweight statistical analysis method called
|
||||
[Procrustes Analysis](https://en.wikipedia.org/wiki/Procrustes_analysis) is
|
||||
employed to drive a robust, performant and portable logic. The analysis runs on
|
||||
CPU and has a minimal speed/memory footprint on top of the ML model inference.
|
||||
|
||||
 |
|
||||
:-------------------------------------------------------------: |
|
||||
*Fig 1. AR effects utilizing facial surface geometry.* |
|
||||
*Fig 1. AR effects utilizing the 3D facial surface.* |
|
||||
|
||||
## ML Pipeline
|
||||
|
||||
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
|
||||
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
|
||||
transformations consisting of rotations, translation and scale changes. Instead
|
||||
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
|
||||
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
|
||||
employed in our [MediaPipe Hands](./hands.md) solution, which uses a palm detector
|
||||
together with a hand landmark model.
|
||||
employed in our [MediaPipe Hands](./hands.md) solution, which uses a palm
|
||||
detector together with a hand landmark model.
|
||||
|
||||
The pipeline is implemented as a MediaPipe
|
||||
[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.* |
|
||||
|
||||
## Face Geometry Module
|
||||
## Face Transform Module
|
||||
|
||||
The [Face Landmark Model](#face-landmark-model) performs a single-camera face landmark
|
||||
detection in the screen coordinate space: the X- and Y- coordinates are
|
||||
@@ -140,7 +140,7 @@ enable the full spectrum of augmented reality (AR) features like aligning a
|
||||
virtual 3D object with a detected face.
|
||||
|
||||
The
|
||||
[Face Geometry module](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_geometry)
|
||||
[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
|
||||
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
|
||||
@@ -151,7 +151,7 @@ landmark positions are not changed.
|
||||
|
||||
#### 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
|
||||
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
|
||||
@@ -184,11 +184,11 @@ functions:
|
||||
|
||||
### Components
|
||||
|
||||
#### Geometry Pipeline
|
||||
#### Transform Pipeline
|
||||
|
||||
The **Geometry Pipeline** is a key component, which is responsible for
|
||||
estimating face geometry objects within the Metric 3D space. On each frame, the
|
||||
following steps are executed in the given order:
|
||||
The **Transform Pipeline** is a key component, which is responsible for
|
||||
estimating the face transform objects within the Metric 3D space. On each frame,
|
||||
the following steps are executed in the given order:
|
||||
|
||||
- Face landmark screen coordinates are converted into the Metric 3D space
|
||||
coordinates;
|
||||
@@ -199,12 +199,12 @@ following steps are executed in the given order:
|
||||
positions (XYZ), while both the vertex texture coordinates (UV) and the
|
||||
triangular topology are inherited from the canonical face model.
|
||||
|
||||
The geometry pipeline is implemented as a MediaPipe
|
||||
The transform pipeline is implemented as a MediaPipe
|
||||
[calculator](https://github.com/google/mediapipe/tree/master/mediapipe/modules/face_geometry/geometry_pipeline_calculator.cc).
|
||||
For your convenience, the face geometry pipeline calculator is bundled together
|
||||
with corresponding metadata into a unified MediaPipe
|
||||
For your convenience, this calculator is bundled together with corresponding
|
||||
metadata into a unified MediaPipe
|
||||
[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).
|
||||
|
||||
#### Effect Renderer
|
||||
@@ -227,7 +227,7 @@ The effect renderer is implemented as a MediaPipe
|
||||
|
||||
|  |
|
||||
| :---------------------------------------------------------------------: |
|
||||
| *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
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ on how to build MediaPipe examples.
|
||||
|
||||
Note: The following runs TensorFlow inference on CPU. If you would like to
|
||||
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.
|
||||
|
||||
To build the TensorFlow CPU inference example on desktop, run:
|
||||
|
||||
@@ -384,7 +384,7 @@ Supported configuration options:
|
||||
<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/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/objectron/objectron.js" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
|
||||
@@ -359,7 +359,7 @@ Supported configuration options:
|
||||
<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/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/pose/pose.js" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
|
||||
@@ -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)
|
||||
for a friendlier introduction.
|
||||
|
||||
histogram_interval_size_usec :Specifies the size of the runtimes histogram
|
||||
intervals (in microseconds) to generate the histogram of the Process() time. The
|
||||
last interval extends to +inf. If not specified, the interval is 1000000 usec =
|
||||
1 sec.
|
||||
histogram_interval_size_usec
|
||||
: Specifies the size of the runtimes histogram intervals (in microseconds) to
|
||||
generate the histogram of the `Process()` time. The last interval extends to
|
||||
+inf. If not specified, the interval is 1000000 usec = 1 sec.
|
||||
|
||||
num_histogram_intervals :Specifies the number of intervals to generate the
|
||||
histogram of the `Process()` runtime. If not specified, one interval is used.
|
||||
num_histogram_intervals
|
||||
: Specifies the number of intervals to generate the histogram of the
|
||||
`Process()` runtime. If not specified, one interval is used.
|
||||
|
||||
enable_profiler
|
||||
: If true, the profiler starts profiling when graph is initialized.
|
||||
@@ -288,7 +289,7 @@ trace_event_types_disabled
|
||||
|
||||
trace_log_path
|
||||
: 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
|
||||
: 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
|
||||
: 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`.
|
||||
The default value specifies 10 intervals per file.
|
||||
`trace_log_interval_usec * trace_log_count * trace_log_interval_count`. The
|
||||
default value specifies 10 intervals per file.
|
||||
|
||||
trace_log_disabled
|
||||
: An option to turn ON/OFF writing trace files to disk. Saving trace files to
|
||||
|
||||
@@ -75,6 +75,7 @@ alias(
|
||||
actual = select({
|
||||
":macos_i386": ":macos_i386",
|
||||
":macos_x86_64": ":macos_x86_64",
|
||||
":macos_arm64": ":macos_arm64",
|
||||
"//conditions:default": ":macos_i386", # Arbitrarily chosen from above.
|
||||
}),
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -119,6 +120,15 @@ config_setting(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
config_setting(
|
||||
name = "macos_arm64",
|
||||
values = {
|
||||
"apple_platform_type": "macos",
|
||||
"cpu": "darwin_arm64",
|
||||
},
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
[
|
||||
config_setting(
|
||||
name = arch,
|
||||
|
||||
@@ -117,6 +117,7 @@ mediapipe_proto_library(
|
||||
"//mediapipe/framework:calculator_options_proto",
|
||||
"//mediapipe/framework:calculator_proto",
|
||||
"//mediapipe/framework/formats:classification_proto",
|
||||
"//mediapipe/framework/formats:landmark_proto",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -213,6 +214,7 @@ cc_library(
|
||||
"//mediapipe/framework:collection_item_id",
|
||||
"//mediapipe/framework:packet",
|
||||
"//mediapipe/framework/formats:classification_cc_proto",
|
||||
"//mediapipe/framework/formats:detection_cc_proto",
|
||||
"//mediapipe/framework/formats:landmark_cc_proto",
|
||||
"//mediapipe/framework/formats:rect_cc_proto",
|
||||
"//mediapipe/framework/port:integral_types",
|
||||
@@ -309,8 +311,8 @@ cc_library(
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "concatenate_normalized_landmark_list_calculator",
|
||||
srcs = ["concatenate_normalized_landmark_list_calculator.cc"],
|
||||
name = "concatenate_proto_list_calculator",
|
||||
srcs = ["concatenate_proto_list_calculator.cc"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":concatenate_vector_calculator_cc_proto",
|
||||
@@ -324,10 +326,10 @@ cc_library(
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "concatenate_normalized_landmark_list_calculator_test",
|
||||
srcs = ["concatenate_normalized_landmark_list_calculator_test.cc"],
|
||||
name = "concatenate_proto_list_calculator_test",
|
||||
srcs = ["concatenate_proto_list_calculator_test.cc"],
|
||||
deps = [
|
||||
":concatenate_normalized_landmark_list_calculator",
|
||||
":concatenate_proto_list_calculator",
|
||||
":concatenate_vector_calculator_cc_proto",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:calculator_runner",
|
||||
@@ -964,8 +966,8 @@ cc_test(
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "split_landmarks_calculator",
|
||||
srcs = ["split_landmarks_calculator.cc"],
|
||||
name = "split_proto_list_calculator",
|
||||
srcs = ["split_proto_list_calculator.cc"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":split_vector_calculator_cc_proto",
|
||||
@@ -979,10 +981,10 @@ cc_library(
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "split_landmarks_calculator_test",
|
||||
srcs = ["split_landmarks_calculator_test.cc"],
|
||||
name = "split_proto_list_calculator_test",
|
||||
srcs = ["split_proto_list_calculator_test.cc"],
|
||||
deps = [
|
||||
":split_landmarks_calculator",
|
||||
":split_proto_list_calculator",
|
||||
":split_vector_calculator_cc_proto",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:calculator_runner",
|
||||
@@ -1195,6 +1197,7 @@ cc_library(
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:collection_item_id",
|
||||
"//mediapipe/framework/formats:classification_cc_proto",
|
||||
"//mediapipe/framework/formats:landmark_cc_proto",
|
||||
"//mediapipe/framework/port:integral_types",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
@@ -1255,3 +1258,36 @@ cc_test(
|
||||
"@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;
|
||||
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>.
|
||||
typedef BeginLoopCalculator<std::vector<::mediapipe::NormalizedRect>>
|
||||
BeginLoopNormalizedRectCalculator;
|
||||
|
||||
@@ -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/collection_item_id.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/integral_types.h"
|
||||
#include "mediapipe/framework/port/ret_check.h"
|
||||
@@ -79,6 +80,8 @@ class ConstantSidePacketCalculator : public CalculatorBase {
|
||||
packet.Set<uint64>();
|
||||
} else if (packet_options.has_classification_list_value()) {
|
||||
packet.Set<ClassificationList>();
|
||||
} else if (packet_options.has_landmark_list_value()) {
|
||||
packet.Set<LandmarkList>();
|
||||
} else {
|
||||
return absl::InvalidArgumentError(
|
||||
"None of supported values were specified in options.");
|
||||
@@ -108,6 +111,9 @@ class ConstantSidePacketCalculator : public CalculatorBase {
|
||||
} else if (packet_options.has_classification_list_value()) {
|
||||
packet.Set(MakePacket<ClassificationList>(
|
||||
packet_options.classification_list_value()));
|
||||
} else if (packet_options.has_landmark_list_value()) {
|
||||
packet.Set(
|
||||
MakePacket<LandmarkList>(packet_options.landmark_list_value()));
|
||||
} else {
|
||||
return absl::InvalidArgumentError(
|
||||
"None of supported values were specified in options.");
|
||||
|
||||
@@ -18,6 +18,7 @@ package mediapipe;
|
||||
|
||||
import "mediapipe/framework/calculator.proto";
|
||||
import "mediapipe/framework/formats/classification.proto";
|
||||
import "mediapipe/framework/formats/landmark.proto";
|
||||
|
||||
option objc_class_prefix = "MediaPipe";
|
||||
|
||||
@@ -34,6 +35,7 @@ message ConstantSidePacketCalculatorOptions {
|
||||
string string_value = 4;
|
||||
uint64 uint64_value = 5;
|
||||
ClassificationList classification_list_value = 6;
|
||||
LandmarkList landmark_list_value = 7;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <vector>
|
||||
|
||||
#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/rect.pb.h"
|
||||
#include "mediapipe/util/render_data.pb.h"
|
||||
@@ -50,4 +51,8 @@ REGISTER_CALCULATOR(EndLoopClassificationListCalculator);
|
||||
typedef EndLoopCalculator<std::vector<TfLiteTensor>> EndLoopTensorCalculator;
|
||||
REGISTER_CALCULATOR(EndLoopTensorCalculator);
|
||||
|
||||
typedef EndLoopCalculator<std::vector<::mediapipe::Detection>>
|
||||
EndLoopDetectionCalculator;
|
||||
REGISTER_CALCULATOR(EndLoopDetectionCalculator);
|
||||
|
||||
} // 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
|
||||
// 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:
|
||||
// node {
|
||||
// calculator: "GraphProfileCalculator"
|
||||
@@ -50,11 +55,14 @@ class GraphProfileCalculator : public Node {
|
||||
absl::Status Process(CalculatorContext* cc) final {
|
||||
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()) {
|
||||
prev_profile_ts_ = cc->InputTimestamp();
|
||||
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);
|
||||
}
|
||||
return absl::OkStatus();
|
||||
|
||||
@@ -202,6 +202,8 @@ TEST_F(GraphProfileCalculatorTest, GraphProfile) {
|
||||
}
|
||||
})pb");
|
||||
|
||||
ASSERT_EQ(output_packets.size(), 2);
|
||||
EXPECT_TRUE(output_packets[0].Get<GraphProfile>().has_config());
|
||||
EXPECT_THAT(output_packets[1].Get<GraphProfile>(),
|
||||
mediapipe::EqualsProto(expected_profile));
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
#include "mediapipe/framework/port/canonical_errors.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
|
||||
// Quantizes a vector of floats to a std::string so that each float becomes a
|
||||
// byte in the [0, 255] range. Any value above max_quantized_value or below
|
||||
// Quantizes a vector of floats to a string so that each float becomes a byte
|
||||
// in the [0, 255] range. Any value above max_quantized_value or below
|
||||
// min_quantized_value will be saturated to '/xFF' or '/0'.
|
||||
//
|
||||
// Example config:
|
||||
|
||||
+62
-33
@@ -12,8 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MEDIAPIPE_CALCULATORS_CORE_SPLIT_LANDMARKS_CALCULATOR_H_ // NOLINT
|
||||
#define MEDIAPIPE_CALCULATORS_CORE_SPLIT_LANDMARKS_CALCULATOR_H_ // NOLINT
|
||||
#ifndef MEDIAPIPE_CALCULATORS_CORE_SPLIT_PROTO_LIST_CALCULATOR_H_ // NOLINT
|
||||
#define MEDIAPIPE_CALCULATORS_CORE_SPLIT_PROTO_LIST_CALCULATOR_H_ // NOLINT
|
||||
|
||||
#include "mediapipe/calculators/core/split_vector_calculator.pb.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
@@ -24,30 +24,30 @@
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
// Splits an input packet with LandmarkListType into
|
||||
// multiple LandmarkListType output packets using the [begin, end) ranges
|
||||
// Splits an input packet of ListType with a repeated field of ItemType
|
||||
// into multiple ListType output packets using the [begin, end) ranges
|
||||
// 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
|
||||
// of type LandmarkType. If "element_only" is false, ranges can be
|
||||
// non-zero in size and all outputs will be of type LandmarkListType.
|
||||
// of type ItemType. If "element_only" is false, ranges can be
|
||||
// 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
|
||||
// specified and all ranges of elements will be combined into one
|
||||
// LandmarkListType.
|
||||
template <typename LandmarkType, typename LandmarkListType>
|
||||
class SplitLandmarksCalculator : public CalculatorBase {
|
||||
// ListType.
|
||||
template <typename ItemType, typename ListType>
|
||||
class SplitListsCalculator : public CalculatorBase {
|
||||
public:
|
||||
static absl::Status GetContract(CalculatorContract* cc) {
|
||||
RET_CHECK(cc->Inputs().NumEntries() == 1);
|
||||
RET_CHECK(cc->Outputs().NumEntries() != 0);
|
||||
|
||||
cc->Inputs().Index(0).Set<LandmarkListType>();
|
||||
cc->Inputs().Index(0).Set<ListType>();
|
||||
|
||||
const auto& options =
|
||||
cc->Options<::mediapipe::SplitVectorCalculatorOptions>();
|
||||
|
||||
if (options.combine_outputs()) {
|
||||
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 j = i + 1; j < options.ranges_size(); ++j) {
|
||||
const auto& range_0 = options.ranges(i);
|
||||
@@ -82,9 +82,9 @@ class SplitLandmarksCalculator : public CalculatorBase {
|
||||
return absl::InvalidArgumentError(
|
||||
"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 {
|
||||
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 {
|
||||
const LandmarkListType& input =
|
||||
cc->Inputs().Index(0).Get<LandmarkListType>();
|
||||
RET_CHECK_GE(input.landmark_size(), max_range_end_)
|
||||
<< "Max range end " << max_range_end_ << " exceeds landmarks size "
|
||||
<< input.landmark_size();
|
||||
const ListType& input = cc->Inputs().Index(0).Get<ListType>();
|
||||
RET_CHECK_GE(ListSize(input), max_range_end_)
|
||||
<< "Max range end " << max_range_end_ << " exceeds list size "
|
||||
<< ListSize(input);
|
||||
|
||||
if (combine_outputs_) {
|
||||
LandmarkListType output;
|
||||
ListType output;
|
||||
for (int i = 0; i < ranges_.size(); ++i) {
|
||||
for (int j = ranges_[i].first; j < ranges_[i].second; ++j) {
|
||||
const LandmarkType& input_landmark = input.landmark(j);
|
||||
*output.add_landmark() = input_landmark;
|
||||
const ItemType& input_item = GetItem(input, j);
|
||||
*AddItem(output) = input_item;
|
||||
}
|
||||
}
|
||||
RET_CHECK_EQ(output.landmark_size(), total_elements_);
|
||||
RET_CHECK_EQ(ListSize(output), total_elements_);
|
||||
cc->Outputs().Index(0).AddPacket(
|
||||
MakePacket<LandmarkListType>(output).At(cc->InputTimestamp()));
|
||||
MakePacket<ListType>(output).At(cc->InputTimestamp()));
|
||||
} else {
|
||||
if (element_only_) {
|
||||
for (int i = 0; i < ranges_.size(); ++i) {
|
||||
cc->Outputs().Index(i).AddPacket(
|
||||
MakePacket<LandmarkType>(input.landmark(ranges_[i].first))
|
||||
MakePacket<ItemType>(GetItem(input, ranges_[i].first))
|
||||
.At(cc->InputTimestamp()));
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < ranges_.size(); ++i) {
|
||||
LandmarkListType output;
|
||||
ListType output;
|
||||
for (int j = ranges_[i].first; j < ranges_[i].second; ++j) {
|
||||
const LandmarkType& input_landmark = input.landmark(j);
|
||||
*output.add_landmark() = input_landmark;
|
||||
const ItemType& input_item = GetItem(input, j);
|
||||
*AddItem(output) = input_item;
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
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:
|
||||
std::vector<std::pair<int32, int32>> ranges_;
|
||||
int32 max_range_end_ = -1;
|
||||
@@ -159,15 +163,40 @@ class SplitLandmarksCalculator : public CalculatorBase {
|
||||
bool combine_outputs_ = false;
|
||||
};
|
||||
|
||||
typedef SplitLandmarksCalculator<NormalizedLandmark, NormalizedLandmarkList>
|
||||
SplitNormalizedLandmarkListCalculator;
|
||||
// TODO: Move calculators to separate *.cc files
|
||||
|
||||
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);
|
||||
|
||||
typedef SplitLandmarksCalculator<Landmark, LandmarkList>
|
||||
SplitLandmarkListCalculator;
|
||||
class 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);
|
||||
|
||||
} // namespace mediapipe
|
||||
|
||||
// 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;
|
||||
REGISTER_CALCULATOR(SplitUint64tVectorCalculator);
|
||||
|
||||
typedef SplitVectorCalculator<float, false> SplitFloatVectorCalculator;
|
||||
REGISTER_CALCULATOR(SplitFloatVectorCalculator);
|
||||
|
||||
} // namespace mediapipe
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
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.
|
||||
//
|
||||
// Example config:
|
||||
@@ -47,7 +47,7 @@ class StringToIntCalculatorTemplate : public CalculatorBase {
|
||||
if (!absl::SimpleAtoi(cc->InputSidePackets().Index(0).Get<std::string>(),
|
||||
&number)) {
|
||||
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));
|
||||
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_
|
||||
@@ -239,10 +239,13 @@ cc_library(
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":image_transformation_calculator_cc_proto",
|
||||
"//mediapipe/framework:packet",
|
||||
"//mediapipe/framework:timestamp",
|
||||
"//mediapipe/gpu:scale_mode_cc_proto",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/formats:image_frame",
|
||||
"//mediapipe/framework/formats:image_frame_opencv",
|
||||
"//mediapipe/framework/formats:video_stream_header",
|
||||
"//mediapipe/framework/port:opencv_core",
|
||||
"//mediapipe/framework/port:opencv_imgproc",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
|
||||
@@ -105,7 +105,7 @@ absl::StatusOr<ImageFileProperties> GetImageFileProperites(
|
||||
} // namespace
|
||||
|
||||
// Calculator to extract EXIF information from an image file. The input is
|
||||
// a std::string containing raw byte data from a file, and the output is an
|
||||
// a string containing raw byte data from a file, and the output is an
|
||||
// ImageFileProperties proto object with the relevant fields filled in.
|
||||
// The calculator accepts the input as a stream or a side packet, and can output
|
||||
// the result as a stream or a side packet. The calculator checks that if an
|
||||
|
||||
@@ -16,10 +16,13 @@
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/formats/image_frame.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_imgproc_inc.h"
|
||||
#include "mediapipe/framework/port/ret_check.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/framework/timestamp.h"
|
||||
#include "mediapipe/gpu/scale_mode.pb.h"
|
||||
|
||||
#if !MEDIAPIPE_DISABLE_GPU
|
||||
@@ -52,6 +55,7 @@ namespace mediapipe {
|
||||
namespace {
|
||||
constexpr char kImageFrameTag[] = "IMAGE";
|
||||
constexpr char kGpuBufferTag[] = "IMAGE_GPU";
|
||||
constexpr char kVideoPrestreamTag[] = "VIDEO_PRESTREAM";
|
||||
|
||||
int RotationModeToDegrees(mediapipe::RotationMode_Mode rotation) {
|
||||
switch (rotation) {
|
||||
@@ -122,6 +126,12 @@ mediapipe::ScaleMode_Mode ParseScaleMode(
|
||||
// provided, it overrides the FLIP_VERTICALLY input side packet and/or
|
||||
// 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:
|
||||
// One of the following tags:
|
||||
// IMAGE - ImageFrame representing the output image.
|
||||
@@ -242,6 +252,21 @@ absl::Status ImageTransformationCalculator::GetContract(
|
||||
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")) {
|
||||
cc->InputSidePackets().Tag("OUTPUT_DIMENSIONS").Set<DimensionsPacketType>();
|
||||
}
|
||||
@@ -326,6 +351,24 @@ absl::Status ImageTransformationCalculator::Open(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.
|
||||
if (cc->Inputs().HasTag("ROTATION_DEGREES") &&
|
||||
!cc->Inputs().Tag("ROTATION_DEGREES").IsEmpty()) {
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
// Takes in an encoded image std::string, decodes it by OpenCV, and converts to
|
||||
// an ImageFrame. Note that this calculator only supports grayscale and RGB
|
||||
// images for now.
|
||||
// Takes in an encoded image string, decodes it by OpenCV, and converts to an
|
||||
// ImageFrame. Note that this calculator only supports grayscale and RGB images
|
||||
// for now.
|
||||
//
|
||||
// Example config:
|
||||
// node {
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
// Takes in a std::string, draws the text std::string by cv::putText(), and
|
||||
// outputs an ImageFrame.
|
||||
// Takes in a string, draws the text string by cv::putText(), and outputs an
|
||||
// ImageFrame.
|
||||
//
|
||||
// Example config:
|
||||
// node {
|
||||
|
||||
@@ -421,6 +421,10 @@ absl::Status ScaleImageCalculator::InitializeFromOptions() {
|
||||
alignment_boundary_ = options_.alignment_boundary();
|
||||
}
|
||||
|
||||
if (options_.has_output_format()) {
|
||||
output_format_ = options_.output_format();
|
||||
}
|
||||
|
||||
downscaler_.reset(new ImageResizer(options_.post_sharpening_coefficient()));
|
||||
|
||||
return absl::OkStatus();
|
||||
@@ -433,13 +437,17 @@ absl::Status ScaleImageCalculator::ValidateImageFormats() const {
|
||||
<< "The output image format was set to UNKNOWN.";
|
||||
// TODO Remove these conditions.
|
||||
RET_CHECK(output_format_ == ImageFormat::SRGB ||
|
||||
output_format_ == ImageFormat::SRGBA ||
|
||||
(input_format_ == output_format_ &&
|
||||
output_format_ == ImageFormat::YCBCR420P))
|
||||
<< "Outputting YCbCr420P images from SRGB input is not yet supported";
|
||||
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 "
|
||||
"YCbCr420P to SRGB) is not yet supported.";
|
||||
"YCbCr420P to SRGB or SRGB to SRBGA) is not yet supported.";
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -553,7 +561,6 @@ absl::Status ScaleImageCalculator::Process(CalculatorContext* cc) {
|
||||
}
|
||||
}
|
||||
|
||||
cc->GetCounter("Inputs")->Increment();
|
||||
const ImageFrame* image_frame;
|
||||
ImageFrame converted_image_frame;
|
||||
if (input_format_ == ImageFormat::YCBCR420P) {
|
||||
@@ -605,6 +612,15 @@ absl::Status ScaleImageCalculator::Process(CalculatorContext* cc) {
|
||||
.Add(output_image.release(), cc->InputTimestamp());
|
||||
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 {
|
||||
image_frame = &cc->Inputs().Get(input_data_id_).Get<ImageFrame>();
|
||||
MP_RETURN_IF_ERROR(ValidateImageFrame(cc, *image_frame));
|
||||
|
||||
@@ -183,22 +183,22 @@ absl::Status SegmentationSmoothingCalculator::Close(CalculatorContext* cc) {
|
||||
absl::Status SegmentationSmoothingCalculator::RenderCpu(CalculatorContext* cc) {
|
||||
// Setup source images.
|
||||
const auto& current_frame = cc->Inputs().Tag(kCurrentMaskTag).Get<Image>();
|
||||
const cv::Mat current_mat = mediapipe::formats::MatView(¤t_frame);
|
||||
RET_CHECK_EQ(current_mat.type(), CV_32FC1)
|
||||
auto current_mat = mediapipe::formats::MatView(¤t_frame);
|
||||
RET_CHECK_EQ(current_mat->type(), CV_32FC1)
|
||||
<< "Only 1-channel float input image is supported.";
|
||||
|
||||
const auto& previous_frame = cc->Inputs().Tag(kPreviousMaskTag).Get<Image>();
|
||||
const cv::Mat previous_mat = mediapipe::formats::MatView(&previous_frame);
|
||||
RET_CHECK_EQ(previous_mat.type(), current_mat.type())
|
||||
<< "Warning: mixing input format types: " << previous_mat.type()
|
||||
<< " != " << previous_mat.type();
|
||||
auto previous_mat = mediapipe::formats::MatView(&previous_frame);
|
||||
RET_CHECK_EQ(previous_mat->type(), current_mat->type())
|
||||
<< "Warning: mixing input format types: " << previous_mat->type()
|
||||
<< " != " << previous_mat->type();
|
||||
|
||||
RET_CHECK_EQ(current_mat.rows, previous_mat.rows);
|
||||
RET_CHECK_EQ(current_mat.cols, previous_mat.cols);
|
||||
RET_CHECK_EQ(current_mat->rows, previous_mat->rows);
|
||||
RET_CHECK_EQ(current_mat->cols, previous_mat->cols);
|
||||
|
||||
// Setup destination image.
|
||||
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());
|
||||
output_mat.setTo(cv::Scalar(0));
|
||||
|
||||
@@ -233,8 +233,8 @@ absl::Status SegmentationSmoothingCalculator::RenderCpu(CalculatorContext* cc) {
|
||||
// Write directly to the first channel of output.
|
||||
for (int i = 0; i < output_mat.rows; ++i) {
|
||||
float* out_ptr = output_mat.ptr<float>(i);
|
||||
const float* curr_ptr = current_mat.ptr<float>(i);
|
||||
const float* prev_ptr = previous_mat.ptr<float>(i);
|
||||
const float* curr_ptr = current_mat->ptr<float>(i);
|
||||
const float* prev_ptr = previous_mat->ptr<float>(i);
|
||||
for (int j = 0; j < output_mat.cols; ++j) {
|
||||
const float new_mask_value = curr_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());
|
||||
|
||||
Image result_image = output_packets[0].Get<Image>();
|
||||
cv::Mat result_mat = formats::MatView(&result_image);
|
||||
result_mat.copyTo(*result);
|
||||
auto result_mat = formats::MatView(&result_image);
|
||||
result_mat->copyTo(*result);
|
||||
|
||||
// Fully close graph at end, otherwise calculator+Images are destroyed
|
||||
// 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>(
|
||||
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>(
|
||||
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;
|
||||
RunGraph(curr_packet, prev_packet, use_gpu, mix_ratio, &result);
|
||||
|
||||
@@ -28,7 +28,9 @@ package(default_visibility = ["//visibility:private"])
|
||||
|
||||
exports_files(
|
||||
glob(["testdata/image_to_tensor/*"]),
|
||||
visibility = ["//mediapipe/calculators/image:__subpackages__"],
|
||||
visibility = [
|
||||
"//mediapipe/calculators/image:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
selects.config_setting_group(
|
||||
@@ -64,15 +66,16 @@ cc_library(
|
||||
":inference_calculator_cc_proto",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/api2:node",
|
||||
"//mediapipe/framework/api2:packet",
|
||||
"//mediapipe/framework/formats:tensor",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/stream_handler:fixed_size_input_stream_handler",
|
||||
"//mediapipe/framework/tool:subgraph_expansion",
|
||||
"//mediapipe/util/tflite:config",
|
||||
"//mediapipe/util/tflite:tflite_model_loader",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@org_tensorflow//tensorflow/lite:framework",
|
||||
"@org_tensorflow//tensorflow/lite/core/api:op_resolver",
|
||||
"@org_tensorflow//tensorflow/lite/kernels:builtin_ops",
|
||||
],
|
||||
alwayslink = 1,
|
||||
@@ -84,14 +87,16 @@ cc_library(
|
||||
tags = ["nomac"], # config problem with cpuinfo via TF
|
||||
deps = [
|
||||
"inference_calculator_interface",
|
||||
"//mediapipe/framework/deps:file_path",
|
||||
"//mediapipe/gpu:gl_calculator_helper",
|
||||
"//mediapipe/gpu:gpu_buffer",
|
||||
"//mediapipe/util/tflite:config",
|
||||
"//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/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,
|
||||
)
|
||||
@@ -141,6 +146,8 @@ cc_library(
|
||||
":inference_calculator_interface",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@org_tensorflow//tensorflow/lite/delegates/xnnpack:xnnpack_delegate",
|
||||
"@org_tensorflow//tensorflow/lite:framework_stable",
|
||||
"@org_tensorflow//tensorflow/lite/c:c_api_types",
|
||||
] + select({
|
||||
"//conditions:default": [
|
||||
"//mediapipe/util:cpu_util",
|
||||
@@ -154,7 +161,7 @@ cc_library(
|
||||
|
||||
cc_library(
|
||||
name = "inference_calculator_gl_if_compute_shader_available",
|
||||
deps = select({
|
||||
deps = selects.with_or({
|
||||
":compute_shader_unavailable": [],
|
||||
"//conditions:default": [":inference_calculator_gl"],
|
||||
}),
|
||||
@@ -303,7 +310,7 @@ cc_library(
|
||||
"//mediapipe/framework/formats:tensor",
|
||||
"//mediapipe/framework/formats/object_detection:anchor_cc_proto",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
] + select({
|
||||
] + selects.with_or({
|
||||
":compute_shader_unavailable": [],
|
||||
"//conditions:default": [":tensors_to_detections_calculator_gpu_deps"],
|
||||
}),
|
||||
@@ -560,7 +567,7 @@ cc_library(
|
||||
|
||||
cc_library(
|
||||
name = "image_to_tensor_calculator_gpu_deps",
|
||||
deps = select({
|
||||
deps = selects.with_or({
|
||||
"//mediapipe:android": [
|
||||
":image_to_tensor_converter_gl_buffer",
|
||||
"//mediapipe/gpu:gl_calculator_helper",
|
||||
@@ -684,7 +691,7 @@ cc_library(
|
||||
name = "image_to_tensor_converter_gl_buffer",
|
||||
srcs = ["image_to_tensor_converter_gl_buffer.cc"],
|
||||
hdrs = ["image_to_tensor_converter_gl_buffer.h"],
|
||||
deps = ["//mediapipe/framework:port"] + select({
|
||||
deps = ["//mediapipe/framework:port"] + selects.with_or({
|
||||
"//mediapipe:apple": [],
|
||||
"//conditions:default": [
|
||||
":image_to_tensor_converter",
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
#include "mediapipe/calculators/tensor/image_to_tensor_converter_gl_texture.h"
|
||||
#include "mediapipe/gpu/gl_calculator_helper.h"
|
||||
#endif // MEDIAPIPE_METAL_ENABLED
|
||||
|
||||
#endif // !MEDIAPIPE_DISABLE_GPU
|
||||
|
||||
namespace mediapipe {
|
||||
@@ -142,11 +141,37 @@ class ImageToTensorCalculator : public Node {
|
||||
const auto& options =
|
||||
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.";
|
||||
RET_CHECK_LT(options.output_tensor_float_range().min(),
|
||||
options.output_tensor_float_range().max())
|
||||
<< "Valid output tensor range is required.";
|
||||
if (options.has_output_tensor_float_range()) {
|
||||
RET_CHECK_LT(options.output_tensor_float_range().min(),
|
||||
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)
|
||||
<< "Valid output tensor width is required.";
|
||||
RET_CHECK_GT(options.output_tensor_height(), 0)
|
||||
@@ -175,9 +200,19 @@ class ImageToTensorCalculator : public Node {
|
||||
options_ = cc->Options<mediapipe::ImageToTensorCalculatorOptions>();
|
||||
output_width_ = options_.output_tensor_width();
|
||||
output_height_ = options_.output_tensor_height();
|
||||
range_min_ = options_.output_tensor_float_range().min();
|
||||
range_max_ = options_.output_tensor_float_range().max();
|
||||
|
||||
is_float_output_ = options_.has_output_tensor_float_range();
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -225,7 +260,7 @@ class ImageToTensorCalculator : public Node {
|
||||
}
|
||||
|
||||
// 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,
|
||||
(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(
|
||||
CalculatorContext* cc) {
|
||||
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.
|
||||
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 !MEDIAPIPE_DISABLE_GPU
|
||||
#if MEDIAPIPE_METAL_ENABLED
|
||||
@@ -296,17 +348,26 @@ class ImageToTensorCalculator : public Node {
|
||||
CreateImageToGlBufferTensorConverter(
|
||||
cc, DoesGpuInputStartAtBottom(), GetBorderMode()));
|
||||
#else
|
||||
ASSIGN_OR_RETURN(gpu_converter_,
|
||||
CreateImageToGlTextureTensorConverter(
|
||||
cc, DoesGpuInputStartAtBottom(), GetBorderMode()));
|
||||
// Check whether the underlying storage object is a GL texture.
|
||||
if (image.GetGpuBuffer()
|
||||
.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_DISABLE_GPU
|
||||
}
|
||||
} else {
|
||||
if (!cpu_converter_) {
|
||||
#if !MEDIAPIPE_DISABLE_OPENCV
|
||||
ASSIGN_OR_RETURN(cpu_converter_,
|
||||
CreateOpenCvConverter(cc, GetBorderMode()));
|
||||
ASSIGN_OR_RETURN(
|
||||
cpu_converter_,
|
||||
CreateOpenCvConverter(cc, GetBorderMode(), GetOutputTensorType()));
|
||||
#else
|
||||
LOG(FATAL) << "Cannot create image to tensor opencv converter since "
|
||||
"MEDIAPIPE_DISABLE_OPENCV is defined.";
|
||||
@@ -321,6 +382,7 @@ class ImageToTensorCalculator : public Node {
|
||||
mediapipe::ImageToTensorCalculatorOptions options_;
|
||||
int output_width_ = 0;
|
||||
int output_height_ = 0;
|
||||
bool is_float_output_ = false;
|
||||
float range_min_ = 0.0f;
|
||||
float range_max_ = 1.0f;
|
||||
};
|
||||
|
||||
@@ -31,6 +31,22 @@ message ImageToTensorCalculatorOptions {
|
||||
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.
|
||||
enum BorderMode {
|
||||
BORDER_UNSPECIFIED = 0;
|
||||
@@ -49,6 +65,8 @@ message ImageToTensorCalculatorOptions {
|
||||
// Output tensor element range/type image pixels are converted to.
|
||||
oneof range {
|
||||
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
|
||||
|
||||
@@ -61,7 +61,8 @@ void RunTestWithInputImagePacket(const Packet& input_image_packet,
|
||||
float range_max, int tensor_width,
|
||||
int tensor_height, bool keep_aspect,
|
||||
absl::optional<BorderMode> border_mode,
|
||||
const mediapipe::NormalizedRect& roi) {
|
||||
const mediapipe::NormalizedRect& roi,
|
||||
bool output_int_tensor) {
|
||||
std::string border_mode_str;
|
||||
if (border_mode) {
|
||||
switch (*border_mode) {
|
||||
@@ -73,6 +74,30 @@ void RunTestWithInputImagePacket(const Packet& input_image_packet,
|
||||
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>(
|
||||
absl::Substitute(R"(
|
||||
input_stream: "input_image"
|
||||
@@ -86,22 +111,18 @@ void RunTestWithInputImagePacket(const Packet& input_image_packet,
|
||||
[mediapipe.ImageToTensorCalculatorOptions.ext] {
|
||||
output_tensor_width: $0
|
||||
output_tensor_height: $1
|
||||
keep_aspect_ratio: $4
|
||||
output_tensor_float_range {
|
||||
min: $2
|
||||
max: $3
|
||||
}
|
||||
$5 # border mode
|
||||
keep_aspect_ratio: $2
|
||||
$3 # output range
|
||||
$4 # border mode
|
||||
}
|
||||
}
|
||||
}
|
||||
)",
|
||||
/*$0=*/tensor_width,
|
||||
/*$1=*/tensor_height,
|
||||
/*$2=*/range_min,
|
||||
/*$3=*/range_max,
|
||||
/*$4=*/keep_aspect ? "true" : "false",
|
||||
/*$5=*/border_mode_str));
|
||||
/*$2=*/keep_aspect ? "true" : "false",
|
||||
/*$3=*/output_tensor_range,
|
||||
/*$4=*/border_mode_str));
|
||||
|
||||
std::vector<Packet> 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));
|
||||
|
||||
const Tensor& tensor = tensor_vec[0];
|
||||
EXPECT_EQ(tensor.element_type(), Tensor::ElementType::kFloat32);
|
||||
|
||||
auto view = tensor.GetCpuReadView();
|
||||
cv::Mat tensor_mat(tensor_height, tensor_width, CV_32FC3,
|
||||
const_cast<float*>(view.buffer<float>()));
|
||||
cv::Mat tensor_mat;
|
||||
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;
|
||||
auto transformation =
|
||||
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,
|
||||
InputType::kImage};
|
||||
|
||||
void RunTest(cv::Mat input, cv::Mat expected_result, float range_min,
|
||||
float range_max, int tensor_width, int tensor_height,
|
||||
bool keep_aspect, absl::optional<BorderMode> border_mode,
|
||||
void RunTest(cv::Mat input, cv::Mat expected_result,
|
||||
std::vector<std::pair<float, float>> float_ranges,
|
||||
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) {
|
||||
for (auto input_type : kInputTypesToTest) {
|
||||
RunTestWithInputImagePacket(
|
||||
input_type == InputType::kImageFrame ? MakeImageFramePacket(input)
|
||||
: MakeImagePacket(input),
|
||||
expected_result, range_min, range_max, tensor_width, tensor_height,
|
||||
keep_aspect, border_mode, roi);
|
||||
for (auto float_range : float_ranges) {
|
||||
RunTestWithInputImagePacket(
|
||||
input_type == InputType::kImageFrame ? MakeImageFramePacket(input)
|
||||
: MakeImagePacket(input),
|
||||
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"),
|
||||
GetRgb("/mediapipe/calculators/"
|
||||
"tensor/testdata/image_to_tensor/medium_sub_rect_keep_aspect.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,
|
||||
/*border mode*/ {}, roi);
|
||||
}
|
||||
@@ -213,8 +260,8 @@ TEST(ImageToTensorCalculatorTest, MediumSubRectKeepAspectBorderZero) {
|
||||
GetRgb("/mediapipe/calculators/"
|
||||
"tensor/testdata/image_to_tensor/"
|
||||
"medium_sub_rect_keep_aspect_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,
|
||||
BorderMode::kZero, roi);
|
||||
}
|
||||
@@ -231,7 +278,8 @@ TEST(ImageToTensorCalculatorTest, MediumSubRectKeepAspectWithRotation) {
|
||||
GetRgb("/mediapipe/calculators/"
|
||||
"tensor/testdata/image_to_tensor/"
|
||||
"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,
|
||||
BorderMode::kReplicate, roi);
|
||||
}
|
||||
@@ -249,7 +297,8 @@ TEST(ImageToTensorCalculatorTest,
|
||||
GetRgb("/mediapipe/calculators/"
|
||||
"tensor/testdata/image_to_tensor/"
|
||||
"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,
|
||||
BorderMode::kZero, roi);
|
||||
}
|
||||
@@ -267,8 +316,8 @@ TEST(ImageToTensorCalculatorTest, MediumSubRectWithRotation) {
|
||||
GetRgb(
|
||||
"/mediapipe/calculators/"
|
||||
"tensor/testdata/image_to_tensor/medium_sub_rect_with_rotation.png"),
|
||||
/*range_min=*/-1.0f,
|
||||
/*range_max=*/1.0f,
|
||||
/*float_ranges=*/{{-1.0f, 1.0f}},
|
||||
/*int_ranges=*/{{0, 255}, {-128, 127}},
|
||||
/*tensor_width=*/256, /*tensor_height=*/256, /*keep_aspect=*/false,
|
||||
BorderMode::kReplicate, roi);
|
||||
}
|
||||
@@ -285,8 +334,8 @@ TEST(ImageToTensorCalculatorTest, MediumSubRectWithRotationBorderZero) {
|
||||
GetRgb("/mediapipe/calculators/"
|
||||
"tensor/testdata/image_to_tensor/"
|
||||
"medium_sub_rect_with_rotation_border_zero.png"),
|
||||
/*range_min=*/-1.0f,
|
||||
/*range_max=*/1.0f,
|
||||
/*float_ranges=*/{{-1.0f, 1.0f}},
|
||||
/*int_ranges=*/{{0, 255}, {-128, 127}},
|
||||
/*tensor_width=*/256, /*tensor_height=*/256, /*keep_aspect=*/false,
|
||||
BorderMode::kZero, roi);
|
||||
}
|
||||
@@ -302,8 +351,8 @@ TEST(ImageToTensorCalculatorTest, LargeSubRect) {
|
||||
"tensor/testdata/image_to_tensor/input.jpg"),
|
||||
GetRgb("/mediapipe/calculators/"
|
||||
"tensor/testdata/image_to_tensor/large_sub_rect.png"),
|
||||
/*range_min=*/0.0f,
|
||||
/*range_max=*/1.0f,
|
||||
/*float_ranges=*/{{0.0f, 1.0f}},
|
||||
/*int_ranges=*/{{0, 255}},
|
||||
/*tensor_width=*/128, /*tensor_height=*/128, /*keep_aspect=*/false,
|
||||
BorderMode::kReplicate, roi);
|
||||
}
|
||||
@@ -320,8 +369,8 @@ TEST(ImageToTensorCalculatorTest, LargeSubRectBorderZero) {
|
||||
"tensor/testdata/image_to_tensor/input.jpg"),
|
||||
GetRgb("/mediapipe/calculators/"
|
||||
"tensor/testdata/image_to_tensor/large_sub_rect_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=*/128, /*tensor_height=*/128, /*keep_aspect=*/false,
|
||||
BorderMode::kZero, roi);
|
||||
}
|
||||
@@ -338,8 +387,8 @@ TEST(ImageToTensorCalculatorTest, LargeSubRectKeepAspect) {
|
||||
"tensor/testdata/image_to_tensor/input.jpg"),
|
||||
GetRgb("/mediapipe/calculators/"
|
||||
"tensor/testdata/image_to_tensor/large_sub_rect_keep_aspect.png"),
|
||||
/*range_min=*/0.0f,
|
||||
/*range_max=*/1.0f,
|
||||
/*float_ranges=*/{{0.0f, 1.0f}},
|
||||
/*int_ranges=*/{{0, 255}, {-128, 127}},
|
||||
/*tensor_width=*/128, /*tensor_height=*/128, /*keep_aspect=*/true,
|
||||
BorderMode::kReplicate, roi);
|
||||
}
|
||||
@@ -356,8 +405,8 @@ TEST(ImageToTensorCalculatorTest, LargeSubRectKeepAspectBorderZero) {
|
||||
GetRgb("/mediapipe/calculators/"
|
||||
"tensor/testdata/image_to_tensor/"
|
||||
"large_sub_rect_keep_aspect_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=*/128, /*tensor_height=*/128, /*keep_aspect=*/true,
|
||||
BorderMode::kZero, roi);
|
||||
}
|
||||
@@ -374,8 +423,8 @@ TEST(ImageToTensorCalculatorTest, LargeSubRectKeepAspectWithRotation) {
|
||||
GetRgb("/mediapipe/calculators/"
|
||||
"tensor/testdata/image_to_tensor/"
|
||||
"large_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}, {-128, 127}},
|
||||
/*tensor_width=*/128, /*tensor_height=*/128, /*keep_aspect=*/true,
|
||||
/*border_mode=*/{}, roi);
|
||||
}
|
||||
@@ -393,8 +442,8 @@ TEST(ImageToTensorCalculatorTest,
|
||||
GetRgb("/mediapipe/calculators/"
|
||||
"tensor/testdata/image_to_tensor/"
|
||||
"large_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}},
|
||||
/*tensor_width=*/128, /*tensor_height=*/128, /*keep_aspect=*/true,
|
||||
/*border_mode=*/BorderMode::kZero, roi);
|
||||
}
|
||||
@@ -410,8 +459,8 @@ TEST(ImageToTensorCalculatorTest, NoOpExceptRange) {
|
||||
"tensor/testdata/image_to_tensor/input.jpg"),
|
||||
GetRgb("/mediapipe/calculators/"
|
||||
"tensor/testdata/image_to_tensor/noop_except_range.png"),
|
||||
/*range_min=*/0.0f,
|
||||
/*range_max=*/1.0f,
|
||||
/*float_ranges=*/{{0.0f, 1.0f}},
|
||||
/*int_ranges=*/{{0, 255}, {-128, 127}},
|
||||
/*tensor_width=*/64, /*tensor_height=*/128, /*keep_aspect=*/true,
|
||||
BorderMode::kReplicate, roi);
|
||||
}
|
||||
@@ -427,8 +476,8 @@ TEST(ImageToTensorCalculatorTest, NoOpExceptRangeBorderZero) {
|
||||
"tensor/testdata/image_to_tensor/input.jpg"),
|
||||
GetRgb("/mediapipe/calculators/"
|
||||
"tensor/testdata/image_to_tensor/noop_except_range.png"),
|
||||
/*range_min=*/0.0f,
|
||||
/*range_max=*/1.0f,
|
||||
/*float_ranges=*/{{0.0f, 1.0f}},
|
||||
/*int_ranges=*/{{0, 255}, {-128, 127}},
|
||||
/*tensor_width=*/64, /*tensor_height=*/128, /*keep_aspect=*/true,
|
||||
BorderMode::kZero, roi);
|
||||
}
|
||||
|
||||
@@ -268,10 +268,12 @@ class GlProcessor : public ImageToTensorConverter {
|
||||
const RotatedRect& roi,
|
||||
const Size& output_dims, float range_min,
|
||||
float range_max) override {
|
||||
if (input.format() != mediapipe::GpuBufferFormat::kBGRA32) {
|
||||
return InvalidArgumentError(
|
||||
absl::StrCat("Only BGRA/RGBA textures are supported, passed format: ",
|
||||
static_cast<uint32_t>(input.format())));
|
||||
if (input.format() != mediapipe::GpuBufferFormat::kBGRA32 &&
|
||||
input.format() != mediapipe::GpuBufferFormat::kRGBAHalf64 &&
|
||||
input.format() != mediapipe::GpuBufferFormat::kRGBAFloat128) {
|
||||
return InvalidArgumentError(absl::StrCat(
|
||||
"Only 4-channel texture input formats are supported, passed format: ",
|
||||
static_cast<uint32_t>(input.format())));
|
||||
}
|
||||
|
||||
constexpr int kNumChannels = 3;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
#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 <memory>
|
||||
@@ -172,10 +172,12 @@ class GlProcessor : public ImageToTensorConverter {
|
||||
const RotatedRect& roi,
|
||||
const Size& output_dims, float range_min,
|
||||
float range_max) override {
|
||||
if (input.format() != mediapipe::GpuBufferFormat::kBGRA32) {
|
||||
return InvalidArgumentError(
|
||||
absl::StrCat("Only BGRA/RGBA textures are supported, passed format: ",
|
||||
static_cast<uint32_t>(input.format())));
|
||||
if (input.format() != mediapipe::GpuBufferFormat::kBGRA32 &&
|
||||
input.format() != mediapipe::GpuBufferFormat::kRGBAHalf64 &&
|
||||
input.format() != mediapipe::GpuBufferFormat::kRGBAFloat128) {
|
||||
return InvalidArgumentError(absl::StrCat(
|
||||
"Only 4-channel texture input formats are supported, passed format: ",
|
||||
static_cast<uint32_t>(input.format())));
|
||||
}
|
||||
|
||||
constexpr int kNumChannels = 3;
|
||||
@@ -339,4 +341,4 @@ CreateImageToGlTextureTensorConverter(CalculatorContext* cc,
|
||||
|
||||
} // 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"
|
||||
|
||||
#if MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_20
|
||||
#if MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_30
|
||||
|
||||
#include <memory>
|
||||
|
||||
@@ -37,6 +37,6 @@ CreateImageToGlTextureTensorConverter(CalculatorContext* cc,
|
||||
|
||||
} // 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_
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#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 <memory>
|
||||
@@ -85,4 +85,4 @@ bool IsGlClampToBorderSupported(const mediapipe::GlContext& gl_context) {
|
||||
|
||||
} // 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"
|
||||
|
||||
#if MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_20
|
||||
#if MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_30
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
@@ -40,6 +40,6 @@ bool IsGlClampToBorderSupported(const mediapipe::GlContext& gl_context);
|
||||
|
||||
} // 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_
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#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/framework/port/gtest.h"
|
||||
@@ -46,4 +46,4 @@ TEST(ImageToTensorConverterGlUtilsTest, GlTexParameteriOverrider) {
|
||||
} // namespace
|
||||
} // 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 Size& output_dims, float range_min,
|
||||
float range_max) override {
|
||||
if (input.format() != mediapipe::GpuBufferFormat::kBGRA32) {
|
||||
return InvalidArgumentError(
|
||||
absl::StrCat("Only BGRA/RGBA textures are supported, passed "
|
||||
"format: ",
|
||||
static_cast<uint32_t>(input.format())));
|
||||
if (input.format() != mediapipe::GpuBufferFormat::kBGRA32 &&
|
||||
input.format() != mediapipe::GpuBufferFormat::kRGBAHalf64 &&
|
||||
input.format() != mediapipe::GpuBufferFormat::kRGBAFloat128) {
|
||||
return InvalidArgumentError(absl::StrCat(
|
||||
"Only 4-channel texture input formats are supported, passed format: ",
|
||||
static_cast<uint32_t>(input.format())));
|
||||
}
|
||||
|
||||
@autoreleasepool {
|
||||
|
||||
@@ -35,7 +35,8 @@ namespace {
|
||||
|
||||
class OpenCvProcessor : public ImageToTensorConverter {
|
||||
public:
|
||||
OpenCvProcessor(BorderMode border_mode) {
|
||||
OpenCvProcessor(BorderMode border_mode, Tensor::ElementType tensor_type)
|
||||
: tensor_type_(tensor_type) {
|
||||
switch (border_mode) {
|
||||
case BorderMode::kReplicate:
|
||||
border_mode_ = cv::BORDER_REPLICATE;
|
||||
@@ -44,6 +45,19 @@ class OpenCvProcessor : public ImageToTensorConverter {
|
||||
border_mode_ = cv::BORDER_CONSTANT;
|
||||
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,
|
||||
@@ -56,15 +70,30 @@ class OpenCvProcessor : public ImageToTensorConverter {
|
||||
absl::StrCat("Only RGBA/RGB formats are supported, passed 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;
|
||||
Tensor tensor(
|
||||
Tensor::ElementType::kFloat32,
|
||||
Tensor::Shape{1, output_dims.height, output_dims.width, kNumChannels});
|
||||
Tensor tensor(tensor_type_, Tensor::Shape{1, output_dims.height,
|
||||
output_dims.width, kNumChannels});
|
||||
auto buffer_view = tensor.GetCpuWriteView();
|
||||
cv::Mat dst(output_dims.height, output_dims.width, CV_32FC3,
|
||||
buffer_view.buffer<float>());
|
||||
cv::Mat dst;
|
||||
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),
|
||||
cv::Size2f(roi.width, roi.height),
|
||||
@@ -85,7 +114,7 @@ class OpenCvProcessor : public ImageToTensorConverter {
|
||||
cv::Mat projection_matrix =
|
||||
cv::getPerspectiveTransform(src_points, dst_points);
|
||||
cv::Mat transformed;
|
||||
cv::warpPerspective(src, transformed, projection_matrix,
|
||||
cv::warpPerspective(*src, transformed, projection_matrix,
|
||||
cv::Size(dst_width, dst_height),
|
||||
/*flags=*/cv::INTER_LINEAR,
|
||||
/*borderMode=*/border_mode_);
|
||||
@@ -102,19 +131,29 @@ class OpenCvProcessor : public ImageToTensorConverter {
|
||||
auto transform,
|
||||
GetValueRangeTransformation(kInputImageRangeMin, kInputImageRangeMax,
|
||||
range_min, range_max));
|
||||
transformed.convertTo(dst, CV_32FC3, transform.scale, transform.offset);
|
||||
transformed.convertTo(dst, mat_type_, transform.scale, transform.offset);
|
||||
return tensor;
|
||||
}
|
||||
|
||||
private:
|
||||
enum cv::BorderTypes border_mode_;
|
||||
Tensor::ElementType tensor_type_;
|
||||
int mat_type_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
absl::StatusOr<std::unique_ptr<ImageToTensorConverter>> CreateOpenCvConverter(
|
||||
CalculatorContext* cc, BorderMode border_mode) {
|
||||
return absl::make_unique<OpenCvProcessor>(border_mode);
|
||||
CalculatorContext* cc, BorderMode 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
|
||||
|
||||
@@ -25,7 +25,8 @@ namespace mediapipe {
|
||||
|
||||
// Creates OpenCV image-to-tensor converter.
|
||||
absl::StatusOr<std::unique_ptr<ImageToTensorConverter>> CreateOpenCvConverter(
|
||||
CalculatorContext* cc, BorderMode border_mode);
|
||||
CalculatorContext* cc, BorderMode border_mode,
|
||||
Tensor::ElementType tensor_type);
|
||||
|
||||
} // namespace mediapipe
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "mediapipe/framework/api2/packet.h"
|
||||
#include "mediapipe/framework/tool/subgraph_expansion.h"
|
||||
#include "tensorflow/lite/core/api/op_resolver.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace api2 {
|
||||
@@ -36,6 +38,7 @@ class InferenceCalculatorSelectorImpl
|
||||
Subgraph::GetOptions<mediapipe::InferenceCalculatorOptions>(
|
||||
subgraph_node);
|
||||
std::vector<absl::string_view> impls;
|
||||
|
||||
const bool should_use_gpu =
|
||||
!options.has_delegate() || // Use GPU delegate if not specified
|
||||
(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.");
|
||||
}
|
||||
|
||||
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 mediapipe
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "mediapipe/framework/formats/tensor.h"
|
||||
#include "mediapipe/framework/port/ret_check.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/interpreter.h"
|
||||
#include "tensorflow/lite/kernels/register.h"
|
||||
@@ -55,8 +56,11 @@ namespace api2 {
|
||||
// TENSORS - Vector of Tensors
|
||||
//
|
||||
// Input side packet:
|
||||
// DEPRECATED: Prefer to use the "OP_RESOLVER" input side packet instead.
|
||||
// CUSTOM_OP_RESOLVER (optional) - Use a custom op resolver,
|
||||
// instead of the builtin one.
|
||||
// OP_RESOLVER (optional) - Use to provide tflite op resolver
|
||||
// (tflite::OpResolver)
|
||||
// MODEL (optional) - Use to specify TfLite model
|
||||
// (std::unique_ptr<tflite::FlatBufferModel,
|
||||
// std::function<void(tflite::FlatBufferModel*)>>)
|
||||
@@ -95,15 +99,21 @@ namespace api2 {
|
||||
class InferenceCalculator : public NodeIntf {
|
||||
public:
|
||||
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
|
||||
kSideInCustomOpResolver{"CUSTOM_OP_RESOLVER"};
|
||||
static constexpr SideInput<tflite::OpResolver>::Optional kSideInOpResolver{
|
||||
"OP_RESOLVER"};
|
||||
static constexpr SideInput<TfLiteModelPtr>::Optional kSideInModel{"MODEL"};
|
||||
static constexpr Output<std::vector<Tensor>> kOutTensors{"TENSORS"};
|
||||
static constexpr SideInput<
|
||||
mediapipe::InferenceCalculatorOptions::Delegate>::Optional kDelegate{
|
||||
"DELEGATE"};
|
||||
MEDIAPIPE_NODE_CONTRACT(kInTensors, kSideInCustomOpResolver, kSideInModel,
|
||||
kOutTensors, kDelegate);
|
||||
MEDIAPIPE_NODE_CONTRACT(kInTensors, kSideInCustomOpResolver,
|
||||
kSideInOpResolver, kSideInModel, kOutTensors,
|
||||
kDelegate);
|
||||
|
||||
protected:
|
||||
using TfLiteDelegatePtr =
|
||||
@@ -111,6 +121,9 @@ class InferenceCalculator : public NodeIntf {
|
||||
|
||||
absl::StatusOr<Packet<TfLiteModelPtr>> GetModelAsPacket(
|
||||
CalculatorContext* cc);
|
||||
|
||||
absl::StatusOr<Packet<tflite::OpResolver>> GetOpResolverAsPacket(
|
||||
CalculatorContext* cc);
|
||||
};
|
||||
|
||||
struct InferenceCalculatorSelector : public InferenceCalculator {
|
||||
|
||||
@@ -116,6 +116,9 @@ message InferenceCalculatorOptions {
|
||||
// to ensure there is no clash of the tokens. If unspecified, NNAPI will
|
||||
// not try caching the compilation.
|
||||
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 {
|
||||
// Number of threads for XNNPACK delegate. (By default, calculator tries
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "mediapipe/calculators/tensor/inference_calculator.h"
|
||||
|
||||
#include "tensorflow/lite/interpreter_builder.h"
|
||||
#if defined(MEDIAPIPE_ANDROID)
|
||||
#include "tensorflow/lite/delegates/nnapi/nnapi_delegate.h"
|
||||
#endif // ANDROID
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "mediapipe/util/cpu_util.h"
|
||||
#endif // !__EMSCRIPTEN__ || __EMSCRIPTEN_PTHREADS__
|
||||
|
||||
#include "tensorflow/lite/c/c_api_types.h"
|
||||
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
|
||||
|
||||
namespace mediapipe {
|
||||
@@ -61,6 +62,17 @@ int GetXnnpackNumThreads(
|
||||
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
|
||||
|
||||
class InferenceCalculatorCpuImpl
|
||||
@@ -73,14 +85,16 @@ class InferenceCalculatorCpuImpl
|
||||
absl::Status Close(CalculatorContext* cc) override;
|
||||
|
||||
private:
|
||||
absl::Status LoadModel(CalculatorContext* cc);
|
||||
absl::Status LoadDelegate(CalculatorContext* cc);
|
||||
absl::Status LoadDelegateAndAllocateTensors(CalculatorContext* cc);
|
||||
absl::Status InitInterpreter(CalculatorContext* cc);
|
||||
absl::Status LoadDelegate(CalculatorContext* cc,
|
||||
tflite::InterpreterBuilder* interpreter_builder);
|
||||
absl::Status AllocateTensors();
|
||||
|
||||
// TfLite requires us to keep the model alive as long as the interpreter is.
|
||||
Packet<TfLiteModelPtr> model_packet_;
|
||||
std::unique_ptr<tflite::Interpreter> interpreter_;
|
||||
TfLiteDelegatePtr delegate_;
|
||||
TfLiteType input_tensor_type_ = TfLiteType::kTfLiteNoType;
|
||||
};
|
||||
|
||||
absl::Status InferenceCalculatorCpuImpl::UpdateContract(
|
||||
@@ -93,8 +107,7 @@ absl::Status InferenceCalculatorCpuImpl::UpdateContract(
|
||||
}
|
||||
|
||||
absl::Status InferenceCalculatorCpuImpl::Open(CalculatorContext* cc) {
|
||||
MP_RETURN_IF_ERROR(LoadModel(cc));
|
||||
return LoadDelegateAndAllocateTensors(cc);
|
||||
return InitInterpreter(cc);
|
||||
}
|
||||
|
||||
absl::Status InferenceCalculatorCpuImpl::Process(CalculatorContext* cc) {
|
||||
@@ -107,12 +120,24 @@ absl::Status InferenceCalculatorCpuImpl::Process(CalculatorContext* cc) {
|
||||
|
||||
// Read CPU input into tensors.
|
||||
for (int i = 0; i < input_tensors.size(); ++i) {
|
||||
const Tensor* input_tensor = &input_tensors[i];
|
||||
auto input_tensor_view = input_tensor->GetCpuReadView();
|
||||
auto input_tensor_buffer = input_tensor_view.buffer<float>();
|
||||
float* local_tensor_buffer = interpreter_->typed_input_tensor<float>(i);
|
||||
std::memcpy(local_tensor_buffer, input_tensor_buffer,
|
||||
input_tensor->bytes());
|
||||
switch (input_tensor_type_) {
|
||||
case TfLiteType::kTfLiteFloat16:
|
||||
case TfLiteType::kTfLiteFloat32: {
|
||||
CopyTensorBuffer<float>(input_tensors[i], interpreter_.get(), i);
|
||||
break;
|
||||
}
|
||||
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.
|
||||
@@ -141,40 +166,34 @@ absl::Status InferenceCalculatorCpuImpl::Close(CalculatorContext* cc) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status InferenceCalculatorCpuImpl::LoadModel(CalculatorContext* cc) {
|
||||
absl::Status InferenceCalculatorCpuImpl::InitInterpreter(
|
||||
CalculatorContext* cc) {
|
||||
ASSIGN_OR_RETURN(model_packet_, GetModelAsPacket(cc));
|
||||
const auto& model = *model_packet_.Get();
|
||||
tflite::ops::builtin::BuiltinOpResolver op_resolver =
|
||||
kSideInCustomOpResolver(cc).GetOr(
|
||||
tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates());
|
||||
|
||||
tflite::InterpreterBuilder(model, op_resolver)(&interpreter_);
|
||||
RET_CHECK(interpreter_);
|
||||
|
||||
ASSIGN_OR_RETURN(auto op_resolver_packet, GetOpResolverAsPacket(cc));
|
||||
const auto& op_resolver = op_resolver_packet.Get();
|
||||
tflite::InterpreterBuilder interpreter_builder(model, op_resolver);
|
||||
MP_RETURN_IF_ERROR(LoadDelegate(cc, &interpreter_builder));
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
interpreter_->SetNumThreads(1);
|
||||
interpreter_builder.SetNumThreads(1);
|
||||
#else
|
||||
interpreter_->SetNumThreads(
|
||||
interpreter_builder.SetNumThreads(
|
||||
cc->Options<mediapipe::InferenceCalculatorOptions>().cpu_num_thread());
|
||||
#endif // __EMSCRIPTEN__
|
||||
|
||||
return absl::OkStatus();
|
||||
RET_CHECK_EQ(interpreter_builder(&interpreter_), kTfLiteOk);
|
||||
RET_CHECK(interpreter_);
|
||||
return AllocateTensors();
|
||||
}
|
||||
|
||||
absl::Status InferenceCalculatorCpuImpl::LoadDelegateAndAllocateTensors(
|
||||
CalculatorContext* cc) {
|
||||
MP_RETURN_IF_ERROR(LoadDelegate(cc));
|
||||
|
||||
// AllocateTensors() can be called only after ModifyGraphWithDelegate.
|
||||
absl::Status InferenceCalculatorCpuImpl::AllocateTensors() {
|
||||
RET_CHECK_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
|
||||
// TODO: Support quantized tensors.
|
||||
RET_CHECK_NE(
|
||||
interpreter_->tensor(interpreter_->inputs()[0])->quantization.type,
|
||||
kTfLiteAffineQuantization);
|
||||
input_tensor_type_ = interpreter_->tensor(interpreter_->inputs()[0])->type;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status InferenceCalculatorCpuImpl::LoadDelegate(CalculatorContext* cc) {
|
||||
absl::Status InferenceCalculatorCpuImpl::LoadDelegate(
|
||||
CalculatorContext* cc, tflite::InterpreterBuilder* interpreter_builder) {
|
||||
const auto& calculator_opts =
|
||||
cc->Options<mediapipe::InferenceCalculatorOptions>();
|
||||
auto opts_delegate = calculator_opts.delegate();
|
||||
@@ -203,18 +222,20 @@ absl::Status InferenceCalculatorCpuImpl::LoadDelegate(CalculatorContext* cc) {
|
||||
if (nnapi_requested) {
|
||||
// Attempt to use NNAPI.
|
||||
// If not supported, the default CPU delegate will be created and used.
|
||||
interpreter_->SetAllowFp16PrecisionForFp32(1);
|
||||
tflite::StatefulNnApiDelegate::Options options;
|
||||
const auto& nnapi = opts_delegate.nnapi();
|
||||
options.allow_fp16 = true;
|
||||
// Set up cache_dir and model_token for NNAPI compilation cache.
|
||||
options.cache_dir =
|
||||
nnapi.has_cache_dir() ? nnapi.cache_dir().c_str() : nullptr;
|
||||
options.model_token =
|
||||
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),
|
||||
[](TfLiteDelegate*) {});
|
||||
RET_CHECK_EQ(interpreter_->ModifyGraphWithDelegate(delegate_.get()),
|
||||
kTfLiteOk);
|
||||
interpreter_builder->AddDelegate(delegate_.get());
|
||||
return absl::OkStatus();
|
||||
}
|
||||
#endif // MEDIAPIPE_ANDROID
|
||||
@@ -226,13 +247,12 @@ absl::Status InferenceCalculatorCpuImpl::LoadDelegate(CalculatorContext* cc) {
|
||||
#endif // defined(__EMSCRIPTEN__)
|
||||
|
||||
if (use_xnnpack) {
|
||||
TfLiteXNNPackDelegateOptions xnnpack_opts{};
|
||||
auto xnnpack_opts = TfLiteXNNPackDelegateOptionsDefault();
|
||||
xnnpack_opts.num_threads =
|
||||
GetXnnpackNumThreads(opts_has_delegate, opts_delegate);
|
||||
delegate_ = TfLiteDelegatePtr(TfLiteXNNPackDelegateCreate(&xnnpack_opts),
|
||||
&TfLiteXNNPackDelegateDelete);
|
||||
RET_CHECK_EQ(interpreter_->ModifyGraphWithDelegate(delegate_.get()),
|
||||
kTfLiteOk);
|
||||
interpreter_builder->AddDelegate(delegate_.get());
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
|
||||
@@ -154,8 +154,9 @@ TEST_P(InferenceCalculatorTest, TestFaceDetection) {
|
||||
detection_packets[0].Get<std::vector<Detection>>();
|
||||
#if !defined(MEDIAPIPE_PROTO_LITE)
|
||||
// Approximately is not available with lite protos (b/178137094).
|
||||
EXPECT_THAT(dets,
|
||||
ElementsAre(Approximately(EqualsProto(expected_detection))));
|
||||
constexpr float kEpison = 0.001;
|
||||
EXPECT_THAT(dets, ElementsAre(Approximately(EqualsProto(expected_detection),
|
||||
kEpison)));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -59,8 +59,6 @@ class InferenceCalculatorGlImpl
|
||||
|
||||
// TfLite requires us to keep the model alive as long as the interpreter is.
|
||||
Packet<TfLiteModelPtr> model_packet_;
|
||||
std::unique_ptr<tflite::Interpreter> interpreter_;
|
||||
TfLiteDelegatePtr delegate_;
|
||||
|
||||
#if MEDIAPIPE_TFLITE_GL_INFERENCE
|
||||
mediapipe::GlCalculatorHelper gpu_helper_;
|
||||
@@ -72,6 +70,9 @@ class InferenceCalculatorGlImpl
|
||||
tflite_gpu_runner_usage_;
|
||||
#endif // MEDIAPIPE_TFLITE_GL_INFERENCE
|
||||
|
||||
TfLiteDelegatePtr delegate_;
|
||||
std::unique_ptr<tflite::Interpreter> interpreter_;
|
||||
|
||||
#if MEDIAPIPE_TFLITE_GPU_SUPPORTED
|
||||
std::vector<Tensor::Shape> output_shapes_;
|
||||
std::vector<std::unique_ptr<Tensor>> gpu_buffers_in_;
|
||||
@@ -252,12 +253,17 @@ absl::Status InferenceCalculatorGlImpl::Close(CalculatorContext* cc) {
|
||||
MP_RETURN_IF_ERROR(gpu_helper_.RunInGlContext([this]() -> Status {
|
||||
gpu_buffers_in_.clear();
|
||||
gpu_buffers_out_.clear();
|
||||
// Delegate must outlive the interpreter, hence the order is important.
|
||||
interpreter_ = nullptr;
|
||||
delegate_ = nullptr;
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -288,9 +294,6 @@ absl::Status InferenceCalculatorGlImpl::InitTFLiteGPURunner(
|
||||
CalculatorContext* cc) {
|
||||
ASSIGN_OR_RETURN(model_packet_, GetModelAsPacket(cc));
|
||||
const auto& model = *model_packet_.Get();
|
||||
tflite::ops::builtin::BuiltinOpResolver op_resolver =
|
||||
kSideInCustomOpResolver(cc).GetOr(
|
||||
tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates());
|
||||
|
||||
// Create runner
|
||||
tflite::gpu::InferenceOptions options;
|
||||
@@ -329,8 +332,17 @@ absl::Status InferenceCalculatorGlImpl::InitTFLiteGPURunner(
|
||||
break;
|
||||
}
|
||||
}
|
||||
MP_RETURN_IF_ERROR(tflite_gpu_runner_->InitializeWithModel(
|
||||
model, op_resolver, /*allow_quant_ops=*/true));
|
||||
if (kSideInOpResolver(cc).IsConnected()) {
|
||||
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.
|
||||
// The buffers are created once and their ids are passed to calculator outputs
|
||||
@@ -352,11 +364,15 @@ absl::Status InferenceCalculatorGlImpl::InitTFLiteGPURunner(
|
||||
absl::Status InferenceCalculatorGlImpl::LoadModel(CalculatorContext* cc) {
|
||||
ASSIGN_OR_RETURN(model_packet_, GetModelAsPacket(cc));
|
||||
const auto& model = *model_packet_.Get();
|
||||
tflite::ops::builtin::BuiltinOpResolver op_resolver =
|
||||
kSideInCustomOpResolver(cc).GetOr(
|
||||
tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates());
|
||||
|
||||
tflite::InterpreterBuilder(model, op_resolver)(&interpreter_);
|
||||
if (kSideInOpResolver(cc).IsConnected()) {
|
||||
const tflite::OpResolver& op_resolver = kSideInOpResolver(cc).Get();
|
||||
tflite::InterpreterBuilder(model, op_resolver)(&interpreter_);
|
||||
} else {
|
||||
tflite::ops::builtin::BuiltinOpResolver op_resolver =
|
||||
kSideInCustomOpResolver(cc).GetOr(
|
||||
tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates());
|
||||
tflite::InterpreterBuilder(model, op_resolver)(&interpreter_);
|
||||
}
|
||||
RET_CHECK(interpreter_);
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
|
||||
@@ -90,9 +90,10 @@ class InferenceCalculatorMetalImpl
|
||||
absl::Status Close(CalculatorContext* cc) override;
|
||||
|
||||
private:
|
||||
absl::Status LoadModel(CalculatorContext* cc);
|
||||
absl::Status LoadDelegate(CalculatorContext* cc);
|
||||
absl::Status LoadDelegateAndAllocateTensors(CalculatorContext* cc);
|
||||
absl::Status InitInterpreter(CalculatorContext* cc);
|
||||
void AddDelegate(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.
|
||||
Packet<TfLiteModelPtr> model_packet_;
|
||||
@@ -127,11 +128,9 @@ absl::Status InferenceCalculatorMetalImpl::Open(CalculatorContext* cc) {
|
||||
const auto& options = cc->Options<::mediapipe::InferenceCalculatorOptions>();
|
||||
allow_precision_loss_ = options.delegate().gpu().allow_precision_loss();
|
||||
|
||||
MP_RETURN_IF_ERROR(LoadModel(cc));
|
||||
|
||||
gpu_helper_ = [[MPPMetalHelper alloc] initWithCalculatorContext:cc];
|
||||
RET_CHECK(gpu_helper_);
|
||||
return LoadDelegateAndAllocateTensors(cc);
|
||||
return InitInterpreter(cc);
|
||||
}
|
||||
|
||||
absl::Status InferenceCalculatorMetalImpl::Process(CalculatorContext* cc) {
|
||||
@@ -199,27 +198,20 @@ absl::Status InferenceCalculatorMetalImpl::Close(CalculatorContext* cc) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status InferenceCalculatorMetalImpl::LoadModel(CalculatorContext* cc) {
|
||||
absl::Status InferenceCalculatorMetalImpl::InitInterpreter(
|
||||
CalculatorContext* cc) {
|
||||
ASSIGN_OR_RETURN(model_packet_, GetModelAsPacket(cc));
|
||||
const auto& model = *model_packet_.Get();
|
||||
tflite::ops::builtin::BuiltinOpResolver op_resolver =
|
||||
kSideInCustomOpResolver(cc).GetOr(
|
||||
tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates());
|
||||
|
||||
tflite::InterpreterBuilder(model, op_resolver)(&interpreter_);
|
||||
ASSIGN_OR_RETURN(auto op_resolver_packet, GetOpResolverAsPacket(cc));
|
||||
const auto& op_resolver = op_resolver_packet.Get();
|
||||
tflite::InterpreterBuilder interpreter_builder(model, op_resolver);
|
||||
AddDelegate(cc, &interpreter_builder);
|
||||
interpreter_builder.SetNumThreads(
|
||||
cc->Options<mediapipe::InferenceCalculatorOptions>().cpu_num_thread());
|
||||
RET_CHECK_EQ(interpreter_builder(&interpreter_), kTfLiteOk);
|
||||
RET_CHECK(interpreter_);
|
||||
|
||||
interpreter_->SetNumThreads(
|
||||
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.
|
||||
MP_RETURN_IF_ERROR(CreateConverters(cc));
|
||||
RET_CHECK_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
|
||||
// TODO: Support quantized tensors.
|
||||
RET_CHECK_NE(
|
||||
@@ -228,7 +220,8 @@ absl::Status InferenceCalculatorMetalImpl::LoadDelegateAndAllocateTensors(
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status InferenceCalculatorMetalImpl::LoadDelegate(CalculatorContext* cc) {
|
||||
void InferenceCalculatorMetalImpl::AddDelegate(
|
||||
CalculatorContext* cc, tflite::InterpreterBuilder* interpreter_builder) {
|
||||
const auto& calculator_opts =
|
||||
cc->Options<mediapipe::InferenceCalculatorOptions>();
|
||||
|
||||
@@ -242,9 +235,11 @@ absl::Status InferenceCalculatorMetalImpl::LoadDelegate(CalculatorContext* cc) {
|
||||
options.wait_type = TFLGpuDelegateWaitType::TFLGpuDelegateWaitTypeDoNotWait;
|
||||
delegate_ =
|
||||
TfLiteDelegatePtr(TFLGpuDelegateCreate(&options), &TFLGpuDelegateDelete);
|
||||
RET_CHECK_EQ(interpreter_->ModifyGraphWithDelegate(delegate_.get()),
|
||||
kTfLiteOk);
|
||||
interpreter_builder->AddDelegate(delegate_.get());
|
||||
}
|
||||
|
||||
absl::Status InferenceCalculatorMetalImpl::CreateConverters(
|
||||
CalculatorContext* cc) {
|
||||
id<MTLDevice> device = gpu_helper_.mtlDevice;
|
||||
|
||||
// Get input image sizes.
|
||||
|
||||
@@ -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
|
||||
|
||||
// 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,
|
||||
float box_xmax, float score, int class_id,
|
||||
bool flip_vertically);
|
||||
bool IsClassIndexAllowed(int class_index);
|
||||
|
||||
int num_classes_ = 0;
|
||||
int num_boxes_ = 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_;
|
||||
|
||||
#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_inited_) {
|
||||
@@ -263,12 +326,15 @@ absl::Status TensorsToDetectionsCalculator::ProcessCPU(
|
||||
// Postprocessing on CPU for model without postprocessing op. E.g. output
|
||||
// raw score tensor and box tensor. Anchor decoding will be handled below.
|
||||
// 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[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[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[0], 1);
|
||||
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.
|
||||
if (!anchors_init_) {
|
||||
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[0], num_boxes_);
|
||||
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();
|
||||
// Find the top score for box i.
|
||||
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];
|
||||
if (options_.sigmoid_score()) {
|
||||
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
|
||||
// non-maximum suppression) within the model.
|
||||
RET_CHECK_EQ(input_tensors.size(), 4);
|
||||
|
||||
auto num_boxes_tensor = &input_tensors[3];
|
||||
auto num_boxes_tensor =
|
||||
&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[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[0], 1);
|
||||
const int max_detections = detection_boxes_tensor->shape().dims[1];
|
||||
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[0], 1);
|
||||
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[0], 1);
|
||||
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) {
|
||||
const auto& input_tensors = *kInTensors(cc);
|
||||
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
|
||||
|
||||
MP_RETURN_IF_ERROR(gpu_helper_.RunInGlContext([this, &input_tensors, &cc,
|
||||
@@ -392,12 +463,14 @@ absl::Status TensorsToDetectionsCalculator::ProcessGPU(
|
||||
-> absl::Status {
|
||||
if (!anchors_init_) {
|
||||
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());
|
||||
auto write_view = raw_anchors_buffer_->GetOpenGlBufferWriteView();
|
||||
glBindBuffer(GL_COPY_WRITE_BUFFER, write_view.name());
|
||||
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0,
|
||||
input_tensors[2].bytes());
|
||||
glCopyBufferSubData(
|
||||
GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0,
|
||||
input_tensors[tensor_mapping_.anchors_tensor_index()].bytes());
|
||||
} else if (!kInAnchors(cc).IsEmpty()) {
|
||||
const auto& anchors = *kInAnchors(cc);
|
||||
auto anchors_view = raw_anchors_buffer_->GetCpuWriteView();
|
||||
@@ -416,7 +489,9 @@ absl::Status TensorsToDetectionsCalculator::ProcessGPU(
|
||||
auto decoded_boxes_view =
|
||||
decoded_boxes_buffer_->GetOpenGlBufferWriteView();
|
||||
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());
|
||||
auto raw_anchors_view = raw_anchors_buffer_->GetOpenGlBufferReadView();
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, raw_anchors_view.name());
|
||||
@@ -425,7 +500,8 @@ absl::Status TensorsToDetectionsCalculator::ProcessGPU(
|
||||
|
||||
// Score boxes.
|
||||
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());
|
||||
glUseProgram(score_program_);
|
||||
glDispatchCompute(num_boxes_, 1, 1);
|
||||
@@ -457,7 +533,8 @@ absl::Status TensorsToDetectionsCalculator::ProcessGPU(
|
||||
if (input_tensors.size() == kNumInputTensorsWithAnchors) {
|
||||
RET_CHECK_EQ(input_tensors.size(), kNumInputTensorsWithAnchors);
|
||||
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 =
|
||||
raw_anchors_buffer_->GetMtlBufferWriteView(command_buffer);
|
||||
id<MTLBlitCommandEncoder> blit_command =
|
||||
@@ -466,7 +543,9 @@ absl::Status TensorsToDetectionsCalculator::ProcessGPU(
|
||||
sourceOffset:0
|
||||
toBuffer:dest_buffer.buffer()
|
||||
destinationOffset:0
|
||||
size:input_tensors[2].bytes()];
|
||||
size:input_tensors[tensor_mapping_
|
||||
.anchors_tensor_index()]
|
||||
.bytes()];
|
||||
[blit_command endEncoding];
|
||||
[command_buffer commit];
|
||||
} else if (!kInAnchors(cc).IsEmpty()) {
|
||||
@@ -493,7 +572,8 @@ absl::Status TensorsToDetectionsCalculator::ProcessGPU(
|
||||
auto decoded_boxes_view =
|
||||
decoded_boxes_buffer_->GetMtlBufferWriteView(command_buffer);
|
||||
[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];
|
||||
auto raw_anchors_view =
|
||||
raw_anchors_buffer_->GetMtlBufferReadView(command_buffer);
|
||||
@@ -505,7 +585,8 @@ absl::Status TensorsToDetectionsCalculator::ProcessGPU(
|
||||
|
||||
[command_encoder setComputePipelineState:score_program_];
|
||||
[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];
|
||||
MTLSize score_threads_per_group = MTLSizeMake(1, num_classes_, 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.
|
||||
options_ = cc->Options<::mediapipe::TensorsToDetectionsCalculatorOptions>();
|
||||
RET_CHECK(options_.has_num_classes());
|
||||
RET_CHECK(options_.has_num_boxes());
|
||||
RET_CHECK(options_.has_num_coords());
|
||||
|
||||
num_classes_ = options_.num_classes();
|
||||
num_boxes_ = options_.num_boxes();
|
||||
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.
|
||||
CHECK_EQ(options_.num_values_per_keypoint(), 2);
|
||||
@@ -580,15 +664,55 @@ absl::Status TensorsToDetectionsCalculator::LoadOptions(CalculatorContext* cc) {
|
||||
|
||||
if (kSideInIgnoreClasses(cc).IsConnected()) {
|
||||
RET_CHECK(!kSideInIgnoreClasses(cc).IsEmpty());
|
||||
RET_CHECK(options_.allow_classes().empty());
|
||||
class_index_set_.is_allowlist = false;
|
||||
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 {
|
||||
class_index_set_.is_allowlist = false;
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -660,14 +784,22 @@ absl::Status TensorsToDetectionsCalculator::ConvertToDetections(
|
||||
const float* detection_boxes, const float* detection_scores,
|
||||
const int* detection_classes, std::vector<Detection>* output_detections) {
|
||||
for (int i = 0; i < num_boxes_; ++i) {
|
||||
if (max_results_ > 0 && output_detections->size() == max_results_) {
|
||||
break;
|
||||
}
|
||||
if (options_.has_min_score_thresh() &&
|
||||
detection_scores[i] < options_.min_score_thresh()) {
|
||||
continue;
|
||||
}
|
||||
if (!IsClassIndexAllowed(detection_classes[i])) {
|
||||
continue;
|
||||
}
|
||||
const int box_offset = i * num_coords_;
|
||||
Detection detection = ConvertToDetection(
|
||||
detection_boxes[box_offset + 0], detection_boxes[box_offset + 1],
|
||||
detection_boxes[box_offset + 2], detection_boxes[box_offset + 3],
|
||||
/*box_ymin=*/detection_boxes[box_offset + box_indices_[0]],
|
||||
/*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());
|
||||
const auto& bbox = detection.location_data().relative_bounding_box();
|
||||
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() ? options_.score_clipping_thresh()
|
||||
: 0,
|
||||
!ignore_classes_.empty() ? 1 : 0);
|
||||
!IsClassIndexAllowed(0));
|
||||
|
||||
// # filter classes supported is hardware dependent.
|
||||
int max_wg_size; // typically <= 1024
|
||||
@@ -918,7 +1050,14 @@ void main() {
|
||||
CHECK_LT(num_classes_, max_wg_size)
|
||||
<< "# classes must be < " << max_wg_size;
|
||||
// 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
|
||||
{
|
||||
@@ -1125,10 +1264,17 @@ kernel void scoreKernel(
|
||||
options_.has_score_clipping_thresh() ? 1 : 0,
|
||||
options_.has_score_clipping_thresh() ? options_.score_clipping_thresh()
|
||||
: 0,
|
||||
ignore_classes_.size() ? 1 : 0);
|
||||
!IsClassIndexAllowed(0));
|
||||
|
||||
// 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
|
||||
@@ -1160,5 +1306,16 @@ kernel void scoreKernel(
|
||||
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 mediapipe
|
||||
|
||||
@@ -57,7 +57,12 @@ message TensorsToDetectionsCalculatorOptions {
|
||||
optional bool reverse_output_order = 14 [default = false];
|
||||
// The ids of classes that should be ignored during decoding the score for
|
||||
// each predicted box. Can be overridden with IGNORE_CLASSES side packet.
|
||||
// `ignore_classes` and `allow_classes` are mutually exclusive.
|
||||
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 float score_clipping_thresh = 16;
|
||||
@@ -71,4 +76,40 @@ message TensorsToDetectionsCalculatorOptions {
|
||||
|
||||
// Score threshold for perserving decoded detections.
|
||||
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>(
|
||||
ImageFormat::VEC32F1, output_width, output_height);
|
||||
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.
|
||||
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());
|
||||
|
||||
return absl::OkStatus();
|
||||
|
||||
@@ -334,6 +334,7 @@ cc_library(
|
||||
":image_frame_to_tensor_calculator_cc_proto",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/formats:image_frame",
|
||||
"//mediapipe/framework/port:core_proto",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
] + select({
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "mediapipe/calculators/tensorflow/image_frame_to_tensor_calculator.pb.h"
|
||||
#include "mediapipe/framework/calculator_framework.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/status.h"
|
||||
#include "mediapipe/framework/port/status_macros.h"
|
||||
@@ -32,7 +33,10 @@ namespace {
|
||||
// Convert the ImageFrame into Tensor with floating point value type.
|
||||
// The value will be normalized based on mean and stddev.
|
||||
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 rows = image_frame.Height();
|
||||
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 col = 0; col < cols; ++col) {
|
||||
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;
|
||||
}
|
||||
@@ -126,7 +143,18 @@ absl::Status ImageFrameToTensorCalculator::Process(CalculatorContext* cc) {
|
||||
const tf::DataType data_type = options_.data_type();
|
||||
RET_CHECK_EQ(data_type, tf::DT_FLOAT)
|
||||
<< "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(),
|
||||
options_.stddev());
|
||||
} else {
|
||||
|
||||
@@ -32,6 +32,6 @@ message ImageFrameToTensorCalculatorOptions {
|
||||
// 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,
|
||||
// respectively. Otherwise, T is equal to F.
|
||||
optional float mean = 2;
|
||||
optional float stddev = 3;
|
||||
repeated float mean = 2;
|
||||
repeated float stddev = 3;
|
||||
}
|
||||
|
||||
@@ -454,4 +454,32 @@ TEST_F(ImageFrameToTensorCalculatorTest, FixedRGBFrameWithMeanAndStddev) {
|
||||
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
|
||||
|
||||
+2
-2
@@ -70,10 +70,10 @@ const int kNumCoordsPerBox = 4;
|
||||
// image/understanding/object_detection/export_inference_graph.py
|
||||
//
|
||||
// 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
|
||||
// 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:
|
||||
// node {
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace mpms = mediapipe::mediasequence;
|
||||
// bounding boxes from vector<Detections>, and streams with the
|
||||
// "FLOAT_FEATURE_${NAME}" pattern, which stores the values from vector<float>'s
|
||||
// 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
|
||||
// each stream, which allows for multiple image streams to be included. However,
|
||||
// the default names are suppored by more tools.
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
// output_side_packet: "SEQUENCE_EXAMPLE:sequence_example"
|
||||
// }
|
||||
//
|
||||
// Example converting to std::string in Close():
|
||||
// Example converting to string in Close():
|
||||
// node {
|
||||
// calculator: "StringToSequenceExampleCalculator"
|
||||
// input_side_packet: "SEQUENCE_EXAMPLE:sequence_example"
|
||||
|
||||
@@ -121,8 +121,12 @@ absl::Status TensorToImageFrameCalculator::Process(CalculatorContext* cc) {
|
||||
if (d > 255) d = 255;
|
||||
buffer[i] = d;
|
||||
}
|
||||
output = ::absl::make_unique<ImageFrame>(format, width, height,
|
||||
width * depth, buffer.release());
|
||||
output = ::absl::make_unique<ImageFrame>(
|
||||
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) {
|
||||
if (scale_factor_ != 1.0) {
|
||||
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.";
|
||||
for (const auto& tag_pair : options_.recurrent_tag_pair()) {
|
||||
const std::vector<std::string> tags = absl::StrSplit(tag_pair, ':');
|
||||
RET_CHECK_EQ(tags.size(), 2)
|
||||
<< "recurrent_tag_pair must be a colon "
|
||||
"separated std::string with two components: "
|
||||
<< tag_pair;
|
||||
RET_CHECK_EQ(tags.size(), 2) << "recurrent_tag_pair must be a colon "
|
||||
"separated string with two components: "
|
||||
<< tag_pair;
|
||||
RET_CHECK(mediapipe::ContainsKey(tag_to_tensor_map_, tags[0]))
|
||||
<< "Can't find tag '" << tags[0] << "' in signature "
|
||||
<< options_.signature_name();
|
||||
|
||||
@@ -86,7 +86,7 @@ class TensorFlowSessionFromFrozenGraphCalculator : public CalculatorBase {
|
||||
cc->InputSidePackets()
|
||||
.Tag(kStringModelFilePathTag)
|
||||
.Set<std::string>(
|
||||
// Filename of std::string model.
|
||||
// Filename of string model.
|
||||
);
|
||||
}
|
||||
cc->OutputSidePackets()
|
||||
|
||||
@@ -84,7 +84,7 @@ class TensorFlowSessionFromFrozenGraphGenerator : public PacketGenerator {
|
||||
} else if (input_side_packets->HasTag(kStringModelFilePathTag)) {
|
||||
input_side_packets->Tag(kStringModelFilePathTag)
|
||||
.Set<std::string>(
|
||||
// Filename of std::string model.
|
||||
// Filename of string model.
|
||||
);
|
||||
}
|
||||
output_side_packets->Tag(kSessionTag)
|
||||
|
||||
@@ -69,6 +69,8 @@ const std::string MaybeConvertSignatureToTag(
|
||||
[](unsigned char c) { return std::toupper(c); });
|
||||
output = absl::StrReplaceAll(output, {{"/", "_"}});
|
||||
output = absl::StrReplaceAll(output, {{"-", "_"}});
|
||||
output = absl::StrReplaceAll(output, {{".", "_"}});
|
||||
LOG(INFO) << "Renamed TAG from: " << name << " to " << output;
|
||||
return output;
|
||||
} else {
|
||||
return name;
|
||||
|
||||
@@ -71,6 +71,8 @@ const std::string MaybeConvertSignatureToTag(
|
||||
[](unsigned char c) { return std::toupper(c); });
|
||||
output = absl::StrReplaceAll(output, {{"/", "_"}});
|
||||
output = absl::StrReplaceAll(output, {{"-", "_"}});
|
||||
output = absl::StrReplaceAll(output, {{".", "_"}});
|
||||
LOG(INFO) << "Renamed TAG from: " << name << " to " << output;
|
||||
return output;
|
||||
} else {
|
||||
return name;
|
||||
|
||||
@@ -121,10 +121,11 @@ cc_library(
|
||||
deps = [
|
||||
":tflite_custom_op_resolver_calculator_cc_proto",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/api2:packet",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
"//mediapipe/util/tflite:cpu_op_resolver",
|
||||
"//mediapipe/util/tflite:op_resolver",
|
||||
"@org_tensorflow//tensorflow/lite/core/api:op_resolver",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
@@ -12,14 +12,22 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "mediapipe/calculators/tflite/tflite_custom_op_resolver_calculator.pb.h"
|
||||
#include "mediapipe/framework/api2/packet.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/port/ret_check.h"
|
||||
#include "mediapipe/util/tflite/cpu_op_resolver.h"
|
||||
#include "mediapipe/util/tflite/op_resolver.h"
|
||||
#include "tensorflow/lite/core/api/op_resolver.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace {
|
||||
constexpr char kOpResolverTag[] = "OP_RESOLVER";
|
||||
} // namespace
|
||||
|
||||
// This calculator creates a custom op resolver as a side packet that can be
|
||||
// used in TfLiteInferenceCalculator. Current custom op resolver supports the
|
||||
// following custom op on CPU and GPU:
|
||||
@@ -27,7 +35,9 @@ namespace mediapipe {
|
||||
// MaxPoolArgmax
|
||||
// MaxUnpooling
|
||||
//
|
||||
// Usage example:
|
||||
// Usage examples:
|
||||
//
|
||||
// For using with TfliteInferenceCalculator:
|
||||
// node {
|
||||
// calculator: "TfLiteCustomOpResolverCalculator"
|
||||
// output_side_packet: "op_resolver"
|
||||
@@ -37,12 +47,27 @@ namespace mediapipe {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// For using with InferenceCalculator:
|
||||
// node {
|
||||
// calculator: "TfLiteCustomOpResolverCalculator"
|
||||
// output_side_packet: "OP_RESOLVER:op_resolver"
|
||||
// node_options: {
|
||||
// [type.googleapis.com/mediapipe.TfLiteCustomOpResolverCalculatorOptions] {
|
||||
// use_gpu: true
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
class TfLiteCustomOpResolverCalculator : public CalculatorBase {
|
||||
public:
|
||||
static absl::Status GetContract(CalculatorContract* cc) {
|
||||
cc->OutputSidePackets()
|
||||
.Index(0)
|
||||
.Set<tflite::ops::builtin::BuiltinOpResolver>();
|
||||
if (cc->OutputSidePackets().HasTag(kOpResolverTag)) {
|
||||
cc->OutputSidePackets().Tag(kOpResolverTag).Set<tflite::OpResolver>();
|
||||
} else {
|
||||
cc->OutputSidePackets()
|
||||
.Index(0)
|
||||
.Set<tflite::ops::builtin::BuiltinOpResolver>();
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -59,7 +84,14 @@ class TfLiteCustomOpResolverCalculator : public CalculatorBase {
|
||||
op_resolver = absl::make_unique<mediapipe::CpuOpResolver>();
|
||||
}
|
||||
|
||||
cc->OutputSidePackets().Index(0).Set(Adopt(op_resolver.release()));
|
||||
if (cc->OutputSidePackets().HasTag(kOpResolverTag)) {
|
||||
cc->OutputSidePackets()
|
||||
.Tag(kOpResolverTag)
|
||||
.Set(mediapipe::api2::PacketAdopting<tflite::OpResolver>(
|
||||
std::move(op_resolver)));
|
||||
} else {
|
||||
cc->OutputSidePackets().Index(0).Set(Adopt(op_resolver.release()));
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
|
||||
@@ -939,7 +939,7 @@ absl::Status TfLiteInferenceCalculator::LoadDelegate(CalculatorContext* cc) {
|
||||
|
||||
#if !defined(MEDIAPIPE_EDGE_TPU)
|
||||
if (use_xnnpack) {
|
||||
TfLiteXNNPackDelegateOptions xnnpack_opts{};
|
||||
auto xnnpack_opts = TfLiteXNNPackDelegateOptionsDefault();
|
||||
xnnpack_opts.num_threads = GetXnnpackNumThreads(calculator_opts);
|
||||
delegate_ = TfLiteDelegatePtr(TfLiteXNNPackDelegateCreate(&xnnpack_opts),
|
||||
&TfLiteXNNPackDelegateDelete);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
@@ -558,7 +559,7 @@ uniform ivec2 out_size;
|
||||
const int output_layer_index = int($1);
|
||||
const float combine_with_previous_ratio = float($2);
|
||||
|
||||
// Will be replaced with either '#define READ_PREVIOUS' or empty std::string
|
||||
// Will be replaced with either '#define READ_PREVIOUS' or empty string
|
||||
$3 //DEFINE_READ_PREVIOUS
|
||||
|
||||
void main() {
|
||||
|
||||
@@ -54,6 +54,7 @@ mediapipe_proto_library(
|
||||
deps = [
|
||||
"//mediapipe/framework:calculator_options_proto",
|
||||
"//mediapipe/framework:calculator_proto",
|
||||
"//mediapipe/util:label_map_proto",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -304,6 +305,7 @@ cc_library(
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:packet",
|
||||
"//mediapipe/util:resource_util",
|
||||
"//mediapipe/util:label_map_cc_proto",
|
||||
] + select({
|
||||
"//mediapipe:android": [
|
||||
"//mediapipe/util/android/file/base",
|
||||
@@ -350,6 +352,40 @@ cc_library(
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "detection_transformation_calculator",
|
||||
srcs = ["detection_transformation_calculator.cc"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/api2:node",
|
||||
"//mediapipe/framework/api2:packet",
|
||||
"//mediapipe/framework/api2:port",
|
||||
"//mediapipe/framework/formats:detection_cc_proto",
|
||||
"//mediapipe/framework/formats:location_data_cc_proto",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "detection_transformation_calculator_test",
|
||||
size = "small",
|
||||
srcs = ["detection_transformation_calculator_test.cc"],
|
||||
deps = [
|
||||
":detection_transformation_calculator",
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:calculator_runner",
|
||||
"//mediapipe/framework:packet",
|
||||
"//mediapipe/framework/formats:detection_cc_proto",
|
||||
"//mediapipe/framework/formats:location_data_cc_proto",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"//mediapipe/framework/port:parse_text_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "non_max_suppression_calculator",
|
||||
srcs = ["non_max_suppression_calculator.cc"],
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "absl/container/node_hash_map.h"
|
||||
#include "mediapipe/calculators/util/detection_label_id_to_text_calculator.pb.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/formats/detection.pb.h"
|
||||
#include "mediapipe/framework/packet.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/util/label_map.pb.h"
|
||||
#include "mediapipe/util/resource_util.h"
|
||||
|
||||
#if defined(MEDIAPIPE_MOBILE)
|
||||
@@ -53,8 +53,11 @@ class DetectionLabelIdToTextCalculator : public CalculatorBase {
|
||||
absl::Status Process(CalculatorContext* cc) override;
|
||||
|
||||
private:
|
||||
absl::node_hash_map<int, std::string> label_map_;
|
||||
::mediapipe::DetectionLabelIdToTextCalculatorOptions options_;
|
||||
// Local label map built from the calculator options' `label_map_path` or
|
||||
// `label` field.
|
||||
LabelMap local_label_map_;
|
||||
bool keep_label_id_;
|
||||
const LabelMap& GetLabelMap(CalculatorContext* cc);
|
||||
};
|
||||
REGISTER_CALCULATOR(DetectionLabelIdToTextCalculator);
|
||||
|
||||
@@ -69,13 +72,16 @@ absl::Status DetectionLabelIdToTextCalculator::GetContract(
|
||||
absl::Status DetectionLabelIdToTextCalculator::Open(CalculatorContext* cc) {
|
||||
cc->SetOffset(TimestampDiff(0));
|
||||
|
||||
options_ =
|
||||
const auto& options =
|
||||
cc->Options<::mediapipe::DetectionLabelIdToTextCalculatorOptions>();
|
||||
|
||||
if (options_.has_label_map_path()) {
|
||||
if (options.has_label_map_path()) {
|
||||
RET_CHECK(!options.has_label_map() && options.label().empty())
|
||||
<< "Only can set one of the following fields in the CalculatorOptions: "
|
||||
"label_map_path, label, and label_map.";
|
||||
std::string string_path;
|
||||
ASSIGN_OR_RETURN(string_path,
|
||||
PathToResourceAsFile(options_.label_map_path()));
|
||||
PathToResourceAsFile(options.label_map_path()));
|
||||
std::string label_map_string;
|
||||
MP_RETURN_IF_ERROR(file::GetContents(string_path, &label_map_string));
|
||||
|
||||
@@ -83,13 +89,21 @@ absl::Status DetectionLabelIdToTextCalculator::Open(CalculatorContext* cc) {
|
||||
std::string line;
|
||||
int i = 0;
|
||||
while (std::getline(stream, line)) {
|
||||
label_map_[i++] = line;
|
||||
LabelMapItem item;
|
||||
item.set_name(line);
|
||||
(*local_label_map_.mutable_index_to_item())[i++] = item;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < options_.label_size(); ++i) {
|
||||
label_map_[i] = options_.label(i);
|
||||
} else if (!options.label().empty()) {
|
||||
RET_CHECK(!options.has_label_map())
|
||||
<< "Only can set one of the following fields in the CalculatorOptions: "
|
||||
"label_map_path, label, and label_map.";
|
||||
for (int i = 0; i < options.label_size(); ++i) {
|
||||
LabelMapItem item;
|
||||
item.set_name(options.label(i));
|
||||
(*local_label_map_.mutable_index_to_item())[i] = item;
|
||||
}
|
||||
}
|
||||
keep_label_id_ = options.keep_label_id();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -101,13 +115,18 @@ absl::Status DetectionLabelIdToTextCalculator::Process(CalculatorContext* cc) {
|
||||
Detection& output_detection = output_detections.back();
|
||||
bool has_text_label = false;
|
||||
for (const int32 label_id : output_detection.label_id()) {
|
||||
if (label_map_.find(label_id) != label_map_.end()) {
|
||||
output_detection.add_label(label_map_[label_id]);
|
||||
if (GetLabelMap(cc).index_to_item().find(label_id) !=
|
||||
GetLabelMap(cc).index_to_item().end()) {
|
||||
auto item = GetLabelMap(cc).index_to_item().at(label_id);
|
||||
output_detection.add_label(item.name());
|
||||
if (item.has_display_name()) {
|
||||
output_detection.add_display_name(item.display_name());
|
||||
}
|
||||
has_text_label = true;
|
||||
}
|
||||
}
|
||||
// Remove label_id field if text labels exist.
|
||||
if (has_text_label && !options_.keep_label_id()) {
|
||||
if (has_text_label && !keep_label_id_) {
|
||||
output_detection.clear_label_id();
|
||||
}
|
||||
}
|
||||
@@ -117,4 +136,13 @@ absl::Status DetectionLabelIdToTextCalculator::Process(CalculatorContext* cc) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
const LabelMap& DetectionLabelIdToTextCalculator::GetLabelMap(
|
||||
CalculatorContext* cc) {
|
||||
return !local_label_map_.index_to_item().empty()
|
||||
? local_label_map_
|
||||
: cc->Options<
|
||||
::mediapipe::DetectionLabelIdToTextCalculatorOptions>()
|
||||
.label_map();
|
||||
}
|
||||
|
||||
} // namespace mediapipe
|
||||
|
||||
@@ -17,6 +17,7 @@ syntax = "proto2";
|
||||
package mediapipe;
|
||||
|
||||
import "mediapipe/framework/calculator.proto";
|
||||
import "mediapipe/util/label_map.proto";
|
||||
|
||||
message DetectionLabelIdToTextCalculatorOptions {
|
||||
extend mediapipe.CalculatorOptions {
|
||||
@@ -26,7 +27,7 @@ message DetectionLabelIdToTextCalculatorOptions {
|
||||
// Path to a label map file for getting the actual name of detected classes.
|
||||
optional string label_map_path = 1;
|
||||
|
||||
// Alternative way to specify label map
|
||||
// Alternative way to specify label map.
|
||||
// label: "label for id 0"
|
||||
// label: "label for id 1"
|
||||
// ...
|
||||
@@ -36,4 +37,7 @@ message DetectionLabelIdToTextCalculatorOptions {
|
||||
// could be found. By setting this field to true, it is always copied to the
|
||||
// output detections.
|
||||
optional bool keep_label_id = 3;
|
||||
|
||||
// Label map.
|
||||
optional LabelMap label_map = 4;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
// 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 <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "mediapipe/framework/api2/node.h"
|
||||
#include "mediapipe/framework/api2/packet.h"
|
||||
#include "mediapipe/framework/api2/port.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/formats/detection.pb.h"
|
||||
#include "mediapipe/framework/formats/location_data.pb.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace api2 {
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
T BoundedValue(T value, T upper_bound) {
|
||||
T output = std::min(value, upper_bound);
|
||||
if (output < 0) {
|
||||
return 0;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
absl::Status ConvertRelativeBoundingBoxToBoundingBox(
|
||||
const std::pair<int, int>& image_size, Detection* detection) {
|
||||
const int image_width = image_size.first;
|
||||
const int image_height = image_size.second;
|
||||
const auto& relative_bbox =
|
||||
detection->location_data().relative_bounding_box();
|
||||
auto* bbox = detection->mutable_location_data()->mutable_bounding_box();
|
||||
bbox->set_xmin(
|
||||
BoundedValue<int>(relative_bbox.xmin() * image_width, image_width));
|
||||
bbox->set_ymin(
|
||||
BoundedValue<int>(relative_bbox.ymin() * image_height, image_height));
|
||||
bbox->set_width(
|
||||
BoundedValue<int>(relative_bbox.width() * image_width, image_width));
|
||||
bbox->set_height(
|
||||
BoundedValue<int>(relative_bbox.height() * image_height, image_height));
|
||||
detection->mutable_location_data()->set_format(LocationData::BOUNDING_BOX);
|
||||
detection->mutable_location_data()->clear_relative_bounding_box();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status ConvertBoundingBoxToRelativeBoundingBox(
|
||||
const std::pair<int, int>& image_size, Detection* detection) {
|
||||
int image_width = image_size.first;
|
||||
int image_height = image_size.second;
|
||||
const auto& bbox = detection->location_data().bounding_box();
|
||||
auto* relative_bbox =
|
||||
detection->mutable_location_data()->mutable_relative_bounding_box();
|
||||
relative_bbox->set_xmin(
|
||||
BoundedValue<float>((float)bbox.xmin() / image_width, 1.0f));
|
||||
relative_bbox->set_ymin(
|
||||
BoundedValue<float>((float)bbox.ymin() / image_height, 1.0f));
|
||||
relative_bbox->set_width(
|
||||
BoundedValue<float>((float)bbox.width() / image_width, 1.0f));
|
||||
relative_bbox->set_height(
|
||||
BoundedValue<float>((float)bbox.height() / image_height, 1.0f));
|
||||
detection->mutable_location_data()->clear_bounding_box();
|
||||
detection->mutable_location_data()->set_format(
|
||||
LocationData::RELATIVE_BOUNDING_BOX);
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::StatusOr<LocationData::Format> GetLocationDataFormat(
|
||||
const Detection& detection) {
|
||||
if (!detection.has_location_data()) {
|
||||
return absl::InvalidArgumentError("Detection must have location data.");
|
||||
}
|
||||
LocationData::Format format = detection.location_data().format();
|
||||
RET_CHECK(format == LocationData::RELATIVE_BOUNDING_BOX ||
|
||||
format == LocationData::BOUNDING_BOX)
|
||||
<< "Detection's location data format must be either "
|
||||
"RELATIVE_BOUNDING_BOX or BOUNDING_BOX";
|
||||
return format;
|
||||
}
|
||||
|
||||
absl::StatusOr<LocationData::Format> GetLocationDataFormat(
|
||||
std::vector<Detection>& detections) {
|
||||
RET_CHECK(!detections.empty());
|
||||
LocationData::Format output_format;
|
||||
ASSIGN_OR_RETURN(output_format, GetLocationDataFormat(detections[0]));
|
||||
for (int i = 1; i < detections.size(); ++i) {
|
||||
ASSIGN_OR_RETURN(LocationData::Format format,
|
||||
GetLocationDataFormat(detections[i]));
|
||||
if (output_format != format) {
|
||||
return absl::InvalidArgumentError(
|
||||
"Input detections have different location data formats.");
|
||||
}
|
||||
}
|
||||
return output_format;
|
||||
}
|
||||
|
||||
absl::Status ConvertBoundingBox(const std::pair<int, int>& image_size,
|
||||
Detection* detection) {
|
||||
if (!detection->has_location_data()) {
|
||||
return absl::InvalidArgumentError("Detection must have location data.");
|
||||
}
|
||||
switch (detection->location_data().format()) {
|
||||
case LocationData::RELATIVE_BOUNDING_BOX:
|
||||
return ConvertRelativeBoundingBoxToBoundingBox(image_size, detection);
|
||||
case LocationData::BOUNDING_BOX:
|
||||
return ConvertBoundingBoxToRelativeBoundingBox(image_size, detection);
|
||||
default:
|
||||
return absl::InvalidArgumentError(
|
||||
"Detection's location data format must be either "
|
||||
"RELATIVE_BOUNDING_BOX or BOUNDING_BOX.");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Transforms relative bounding box(es) to pixel bounding box(es) in a detection
|
||||
// proto/detection list/detection vector, or vice versa.
|
||||
//
|
||||
// Inputs:
|
||||
// One of the following:
|
||||
// DETECTION: A Detection proto.
|
||||
// DETECTIONS: An std::vector<Detection>/ a DetectionList proto.
|
||||
// IMAGE_SIZE: A std::pair<int, int> represention image width and height.
|
||||
//
|
||||
// Outputs:
|
||||
// At least one of the following:
|
||||
// PIXEL_DETECTION: A Detection proto with pixel bounding box.
|
||||
// PIXEL_DETECTIONS: An std::vector<Detection> with pixel bounding boxes.
|
||||
// PIXEL_DETECTION_LIST: A DetectionList proto with pixel bounding boxes.
|
||||
// RELATIVE_DETECTION: A Detection proto with relative bounding box.
|
||||
// RELATIVE_DETECTIONS: An std::vector<Detection> with relative bounding boxes.
|
||||
// RELATIVE_DETECTION_LIST: A DetectionList proto with relative bounding boxes.
|
||||
//
|
||||
// Example config:
|
||||
// For input detection(s) with relative bounding box(es):
|
||||
// node {
|
||||
// calculator: "DetectionTransformationCalculator"
|
||||
// input_stream: "DETECTION:input_detection"
|
||||
// input_stream: "IMAGE_SIZE:image_size"
|
||||
// output_stream: "PIXEL_DETECTION:output_detection"
|
||||
// output_stream: "PIXEL_DETECTIONS:output_detections"
|
||||
// output_stream: "PIXEL_DETECTION_LIST:output_detection_list"
|
||||
// }
|
||||
//
|
||||
// For input detection(s) with pixel bounding box(es):
|
||||
// node {
|
||||
// calculator: "DetectionTransformationCalculator"
|
||||
// input_stream: "DETECTION:input_detection"
|
||||
// input_stream: "IMAGE_SIZE:image_size"
|
||||
// output_stream: "RELATIVE_DETECTION:output_detection"
|
||||
// output_stream: "RELATIVE_DETECTIONS:output_detections"
|
||||
// output_stream: "RELATIVE_DETECTION_LIST:output_detection_list"
|
||||
// }
|
||||
class DetectionTransformationCalculator : public Node {
|
||||
public:
|
||||
static constexpr Input<Detection>::Optional kInDetection{"DETECTION"};
|
||||
static constexpr Input<OneOf<DetectionList, std::vector<Detection>>>::Optional
|
||||
kInDetections{"DETECTIONS"};
|
||||
static constexpr Input<std::pair<int, int>> kInImageSize{"IMAGE_SIZE"};
|
||||
static constexpr Output<Detection>::Optional kOutPixelDetection{
|
||||
"PIXEL_DETECTION"};
|
||||
static constexpr Output<std::vector<Detection>>::Optional kOutPixelDetections{
|
||||
"PIXEL_DETECTIONS"};
|
||||
static constexpr Output<DetectionList>::Optional kOutPixelDetectionList{
|
||||
"PIXEL_DETECTION_LIST"};
|
||||
static constexpr Output<Detection>::Optional kOutRelativeDetection{
|
||||
"RELATIVE_DETECTION"};
|
||||
static constexpr Output<std::vector<Detection>>::Optional
|
||||
kOutRelativeDetections{"RELATIVE_DETECTIONS"};
|
||||
static constexpr Output<DetectionList>::Optional kOutRelativeDetectionList{
|
||||
"RELATIVE_DETECTION_LIST"};
|
||||
|
||||
MEDIAPIPE_NODE_CONTRACT(kInDetection, kInDetections, kInImageSize,
|
||||
kOutPixelDetection, kOutPixelDetections,
|
||||
kOutPixelDetectionList, kOutRelativeDetection,
|
||||
kOutRelativeDetections, kOutRelativeDetectionList);
|
||||
|
||||
static absl::Status UpdateContract(CalculatorContract* cc) {
|
||||
RET_CHECK(kInImageSize(cc).IsConnected()) << "Image size must be provided.";
|
||||
RET_CHECK(kInDetections(cc).IsConnected() ^ kInDetection(cc).IsConnected());
|
||||
if (kInDetections(cc).IsConnected()) {
|
||||
RET_CHECK(kOutPixelDetections(cc).IsConnected() ||
|
||||
kOutPixelDetectionList(cc).IsConnected() ||
|
||||
kOutRelativeDetections(cc).IsConnected() ||
|
||||
kOutRelativeDetectionList(cc).IsConnected())
|
||||
<< "Output must be a container of detections.";
|
||||
}
|
||||
RET_CHECK(kOutPixelDetections(cc).IsConnected() ||
|
||||
kOutPixelDetectionList(cc).IsConnected() ||
|
||||
kOutPixelDetection(cc).IsConnected() ||
|
||||
kOutRelativeDetections(cc).IsConnected() ||
|
||||
kOutRelativeDetectionList(cc).IsConnected() ||
|
||||
kOutRelativeDetection(cc).IsConnected())
|
||||
<< "Must connect at least one output stream.";
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Open(CalculatorContext* cc) override {
|
||||
output_pixel_bounding_boxes_ = kOutPixelDetections(cc).IsConnected() ||
|
||||
kOutPixelDetectionList(cc).IsConnected() ||
|
||||
kOutPixelDetection(cc).IsConnected();
|
||||
output_relative_bounding_boxes_ =
|
||||
kOutRelativeDetections(cc).IsConnected() ||
|
||||
kOutRelativeDetectionList(cc).IsConnected() ||
|
||||
kOutRelativeDetection(cc).IsConnected();
|
||||
RET_CHECK(output_pixel_bounding_boxes_ ^ output_relative_bounding_boxes_)
|
||||
<< "All output streams must have the same stream tag prefix, either "
|
||||
"\"PIXEL\" or \"RELATIVE_\".";
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Process(CalculatorContext* cc) override {
|
||||
std::pair<int, int> image_size = kInImageSize(cc).Get();
|
||||
std::vector<Detection> transformed_detections;
|
||||
LocationData::Format input_location_data_format;
|
||||
if (kInDetections(cc).IsConnected()) {
|
||||
transformed_detections = kInDetections(cc).Visit(
|
||||
[&](const DetectionList& detection_list) {
|
||||
return std::vector<Detection>(detection_list.detection().begin(),
|
||||
detection_list.detection().end());
|
||||
},
|
||||
[&](const std::vector<Detection>& detection_vector) {
|
||||
return detection_vector;
|
||||
});
|
||||
ASSIGN_OR_RETURN(input_location_data_format,
|
||||
GetLocationDataFormat(transformed_detections));
|
||||
for (Detection& detection : transformed_detections) {
|
||||
MP_RETURN_IF_ERROR(ConvertBoundingBox(image_size, &detection));
|
||||
}
|
||||
} else {
|
||||
ASSIGN_OR_RETURN(input_location_data_format,
|
||||
GetLocationDataFormat(kInDetection(cc).Get()));
|
||||
Detection transformed_detection(kInDetection(cc).Get());
|
||||
MP_RETURN_IF_ERROR(
|
||||
ConvertBoundingBox(image_size, &transformed_detection));
|
||||
transformed_detections.push_back(transformed_detection);
|
||||
}
|
||||
if (input_location_data_format == LocationData::RELATIVE_BOUNDING_BOX) {
|
||||
RET_CHECK(!output_relative_bounding_boxes_)
|
||||
<< "Input detections are with relative bounding box(es), and the "
|
||||
"output detections must have pixel bounding box(es).";
|
||||
if (kOutPixelDetection(cc).IsConnected()) {
|
||||
kOutPixelDetection(cc).Send(transformed_detections[0]);
|
||||
}
|
||||
if (kOutPixelDetections(cc).IsConnected()) {
|
||||
kOutPixelDetections(cc).Send(transformed_detections);
|
||||
}
|
||||
if (kOutPixelDetectionList(cc).IsConnected()) {
|
||||
DetectionList detection_list;
|
||||
for (const auto& detection : transformed_detections) {
|
||||
detection_list.add_detection()->CopyFrom(detection);
|
||||
}
|
||||
kOutPixelDetectionList(cc).Send(detection_list);
|
||||
}
|
||||
} else {
|
||||
RET_CHECK(!output_pixel_bounding_boxes_)
|
||||
<< "Input detections are with pixel bounding box(es), and the "
|
||||
"output detections must have relative bounding box(es).";
|
||||
if (kOutRelativeDetection(cc).IsConnected()) {
|
||||
kOutRelativeDetection(cc).Send(transformed_detections[0]);
|
||||
}
|
||||
if (kOutRelativeDetections(cc).IsConnected()) {
|
||||
kOutRelativeDetections(cc).Send(transformed_detections);
|
||||
}
|
||||
if (kOutRelativeDetectionList(cc).IsConnected()) {
|
||||
DetectionList detection_list;
|
||||
for (const auto& detection : transformed_detections) {
|
||||
detection_list.add_detection()->CopyFrom(detection);
|
||||
}
|
||||
kOutRelativeDetectionList(cc).Send(detection_list);
|
||||
}
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
private:
|
||||
bool output_relative_bounding_boxes_;
|
||||
bool output_pixel_bounding_boxes_;
|
||||
};
|
||||
|
||||
MEDIAPIPE_REGISTER_NODE(DetectionTransformationCalculator);
|
||||
|
||||
} // namespace api2
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,287 @@
|
||||
// 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 <algorithm>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "mediapipe/framework/calculator.pb.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/calculator_runner.h"
|
||||
#include "mediapipe/framework/formats/detection.pb.h"
|
||||
#include "mediapipe/framework/formats/location_data.pb.h"
|
||||
#include "mediapipe/framework/packet.h"
|
||||
#include "mediapipe/framework/port/gmock.h"
|
||||
#include "mediapipe/framework/port/gtest.h"
|
||||
#include "mediapipe/framework/port/parse_text_proto.h"
|
||||
#include "mediapipe/framework/port/status_matchers.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace {
|
||||
|
||||
constexpr char kDetectionTag[] = "DETECTION";
|
||||
constexpr char kDetectionsTag[] = "DETECTIONS";
|
||||
constexpr char kImageSizeTag[] = "IMAGE_SIZE";
|
||||
constexpr char kPixelDetectionTag[] = "PIXEL_DETECTION";
|
||||
constexpr char kPixelDetectionListTag[] = "PIXEL_DETECTION_LIST";
|
||||
constexpr char kPixelDetectionsTag[] = "PIXEL_DETECTIONS";
|
||||
constexpr char kRelativeDetectionListTag[] = "RELATIVE_DETECTION_LIST";
|
||||
constexpr char kRelativeDetectionsTag[] = "RELATIVE_DETECTIONS";
|
||||
|
||||
Detection DetectionWithBoundingBox(int32 xmin, int32 ymin, int32 width,
|
||||
int32 height) {
|
||||
Detection detection;
|
||||
LocationData* location_data = detection.mutable_location_data();
|
||||
location_data->set_format(LocationData::BOUNDING_BOX);
|
||||
location_data->mutable_bounding_box()->set_xmin(xmin);
|
||||
location_data->mutable_bounding_box()->set_ymin(ymin);
|
||||
location_data->mutable_bounding_box()->set_width(width);
|
||||
location_data->mutable_bounding_box()->set_height(height);
|
||||
return detection;
|
||||
}
|
||||
|
||||
Detection DetectionWithRelativeBoundingBox(float xmin, float ymin, float width,
|
||||
float height) {
|
||||
Detection detection;
|
||||
LocationData* location_data = detection.mutable_location_data();
|
||||
location_data->set_format(LocationData::RELATIVE_BOUNDING_BOX);
|
||||
location_data->mutable_relative_bounding_box()->set_xmin(xmin);
|
||||
location_data->mutable_relative_bounding_box()->set_ymin(ymin);
|
||||
location_data->mutable_relative_bounding_box()->set_width(width);
|
||||
location_data->mutable_relative_bounding_box()->set_height(height);
|
||||
return detection;
|
||||
}
|
||||
|
||||
std::vector<Detection> ConvertToDetectionVector(
|
||||
const DetectionList& detection_list) {
|
||||
std::vector<Detection> output;
|
||||
for (const auto& detection : detection_list.detection()) {
|
||||
output.push_back(detection);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
void CheckBoundingBox(const Detection& output, const Detection& expected) {
|
||||
const auto& output_bbox = output.location_data().bounding_box();
|
||||
const auto& expected_bbox = output.location_data().bounding_box();
|
||||
EXPECT_THAT(output_bbox.xmin(), testing::Eq(expected_bbox.xmin()));
|
||||
EXPECT_THAT(output_bbox.ymin(), testing::Eq(expected_bbox.ymin()));
|
||||
EXPECT_THAT(output_bbox.width(), testing::Eq(expected_bbox.width()));
|
||||
EXPECT_THAT(output_bbox.height(), testing::Eq(expected_bbox.height()));
|
||||
}
|
||||
|
||||
void CheckRelativeBoundingBox(const Detection& output,
|
||||
const Detection& expected) {
|
||||
const auto& output_bbox = output.location_data().relative_bounding_box();
|
||||
const auto& expected_bbox = output.location_data().relative_bounding_box();
|
||||
EXPECT_THAT(output_bbox.xmin(), testing::FloatEq(expected_bbox.xmin()));
|
||||
EXPECT_THAT(output_bbox.ymin(), testing::FloatEq(expected_bbox.ymin()));
|
||||
EXPECT_THAT(output_bbox.width(), testing::FloatEq(expected_bbox.width()));
|
||||
EXPECT_THAT(output_bbox.height(), testing::FloatEq(expected_bbox.height()));
|
||||
}
|
||||
|
||||
void CheckOutputDetections(const std::vector<Detection>& expected,
|
||||
const std::vector<Detection>& output) {
|
||||
ASSERT_EQ(output.size(), expected.size());
|
||||
for (int i = 0; i < output.size(); ++i) {
|
||||
auto output_format = output[i].location_data().format();
|
||||
ASSERT_TRUE(output_format == LocationData::RELATIVE_BOUNDING_BOX ||
|
||||
output_format == LocationData::BOUNDING_BOX);
|
||||
ASSERT_EQ(output_format, expected[i].location_data().format());
|
||||
if (output_format == LocationData::RELATIVE_BOUNDING_BOX) {
|
||||
CheckRelativeBoundingBox(output[i], expected[i]);
|
||||
}
|
||||
if (output_format == LocationData::BOUNDING_BOX) {
|
||||
CheckBoundingBox(output[i], expected[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DetectionsTransformationCalculatorTest, MissingImageSize) {
|
||||
CalculatorRunner runner(ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"pb(
|
||||
calculator: "DetectionTransformationCalculator"
|
||||
input_stream: "DETECTIONS:detections"
|
||||
output_stream: "PIXEL_DETECTION:detection"
|
||||
)pb"));
|
||||
|
||||
auto status = runner.Run();
|
||||
ASSERT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.message(),
|
||||
testing::HasSubstr("Image size must be provided"));
|
||||
}
|
||||
|
||||
TEST(DetectionsTransformationCalculatorTest, WrongOutputType) {
|
||||
CalculatorRunner runner(ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"pb(
|
||||
calculator: "DetectionTransformationCalculator"
|
||||
input_stream: "DETECTIONS:detections"
|
||||
input_stream: "IMAGE_SIZE:image_size"
|
||||
output_stream: "PIXEL_DETECTION:detection"
|
||||
)pb"));
|
||||
|
||||
auto status = runner.Run();
|
||||
ASSERT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.message(),
|
||||
testing::HasSubstr("Output must be a container of detections"));
|
||||
}
|
||||
|
||||
TEST(DetectionsTransformationCalculatorTest, WrongLocationDataFormat) {
|
||||
CalculatorRunner runner(ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"pb(
|
||||
calculator: "DetectionTransformationCalculator"
|
||||
input_stream: "DETECTION:input_detection"
|
||||
input_stream: "IMAGE_SIZE:image_size"
|
||||
output_stream: "PIXEL_DETECTION:output_detection"
|
||||
)pb"));
|
||||
|
||||
Detection detection;
|
||||
detection.mutable_location_data()->set_format(LocationData::GLOBAL);
|
||||
runner.MutableInputs()
|
||||
->Tag(kDetectionTag)
|
||||
.packets.push_back(MakePacket<Detection>(detection).At(Timestamp(0)));
|
||||
std::pair<int, int> image_size({2000, 1000});
|
||||
runner.MutableInputs()
|
||||
->Tag(kImageSizeTag)
|
||||
.packets.push_back(
|
||||
MakePacket<std::pair<int, int>>(image_size).At(Timestamp(0)));
|
||||
|
||||
auto status = runner.Run();
|
||||
ASSERT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.message(),
|
||||
testing::HasSubstr("location data format must be either "
|
||||
"RELATIVE_BOUNDING_BOX or BOUNDING_BOX"));
|
||||
}
|
||||
|
||||
TEST(DetectionsTransformationCalculatorTest,
|
||||
ConvertBoundingBoxToRelativeBoundingBox) {
|
||||
CalculatorRunner runner(ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"pb(
|
||||
calculator: "DetectionTransformationCalculator"
|
||||
input_stream: "DETECTIONS:input_detections"
|
||||
input_stream: "IMAGE_SIZE:image_size"
|
||||
output_stream: "RELATIVE_DETECTIONS:output_detections"
|
||||
output_stream: "RELATIVE_DETECTION_LIST:output_detection_list"
|
||||
)pb"));
|
||||
|
||||
auto detections(absl::make_unique<std::vector<Detection>>());
|
||||
detections->push_back(DetectionWithBoundingBox(100, 200, 400, 300));
|
||||
detections->push_back(DetectionWithBoundingBox(0, 0, 2000, 1000));
|
||||
std::pair<int, int> image_size({2000, 1000});
|
||||
runner.MutableInputs()
|
||||
->Tag(kDetectionsTag)
|
||||
.packets.push_back(Adopt(detections.release()).At(Timestamp(0)));
|
||||
runner.MutableInputs()
|
||||
->Tag(kImageSizeTag)
|
||||
.packets.push_back(
|
||||
MakePacket<std::pair<int, int>>(image_size).At(Timestamp(0)));
|
||||
MP_ASSERT_OK(runner.Run());
|
||||
|
||||
std::vector<Detection> expected(
|
||||
{DetectionWithRelativeBoundingBox(0.05, 0.2, 0.2, 0.3),
|
||||
DetectionWithRelativeBoundingBox(0, 0, 1, 1)});
|
||||
const std::vector<Packet>& detections_output =
|
||||
runner.Outputs().Tag(kRelativeDetectionsTag).packets;
|
||||
ASSERT_EQ(1, detections_output.size());
|
||||
CheckOutputDetections(expected,
|
||||
detections_output[0].Get<std::vector<Detection>>());
|
||||
|
||||
const std::vector<Packet>& detection_list_output =
|
||||
runner.Outputs().Tag(kRelativeDetectionListTag).packets;
|
||||
ASSERT_EQ(1, detection_list_output.size());
|
||||
CheckOutputDetections(
|
||||
expected,
|
||||
ConvertToDetectionVector(detection_list_output[0].Get<DetectionList>()));
|
||||
}
|
||||
|
||||
TEST(DetectionsTransformationCalculatorTest,
|
||||
ConvertRelativeBoundingBoxToBoundingBox) {
|
||||
CalculatorRunner runner(ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"pb(
|
||||
calculator: "DetectionTransformationCalculator"
|
||||
input_stream: "DETECTIONS:input_detections"
|
||||
input_stream: "IMAGE_SIZE:image_size"
|
||||
output_stream: "PIXEL_DETECTIONS:output_detections"
|
||||
output_stream: "PIXEL_DETECTION_LIST:output_detection_list"
|
||||
)pb"));
|
||||
|
||||
auto detections(absl::make_unique<std::vector<Detection>>());
|
||||
detections->push_back(DetectionWithRelativeBoundingBox(0.1, 0.2, 0.3, 0.4));
|
||||
detections->push_back(DetectionWithRelativeBoundingBox(0, 0, 1, 1));
|
||||
std::pair<int, int> image_size({2000, 1000});
|
||||
runner.MutableInputs()
|
||||
->Tag(kDetectionsTag)
|
||||
.packets.push_back(Adopt(detections.release()).At(Timestamp(0)));
|
||||
runner.MutableInputs()
|
||||
->Tag(kImageSizeTag)
|
||||
.packets.push_back(
|
||||
MakePacket<std::pair<int, int>>(image_size).At(Timestamp(0)));
|
||||
MP_ASSERT_OK(runner.Run());
|
||||
|
||||
std::vector<Detection> expected({DetectionWithBoundingBox(100, 200, 400, 300),
|
||||
DetectionWithBoundingBox(0, 0, 2000, 1000)});
|
||||
const std::vector<Packet>& detections_output =
|
||||
runner.Outputs().Tag(kPixelDetectionsTag).packets;
|
||||
ASSERT_EQ(1, detections_output.size());
|
||||
CheckOutputDetections(expected,
|
||||
detections_output[0].Get<std::vector<Detection>>());
|
||||
|
||||
const std::vector<Packet>& detection_list_output =
|
||||
runner.Outputs().Tag(kPixelDetectionListTag).packets;
|
||||
ASSERT_EQ(1, detection_list_output.size());
|
||||
CheckOutputDetections(
|
||||
expected,
|
||||
ConvertToDetectionVector(detection_list_output[0].Get<DetectionList>()));
|
||||
}
|
||||
|
||||
TEST(DetectionsTransformationCalculatorTest, ConvertSingleDetection) {
|
||||
CalculatorRunner runner(ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"pb(
|
||||
calculator: "DetectionTransformationCalculator"
|
||||
input_stream: "DETECTION:input_detection"
|
||||
input_stream: "IMAGE_SIZE:image_size"
|
||||
output_stream: "PIXEL_DETECTION:outpu_detection"
|
||||
output_stream: "PIXEL_DETECTIONS:output_detections"
|
||||
output_stream: "PIXEL_DETECTION_LIST:output_detection_list"
|
||||
)pb"));
|
||||
|
||||
runner.MutableInputs()
|
||||
->Tag(kDetectionTag)
|
||||
.packets.push_back(MakePacket<Detection>(DetectionWithRelativeBoundingBox(
|
||||
0.05, 0.2, 0.2, 0.3))
|
||||
.At(Timestamp(0)));
|
||||
std::pair<int, int> image_size({2000, 1000});
|
||||
runner.MutableInputs()
|
||||
->Tag(kImageSizeTag)
|
||||
.packets.push_back(
|
||||
MakePacket<std::pair<int, int>>(image_size).At(Timestamp(0)));
|
||||
MP_ASSERT_OK(runner.Run());
|
||||
|
||||
std::vector<Detection> expected(
|
||||
{DetectionWithBoundingBox(100, 200, 400, 300)});
|
||||
const std::vector<Packet>& detection_output =
|
||||
runner.Outputs().Tag(kPixelDetectionTag).packets;
|
||||
ASSERT_EQ(1, detection_output.size());
|
||||
CheckOutputDetections(expected, {detection_output[0].Get<Detection>()});
|
||||
|
||||
const std::vector<Packet>& detections_output =
|
||||
runner.Outputs().Tag(kPixelDetectionsTag).packets;
|
||||
ASSERT_EQ(1, detections_output.size());
|
||||
CheckOutputDetections(expected,
|
||||
detections_output[0].Get<std::vector<Detection>>());
|
||||
|
||||
const std::vector<Packet>& detection_list_output =
|
||||
runner.Outputs().Tag(kPixelDetectionListTag).packets;
|
||||
ASSERT_EQ(1, detection_list_output.size());
|
||||
CheckOutputDetections(
|
||||
expected,
|
||||
ConvertToDetectionVector(detection_list_output[0].Get<DetectionList>()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediapipe
|
||||
@@ -51,6 +51,7 @@ class LocalFilePatternContentsCalculator : public CalculatorBase {
|
||||
cc->InputSidePackets().Tag(kFileDirectoryTag).Get<std::string>(),
|
||||
cc->InputSidePackets().Tag(kFileSuffixTag).Get<std::string>(),
|
||||
&filenames_));
|
||||
std::sort(filenames_.begin(), filenames_.end());
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
|
||||
@@ -129,8 +129,8 @@ TEST(PacketFrequencyCalculatorTest, MultiPacketTest) {
|
||||
// Tests packet frequency with multiple input/output streams.
|
||||
TEST(PacketFrequencyCalculatorTest, MultiStreamTest) {
|
||||
// Setup the calculator runner and provide strings as input on all streams
|
||||
// (note that it doesn't have to be std::string; the calculator can take any
|
||||
// type as input).
|
||||
// (note that it doesn't have to be string; the calculator can take any type
|
||||
// as input).
|
||||
CalculatorRunner runner(GetNodeWithMultipleStreams());
|
||||
|
||||
// Packet 1 on stream 1.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user