Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c68eb4a70 | ||
|
|
a3d36eee32 | ||
|
|
1722d4b8a2 | ||
|
|
3b6d3c4058 | ||
|
|
252a5713c7 |
@@ -12,9 +12,9 @@ build --copt='-Wno-comment'
|
||||
build --copt='-Wno-return-type'
|
||||
build --copt='-Wno-unused-local-typedefs'
|
||||
build --copt='-Wno-ignored-attributes'
|
||||
# Temporarily set the incompatibility flag for Bazel 0.27.0 and above
|
||||
build --incompatible_disable_deprecated_attr_params=false
|
||||
build --incompatible_depset_is_not_iterable=false
|
||||
|
||||
# Tensorflow needs remote repo
|
||||
build --experimental_repo_remote_exec
|
||||
|
||||
# Sets the default Apple platform to macOS.
|
||||
build --apple_platform_type=macos
|
||||
@@ -23,6 +23,10 @@ build --apple_platform_type=macos
|
||||
build --apple_generate_dsym
|
||||
|
||||
# Android configs.
|
||||
# Note: the documentation tells us to use @androidndk//:default_crosstool, but
|
||||
# the automatic configuration transition uses //external:android/crosstool.
|
||||
# Using it here works and spares us from having two different config_settings
|
||||
# for Android.
|
||||
build:android --crosstool_top=//external:android/crosstool
|
||||
build:android --host_crosstool_top=@bazel_tools//tools/cpp:toolchain
|
||||
build:android --linkopt=-landroid
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ RUN pip install future
|
||||
RUN pip3 install six
|
||||
|
||||
# Install bazel
|
||||
ARG BAZEL_VERSION=1.1.0
|
||||
ARG BAZEL_VERSION=2.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" && \
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* [Hair Segmentation](mediapipe/docs/hair_segmentation_mobile_gpu.md) [[Web Demo]](https://viz.mediapipe.dev/runner/demos/hair_segmentation/hair_segmentation.html)
|
||||
* [Object Detection](mediapipe/docs/object_detection_mobile_gpu.md)
|
||||
* [Object Detection and Tracking](mediapipe/docs/object_tracking_mobile_gpu.md)
|
||||
* [Objectron: 3D Object Detection and Tracking](mediapipe/docs/objectron_mobile_gpu.md)
|
||||
* [AutoFlip](mediapipe/docs/autoflip.md)
|
||||
|
||||

|
||||
@@ -43,6 +44,8 @@ A web-based visualizer is hosted on [viz.mediapipe.dev](https://viz.mediapipe.de
|
||||
* [YouTube Channel](https://www.youtube.com/channel/UCObqmpuSMx-usADtL_qdMAw)
|
||||
|
||||
## Publications
|
||||
* [MediaPipe Objectron: Real-time 3D Object Detection on Mobile Devices](https://mediapipe.page.link/objectron-aiblog)
|
||||
* [AutoFlip: An Open Source Framework for Intelligent Video Reframing](https://mediapipe.page.link/autoflip)
|
||||
* [Google Developer Blog: MediaPipe on the Web](https://mediapipe.page.link/webdevblog)
|
||||
* [Google Developer Blog: Object Detection and Tracking using MediaPipe](https://mediapipe.page.link/objecttrackingblog)
|
||||
* [On-Device, Real-Time Hand Tracking with MediaPipe](https://ai.googleblog.com/2019/08/on-device-real-time-hand-tracking-with.html)
|
||||
@@ -63,7 +66,7 @@ A web-based visualizer is hosted on [viz.mediapipe.dev](https://viz.mediapipe.de
|
||||
* [Discuss](https://groups.google.com/forum/#!forum/mediapipe) - General community discussion around MediaPipe
|
||||
|
||||
## Alpha Disclaimer
|
||||
MediaPipe is currently in alpha for v0.6. We are still making breaking API changes and expect to get to stable API by v1.0.
|
||||
MediaPipe is currently in alpha for v0.7. We are still making breaking API changes and expect to get to stable API by v1.0.
|
||||
|
||||
## Contributing
|
||||
We welcome contributions. Please follow these [guidelines](./CONTRIBUTING.md).
|
||||
|
||||
@@ -2,23 +2,22 @@ workspace(name = "mediapipe")
|
||||
|
||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
|
||||
skylib_version = "0.8.0"
|
||||
skylib_version = "0.9.0"
|
||||
http_archive(
|
||||
name = "bazel_skylib",
|
||||
type = "tar.gz",
|
||||
url = "https://github.com/bazelbuild/bazel-skylib/releases/download/{}/bazel-skylib.{}.tar.gz".format (skylib_version, skylib_version),
|
||||
sha256 = "2ef429f5d7ce7111263289644d233707dba35e39696377ebab8b0bc701f7818e",
|
||||
url = "https://github.com/bazelbuild/bazel-skylib/releases/download/{}/bazel_skylib-{}.tar.gz".format (skylib_version, skylib_version),
|
||||
sha256 = "1dde365491125a3db70731e25658dfdd3bc5dbdfd11b840b3e987ecf043c7ca0",
|
||||
)
|
||||
load("@bazel_skylib//lib:versions.bzl", "versions")
|
||||
versions.check(minimum_bazel_version = "0.24.1",
|
||||
maximum_bazel_version = "1.2.1")
|
||||
versions.check(minimum_bazel_version = "2.0.0")
|
||||
|
||||
|
||||
# ABSL cpp library lts_2019_08_08.
|
||||
# ABSL cpp library lts_2020_02_25
|
||||
http_archive(
|
||||
name = "com_google_absl",
|
||||
urls = [
|
||||
"https://github.com/abseil/abseil-cpp/archive/20190808.tar.gz",
|
||||
"https://github.com/abseil/abseil-cpp/archive/20200225.tar.gz",
|
||||
],
|
||||
# Remove after https://github.com/abseil/abseil-cpp/issues/326 is solved.
|
||||
patches = [
|
||||
@@ -27,8 +26,8 @@ http_archive(
|
||||
patch_args = [
|
||||
"-p1",
|
||||
],
|
||||
strip_prefix = "abseil-cpp-20190808",
|
||||
sha256 = "8100085dada279bf3ee00cd064d43b5f55e5d913be0dfe2906f06f8f28d5b37e"
|
||||
strip_prefix = "abseil-cpp-20200225",
|
||||
sha256 = "728a813291bdec2aa46eab8356ace9f75ac2ed9dfe2df5ab603c4e6c09f1c353"
|
||||
)
|
||||
|
||||
http_archive(
|
||||
@@ -93,11 +92,13 @@ http_archive(
|
||||
build_file = "@//third_party:libyuv.BUILD",
|
||||
)
|
||||
|
||||
# Note: protobuf-javalite is no longer released as a separate download, it's included in the main Java download.
|
||||
# ...but the Java download is currently broken, so we use the "source" download.
|
||||
http_archive(
|
||||
name = "com_google_protobuf_javalite",
|
||||
sha256 = "79d102c61e2a479a0b7e5fc167bcfaa4832a0c6aad4a75fa7da0480564931bcc",
|
||||
strip_prefix = "protobuf-384989534b2246d413dbcd750744faab2607b516",
|
||||
urls = ["https://github.com/google/protobuf/archive/384989534b2246d413dbcd750744faab2607b516.zip"],
|
||||
sha256 = "a79d19dcdf9139fa4b81206e318e33d245c4c9da1ffed21c87288ed4380426f9",
|
||||
strip_prefix = "protobuf-3.11.4",
|
||||
urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.11.4.tar.gz"],
|
||||
)
|
||||
|
||||
http_archive(
|
||||
@@ -117,18 +118,18 @@ http_archive(
|
||||
],
|
||||
)
|
||||
|
||||
# 2019-11-21
|
||||
_TENSORFLOW_GIT_COMMIT = "f482488b481a799ca07e7e2d153cf47b8e91a60c"
|
||||
_TENSORFLOW_SHA256= "8d9118c2ce186c7e1403f04b96982fe72c184060c7f7a93e30a28dca358694f0"
|
||||
# 2020-04-01
|
||||
_TENSORFLOW_GIT_COMMIT = "805e47cea96c7e8c6fccf494d40a2392dc99fdd8"
|
||||
_TENSORFLOW_SHA256= "9ee3ae604c2e1345ac60345becee6d659364721513f9cb8652eb2e7138320ca5"
|
||||
http_archive(
|
||||
name = "org_tensorflow",
|
||||
urls = [
|
||||
"https://mirror.bazel.build/github.com/tensorflow/tensorflow/archive/%s.tar.gz" % _TENSORFLOW_GIT_COMMIT,
|
||||
"https://github.com/tensorflow/tensorflow/archive/%s.tar.gz" % _TENSORFLOW_GIT_COMMIT,
|
||||
],
|
||||
# Patch https://github.com/tensorflow/tensorflow/commit/e3a7bdbebb99352351a19e2e403136166aa52934
|
||||
patches = [
|
||||
"@//third_party:org_tensorflow_e3a7bdbebb99352351a19e2e403136166aa52934.diff"
|
||||
"@//third_party:org_tensorflow_compatibility_fixes.diff",
|
||||
"@//third_party:org_tensorflow_protobuf_updates.diff",
|
||||
],
|
||||
patch_args = [
|
||||
"-p1",
|
||||
@@ -153,10 +154,6 @@ http_archive(
|
||||
sha256 = "5ba6d0db4e784621fda44a50c58bb23b0892684692f0c623e2063f9c19f192f1"
|
||||
)
|
||||
|
||||
# Please run
|
||||
# $ sudo apt-get install libopencv-core-dev libopencv-highgui-dev \
|
||||
# libopencv-calib3d-dev libopencv-features2d-dev \
|
||||
# libopencv-imgproc-dev libopencv-video-dev
|
||||
new_local_repository(
|
||||
name = "linux_opencv",
|
||||
build_file = "@//third_party:opencv_linux.BUILD",
|
||||
@@ -169,7 +166,6 @@ new_local_repository(
|
||||
path = "/usr"
|
||||
)
|
||||
|
||||
# Please run $ brew install opencv@3
|
||||
new_local_repository(
|
||||
name = "macos_opencv",
|
||||
build_file = "@//third_party:opencv_macos.BUILD",
|
||||
@@ -202,79 +198,6 @@ http_archive(
|
||||
url = "https://github.com/opencv/opencv/releases/download/3.2.0/opencv-3.2.0-ios-framework.zip",
|
||||
)
|
||||
|
||||
RULES_JVM_EXTERNAL_TAG = "2.2"
|
||||
RULES_JVM_EXTERNAL_SHA = "f1203ce04e232ab6fdd81897cf0ff76f2c04c0741424d192f28e65ae752ce2d6"
|
||||
|
||||
http_archive(
|
||||
name = "rules_jvm_external",
|
||||
strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
|
||||
sha256 = RULES_JVM_EXTERNAL_SHA,
|
||||
url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
|
||||
)
|
||||
|
||||
load("@rules_jvm_external//:defs.bzl", "maven_install")
|
||||
|
||||
maven_install(
|
||||
artifacts = [
|
||||
"androidx.annotation:annotation:aar:1.1.0",
|
||||
"androidx.appcompat:appcompat:aar:1.1.0-rc01",
|
||||
"androidx.camera:camera-core:aar:1.0.0-alpha06",
|
||||
"androidx.camera:camera-camera2:aar:1.0.0-alpha06",
|
||||
"androidx.constraintlayout:constraintlayout:aar:1.1.3",
|
||||
"androidx.core:core:aar:1.1.0-rc03",
|
||||
"androidx.legacy:legacy-support-v4:aar:1.0.0",
|
||||
"androidx.recyclerview:recyclerview:aar:1.1.0-beta02",
|
||||
"com.google.android.material:material:aar:1.0.0-rc01",
|
||||
],
|
||||
repositories = [
|
||||
"https://dl.google.com/dl/android/maven2",
|
||||
"https://repo1.maven.org/maven2",
|
||||
],
|
||||
)
|
||||
|
||||
maven_server(
|
||||
name = "google_server",
|
||||
url = "https://dl.google.com/dl/android/maven2",
|
||||
)
|
||||
|
||||
maven_jar(
|
||||
name = "androidx_lifecycle",
|
||||
artifact = "androidx.lifecycle:lifecycle-common:2.0.0",
|
||||
sha1 = "e070ffae07452331bc5684734fce6831d531785c",
|
||||
server = "google_server",
|
||||
)
|
||||
|
||||
maven_jar(
|
||||
name = "androidx_concurrent_futures",
|
||||
artifact = "androidx.concurrent:concurrent-futures:1.0.0-alpha03",
|
||||
sha1 = "b528df95c7e2fefa2210c0c742bf3e491c1818ae",
|
||||
server = "google_server",
|
||||
)
|
||||
|
||||
maven_jar(
|
||||
name = "com_google_guava_android",
|
||||
artifact = "com.google.guava:guava:27.0.1-android",
|
||||
sha1 = "b7e1c37f66ef193796ccd7ea6e80c2b05426182d",
|
||||
)
|
||||
|
||||
maven_jar(
|
||||
name = "com_google_common_flogger",
|
||||
artifact = "com.google.flogger:flogger:0.3.1",
|
||||
sha1 = "585030fe1ec709760cbef997a459729fb965df0e",
|
||||
)
|
||||
|
||||
maven_jar(
|
||||
name = "com_google_common_flogger_system_backend",
|
||||
artifact = "com.google.flogger:flogger-system-backend:0.3.1",
|
||||
sha1 = "287b569d76abcd82f9de87fe41829fbc7ebd8ac9",
|
||||
)
|
||||
|
||||
maven_jar(
|
||||
name = "com_google_code_findbugs",
|
||||
artifact = "com.google.code.findbugs:jsr305:3.0.2",
|
||||
sha1 = "25ea2e8b0c338a877313bd4672d3fe056ea78f0d",
|
||||
)
|
||||
|
||||
# You may run setup_android.sh to install Android SDK and NDK.
|
||||
android_ndk_repository(
|
||||
name = "androidndk",
|
||||
@@ -288,9 +211,15 @@ android_sdk_repository(
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_apple",
|
||||
sha256 = "bdc8e66e70b8a75da23b79f1f8c6207356df07d041d96d2189add7ee0780cf4e",
|
||||
strip_prefix = "rules_apple-b869b0d3868d78a1d4ffd866ccb304fb68aa12c3",
|
||||
url = "https://github.com/bazelbuild/rules_apple/archive/b869b0d3868d78a1d4ffd866ccb304fb68aa12c3.tar.gz",
|
||||
sha256 = "7a7afdd4869bb201c9352eed2daf37294d42b093579b70423490c1b4d4f6ce42",
|
||||
url = "https://github.com/bazelbuild/rules_apple/releases/download/0.19.0/rules_apple.0.19.0.tar.gz",
|
||||
patches = [
|
||||
# Bypass checking ios unit test runner when building MP ios applications.
|
||||
"@//third_party:build_bazel_rules_apple_bypass_test_runner_check.diff"
|
||||
],
|
||||
patch_args = [
|
||||
"-p1",
|
||||
],
|
||||
)
|
||||
|
||||
load(
|
||||
@@ -324,3 +253,49 @@ http_archive(
|
||||
build_file = "@//third_party:google_toolbox_for_mac.BUILD",
|
||||
)
|
||||
|
||||
# Maven dependencies.
|
||||
|
||||
RULES_JVM_EXTERNAL_TAG = "3.2"
|
||||
RULES_JVM_EXTERNAL_SHA = "82262ff4223c5fda6fb7ff8bd63db8131b51b413d26eb49e3131037e79e324af"
|
||||
|
||||
http_archive(
|
||||
name = "rules_jvm_external",
|
||||
strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
|
||||
sha256 = RULES_JVM_EXTERNAL_SHA,
|
||||
url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
|
||||
)
|
||||
|
||||
load("@rules_jvm_external//:defs.bzl", "maven_install")
|
||||
|
||||
# Important: there can only be one maven_install rule. Add new maven deps here.
|
||||
maven_install(
|
||||
name = "maven",
|
||||
artifacts = [
|
||||
"junit:junit:4.12",
|
||||
"androidx.test.espresso:espresso-core:3.1.1",
|
||||
"org.hamcrest:hamcrest-library:1.3",
|
||||
"androidx.concurrent:concurrent-futures:1.0.0-alpha03",
|
||||
"androidx.lifecycle:lifecycle-common:2.2.0",
|
||||
"androidx.annotation:annotation:aar:1.1.0",
|
||||
"androidx.appcompat:appcompat:aar:1.1.0-rc01",
|
||||
"androidx.camera:camera-core:aar:1.0.0-alpha06",
|
||||
"androidx.camera:camera-camera2:aar:1.0.0-alpha06",
|
||||
"androidx.constraintlayout:constraintlayout:aar:1.1.3",
|
||||
"androidx.core:core:aar:1.1.0-rc03",
|
||||
"androidx.legacy:legacy-support-v4:aar:1.0.0",
|
||||
"androidx.recyclerview:recyclerview:aar:1.1.0-beta02",
|
||||
"com.google.android.material:material:aar:1.0.0-rc01",
|
||||
"com.google.code.findbugs:jsr305:3.0.2",
|
||||
"com.google.flogger:flogger-system-backend:0.3.1",
|
||||
"com.google.flogger:flogger:0.3.1",
|
||||
"com.google.guava:guava:27.0.1-android",
|
||||
],
|
||||
repositories = [
|
||||
"https://jcenter.bintray.com",
|
||||
"https://maven.google.com",
|
||||
"https://dl.google.com/dl/android/maven2",
|
||||
"https://repo1.maven.org/maven2",
|
||||
],
|
||||
fetch_sources = True,
|
||||
version_conflict_policy = "pinned",
|
||||
)
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
|
||||
licenses(["notice"]) # Apache 2.0
|
||||
|
||||
# Note: yes, these need to use "//external:android/crosstool", not
|
||||
# @androidndk//:default_crosstool.
|
||||
|
||||
config_setting(
|
||||
name = "android",
|
||||
values = {"crosstool_top": "//external:android/crosstool"},
|
||||
|
||||
@@ -228,7 +228,9 @@ cc_library(
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:collection_item_id",
|
||||
"//mediapipe/framework:packet",
|
||||
"//mediapipe/framework/formats:detection_cc_proto",
|
||||
"//mediapipe/framework/formats:landmark_cc_proto",
|
||||
"//mediapipe/framework/formats:matrix",
|
||||
"//mediapipe/framework/formats:rect_cc_proto",
|
||||
"//mediapipe/framework/port:integral_types",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
@@ -249,12 +251,14 @@ cc_library(
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:collection_item_id",
|
||||
"//mediapipe/framework:packet",
|
||||
"//mediapipe/framework/formats:classification_cc_proto",
|
||||
"//mediapipe/framework/formats:landmark_cc_proto",
|
||||
"//mediapipe/framework/formats:rect_cc_proto",
|
||||
"//mediapipe/framework/port:integral_types",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
"//mediapipe/util:render_data_cc_proto",
|
||||
"@org_tensorflow//tensorflow/lite:framework",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
@@ -265,10 +269,11 @@ cc_test(
|
||||
deps = [
|
||||
":begin_loop_calculator",
|
||||
":end_loop_calculator",
|
||||
"//mediapipe/calculators/core:packet_cloner_calculator",
|
||||
":gate_calculator",
|
||||
"//mediapipe/framework:calculator_context",
|
||||
"//mediapipe/framework:calculator_contract",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:packet",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"//mediapipe/framework/port:integral_types",
|
||||
"//mediapipe/framework/port:parse_text_proto",
|
||||
@@ -334,6 +339,7 @@ cc_library(
|
||||
deps = [
|
||||
":clip_vector_size_calculator_cc_proto",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/formats:detection_cc_proto",
|
||||
"//mediapipe/framework/formats:rect_cc_proto",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
@@ -610,6 +616,22 @@ cc_library(
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "side_packet_to_stream_calculator_test",
|
||||
srcs = ["side_packet_to_stream_calculator_test.cc"],
|
||||
deps = [
|
||||
":side_packet_to_stream_calculator",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"//mediapipe/framework/port:integral_types",
|
||||
"//mediapipe/framework/port:parse_text_proto",
|
||||
"//mediapipe/framework/port:status",
|
||||
"//mediapipe/framework/tool:options_util",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "immediate_mux_calculator_test",
|
||||
srcs = ["immediate_mux_calculator_test.cc"],
|
||||
@@ -677,15 +699,17 @@ cc_test(
|
||||
name = "previous_loopback_calculator_test",
|
||||
srcs = ["previous_loopback_calculator_test.cc"],
|
||||
deps = [
|
||||
":gate_calculator",
|
||||
":make_pair_calculator",
|
||||
":pass_through_calculator",
|
||||
":previous_loopback_calculator",
|
||||
"//mediapipe/calculators/core:make_pair_calculator",
|
||||
"//mediapipe/calculators/core:pass_through_calculator",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:calculator_runner",
|
||||
"//mediapipe/framework:timestamp",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"//mediapipe/framework/port:integral_types",
|
||||
"//mediapipe/framework/port:parse_text_proto",
|
||||
"//mediapipe/framework/port:status",
|
||||
"//mediapipe/framework/stream_handler:immediate_input_stream_handler",
|
||||
"//mediapipe/framework/tool:sink",
|
||||
"@com_google_absl//absl/time",
|
||||
@@ -753,9 +777,11 @@ cc_library(
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":split_vector_calculator_cc_proto",
|
||||
"//mediapipe/framework/formats:detection_cc_proto",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/formats:landmark_cc_proto",
|
||||
"//mediapipe/framework/formats:rect_cc_proto",
|
||||
"//mediapipe/framework/formats:matrix",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
"//mediapipe/util:resource_util",
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include "mediapipe/calculators/core/end_loop_calculator.h"
|
||||
#include "mediapipe/framework/calculator_contract.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/packet.h"
|
||||
#include "mediapipe/framework/port/gmock.h"
|
||||
#include "mediapipe/framework/port/gtest.h"
|
||||
#include "mediapipe/framework/port/integral_types.h"
|
||||
#include "mediapipe/framework/port/parse_text_proto.h"
|
||||
@@ -28,6 +30,13 @@
|
||||
namespace mediapipe {
|
||||
namespace {
|
||||
|
||||
MATCHER_P2(PacketOfIntsEq, timestamp, value, "") {
|
||||
Timestamp actual_timestamp = arg.Timestamp();
|
||||
const auto& actual_value = arg.template Get<std::vector<int>>();
|
||||
return testing::Value(actual_timestamp, testing::Eq(timestamp)) &&
|
||||
testing::Value(actual_value, testing::ElementsAreArray(value));
|
||||
}
|
||||
|
||||
typedef BeginLoopCalculator<std::vector<int>> BeginLoopIntegerCalculator;
|
||||
REGISTER_CALCULATOR(BeginLoopIntegerCalculator);
|
||||
|
||||
@@ -59,8 +68,8 @@ REGISTER_CALCULATOR(EndLoopIntegersCalculator);
|
||||
|
||||
class BeginEndLoopCalculatorGraphTest : public ::testing::Test {
|
||||
protected:
|
||||
BeginEndLoopCalculatorGraphTest() {
|
||||
graph_config_ = ParseTextProtoOrDie<CalculatorGraphConfig>(
|
||||
void SetUp() override {
|
||||
auto graph_config = ParseTextProtoOrDie<CalculatorGraphConfig>(
|
||||
R"(
|
||||
num_threads: 4
|
||||
input_stream: "ints"
|
||||
@@ -82,94 +91,222 @@ class BeginEndLoopCalculatorGraphTest : public ::testing::Test {
|
||||
output_stream: "ITERABLE:ints_plus_one"
|
||||
}
|
||||
)");
|
||||
tool::AddVectorSink("ints_plus_one", &graph_config_, &output_packets_);
|
||||
tool::AddVectorSink("ints_plus_one", &graph_config, &output_packets_);
|
||||
MP_ASSERT_OK(graph_.Initialize(graph_config));
|
||||
MP_ASSERT_OK(graph_.StartRun({}));
|
||||
}
|
||||
|
||||
CalculatorGraphConfig graph_config_;
|
||||
void SendPacketOfInts(Timestamp timestamp, std::vector<int> ints) {
|
||||
MP_ASSERT_OK(graph_.AddPacketToInputStream(
|
||||
"ints", MakePacket<std::vector<int>>(std::move(ints)).At(timestamp)));
|
||||
}
|
||||
|
||||
CalculatorGraph graph_;
|
||||
std::vector<Packet> output_packets_;
|
||||
};
|
||||
|
||||
TEST_F(BeginEndLoopCalculatorGraphTest, InputStreamForIterableIsEmpty) {
|
||||
MP_ASSERT_OK(graph_.WaitUntilIdle());
|
||||
|
||||
// EndLoopCalc will forward the timestamp bound because there are no packets
|
||||
// to process.
|
||||
ASSERT_EQ(0, output_packets_.size());
|
||||
|
||||
MP_ASSERT_OK(graph_.CloseAllPacketSources());
|
||||
MP_ASSERT_OK(graph_.WaitUntilDone());
|
||||
}
|
||||
|
||||
TEST_F(BeginEndLoopCalculatorGraphTest, SingleEmptyVector) {
|
||||
CalculatorGraph graph;
|
||||
MP_EXPECT_OK(graph.Initialize(graph_config_));
|
||||
MP_EXPECT_OK(graph.StartRun({}));
|
||||
auto input_vector = absl::make_unique<std::vector<int>>();
|
||||
Timestamp input_timestamp = Timestamp(0);
|
||||
MP_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"ints", Adopt(input_vector.release()).At(input_timestamp)));
|
||||
MP_ASSERT_OK(graph.WaitUntilIdle());
|
||||
SendPacketOfInts(Timestamp(0), {});
|
||||
MP_ASSERT_OK(graph_.WaitUntilIdle());
|
||||
|
||||
// EndLoopCalc will forward the timestamp bound because there are no elements
|
||||
// in collection to output.
|
||||
ASSERT_EQ(0, output_packets_.size());
|
||||
EXPECT_TRUE(output_packets_.empty());
|
||||
|
||||
MP_ASSERT_OK(graph.CloseAllPacketSources());
|
||||
MP_ASSERT_OK(graph.WaitUntilDone());
|
||||
MP_ASSERT_OK(graph_.CloseAllPacketSources());
|
||||
MP_ASSERT_OK(graph_.WaitUntilDone());
|
||||
}
|
||||
|
||||
TEST_F(BeginEndLoopCalculatorGraphTest, SingleNonEmptyVector) {
|
||||
CalculatorGraph graph;
|
||||
MP_EXPECT_OK(graph.Initialize(graph_config_));
|
||||
MP_EXPECT_OK(graph.StartRun({}));
|
||||
auto input_vector = absl::make_unique<std::vector<int>>();
|
||||
input_vector->emplace_back(0);
|
||||
input_vector->emplace_back(1);
|
||||
input_vector->emplace_back(2);
|
||||
Timestamp input_timestamp = Timestamp(0);
|
||||
MP_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"ints", Adopt(input_vector.release()).At(input_timestamp)));
|
||||
MP_ASSERT_OK(graph.WaitUntilIdle());
|
||||
SendPacketOfInts(input_timestamp, {0, 1, 2});
|
||||
MP_ASSERT_OK(graph_.WaitUntilIdle());
|
||||
|
||||
ASSERT_EQ(1, output_packets_.size());
|
||||
EXPECT_EQ(input_timestamp, output_packets_[0].Timestamp());
|
||||
std::vector<int> expected_output_vector = {1, 2, 3};
|
||||
EXPECT_EQ(expected_output_vector, output_packets_[0].Get<std::vector<int>>());
|
||||
EXPECT_THAT(output_packets_,
|
||||
testing::ElementsAre(
|
||||
PacketOfIntsEq(input_timestamp, std::vector<int>{1, 2, 3})));
|
||||
|
||||
MP_ASSERT_OK(graph.CloseAllPacketSources());
|
||||
MP_ASSERT_OK(graph.WaitUntilDone());
|
||||
MP_ASSERT_OK(graph_.CloseAllPacketSources());
|
||||
MP_ASSERT_OK(graph_.WaitUntilDone());
|
||||
}
|
||||
|
||||
TEST_F(BeginEndLoopCalculatorGraphTest, MultipleVectors) {
|
||||
CalculatorGraph graph;
|
||||
MP_EXPECT_OK(graph.Initialize(graph_config_));
|
||||
MP_EXPECT_OK(graph.StartRun({}));
|
||||
|
||||
auto input_vector0 = absl::make_unique<std::vector<int>>();
|
||||
input_vector0->emplace_back(0);
|
||||
input_vector0->emplace_back(1);
|
||||
Timestamp input_timestamp0 = Timestamp(0);
|
||||
MP_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"ints", Adopt(input_vector0.release()).At(input_timestamp0)));
|
||||
SendPacketOfInts(input_timestamp0, {0, 1});
|
||||
|
||||
auto input_vector1 = absl::make_unique<std::vector<int>>();
|
||||
Timestamp input_timestamp1 = Timestamp(1);
|
||||
MP_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"ints", Adopt(input_vector1.release()).At(input_timestamp1)));
|
||||
SendPacketOfInts(input_timestamp1, {});
|
||||
|
||||
auto input_vector2 = absl::make_unique<std::vector<int>>();
|
||||
input_vector2->emplace_back(2);
|
||||
input_vector2->emplace_back(3);
|
||||
Timestamp input_timestamp2 = Timestamp(2);
|
||||
MP_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"ints", Adopt(input_vector2.release()).At(input_timestamp2)));
|
||||
SendPacketOfInts(input_timestamp2, {2, 3});
|
||||
|
||||
MP_ASSERT_OK(graph.CloseAllPacketSources());
|
||||
MP_ASSERT_OK(graph.WaitUntilDone());
|
||||
|
||||
ASSERT_EQ(2, output_packets_.size());
|
||||
|
||||
EXPECT_EQ(input_timestamp0, output_packets_[0].Timestamp());
|
||||
std::vector<int> expected_output_vector0 = {1, 2};
|
||||
EXPECT_EQ(expected_output_vector0,
|
||||
output_packets_[0].Get<std::vector<int>>());
|
||||
MP_ASSERT_OK(graph_.CloseAllPacketSources());
|
||||
MP_ASSERT_OK(graph_.WaitUntilDone());
|
||||
|
||||
// At input_timestamp1, EndLoopCalc will forward timestamp bound as there are
|
||||
// no elements in vector to process.
|
||||
EXPECT_THAT(output_packets_,
|
||||
testing::ElementsAre(
|
||||
PacketOfIntsEq(input_timestamp0, std::vector<int>{1, 2}),
|
||||
PacketOfIntsEq(input_timestamp2, std::vector<int>{3, 4})));
|
||||
}
|
||||
|
||||
EXPECT_EQ(input_timestamp2, output_packets_[1].Timestamp());
|
||||
std::vector<int> expected_output_vector2 = {3, 4};
|
||||
EXPECT_EQ(expected_output_vector2,
|
||||
output_packets_[1].Get<std::vector<int>>());
|
||||
// Passes non empty vector through or outputs empty vector in case of timestamp
|
||||
// bound update.
|
||||
class PassThroughOrEmptyVectorCalculator : public CalculatorBase {
|
||||
public:
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
|
||||
cc->SetProcessTimestampBounds(true);
|
||||
cc->Inputs().Index(0).Set<std::vector<int>>();
|
||||
cc->Outputs().Index(0).Set<std::vector<int>>();
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status Open(CalculatorContext* cc) override {
|
||||
cc->SetOffset(TimestampDiff(0));
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status Process(CalculatorContext* cc) override {
|
||||
if (!cc->Inputs().Index(0).IsEmpty()) {
|
||||
cc->Outputs().Index(0).AddPacket(cc->Inputs().Index(0).Value());
|
||||
} else {
|
||||
cc->Outputs().Index(0).AddPacket(
|
||||
MakePacket<std::vector<int>>(std::vector<int>())
|
||||
.At(cc->InputTimestamp()));
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
};
|
||||
|
||||
REGISTER_CALCULATOR(PassThroughOrEmptyVectorCalculator);
|
||||
|
||||
class BeginEndLoopCalculatorGraphProcessingEmptyPacketsTest
|
||||
: public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
auto graph_config = ParseTextProtoOrDie<CalculatorGraphConfig>(
|
||||
R"(
|
||||
num_threads: 4
|
||||
input_stream: "ints"
|
||||
input_stream: "force_ints_to_be_timestamp_bound_update"
|
||||
node {
|
||||
calculator: "GateCalculator"
|
||||
input_stream: "ints"
|
||||
input_stream: "DISALLOW:force_ints_to_be_timestamp_bound_update"
|
||||
output_stream: "ints_passed_through"
|
||||
}
|
||||
node {
|
||||
calculator: "BeginLoopIntegerCalculator"
|
||||
input_stream: "ITERABLE:ints_passed_through"
|
||||
output_stream: "ITEM:int"
|
||||
output_stream: "BATCH_END:timestamp"
|
||||
}
|
||||
node {
|
||||
calculator: "IncrementCalculator"
|
||||
input_stream: "int"
|
||||
output_stream: "int_plus_one"
|
||||
}
|
||||
node {
|
||||
calculator: "EndLoopIntegersCalculator"
|
||||
input_stream: "ITEM:int_plus_one"
|
||||
input_stream: "BATCH_END:timestamp"
|
||||
output_stream: "ITERABLE:ints_plus_one"
|
||||
}
|
||||
node {
|
||||
calculator: "PassThroughOrEmptyVectorCalculator"
|
||||
input_stream: "ints_plus_one"
|
||||
output_stream: "ints_plus_one_passed_through"
|
||||
}
|
||||
)");
|
||||
tool::AddVectorSink("ints_plus_one_passed_through", &graph_config,
|
||||
&output_packets_);
|
||||
MP_ASSERT_OK(graph_.Initialize(graph_config));
|
||||
MP_ASSERT_OK(graph_.StartRun({}));
|
||||
}
|
||||
|
||||
void SendPacketOfIntsOrBound(Timestamp timestamp, std::vector<int> ints) {
|
||||
// All "ints" packets which are empty are forced to be just timestamp
|
||||
// bound updates for begin loop calculator.
|
||||
bool force_ints_to_be_timestamp_bound_update = ints.empty();
|
||||
MP_ASSERT_OK(graph_.AddPacketToInputStream(
|
||||
"force_ints_to_be_timestamp_bound_update",
|
||||
MakePacket<bool>(force_ints_to_be_timestamp_bound_update)
|
||||
.At(timestamp)));
|
||||
MP_ASSERT_OK(graph_.AddPacketToInputStream(
|
||||
"ints", MakePacket<std::vector<int>>(std::move(ints)).At(timestamp)));
|
||||
}
|
||||
|
||||
CalculatorGraph graph_;
|
||||
std::vector<Packet> output_packets_;
|
||||
};
|
||||
|
||||
TEST_F(BeginEndLoopCalculatorGraphProcessingEmptyPacketsTest,
|
||||
SingleEmptyVector) {
|
||||
SendPacketOfIntsOrBound(Timestamp(0), {});
|
||||
MP_ASSERT_OK(graph_.WaitUntilIdle());
|
||||
|
||||
EXPECT_THAT(output_packets_, testing::ElementsAre(PacketOfIntsEq(
|
||||
Timestamp(0), std::vector<int>{})));
|
||||
|
||||
MP_ASSERT_OK(graph_.CloseAllPacketSources());
|
||||
MP_ASSERT_OK(graph_.WaitUntilDone());
|
||||
}
|
||||
|
||||
TEST_F(BeginEndLoopCalculatorGraphProcessingEmptyPacketsTest,
|
||||
SingleNonEmptyVector) {
|
||||
SendPacketOfIntsOrBound(Timestamp(0), {0, 1, 2});
|
||||
MP_ASSERT_OK(graph_.WaitUntilIdle());
|
||||
|
||||
EXPECT_THAT(output_packets_, testing::ElementsAre(PacketOfIntsEq(
|
||||
Timestamp(0), std::vector<int>{1, 2, 3})));
|
||||
|
||||
MP_ASSERT_OK(graph_.CloseAllPacketSources());
|
||||
MP_ASSERT_OK(graph_.WaitUntilDone());
|
||||
}
|
||||
|
||||
TEST_F(BeginEndLoopCalculatorGraphProcessingEmptyPacketsTest, MultipleVectors) {
|
||||
SendPacketOfIntsOrBound(Timestamp(0), {});
|
||||
// Waiting until idle to guarantee all timestamp bound updates are processed
|
||||
// individually. (Timestamp bounds updates occur in the provide config only
|
||||
// if input is an empty vector.)
|
||||
MP_ASSERT_OK(graph_.WaitUntilIdle());
|
||||
|
||||
SendPacketOfIntsOrBound(Timestamp(1), {0, 1});
|
||||
SendPacketOfIntsOrBound(Timestamp(2), {});
|
||||
// Waiting until idle to guarantee all timestamp bound updates are processed
|
||||
// individually. (Timestamp bounds updates occur in the provide config only
|
||||
// if input is an empty vector.)
|
||||
MP_ASSERT_OK(graph_.WaitUntilIdle());
|
||||
|
||||
SendPacketOfIntsOrBound(Timestamp(3), {2, 3});
|
||||
SendPacketOfIntsOrBound(Timestamp(4), {});
|
||||
// Waiting until idle to guarantee all timestamp bound updates are processed
|
||||
// individually. (Timestamp bounds updates occur in the provide config only
|
||||
// if input is an empty vector.)
|
||||
MP_ASSERT_OK(graph_.WaitUntilIdle());
|
||||
|
||||
MP_ASSERT_OK(graph_.CloseAllPacketSources());
|
||||
MP_ASSERT_OK(graph_.WaitUntilDone());
|
||||
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
testing::ElementsAre(PacketOfIntsEq(Timestamp(0), std::vector<int>{}),
|
||||
PacketOfIntsEq(Timestamp(1), std::vector<int>{1, 2}),
|
||||
PacketOfIntsEq(Timestamp(2), std::vector<int>{}),
|
||||
PacketOfIntsEq(Timestamp(3), std::vector<int>{3, 4}),
|
||||
PacketOfIntsEq(Timestamp(4), std::vector<int>{})));
|
||||
}
|
||||
|
||||
class MultiplierCalculator : public CalculatorBase {
|
||||
@@ -199,8 +336,8 @@ REGISTER_CALCULATOR(MultiplierCalculator);
|
||||
|
||||
class BeginEndLoopCalculatorGraphWithClonedInputsTest : public ::testing::Test {
|
||||
protected:
|
||||
BeginEndLoopCalculatorGraphWithClonedInputsTest() {
|
||||
graph_config_ = ParseTextProtoOrDie<CalculatorGraphConfig>(
|
||||
void SetUp() override {
|
||||
auto graph_config = ParseTextProtoOrDie<CalculatorGraphConfig>(
|
||||
R"(
|
||||
num_threads: 4
|
||||
input_stream: "ints"
|
||||
@@ -226,109 +363,85 @@ class BeginEndLoopCalculatorGraphWithClonedInputsTest : public ::testing::Test {
|
||||
output_stream: "ITERABLE:multiplied_ints"
|
||||
}
|
||||
)");
|
||||
tool::AddVectorSink("multiplied_ints", &graph_config_, &output_packets_);
|
||||
tool::AddVectorSink("multiplied_ints", &graph_config, &output_packets_);
|
||||
MP_ASSERT_OK(graph_.Initialize(graph_config));
|
||||
MP_ASSERT_OK(graph_.StartRun({}));
|
||||
}
|
||||
|
||||
CalculatorGraphConfig graph_config_;
|
||||
void SendPackets(Timestamp timestamp, int multiplier, std::vector<int> ints) {
|
||||
MP_ASSERT_OK(graph_.AddPacketToInputStream(
|
||||
"ints", MakePacket<std::vector<int>>(std::move(ints)).At(timestamp)));
|
||||
MP_ASSERT_OK(graph_.AddPacketToInputStream(
|
||||
"multiplier", MakePacket<int>(multiplier).At(timestamp)));
|
||||
}
|
||||
|
||||
void SendMultiplier(Timestamp timestamp, int multiplier) {
|
||||
MP_ASSERT_OK(graph_.AddPacketToInputStream(
|
||||
"multiplier", MakePacket<int>(multiplier).At(timestamp)));
|
||||
}
|
||||
|
||||
CalculatorGraph graph_;
|
||||
std::vector<Packet> output_packets_;
|
||||
};
|
||||
|
||||
TEST_F(BeginEndLoopCalculatorGraphWithClonedInputsTest, SingleEmptyVector) {
|
||||
CalculatorGraph graph;
|
||||
MP_EXPECT_OK(graph.Initialize(graph_config_));
|
||||
MP_EXPECT_OK(graph.StartRun({}));
|
||||
auto input_vector = absl::make_unique<std::vector<int>>();
|
||||
TEST_F(BeginEndLoopCalculatorGraphWithClonedInputsTest,
|
||||
InputStreamForIterableIsEmpty) {
|
||||
Timestamp input_timestamp = Timestamp(42);
|
||||
MP_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"ints", Adopt(input_vector.release()).At(input_timestamp)));
|
||||
auto multiplier = absl::make_unique<int>(2);
|
||||
MP_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"multiplier", Adopt(multiplier.release()).At(input_timestamp)));
|
||||
MP_ASSERT_OK(graph.WaitUntilIdle());
|
||||
SendMultiplier(input_timestamp, /*multiplier=*/2);
|
||||
MP_ASSERT_OK(graph_.WaitUntilIdle());
|
||||
|
||||
// EndLoopCalc will forward the timestamp bound because there are no packets
|
||||
// to process.
|
||||
ASSERT_EQ(0, output_packets_.size());
|
||||
|
||||
MP_ASSERT_OK(graph_.CloseAllPacketSources());
|
||||
MP_ASSERT_OK(graph_.WaitUntilDone());
|
||||
}
|
||||
|
||||
TEST_F(BeginEndLoopCalculatorGraphWithClonedInputsTest, SingleEmptyVector) {
|
||||
SendPackets(Timestamp(0), /*multiplier=*/2, /*ints=*/{});
|
||||
MP_ASSERT_OK(graph_.WaitUntilIdle());
|
||||
|
||||
// EndLoopCalc will forward the timestamp bound because there are no elements
|
||||
// in collection to output.
|
||||
ASSERT_EQ(0, output_packets_.size());
|
||||
EXPECT_TRUE(output_packets_.empty());
|
||||
|
||||
MP_ASSERT_OK(graph.CloseAllPacketSources());
|
||||
MP_ASSERT_OK(graph.WaitUntilDone());
|
||||
MP_ASSERT_OK(graph_.CloseAllPacketSources());
|
||||
MP_ASSERT_OK(graph_.WaitUntilDone());
|
||||
}
|
||||
|
||||
TEST_F(BeginEndLoopCalculatorGraphWithClonedInputsTest, SingleNonEmptyVector) {
|
||||
CalculatorGraph graph;
|
||||
MP_EXPECT_OK(graph.Initialize(graph_config_));
|
||||
MP_EXPECT_OK(graph.StartRun({}));
|
||||
auto input_vector = absl::make_unique<std::vector<int>>();
|
||||
input_vector->emplace_back(0);
|
||||
input_vector->emplace_back(1);
|
||||
input_vector->emplace_back(2);
|
||||
Timestamp input_timestamp = Timestamp(42);
|
||||
MP_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"ints", Adopt(input_vector.release()).At(input_timestamp)));
|
||||
auto multiplier = absl::make_unique<int>(2);
|
||||
MP_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"multiplier", Adopt(multiplier.release()).At(input_timestamp)));
|
||||
MP_ASSERT_OK(graph.WaitUntilIdle());
|
||||
SendPackets(input_timestamp, /*multiplier=*/2, /*ints=*/{0, 1, 2});
|
||||
MP_ASSERT_OK(graph_.WaitUntilIdle());
|
||||
|
||||
ASSERT_EQ(1, output_packets_.size());
|
||||
EXPECT_EQ(input_timestamp, output_packets_[0].Timestamp());
|
||||
std::vector<int> expected_output_vector = {0, 2, 4};
|
||||
EXPECT_EQ(expected_output_vector, output_packets_[0].Get<std::vector<int>>());
|
||||
EXPECT_THAT(output_packets_,
|
||||
testing::ElementsAre(
|
||||
PacketOfIntsEq(input_timestamp, std::vector<int>{0, 2, 4})));
|
||||
|
||||
MP_ASSERT_OK(graph.CloseAllPacketSources());
|
||||
MP_ASSERT_OK(graph.WaitUntilDone());
|
||||
MP_ASSERT_OK(graph_.CloseAllPacketSources());
|
||||
MP_ASSERT_OK(graph_.WaitUntilDone());
|
||||
}
|
||||
|
||||
TEST_F(BeginEndLoopCalculatorGraphWithClonedInputsTest, MultipleVectors) {
|
||||
CalculatorGraph graph;
|
||||
MP_EXPECT_OK(graph.Initialize(graph_config_));
|
||||
MP_EXPECT_OK(graph.StartRun({}));
|
||||
|
||||
auto input_vector0 = absl::make_unique<std::vector<int>>();
|
||||
input_vector0->emplace_back(0);
|
||||
input_vector0->emplace_back(1);
|
||||
Timestamp input_timestamp0 = Timestamp(42);
|
||||
MP_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"ints", Adopt(input_vector0.release()).At(input_timestamp0)));
|
||||
auto multiplier0 = absl::make_unique<int>(2);
|
||||
MP_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"multiplier", Adopt(multiplier0.release()).At(input_timestamp0)));
|
||||
SendPackets(input_timestamp0, /*multiplier=*/2, /*ints=*/{0, 1});
|
||||
|
||||
auto input_vector1 = absl::make_unique<std::vector<int>>();
|
||||
Timestamp input_timestamp1 = Timestamp(43);
|
||||
MP_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"ints", Adopt(input_vector1.release()).At(input_timestamp1)));
|
||||
auto multiplier1 = absl::make_unique<int>(2);
|
||||
MP_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"multiplier", Adopt(multiplier1.release()).At(input_timestamp1)));
|
||||
SendPackets(input_timestamp1, /*multiplier=*/2, /*ints=*/{});
|
||||
|
||||
auto input_vector2 = absl::make_unique<std::vector<int>>();
|
||||
input_vector2->emplace_back(2);
|
||||
input_vector2->emplace_back(3);
|
||||
Timestamp input_timestamp2 = Timestamp(44);
|
||||
MP_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"ints", Adopt(input_vector2.release()).At(input_timestamp2)));
|
||||
auto multiplier2 = absl::make_unique<int>(3);
|
||||
MP_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"multiplier", Adopt(multiplier2.release()).At(input_timestamp2)));
|
||||
SendPackets(input_timestamp2, /*multiplier=*/3, /*ints=*/{2, 3});
|
||||
|
||||
MP_ASSERT_OK(graph.CloseAllPacketSources());
|
||||
MP_ASSERT_OK(graph.WaitUntilDone());
|
||||
|
||||
ASSERT_EQ(2, output_packets_.size());
|
||||
|
||||
EXPECT_EQ(input_timestamp0, output_packets_[0].Timestamp());
|
||||
std::vector<int> expected_output_vector0 = {0, 2};
|
||||
EXPECT_EQ(expected_output_vector0,
|
||||
output_packets_[0].Get<std::vector<int>>());
|
||||
MP_ASSERT_OK(graph_.CloseAllPacketSources());
|
||||
MP_ASSERT_OK(graph_.WaitUntilDone());
|
||||
|
||||
// At input_timestamp1, EndLoopCalc will forward timestamp bound as there are
|
||||
// no elements in vector to process.
|
||||
|
||||
EXPECT_EQ(input_timestamp2, output_packets_[1].Timestamp());
|
||||
std::vector<int> expected_output_vector2 = {6, 9};
|
||||
EXPECT_EQ(expected_output_vector2,
|
||||
output_packets_[1].Get<std::vector<int>>());
|
||||
EXPECT_THAT(output_packets_,
|
||||
testing::ElementsAre(
|
||||
PacketOfIntsEq(input_timestamp0, std::vector<int>{0, 2}),
|
||||
PacketOfIntsEq(input_timestamp2, std::vector<int>{6, 9})));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "mediapipe/framework/formats/detection.pb.h"
|
||||
#include "mediapipe/framework/formats/landmark.pb.h"
|
||||
#include "mediapipe/framework/formats/matrix.h"
|
||||
#include "mediapipe/framework/formats/rect.pb.h"
|
||||
|
||||
namespace mediapipe {
|
||||
@@ -31,4 +33,13 @@ typedef BeginLoopCalculator<std::vector<::mediapipe::NormalizedRect>>
|
||||
BeginLoopNormalizedRectCalculator;
|
||||
REGISTER_CALCULATOR(BeginLoopNormalizedRectCalculator);
|
||||
|
||||
// A calculator to process std::vector<Detection>.
|
||||
typedef BeginLoopCalculator<std::vector<::mediapipe::Detection>>
|
||||
BeginLoopDetectionCalculator;
|
||||
REGISTER_CALCULATOR(BeginLoopDetectionCalculator);
|
||||
|
||||
// A calculator to process std::vector<Matrix>.
|
||||
typedef BeginLoopCalculator<std::vector<Matrix>> BeginLoopMatrixCalculator;
|
||||
REGISTER_CALCULATOR(BeginLoopMatrixCalculator);
|
||||
|
||||
} // namespace mediapipe
|
||||
|
||||
@@ -52,20 +52,28 @@ namespace mediapipe {
|
||||
// output_stream: "OUTPUT:aggregated_result" # IterableU @ext_ts
|
||||
// }
|
||||
//
|
||||
// BeginLoopCalculator accepts an optional input stream tagged with "TICK"
|
||||
// which if non-empty, wakes up the calculator and calls
|
||||
// BeginLoopCalculator::Process(). Input streams tagged with "CLONE" are cloned
|
||||
// to the corresponding output streams at loop timestamps. This ensures that a
|
||||
// MediaPipe graph or sub-graph can run multiple times, once per element in the
|
||||
// "ITERABLE" for each pakcet clone of the packets in the "CLONE" input streams.
|
||||
// Input streams tagged with "CLONE" are cloned to the corresponding output
|
||||
// streams at loop timestamps. This ensures that a MediaPipe graph or sub-graph
|
||||
// can run multiple times, once per element in the "ITERABLE" for each pakcet
|
||||
// clone of the packets in the "CLONE" input streams.
|
||||
template <typename IterableT>
|
||||
class BeginLoopCalculator : public CalculatorBase {
|
||||
using ItemT = typename IterableT::value_type;
|
||||
|
||||
public:
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
|
||||
// The below enables processing of timestamp bound updates, and that enables
|
||||
// correct timestamp propagation by the companion EndLoopCalculator.
|
||||
//
|
||||
// For instance, Process() function will be still invoked even if upstream
|
||||
// calculator has updated timestamp bound for ITERABLE input instead of
|
||||
// providing actual value.
|
||||
cc->SetProcessTimestampBounds(true);
|
||||
|
||||
// A non-empty packet in the optional "TICK" input stream wakes up the
|
||||
// calculator.
|
||||
// DEPRECATED as timestamp bound updates are processed by default in this
|
||||
// calculator.
|
||||
if (cc->Inputs().HasTag("TICK")) {
|
||||
cc->Inputs().Tag("TICK").SetAny();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/formats/detection.pb.h"
|
||||
#include "mediapipe/framework/formats/rect.pb.h"
|
||||
|
||||
namespace mediapipe {
|
||||
@@ -25,4 +26,8 @@ typedef ClipVectorSizeCalculator<::mediapipe::NormalizedRect>
|
||||
ClipNormalizedRectVectorSizeCalculator;
|
||||
REGISTER_CALCULATOR(ClipNormalizedRectVectorSizeCalculator);
|
||||
|
||||
typedef ClipVectorSizeCalculator<::mediapipe::Detection>
|
||||
ClipDetectionVectorSizeCalculator;
|
||||
REGISTER_CALCULATOR(ClipDetectionVectorSizeCalculator);
|
||||
|
||||
} // namespace mediapipe
|
||||
|
||||
@@ -16,9 +16,11 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "mediapipe/framework/formats/classification.pb.h"
|
||||
#include "mediapipe/framework/formats/landmark.pb.h"
|
||||
#include "mediapipe/framework/formats/rect.pb.h"
|
||||
#include "mediapipe/util/render_data.pb.h"
|
||||
#include "tensorflow/lite/interpreter.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
@@ -37,4 +39,11 @@ typedef EndLoopCalculator<std::vector<::mediapipe::RenderData>>
|
||||
EndLoopRenderDataCalculator;
|
||||
REGISTER_CALCULATOR(EndLoopRenderDataCalculator);
|
||||
|
||||
typedef EndLoopCalculator<std::vector<::mediapipe::ClassificationList>>
|
||||
EndLoopClassificationListCalculator;
|
||||
REGISTER_CALCULATOR(EndLoopClassificationListCalculator);
|
||||
|
||||
typedef EndLoopCalculator<std::vector<TfLiteTensor>> EndLoopTensorCalculator;
|
||||
REGISTER_CALCULATOR(EndLoopTensorCalculator);
|
||||
|
||||
} // namespace mediapipe
|
||||
|
||||
@@ -25,13 +25,17 @@ namespace mediapipe {
|
||||
// together with some previous output.
|
||||
//
|
||||
// For the first packet that arrives on the MAIN input, the timestamp bound is
|
||||
// advanced on the output. Downstream calculators will see this as an empty
|
||||
// advanced on the PREV_LOOP. Downstream calculators will see this as an empty
|
||||
// packet. This way they are not kept waiting for the previous output, which
|
||||
// for the first iteration does not exist.
|
||||
//
|
||||
// Thereafter, each packet received on MAIN is matched with a packet received
|
||||
// on LOOP; the LOOP packet's timestamp is changed to that of the MAIN packet,
|
||||
// and it is output on PREV_LOOP.
|
||||
// Thereafter,
|
||||
// - Each non-empty MAIN packet results in:
|
||||
// a) a PREV_LOOP packet with contents of the LOOP packet received at the
|
||||
// timestamp of the previous non-empty MAIN packet
|
||||
// b) or in a PREV_LOOP timestamp bound update if the LOOP packet was empty.
|
||||
// - Each empty MAIN packet indicating timestamp bound update results in a
|
||||
// PREV_LOOP timestamp bound update.
|
||||
//
|
||||
// Example config:
|
||||
// node {
|
||||
@@ -56,83 +60,115 @@ class PreviousLoopbackCalculator : public CalculatorBase {
|
||||
// TODO: an optional PREV_TIMESTAMP output could be added to
|
||||
// carry the original timestamp of the packet on PREV_LOOP.
|
||||
cc->SetInputStreamHandler("ImmediateInputStreamHandler");
|
||||
// Process() function is invoked in response to MAIN/LOOP stream timestamp
|
||||
// bound updates.
|
||||
cc->SetProcessTimestampBounds(true);
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status Open(CalculatorContext* cc) final {
|
||||
main_id_ = cc->Inputs().GetId("MAIN", 0);
|
||||
loop_id_ = cc->Inputs().GetId("LOOP", 0);
|
||||
loop_out_id_ = cc->Outputs().GetId("PREV_LOOP", 0);
|
||||
prev_loop_id_ = cc->Outputs().GetId("PREV_LOOP", 0);
|
||||
cc->Outputs()
|
||||
.Get(loop_out_id_)
|
||||
.Get(prev_loop_id_)
|
||||
.SetHeader(cc->Inputs().Get(loop_id_).Header());
|
||||
|
||||
// Use an empty packet for the first round, since there is no previous
|
||||
// output.
|
||||
loopback_packets_.push_back({});
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status Process(CalculatorContext* cc) final {
|
||||
Packet& main_packet = cc->Inputs().Get(main_id_).Value();
|
||||
if (!main_packet.IsEmpty()) {
|
||||
main_ts_.push_back(main_packet.Timestamp());
|
||||
}
|
||||
Packet& loopback_packet = cc->Inputs().Get(loop_id_).Value();
|
||||
if (!loopback_packet.IsEmpty()) {
|
||||
loopback_packets_.push_back(loopback_packet);
|
||||
while (!main_ts_.empty() &&
|
||||
main_ts_.front() <= loopback_packets_.front().Timestamp()) {
|
||||
main_ts_.pop_front();
|
||||
}
|
||||
}
|
||||
auto& loop_out = cc->Outputs().Get(loop_out_id_);
|
||||
// Non-empty packets and empty packets indicating timestamp bound updates
|
||||
// are guaranteed to have timestamps greater than timestamps of previous
|
||||
// packets within the same stream. Calculator tracks and operates on such
|
||||
// packets.
|
||||
|
||||
while (!main_ts_.empty() && !loopback_packets_.empty()) {
|
||||
Timestamp main_timestamp = main_ts_.front();
|
||||
main_ts_.pop_front();
|
||||
Packet previous_loopback = loopback_packets_.front().At(main_timestamp);
|
||||
loopback_packets_.pop_front();
|
||||
|
||||
if (previous_loopback.IsEmpty()) {
|
||||
// TODO: SetCompleteTimestampBound would be more useful.
|
||||
loop_out.SetNextTimestampBound(main_timestamp + 1);
|
||||
const Packet& main_packet = cc->Inputs().Get(main_id_).Value();
|
||||
if (prev_main_ts_ < main_packet.Timestamp()) {
|
||||
Timestamp loop_timestamp;
|
||||
if (!main_packet.IsEmpty()) {
|
||||
loop_timestamp = prev_non_empty_main_ts_;
|
||||
prev_non_empty_main_ts_ = main_packet.Timestamp();
|
||||
} else {
|
||||
loop_out.AddPacket(std::move(previous_loopback));
|
||||
// Calculator advances PREV_LOOP timestamp bound in response to empty
|
||||
// MAIN packet, hence not caring about corresponding loop packet.
|
||||
loop_timestamp = Timestamp::Unset();
|
||||
}
|
||||
main_packet_specs_.push_back({.timestamp = main_packet.Timestamp(),
|
||||
.loop_timestamp = loop_timestamp});
|
||||
prev_main_ts_ = main_packet.Timestamp();
|
||||
}
|
||||
|
||||
const Packet& loop_packet = cc->Inputs().Get(loop_id_).Value();
|
||||
if (prev_loop_ts_ < loop_packet.Timestamp()) {
|
||||
loop_packets_.push_back(loop_packet);
|
||||
prev_loop_ts_ = loop_packet.Timestamp();
|
||||
}
|
||||
|
||||
auto& prev_loop = cc->Outputs().Get(prev_loop_id_);
|
||||
while (!main_packet_specs_.empty() && !loop_packets_.empty()) {
|
||||
// The earliest MAIN packet.
|
||||
const MainPacketSpec& main_spec = main_packet_specs_.front();
|
||||
// The earliest LOOP packet.
|
||||
const Packet& loop_candidate = loop_packets_.front();
|
||||
// Match LOOP and MAIN packets.
|
||||
if (main_spec.loop_timestamp < loop_candidate.Timestamp()) {
|
||||
// No LOOP packet can match the MAIN packet under review.
|
||||
prev_loop.SetNextTimestampBound(main_spec.timestamp + 1);
|
||||
main_packet_specs_.pop_front();
|
||||
} else if (main_spec.loop_timestamp > loop_candidate.Timestamp()) {
|
||||
// No MAIN packet can match the LOOP packet under review.
|
||||
loop_packets_.pop_front();
|
||||
} else {
|
||||
// Exact match found.
|
||||
if (loop_candidate.IsEmpty()) {
|
||||
// However, LOOP packet is empty.
|
||||
prev_loop.SetNextTimestampBound(main_spec.timestamp + 1);
|
||||
} else {
|
||||
prev_loop.AddPacket(loop_candidate.At(main_spec.timestamp));
|
||||
}
|
||||
loop_packets_.pop_front();
|
||||
main_packet_specs_.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
// In case of an empty loopback input, the next timestamp bound for
|
||||
// loopback input is the loopback timestamp + 1. The next timestamp bound
|
||||
// for output is set and the main_ts_ vector is truncated accordingly.
|
||||
if (loopback_packet.IsEmpty() &&
|
||||
loopback_packet.Timestamp() != Timestamp::Unstarted()) {
|
||||
Timestamp loopback_bound =
|
||||
loopback_packet.Timestamp().NextAllowedInStream();
|
||||
while (!main_ts_.empty() && main_ts_.front() <= loopback_bound) {
|
||||
main_ts_.pop_front();
|
||||
}
|
||||
if (main_ts_.empty()) {
|
||||
loop_out.SetNextTimestampBound(loopback_bound.NextAllowedInStream());
|
||||
}
|
||||
}
|
||||
if (!main_ts_.empty()) {
|
||||
loop_out.SetNextTimestampBound(main_ts_.front());
|
||||
}
|
||||
if (cc->Inputs().Get(main_id_).IsDone() && main_ts_.empty()) {
|
||||
loop_out.Close();
|
||||
if (main_packet_specs_.empty() && cc->Inputs().Get(main_id_).IsDone()) {
|
||||
prev_loop.Close();
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
private:
|
||||
struct MainPacketSpec {
|
||||
Timestamp timestamp;
|
||||
// Expected timestamp of the packet from LOOP stream that corresponds to the
|
||||
// packet from MAIN stream descirbed by this spec.
|
||||
Timestamp loop_timestamp;
|
||||
};
|
||||
|
||||
CollectionItemId main_id_;
|
||||
CollectionItemId loop_id_;
|
||||
CollectionItemId loop_out_id_;
|
||||
CollectionItemId prev_loop_id_;
|
||||
|
||||
std::deque<Timestamp> main_ts_;
|
||||
std::deque<Packet> loopback_packets_;
|
||||
// Contains specs for MAIN packets which only can be:
|
||||
// - non-empty packets
|
||||
// - empty packets indicating timestamp bound updates
|
||||
//
|
||||
// Sorted according to packet timestamps.
|
||||
std::deque<MainPacketSpec> main_packet_specs_;
|
||||
Timestamp prev_main_ts_ = Timestamp::Unstarted();
|
||||
Timestamp prev_non_empty_main_ts_ = Timestamp::Unstarted();
|
||||
|
||||
// Contains LOOP packets which only can be:
|
||||
// - the very first empty packet
|
||||
// - non empty packets
|
||||
// - empty packets indicating timestamp bound updates
|
||||
//
|
||||
// Sorted according to packet timestamps.
|
||||
std::deque<Packet> loop_packets_;
|
||||
// Using "Timestamp::Unset" instead of "Timestamp::Unstarted" in order to
|
||||
// allow addition of the very first empty packet (which doesn't indicate
|
||||
// timestamp bound change necessarily).
|
||||
Timestamp prev_loop_ts_ = Timestamp::Unset();
|
||||
};
|
||||
REGISTER_CALCULATOR(PreviousLoopbackCalculator);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -25,12 +26,17 @@
|
||||
#include "mediapipe/framework/port/gtest.h"
|
||||
#include "mediapipe/framework/port/integral_types.h"
|
||||
#include "mediapipe/framework/port/parse_text_proto.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/framework/port/status_matchers.h"
|
||||
#include "mediapipe/framework/timestamp.h"
|
||||
#include "mediapipe/framework/tool/sink.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::Eq;
|
||||
using ::testing::Pair;
|
||||
using ::testing::Value;
|
||||
namespace {
|
||||
|
||||
// Returns the timestamp values for a vector of Packets.
|
||||
@@ -43,6 +49,23 @@ std::vector<int64> TimestampValues(const std::vector<Packet>& packets) {
|
||||
return result;
|
||||
}
|
||||
|
||||
MATCHER(EmptyPacket, negation ? "isn't empty" : "is empty") {
|
||||
if (arg.IsEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
MATCHER_P(IntPacket, value, "") {
|
||||
return Value(arg.template Get<int>(), Eq(value));
|
||||
}
|
||||
|
||||
MATCHER_P2(PairPacket, timestamp, pair, "") {
|
||||
Timestamp actual_timestamp = arg.Timestamp();
|
||||
const auto& actual_pair = arg.template Get<std::pair<Packet, Packet>>();
|
||||
return Value(actual_timestamp, Eq(timestamp)) && Value(actual_pair, pair);
|
||||
}
|
||||
|
||||
TEST(PreviousLoopbackCalculator, CorrectTimestamps) {
|
||||
std::vector<Packet> in_prev;
|
||||
CalculatorGraphConfig graph_config_ =
|
||||
@@ -81,32 +104,30 @@ TEST(PreviousLoopbackCalculator, CorrectTimestamps) {
|
||||
MP_EXPECT_OK(graph_.AddPacketToInputStream(
|
||||
input_name, MakePacket<int>(n).At(Timestamp(n))));
|
||||
};
|
||||
auto pair_values = [](const Packet& packet) {
|
||||
auto pair = packet.Get<std::pair<Packet, Packet>>();
|
||||
int first = pair.first.IsEmpty() ? -1 : pair.first.Get<int>();
|
||||
int second = pair.second.IsEmpty() ? -1 : pair.second.Get<int>();
|
||||
return std::make_pair(first, second);
|
||||
};
|
||||
|
||||
send_packet("in", 1);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_EQ(TimestampValues(in_prev), (std::vector<int64>{1}));
|
||||
EXPECT_EQ(pair_values(in_prev.back()), std::make_pair(1, -1));
|
||||
EXPECT_THAT(TimestampValues(in_prev), ElementsAre(1));
|
||||
EXPECT_THAT(in_prev.back(),
|
||||
PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())));
|
||||
|
||||
send_packet("in", 2);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_EQ(TimestampValues(in_prev), (std::vector<int64>{1, 2}));
|
||||
EXPECT_EQ(pair_values(in_prev.back()), std::make_pair(2, 1));
|
||||
EXPECT_THAT(TimestampValues(in_prev), ElementsAre(1, 2));
|
||||
EXPECT_THAT(in_prev.back(),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), IntPacket(1))));
|
||||
|
||||
send_packet("in", 5);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_EQ(TimestampValues(in_prev), (std::vector<int64>{1, 2, 5}));
|
||||
EXPECT_EQ(pair_values(in_prev.back()), std::make_pair(5, 2));
|
||||
EXPECT_THAT(TimestampValues(in_prev), ElementsAre(1, 2, 5));
|
||||
EXPECT_THAT(in_prev.back(),
|
||||
PairPacket(Timestamp(5), Pair(IntPacket(5), IntPacket(2))));
|
||||
|
||||
send_packet("in", 15);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_EQ(TimestampValues(in_prev), (std::vector<int64>{1, 2, 5, 15}));
|
||||
EXPECT_EQ(pair_values(in_prev.back()), std::make_pair(15, 5));
|
||||
EXPECT_THAT(TimestampValues(in_prev), ElementsAre(1, 2, 5, 15));
|
||||
EXPECT_THAT(in_prev.back(),
|
||||
PairPacket(Timestamp(15), Pair(IntPacket(15), IntPacket(5))));
|
||||
|
||||
MP_EXPECT_OK(graph_.CloseAllInputStreams());
|
||||
MP_EXPECT_OK(graph_.WaitUntilDone());
|
||||
@@ -185,24 +206,24 @@ TEST(PreviousLoopbackCalculator, ClosesCorrectly) {
|
||||
|
||||
send_packet("in", 1);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_EQ(TimestampValues(outputs), (std::vector<int64>{1}));
|
||||
EXPECT_THAT(TimestampValues(outputs), ElementsAre(1));
|
||||
|
||||
send_packet("in", 2);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_EQ(TimestampValues(outputs), (std::vector<int64>{1, 2}));
|
||||
EXPECT_THAT(TimestampValues(outputs), ElementsAre(1, 2));
|
||||
|
||||
send_packet("in", 5);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_EQ(TimestampValues(outputs), (std::vector<int64>{1, 2, 5}));
|
||||
EXPECT_THAT(TimestampValues(outputs), ElementsAre(1, 2, 5));
|
||||
|
||||
send_packet("in", 15);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_EQ(TimestampValues(outputs), (std::vector<int64>{1, 2, 5, 15}));
|
||||
EXPECT_THAT(TimestampValues(outputs), ElementsAre(1, 2, 5, 15));
|
||||
|
||||
MP_EXPECT_OK(graph_.CloseAllInputStreams());
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_EQ(TimestampValues(outputs),
|
||||
(std::vector<int64>{1, 2, 5, 15, Timestamp::Max().Value()}));
|
||||
EXPECT_THAT(TimestampValues(outputs),
|
||||
ElementsAre(1, 2, 5, 15, Timestamp::Max().Value()));
|
||||
|
||||
MP_EXPECT_OK(graph_.WaitUntilDone());
|
||||
}
|
||||
@@ -247,16 +268,12 @@ TEST(PreviousLoopbackCalculator, EmptyLoopForever) {
|
||||
input_name, MakePacket<int>(n).At(Timestamp(n))));
|
||||
};
|
||||
|
||||
send_packet("in", 0);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_EQ(TimestampValues(outputs), (std::vector<int64>{0}));
|
||||
|
||||
for (int main_ts = 1; main_ts < 50; ++main_ts) {
|
||||
for (int main_ts = 0; main_ts < 50; ++main_ts) {
|
||||
send_packet("in", main_ts);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
std::vector<int64> ts_values = TimestampValues(outputs);
|
||||
EXPECT_EQ(ts_values.size(), main_ts + 1);
|
||||
for (int j = 0; j < main_ts; ++j) {
|
||||
for (int j = 0; j < main_ts + 1; ++j) {
|
||||
EXPECT_EQ(ts_values[j], j);
|
||||
}
|
||||
}
|
||||
@@ -266,5 +283,487 @@ TEST(PreviousLoopbackCalculator, EmptyLoopForever) {
|
||||
MP_EXPECT_OK(graph_.WaitUntilDone());
|
||||
}
|
||||
|
||||
class PreviousLoopbackCalculatorProcessingTimestampsTest
|
||||
: public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
CalculatorGraphConfig graph_config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig>(R"(
|
||||
input_stream: 'input'
|
||||
input_stream: 'force_main_empty'
|
||||
input_stream: 'force_loop_empty'
|
||||
# Used to indicate "main" timestamp bound updates.
|
||||
node {
|
||||
calculator: 'GateCalculator'
|
||||
input_stream: 'input'
|
||||
input_stream: 'DISALLOW:force_main_empty'
|
||||
output_stream: 'main'
|
||||
}
|
||||
node {
|
||||
calculator: 'PreviousLoopbackCalculator'
|
||||
input_stream: 'MAIN:main'
|
||||
input_stream: 'LOOP:loop'
|
||||
input_stream_info: { tag_index: 'LOOP' back_edge: true }
|
||||
output_stream: 'PREV_LOOP:prev_loop'
|
||||
}
|
||||
node {
|
||||
calculator: 'PassThroughCalculator'
|
||||
input_stream: 'input'
|
||||
input_stream: 'prev_loop'
|
||||
output_stream: 'passed_through_input'
|
||||
output_stream: 'passed_through_prev_loop'
|
||||
}
|
||||
# Used to indicate "loop" timestamp bound updates.
|
||||
node {
|
||||
calculator: 'GateCalculator'
|
||||
input_stream: 'input'
|
||||
input_stream: 'DISALLOW:force_loop_empty'
|
||||
output_stream: 'loop'
|
||||
}
|
||||
node {
|
||||
calculator: 'MakePairCalculator'
|
||||
input_stream: 'passed_through_input'
|
||||
input_stream: 'passed_through_prev_loop'
|
||||
output_stream: 'passed_through_input_and_prev_loop'
|
||||
}
|
||||
)");
|
||||
tool::AddVectorSink("passed_through_input_and_prev_loop", &graph_config,
|
||||
&output_packets_);
|
||||
MP_ASSERT_OK(graph_.Initialize(graph_config, {}));
|
||||
MP_ASSERT_OK(graph_.StartRun({}));
|
||||
}
|
||||
|
||||
void SendPackets(int timestamp, int input, bool force_main_empty,
|
||||
bool force_loop_empty) {
|
||||
MP_ASSERT_OK(graph_.AddPacketToInputStream(
|
||||
"input", MakePacket<int>(input).At(Timestamp(timestamp))));
|
||||
MP_ASSERT_OK(graph_.AddPacketToInputStream(
|
||||
"force_main_empty",
|
||||
MakePacket<bool>(force_main_empty).At(Timestamp(timestamp))));
|
||||
MP_ASSERT_OK(graph_.AddPacketToInputStream(
|
||||
"force_loop_empty",
|
||||
MakePacket<bool>(force_loop_empty).At(Timestamp(timestamp))));
|
||||
}
|
||||
|
||||
CalculatorGraph graph_;
|
||||
std::vector<Packet> output_packets_;
|
||||
};
|
||||
|
||||
TEST_F(PreviousLoopbackCalculatorProcessingTimestampsTest,
|
||||
MultiplePacketsEmptyMainNonEmptyLoop) {
|
||||
SendPackets(/*timestamp=*/1, /*input=*/1, /*force_main_empty=*/true,
|
||||
/*force_loop_empty=*/false);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket()))));
|
||||
|
||||
SendPackets(/*timestamp=*/2, /*input=*/2, /*force_main_empty=*/true,
|
||||
/*force_loop_empty=*/false);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket()))));
|
||||
|
||||
SendPackets(/*timestamp=*/3, /*input=*/3, /*force_main_empty=*/true,
|
||||
/*force_loop_empty=*/false);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
|
||||
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket()))));
|
||||
|
||||
SendPackets(/*timestamp=*/5, /*input=*/5, /*force_main_empty=*/true,
|
||||
/*force_loop_empty=*/false);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
|
||||
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
|
||||
PairPacket(Timestamp(5), Pair(IntPacket(5), EmptyPacket()))));
|
||||
|
||||
SendPackets(/*timestamp=*/15, /*input=*/15,
|
||||
/*force_main_empty=*/true,
|
||||
/*force_loop_empty=*/false);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(
|
||||
PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
|
||||
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
|
||||
PairPacket(Timestamp(5), Pair(IntPacket(5), EmptyPacket())),
|
||||
PairPacket(Timestamp(15), Pair(IntPacket(15), EmptyPacket()))));
|
||||
|
||||
MP_EXPECT_OK(graph_.CloseAllInputStreams());
|
||||
MP_EXPECT_OK(graph_.WaitUntilDone());
|
||||
}
|
||||
|
||||
TEST_F(PreviousLoopbackCalculatorProcessingTimestampsTest,
|
||||
MultiplePacketsNonEmptyMainEmptyLoop) {
|
||||
SendPackets(/*timestamp=*/1, /*input=*/1,
|
||||
/*force_main_empty=*/false,
|
||||
/*force_loop_empty=*/true);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket()))));
|
||||
|
||||
SendPackets(/*timestamp=*/2, /*input=*/2,
|
||||
/*force_main_empty=*/false,
|
||||
/*force_loop_empty=*/true);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket()))));
|
||||
|
||||
SendPackets(/*timestamp=*/3, /*input=*/3,
|
||||
/*force_main_empty=*/false,
|
||||
/*force_loop_empty=*/true);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
|
||||
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket()))));
|
||||
|
||||
SendPackets(/*timestamp=*/5, /*input=*/5,
|
||||
/*force_main_empty=*/false,
|
||||
/*force_loop_empty=*/true);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
|
||||
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
|
||||
PairPacket(Timestamp(5), Pair(IntPacket(5), EmptyPacket()))));
|
||||
|
||||
SendPackets(/*timestamp=*/15, /*input=*/15,
|
||||
/*force_main_empty=*/false,
|
||||
/*force_loop_empty=*/true);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(
|
||||
PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
|
||||
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
|
||||
PairPacket(Timestamp(5), Pair(IntPacket(5), EmptyPacket())),
|
||||
PairPacket(Timestamp(15), Pair(IntPacket(15), EmptyPacket()))));
|
||||
|
||||
MP_EXPECT_OK(graph_.CloseAllInputStreams());
|
||||
MP_EXPECT_OK(graph_.WaitUntilDone());
|
||||
}
|
||||
|
||||
TEST_F(PreviousLoopbackCalculatorProcessingTimestampsTest,
|
||||
MultiplePacketsAlteringMainNonEmptyLoop) {
|
||||
SendPackets(/*timestamp=*/1, /*input=*/1,
|
||||
/*force_main_empty=*/false,
|
||||
/*force_loop_empty=*/false);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket()))));
|
||||
|
||||
SendPackets(/*timestamp=*/2, /*input=*/2, /*force_main_empty=*/true,
|
||||
/*force_loop_empty=*/false);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket()))));
|
||||
|
||||
SendPackets(/*timestamp=*/3, /*input=*/3,
|
||||
/*force_main_empty=*/false,
|
||||
/*force_loop_empty=*/false);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
|
||||
PairPacket(Timestamp(3), Pair(IntPacket(3), IntPacket(1)))));
|
||||
|
||||
SendPackets(/*timestamp=*/5, /*input=*/5,
|
||||
/*force_main_empty=*/false,
|
||||
/*force_loop_empty=*/false);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
|
||||
PairPacket(Timestamp(3), Pair(IntPacket(3), IntPacket(1))),
|
||||
PairPacket(Timestamp(5), Pair(IntPacket(5), IntPacket(3)))));
|
||||
|
||||
SendPackets(/*timestamp=*/15, /*input=*/15,
|
||||
/*force_main_empty=*/true,
|
||||
/*force_loop_empty=*/false);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(
|
||||
PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
|
||||
PairPacket(Timestamp(3), Pair(IntPacket(3), IntPacket(1))),
|
||||
PairPacket(Timestamp(5), Pair(IntPacket(5), IntPacket(3))),
|
||||
PairPacket(Timestamp(15), Pair(IntPacket(15), EmptyPacket()))));
|
||||
|
||||
MP_EXPECT_OK(graph_.CloseAllInputStreams());
|
||||
MP_EXPECT_OK(graph_.WaitUntilDone());
|
||||
}
|
||||
|
||||
TEST_F(PreviousLoopbackCalculatorProcessingTimestampsTest,
|
||||
MultiplePacketsNonEmptyMainAlteringLoop) {
|
||||
SendPackets(/*timestamp=*/1, /*input=*/1,
|
||||
/*force_main_empty=*/false,
|
||||
/*force_loop_empty=*/false);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket()))));
|
||||
|
||||
SendPackets(/*timestamp=*/2, /*input=*/2,
|
||||
/*force_main_empty=*/false,
|
||||
/*force_loop_empty=*/true);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), IntPacket(1)))));
|
||||
|
||||
SendPackets(/*timestamp=*/3, /*input=*/3,
|
||||
/*force_main_empty=*/false,
|
||||
/*force_loop_empty=*/false);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), IntPacket(1))),
|
||||
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket()))));
|
||||
|
||||
SendPackets(/*timestamp=*/5, /*input=*/5,
|
||||
/*force_main_empty=*/false,
|
||||
/*force_loop_empty=*/true);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), IntPacket(1))),
|
||||
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
|
||||
PairPacket(Timestamp(5), Pair(IntPacket(5), IntPacket(3)))));
|
||||
|
||||
SendPackets(/*timestamp=*/15, /*input=*/15,
|
||||
/*force_main_empty=*/false,
|
||||
/*force_loop_empty=*/false);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(
|
||||
PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), IntPacket(1))),
|
||||
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
|
||||
PairPacket(Timestamp(5), Pair(IntPacket(5), IntPacket(3))),
|
||||
PairPacket(Timestamp(15), Pair(IntPacket(15), EmptyPacket()))));
|
||||
|
||||
MP_EXPECT_OK(graph_.CloseAllInputStreams());
|
||||
MP_EXPECT_OK(graph_.WaitUntilDone());
|
||||
}
|
||||
|
||||
TEST_F(PreviousLoopbackCalculatorProcessingTimestampsTest,
|
||||
MultiplePacketsCheckIfLastCorrectAlteringMainAlteringLoop) {
|
||||
int num_packets = 1000;
|
||||
for (int i = 0; i < num_packets; ++i) {
|
||||
bool force_main_empty = i % 3 == 0 ? true : false;
|
||||
bool force_loop_empty = i % 2 == 0 ? true : false;
|
||||
SendPackets(/*timestamp=*/i + 1, /*input=*/i + 1, force_main_empty,
|
||||
force_loop_empty);
|
||||
}
|
||||
SendPackets(/*timestamp=*/num_packets + 1,
|
||||
/*input=*/num_packets + 1, /*force_main_empty=*/false,
|
||||
/*force_loop_empty=*/false);
|
||||
SendPackets(/*timestamp=*/num_packets + 2,
|
||||
/*input=*/num_packets + 2, /*force_main_empty=*/false,
|
||||
/*force_loop_empty=*/false);
|
||||
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
ASSERT_FALSE(output_packets_.empty());
|
||||
EXPECT_THAT(
|
||||
output_packets_.back(),
|
||||
PairPacket(Timestamp(num_packets + 2),
|
||||
Pair(IntPacket(num_packets + 2), IntPacket(num_packets + 1))));
|
||||
|
||||
MP_EXPECT_OK(graph_.CloseAllInputStreams());
|
||||
MP_EXPECT_OK(graph_.WaitUntilDone());
|
||||
}
|
||||
|
||||
// Similar to GateCalculator, but it doesn't propagate timestamp bound updates.
|
||||
class DroppingGateCalculator : public CalculatorBase {
|
||||
public:
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
|
||||
cc->Inputs().Index(0).SetAny();
|
||||
cc->Inputs().Tag("DISALLOW").Set<bool>();
|
||||
cc->Outputs().Index(0).SetSameAs(&cc->Inputs().Index(0));
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status Process(CalculatorContext* cc) final {
|
||||
if (!cc->Inputs().Index(0).IsEmpty() &&
|
||||
!cc->Inputs().Tag("DISALLOW").Get<bool>()) {
|
||||
cc->Outputs().Index(0).AddPacket(cc->Inputs().Index(0).Value());
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
};
|
||||
REGISTER_CALCULATOR(DroppingGateCalculator);
|
||||
|
||||
// Tests PreviousLoopbackCalculator in cases when there are no "LOOP" timestamp
|
||||
// bound updates and non-empty packets for a while and the aforementioned start
|
||||
// to arrive at some point. So, "PREV_LOOP" is delayed for a couple of inputs.
|
||||
class PreviousLoopbackCalculatorDelayBehaviorTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
CalculatorGraphConfig graph_config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig>(R"(
|
||||
input_stream: 'input'
|
||||
# Drops "loop" when set to "true", delaying output of prev_loop, hence
|
||||
# delaying output of the graph.
|
||||
input_stream: 'delay_next_output'
|
||||
node {
|
||||
calculator: 'PreviousLoopbackCalculator'
|
||||
input_stream: 'MAIN:input'
|
||||
input_stream: 'LOOP:loop'
|
||||
input_stream_info: { tag_index: 'LOOP' back_edge: true }
|
||||
output_stream: 'PREV_LOOP:prev_loop'
|
||||
}
|
||||
node {
|
||||
calculator: 'PassThroughCalculator'
|
||||
input_stream: 'input'
|
||||
input_stream: 'prev_loop'
|
||||
output_stream: 'passed_through_input'
|
||||
output_stream: 'passed_through_prev_loop'
|
||||
}
|
||||
node {
|
||||
calculator: 'DroppingGateCalculator'
|
||||
input_stream: 'input'
|
||||
input_stream: 'DISALLOW:delay_next_output'
|
||||
output_stream: 'loop'
|
||||
}
|
||||
node {
|
||||
calculator: 'MakePairCalculator'
|
||||
input_stream: 'passed_through_input'
|
||||
input_stream: 'passed_through_prev_loop'
|
||||
output_stream: 'passed_through_input_and_prev_loop'
|
||||
}
|
||||
)");
|
||||
tool::AddVectorSink("passed_through_input_and_prev_loop", &graph_config,
|
||||
&output_packets_);
|
||||
MP_ASSERT_OK(graph_.Initialize(graph_config, {}));
|
||||
MP_ASSERT_OK(graph_.StartRun({}));
|
||||
}
|
||||
|
||||
void SendPackets(int timestamp, int input, bool delay_next_output) {
|
||||
MP_ASSERT_OK(graph_.AddPacketToInputStream(
|
||||
"input", MakePacket<int>(input).At(Timestamp(timestamp))));
|
||||
MP_ASSERT_OK(graph_.AddPacketToInputStream(
|
||||
"delay_next_output",
|
||||
MakePacket<bool>(delay_next_output).At(Timestamp(timestamp))));
|
||||
}
|
||||
|
||||
CalculatorGraph graph_;
|
||||
std::vector<Packet> output_packets_;
|
||||
};
|
||||
|
||||
TEST_F(PreviousLoopbackCalculatorDelayBehaviorTest, MultipleDelayedOutputs) {
|
||||
SendPackets(/*timestamp=*/1, /*input=*/1, /*delay_next_output=*/true);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket()))));
|
||||
|
||||
SendPackets(/*timestamp=*/2, /*input=*/2, /*delay_next_output=*/true);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket()))));
|
||||
|
||||
SendPackets(/*timestamp=*/3, /*input=*/3, /*delay_next_output=*/true);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket()))));
|
||||
|
||||
SendPackets(/*timestamp=*/5, /*input=*/5, /*delay_next_output=*/false);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
|
||||
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
|
||||
PairPacket(Timestamp(5), Pair(IntPacket(5), EmptyPacket()))));
|
||||
|
||||
SendPackets(/*timestamp=*/15, /*input=*/15, /*delay_next_output=*/false);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(
|
||||
PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
|
||||
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
|
||||
PairPacket(Timestamp(5), Pair(IntPacket(5), EmptyPacket())),
|
||||
PairPacket(Timestamp(15), Pair(IntPacket(15), IntPacket(5)))));
|
||||
|
||||
MP_EXPECT_OK(graph_.CloseAllInputStreams());
|
||||
MP_EXPECT_OK(graph_.WaitUntilDone());
|
||||
}
|
||||
|
||||
TEST_F(PreviousLoopbackCalculatorDelayBehaviorTest,
|
||||
NonDelayedOutputFollowedByMultipleDelayedOutputs) {
|
||||
SendPackets(/*timestamp=*/1, /*input=*/1, /*delay_next_output=*/false);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket()))));
|
||||
|
||||
SendPackets(/*timestamp=*/2, /*input=*/2, /*delay_next_output=*/true);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), IntPacket(1)))));
|
||||
|
||||
SendPackets(/*timestamp=*/3, /*input=*/3, /*delay_next_output=*/true);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), IntPacket(1)))));
|
||||
|
||||
SendPackets(/*timestamp=*/5, /*input=*/5, /*delay_next_output=*/false);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), IntPacket(1))),
|
||||
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
|
||||
PairPacket(Timestamp(5), Pair(IntPacket(5), EmptyPacket()))));
|
||||
|
||||
SendPackets(/*timestamp=*/15, /*input=*/15, /*delay_next_output=*/false);
|
||||
MP_EXPECT_OK(graph_.WaitUntilIdle());
|
||||
EXPECT_THAT(
|
||||
output_packets_,
|
||||
ElementsAre(
|
||||
PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
|
||||
PairPacket(Timestamp(2), Pair(IntPacket(2), IntPacket(1))),
|
||||
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
|
||||
PairPacket(Timestamp(5), Pair(IntPacket(5), EmptyPacket())),
|
||||
PairPacket(Timestamp(15), Pair(IntPacket(15), IntPacket(5)))));
|
||||
|
||||
MP_EXPECT_OK(graph_.CloseAllInputStreams());
|
||||
MP_EXPECT_OK(graph_.WaitUntilDone());
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
} // namespace mediapipe
|
||||
|
||||
@@ -28,55 +28,133 @@ using mediapipe::PacketTypeSet;
|
||||
using mediapipe::Timestamp;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kTagAtPreStream[] = "AT_PRESTREAM";
|
||||
constexpr char kTagAtPostStream[] = "AT_POSTSTREAM";
|
||||
constexpr char kTagAtZero[] = "AT_ZERO";
|
||||
constexpr char kTagAtTick[] = "AT_TICK";
|
||||
constexpr char kTagTick[] = "TICK";
|
||||
|
||||
static std::map<std::string, Timestamp>* kTimestampMap = []() {
|
||||
auto* res = new std::map<std::string, Timestamp>();
|
||||
res->emplace("AT_PRESTREAM", Timestamp::PreStream());
|
||||
res->emplace("AT_POSTSTREAM", Timestamp::PostStream());
|
||||
res->emplace("AT_ZERO", Timestamp(0));
|
||||
res->emplace(kTagAtPreStream, Timestamp::PreStream());
|
||||
res->emplace(kTagAtPostStream, Timestamp::PostStream());
|
||||
res->emplace(kTagAtZero, Timestamp(0));
|
||||
res->emplace(kTagAtTick, Timestamp::Unset());
|
||||
return res;
|
||||
}();
|
||||
|
||||
template <typename CC>
|
||||
std::string GetOutputTag(const CC& cc) {
|
||||
// Single output tag only is required by contract.
|
||||
return *cc.Outputs().GetTags().begin();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Outputs the single input_side_packet at the timestamp specified in the
|
||||
// output_stream tag. Valid tags are AT_PRESTREAM, AT_POSTSTREAM and AT_ZERO.
|
||||
// Outputs side packet(s) in corresponding output stream(s) with a particular
|
||||
// timestamp, depending on the tag used to define output stream(s). (One tag can
|
||||
// be used only.)
|
||||
//
|
||||
// Valid tags are AT_PRESTREAM, AT_POSTSTREAM, AT_ZERO and AT_TICK and
|
||||
// corresponding timestamps are Timestamp::PreStream(), Timestamp::PostStream(),
|
||||
// Timestamp(0) and timestamp of a packet received in TICK input.
|
||||
//
|
||||
// Examples:
|
||||
// node {
|
||||
// calculator: "SidePacketToStreamCalculator"
|
||||
// input_side_packet: "side_packet"
|
||||
// output_stream: "AT_PRESTREAM:packet"
|
||||
// }
|
||||
//
|
||||
// node {
|
||||
// calculator: "SidePacketToStreamCalculator"
|
||||
// input_stream: "TICK:tick"
|
||||
// input_side_packet: "side_packet"
|
||||
// output_stream: "AT_TICK:packet"
|
||||
// }
|
||||
class SidePacketToStreamCalculator : public CalculatorBase {
|
||||
public:
|
||||
SidePacketToStreamCalculator() = default;
|
||||
~SidePacketToStreamCalculator() override = default;
|
||||
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc);
|
||||
::mediapipe::Status Open(CalculatorContext* cc) override;
|
||||
::mediapipe::Status Process(CalculatorContext* cc) override;
|
||||
::mediapipe::Status Close(CalculatorContext* cc) override;
|
||||
|
||||
private:
|
||||
bool is_tick_processing_ = false;
|
||||
std::string output_tag_;
|
||||
};
|
||||
REGISTER_CALCULATOR(SidePacketToStreamCalculator);
|
||||
|
||||
::mediapipe::Status SidePacketToStreamCalculator::GetContract(
|
||||
CalculatorContract* cc) {
|
||||
cc->InputSidePackets().Index(0).SetAny();
|
||||
const auto& tags = cc->Outputs().GetTags();
|
||||
RET_CHECK(tags.size() == 1 && kTimestampMap->count(*tags.begin()) == 1)
|
||||
<< "Only one of AT_PRESTREAM, AT_POSTSTREAM, AT_ZERO and AT_TICK tags is "
|
||||
"allowed and required to specify output stream(s).";
|
||||
RET_CHECK(
|
||||
(cc->Outputs().HasTag(kTagAtTick) && cc->Inputs().HasTag(kTagTick)) ||
|
||||
(!cc->Outputs().HasTag(kTagAtTick) && !cc->Inputs().HasTag(kTagTick)))
|
||||
<< "Either both of TICK and AT_TICK should be used or none of them.";
|
||||
const std::string output_tag = GetOutputTag(*cc);
|
||||
const int num_entries = cc->Outputs().NumEntries(output_tag);
|
||||
RET_CHECK_EQ(num_entries, cc->InputSidePackets().NumEntries())
|
||||
<< "Same number of input side packets and output streams is required.";
|
||||
for (int i = 0; i < num_entries; ++i) {
|
||||
cc->InputSidePackets().Index(i).SetAny();
|
||||
cc->Outputs()
|
||||
.Get(output_tag, i)
|
||||
.SetSameAs(cc->InputSidePackets().Index(i).GetSameAs());
|
||||
}
|
||||
|
||||
std::set<std::string> tags = cc->Outputs().GetTags();
|
||||
RET_CHECK_EQ(tags.size(), 1);
|
||||
if (cc->Inputs().HasTag(kTagTick)) {
|
||||
cc->Inputs().Tag(kTagTick).SetAny();
|
||||
}
|
||||
|
||||
RET_CHECK_EQ(kTimestampMap->count(*tags.begin()), 1);
|
||||
cc->Outputs().Tag(*tags.begin()).SetAny();
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status SidePacketToStreamCalculator::Open(CalculatorContext* cc) {
|
||||
output_tag_ = GetOutputTag(*cc);
|
||||
if (cc->Inputs().HasTag(kTagTick)) {
|
||||
is_tick_processing_ = true;
|
||||
// Set offset, so output timestamp bounds are updated in response to TICK
|
||||
// timestamp bound update.
|
||||
cc->SetOffset(TimestampDiff(0));
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status SidePacketToStreamCalculator::Process(
|
||||
CalculatorContext* cc) {
|
||||
return mediapipe::tool::StatusStop();
|
||||
if (is_tick_processing_) {
|
||||
// TICK input is guaranteed to be non-empty, as it's the only input stream
|
||||
// for this calculator.
|
||||
const auto& timestamp = cc->Inputs().Tag(kTagTick).Value().Timestamp();
|
||||
for (int i = 0; i < cc->Outputs().NumEntries(output_tag_); ++i) {
|
||||
cc->Outputs()
|
||||
.Get(output_tag_, i)
|
||||
.AddPacket(cc->InputSidePackets().Index(i).At(timestamp));
|
||||
}
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
return ::mediapipe::tool::StatusStop();
|
||||
}
|
||||
|
||||
::mediapipe::Status SidePacketToStreamCalculator::Close(CalculatorContext* cc) {
|
||||
std::set<std::string> tags = cc->Outputs().GetTags();
|
||||
RET_CHECK_EQ(tags.size(), 1);
|
||||
const std::string& tag = *tags.begin();
|
||||
RET_CHECK_EQ(kTimestampMap->count(tag), 1);
|
||||
cc->Outputs().Tag(tag).AddPacket(
|
||||
cc->InputSidePackets().Index(0).At(kTimestampMap->at(tag)));
|
||||
|
||||
if (!cc->Outputs().HasTag(kTagAtTick)) {
|
||||
const auto& timestamp = kTimestampMap->at(output_tag_);
|
||||
for (int i = 0; i < cc->Outputs().NumEntries(output_tag_); ++i) {
|
||||
cc->Outputs()
|
||||
.Get(output_tag_, i)
|
||||
.AddPacket(cc->InputSidePackets().Index(i).At(timestamp));
|
||||
}
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
// Copyright 2020 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/match.h"
|
||||
#include "absl/strings/str_replace.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/port/gtest.h"
|
||||
#include "mediapipe/framework/port/integral_types.h"
|
||||
#include "mediapipe/framework/port/parse_text_proto.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/framework/port/status_matchers.h"
|
||||
#include "mediapipe/framework/tool/options_util.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace {
|
||||
|
||||
TEST(SidePacketToStreamCalculator, WrongConfig_MissingTick) {
|
||||
CalculatorGraphConfig graph_config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig>(
|
||||
R"(
|
||||
input_stream: "tick"
|
||||
input_side_packet: "side_packet"
|
||||
output_stream: "packet"
|
||||
node {
|
||||
calculator: "SidePacketToStreamCalculator"
|
||||
input_side_packet: "side_packet"
|
||||
output_stream: "AT_TICK:packet"
|
||||
}
|
||||
)");
|
||||
CalculatorGraph graph;
|
||||
auto status = graph.Initialize(graph_config);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_PRED2(
|
||||
absl::StrContains, status.message(),
|
||||
"Either both of TICK and AT_TICK should be used or none of them.");
|
||||
}
|
||||
|
||||
TEST(SidePacketToStreamCalculator, WrongConfig_NonExistentTag) {
|
||||
CalculatorGraphConfig graph_config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig>(
|
||||
R"(
|
||||
input_stream: "tick"
|
||||
input_side_packet: "side_packet"
|
||||
output_stream: "packet"
|
||||
node {
|
||||
calculator: "SidePacketToStreamCalculator"
|
||||
input_side_packet: "side_packet"
|
||||
output_stream: "DOES_NOT_EXIST:packet"
|
||||
}
|
||||
)");
|
||||
CalculatorGraph graph;
|
||||
auto status = graph.Initialize(graph_config);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_PRED2(absl::StrContains, status.message(),
|
||||
"Only one of AT_PRESTREAM, AT_POSTSTREAM, AT_ZERO and AT_TICK "
|
||||
"tags is allowed and required to specify output stream(s).");
|
||||
}
|
||||
|
||||
TEST(SidePacketToStreamCalculator, WrongConfig_MixedTags) {
|
||||
CalculatorGraphConfig graph_config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig>(
|
||||
R"(
|
||||
input_stream: "tick"
|
||||
input_side_packet: "side_packet0"
|
||||
input_side_packet: "side_packet1"
|
||||
node {
|
||||
calculator: "SidePacketToStreamCalculator"
|
||||
input_side_packet: "side_packet0"
|
||||
input_side_packet: "side_packet1"
|
||||
output_stream: "AT_TICK:packet0"
|
||||
output_stream: "AT_PRE_STREAM:packet1"
|
||||
}
|
||||
)");
|
||||
CalculatorGraph graph;
|
||||
auto status = graph.Initialize(graph_config);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_PRED2(absl::StrContains, status.message(),
|
||||
"Only one of AT_PRESTREAM, AT_POSTSTREAM, AT_ZERO and AT_TICK "
|
||||
"tags is allowed and required to specify output stream(s).");
|
||||
}
|
||||
|
||||
TEST(SidePacketToStreamCalculator, WrongConfig_NotEnoughSidePackets) {
|
||||
CalculatorGraphConfig graph_config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig>(
|
||||
R"(
|
||||
input_side_packet: "side_packet0"
|
||||
input_side_packet: "side_packet1"
|
||||
node {
|
||||
calculator: "SidePacketToStreamCalculator"
|
||||
input_side_packet: "side_packet0"
|
||||
output_stream: "AT_PRESTREAM:0:packet0"
|
||||
output_stream: "AT_PRESTREAM:1:packet1"
|
||||
}
|
||||
)");
|
||||
CalculatorGraph graph;
|
||||
auto status = graph.Initialize(graph_config);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_PRED2(
|
||||
absl::StrContains, status.message(),
|
||||
"Same number of input side packets and output streams is required.");
|
||||
}
|
||||
|
||||
TEST(SidePacketToStreamCalculator, WrongConfig_NotEnoughOutputStreams) {
|
||||
CalculatorGraphConfig graph_config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig>(
|
||||
R"(
|
||||
input_side_packet: "side_packet0"
|
||||
input_side_packet: "side_packet1"
|
||||
node {
|
||||
calculator: "SidePacketToStreamCalculator"
|
||||
input_side_packet: "side_packet0"
|
||||
input_side_packet: "side_packet1"
|
||||
output_stream: "AT_PRESTREAM:packet0"
|
||||
}
|
||||
)");
|
||||
CalculatorGraph graph;
|
||||
auto status = graph.Initialize(graph_config);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_PRED2(
|
||||
absl::StrContains, status.message(),
|
||||
"Same number of input side packets and output streams is required.");
|
||||
}
|
||||
|
||||
void DoTestNonAtTickOutputTag(absl::string_view tag,
|
||||
Timestamp expected_timestamp) {
|
||||
CalculatorGraphConfig graph_config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig>(absl::StrReplaceAll(
|
||||
R"(
|
||||
input_side_packet: "side_packet"
|
||||
output_stream: "packet"
|
||||
node {
|
||||
calculator: "SidePacketToStreamCalculator"
|
||||
input_side_packet: "side_packet"
|
||||
output_stream: "$tag:packet"
|
||||
}
|
||||
)",
|
||||
{{"$tag", tag}}));
|
||||
CalculatorGraph graph;
|
||||
MP_ASSERT_OK(graph.Initialize(graph_config));
|
||||
const int expected_value = 10;
|
||||
std::vector<Packet> output_packets;
|
||||
MP_ASSERT_OK(graph.ObserveOutputStream(
|
||||
"packet", [&output_packets](const Packet& packet) {
|
||||
output_packets.push_back(packet);
|
||||
return ::mediapipe::OkStatus();
|
||||
}));
|
||||
MP_ASSERT_OK(
|
||||
graph.StartRun({{"side_packet", MakePacket<int>(expected_value)}}));
|
||||
MP_ASSERT_OK(graph.WaitForObservedOutput());
|
||||
|
||||
ASSERT_FALSE(output_packets.empty());
|
||||
EXPECT_EQ(expected_timestamp, output_packets.back().Timestamp());
|
||||
EXPECT_EQ(expected_value, output_packets.back().Get<int>());
|
||||
}
|
||||
|
||||
TEST(SidePacketToStreamCalculator, NoAtTickOutputTags) {
|
||||
DoTestNonAtTickOutputTag("AT_PRESTREAM", Timestamp::PreStream());
|
||||
DoTestNonAtTickOutputTag("AT_POSTSTREAM", Timestamp::PostStream());
|
||||
DoTestNonAtTickOutputTag("AT_ZERO", Timestamp(0));
|
||||
}
|
||||
|
||||
TEST(SidePacketToStreamCalculator, AtTick) {
|
||||
CalculatorGraphConfig graph_config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig>(
|
||||
R"(
|
||||
input_stream: "tick"
|
||||
input_side_packet: "side_packet"
|
||||
output_stream: "packet"
|
||||
node {
|
||||
calculator: "SidePacketToStreamCalculator"
|
||||
input_stream: "TICK:tick"
|
||||
input_side_packet: "side_packet"
|
||||
output_stream: "AT_TICK:packet"
|
||||
}
|
||||
)");
|
||||
std::vector<Packet> output_packets;
|
||||
tool::AddVectorSink("packet", &graph_config, &output_packets);
|
||||
CalculatorGraph graph;
|
||||
|
||||
MP_ASSERT_OK(graph.Initialize(graph_config));
|
||||
const int expected_value = 20;
|
||||
MP_ASSERT_OK(
|
||||
graph.StartRun({{"side_packet", MakePacket<int>(expected_value)}}));
|
||||
|
||||
auto tick_and_verify = [&graph, &output_packets,
|
||||
expected_value](int at_timestamp) {
|
||||
MP_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"tick",
|
||||
MakePacket<int>(/*doesn't matter*/ 1).At(Timestamp(at_timestamp))));
|
||||
MP_ASSERT_OK(graph.WaitUntilIdle());
|
||||
|
||||
ASSERT_FALSE(output_packets.empty());
|
||||
EXPECT_EQ(Timestamp(at_timestamp), output_packets.back().Timestamp());
|
||||
EXPECT_EQ(expected_value, output_packets.back().Get<int>());
|
||||
};
|
||||
|
||||
tick_and_verify(/*at_timestamp=*/0);
|
||||
tick_and_verify(/*at_timestamp=*/1);
|
||||
tick_and_verify(/*at_timestamp=*/128);
|
||||
tick_and_verify(/*at_timestamp=*/1024);
|
||||
tick_and_verify(/*at_timestamp=*/1025);
|
||||
}
|
||||
|
||||
TEST(SidePacketToStreamCalculator, AtTick_MultipleSidePackets) {
|
||||
CalculatorGraphConfig graph_config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig>(
|
||||
R"(
|
||||
input_stream: "tick"
|
||||
input_side_packet: "side_packet0"
|
||||
input_side_packet: "side_packet1"
|
||||
output_stream: "packet0"
|
||||
output_stream: "packet1"
|
||||
node {
|
||||
calculator: "SidePacketToStreamCalculator"
|
||||
input_stream: "TICK:tick"
|
||||
input_side_packet: "side_packet0"
|
||||
input_side_packet: "side_packet1"
|
||||
output_stream: "AT_TICK:0:packet0"
|
||||
output_stream: "AT_TICK:1:packet1"
|
||||
}
|
||||
)");
|
||||
std::vector<Packet> output_packets0;
|
||||
tool::AddVectorSink("packet0", &graph_config, &output_packets0);
|
||||
std::vector<Packet> output_packets1;
|
||||
tool::AddVectorSink("packet1", &graph_config, &output_packets1);
|
||||
CalculatorGraph graph;
|
||||
|
||||
MP_ASSERT_OK(graph.Initialize(graph_config));
|
||||
const int expected_value0 = 20;
|
||||
const int expected_value1 = 128;
|
||||
MP_ASSERT_OK(
|
||||
graph.StartRun({{"side_packet0", MakePacket<int>(expected_value0)},
|
||||
{"side_packet1", MakePacket<int>(expected_value1)}}));
|
||||
|
||||
auto tick_and_verify = [&graph, &output_packets0, &output_packets1,
|
||||
expected_value0, expected_value1](int at_timestamp) {
|
||||
MP_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"tick",
|
||||
MakePacket<int>(/*doesn't matter*/ 1).At(Timestamp(at_timestamp))));
|
||||
MP_ASSERT_OK(graph.WaitUntilIdle());
|
||||
|
||||
ASSERT_FALSE(output_packets0.empty());
|
||||
ASSERT_FALSE(output_packets1.empty());
|
||||
|
||||
EXPECT_EQ(Timestamp(at_timestamp), output_packets0.back().Timestamp());
|
||||
EXPECT_EQ(expected_value0, output_packets0.back().Get<int>());
|
||||
EXPECT_EQ(Timestamp(at_timestamp), output_packets1.back().Timestamp());
|
||||
EXPECT_EQ(expected_value1, output_packets1.back().Get<int>());
|
||||
};
|
||||
|
||||
tick_and_verify(/*at_timestamp=*/0);
|
||||
tick_and_verify(/*at_timestamp=*/1);
|
||||
tick_and_verify(/*at_timestamp=*/128);
|
||||
tick_and_verify(/*at_timestamp=*/1024);
|
||||
tick_and_verify(/*at_timestamp=*/1025);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediapipe
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "mediapipe/framework/formats/detection.pb.h"
|
||||
#include "mediapipe/framework/formats/landmark.pb.h"
|
||||
#include "mediapipe/framework/formats/matrix.h"
|
||||
#include "mediapipe/framework/formats/rect.pb.h"
|
||||
#include "tensorflow/lite/interpreter.h"
|
||||
|
||||
@@ -48,13 +50,25 @@ typedef SplitVectorCalculator<::mediapipe::NormalizedLandmark, false>
|
||||
SplitLandmarkVectorCalculator;
|
||||
REGISTER_CALCULATOR(SplitLandmarkVectorCalculator);
|
||||
|
||||
typedef SplitVectorCalculator<::mediapipe::NormalizedLandmarkList, false>
|
||||
SplitNormalizedLandmarkListVectorCalculator;
|
||||
REGISTER_CALCULATOR(SplitNormalizedLandmarkListVectorCalculator);
|
||||
|
||||
typedef SplitVectorCalculator<::mediapipe::NormalizedRect, false>
|
||||
SplitNormalizedRectVectorCalculator;
|
||||
REGISTER_CALCULATOR(SplitNormalizedRectVectorCalculator);
|
||||
|
||||
typedef SplitVectorCalculator<Matrix, false> SplitMatrixVectorCalculator;
|
||||
REGISTER_CALCULATOR(SplitMatrixVectorCalculator);
|
||||
|
||||
#if !defined(MEDIAPIPE_DISABLE_GL_COMPUTE)
|
||||
typedef SplitVectorCalculator<::tflite::gpu::gl::GlBuffer, true>
|
||||
MovableSplitGlBufferVectorCalculator;
|
||||
REGISTER_CALCULATOR(MovableSplitGlBufferVectorCalculator);
|
||||
#endif
|
||||
|
||||
typedef SplitVectorCalculator<::mediapipe::Detection, false>
|
||||
SplitDetectionVectorCalculator;
|
||||
REGISTER_CALCULATOR(SplitDetectionVectorCalculator);
|
||||
|
||||
} // namespace mediapipe
|
||||
|
||||
@@ -86,6 +86,14 @@ mediapipe_cc_proto_library(
|
||||
deps = [":opencv_image_encoder_calculator_proto"],
|
||||
)
|
||||
|
||||
mediapipe_cc_proto_library(
|
||||
name = "opencv_encoded_image_to_image_frame_calculator_cc_proto",
|
||||
srcs = ["opencv_encoded_image_to_image_frame_calculator.proto"],
|
||||
cc_deps = ["//mediapipe/framework:calculator_cc_proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [":opencv_encoded_image_to_image_frame_calculator_proto"],
|
||||
)
|
||||
|
||||
mediapipe_cc_proto_library(
|
||||
name = "mask_overlay_calculator_cc_proto",
|
||||
srcs = ["mask_overlay_calculator.proto"],
|
||||
@@ -172,6 +180,7 @@ cc_library(
|
||||
srcs = ["opencv_encoded_image_to_image_frame_calculator.cc"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":opencv_encoded_image_to_image_frame_calculator_cc_proto",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/formats:image_frame_opencv",
|
||||
"//mediapipe/framework/port:opencv_imgcodecs",
|
||||
@@ -346,9 +355,7 @@ cc_library(
|
||||
],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":image_cropping_calculator_cc_proto",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
@@ -424,9 +431,12 @@ cc_library(
|
||||
":recolor_calculator_cc_proto",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/formats:image_frame",
|
||||
"//mediapipe/framework/formats:image_frame_opencv",
|
||||
"//mediapipe/framework/port:status",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/util:color_cc_proto",
|
||||
"//mediapipe/framework/port:opencv_core",
|
||||
"//mediapipe/framework/port:opencv_imgproc",
|
||||
] + select({
|
||||
"//mediapipe/gpu:disable_gpu": [],
|
||||
"//conditions:default": [
|
||||
@@ -556,6 +566,27 @@ proto_library(
|
||||
deps = ["//mediapipe/framework:calculator_proto"],
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "opencv_encoded_image_to_image_frame_calculator_proto",
|
||||
srcs = ["opencv_encoded_image_to_image_frame_calculator.proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["//mediapipe/framework:calculator_proto"],
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "feature_detector_calculator_proto",
|
||||
srcs = ["feature_detector_calculator.proto"],
|
||||
deps = ["//mediapipe/framework:calculator_proto"],
|
||||
)
|
||||
|
||||
mediapipe_cc_proto_library(
|
||||
name = "feature_detector_calculator_cc_proto",
|
||||
srcs = ["feature_detector_calculator.proto"],
|
||||
cc_deps = ["//mediapipe/framework:calculator_cc_proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [":feature_detector_calculator_proto"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "mask_overlay_calculator",
|
||||
srcs = ["mask_overlay_calculator.cc"],
|
||||
@@ -571,3 +602,30 @@ cc_library(
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "feature_detector_calculator",
|
||||
srcs = ["feature_detector_calculator.cc"],
|
||||
visibility = ["//mediapipe:__subpackages__"],
|
||||
deps = [
|
||||
":feature_detector_calculator_cc_proto",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/formats:image_frame",
|
||||
"//mediapipe/framework/formats:image_frame_opencv",
|
||||
"//mediapipe/framework/formats:landmark_cc_proto",
|
||||
"//mediapipe/framework/formats:video_stream_header",
|
||||
"//mediapipe/framework/port:integral_types",
|
||||
"//mediapipe/framework/port:logging",
|
||||
"//mediapipe/framework/port:opencv_core",
|
||||
"//mediapipe/framework/port:opencv_features2d",
|
||||
"//mediapipe/framework/port:opencv_imgproc",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
"//mediapipe/framework/port:threadpool",
|
||||
"//mediapipe/framework/tool:options_util",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
"@org_tensorflow//tensorflow/lite:framework",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
@@ -75,6 +75,11 @@ class ColorConvertCalculator : public CalculatorBase {
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc);
|
||||
::mediapipe::Status Process(CalculatorContext* cc) override;
|
||||
|
||||
::mediapipe::Status Open(CalculatorContext* cc) override {
|
||||
cc->SetOffset(TimestampDiff(0));
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
private:
|
||||
// Wrangles the appropriate inputs and outputs to perform the color
|
||||
// conversion. The ImageFrame on input_tag is converted using the
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
// Copyright 2020 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/synchronization/blocking_counter.h"
|
||||
#include "mediapipe/calculators/image/feature_detector_calculator.pb.h"
|
||||
#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/landmark.pb.h"
|
||||
#include "mediapipe/framework/formats/video_stream_header.h"
|
||||
#include "mediapipe/framework/port/integral_types.h"
|
||||
#include "mediapipe/framework/port/logging.h"
|
||||
#include "mediapipe/framework/port/opencv_core_inc.h"
|
||||
#include "mediapipe/framework/port/opencv_features2d_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/port/threadpool.h"
|
||||
#include "mediapipe/framework/tool/options_util.h"
|
||||
#include "tensorflow/lite/interpreter.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
const char kOptionsTag[] = "OPTIONS";
|
||||
const int kPatchSize = 32;
|
||||
const int kNumThreads = 16;
|
||||
|
||||
// A calculator to apply local feature detection.
|
||||
// Input stream:
|
||||
// IMAGE: Input image frame of type ImageFrame from video stream.
|
||||
// Output streams:
|
||||
// FEATURES: The detected keypoints from input image as vector<cv::KeyPoint>.
|
||||
// PATCHES: Optional output the extracted patches as vector<cv::Mat>
|
||||
class FeatureDetectorCalculator : public CalculatorBase {
|
||||
public:
|
||||
~FeatureDetectorCalculator() override = default;
|
||||
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc);
|
||||
|
||||
::mediapipe::Status Open(CalculatorContext* cc) override;
|
||||
::mediapipe::Status Process(CalculatorContext* cc) override;
|
||||
|
||||
private:
|
||||
FeatureDetectorCalculatorOptions options_;
|
||||
cv::Ptr<cv::Feature2D> feature_detector_;
|
||||
std::unique_ptr<::mediapipe::ThreadPool> pool_;
|
||||
|
||||
// Create image pyramid based on input image.
|
||||
void ComputeImagePyramid(const cv::Mat& input_image,
|
||||
std::vector<cv::Mat>* image_pyramid);
|
||||
|
||||
// Extract the patch for single feature with image pyramid.
|
||||
cv::Mat ExtractPatch(const cv::KeyPoint& feature,
|
||||
const std::vector<cv::Mat>& image_pyramid);
|
||||
};
|
||||
|
||||
REGISTER_CALCULATOR(FeatureDetectorCalculator);
|
||||
|
||||
::mediapipe::Status FeatureDetectorCalculator::GetContract(
|
||||
CalculatorContract* cc) {
|
||||
if (cc->Inputs().HasTag("IMAGE")) {
|
||||
cc->Inputs().Tag("IMAGE").Set<ImageFrame>();
|
||||
}
|
||||
if (cc->Outputs().HasTag("FEATURES")) {
|
||||
cc->Outputs().Tag("FEATURES").Set<std::vector<cv::KeyPoint>>();
|
||||
}
|
||||
if (cc->Outputs().HasTag("LANDMARKS")) {
|
||||
cc->Outputs().Tag("LANDMARKS").Set<NormalizedLandmarkList>();
|
||||
}
|
||||
if (cc->Outputs().HasTag("PATCHES")) {
|
||||
cc->Outputs().Tag("PATCHES").Set<std::vector<TfLiteTensor>>();
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status FeatureDetectorCalculator::Open(CalculatorContext* cc) {
|
||||
options_ =
|
||||
tool::RetrieveOptions(cc->Options(), cc->InputSidePackets(), kOptionsTag)
|
||||
.GetExtension(FeatureDetectorCalculatorOptions::ext);
|
||||
feature_detector_ = cv::ORB::create(
|
||||
options_.max_features(), options_.scale_factor(),
|
||||
options_.pyramid_level(), kPatchSize - 1, 0, 2, cv::ORB::FAST_SCORE);
|
||||
pool_ = absl::make_unique<::mediapipe::ThreadPool>("ThreadPool", kNumThreads);
|
||||
pool_->StartWorkers();
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status FeatureDetectorCalculator::Process(CalculatorContext* cc) {
|
||||
const Timestamp& timestamp = cc->InputTimestamp();
|
||||
if (timestamp == Timestamp::PreStream()) {
|
||||
// Indicator packet.
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
InputStream* input_frame = &(cc->Inputs().Tag("IMAGE"));
|
||||
cv::Mat input_view = formats::MatView(&input_frame->Get<ImageFrame>());
|
||||
cv::Mat grayscale_view;
|
||||
cv::cvtColor(input_view, grayscale_view, cv::COLOR_RGB2GRAY);
|
||||
|
||||
std::vector<cv::KeyPoint> keypoints;
|
||||
feature_detector_->detect(grayscale_view, keypoints);
|
||||
if (keypoints.size() > options_.max_features()) {
|
||||
keypoints.resize(options_.max_features());
|
||||
}
|
||||
|
||||
if (cc->Outputs().HasTag("FEATURES")) {
|
||||
auto features_ptr = absl::make_unique<std::vector<cv::KeyPoint>>(keypoints);
|
||||
cc->Outputs().Tag("FEATURES").Add(features_ptr.release(), timestamp);
|
||||
}
|
||||
|
||||
if (cc->Outputs().HasTag("LANDMARKS")) {
|
||||
auto landmarks_ptr = absl::make_unique<NormalizedLandmarkList>();
|
||||
for (int j = 0; j < keypoints.size(); ++j) {
|
||||
auto feature_landmark = landmarks_ptr->add_landmark();
|
||||
feature_landmark->set_x(keypoints[j].pt.x / grayscale_view.cols);
|
||||
feature_landmark->set_y(keypoints[j].pt.y / grayscale_view.rows);
|
||||
}
|
||||
cc->Outputs().Tag("LANDMARKS").Add(landmarks_ptr.release(), timestamp);
|
||||
}
|
||||
|
||||
if (cc->Outputs().HasTag("PATCHES")) {
|
||||
std::vector<cv::Mat> image_pyramid;
|
||||
ComputeImagePyramid(grayscale_view, &image_pyramid);
|
||||
std::vector<cv::Mat> patch_mat;
|
||||
patch_mat.resize(keypoints.size());
|
||||
absl::BlockingCounter counter(keypoints.size());
|
||||
for (int i = 0; i < keypoints.size(); i++) {
|
||||
pool_->Schedule(
|
||||
[this, &image_pyramid, &keypoints, &patch_mat, i, &counter] {
|
||||
patch_mat[i] = ExtractPatch(keypoints[i], image_pyramid);
|
||||
counter.DecrementCount();
|
||||
});
|
||||
}
|
||||
counter.Wait();
|
||||
const int batch_size = options_.max_features();
|
||||
auto patches = absl::make_unique<std::vector<TfLiteTensor>>();
|
||||
TfLiteTensor tensor;
|
||||
tensor.type = kTfLiteFloat32;
|
||||
tensor.dims = TfLiteIntArrayCreate(4);
|
||||
tensor.dims->data[0] = batch_size;
|
||||
tensor.dims->data[1] = kPatchSize;
|
||||
tensor.dims->data[2] = kPatchSize;
|
||||
tensor.dims->data[3] = 1;
|
||||
int num_bytes = batch_size * kPatchSize * kPatchSize * sizeof(float);
|
||||
tensor.data.data = malloc(num_bytes);
|
||||
tensor.bytes = num_bytes;
|
||||
tensor.allocation_type = kTfLiteArenaRw;
|
||||
float* tensor_buffer = tensor.data.f;
|
||||
for (int i = 0; i < keypoints.size(); i++) {
|
||||
for (int j = 0; j < patch_mat[i].rows; ++j) {
|
||||
for (int k = 0; k < patch_mat[i].cols; ++k) {
|
||||
*tensor_buffer++ = patch_mat[i].at<uchar>(j, k) / 128.0f - 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = keypoints.size() * kPatchSize * kPatchSize; i < num_bytes / 4;
|
||||
i++) {
|
||||
*tensor_buffer++ = 0;
|
||||
}
|
||||
|
||||
patches->emplace_back(tensor);
|
||||
cc->Outputs().Tag("PATCHES").Add(patches.release(), timestamp);
|
||||
}
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
void FeatureDetectorCalculator::ComputeImagePyramid(
|
||||
const cv::Mat& input_image, std::vector<cv::Mat>* image_pyramid) {
|
||||
cv::Mat tmp_image = input_image;
|
||||
cv::Mat src_image = input_image;
|
||||
for (int i = 0; i < options_.pyramid_level(); ++i) {
|
||||
image_pyramid->push_back(src_image);
|
||||
cv::resize(src_image, tmp_image, cv::Size(), 1.0f / options_.scale_factor(),
|
||||
1.0f / options_.scale_factor());
|
||||
src_image = tmp_image;
|
||||
}
|
||||
}
|
||||
|
||||
cv::Mat FeatureDetectorCalculator::ExtractPatch(
|
||||
const cv::KeyPoint& feature, const std::vector<cv::Mat>& image_pyramid) {
|
||||
cv::Mat img = image_pyramid[feature.octave];
|
||||
float scale_factor = 1 / pow(options_.scale_factor(), feature.octave);
|
||||
cv::Point2f center =
|
||||
cv::Point2f(feature.pt.x * scale_factor, feature.pt.y * scale_factor);
|
||||
cv::Mat rot = cv::getRotationMatrix2D(center, feature.angle, 1.0);
|
||||
rot.at<double>(0, 2) += kPatchSize / 2 - center.x;
|
||||
rot.at<double>(1, 2) += kPatchSize / 2 - center.y;
|
||||
cv::Mat cropped_img;
|
||||
// perform the affine transformation
|
||||
cv::warpAffine(img, cropped_img, rot, cv::Size(kPatchSize, kPatchSize),
|
||||
cv::INTER_LINEAR);
|
||||
return cropped_img;
|
||||
}
|
||||
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,24 @@
|
||||
// Options for FeatureDetectorCalculator
|
||||
syntax = "proto2";
|
||||
|
||||
package mediapipe;
|
||||
|
||||
import "mediapipe/framework/calculator.proto";
|
||||
|
||||
message FeatureDetectorCalculatorOptions {
|
||||
extend CalculatorOptions {
|
||||
optional FeatureDetectorCalculatorOptions ext = 278741680;
|
||||
}
|
||||
|
||||
// Set to true if output patches, otherwise only output cv::KeyPoint
|
||||
optional bool output_patch = 1;
|
||||
|
||||
// The max number of detected features.
|
||||
optional int32 max_features = 2 [default = 200];
|
||||
|
||||
// The number of pyramid levels.
|
||||
optional int32 pyramid_level = 3 [default = 4];
|
||||
|
||||
// Pyramid decimation ratio.
|
||||
optional float scale_factor = 4 [default = 1.2];
|
||||
}
|
||||
@@ -75,11 +75,28 @@ REGISTER_CALCULATOR(ImageCroppingCalculator);
|
||||
}
|
||||
#endif // !MEDIAPIPE_DISABLE_GPU
|
||||
|
||||
RET_CHECK(cc->Inputs().HasTag(kRectTag) ^ cc->Inputs().HasTag(kNormRectTag) ^
|
||||
(cc->Options<mediapipe::ImageCroppingCalculatorOptions>()
|
||||
.has_norm_width() &&
|
||||
cc->Options<mediapipe::ImageCroppingCalculatorOptions>()
|
||||
.has_norm_height()));
|
||||
int flags = 0;
|
||||
if (cc->Inputs().HasTag(kRectTag)) {
|
||||
++flags;
|
||||
}
|
||||
if (cc->Inputs().HasTag(kWidthTag) && cc->Inputs().HasTag(kHeightTag)) {
|
||||
++flags;
|
||||
}
|
||||
if (cc->Inputs().HasTag(kNormRectTag)) {
|
||||
++flags;
|
||||
}
|
||||
if (cc->Options<mediapipe::ImageCroppingCalculatorOptions>()
|
||||
.has_norm_width() &&
|
||||
cc->Options<mediapipe::ImageCroppingCalculatorOptions>()
|
||||
.has_norm_height()) {
|
||||
++flags;
|
||||
}
|
||||
if (cc->Options<mediapipe::ImageCroppingCalculatorOptions>().has_width() &&
|
||||
cc->Options<mediapipe::ImageCroppingCalculatorOptions>().has_height()) {
|
||||
++flags;
|
||||
}
|
||||
RET_CHECK(flags == 1) << "Illegal combination of input streams/options.";
|
||||
|
||||
if (cc->Inputs().HasTag(kRectTag)) {
|
||||
cc->Inputs().Tag(kRectTag).Set<Rect>();
|
||||
}
|
||||
@@ -119,6 +136,13 @@ REGISTER_CALCULATOR(ImageCroppingCalculator);
|
||||
#endif // !MEDIAPIPE_DISABLE_GPU
|
||||
}
|
||||
|
||||
// Validate border mode.
|
||||
if (use_gpu_) {
|
||||
MP_RETURN_IF_ERROR(ValidateBorderModeForGPU(cc));
|
||||
} else {
|
||||
MP_RETURN_IF_ERROR(ValidateBorderModeForCPU(cc));
|
||||
}
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
@@ -162,6 +186,32 @@ REGISTER_CALCULATOR(ImageCroppingCalculator);
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status ImageCroppingCalculator::ValidateBorderModeForCPU(
|
||||
CalculatorContext* cc) {
|
||||
int border_mode;
|
||||
return GetBorderModeForOpenCV(cc, &border_mode);
|
||||
}
|
||||
|
||||
::mediapipe::Status ImageCroppingCalculator::ValidateBorderModeForGPU(
|
||||
CalculatorContext* cc) {
|
||||
mediapipe::ImageCroppingCalculatorOptions options =
|
||||
cc->Options<mediapipe::ImageCroppingCalculatorOptions>();
|
||||
|
||||
switch (options.border_mode()) {
|
||||
case mediapipe::ImageCroppingCalculatorOptions::BORDER_ZERO:
|
||||
LOG(WARNING) << "BORDER_ZERO mode is not supported by GPU "
|
||||
<< "implementation and will fall back into BORDER_REPLICATE";
|
||||
break;
|
||||
case mediapipe::ImageCroppingCalculatorOptions::BORDER_REPLICATE:
|
||||
break;
|
||||
default:
|
||||
RET_CHECK_FAIL() << "Unsupported border mode for GPU: "
|
||||
<< options.border_mode();
|
||||
}
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status ImageCroppingCalculator::RenderCpu(CalculatorContext* cc) {
|
||||
if (cc->Inputs().Tag(kImageTag).IsEmpty()) {
|
||||
return ::mediapipe::OkStatus();
|
||||
@@ -169,8 +219,14 @@ REGISTER_CALCULATOR(ImageCroppingCalculator);
|
||||
const auto& input_img = cc->Inputs().Tag(kImageTag).Get<ImageFrame>();
|
||||
cv::Mat input_mat = formats::MatView(&input_img);
|
||||
|
||||
auto [target_width, target_height, rect_center_x, rect_center_y, rotation] =
|
||||
GetCropSpecs(cc, input_img.Width(), input_img.Height());
|
||||
RectSpec specs = GetCropSpecs(cc, input_img.Width(), input_img.Height());
|
||||
int target_width = specs.width, target_height = specs.height,
|
||||
rect_center_x = specs.center_x, rect_center_y = specs.center_y;
|
||||
float rotation = specs.rotation;
|
||||
|
||||
// Get border mode and value for OpenCV.
|
||||
int border_mode;
|
||||
MP_RETURN_IF_ERROR(GetBorderModeForOpenCV(cc, &border_mode));
|
||||
|
||||
const cv::RotatedRect min_rect(cv::Point2f(rect_center_x, rect_center_y),
|
||||
cv::Size2f(target_width, target_height),
|
||||
@@ -191,7 +247,9 @@ REGISTER_CALCULATOR(ImageCroppingCalculator);
|
||||
cv::getPerspectiveTransform(src_points, dst_points);
|
||||
cv::Mat cropped_image;
|
||||
cv::warpPerspective(input_mat, cropped_image, projection_matrix,
|
||||
cv::Size(min_rect.size.width, min_rect.size.height));
|
||||
cv::Size(min_rect.size.width, min_rect.size.height),
|
||||
/* flags = */ 0,
|
||||
/* borderMode = */ border_mode);
|
||||
|
||||
std::unique_ptr<ImageFrame> output_frame(new ImageFrame(
|
||||
input_img.Format(), cropped_image.cols, cropped_image.rows));
|
||||
@@ -347,8 +405,10 @@ void ImageCroppingCalculator::GetOutputDimensions(CalculatorContext* cc,
|
||||
int src_width, int src_height,
|
||||
int* dst_width,
|
||||
int* dst_height) {
|
||||
auto [crop_width, crop_height, x_center, y_center, rotation] =
|
||||
GetCropSpecs(cc, src_width, src_height);
|
||||
RectSpec specs = GetCropSpecs(cc, src_width, src_height);
|
||||
int crop_width = specs.width, crop_height = specs.height,
|
||||
x_center = specs.center_x, y_center = specs.center_y;
|
||||
float rotation = specs.rotation;
|
||||
|
||||
const float half_width = crop_width / 2.0f;
|
||||
const float half_height = crop_height / 2.0f;
|
||||
@@ -453,6 +513,7 @@ RectSpec ImageCroppingCalculator::GetCropSpecs(const CalculatorContext* cc,
|
||||
rotation = options.rotation();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
.width = crop_width,
|
||||
.height = crop_height,
|
||||
@@ -462,4 +523,24 @@ RectSpec ImageCroppingCalculator::GetCropSpecs(const CalculatorContext* cc,
|
||||
};
|
||||
}
|
||||
|
||||
::mediapipe::Status ImageCroppingCalculator::GetBorderModeForOpenCV(
|
||||
CalculatorContext* cc, int* border_mode) {
|
||||
mediapipe::ImageCroppingCalculatorOptions options =
|
||||
cc->Options<mediapipe::ImageCroppingCalculatorOptions>();
|
||||
|
||||
switch (options.border_mode()) {
|
||||
case mediapipe::ImageCroppingCalculatorOptions::BORDER_ZERO:
|
||||
*border_mode = cv::BORDER_CONSTANT;
|
||||
break;
|
||||
case mediapipe::ImageCroppingCalculatorOptions::BORDER_REPLICATE:
|
||||
*border_mode = cv::BORDER_REPLICATE;
|
||||
break;
|
||||
default:
|
||||
RET_CHECK_FAIL() << "Unsupported border mode for CPU: "
|
||||
<< options.border_mode();
|
||||
}
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace mediapipe
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
// Note: input_stream values take precedence over options defined in the graph.
|
||||
//
|
||||
namespace mediapipe {
|
||||
|
||||
struct RectSpec {
|
||||
int width;
|
||||
int height;
|
||||
@@ -63,12 +64,16 @@ class ImageCroppingCalculator : public CalculatorBase {
|
||||
int src_height);
|
||||
|
||||
private:
|
||||
::mediapipe::Status ValidateBorderModeForCPU(CalculatorContext* cc);
|
||||
::mediapipe::Status ValidateBorderModeForGPU(CalculatorContext* cc);
|
||||
::mediapipe::Status RenderCpu(CalculatorContext* cc);
|
||||
::mediapipe::Status RenderGpu(CalculatorContext* cc);
|
||||
::mediapipe::Status InitGpu(CalculatorContext* cc);
|
||||
void GlRender();
|
||||
void GetOutputDimensions(CalculatorContext* cc, int src_width, int src_height,
|
||||
int* dst_width, int* dst_height);
|
||||
::mediapipe::Status GetBorderModeForOpenCV(CalculatorContext* cc,
|
||||
int* border_mode);
|
||||
|
||||
mediapipe::ImageCroppingCalculatorOptions options_;
|
||||
|
||||
|
||||
@@ -40,4 +40,15 @@ message ImageCroppingCalculatorOptions {
|
||||
// The (0, 0) point is at the (top, left) corner.
|
||||
optional float norm_center_x = 6 [default = 0];
|
||||
optional float norm_center_y = 7 [default = 0];
|
||||
|
||||
enum BorderMode {
|
||||
// First unspecified value is required by the guideline. See details here:
|
||||
// https://developers.google.com/protocol-buffers/docs/style#enums
|
||||
BORDER_UNSPECIFIED = 0;
|
||||
BORDER_ZERO = 1;
|
||||
BORDER_REPLICATE = 2;
|
||||
}
|
||||
|
||||
// Specifies behaviour for crops that go beyond image borders.
|
||||
optional BorderMode border_mode = 8 [default = BORDER_ZERO];
|
||||
}
|
||||
|
||||
@@ -56,11 +56,11 @@ TEST(ImageCroppingCalculatorTest, GetCroppingDimensionsNormal) {
|
||||
}
|
||||
)");
|
||||
|
||||
auto calculator_state =
|
||||
CalculatorState("Node", 0, "Calculator", calculator_node, nullptr);
|
||||
auto cc =
|
||||
CalculatorContext(&calculator_state, tool::CreateTagMap({}).ValueOrDie(),
|
||||
tool::CreateTagMap({}).ValueOrDie());
|
||||
auto calculator_state = absl::make_unique<CalculatorState>(
|
||||
"Node", 0, "Calculator", calculator_node, nullptr);
|
||||
auto cc = absl::make_unique<CalculatorContext>(
|
||||
calculator_state.get(), tool::CreateTagMap({}).ValueOrDie(),
|
||||
tool::CreateTagMap({}).ValueOrDie());
|
||||
|
||||
RectSpec expectRect = {
|
||||
.width = 60,
|
||||
@@ -69,9 +69,9 @@ TEST(ImageCroppingCalculatorTest, GetCroppingDimensionsNormal) {
|
||||
.center_y = 50,
|
||||
.rotation = 0.3,
|
||||
};
|
||||
EXPECT_EQ(
|
||||
ImageCroppingCalculator::GetCropSpecs(&cc, input_width, input_height),
|
||||
expectRect);
|
||||
EXPECT_EQ(ImageCroppingCalculator::GetCropSpecs(cc.get(), input_width,
|
||||
input_height),
|
||||
expectRect);
|
||||
} // TEST
|
||||
|
||||
// Test when (width height) + (norm_width norm_height) are set in options.
|
||||
@@ -96,11 +96,11 @@ TEST(ImageCroppingCalculatorTest, RedundantSpecInOptions) {
|
||||
}
|
||||
)");
|
||||
|
||||
auto calculator_state =
|
||||
CalculatorState("Node", 0, "Calculator", calculator_node, nullptr);
|
||||
auto cc =
|
||||
CalculatorContext(&calculator_state, tool::CreateTagMap({}).ValueOrDie(),
|
||||
tool::CreateTagMap({}).ValueOrDie());
|
||||
auto calculator_state = absl::make_unique<CalculatorState>(
|
||||
"Node", 0, "Calculator", calculator_node, nullptr);
|
||||
auto cc = absl::make_unique<CalculatorContext>(
|
||||
calculator_state.get(), tool::CreateTagMap({}).ValueOrDie(),
|
||||
tool::CreateTagMap({}).ValueOrDie());
|
||||
RectSpec expectRect = {
|
||||
.width = 50,
|
||||
.height = 50,
|
||||
@@ -108,9 +108,9 @@ TEST(ImageCroppingCalculatorTest, RedundantSpecInOptions) {
|
||||
.center_y = 50,
|
||||
.rotation = 0.3,
|
||||
};
|
||||
EXPECT_EQ(
|
||||
ImageCroppingCalculator::GetCropSpecs(&cc, input_width, input_height),
|
||||
expectRect);
|
||||
EXPECT_EQ(ImageCroppingCalculator::GetCropSpecs(cc.get(), input_width,
|
||||
input_height),
|
||||
expectRect);
|
||||
} // TEST
|
||||
|
||||
// Test when WIDTH HEIGHT are set from input stream,
|
||||
@@ -138,16 +138,16 @@ TEST(ImageCroppingCalculatorTest, RedundantSpectWithInputStream) {
|
||||
}
|
||||
)");
|
||||
|
||||
auto calculator_state =
|
||||
CalculatorState("Node", 0, "Calculator", calculator_node, nullptr);
|
||||
auto calculator_state = absl::make_unique<CalculatorState>(
|
||||
"Node", 0, "Calculator", calculator_node, nullptr);
|
||||
auto inputTags = tool::CreateTagMap({
|
||||
"HEIGHT:0:crop_height",
|
||||
"WIDTH:0:crop_width",
|
||||
})
|
||||
.ValueOrDie();
|
||||
auto cc = CalculatorContext(&calculator_state, inputTags,
|
||||
tool::CreateTagMap({}).ValueOrDie());
|
||||
auto& inputs = cc.Inputs();
|
||||
auto cc = absl::make_unique<CalculatorContext>(
|
||||
calculator_state.get(), inputTags, tool::CreateTagMap({}).ValueOrDie());
|
||||
auto& inputs = cc->Inputs();
|
||||
inputs.Tag(kHeightTag).Value() = MakePacket<int>(1);
|
||||
inputs.Tag(kWidthTag).Value() = MakePacket<int>(1);
|
||||
RectSpec expectRect = {
|
||||
@@ -157,9 +157,9 @@ TEST(ImageCroppingCalculatorTest, RedundantSpectWithInputStream) {
|
||||
.center_y = 50,
|
||||
.rotation = 0.3,
|
||||
};
|
||||
EXPECT_EQ(
|
||||
ImageCroppingCalculator::GetCropSpecs(&cc, input_width, input_height),
|
||||
expectRect);
|
||||
EXPECT_EQ(ImageCroppingCalculator::GetCropSpecs(cc.get(), input_width,
|
||||
input_height),
|
||||
expectRect);
|
||||
} // TEST
|
||||
|
||||
// Test when RECT is set from input stream,
|
||||
@@ -186,15 +186,15 @@ TEST(ImageCroppingCalculatorTest, RedundantSpecWithInputStream) {
|
||||
}
|
||||
)");
|
||||
|
||||
auto calculator_state =
|
||||
CalculatorState("Node", 0, "Calculator", calculator_node, nullptr);
|
||||
auto calculator_state = absl::make_unique<CalculatorState>(
|
||||
"Node", 0, "Calculator", calculator_node, nullptr);
|
||||
auto inputTags = tool::CreateTagMap({
|
||||
"RECT:0:rect",
|
||||
})
|
||||
.ValueOrDie();
|
||||
auto cc = CalculatorContext(&calculator_state, inputTags,
|
||||
tool::CreateTagMap({}).ValueOrDie());
|
||||
auto& inputs = cc.Inputs();
|
||||
auto cc = absl::make_unique<CalculatorContext>(
|
||||
calculator_state.get(), inputTags, tool::CreateTagMap({}).ValueOrDie());
|
||||
auto& inputs = cc->Inputs();
|
||||
mediapipe::Rect rect = ParseTextProtoOrDie<mediapipe::Rect>(
|
||||
R"(
|
||||
width: 1 height: 1 x_center: 40 y_center: 40 rotation: 0.5
|
||||
@@ -207,9 +207,9 @@ TEST(ImageCroppingCalculatorTest, RedundantSpecWithInputStream) {
|
||||
.center_y = 40,
|
||||
.rotation = 0.5,
|
||||
};
|
||||
EXPECT_EQ(
|
||||
ImageCroppingCalculator::GetCropSpecs(&cc, input_width, input_height),
|
||||
expectRect);
|
||||
EXPECT_EQ(ImageCroppingCalculator::GetCropSpecs(cc.get(), input_width,
|
||||
input_height),
|
||||
expectRect);
|
||||
} // TEST
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -104,6 +104,14 @@ mediapipe::ScaleMode_Mode ParseScaleMode(
|
||||
// to be a multiple of 90 degrees. If provided, it overrides the
|
||||
// ROTATION_DEGREES input side packet.
|
||||
//
|
||||
// FLIP_HORIZONTALLY (optional): Whether to flip image horizontally or not. If
|
||||
// provided, it overrides the FLIP_HORIZONTALLY input side packet and/or
|
||||
// corresponding field in the calculator options.
|
||||
//
|
||||
// FLIP_VERTICALLY (optional): Whether to flip image vertically or not. If
|
||||
// provided, it overrides the FLIP_VERTICALLY input side packet and/or
|
||||
// corresponding field in the calculator options.
|
||||
//
|
||||
// Output:
|
||||
// One of the following two tags:
|
||||
// IMAGE - ImageFrame representing the output image.
|
||||
@@ -129,6 +137,12 @@ mediapipe::ScaleMode_Mode ParseScaleMode(
|
||||
// degrees. It has to be a multiple of 90 degrees. It overrides the
|
||||
// corresponding field in the calculator options.
|
||||
//
|
||||
// FLIP_HORIZONTALLY (optional): Whether to flip image horizontally or not.
|
||||
// It overrides the corresponding field in the calculator options.
|
||||
//
|
||||
// FLIP_VERTICALLY (optional): Whether to flip image vertically or not.
|
||||
// It overrides the corresponding field in the calculator options.
|
||||
//
|
||||
// Calculator options (see image_transformation_calculator.proto):
|
||||
// output_width, output_height - (optional) Desired scaled image size.
|
||||
// rotation_mode - (optional) Rotation in multiples of 90 degrees.
|
||||
@@ -167,6 +181,8 @@ class ImageTransformationCalculator : public CalculatorBase {
|
||||
int output_height_ = 0;
|
||||
mediapipe::RotationMode_Mode rotation_;
|
||||
mediapipe::ScaleMode_Mode scale_mode_;
|
||||
bool flip_horizontally_ = false;
|
||||
bool flip_vertically_ = false;
|
||||
|
||||
bool use_gpu_ = false;
|
||||
#if !defined(MEDIAPIPE_DISABLE_GPU)
|
||||
@@ -203,6 +219,12 @@ REGISTER_CALCULATOR(ImageTransformationCalculator);
|
||||
if (cc->Inputs().HasTag("ROTATION_DEGREES")) {
|
||||
cc->Inputs().Tag("ROTATION_DEGREES").Set<int>();
|
||||
}
|
||||
if (cc->Inputs().HasTag("FLIP_HORIZONTALLY")) {
|
||||
cc->Inputs().Tag("FLIP_HORIZONTALLY").Set<bool>();
|
||||
}
|
||||
if (cc->Inputs().HasTag("FLIP_VERTICALLY")) {
|
||||
cc->Inputs().Tag("FLIP_VERTICALLY").Set<bool>();
|
||||
}
|
||||
|
||||
if (cc->InputSidePackets().HasTag("OUTPUT_DIMENSIONS")) {
|
||||
cc->InputSidePackets().Tag("OUTPUT_DIMENSIONS").Set<DimensionsPacketType>();
|
||||
@@ -210,6 +232,12 @@ REGISTER_CALCULATOR(ImageTransformationCalculator);
|
||||
if (cc->InputSidePackets().HasTag("ROTATION_DEGREES")) {
|
||||
cc->InputSidePackets().Tag("ROTATION_DEGREES").Set<int>();
|
||||
}
|
||||
if (cc->InputSidePackets().HasTag("FLIP_HORIZONTALLY")) {
|
||||
cc->InputSidePackets().Tag("FLIP_HORIZONTALLY").Set<bool>();
|
||||
}
|
||||
if (cc->InputSidePackets().HasTag("FLIP_VERTICALLY")) {
|
||||
cc->InputSidePackets().Tag("FLIP_VERTICALLY").Set<bool>();
|
||||
}
|
||||
|
||||
if (cc->Outputs().HasTag("LETTERBOX_PADDING")) {
|
||||
cc->Outputs().Tag("LETTERBOX_PADDING").Set<std::array<float, 4>>();
|
||||
@@ -245,6 +273,7 @@ REGISTER_CALCULATOR(ImageTransformationCalculator);
|
||||
output_width_ = options_.output_width();
|
||||
output_height_ = options_.output_height();
|
||||
}
|
||||
|
||||
if (cc->InputSidePackets().HasTag("ROTATION_DEGREES")) {
|
||||
rotation_ = DegreesToRotationMode(
|
||||
cc->InputSidePackets().Tag("ROTATION_DEGREES").Get<int>());
|
||||
@@ -252,6 +281,20 @@ REGISTER_CALCULATOR(ImageTransformationCalculator);
|
||||
rotation_ = options_.rotation_mode();
|
||||
}
|
||||
|
||||
if (cc->InputSidePackets().HasTag("FLIP_HORIZONTALLY")) {
|
||||
flip_horizontally_ =
|
||||
cc->InputSidePackets().Tag("FLIP_HORIZONTALLY").Get<bool>();
|
||||
} else {
|
||||
flip_horizontally_ = options_.flip_horizontally();
|
||||
}
|
||||
|
||||
if (cc->InputSidePackets().HasTag("FLIP_VERTICALLY")) {
|
||||
flip_vertically_ =
|
||||
cc->InputSidePackets().Tag("FLIP_VERTICALLY").Get<bool>();
|
||||
} else {
|
||||
flip_vertically_ = options_.flip_vertically();
|
||||
}
|
||||
|
||||
scale_mode_ = ParseScaleMode(options_.scale_mode(), DEFAULT_SCALE_MODE);
|
||||
|
||||
if (use_gpu_) {
|
||||
@@ -268,12 +311,37 @@ REGISTER_CALCULATOR(ImageTransformationCalculator);
|
||||
|
||||
::mediapipe::Status ImageTransformationCalculator::Process(
|
||||
CalculatorContext* cc) {
|
||||
// Override values if specified so.
|
||||
if (cc->Inputs().HasTag("ROTATION_DEGREES") &&
|
||||
!cc->Inputs().Tag("ROTATION_DEGREES").IsEmpty()) {
|
||||
rotation_ =
|
||||
DegreesToRotationMode(cc->Inputs().Tag("ROTATION_DEGREES").Get<int>());
|
||||
}
|
||||
if (cc->Inputs().HasTag("FLIP_HORIZONTALLY") &&
|
||||
!cc->Inputs().Tag("FLIP_HORIZONTALLY").IsEmpty()) {
|
||||
flip_horizontally_ = cc->Inputs().Tag("FLIP_HORIZONTALLY").Get<bool>();
|
||||
}
|
||||
if (cc->Inputs().HasTag("FLIP_VERTICALLY") &&
|
||||
!cc->Inputs().Tag("FLIP_VERTICALLY").IsEmpty()) {
|
||||
flip_vertically_ = cc->Inputs().Tag("FLIP_VERTICALLY").Get<bool>();
|
||||
}
|
||||
|
||||
if (use_gpu_) {
|
||||
#if !defined(MEDIAPIPE_DISABLE_GPU)
|
||||
if (cc->Inputs().Tag("IMAGE_GPU").IsEmpty()) {
|
||||
// Image is missing, hence no way to produce output image. (Timestamp
|
||||
// bound will be updated automatically.)
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
return helper_.RunInGlContext(
|
||||
[this, cc]() -> ::mediapipe::Status { return RenderGpu(cc); });
|
||||
#endif // !MEDIAPIPE_DISABLE_GPU
|
||||
} else {
|
||||
if (cc->Inputs().Tag("IMAGE").IsEmpty()) {
|
||||
// Image is missing, hence no way to produce output image. (Timestamp
|
||||
// bound will be updated automatically.)
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
return RenderCpu(cc);
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
@@ -360,11 +428,6 @@ REGISTER_CALCULATOR(ImageTransformationCalculator);
|
||||
.Add(padding.release(), cc->InputTimestamp());
|
||||
}
|
||||
|
||||
if (cc->InputSidePackets().HasTag("ROTATION_DEGREES")) {
|
||||
rotation_ = DegreesToRotationMode(
|
||||
cc->InputSidePackets().Tag("ROTATION_DEGREES").Get<int>());
|
||||
}
|
||||
|
||||
cv::Mat rotated_mat;
|
||||
const int angle = RotationModeToDegrees(rotation_);
|
||||
cv::Point2f src_center(scaled_mat.cols / 2.0, scaled_mat.rows / 2.0);
|
||||
@@ -372,11 +435,9 @@ REGISTER_CALCULATOR(ImageTransformationCalculator);
|
||||
cv::warpAffine(scaled_mat, rotated_mat, rotation_mat, scaled_mat.size());
|
||||
|
||||
cv::Mat flipped_mat;
|
||||
if (options_.flip_horizontally() || options_.flip_vertically()) {
|
||||
if (flip_horizontally_ || flip_vertically_) {
|
||||
const int flip_code =
|
||||
options_.flip_horizontally() && options_.flip_vertically()
|
||||
? -1
|
||||
: options_.flip_horizontally();
|
||||
flip_horizontally_ && flip_vertically_ ? -1 : flip_horizontally_;
|
||||
cv::flip(rotated_mat, flipped_mat, flip_code);
|
||||
} else {
|
||||
flipped_mat = rotated_mat;
|
||||
@@ -450,11 +511,6 @@ REGISTER_CALCULATOR(ImageTransformationCalculator);
|
||||
}
|
||||
RET_CHECK(renderer) << "Unsupported input texture type";
|
||||
|
||||
if (cc->InputSidePackets().HasTag("ROTATION_DEGREES")) {
|
||||
rotation_ = DegreesToRotationMode(
|
||||
cc->InputSidePackets().Tag("ROTATION_DEGREES").Get<int>());
|
||||
}
|
||||
|
||||
mediapipe::FrameScaleMode scale_mode = mediapipe::FrameScaleModeFromProto(
|
||||
scale_mode_, mediapipe::FrameScaleMode::kStretch);
|
||||
mediapipe::FrameRotation rotation =
|
||||
@@ -469,7 +525,7 @@ REGISTER_CALCULATOR(ImageTransformationCalculator);
|
||||
|
||||
MP_RETURN_IF_ERROR(renderer->GlRender(
|
||||
src1.width(), src1.height(), dst.width(), dst.height(), scale_mode,
|
||||
rotation, options_.flip_horizontally(), options_.flip_vertically(),
|
||||
rotation, flip_horizontally_, flip_vertically_,
|
||||
/*flip_texture=*/false));
|
||||
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/calculators/image/opencv_encoded_image_to_image_frame_calculator.pb.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/formats/image_frame_opencv.h"
|
||||
#include "mediapipe/framework/port/opencv_imgcodecs_inc.h"
|
||||
@@ -34,7 +35,11 @@ namespace mediapipe {
|
||||
class OpenCvEncodedImageToImageFrameCalculator : public CalculatorBase {
|
||||
public:
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc);
|
||||
::mediapipe::Status Open(CalculatorContext* cc) override;
|
||||
::mediapipe::Status Process(CalculatorContext* cc) override;
|
||||
|
||||
private:
|
||||
mediapipe::OpenCvEncodedImageToImageFrameCalculatorOptions options_;
|
||||
};
|
||||
|
||||
::mediapipe::Status OpenCvEncodedImageToImageFrameCalculator::GetContract(
|
||||
@@ -44,13 +49,29 @@ class OpenCvEncodedImageToImageFrameCalculator : public CalculatorBase {
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status OpenCvEncodedImageToImageFrameCalculator::Open(
|
||||
CalculatorContext* cc) {
|
||||
options_ =
|
||||
cc->Options<mediapipe::OpenCvEncodedImageToImageFrameCalculatorOptions>();
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status OpenCvEncodedImageToImageFrameCalculator::Process(
|
||||
CalculatorContext* cc) {
|
||||
const std::string& contents = cc->Inputs().Index(0).Get<std::string>();
|
||||
const std::vector<char> contents_vector(contents.begin(), contents.end());
|
||||
cv::Mat decoded_mat =
|
||||
cv::imdecode(contents_vector, -1 /* return the loaded image as-is */);
|
||||
|
||||
cv::Mat decoded_mat;
|
||||
if (options_.apply_orientation_from_exif_data()) {
|
||||
// We want to respect the orientation from the EXIF data, which
|
||||
// IMREAD_UNCHANGED ignores, but otherwise we want to be as permissive as
|
||||
// possible with our reading flags. Therefore, we use IMREAD_ANYCOLOR and
|
||||
// IMREAD_ANYDEPTH.
|
||||
decoded_mat = cv::imdecode(contents_vector,
|
||||
cv::IMREAD_ANYCOLOR | cv::IMREAD_ANYDEPTH);
|
||||
} else {
|
||||
// Return the loaded image as-is
|
||||
decoded_mat = cv::imdecode(contents_vector, cv::IMREAD_UNCHANGED);
|
||||
}
|
||||
ImageFormat::Format image_format = ImageFormat::UNKNOWN;
|
||||
cv::Mat output_mat;
|
||||
switch (decoded_mat.channels()) {
|
||||
@@ -70,7 +91,8 @@ class OpenCvEncodedImageToImageFrameCalculator : public CalculatorBase {
|
||||
<< "Unsupported number of channels: " << decoded_mat.channels();
|
||||
}
|
||||
std::unique_ptr<ImageFrame> output_frame = absl::make_unique<ImageFrame>(
|
||||
image_format, decoded_mat.size().width, decoded_mat.size().height);
|
||||
image_format, decoded_mat.size().width, decoded_mat.size().height,
|
||||
ImageFrame::kGlDefaultAlignmentBoundary);
|
||||
output_mat.copyTo(formats::MatView(output_frame.get()));
|
||||
cc->Outputs().Index(0).Add(output_frame.release(), cc->InputTimestamp());
|
||||
return ::mediapipe::OkStatus();
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2020 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package mediapipe;
|
||||
|
||||
import "mediapipe/framework/calculator.proto";
|
||||
|
||||
message OpenCvEncodedImageToImageFrameCalculatorOptions {
|
||||
extend CalculatorOptions {
|
||||
optional OpenCvEncodedImageToImageFrameCalculatorOptions ext = 303447308;
|
||||
}
|
||||
|
||||
// If set, we will attempt to automatically apply the orientation specified by
|
||||
// the image's EXIF data when loading the image. Otherwise, the image data
|
||||
// will be loaded as-is.
|
||||
optional bool apply_orientation_from_exif_data = 1 [default = false];
|
||||
}
|
||||
@@ -17,6 +17,9 @@
|
||||
#include "mediapipe/calculators/image/recolor_calculator.pb.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/formats/image_frame.h"
|
||||
#include "mediapipe/framework/formats/image_frame_opencv.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/util/color.pb.h"
|
||||
@@ -39,8 +42,6 @@ namespace mediapipe {
|
||||
// The luminance of the input image is used to adjust the blending weight,
|
||||
// to help preserve image textures.
|
||||
//
|
||||
// TODO implement cpu support.
|
||||
//
|
||||
// Inputs:
|
||||
// One of the following IMAGE tags:
|
||||
// IMAGE: An ImageFrame input image, RGB or RGBA.
|
||||
@@ -71,6 +72,8 @@ namespace mediapipe {
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Note: Cannot mix-match CPU & GPU inputs/outputs.
|
||||
// CPU-in & CPU-out <or> GPU-in & GPU-out
|
||||
class RecolorCalculator : public CalculatorBase {
|
||||
public:
|
||||
RecolorCalculator() = default;
|
||||
@@ -138,6 +141,11 @@ REGISTER_CALCULATOR(RecolorCalculator);
|
||||
cc->Outputs().Tag("IMAGE").Set<ImageFrame>();
|
||||
}
|
||||
|
||||
// Confirm only one of the input streams is present.
|
||||
RET_CHECK(cc->Inputs().HasTag("IMAGE") ^ cc->Inputs().HasTag("IMAGE_GPU"));
|
||||
// Confirm only one of the output streams is present.
|
||||
RET_CHECK(cc->Outputs().HasTag("IMAGE") ^ cc->Outputs().HasTag("IMAGE_GPU"));
|
||||
|
||||
if (use_gpu) {
|
||||
#if !defined(MEDIAPIPE_DISABLE_GPU)
|
||||
MP_RETURN_IF_ERROR(mediapipe::GlCalculatorHelper::UpdateContract(cc));
|
||||
@@ -193,7 +201,62 @@ REGISTER_CALCULATOR(RecolorCalculator);
|
||||
}
|
||||
|
||||
::mediapipe::Status RecolorCalculator::RenderCpu(CalculatorContext* cc) {
|
||||
return ::mediapipe::UnimplementedError("CPU support is not implemented yet.");
|
||||
if (cc->Inputs().Tag("MASK").IsEmpty()) {
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
// Get inputs and setup output.
|
||||
const auto& input_img = cc->Inputs().Tag("IMAGE").Get<ImageFrame>();
|
||||
const auto& mask_img = cc->Inputs().Tag("MASK").Get<ImageFrame>();
|
||||
|
||||
cv::Mat input_mat = formats::MatView(&input_img);
|
||||
cv::Mat mask_mat = formats::MatView(&mask_img);
|
||||
|
||||
RET_CHECK(input_mat.channels() == 3); // RGB only.
|
||||
|
||||
if (mask_mat.channels() > 1) {
|
||||
std::vector<cv::Mat> channels;
|
||||
cv::split(mask_mat, channels);
|
||||
if (mask_channel_ == mediapipe::RecolorCalculatorOptions_MaskChannel_ALPHA)
|
||||
mask_mat = channels[3];
|
||||
else
|
||||
mask_mat = channels[0];
|
||||
}
|
||||
cv::Mat mask_full;
|
||||
cv::resize(mask_mat, mask_full, input_mat.size());
|
||||
|
||||
auto output_img = absl::make_unique<ImageFrame>(
|
||||
input_img.Format(), input_mat.cols, input_mat.rows);
|
||||
cv::Mat output_mat = mediapipe::formats::MatView(output_img.get());
|
||||
|
||||
// From GPU shader:
|
||||
/*
|
||||
vec4 weight = texture2D(mask, sample_coordinate);
|
||||
vec4 color1 = texture2D(frame, sample_coordinate);
|
||||
vec4 color2 = vec4(recolor, 1.0);
|
||||
|
||||
float luminance = dot(color1.rgb, vec3(0.299, 0.587, 0.114));
|
||||
float mix_value = weight.MASK_COMPONENT * luminance;
|
||||
|
||||
fragColor = mix(color1, color2, mix_value);
|
||||
*/
|
||||
for (int i = 0; i < output_mat.rows; ++i) {
|
||||
for (int j = 0; j < output_mat.cols; ++j) {
|
||||
float weight = mask_full.at<uchar>(i, j) * (1.0 / 255.0);
|
||||
cv::Vec3f color1 = input_mat.at<cv::Vec3b>(i, j);
|
||||
cv::Vec3f color2 = {color_[0], color_[1], color_[2]};
|
||||
|
||||
float luminance =
|
||||
(color1[0] * 0.299 + color1[1] * 0.587 + color1[2] * 0.114) / 255;
|
||||
float mix_value = weight * luminance;
|
||||
|
||||
cv::Vec3b mix_color = color1 * (1.0 - mix_value) + color2 * mix_value;
|
||||
output_mat.at<cv::Vec3b>(i, j) = mix_color;
|
||||
}
|
||||
}
|
||||
|
||||
cc->Outputs().Tag("IMAGE").Add(output_img.release(), cc->InputTimestamp());
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status RecolorCalculator::RenderGpu(CalculatorContext* cc) {
|
||||
@@ -303,9 +366,9 @@ void RecolorCalculator::GlRender() {
|
||||
|
||||
if (!options.has_color()) RET_CHECK_FAIL() << "Missing color option.";
|
||||
|
||||
color_.push_back(options.color().r() / 255.0);
|
||||
color_.push_back(options.color().g() / 255.0);
|
||||
color_.push_back(options.color().b() / 255.0);
|
||||
color_.push_back(options.color().r());
|
||||
color_.push_back(options.color().g());
|
||||
color_.push_back(options.color().b());
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
@@ -378,8 +441,8 @@ void RecolorCalculator::GlRender() {
|
||||
glUseProgram(program_);
|
||||
glUniform1i(glGetUniformLocation(program_, "frame"), 1);
|
||||
glUniform1i(glGetUniformLocation(program_, "mask"), 2);
|
||||
glUniform3f(glGetUniformLocation(program_, "recolor"), color_[0], color_[1],
|
||||
color_[2]);
|
||||
glUniform3f(glGetUniformLocation(program_, "recolor"), color_[0] / 255.0,
|
||||
color_[1] / 255.0, color_[2] / 255.0);
|
||||
#endif // !MEDIAPIPE_DISABLE_GPU
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
|
||||
@@ -260,11 +260,11 @@ ScaleImageCalculator::~ScaleImageCalculator() {}
|
||||
&crop_width_, &crop_height_, //
|
||||
&col_start_, &row_start_));
|
||||
MP_RETURN_IF_ERROR(
|
||||
scale_image::FindOutputDimensions(crop_width_, crop_height_, //
|
||||
options_.target_width(), //
|
||||
options_.target_height(), //
|
||||
options_.preserve_aspect_ratio(), //
|
||||
options_.scale_to_multiple_of_two(), //
|
||||
scale_image::FindOutputDimensions(crop_width_, crop_height_, //
|
||||
options_.target_width(), //
|
||||
options_.target_height(), //
|
||||
options_.preserve_aspect_ratio(), //
|
||||
options_.scale_to_multiple_of(), //
|
||||
&output_width_, &output_height_));
|
||||
MP_RETURN_IF_ERROR(FindInterpolationAlgorithm(options_.algorithm(),
|
||||
&interpolation_algorithm_));
|
||||
@@ -361,17 +361,21 @@ ScaleImageCalculator::~ScaleImageCalculator() {}
|
||||
output_format_ = input_format_;
|
||||
}
|
||||
|
||||
const bool is_positive_and_even =
|
||||
(options_.scale_to_multiple_of() >= 1) &&
|
||||
(options_.scale_to_multiple_of() % 2 == 0);
|
||||
|
||||
if (output_format_ == ImageFormat::YCBCR420P) {
|
||||
RET_CHECK(options_.scale_to_multiple_of_two())
|
||||
RET_CHECK(is_positive_and_even)
|
||||
<< "ScaleImageCalculator always outputs width and height that are "
|
||||
"divisible by 2 when output format is YCbCr420P. To scale to "
|
||||
"width and height of odd numbers, the output format must be SRGB.";
|
||||
} else if (options_.preserve_aspect_ratio()) {
|
||||
RET_CHECK(options_.scale_to_multiple_of_two())
|
||||
RET_CHECK(options_.scale_to_multiple_of() == 2)
|
||||
<< "ScaleImageCalculator always outputs width and height that are "
|
||||
"divisible by 2 when perserving aspect ratio. To scale to width "
|
||||
"and height of odd numbers, please set "
|
||||
"preserve_aspect_ratio to false.";
|
||||
"divisible by 2 when preserving aspect ratio. If you'd like to "
|
||||
"set scale_to_multiple_of to something other than 2, please "
|
||||
"set preserve_aspect_ratio to false.";
|
||||
}
|
||||
|
||||
if (input_width_ > 0 && input_height_ > 0 &&
|
||||
|
||||
@@ -11,9 +11,10 @@ import "mediapipe/framework/formats/image_format.proto";
|
||||
// 2) Scale and convert the image to fit inside target_width x target_height
|
||||
// using the specified scaling algorithm. (maintaining the aspect
|
||||
// ratio if preserve_aspect_ratio is true).
|
||||
// The output width and height will be divisible by 2. It is possible to output
|
||||
// width and height that are odd number when the output format is SRGB and not
|
||||
// perserving the aspect ratio. See scale_to_multiple_of_two option for details.
|
||||
// The output width and height will be divisible by 2, by default. It is
|
||||
// possible to output width and height that are odd numbers when the output
|
||||
// format is SRGB and the aspect ratio is left unpreserved. See
|
||||
// scale_to_multiple_of for details.
|
||||
message ScaleImageCalculatorOptions {
|
||||
extend CalculatorOptions {
|
||||
optional ScaleImageCalculatorOptions ext = 66237115;
|
||||
@@ -23,7 +24,7 @@ message ScaleImageCalculatorOptions {
|
||||
// depending on the other options below. If unset, use the same width
|
||||
// or height as the input. If only one is set then determine the other
|
||||
// from the aspect ratio (after cropping). The output width and height
|
||||
// will be divisible by 2.
|
||||
// will be divisible by 2, by default.
|
||||
optional int32 target_width = 1;
|
||||
optional int32 target_height = 2;
|
||||
|
||||
@@ -31,7 +32,8 @@ message ScaleImageCalculatorOptions {
|
||||
// fits inside the box represented by target_width and target_height.
|
||||
// Otherwise it is scaled to fit target_width and target_height
|
||||
// completely. In any case, the aspect ratio that is preserved is
|
||||
// that after cropping to the minimum/maximum aspect ratio.
|
||||
// that after cropping to the minimum/maximum aspect ratio. Additionally, if
|
||||
// true, the output width and height will be divisible by 2.
|
||||
optional bool preserve_aspect_ratio = 3 [default = true];
|
||||
|
||||
// If ratio is positive, crop the image to this minimum and maximum
|
||||
@@ -95,11 +97,13 @@ message ScaleImageCalculatorOptions {
|
||||
// SRGB or YCBCR420P.
|
||||
optional ImageFormat.Format input_format = 12;
|
||||
|
||||
// If true, the output width and height will be divisible by 2. Otherwise it
|
||||
// will use the exact specified output width and height, which is only
|
||||
// supported when the output format is SRGB and preserve_aspect_ratio option
|
||||
// is set to false.
|
||||
optional bool scale_to_multiple_of_two = 13 [default = true];
|
||||
// If set to 2, the target width and height will be rounded-down
|
||||
// to the nearest even number. If set to any positive value other than 2,
|
||||
// preserve_aspect_ratio must be false and the target width and height will be
|
||||
// rounded-down to multiples of the given value. If set to any value less than
|
||||
// 1, it will be treated like 1.
|
||||
// NOTE: If set to an odd number, the output format must be SRGB.
|
||||
optional int32 scale_to_multiple_of = 13 [default = 2];
|
||||
|
||||
// If true, assume the input YUV is BT.709 (this is the HDTV standard, so most
|
||||
// content is likely using it). If false use the previous assumption of BT.601
|
||||
|
||||
@@ -88,17 +88,27 @@ double ParseRational(const std::string& rational) {
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status FindOutputDimensions(int input_width, //
|
||||
int input_height, //
|
||||
int target_width, //
|
||||
int target_height, //
|
||||
bool preserve_aspect_ratio, //
|
||||
bool scale_to_multiple_of_two, //
|
||||
::mediapipe::Status FindOutputDimensions(int input_width, //
|
||||
int input_height, //
|
||||
int target_width, //
|
||||
int target_height, //
|
||||
bool preserve_aspect_ratio, //
|
||||
int scale_to_multiple_of, //
|
||||
int* output_width,
|
||||
int* output_height) {
|
||||
CHECK(output_width);
|
||||
CHECK(output_height);
|
||||
|
||||
if (preserve_aspect_ratio) {
|
||||
RET_CHECK(scale_to_multiple_of == 2)
|
||||
<< "FindOutputDimensions always outputs width and height that are "
|
||||
"divisible by 2 when preserving aspect ratio. If you'd like to "
|
||||
"set scale_to_multiple_of to something other than 2, please "
|
||||
"set preserve_aspect_ratio to false.";
|
||||
}
|
||||
|
||||
if (scale_to_multiple_of < 1) scale_to_multiple_of = 1;
|
||||
|
||||
if (!preserve_aspect_ratio || (target_width <= 0 && target_height <= 0)) {
|
||||
if (target_width <= 0) {
|
||||
target_width = input_width;
|
||||
@@ -106,13 +116,13 @@ double ParseRational(const std::string& rational) {
|
||||
if (target_height <= 0) {
|
||||
target_height = input_height;
|
||||
}
|
||||
if (scale_to_multiple_of_two) {
|
||||
*output_width = (target_width / 2) * 2;
|
||||
*output_height = (target_height / 2) * 2;
|
||||
} else {
|
||||
*output_width = target_width;
|
||||
*output_height = target_height;
|
||||
}
|
||||
|
||||
target_width -= target_width % scale_to_multiple_of;
|
||||
target_height -= target_height % scale_to_multiple_of;
|
||||
|
||||
*output_width = target_width;
|
||||
*output_height = target_height;
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
|
||||
@@ -35,17 +35,19 @@ namespace scale_image {
|
||||
int* col_start, int* row_start);
|
||||
|
||||
// Given an input width and height, a target width and height, whether to
|
||||
// preserve the aspect ratio, and whether to round down to a multiple of 2,
|
||||
// determine the output width and height. If target_width or target_height is
|
||||
// non-positive, then they will be set to the input_width and input_height
|
||||
// respectively. The output_width and output_height will be reduced as necessary
|
||||
// to preserve_aspect_ratio and to scale_to_multipe_of_two if these options are
|
||||
// specified.
|
||||
// preserve the aspect ratio, and whether to round-down to the multiple of a
|
||||
// given number nearest to the targets, determine the output width and height.
|
||||
// If target_width or target_height is non-positive, then they will be set to
|
||||
// the input_width and input_height respectively. If scale_to_multiple_of is
|
||||
// less than 1, it will be treated like 1. The output_width and
|
||||
// output_height will be reduced as necessary to preserve_aspect_ratio if the
|
||||
// option is specified. If preserving the aspect ratio is desired, you must set
|
||||
// scale_to_multiple_of to 2.
|
||||
::mediapipe::Status FindOutputDimensions(int input_width, int input_height, //
|
||||
int target_width,
|
||||
int target_height, //
|
||||
bool preserve_aspect_ratio, //
|
||||
bool scale_to_multiple_of_two, //
|
||||
int target_height, //
|
||||
bool preserve_aspect_ratio, //
|
||||
int scale_to_multiple_of, //
|
||||
int* output_width, int* output_height);
|
||||
|
||||
} // namespace scale_image
|
||||
|
||||
@@ -79,49 +79,49 @@ TEST(ScaleImageUtilsTest, FindOutputDimensionsPreserveRatio) {
|
||||
int output_width;
|
||||
int output_height;
|
||||
// Not scale.
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, -1, -1, true, true, &output_width,
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, -1, -1, true, 2, &output_width,
|
||||
&output_height));
|
||||
EXPECT_EQ(200, output_width);
|
||||
EXPECT_EQ(100, output_height);
|
||||
// Not scale with odd input size.
|
||||
MP_ASSERT_OK(FindOutputDimensions(201, 101, -1, -1, false, false,
|
||||
&output_width, &output_height));
|
||||
MP_ASSERT_OK(FindOutputDimensions(201, 101, -1, -1, false, 1, &output_width,
|
||||
&output_height));
|
||||
EXPECT_EQ(201, output_width);
|
||||
EXPECT_EQ(101, output_height);
|
||||
// Scale down by 1/2.
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 100, -1, true, true,
|
||||
&output_width, &output_height));
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 100, -1, true, 2, &output_width,
|
||||
&output_height));
|
||||
EXPECT_EQ(100, output_width);
|
||||
EXPECT_EQ(50, output_height);
|
||||
// Scale up, doubling dimensions.
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, -1, 200, true, true,
|
||||
&output_width, &output_height));
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, -1, 200, true, 2, &output_width,
|
||||
&output_height));
|
||||
EXPECT_EQ(400, output_width);
|
||||
EXPECT_EQ(200, output_height);
|
||||
// Fits a 2:1 image into a 150 x 150 box. Output dimensions are always
|
||||
// visible by 2.
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 150, 150, true, true,
|
||||
&output_width, &output_height));
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 150, 150, true, 2, &output_width,
|
||||
&output_height));
|
||||
EXPECT_EQ(150, output_width);
|
||||
EXPECT_EQ(74, output_height);
|
||||
// Fits a 2:1 image into a 400 x 50 box.
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 400, 50, true, true,
|
||||
&output_width, &output_height));
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 400, 50, true, 2, &output_width,
|
||||
&output_height));
|
||||
EXPECT_EQ(100, output_width);
|
||||
EXPECT_EQ(50, output_height);
|
||||
// Scale to multiple number with odd targe size.
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 101, -1, true, true,
|
||||
&output_width, &output_height));
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 101, -1, true, 2, &output_width,
|
||||
&output_height));
|
||||
EXPECT_EQ(100, output_width);
|
||||
EXPECT_EQ(50, output_height);
|
||||
// Scale to multiple number with odd targe size.
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 101, -1, true, false,
|
||||
&output_width, &output_height));
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 101, -1, true, 2, &output_width,
|
||||
&output_height));
|
||||
EXPECT_EQ(100, output_width);
|
||||
EXPECT_EQ(50, output_height);
|
||||
// Scale to odd size.
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 151, 101, false, false,
|
||||
&output_width, &output_height));
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 151, 101, false, 1, &output_width,
|
||||
&output_height));
|
||||
EXPECT_EQ(151, output_width);
|
||||
EXPECT_EQ(101, output_height);
|
||||
}
|
||||
@@ -131,22 +131,62 @@ TEST(ScaleImageUtilsTest, FindOutputDimensionsNoAspectRatio) {
|
||||
int output_width;
|
||||
int output_height;
|
||||
// Scale width only.
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 100, -1, false, true,
|
||||
&output_width, &output_height));
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 100, -1, false, 2, &output_width,
|
||||
&output_height));
|
||||
EXPECT_EQ(100, output_width);
|
||||
EXPECT_EQ(100, output_height);
|
||||
// Scale height only.
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, -1, 200, false, true,
|
||||
&output_width, &output_height));
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, -1, 200, false, 2, &output_width,
|
||||
&output_height));
|
||||
EXPECT_EQ(200, output_width);
|
||||
EXPECT_EQ(200, output_height);
|
||||
// Scale both dimensions.
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 150, 200, false, true,
|
||||
&output_width, &output_height));
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 150, 200, false, 2, &output_width,
|
||||
&output_height));
|
||||
EXPECT_EQ(150, output_width);
|
||||
EXPECT_EQ(200, output_height);
|
||||
}
|
||||
|
||||
// Tests scale_to_multiple_of.
|
||||
TEST(ScaleImageUtilsTest, FindOutputDimensionsDownScaleToMultipleOf) {
|
||||
int output_width;
|
||||
int output_height;
|
||||
// Set no targets, downscale to a multiple of 8.
|
||||
MP_ASSERT_OK(FindOutputDimensions(100, 100, -1, -1, false, 8, &output_width,
|
||||
&output_height));
|
||||
EXPECT_EQ(96, output_width);
|
||||
EXPECT_EQ(96, output_height);
|
||||
// Set width target, downscale to a multiple of 8.
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 100, -1, false, 8, &output_width,
|
||||
&output_height));
|
||||
EXPECT_EQ(96, output_width);
|
||||
EXPECT_EQ(96, output_height);
|
||||
// Set height target, downscale to a multiple of 8.
|
||||
MP_ASSERT_OK(FindOutputDimensions(201, 101, -1, 201, false, 8, &output_width,
|
||||
&output_height));
|
||||
EXPECT_EQ(200, output_width);
|
||||
EXPECT_EQ(200, output_height);
|
||||
// Set both targets, downscale to a multiple of 8.
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 150, 200, false, 8, &output_width,
|
||||
&output_height));
|
||||
EXPECT_EQ(144, output_width);
|
||||
EXPECT_EQ(200, output_height);
|
||||
// Doesn't throw error if keep aspect is true and downscale multiple is 2.
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 400, 200, true, 2, &output_width,
|
||||
&output_height));
|
||||
EXPECT_EQ(400, output_width);
|
||||
EXPECT_EQ(200, output_height);
|
||||
// Throws error if keep aspect is true, but downscale multiple is not 2.
|
||||
ASSERT_THAT(FindOutputDimensions(200, 100, 400, 200, true, 4, &output_width,
|
||||
&output_height),
|
||||
testing::Not(testing::status::IsOk()));
|
||||
// Downscaling to multiple ignored if multiple is less than 2.
|
||||
MP_ASSERT_OK(FindOutputDimensions(200, 100, 401, 201, false, 1, &output_width,
|
||||
&output_height));
|
||||
EXPECT_EQ(401, output_width);
|
||||
EXPECT_EQ(201, output_height);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace scale_image
|
||||
} // namespace mediapipe
|
||||
|
||||
@@ -138,7 +138,7 @@ mediapipe_cc_proto_library(
|
||||
srcs = ["image_frame_to_tensor_calculator.proto"],
|
||||
cc_deps = [
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [":image_frame_to_tensor_calculator_proto"],
|
||||
@@ -173,7 +173,7 @@ mediapipe_cc_proto_library(
|
||||
srcs = ["pack_media_sequence_calculator.proto"],
|
||||
cc_deps = [
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [":pack_media_sequence_calculator_proto"],
|
||||
@@ -192,7 +192,7 @@ mediapipe_cc_proto_library(
|
||||
srcs = ["tensorflow_session_from_frozen_graph_generator.proto"],
|
||||
cc_deps = [
|
||||
"//mediapipe/framework:packet_generator_cc_proto",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [":tensorflow_session_from_frozen_graph_generator_proto"],
|
||||
@@ -203,7 +203,7 @@ mediapipe_cc_proto_library(
|
||||
srcs = ["tensorflow_session_from_frozen_graph_calculator.proto"],
|
||||
cc_deps = [
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [":tensorflow_session_from_frozen_graph_calculator_proto"],
|
||||
@@ -277,7 +277,7 @@ mediapipe_cc_proto_library(
|
||||
srcs = ["vector_int_to_tensor_calculator_options.proto"],
|
||||
cc_deps = [
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [":vector_int_to_tensor_calculator_options_proto"],
|
||||
@@ -408,7 +408,7 @@ cc_library(
|
||||
"//mediapipe/util/sequence:media_sequence",
|
||||
"//mediapipe/util/sequence:media_sequence_util",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
@@ -423,7 +423,7 @@ cc_library(
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
@@ -654,7 +654,7 @@ cc_library(
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
"@org_tensorflow//tensorflow/core:lib",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
@@ -695,7 +695,7 @@ cc_library(
|
||||
"//mediapipe/util:audio_decoder_cc_proto",
|
||||
"//mediapipe/util/sequence:media_sequence",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
@@ -737,7 +737,7 @@ cc_library(
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:packet",
|
||||
"//mediapipe/framework/port:status",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
@@ -745,6 +745,7 @@ cc_library(
|
||||
cc_test(
|
||||
name = "graph_tensors_packet_generator_test",
|
||||
srcs = ["graph_tensors_packet_generator_test.cc"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
":graph_tensors_packet_generator",
|
||||
":graph_tensors_packet_generator_cc_proto",
|
||||
@@ -761,6 +762,7 @@ cc_test(
|
||||
name = "image_frame_to_tensor_calculator_test",
|
||||
size = "small",
|
||||
srcs = ["image_frame_to_tensor_calculator_test.cc"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
":image_frame_to_tensor_calculator",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
@@ -777,6 +779,7 @@ cc_test(
|
||||
name = "matrix_to_tensor_calculator_test",
|
||||
size = "small",
|
||||
srcs = ["matrix_to_tensor_calculator_test.cc"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
":matrix_to_tensor_calculator",
|
||||
":matrix_to_tensor_calculator_options_cc_proto",
|
||||
@@ -793,6 +796,7 @@ cc_test(
|
||||
name = "lapped_tensor_buffer_calculator_test",
|
||||
size = "small",
|
||||
srcs = ["lapped_tensor_buffer_calculator_test.cc"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
":lapped_tensor_buffer_calculator",
|
||||
":lapped_tensor_buffer_calculator_cc_proto",
|
||||
@@ -801,7 +805,7 @@ cc_test(
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@org_tensorflow//tensorflow/core:framework",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -840,7 +844,7 @@ cc_test(
|
||||
"//mediapipe/util/sequence:media_sequence",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -867,7 +871,7 @@ cc_test(
|
||||
"@com_google_absl//absl/strings",
|
||||
"@org_tensorflow//tensorflow/core:direct_session",
|
||||
"@org_tensorflow//tensorflow/core:framework",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
"@org_tensorflow//tensorflow/core:testlib",
|
||||
"@org_tensorflow//tensorflow/core/kernels:conv_ops",
|
||||
"@org_tensorflow//tensorflow/core/kernels:math",
|
||||
@@ -897,7 +901,7 @@ cc_test(
|
||||
"@com_google_absl//absl/strings",
|
||||
"@org_tensorflow//tensorflow/core:direct_session",
|
||||
"@org_tensorflow//tensorflow/core:framework",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
"@org_tensorflow//tensorflow/core:testlib",
|
||||
"@org_tensorflow//tensorflow/core/kernels:conv_ops",
|
||||
"@org_tensorflow//tensorflow/core/kernels:math",
|
||||
@@ -956,6 +960,7 @@ cc_test(
|
||||
cc_test(
|
||||
name = "tensor_squeeze_dimensions_calculator_test",
|
||||
srcs = ["tensor_squeeze_dimensions_calculator_test.cc"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
":tensor_squeeze_dimensions_calculator",
|
||||
":tensor_squeeze_dimensions_calculator_cc_proto",
|
||||
@@ -963,7 +968,7 @@ cc_test(
|
||||
"//mediapipe/framework:calculator_runner",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"@org_tensorflow//tensorflow/core:framework",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -971,6 +976,7 @@ cc_test(
|
||||
name = "tensor_to_image_frame_calculator_test",
|
||||
size = "small",
|
||||
srcs = ["tensor_to_image_frame_calculator_test.cc"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
":tensor_to_image_frame_calculator",
|
||||
":tensor_to_image_frame_calculator_cc_proto",
|
||||
@@ -979,7 +985,7 @@ cc_test(
|
||||
"//mediapipe/framework/formats:image_frame",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"@org_tensorflow//tensorflow/core:framework",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -987,6 +993,7 @@ cc_test(
|
||||
name = "tensor_to_matrix_calculator_test",
|
||||
size = "small",
|
||||
srcs = ["tensor_to_matrix_calculator_test.cc"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
":tensor_to_matrix_calculator",
|
||||
":tensor_to_matrix_calculator_cc_proto",
|
||||
@@ -996,13 +1003,14 @@ cc_test(
|
||||
"//mediapipe/framework/formats:time_series_header_cc_proto",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"@org_tensorflow//tensorflow/core:framework",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "tensor_to_vector_float_calculator_test",
|
||||
srcs = ["tensor_to_vector_float_calculator_test.cc"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
":tensor_to_vector_float_calculator",
|
||||
":tensor_to_vector_float_calculator_options_cc_proto",
|
||||
@@ -1010,7 +1018,7 @@ cc_test(
|
||||
"//mediapipe/framework:calculator_runner",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"@org_tensorflow//tensorflow/core:framework",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1030,13 +1038,14 @@ cc_test(
|
||||
"//mediapipe/util/sequence:media_sequence",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "vector_int_to_tensor_calculator_test",
|
||||
srcs = ["vector_int_to_tensor_calculator_test.cc"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
":vector_int_to_tensor_calculator",
|
||||
":vector_int_to_tensor_calculator_options_cc_proto",
|
||||
@@ -1044,13 +1053,14 @@ cc_test(
|
||||
"//mediapipe/framework:calculator_runner",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"@org_tensorflow//tensorflow/core:framework",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "vector_float_to_tensor_calculator_test",
|
||||
srcs = ["vector_float_to_tensor_calculator_test.cc"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
":vector_float_to_tensor_calculator",
|
||||
":vector_float_to_tensor_calculator_options_cc_proto",
|
||||
@@ -1058,7 +1068,7 @@ cc_test(
|
||||
"//mediapipe/framework:calculator_runner",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"@org_tensorflow//tensorflow/core:framework",
|
||||
"@org_tensorflow//tensorflow/core:protos_all",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1100,6 +1110,7 @@ cc_test(
|
||||
],
|
||||
"//mediapipe:android": [
|
||||
"@org_tensorflow//tensorflow/core:android_tensorflow_lib_with_ops_lite_proto_no_rtti_lib",
|
||||
"@org_tensorflow//tensorflow/core:android_tensorflow_test_lib",
|
||||
],
|
||||
"//mediapipe:ios": [
|
||||
"@org_tensorflow//tensorflow/core:ios_tensorflow_test_lib",
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#if !defined(__ANDROID__)
|
||||
#include "mediapipe/framework/port/file_helpers.h"
|
||||
#endif
|
||||
#include "absl/strings/substitute.h"
|
||||
#include "absl/strings/str_replace.h"
|
||||
#include "mediapipe/calculators/tensorflow/tensorflow_session.h"
|
||||
#include "mediapipe/calculators/tensorflow/tensorflow_session_from_saved_model_calculator.pb.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
@@ -51,10 +51,11 @@ static constexpr char kStringSavedModelPath[] = "STRING_SAVED_MODEL_PATH";
|
||||
#endif
|
||||
}
|
||||
|
||||
// If options.convert_signature_to_tags() will convert letters to uppercase
|
||||
// and replace /'s with _'s. If set, this enables the standard SavedModel
|
||||
// classification, regression, and prediction signatures to be used as
|
||||
// uppercase INPUTS and OUTPUTS tags for streams.
|
||||
// If options.convert_signature_to_tags() is set, will convert letters to
|
||||
// uppercase and replace /'s and -'s with _'s. This enables the standard
|
||||
// SavedModel classification, regression, and prediction signatures to be used
|
||||
// as uppercase INPUTS and OUTPUTS tags for streams and supports other common
|
||||
// patterns.
|
||||
const std::string MaybeConvertSignatureToTag(
|
||||
const std::string& name,
|
||||
const TensorFlowSessionFromSavedModelCalculatorOptions& options) {
|
||||
@@ -63,7 +64,8 @@ const std::string MaybeConvertSignatureToTag(
|
||||
output.resize(name.length());
|
||||
std::transform(name.begin(), name.end(), output.begin(),
|
||||
[](unsigned char c) { return std::toupper(c); });
|
||||
output = absl::Substitute(output, "/", "_");
|
||||
output = absl::StrReplaceAll(output, {{"/", "_"}});
|
||||
output = absl::StrReplaceAll(output, {{"-", "_"}});
|
||||
return output;
|
||||
} else {
|
||||
return name;
|
||||
|
||||
+2
-2
@@ -32,8 +32,8 @@ message TensorFlowSessionFromSavedModelCalculatorOptions {
|
||||
// The name of the generic signature to load into the mapping from tags to
|
||||
// tensor names.
|
||||
optional string signature_name = 2 [default = "serving_default"];
|
||||
// Whether to convert the signature keys to uppercase and switch /'s to
|
||||
// _'s, which enables standard signatures to be used as Tags.
|
||||
// Whether to convert the signature keys to uppercase as well as switch /'s
|
||||
// and -'s to _'s, which enables common signatures to be used as Tags.
|
||||
optional bool convert_signature_to_tags = 3 [default = true];
|
||||
// If true, saved_model_path can have multiple exported models in
|
||||
// subdirectories saved_model_path/%08d and the alphabetically last (i.e.,
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "absl/strings/substitute.h"
|
||||
#include "absl/strings/str_replace.h"
|
||||
#include "mediapipe/calculators/tensorflow/tensorflow_session.h"
|
||||
#include "mediapipe/calculators/tensorflow/tensorflow_session_from_saved_model_calculator.pb.h"
|
||||
#include "mediapipe/framework/calculator.pb.h"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#if !defined(__ANDROID__)
|
||||
#include "mediapipe/framework/port/file_helpers.h"
|
||||
#endif
|
||||
#include "absl/strings/substitute.h"
|
||||
#include "absl/strings/str_replace.h"
|
||||
#include "mediapipe/calculators/tensorflow/tensorflow_session.h"
|
||||
#include "mediapipe/calculators/tensorflow/tensorflow_session_from_saved_model_generator.pb.h"
|
||||
#include "mediapipe/framework/deps/file_path.h"
|
||||
@@ -53,10 +53,11 @@ static constexpr char kStringSavedModelPath[] = "STRING_SAVED_MODEL_PATH";
|
||||
#endif
|
||||
}
|
||||
|
||||
// If options.convert_signature_to_tags() will convert letters to uppercase
|
||||
// and replace /'s with _'s. If set, this enables the standard SavedModel
|
||||
// classification, regression, and prediction signatures to be used as
|
||||
// uppercase INPUTS and OUTPUTS tags for streams.
|
||||
// If options.convert_signature_to_tags() is set, will convert letters to
|
||||
// uppercase and replace /'s and -'s with _'s. This enables the standard
|
||||
// SavedModel classification, regression, and prediction signatures to be used
|
||||
// as uppercase INPUTS and OUTPUTS tags for streams and supports other common
|
||||
// patterns.
|
||||
const std::string MaybeConvertSignatureToTag(
|
||||
const std::string& name,
|
||||
const TensorFlowSessionFromSavedModelGeneratorOptions& options) {
|
||||
@@ -65,7 +66,8 @@ const std::string MaybeConvertSignatureToTag(
|
||||
output.resize(name.length());
|
||||
std::transform(name.begin(), name.end(), output.begin(),
|
||||
[](unsigned char c) { return std::toupper(c); });
|
||||
output = absl::Substitute(output, "/", "_");
|
||||
output = absl::StrReplaceAll(output, {{"/", "_"}});
|
||||
output = absl::StrReplaceAll(output, {{"-", "_"}});
|
||||
return output;
|
||||
} else {
|
||||
return name;
|
||||
|
||||
+2
-2
@@ -32,8 +32,8 @@ message TensorFlowSessionFromSavedModelGeneratorOptions {
|
||||
// The name of the generic signature to load into the mapping from tags to
|
||||
// tensor names.
|
||||
optional string signature_name = 2 [default = "serving_default"];
|
||||
// Whether to convert the signature keys to uppercase and switch /'s to
|
||||
// _'s, which enables standard signatures to be used as Tags.
|
||||
// Whether to convert the signature keys to uppercase as well as switch /'s
|
||||
// and -'s to _'s, which enables common signatures to be used as Tags.
|
||||
optional bool convert_signature_to_tags = 3 [default = true];
|
||||
// If true, saved_model_path can have multiple exported models in
|
||||
// subdirectories saved_model_path/%08d and the alphabetically last (i.e.,
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "absl/strings/substitute.h"
|
||||
#include "absl/strings/str_replace.h"
|
||||
#include "mediapipe/calculators/tensorflow/tensorflow_session.h"
|
||||
#include "mediapipe/calculators/tensorflow/tensorflow_session_from_saved_model_generator.pb.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
|
||||
@@ -222,9 +222,11 @@ cc_library(
|
||||
deps = [
|
||||
":util",
|
||||
":tflite_inference_calculator_cc_proto",
|
||||
"@com_google_absl//absl/memory",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/util:resource_util",
|
||||
"@org_tensorflow//tensorflow/lite:framework",
|
||||
"@org_tensorflow//tensorflow/lite/delegates/xnnpack:xnnpack_delegate",
|
||||
"@org_tensorflow//tensorflow/lite/kernels:builtin_ops",
|
||||
"//mediapipe/framework/stream_handler:fixed_size_input_stream_handler",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
@@ -254,6 +256,10 @@ cc_library(
|
||||
"//mediapipe:android": [
|
||||
"@org_tensorflow//tensorflow/lite/delegates/nnapi:nnapi_delegate",
|
||||
],
|
||||
}) + select({
|
||||
"//conditions:default": [
|
||||
"//mediapipe/util:cpu_util",
|
||||
],
|
||||
}),
|
||||
alwayslink = 1,
|
||||
)
|
||||
@@ -308,6 +314,20 @@ cc_library(
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "tflite_model_calculator",
|
||||
srcs = ["tflite_model_calculator.cc"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":util",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:packet",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"@org_tensorflow//tensorflow/lite:framework",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "tflite_tensors_to_segmentation_calculator",
|
||||
srcs = ["tflite_tensors_to_segmentation_calculator.cc"],
|
||||
@@ -431,7 +451,7 @@ cc_library(
|
||||
"//mediapipe:android": [
|
||||
"//mediapipe/util/android/file/base",
|
||||
],
|
||||
"//mediapipe:apple": [
|
||||
"//mediapipe:ios": [
|
||||
"//mediapipe/util/android/file/base",
|
||||
],
|
||||
"//mediapipe:macos": [
|
||||
@@ -478,6 +498,9 @@ cc_test(
|
||||
deps = [
|
||||
":tflite_inference_calculator",
|
||||
":tflite_inference_calculator_cc_proto",
|
||||
":tflite_model_calculator",
|
||||
"//mediapipe/calculators/core:constant_side_packet_calculator",
|
||||
"//mediapipe/calculators/util:local_file_contents_calculator",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:calculator_runner",
|
||||
"//mediapipe/framework/deps:file_path",
|
||||
@@ -485,6 +508,9 @@ cc_test(
|
||||
"//mediapipe/framework/port:integral_types",
|
||||
"//mediapipe/framework/port:parse_text_proto",
|
||||
"//mediapipe/framework/tool:validate_type",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/types:optional",
|
||||
"@org_tensorflow//tensorflow/lite:framework",
|
||||
"@org_tensorflow//tensorflow/lite/kernels:builtin_ops",
|
||||
],
|
||||
@@ -510,3 +536,19 @@ cc_test(
|
||||
"@org_tensorflow//tensorflow/lite/kernels:builtin_ops",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "tflite_model_calculator_test",
|
||||
srcs = ["tflite_model_calculator_test.cc"],
|
||||
data = ["testdata/add.bin"],
|
||||
deps = [
|
||||
":tflite_model_calculator",
|
||||
"//mediapipe/calculators/core:constant_side_packet_calculator",
|
||||
"//mediapipe/calculators/util:local_file_contents_calculator",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:calculator_runner",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"//mediapipe/framework/port:parse_text_proto",
|
||||
"@org_tensorflow//tensorflow/lite:framework",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -17,10 +17,16 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "mediapipe/calculators/tflite/tflite_inference_calculator.pb.h"
|
||||
#include "mediapipe/calculators/tflite/util.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/port/ret_check.h"
|
||||
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
#include "mediapipe/util/cpu_util.h"
|
||||
#endif // !__EMSCRIPTEN__
|
||||
|
||||
#include "mediapipe/util/resource_util.h"
|
||||
#include "tensorflow/lite/error_reporter.h"
|
||||
#include "tensorflow/lite/interpreter.h"
|
||||
@@ -50,7 +56,7 @@
|
||||
#include "tensorflow/lite/delegates/gpu/metal_delegate.h"
|
||||
#include "tensorflow/lite/delegates/gpu/metal_delegate_internal.h"
|
||||
#endif // iOS
|
||||
|
||||
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
|
||||
#if defined(MEDIAPIPE_ANDROID)
|
||||
#include "tensorflow/lite/delegates/nnapi/nnapi_delegate.h"
|
||||
#endif // ANDROID
|
||||
@@ -113,6 +119,23 @@ struct GPUData {
|
||||
};
|
||||
#endif
|
||||
|
||||
// Returns number of threads to configure XNNPACK delegate with.
|
||||
// (Equal to user provided value if specified. Otherwise, it returns number of
|
||||
// high cores (hard-coded to 1 for __EMSCRIPTEN__))
|
||||
int GetXnnpackNumThreads(
|
||||
const mediapipe::TfLiteInferenceCalculatorOptions& opts) {
|
||||
static constexpr int kDefaultNumThreads = -1;
|
||||
if (opts.has_delegate() && opts.delegate().has_xnnpack() &&
|
||||
opts.delegate().xnnpack().num_threads() != kDefaultNumThreads) {
|
||||
return opts.delegate().xnnpack().num_threads();
|
||||
}
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
return InferHigherCoreIds().size();
|
||||
#else
|
||||
return 1;
|
||||
#endif // !__EMSCRIPTEN__
|
||||
}
|
||||
|
||||
// Calculator Header Section
|
||||
|
||||
// Runs inference on the provided input TFLite tensors and TFLite model.
|
||||
@@ -139,6 +162,9 @@ struct GPUData {
|
||||
// Input side packet:
|
||||
// CUSTOM_OP_RESOLVER (optional) - Use a custom op resolver,
|
||||
// instead of the builtin one.
|
||||
// MODEL (optional) - Use to specify TfLite model
|
||||
// (std::unique_ptr<tflite::FlatBufferModel,
|
||||
// std::function<void(tflite::FlatBufferModel*)>>)
|
||||
//
|
||||
// Example use:
|
||||
// node {
|
||||
@@ -148,7 +174,21 @@ struct GPUData {
|
||||
// options: {
|
||||
// [mediapipe.TfLiteInferenceCalculatorOptions.ext] {
|
||||
// model_path: "modelname.tflite"
|
||||
// use_gpu: true
|
||||
// delegate { gpu {} }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// or
|
||||
//
|
||||
// node {
|
||||
// calculator: "TfLiteInferenceCalculator"
|
||||
// input_stream: "TENSORS:tensor_image"
|
||||
// input_side_packet: "MODEL:model"
|
||||
// output_stream: "TENSORS:tensors"
|
||||
// options: {
|
||||
// [mediapipe.TfLiteInferenceCalculatorOptions.ext] {
|
||||
// delegate { gpu {} }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -163,6 +203,12 @@ struct GPUData {
|
||||
//
|
||||
class TfLiteInferenceCalculator : public CalculatorBase {
|
||||
public:
|
||||
using TfLiteDelegatePtr =
|
||||
std::unique_ptr<TfLiteDelegate, std::function<void(TfLiteDelegate*)>>;
|
||||
using TfLiteModelPtr =
|
||||
std::unique_ptr<tflite::FlatBufferModel,
|
||||
std::function<void(tflite::FlatBufferModel*)>>;
|
||||
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc);
|
||||
|
||||
::mediapipe::Status Open(CalculatorContext* cc) override;
|
||||
@@ -170,13 +216,13 @@ class TfLiteInferenceCalculator : public CalculatorBase {
|
||||
::mediapipe::Status Close(CalculatorContext* cc) override;
|
||||
|
||||
private:
|
||||
::mediapipe::Status LoadOptions(CalculatorContext* cc);
|
||||
::mediapipe::Status LoadModel(CalculatorContext* cc);
|
||||
::mediapipe::StatusOr<Packet> GetModelAsPacket(const CalculatorContext& cc);
|
||||
::mediapipe::Status LoadDelegate(CalculatorContext* cc);
|
||||
|
||||
Packet model_packet_;
|
||||
std::unique_ptr<tflite::Interpreter> interpreter_;
|
||||
std::unique_ptr<tflite::FlatBufferModel> model_;
|
||||
TfLiteDelegate* delegate_ = nullptr;
|
||||
TfLiteDelegatePtr delegate_;
|
||||
|
||||
#if !defined(MEDIAPIPE_DISABLE_GL_COMPUTE)
|
||||
mediapipe::GlCalculatorHelper gpu_helper_;
|
||||
@@ -195,7 +241,6 @@ class TfLiteInferenceCalculator : public CalculatorBase {
|
||||
edgetpu::EdgeTpuManager::GetSingleton()->OpenDevice();
|
||||
#endif
|
||||
|
||||
std::string model_path_ = "";
|
||||
bool gpu_inference_ = false;
|
||||
bool gpu_input_ = false;
|
||||
bool gpu_output_ = false;
|
||||
@@ -212,12 +257,22 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
|
||||
RET_CHECK(cc->Outputs().HasTag("TENSORS") ^
|
||||
cc->Outputs().HasTag("TENSORS_GPU"));
|
||||
|
||||
bool use_gpu = false;
|
||||
const auto& options =
|
||||
cc->Options<::mediapipe::TfLiteInferenceCalculatorOptions>();
|
||||
RET_CHECK(!options.model_path().empty() ^
|
||||
cc->InputSidePackets().HasTag("MODEL"))
|
||||
<< "Either model as side packet or model path in options is required.";
|
||||
|
||||
bool use_gpu =
|
||||
options.has_delegate() ? options.delegate().has_gpu() : options.use_gpu();
|
||||
|
||||
if (cc->Inputs().HasTag("TENSORS"))
|
||||
cc->Inputs().Tag("TENSORS").Set<std::vector<TfLiteTensor>>();
|
||||
#if !defined(MEDIAPIPE_DISABLE_GPU) && !defined(__EMSCRIPTEN__)
|
||||
if (cc->Inputs().HasTag("TENSORS_GPU")) {
|
||||
RET_CHECK(!options.has_delegate() || options.delegate().has_gpu())
|
||||
<< "GPU input is compatible with GPU delegate only.";
|
||||
|
||||
cc->Inputs().Tag("TENSORS_GPU").Set<std::vector<GpuTensor>>();
|
||||
use_gpu |= true;
|
||||
}
|
||||
@@ -227,6 +282,9 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
|
||||
cc->Outputs().Tag("TENSORS").Set<std::vector<TfLiteTensor>>();
|
||||
#if !defined(MEDIAPIPE_DISABLE_GPU) && !defined(__EMSCRIPTEN__)
|
||||
if (cc->Outputs().HasTag("TENSORS_GPU")) {
|
||||
RET_CHECK(!options.has_delegate() || options.delegate().has_gpu())
|
||||
<< "GPU output is compatible with GPU delegate only.";
|
||||
|
||||
cc->Outputs().Tag("TENSORS_GPU").Set<std::vector<GpuTensor>>();
|
||||
use_gpu |= true;
|
||||
}
|
||||
@@ -237,10 +295,9 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
|
||||
.Tag("CUSTOM_OP_RESOLVER")
|
||||
.Set<tflite::ops::builtin::BuiltinOpResolver>();
|
||||
}
|
||||
|
||||
const auto& options =
|
||||
cc->Options<::mediapipe::TfLiteInferenceCalculatorOptions>();
|
||||
use_gpu |= options.use_gpu();
|
||||
if (cc->InputSidePackets().HasTag("MODEL")) {
|
||||
cc->InputSidePackets().Tag("MODEL").Set<TfLiteModelPtr>();
|
||||
}
|
||||
|
||||
if (use_gpu) {
|
||||
#if !defined(MEDIAPIPE_DISABLE_GL_COMPUTE)
|
||||
@@ -259,7 +316,9 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
|
||||
::mediapipe::Status TfLiteInferenceCalculator::Open(CalculatorContext* cc) {
|
||||
cc->SetOffset(TimestampDiff(0));
|
||||
|
||||
MP_RETURN_IF_ERROR(LoadOptions(cc));
|
||||
const auto& options =
|
||||
cc->Options<::mediapipe::TfLiteInferenceCalculatorOptions>();
|
||||
gpu_inference_ = options.use_gpu();
|
||||
|
||||
if (cc->Inputs().HasTag("TENSORS_GPU")) {
|
||||
#if !defined(MEDIAPIPE_DISABLE_GPU) && !defined(__EMSCRIPTEN__)
|
||||
@@ -454,7 +513,7 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
|
||||
if (gpu_inference_) {
|
||||
#if !defined(MEDIAPIPE_DISABLE_GL_COMPUTE)
|
||||
MP_RETURN_IF_ERROR(gpu_helper_.RunInGlContext([this]() -> Status {
|
||||
TfLiteGpuDelegateDelete(delegate_);
|
||||
delegate_ = nullptr;
|
||||
for (int i = 0; i < gpu_data_in_.size(); ++i) {
|
||||
gpu_data_in_[i].reset();
|
||||
}
|
||||
@@ -464,7 +523,7 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
|
||||
return ::mediapipe::OkStatus();
|
||||
}));
|
||||
#elif defined(MEDIAPIPE_IOS)
|
||||
TFLGpuDelegateDelete(delegate_);
|
||||
delegate_ = nullptr;
|
||||
for (int i = 0; i < gpu_data_in_.size(); ++i) {
|
||||
gpu_data_in_[i].reset();
|
||||
}
|
||||
@@ -472,8 +531,9 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
|
||||
gpu_data_out_[i].reset();
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
delegate_ = nullptr;
|
||||
}
|
||||
delegate_ = nullptr;
|
||||
}
|
||||
#if defined(MEDIAPIPE_EDGE_TPU)
|
||||
edgetpu_context_.reset();
|
||||
@@ -483,33 +543,10 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
|
||||
|
||||
// Calculator Auxiliary Section
|
||||
|
||||
::mediapipe::Status TfLiteInferenceCalculator::LoadOptions(
|
||||
CalculatorContext* cc) {
|
||||
// Get calculator options specified in the graph.
|
||||
const auto& options =
|
||||
cc->Options<::mediapipe::TfLiteInferenceCalculatorOptions>();
|
||||
|
||||
// Get model name.
|
||||
if (!options.model_path().empty()) {
|
||||
std::string model_path = options.model_path();
|
||||
|
||||
ASSIGN_OR_RETURN(model_path_, mediapipe::PathToResourceAsFile(model_path));
|
||||
} else {
|
||||
LOG(ERROR) << "Must specify path to TFLite model.";
|
||||
return ::mediapipe::Status(::mediapipe::StatusCode::kNotFound,
|
||||
"Must specify path to TFLite model.");
|
||||
}
|
||||
|
||||
// Get execution modes.
|
||||
gpu_inference_ = options.use_gpu();
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status TfLiteInferenceCalculator::LoadModel(
|
||||
CalculatorContext* cc) {
|
||||
model_ = tflite::FlatBufferModel::BuildFromFile(model_path_.c_str());
|
||||
RET_CHECK(model_);
|
||||
ASSIGN_OR_RETURN(model_packet_, GetModelAsPacket(*cc));
|
||||
const auto& model = *model_packet_.Get<TfLiteModelPtr>();
|
||||
|
||||
tflite::ops::builtin::BuiltinOpResolver op_resolver;
|
||||
if (cc->InputSidePackets().HasTag("CUSTOM_OP_RESOLVER")) {
|
||||
@@ -519,15 +556,19 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
|
||||
}
|
||||
#if defined(MEDIAPIPE_EDGE_TPU)
|
||||
interpreter_ =
|
||||
BuildEdgeTpuInterpreter(*model_, &op_resolver, edgetpu_context_.get());
|
||||
BuildEdgeTpuInterpreter(model, &op_resolver, edgetpu_context_.get());
|
||||
#else
|
||||
tflite::InterpreterBuilder(*model_, op_resolver)(&interpreter_);
|
||||
tflite::InterpreterBuilder(model, op_resolver)(&interpreter_);
|
||||
#endif // MEDIAPIPE_EDGE_TPU
|
||||
|
||||
RET_CHECK(interpreter_);
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
#if defined(__EMSCRIPTEN__) || defined(MEDIAPIPE_EDGE_TPU)
|
||||
interpreter_->SetNumThreads(1);
|
||||
#else
|
||||
interpreter_->SetNumThreads(
|
||||
cc->Options<mediapipe::TfLiteInferenceCalculatorOptions>()
|
||||
.cpu_num_thread());
|
||||
#endif // __EMSCRIPTEN__
|
||||
|
||||
if (gpu_output_) {
|
||||
@@ -543,22 +584,77 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::StatusOr<Packet> TfLiteInferenceCalculator::GetModelAsPacket(
|
||||
const CalculatorContext& cc) {
|
||||
const auto& options =
|
||||
cc.Options<mediapipe::TfLiteInferenceCalculatorOptions>();
|
||||
if (!options.model_path().empty()) {
|
||||
std::string model_path = options.model_path();
|
||||
|
||||
ASSIGN_OR_RETURN(model_path, mediapipe::PathToResourceAsFile(model_path));
|
||||
|
||||
auto model = tflite::FlatBufferModel::BuildFromFile(model_path.c_str());
|
||||
RET_CHECK(model) << "Failed to load model from path.";
|
||||
return MakePacket<TfLiteModelPtr>(TfLiteModelPtr(
|
||||
model.release(), [](tflite::FlatBufferModel* model) { delete model; }));
|
||||
}
|
||||
if (cc.InputSidePackets().HasTag("MODEL")) {
|
||||
return cc.InputSidePackets().Tag("MODEL");
|
||||
}
|
||||
return ::mediapipe::Status(
|
||||
::mediapipe::StatusCode::kNotFound,
|
||||
"Must specify TFLite model as path or loaded model.");
|
||||
}
|
||||
|
||||
::mediapipe::Status TfLiteInferenceCalculator::LoadDelegate(
|
||||
CalculatorContext* cc) {
|
||||
#if defined(MEDIAPIPE_ANDROID)
|
||||
const auto& calculator_opts =
|
||||
cc->Options<mediapipe::TfLiteInferenceCalculatorOptions>();
|
||||
if (calculator_opts.has_delegate() &&
|
||||
calculator_opts.delegate().has_tflite()) {
|
||||
// Default tflite inference requeqsted - no need to modify graph.
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
if (!gpu_inference_) {
|
||||
if (cc->Options<mediapipe::TfLiteInferenceCalculatorOptions>()
|
||||
.use_nnapi()) {
|
||||
#if defined(MEDIAPIPE_ANDROID)
|
||||
const bool nnapi_requested = calculator_opts.has_delegate()
|
||||
? calculator_opts.delegate().has_nnapi()
|
||||
: calculator_opts.use_nnapi();
|
||||
if (nnapi_requested) {
|
||||
// Attempt to use NNAPI.
|
||||
// If not supported, the default CPU delegate will be created and used.
|
||||
interpreter_->SetAllowFp16PrecisionForFp32(1);
|
||||
delegate_ = tflite::NnApiDelegate();
|
||||
RET_CHECK_EQ(interpreter_->ModifyGraphWithDelegate(delegate_), kTfLiteOk);
|
||||
delegate_ =
|
||||
TfLiteDelegatePtr(tflite::NnApiDelegate(), [](TfLiteDelegate*) {
|
||||
// No need to free according to tflite::NnApiDelegate()
|
||||
// documentation.
|
||||
});
|
||||
RET_CHECK_EQ(interpreter_->ModifyGraphWithDelegate(delegate_.get()),
|
||||
kTfLiteOk);
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
#endif // MEDIAPIPE_ANDROID
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
const bool xnnpack_requested = true;
|
||||
#else
|
||||
const bool xnnpack_requested = calculator_opts.has_delegate() &&
|
||||
calculator_opts.delegate().has_xnnpack();
|
||||
#endif // __EMSCRIPTEN__
|
||||
|
||||
if (xnnpack_requested) {
|
||||
TfLiteXNNPackDelegateOptions xnnpack_opts{};
|
||||
xnnpack_opts.num_threads = GetXnnpackNumThreads(calculator_opts);
|
||||
delegate_ = TfLiteDelegatePtr(TfLiteXNNPackDelegateCreate(&xnnpack_opts),
|
||||
&TfLiteXNNPackDelegateDelete);
|
||||
RET_CHECK_EQ(interpreter_->ModifyGraphWithDelegate(delegate_.get()),
|
||||
kTfLiteOk);
|
||||
}
|
||||
|
||||
// Return, no need for GPU delegate below.
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
#endif // ANDROID
|
||||
|
||||
#if !defined(MEDIAPIPE_DISABLE_GL_COMPUTE)
|
||||
// Configure and create the delegate.
|
||||
@@ -568,14 +664,16 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
|
||||
TFLITE_GL_OBJECT_TYPE_FASTEST;
|
||||
options.compile_options.dynamic_batch_enabled = 0;
|
||||
options.compile_options.inline_parameters = 1;
|
||||
if (!delegate_) delegate_ = TfLiteGpuDelegateCreate(&options);
|
||||
if (!delegate_)
|
||||
delegate_ = TfLiteDelegatePtr(TfLiteGpuDelegateCreate(&options),
|
||||
&TfLiteGpuDelegateDelete);
|
||||
|
||||
if (gpu_input_) {
|
||||
// Get input image sizes.
|
||||
const auto& input_indices = interpreter_->inputs();
|
||||
gpu_data_in_.resize(input_indices.size());
|
||||
for (int i = 0; i < input_indices.size(); ++i) {
|
||||
const TfLiteTensor* tensor = interpreter_->tensor(input_indices[0]);
|
||||
const TfLiteTensor* tensor = interpreter_->tensor(input_indices[i]);
|
||||
gpu_data_in_[i] = absl::make_unique<GPUData>();
|
||||
gpu_data_in_[i]->elements = 1;
|
||||
for (int d = 0; d < tensor->dims->size; ++d) {
|
||||
@@ -586,7 +684,7 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
|
||||
::tflite::gpu::gl::CreateReadWriteShaderStorageBuffer<float>(
|
||||
gpu_data_in_[i]->elements, &gpu_data_in_[i]->buffer));
|
||||
RET_CHECK_EQ(TfLiteGpuDelegateBindBufferToTensor(
|
||||
delegate_, gpu_data_in_[i]->buffer.id(),
|
||||
delegate_.get(), gpu_data_in_[i]->buffer.id(),
|
||||
interpreter_->inputs()[i]),
|
||||
kTfLiteOk);
|
||||
}
|
||||
@@ -609,15 +707,16 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
|
||||
for (int i = 0; i < gpu_data_out_.size(); ++i) {
|
||||
RET_CHECK_CALL(CreateReadWriteShaderStorageBuffer<float>(
|
||||
gpu_data_out_[i]->elements, &gpu_data_out_[i]->buffer));
|
||||
RET_CHECK_EQ(
|
||||
TfLiteGpuDelegateBindBufferToTensor(
|
||||
delegate_, gpu_data_out_[i]->buffer.id(), output_indices[i]),
|
||||
kTfLiteOk);
|
||||
RET_CHECK_EQ(TfLiteGpuDelegateBindBufferToTensor(
|
||||
delegate_.get(), gpu_data_out_[i]->buffer.id(),
|
||||
output_indices[i]),
|
||||
kTfLiteOk);
|
||||
}
|
||||
}
|
||||
|
||||
// Must call this last.
|
||||
RET_CHECK_EQ(interpreter_->ModifyGraphWithDelegate(delegate_), kTfLiteOk);
|
||||
RET_CHECK_EQ(interpreter_->ModifyGraphWithDelegate(delegate_.get()),
|
||||
kTfLiteOk);
|
||||
#endif // OpenGL
|
||||
|
||||
#if defined(MEDIAPIPE_IOS)
|
||||
@@ -626,7 +725,9 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
|
||||
TFLGpuDelegateOptions options;
|
||||
options.allow_precision_loss = true;
|
||||
options.wait_type = TFLGpuDelegateWaitType::TFLGpuDelegateWaitTypePassive;
|
||||
if (!delegate_) delegate_ = TFLGpuDelegateCreate(&options);
|
||||
if (!delegate_)
|
||||
delegate_ = TfLiteDelegatePtr(TFLGpuDelegateCreate(&options),
|
||||
&TFLGpuDelegateDelete);
|
||||
id<MTLDevice> device = gpu_helper_.mtlDevice;
|
||||
|
||||
if (gpu_input_) {
|
||||
@@ -678,10 +779,12 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
|
||||
gpu_data_in_[i]->buffer =
|
||||
[device newBufferWithLength:gpu_data_in_[i]->elements * kHalfSize
|
||||
options:MTLResourceStorageModeShared];
|
||||
RET_CHECK_EQ(interpreter_->ModifyGraphWithDelegate(delegate_), kTfLiteOk);
|
||||
RET_CHECK_EQ(TFLGpuDelegateBindMetalBufferToTensor(
|
||||
delegate_, input_indices[i], gpu_data_in_[i]->buffer),
|
||||
true);
|
||||
RET_CHECK_EQ(interpreter_->ModifyGraphWithDelegate(delegate_.get()),
|
||||
kTfLiteOk);
|
||||
RET_CHECK_EQ(
|
||||
TFLGpuDelegateBindMetalBufferToTensor(
|
||||
delegate_.get(), input_indices[i], gpu_data_in_[i]->buffer),
|
||||
true);
|
||||
}
|
||||
}
|
||||
if (gpu_output_) {
|
||||
@@ -725,9 +828,10 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
|
||||
gpu_data_out_[i]->buffer =
|
||||
[device newBufferWithLength:gpu_data_out_[i]->elements * kHalfSize
|
||||
options:MTLResourceStorageModeShared];
|
||||
RET_CHECK_EQ(TFLGpuDelegateBindMetalBufferToTensor(
|
||||
delegate_, output_indices[i], gpu_data_out_[i]->buffer),
|
||||
true);
|
||||
RET_CHECK_EQ(
|
||||
TFLGpuDelegateBindMetalBufferToTensor(
|
||||
delegate_.get(), output_indices[i], gpu_data_out_[i]->buffer),
|
||||
true);
|
||||
}
|
||||
|
||||
// Create converter for GPU output.
|
||||
|
||||
@@ -27,7 +27,7 @@ import "mediapipe/framework/calculator.proto";
|
||||
// options {
|
||||
// [mediapipe.TfLiteInferenceCalculatorOptions.ext] {
|
||||
// model_path: "model.tflite"
|
||||
// use_gpu: true
|
||||
// delegate { gpu {} }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -37,6 +37,28 @@ message TfLiteInferenceCalculatorOptions {
|
||||
optional TfLiteInferenceCalculatorOptions ext = 233867213;
|
||||
}
|
||||
|
||||
message Delegate {
|
||||
// Default inference provided by tflite.
|
||||
message TfLite {}
|
||||
// Delegate to run GPU inference depending on the device.
|
||||
// (Can use OpenGl, OpenCl, Metal depending on the device.)
|
||||
message Gpu {}
|
||||
// Android only.
|
||||
message Nnapi {}
|
||||
message Xnnpack {
|
||||
// Number of threads for XNNPACK delegate. (By default, calculator tries
|
||||
// to choose optimal number of threads depending on the device.)
|
||||
optional int32 num_threads = 1 [default = -1];
|
||||
}
|
||||
|
||||
oneof delegate {
|
||||
TfLite tflite = 1;
|
||||
Gpu gpu = 2;
|
||||
Nnapi nnapi = 3;
|
||||
Xnnpack xnnpack = 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Path to the TF Lite model (ex: /path/to/modelname.tflite).
|
||||
// On mobile, this is generally just modelname.tflite.
|
||||
optional string model_path = 1;
|
||||
@@ -44,10 +66,22 @@ message TfLiteInferenceCalculatorOptions {
|
||||
// Whether the TF Lite GPU or CPU backend should be used. Effective only when
|
||||
// input tensors are on CPU. For input tensors on GPU, GPU backend is always
|
||||
// used.
|
||||
optional bool use_gpu = 2 [default = false];
|
||||
// DEPRECATED: configure "delegate" instead.
|
||||
optional bool use_gpu = 2 [deprecated = true, default = false];
|
||||
|
||||
// Android only. When true, an NNAPI delegate will be used for inference.
|
||||
// If NNAPI is not available, then the default CPU delegate will be used
|
||||
// automatically.
|
||||
optional bool use_nnapi = 3 [default = false];
|
||||
// DEPRECATED: configure "delegate" instead.
|
||||
optional bool use_nnapi = 3 [deprecated = true, default = false];
|
||||
|
||||
// The number of threads available to the interpreter. Effective only when
|
||||
// input tensors are on CPU and 'use_gpu' is false.
|
||||
optional int32 cpu_num_thread = 4 [default = -1];
|
||||
|
||||
// TfLite delegate to run inference.
|
||||
// NOTE: calculator is free to choose delegate if not specified explicitly.
|
||||
// NOTE: use_gpu/use_nnapi are ignored if specified. (Delegate takes
|
||||
// precedence over use_* deprecated options.)
|
||||
optional Delegate delegate = 5;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/str_replace.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "mediapipe/calculators/tflite/tflite_inference_calculator.pb.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/calculator_runner.h"
|
||||
@@ -39,13 +41,7 @@ namespace mediapipe {
|
||||
|
||||
using ::tflite::Interpreter;
|
||||
|
||||
class TfLiteInferenceCalculatorTest : public ::testing::Test {
|
||||
protected:
|
||||
std::unique_ptr<CalculatorRunner> runner_ = nullptr;
|
||||
};
|
||||
|
||||
// Tests a simple add model that adds an input tensor to itself.
|
||||
TEST_F(TfLiteInferenceCalculatorTest, SmokeTest) {
|
||||
void DoSmokeTest(const std::string& graph_proto) {
|
||||
const int width = 8;
|
||||
const int height = 8;
|
||||
const int channels = 3;
|
||||
@@ -75,21 +71,7 @@ TEST_F(TfLiteInferenceCalculatorTest, SmokeTest) {
|
||||
|
||||
// Prepare single calculator graph to and wait for packets.
|
||||
CalculatorGraphConfig graph_config =
|
||||
::mediapipe::ParseTextProtoOrDie<CalculatorGraphConfig>(
|
||||
R"(
|
||||
input_stream: "tensor_in"
|
||||
node {
|
||||
calculator: "TfLiteInferenceCalculator"
|
||||
input_stream: "TENSORS:tensor_in"
|
||||
output_stream: "TENSORS:tensor_out"
|
||||
options {
|
||||
[mediapipe.TfLiteInferenceCalculatorOptions.ext] {
|
||||
use_gpu: false
|
||||
model_path: "mediapipe/calculators/tflite/testdata/add.bin"
|
||||
}
|
||||
}
|
||||
}
|
||||
)");
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig>(graph_proto);
|
||||
std::vector<Packet> output_packets;
|
||||
tool::AddVectorSink("tensor_out", &graph_config, &output_packets);
|
||||
CalculatorGraph graph(graph_config);
|
||||
@@ -120,4 +102,72 @@ TEST_F(TfLiteInferenceCalculatorTest, SmokeTest) {
|
||||
MP_ASSERT_OK(graph.WaitUntilDone());
|
||||
}
|
||||
|
||||
// Tests a simple add model that adds an input tensor to itself.
|
||||
TEST(TfLiteInferenceCalculatorTest, SmokeTest) {
|
||||
std::string graph_proto = R"(
|
||||
input_stream: "tensor_in"
|
||||
node {
|
||||
calculator: "TfLiteInferenceCalculator"
|
||||
input_stream: "TENSORS:tensor_in"
|
||||
output_stream: "TENSORS:tensor_out"
|
||||
options {
|
||||
[mediapipe.TfLiteInferenceCalculatorOptions.ext] {
|
||||
model_path: "mediapipe/calculators/tflite/testdata/add.bin"
|
||||
$delegate
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
DoSmokeTest(
|
||||
/*graph_proto=*/absl::StrReplaceAll(graph_proto, {{"$delegate", ""}}));
|
||||
DoSmokeTest(/*graph_proto=*/absl::StrReplaceAll(
|
||||
graph_proto, {{"$delegate", "delegate { tflite {} }"}}));
|
||||
DoSmokeTest(/*graph_proto=*/absl::StrReplaceAll(
|
||||
graph_proto, {{"$delegate", "delegate { xnnpack {} }"}}));
|
||||
DoSmokeTest(/*graph_proto=*/absl::StrReplaceAll(
|
||||
graph_proto,
|
||||
{{"$delegate", "delegate { xnnpack { num_threads: 10 } }"}}));
|
||||
}
|
||||
|
||||
TEST(TfLiteInferenceCalculatorTest, SmokeTest_ModelAsInputSidePacket) {
|
||||
std::string graph_proto = R"(
|
||||
input_stream: "tensor_in"
|
||||
|
||||
node {
|
||||
calculator: "ConstantSidePacketCalculator"
|
||||
output_side_packet: "PACKET:model_path"
|
||||
options: {
|
||||
[mediapipe.ConstantSidePacketCalculatorOptions.ext]: {
|
||||
packet { string_value: "mediapipe/calculators/tflite/testdata/add.bin" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node {
|
||||
calculator: "LocalFileContentsCalculator"
|
||||
input_side_packet: "FILE_PATH:model_path"
|
||||
output_side_packet: "CONTENTS:model_blob"
|
||||
}
|
||||
|
||||
node {
|
||||
calculator: "TfLiteModelCalculator"
|
||||
input_side_packet: "MODEL_BLOB:model_blob"
|
||||
output_side_packet: "MODEL:model"
|
||||
}
|
||||
|
||||
node {
|
||||
calculator: "TfLiteInferenceCalculator"
|
||||
input_stream: "TENSORS:tensor_in"
|
||||
output_stream: "TENSORS:tensor_out"
|
||||
input_side_packet: "MODEL:model"
|
||||
options {
|
||||
[mediapipe.TfLiteInferenceCalculatorOptions.ext] {
|
||||
use_gpu: false
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
DoSmokeTest(graph_proto);
|
||||
}
|
||||
|
||||
} // namespace mediapipe
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2020 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/packet.h"
|
||||
#include "mediapipe/framework/port/ret_check.h"
|
||||
#include "tensorflow/lite/model.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
// Loads TfLite model from model blob specified as input side packet and outputs
|
||||
// corresponding side packet.
|
||||
//
|
||||
// Input side packets:
|
||||
// MODEL_BLOB - TfLite model blob/file-contents (std::string). You can read
|
||||
// model blob from file (using whatever APIs you have) and pass
|
||||
// it to the graph as input side packet or you can use some of
|
||||
// calculators like LocalFileContentsCalculator to get model
|
||||
// blob and use it as input here.
|
||||
//
|
||||
// Output side packets:
|
||||
// MODEL - TfLite model. (std::unique_ptr<tflite::FlatBufferModel,
|
||||
// std::function<void(tflite::FlatBufferModel*)>>)
|
||||
//
|
||||
// Example use:
|
||||
//
|
||||
// node {
|
||||
// calculator: "TfLiteModelCalculator"
|
||||
// input_side_packet: "MODEL_BLOB:model_blob"
|
||||
// output_side_packet: "MODEL:model"
|
||||
// }
|
||||
//
|
||||
class TfLiteModelCalculator : public CalculatorBase {
|
||||
public:
|
||||
using TfLiteModelPtr =
|
||||
std::unique_ptr<tflite::FlatBufferModel,
|
||||
std::function<void(tflite::FlatBufferModel*)>>;
|
||||
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
|
||||
cc->InputSidePackets().Tag("MODEL_BLOB").Set<std::string>();
|
||||
cc->OutputSidePackets().Tag("MODEL").Set<TfLiteModelPtr>();
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status Open(CalculatorContext* cc) override {
|
||||
const Packet& model_packet = cc->InputSidePackets().Tag("MODEL_BLOB");
|
||||
const std::string& model_blob = model_packet.Get<std::string>();
|
||||
std::unique_ptr<tflite::FlatBufferModel> model =
|
||||
tflite::FlatBufferModel::BuildFromBuffer(model_blob.data(),
|
||||
model_blob.size());
|
||||
RET_CHECK(model) << "Failed to load TfLite model from blob.";
|
||||
|
||||
cc->OutputSidePackets().Tag("MODEL").Set(
|
||||
MakePacket<TfLiteModelPtr>(TfLiteModelPtr(
|
||||
model.release(), [model_packet](tflite::FlatBufferModel* model) {
|
||||
// Keeping model_packet in order to keep underlying model blob
|
||||
// which can be released only after TfLite model is not needed
|
||||
// anymore (deleted).
|
||||
delete model;
|
||||
})));
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status Process(CalculatorContext* cc) override {
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
};
|
||||
REGISTER_CALCULATOR(TfLiteModelCalculator);
|
||||
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright 2020 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/calculator_runner.h"
|
||||
#include "mediapipe/framework/port/gmock.h"
|
||||
#include "mediapipe/framework/port/gtest.h"
|
||||
#include "mediapipe/framework/port/parse_text_proto.h"
|
||||
#include "mediapipe/framework/port/status_matchers.h" // NOLINT
|
||||
#include "tensorflow/lite/model.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
TEST(TfLiteModelCalculatorTest, SmokeTest) {
|
||||
// Prepare single calculator graph to and wait for packets.
|
||||
CalculatorGraphConfig graph_config = ParseTextProtoOrDie<
|
||||
CalculatorGraphConfig>(
|
||||
R"(
|
||||
node {
|
||||
calculator: "ConstantSidePacketCalculator"
|
||||
output_side_packet: "PACKET:model_path"
|
||||
options: {
|
||||
[mediapipe.ConstantSidePacketCalculatorOptions.ext]: {
|
||||
packet {
|
||||
string_value: "mediapipe/calculators/tflite/testdata/add.bin"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node {
|
||||
calculator: "LocalFileContentsCalculator"
|
||||
input_side_packet: "FILE_PATH:model_path"
|
||||
output_side_packet: "CONTENTS:model_blob"
|
||||
}
|
||||
|
||||
node {
|
||||
calculator: "TfLiteModelCalculator"
|
||||
input_side_packet: "MODEL_BLOB:model_blob"
|
||||
output_side_packet: "MODEL:model"
|
||||
}
|
||||
)");
|
||||
CalculatorGraph graph(graph_config);
|
||||
MP_ASSERT_OK(graph.StartRun({}));
|
||||
MP_ASSERT_OK(graph.WaitUntilIdle());
|
||||
auto status_or_packet = graph.GetOutputSidePacket("model");
|
||||
MP_ASSERT_OK(status_or_packet);
|
||||
auto model_packet = status_or_packet.ValueOrDie();
|
||||
const auto& model = model_packet.Get<
|
||||
std::unique_ptr<tflite::FlatBufferModel,
|
||||
std::function<void(tflite::FlatBufferModel*)>>>();
|
||||
|
||||
auto expected_model = tflite::FlatBufferModel::BuildFromFile(
|
||||
"mediapipe/calculators/tflite/testdata/add.bin");
|
||||
|
||||
EXPECT_EQ(model->GetModel()->version(),
|
||||
expected_model->GetModel()->version());
|
||||
EXPECT_EQ(model->GetModel()->buffers()->size(),
|
||||
expected_model->GetModel()->buffers()->size());
|
||||
const int num_subgraphs = expected_model->GetModel()->subgraphs()->size();
|
||||
EXPECT_EQ(model->GetModel()->subgraphs()->size(), num_subgraphs);
|
||||
for (int i = 0; i < num_subgraphs; ++i) {
|
||||
const auto* expected_subgraph =
|
||||
expected_model->GetModel()->subgraphs()->Get(i);
|
||||
const auto* subgraph = model->GetModel()->subgraphs()->Get(i);
|
||||
const int num_tensors = expected_subgraph->tensors()->size();
|
||||
EXPECT_EQ(subgraph->tensors()->size(), num_tensors);
|
||||
for (int j = 0; j < num_tensors; ++j) {
|
||||
EXPECT_EQ(subgraph->tensors()->Get(j)->name()->str(),
|
||||
expected_subgraph->tensors()->Get(j)->name()->str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mediapipe
|
||||
@@ -129,22 +129,43 @@ REGISTER_CALCULATOR(TfLiteTensorsToClassificationCalculator);
|
||||
num_classes *= raw_score_tensor->dims->data[i];
|
||||
}
|
||||
|
||||
if (options_.binary_classification()) {
|
||||
RET_CHECK_EQ(num_classes, 1);
|
||||
// Number of classes for binary classification.
|
||||
num_classes = 2;
|
||||
}
|
||||
if (label_map_loaded_) {
|
||||
RET_CHECK_EQ(num_classes, label_map_.size());
|
||||
}
|
||||
const float* raw_scores = raw_score_tensor->data.f;
|
||||
|
||||
auto classification_list = absl::make_unique<ClassificationList>();
|
||||
for (int i = 0; i < num_classes; ++i) {
|
||||
if (options_.has_min_score_threshold() &&
|
||||
raw_scores[i] < options_.min_score_threshold()) {
|
||||
continue;
|
||||
}
|
||||
Classification* classification = classification_list->add_classification();
|
||||
classification->set_index(i);
|
||||
classification->set_score(raw_scores[i]);
|
||||
if (options_.binary_classification()) {
|
||||
Classification* class_first = classification_list->add_classification();
|
||||
Classification* class_second = classification_list->add_classification();
|
||||
class_first->set_index(0);
|
||||
class_second->set_index(1);
|
||||
class_first->set_score(raw_scores[0]);
|
||||
class_second->set_score(1. - raw_scores[0]);
|
||||
|
||||
if (label_map_loaded_) {
|
||||
classification->set_label(label_map_[i]);
|
||||
class_first->set_label(label_map_[0]);
|
||||
class_second->set_label(label_map_[1]);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < num_classes; ++i) {
|
||||
if (options_.has_min_score_threshold() &&
|
||||
raw_scores[i] < options_.min_score_threshold()) {
|
||||
continue;
|
||||
}
|
||||
Classification* classification =
|
||||
classification_list->add_classification();
|
||||
classification->set_index(i);
|
||||
classification->set_score(raw_scores[i]);
|
||||
|
||||
if (label_map_loaded_) {
|
||||
classification->set_label(label_map_[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,4 +32,10 @@ message TfLiteTensorsToClassificationCalculatorOptions {
|
||||
optional int32 top_k = 2;
|
||||
// Path to a label map file for getting the actual name of class ids.
|
||||
optional string label_map_path = 3;
|
||||
// Whether the input is a single float for binary classification.
|
||||
// When true, only a single float is expected in the input tensor and the
|
||||
// label map, if provided, is expected to have exactly two labels.
|
||||
// The single score(float) represent the probability of first label, and
|
||||
// 1 - score is the probabilility of the second label.
|
||||
optional bool binary_classification = 4;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,21 @@ namespace mediapipe {
|
||||
// TENSORS - Vector of TfLiteTensor of type kTfLiteFloat32. Only the first
|
||||
// tensor will be used. The size of the values must be
|
||||
// (num_dimension x num_landmarks).
|
||||
//
|
||||
// FLIP_HORIZONTALLY (optional): Whether to flip landmarks horizontally or
|
||||
// not. Overrides corresponding side packet and/or field in the calculator
|
||||
// options.
|
||||
//
|
||||
// FLIP_VERTICALLY (optional): Whether to flip landmarks vertically or not.
|
||||
// Overrides corresponding side packet and/or field in the calculator options.
|
||||
//
|
||||
// Input side packet:
|
||||
// FLIP_HORIZONTALLY (optional): Whether to flip landmarks horizontally or
|
||||
// not. Overrides the corresponding field in the calculator options.
|
||||
//
|
||||
// FLIP_VERTICALLY (optional): Whether to flip landmarks vertically or not.
|
||||
// Overrides the corresponding field in the calculator options.
|
||||
//
|
||||
// Output:
|
||||
// LANDMARKS(optional) - Result MediaPipe landmarks.
|
||||
// NORM_LANDMARKS(optional) - Result MediaPipe normalized landmarks.
|
||||
@@ -61,6 +76,8 @@ class TfLiteTensorsToLandmarksCalculator : public CalculatorBase {
|
||||
private:
|
||||
::mediapipe::Status LoadOptions(CalculatorContext* cc);
|
||||
int num_landmarks_ = 0;
|
||||
bool flip_vertically_ = false;
|
||||
bool flip_horizontally_ = false;
|
||||
|
||||
::mediapipe::TfLiteTensorsToLandmarksCalculatorOptions options_;
|
||||
};
|
||||
@@ -75,6 +92,22 @@ REGISTER_CALCULATOR(TfLiteTensorsToLandmarksCalculator);
|
||||
cc->Inputs().Tag("TENSORS").Set<std::vector<TfLiteTensor>>();
|
||||
}
|
||||
|
||||
if (cc->Inputs().HasTag("FLIP_HORIZONTALLY")) {
|
||||
cc->Inputs().Tag("FLIP_HORIZONTALLY").Set<bool>();
|
||||
}
|
||||
|
||||
if (cc->Inputs().HasTag("FLIP_VERTICALLY")) {
|
||||
cc->Inputs().Tag("FLIP_VERTICALLY").Set<bool>();
|
||||
}
|
||||
|
||||
if (cc->InputSidePackets().HasTag("FLIP_HORIZONTALLY")) {
|
||||
cc->InputSidePackets().Tag("FLIP_HORIZONTALLY").Set<bool>();
|
||||
}
|
||||
|
||||
if (cc->InputSidePackets().HasTag("FLIP_VERTICALLY")) {
|
||||
cc->InputSidePackets().Tag("FLIP_VERTICALLY").Set<bool>();
|
||||
}
|
||||
|
||||
if (cc->Outputs().HasTag("LANDMARKS")) {
|
||||
cc->Outputs().Tag("LANDMARKS").Set<LandmarkList>();
|
||||
}
|
||||
@@ -98,17 +131,40 @@ REGISTER_CALCULATOR(TfLiteTensorsToLandmarksCalculator);
|
||||
<< "Must provide input with/height for getting normalized landmarks.";
|
||||
}
|
||||
if (cc->Outputs().HasTag("LANDMARKS") &&
|
||||
(options_.flip_vertically() || options_.flip_horizontally())) {
|
||||
(options_.flip_vertically() || options_.flip_horizontally() ||
|
||||
cc->InputSidePackets().HasTag("FLIP_HORIZONTALLY") ||
|
||||
cc->InputSidePackets().HasTag("FLIP_VERTICALLY"))) {
|
||||
RET_CHECK(options_.has_input_image_height() &&
|
||||
options_.has_input_image_width())
|
||||
<< "Must provide input with/height for using flip_vertically option "
|
||||
"when outputing landmarks in absolute coordinates.";
|
||||
}
|
||||
|
||||
flip_horizontally_ =
|
||||
cc->InputSidePackets().HasTag("FLIP_HORIZONTALLY")
|
||||
? cc->InputSidePackets().Tag("FLIP_HORIZONTALLY").Get<bool>()
|
||||
: options_.flip_horizontally();
|
||||
|
||||
flip_vertically_ =
|
||||
cc->InputSidePackets().HasTag("FLIP_VERTICALLY")
|
||||
? cc->InputSidePackets().Tag("FLIP_VERTICALLY").Get<bool>()
|
||||
: options_.flip_vertically();
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status TfLiteTensorsToLandmarksCalculator::Process(
|
||||
CalculatorContext* cc) {
|
||||
// Override values if specified so.
|
||||
if (cc->Inputs().HasTag("FLIP_HORIZONTALLY") &&
|
||||
!cc->Inputs().Tag("FLIP_HORIZONTALLY").IsEmpty()) {
|
||||
flip_horizontally_ = cc->Inputs().Tag("FLIP_HORIZONTALLY").Get<bool>();
|
||||
}
|
||||
if (cc->Inputs().HasTag("FLIP_VERTICALLY") &&
|
||||
!cc->Inputs().Tag("FLIP_VERTICALLY").IsEmpty()) {
|
||||
flip_vertically_ = cc->Inputs().Tag("FLIP_VERTICALLY").Get<bool>();
|
||||
}
|
||||
|
||||
if (cc->Inputs().Tag("TENSORS").IsEmpty()) {
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
@@ -133,13 +189,13 @@ REGISTER_CALCULATOR(TfLiteTensorsToLandmarksCalculator);
|
||||
const int offset = ld * num_dimensions;
|
||||
Landmark* landmark = output_landmarks.add_landmark();
|
||||
|
||||
if (options_.flip_horizontally()) {
|
||||
if (flip_horizontally_) {
|
||||
landmark->set_x(options_.input_image_width() - raw_landmarks[offset]);
|
||||
} else {
|
||||
landmark->set_x(raw_landmarks[offset]);
|
||||
}
|
||||
if (num_dimensions > 1) {
|
||||
if (options_.flip_vertically()) {
|
||||
if (flip_vertically_) {
|
||||
landmark->set_y(options_.input_image_height() -
|
||||
raw_landmarks[offset + 1]);
|
||||
} else {
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
#ifndef MEDIAPIPE_CALCULATORS_TFLITE_UTIL_H_
|
||||
#define MEDIAPIPE_CALCULATORS_TFLITE_UTIL_H_
|
||||
|
||||
#define RET_CHECK_CALL(call) \
|
||||
do { \
|
||||
const auto status = (call); \
|
||||
if (ABSL_PREDICT_FALSE(!status.ok())) \
|
||||
return ::mediapipe::InternalError(status.error_message()); \
|
||||
#define RET_CHECK_CALL(call) \
|
||||
do { \
|
||||
const auto status = (call); \
|
||||
if (ABSL_PREDICT_FALSE(!status.ok())) \
|
||||
return ::mediapipe::InternalError(status.message()); \
|
||||
} while (0);
|
||||
|
||||
#endif // MEDIAPIPE_CALCULATORS_TFLITE_UTIL_H_
|
||||
|
||||
@@ -39,6 +39,15 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "timed_box_list_id_to_label_calculator_proto",
|
||||
srcs = ["timed_box_list_id_to_label_calculator.proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//mediapipe/framework:calculator_proto",
|
||||
],
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "latency_proto",
|
||||
srcs = ["latency.proto"],
|
||||
@@ -113,6 +122,18 @@ mediapipe_cc_proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_cc_proto_library(
|
||||
name = "timed_box_list_id_to_label_calculator_cc_proto",
|
||||
srcs = ["timed_box_list_id_to_label_calculator.proto"],
|
||||
cc_deps = [
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":timed_box_list_id_to_label_calculator_proto",
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_cc_proto_library(
|
||||
name = "latency_cc_proto",
|
||||
srcs = ["latency.proto"],
|
||||
@@ -300,7 +321,35 @@ cc_library(
|
||||
"//mediapipe:android": [
|
||||
"//mediapipe/util/android/file/base",
|
||||
],
|
||||
"//mediapipe:apple": [
|
||||
"//mediapipe:ios": [
|
||||
"//mediapipe/util/android/file/base",
|
||||
],
|
||||
"//mediapipe:macos": [
|
||||
"//mediapipe/framework/port:file_helpers",
|
||||
],
|
||||
"//conditions:default": [
|
||||
"//mediapipe/framework/port:file_helpers",
|
||||
],
|
||||
}),
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "timed_box_list_id_to_label_calculator",
|
||||
srcs = ["timed_box_list_id_to_label_calculator.cc"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":timed_box_list_id_to_label_calculator_cc_proto",
|
||||
"//mediapipe/framework/port:status",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:packet",
|
||||
"//mediapipe/util/tracking:box_tracker_cc_proto",
|
||||
"//mediapipe/util:resource_util",
|
||||
] + select({
|
||||
"//mediapipe:android": [
|
||||
"//mediapipe/util/android/file/base",
|
||||
],
|
||||
"//mediapipe:ios": [
|
||||
"//mediapipe/util/android/file/base",
|
||||
],
|
||||
"//mediapipe:macos": [
|
||||
@@ -437,6 +486,7 @@ cc_library(
|
||||
"//mediapipe/framework/formats:rect_cc_proto",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
"@com_google_absl//absl/types:optional",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
@@ -876,7 +926,7 @@ cc_library(
|
||||
"//mediapipe:android": [
|
||||
"//mediapipe/util/android/file/base",
|
||||
],
|
||||
"//mediapipe:apple": [
|
||||
"//mediapipe:ios": [
|
||||
"//mediapipe/util/android/file/base",
|
||||
],
|
||||
"//mediapipe:macos": [
|
||||
@@ -919,6 +969,19 @@ cc_library(
|
||||
name = "local_file_contents_calculator",
|
||||
srcs = ["local_file_contents_calculator.cc"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
"//mediapipe/util:resource_util",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "local_file_pattern_contents_calculator",
|
||||
srcs = ["local_file_pattern_contents_calculator.cc"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/port:file_helpers",
|
||||
@@ -935,6 +998,7 @@ cc_library(
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/formats:classification_cc_proto",
|
||||
"//mediapipe/framework/formats:landmark_cc_proto",
|
||||
"//mediapipe/framework/formats:rect_cc_proto",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
@@ -952,6 +1016,7 @@ cc_library(
|
||||
deps = [
|
||||
":collection_has_min_size_calculator_cc_proto",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/formats:landmark_cc_proto",
|
||||
"//mediapipe/framework/formats:rect_cc_proto",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
@@ -959,6 +1024,18 @@ cc_library(
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "collection_has_min_size_calculator_test",
|
||||
srcs = ["collection_has_min_size_calculator_test.cc"],
|
||||
deps = [
|
||||
":collection_has_min_size_calculator",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:calculator_runner",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"//mediapipe/framework/port:parse_text_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "association_calculator",
|
||||
hdrs = ["association_calculator.h"],
|
||||
|
||||
@@ -39,13 +39,13 @@ namespace mediapipe {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kInputFrameTag[] = "INPUT_FRAME";
|
||||
constexpr char kOutputFrameTag[] = "OUTPUT_FRAME";
|
||||
constexpr char kInputFrameTag[] = "IMAGE";
|
||||
constexpr char kOutputFrameTag[] = "IMAGE";
|
||||
|
||||
constexpr char kInputVectorTag[] = "VECTOR";
|
||||
|
||||
constexpr char kInputFrameTagGpu[] = "INPUT_FRAME_GPU";
|
||||
constexpr char kOutputFrameTagGpu[] = "OUTPUT_FRAME_GPU";
|
||||
constexpr char kInputFrameTagGpu[] = "IMAGE_GPU";
|
||||
constexpr char kOutputFrameTagGpu[] = "IMAGE_GPU";
|
||||
|
||||
enum { ATTRIB_VERTEX, ATTRIB_TEXTURE_POSITION, NUM_ATTRIBUTES };
|
||||
|
||||
@@ -55,13 +55,13 @@ size_t RoundUp(size_t n, size_t m) { return ((n + m - 1) / m) * m; } // NOLINT
|
||||
// When using GPU, this color will become transparent when the calculator
|
||||
// merges the annotation overlay with the image frame. As a result, drawing in
|
||||
// this color is not supported and it should be set to something unlikely used.
|
||||
constexpr int kAnnotationBackgroundColor[] = {100, 101, 102};
|
||||
constexpr uchar kAnnotationBackgroundColor = 2; // Grayscale value.
|
||||
} // namespace
|
||||
|
||||
// A calculator for rendering data on images.
|
||||
//
|
||||
// Inputs:
|
||||
// 1. INPUT_FRAME or INPUT_FRAME_GPU (optional): An ImageFrame (or GpuBuffer)
|
||||
// 1. IMAGE or IMAGE_GPU (optional): An ImageFrame (or GpuBuffer)
|
||||
// containing the input image.
|
||||
// If output is CPU, and input isn't provided, the renderer creates a
|
||||
// blank canvas with the width, height and color provided in the options.
|
||||
@@ -73,7 +73,7 @@ constexpr int kAnnotationBackgroundColor[] = {100, 101, 102};
|
||||
// input vector items. These input streams are tagged with "VECTOR".
|
||||
//
|
||||
// Output:
|
||||
// 1. OUTPUT_FRAME or OUTPUT_FRAME_GPU: A rendered ImageFrame (or GpuBuffer).
|
||||
// 1. IMAGE or IMAGE_GPU: A rendered ImageFrame (or GpuBuffer).
|
||||
//
|
||||
// For CPU input frames, only SRGBA, SRGB and GRAY8 format are supported. The
|
||||
// output format is the same as input except for GRAY8 where the output is in
|
||||
@@ -87,13 +87,13 @@ constexpr int kAnnotationBackgroundColor[] = {100, 101, 102};
|
||||
// Example config (CPU):
|
||||
// node {
|
||||
// calculator: "AnnotationOverlayCalculator"
|
||||
// input_stream: "INPUT_FRAME:image_frames"
|
||||
// input_stream: "IMAGE:image_frames"
|
||||
// input_stream: "render_data_1"
|
||||
// input_stream: "render_data_2"
|
||||
// input_stream: "render_data_3"
|
||||
// input_stream: "VECTOR:0:render_data_vec_0"
|
||||
// input_stream: "VECTOR:1:render_data_vec_1"
|
||||
// output_stream: "OUTPUT_FRAME:decorated_frames"
|
||||
// output_stream: "IMAGE:decorated_frames"
|
||||
// options {
|
||||
// [mediapipe.AnnotationOverlayCalculatorOptions.ext] {
|
||||
// }
|
||||
@@ -103,13 +103,13 @@ constexpr int kAnnotationBackgroundColor[] = {100, 101, 102};
|
||||
// Example config (GPU):
|
||||
// node {
|
||||
// calculator: "AnnotationOverlayCalculator"
|
||||
// input_stream: "INPUT_FRAME_GPU:image_frames"
|
||||
// input_stream: "IMAGE_GPU:image_frames"
|
||||
// input_stream: "render_data_1"
|
||||
// input_stream: "render_data_2"
|
||||
// input_stream: "render_data_3"
|
||||
// input_stream: "VECTOR:0:render_data_vec_0"
|
||||
// input_stream: "VECTOR:1:render_data_vec_1"
|
||||
// output_stream: "OUTPUT_FRAME_GPU:decorated_frames"
|
||||
// output_stream: "IMAGE_GPU:decorated_frames"
|
||||
// options {
|
||||
// [mediapipe.AnnotationOverlayCalculatorOptions.ext] {
|
||||
// }
|
||||
@@ -491,11 +491,9 @@ REGISTER_CALCULATOR(AnnotationOverlayCalculator);
|
||||
if (format != mediapipe::ImageFormat::SRGBA &&
|
||||
format != mediapipe::ImageFormat::SRGB)
|
||||
RET_CHECK_FAIL() << "Unsupported GPU input format: " << format;
|
||||
|
||||
image_mat = absl::make_unique<cv::Mat>(
|
||||
height_, width_, CV_8UC3,
|
||||
cv::Scalar(kAnnotationBackgroundColor[0], kAnnotationBackgroundColor[1],
|
||||
kAnnotationBackgroundColor[2]));
|
||||
image_mat = absl::make_unique<cv::Mat>(height_, width_, CV_8UC3);
|
||||
memset(image_mat->data, kAnnotationBackgroundColor,
|
||||
height_ * width_ * image_mat->elemSize());
|
||||
} else {
|
||||
image_mat = absl::make_unique<cv::Mat>(
|
||||
options_.canvas_height_px(), options_.canvas_width_px(), CV_8UC3,
|
||||
@@ -617,9 +615,9 @@ REGISTER_CALCULATOR(AnnotationOverlayCalculator);
|
||||
glUniform1i(glGetUniformLocation(program_, "input_frame"), 1);
|
||||
glUniform1i(glGetUniformLocation(program_, "overlay"), 2);
|
||||
glUniform3f(glGetUniformLocation(program_, "transparent_color"),
|
||||
kAnnotationBackgroundColor[0] / 255.0,
|
||||
kAnnotationBackgroundColor[1] / 255.0,
|
||||
kAnnotationBackgroundColor[2] / 255.0);
|
||||
kAnnotationBackgroundColor / 255.0,
|
||||
kAnnotationBackgroundColor / 255.0,
|
||||
kAnnotationBackgroundColor / 255.0);
|
||||
|
||||
// Init texture for opencv rendered frame.
|
||||
const auto& input_frame =
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
|
||||
#include "mediapipe/calculators/util/collection_has_min_size_calculator.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "mediapipe/framework/formats/landmark.pb.h"
|
||||
#include "mediapipe/framework/formats/rect.pb.h"
|
||||
|
||||
namespace mediapipe {
|
||||
@@ -23,4 +26,9 @@ typedef CollectionHasMinSizeCalculator<std::vector<::mediapipe::NormalizedRect>>
|
||||
NormalizedRectVectorHasMinSizeCalculator;
|
||||
REGISTER_CALCULATOR(NormalizedRectVectorHasMinSizeCalculator);
|
||||
|
||||
typedef CollectionHasMinSizeCalculator<
|
||||
std::vector<::mediapipe::NormalizedLandmarkList>>
|
||||
NormalizedLandmarkListVectorHasMinSizeCalculator;
|
||||
REGISTER_CALCULATOR(NormalizedLandmarkListVectorHasMinSizeCalculator);
|
||||
|
||||
} // namespace mediapipe
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright 2020 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/calculators/util/collection_has_min_size_calculator.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/calculator_runner.h"
|
||||
#include "mediapipe/framework/port/gmock.h"
|
||||
#include "mediapipe/framework/port/gtest.h"
|
||||
#include "mediapipe/framework/port/parse_text_proto.h"
|
||||
#include "mediapipe/framework/port/status_matchers.h" // NOLINT
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
typedef CollectionHasMinSizeCalculator<std::vector<int>>
|
||||
TestIntCollectionHasMinSizeCalculator;
|
||||
REGISTER_CALCULATOR(TestIntCollectionHasMinSizeCalculator);
|
||||
|
||||
void AddInputVector(const std::vector<int>& input, int64 timestamp,
|
||||
CalculatorRunner* runner) {
|
||||
runner->MutableInputs()
|
||||
->Tag("ITERABLE")
|
||||
.packets.push_back(
|
||||
MakePacket<std::vector<int>>(input).At(Timestamp(timestamp)));
|
||||
}
|
||||
|
||||
TEST(TestIntCollectionHasMinSizeCalculator, DoesHaveMinSize) {
|
||||
CalculatorGraphConfig::Node node_config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"(
|
||||
calculator: "TestIntCollectionHasMinSizeCalculator"
|
||||
input_stream: "ITERABLE:input_vector"
|
||||
output_stream: "output_vector"
|
||||
options {
|
||||
[mediapipe.CollectionHasMinSizeCalculatorOptions.ext] { min_size: 2 }
|
||||
}
|
||||
)");
|
||||
CalculatorRunner runner(node_config);
|
||||
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
|
||||
|
||||
AddInputVector({1, 2}, /*timestamp=*/1, &runner);
|
||||
MP_ASSERT_OK(runner.Run());
|
||||
|
||||
EXPECT_EQ(1, outputs.size());
|
||||
EXPECT_EQ(Timestamp(1), outputs[0].Timestamp());
|
||||
EXPECT_TRUE(outputs[0].Get<bool>());
|
||||
|
||||
AddInputVector({1, 2, 3}, /*timestamp=*/2, &runner);
|
||||
MP_ASSERT_OK(runner.Run());
|
||||
|
||||
EXPECT_EQ(2, outputs.size());
|
||||
EXPECT_EQ(Timestamp(2), outputs[1].Timestamp());
|
||||
EXPECT_TRUE(outputs[1].Get<bool>());
|
||||
}
|
||||
|
||||
TEST(TestIntCollectionHasMinSizeCalculator,
|
||||
DoesHaveMinSize_MinSizeAsSidePacket) {
|
||||
CalculatorGraphConfig::Node node_config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"(
|
||||
calculator: "TestIntCollectionHasMinSizeCalculator"
|
||||
input_stream: "ITERABLE:input_vector"
|
||||
input_side_packet: "min_size"
|
||||
output_stream: "output_vector"
|
||||
)");
|
||||
CalculatorRunner runner(node_config);
|
||||
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
|
||||
|
||||
runner.MutableSidePackets()->Index(0) = MakePacket<int>(2);
|
||||
|
||||
AddInputVector({1, 2}, /*timestamp=*/1, &runner);
|
||||
MP_ASSERT_OK(runner.Run());
|
||||
|
||||
EXPECT_EQ(1, outputs.size());
|
||||
EXPECT_EQ(Timestamp(1), outputs[0].Timestamp());
|
||||
EXPECT_TRUE(outputs[0].Get<bool>());
|
||||
|
||||
AddInputVector({1, 2, 3}, /*timestamp=*/2, &runner);
|
||||
MP_ASSERT_OK(runner.Run());
|
||||
|
||||
EXPECT_EQ(2, outputs.size());
|
||||
EXPECT_EQ(Timestamp(2), outputs[1].Timestamp());
|
||||
EXPECT_TRUE(outputs[1].Get<bool>());
|
||||
}
|
||||
|
||||
TEST(TestIntCollectionHasMinSizeCalculator, DoesNotHaveMinSize) {
|
||||
CalculatorGraphConfig::Node node_config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"(
|
||||
calculator: "TestIntCollectionHasMinSizeCalculator"
|
||||
input_stream: "ITERABLE:input_vector"
|
||||
output_stream: "output_vector"
|
||||
options {
|
||||
[mediapipe.CollectionHasMinSizeCalculatorOptions.ext] { min_size: 3 }
|
||||
}
|
||||
)");
|
||||
CalculatorRunner runner(node_config);
|
||||
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
|
||||
|
||||
AddInputVector({1}, /*timestamp=*/1, &runner);
|
||||
MP_ASSERT_OK(runner.Run());
|
||||
|
||||
EXPECT_EQ(1, outputs.size());
|
||||
EXPECT_EQ(Timestamp(1), outputs[0].Timestamp());
|
||||
EXPECT_FALSE(outputs[0].Get<bool>());
|
||||
|
||||
AddInputVector({1, 2}, /*timestamp=*/2, &runner);
|
||||
MP_ASSERT_OK(runner.Run());
|
||||
|
||||
EXPECT_EQ(2, outputs.size());
|
||||
EXPECT_EQ(Timestamp(2), outputs[1].Timestamp());
|
||||
EXPECT_FALSE(outputs[1].Get<bool>());
|
||||
}
|
||||
|
||||
TEST(TestIntCollectionHasMinSizeCalculator,
|
||||
DoesNotHaveMinSize_MinSizeAsSidePacket) {
|
||||
CalculatorGraphConfig::Node node_config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"(
|
||||
calculator: "TestIntCollectionHasMinSizeCalculator"
|
||||
input_stream: "ITERABLE:input_vector"
|
||||
input_side_packet: "min_size"
|
||||
output_stream: "output_vector"
|
||||
)");
|
||||
CalculatorRunner runner(node_config);
|
||||
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
|
||||
|
||||
runner.MutableSidePackets()->Index(0) = MakePacket<int>(3);
|
||||
|
||||
AddInputVector({1}, /*timestamp=*/1, &runner);
|
||||
MP_ASSERT_OK(runner.Run());
|
||||
|
||||
EXPECT_EQ(1, outputs.size());
|
||||
EXPECT_EQ(Timestamp(1), outputs[0].Timestamp());
|
||||
EXPECT_FALSE(outputs[0].Get<bool>());
|
||||
|
||||
AddInputVector({1, 2}, /*timestamp=*/2, &runner);
|
||||
MP_ASSERT_OK(runner.Run());
|
||||
|
||||
EXPECT_EQ(2, outputs.size());
|
||||
EXPECT_EQ(Timestamp(2), outputs[1].Timestamp());
|
||||
EXPECT_FALSE(outputs[1].Get<bool>());
|
||||
}
|
||||
|
||||
} // namespace mediapipe
|
||||
@@ -12,10 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe//framework/packet.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/resource_util.h"
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@ constexpr char kNormRectsTag[] = "NORM_RECTS";
|
||||
} // namespace
|
||||
|
||||
::mediapipe::Status DetectionsToRectsCalculator::DetectionToRect(
|
||||
const Detection& detection, Rect* rect) {
|
||||
const Detection& detection, const DetectionSpec& detection_spec,
|
||||
Rect* rect) {
|
||||
const LocationData location_data = detection.location_data();
|
||||
RET_CHECK(location_data.format() == LocationData::BOUNDING_BOX)
|
||||
<< "Only Detection with formats of BOUNDING_BOX can be converted to Rect";
|
||||
@@ -52,7 +53,8 @@ constexpr char kNormRectsTag[] = "NORM_RECTS";
|
||||
}
|
||||
|
||||
::mediapipe::Status DetectionsToRectsCalculator::DetectionToNormalizedRect(
|
||||
const Detection& detection, NormalizedRect* rect) {
|
||||
const Detection& detection, const DetectionSpec& detection_spec,
|
||||
NormalizedRect* rect) {
|
||||
const LocationData location_data = detection.location_data();
|
||||
RET_CHECK(location_data.format() == LocationData::RELATIVE_BOUNDING_BOX)
|
||||
<< "Only Detection with formats of RELATIVE_BOUNDING_BOX can be "
|
||||
@@ -174,27 +176,31 @@ constexpr char kNormRectsTag[] = "NORM_RECTS";
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<int, int> image_size;
|
||||
if (rotate_) {
|
||||
RET_CHECK(!cc->Inputs().Tag(kImageSizeTag).IsEmpty());
|
||||
image_size = cc->Inputs().Tag(kImageSizeTag).Get<std::pair<int, int>>();
|
||||
}
|
||||
// Get dynamic calculator options (e.g. `image_size`).
|
||||
const DetectionSpec detection_spec = GetDetectionSpec(cc);
|
||||
|
||||
if (cc->Outputs().HasTag(kRectTag)) {
|
||||
auto output_rect = absl::make_unique<Rect>();
|
||||
MP_RETURN_IF_ERROR(DetectionToRect(detections[0], output_rect.get()));
|
||||
MP_RETURN_IF_ERROR(
|
||||
DetectionToRect(detections[0], detection_spec, output_rect.get()));
|
||||
if (rotate_) {
|
||||
output_rect->set_rotation(ComputeRotation(detections[0], image_size));
|
||||
float rotation;
|
||||
MP_RETURN_IF_ERROR(
|
||||
ComputeRotation(detections[0], detection_spec, &rotation));
|
||||
output_rect->set_rotation(rotation);
|
||||
}
|
||||
cc->Outputs().Tag(kRectTag).Add(output_rect.release(),
|
||||
cc->InputTimestamp());
|
||||
}
|
||||
if (cc->Outputs().HasTag(kNormRectTag)) {
|
||||
auto output_rect = absl::make_unique<NormalizedRect>();
|
||||
MP_RETURN_IF_ERROR(
|
||||
DetectionToNormalizedRect(detections[0], output_rect.get()));
|
||||
MP_RETURN_IF_ERROR(DetectionToNormalizedRect(detections[0], detection_spec,
|
||||
output_rect.get()));
|
||||
if (rotate_) {
|
||||
output_rect->set_rotation(ComputeRotation(detections[0], image_size));
|
||||
float rotation;
|
||||
MP_RETURN_IF_ERROR(
|
||||
ComputeRotation(detections[0], detection_spec, &rotation));
|
||||
output_rect->set_rotation(rotation);
|
||||
}
|
||||
cc->Outputs()
|
||||
.Tag(kNormRectTag)
|
||||
@@ -203,11 +209,13 @@ constexpr char kNormRectsTag[] = "NORM_RECTS";
|
||||
if (cc->Outputs().HasTag(kRectsTag)) {
|
||||
auto output_rects = absl::make_unique<std::vector<Rect>>(detections.size());
|
||||
for (int i = 0; i < detections.size(); ++i) {
|
||||
MP_RETURN_IF_ERROR(
|
||||
DetectionToRect(detections[i], &(output_rects->at(i))));
|
||||
MP_RETURN_IF_ERROR(DetectionToRect(detections[i], detection_spec,
|
||||
&(output_rects->at(i))));
|
||||
if (rotate_) {
|
||||
output_rects->at(i).set_rotation(
|
||||
ComputeRotation(detections[i], image_size));
|
||||
float rotation;
|
||||
MP_RETURN_IF_ERROR(
|
||||
ComputeRotation(detections[i], detection_spec, &rotation));
|
||||
output_rects->at(i).set_rotation(rotation);
|
||||
}
|
||||
}
|
||||
cc->Outputs().Tag(kRectsTag).Add(output_rects.release(),
|
||||
@@ -217,11 +225,13 @@ constexpr char kNormRectsTag[] = "NORM_RECTS";
|
||||
auto output_rects =
|
||||
absl::make_unique<std::vector<NormalizedRect>>(detections.size());
|
||||
for (int i = 0; i < detections.size(); ++i) {
|
||||
MP_RETURN_IF_ERROR(
|
||||
DetectionToNormalizedRect(detections[i], &(output_rects->at(i))));
|
||||
MP_RETURN_IF_ERROR(DetectionToNormalizedRect(
|
||||
detections[i], detection_spec, &(output_rects->at(i))));
|
||||
if (rotate_) {
|
||||
output_rects->at(i).set_rotation(
|
||||
ComputeRotation(detections[i], image_size));
|
||||
float rotation;
|
||||
MP_RETURN_IF_ERROR(
|
||||
ComputeRotation(detections[i], detection_spec, &rotation));
|
||||
output_rects->at(i).set_rotation(rotation);
|
||||
}
|
||||
}
|
||||
cc->Outputs()
|
||||
@@ -232,21 +242,35 @@ constexpr char kNormRectsTag[] = "NORM_RECTS";
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
float DetectionsToRectsCalculator::ComputeRotation(
|
||||
const Detection& detection, const std::pair<int, int> image_size) {
|
||||
::mediapipe::Status DetectionsToRectsCalculator::ComputeRotation(
|
||||
const Detection& detection, const DetectionSpec& detection_spec,
|
||||
float* rotation) {
|
||||
const auto& location_data = detection.location_data();
|
||||
const auto& image_size = detection_spec.image_size;
|
||||
RET_CHECK(image_size) << "Image size is required to calculate rotation";
|
||||
|
||||
const float x0 = location_data.relative_keypoints(start_keypoint_index_).x() *
|
||||
image_size.first;
|
||||
image_size->first;
|
||||
const float y0 = location_data.relative_keypoints(start_keypoint_index_).y() *
|
||||
image_size.second;
|
||||
image_size->second;
|
||||
const float x1 = location_data.relative_keypoints(end_keypoint_index_).x() *
|
||||
image_size.first;
|
||||
image_size->first;
|
||||
const float y1 = location_data.relative_keypoints(end_keypoint_index_).y() *
|
||||
image_size.second;
|
||||
image_size->second;
|
||||
|
||||
float rotation = target_angle_ - std::atan2(-(y1 - y0), x1 - x0);
|
||||
*rotation = NormalizeRadians(target_angle_ - std::atan2(-(y1 - y0), x1 - x0));
|
||||
|
||||
return NormalizeRadians(rotation);
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
DetectionSpec DetectionsToRectsCalculator::GetDetectionSpec(
|
||||
const CalculatorContext* cc) {
|
||||
absl::optional<std::pair<int, int>> image_size;
|
||||
if (cc->Inputs().HasTag(kImageSizeTag)) {
|
||||
image_size = cc->Inputs().Tag(kImageSizeTag).Get<std::pair<int, int>>();
|
||||
}
|
||||
|
||||
return {image_size};
|
||||
}
|
||||
|
||||
REGISTER_CALCULATOR(DetectionsToRectsCalculator);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "mediapipe/calculators/util/detections_to_rects_calculator.pb.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/calculator_options.pb.h"
|
||||
@@ -27,6 +28,13 @@
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
// Dynamic options passed as calculator `input_stream` that can be used for
|
||||
// calculation of rectangle or rotation for given detection. Does not include
|
||||
// static calculator options which are available via private fields.
|
||||
struct DetectionSpec {
|
||||
absl::optional<std::pair<int, int>> image_size;
|
||||
};
|
||||
|
||||
// A calculator that converts Detection proto to Rect proto.
|
||||
//
|
||||
// Detection is the format for encoding one or more detections in an image.
|
||||
@@ -81,13 +89,16 @@ class DetectionsToRectsCalculator : public CalculatorBase {
|
||||
::mediapipe::Status Process(CalculatorContext* cc) override;
|
||||
|
||||
protected:
|
||||
virtual float ComputeRotation(const ::mediapipe::Detection& detection,
|
||||
const std::pair<int, int> image_size);
|
||||
virtual ::mediapipe::Status DetectionToRect(
|
||||
const ::mediapipe::Detection& detection, ::mediapipe::Rect* rect);
|
||||
const ::mediapipe::Detection& detection,
|
||||
const DetectionSpec& detection_spec, ::mediapipe::Rect* rect);
|
||||
virtual ::mediapipe::Status DetectionToNormalizedRect(
|
||||
const ::mediapipe::Detection& detection,
|
||||
::mediapipe::NormalizedRect* rect);
|
||||
const DetectionSpec& detection_spec, ::mediapipe::NormalizedRect* rect);
|
||||
virtual ::mediapipe::Status ComputeRotation(
|
||||
const ::mediapipe::Detection& detection,
|
||||
const DetectionSpec& detection_spec, float* rotation);
|
||||
virtual DetectionSpec GetDetectionSpec(const CalculatorContext* cc);
|
||||
|
||||
static inline float NormalizeRadians(float angle) {
|
||||
return angle - 2 * M_PI * std::floor((angle - (-M_PI)) / (2 * M_PI));
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "mediapipe/framework/formats/classification.pb.h"
|
||||
#include "mediapipe/framework/formats/landmark.pb.h"
|
||||
#include "mediapipe/framework/formats/rect.pb.h"
|
||||
|
||||
@@ -31,4 +32,8 @@ typedef FilterCollectionCalculator<
|
||||
FilterLandmarkListCollectionCalculator;
|
||||
REGISTER_CALCULATOR(FilterLandmarkListCollectionCalculator);
|
||||
|
||||
typedef FilterCollectionCalculator<std::vector<::mediapipe::ClassificationList>>
|
||||
FilterClassificationListCollectionCalculator;
|
||||
REGISTER_CALCULATOR(FilterClassificationListCollectionCalculator);
|
||||
|
||||
} // namespace mediapipe
|
||||
|
||||
@@ -128,16 +128,19 @@ REGISTER_CALCULATOR(LabelsToRenderDataCalculator);
|
||||
} else {
|
||||
const std::vector<std::string>& label_vector =
|
||||
cc->Inputs().Tag("LABELS").Get<std::vector<std::string>>();
|
||||
std::vector<float> score_vector;
|
||||
if (cc->Inputs().HasTag("SCORES")) {
|
||||
score_vector = cc->Inputs().Tag("SCORES").Get<std::vector<float>>();
|
||||
}
|
||||
CHECK_EQ(label_vector.size(), score_vector.size());
|
||||
labels.resize(label_vector.size());
|
||||
scores.resize(label_vector.size());
|
||||
for (int i = 0; i < label_vector.size(); ++i) {
|
||||
labels[i] = label_vector[i];
|
||||
scores[i] = score_vector[i];
|
||||
}
|
||||
|
||||
if (cc->Inputs().HasTag("SCORES")) {
|
||||
std::vector<float> score_vector =
|
||||
cc->Inputs().Tag("SCORES").Get<std::vector<float>>();
|
||||
CHECK_EQ(label_vector.size(), score_vector.size());
|
||||
scores.resize(label_vector.size());
|
||||
for (int i = 0; i < label_vector.size(); ++i) {
|
||||
scores[i] = score_vector[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,20 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
@@ -67,6 +53,15 @@ constexpr char kLetterboxPaddingTag[] = "LETTERBOX_PADDING";
|
||||
// input_stream: "LETTERBOX_PADDING:letterbox_padding"
|
||||
// output_stream: "LANDMARKS:adjusted_landmarks"
|
||||
// }
|
||||
//
|
||||
// node {
|
||||
// calculator: "LandmarkLetterboxRemovalCalculator"
|
||||
// input_stream: "LANDMARKS:0:landmarks_0"
|
||||
// input_stream: "LANDMARKS:1:landmarks_1"
|
||||
// input_stream: "LETTERBOX_PADDING:letterbox_padding"
|
||||
// output_stream: "LANDMARKS:0:adjusted_landmarks_0"
|
||||
// output_stream: "LANDMARKS:1:adjusted_landmarks_1"
|
||||
// }
|
||||
class LandmarkLetterboxRemovalCalculator : public CalculatorBase {
|
||||
public:
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
|
||||
@@ -74,10 +69,20 @@ class LandmarkLetterboxRemovalCalculator : public CalculatorBase {
|
||||
cc->Inputs().HasTag(kLetterboxPaddingTag))
|
||||
<< "Missing one or more input streams.";
|
||||
|
||||
cc->Inputs().Tag(kLandmarksTag).Set<NormalizedLandmarkList>();
|
||||
RET_CHECK_EQ(cc->Inputs().NumEntries(kLandmarksTag),
|
||||
cc->Outputs().NumEntries(kLandmarksTag))
|
||||
<< "Same number of input and output landmarks is required.";
|
||||
|
||||
for (CollectionItemId id = cc->Inputs().BeginId(kLandmarksTag);
|
||||
id != cc->Inputs().EndId(kLandmarksTag); ++id) {
|
||||
cc->Inputs().Get(id).Set<NormalizedLandmarkList>();
|
||||
}
|
||||
cc->Inputs().Tag(kLetterboxPaddingTag).Set<std::array<float, 4>>();
|
||||
|
||||
cc->Outputs().Tag(kLandmarksTag).Set<NormalizedLandmarkList>();
|
||||
for (CollectionItemId id = cc->Outputs().BeginId(kLandmarksTag);
|
||||
id != cc->Outputs().EndId(kLandmarksTag); ++id) {
|
||||
cc->Outputs().Get(id).Set<NormalizedLandmarkList>();
|
||||
}
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
@@ -89,38 +94,45 @@ class LandmarkLetterboxRemovalCalculator : public CalculatorBase {
|
||||
}
|
||||
|
||||
::mediapipe::Status Process(CalculatorContext* cc) override {
|
||||
// Only process if there's input landmarks.
|
||||
if (cc->Inputs().Tag(kLandmarksTag).IsEmpty()) {
|
||||
if (cc->Inputs().Tag(kLetterboxPaddingTag).IsEmpty()) {
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
const NormalizedLandmarkList& input_landmarks =
|
||||
cc->Inputs().Tag(kLandmarksTag).Get<NormalizedLandmarkList>();
|
||||
const auto& letterbox_padding =
|
||||
cc->Inputs().Tag(kLetterboxPaddingTag).Get<std::array<float, 4>>();
|
||||
|
||||
const float left = letterbox_padding[0];
|
||||
const float top = letterbox_padding[1];
|
||||
const float left_and_right = letterbox_padding[0] + letterbox_padding[2];
|
||||
const float top_and_bottom = letterbox_padding[1] + letterbox_padding[3];
|
||||
|
||||
NormalizedLandmarkList output_landmarks;
|
||||
for (int i = 0; i < input_landmarks.landmark_size(); ++i) {
|
||||
const NormalizedLandmark& landmark = input_landmarks.landmark(i);
|
||||
NormalizedLandmark* new_landmark = output_landmarks.add_landmark();
|
||||
const float new_x = (landmark.x() - left) / (1.0f - left_and_right);
|
||||
const float new_y = (landmark.y() - top) / (1.0f - top_and_bottom);
|
||||
CollectionItemId input_id = cc->Inputs().BeginId(kLandmarksTag);
|
||||
CollectionItemId output_id = cc->Outputs().BeginId(kLandmarksTag);
|
||||
// Number of inputs and outpus is the same according to the contract.
|
||||
for (; input_id != cc->Inputs().EndId(kLandmarksTag);
|
||||
++input_id, ++output_id) {
|
||||
const auto& input_packet = cc->Inputs().Get(input_id);
|
||||
if (input_packet.IsEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
new_landmark->set_x(new_x);
|
||||
new_landmark->set_y(new_y);
|
||||
// Keep z-coord as is.
|
||||
new_landmark->set_z(landmark.z());
|
||||
const NormalizedLandmarkList& input_landmarks =
|
||||
input_packet.Get<NormalizedLandmarkList>();
|
||||
NormalizedLandmarkList output_landmarks;
|
||||
for (int i = 0; i < input_landmarks.landmark_size(); ++i) {
|
||||
const NormalizedLandmark& landmark = input_landmarks.landmark(i);
|
||||
NormalizedLandmark* new_landmark = output_landmarks.add_landmark();
|
||||
const float new_x = (landmark.x() - left) / (1.0f - left_and_right);
|
||||
const float new_y = (landmark.y() - top) / (1.0f - top_and_bottom);
|
||||
|
||||
new_landmark->set_x(new_x);
|
||||
new_landmark->set_y(new_y);
|
||||
// Keep z-coord as is.
|
||||
new_landmark->set_z(landmark.z());
|
||||
}
|
||||
|
||||
cc->Outputs().Get(output_id).AddPacket(
|
||||
MakePacket<NormalizedLandmarkList>(output_landmarks)
|
||||
.At(cc->InputTimestamp()));
|
||||
}
|
||||
|
||||
cc->Outputs()
|
||||
.Tag(kLandmarksTag)
|
||||
.AddPacket(MakePacket<NormalizedLandmarkList>(output_landmarks)
|
||||
.At(cc->InputTimestamp()));
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -12,20 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
@@ -63,6 +49,15 @@ constexpr char kRectTag[] = "NORM_RECT";
|
||||
// input_stream: "NORM_RECT:rect"
|
||||
// output_stream: "NORM_LANDMARKS:projected_landmarks"
|
||||
// }
|
||||
//
|
||||
// node {
|
||||
// calculator: "LandmarkProjectionCalculator"
|
||||
// input_stream: "NORM_LANDMARKS:0:landmarks_0"
|
||||
// input_stream: "NORM_LANDMARKS:1:landmarks_1"
|
||||
// input_stream: "NORM_RECT:rect"
|
||||
// output_stream: "NORM_LANDMARKS:0:projected_landmarks_0"
|
||||
// output_stream: "NORM_LANDMARKS:1:projected_landmarks_1"
|
||||
// }
|
||||
class LandmarkProjectionCalculator : public CalculatorBase {
|
||||
public:
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
|
||||
@@ -70,10 +65,20 @@ class LandmarkProjectionCalculator : public CalculatorBase {
|
||||
cc->Inputs().HasTag(kRectTag))
|
||||
<< "Missing one or more input streams.";
|
||||
|
||||
cc->Inputs().Tag(kLandmarksTag).Set<NormalizedLandmarkList>();
|
||||
RET_CHECK_EQ(cc->Inputs().NumEntries(kLandmarksTag),
|
||||
cc->Outputs().NumEntries(kLandmarksTag))
|
||||
<< "Same number of input and output landmarks is required.";
|
||||
|
||||
for (CollectionItemId id = cc->Inputs().BeginId(kLandmarksTag);
|
||||
id != cc->Inputs().EndId(kLandmarksTag); ++id) {
|
||||
cc->Inputs().Get(id).Set<NormalizedLandmarkList>();
|
||||
}
|
||||
cc->Inputs().Tag(kRectTag).Set<NormalizedRect>();
|
||||
|
||||
cc->Outputs().Tag(kLandmarksTag).Set<NormalizedLandmarkList>();
|
||||
for (CollectionItemId id = cc->Outputs().BeginId(kLandmarksTag);
|
||||
id != cc->Outputs().EndId(kLandmarksTag); ++id) {
|
||||
cc->Outputs().Get(id).Set<NormalizedLandmarkList>();
|
||||
}
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
@@ -85,41 +90,50 @@ class LandmarkProjectionCalculator : public CalculatorBase {
|
||||
}
|
||||
|
||||
::mediapipe::Status Process(CalculatorContext* cc) override {
|
||||
const auto& options =
|
||||
cc->Options<::mediapipe::LandmarkProjectionCalculatorOptions>();
|
||||
// Only process if there's input landmarks.
|
||||
if (cc->Inputs().Tag(kLandmarksTag).IsEmpty()) {
|
||||
if (cc->Inputs().Tag(kRectTag).IsEmpty()) {
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
const NormalizedLandmarkList& input_landmarks =
|
||||
cc->Inputs().Tag(kLandmarksTag).Get<NormalizedLandmarkList>();
|
||||
const auto& input_rect = cc->Inputs().Tag(kRectTag).Get<NormalizedRect>();
|
||||
|
||||
NormalizedLandmarkList output_landmarks;
|
||||
for (int i = 0; i < input_landmarks.landmark_size(); ++i) {
|
||||
const NormalizedLandmark& landmark = input_landmarks.landmark(i);
|
||||
NormalizedLandmark* new_landmark = output_landmarks.add_landmark();
|
||||
const auto& options =
|
||||
cc->Options<::mediapipe::LandmarkProjectionCalculatorOptions>();
|
||||
|
||||
const float x = landmark.x() - 0.5f;
|
||||
const float y = landmark.y() - 0.5f;
|
||||
const float angle = options.ignore_rotation() ? 0 : input_rect.rotation();
|
||||
float new_x = std::cos(angle) * x - std::sin(angle) * y;
|
||||
float new_y = std::sin(angle) * x + std::cos(angle) * y;
|
||||
CollectionItemId input_id = cc->Inputs().BeginId(kLandmarksTag);
|
||||
CollectionItemId output_id = cc->Outputs().BeginId(kLandmarksTag);
|
||||
// Number of inputs and outpus is the same according to the contract.
|
||||
for (; input_id != cc->Inputs().EndId(kLandmarksTag);
|
||||
++input_id, ++output_id) {
|
||||
const auto& input_packet = cc->Inputs().Get(input_id);
|
||||
if (input_packet.IsEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
new_x = new_x * input_rect.width() + input_rect.x_center();
|
||||
new_y = new_y * input_rect.height() + input_rect.y_center();
|
||||
const auto& input_landmarks = input_packet.Get<NormalizedLandmarkList>();
|
||||
NormalizedLandmarkList output_landmarks;
|
||||
for (int i = 0; i < input_landmarks.landmark_size(); ++i) {
|
||||
const NormalizedLandmark& landmark = input_landmarks.landmark(i);
|
||||
NormalizedLandmark* new_landmark = output_landmarks.add_landmark();
|
||||
|
||||
new_landmark->set_x(new_x);
|
||||
new_landmark->set_y(new_y);
|
||||
// Keep z-coord as is.
|
||||
new_landmark->set_z(landmark.z());
|
||||
const float x = landmark.x() - 0.5f;
|
||||
const float y = landmark.y() - 0.5f;
|
||||
const float angle =
|
||||
options.ignore_rotation() ? 0 : input_rect.rotation();
|
||||
float new_x = std::cos(angle) * x - std::sin(angle) * y;
|
||||
float new_y = std::sin(angle) * x + std::cos(angle) * y;
|
||||
|
||||
new_x = new_x * input_rect.width() + input_rect.x_center();
|
||||
new_y = new_y * input_rect.height() + input_rect.y_center();
|
||||
|
||||
new_landmark->set_x(new_x);
|
||||
new_landmark->set_y(new_y);
|
||||
// Keep z-coord as is.
|
||||
new_landmark->set_z(landmark.z());
|
||||
}
|
||||
|
||||
cc->Outputs().Get(output_id).AddPacket(
|
||||
MakePacket<NormalizedLandmarkList>(output_landmarks)
|
||||
.At(cc->InputTimestamp()));
|
||||
}
|
||||
|
||||
cc->Outputs()
|
||||
.Tag(kLandmarksTag)
|
||||
.AddPacket(MakePacket<NormalizedLandmarkList>(output_landmarks)
|
||||
.At(cc->InputTimestamp()));
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace {
|
||||
|
||||
constexpr char kLandmarksTag[] = "LANDMARKS";
|
||||
constexpr char kNormLandmarksTag[] = "NORM_LANDMARKS";
|
||||
constexpr char kRenderScaleTag[] = "RENDER_SCALE";
|
||||
constexpr char kRenderDataTag[] = "RENDER_DATA";
|
||||
constexpr char kLandmarkLabel[] = "KEYPOINT";
|
||||
constexpr int kMaxLandmarkThickness = 18;
|
||||
@@ -71,6 +72,83 @@ void SetColorSizeValueFromZ(float z, float z_min, float z_max,
|
||||
render_annotation->set_thickness(thickness);
|
||||
}
|
||||
|
||||
template <class LandmarkType>
|
||||
void AddConnectionToRenderData(const LandmarkType& start,
|
||||
const LandmarkType& end, int gray_val1,
|
||||
int gray_val2, float thickness, bool normalized,
|
||||
RenderData* render_data) {
|
||||
auto* connection_annotation = render_data->add_render_annotations();
|
||||
RenderAnnotation::GradientLine* line =
|
||||
connection_annotation->mutable_gradient_line();
|
||||
line->set_x_start(start.x());
|
||||
line->set_y_start(start.y());
|
||||
line->set_x_end(end.x());
|
||||
line->set_y_end(end.y());
|
||||
line->set_normalized(normalized);
|
||||
line->mutable_color1()->set_r(gray_val1);
|
||||
line->mutable_color1()->set_g(gray_val1);
|
||||
line->mutable_color1()->set_b(gray_val1);
|
||||
line->mutable_color2()->set_r(gray_val2);
|
||||
line->mutable_color2()->set_g(gray_val2);
|
||||
line->mutable_color2()->set_b(gray_val2);
|
||||
connection_annotation->set_thickness(thickness);
|
||||
}
|
||||
|
||||
template <class LandmarkListType, class LandmarkType>
|
||||
void AddConnectionsWithDepth(const LandmarkListType& landmarks,
|
||||
const std::vector<int>& landmark_connections,
|
||||
float thickness, bool normalized, float min_z,
|
||||
float max_z, RenderData* render_data) {
|
||||
for (int i = 0; i < landmark_connections.size(); i += 2) {
|
||||
const auto& ld0 = landmarks.landmark(landmark_connections[i]);
|
||||
const auto& ld1 = landmarks.landmark(landmark_connections[i + 1]);
|
||||
const int gray_val1 =
|
||||
255 - static_cast<int>(Remap(ld0.z(), min_z, max_z, 255));
|
||||
const int gray_val2 =
|
||||
255 - static_cast<int>(Remap(ld1.z(), min_z, max_z, 255));
|
||||
AddConnectionToRenderData<LandmarkType>(ld0, ld1, gray_val1, gray_val2,
|
||||
thickness, normalized, render_data);
|
||||
}
|
||||
}
|
||||
|
||||
template <class LandmarkType>
|
||||
void AddConnectionToRenderData(const LandmarkType& start,
|
||||
const LandmarkType& end,
|
||||
const Color& connection_color, float thickness,
|
||||
bool normalized, RenderData* render_data) {
|
||||
auto* connection_annotation = render_data->add_render_annotations();
|
||||
RenderAnnotation::Line* line = connection_annotation->mutable_line();
|
||||
line->set_x_start(start.x());
|
||||
line->set_y_start(start.y());
|
||||
line->set_x_end(end.x());
|
||||
line->set_y_end(end.y());
|
||||
line->set_normalized(normalized);
|
||||
SetColor(connection_annotation, connection_color);
|
||||
connection_annotation->set_thickness(thickness);
|
||||
}
|
||||
|
||||
template <class LandmarkListType, class LandmarkType>
|
||||
void AddConnections(const LandmarkListType& landmarks,
|
||||
const std::vector<int>& landmark_connections,
|
||||
const Color& connection_color, float thickness,
|
||||
bool normalized, RenderData* render_data) {
|
||||
for (int i = 0; i < landmark_connections.size(); i += 2) {
|
||||
const auto& ld0 = landmarks.landmark(landmark_connections[i]);
|
||||
const auto& ld1 = landmarks.landmark(landmark_connections[i + 1]);
|
||||
AddConnectionToRenderData<LandmarkType>(ld0, ld1, connection_color,
|
||||
thickness, normalized, render_data);
|
||||
}
|
||||
}
|
||||
|
||||
RenderAnnotation* AddPointRenderData(const Color& landmark_color,
|
||||
float thickness, RenderData* render_data) {
|
||||
auto* landmark_data_annotation = render_data->add_render_annotations();
|
||||
landmark_data_annotation->set_scene_tag(kLandmarkLabel);
|
||||
SetColor(landmark_data_annotation, landmark_color);
|
||||
landmark_data_annotation->set_thickness(thickness);
|
||||
return landmark_data_annotation;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// A calculator that converts Landmark proto to RenderData proto for
|
||||
@@ -107,29 +185,6 @@ class LandmarksToRenderDataCalculator : public CalculatorBase {
|
||||
::mediapipe::Status Process(CalculatorContext* cc) override;
|
||||
|
||||
private:
|
||||
static void AddConnectionToRenderData(
|
||||
float start_x, float start_y, float end_x, float end_y,
|
||||
const LandmarksToRenderDataCalculatorOptions& options, bool normalized,
|
||||
RenderData* render_data);
|
||||
static void SetRenderAnnotationColorThickness(
|
||||
const LandmarksToRenderDataCalculatorOptions& options,
|
||||
RenderAnnotation* render_annotation);
|
||||
static RenderAnnotation* AddPointRenderData(
|
||||
const LandmarksToRenderDataCalculatorOptions& options,
|
||||
RenderData* render_data);
|
||||
static void AddConnectionToRenderData(
|
||||
float start_x, float start_y, float end_x, float end_y,
|
||||
const LandmarksToRenderDataCalculatorOptions& options, bool normalized,
|
||||
int gray_val1, int gray_val2, RenderData* render_data);
|
||||
|
||||
template <class LandmarkListType>
|
||||
void AddConnections(const LandmarkListType& landmarks, bool normalized,
|
||||
RenderData* render_data);
|
||||
template <class LandmarkListType>
|
||||
void AddConnectionsWithDepth(const LandmarkListType& landmarks,
|
||||
bool normalized, float min_z, float max_z,
|
||||
RenderData* render_data);
|
||||
|
||||
LandmarksToRenderDataCalculatorOptions options_;
|
||||
};
|
||||
REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
|
||||
@@ -150,6 +205,9 @@ REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
|
||||
if (cc->Inputs().HasTag(kNormLandmarksTag)) {
|
||||
cc->Inputs().Tag(kNormLandmarksTag).Set<NormalizedLandmarkList>();
|
||||
}
|
||||
if (cc->Inputs().HasTag(kRenderScaleTag)) {
|
||||
cc->Inputs().Tag(kRenderScaleTag).Set<float>();
|
||||
}
|
||||
cc->Outputs().Tag(kRenderDataTag).Set<RenderData>();
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
@@ -169,11 +227,26 @@ REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
|
||||
float z_min = 0.f;
|
||||
float z_max = 0.f;
|
||||
|
||||
// Apply scale to `thickness` of rendered landmarks and connections to make
|
||||
// them bigger when object (e.g. pose, hand or face) is closer/bigger and
|
||||
// snaller when object is further/smaller.
|
||||
float thickness = options_.thickness();
|
||||
if (cc->Inputs().HasTag(kRenderScaleTag)) {
|
||||
const float render_scale = cc->Inputs().Tag(kRenderScaleTag).Get<float>();
|
||||
thickness *= render_scale;
|
||||
}
|
||||
|
||||
// Parse landmarks connections to a vector.
|
||||
RET_CHECK_EQ(options_.landmark_connections_size() % 2, 0)
|
||||
<< "Number of entries in landmark connections must be a multiple of 2";
|
||||
std::vector<int> landmark_connections;
|
||||
for (int i = 0; i < options_.landmark_connections_size(); i += 1) {
|
||||
landmark_connections.push_back(options_.landmark_connections(i));
|
||||
}
|
||||
|
||||
if (cc->Inputs().HasTag(kLandmarksTag)) {
|
||||
const LandmarkList& landmarks =
|
||||
cc->Inputs().Tag(kLandmarksTag).Get<LandmarkList>();
|
||||
RET_CHECK_EQ(options_.landmark_connections_size() % 2, 0)
|
||||
<< "Number of entries in landmark connections must be a multiple of 2";
|
||||
if (visualize_depth) {
|
||||
GetMinMaxZ<LandmarkList, Landmark>(landmarks, &z_min, &z_max);
|
||||
}
|
||||
@@ -181,8 +254,8 @@ REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
|
||||
visualize_depth &= ((z_max - z_min) > 1e-3);
|
||||
for (int i = 0; i < landmarks.landmark_size(); ++i) {
|
||||
const Landmark& landmark = landmarks.landmark(i);
|
||||
auto* landmark_data_render =
|
||||
AddPointRenderData(options_, render_data.get());
|
||||
auto* landmark_data_render = AddPointRenderData(
|
||||
options_.landmark_color(), thickness, render_data.get());
|
||||
if (visualize_depth) {
|
||||
SetColorSizeValueFromZ(landmark.z(), z_min, z_max,
|
||||
landmark_data_render);
|
||||
@@ -193,19 +266,19 @@ REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
|
||||
landmark_data->set_y(landmark.y());
|
||||
}
|
||||
if (visualize_depth) {
|
||||
AddConnectionsWithDepth<LandmarkList>(landmarks, /*normalized=*/false,
|
||||
z_min, z_max, render_data.get());
|
||||
AddConnectionsWithDepth<LandmarkList, Landmark>(
|
||||
landmarks, landmark_connections, thickness, /*normalized=*/false,
|
||||
z_min, z_max, render_data.get());
|
||||
} else {
|
||||
AddConnections<LandmarkList>(landmarks, /*normalized=*/false,
|
||||
render_data.get());
|
||||
AddConnections<LandmarkList, Landmark>(
|
||||
landmarks, landmark_connections, options_.connection_color(),
|
||||
thickness, /*normalized=*/false, render_data.get());
|
||||
}
|
||||
}
|
||||
|
||||
if (cc->Inputs().HasTag(kNormLandmarksTag)) {
|
||||
const NormalizedLandmarkList& landmarks =
|
||||
cc->Inputs().Tag(kNormLandmarksTag).Get<NormalizedLandmarkList>();
|
||||
RET_CHECK_EQ(options_.landmark_connections_size() % 2, 0)
|
||||
<< "Number of entries in landmark connections must be a multiple of 2";
|
||||
if (visualize_depth) {
|
||||
GetMinMaxZ<NormalizedLandmarkList, NormalizedLandmark>(landmarks, &z_min,
|
||||
&z_max);
|
||||
@@ -214,8 +287,8 @@ REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
|
||||
visualize_depth &= ((z_max - z_min) > 1e-3);
|
||||
for (int i = 0; i < landmarks.landmark_size(); ++i) {
|
||||
const NormalizedLandmark& landmark = landmarks.landmark(i);
|
||||
auto* landmark_data_render =
|
||||
AddPointRenderData(options_, render_data.get());
|
||||
auto* landmark_data_render = AddPointRenderData(
|
||||
options_.landmark_color(), thickness, render_data.get());
|
||||
if (visualize_depth) {
|
||||
SetColorSizeValueFromZ(landmark.z(), z_min, z_max,
|
||||
landmark_data_render);
|
||||
@@ -226,11 +299,13 @@ REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
|
||||
landmark_data->set_y(landmark.y());
|
||||
}
|
||||
if (visualize_depth) {
|
||||
AddConnectionsWithDepth<NormalizedLandmarkList>(
|
||||
landmarks, /*normalized=*/true, z_min, z_max, render_data.get());
|
||||
AddConnectionsWithDepth<NormalizedLandmarkList, NormalizedLandmark>(
|
||||
landmarks, landmark_connections, thickness, /*normalized=*/true,
|
||||
z_min, z_max, render_data.get());
|
||||
} else {
|
||||
AddConnections<NormalizedLandmarkList>(landmarks, /*normalized=*/true,
|
||||
render_data.get());
|
||||
AddConnections<NormalizedLandmarkList, NormalizedLandmark>(
|
||||
landmarks, landmark_connections, options_.connection_color(),
|
||||
thickness, /*normalized=*/true, render_data.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,84 +315,4 @@ REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
template <class LandmarkListType>
|
||||
void LandmarksToRenderDataCalculator::AddConnectionsWithDepth(
|
||||
const LandmarkListType& landmarks, bool normalized, float min_z,
|
||||
float max_z, RenderData* render_data) {
|
||||
for (int i = 0; i < options_.landmark_connections_size(); i += 2) {
|
||||
const auto& ld0 = landmarks.landmark(options_.landmark_connections(i));
|
||||
const auto& ld1 = landmarks.landmark(options_.landmark_connections(i + 1));
|
||||
const int gray_val1 =
|
||||
255 - static_cast<int>(Remap(ld0.z(), min_z, max_z, 255));
|
||||
const int gray_val2 =
|
||||
255 - static_cast<int>(Remap(ld1.z(), min_z, max_z, 255));
|
||||
AddConnectionToRenderData(ld0.x(), ld0.y(), ld1.x(), ld1.y(), options_,
|
||||
normalized, gray_val1, gray_val2, render_data);
|
||||
}
|
||||
}
|
||||
|
||||
void LandmarksToRenderDataCalculator::AddConnectionToRenderData(
|
||||
float start_x, float start_y, float end_x, float end_y,
|
||||
const LandmarksToRenderDataCalculatorOptions& options, bool normalized,
|
||||
int gray_val1, int gray_val2, RenderData* render_data) {
|
||||
auto* connection_annotation = render_data->add_render_annotations();
|
||||
RenderAnnotation::GradientLine* line =
|
||||
connection_annotation->mutable_gradient_line();
|
||||
line->set_x_start(start_x);
|
||||
line->set_y_start(start_y);
|
||||
line->set_x_end(end_x);
|
||||
line->set_y_end(end_y);
|
||||
line->set_normalized(normalized);
|
||||
line->mutable_color1()->set_r(gray_val1);
|
||||
line->mutable_color1()->set_g(gray_val1);
|
||||
line->mutable_color1()->set_b(gray_val1);
|
||||
line->mutable_color2()->set_r(gray_val2);
|
||||
line->mutable_color2()->set_g(gray_val2);
|
||||
line->mutable_color2()->set_b(gray_val2);
|
||||
connection_annotation->set_thickness(options.thickness());
|
||||
}
|
||||
|
||||
template <class LandmarkListType>
|
||||
void LandmarksToRenderDataCalculator::AddConnections(
|
||||
const LandmarkListType& landmarks, bool normalized,
|
||||
RenderData* render_data) {
|
||||
for (int i = 0; i < options_.landmark_connections_size(); i += 2) {
|
||||
const auto& ld0 = landmarks.landmark(options_.landmark_connections(i));
|
||||
const auto& ld1 = landmarks.landmark(options_.landmark_connections(i + 1));
|
||||
AddConnectionToRenderData(ld0.x(), ld0.y(), ld1.x(), ld1.y(), options_,
|
||||
normalized, render_data);
|
||||
}
|
||||
}
|
||||
|
||||
void LandmarksToRenderDataCalculator::AddConnectionToRenderData(
|
||||
float start_x, float start_y, float end_x, float end_y,
|
||||
const LandmarksToRenderDataCalculatorOptions& options, bool normalized,
|
||||
RenderData* render_data) {
|
||||
auto* connection_annotation = render_data->add_render_annotations();
|
||||
RenderAnnotation::Line* line = connection_annotation->mutable_line();
|
||||
line->set_x_start(start_x);
|
||||
line->set_y_start(start_y);
|
||||
line->set_x_end(end_x);
|
||||
line->set_y_end(end_y);
|
||||
line->set_normalized(normalized);
|
||||
SetColor(connection_annotation, options.connection_color());
|
||||
connection_annotation->set_thickness(options.thickness());
|
||||
}
|
||||
|
||||
RenderAnnotation* LandmarksToRenderDataCalculator::AddPointRenderData(
|
||||
const LandmarksToRenderDataCalculatorOptions& options,
|
||||
RenderData* render_data) {
|
||||
auto* landmark_data_annotation = render_data->add_render_annotations();
|
||||
landmark_data_annotation->set_scene_tag(kLandmarkLabel);
|
||||
SetRenderAnnotationColorThickness(options, landmark_data_annotation);
|
||||
return landmark_data_annotation;
|
||||
}
|
||||
|
||||
void LandmarksToRenderDataCalculator::SetRenderAnnotationColorThickness(
|
||||
const LandmarksToRenderDataCalculatorOptions& options,
|
||||
RenderAnnotation* render_annotation) {
|
||||
SetColor(render_annotation, options.landmark_color());
|
||||
render_annotation->set_thickness(options.thickness());
|
||||
}
|
||||
|
||||
} // namespace mediapipe
|
||||
|
||||
@@ -16,34 +16,80 @@
|
||||
#include <string>
|
||||
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/port/file_helpers.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/util/resource_util.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kFilePathTag[] = "FILE_PATH";
|
||||
constexpr char kContentsTag[] = "CONTENTS";
|
||||
|
||||
} // namespace
|
||||
|
||||
// The calculator takes the path to the local file as an input side packet and
|
||||
// outputs the contents of that file.
|
||||
//
|
||||
// NOTE: file loading can be batched by providing multiple input/output side
|
||||
// packets.
|
||||
//
|
||||
// Example config:
|
||||
// node {
|
||||
// calculator: "LocalFileContentsCalculator"
|
||||
// input_side_packet: "FILE_PATH:file_path"
|
||||
// output_side_packet: "CONTENTS:contents"
|
||||
// }
|
||||
//
|
||||
// node {
|
||||
// calculator: "LocalFileContentsCalculator"
|
||||
// input_side_packet: "FILE_PATH:0:file_path1"
|
||||
// input_side_packet: "FILE_PATH:1:file_path2"
|
||||
// ...
|
||||
// output_side_packet: "CONTENTS:0:contents1"
|
||||
// output_side_packet: "CONTENTS:1:contents2"
|
||||
// ...
|
||||
// }
|
||||
class LocalFileContentsCalculator : public CalculatorBase {
|
||||
public:
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
|
||||
cc->InputSidePackets().Tag("FILE_PATH").Set<std::string>();
|
||||
cc->OutputSidePackets().Tag("CONTENTS").Set<std::string>();
|
||||
RET_CHECK(cc->InputSidePackets().HasTag(kFilePathTag))
|
||||
<< "Missing PATH input side packet(s)";
|
||||
RET_CHECK(cc->OutputSidePackets().HasTag(kContentsTag))
|
||||
<< "Missing CONTENTS output side packet(s)";
|
||||
|
||||
RET_CHECK_EQ(cc->InputSidePackets().NumEntries(kFilePathTag),
|
||||
cc->OutputSidePackets().NumEntries(kContentsTag))
|
||||
<< "Same number of input streams and output streams is required.";
|
||||
|
||||
for (CollectionItemId id = cc->InputSidePackets().BeginId(kFilePathTag);
|
||||
id != cc->InputSidePackets().EndId(kFilePathTag); ++id) {
|
||||
cc->InputSidePackets().Get(id).Set<std::string>();
|
||||
}
|
||||
|
||||
for (CollectionItemId id = cc->OutputSidePackets().BeginId(kContentsTag);
|
||||
id != cc->OutputSidePackets().EndId(kContentsTag); ++id) {
|
||||
cc->OutputSidePackets().Get(id).Set<std::string>();
|
||||
}
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status Open(CalculatorContext* cc) override {
|
||||
std::string contents;
|
||||
MP_RETURN_IF_ERROR(mediapipe::file::GetContents(
|
||||
cc->InputSidePackets().Tag("FILE_PATH").Get<std::string>(), &contents));
|
||||
cc->OutputSidePackets()
|
||||
.Tag("CONTENTS")
|
||||
.Set(MakePacket<std::string>(std::move(contents)));
|
||||
CollectionItemId input_id = cc->InputSidePackets().BeginId(kFilePathTag);
|
||||
CollectionItemId output_id = cc->OutputSidePackets().BeginId(kContentsTag);
|
||||
// Number of inputs and outpus is the same according to the contract.
|
||||
for (; input_id != cc->InputSidePackets().EndId(kFilePathTag);
|
||||
++input_id, ++output_id) {
|
||||
std::string file_path =
|
||||
cc->InputSidePackets().Get(input_id).Get<std::string>();
|
||||
ASSIGN_OR_RETURN(file_path, PathToResourceAsFile(file_path));
|
||||
|
||||
std::string contents;
|
||||
MP_RETURN_IF_ERROR(GetResourceContents(file_path, &contents));
|
||||
cc->OutputSidePackets().Get(output_id).Set(
|
||||
MakePacket<std::string>(std::move(contents)));
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/port/file_helpers.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
|
||||
namespace mediapipe {
|
||||
// The calculator takes the path to local directory and desired file suffix to
|
||||
// mach as input side packets, and outputs the contents of those files that
|
||||
// match the pattern. Those matched files will be sent sequentially through the
|
||||
// output stream with incremental timestamp difference by 1.
|
||||
//
|
||||
// Example config:
|
||||
// node {
|
||||
// calculator: "LocalFilePatternContentsCalculator"
|
||||
// input_side_packet: "FILE_DIRECTORY:file_directory"
|
||||
// input_side_packet: "FILE_SUFFIX:file_suffix"
|
||||
// output_stream: "CONTENTS:contents"
|
||||
// }
|
||||
class LocalFilePatternContentsCalculator : public CalculatorBase {
|
||||
public:
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
|
||||
cc->InputSidePackets().Tag("FILE_DIRECTORY").Set<std::string>();
|
||||
cc->InputSidePackets().Tag("FILE_SUFFIX").Set<std::string>();
|
||||
cc->Outputs().Tag("CONTENTS").Set<std::string>();
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status Open(CalculatorContext* cc) override {
|
||||
MP_RETURN_IF_ERROR(::mediapipe::file::MatchFileTypeInDirectory(
|
||||
cc->InputSidePackets().Tag("FILE_DIRECTORY").Get<std::string>(),
|
||||
cc->InputSidePackets().Tag("FILE_SUFFIX").Get<std::string>(),
|
||||
&filenames_));
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status Process(CalculatorContext* cc) override {
|
||||
if (current_output_ < filenames_.size()) {
|
||||
auto contents = absl::make_unique<std::string>();
|
||||
LOG(INFO) << filenames_[current_output_];
|
||||
MP_RETURN_IF_ERROR(mediapipe::file::GetContents(
|
||||
filenames_[current_output_], contents.get()));
|
||||
++current_output_;
|
||||
cc->Outputs()
|
||||
.Tag("CONTENTS")
|
||||
.Add(contents.release(), Timestamp(current_output_));
|
||||
} else {
|
||||
return tool::StatusStop();
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::string> filenames_;
|
||||
int current_output_ = 0;
|
||||
};
|
||||
|
||||
REGISTER_CALCULATOR(LocalFilePatternContentsCalculator);
|
||||
|
||||
} // namespace mediapipe
|
||||
@@ -276,6 +276,7 @@ TEST_F(PacketLatencyCalculatorTest, DoesNotOutputUntilReferencePacketReceived) {
|
||||
"delayed_packet_0", Adopt(new double()).At(Timestamp(2))));
|
||||
|
||||
// Send a reference packet with timestamp 10 usec.
|
||||
simulation_clock_->Sleep(absl::Microseconds(1));
|
||||
MP_ASSERT_OK(graph_.AddPacketToInputStream(
|
||||
"camera_frames", Adopt(new double()).At(Timestamp(10))));
|
||||
simulation_clock_->Sleep(absl::Microseconds(1));
|
||||
|
||||
@@ -45,15 +45,17 @@ RenderAnnotation::Rectangle* NewRect(
|
||||
void SetRect(bool normalized, double xmin, double ymin, double width,
|
||||
double height, double rotation,
|
||||
RenderAnnotation::Rectangle* rect) {
|
||||
if (xmin + width < 0.0 || ymin + height < 0.0) return;
|
||||
if (normalized) {
|
||||
if (xmin > 1.0 || ymin > 1.0) return;
|
||||
if (rotation == 0.0) {
|
||||
if (xmin + width < 0.0 || ymin + height < 0.0) return;
|
||||
if (normalized) {
|
||||
if (xmin > 1.0 || ymin > 1.0) return;
|
||||
}
|
||||
}
|
||||
rect->set_normalized(normalized);
|
||||
rect->set_left(normalized ? std::max(xmin, 0.0) : xmin);
|
||||
rect->set_top(normalized ? std::max(ymin, 0.0) : ymin);
|
||||
rect->set_right(normalized ? std::min(xmin + width, 1.0) : xmin + width);
|
||||
rect->set_bottom(normalized ? std::min(ymin + height, 1.0) : ymin + height);
|
||||
rect->set_left(xmin);
|
||||
rect->set_top(ymin);
|
||||
rect->set_right(xmin + width);
|
||||
rect->set_bottom(ymin + height);
|
||||
rect->set_rotation(rotation);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/calculators/util/timed_box_list_id_to_label_calculator.pb.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/packet.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/util/resource_util.h"
|
||||
#include "mediapipe/util/tracking/box_tracker.pb.h"
|
||||
|
||||
#if defined(MEDIAPIPE_MOBILE)
|
||||
#include "mediapipe/util/android/file/base/file.h"
|
||||
#include "mediapipe/util/android/file/base/helpers.h"
|
||||
#else
|
||||
#include "mediapipe/framework/port/file_helpers.h"
|
||||
#endif
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
using mediapipe::TimedBoxProto;
|
||||
using mediapipe::TimedBoxProtoList;
|
||||
|
||||
// Takes a label map (from label IDs to names), and populate the label field in
|
||||
// TimedBoxProto according to it's ID.
|
||||
//
|
||||
// Example usage:
|
||||
// node {
|
||||
// calculator: "TimedBoxListIdToLabelCalculator"
|
||||
// input_stream: "input_timed_box_list"
|
||||
// output_stream: "output_timed_box_list"
|
||||
// node_options: {
|
||||
// [mediapipe.TimedBoxListIdToLabelCalculatorOptions] {
|
||||
// label_map_path: "labelmap.txt"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
class TimedBoxListIdToLabelCalculator : public CalculatorBase {
|
||||
public:
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc);
|
||||
|
||||
::mediapipe::Status Open(CalculatorContext* cc) override;
|
||||
::mediapipe::Status Process(CalculatorContext* cc) override;
|
||||
|
||||
private:
|
||||
std::unordered_map<int, std::string> label_map_;
|
||||
};
|
||||
REGISTER_CALCULATOR(TimedBoxListIdToLabelCalculator);
|
||||
|
||||
::mediapipe::Status TimedBoxListIdToLabelCalculator::GetContract(
|
||||
CalculatorContract* cc) {
|
||||
cc->Inputs().Index(0).Set<TimedBoxProtoList>();
|
||||
cc->Outputs().Index(0).Set<TimedBoxProtoList>();
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status TimedBoxListIdToLabelCalculator::Open(
|
||||
CalculatorContext* cc) {
|
||||
cc->SetOffset(TimestampDiff(0));
|
||||
|
||||
const auto& options =
|
||||
cc->Options<::mediapipe::TimedBoxListIdToLabelCalculatorOptions>();
|
||||
|
||||
std::string string_path;
|
||||
ASSIGN_OR_RETURN(string_path, PathToResourceAsFile(options.label_map_path()));
|
||||
std::string label_map_string;
|
||||
MP_RETURN_IF_ERROR(file::GetContents(string_path, &label_map_string));
|
||||
|
||||
std::istringstream stream(label_map_string);
|
||||
std::string line;
|
||||
int i = 0;
|
||||
while (std::getline(stream, line)) {
|
||||
label_map_[i++] = line;
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status TimedBoxListIdToLabelCalculator::Process(
|
||||
CalculatorContext* cc) {
|
||||
const auto& input_list = cc->Inputs().Index(0).Get<TimedBoxProtoList>();
|
||||
auto output_list = absl::make_unique<TimedBoxProtoList>();
|
||||
for (const auto& input_box : input_list.box()) {
|
||||
TimedBoxProto* box_ptr = output_list->add_box();
|
||||
*box_ptr = input_box;
|
||||
|
||||
if (label_map_.find(input_box.id()) != label_map_.end()) {
|
||||
box_ptr->set_label(label_map_[input_box.id()]);
|
||||
}
|
||||
}
|
||||
cc->Outputs().Index(0).Add(output_list.release(), cc->InputTimestamp());
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package mediapipe;
|
||||
|
||||
import "mediapipe/framework/calculator.proto";
|
||||
|
||||
message TimedBoxListIdToLabelCalculatorOptions {
|
||||
extend mediapipe.CalculatorOptions {
|
||||
optional TimedBoxListIdToLabelCalculatorOptions ext = 297701606;
|
||||
}
|
||||
|
||||
// Path to a label map file for getting the actual name of detected classes.
|
||||
optional string label_map_path = 1;
|
||||
}
|
||||
@@ -12,6 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
@@ -66,6 +68,27 @@ void AddTimedBoxProtoToRenderData(
|
||||
rect->set_bottom(box_proto.bottom());
|
||||
rect->set_rotation(box_proto.rotation());
|
||||
}
|
||||
|
||||
if (box_proto.has_label()) {
|
||||
auto* label_annotation = render_data->add_render_annotations();
|
||||
label_annotation->mutable_color()->set_r(options.box_color().r());
|
||||
label_annotation->mutable_color()->set_g(options.box_color().g());
|
||||
label_annotation->mutable_color()->set_b(options.box_color().b());
|
||||
label_annotation->set_thickness(options.thickness());
|
||||
RenderAnnotation::Text* text = label_annotation->mutable_text();
|
||||
text->set_display_text(box_proto.label());
|
||||
text->set_normalized(true);
|
||||
constexpr float text_left_start = 0.2f;
|
||||
text->set_left((1.0f - text_left_start) * box_proto.left() +
|
||||
text_left_start * box_proto.right());
|
||||
constexpr float text_baseline = 0.6f;
|
||||
text->set_baseline(text_baseline * box_proto.bottom() +
|
||||
(1.0f - text_baseline) * box_proto.top());
|
||||
constexpr float text_height = 0.1f;
|
||||
text->set_font_height(std::min(box_proto.bottom() - box_proto.top(),
|
||||
box_proto.right() - box_proto.left()) *
|
||||
text_height);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -65,6 +65,26 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "tracked_detection_manager_calculator_proto",
|
||||
srcs = ["tracked_detection_manager_calculator.proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//mediapipe/framework:calculator_proto",
|
||||
"//mediapipe/util/tracking:tracked_detection_manager_config_proto",
|
||||
],
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "box_detector_calculator_proto",
|
||||
srcs = ["box_detector_calculator.proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//mediapipe/framework:calculator_proto",
|
||||
"//mediapipe/util/tracking:box_detector_proto",
|
||||
],
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "video_pre_stream_calculator_proto",
|
||||
srcs = ["video_pre_stream_calculator.proto"],
|
||||
@@ -107,6 +127,28 @@ mediapipe_cc_proto_library(
|
||||
deps = [":box_tracker_calculator_proto"],
|
||||
)
|
||||
|
||||
mediapipe_cc_proto_library(
|
||||
name = "tracked_detection_manager_calculator_cc_proto",
|
||||
srcs = ["tracked_detection_manager_calculator.proto"],
|
||||
cc_deps = [
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
"//mediapipe/util/tracking:tracked_detection_manager_config_cc_proto",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [":tracked_detection_manager_calculator_proto"],
|
||||
)
|
||||
|
||||
mediapipe_cc_proto_library(
|
||||
name = "box_detector_calculator_cc_proto",
|
||||
srcs = ["box_detector_calculator.proto"],
|
||||
cc_deps = [
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
"//mediapipe/util/tracking:box_detector_cc_proto",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [":box_detector_calculator_proto"],
|
||||
)
|
||||
|
||||
mediapipe_cc_proto_library(
|
||||
name = "video_pre_stream_calculator_cc_proto",
|
||||
srcs = ["video_pre_stream_calculator.proto"],
|
||||
@@ -138,7 +180,7 @@ cc_library(
|
||||
srcs = ["flow_to_image_calculator.cc"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//mediapipe/calculators/video:flow_to_image_calculator_cc_proto",
|
||||
":flow_to_image_calculator_cc_proto",
|
||||
"//mediapipe/calculators/video/tool:flow_quantizer_model",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/formats:image_format_cc_proto",
|
||||
@@ -279,11 +321,54 @@ cc_library(
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "box_detector_calculator",
|
||||
srcs = ["box_detector_calculator.cc"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":box_detector_calculator_cc_proto",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/strings",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/formats:image_frame",
|
||||
"//mediapipe/framework/formats:image_frame_opencv",
|
||||
"//mediapipe/framework/formats:video_stream_header", # fixdeps: keep -- required for exobazel build.
|
||||
"//mediapipe/framework/port:integral_types",
|
||||
"//mediapipe/framework/port:logging",
|
||||
"//mediapipe/framework/port:opencv_core",
|
||||
"//mediapipe/framework/port:opencv_features2d",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
"//mediapipe/util:resource_util",
|
||||
"//mediapipe/util/tracking",
|
||||
"//mediapipe/util/tracking:box_detector",
|
||||
"//mediapipe/util/tracking:box_tracker",
|
||||
"//mediapipe/util/tracking:box_tracker_cc_proto",
|
||||
"//mediapipe/util/tracking:flow_packager_cc_proto",
|
||||
"//mediapipe/util/tracking:tracking_visualization_utilities",
|
||||
] + select({
|
||||
"//mediapipe:android": [
|
||||
"//mediapipe/util/android/file/base",
|
||||
],
|
||||
"//mediapipe:ios": [
|
||||
"//mediapipe/util/android/file/base",
|
||||
],
|
||||
"//mediapipe:macos": [
|
||||
"//mediapipe/framework/port:file_helpers",
|
||||
],
|
||||
"//conditions:default": [
|
||||
"//mediapipe/framework/port:file_helpers",
|
||||
],
|
||||
}),
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "tracked_detection_manager_calculator",
|
||||
srcs = ["tracked_detection_manager_calculator.cc"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":tracked_detection_manager_calculator_cc_proto",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/formats:detection_cc_proto",
|
||||
"//mediapipe/framework/formats:location_data_cc_proto",
|
||||
@@ -368,6 +453,7 @@ cc_test(
|
||||
cc_test(
|
||||
name = "tvl1_optical_flow_calculator_test",
|
||||
srcs = ["tvl1_optical_flow_calculator_test.cc"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
":tvl1_optical_flow_calculator",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
@@ -383,20 +469,18 @@ cc_test(
|
||||
],
|
||||
)
|
||||
|
||||
MEDIAPIPE_DEPS = [
|
||||
"//mediapipe/calculators/video:box_tracker_calculator",
|
||||
"//mediapipe/calculators/video:flow_packager_calculator",
|
||||
"//mediapipe/calculators/video:motion_analysis_calculator",
|
||||
"//mediapipe/framework/stream_handler:fixed_size_input_stream_handler",
|
||||
"//mediapipe/framework/stream_handler:sync_set_input_stream_handler",
|
||||
]
|
||||
|
||||
mediapipe_binary_graph(
|
||||
name = "parallel_tracker_binarypb",
|
||||
graph = "testdata/parallel_tracker_graph.pbtxt",
|
||||
output_name = "testdata/parallel_tracker.binarypb",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = MEDIAPIPE_DEPS,
|
||||
deps = [
|
||||
":box_tracker_calculator",
|
||||
":flow_packager_calculator",
|
||||
":motion_analysis_calculator",
|
||||
"//mediapipe/framework/stream_handler:fixed_size_input_stream_handler",
|
||||
"//mediapipe/framework/stream_handler:sync_set_input_stream_handler",
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_binary_graph(
|
||||
@@ -404,7 +488,13 @@ mediapipe_binary_graph(
|
||||
graph = "testdata/tracker_graph.pbtxt",
|
||||
output_name = "testdata/tracker.binarypb",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = MEDIAPIPE_DEPS,
|
||||
deps = [
|
||||
":box_tracker_calculator",
|
||||
":flow_packager_calculator",
|
||||
":motion_analysis_calculator",
|
||||
"//mediapipe/framework/stream_handler:fixed_size_input_stream_handler",
|
||||
"//mediapipe/framework/stream_handler:sync_set_input_stream_handler",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
|
||||
@@ -0,0 +1,393 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/numbers.h"
|
||||
#include "mediapipe/calculators/video/box_detector_calculator.pb.h"
|
||||
#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/port/integral_types.h"
|
||||
#include "mediapipe/framework/port/logging.h"
|
||||
#include "mediapipe/framework/port/opencv_core_inc.h"
|
||||
#include "mediapipe/framework/port/opencv_features2d_inc.h"
|
||||
#include "mediapipe/framework/port/ret_check.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/util/resource_util.h"
|
||||
#include "mediapipe/util/tracking/box_detector.h"
|
||||
#include "mediapipe/util/tracking/box_tracker.h"
|
||||
#include "mediapipe/util/tracking/box_tracker.pb.h"
|
||||
#include "mediapipe/util/tracking/flow_packager.pb.h"
|
||||
#include "mediapipe/util/tracking/tracking.h"
|
||||
#include "mediapipe/util/tracking/tracking_visualization_utilities.h"
|
||||
|
||||
#if defined(MEDIAPIPE_MOBILE)
|
||||
#include "mediapipe/util/android/file/base/file.h"
|
||||
#include "mediapipe/util/android/file/base/helpers.h"
|
||||
#else
|
||||
#include "mediapipe/framework/port/file_helpers.h"
|
||||
#endif
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
// A calculator to detect reappeared box positions from single frame.
|
||||
//
|
||||
// Input stream:
|
||||
// TRACKING: Input tracking data (proto TrackingData) containing features and
|
||||
// descriptors.
|
||||
// VIDEO: Optional input video stream tracked boxes are rendered over
|
||||
// (Required if VIZ is specified).
|
||||
// FEATURES: Input feature points (std::vector<cv::KeyPoint>) in the original
|
||||
// pixel space.
|
||||
// DESCRIPTORS: Input feature descriptors (std::vector<float>). Actual feature
|
||||
// dimension needs to be specified in detector_options.
|
||||
// IMAGE_SIZE: Input image dimension.
|
||||
// TRACKED_BOXES : input box tracking result (proto TimedBoxProtoList) from
|
||||
// BoxTrackerCalculator.
|
||||
// ADD_INDEX: Optional std::string containing binary format proto of type
|
||||
// BoxDetectorIndex. Used for adding target index to the detector
|
||||
// search index during runtime.
|
||||
// CANCEL_OBJECT_ID: Optional id of box to be removed. This is recommended
|
||||
// to be used with SyncSetInputStreamHandler.
|
||||
// REACQ_SWITCH: Optional bool for swithcing on and off reacquisition
|
||||
// functionality. User should initialize a graph with box detector
|
||||
// calculator and be able to switch it on and off in runtime.
|
||||
//
|
||||
// Output streams:
|
||||
// VIZ: Optional output video stream with rendered box positions
|
||||
// (requires VIDEO to be present)
|
||||
// BOXES: Optional output stream of type TimedBoxProtoList for each lost box.
|
||||
//
|
||||
// Imput side packets:
|
||||
// INDEX_PROTO_STRING: Optional std::string containing binary format proto of
|
||||
// type
|
||||
// BoxDetectorIndex. Used for initializing box_detector
|
||||
// with predefined template images.
|
||||
// FRAME_ALIGNMENT: Optional integer to indicate alignment_boundary for
|
||||
// outputing ImageFrame in "VIZ" stream.
|
||||
// Set to ImageFrame::kDefaultAlignmentBoundary for
|
||||
// offline pipeline to be compatible with FFmpeg.
|
||||
// Set to ImageFrame::kGlDefaultAlignmentBoundary for Apps
|
||||
// to be compatible with GL renderer.
|
||||
// OUTPUT_INDEX_FILENAME: File path to the output index file.
|
||||
|
||||
class BoxDetectorCalculator : public CalculatorBase {
|
||||
public:
|
||||
~BoxDetectorCalculator() override = default;
|
||||
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc);
|
||||
|
||||
::mediapipe::Status Open(CalculatorContext* cc) override;
|
||||
::mediapipe::Status Process(CalculatorContext* cc) override;
|
||||
::mediapipe::Status Close(CalculatorContext* cc) override;
|
||||
|
||||
private:
|
||||
BoxDetectorCalculatorOptions options_;
|
||||
std::unique_ptr<BoxDetectorInterface> box_detector_;
|
||||
bool detector_switch_ = true;
|
||||
uint32 frame_alignment_ = ImageFrame::kDefaultAlignmentBoundary;
|
||||
bool write_index_ = false;
|
||||
int box_id_ = 0;
|
||||
};
|
||||
|
||||
REGISTER_CALCULATOR(BoxDetectorCalculator);
|
||||
|
||||
::mediapipe::Status BoxDetectorCalculator::GetContract(CalculatorContract* cc) {
|
||||
if (cc->Inputs().HasTag("TRACKING")) {
|
||||
cc->Inputs().Tag("TRACKING").Set<TrackingData>();
|
||||
}
|
||||
|
||||
if (cc->Inputs().HasTag("TRACKED_BOXES")) {
|
||||
cc->Inputs().Tag("TRACKED_BOXES").Set<TimedBoxProtoList>();
|
||||
}
|
||||
|
||||
if (cc->Inputs().HasTag("VIDEO")) {
|
||||
cc->Inputs().Tag("VIDEO").Set<ImageFrame>();
|
||||
}
|
||||
|
||||
if (cc->Inputs().HasTag("FEATURES")) {
|
||||
RET_CHECK(cc->Inputs().HasTag("DESCRIPTORS"))
|
||||
<< "FEATURES and DESCRIPTORS need to be specified together.";
|
||||
cc->Inputs().Tag("FEATURES").Set<std::vector<cv::KeyPoint>>();
|
||||
}
|
||||
|
||||
if (cc->Inputs().HasTag("DESCRIPTORS")) {
|
||||
RET_CHECK(cc->Inputs().HasTag("FEATURES"))
|
||||
<< "FEATURES and DESCRIPTORS need to be specified together.";
|
||||
cc->Inputs().Tag("DESCRIPTORS").Set<std::vector<float>>();
|
||||
}
|
||||
|
||||
if (cc->Inputs().HasTag("IMAGE_SIZE")) {
|
||||
cc->Inputs().Tag("IMAGE_SIZE").Set<std::pair<int, int>>();
|
||||
}
|
||||
|
||||
if (cc->Inputs().HasTag("ADD_INDEX")) {
|
||||
cc->Inputs().Tag("ADD_INDEX").Set<std::string>();
|
||||
}
|
||||
|
||||
if (cc->Inputs().HasTag("CANCEL_OBJECT_ID")) {
|
||||
cc->Inputs().Tag("CANCEL_OBJECT_ID").Set<int>();
|
||||
}
|
||||
|
||||
if (cc->Inputs().HasTag("REACQ_SWITCH")) {
|
||||
cc->Inputs().Tag("REACQ_SWITCH").Set<bool>();
|
||||
}
|
||||
|
||||
if (cc->Outputs().HasTag("BOXES")) {
|
||||
cc->Outputs().Tag("BOXES").Set<TimedBoxProtoList>();
|
||||
}
|
||||
|
||||
if (cc->Outputs().HasTag("VIZ")) {
|
||||
RET_CHECK(cc->Inputs().HasTag("VIDEO"))
|
||||
<< "Output stream VIZ requires VIDEO to be present.";
|
||||
cc->Outputs().Tag("VIZ").Set<ImageFrame>();
|
||||
}
|
||||
|
||||
if (cc->InputSidePackets().HasTag("INDEX_PROTO_STRING")) {
|
||||
cc->InputSidePackets().Tag("INDEX_PROTO_STRING").Set<std::string>();
|
||||
}
|
||||
|
||||
if (cc->InputSidePackets().HasTag("OUTPUT_INDEX_FILENAME")) {
|
||||
cc->InputSidePackets().Tag("OUTPUT_INDEX_FILENAME").Set<std::string>();
|
||||
}
|
||||
|
||||
if (cc->InputSidePackets().HasTag("FRAME_ALIGNMENT")) {
|
||||
cc->InputSidePackets().Tag("FRAME_ALIGNMENT").Set<int>();
|
||||
}
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status BoxDetectorCalculator::Open(CalculatorContext* cc) {
|
||||
options_ = cc->Options<BoxDetectorCalculatorOptions>();
|
||||
box_detector_ = BoxDetectorInterface::Create(options_.detector_options());
|
||||
|
||||
if (cc->InputSidePackets().HasTag("INDEX_PROTO_STRING")) {
|
||||
BoxDetectorIndex predefined_index;
|
||||
if (!predefined_index.ParseFromString(cc->InputSidePackets()
|
||||
.Tag("INDEX_PROTO_STRING")
|
||||
.Get<std::string>())) {
|
||||
LOG(FATAL) << "failed to parse BoxDetectorIndex from INDEX_PROTO_STRING";
|
||||
}
|
||||
box_detector_->AddBoxDetectorIndex(predefined_index);
|
||||
}
|
||||
|
||||
for (const auto& filename : options_.index_proto_filename()) {
|
||||
std::string string_path;
|
||||
ASSIGN_OR_RETURN(string_path, PathToResourceAsFile(filename));
|
||||
std::string index_string;
|
||||
MP_RETURN_IF_ERROR(file::GetContents(string_path, &index_string));
|
||||
BoxDetectorIndex predefined_index;
|
||||
if (!predefined_index.ParseFromString(index_string)) {
|
||||
LOG(FATAL)
|
||||
<< "failed to parse BoxDetectorIndex from index_proto_filename";
|
||||
}
|
||||
box_detector_->AddBoxDetectorIndex(predefined_index);
|
||||
}
|
||||
|
||||
if (cc->InputSidePackets().HasTag("OUTPUT_INDEX_FILENAME")) {
|
||||
write_index_ = true;
|
||||
}
|
||||
|
||||
if (cc->InputSidePackets().HasTag("FRAME_ALIGNMENT")) {
|
||||
frame_alignment_ = cc->InputSidePackets().Tag("FRAME_ALIGNMENT").Get<int>();
|
||||
}
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status BoxDetectorCalculator::Process(CalculatorContext* cc) {
|
||||
const Timestamp timestamp = cc->InputTimestamp();
|
||||
const int64 timestamp_msec = timestamp.Value() / 1000;
|
||||
|
||||
InputStream* cancel_object_id_stream =
|
||||
cc->Inputs().HasTag("CANCEL_OBJECT_ID")
|
||||
? &(cc->Inputs().Tag("CANCEL_OBJECT_ID"))
|
||||
: nullptr;
|
||||
if (cancel_object_id_stream && !cancel_object_id_stream->IsEmpty()) {
|
||||
const int cancel_object_id = cancel_object_id_stream->Get<int>();
|
||||
box_detector_->CancelBoxDetection(cancel_object_id);
|
||||
}
|
||||
|
||||
InputStream* add_index_stream = cc->Inputs().HasTag("ADD_INDEX")
|
||||
? &(cc->Inputs().Tag("ADD_INDEX"))
|
||||
: nullptr;
|
||||
if (add_index_stream && !add_index_stream->IsEmpty()) {
|
||||
BoxDetectorIndex predefined_index;
|
||||
if (!predefined_index.ParseFromString(
|
||||
add_index_stream->Get<std::string>())) {
|
||||
LOG(FATAL) << "failed to parse BoxDetectorIndex from ADD_INDEX";
|
||||
}
|
||||
box_detector_->AddBoxDetectorIndex(predefined_index);
|
||||
}
|
||||
|
||||
InputStream* reacq_switch_stream = cc->Inputs().HasTag("REACQ_SWITCH")
|
||||
? &(cc->Inputs().Tag("REACQ_SWITCH"))
|
||||
: nullptr;
|
||||
if (reacq_switch_stream && !reacq_switch_stream->IsEmpty()) {
|
||||
detector_switch_ = reacq_switch_stream->Get<bool>();
|
||||
}
|
||||
|
||||
if (!detector_switch_) {
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
InputStream* track_stream = cc->Inputs().HasTag("TRACKING")
|
||||
? &(cc->Inputs().Tag("TRACKING"))
|
||||
: nullptr;
|
||||
InputStream* video_stream =
|
||||
cc->Inputs().HasTag("VIDEO") ? &(cc->Inputs().Tag("VIDEO")) : nullptr;
|
||||
InputStream* feature_stream = cc->Inputs().HasTag("FEATURES")
|
||||
? &(cc->Inputs().Tag("FEATURES"))
|
||||
: nullptr;
|
||||
InputStream* descriptor_stream = cc->Inputs().HasTag("DESCRIPTORS")
|
||||
? &(cc->Inputs().Tag("DESCRIPTORS"))
|
||||
: nullptr;
|
||||
|
||||
CHECK(track_stream != nullptr || video_stream != nullptr ||
|
||||
(feature_stream != nullptr && descriptor_stream != nullptr))
|
||||
<< "One and only one of {tracking_data, input image frame, "
|
||||
"feature/descriptor} need to be valid.";
|
||||
|
||||
InputStream* tracked_boxes_stream = cc->Inputs().HasTag("TRACKED_BOXES")
|
||||
? &(cc->Inputs().Tag("TRACKED_BOXES"))
|
||||
: nullptr;
|
||||
std::unique_ptr<TimedBoxProtoList> detected_boxes(new TimedBoxProtoList());
|
||||
|
||||
if (track_stream != nullptr) {
|
||||
// Detect from tracking data
|
||||
if (track_stream->IsEmpty()) {
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
const TrackingData& tracking_data = track_stream->Get<TrackingData>();
|
||||
|
||||
CHECK(tracked_boxes_stream != nullptr) << "tracked_boxes needed.";
|
||||
|
||||
const TimedBoxProtoList tracked_boxes =
|
||||
tracked_boxes_stream->Get<TimedBoxProtoList>();
|
||||
|
||||
box_detector_->DetectAndAddBox(tracking_data, tracked_boxes, timestamp_msec,
|
||||
detected_boxes.get());
|
||||
} else if (video_stream != nullptr) {
|
||||
// Detect from input frame
|
||||
if (video_stream->IsEmpty()) {
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
TimedBoxProtoList tracked_boxes;
|
||||
if (tracked_boxes_stream != nullptr && !tracked_boxes_stream->IsEmpty()) {
|
||||
tracked_boxes = tracked_boxes_stream->Get<TimedBoxProtoList>();
|
||||
}
|
||||
|
||||
// Just directly pass along the image frame data as-is for detection; we
|
||||
// don't need to worry about conforming to a specific alignment here.
|
||||
const cv::Mat input_view =
|
||||
formats::MatView(&video_stream->Get<ImageFrame>());
|
||||
box_detector_->DetectAndAddBox(input_view, tracked_boxes, timestamp_msec,
|
||||
detected_boxes.get());
|
||||
} else {
|
||||
if (feature_stream->IsEmpty() || descriptor_stream->IsEmpty()) {
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
const auto& image_size =
|
||||
cc->Inputs().Tag("IMAGE_SIZE").Get<std::pair<int, int>>();
|
||||
float inv_scale = 1.0f / std::max(image_size.first, image_size.second);
|
||||
|
||||
TimedBoxProtoList tracked_boxes;
|
||||
if (tracked_boxes_stream != nullptr && !tracked_boxes_stream->IsEmpty()) {
|
||||
tracked_boxes = tracked_boxes_stream->Get<TimedBoxProtoList>();
|
||||
} else if (write_index_) {
|
||||
auto* box_ptr = tracked_boxes.add_box();
|
||||
box_ptr->set_id(box_id_);
|
||||
box_ptr->set_reacquisition(true);
|
||||
box_ptr->set_aspect_ratio((float)image_size.first /
|
||||
(float)image_size.second);
|
||||
|
||||
box_ptr->mutable_quad()->add_vertices(0);
|
||||
box_ptr->mutable_quad()->add_vertices(0);
|
||||
|
||||
box_ptr->mutable_quad()->add_vertices(0);
|
||||
box_ptr->mutable_quad()->add_vertices(1);
|
||||
|
||||
box_ptr->mutable_quad()->add_vertices(1);
|
||||
box_ptr->mutable_quad()->add_vertices(1);
|
||||
|
||||
box_ptr->mutable_quad()->add_vertices(1);
|
||||
box_ptr->mutable_quad()->add_vertices(0);
|
||||
|
||||
++box_id_;
|
||||
}
|
||||
|
||||
const auto& features = feature_stream->Get<std::vector<cv::KeyPoint>>();
|
||||
const int feature_size = features.size();
|
||||
std::vector<Vector2_f> features_vec(feature_size);
|
||||
|
||||
const auto& descriptors = descriptor_stream->Get<std::vector<float>>();
|
||||
const int dims = options_.detector_options().descriptor_dims();
|
||||
CHECK_GE(descriptors.size(), feature_size * dims);
|
||||
cv::Mat descriptors_mat(feature_size, dims, CV_32F);
|
||||
for (int j = 0; j < feature_size; ++j) {
|
||||
features_vec[j].Set(features[j].pt.x * inv_scale,
|
||||
features[j].pt.y * inv_scale);
|
||||
for (int i = 0; i < dims; ++i) {
|
||||
descriptors_mat.at<float>(j, i) = descriptors[j * dims + i];
|
||||
}
|
||||
}
|
||||
|
||||
box_detector_->DetectAndAddBoxFromFeatures(
|
||||
features_vec, descriptors_mat, tracked_boxes, timestamp_msec,
|
||||
image_size.first * inv_scale, image_size.second * inv_scale,
|
||||
detected_boxes.get());
|
||||
}
|
||||
|
||||
if (cc->Outputs().HasTag("VIZ")) {
|
||||
cv::Mat viz_view;
|
||||
std::unique_ptr<ImageFrame> viz_frame;
|
||||
if (video_stream != nullptr && !video_stream->IsEmpty()) {
|
||||
viz_frame = absl::make_unique<ImageFrame>();
|
||||
viz_frame->CopyFrom(video_stream->Get<ImageFrame>(), frame_alignment_);
|
||||
viz_view = formats::MatView(viz_frame.get());
|
||||
}
|
||||
for (const auto& box : detected_boxes->box()) {
|
||||
RenderBox(box, &viz_view);
|
||||
}
|
||||
cc->Outputs().Tag("VIZ").Add(viz_frame.release(), timestamp);
|
||||
}
|
||||
|
||||
if (cc->Outputs().HasTag("BOXES")) {
|
||||
cc->Outputs().Tag("BOXES").Add(detected_boxes.release(), timestamp);
|
||||
}
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status BoxDetectorCalculator::Close(CalculatorContext* cc) {
|
||||
if (write_index_) {
|
||||
BoxDetectorIndex index = box_detector_->ObtainBoxDetectorIndex();
|
||||
MEDIAPIPE_CHECK_OK(mediapipe::file::SetContents(
|
||||
cc->InputSidePackets().Tag("OUTPUT_INDEX_FILENAME").Get<std::string>(),
|
||||
index.SerializeAsString()));
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package mediapipe;
|
||||
|
||||
import "mediapipe/framework/calculator.proto";
|
||||
import "mediapipe/util/tracking/box_detector.proto";
|
||||
|
||||
message BoxDetectorCalculatorOptions {
|
||||
extend CalculatorOptions {
|
||||
optional BoxDetectorCalculatorOptions ext = 289746530;
|
||||
}
|
||||
|
||||
optional BoxDetectorOptions detector_options = 1;
|
||||
|
||||
// File path to the template index files.
|
||||
repeated string index_proto_filename = 2;
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "absl/container/node_hash_map.h"
|
||||
#include "mediapipe/calculators/video/tracked_detection_manager_calculator.pb.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/formats/detection.pb.h"
|
||||
#include "mediapipe/framework/formats/location_data.pb.h"
|
||||
@@ -139,6 +140,7 @@ Detection GetAxisAlignedDetectionFromTrackedDetection(
|
||||
class TrackedDetectionManagerCalculator : public CalculatorBase {
|
||||
public:
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc);
|
||||
::mediapipe::Status Open(CalculatorContext* cc) override;
|
||||
|
||||
::mediapipe::Status Process(CalculatorContext* cc) override;
|
||||
|
||||
@@ -184,6 +186,15 @@ REGISTER_CALCULATOR(TrackedDetectionManagerCalculator);
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status TrackedDetectionManagerCalculator::Open(
|
||||
CalculatorContext* cc) {
|
||||
mediapipe::TrackedDetectionManagerCalculatorOptions options =
|
||||
cc->Options<mediapipe::TrackedDetectionManagerCalculatorOptions>();
|
||||
tracked_detection_manager_.SetConfig(
|
||||
options.tracked_detection_manager_options());
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status TrackedDetectionManagerCalculator::Process(
|
||||
CalculatorContext* cc) {
|
||||
if (cc->Inputs().HasTag("TRACKING_BOXES")) {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2020 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package mediapipe;
|
||||
|
||||
import "mediapipe/framework/calculator.proto";
|
||||
import "mediapipe/util/tracking/tracked_detection_manager_config.proto";
|
||||
|
||||
message TrackedDetectionManagerCalculatorOptions {
|
||||
extend CalculatorOptions {
|
||||
optional TrackedDetectionManagerCalculatorOptions ext = 301970230;
|
||||
}
|
||||
|
||||
optional TrackedDetectionManagerConfig tracked_detection_manager_options = 1;
|
||||
}
|
||||
@@ -15,6 +15,9 @@ For overall context on AutoFlip, please read this
|
||||
|
||||
Run the following command to build the AutoFlip pipeline:
|
||||
|
||||
Note: AutoFlip currently only works with OpenCV 3 . Please verify your OpenCV
|
||||
version beforehand.
|
||||
|
||||
```bash
|
||||
bazel build -c opt --define MEDIAPIPE_DISABLE_GPU=1 mediapipe/examples/desktop/autoflip:run_autoflip
|
||||
```
|
||||
|
||||
@@ -44,6 +44,14 @@ graphs can be easily adapted to run on CPU v.s. GPU.
|
||||
[Object Detection and Tracking with GPU](./object_tracking_mobile_gpu.md) illustrates how to
|
||||
use MediaPipe for object detection and tracking.
|
||||
|
||||
### Objectron: 3D Object Detection and Tracking with GPU
|
||||
|
||||
[MediaPipe Objectron is 3D Object Detection with GPU](./objectron_mobile_gpu.md)
|
||||
illustrates mobile real-time 3D object detection and tracking pipeline for every
|
||||
day objects like shoes and chairs
|
||||
|
||||
* [Android](./objectron_mobile_gpu.md)
|
||||
|
||||
### Face Detection with GPU
|
||||
|
||||
[Face Detection with GPU](./face_detection_mobile_gpu.md) illustrates how to use
|
||||
|
||||
@@ -51,7 +51,7 @@ To build and run the TensorFlow Lite example on desktop (GPU) with Webcam, run:
|
||||
```bash
|
||||
# Video from webcam running on desktop GPU
|
||||
# This works only for linux currently
|
||||
$ bazel build -c opt --copt -DMESA_EGL_NO_X11_HEADERS \
|
||||
$ bazel build -c opt --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 \
|
||||
mediapipe/examples/desktop/face_detection:face_detection_gpu
|
||||
|
||||
# It should print:
|
||||
@@ -259,9 +259,9 @@ node {
|
||||
# Draws annotations and overlays them on top of the input images.
|
||||
node {
|
||||
calculator: "AnnotationOverlayCalculator"
|
||||
input_stream: "INPUT_FRAME:throttled_input_video"
|
||||
input_stream: "IMAGE:throttled_input_video"
|
||||
input_stream: "render_data"
|
||||
output_stream: "OUTPUT_FRAME:output_video"
|
||||
output_stream: "IMAGE:output_video"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -229,9 +229,9 @@ node {
|
||||
# Draws annotations and overlays them on top of the input images.
|
||||
node {
|
||||
calculator: "AnnotationOverlayCalculator"
|
||||
input_stream: "INPUT_FRAME:input_video_cpu"
|
||||
input_stream: "IMAGE:input_video_cpu"
|
||||
input_stream: "render_data"
|
||||
output_stream: "OUTPUT_FRAME:output_video_cpu"
|
||||
output_stream: "IMAGE:output_video_cpu"
|
||||
}
|
||||
|
||||
# Transfers the annotated image from CPU back to GPU memory, to be sent out of
|
||||
|
||||
@@ -221,8 +221,8 @@ node {
|
||||
# Draws annotations and overlays them on top of the input images.
|
||||
node {
|
||||
calculator: "AnnotationOverlayCalculator"
|
||||
input_stream: "INPUT_FRAME_GPU:throttled_input_video"
|
||||
input_stream: "IMAGE_GPU:throttled_input_video"
|
||||
input_stream: "render_data"
|
||||
output_stream: "OUTPUT_FRAME_GPU:output_video"
|
||||
output_stream: "IMAGE_GPU:output_video"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -190,10 +190,10 @@ within the MediaPipe framework:
|
||||
bazel build --define MEDIAPIPE_DISABLE_GPU=1 <my-target>
|
||||
|
||||
# to enable full GPU support (OpenGL ES 3.1+ & Metal)
|
||||
bazel build --copt -DMESA_EGL_NO_X11_HEADERS <my-target>
|
||||
bazel build --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 <my-target>
|
||||
|
||||
# to enable only OpenGL ES 3.0 and below (no GLES 3.1+ features)
|
||||
bazel build --copt -DMESA_EGL_NO_X11_HEADERS --copt -DMEDIAPIPE_DISABLE_GL_COMPUTE <my-target>
|
||||
bazel build --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 --copt -DMEDIAPIPE_DISABLE_GL_COMPUTE <my-target>
|
||||
```
|
||||
|
||||
Note *MEDIAPIPE_DISABLE_GL_COMPUTE* is automatically defined on all Apple
|
||||
|
||||
@@ -26,7 +26,7 @@ To build and run the TensorFlow Lite example on desktop (GPU) with Webcam, run:
|
||||
```bash
|
||||
# Video from webcam running on desktop GPU
|
||||
# This works only for linux currently
|
||||
$ bazel build -c opt --copt -DMESA_EGL_NO_X11_HEADERS \
|
||||
$ bazel build -c opt --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 \
|
||||
mediapipe/examples/desktop/hair_segmentation:hair_segmentation_gpu
|
||||
|
||||
# It should print:
|
||||
|
||||
@@ -136,10 +136,10 @@ node {
|
||||
# Draws annotations and overlays them on top of the input images.
|
||||
node {
|
||||
calculator: "AnnotationOverlayCalculator"
|
||||
input_stream: "INPUT_FRAME_GPU:throttled_input_video"
|
||||
input_stream: "IMAGE_GPU:throttled_input_video"
|
||||
input_stream: "detection_render_data"
|
||||
input_stream: "rect_render_data"
|
||||
output_stream: "OUTPUT_FRAME_GPU:output_video"
|
||||
output_stream: "IMAGE_GPU:output_video"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ To build and run the TensorFlow Lite example on desktop (GPU) with Webcam, run:
|
||||
```bash
|
||||
# Video from webcam running on desktop GPU
|
||||
# This works only for linux currently
|
||||
$ bazel build -c opt --copt -DMESA_EGL_NO_X11_HEADERS \
|
||||
$ bazel build -c opt --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 \
|
||||
mediapipe/examples/desktop/hand_tracking:hand_tracking_gpu
|
||||
|
||||
# It should print:
|
||||
|
||||
@@ -716,10 +716,10 @@ node {
|
||||
# Draws annotations and overlays them on top of the input images.
|
||||
node {
|
||||
calculator: "AnnotationOverlayCalculator"
|
||||
input_stream: "INPUT_FRAME_GPU:input_image"
|
||||
input_stream: "IMAGE_GPU:input_image"
|
||||
input_stream: "detection_render_data"
|
||||
input_stream: "landmark_render_data"
|
||||
input_stream: "rect_render_data"
|
||||
output_stream: "OUTPUT_FRAME_GPU:output_image"
|
||||
output_stream: "IMAGE_GPU:output_image"
|
||||
}
|
||||
```
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
+23
-28
@@ -40,12 +40,11 @@ To build and run iOS apps:
|
||||
$ cd mediapipe
|
||||
```
|
||||
|
||||
2. Install Bazel (version between 0.24.1 and 1.2.1).
|
||||
2. Install Bazel.
|
||||
|
||||
Follow the official
|
||||
[Bazel documentation](https://docs.bazel.build/versions/master/install-ubuntu.html)
|
||||
to install Bazel manually. Note that MediaPipe doesn't support Bazel 2.0.0+
|
||||
yet.
|
||||
to install Bazel 2.0 or higher.
|
||||
|
||||
3. Install OpenCV and FFmpeg.
|
||||
|
||||
@@ -111,7 +110,7 @@ To build and run iOS apps:
|
||||
# To compile with GPU support, replace
|
||||
--define MEDIAPIPE_DISABLE_GPU=1
|
||||
# with
|
||||
--copt -DMESA_EGL_NO_X11_HEADERS
|
||||
--copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11
|
||||
# when building GPU examples.
|
||||
```
|
||||
|
||||
@@ -125,7 +124,7 @@ To build and run iOS apps:
|
||||
mediapipe/examples/desktop/hello_world:hello_world
|
||||
|
||||
# If you are running on Linux desktop with GPU support enabled (via mesa drivers)
|
||||
$ bazel run --copt -DMESA_EGL_NO_X11_HEADERS \
|
||||
$ bazel run --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 \
|
||||
mediapipe/examples/desktop/hello_world:hello_world
|
||||
|
||||
# Should print:
|
||||
@@ -152,12 +151,11 @@ To build and run iOS apps:
|
||||
$ cd mediapipe
|
||||
```
|
||||
|
||||
2. Install Bazel (version between 0.24.1 and 1.2.1).
|
||||
2. Install Bazel.
|
||||
|
||||
Follow the official
|
||||
[Bazel documentation](https://docs.bazel.build/versions/master/install-redhat.html)
|
||||
to install Bazel manually. Note that MediaPipe doesn't support Bazel 2.0.0+
|
||||
yet.
|
||||
to install Bazel 2.0 or higher.
|
||||
|
||||
3. Install OpenCV.
|
||||
|
||||
@@ -241,23 +239,18 @@ To build and run iOS apps:
|
||||
$ cd mediapipe
|
||||
```
|
||||
|
||||
3. Install Bazel (version between 0.24.1 and 1.1.0).
|
||||
3. Install Bazel.
|
||||
|
||||
Option 1. Use package manager tool to install Bazel 1.1.0
|
||||
Option 1. Use package manager tool to install Bazel
|
||||
|
||||
```bash
|
||||
# If Bazel 1.1.0+ was installed.
|
||||
$ brew uninstall bazel
|
||||
# Install Bazel 1.1.0
|
||||
$ brew install https://raw.githubusercontent.com/bazelbuild/homebrew-tap/f8a0fa981bcb1784a0d0823e14867b844e94fb3d/Formula/bazel.rb
|
||||
$ brew link bazel
|
||||
$ brew install bazel
|
||||
# Run 'bazel version' to check version of bazel
|
||||
```
|
||||
|
||||
Option 2. Follow the official
|
||||
[Bazel documentation](https://docs.bazel.build/versions/master/install-os-x.html#install-with-installer-mac-os-x)
|
||||
to install any version of Bazel manually. Note that MediaPipe doesn't
|
||||
support Bazel 1.1.0+ on macOS yet.
|
||||
to install Bazel 2.0 or higher.
|
||||
|
||||
4. Install OpenCV and FFmpeg.
|
||||
|
||||
@@ -364,8 +357,10 @@ To build and run iOS apps:
|
||||
|
||||
### Installing on Windows Subsystem for Linux (WSL)
|
||||
|
||||
Note: WSL has historically not provided access to USB cameras. Mediapipe can use
|
||||
a video file as input.
|
||||
Note: The pre-built OpenCV packages don't support cameras in WSL. Unless you
|
||||
[compile](https://funvision.blogspot.com/2019/12/opencv-web-camera-and-video-streams-in.html)
|
||||
OpenCV with FFMPEG and GStreamer in WSL, the live demos won't work with any
|
||||
cameras. Alternatively, you use a video file as input.
|
||||
|
||||
1. Follow the
|
||||
[instruction](https://docs.microsoft.com/en-us/windows/wsl/install-win10) to
|
||||
@@ -373,7 +368,7 @@ a video file as input.
|
||||
|
||||
2. Install Windows ADB and start the ADB server in Windows.
|
||||
|
||||
Note: Window’s and WSL’s adb versions must be the same version, e.g., if WSL
|
||||
Note: Windows' and WSL’s adb versions must be the same version, e.g., if WSL
|
||||
has ADB 1.0.39, you need to download the corresponding Windows ADB from
|
||||
[here](https://dl.google.com/android/repository/platform-tools_r26.0.1-windows.zip).
|
||||
|
||||
@@ -389,18 +384,18 @@ a video file as input.
|
||||
username@DESKTOP-TMVLBJ1:~$ sudo apt-get update && sudo apt-get install -y build-essential git python zip adb openjdk-8-jdk
|
||||
```
|
||||
|
||||
5. Install Bazel (version between 0.24.1 and 1.2.1).
|
||||
5. Install Bazel.
|
||||
|
||||
```bash
|
||||
username@DESKTOP-TMVLBJ1:~$ curl -sLO --retry 5 --retry-max-time 10 \
|
||||
https://storage.googleapis.com/bazel/0.27.0/release/bazel-0.27.0-installer-linux-x86_64.sh && \
|
||||
sudo mkdir -p /usr/local/bazel/0.27.0 && \
|
||||
chmod 755 bazel-0.27.0-installer-linux-x86_64.sh && \
|
||||
sudo ./bazel-0.27.0-installer-linux-x86_64.sh --prefix=/usr/local/bazel/0.27.0 && \
|
||||
source /usr/local/bazel/0.27.0/lib/bazel/bin/bazel-complete.bash
|
||||
https://storage.googleapis.com/bazel/2.0.0/release/bazel-2.0.0-installer-linux-x86_64.sh && \
|
||||
sudo mkdir -p /usr/local/bazel/2.0.0 && \
|
||||
chmod 755 bazel-2.0.0-installer-linux-x86_64.sh && \
|
||||
sudo ./bazel-2.0.0-installer-linux-x86_64.sh --prefix=/usr/local/bazel/2.0.0 && \
|
||||
source /usr/local/bazel/2.0.0/lib/bazel/bin/bazel-complete.bash
|
||||
|
||||
username@DESKTOP-TMVLBJ1:~$ /usr/local/bazel/0.27.0/lib/bazel/bin/bazel version && \
|
||||
alias bazel='/usr/local/bazel/0.27.0/lib/bazel/bin/bazel'
|
||||
username@DESKTOP-TMVLBJ1:~$ /usr/local/bazel/2.0.0/lib/bazel/bin/bazel version && \
|
||||
alias bazel='/usr/local/bazel/2.0.0/lib/bazel/bin/bazel'
|
||||
```
|
||||
|
||||
6. Checkout MediaPipe repository.
|
||||
|
||||
@@ -89,11 +89,11 @@ process new data sets, in the documentation of
|
||||
dataset = d.as_dataset('test')
|
||||
# implement additional processing and batching here
|
||||
dataset_output = dataset.make_one_shot_iterator().get_next()
|
||||
images = dataset_output=['images']
|
||||
labels = dataset_output=['labels']
|
||||
images = dataset_output['images']
|
||||
labels = dataset_output['labels']
|
||||
|
||||
with tf.Session() as sess:
|
||||
images_, labels_ = sess.run(images, labels)
|
||||
images_, labels_ = sess.run([images, labels])
|
||||
print('The shape of images_ is %s' % str(images_.shape))
|
||||
print('The shape of labels_ is %s' % str(labels_.shape))
|
||||
```
|
||||
|
||||
@@ -76,6 +76,10 @@ in the next section.
|
||||
|
||||
2. Open `mediapipe/Mediapipe.tulsiproj` using the Tulsi app.
|
||||
|
||||
Important: If Tulsi displays an error saying "Bazel could not be found",
|
||||
press the "Bazel..." button in the Packages tab and select the `bazel`
|
||||
executable in your homebrew `/bin/` directory.
|
||||
|
||||
3. Select the MediaPipe config in the Configs tab, then press the Generate
|
||||
button below. You will be asked for a location to save the Xcode project.
|
||||
Once the project is generated, it will be opened in Xcode.
|
||||
|
||||
@@ -48,7 +48,7 @@ To build and run the TensorFlow Lite example on desktop (GPU) with Webcam, run:
|
||||
```bash
|
||||
# Video from webcam running on desktop GPU
|
||||
# This works only for linux currently
|
||||
$ bazel build -c opt --copt -DMESA_EGL_NO_X11_HEADERS \
|
||||
$ bazel build -c opt --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 \
|
||||
mediapipe/examples/desktop/multi_hand_tracking:multi_hand_tracking_gpu
|
||||
|
||||
# It should print:
|
||||
|
||||
@@ -745,11 +745,11 @@ node {
|
||||
# a vector of RenderData objects and draws each of them on the input frame.
|
||||
node {
|
||||
calculator: "AnnotationOverlayCalculator"
|
||||
input_stream: "INPUT_FRAME_GPU:input_image"
|
||||
input_stream: "IMAGE_GPU:input_image"
|
||||
input_stream: "detection_render_data"
|
||||
input_stream: "multi_hand_rects_render_data"
|
||||
input_stream: "multi_palm_rects_render_data"
|
||||
input_stream: "VECTOR:0:multi_hand_landmarks_render_data"
|
||||
output_stream: "OUTPUT_FRAME_GPU:output_image"
|
||||
output_stream: "IMAGE_GPU:output_image"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -26,6 +26,7 @@ To build and run the TensorFlow example on desktop, run:
|
||||
$ bazel build -c opt \
|
||||
--define MEDIAPIPE_DISABLE_GPU=1 \
|
||||
--define no_aws_support=true \
|
||||
--linkopt=-s \
|
||||
mediapipe/examples/desktop/object_detection:object_detection_tensorflow
|
||||
|
||||
# It should print:
|
||||
@@ -163,9 +164,9 @@ node {
|
||||
# the graph.
|
||||
node {
|
||||
calculator: "AnnotationOverlayCalculator"
|
||||
input_stream: "INPUT_FRAME:input_video"
|
||||
input_stream: "IMAGE:input_video"
|
||||
input_stream: "render_data"
|
||||
output_stream: "OUTPUT_FRAME:output_video"
|
||||
output_stream: "IMAGE:output_video"
|
||||
}
|
||||
|
||||
# Encodes the annotated images into a video file, adopting properties specified
|
||||
@@ -396,9 +397,9 @@ node {
|
||||
# the graph.
|
||||
node {
|
||||
calculator: "AnnotationOverlayCalculator"
|
||||
input_stream: "INPUT_FRAME:input_video"
|
||||
input_stream: "IMAGE:input_video"
|
||||
input_stream: "render_data"
|
||||
output_stream: "OUTPUT_FRAME:output_video"
|
||||
output_stream: "IMAGE:output_video"
|
||||
}
|
||||
|
||||
# Encodes the annotated images into a video file, adopting properties specified
|
||||
|
||||
@@ -230,9 +230,9 @@ node {
|
||||
# Draws annotations and overlays them on top of the input images.
|
||||
node {
|
||||
calculator: "AnnotationOverlayCalculator"
|
||||
input_stream: "INPUT_FRAME:throttled_input_video_cpu"
|
||||
input_stream: "IMAGE:throttled_input_video_cpu"
|
||||
input_stream: "render_data"
|
||||
output_stream: "OUTPUT_FRAME:output_video_cpu"
|
||||
output_stream: "IMAGE:output_video_cpu"
|
||||
}
|
||||
|
||||
# Transfers the annotated image from CPU back to GPU memory, to be sent out of
|
||||
|
||||
@@ -212,8 +212,8 @@ node {
|
||||
# Draws annotations and overlays them on top of the input images.
|
||||
node {
|
||||
calculator: "AnnotationOverlayCalculator"
|
||||
input_stream: "INPUT_FRAME_GPU:throttled_input_video"
|
||||
input_stream: "IMAGE_GPU:throttled_input_video"
|
||||
input_stream: "render_data"
|
||||
output_stream: "OUTPUT_FRAME_GPU:output_video"
|
||||
output_stream: "IMAGE_GPU:output_video"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -467,9 +467,9 @@ node {
|
||||
# Draws annotations and overlays them on top of the input images.
|
||||
node {
|
||||
calculator: "AnnotationOverlayCalculator"
|
||||
input_stream: "INPUT_FRAME_GPU:input_image"
|
||||
input_stream: "IMAGE_GPU:input_image"
|
||||
input_stream: "detections_render_data"
|
||||
output_stream: "OUTPUT_FRAME_GPU:output_image"
|
||||
output_stream: "IMAGE_GPU:output_image"
|
||||
}
|
||||
|
||||
```
|
||||
@@ -484,7 +484,8 @@ CPU.
|
||||
To build and run the app:
|
||||
|
||||
```bash
|
||||
bazel build -c opt mediapipe/examples/desktop/object_tracking:object_tracking_cpu
|
||||
bazel build -c opt mediapipe/examples/desktop/object_tracking:object_tracking_cpu \
|
||||
--define MEDIAPIPE_DISABLE_GPU=1
|
||||
|
||||
bazel-bin/mediapipe/examples/desktop/object_tracking/object_tracking_cpu \
|
||||
--calculator_graph_config_file=mediapipe/graphs/tracking/object_detection_tracking_desktop_live.pbtxt
|
||||
|
||||
@@ -0,0 +1,489 @@
|
||||
# MediaPipe Objectron (GPU)
|
||||
|
||||
This doc focuses on the
|
||||
[below example graph](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/object_detection_3d/shoe_classic_occlusion_tracking.pbtxt)
|
||||
that performs 3D object detection and tracking with TensorFlow Lite on GPU.
|
||||
|
||||
Objectron for shoes | Objectron for chairs
|
||||
:-----------------------------------------------------------------------------: | :------------------:
|
||||
 | 
|
||||
|
||||
For overall context on MediaPipe Objectron, please read the
|
||||
[Google AI Blog](https://mediapipe.page.link/objectron-aiblog). The Objectron's
|
||||
ML model (see also the [model card](https://mediapipe.page.link/objectron-mc))
|
||||
estimates a 3D bounding box for the detected object.
|
||||
|
||||
## Android
|
||||
|
||||
[Source](https://github.com/google/mediapipe/tree/master/mediapipe/examples/android/src/java/com/google/mediapipe/apps/objectdetection3d)
|
||||
|
||||
An arm64 build of Objectron for shoes can be
|
||||
[downloaded here](https://drive.google.com/open?id=1S0K4hbWt3o31FfQ4QU3Rz7IHrvOUMx1d),
|
||||
and for chairs can be
|
||||
[downloaded here](https://drive.google.com/open?id=1MM8K-13bXLCVS1EHQ-KgkVyEahEPrKej).
|
||||
|
||||
To build and install the Objectron for shoes:
|
||||
|
||||
```bash
|
||||
bazel build -c opt --config android_arm64 mediapipe/examples/android/src/java/com/google/mediapipe/apps/objectdetection3d:objectdetection3d
|
||||
```
|
||||
|
||||
Similarly to build and install the Objectron for chairs, add **--define
|
||||
chair=true** flag to build command.
|
||||
|
||||
```bash
|
||||
bazel build -c opt --define chair=true --config android_arm64 mediapipe/examples/android/src/java/com/google/mediapipe/apps/objectdetection3d:objectdetection3d
|
||||
```
|
||||
|
||||
Once the app is built, install in on Android device with:
|
||||
|
||||
```bash
|
||||
adb install bazel-bin/mediapipe/examples/android/src/java/com/google/mediapipe/apps/objectdetection3d/objectdetection3d.apk
|
||||
```
|
||||
|
||||
## Graph
|
||||
|
||||
The Objectron main graph internally utilizes the Objectron detection subgraph,
|
||||
and the Objectron tracking subgraph. To visualize the graph as shown above, copy
|
||||
the text specification of the graph below and paste it into
|
||||
[MediaPipe Visualizer](https://viz.mediapipe.dev/).
|
||||
|
||||
### Main Graph
|
||||
|
||||
This is the main graph for the shoe detector. This graph runs detection and
|
||||
tracking and renders the output to the display.
|
||||
|
||||

|
||||
|
||||
[Source pbtxt file](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/object_detection_3d/shoe_classic_occlusion_tracking.pbtxt)
|
||||
|
||||
```bash
|
||||
# MediaPipe object detection 3D with tracking graph.
|
||||
|
||||
# Images on GPU coming into and out of the graph.
|
||||
input_stream: "input_video"
|
||||
output_stream: "output_video"
|
||||
|
||||
# Creates a copy of the input_video stream. At the end of the graph, the
|
||||
# GlAnimationOverlayCalculator will consume the input_video texture and draws
|
||||
# on top of it.
|
||||
node: {
|
||||
calculator: "GlScalerCalculator"
|
||||
input_stream: "VIDEO:input_video"
|
||||
output_stream: "VIDEO:input_video_copy"
|
||||
}
|
||||
|
||||
# Resamples the images by specific frame rate. This calculator is used to
|
||||
# control the frequecy of subsequent calculators/subgraphs, e.g. less power
|
||||
# consumption for expensive process.
|
||||
node {
|
||||
calculator: "PacketResamplerCalculator"
|
||||
input_stream: "DATA:input_video_copy"
|
||||
output_stream: "DATA:sampled_input_video"
|
||||
node_options: {
|
||||
[type.googleapis.com/mediapipe.PacketResamplerCalculatorOptions] {
|
||||
frame_rate: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node {
|
||||
calculator: "ObjectronDetectionSubgraphGpu"
|
||||
input_stream: "IMAGE_GPU:sampled_input_video"
|
||||
output_stream: "ANNOTATIONS:objects"
|
||||
}
|
||||
|
||||
node {
|
||||
calculator: "ObjectronTrackingSubgraphGpu"
|
||||
input_stream: "FRAME_ANNOTATION:objects"
|
||||
input_stream: "IMAGE_GPU:input_video_copy"
|
||||
output_stream: "LIFTED_FRAME_ANNOTATION:lifted_tracked_objects"
|
||||
}
|
||||
|
||||
# The rendering nodes:
|
||||
# We are rendering two meshes: 1) a 3D bounding box, which we overlay directly
|
||||
# on the texture, and 2) a shoe CAD model, which we use as an occlusion mask.
|
||||
# These models are designed using different tools, so we supply a transformation
|
||||
# to bring both of them to the Objectron's coordinate system.
|
||||
|
||||
# Creates a model matrices for the tracked object given the lifted 3D points.
|
||||
# This calculator does two things: 1) Estimates object's pose (orientation,
|
||||
# translation, and scale) from the 3D vertices, and
|
||||
# 2) bring the object from the objectron's coordinate system to the renderer
|
||||
# (OpenGL) coordinate system. Since the final goal is to render a mesh file on
|
||||
# top of the object, we also supply a transformation to bring the mesh to the
|
||||
# objectron's coordinate system, and rescale mesh to the unit size.
|
||||
node {
|
||||
calculator: "AnnotationsToModelMatricesCalculator"
|
||||
input_stream: "ANNOTATIONS:lifted_tracked_objects"
|
||||
output_stream: "MODEL_MATRICES:model_matrices"
|
||||
node_options: {
|
||||
[type.googleapis.com/mediapipe.AnnotationsToModelMatricesCalculatorOptions] {
|
||||
# Re-scale the CAD model to the size of a unit box
|
||||
model_scale: 0.05
|
||||
model_scale: 0.05
|
||||
model_scale: 0.05
|
||||
# Bring the box CAD model to objectron's coordinate system. This
|
||||
# is equivalent of -pi/2 rotation along the y-axis (right-hand rule):
|
||||
# Eigen::AngleAxisf(-M_PI / 2., Eigen::Vector3f::UnitY())
|
||||
model_transformation: 0.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: -1.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 1.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 1.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Compute the model matrices for the CAD model of the shoe, to be used as an
|
||||
# occlusion mask. The model will be rendered at the exact same location as the
|
||||
# bounding box.
|
||||
node {
|
||||
calculator: "AnnotationsToModelMatricesCalculator"
|
||||
input_stream: "ANNOTATIONS:lifted_tracked_objects"
|
||||
output_stream: "MODEL_MATRICES:mask_model_matrices"
|
||||
#input_side_packet: "MODEL_SCALE:model_scale"
|
||||
node_options: {
|
||||
[type.googleapis.com/mediapipe.AnnotationsToModelMatricesCalculatorOptions] {
|
||||
# Re-scale the CAD model to the size of a unit box
|
||||
model_scale: 0.45
|
||||
model_scale: 0.25
|
||||
model_scale: 0.15
|
||||
# Bring the shoe CAD model to Deep Pursuit 3D's coordinate system. This
|
||||
# is equivalent of -pi/2 rotation along the x-axis (right-hand rule):
|
||||
# Eigen::AngleAxisf(-M_PI / 2., Eigen::Vector3f::UnitX())
|
||||
model_transformation: 1.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 1.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: -1.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 0.0
|
||||
model_transformation: 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Render everything together. First we render the 3D bounding box animation,
|
||||
# then we render the occlusion mask.
|
||||
node: {
|
||||
calculator: "GlAnimationOverlayCalculator"
|
||||
input_stream: "VIDEO:input_video"
|
||||
input_stream: "MODEL_MATRICES:model_matrices"
|
||||
input_stream: "MASK_MODEL_MATRICES:mask_model_matrices"
|
||||
output_stream: "output_video"
|
||||
input_side_packet: "TEXTURE:box_texture"
|
||||
input_side_packet: "ANIMATION_ASSET:box_asset_name"
|
||||
input_side_packet: "MASK_TEXTURE:obj_texture"
|
||||
input_side_packet: "MASK_ASSET:obj_asset_name"
|
||||
node_options: {
|
||||
[type.googleapis.com/mediapipe.GlAnimationOverlayCalculatorOptions] {
|
||||
# Output resolution is 480x640 with the aspect ratio of 0.75
|
||||
aspect_ratio: 0.75
|
||||
vertical_fov_degrees: 70.
|
||||
animation_speed_fps: 25
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Objectron Detection Subgraph
|
||||
|
||||
Objectron detection subgraph uses the *TfLiteInferenceCalculator* to run
|
||||
inference and decodes the output tensor to *FrameAnnotation* protobuf. The
|
||||
*FrameAnnotation* contains nine keypoints: the bounding box's center, as well as
|
||||
its eight vertices. The boxes will be passed to the Objectron tracking subgraph.
|
||||
|
||||

|
||||
|
||||
[Source pbtxt file](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/object_detection_3d/subgraphs/objectron_detection_gpu.pbtxt)
|
||||
|
||||
```bash
|
||||
# MediaPipe Objectron detection gpu subgraph
|
||||
|
||||
type: "ObjectronDetectionSubgraphGpu"
|
||||
|
||||
input_stream: "IMAGE_GPU:input_video"
|
||||
output_stream: "ANNOTATIONS:objects"
|
||||
|
||||
# Transforms the input image on GPU to a 480x640 image. To scale the input
|
||||
# image, the scale_mode option is set to FIT to preserve the aspect ratio,
|
||||
# resulting in potential letterboxing in the transformed image.
|
||||
node: {
|
||||
calculator: "ImageTransformationCalculator"
|
||||
input_stream: "IMAGE_GPU:input_video"
|
||||
output_stream: "IMAGE_GPU:transformed_input_video"
|
||||
node_options: {
|
||||
[type.googleapis.com/mediapipe.ImageTransformationCalculatorOptions] {
|
||||
output_width: 480
|
||||
output_height: 640
|
||||
scale_mode: FIT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Converts the transformed input image on GPU into an image tensor stored as a
|
||||
# TfLiteTensor.
|
||||
node {
|
||||
calculator: "TfLiteConverterCalculator"
|
||||
input_stream: "IMAGE_GPU:transformed_input_video"
|
||||
output_stream: "TENSORS_GPU:image_tensor"
|
||||
}
|
||||
|
||||
# Generates a single side packet containing a TensorFlow Lite op resolver that
|
||||
# supports custom ops needed by the model used in this graph.
|
||||
node {
|
||||
calculator: "TfLiteCustomOpResolverCalculator"
|
||||
output_side_packet: "opresolver"
|
||||
node_options: {
|
||||
[type.googleapis.com/mediapipe.TfLiteCustomOpResolverCalculatorOptions] {
|
||||
use_gpu: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Runs a TensorFlow Lite model on GPU that takes an image tensor and outputs a
|
||||
# vector of tensors representing, for instance, detection boxes/keypoints and
|
||||
# scores.
|
||||
node {
|
||||
calculator: "TfLiteInferenceCalculator"
|
||||
input_stream: "TENSORS_GPU:image_tensor"
|
||||
output_stream: "TENSORS:detection_tensors"
|
||||
input_side_packet: "CUSTOM_OP_RESOLVER:opresolver"
|
||||
node_options: {
|
||||
[type.googleapis.com/mediapipe.TfLiteInferenceCalculatorOptions] {
|
||||
model_path: "object_detection_3d.tflite"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Decodes the model's output tensor (the heatmap and the distance fields) to 2D
|
||||
# keypoints. There are nine 2D keypoints: one center keypoint and eight vertices
|
||||
# for the 3D bounding box. The calculator parameters determine's the decoder's
|
||||
# sensitivity.
|
||||
node {
|
||||
calculator: "TfLiteTensorsToObjectsCalculator"
|
||||
input_stream: "TENSORS:detection_tensors"
|
||||
output_stream: "ANNOTATIONS:objects"
|
||||
node_options: {
|
||||
[type.googleapis.com/mediapipe.TfLiteTensorsToObjectsCalculatorOptions] {
|
||||
num_classes: 1
|
||||
num_keypoints: 9
|
||||
decoder_config {
|
||||
heatmap_threshold: 0.6
|
||||
local_max_distance: 2
|
||||
offset_scale_coef: 1.0
|
||||
voting_radius: 2
|
||||
voting_allowance: 1
|
||||
voting_threshold: 0.2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Object Tracking Subgraph
|
||||
|
||||
Object tracking subgraph uses a *BoxTracker* calculator which is a generic
|
||||
tracking library, also used in
|
||||
[Mediapipe's 2D Object Detection and Tracking](https://github.com/google/mediapipe/tree/master/mediapipe/g3doc/object_tracking_mobile_gpu.md).
|
||||
The tracking runs every frame and when a new detection is available, it
|
||||
consolidates the detection and tracking results. The tracker tracks the box with
|
||||
its 2D keypoints, so at the end we lift the 2D keypoints to 3D using EPnP
|
||||
algorithm in *Lift2DFrameAnnotationTo3D* Calculator.
|
||||
|
||||

|
||||
|
||||
[Source pbtxt file](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/object_detection_3d/subgraphs/objectron_tracking_gpu.pbtxt)
|
||||
|
||||
```bash
|
||||
# MediaPipe Objectron tracking gpu subgraph
|
||||
|
||||
type: "ObjectronTrackingSubgraphGpu"
|
||||
|
||||
input_stream: "FRAME_ANNOTATION:objects"
|
||||
input_stream: "IMAGE_GPU:input_video"
|
||||
output_stream: "LIFTED_FRAME_ANNOTATION:lifted_tracked_objects"
|
||||
|
||||
|
||||
# Converts the detected keypoints to Boxes, used by the tracking subgraph.
|
||||
node {
|
||||
calculator: "FrameAnnotationToTimedBoxListCalculator"
|
||||
input_stream: "FRAME_ANNOTATION:objects"
|
||||
output_stream: "BOXES:start_pos"
|
||||
}
|
||||
|
||||
node: {
|
||||
calculator: "ImageTransformationCalculator"
|
||||
input_stream: "IMAGE_GPU:input_video"
|
||||
output_stream: "IMAGE_GPU:downscaled_input_video"
|
||||
node_options: {
|
||||
[type.googleapis.com/mediapipe.ImageTransformationCalculatorOptions] {
|
||||
output_width: 240
|
||||
output_height: 320
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Converts GPU buffer to ImageFrame for processing tracking.
|
||||
node: {
|
||||
calculator: "GpuBufferToImageFrameCalculator"
|
||||
input_stream: "downscaled_input_video"
|
||||
output_stream: "downscaled_input_video_cpu"
|
||||
}
|
||||
|
||||
# Performs motion analysis on an incoming video stream.
|
||||
node: {
|
||||
calculator: "MotionAnalysisCalculator"
|
||||
input_stream: "VIDEO:downscaled_input_video_cpu"
|
||||
output_stream: "CAMERA:camera_motion"
|
||||
output_stream: "FLOW:region_flow"
|
||||
|
||||
node_options: {
|
||||
[type.googleapis.com/mediapipe.MotionAnalysisCalculatorOptions]: {
|
||||
analysis_options {
|
||||
analysis_policy: ANALYSIS_POLICY_CAMERA_MOBILE
|
||||
flow_options {
|
||||
fast_estimation_min_block_size: 100
|
||||
top_inlier_sets: 1
|
||||
frac_inlier_error_threshold: 3e-3
|
||||
downsample_mode: DOWNSAMPLE_TO_INPUT_SIZE
|
||||
verification_distance: 5.0
|
||||
verify_long_feature_acceleration: true
|
||||
verify_long_feature_trigger_ratio: 0.1
|
||||
tracking_options {
|
||||
max_features: 500
|
||||
adaptive_extraction_levels: 2
|
||||
min_eig_val_settings {
|
||||
adaptive_lowest_quality_level: 2e-4
|
||||
}
|
||||
klt_tracker_implementation: KLT_OPENCV
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Reads optical flow fields defined in
|
||||
# mediapipe/framework/formats/motion/optical_flow_field.h,
|
||||
# returns a VideoFrame with 2 channels (v_x and v_y), each channel is quantized
|
||||
# to 0-255.
|
||||
node: {
|
||||
calculator: "FlowPackagerCalculator"
|
||||
input_stream: "FLOW:region_flow"
|
||||
input_stream: "CAMERA:camera_motion"
|
||||
output_stream: "TRACKING:tracking_data"
|
||||
|
||||
node_options: {
|
||||
[type.googleapis.com/mediapipe.FlowPackagerCalculatorOptions]: {
|
||||
flow_packager_options: {
|
||||
binary_tracking_data_support: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Tracks box positions over time.
|
||||
node: {
|
||||
calculator: "BoxTrackerCalculator"
|
||||
input_stream: "TRACKING:tracking_data"
|
||||
input_stream: "TRACK_TIME:input_video"
|
||||
input_stream: "START_POS:start_pos"
|
||||
input_stream: "CANCEL_OBJECT_ID:cancel_object_id"
|
||||
input_stream_info: {
|
||||
tag_index: "CANCEL_OBJECT_ID"
|
||||
back_edge: true
|
||||
}
|
||||
output_stream: "BOXES:boxes"
|
||||
|
||||
input_stream_handler {
|
||||
input_stream_handler: "SyncSetInputStreamHandler"
|
||||
options {
|
||||
[mediapipe.SyncSetInputStreamHandlerOptions.ext] {
|
||||
sync_set {
|
||||
tag_index: "TRACKING"
|
||||
tag_index: "TRACK_TIME"
|
||||
}
|
||||
sync_set {
|
||||
tag_index: "START_POS"
|
||||
}
|
||||
sync_set {
|
||||
tag_index: "CANCEL_OBJECT_ID"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node_options: {
|
||||
[type.googleapis.com/mediapipe.BoxTrackerCalculatorOptions]: {
|
||||
tracker_options: {
|
||||
track_step_options {
|
||||
track_object_and_camera: true
|
||||
tracking_degrees: TRACKING_DEGREE_OBJECT_ROTATION_SCALE
|
||||
inlier_spring_force: 0.0
|
||||
static_motion_temporal_ratio: 3e-2
|
||||
}
|
||||
}
|
||||
visualize_tracking_data: false
|
||||
streaming_track_data_cache_size: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Consolidates tracking and detection results.
|
||||
node {
|
||||
calculator: "FrameAnnotationTrackerCalculator"
|
||||
input_stream: "FRAME_ANNOTATION:objects"
|
||||
input_stream: "TRACKED_BOXES:boxes"
|
||||
output_stream: "TRACKED_FRAME_ANNOTATION:tracked_objects"
|
||||
output_stream: "CANCEL_OBJECT_ID:cancel_object_id"
|
||||
node_options: {
|
||||
[type.googleapis.com/mediapipe.FrameAnnotationTrackerCalculatorOptions] {
|
||||
img_width: 240
|
||||
img_height: 320
|
||||
}
|
||||
}
|
||||
|
||||
input_stream_handler {
|
||||
input_stream_handler: "SyncSetInputStreamHandler"
|
||||
options {
|
||||
[mediapipe.SyncSetInputStreamHandlerOptions.ext] {
|
||||
sync_set {
|
||||
tag_index: "FRAME_ANNOTATION"
|
||||
}
|
||||
sync_set {
|
||||
tag_index: "TRACKED_BOXES"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Lift the tracked 2D keypoints to 3D using EPnP algorithm.
|
||||
node {
|
||||
calculator: "Lift2DFrameAnnotationTo3DCalculator"
|
||||
input_stream: "FRAME_ANNOTATION:tracked_objects"
|
||||
output_stream: "LIFTED_FRAME_ANNOTATION:lifted_tracked_objects"
|
||||
}
|
||||
```
|
||||
@@ -61,6 +61,8 @@ videos.
|
||||
```bash
|
||||
# cd to the root directory of the MediaPipe repo
|
||||
cd -
|
||||
|
||||
pip3 install tf_slim
|
||||
python -m mediapipe.examples.desktop.youtube8m.generate_vggish_frozen_graph
|
||||
```
|
||||
|
||||
@@ -78,7 +80,7 @@ videos.
|
||||
5. Run the MediaPipe binary to extract the features.
|
||||
|
||||
```bash
|
||||
bazel build -c opt \
|
||||
bazel build -c opt --linkopt=-s \
|
||||
--define MEDIAPIPE_DISABLE_GPU=1 --define no_aws_support=true \
|
||||
mediapipe/examples/desktop/youtube8m:extract_yt8m_features
|
||||
|
||||
@@ -126,13 +128,13 @@ the inference for both local videos and the dataset
|
||||
2. Build the inference binary.
|
||||
|
||||
```bash
|
||||
bazel build -c opt --define='MEDIAPIPE_DISABLE_GPU=1' \
|
||||
bazel build -c opt --define='MEDIAPIPE_DISABLE_GPU=1' --linkopt=-s \
|
||||
mediapipe/examples/desktop/youtube8m:model_inference
|
||||
```
|
||||
|
||||
3. Run the python web server.
|
||||
|
||||
Note: pip install absl-py
|
||||
Note: pip3 install absl-py
|
||||
|
||||
```bash
|
||||
python mediapipe/examples/desktop/youtube8m/viewer/server.py --root `pwd`
|
||||
@@ -162,7 +164,7 @@ the inference for both local videos and the dataset
|
||||
3. Build and run the inference binary.
|
||||
|
||||
```bash
|
||||
bazel build -c opt --define='MEDIAPIPE_DISABLE_GPU=1' \
|
||||
bazel build -c opt --define='MEDIAPIPE_DISABLE_GPU=1' --linkopt=-s \
|
||||
mediapipe/examples/desktop/youtube8m:model_inference
|
||||
|
||||
# segment_size is the number of seconds window of frames.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user