Project import generated by Copybara.

GitOrigin-RevId: 4cee4a2c2317fb190680c17e31ebbb03bb73b71c
This commit is contained in:
MediaPipe Team
2020-09-17 11:09:17 -04:00
committed by chuoling
parent 1db91b550a
commit a908d668c7
142 changed files with 40878 additions and 574 deletions
+43
View File
@@ -0,0 +1,43 @@
# Copyright 2020 The MediaPipe Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
load(
"//mediapipe/framework/tool:mediapipe_graph.bzl",
"mediapipe_binary_graph",
)
licenses(["notice"])
package(default_visibility = ["//visibility:public"])
cc_library(
name = "face_effect_gpu_deps",
deps = [
"//mediapipe/calculators/core:flow_limiter_calculator",
"//mediapipe/calculators/core:gate_calculator",
"//mediapipe/calculators/core:immediate_mux_calculator",
"//mediapipe/calculators/image:image_properties_calculator",
"//mediapipe/graphs/face_effect/subgraphs:single_face_smooth_landmark_gpu",
"//mediapipe/modules/face_geometry",
"//mediapipe/modules/face_geometry:effect_renderer_calculator",
"//mediapipe/modules/face_geometry:env_generator_calculator",
],
)
mediapipe_binary_graph(
name = "face_effect_gpu_binary_graph",
graph = "face_effect_gpu.pbtxt",
output_name = "face_effect_gpu.binarypb",
deps = [":face_effect_gpu_deps"],
)
+36
View File
@@ -0,0 +1,36 @@
# Copyright 2020 The MediaPipe Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
load("//mediapipe/framework:encode_binary_proto.bzl", "encode_binary_proto")
licenses(["notice"])
package(default_visibility = ["//visibility:public"])
encode_binary_proto(
name = "glasses",
input = "glasses.pbtxt",
message_type = "mediapipe.face_geometry.Mesh3d",
output = "glasses.binarypb",
deps = [
"//mediapipe/modules/face_geometry/protos:mesh_3d_proto",
],
)
# `.pngblob` is used instead of `.png` to prevent iOS build from preprocessing the image.
# OpenCV is unable to read a PNG file preprocessed by the iOS build.
exports_files([
"facepaint.pngblob",
"glasses.pngblob",
])
Binary file not shown.

After

Width:  |  Height:  |  Size: 593 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

@@ -0,0 +1,145 @@
# MediaPipe graph that applies a face effect to the input video stream.
# GPU buffer. (GpuBuffer)
input_stream: "input_video"
# Boolean flag, which indicates whether the Facepaint effect is selected. (bool)
#
# If `true`, the Facepaint effect will be rendered.
# If `false`, the Glasses effect will be rendered.
input_stream: "is_facepaint_effect_selected"
# Output image with rendered results. (GpuBuffer)
output_stream: "output_video"
# A list of geometry data for a single detected face.
#
# NOTE: there will not be an output packet in this stream for this particular
# timestamp if none of faces detected.
#
# (std::vector<face_geometry::FaceGeometry>)
output_stream: "multi_face_geometry"
# Throttles the images flowing downstream for flow control. It passes through
# the very first incoming image unaltered, and waits for downstream nodes
# (calculators and subgraphs) in the graph to finish their tasks before it
# passes through another image. All images that come in while waiting are
# dropped, limiting the number of in-flight images in most part of the graph to
# 1. This prevents the downstream nodes from queuing up incoming images and data
# excessively, which leads to increased latency and memory usage, unwanted in
# real-time mobile applications. It also eliminates unnecessarily computation,
# e.g., the output produced by a node may get dropped downstream if the
# subsequent nodes are still busy processing previous inputs.
node {
calculator: "FlowLimiterCalculator"
input_stream: "input_video"
input_stream: "FINISHED:output_video"
input_stream_info: {
tag_index: "FINISHED"
back_edge: true
}
output_stream: "throttled_input_video"
}
# Generates an environment that describes the current virtual scene.
node {
calculator: "FaceGeometryEnvGeneratorCalculator"
output_side_packet: "ENVIRONMENT:environment"
node_options: {
[type.googleapis.com/mediapipe.FaceGeometryEnvGeneratorCalculatorOptions] {
environment: {
origin_point_location: TOP_LEFT_CORNER
perspective_camera: {
vertical_fov_degrees: 63.0 # 63 degrees
near: 1.0 # 1cm
far: 10000.0 # 100m
}
}
}
}
}
# Subgraph that detects a single face and corresponding landmarks. The landmarks
# are also "smoothed" to achieve better visual results.
node {
calculator: "SingleFaceSmoothLandmarkGpu"
input_stream: "IMAGE:throttled_input_video"
output_stream: "LANDMARKS:multi_face_landmarks"
}
# Extracts the throttled input video frame dimensions as a separate packet.
node {
calculator: "ImagePropertiesCalculator"
input_stream: "IMAGE_GPU:throttled_input_video"
output_stream: "SIZE:input_video_size"
}
# Subgraph that computes face geometry from landmarks for a single face.
node {
calculator: "FaceGeometry"
input_stream: "MULTI_FACE_LANDMARKS:multi_face_landmarks"
input_stream: "IMAGE_SIZE:input_video_size"
input_side_packet: "ENVIRONMENT:environment"
output_stream: "MULTI_FACE_GEOMETRY:multi_face_geometry"
}
# Decides whether to render the Facepaint effect based on the
# `is_facepaint_effect_selected` flag value.
node {
calculator: "GateCalculator"
input_stream: "throttled_input_video"
input_stream: "multi_face_geometry"
input_stream: "ALLOW:is_facepaint_effect_selected"
output_stream: "facepaint_effect_throttled_input_video"
output_stream: "facepaint_effect_multi_face_geometry"
}
# Renders the Facepaint effect.
node {
calculator: "FaceGeometryEffectRendererCalculator"
input_side_packet: "ENVIRONMENT:environment"
input_stream: "IMAGE_GPU:facepaint_effect_throttled_input_video"
input_stream: "MULTI_FACE_GEOMETRY:facepaint_effect_multi_face_geometry"
output_stream: "IMAGE_GPU:facepaint_effect_output_video"
node_options: {
[type.googleapis.com/mediapipe.FaceGeometryEffectRendererCalculatorOptions] {
effect_texture_path: "mediapipe/graphs/face_effect/data/facepaint.pngblob"
}
}
}
# Decides whether to render the Glasses effect based on the
# `is_facepaint_effect_selected` flag value.
node {
calculator: "GateCalculator"
input_stream: "throttled_input_video"
input_stream: "multi_face_geometry"
input_stream: "DISALLOW:is_facepaint_effect_selected"
output_stream: "glasses_effect_throttled_input_video"
output_stream: "glasses_effect_multi_face_geometry"
}
# Renders the Glasses effect.
node {
calculator: "FaceGeometryEffectRendererCalculator"
input_side_packet: "ENVIRONMENT:environment"
input_stream: "IMAGE_GPU:glasses_effect_throttled_input_video"
input_stream: "MULTI_FACE_GEOMETRY:glasses_effect_multi_face_geometry"
output_stream: "IMAGE_GPU:glasses_effect_output_video"
node_options: {
[type.googleapis.com/mediapipe.FaceGeometryEffectRendererCalculatorOptions] {
effect_texture_path: "mediapipe/graphs/face_effect/data/glasses.pngblob"
effect_mesh_3d_path: "mediapipe/graphs/face_effect/data/glasses.binarypb"
}
}
}
# Decides which of the Facepaint or the Glasses rendered results should be sent
# as the output GPU frame.
node {
calculator: "ImmediateMuxCalculator"
input_stream: "facepaint_effect_output_video"
input_stream: "glasses_effect_output_video"
output_stream: "output_video"
}
@@ -0,0 +1,36 @@
# Copyright 2020 The MediaPipe Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
load(
"//mediapipe/framework/tool:mediapipe_graph.bzl",
"mediapipe_simple_subgraph",
)
licenses(["notice"])
package(default_visibility = ["//visibility:public"])
mediapipe_simple_subgraph(
name = "single_face_smooth_landmark_gpu",
graph = "single_face_smooth_landmark_gpu.pbtxt",
register_as = "SingleFaceSmoothLandmarkGpu",
deps = [
"//mediapipe/calculators/core:concatenate_vector_calculator",
"//mediapipe/calculators/core:constant_side_packet_calculator",
"//mediapipe/calculators/core:split_vector_calculator",
"//mediapipe/calculators/image:image_properties_calculator",
"//mediapipe/calculators/util:landmarks_smoothing_calculator",
"//mediapipe/modules/face_landmark:face_landmark_front_gpu",
],
)
@@ -0,0 +1,84 @@
# MediaPipe subgraph that detects a single face and corresponding landmarks on
# a input GPU image. The landmarks are also "smoothed" to achieve better visual
# results.
type: "SingleFaceSmoothLandmarkGpu"
# GPU image. (GpuBuffer)
input_stream: "IMAGE:input_image"
# Collection of detected/predicted faces, each represented as a list of face
# landmarks. However, the size of this collection is always 1 because of the
# single-face use in this graph. The decision to wrap the landmark list into a
# collection was made to simplify passing the result into the `FaceGeometry`
# subgraph. (std::vector<NormalizedLandmarkList>)
#
# NOTE: there will not be an output packet in the LANDMARKS stream for this
# particular timestamp if none of faces detected. However, the MediaPipe
# framework will internally inform the downstream calculators of the absence of
# this packet so that they don't wait for it unnecessarily.
output_stream: "LANDMARKS:multi_face_smooth_landmarks"
# Creates a packet to inform the `FaceLandmarkFrontGpu` subgraph to detect at
# most 1 face.
node {
calculator: "ConstantSidePacketCalculator"
output_side_packet: "PACKET:num_faces"
node_options: {
[type.googleapis.com/mediapipe.ConstantSidePacketCalculatorOptions]: {
packet { int_value: 1 }
}
}
}
# Subgraph that detects faces and corresponding landmarks.
node {
calculator: "FaceLandmarkFrontGpu"
input_stream: "IMAGE:input_image"
input_side_packet: "NUM_FACES:num_faces"
output_stream: "LANDMARKS:multi_face_landmarks"
}
# Extracts the detected face landmark list from a collection.
node {
calculator: "SplitNormalizedLandmarkListVectorCalculator"
input_stream: "multi_face_landmarks"
output_stream: "face_landmarks"
node_options: {
[type.googleapis.com/mediapipe.SplitVectorCalculatorOptions] {
ranges: { begin: 0 end: 1 }
element_only: true
}
}
}
# Extracts the input image frame dimensions as a separate packet.
node {
calculator: "ImagePropertiesCalculator"
input_stream: "IMAGE_GPU:input_image"
output_stream: "SIZE:input_image_size"
}
# Applies smoothing to the single face landmarks.
node {
calculator: "LandmarksSmoothingCalculator"
input_stream: "NORM_LANDMARKS:face_landmarks"
input_stream: "IMAGE_SIZE:input_image_size"
output_stream: "NORM_FILTERED_LANDMARKS:face_smooth_landmarks"
node_options: {
[type.googleapis.com/mediapipe.LandmarksSmoothingCalculatorOptions] {
velocity_filter: {
window_size: 5
velocity_scale: 20.0
}
}
}
}
# Puts the single face smooth landmarks back into a collection to simplify
# passing the result into the `FaceGeometry` subgraph.
node {
calculator: "ConcatenateLandmarListVectorCalculator"
input_stream: "face_smooth_landmarks"
output_stream: "multi_face_smooth_landmarks"
}
@@ -16,7 +16,7 @@ load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library"
licenses(["notice"])
package(default_visibility = ["//visibility:private"])
package(default_visibility = ["//visibility:public"])
proto_library(
name = "object_proto",
+2 -2
View File
@@ -26,7 +26,7 @@ cc_library(
deps = [
"//mediapipe/calculators/core:flow_limiter_calculator",
"//mediapipe/calculators/image:image_properties_calculator",
"//mediapipe/graphs/pose_tracking/calculators:landmarks_smoothing_calculator",
"//mediapipe/calculators/util:landmarks_smoothing_calculator",
"//mediapipe/graphs/pose_tracking/subgraphs:upper_body_pose_renderer_gpu",
"//mediapipe/modules/pose_landmark:pose_landmark_upper_body_gpu",
],
@@ -44,7 +44,7 @@ cc_library(
deps = [
"//mediapipe/calculators/core:flow_limiter_calculator",
"//mediapipe/calculators/image:image_properties_calculator",
"//mediapipe/graphs/pose_tracking/calculators:landmarks_smoothing_calculator",
"//mediapipe/calculators/util:landmarks_smoothing_calculator",
"//mediapipe/graphs/pose_tracking/subgraphs:upper_body_pose_renderer_cpu",
"//mediapipe/modules/pose_landmark:pose_landmark_upper_body_cpu",
],
@@ -1,85 +0,0 @@
# Copyright 2020 The MediaPipe Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
load("//mediapipe/framework/port:build_config.bzl", "mediapipe_proto_library")
licenses(["notice"])
package(default_visibility = ["//visibility:public"])
cc_library(
name = "low_pass_filter",
srcs = ["low_pass_filter.cc"],
hdrs = ["low_pass_filter.h"],
deps = [
"//mediapipe/framework/port:logging",
"@com_google_absl//absl/memory",
],
)
cc_test(
name = "low_pass_filter_test",
srcs = ["low_pass_filter_test.cc"],
deps = [
":low_pass_filter",
"//mediapipe/framework/port:gtest_main",
],
)
cc_library(
name = "relative_velocity_filter",
srcs = ["relative_velocity_filter.cc"],
hdrs = ["relative_velocity_filter.h"],
deps = [
":low_pass_filter",
"//mediapipe/framework/port:logging",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/time",
],
)
cc_test(
name = "relative_velocity_filter_test",
srcs = ["relative_velocity_filter_test.cc"],
deps = [
":relative_velocity_filter",
"//mediapipe/framework/port:gtest_main",
"@com_google_absl//absl/time",
],
)
mediapipe_proto_library(
name = "landmarks_smoothing_calculator_proto",
srcs = ["landmarks_smoothing_calculator.proto"],
visibility = ["//visibility:public"],
deps = [
"//mediapipe/framework:calculator_proto",
],
)
cc_library(
name = "landmarks_smoothing_calculator",
srcs = ["landmarks_smoothing_calculator.cc"],
visibility = ["//visibility:public"],
deps = [
":landmarks_smoothing_calculator_cc_proto",
":relative_velocity_filter",
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework:timestamp",
"//mediapipe/framework/formats:landmark_cc_proto",
"//mediapipe/framework/port:ret_check",
"@com_google_absl//absl/algorithm:container",
],
alwayslink = 1,
)
@@ -1,273 +0,0 @@
// Copyright 2020 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "absl/algorithm/container.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/formats/landmark.pb.h"
#include "mediapipe/framework/port/ret_check.h"
#include "mediapipe/framework/timestamp.h"
#include "mediapipe/graphs/pose_tracking/calculators/landmarks_smoothing_calculator.pb.h"
#include "mediapipe/graphs/pose_tracking/calculators/relative_velocity_filter.h"
namespace mediapipe {
namespace {
constexpr char kNormalizedLandmarksTag[] = "NORM_LANDMARKS";
constexpr char kImageSizeTag[] = "IMAGE_SIZE";
constexpr char kNormalizedFilteredLandmarksTag[] = "NORM_FILTERED_LANDMARKS";
using ::mediapipe::RelativeVelocityFilter;
// Estimate object scale to use its inverse value as velocity scale for
// RelativeVelocityFilter. If value will be too small (less than
// `options_.min_allowed_object_scale`) smoothing will be disabled and
// landmarks will be returned as is.
// Object scale is calculated as average between bounding box width and height
// with sides parallel to axis.
float GetObjectScale(const NormalizedLandmarkList& landmarks, int image_width,
int image_height) {
const auto& [lm_min_x, lm_max_x] = absl::c_minmax_element(
landmarks.landmark(),
[](const auto& a, const auto& b) { return a.x() < b.x(); });
const float x_min = lm_min_x->x();
const float x_max = lm_max_x->x();
const auto& [lm_min_y, lm_max_y] = absl::c_minmax_element(
landmarks.landmark(),
[](const auto& a, const auto& b) { return a.y() < b.y(); });
const float y_min = lm_min_y->y();
const float y_max = lm_max_y->y();
const float object_width = (x_max - x_min) * image_width;
const float object_height = (y_max - y_min) * image_height;
return (object_width + object_height) / 2.0f;
}
// Abstract class for various landmarks filters.
class LandmarksFilter {
public:
virtual ~LandmarksFilter() = default;
virtual ::mediapipe::Status Reset() { return ::mediapipe::OkStatus(); }
virtual ::mediapipe::Status Apply(const NormalizedLandmarkList& in_landmarks,
const std::pair<int, int>& image_size,
const absl::Duration& timestamp,
NormalizedLandmarkList* out_landmarks) = 0;
};
// Returns landmarks as is without smoothing.
class NoFilter : public LandmarksFilter {
public:
::mediapipe::Status Apply(const NormalizedLandmarkList& in_landmarks,
const std::pair<int, int>& image_size,
const absl::Duration& timestamp,
NormalizedLandmarkList* out_landmarks) override {
*out_landmarks = in_landmarks;
return ::mediapipe::OkStatus();
}
};
// Please check RelativeVelocityFilter documentation for details.
class VelocityFilter : public LandmarksFilter {
public:
VelocityFilter(int window_size, float velocity_scale,
float min_allowed_object_scale)
: window_size_(window_size),
velocity_scale_(velocity_scale),
min_allowed_object_scale_(min_allowed_object_scale) {}
::mediapipe::Status Reset() override {
x_filters_.clear();
y_filters_.clear();
z_filters_.clear();
return ::mediapipe::OkStatus();
}
::mediapipe::Status Apply(const NormalizedLandmarkList& in_landmarks,
const std::pair<int, int>& image_size,
const absl::Duration& timestamp,
NormalizedLandmarkList* out_landmarks) override {
// Get image size.
int image_width;
int image_height;
std::tie(image_width, image_height) = image_size;
// Get value scale as inverse value of the object scale.
// If value is too small smoothing will be disabled and landmarks will be
// returned as is.
const float object_scale =
GetObjectScale(in_landmarks, image_width, image_height);
if (object_scale < min_allowed_object_scale_) {
*out_landmarks = in_landmarks;
return ::mediapipe::OkStatus();
}
const float value_scale = 1.0f / object_scale;
// Initialize filters once.
MP_RETURN_IF_ERROR(InitializeFiltersIfEmpty(in_landmarks.landmark_size()));
// Filter landmarks. Every axis of every landmark is filtered separately.
for (int i = 0; i < in_landmarks.landmark_size(); ++i) {
const NormalizedLandmark& in_landmark = in_landmarks.landmark(i);
NormalizedLandmark* out_landmark = out_landmarks->add_landmark();
out_landmark->set_x(x_filters_[i].Apply(timestamp, value_scale,
in_landmark.x() * image_width) /
image_width);
out_landmark->set_y(y_filters_[i].Apply(timestamp, value_scale,
in_landmark.y() * image_height) /
image_height);
// Scale Z the save was as X (using image width).
out_landmark->set_z(z_filters_[i].Apply(timestamp, value_scale,
in_landmark.z() * image_width) /
image_width);
// Keep visibility as is.
out_landmark->set_visibility(in_landmark.visibility());
}
return ::mediapipe::OkStatus();
}
private:
// Initializes filters for the first time or after Reset. If initialized then
// check the size.
::mediapipe::Status InitializeFiltersIfEmpty(const int n_landmarks) {
if (!x_filters_.empty()) {
RET_CHECK_EQ(x_filters_.size(), n_landmarks);
RET_CHECK_EQ(y_filters_.size(), n_landmarks);
RET_CHECK_EQ(z_filters_.size(), n_landmarks);
return ::mediapipe::OkStatus();
}
x_filters_.resize(n_landmarks,
RelativeVelocityFilter(window_size_, velocity_scale_));
y_filters_.resize(n_landmarks,
RelativeVelocityFilter(window_size_, velocity_scale_));
z_filters_.resize(n_landmarks,
RelativeVelocityFilter(window_size_, velocity_scale_));
return ::mediapipe::OkStatus();
}
int window_size_;
float velocity_scale_;
float min_allowed_object_scale_;
std::vector<RelativeVelocityFilter> x_filters_;
std::vector<RelativeVelocityFilter> y_filters_;
std::vector<RelativeVelocityFilter> z_filters_;
};
} // namespace
// A calculator to smooth landmarks over time.
//
// Inputs:
// NORM_LANDMARKS: A NormalizedLandmarkList of landmarks you want to smooth.
// IMAGE_SIZE: A std::pair<int, int> represention of image width and height.
// Required to perform all computations in absolute coordinates to avoid any
// influence of normalized values.
//
// Outputs:
// NORM_FILTERED_LANDMARKS: A NormalizedLandmarkList of smoothed landmarks.
//
// Example config:
// node {
// calculator: "LandmarksSmoothingCalculator"
// input_stream: "NORM_LANDMARKS:pose_landmarks"
// input_stream: "IMAGE_SIZE:image_size"
// output_stream: "NORM_FILTERED_LANDMARKS:pose_landmarks_filtered"
// node_options: {
// [type.googleapis.com/mediapipe.LandmarksSmoothingCalculatorOptions] {
// velocity_filter: {
// window_size: 5
// velocity_scale: 10.0
// }
// }
// }
// }
//
class LandmarksSmoothingCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc);
::mediapipe::Status Open(CalculatorContext* cc) override;
::mediapipe::Status Process(CalculatorContext* cc) override;
private:
LandmarksFilter* landmarks_filter_;
};
REGISTER_CALCULATOR(LandmarksSmoothingCalculator);
::mediapipe::Status LandmarksSmoothingCalculator::GetContract(
CalculatorContract* cc) {
cc->Inputs().Tag(kNormalizedLandmarksTag).Set<NormalizedLandmarkList>();
cc->Inputs().Tag(kImageSizeTag).Set<std::pair<int, int>>();
cc->Outputs()
.Tag(kNormalizedFilteredLandmarksTag)
.Set<NormalizedLandmarkList>();
return ::mediapipe::OkStatus();
}
::mediapipe::Status LandmarksSmoothingCalculator::Open(CalculatorContext* cc) {
cc->SetOffset(TimestampDiff(0));
// Pick landmarks filter.
const auto& options = cc->Options<LandmarksSmoothingCalculatorOptions>();
if (options.has_no_filter()) {
landmarks_filter_ = new NoFilter();
} else if (options.has_velocity_filter()) {
landmarks_filter_ = new VelocityFilter(
options.velocity_filter().window_size(),
options.velocity_filter().velocity_scale(),
options.velocity_filter().min_allowed_object_scale());
} else {
RET_CHECK_FAIL()
<< "Landmarks filter is either not specified or not supported";
}
return ::mediapipe::OkStatus();
}
::mediapipe::Status LandmarksSmoothingCalculator::Process(
CalculatorContext* cc) {
// Check that landmarks are not empty and reset the filter if so.
// Don't emit an empty packet for this timestamp.
if (cc->Inputs().Tag(kNormalizedLandmarksTag).IsEmpty()) {
MP_RETURN_IF_ERROR(landmarks_filter_->Reset());
return ::mediapipe::OkStatus();
}
const auto& in_landmarks =
cc->Inputs().Tag(kNormalizedLandmarksTag).Get<NormalizedLandmarkList>();
const auto& image_size =
cc->Inputs().Tag(kImageSizeTag).Get<std::pair<int, int>>();
const auto& timestamp =
absl::Microseconds(cc->InputTimestamp().Microseconds());
auto out_landmarks = absl::make_unique<NormalizedLandmarkList>();
MP_RETURN_IF_ERROR(landmarks_filter_->Apply(in_landmarks, image_size,
timestamp, out_landmarks.get()));
cc->Outputs()
.Tag(kNormalizedFilteredLandmarksTag)
.Add(out_landmarks.release(), cc->InputTimestamp());
return ::mediapipe::OkStatus();
}
} // namespace mediapipe
@@ -1,48 +0,0 @@
// 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 LandmarksSmoothingCalculatorOptions {
extend CalculatorOptions {
optional LandmarksSmoothingCalculatorOptions ext = 325671429;
}
// Default behaviour and fast way to disable smoothing.
message NoFilter {}
message VelocityFilter {
// Number of value changes to keep over time.
// Higher value adds to lag and to stability.
optional int32 window_size = 1 [default = 5];
// Scale to apply to the velocity calculated over the given window. With
// higher velocity `low pass filter` weights new values higher.
// Lower value adds to lag and to stability.
optional float velocity_scale = 2 [default = 10.0];
// If calculated object scale is less than given value smoothing will be
// disabled and landmarks will be returned as is.
optional float min_allowed_object_scale = 3 [default = 1e-6];
}
oneof filter_options {
NoFilter no_filter = 1;
VelocityFilter velocity_filter = 2;
}
}
@@ -1,58 +0,0 @@
// 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/graphs/pose_tracking/calculators/low_pass_filter.h"
#include "absl/memory/memory.h"
#include "mediapipe/framework/port/logging.h"
namespace mediapipe {
LowPassFilter::LowPassFilter(float alpha) : initialized_{false} {
SetAlpha(alpha);
}
float LowPassFilter::Apply(float value) {
float result;
if (initialized_) {
result = alpha_ * value + (1.0 - alpha_) * stored_value_;
} else {
result = value;
initialized_ = true;
}
raw_value_ = value;
stored_value_ = result;
return result;
}
float LowPassFilter::ApplyWithAlpha(float value, float alpha) {
SetAlpha(alpha);
return Apply(value);
}
bool LowPassFilter::HasLastRawValue() { return initialized_; }
float LowPassFilter::LastRawValue() { return raw_value_; }
float LowPassFilter::LastValue() { return stored_value_; }
void LowPassFilter::SetAlpha(float alpha) {
if (alpha < 0.0f || alpha > 1.0f) {
LOG(ERROR) << "alpha: " << alpha << " should be in [0.0, 1.0] range";
return;
}
alpha_ = alpha;
}
} // namespace mediapipe
@@ -1,47 +0,0 @@
// Copyright 2020 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_GRAPHS_POSE_TRACKING_CALCULATORS_LOW_PASS_FILTER_H_
#define MEDIAPIPE_GRAPHS_POSE_TRACKING_CALCULATORS_LOW_PASS_FILTER_H_
#include <memory>
namespace mediapipe {
class LowPassFilter {
public:
explicit LowPassFilter(float alpha);
float Apply(float value);
float ApplyWithAlpha(float value, float alpha);
bool HasLastRawValue();
float LastRawValue();
float LastValue();
private:
void SetAlpha(float alpha);
float raw_value_;
float alpha_;
float stored_value_;
bool initialized_;
};
} // namespace mediapipe
#endif // MEDIAPIPE_GRAPHS_POSE_TRACKING_CALCULATORS_LOW_PASS_FILTER_H_
@@ -1,35 +0,0 @@
// 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/graphs/pose_tracking/calculators/low_pass_filter.h"
#include "mediapipe/framework/port/gtest.h"
namespace mediapipe {
TEST(LowPassFilterTest, LowPassFilterBasicChecks) {
auto filter = absl::make_unique<LowPassFilter>(1.0f);
EXPECT_EQ(2.0f, filter->Apply(2.0f));
EXPECT_EQ(100.0f, filter->Apply(100.0f));
filter = absl::make_unique<LowPassFilter>(0.0f);
EXPECT_EQ(2.0f, filter->Apply(2.0f));
EXPECT_EQ(2.0f, filter->Apply(100.0f));
filter = absl::make_unique<LowPassFilter>(0.5f);
EXPECT_EQ(2.0f, filter->Apply(2.0f));
EXPECT_EQ(51.0f, filter->Apply(100.0f));
}
} // namespace mediapipe
@@ -1,85 +0,0 @@
// 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/graphs/pose_tracking/calculators/relative_velocity_filter.h"
#include <cmath>
#include <deque>
#include "absl/memory/memory.h"
#include "mediapipe/framework/port/logging.h"
namespace mediapipe {
float RelativeVelocityFilter::Apply(absl::Duration timestamp, float value_scale,
float value) {
const int64_t new_timestamp = absl::ToInt64Nanoseconds(timestamp);
if (last_timestamp_ >= new_timestamp) {
// Results are unpredictable in this case, so nothing to do but
// return same value
LOG(WARNING) << "New timestamp is equal or less than the last one.";
return value;
}
float alpha;
if (last_timestamp_ == -1) {
alpha = 1.0;
} else {
DCHECK(distance_mode_ == DistanceEstimationMode::kLegacyTransition ||
distance_mode_ == DistanceEstimationMode::kForceCurrentScale);
const float distance =
distance_mode_ == DistanceEstimationMode::kLegacyTransition
? value * value_scale -
last_value_ * last_value_scale_ // Original.
: value_scale * (value - last_value_); // Translation invariant.
const int64_t duration = new_timestamp - last_timestamp_;
float cumulative_distance = distance;
int64_t cumulative_duration = duration;
// Define max cumulative duration assuming
// 30 frames per second is a good frame rate, so assuming 30 values
// per second or 1 / 30 of a second is a good duration per window element
constexpr int64_t kAssumedMaxDuration = 1000000000 / 30;
const int64_t max_cumulative_duration =
(1 + window_.size()) * kAssumedMaxDuration;
for (const auto& el : window_) {
if (cumulative_duration + el.duration > max_cumulative_duration) {
// This helps in cases when durations are large and outdated
// window elements have bad impact on filtering results
break;
}
cumulative_distance += el.distance;
cumulative_duration += el.duration;
}
constexpr double kNanoSecondsToSecond = 1e-9;
const float velocity =
cumulative_distance / (cumulative_duration * kNanoSecondsToSecond);
alpha = 1.0f - 1.0f / (1.0f + velocity_scale_ * std::abs(velocity));
window_.push_front({distance, duration});
if (window_.size() > max_window_size_) {
window_.pop_back();
}
}
last_value_ = value;
last_value_scale_ = value_scale;
last_timestamp_ = new_timestamp;
return low_pass_filter_.ApplyWithAlpha(value, alpha);
}
} // namespace mediapipe
@@ -1,90 +0,0 @@
// Copyright 2020 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_GRAPHS_POSE_TRACKING_CALCULATORS_RELATIVE_VELOCITY_FILTER_H_
#define MEDIAPIPE_GRAPHS_POSE_TRACKING_CALCULATORS_RELATIVE_VELOCITY_FILTER_H_
#include <deque>
#include <memory>
#include "absl/time/time.h"
#include "mediapipe/graphs/pose_tracking/calculators/low_pass_filter.h"
namespace mediapipe {
// This filter keeps track (on a window of specified size) of
// value changes over time, which as result gives us velocity of how value
// changes over time. With higher velocity it weights new values higher.
//
// Use @window_size and @velocity_scale to tweak this filter for your use case.
//
// - higher @window_size adds to lag and to stability
// - lower @velocity_scale adds to lag and to stability
class RelativeVelocityFilter {
public:
enum class DistanceEstimationMode {
// When the value scale changes, uses a heuristic
// that is not translation invariant (see the implementation for details).
kLegacyTransition,
// The current (i.e. last) value scale is always used for scale estimation.
// When using this mode, the filter is translation invariant, i.e.
// Filter(Data + Offset) = Filter(Data) + Offset.
kForceCurrentScale,
kDefault = kLegacyTransition
};
public:
RelativeVelocityFilter(size_t window_size, float velocity_scale,
DistanceEstimationMode distance_mode)
: max_window_size_{window_size},
window_{window_size},
velocity_scale_{velocity_scale},
distance_mode_{distance_mode} {}
RelativeVelocityFilter(size_t window_size, float velocity_scale)
: RelativeVelocityFilter{window_size, velocity_scale,
DistanceEstimationMode::kDefault} {}
// Applies filter to the value.
// @timestamp - timestamp associated with the value (for instance,
// timestamp of the frame where you got value from)
// @value_scale - value scale (for instance, if your value is a distance
// detected on a frame, it can look same on different
// devices but have quite different absolute values due
// to different resolution, you should come up with an
// appropriate parameter for your particular use case)
// @value - value to filter
float Apply(absl::Duration timestamp, float value_scale, float value);
private:
struct WindowElement {
float distance;
int64_t duration;
};
float last_value_{0.0};
float last_value_scale_{1.0};
int64_t last_timestamp_{-1};
size_t max_window_size_;
std::deque<WindowElement> window_;
LowPassFilter low_pass_filter_{1.0f};
float velocity_scale_;
DistanceEstimationMode distance_mode_;
};
} // namespace mediapipe
#endif // MEDIAPIPE_GRAPHS_POSE_TRACKING_CALCULATORS_RELATIVE_VELOCITY_FILTER_H_
@@ -1,292 +0,0 @@
// 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/graphs/pose_tracking/calculators/relative_velocity_filter.h"
#include <algorithm>
#include <cmath>
#include <vector>
#include "absl/time/time.h"
#include "mediapipe/framework/port/gtest.h"
namespace mediapipe {
using DistanceEstimationMode =
::mediapipe::RelativeVelocityFilter::DistanceEstimationMode;
absl::Duration DurationFromNanos(int64_t nanos) {
return absl::FromChrono(std::chrono::nanoseconds{nanos});
}
absl::Duration DurationFromMillis(int64_t millis) {
return absl::FromChrono(std::chrono::milliseconds{millis});
}
TEST(RelativeVelocityFilterTest, ApplyIncorrectTimestamp) {
auto filter = absl::make_unique<RelativeVelocityFilter>(1, 1.0);
absl::Duration timestamp1 = DurationFromNanos(1);
EXPECT_FLOAT_EQ(95.5f, filter->Apply(timestamp1, 0.5f, 95.5f));
EXPECT_FLOAT_EQ(200.5f, filter->Apply(timestamp1, 0.5f, 200.5f));
EXPECT_FLOAT_EQ(1000.5f, filter->Apply(timestamp1, 0.5f, 1000.5f));
EXPECT_FLOAT_EQ(2000.0f, filter->Apply(DurationFromNanos(1), 0.5f, 2000.0f));
}
void TestSameValueScaleDifferentVelocityScales(
DistanceEstimationMode distance_mode) {
// Changing the distance estimation mode has no effect with constant scales.
// More sensitive filter.
auto filter1 = absl::make_unique<RelativeVelocityFilter>(
/*window_size=*/5, /*velocity_scale=*/45.0f,
/*distance_mode=*/distance_mode);
// Less sensitive filter.
auto filter2 = absl::make_unique<RelativeVelocityFilter>(
/*window_size=*/5, /*velocity_scale=*/0.1f,
/*distance_mode=*/distance_mode);
float result1;
float result2;
float value;
float value_scale = 1.0f;
value = 1.0f;
result1 = filter1->Apply(DurationFromMillis(1), value_scale, value);
result2 = filter2->Apply(DurationFromMillis(1), value_scale, value);
EXPECT_EQ(result1, result2);
value = 10.0f;
result1 = filter1->Apply(DurationFromMillis(2), value_scale, value);
result2 = filter2->Apply(DurationFromMillis(2), value_scale, value);
EXPECT_GT(result1, result2);
value = 2.0f;
result1 = filter1->Apply(DurationFromMillis(3), value_scale, value);
result2 = filter2->Apply(DurationFromMillis(3), value_scale, value);
EXPECT_LT(result1, result2);
value = 20.0f;
result1 = filter1->Apply(DurationFromMillis(4), value_scale, value);
result2 = filter2->Apply(DurationFromMillis(4), value_scale, value);
EXPECT_GT(result1, result2);
value = 10.0f;
result1 = filter1->Apply(DurationFromMillis(5), value_scale, value);
result2 = filter2->Apply(DurationFromMillis(5), value_scale, value);
EXPECT_LT(result1, result2);
value = 50.0f;
result1 = filter1->Apply(DurationFromMillis(6), value_scale, value);
result2 = filter2->Apply(DurationFromMillis(6), value_scale, value);
EXPECT_GT(result1, result2);
value = 30.0f;
result1 = filter1->Apply(DurationFromMillis(7), value_scale, value);
result2 = filter2->Apply(DurationFromMillis(7), value_scale, value);
EXPECT_LT(result1, result2);
}
TEST(RelativeVelocityFilterTest, SameValueScaleDifferentVelocityScalesLegacy) {
TestSameValueScaleDifferentVelocityScales(
DistanceEstimationMode::kLegacyTransition);
}
TEST(RelativeVelocityFilterTest,
SameValueScaleDifferentVelocityScalesForceCurrentScale) {
TestSameValueScaleDifferentVelocityScales(
DistanceEstimationMode::kForceCurrentScale);
}
void TestDifferentConstantValueScalesSameVelocityScale(
DistanceEstimationMode distance_mode) {
const float same_velocity_scale = 1.0f;
auto filter1 = absl::make_unique<RelativeVelocityFilter>(
/*window_size=*/3, /*velocity_scale=*/same_velocity_scale,
/*distance_mode=*/distance_mode);
auto filter2 = absl::make_unique<RelativeVelocityFilter>(
/*window_size=*/3, /*velocity_scale=*/same_velocity_scale,
/*distance_mode=*/distance_mode);
float result1;
float result2;
float value;
// smaller value scale will decrease cumulative speed and alpha
// so with smaller scale and same other params filter will believe
// new values a little bit less
float value_scale1 = 0.5f;
float value_scale2 = 1.0f;
value = 1.0f;
result1 = filter1->Apply(DurationFromMillis(1), value_scale1, value);
result2 = filter2->Apply(DurationFromMillis(1), value_scale2, value);
EXPECT_EQ(result1, result2);
value = 10.0f;
result1 = filter1->Apply(DurationFromMillis(2), value_scale1, value);
result2 = filter2->Apply(DurationFromMillis(2), value_scale2, value);
EXPECT_LT(result1, result2);
value = 2.0f;
result1 = filter1->Apply(DurationFromMillis(3), value_scale1, value);
result2 = filter2->Apply(DurationFromMillis(3), value_scale2, value);
EXPECT_GT(result1, result2);
value = 20.0f;
result1 = filter1->Apply(DurationFromMillis(4), value_scale1, value);
result2 = filter2->Apply(DurationFromMillis(4), value_scale2, value);
EXPECT_LT(result1, result2);
}
TEST(RelativeVelocityFilterTest,
DifferentConstantValueScalesSameVelocityScale) {
TestDifferentConstantValueScalesSameVelocityScale(
DistanceEstimationMode::kLegacyTransition);
}
TEST(RelativeVelocityFilterTest, ApplyCheckValueScales) {
TestDifferentConstantValueScalesSameVelocityScale(
DistanceEstimationMode::kForceCurrentScale);
}
void TestTranslationInvariance(DistanceEstimationMode distance_mode) {
struct ValueAtScale {
float value;
float scale;
};
// Note that the scales change over time.
std::vector<ValueAtScale> original_data_points{
// clang-format off
{.value = 1.0f, .scale = 0.5f},
{.value = 10.0f, .scale = 5.0f},
{.value = 20.0f, .scale = 10.0f},
{.value = 30.0f, .scale = 15.0f},
{.value = 40.0f, .scale = 0.5f},
{.value = 50.0f, .scale = 0.5f},
{.value = 60.0f, .scale = 5.0f},
{.value = 70.0f, .scale = 10.0f},
{.value = 80.0f, .scale = 15.0f},
{.value = 90.0f, .scale = 5.0f},
{.value = 70.0f, .scale = 10.0f},
{.value = 50.0f, .scale = 15.0f},
{.value = 80.0f, .scale = 15.0f},
// clang-format on
};
// The amount by which the input values are uniformly translated.
const float kValueOffset = 100.0f;
// The uniform time delta.
const absl::Duration time_delta = DurationFromMillis(1);
// The filter parameters are the same between the two filters.
const size_t kWindowSize = 5;
const float kVelocityScale = 0.1f;
// Perform the translation.
std::vector<ValueAtScale> translated_data_points = original_data_points;
for (auto& point : translated_data_points) {
point.value += kValueOffset;
}
auto original_points_filter = absl::make_unique<RelativeVelocityFilter>(
/*window_size=*/kWindowSize, /*velocity_scale=*/kVelocityScale,
/*distance_mode=*/distance_mode);
auto translated_points_filter = absl::make_unique<RelativeVelocityFilter>(
/*window_size=*/kWindowSize, /*velocity_scale=*/kVelocityScale,
/*distance_mode=*/distance_mode);
// The minimal difference which is considered a divergence.
const float kDivergenceGap = 0.001f;
// The amount of the times this gap is achieved with `kLegacyTransition`.
// Note that on the first iteration the filters should output the unfiltered
// input values, so no divergence should occur.
// This amount obviously depends on the values in `original_data_points`,
// so should be changed accordingly when they are updated.
const size_t kDivergenceTimes = 5;
// The minimal difference which is considered a large divergence.
const float kLargeDivergenceGap = 10.0f;
// The amount of times it is achieved.
// This amount obviously depends on the values in `original_data_points`,
// so should be changed accordingly when they are updated.
const size_t kLargeDivergenceTimes = 1;
// In contrast, the new mode delivers this error bound across all the samples.
const float kForceCurrentScaleAbsoluteError = 1.53e-05f;
size_t times_diverged = 0;
size_t times_largely_diverged = 0;
absl::Duration timestamp;
for (size_t iteration = 0; iteration < original_data_points.size();
++iteration, timestamp += time_delta) {
const ValueAtScale& original_data_point = original_data_points[iteration];
const float filtered_original_value =
original_points_filter->Apply(/*timestamp=*/timestamp,
/*value_scale=*/original_data_point.scale,
/*value=*/original_data_point.value);
const ValueAtScale& translated_data_point =
translated_data_points[iteration];
const float actual_filtered_translated_value =
translated_points_filter->Apply(
/*timestamp=*/timestamp,
/*value_scale=*/translated_data_point.scale,
/*value=*/translated_data_point.value);
const float expected_filtered_translated_value =
filtered_original_value + kValueOffset;
const float difference = std::fabs(actual_filtered_translated_value -
expected_filtered_translated_value);
if (iteration == 0) {
// On the first iteration, the unfiltered values are returned.
EXPECT_EQ(filtered_original_value, original_data_point.value);
EXPECT_EQ(actual_filtered_translated_value, translated_data_point.value);
EXPECT_EQ(difference, 0.0f);
} else if (distance_mode == DistanceEstimationMode::kLegacyTransition) {
if (difference >= kDivergenceGap) {
++times_diverged;
}
if (difference >= kLargeDivergenceGap) {
++times_largely_diverged;
}
} else {
CHECK(distance_mode == DistanceEstimationMode::kForceCurrentScale);
EXPECT_NEAR(difference, 0.0f, kForceCurrentScaleAbsoluteError);
}
}
if (distance_mode == DistanceEstimationMode::kLegacyTransition) {
EXPECT_GE(times_diverged, kDivergenceTimes);
EXPECT_GE(times_largely_diverged, kLargeDivergenceTimes);
}
}
// This test showcases an undesired property of the current filter design
// that manifests itself when value scales change in time. It turns out that
// the velocity estimation starts depending on the distance from the origin.
TEST(RelativeVelocityFilterTest,
TestLegacyFilterModeIsNotTranslationInvariant) {
TestTranslationInvariance(DistanceEstimationMode::kLegacyTransition);
}
TEST(RelativeVelocityFilterTest, TestOtherFilterModeIsTranslationInvariant) {
TestTranslationInvariance(DistanceEstimationMode::kForceCurrentScale);
}
} // namespace mediapipe