Project import generated by Copybara.
GitOrigin-RevId: f72a0f86c2c2acdb1920973c718a9e26ed3ec4b6
This commit is contained in:
@@ -30,7 +30,9 @@ mediapipe_cc_proto_library(
|
||||
name = "autoflip_messages_cc_proto",
|
||||
srcs = ["autoflip_messages.proto"],
|
||||
cc_deps = ["//mediapipe/framework:calculator_cc_proto"],
|
||||
visibility = ["//mediapipe/examples:__subpackages__"],
|
||||
visibility = [
|
||||
"//mediapipe/examples:__subpackages__",
|
||||
],
|
||||
deps = [":autoflip_messages_proto"],
|
||||
)
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ message RectF {
|
||||
|
||||
// An image region of interest (eg a detected face or object), accompanied by an
|
||||
// importance score.
|
||||
// Next tag: 9
|
||||
// Next tag: 10
|
||||
message SalientRegion {
|
||||
reserved 3;
|
||||
// The bounding box for this region in the image.
|
||||
@@ -98,7 +98,19 @@ message SalientRegion {
|
||||
|
||||
// If true, object cannot move in the output window (e.g. text would look
|
||||
// strange moving around).
|
||||
// TODO: this feature is not implemented, remove proto message.
|
||||
optional bool requires_static_location = 7 [default = false];
|
||||
|
||||
// When used with ContentZoomingCalculator, this flag can be set in the
|
||||
// SignalFusingCalculator indicating that areas outside of these detections
|
||||
// can be cropped from the frame. When no salient regions have this flag set
|
||||
// true, no zooming is performed. When one or more salient regions have this
|
||||
// flag set true, the max zoom value will be used that keeps all
|
||||
// “only_required” detections within view. The ContentZoomingCalculator
|
||||
// currently supports zooming by finding the size of non-salient top/bottom
|
||||
// borders regions and provides this information to the
|
||||
// SceneCroppingCalculator for reframing.
|
||||
optional bool only_required = 9 [default = false];
|
||||
}
|
||||
|
||||
// Stores the message type, including standard types (face, object) and custom
|
||||
@@ -180,4 +192,10 @@ message ExternalRenderFrame {
|
||||
optional Color padding_color = 3;
|
||||
// Timestamp in microseconds of this frame.
|
||||
optional uint64 timestamp_us = 4;
|
||||
// Target width of the cropped video in pixels. |render_to_location| is
|
||||
// relative to this dimension.
|
||||
optional int32 target_width = 5;
|
||||
// Target height of the cropped video in pixels. |render_to_location| is
|
||||
// relative to this dimension.
|
||||
optional int32 target_height = 6;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,10 @@ load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library"
|
||||
|
||||
licenses(["notice"]) # Apache 2.0
|
||||
|
||||
package(default_visibility = ["//mediapipe/examples:__subpackages__"])
|
||||
package(default_visibility = [
|
||||
"//mediapipe/examples:__subpackages__",
|
||||
"//mediapipe/viz:__subpackages__",
|
||||
])
|
||||
|
||||
cc_library(
|
||||
name = "border_detection_calculator",
|
||||
@@ -51,6 +54,44 @@ mediapipe_cc_proto_library(
|
||||
deps = [":border_detection_calculator_proto"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "content_zooming_calculator",
|
||||
srcs = ["content_zooming_calculator.cc"],
|
||||
deps = [
|
||||
":content_zooming_calculator_cc_proto",
|
||||
"//mediapipe/examples/desktop/autoflip:autoflip_messages_cc_proto",
|
||||
"//mediapipe/examples/desktop/autoflip/quality:kinematic_path_solver",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/formats:image_frame",
|
||||
"//mediapipe/framework/formats:image_frame_opencv",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "content_zooming_calculator_proto",
|
||||
srcs = ["content_zooming_calculator.proto"],
|
||||
deps = [
|
||||
"//mediapipe/examples/desktop/autoflip/quality:kinematic_path_solver_proto",
|
||||
"//mediapipe/framework:calculator_proto",
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_cc_proto_library(
|
||||
name = "content_zooming_calculator_cc_proto",
|
||||
srcs = ["content_zooming_calculator.proto"],
|
||||
cc_deps = [
|
||||
"//mediapipe/examples/desktop/autoflip/quality:kinematic_path_solver_cc_proto",
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
],
|
||||
visibility = ["//mediapipe/examples:__subpackages__"],
|
||||
deps = [
|
||||
":content_zooming_calculator_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "border_detection_calculator_test",
|
||||
srcs = [
|
||||
@@ -75,6 +116,30 @@ cc_test(
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "content_zooming_calculator_test",
|
||||
srcs = [
|
||||
"content_zooming_calculator_test.cc",
|
||||
],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
":content_zooming_calculator",
|
||||
":content_zooming_calculator_cc_proto",
|
||||
"//mediapipe/examples/desktop/autoflip:autoflip_messages_cc_proto",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:calculator_runner",
|
||||
"//mediapipe/framework/formats:image_frame",
|
||||
"//mediapipe/framework/formats:image_frame_opencv",
|
||||
"//mediapipe/framework/port:benchmark",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"//mediapipe/framework/port:opencv_core",
|
||||
"//mediapipe/framework/port:parse_text_proto",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "video_filtering_calculator",
|
||||
srcs = ["video_filtering_calculator.cc"],
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
// 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 <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h"
|
||||
#include "mediapipe/examples/desktop/autoflip/calculators/content_zooming_calculator.pb.h"
|
||||
#include "mediapipe/examples/desktop/autoflip/quality/kinematic_path_solver.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/ret_check.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/framework/port/status_builder.h"
|
||||
|
||||
constexpr char kVideoFrame[] = "VIDEO";
|
||||
constexpr char kVideoSize[] = "VIDEO_SIZE";
|
||||
constexpr char kDetectionSet[] = "DETECTIONS";
|
||||
constexpr char kDetectedBorders[] = "BORDERS";
|
||||
// Field-of-view (degrees) of the camera's x-axis (width).
|
||||
// TODO: Parameterize FOV based on camera specs.
|
||||
constexpr float kWidthFieldOfView = 60;
|
||||
|
||||
namespace mediapipe {
|
||||
namespace autoflip {
|
||||
|
||||
// Content zooming calculator zooms in on content when a detection has
|
||||
// "only_required" set true. It does this by computing the value of top/bottom
|
||||
// borders to remove from the output and sends these to the
|
||||
// SceneCroppingCalculator. When more than one detections are received the zoom
|
||||
// box is calculated as the union of the detections. Typical applications
|
||||
// include mobile makeover and autofliplive face reframing. Currently only
|
||||
// supports y-dimension zooming.
|
||||
class ContentZoomingCalculator : public CalculatorBase {
|
||||
public:
|
||||
ContentZoomingCalculator()
|
||||
: initialized_(false), last_only_required_detection_(0) {}
|
||||
~ContentZoomingCalculator() override {}
|
||||
ContentZoomingCalculator(const ContentZoomingCalculator&) = delete;
|
||||
ContentZoomingCalculator& operator=(const ContentZoomingCalculator&) = delete;
|
||||
|
||||
static ::mediapipe::Status GetContract(mediapipe::CalculatorContract* cc);
|
||||
::mediapipe::Status Open(mediapipe::CalculatorContext* cc) override;
|
||||
::mediapipe::Status Process(mediapipe::CalculatorContext* cc) override;
|
||||
|
||||
private:
|
||||
// Converts bounds to tilt offset and height.
|
||||
::mediapipe::Status ConvertToTiltZoom(float xmin, float xmax, float ymin,
|
||||
float ymax, int* tilt_offset,
|
||||
int* height);
|
||||
ContentZoomingCalculatorOptions options_;
|
||||
// Detection frame width/height.
|
||||
int frame_height_;
|
||||
int frame_width_;
|
||||
// Path solver used to smooth top/bottom border crop values.
|
||||
std::unique_ptr<KinematicPathSolver> path_solver_height_;
|
||||
std::unique_ptr<KinematicPathSolver> path_solver_offset_;
|
||||
// Are parameters initialized.
|
||||
bool initialized_;
|
||||
// Stores the time of the last "only_required" input.
|
||||
int64 last_only_required_detection_;
|
||||
// Border values of last message with detection.
|
||||
int last_measured_height_;
|
||||
int last_measured_y_offset_;
|
||||
// Min border values.
|
||||
float min_height_value_;
|
||||
};
|
||||
REGISTER_CALCULATOR(ContentZoomingCalculator);
|
||||
|
||||
::mediapipe::Status ContentZoomingCalculator::GetContract(
|
||||
mediapipe::CalculatorContract* cc) {
|
||||
RET_CHECK(
|
||||
!(cc->Inputs().HasTag(kVideoFrame) && cc->Inputs().HasTag(kVideoSize)))
|
||||
<< "Provide only VIDEO or VIDEO_SIZE, not both.";
|
||||
if (cc->Inputs().HasTag(kVideoFrame)) {
|
||||
cc->Inputs().Tag(kVideoFrame).Set<ImageFrame>();
|
||||
} else if (cc->Inputs().HasTag(kVideoSize)) {
|
||||
cc->Inputs().Tag(kVideoSize).Set<std::pair<int, int>>();
|
||||
} else {
|
||||
return ::mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "Input VIDEO or VIDEO_SIZE must be provided.";
|
||||
}
|
||||
cc->Inputs().Tag(kDetectionSet).Set<DetectionSet>();
|
||||
cc->Outputs().Tag(kDetectedBorders).Set<StaticFeatures>();
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status ContentZoomingCalculator::Open(
|
||||
mediapipe::CalculatorContext* cc) {
|
||||
options_ = cc->Options<ContentZoomingCalculatorOptions>();
|
||||
if (options_.has_kinematic_options()) {
|
||||
return ::mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "Deprecated kinematic_options was set, please set "
|
||||
"kinematic_options_zoom and kinematic_options_tilt.";
|
||||
}
|
||||
if (options_.has_min_motion_to_reframe()) {
|
||||
return ::mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "Deprecated min_motion_to_reframe was set, please set "
|
||||
"in kinematic_options_zoom and kinematic_options_tilt directly.";
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status ContentZoomingCalculator::ConvertToTiltZoom(
|
||||
float xmin, float xmax, float ymin, float ymax, int* tilt_offset,
|
||||
int* height) {
|
||||
// Find center of the y-axis offset (for tilt control).
|
||||
float y_center = ymin + (ymax - ymin) / 2;
|
||||
// Find size and apply scale factor to y-axis.
|
||||
float fit_size = fmax((ymax - ymin) / options_.scale_factor(), xmax - xmin);
|
||||
// Apply min zoom for cases where the target size is wider than input frame
|
||||
// size.
|
||||
fit_size = fmin(min_height_value_, fit_size);
|
||||
// Prevent box from extending beyond the image.
|
||||
if (y_center - fit_size / 2 < 0) {
|
||||
y_center = fit_size / 2;
|
||||
} else if (y_center + fit_size / 2 > 1) {
|
||||
y_center = 1 - fit_size / 2;
|
||||
}
|
||||
// Scale to pixel coordinates.
|
||||
*tilt_offset = frame_height_ * y_center;
|
||||
*height = frame_height_ * fit_size;
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
namespace {
|
||||
::mediapipe::Status UpdateRanges(const SalientRegion& region, float* xmin,
|
||||
float* xmax, float* ymin, float* ymax) {
|
||||
if (!region.has_location_normalized()) {
|
||||
return ::mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "SalientRegion did not have location normalized set.";
|
||||
}
|
||||
*xmin = fmin(*xmin, region.location_normalized().x());
|
||||
*xmax = fmax(*xmax, region.location_normalized().x() +
|
||||
region.location_normalized().width());
|
||||
*ymin = fmin(*ymin, region.location_normalized().y());
|
||||
*ymax = fmax(*ymax, region.location_normalized().y() +
|
||||
region.location_normalized().height());
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
void MakeStaticFeatures(const int top_border, const int bottom_border,
|
||||
const int frame_width, const int frame_height,
|
||||
StaticFeatures* static_feature) {
|
||||
auto border_top = static_feature->add_border();
|
||||
border_top->set_relative_position(Border::TOP);
|
||||
border_top->mutable_border_position()->set_x(0);
|
||||
border_top->mutable_border_position()->set_y(0);
|
||||
border_top->mutable_border_position()->set_width(frame_width);
|
||||
border_top->mutable_border_position()->set_height(top_border);
|
||||
|
||||
auto border_bottom = static_feature->add_border();
|
||||
border_bottom->set_relative_position(Border::BOTTOM);
|
||||
border_bottom->mutable_border_position()->set_x(0);
|
||||
border_bottom->mutable_border_position()->set_y(frame_height - bottom_border);
|
||||
border_bottom->mutable_border_position()->set_width(frame_width);
|
||||
border_bottom->mutable_border_position()->set_height(bottom_border);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
::mediapipe::Status ContentZoomingCalculator::Process(
|
||||
mediapipe::CalculatorContext* cc) {
|
||||
if (cc->Inputs().HasTag(kVideoFrame)) {
|
||||
cv::Mat frame = mediapipe::formats::MatView(
|
||||
&cc->Inputs().Tag(kVideoFrame).Get<ImageFrame>());
|
||||
frame_width_ = frame.cols;
|
||||
frame_height_ = frame.rows;
|
||||
} else if (cc->Inputs().HasTag(kVideoSize)) {
|
||||
frame_width_ =
|
||||
cc->Inputs().Tag(kVideoSize).Get<std::pair<int, int>>().first;
|
||||
frame_height_ =
|
||||
cc->Inputs().Tag(kVideoSize).Get<std::pair<int, int>>().second;
|
||||
} else {
|
||||
return ::mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "Input VIDEO or VIDEO_SIZE must be provided.";
|
||||
}
|
||||
|
||||
if (!initialized_) {
|
||||
path_solver_height_ = std::make_unique<KinematicPathSolver>(
|
||||
options_.kinematic_options_zoom(), 0, frame_height_,
|
||||
static_cast<float>(frame_width_) / kWidthFieldOfView);
|
||||
path_solver_offset_ = std::make_unique<KinematicPathSolver>(
|
||||
options_.kinematic_options_tilt(), 0, frame_height_,
|
||||
static_cast<float>(frame_width_) / kWidthFieldOfView);
|
||||
min_height_value_ = 1.0;
|
||||
// If target size is set and wider than input aspect, make sure to always
|
||||
// crop the min required amount.
|
||||
if (options_.has_target_size()) {
|
||||
RET_CHECK_GT(options_.target_size().width(), 0)
|
||||
<< "Provided target width not valid.";
|
||||
RET_CHECK_GT(options_.target_size().height(), 0)
|
||||
<< "Provided target height not valid.";
|
||||
float input_aspect = frame_width_ / static_cast<float>(frame_height_);
|
||||
float target_aspect = options_.target_size().width() /
|
||||
static_cast<float>(options_.target_size().height());
|
||||
min_height_value_ =
|
||||
(input_aspect < target_aspect) ? input_aspect / target_aspect : 1.0;
|
||||
}
|
||||
last_measured_height_ = min_height_value_ * frame_height_;
|
||||
last_measured_y_offset_ = frame_width_ / 2;
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
auto detection_set = cc->Inputs().Tag(kDetectionSet).Get<DetectionSet>();
|
||||
bool only_required_found = false;
|
||||
|
||||
// Compute the box that contains all "is_required" detections.
|
||||
float xmin = 1, ymin = 1, xmax = 0, ymax = 0;
|
||||
for (const auto& region : detection_set.detections()) {
|
||||
if (!region.only_required()) {
|
||||
continue;
|
||||
}
|
||||
only_required_found = true;
|
||||
MP_RETURN_IF_ERROR(UpdateRanges(region, &xmin, &xmax, &ymin, &ymax));
|
||||
}
|
||||
|
||||
// Convert bounds to tilt/zoom and in pixel coordinates.
|
||||
int offset, height;
|
||||
MP_RETURN_IF_ERROR(
|
||||
ConvertToTiltZoom(xmin, xmax, ymin, ymax, &offset, &height));
|
||||
|
||||
if (only_required_found) {
|
||||
// A only required detection was found.
|
||||
last_only_required_detection_ = cc->InputTimestamp().Microseconds();
|
||||
last_measured_height_ = height;
|
||||
last_measured_y_offset_ = offset;
|
||||
} else if (cc->InputTimestamp().Microseconds() -
|
||||
last_only_required_detection_ >=
|
||||
options_.us_before_zoomout()) {
|
||||
// No only_require detections found within salient regions packets arriving
|
||||
// since us_before_zoomout duration.
|
||||
height = min_height_value_ * frame_height_;
|
||||
offset = frame_height_ / 2;
|
||||
} else {
|
||||
// No only detection found but using last detection due to
|
||||
// duration_before_zoomout_us setting.
|
||||
height = last_measured_height_;
|
||||
offset = last_measured_y_offset_;
|
||||
}
|
||||
|
||||
// Compute smoothed camera paths.
|
||||
MP_RETURN_IF_ERROR(path_solver_height_->AddObservation(
|
||||
height, cc->InputTimestamp().Microseconds()));
|
||||
MP_RETURN_IF_ERROR(path_solver_offset_->AddObservation(
|
||||
offset, cc->InputTimestamp().Microseconds()));
|
||||
int path_size;
|
||||
MP_RETURN_IF_ERROR(path_solver_height_->GetState(&path_size));
|
||||
int path_offset;
|
||||
MP_RETURN_IF_ERROR(path_solver_offset_->GetState(&path_offset));
|
||||
|
||||
// Convert to top/bottom borders to remove.
|
||||
int path_top = path_offset - path_size / 2;
|
||||
int path_bottom = frame_height_ - (path_offset + path_size / 2);
|
||||
|
||||
// Transmit result downstream.
|
||||
std::unique_ptr<StaticFeatures> features =
|
||||
absl::make_unique<StaticFeatures>();
|
||||
MakeStaticFeatures(path_top, path_bottom, frame_width_, frame_height_,
|
||||
features.get());
|
||||
cc->Outputs()
|
||||
.Tag(kDetectedBorders)
|
||||
.AddPacket(Adopt(features.release()).At(cc->InputTimestamp()));
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace autoflip
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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.autoflip;
|
||||
|
||||
import "mediapipe/examples/desktop/autoflip/quality/kinematic_path_solver.proto";
|
||||
import "mediapipe/framework/calculator.proto";
|
||||
|
||||
message ContentZoomingCalculatorOptions {
|
||||
extend mediapipe.CalculatorOptions {
|
||||
optional ContentZoomingCalculatorOptions ext = 313091992;
|
||||
}
|
||||
// Amount the only required area should fill the image. When set to 1,
|
||||
// reframing is done to remove content to the very edge of the salient region
|
||||
// bounding box. When a smaller value is used, zooming will be done to fill
|
||||
// this ratio of the frame.
|
||||
optional float scale_factor = 1 [default = .9];
|
||||
// Kinematic options for zooming.
|
||||
optional KinematicOptions kinematic_options_zoom = 6;
|
||||
// Kinematic options for tilt (y-axis reframing.)
|
||||
optional KinematicOptions kinematic_options_tilt = 7;
|
||||
// Duration (in MicroSeconds) before returning to fully zoomed out position
|
||||
// when no "only_required" frames are received.
|
||||
optional int64 us_before_zoomout = 9 [default = 1000000];
|
||||
// Value of target output size, required to be set if different than input.
|
||||
// Should match target_width and target_height in croppingcalculator.
|
||||
message Size {
|
||||
optional int64 width = 1;
|
||||
optional int64 height = 2;
|
||||
}
|
||||
optional Size target_size = 8;
|
||||
|
||||
// Deprecated parameters
|
||||
optional KinematicOptions kinematic_options = 2 [deprecated = true];
|
||||
optional int64 min_motion_to_reframe = 4 [deprecated = true];
|
||||
optional float min_vertical_zoom = 5 [default = 1, deprecated = true];
|
||||
optional int64 frames_before_zoomout = 3 [default = 30, deprecated = true];
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
// 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 "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h"
|
||||
#include "mediapipe/examples/desktop/autoflip/calculators/content_zooming_calculator.pb.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/calculator_runner.h"
|
||||
#include "mediapipe/framework/formats/image_frame.h"
|
||||
#include "mediapipe/framework/formats/image_frame_opencv.h"
|
||||
#include "mediapipe/framework/port/benchmark.h"
|
||||
#include "mediapipe/framework/port/gmock.h"
|
||||
#include "mediapipe/framework/port/gtest.h"
|
||||
#include "mediapipe/framework/port/opencv_core_inc.h"
|
||||
#include "mediapipe/framework/port/parse_text_proto.h"
|
||||
#include "mediapipe/framework/port/ret_check.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/framework/port/status_matchers.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace autoflip {
|
||||
namespace {
|
||||
|
||||
const char kConfigA[] = R"(
|
||||
calculator: "ContentZoomingCalculator"
|
||||
input_stream: "VIDEO:camera_frames"
|
||||
input_stream: "DETECTIONS:detection_set"
|
||||
output_stream: "BORDERS:borders"
|
||||
)";
|
||||
|
||||
const char kConfigB[] = R"(
|
||||
calculator: "ContentZoomingCalculator"
|
||||
input_stream: "VIDEO:camera_frames"
|
||||
input_stream: "DETECTIONS:detection_set"
|
||||
output_stream: "BORDERS:borders"
|
||||
options: {
|
||||
[mediapipe.autoflip.ContentZoomingCalculatorOptions.ext]: {
|
||||
target_size {
|
||||
width: 1000
|
||||
height: 500
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char kConfigC[] = R"(
|
||||
calculator: "ContentZoomingCalculator"
|
||||
input_stream: "VIDEO_SIZE:size"
|
||||
input_stream: "DETECTIONS:detection_set"
|
||||
output_stream: "BORDERS:borders"
|
||||
)";
|
||||
|
||||
void CheckBorder(const StaticFeatures& static_features, int width, int height,
|
||||
int top_border, int bottom_border) {
|
||||
ASSERT_EQ(2, static_features.border().size());
|
||||
auto part = static_features.border(0);
|
||||
EXPECT_EQ(part.border_position().x(), 0);
|
||||
EXPECT_EQ(part.border_position().y(), 0);
|
||||
EXPECT_EQ(part.border_position().width(), width);
|
||||
EXPECT_EQ(part.border_position().height(), top_border);
|
||||
EXPECT_EQ(Border::TOP, part.relative_position());
|
||||
|
||||
part = static_features.border(1);
|
||||
EXPECT_EQ(part.border_position().x(), 0);
|
||||
EXPECT_EQ(part.border_position().y(), height - bottom_border);
|
||||
EXPECT_EQ(part.border_position().width(), width);
|
||||
EXPECT_EQ(part.border_position().height(), bottom_border);
|
||||
EXPECT_EQ(Border::BOTTOM, part.relative_position());
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, ZoomTest) {
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(kConfigA));
|
||||
auto detection_set = std::make_unique<DetectionSet>();
|
||||
auto* detection = detection_set->add_detections();
|
||||
detection->set_only_required(true);
|
||||
auto* location = detection->mutable_location_normalized();
|
||||
location->set_height(.1);
|
||||
location->set_width(.1);
|
||||
location->set_x(.4);
|
||||
location->set_y(.5);
|
||||
|
||||
auto input_frame =
|
||||
::absl::make_unique<ImageFrame>(ImageFormat::SRGB, 1000, 1000);
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp(0)));
|
||||
|
||||
runner->MutableInputs()
|
||||
->Tag("DETECTIONS")
|
||||
.packets.push_back(Adopt(detection_set.release()).At(Timestamp(0)));
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("BORDERS").packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& static_features = output_packets[0].Get<StaticFeatures>();
|
||||
CheckBorder(static_features, 1000, 1000, 495, 395);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, MinAspectBorderValues) {
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(kConfigB));
|
||||
auto detection_set = std::make_unique<DetectionSet>();
|
||||
auto* detection = detection_set->add_detections();
|
||||
detection->set_only_required(true);
|
||||
auto* location = detection->mutable_location_normalized();
|
||||
location->set_height(1);
|
||||
location->set_width(1);
|
||||
location->set_x(0);
|
||||
location->set_y(0);
|
||||
|
||||
auto input_frame =
|
||||
::absl::make_unique<ImageFrame>(ImageFormat::SRGB, 1000, 1000);
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp(0)));
|
||||
|
||||
runner->MutableInputs()
|
||||
->Tag("DETECTIONS")
|
||||
.packets.push_back(Adopt(detection_set.release()).At(Timestamp(0)));
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("BORDERS").packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& static_features = output_packets[0].Get<StaticFeatures>();
|
||||
CheckBorder(static_features, 1000, 1000, 250, 250);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, TwoFacesWide) {
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(kConfigA));
|
||||
auto detection_set = std::make_unique<DetectionSet>();
|
||||
auto* detection = detection_set->add_detections();
|
||||
detection->set_only_required(true);
|
||||
auto* location = detection->mutable_location_normalized();
|
||||
location->set_height(.2);
|
||||
location->set_width(.2);
|
||||
location->set_x(.2);
|
||||
location->set_y(.4);
|
||||
|
||||
location = detection->mutable_location_normalized();
|
||||
location->set_height(.2);
|
||||
location->set_width(.2);
|
||||
location->set_x(.6);
|
||||
location->set_y(.4);
|
||||
|
||||
auto input_frame =
|
||||
::absl::make_unique<ImageFrame>(ImageFormat::SRGB, 1000, 1000);
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp(0)));
|
||||
|
||||
runner->MutableInputs()
|
||||
->Tag("DETECTIONS")
|
||||
.packets.push_back(Adopt(detection_set.release()).At(Timestamp(0)));
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("BORDERS").packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& static_features = output_packets[0].Get<StaticFeatures>();
|
||||
|
||||
CheckBorder(static_features, 1000, 1000, 389, 389);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, NoDetectionOnInit) {
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(kConfigA));
|
||||
auto detection_set = std::make_unique<DetectionSet>();
|
||||
|
||||
auto input_frame =
|
||||
::absl::make_unique<ImageFrame>(ImageFormat::SRGB, 1000, 1000);
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp(0)));
|
||||
|
||||
runner->MutableInputs()
|
||||
->Tag("DETECTIONS")
|
||||
.packets.push_back(Adopt(detection_set.release()).At(Timestamp(0)));
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("BORDERS").packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& static_features = output_packets[0].Get<StaticFeatures>();
|
||||
|
||||
CheckBorder(static_features, 1000, 1000, 0, 0);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, ZoomTestPairSize) {
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(kConfigC));
|
||||
auto detection_set = std::make_unique<DetectionSet>();
|
||||
auto* detection = detection_set->add_detections();
|
||||
detection->set_only_required(true);
|
||||
auto* location = detection->mutable_location_normalized();
|
||||
location->set_height(.1);
|
||||
location->set_width(.1);
|
||||
location->set_x(.4);
|
||||
location->set_y(.5);
|
||||
|
||||
auto input_size = ::absl::make_unique<std::pair<int, int>>(1000, 1000);
|
||||
runner->MutableInputs()
|
||||
->Tag("VIDEO_SIZE")
|
||||
.packets.push_back(Adopt(input_size.release()).At(Timestamp(0)));
|
||||
|
||||
runner->MutableInputs()
|
||||
->Tag("DETECTIONS")
|
||||
.packets.push_back(Adopt(detection_set.release()).At(Timestamp(0)));
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("BORDERS").packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& static_features = output_packets[0].Get<StaticFeatures>();
|
||||
CheckBorder(static_features, 1000, 1000, 495, 395);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace autoflip
|
||||
|
||||
} // namespace mediapipe
|
||||
@@ -80,7 +80,9 @@ FaceToRegionCalculator::FaceToRegionCalculator() {}
|
||||
|
||||
::mediapipe::Status FaceToRegionCalculator::GetContract(
|
||||
mediapipe::CalculatorContract* cc) {
|
||||
cc->Inputs().Tag("VIDEO").Set<ImageFrame>();
|
||||
if (cc->Inputs().HasTag("VIDEO")) {
|
||||
cc->Inputs().Tag("VIDEO").Set<ImageFrame>();
|
||||
}
|
||||
cc->Inputs().Tag("FACES").Set<std::vector<mediapipe::Detection>>();
|
||||
cc->Outputs().Tag("REGIONS").Set<DetectionSet>();
|
||||
return ::mediapipe::OkStatus();
|
||||
@@ -89,6 +91,17 @@ FaceToRegionCalculator::FaceToRegionCalculator() {}
|
||||
::mediapipe::Status FaceToRegionCalculator::Open(
|
||||
mediapipe::CalculatorContext* cc) {
|
||||
options_ = cc->Options<FaceToRegionCalculatorOptions>();
|
||||
if (!cc->Inputs().HasTag("VIDEO")) {
|
||||
RET_CHECK(!options_.use_visual_scorer())
|
||||
<< "VIDEO input must be provided when using visual_scorer.";
|
||||
RET_CHECK(!options_.export_individual_face_landmarks())
|
||||
<< "VIDEO input must be provided when export_individual_face_landmarks "
|
||||
"is set true.";
|
||||
RET_CHECK(!options_.export_bbox_from_landmarks())
|
||||
<< "VIDEO input must be provided when export_bbox_from_landmarks "
|
||||
"is set true.";
|
||||
}
|
||||
|
||||
scorer_ = absl::make_unique<VisualScorer>(options_.scorer_options());
|
||||
frame_width_ = -1;
|
||||
frame_height_ = -1;
|
||||
@@ -135,14 +148,19 @@ void FaceToRegionCalculator::ExtendSalientRegionWithPoint(
|
||||
|
||||
::mediapipe::Status FaceToRegionCalculator::Process(
|
||||
mediapipe::CalculatorContext* cc) {
|
||||
if (cc->Inputs().Tag("VIDEO").Value().IsEmpty()) {
|
||||
return ::mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC) << "No VIDEO input.";
|
||||
if (cc->Inputs().HasTag("VIDEO") &&
|
||||
cc->Inputs().Tag("VIDEO").Value().IsEmpty()) {
|
||||
return ::mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "No VIDEO input at time " << cc->InputTimestamp().Seconds();
|
||||
}
|
||||
|
||||
cv::Mat frame =
|
||||
mediapipe::formats::MatView(&cc->Inputs().Tag("VIDEO").Get<ImageFrame>());
|
||||
frame_width_ = frame.cols;
|
||||
frame_height_ = frame.rows;
|
||||
cv::Mat frame;
|
||||
if (cc->Inputs().HasTag("VIDEO")) {
|
||||
frame = mediapipe::formats::MatView(
|
||||
&cc->Inputs().Tag("VIDEO").Get<ImageFrame>());
|
||||
frame_width_ = frame.cols;
|
||||
frame_height_ = frame.rows;
|
||||
}
|
||||
|
||||
auto region_set = ::absl::make_unique<DetectionSet>();
|
||||
if (!cc->Inputs().Tag("FACES").Value().IsEmpty()) {
|
||||
|
||||
@@ -40,6 +40,12 @@ const char kConfig[] = R"(
|
||||
output_stream: "REGIONS:regions"
|
||||
)";
|
||||
|
||||
const char kConfigNoVideo[] = R"(
|
||||
calculator: "FaceToRegionCalculator"
|
||||
input_stream: "FACES:faces"
|
||||
output_stream: "REGIONS:regions"
|
||||
)";
|
||||
|
||||
const char kFace1[] = R"(location_data {
|
||||
format: RELATIVE_BOUNDING_BOX
|
||||
relative_bounding_box {
|
||||
@@ -88,13 +94,15 @@ const char kFace3[] = R"(location_data {
|
||||
relative_keypoints { x: 0 y: 0 }
|
||||
})";
|
||||
|
||||
void SetInputs(CalculatorRunner* runner,
|
||||
const std::vector<std::string>& faces) {
|
||||
void SetInputs(const std::vector<std::string>& faces, const bool include_video,
|
||||
CalculatorRunner* runner) {
|
||||
// Setup an input video frame.
|
||||
auto input_frame =
|
||||
::absl::make_unique<ImageFrame>(ImageFormat::SRGB, 800, 600);
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp::PostStream()));
|
||||
if (include_video) {
|
||||
auto input_frame =
|
||||
::absl::make_unique<ImageFrame>(ImageFormat::SRGB, 800, 600);
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp::PostStream()));
|
||||
}
|
||||
// Setup two faces as input.
|
||||
auto input_faces = ::absl::make_unique<std::vector<Detection>>();
|
||||
// A face with landmarks.
|
||||
@@ -105,30 +113,31 @@ void SetInputs(CalculatorRunner* runner,
|
||||
Adopt(input_faces.release()).At(Timestamp::PostStream()));
|
||||
}
|
||||
|
||||
CalculatorGraphConfig::Node MakeConfig(bool whole_face, bool landmarks,
|
||||
bool bb_from_landmarks) {
|
||||
auto config = ParseTextProtoOrDie<CalculatorGraphConfig::Node>(kConfig);
|
||||
|
||||
CalculatorGraphConfig::Node MakeConfig(std::string base_config, bool whole_face,
|
||||
bool landmarks, bool bb_from_landmarks,
|
||||
bool visual_scoring) {
|
||||
auto config = ParseTextProtoOrDie<CalculatorGraphConfig::Node>(base_config);
|
||||
config.mutable_options()
|
||||
->MutableExtension(FaceToRegionCalculatorOptions::ext)
|
||||
->set_export_whole_face(whole_face);
|
||||
|
||||
config.mutable_options()
|
||||
->MutableExtension(FaceToRegionCalculatorOptions::ext)
|
||||
->set_export_individual_face_landmarks(landmarks);
|
||||
|
||||
config.mutable_options()
|
||||
->MutableExtension(FaceToRegionCalculatorOptions::ext)
|
||||
->set_export_bbox_from_landmarks(bb_from_landmarks);
|
||||
config.mutable_options()
|
||||
->MutableExtension(FaceToRegionCalculatorOptions::ext)
|
||||
->set_use_visual_scorer(visual_scoring);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
TEST(FaceToRegionCalculatorTest, FaceFullTypeSize) {
|
||||
// Setup test
|
||||
auto runner =
|
||||
::absl::make_unique<CalculatorRunner>(MakeConfig(true, false, false));
|
||||
SetInputs(runner.get(), {kFace1, kFace2});
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(
|
||||
MakeConfig(kConfig, true, false, false, true));
|
||||
SetInputs({kFace1, kFace2}, true, runner.get());
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
@@ -159,9 +168,9 @@ TEST(FaceToRegionCalculatorTest, FaceFullTypeSize) {
|
||||
|
||||
TEST(FaceToRegionCalculatorTest, FaceLandmarksTypeSize) {
|
||||
// Setup test
|
||||
auto runner =
|
||||
::absl::make_unique<CalculatorRunner>(MakeConfig(false, true, false));
|
||||
SetInputs(runner.get(), {kFace1});
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(
|
||||
MakeConfig(kConfig, false, true, false, true));
|
||||
SetInputs({kFace1}, true, runner.get());
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
@@ -190,9 +199,9 @@ TEST(FaceToRegionCalculatorTest, FaceLandmarksTypeSize) {
|
||||
|
||||
TEST(FaceToRegionCalculatorTest, FaceLandmarksBox) {
|
||||
// Setup test
|
||||
auto runner =
|
||||
::absl::make_unique<CalculatorRunner>(MakeConfig(false, false, true));
|
||||
SetInputs(runner.get(), {kFace1});
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(
|
||||
MakeConfig(kConfig, false, false, true, true));
|
||||
SetInputs({kFace1}, true, runner.get());
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
@@ -225,9 +234,9 @@ TEST(FaceToRegionCalculatorTest, FaceLandmarksBox) {
|
||||
|
||||
TEST(FaceToRegionCalculatorTest, FaceScore) {
|
||||
// Setup test
|
||||
auto runner =
|
||||
::absl::make_unique<CalculatorRunner>(MakeConfig(true, false, false));
|
||||
SetInputs(runner.get(), {kFace3});
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(
|
||||
MakeConfig(kConfig, true, false, false, true));
|
||||
SetInputs({kFace3}, true, runner.get());
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
@@ -242,6 +251,69 @@ TEST(FaceToRegionCalculatorTest, FaceScore) {
|
||||
EXPECT_FLOAT_EQ(landmark_1.score(), 0.25);
|
||||
}
|
||||
|
||||
TEST(FaceToRegionCalculatorTest, FaceNoVideoVisualScoreFail) {
|
||||
// Setup test
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(
|
||||
MakeConfig(kConfigNoVideo, true, false, false, true));
|
||||
SetInputs({kFace3}, false, runner.get());
|
||||
|
||||
// Run the calculator.
|
||||
ASSERT_FALSE(runner->Run().ok());
|
||||
}
|
||||
|
||||
TEST(FaceToRegionCalculatorTest, FaceNoVideoLandmarksFail) {
|
||||
// Setup test
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(
|
||||
MakeConfig(kConfigNoVideo, false, true, false, false));
|
||||
SetInputs({kFace3}, false, runner.get());
|
||||
|
||||
// Run the calculator.
|
||||
ASSERT_FALSE(runner->Run().ok());
|
||||
}
|
||||
|
||||
TEST(FaceToRegionCalculatorTest, FaceNoVideoBBLandmarksFail) {
|
||||
// Setup test
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(
|
||||
MakeConfig(kConfigNoVideo, false, false, true, false));
|
||||
SetInputs({kFace3}, false, runner.get());
|
||||
|
||||
// Run the calculator.
|
||||
ASSERT_FALSE(runner->Run().ok());
|
||||
}
|
||||
|
||||
TEST(FaceToRegionCalculatorTest, FaceNoVideoPass) {
|
||||
// Setup test
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(
|
||||
MakeConfig(kConfigNoVideo, true, false, false, false));
|
||||
SetInputs({kFace1, kFace2}, false, runner.get());
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
// Check the output regions.
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("REGIONS").packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
|
||||
const auto& regions = output_packets[0].Get<DetectionSet>();
|
||||
ASSERT_EQ(2, regions.detections().size());
|
||||
auto face_1 = regions.detections(0);
|
||||
EXPECT_EQ(face_1.signal_type().standard(), SignalType::FACE_FULL);
|
||||
EXPECT_FLOAT_EQ(face_1.location_normalized().x(), 0);
|
||||
EXPECT_FLOAT_EQ(face_1.location_normalized().y(), 0.003333);
|
||||
EXPECT_FLOAT_EQ(face_1.location_normalized().width(), 0.12125);
|
||||
EXPECT_FLOAT_EQ(face_1.location_normalized().height(), 0.33333);
|
||||
EXPECT_FLOAT_EQ(face_1.score(), 1);
|
||||
|
||||
auto face_2 = regions.detections(1);
|
||||
EXPECT_EQ(face_2.signal_type().standard(), SignalType::FACE_FULL);
|
||||
EXPECT_FLOAT_EQ(face_2.location_normalized().x(), 0.0025);
|
||||
EXPECT_FLOAT_EQ(face_2.location_normalized().y(), 0.005);
|
||||
EXPECT_FLOAT_EQ(face_2.location_normalized().width(), 0.25);
|
||||
EXPECT_FLOAT_EQ(face_2.location_normalized().height(), 0.5);
|
||||
EXPECT_FLOAT_EQ(face_2.score(), 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace autoflip
|
||||
} // namespace mediapipe
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#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/canonical_errors.h"
|
||||
#include "mediapipe/framework/port/opencv_core_inc.h"
|
||||
#include "mediapipe/framework/port/opencv_imgproc_inc.h"
|
||||
#include "mediapipe/framework/port/parse_text_proto.h"
|
||||
@@ -35,6 +36,7 @@ namespace mediapipe {
|
||||
namespace autoflip {
|
||||
|
||||
constexpr char kInputVideoFrames[] = "VIDEO_FRAMES";
|
||||
constexpr char kInputVideoSize[] = "VIDEO_SIZE";
|
||||
constexpr char kInputKeyFrames[] = "KEY_FRAMES";
|
||||
constexpr char kInputDetections[] = "DETECTION_FEATURES";
|
||||
constexpr char kInputStaticFeatures[] = "STATIC_FEATURES";
|
||||
@@ -49,8 +51,17 @@ constexpr char kAspectRatio[] = "EXTERNAL_ASPECT_RATIO";
|
||||
// SALIENT_POINT_FRAME_VIZ_FRAMES can only be enabled when CROPPED_FRAMES is
|
||||
// enabled.
|
||||
constexpr char kOutputCroppedFrames[] = "CROPPED_FRAMES";
|
||||
// Shows detections on key frames. Any static borders will be removed from the
|
||||
// output frame.
|
||||
constexpr char kOutputKeyFrameCropViz[] = "KEY_FRAME_CROP_REGION_VIZ_FRAMES";
|
||||
// Shows x/y (raw unsmoothed) cropping and focus points. Any static borders
|
||||
// will be removed from the output frame.
|
||||
constexpr char kOutputFocusPointFrameViz[] = "SALIENT_POINT_FRAME_VIZ_FRAMES";
|
||||
// Shows final smoothed cropping and a focused area of the camera. Any static
|
||||
// borders will remain and be shown in grey. Output frame will match input
|
||||
// frame size.
|
||||
constexpr char kOutputFramingAndDetections[] = "FRAMING_DETECTIONS_VIZ_FRAMES";
|
||||
// Final summary of cropping.
|
||||
constexpr char kOutputSummary[] = "CROPPING_SUMMARY";
|
||||
|
||||
// External rendering outputs
|
||||
@@ -65,7 +76,12 @@ constexpr char kExternalRenderingFullVid[] = "EXTERNAL_RENDERING_FULL_VID";
|
||||
if (cc->InputSidePackets().HasTag(kAspectRatio)) {
|
||||
cc->InputSidePackets().Tag(kAspectRatio).Set<std::string>();
|
||||
}
|
||||
cc->Inputs().Tag(kInputVideoFrames).Set<ImageFrame>();
|
||||
if (cc->Inputs().HasTag(kInputVideoFrames)) {
|
||||
cc->Inputs().Tag(kInputVideoFrames).Set<ImageFrame>();
|
||||
}
|
||||
if (cc->Inputs().HasTag(kInputVideoSize)) {
|
||||
cc->Inputs().Tag(kInputVideoSize).Set<std::pair<int, int>>();
|
||||
}
|
||||
if (cc->Inputs().HasTag(kInputKeyFrames)) {
|
||||
cc->Inputs().Tag(kInputKeyFrames).Set<ImageFrame>();
|
||||
}
|
||||
@@ -73,7 +89,9 @@ constexpr char kExternalRenderingFullVid[] = "EXTERNAL_RENDERING_FULL_VID";
|
||||
if (cc->Inputs().HasTag(kInputStaticFeatures)) {
|
||||
cc->Inputs().Tag(kInputStaticFeatures).Set<StaticFeatures>();
|
||||
}
|
||||
cc->Inputs().Tag(kInputShotBoundaries).Set<bool>();
|
||||
if (cc->Inputs().HasTag(kInputShotBoundaries)) {
|
||||
cc->Inputs().Tag(kInputShotBoundaries).Set<bool>();
|
||||
}
|
||||
|
||||
if (cc->Outputs().HasTag(kOutputCroppedFrames)) {
|
||||
cc->Outputs().Tag(kOutputCroppedFrames).Set<ImageFrame>();
|
||||
@@ -84,6 +102,12 @@ constexpr char kExternalRenderingFullVid[] = "EXTERNAL_RENDERING_FULL_VID";
|
||||
"CROPPED_FRAMES is specified.";
|
||||
cc->Outputs().Tag(kOutputKeyFrameCropViz).Set<ImageFrame>();
|
||||
}
|
||||
if (cc->Outputs().HasTag(kOutputFramingAndDetections)) {
|
||||
RET_CHECK(cc->Outputs().HasTag(kOutputCroppedFrames))
|
||||
<< "FRAMING_DETECTIONS_VIZ_FRAMES can only be used when "
|
||||
"CROPPED_FRAMES is specified.";
|
||||
cc->Outputs().Tag(kOutputFramingAndDetections).Set<ImageFrame>();
|
||||
}
|
||||
if (cc->Outputs().HasTag(kOutputFocusPointFrameViz)) {
|
||||
RET_CHECK(cc->Outputs().HasTag(kOutputCroppedFrames))
|
||||
<< "SALIENT_POINT_FRAME_VIZ_FRAMES can only be used when "
|
||||
@@ -101,6 +125,13 @@ constexpr char kExternalRenderingFullVid[] = "EXTERNAL_RENDERING_FULL_VID";
|
||||
.Tag(kExternalRenderingFullVid)
|
||||
.Set<std::vector<ExternalRenderFrame>>();
|
||||
}
|
||||
RET_CHECK(cc->Inputs().HasTag(kInputVideoFrames) ^
|
||||
cc->Inputs().HasTag(kInputVideoSize))
|
||||
<< "VIDEO_FRAMES or VIDEO_SIZE must be set and not both.";
|
||||
RET_CHECK(!(cc->Inputs().HasTag(kInputVideoSize) &&
|
||||
cc->Inputs().HasTag(kOutputCroppedFrames)))
|
||||
<< "CROPPED_FRAMES (internal cropping) has been set as an output without "
|
||||
"VIDEO_FRAMES (video data) input.";
|
||||
RET_CHECK(cc->Outputs().HasTag(kExternalRenderingPerFrame) ||
|
||||
cc->Outputs().HasTag(kExternalRenderingFullVid) ||
|
||||
cc->Outputs().HasTag(kOutputCroppedFrames))
|
||||
@@ -128,7 +159,12 @@ constexpr char kExternalRenderingFullVid[] = "EXTERNAL_RENDERING_FULL_VID";
|
||||
RET_CHECK(overlay_opacity_ >= 0.0 && overlay_opacity_ <= 1.0)
|
||||
<< "Overlay opacity " << overlay_opacity_ << " is not in [0, 1].";
|
||||
|
||||
scene_cropper_ = absl::make_unique<SceneCropper>();
|
||||
// Set default camera model to polynomial_path_solver.
|
||||
if (!options_.camera_motion_options().has_kinematic_options()) {
|
||||
options_.mutable_camera_motion_options()
|
||||
->mutable_polynomial_path_solver()
|
||||
->set_prior_frame_buffer_size(options_.prior_frame_buffer_size());
|
||||
}
|
||||
if (cc->Outputs().HasTag(kOutputSummary)) {
|
||||
summary_ = absl::make_unique<VideoCroppingSummary>();
|
||||
}
|
||||
@@ -137,6 +173,8 @@ constexpr char kExternalRenderingFullVid[] = "EXTERNAL_RENDERING_FULL_VID";
|
||||
absl::make_unique<std::vector<ExternalRenderFrame>>();
|
||||
}
|
||||
should_perform_frame_cropping_ = cc->Outputs().HasTag(kOutputCroppedFrames);
|
||||
scene_camera_motion_analyzer_ = absl::make_unique<SceneCameraMotionAnalyzer>(
|
||||
options_.scene_camera_motion_analyzer_options());
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
@@ -182,44 +220,59 @@ void ConstructExternalRenderMessage(
|
||||
padding_color_message->set_b(padding_color[2]);
|
||||
external_render_message->set_timestamp_us(timestamp_us);
|
||||
}
|
||||
|
||||
double GetRatio(int width, int height) {
|
||||
return static_cast<double>(width) / height;
|
||||
}
|
||||
|
||||
int RoundToEven(float value) {
|
||||
int rounded_value = std::round(value);
|
||||
if (rounded_value % 2 == 1) {
|
||||
rounded_value = std::max(2, rounded_value - 1);
|
||||
}
|
||||
return rounded_value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
::mediapipe::Status SceneCroppingCalculator::Process(
|
||||
::mediapipe::Status SceneCroppingCalculator::InitializeSceneCroppingCalculator(
|
||||
::mediapipe::CalculatorContext* cc) {
|
||||
// Sets frame dimension and format.
|
||||
if (frame_width_ < 0 &&
|
||||
!cc->Inputs().Tag(kInputVideoFrames).Value().IsEmpty()) {
|
||||
if (cc->Inputs().HasTag(kInputVideoFrames)) {
|
||||
const auto& frame = cc->Inputs().Tag(kInputVideoFrames).Get<ImageFrame>();
|
||||
frame_width_ = frame.Width();
|
||||
RET_CHECK_GT(frame_width_, 0) << "Input frame width is non-positive.";
|
||||
frame_height_ = frame.Height();
|
||||
RET_CHECK_GT(frame_height_, 0) << "Input frame height is non-positive.";
|
||||
frame_format_ = frame.Format();
|
||||
target_width_ = options_.target_width();
|
||||
target_height_ = options_.target_height();
|
||||
if (cc->InputSidePackets().HasTag(kInputExternalSettings)) {
|
||||
auto conversion_options = ParseTextProtoOrDie<ConversionOptions>(
|
||||
cc->InputSidePackets()
|
||||
.Tag(kInputExternalSettings)
|
||||
.Get<std::string>());
|
||||
target_width_ = conversion_options.target_width();
|
||||
target_height_ = conversion_options.target_height();
|
||||
}
|
||||
target_aspect_ratio_ = static_cast<double>(target_width_) / target_height_;
|
||||
RET_CHECK_NE(options_.target_size_type(),
|
||||
SceneCroppingCalculatorOptions::UNKNOWN)
|
||||
<< "TargetSizeType not set properly.";
|
||||
// Resets target size if keep original height or width.
|
||||
if (options_.target_size_type() ==
|
||||
SceneCroppingCalculatorOptions::KEEP_ORIGINAL_HEIGHT) {
|
||||
target_height_ = frame_height_;
|
||||
target_width_ = std::round(target_height_ * target_aspect_ratio_);
|
||||
} else if (options_.target_size_type() ==
|
||||
SceneCroppingCalculatorOptions::KEEP_ORIGINAL_WIDTH) {
|
||||
target_width_ = frame_width_;
|
||||
target_height_ = std::round(target_width_ / target_aspect_ratio_);
|
||||
} else if (options_.target_size_type() ==
|
||||
SceneCroppingCalculatorOptions::MAXIMIZE_TARGET_DIMENSION) {
|
||||
} else if (cc->Inputs().HasTag(kInputVideoSize)) {
|
||||
frame_width_ =
|
||||
cc->Inputs().Tag(kInputVideoSize).Get<std::pair<int, int>>().first;
|
||||
frame_height_ =
|
||||
cc->Inputs().Tag(kInputVideoSize).Get<std::pair<int, int>>().second;
|
||||
} else {
|
||||
return ::mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "Input VIDEO or VIDEO_SIZE must be provided.";
|
||||
}
|
||||
RET_CHECK_GT(frame_height_, 0) << "Input frame height is non-positive.";
|
||||
RET_CHECK_GT(frame_width_, 0) << "Input frame width is non-positive.";
|
||||
|
||||
// Calculate target width and height.
|
||||
switch (options_.target_size_type()) {
|
||||
case SceneCroppingCalculatorOptions::KEEP_ORIGINAL_HEIGHT:
|
||||
RET_CHECK(options_.has_target_width() && options_.has_target_height())
|
||||
<< "Target width and height have to be specified.";
|
||||
target_height_ = RoundToEven(frame_height_);
|
||||
target_width_ =
|
||||
RoundToEven(target_height_ * GetRatio(options_.target_width(),
|
||||
options_.target_height()));
|
||||
break;
|
||||
case SceneCroppingCalculatorOptions::KEEP_ORIGINAL_WIDTH:
|
||||
RET_CHECK(options_.has_target_width() && options_.has_target_height())
|
||||
<< "Target width and height have to be specified.";
|
||||
target_width_ = RoundToEven(frame_width_);
|
||||
target_height_ =
|
||||
RoundToEven(target_width_ / GetRatio(options_.target_width(),
|
||||
options_.target_height()));
|
||||
break;
|
||||
case SceneCroppingCalculatorOptions::MAXIMIZE_TARGET_DIMENSION: {
|
||||
RET_CHECK(cc->InputSidePackets().HasTag(kAspectRatio))
|
||||
<< "MAXIMIZE_TARGET_DIMENSION is set without an "
|
||||
"external_aspect_ratio";
|
||||
@@ -228,49 +281,81 @@ void ConstructExternalRenderMessage(
|
||||
cc->InputSidePackets().Tag(kAspectRatio).Get<std::string>(),
|
||||
&requested_aspect_ratio));
|
||||
const double original_aspect_ratio =
|
||||
static_cast<double>(frame_width_) / frame_height_;
|
||||
GetRatio(frame_width_, frame_height_);
|
||||
if (original_aspect_ratio > requested_aspect_ratio) {
|
||||
target_height_ = frame_height_;
|
||||
target_width_ = std::round(target_height_ * requested_aspect_ratio);
|
||||
target_height_ = RoundToEven(frame_height_);
|
||||
target_width_ = RoundToEven(target_height_ * requested_aspect_ratio);
|
||||
} else {
|
||||
target_width_ = frame_width_;
|
||||
target_height_ = std::round(target_width_ / requested_aspect_ratio);
|
||||
target_width_ = RoundToEven(frame_width_);
|
||||
target_height_ = RoundToEven(target_width_ / requested_aspect_ratio);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Makes sure that target size is even if keep original width or height.
|
||||
if (options_.target_size_type() !=
|
||||
SceneCroppingCalculatorOptions::USE_TARGET_DIMENSION) {
|
||||
if (target_width_ % 2 == 1) {
|
||||
target_width_ = std::max(2, target_width_ - 1);
|
||||
}
|
||||
if (target_height_ % 2 == 1) {
|
||||
target_height_ = std::max(2, target_height_ - 1);
|
||||
}
|
||||
target_aspect_ratio_ =
|
||||
static_cast<double>(target_width_) / target_height_;
|
||||
}
|
||||
// Set keyframe width/height for feature upscaling (overwritten by keyframe
|
||||
// input if provided).
|
||||
if (options_.has_video_features_width() &&
|
||||
options_.has_video_features_height()) {
|
||||
key_frame_width_ = options_.video_features_width();
|
||||
key_frame_height_ = options_.video_features_height();
|
||||
} else if (!cc->Inputs().HasTag(kInputKeyFrames)) {
|
||||
key_frame_width_ = frame_width_;
|
||||
key_frame_height_ = frame_height_;
|
||||
}
|
||||
// Check provided dimensions.
|
||||
RET_CHECK_GT(target_width_, 0) << "Target width is non-positive.";
|
||||
RET_CHECK_NE(target_width_ % 2, 1)
|
||||
<< "Target width cannot be odd, because encoder expects dimension "
|
||||
"values to be even.";
|
||||
RET_CHECK_GT(target_height_, 0) << "Target height is non-positive.";
|
||||
RET_CHECK_NE(target_height_ % 2, 1)
|
||||
<< "Target height cannot be odd, because encoder expects dimension "
|
||||
"values to be even.";
|
||||
case SceneCroppingCalculatorOptions::USE_TARGET_DIMENSION:
|
||||
RET_CHECK(options_.has_target_width() && options_.has_target_height())
|
||||
<< "Target width and height have to be specified.";
|
||||
target_width_ = options_.target_width();
|
||||
target_height_ = options_.target_height();
|
||||
break;
|
||||
case SceneCroppingCalculatorOptions::KEEP_ORIGINAL_DIMENSION:
|
||||
target_width_ = frame_width_;
|
||||
target_height_ = frame_height_;
|
||||
break;
|
||||
case SceneCroppingCalculatorOptions::UNKNOWN:
|
||||
return mediapipe::InvalidArgumentError(
|
||||
"target_size_type not set properly.");
|
||||
}
|
||||
target_aspect_ratio_ = GetRatio(target_width_, target_height_);
|
||||
|
||||
// Set keyframe width/height for feature upscaling.
|
||||
RET_CHECK(!(cc->Inputs().HasTag(kInputKeyFrames) &&
|
||||
(options_.has_video_features_width() ||
|
||||
options_.has_video_features_height())))
|
||||
<< "Key frame size must be defined by either providing the input stream "
|
||||
"KEY_FRAMES or setting video_features_width/video_features_height as "
|
||||
"calculator options. Both methods cannot be used together.";
|
||||
if (options_.has_video_features_width() &&
|
||||
options_.has_video_features_height()) {
|
||||
key_frame_width_ = options_.video_features_width();
|
||||
key_frame_height_ = options_.video_features_height();
|
||||
} else if (!cc->Inputs().HasTag(kInputKeyFrames)) {
|
||||
key_frame_width_ = frame_width_;
|
||||
key_frame_height_ = frame_height_;
|
||||
}
|
||||
// Check provided dimensions.
|
||||
RET_CHECK_GT(target_width_, 0) << "Target width is non-positive.";
|
||||
// TODO: it seems this check is too strict and maybe limiting,
|
||||
// considering the receiver of frames can be something other than encoder.
|
||||
RET_CHECK_NE(target_width_ % 2, 1)
|
||||
<< "Target width cannot be odd, because encoder expects dimension "
|
||||
"values to be even.";
|
||||
RET_CHECK_GT(target_height_, 0) << "Target height is non-positive.";
|
||||
RET_CHECK_NE(target_height_ % 2, 1)
|
||||
<< "Target height cannot be odd, because encoder expects dimension "
|
||||
"values to be even.";
|
||||
|
||||
scene_cropper_ = absl::make_unique<SceneCropper>(
|
||||
options_.camera_motion_options(), frame_width_, frame_height_);
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
bool HasFrameSignal(::mediapipe::CalculatorContext* cc) {
|
||||
if (cc->Inputs().HasTag(kInputVideoFrames)) {
|
||||
return !cc->Inputs().Tag(kInputVideoFrames).Value().IsEmpty();
|
||||
}
|
||||
return !cc->Inputs().Tag(kInputVideoSize).Value().IsEmpty();
|
||||
}
|
||||
|
||||
::mediapipe::Status SceneCroppingCalculator::Process(
|
||||
::mediapipe::CalculatorContext* cc) {
|
||||
// Sets frame dimension and initializes scenecroppingcalculator on first video
|
||||
// frame.
|
||||
if (frame_width_ < 0) {
|
||||
MP_RETURN_IF_ERROR(InitializeSceneCroppingCalculator(cc));
|
||||
}
|
||||
|
||||
// Sets key frame dimension.
|
||||
// Sets key frame dimension on first keyframe.
|
||||
if (cc->Inputs().HasTag(kInputKeyFrames) &&
|
||||
!cc->Inputs().Tag(kInputKeyFrames).Value().IsEmpty() &&
|
||||
key_frame_width_ < 0) {
|
||||
@@ -281,21 +366,18 @@ void ConstructExternalRenderMessage(
|
||||
|
||||
// Processes a scene when shot boundary or buffer is full.
|
||||
bool is_end_of_scene = false;
|
||||
if (!cc->Inputs().Tag(kInputShotBoundaries).Value().IsEmpty()) {
|
||||
if (cc->Inputs().HasTag(kInputShotBoundaries) &&
|
||||
!cc->Inputs().Tag(kInputShotBoundaries).Value().IsEmpty()) {
|
||||
is_end_of_scene = cc->Inputs().Tag(kInputShotBoundaries).Get<bool>();
|
||||
}
|
||||
const bool force_buffer_flush =
|
||||
scene_frame_timestamps_.size() >= options_.max_scene_size();
|
||||
if (!scene_frame_timestamps_.empty() &&
|
||||
(is_end_of_scene || force_buffer_flush)) {
|
||||
|
||||
if (!scene_frame_timestamps_.empty() && (is_end_of_scene)) {
|
||||
continue_last_scene_ = false;
|
||||
MP_RETURN_IF_ERROR(ProcessScene(is_end_of_scene, cc));
|
||||
}
|
||||
|
||||
// Saves frame and timestamp and whether it is a key frame.
|
||||
if (!cc->Inputs().Tag(kInputVideoFrames).Value().IsEmpty()) {
|
||||
LOG_EVERY_N(ERROR, 10)
|
||||
<< "------------------------ (Breathing) Time(s): "
|
||||
<< cc->Inputs().Tag(kInputVideoFrames).Value().Timestamp().Seconds();
|
||||
if (HasFrameSignal(cc)) {
|
||||
// Only buffer frames if |should_perform_frame_cropping_| is true.
|
||||
if (should_perform_frame_cropping_) {
|
||||
const auto& frame = cc->Inputs().Tag(kInputVideoFrames).Get<ImageFrame>();
|
||||
@@ -328,6 +410,13 @@ void ConstructExternalRenderMessage(
|
||||
static_features_timestamps_.push_back(cc->InputTimestamp().Value());
|
||||
}
|
||||
|
||||
const bool force_buffer_flush =
|
||||
scene_frame_timestamps_.size() >= options_.max_scene_size();
|
||||
if (!scene_frame_timestamps_.empty() && force_buffer_flush) {
|
||||
MP_RETURN_IF_ERROR(ProcessScene(is_end_of_scene, cc));
|
||||
continue_last_scene_ = true;
|
||||
}
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
@@ -352,7 +441,7 @@ void ConstructExternalRenderMessage(
|
||||
// TODO: split this function into two, one for calculating the border
|
||||
// sizes, the other for the actual removal of borders from the frames.
|
||||
::mediapipe::Status SceneCroppingCalculator::RemoveStaticBorders(
|
||||
int* top_border_size, int* bottom_border_size) {
|
||||
CalculatorContext* cc, int* top_border_size, int* bottom_border_size) {
|
||||
*top_border_size = 0;
|
||||
*bottom_border_size = 0;
|
||||
MP_RETURN_IF_ERROR(ComputeSceneStaticBordersSize(
|
||||
@@ -363,6 +452,13 @@ void ConstructExternalRenderMessage(
|
||||
effective_frame_height_ =
|
||||
frame_height_ - top_border_distance_ - bottom_border_distance;
|
||||
|
||||
// Store shallow copy of the original frames for debug display if required
|
||||
// before static areas are removed.
|
||||
if (cc->Outputs().HasTag(kOutputFramingAndDetections)) {
|
||||
raw_scene_frames_or_empty_ = {scene_frames_or_empty_.begin(),
|
||||
scene_frames_or_empty_.end()};
|
||||
}
|
||||
|
||||
if (top_border_distance_ > 0 || bottom_border_distance > 0) {
|
||||
VLOG(1) << "Remove top border " << top_border_distance_ << " bottom border "
|
||||
<< bottom_border_distance;
|
||||
@@ -442,8 +538,8 @@ void SceneCroppingCalculator::FilterKeyFrameInfo() {
|
||||
|
||||
// Removes any static borders.
|
||||
int top_static_border_size, bottom_static_border_size;
|
||||
MP_RETURN_IF_ERROR(
|
||||
RemoveStaticBorders(&top_static_border_size, &bottom_static_border_size));
|
||||
MP_RETURN_IF_ERROR(RemoveStaticBorders(cc, &top_static_border_size,
|
||||
&bottom_static_border_size));
|
||||
|
||||
// Decides if solid background color padding is possible and sets up color
|
||||
// interpolation functions in CIELAB. Uses linear interpolation by default.
|
||||
@@ -453,7 +549,8 @@ void SceneCroppingCalculator::FilterKeyFrameInfo() {
|
||||
&has_solid_background_, &background_color_l_function_,
|
||||
&background_color_a_function_, &background_color_b_function_));
|
||||
|
||||
// Computes key frame crop regions.
|
||||
// Computes key frame crop regions and moves information from raw
|
||||
// key_frame_infos_ to key_frame_crop_results.
|
||||
MP_RETURN_IF_ERROR(InitializeFrameCropRegionComputer());
|
||||
const int num_key_frames = key_frame_infos_.size();
|
||||
std::vector<KeyFrameCropResult> key_frame_crop_results(num_key_frames);
|
||||
@@ -462,20 +559,15 @@ void SceneCroppingCalculator::FilterKeyFrameInfo() {
|
||||
key_frame_infos_[i], &key_frame_crop_results[i]));
|
||||
}
|
||||
|
||||
// Analyzes scene camera motion and generates FocusPointFrames.
|
||||
auto analyzer_options = options_.scene_camera_motion_analyzer_options();
|
||||
analyzer_options.set_allow_sweeping(analyzer_options.allow_sweeping() &&
|
||||
!has_solid_background_);
|
||||
scene_camera_motion_analyzer_ =
|
||||
absl::make_unique<SceneCameraMotionAnalyzer>(analyzer_options);
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
std::vector<FocusPointFrame> focus_point_frames;
|
||||
SceneCameraMotion scene_camera_motion;
|
||||
MP_RETURN_IF_ERROR(
|
||||
scene_camera_motion_analyzer_->AnalyzeSceneAndPopulateFocusPointFrames(
|
||||
key_frame_infos_, key_frame_crop_options_, key_frame_crop_results,
|
||||
frame_width_, effective_frame_height_, scene_frame_timestamps_,
|
||||
&scene_summary, &focus_point_frames, &scene_camera_motion));
|
||||
key_frame_crop_options_, key_frame_crop_results, frame_width_,
|
||||
effective_frame_height_, scene_frame_timestamps_,
|
||||
has_solid_background_, &scene_summary, &focus_point_frames,
|
||||
&scene_camera_motion));
|
||||
|
||||
// Crops scene frames.
|
||||
std::vector<cv::Mat> cropped_frames;
|
||||
@@ -485,26 +577,28 @@ void SceneCroppingCalculator::FilterKeyFrameInfo() {
|
||||
should_perform_frame_cropping_ ? &cropped_frames : nullptr;
|
||||
|
||||
MP_RETURN_IF_ERROR(scene_cropper_->CropFrames(
|
||||
scene_summary, scene_frame_timestamps_.size(), scene_frames_or_empty_,
|
||||
focus_point_frames, prior_focus_point_frames_, top_static_border_size,
|
||||
bottom_static_border_size, &crop_from_locations, cropped_frames_ptr));
|
||||
scene_summary, scene_frame_timestamps_, is_key_frames_,
|
||||
scene_frames_or_empty_, focus_point_frames, prior_focus_point_frames_,
|
||||
top_static_border_size, bottom_static_border_size, continue_last_scene_,
|
||||
&crop_from_locations, cropped_frames_ptr));
|
||||
|
||||
// Formats and outputs cropped frames.
|
||||
bool apply_padding = false;
|
||||
float vertical_fill_percent;
|
||||
std::vector<cv::Rect> render_to_locations;
|
||||
std::vector<cv::Scalar> padding_colors;
|
||||
if (should_perform_frame_cropping_) {
|
||||
MP_RETURN_IF_ERROR(FormatAndOutputCroppedFrames(
|
||||
cropped_frames, &render_to_locations, &apply_padding, &padding_colors,
|
||||
&vertical_fill_percent, cc));
|
||||
}
|
||||
MP_RETURN_IF_ERROR(FormatAndOutputCroppedFrames(
|
||||
scene_summary.crop_window_width(), scene_summary.crop_window_height(),
|
||||
scene_frame_timestamps_.size(), &render_to_locations, &apply_padding,
|
||||
&padding_colors, &vertical_fill_percent, cropped_frames_ptr, cc));
|
||||
// Caches prior FocusPointFrames if this was not the end of a scene.
|
||||
prior_focus_point_frames_.clear();
|
||||
if (!is_end_of_scene) {
|
||||
const int start =
|
||||
std::max(0, static_cast<int>(scene_frame_timestamps_.size()) -
|
||||
options_.prior_frame_buffer_size());
|
||||
options_.camera_motion_options()
|
||||
.polynomial_path_solver()
|
||||
.prior_frame_buffer_size());
|
||||
for (int i = start; i < num_key_frames; ++i) {
|
||||
prior_focus_point_frames_.push_back(focus_point_frames[i]);
|
||||
}
|
||||
@@ -512,6 +606,7 @@ void SceneCroppingCalculator::FilterKeyFrameInfo() {
|
||||
|
||||
// Optionally outputs visualization frames.
|
||||
MP_RETURN_IF_ERROR(OutputVizFrames(key_frame_crop_results, focus_point_frames,
|
||||
crop_from_locations,
|
||||
scene_summary.crop_window_width(),
|
||||
scene_summary.crop_window_height(), cc));
|
||||
|
||||
@@ -563,18 +658,13 @@ void SceneCroppingCalculator::FilterKeyFrameInfo() {
|
||||
}
|
||||
|
||||
::mediapipe::Status SceneCroppingCalculator::FormatAndOutputCroppedFrames(
|
||||
const std::vector<cv::Mat>& cropped_frames,
|
||||
const int crop_width, const int crop_height, const int num_frames,
|
||||
std::vector<cv::Rect>* render_to_locations, bool* apply_padding,
|
||||
std::vector<cv::Scalar>* padding_colors, float* vertical_fill_percent,
|
||||
CalculatorContext* cc) {
|
||||
const std::vector<cv::Mat>* cropped_frames_ptr, CalculatorContext* cc) {
|
||||
RET_CHECK(apply_padding) << "Has padding boolean is null.";
|
||||
if (cropped_frames.empty()) {
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
// Computes scaling factor and decides if padding is needed.
|
||||
const int crop_width = cropped_frames.front().cols;
|
||||
const int crop_height = cropped_frames.front().rows;
|
||||
VLOG(1) << "crop_width = " << crop_width << " crop_height = " << crop_height;
|
||||
const double scaling =
|
||||
std::max(static_cast<double>(target_width_) / crop_width,
|
||||
@@ -602,7 +692,6 @@ void SceneCroppingCalculator::FilterKeyFrameInfo() {
|
||||
// Compute the "render to" location. This is where the rect taken from the
|
||||
// input video gets pasted on the output frame. For use with external
|
||||
// rendering solutions.
|
||||
const int num_frames = cropped_frames.size();
|
||||
for (int i = 0; i < num_frames; i++) {
|
||||
if (*apply_padding) {
|
||||
render_to_locations->push_back(padder_->ComputeOutputLocation());
|
||||
@@ -612,26 +701,11 @@ void SceneCroppingCalculator::FilterKeyFrameInfo() {
|
||||
}
|
||||
}
|
||||
|
||||
// Resizes cropped frames, pads frames, and output frames.
|
||||
cv::Scalar* background_color = nullptr;
|
||||
cv::Scalar interpolated_color;
|
||||
// Compute padding colors.
|
||||
for (int i = 0; i < num_frames; ++i) {
|
||||
// Set default padding color to white.
|
||||
cv::Scalar padding_color_to_add = cv::Scalar(255, 255, 255);
|
||||
const int64 time_ms = scene_frame_timestamps_[i];
|
||||
const Timestamp timestamp(time_ms);
|
||||
auto scaled_frame = absl::make_unique<ImageFrame>(
|
||||
frame_format_, scaled_width, scaled_height);
|
||||
auto destination = formats::MatView(scaled_frame.get());
|
||||
if (scaled_width == crop_width && scaled_height == crop_height) {
|
||||
cropped_frames[i].copyTo(destination);
|
||||
} else {
|
||||
// cubic is better quality for upscaling and area is good for downscaling
|
||||
const int interpolation_method =
|
||||
scaling > 1 ? cv::INTER_CUBIC : cv::INTER_AREA;
|
||||
cv::resize(cropped_frames[i], destination, destination.size(), 0, 0,
|
||||
interpolation_method);
|
||||
}
|
||||
if (*apply_padding) {
|
||||
if (has_solid_background_) {
|
||||
double lab[3];
|
||||
@@ -648,11 +722,39 @@ void SceneCroppingCalculator::FilterKeyFrameInfo() {
|
||||
k[0] = k[0] < 0.0 ? 0.0 : k[0] > 255.0 ? 255.0 : k[0];
|
||||
k[1] = k[1] < 0.0 ? 0.0 : k[1] > 255.0 ? 255.0 : k[1];
|
||||
k[2] = k[2] < 0.0 ? 0.0 : k[2] > 255.0 ? 255.0 : k[2];
|
||||
interpolated_color =
|
||||
cv::Scalar interpolated_color =
|
||||
cv::Scalar(std::round(k[0]), std::round(k[1]), std::round(k[2]));
|
||||
background_color = &interpolated_color;
|
||||
padding_color_to_add = interpolated_color;
|
||||
}
|
||||
}
|
||||
padding_colors->push_back(padding_color_to_add);
|
||||
}
|
||||
if (!cropped_frames_ptr) {
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
// Resizes cropped frames, pads frames, and output frames.
|
||||
for (int i = 0; i < num_frames; ++i) {
|
||||
const int64 time_ms = scene_frame_timestamps_[i];
|
||||
const Timestamp timestamp(time_ms);
|
||||
auto scaled_frame = absl::make_unique<ImageFrame>(
|
||||
frame_format_, scaled_width, scaled_height);
|
||||
auto destination = formats::MatView(scaled_frame.get());
|
||||
if (scaled_width == crop_width && scaled_height == crop_height) {
|
||||
cropped_frames_ptr->at(i).copyTo(destination);
|
||||
} else {
|
||||
// cubic is better quality for upscaling and area is good for
|
||||
// downscaling
|
||||
const int interpolation_method =
|
||||
scaling > 1 ? cv::INTER_CUBIC : cv::INTER_AREA;
|
||||
cv::resize(cropped_frames_ptr->at(i), destination, destination.size(), 0,
|
||||
0, interpolation_method);
|
||||
}
|
||||
if (*apply_padding) {
|
||||
cv::Scalar* background_color = nullptr;
|
||||
if (has_solid_background_) {
|
||||
background_color = &padding_colors->at(i);
|
||||
}
|
||||
auto padded_frame = absl::make_unique<ImageFrame>();
|
||||
MP_RETURN_IF_ERROR(padder_->Process(
|
||||
*scaled_frame, background_contrast_,
|
||||
@@ -670,7 +772,6 @@ void SceneCroppingCalculator::FilterKeyFrameInfo() {
|
||||
.Tag(kOutputCroppedFrames)
|
||||
.Add(scaled_frame.release(), timestamp);
|
||||
}
|
||||
padding_colors->push_back(padding_color_to_add);
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
@@ -678,6 +779,7 @@ void SceneCroppingCalculator::FilterKeyFrameInfo() {
|
||||
mediapipe::Status SceneCroppingCalculator::OutputVizFrames(
|
||||
const std::vector<KeyFrameCropResult>& key_frame_crop_results,
|
||||
const std::vector<FocusPointFrame>& focus_point_frames,
|
||||
const std::vector<cv::Rect>& crop_from_locations,
|
||||
const int crop_window_width, const int crop_window_height,
|
||||
CalculatorContext* cc) const {
|
||||
if (cc->Outputs().HasTag(kOutputKeyFrameCropViz)) {
|
||||
@@ -703,6 +805,17 @@ mediapipe::Status SceneCroppingCalculator::OutputVizFrames(
|
||||
.Add(viz_frames[i].release(), Timestamp(scene_frame_timestamps_[i]));
|
||||
}
|
||||
}
|
||||
if (cc->Outputs().HasTag(kOutputFramingAndDetections)) {
|
||||
std::vector<std::unique_ptr<ImageFrame>> viz_frames;
|
||||
MP_RETURN_IF_ERROR(DrawDetectionAndFramingWindow(
|
||||
raw_scene_frames_or_empty_, crop_from_locations, frame_format_,
|
||||
options_.viz_overlay_opacity(), &viz_frames));
|
||||
for (int i = 0; i < raw_scene_frames_or_empty_.size(); ++i) {
|
||||
cc->Outputs()
|
||||
.Tag(kOutputFramingAndDetections)
|
||||
.Add(viz_frames[i].release(), Timestamp(scene_frame_timestamps_[i]));
|
||||
}
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
|
||||
@@ -145,9 +145,13 @@ class SceneCroppingCalculator : public CalculatorBase {
|
||||
// Removes any static borders from the scene frames before cropping. The
|
||||
// arguments |top_border_size| and |bottom_border_size| report the size of the
|
||||
// removed borders.
|
||||
::mediapipe::Status RemoveStaticBorders(int* top_border_size,
|
||||
::mediapipe::Status RemoveStaticBorders(CalculatorContext* cc,
|
||||
int* top_border_size,
|
||||
int* bottom_border_size);
|
||||
|
||||
// Sets up autoflip after first frame is received and input size is known.
|
||||
::mediapipe::Status InitializeSceneCroppingCalculator(
|
||||
::mediapipe::CalculatorContext* cc);
|
||||
// Initializes a FrameCropRegionComputer given input and target frame sizes.
|
||||
::mediapipe::Status InitializeFrameCropRegionComputer();
|
||||
|
||||
@@ -164,20 +168,26 @@ class SceneCroppingCalculator : public CalculatorBase {
|
||||
::mediapipe::Status ProcessScene(const bool is_end_of_scene,
|
||||
CalculatorContext* cc);
|
||||
|
||||
// Formats and outputs the cropped frames. Scales them to be at least as big
|
||||
// as the target size. If the aspect ratio is different, applies padding. Uses
|
||||
// solid background from static features if possible, otherwise uses blurred
|
||||
// background. Sets apply_padding to true if the scene is padded.
|
||||
// Formats and outputs the cropped frames passed in through
|
||||
// |cropped_frames_ptr|. Scales them to be at least as big as the target
|
||||
// size. If the aspect ratio is different, applies padding. Uses solid
|
||||
// background from static features if possible, otherwise uses blurred
|
||||
// background. Sets |apply_padding| to true if the scene is padded. Set
|
||||
// |cropped_frames_ptr| to nullptr, to bypass the actual output of the
|
||||
// cropped frames. This is useful when the calculator is only used for
|
||||
// computing the cropping metadata rather than doing the actual cropping
|
||||
// operation.
|
||||
::mediapipe::Status FormatAndOutputCroppedFrames(
|
||||
const std::vector<cv::Mat>& cropped_frames,
|
||||
const int crop_width, const int crop_height, const int num_frames,
|
||||
std::vector<cv::Rect>* render_to_locations, bool* apply_padding,
|
||||
std::vector<cv::Scalar>* padding_colors, float* vertical_fill_percent,
|
||||
CalculatorContext* cc);
|
||||
const std::vector<cv::Mat>* cropped_frames_ptr, CalculatorContext* cc);
|
||||
|
||||
// Draws and outputs visualization frames if those streams are present.
|
||||
::mediapipe::Status OutputVizFrames(
|
||||
const std::vector<KeyFrameCropResult>& key_frame_crop_results,
|
||||
const std::vector<FocusPointFrame>& focus_point_frames,
|
||||
const std::vector<cv::Rect>& crop_from_locations,
|
||||
const int crop_window_width, const int crop_window_height,
|
||||
CalculatorContext* cc) const;
|
||||
|
||||
@@ -201,16 +211,21 @@ class SceneCroppingCalculator : public CalculatorBase {
|
||||
// Calculator options.
|
||||
SceneCroppingCalculatorOptions options_;
|
||||
|
||||
// Buffered KeyFrameInfos for the current scene (size = number of key frames).
|
||||
// Buffered KeyFrameInfos for the current scene (size = number of key
|
||||
// frames).
|
||||
std::vector<KeyFrameInfo> key_frame_infos_;
|
||||
|
||||
// Buffered frames, timestamps, and indicators for key frames in the current
|
||||
// scene (size = number of input video frames).
|
||||
// Note: scene_frames_or_empty_ may be empty if the actual cropping operation
|
||||
// of frames is turned off, e.g. when |should_perform_frame_cropping_| is
|
||||
// false, so rely on scene_frame_timestamps_.size() to query the number of
|
||||
// accumulated timestamps rather than scene_frames_or_empty_.size().
|
||||
// Note: scene_frames_or_empty_ may be empty if the actual cropping
|
||||
// operation of frames is turned off, e.g. when
|
||||
// |should_perform_frame_cropping_| is false, so rely on
|
||||
// scene_frame_timestamps_.size() to query the number of accumulated
|
||||
// timestamps rather than scene_frames_or_empty_.size().
|
||||
// TODO: all of the following vectors are expected to be the same
|
||||
// size. Add to struct and store together in one vector.
|
||||
std::vector<cv::Mat> scene_frames_or_empty_;
|
||||
std::vector<cv::Mat> raw_scene_frames_or_empty_;
|
||||
std::vector<int64> scene_frame_timestamps_;
|
||||
std::vector<bool> is_key_frames_;
|
||||
|
||||
@@ -221,6 +236,9 @@ class SceneCroppingCalculator : public CalculatorBase {
|
||||
// Stored FocusPointFrames from prior scene when there was no actual scene
|
||||
// change (due to forced flush when buffer is full).
|
||||
std::vector<FocusPointFrame> prior_focus_point_frames_;
|
||||
// Indicates if this scene is a continuation of the last scene (due to
|
||||
// forced flush when buffer is full).
|
||||
bool continue_last_scene_ = false;
|
||||
|
||||
// KeyFrameCropOptions used by the FrameCropRegionComputer.
|
||||
KeyFrameCropOptions key_frame_crop_options_;
|
||||
@@ -242,8 +260,8 @@ class SceneCroppingCalculator : public CalculatorBase {
|
||||
std::vector<StaticFeatures> static_features_;
|
||||
std::vector<int64> static_features_timestamps_;
|
||||
bool has_solid_background_ = false;
|
||||
// CIELAB yields more natural color transitions than RGB and HSV: RGB tends to
|
||||
// produce darker in-between colors and HSV can introduce new hues. See
|
||||
// CIELAB yields more natural color transitions than RGB and HSV: RGB tends
|
||||
// to produce darker in-between colors and HSV can introduce new hues. See
|
||||
// https://howaboutanorange.com/blog/2011/08/10/color_interpolation/ for
|
||||
// visual comparisons of color transition in different spaces.
|
||||
PiecewiseLinearFunction background_color_l_function_; // CIELAB - l
|
||||
@@ -264,11 +282,11 @@ class SceneCroppingCalculator : public CalculatorBase {
|
||||
std::unique_ptr<std::vector<ExternalRenderFrame>> external_render_list_;
|
||||
|
||||
// Determines whether to perform real cropping on input frames. This flag is
|
||||
// useful when the user only needs to compute cropping windows, in which case
|
||||
// setting this flag to false can avoid buffering as well as cropping frames.
|
||||
// This can significantly reduce memory usage and speed up processing. Some
|
||||
// debugging visualization inevitably will be disabled because of this flag
|
||||
// too.
|
||||
// useful when the user only needs to compute cropping windows, in which
|
||||
// case setting this flag to false can avoid buffering as well as cropping
|
||||
// frames. This can significantly reduce memory usage and speed up
|
||||
// processing. Some debugging visualization inevitably will be disabled
|
||||
// because of this flag too.
|
||||
bool should_perform_frame_cropping_ = false;
|
||||
};
|
||||
} // namespace autoflip
|
||||
|
||||
@@ -44,6 +44,8 @@ message SceneCroppingCalculatorOptions {
|
||||
// Used on conjuntion with external_aspect_ratio, create the largest sized
|
||||
// output without upscaling the video.
|
||||
MAXIMIZE_TARGET_DIMENSION = 4;
|
||||
// Uses original dimensions to calculate aspect ratio.
|
||||
KEEP_ORIGINAL_DIMENSION = 5;
|
||||
}
|
||||
optional TargetSizeType target_size_type = 3 [default = USE_TARGET_DIMENSION];
|
||||
|
||||
@@ -53,7 +55,10 @@ message SceneCroppingCalculatorOptions {
|
||||
|
||||
// Number of frames from prior buffer to be used to smooth out camera
|
||||
// trajectory when it was a forced flush.
|
||||
optional int32 prior_frame_buffer_size = 5 [default = 30];
|
||||
optional int32 prior_frame_buffer_size = 5 [default = 30, deprecated = true];
|
||||
// Set camera motion type along with parameters. Must select between the two
|
||||
// provided options.
|
||||
optional CameraMotionOptions camera_motion_options = 14;
|
||||
|
||||
// Options for computing key frame crop regions using the
|
||||
// FrameCropRegionComputer.
|
||||
|
||||
+260
-23
@@ -94,6 +94,7 @@ constexpr char kDebugConfig[] = R"(
|
||||
output_stream: "CROPPED_FRAMES:cropped_frames"
|
||||
output_stream: "KEY_FRAME_CROP_REGION_VIZ_FRAMES:key_frame_crop_viz_frames"
|
||||
output_stream: "SALIENT_POINT_FRAME_VIZ_FRAMES:salient_point_viz_frames"
|
||||
output_stream: "FRAMING_DETECTIONS_VIZ_FRAMES:framing_viz_frames"
|
||||
output_stream: "CROPPING_SUMMARY:cropping_summaries"
|
||||
output_stream: "EXTERNAL_RENDERING_PER_FRAME:external_rendering_per_frame"
|
||||
output_stream: "EXTERNAL_RENDERING_FULL_VID:external_rendering_full_vid"
|
||||
@@ -104,6 +105,39 @@ constexpr char kDebugConfig[] = R"(
|
||||
}
|
||||
})";
|
||||
|
||||
constexpr char kExternalRenderConfig[] = R"(
|
||||
calculator: "SceneCroppingCalculator"
|
||||
input_stream: "VIDEO_FRAMES:camera_frames_org"
|
||||
input_stream: "KEY_FRAMES:down_sampled_frames"
|
||||
input_stream: "DETECTION_FEATURES:salient_regions"
|
||||
input_stream: "STATIC_FEATURES:border_features"
|
||||
input_stream: "SHOT_BOUNDARIES:shot_boundary_frames"
|
||||
output_stream: "EXTERNAL_RENDERING_PER_FRAME:external_rendering_per_frame"
|
||||
output_stream: "EXTERNAL_RENDERING_FULL_VID:external_rendering_full_vid"
|
||||
options: {
|
||||
[mediapipe.autoflip.SceneCroppingCalculatorOptions.ext]: {
|
||||
target_width: $0
|
||||
target_height: $1
|
||||
}
|
||||
})";
|
||||
|
||||
constexpr char kExternalRenderConfigNoVideo[] = R"(
|
||||
calculator: "SceneCroppingCalculator"
|
||||
input_stream: "VIDEO_SIZE:camera_size"
|
||||
input_stream: "DETECTION_FEATURES:salient_regions"
|
||||
input_stream: "STATIC_FEATURES:border_features"
|
||||
input_stream: "SHOT_BOUNDARIES:shot_boundary_frames"
|
||||
output_stream: "EXTERNAL_RENDERING_PER_FRAME:external_rendering_per_frame"
|
||||
output_stream: "EXTERNAL_RENDERING_FULL_VID:external_rendering_full_vid"
|
||||
options: {
|
||||
[mediapipe.autoflip.SceneCroppingCalculatorOptions.ext]: {
|
||||
target_width: $0
|
||||
target_height: $1
|
||||
video_features_width: $2
|
||||
video_features_height: $3
|
||||
}
|
||||
})";
|
||||
|
||||
constexpr int kInputFrameWidth = 1280;
|
||||
constexpr int kInputFrameHeight = 720;
|
||||
|
||||
@@ -172,6 +206,22 @@ std::unique_ptr<DetectionSet> MakeDetections(const int num_detections,
|
||||
return detections;
|
||||
}
|
||||
|
||||
// Makes a detection set given number of detections. Each detection has randomly
|
||||
// generated regions within given width and height with random score in [0, 1],
|
||||
// and is randomly set to be required or non-required.
|
||||
std::unique_ptr<DetectionSet> MakeCenterDetection(const int width,
|
||||
const int height) {
|
||||
auto detections = absl::make_unique<DetectionSet>();
|
||||
auto* region = detections->add_detections();
|
||||
auto* location = region->mutable_location();
|
||||
location->set_x(width / 2 - 5);
|
||||
location->set_width(width / 2 + 10);
|
||||
location->set_y(height / 2 - 5);
|
||||
location->set_height(height);
|
||||
region->set_score(1);
|
||||
return detections;
|
||||
}
|
||||
|
||||
// Makes an image frame of solid color given color, width, and height.
|
||||
std::unique_ptr<ImageFrame> MakeImageFrameFromColor(const cv::Scalar& color,
|
||||
const int width,
|
||||
@@ -188,7 +238,7 @@ std::unique_ptr<ImageFrame> MakeImageFrameFromColor(const cv::Scalar& color,
|
||||
// and kMaxNumDetections. Optionally add a key image frame of random solid color
|
||||
// and given size.
|
||||
void AddKeyFrameFeatures(const int64 time_ms, const int key_frame_width,
|
||||
const int key_frame_height,
|
||||
const int key_frame_height, bool randomize,
|
||||
CalculatorRunner::StreamContentsSet* inputs) {
|
||||
Timestamp timestamp(time_ms);
|
||||
if (inputs->HasTag("KEY_FRAMES")) {
|
||||
@@ -197,13 +247,18 @@ void AddKeyFrameFeatures(const int64 time_ms, const int key_frame_width,
|
||||
inputs->Tag("KEY_FRAMES")
|
||||
.packets.push_back(Adopt(key_frame.release()).At(timestamp));
|
||||
}
|
||||
|
||||
const int num_detections = std::uniform_int_distribution<int>(
|
||||
kMinNumDetections, kMaxNumDetections)(GetGen());
|
||||
auto detections =
|
||||
MakeDetections(num_detections, key_frame_width, key_frame_height);
|
||||
inputs->Tag("DETECTION_FEATURES")
|
||||
.packets.push_back(Adopt(detections.release()).At(timestamp));
|
||||
if (randomize) {
|
||||
const int num_detections = std::uniform_int_distribution<int>(
|
||||
kMinNumDetections, kMaxNumDetections)(GetGen());
|
||||
auto detections =
|
||||
MakeDetections(num_detections, key_frame_width, key_frame_height);
|
||||
inputs->Tag("DETECTION_FEATURES")
|
||||
.packets.push_back(Adopt(detections.release()).At(timestamp));
|
||||
} else {
|
||||
auto detections = MakeCenterDetection(key_frame_width, key_frame_height);
|
||||
inputs->Tag("DETECTION_FEATURES")
|
||||
.packets.push_back(Adopt(detections.release()).At(timestamp));
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a scene given number of frames to the input stream. Spaces frame at the
|
||||
@@ -212,19 +267,31 @@ void AddKeyFrameFeatures(const int64 time_ms, const int key_frame_width,
|
||||
void AddScene(const int start_frame_index, const int num_scene_frames,
|
||||
const int frame_width, const int frame_height,
|
||||
const int key_frame_width, const int key_frame_height,
|
||||
const int DownSampleRate,
|
||||
CalculatorRunner::StreamContentsSet* inputs) {
|
||||
int64 time_ms = start_frame_index * kTimestampDiff;
|
||||
for (int i = 0; i < num_scene_frames; ++i) {
|
||||
Timestamp timestamp(time_ms);
|
||||
auto frame =
|
||||
MakeImageFrameFromColor(GetRandomColor(), frame_width, frame_height);
|
||||
inputs->Tag("VIDEO_FRAMES")
|
||||
.packets.push_back(Adopt(frame.release()).At(timestamp));
|
||||
if (inputs->HasTag("VIDEO_FRAMES")) {
|
||||
auto frame =
|
||||
MakeImageFrameFromColor(GetRandomColor(), frame_width, frame_height);
|
||||
inputs->Tag("VIDEO_FRAMES")
|
||||
.packets.push_back(Adopt(frame.release()).At(timestamp));
|
||||
} else {
|
||||
auto input_size =
|
||||
::absl::make_unique<std::pair<int, int>>(frame_width, frame_height);
|
||||
inputs->Tag("VIDEO_SIZE")
|
||||
.packets.push_back(Adopt(input_size.release()).At(timestamp));
|
||||
}
|
||||
auto static_features = absl::make_unique<StaticFeatures>();
|
||||
inputs->Tag("STATIC_FEATURES")
|
||||
.packets.push_back(Adopt(static_features.release()).At(timestamp));
|
||||
if (i % kDownSampleRate == 0) { // is a key frame
|
||||
AddKeyFrameFeatures(time_ms, key_frame_width, key_frame_height, inputs);
|
||||
if (DownSampleRate == 1) {
|
||||
AddKeyFrameFeatures(time_ms, key_frame_width, key_frame_height, false,
|
||||
inputs);
|
||||
} else if (i % DownSampleRate == 0) { // is a key frame
|
||||
AddKeyFrameFeatures(time_ms, key_frame_width, key_frame_height, true,
|
||||
inputs);
|
||||
}
|
||||
if (i == num_scene_frames - 1) { // adds shot boundary
|
||||
inputs->Tag("SHOT_BOUNDARIES")
|
||||
@@ -294,7 +361,7 @@ TEST(SceneCroppingCalculatorTest, HandlesNoKeyFrames) {
|
||||
absl::Substitute(kNoKeyFrameConfig, kTargetWidth, kTargetHeight));
|
||||
auto runner = absl::make_unique<CalculatorRunner>(config);
|
||||
AddScene(0, kSceneSize, kInputFrameWidth, kInputFrameHeight, kKeyFrameWidth,
|
||||
kKeyFrameHeight, runner->MutableInputs());
|
||||
kKeyFrameHeight, kDownSampleRate, runner->MutableInputs());
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
CheckCroppedFrames(*runner, kSceneSize, kTargetWidth, kTargetHeight);
|
||||
}
|
||||
@@ -308,7 +375,8 @@ TEST(SceneCroppingCalculatorTest, HandlesLongScene) {
|
||||
kPriorFrameBufferSize));
|
||||
auto runner = absl::make_unique<CalculatorRunner>(config);
|
||||
AddScene(0, 2 * kMaxSceneSize, kInputFrameWidth, kInputFrameHeight,
|
||||
kKeyFrameWidth, kKeyFrameHeight, runner->MutableInputs());
|
||||
kKeyFrameWidth, kKeyFrameHeight, kDownSampleRate,
|
||||
runner->MutableInputs());
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
CheckCroppedFrames(*runner, 2 * kMaxSceneSize, kTargetWidth, kTargetHeight);
|
||||
}
|
||||
@@ -321,7 +389,7 @@ TEST(SceneCroppingCalculatorTest, OutputsDebugStreams) {
|
||||
auto runner = absl::make_unique<CalculatorRunner>(config);
|
||||
const int num_frames = kSceneSize;
|
||||
AddScene(0, num_frames, kInputFrameWidth, kInputFrameHeight, kKeyFrameWidth,
|
||||
kKeyFrameHeight, runner->MutableInputs());
|
||||
kKeyFrameHeight, kDownSampleRate, runner->MutableInputs());
|
||||
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
const auto& outputs = runner->Outputs();
|
||||
@@ -330,6 +398,7 @@ TEST(SceneCroppingCalculatorTest, OutputsDebugStreams) {
|
||||
EXPECT_TRUE(outputs.HasTag("CROPPING_SUMMARY"));
|
||||
EXPECT_TRUE(outputs.HasTag("EXTERNAL_RENDERING_PER_FRAME"));
|
||||
EXPECT_TRUE(outputs.HasTag("EXTERNAL_RENDERING_FULL_VID"));
|
||||
EXPECT_TRUE(outputs.HasTag("FRAMING_DETECTIONS_VIZ_FRAMES"));
|
||||
const auto& crop_region_viz_frames_outputs =
|
||||
outputs.Tag("KEY_FRAME_CROP_REGION_VIZ_FRAMES").packets;
|
||||
const auto& salient_point_viz_frames_outputs =
|
||||
@@ -339,8 +408,11 @@ TEST(SceneCroppingCalculatorTest, OutputsDebugStreams) {
|
||||
outputs.Tag("EXTERNAL_RENDERING_PER_FRAME").packets;
|
||||
const auto& ext_render_full_vid =
|
||||
outputs.Tag("EXTERNAL_RENDERING_FULL_VID").packets;
|
||||
const auto& framing_viz_frames_output =
|
||||
outputs.Tag("FRAMING_DETECTIONS_VIZ_FRAMES").packets;
|
||||
EXPECT_EQ(crop_region_viz_frames_outputs.size(), num_frames);
|
||||
EXPECT_EQ(salient_point_viz_frames_outputs.size(), num_frames);
|
||||
EXPECT_EQ(framing_viz_frames_output.size(), num_frames);
|
||||
EXPECT_EQ(summary_output.size(), 1);
|
||||
EXPECT_EQ(ext_render_per_frame.size(), num_frames);
|
||||
EXPECT_EQ(ext_render_full_vid.size(), 1);
|
||||
@@ -387,7 +459,8 @@ TEST(SceneCroppingCalculatorTest, HandlesLandscapeTarget) {
|
||||
auto runner = absl::make_unique<CalculatorRunner>(config);
|
||||
for (int i = 0; i < kNumScenes; ++i) {
|
||||
AddScene(i * kSceneSize, kSceneSize, input_width, input_height,
|
||||
kKeyFrameWidth, kKeyFrameHeight, runner->MutableInputs());
|
||||
kKeyFrameWidth, kKeyFrameHeight, kDownSampleRate,
|
||||
runner->MutableInputs());
|
||||
}
|
||||
const int num_frames = kSceneSize * kNumScenes;
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
@@ -404,13 +477,34 @@ TEST(SceneCroppingCalculatorTest, CropsToTargetSize) {
|
||||
auto runner = absl::make_unique<CalculatorRunner>(config);
|
||||
for (int i = 0; i < kNumScenes; ++i) {
|
||||
AddScene(i * kSceneSize, kSceneSize, kInputFrameWidth, kInputFrameHeight,
|
||||
kKeyFrameWidth, kKeyFrameHeight, runner->MutableInputs());
|
||||
kKeyFrameWidth, kKeyFrameHeight, kDownSampleRate,
|
||||
runner->MutableInputs());
|
||||
}
|
||||
const int num_frames = kSceneSize * kNumScenes;
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
CheckCroppedFrames(*runner, num_frames, kTargetWidth, kTargetHeight);
|
||||
}
|
||||
|
||||
// Checks that the calculator crops scene frames to input size when the target
|
||||
// size type is KEEP_ORIGINAL_DIMENSION.
|
||||
TEST(SceneCroppingCalculatorTest, CropsToOriginalDimension) {
|
||||
// target_width and target_height are ignored
|
||||
const CalculatorGraphConfig::Node config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(absl::Substitute(
|
||||
kConfig, /*target_width*/ 2, /*target_height*/ 2,
|
||||
SceneCroppingCalculatorOptions::KEEP_ORIGINAL_DIMENSION,
|
||||
kMaxSceneSize, kPriorFrameBufferSize));
|
||||
auto runner = absl::make_unique<CalculatorRunner>(config);
|
||||
for (int i = 0; i < kNumScenes; ++i) {
|
||||
AddScene(i * kSceneSize, kSceneSize, kInputFrameWidth, kInputFrameHeight,
|
||||
kKeyFrameWidth, kKeyFrameHeight, kDownSampleRate,
|
||||
runner->MutableInputs());
|
||||
}
|
||||
const int num_frames = kSceneSize * kNumScenes;
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
CheckCroppedFrames(*runner, num_frames, kInputFrameWidth, kInputFrameHeight);
|
||||
}
|
||||
|
||||
// Checks that the calculator keeps original height if the target size type is
|
||||
// set to KEEP_ORIGINAL_HEIGHT.
|
||||
TEST(SceneCroppingCalculatorTest, KeepsOriginalHeight) {
|
||||
@@ -427,7 +521,8 @@ TEST(SceneCroppingCalculatorTest, KeepsOriginalHeight) {
|
||||
kPriorFrameBufferSize));
|
||||
auto runner = absl::make_unique<CalculatorRunner>(config);
|
||||
AddScene(0, kMaxSceneSize, kInputFrameWidth, kInputFrameHeight,
|
||||
kKeyFrameWidth, kKeyFrameHeight, runner->MutableInputs());
|
||||
kKeyFrameWidth, kKeyFrameHeight, kDownSampleRate,
|
||||
runner->MutableInputs());
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
CheckCroppedFrames(*runner, kMaxSceneSize, target_width, target_height);
|
||||
}
|
||||
@@ -448,7 +543,8 @@ TEST(SceneCroppingCalculatorTest, KeepsOriginalWidth) {
|
||||
kPriorFrameBufferSize));
|
||||
auto runner = absl::make_unique<CalculatorRunner>(config);
|
||||
AddScene(0, kMaxSceneSize, kInputFrameWidth, kInputFrameHeight,
|
||||
kKeyFrameWidth, kKeyFrameHeight, runner->MutableInputs());
|
||||
kKeyFrameWidth, kKeyFrameHeight, kDownSampleRate,
|
||||
runner->MutableInputs());
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
CheckCroppedFrames(*runner, kMaxSceneSize, target_width, target_height);
|
||||
}
|
||||
@@ -461,7 +557,8 @@ TEST(SceneCroppingCalculatorTest, RejectsOddTargetSize) {
|
||||
kMaxSceneSize, kPriorFrameBufferSize));
|
||||
auto runner = absl::make_unique<CalculatorRunner>(config);
|
||||
AddScene(0, kMaxSceneSize, kInputFrameWidth, kInputFrameHeight,
|
||||
kKeyFrameWidth, kKeyFrameHeight, runner->MutableInputs());
|
||||
kKeyFrameWidth, kKeyFrameHeight, kDownSampleRate,
|
||||
runner->MutableInputs());
|
||||
const auto status = runner->Run();
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.ToString(), HasSubstr("Target width cannot be odd"));
|
||||
@@ -498,7 +595,7 @@ TEST(SceneCroppingCalculatorTest, ProducesEvenFrameSize) {
|
||||
kMaxSceneSize, kPriorFrameBufferSize));
|
||||
auto runner = absl::make_unique<CalculatorRunner>(config);
|
||||
AddScene(0, 1, frame_width, frame_height, kKeyFrameWidth,
|
||||
kKeyFrameHeight, runner->MutableInputs());
|
||||
kKeyFrameHeight, kDownSampleRate, runner->MutableInputs());
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
const auto& output_frame = runner->Outputs()
|
||||
.Tag("CROPPED_FRAMES")
|
||||
@@ -665,6 +762,146 @@ TEST(SceneCroppingCalculatorTest, RemovesStaticBorders) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Checks external render message with default poly path solver.
|
||||
TEST(SceneCroppingCalculatorTest, OutputsCropMessagePolyPath) {
|
||||
const CalculatorGraphConfig::Node config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(
|
||||
absl::Substitute(kExternalRenderConfig, kTargetWidth, kTargetHeight));
|
||||
auto runner = absl::make_unique<CalculatorRunner>(config);
|
||||
const int num_frames = kSceneSize;
|
||||
AddScene(0, num_frames, kInputFrameWidth, kInputFrameHeight, kKeyFrameWidth,
|
||||
kKeyFrameHeight, 1, runner->MutableInputs());
|
||||
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
const auto& outputs = runner->Outputs();
|
||||
const auto& ext_render_per_frame =
|
||||
outputs.Tag("EXTERNAL_RENDERING_PER_FRAME").packets;
|
||||
EXPECT_EQ(ext_render_per_frame.size(), num_frames);
|
||||
|
||||
for (int i = 0; i < num_frames - 1; ++i) {
|
||||
const auto& ext_render_message =
|
||||
ext_render_per_frame[i].Get<ExternalRenderFrame>();
|
||||
EXPECT_EQ(ext_render_message.timestamp_us(), i * 20000);
|
||||
EXPECT_EQ(ext_render_message.crop_from_location().x(), 725);
|
||||
EXPECT_EQ(ext_render_message.crop_from_location().y(), 0);
|
||||
EXPECT_EQ(ext_render_message.crop_from_location().width(), 461);
|
||||
EXPECT_EQ(ext_render_message.crop_from_location().height(), 720);
|
||||
EXPECT_EQ(ext_render_message.render_to_location().x(), 0);
|
||||
EXPECT_EQ(ext_render_message.render_to_location().y(), 0);
|
||||
EXPECT_EQ(ext_render_message.render_to_location().width(), 720);
|
||||
EXPECT_EQ(ext_render_message.render_to_location().height(), 1124);
|
||||
}
|
||||
}
|
||||
|
||||
// Checks external render message with kinematic path solver.
|
||||
TEST(SceneCroppingCalculatorTest, OutputsCropMessageKinematicPath) {
|
||||
CalculatorGraphConfig::Node config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(
|
||||
absl::Substitute(kDebugConfig, kTargetWidth, kTargetHeight));
|
||||
auto* options = config.mutable_options()->MutableExtension(
|
||||
SceneCroppingCalculatorOptions::ext);
|
||||
auto* kinematic_options =
|
||||
options->mutable_camera_motion_options()->mutable_kinematic_options();
|
||||
kinematic_options->set_max_velocity(200);
|
||||
|
||||
auto runner = absl::make_unique<CalculatorRunner>(config);
|
||||
const int num_frames = kSceneSize;
|
||||
AddScene(0, num_frames, kInputFrameWidth, kInputFrameHeight, kKeyFrameWidth,
|
||||
kKeyFrameHeight, 1, runner->MutableInputs());
|
||||
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
const auto& outputs = runner->Outputs();
|
||||
const auto& ext_render_per_frame =
|
||||
outputs.Tag("EXTERNAL_RENDERING_PER_FRAME").packets;
|
||||
EXPECT_EQ(ext_render_per_frame.size(), num_frames);
|
||||
|
||||
for (int i = 0; i < num_frames - 1; ++i) {
|
||||
const auto& ext_render_message =
|
||||
ext_render_per_frame[i].Get<ExternalRenderFrame>();
|
||||
EXPECT_EQ(ext_render_message.timestamp_us(), i * 20000);
|
||||
EXPECT_EQ(ext_render_message.crop_from_location().x(), 725);
|
||||
EXPECT_EQ(ext_render_message.crop_from_location().y(), 0);
|
||||
EXPECT_EQ(ext_render_message.crop_from_location().width(), 461);
|
||||
EXPECT_EQ(ext_render_message.crop_from_location().height(), 720);
|
||||
EXPECT_EQ(ext_render_message.render_to_location().x(), 0);
|
||||
EXPECT_EQ(ext_render_message.render_to_location().y(), 0);
|
||||
EXPECT_EQ(ext_render_message.render_to_location().width(), 720);
|
||||
EXPECT_EQ(ext_render_message.render_to_location().height(), 1124);
|
||||
}
|
||||
}
|
||||
|
||||
// Checks external render message with default poly path solver without video
|
||||
// input.
|
||||
TEST(SceneCroppingCalculatorTest, OutputsCropMessagePolyPathNoVideo) {
|
||||
const CalculatorGraphConfig::Node config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(
|
||||
absl::Substitute(kExternalRenderConfigNoVideo, kTargetWidth,
|
||||
kTargetHeight, kKeyFrameWidth, kKeyFrameHeight));
|
||||
auto runner = absl::make_unique<CalculatorRunner>(config);
|
||||
const int num_frames = kSceneSize;
|
||||
AddScene(0, num_frames, kInputFrameWidth, kInputFrameHeight, kKeyFrameWidth,
|
||||
kKeyFrameHeight, 1, runner->MutableInputs());
|
||||
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
const auto& outputs = runner->Outputs();
|
||||
const auto& ext_render_per_frame =
|
||||
outputs.Tag("EXTERNAL_RENDERING_PER_FRAME").packets;
|
||||
EXPECT_EQ(ext_render_per_frame.size(), num_frames);
|
||||
|
||||
for (int i = 0; i < num_frames - 1; ++i) {
|
||||
const auto& ext_render_message =
|
||||
ext_render_per_frame[i].Get<ExternalRenderFrame>();
|
||||
EXPECT_EQ(ext_render_message.timestamp_us(), i * 20000);
|
||||
EXPECT_EQ(ext_render_message.crop_from_location().x(), 725);
|
||||
EXPECT_EQ(ext_render_message.crop_from_location().y(), 0);
|
||||
EXPECT_EQ(ext_render_message.crop_from_location().width(), 461);
|
||||
EXPECT_EQ(ext_render_message.crop_from_location().height(), 720);
|
||||
EXPECT_EQ(ext_render_message.render_to_location().x(), 0);
|
||||
EXPECT_EQ(ext_render_message.render_to_location().y(), 0);
|
||||
EXPECT_EQ(ext_render_message.render_to_location().width(), 720);
|
||||
EXPECT_EQ(ext_render_message.render_to_location().height(), 1124);
|
||||
}
|
||||
}
|
||||
|
||||
// Checks external render message with kinematic path solver without video
|
||||
// input.
|
||||
TEST(SceneCroppingCalculatorTest, OutputsCropMessageKinematicPathNoVideo) {
|
||||
CalculatorGraphConfig::Node config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(
|
||||
absl::Substitute(kExternalRenderConfigNoVideo, kTargetWidth,
|
||||
kTargetHeight, kKeyFrameWidth, kKeyFrameHeight));
|
||||
auto* options = config.mutable_options()->MutableExtension(
|
||||
SceneCroppingCalculatorOptions::ext);
|
||||
auto* kinematic_options =
|
||||
options->mutable_camera_motion_options()->mutable_kinematic_options();
|
||||
kinematic_options->set_max_velocity(2.0);
|
||||
|
||||
auto runner = absl::make_unique<CalculatorRunner>(config);
|
||||
const int num_frames = kSceneSize;
|
||||
AddScene(0, num_frames, kInputFrameWidth, kInputFrameHeight, kKeyFrameWidth,
|
||||
kKeyFrameHeight, 1, runner->MutableInputs());
|
||||
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
const auto& outputs = runner->Outputs();
|
||||
const auto& ext_render_per_frame =
|
||||
outputs.Tag("EXTERNAL_RENDERING_PER_FRAME").packets;
|
||||
EXPECT_EQ(ext_render_per_frame.size(), num_frames);
|
||||
|
||||
for (int i = 0; i < num_frames - 1; ++i) {
|
||||
const auto& ext_render_message =
|
||||
ext_render_per_frame[i].Get<ExternalRenderFrame>();
|
||||
EXPECT_EQ(ext_render_message.timestamp_us(), i * 20000);
|
||||
EXPECT_EQ(ext_render_message.crop_from_location().x(), 725);
|
||||
EXPECT_EQ(ext_render_message.crop_from_location().y(), 0);
|
||||
EXPECT_EQ(ext_render_message.crop_from_location().width(), 461);
|
||||
EXPECT_EQ(ext_render_message.crop_from_location().height(), 720);
|
||||
EXPECT_EQ(ext_render_message.render_to_location().x(), 0);
|
||||
EXPECT_EQ(ext_render_message.render_to_location().y(), 0);
|
||||
EXPECT_EQ(ext_render_message.render_to_location().width(), 720);
|
||||
EXPECT_EQ(ext_render_message.render_to_location().height(), 1124);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
} // namespace autoflip
|
||||
} // namespace mediapipe
|
||||
|
||||
@@ -30,6 +30,10 @@ using mediapipe::autoflip::DetectionSet;
|
||||
using mediapipe::autoflip::SalientRegion;
|
||||
using mediapipe::autoflip::SignalType;
|
||||
|
||||
constexpr char kIsShotBoundaryTag[] = "IS_SHOT_BOUNDARY";
|
||||
constexpr char kSignalInputsTag[] = "SIGNAL";
|
||||
constexpr char kOutputTag[] = "OUTPUT";
|
||||
|
||||
namespace mediapipe {
|
||||
namespace autoflip {
|
||||
|
||||
@@ -43,16 +47,16 @@ struct Frame {
|
||||
mediapipe::Timestamp time;
|
||||
};
|
||||
|
||||
// This calculator takes one scene change signal and an arbitrary number of
|
||||
// detection signals and outputs a single list of detections. The scores for
|
||||
// the detections can be re-normalized using the options proto. Additionally,
|
||||
// if a detection has a consistent tracking id during a scene the score for that
|
||||
// detection is averaged over the whole scene.
|
||||
// This calculator takes one scene change signal (optional, see below) and an
|
||||
// arbitrary number of detection signals and outputs a single list of
|
||||
// detections. The scores for the detections can be re-normalized using the
|
||||
// options proto. Additionally, if a detection has a consistent tracking id
|
||||
// during a scene the score for that detection is averaged over the whole scene.
|
||||
//
|
||||
// Example:
|
||||
// Example (ordered interface):
|
||||
// node {
|
||||
// calculator: "SignalFusingCalculator"
|
||||
// input_stream: "scene_change"
|
||||
// input_stream: "scene_change" (required for ordered interface)
|
||||
// input_stream: "detection_faces"
|
||||
// input_stream: "detection_custom_text"
|
||||
// output_stream: "salient_region"
|
||||
@@ -71,9 +75,33 @@ struct Frame {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Example (tag interface):
|
||||
// node {
|
||||
// calculator: "SignalFusingCalculator"
|
||||
// input_stream: "IS_SHOT_BOUNDARY:scene_change" (optional)
|
||||
// input_stream: "SIGNAL:0:detection_faces"
|
||||
// input_stream: "SIGNAL:1:detection_custom_text"
|
||||
// output_stream: "OUTPUT:salient_region"
|
||||
// options:{
|
||||
// [mediapipe.autoflip.SignalFusingCalculatorOptions.ext]:{
|
||||
// signal_settings{
|
||||
// type: {standard: FACE}
|
||||
// min_score: 0.5
|
||||
// max_score: 0.6
|
||||
// }
|
||||
// signal_settings{
|
||||
// type: {custom: "custom_text"}
|
||||
// min_score: 0.9
|
||||
// max_score: 1.0
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
class SignalFusingCalculator : public mediapipe::CalculatorBase {
|
||||
public:
|
||||
SignalFusingCalculator() {}
|
||||
SignalFusingCalculator()
|
||||
: tag_input_interface_(false), process_by_scene_(true) {}
|
||||
SignalFusingCalculator(const SignalFusingCalculator&) = delete;
|
||||
SignalFusingCalculator& operator=(const SignalFusingCalculator&) = delete;
|
||||
|
||||
@@ -84,9 +112,12 @@ class SignalFusingCalculator : public mediapipe::CalculatorBase {
|
||||
|
||||
private:
|
||||
mediapipe::Status ProcessScene(mediapipe::CalculatorContext* cc);
|
||||
std::vector<Packet> GetSignalPackets(mediapipe::CalculatorContext* cc);
|
||||
SignalFusingCalculatorOptions options_;
|
||||
std::map<std::string, SignalSettings> settings_by_type_;
|
||||
std::vector<Frame> scene_frames_;
|
||||
bool tag_input_interface_;
|
||||
bool process_by_scene_;
|
||||
};
|
||||
REGISTER_CALCULATOR(SignalFusingCalculator);
|
||||
|
||||
@@ -104,7 +135,23 @@ std::string CreateKey(const InputSignal& detection) {
|
||||
std::string id = id_source + ":" + id_signal;
|
||||
return id;
|
||||
}
|
||||
void SetupTagInput(mediapipe::CalculatorContract* cc) {
|
||||
if (cc->Inputs().HasTag(kIsShotBoundaryTag)) {
|
||||
cc->Inputs().Tag(kIsShotBoundaryTag).Set<bool>();
|
||||
}
|
||||
for (int i = 0; i < cc->Inputs().NumEntries(kSignalInputsTag); i++) {
|
||||
cc->Inputs().Get(kSignalInputsTag, i).Set<autoflip::DetectionSet>();
|
||||
}
|
||||
cc->Outputs().Tag(kOutputTag).Set<autoflip::DetectionSet>();
|
||||
}
|
||||
|
||||
void SetupOrderedInput(mediapipe::CalculatorContract* cc) {
|
||||
cc->Inputs().Index(0).Set<bool>();
|
||||
for (int i = 1; i < cc->Inputs().NumEntries(); ++i) {
|
||||
cc->Inputs().Index(i).Set<autoflip::DetectionSet>();
|
||||
}
|
||||
cc->Outputs().Index(0).Set<autoflip::DetectionSet>();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
mediapipe::Status SignalFusingCalculator::Open(
|
||||
@@ -113,6 +160,12 @@ mediapipe::Status SignalFusingCalculator::Open(
|
||||
for (const auto& setting : options_.signal_settings()) {
|
||||
settings_by_type_[CreateSettingsKey(setting.type())] = setting;
|
||||
}
|
||||
if (cc->Inputs().HasTag(kSignalInputsTag)) {
|
||||
tag_input_interface_ = true;
|
||||
if (!cc->Inputs().HasTag(kIsShotBoundaryTag)) {
|
||||
process_by_scene_ = false;
|
||||
}
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
@@ -144,14 +197,12 @@ mediapipe::Status SignalFusingCalculator::ProcessScene(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Average scores.
|
||||
for (auto iterator = multiframe_score.begin();
|
||||
iterator != multiframe_score.end(); iterator++) {
|
||||
multiframe_score[iterator->first] =
|
||||
iterator->second / detection_count[iterator->first];
|
||||
}
|
||||
|
||||
// Process detections.
|
||||
for (const Frame& frame : scene_frames_) {
|
||||
std::unique_ptr<DetectionSet> processed_detections(new DetectionSet());
|
||||
@@ -173,33 +224,63 @@ mediapipe::Status SignalFusingCalculator::ProcessScene(
|
||||
min_value = settings_it->second.min_score();
|
||||
max_value = settings_it->second.max_score();
|
||||
detection.signal.set_is_required(settings_it->second.is_required());
|
||||
detection.signal.set_only_required(settings_it->second.only_required());
|
||||
}
|
||||
|
||||
float final_score = score * (max_value - min_value) + min_value;
|
||||
detection.signal.set_score(final_score);
|
||||
*processed_detections->add_detections() = detection.signal;
|
||||
}
|
||||
cc->Outputs().Index(0).Add(processed_detections.release(), frame.time);
|
||||
if (tag_input_interface_) {
|
||||
cc->Outputs()
|
||||
.Tag(kOutputTag)
|
||||
.Add(processed_detections.release(), frame.time);
|
||||
} else {
|
||||
cc->Outputs().Index(0).Add(processed_detections.release(), frame.time);
|
||||
}
|
||||
}
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
std::vector<Packet> SignalFusingCalculator::GetSignalPackets(
|
||||
mediapipe::CalculatorContext* cc) {
|
||||
std::vector<Packet> signal_packets;
|
||||
if (tag_input_interface_) {
|
||||
for (int i = 0; i < cc->Inputs().NumEntries(kSignalInputsTag); i++) {
|
||||
const Packet& packet = cc->Inputs().Get(kSignalInputsTag, i).Value();
|
||||
signal_packets.push_back(packet);
|
||||
}
|
||||
} else {
|
||||
for (int i = 1; i < cc->Inputs().NumEntries(); i++) {
|
||||
const Packet& packet = cc->Inputs().Index(i).Value();
|
||||
signal_packets.push_back(packet);
|
||||
}
|
||||
}
|
||||
return signal_packets;
|
||||
}
|
||||
|
||||
mediapipe::Status SignalFusingCalculator::Process(
|
||||
mediapipe::CalculatorContext* cc) {
|
||||
bool is_boundary = false;
|
||||
if (!cc->Inputs().Index(0).Value().IsEmpty()) {
|
||||
is_boundary = cc->Inputs().Index(0).Get<bool>();
|
||||
if (process_by_scene_) {
|
||||
const auto& shot_tag = (tag_input_interface_)
|
||||
? cc->Inputs().Tag(kIsShotBoundaryTag)
|
||||
: cc->Inputs().Index(0);
|
||||
if (!shot_tag.Value().IsEmpty()) {
|
||||
is_boundary = shot_tag.Get<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
if (is_boundary || scene_frames_.size() > options_.max_scene_size()) {
|
||||
if (is_boundary) {
|
||||
MP_RETURN_IF_ERROR(ProcessScene(cc));
|
||||
scene_frames_.clear();
|
||||
}
|
||||
|
||||
Frame frame;
|
||||
for (int i = 1; i < cc->Inputs().NumEntries(); ++i) {
|
||||
const Packet& packet = cc->Inputs().Index(i).Value();
|
||||
const auto& signal_packets = GetSignalPackets(cc);
|
||||
for (int i = 0; i < signal_packets.size(); i++) {
|
||||
const Packet& packet = signal_packets[i];
|
||||
if (packet.IsEmpty()) {
|
||||
continue;
|
||||
}
|
||||
@@ -214,16 +295,23 @@ mediapipe::Status SignalFusingCalculator::Process(
|
||||
frame.time = cc->InputTimestamp();
|
||||
scene_frames_.push_back(frame);
|
||||
|
||||
// Flush buffer on same input if it exceeds max_scene_size or if there is not
|
||||
// shot input information.
|
||||
if (scene_frames_.size() > options_.max_scene_size() || !process_by_scene_) {
|
||||
MP_RETURN_IF_ERROR(ProcessScene(cc));
|
||||
scene_frames_.clear();
|
||||
}
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status SignalFusingCalculator::GetContract(
|
||||
mediapipe::CalculatorContract* cc) {
|
||||
cc->Inputs().Index(0).Set<bool>();
|
||||
for (int i = 1; i < cc->Inputs().NumEntries(); ++i) {
|
||||
cc->Inputs().Index(i).Set<autoflip::DetectionSet>();
|
||||
if (cc->Inputs().NumEntries(kSignalInputsTag) > 0) {
|
||||
SetupTagInput(cc);
|
||||
} else {
|
||||
SetupOrderedInput(cc);
|
||||
}
|
||||
cc->Outputs().Index(0).Set<autoflip::DetectionSet>();
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ message SignalFusingCalculatorOptions {
|
||||
optional int32 max_scene_size = 2 [default = 600];
|
||||
}
|
||||
|
||||
// Next tag: 5
|
||||
// Next tag: 6
|
||||
message SignalSettings {
|
||||
// The type of signal these settings pertain to.
|
||||
optional SignalType type = 1;
|
||||
@@ -51,4 +51,14 @@ message SignalSettings {
|
||||
// Is this signal required within the output cropped video? If it is it will
|
||||
// be included or the video will be marked as failed to convert.
|
||||
optional bool is_required = 4 [default = false];
|
||||
|
||||
// When used with ContentZoomingCalculator, this flag can be set indicating
|
||||
// that areas outside of these salient regions can be cropped from the frame.
|
||||
// When no salient regions have this flag set true, no zooming is performed.
|
||||
// When one or more salient regions have this flag set true, the max zoom
|
||||
// value will be used that keeps all “only_required” detections within view.
|
||||
// The ContentZoomingCalculator currently supports zooming by finding the size
|
||||
// of non-salient top/bottom borders regions and provides this information to
|
||||
// the SceneCroppingCalculator for reframing.
|
||||
optional bool only_required = 5 [default = false];
|
||||
}
|
||||
|
||||
@@ -80,6 +80,27 @@ const char kConfigB[] = R"(
|
||||
}
|
||||
})";
|
||||
|
||||
const char kConfigC[] = R"(
|
||||
calculator: "SignalFusingCalculator"
|
||||
input_stream: "IS_SHOT_BOUNDARY:scene_change"
|
||||
input_stream: "SIGNAL:0:detection_set_a"
|
||||
input_stream: "SIGNAL:1:detection_set_b"
|
||||
output_stream: "OUTPUT:salient_region"
|
||||
options:{
|
||||
[mediapipe.autoflip.SignalFusingCalculatorOptions.ext]:{
|
||||
signal_settings{
|
||||
type: {standard: FACE_FULL}
|
||||
min_score: 0.5
|
||||
max_score: 0.6
|
||||
}
|
||||
signal_settings{
|
||||
type: {standard: TEXT}
|
||||
min_score: 0.9
|
||||
max_score: 1.0
|
||||
}
|
||||
}
|
||||
})";
|
||||
|
||||
TEST(SignalFusingCalculatorTest, TwoInputNoTracking) {
|
||||
auto runner = absl::make_unique<CalculatorRunner>(
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(kConfigA));
|
||||
@@ -133,6 +154,113 @@ TEST(SignalFusingCalculatorTest, TwoInputNoTracking) {
|
||||
EXPECT_FLOAT_EQ(detection_set.detections(3).score(), .99);
|
||||
}
|
||||
|
||||
TEST(SignalFusingCalculatorTest, TwoInputShotLabeledTags) {
|
||||
auto runner = absl::make_unique<CalculatorRunner>(
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(kConfigC));
|
||||
|
||||
auto input_shot = absl::make_unique<bool>(false);
|
||||
runner->MutableInputs()
|
||||
->Tag("IS_SHOT_BOUNDARY")
|
||||
.packets.push_back(Adopt(input_shot.release()).At(Timestamp(0)));
|
||||
|
||||
auto input_face =
|
||||
absl::make_unique<DetectionSet>(ParseTextProtoOrDie<DetectionSet>(
|
||||
R"(
|
||||
detections {
|
||||
score: 0.5
|
||||
signal_type: { standard: FACE_FULL }
|
||||
}
|
||||
detections {
|
||||
score: 0.3
|
||||
signal_type: { standard: FACE_FULL }
|
||||
}
|
||||
)"));
|
||||
|
||||
runner->MutableInputs()
|
||||
->Get("SIGNAL", 0)
|
||||
.packets.push_back(Adopt(input_face.release()).At(Timestamp(0)));
|
||||
|
||||
auto input_ocr =
|
||||
absl::make_unique<DetectionSet>(ParseTextProtoOrDie<DetectionSet>(
|
||||
R"(
|
||||
detections {
|
||||
score: 0.3
|
||||
signal_type: { standard: TEXT }
|
||||
}
|
||||
detections {
|
||||
score: 0.9
|
||||
signal_type: { standard: TEXT }
|
||||
}
|
||||
)"));
|
||||
|
||||
runner->MutableInputs()
|
||||
->Get("SIGNAL", 1)
|
||||
.packets.push_back(Adopt(input_ocr.release()).At(Timestamp(0)));
|
||||
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("OUTPUT").packets;
|
||||
const auto& detection_set = output_packets[0].Get<DetectionSet>();
|
||||
|
||||
ASSERT_EQ(detection_set.detections().size(), 4);
|
||||
EXPECT_FLOAT_EQ(detection_set.detections(0).score(), .55);
|
||||
EXPECT_FLOAT_EQ(detection_set.detections(1).score(), .53);
|
||||
EXPECT_FLOAT_EQ(detection_set.detections(2).score(), .93);
|
||||
EXPECT_FLOAT_EQ(detection_set.detections(3).score(), .99);
|
||||
}
|
||||
|
||||
TEST(SignalFusingCalculatorTest, TwoInputNoShotLabeledTags) {
|
||||
auto runner = absl::make_unique<CalculatorRunner>(
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(kConfigC));
|
||||
|
||||
auto input_face =
|
||||
absl::make_unique<DetectionSet>(ParseTextProtoOrDie<DetectionSet>(
|
||||
R"(
|
||||
detections {
|
||||
score: 0.5
|
||||
signal_type: { standard: FACE_FULL }
|
||||
}
|
||||
detections {
|
||||
score: 0.3
|
||||
signal_type: { standard: FACE_FULL }
|
||||
}
|
||||
)"));
|
||||
|
||||
runner->MutableInputs()
|
||||
->Get("SIGNAL", 0)
|
||||
.packets.push_back(Adopt(input_face.release()).At(Timestamp(0)));
|
||||
|
||||
auto input_ocr =
|
||||
absl::make_unique<DetectionSet>(ParseTextProtoOrDie<DetectionSet>(
|
||||
R"(
|
||||
detections {
|
||||
score: 0.3
|
||||
signal_type: { standard: TEXT }
|
||||
}
|
||||
detections {
|
||||
score: 0.9
|
||||
signal_type: { standard: TEXT }
|
||||
}
|
||||
)"));
|
||||
|
||||
runner->MutableInputs()
|
||||
->Get("SIGNAL", 1)
|
||||
.packets.push_back(Adopt(input_ocr.release()).At(Timestamp(0)));
|
||||
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("OUTPUT").packets;
|
||||
const auto& detection_set = output_packets[0].Get<DetectionSet>();
|
||||
|
||||
ASSERT_EQ(detection_set.detections().size(), 4);
|
||||
EXPECT_FLOAT_EQ(detection_set.detections(0).score(), .55);
|
||||
EXPECT_FLOAT_EQ(detection_set.detections(1).score(), .53);
|
||||
EXPECT_FLOAT_EQ(detection_set.detections(2).score(), .93);
|
||||
EXPECT_FLOAT_EQ(detection_set.detections(3).score(), .99);
|
||||
}
|
||||
|
||||
TEST(SignalFusingCalculatorTest, ThreeInputTracking) {
|
||||
auto runner = absl::make_unique<CalculatorRunner>(
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(kConfigB));
|
||||
|
||||
@@ -23,17 +23,33 @@ proto_library(
|
||||
srcs = ["cropping.proto"],
|
||||
deps = [
|
||||
"//mediapipe/examples/desktop/autoflip:autoflip_messages_proto",
|
||||
"//mediapipe/examples/desktop/autoflip/quality:kinematic_path_solver_proto",
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_cc_proto_library(
|
||||
name = "cropping_cc_proto",
|
||||
srcs = ["cropping.proto"],
|
||||
cc_deps = ["//mediapipe/examples/desktop/autoflip:autoflip_messages_cc_proto"],
|
||||
cc_deps = [
|
||||
":kinematic_path_solver_cc_proto",
|
||||
"//mediapipe/examples/desktop/autoflip:autoflip_messages_cc_proto",
|
||||
],
|
||||
visibility = ["//mediapipe/examples:__subpackages__"],
|
||||
deps = [":cropping_proto"],
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "kinematic_path_solver_proto",
|
||||
srcs = ["kinematic_path_solver.proto"],
|
||||
)
|
||||
|
||||
mediapipe_cc_proto_library(
|
||||
name = "kinematic_path_solver_cc_proto",
|
||||
srcs = ["kinematic_path_solver.proto"],
|
||||
visibility = ["//mediapipe/examples:__subpackages__"],
|
||||
deps = [":kinematic_path_solver_proto"],
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "focus_point_proto",
|
||||
srcs = ["focus_point.proto"],
|
||||
@@ -142,6 +158,29 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "kinematic_path_solver",
|
||||
srcs = ["kinematic_path_solver.cc"],
|
||||
hdrs = ["kinematic_path_solver.h"],
|
||||
deps = [
|
||||
":kinematic_path_solver_cc_proto",
|
||||
"//mediapipe/framework/port:integral_types",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "kinematic_path_solver_test",
|
||||
srcs = ["kinematic_path_solver_test.cc"],
|
||||
deps = [
|
||||
":kinematic_path_solver",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"//mediapipe/framework/port:integral_types",
|
||||
"//mediapipe/framework/port:status",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "scene_cropper",
|
||||
srcs = ["scene_cropper.cc"],
|
||||
@@ -149,6 +188,7 @@ cc_library(
|
||||
deps = [
|
||||
":cropping_cc_proto",
|
||||
":focus_point_cc_proto",
|
||||
":kinematic_path_solver",
|
||||
":polynomial_regression_path_solver",
|
||||
":utils",
|
||||
"//mediapipe/framework/port:opencv_core",
|
||||
|
||||
@@ -17,6 +17,7 @@ syntax = "proto2";
|
||||
package mediapipe.autoflip;
|
||||
|
||||
import "mediapipe/examples/desktop/autoflip/autoflip_messages.proto";
|
||||
import "mediapipe/examples/desktop/autoflip/quality/kinematic_path_solver.proto";
|
||||
|
||||
// All relevant information for key frames, including timestamp and detected
|
||||
// features. This object should be generated by calling PackKeyFrameInfo() in
|
||||
@@ -77,6 +78,8 @@ message KeyFrameCropResult {
|
||||
optional Rect region = 6;
|
||||
// Score of the computed crop region based on the detected features.
|
||||
optional float region_score = 7;
|
||||
// Frame timestamp (in microseconds).
|
||||
optional int64 timestamp_ms = 8;
|
||||
}
|
||||
|
||||
// Compact processed scene key frame info containing timestamp, center position,
|
||||
@@ -172,6 +175,7 @@ message SceneCameraMotion {
|
||||
// User-specified options for analyzing scene camera motion from a collection of
|
||||
// key frame crop regions.
|
||||
message SceneCameraMotionAnalyzerOptions {
|
||||
reserved 9;
|
||||
// If there is small motion within the scene keep the camera steady at the
|
||||
// center.
|
||||
optional float motion_stabilization_threshold_percent = 1 [default = .30];
|
||||
@@ -196,6 +200,11 @@ message SceneCameraMotionAnalyzerOptions {
|
||||
optional float minimum_success_rate_for_sweeping = 7 [default = 0.4];
|
||||
// If true, sweep entire frame. Otherwise, sweep the crop window.
|
||||
optional bool sweep_entire_frame = 8 [default = true];
|
||||
// When no salient region is received, the default behavior is the return the
|
||||
// camera to center-focused location. When this flag is set to a value >0,
|
||||
// the camera will remain at its last position for this amount of time before
|
||||
// recentering (if the last scene camera motion type was steady).
|
||||
optional int64 duration_before_centering_us = 10;
|
||||
}
|
||||
|
||||
// Video cropping summary information for debugging/statistics.
|
||||
@@ -215,3 +224,18 @@ message VideoCroppingSummary {
|
||||
// Cropping summaries for all the scenes in the video.
|
||||
repeated SceneCroppingSummary scene_summaries = 1;
|
||||
}
|
||||
|
||||
message CameraMotionOptions {
|
||||
message PolynomialRegressionPathSolver {
|
||||
// Number of frames from prior buffer to be used to smooth out camera
|
||||
// trajectory when it was a forced flush.
|
||||
optional int32 prior_frame_buffer_size = 1 [default = 30];
|
||||
}
|
||||
oneof camera_model_oneof {
|
||||
// Fits a poly line to keypoints to find a smooth camera path.
|
||||
PolynomialRegressionPathSolver polynomial_path_solver = 1;
|
||||
// Maintains a kinematic state of the camera, updated with keypoints, to
|
||||
// find a smooth camera path. Currently optimized for real-time operation.
|
||||
KinematicOptions kinematic_options = 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,6 +171,9 @@ void FrameCropRegionComputer::UpdateCropRegionScore(
|
||||
const KeyFrameInfo& frame_info, KeyFrameCropResult* crop_result) const {
|
||||
RET_CHECK(crop_result != nullptr) << "KeyFrameCropResult is null.";
|
||||
|
||||
// Set timestamp of KeyFrameCropResult
|
||||
crop_result->set_timestamp_ms(frame_info.timestamp_ms());
|
||||
|
||||
// Sorts required and non-required regions.
|
||||
std::vector<SalientRegion> required_regions, non_required_regions;
|
||||
const auto sort_status = SortDetections(
|
||||
@@ -180,7 +183,6 @@ void FrameCropRegionComputer::UpdateCropRegionScore(
|
||||
int target_width = options_.target_width();
|
||||
int target_height = options_.target_height();
|
||||
auto* region = crop_result->mutable_region();
|
||||
RET_CHECK(region != nullptr) << "Crop region is null.";
|
||||
|
||||
bool crop_region_is_empty = true;
|
||||
float crop_region_score = 0.0;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#include "mediapipe/examples/desktop/autoflip/quality/kinematic_path_solver.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace autoflip {
|
||||
|
||||
::mediapipe::Status KinematicPathSolver::AddObservation(int position,
|
||||
const uint64 time_us) {
|
||||
if (!initialized_) {
|
||||
current_position_px_ = position;
|
||||
current_time_ = time_us;
|
||||
initialized_ = true;
|
||||
current_velocity_deg_per_s_ = 0;
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
RET_CHECK(current_time_ < time_us)
|
||||
<< "Observation added before a prior observations.";
|
||||
|
||||
double delta_degs = (position - current_position_px_) / pixels_per_degree_;
|
||||
|
||||
// If the motion is smaller than the min, don't use the update.
|
||||
if (abs(delta_degs) < options_.min_motion_to_reframe()) {
|
||||
position = current_position_px_;
|
||||
delta_degs = 0;
|
||||
}
|
||||
|
||||
// Time and position updates.
|
||||
double delta_t = (time_us - current_time_) / 1000000.0;
|
||||
|
||||
// Observed velocity and then weighted update of this velocity.
|
||||
double observed_velocity = delta_degs / delta_t;
|
||||
double updated_velocity =
|
||||
current_velocity_deg_per_s_ * (1 - options_.update_rate()) +
|
||||
observed_velocity * options_.update_rate();
|
||||
// Limited current velocity.
|
||||
current_velocity_deg_per_s_ =
|
||||
updated_velocity > 0 ? fmin(updated_velocity, options_.max_velocity())
|
||||
: fmax(updated_velocity, -options_.max_velocity());
|
||||
|
||||
// Update prediction based on time input.
|
||||
return UpdatePrediction(time_us);
|
||||
}
|
||||
|
||||
::mediapipe::Status KinematicPathSolver::UpdatePrediction(const int64 time_us) {
|
||||
RET_CHECK(current_time_ < time_us)
|
||||
<< "Prediction time added before a prior observation or prediction.";
|
||||
// Time since last state/prediction update.
|
||||
double delta_t = (time_us - current_time_) / 1000000.0;
|
||||
|
||||
// Position update limited by min/max.
|
||||
|
||||
const double update_position_px =
|
||||
current_position_px_ +
|
||||
current_velocity_deg_per_s_ * delta_t * pixels_per_degree_;
|
||||
if (update_position_px < min_location_) {
|
||||
current_position_px_ = min_location_;
|
||||
current_velocity_deg_per_s_ = 0;
|
||||
} else if (update_position_px > max_location_) {
|
||||
current_position_px_ = max_location_;
|
||||
current_velocity_deg_per_s_ = 0;
|
||||
} else {
|
||||
current_position_px_ = update_position_px;
|
||||
}
|
||||
current_time_ = time_us;
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status KinematicPathSolver::GetState(int* position) {
|
||||
RET_CHECK(initialized_) << "GetState called before first observation added.";
|
||||
*position = round(current_position_px_);
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace autoflip
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_UNIFORM_ACCELERATION_PATH_SOLVER_H_
|
||||
#define MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_UNIFORM_ACCELERATION_PATH_SOLVER_H_
|
||||
|
||||
#include "mediapipe/examples/desktop/autoflip/quality/kinematic_path_solver.pb.h"
|
||||
#include "mediapipe/framework/port/integral_types.h"
|
||||
#include "mediapipe/framework/port/ret_check.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace autoflip {
|
||||
|
||||
// Kinematic path solver class is a stateful 1d position estimator based loosely
|
||||
// on a differential kalman filter that is specifically designed to control a
|
||||
// camera. It utilizes a Kalman filters predict/update interface for estimating
|
||||
// the best camera focus position and updating that estimate when a measurement
|
||||
// is available. Tuning controls include: update_rate: how much to update the
|
||||
// existing state with a new state. max_velocity: max speed of the state per
|
||||
// second. min_motion_to_reframe: only updating the state if a measurement
|
||||
// exceeds this threshold.
|
||||
class KinematicPathSolver {
|
||||
public:
|
||||
KinematicPathSolver(const KinematicOptions& options, const int min_location,
|
||||
const int max_location, float pixels_per_degree)
|
||||
: options_(options),
|
||||
min_location_(min_location),
|
||||
max_location_(max_location),
|
||||
initialized_(false),
|
||||
pixels_per_degree_(pixels_per_degree) {}
|
||||
// Add an observation (detection) at a position and time.
|
||||
::mediapipe::Status AddObservation(int position, const uint64 time_us);
|
||||
// Get the predicted position at a time.
|
||||
::mediapipe::Status UpdatePrediction(const int64 time_us);
|
||||
// Get the state at a time.
|
||||
::mediapipe::Status GetState(int* position);
|
||||
|
||||
private:
|
||||
// Tuning options.
|
||||
KinematicOptions options_;
|
||||
// Min and max value the state can be.
|
||||
const int min_location_;
|
||||
const int max_location_;
|
||||
bool initialized_;
|
||||
float pixels_per_degree_;
|
||||
// Current state values.
|
||||
double current_position_px_;
|
||||
double current_velocity_deg_per_s_;
|
||||
uint64 current_time_;
|
||||
};
|
||||
|
||||
} // namespace autoflip
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_UNIFORM_ACCELERATION_PATH_SOLVER_H_
|
||||
@@ -0,0 +1,13 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package mediapipe.autoflip;
|
||||
|
||||
message KinematicOptions {
|
||||
// Weighted update of new camera velocity (measurement) vs current state
|
||||
// (prediction).
|
||||
optional double update_rate = 1 [default = 0.5];
|
||||
// Max velocity (degrees per second) that the camera can move.
|
||||
optional double max_velocity = 2 [default = 18];
|
||||
// Min motion (in degrees) to react in pixels.
|
||||
optional float min_motion_to_reframe = 3 [default = 1.8];
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// 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/examples/desktop/autoflip/quality/kinematic_path_solver.h"
|
||||
|
||||
#include "mediapipe/framework/port/gmock.h"
|
||||
#include "mediapipe/framework/port/gtest.h"
|
||||
#include "mediapipe/framework/port/integral_types.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/framework/port/status_matchers.h"
|
||||
|
||||
constexpr int64 kMicroSecInSec = 1000000;
|
||||
constexpr float kWidthFieldOfView = 60;
|
||||
|
||||
namespace mediapipe {
|
||||
namespace autoflip {
|
||||
namespace {
|
||||
|
||||
TEST(KinematicPathSolverTest, FailNotInitializedState) {
|
||||
KinematicOptions options;
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
EXPECT_FALSE(solver.GetState(&state).ok());
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, FailNotInitializedPrediction) {
|
||||
KinematicOptions options;
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int64 timestamp = 0;
|
||||
EXPECT_FALSE(solver.UpdatePrediction(timestamp).ok());
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassNotEnoughMotionLargeImg) {
|
||||
KinematicOptions options;
|
||||
// Set min motion to 2deg
|
||||
options.set_min_motion_to_reframe(2.0);
|
||||
options.set_update_rate(1);
|
||||
options.set_max_velocity(1000);
|
||||
// Set degrees / pixel to 16.6
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0));
|
||||
// Move target by 20px / 16.6 = 1.2deg
|
||||
MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 1));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
// Expect cam to not move.
|
||||
EXPECT_EQ(state, 500);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassNotEnoughMotionSmallImg) {
|
||||
KinematicOptions options;
|
||||
// Set min motion to 2deg
|
||||
options.set_min_motion_to_reframe(2.0);
|
||||
options.set_update_rate(1);
|
||||
options.set_max_velocity(500);
|
||||
// Set degrees / pixel to 8.3
|
||||
KinematicPathSolver solver(options, 0, 500, 500.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
MP_ASSERT_OK(solver.AddObservation(400, kMicroSecInSec * 0));
|
||||
// Move target by 10px / 8.3 = 1.2deg
|
||||
MP_ASSERT_OK(solver.AddObservation(410, kMicroSecInSec * 1));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
// Expect cam to not move.
|
||||
EXPECT_EQ(state, 400);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassEnoughMotionLargeImg) {
|
||||
KinematicOptions options;
|
||||
// Set min motion to 1deg
|
||||
options.set_min_motion_to_reframe(1.0);
|
||||
options.set_update_rate(1);
|
||||
options.set_max_velocity(1000);
|
||||
// Set degrees / pixel to 16.6
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0));
|
||||
// Move target by 20px / 16.6 = 1.2deg
|
||||
MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 1));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
// Expect cam to move.
|
||||
EXPECT_EQ(state, 520);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassEnoughMotionSmallImg) {
|
||||
KinematicOptions options;
|
||||
// Set min motion to 2deg
|
||||
options.set_min_motion_to_reframe(1.0);
|
||||
options.set_update_rate(1);
|
||||
options.set_max_velocity(18);
|
||||
// Set degrees / pixel to 8.3
|
||||
KinematicPathSolver solver(options, 0, 500, 500.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
MP_ASSERT_OK(solver.AddObservation(400, kMicroSecInSec * 0));
|
||||
// Move target by 10px / 8.3 = 1.2deg
|
||||
MP_ASSERT_OK(solver.AddObservation(410, kMicroSecInSec * 1));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
// Expect cam to move.
|
||||
EXPECT_EQ(state, 410);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassUpdateRate) {
|
||||
KinematicOptions options;
|
||||
options.set_min_motion_to_reframe(1.0);
|
||||
options.set_update_rate(0.25);
|
||||
options.set_max_velocity(18);
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0));
|
||||
MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 1));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
EXPECT_EQ(state, 505);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassMaxVelocity) {
|
||||
KinematicOptions options;
|
||||
options.set_min_motion_to_reframe(1.0);
|
||||
options.set_update_rate(1.0);
|
||||
options.set_max_velocity(6);
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0));
|
||||
MP_ASSERT_OK(solver.AddObservation(1000, kMicroSecInSec * 1));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
EXPECT_EQ(state, 600);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace autoflip
|
||||
} // namespace mediapipe
|
||||
@@ -32,17 +32,19 @@ namespace autoflip {
|
||||
|
||||
::mediapipe::Status
|
||||
SceneCameraMotionAnalyzer::AnalyzeSceneAndPopulateFocusPointFrames(
|
||||
const std::vector<KeyFrameInfo>& key_frame_infos,
|
||||
const KeyFrameCropOptions& key_frame_crop_options,
|
||||
const std::vector<KeyFrameCropResult>& key_frame_crop_results,
|
||||
const int scene_frame_width, const int scene_frame_height,
|
||||
const std::vector<int64>& scene_frame_timestamps,
|
||||
const bool has_solid_color_background,
|
||||
SceneKeyFrameCropSummary* scene_summary,
|
||||
std::vector<FocusPointFrame>* focus_point_frames,
|
||||
SceneCameraMotion* scene_camera_motion) const {
|
||||
SceneCameraMotion* scene_camera_motion) {
|
||||
has_solid_color_background_ = has_solid_color_background;
|
||||
total_scene_frames_ = scene_frame_timestamps.size();
|
||||
MP_RETURN_IF_ERROR(AggregateKeyFrameResults(
|
||||
key_frame_infos, key_frame_crop_options, key_frame_crop_results,
|
||||
scene_frame_width, scene_frame_height, scene_summary));
|
||||
key_frame_crop_options, key_frame_crop_results, scene_frame_width,
|
||||
scene_frame_height, scene_summary));
|
||||
|
||||
const int64 scene_span_ms =
|
||||
scene_frame_timestamps.empty()
|
||||
@@ -51,7 +53,12 @@ SceneCameraMotionAnalyzer::AnalyzeSceneAndPopulateFocusPointFrames(
|
||||
const double scene_span_sec = TimestampDiff(scene_span_ms).Seconds();
|
||||
SceneCameraMotion camera_motion;
|
||||
MP_RETURN_IF_ERROR(DecideCameraMotionType(
|
||||
key_frame_crop_options, scene_span_sec, scene_summary, &camera_motion));
|
||||
key_frame_crop_options, scene_span_sec, scene_frame_timestamps.back(),
|
||||
scene_summary, &camera_motion));
|
||||
if (scene_summary->has_salient_region()) {
|
||||
last_scene_with_salient_region_ = camera_motion;
|
||||
time_since_last_salient_region_us_ = scene_frame_timestamps.back();
|
||||
}
|
||||
if (scene_camera_motion != nullptr) {
|
||||
*scene_camera_motion = camera_motion;
|
||||
}
|
||||
@@ -97,7 +104,8 @@ SceneCameraMotionAnalyzer::AnalyzeSceneAndPopulateFocusPointFrames(
|
||||
|
||||
::mediapipe::Status SceneCameraMotionAnalyzer::DecideCameraMotionType(
|
||||
const KeyFrameCropOptions& key_frame_crop_options,
|
||||
const double scene_span_sec, SceneKeyFrameCropSummary* scene_summary,
|
||||
const double scene_span_sec, const int64 end_time_us,
|
||||
SceneKeyFrameCropSummary* scene_summary,
|
||||
SceneCameraMotion* scene_camera_motion) const {
|
||||
RET_CHECK_GE(scene_span_sec, 0.0) << "Scene time span is negative.";
|
||||
RET_CHECK_NE(scene_summary, nullptr) << "Scene summary is null.";
|
||||
@@ -109,8 +117,18 @@ SceneCameraMotionAnalyzer::AnalyzeSceneAndPopulateFocusPointFrames(
|
||||
// regions, then default to look at the center.
|
||||
if (!scene_summary->has_salient_region()) {
|
||||
VLOG(1) << "No focus regions - camera is set to be steady on center.";
|
||||
float no_salient_position_x = scene_frame_center_x;
|
||||
float no_salient_position_y = scene_frame_center_y;
|
||||
if (end_time_us - time_since_last_salient_region_us_ <
|
||||
options_.duration_before_centering_us() &&
|
||||
last_scene_with_salient_region_.has_steady_motion()) {
|
||||
no_salient_position_x = last_scene_with_salient_region_.steady_motion()
|
||||
.steady_look_at_center_x();
|
||||
no_salient_position_y = last_scene_with_salient_region_.steady_motion()
|
||||
.steady_look_at_center_y();
|
||||
}
|
||||
MP_RETURN_IF_ERROR(ToUseSteadyMotion(
|
||||
scene_frame_center_x, scene_frame_center_y,
|
||||
no_salient_position_x, no_salient_position_y,
|
||||
scene_summary->crop_window_width(), scene_summary->crop_window_height(),
|
||||
scene_summary, scene_camera_motion));
|
||||
return ::mediapipe::OkStatus();
|
||||
@@ -118,7 +136,7 @@ SceneCameraMotionAnalyzer::AnalyzeSceneAndPopulateFocusPointFrames(
|
||||
|
||||
// Sweep across the scene when 1) success rate is too low, AND 2) the current
|
||||
// scene is long enough.
|
||||
if (options_.allow_sweeping() &&
|
||||
if (options_.allow_sweeping() && !has_solid_color_background_ &&
|
||||
scene_summary->frame_success_rate() <
|
||||
options_.minimum_success_rate_for_sweeping() &&
|
||||
scene_span_sec >= options_.minimum_scene_span_sec_for_sweeping()) {
|
||||
@@ -150,10 +168,11 @@ SceneCameraMotionAnalyzer::AnalyzeSceneAndPopulateFocusPointFrames(
|
||||
}
|
||||
|
||||
// If scene motion is small, then look at a steady point in the scene.
|
||||
if (scene_summary->horizontal_motion_amount() <
|
||||
options_.motion_stabilization_threshold_percent() &&
|
||||
scene_summary->vertical_motion_amount() <
|
||||
options_.motion_stabilization_threshold_percent()) {
|
||||
if ((scene_summary->horizontal_motion_amount() <
|
||||
options_.motion_stabilization_threshold_percent() &&
|
||||
scene_summary->vertical_motion_amount() <
|
||||
options_.motion_stabilization_threshold_percent()) ||
|
||||
total_scene_frames_ == 1) {
|
||||
return DecideSteadyLookAtRegion(key_frame_crop_options, scene_summary,
|
||||
scene_camera_motion);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,9 @@ class SceneCameraMotionAnalyzer {
|
||||
|
||||
explicit SceneCameraMotionAnalyzer(const SceneCameraMotionAnalyzerOptions&
|
||||
scene_camera_motion_analyzer_options)
|
||||
: options_(scene_camera_motion_analyzer_options) {}
|
||||
: options_(scene_camera_motion_analyzer_options),
|
||||
time_since_last_salient_region_us_(0),
|
||||
has_solid_color_background_(false) {}
|
||||
|
||||
~SceneCameraMotionAnalyzer() {}
|
||||
|
||||
@@ -61,21 +63,22 @@ class SceneCameraMotionAnalyzer {
|
||||
// SceneKeyFrameCropSummary, and populates FocusPointFrames given scene
|
||||
// frame timestamps. Optionally returns SceneCameraMotion.
|
||||
::mediapipe::Status AnalyzeSceneAndPopulateFocusPointFrames(
|
||||
const std::vector<KeyFrameInfo>& key_frame_infos,
|
||||
const KeyFrameCropOptions& key_frame_crop_options,
|
||||
const std::vector<KeyFrameCropResult>& key_frame_crop_results,
|
||||
const int scene_frame_width, const int scene_frame_height,
|
||||
const std::vector<int64>& scene_frame_timestamps,
|
||||
const bool has_solid_color_background,
|
||||
SceneKeyFrameCropSummary* scene_summary,
|
||||
std::vector<FocusPointFrame>* focus_point_frames,
|
||||
SceneCameraMotion* scene_camera_motion = nullptr) const;
|
||||
SceneCameraMotion* scene_camera_motion = nullptr);
|
||||
|
||||
protected:
|
||||
// Decides SceneCameraMotion based on SceneKeyFrameCropSummary. Updates the
|
||||
// crop window in SceneKeyFrameCropSummary in the case of steady motion.
|
||||
::mediapipe::Status DecideCameraMotionType(
|
||||
const KeyFrameCropOptions& key_frame_crop_options,
|
||||
const double scene_span_sec, SceneKeyFrameCropSummary* scene_summary,
|
||||
const double scene_span_sec, const int64 end_time_us,
|
||||
SceneKeyFrameCropSummary* scene_summary,
|
||||
SceneCameraMotion* scene_camera_motion) const;
|
||||
|
||||
// Populates the FocusPointFrames for each scene frame based on
|
||||
@@ -134,6 +137,16 @@ class SceneCameraMotionAnalyzer {
|
||||
|
||||
// Scene camera motion analyzer options.
|
||||
SceneCameraMotionAnalyzerOptions options_;
|
||||
|
||||
// Last position
|
||||
SceneCameraMotion last_scene_with_salient_region_;
|
||||
int64 time_since_last_salient_region_us_;
|
||||
|
||||
// Scene has solid color background.
|
||||
bool has_solid_color_background_;
|
||||
|
||||
// Total number of frames for this scene.
|
||||
int total_scene_frames_;
|
||||
};
|
||||
|
||||
} // namespace autoflip
|
||||
|
||||
@@ -63,16 +63,6 @@ Rect MakeRect(const int x, const int y, const int width, const int height) {
|
||||
return rect;
|
||||
}
|
||||
|
||||
// Returns default values for KeyFrameInfos. Populates timestamps using the
|
||||
// default spacing kKeyFrameTimestampDiff starting from 0.
|
||||
std::vector<KeyFrameInfo> GetDefaultKeyFrameInfos() {
|
||||
std::vector<KeyFrameInfo> key_frame_infos(kNumKeyFrames);
|
||||
for (int i = 0; i < kNumKeyFrames; ++i) {
|
||||
key_frame_infos[i].set_timestamp_ms(kKeyFrameTimestampDiff * i);
|
||||
}
|
||||
return key_frame_infos;
|
||||
}
|
||||
|
||||
// Returns default values for scene frame timestamps. Populates timestamps using
|
||||
// the default spacing kSceneFrameTimestampDiff starting from 0.
|
||||
std::vector<int64> GetDefaultSceneFrameTimestamps() {
|
||||
@@ -108,6 +98,7 @@ std::vector<KeyFrameCropResult> GetDefaultKeyFrameCropResults() {
|
||||
*(key_frame_crop_results[i].mutable_required_region()) =
|
||||
MakeRect(10, 10, 20, 20);
|
||||
key_frame_crop_results[i].set_region_score(1.0);
|
||||
key_frame_crop_results[i].set_timestamp_ms(kKeyFrameTimestampDiff * i);
|
||||
}
|
||||
return key_frame_crop_results;
|
||||
}
|
||||
@@ -183,10 +174,10 @@ TEST(SceneCameraMotionAnalyzerTest, DecideCameraMotionTypeChecksOutputNotNull) {
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
SceneCameraMotion camera_motion;
|
||||
auto status = analyzer.DecideCameraMotionType(crop_options, kSceneTimeSpanSec,
|
||||
nullptr, &camera_motion);
|
||||
0, nullptr, &camera_motion);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.ToString(), HasSubstr("Scene summary is null."));
|
||||
status = analyzer.DecideCameraMotionType(crop_options, kSceneTimeSpanSec,
|
||||
status = analyzer.DecideCameraMotionType(crop_options, kSceneTimeSpanSec, 0,
|
||||
&scene_summary, nullptr);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.ToString(), HasSubstr("Scene camera motion is null."));
|
||||
@@ -204,8 +195,8 @@ TEST(SceneCameraMotionAnalyzerTest,
|
||||
scene_summary.set_has_salient_region(false);
|
||||
SceneCameraMotion camera_motion;
|
||||
|
||||
MP_EXPECT_OK(analyzer.DecideCameraMotionType(crop_options, kSceneTimeSpanSec,
|
||||
&scene_summary, &camera_motion));
|
||||
MP_EXPECT_OK(analyzer.DecideCameraMotionType(
|
||||
crop_options, kSceneTimeSpanSec, 0, &scene_summary, &camera_motion));
|
||||
EXPECT_TRUE(camera_motion.has_steady_motion());
|
||||
const auto& steady_motion = camera_motion.steady_motion();
|
||||
EXPECT_FLOAT_EQ(steady_motion.steady_look_at_center_x(),
|
||||
@@ -229,7 +220,7 @@ TEST(SceneCameraMotionAnalyzerTest, DecideCameraMotionTypeSweepingLeftToRight) {
|
||||
SceneCameraMotion camera_motion;
|
||||
|
||||
MP_EXPECT_OK(analyzer.DecideCameraMotionType(GetDefaultKeyFrameCropOptions(),
|
||||
time_span, &scene_summary,
|
||||
time_span, 0, &scene_summary,
|
||||
&camera_motion));
|
||||
|
||||
EXPECT_TRUE(camera_motion.has_sweeping_motion());
|
||||
@@ -258,7 +249,7 @@ TEST(SceneCameraMotionAnalyzerTest, DecideCameraMotionTypeSweepingTopToBottom) {
|
||||
SceneCameraMotion camera_motion;
|
||||
|
||||
MP_EXPECT_OK(analyzer.DecideCameraMotionType(GetDefaultKeyFrameCropOptions(),
|
||||
time_span, &scene_summary,
|
||||
time_span, 0, &scene_summary,
|
||||
&camera_motion));
|
||||
|
||||
EXPECT_TRUE(camera_motion.has_sweeping_motion());
|
||||
@@ -287,7 +278,7 @@ TEST(SceneCameraMotionAnalyzerTest, DecideCameraMotionTypeSweepingCenterRange) {
|
||||
SceneCameraMotion camera_motion;
|
||||
|
||||
MP_EXPECT_OK(analyzer.DecideCameraMotionType(GetDefaultKeyFrameCropOptions(),
|
||||
time_span, &scene_summary,
|
||||
time_span, 0, &scene_summary,
|
||||
&camera_motion));
|
||||
|
||||
EXPECT_TRUE(camera_motion.has_sweeping_motion());
|
||||
@@ -316,7 +307,7 @@ TEST(SceneCameraMotionAnalyzerTest,
|
||||
SceneCameraMotion camera_motion;
|
||||
|
||||
MP_EXPECT_OK(analyzer.DecideCameraMotionType(GetDefaultKeyFrameCropOptions(),
|
||||
kSceneTimeSpanSec,
|
||||
kSceneTimeSpanSec, 0,
|
||||
&scene_summary, &camera_motion));
|
||||
EXPECT_TRUE(camera_motion.has_steady_motion());
|
||||
EXPECT_EQ(camera_motion.steady_motion().steady_look_at_center_x(),
|
||||
@@ -341,7 +332,7 @@ TEST(SceneCameraMotionAnalyzerTest,
|
||||
SceneCameraMotion camera_motion;
|
||||
|
||||
MP_EXPECT_OK(analyzer.DecideCameraMotionType(GetDefaultKeyFrameCropOptions(),
|
||||
kSceneTimeSpanSec,
|
||||
kSceneTimeSpanSec, 0,
|
||||
&scene_summary, &camera_motion));
|
||||
EXPECT_TRUE(camera_motion.has_steady_motion());
|
||||
EXPECT_EQ(camera_motion.steady_motion().steady_look_at_center_x(),
|
||||
@@ -366,7 +357,7 @@ TEST(SceneCameraMotionAnalyzerTest,
|
||||
SceneCameraMotion camera_motion;
|
||||
|
||||
MP_EXPECT_OK(analyzer.DecideCameraMotionType(GetDefaultKeyFrameCropOptions(),
|
||||
kSceneTimeSpanSec,
|
||||
kSceneTimeSpanSec, 0,
|
||||
&scene_summary, &camera_motion));
|
||||
EXPECT_TRUE(camera_motion.has_steady_motion());
|
||||
EXPECT_EQ(camera_motion.steady_motion().steady_look_at_center_x(),
|
||||
@@ -391,8 +382,8 @@ TEST(SceneCameraMotionAnalyzerTest,
|
||||
scene_summary.set_key_frame_center_max_x(frame_center_x);
|
||||
SceneCameraMotion camera_motion;
|
||||
|
||||
MP_EXPECT_OK(analyzer.DecideCameraMotionType(crop_options, kSceneTimeSpanSec,
|
||||
&scene_summary, &camera_motion));
|
||||
MP_EXPECT_OK(analyzer.DecideCameraMotionType(
|
||||
crop_options, kSceneTimeSpanSec, 0, &scene_summary, &camera_motion));
|
||||
EXPECT_TRUE(camera_motion.has_steady_motion());
|
||||
EXPECT_FLOAT_EQ(camera_motion.steady_motion().steady_look_at_center_x(),
|
||||
frame_center_x);
|
||||
@@ -409,7 +400,7 @@ TEST(SceneCameraMotionAnalyzerTest, DecideCameraMotionTypeTracking) {
|
||||
SceneCameraMotion camera_motion;
|
||||
|
||||
MP_EXPECT_OK(analyzer.DecideCameraMotionType(GetDefaultKeyFrameCropOptions(),
|
||||
kSceneTimeSpanSec,
|
||||
kSceneTimeSpanSec, 0,
|
||||
&scene_summary, &camera_motion));
|
||||
EXPECT_TRUE(camera_motion.has_tracking_motion());
|
||||
}
|
||||
@@ -785,9 +776,9 @@ TEST(SceneCameraMotionAnalyzerTest, AnalyzeSceneAndPopulateFocusPointFrames) {
|
||||
std::vector<FocusPointFrame> focus_point_frames;
|
||||
|
||||
MP_EXPECT_OK(analyzer.AnalyzeSceneAndPopulateFocusPointFrames(
|
||||
GetDefaultKeyFrameInfos(), GetDefaultKeyFrameCropOptions(),
|
||||
GetDefaultKeyFrameCropResults(), kSceneFrameWidth, kSceneFrameHeight,
|
||||
GetDefaultSceneFrameTimestamps(), &scene_summary, &focus_point_frames));
|
||||
GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(),
|
||||
kSceneFrameWidth, kSceneFrameHeight, GetDefaultSceneFrameTimestamps(),
|
||||
false, &scene_summary, &focus_point_frames));
|
||||
EXPECT_EQ(scene_summary.num_key_frames(), kNumKeyFrames);
|
||||
EXPECT_EQ(focus_point_frames.size(), kNumSceneFrames);
|
||||
}
|
||||
@@ -803,10 +794,9 @@ TEST(SceneCameraMotionAnalyzerTest,
|
||||
SceneCameraMotion scene_camera_motion;
|
||||
|
||||
MP_EXPECT_OK(analyzer.AnalyzeSceneAndPopulateFocusPointFrames(
|
||||
GetDefaultKeyFrameInfos(), GetDefaultKeyFrameCropOptions(),
|
||||
GetDefaultKeyFrameCropResults(), kSceneFrameWidth, kSceneFrameHeight,
|
||||
GetDefaultSceneFrameTimestamps(), &scene_summary, &focus_point_frames,
|
||||
&scene_camera_motion));
|
||||
GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(),
|
||||
kSceneFrameWidth, kSceneFrameHeight, GetDefaultSceneFrameTimestamps(),
|
||||
false, &scene_summary, &focus_point_frames, &scene_camera_motion));
|
||||
EXPECT_TRUE(scene_camera_motion.has_steady_motion());
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
#include "mediapipe/examples/desktop/autoflip/quality/scene_cropper.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "mediapipe/examples/desktop/autoflip/quality/polynomial_regression_path_solver.h"
|
||||
#include "mediapipe/examples/desktop/autoflip/quality/utils.h"
|
||||
@@ -21,17 +23,74 @@
|
||||
#include "mediapipe/framework/port/ret_check.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
|
||||
// TODO: Parameterize FOV based on camera specs.
|
||||
constexpr float kWidthFieldOfView = 60;
|
||||
|
||||
namespace mediapipe {
|
||||
namespace autoflip {
|
||||
|
||||
::mediapipe::Status SceneCropper::ProcessKinematicPathSolver(
|
||||
const SceneKeyFrameCropSummary& scene_summary,
|
||||
const std::vector<int64>& scene_timestamps,
|
||||
const std::vector<bool>& is_key_frames,
|
||||
const std::vector<FocusPointFrame>& focus_point_frames,
|
||||
const bool continue_last_scene, std::vector<cv::Mat>* all_xforms) {
|
||||
// TODO: Fix upstream calculators to not crop beyond portrait target
|
||||
// value.
|
||||
/*
|
||||
RET_CHECK(scene_summary.scene_frame_height() ==
|
||||
scene_summary.crop_window_height())
|
||||
<< "Kinematic path solver does not yet support horizontal cropping.";
|
||||
*/
|
||||
|
||||
RET_CHECK(scene_timestamps.size() == focus_point_frames.size())
|
||||
<< "Kinematic path solver does not yet support downsampled detections.";
|
||||
|
||||
if (!path_solver_initalized_ || !continue_last_scene) {
|
||||
int min_location = scene_summary.crop_window_width() / 2;
|
||||
int max_location = scene_summary.scene_frame_width() -
|
||||
scene_summary.crop_window_width() / 2;
|
||||
kinematic_path_solver_ = std::make_unique<KinematicPathSolver>(
|
||||
camera_motion_options_.kinematic_options(), min_location, max_location,
|
||||
static_cast<float>(frame_width_) / kWidthFieldOfView);
|
||||
path_solver_initalized_ = true;
|
||||
}
|
||||
int keyframe_counter = 0;
|
||||
for (int i = 0; i < is_key_frames.size(); i++) {
|
||||
if (is_key_frames[i]) {
|
||||
RET_CHECK_EQ(focus_point_frames[keyframe_counter].point().size(), 2)
|
||||
<< "Expected focus_points to equal 2";
|
||||
int observed_x = std::round(
|
||||
focus_point_frames[keyframe_counter].point(0).norm_point_x() *
|
||||
scene_summary.scene_frame_width());
|
||||
MP_RETURN_IF_ERROR(kinematic_path_solver_->AddObservation(
|
||||
observed_x, scene_timestamps[i]));
|
||||
keyframe_counter++;
|
||||
} else {
|
||||
MP_RETURN_IF_ERROR(
|
||||
kinematic_path_solver_->UpdatePrediction(scene_timestamps[i]));
|
||||
}
|
||||
int x_path;
|
||||
MP_RETURN_IF_ERROR(kinematic_path_solver_->GetState(&x_path));
|
||||
cv::Mat transform = cv::Mat::eye(2, 3, CV_32FC1);
|
||||
transform.at<float>(0, 2) =
|
||||
-(x_path - scene_summary.crop_window_width() / 2);
|
||||
all_xforms->push_back(transform);
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status SceneCropper::CropFrames(
|
||||
const SceneKeyFrameCropSummary& scene_summary, const int num_scene_frames,
|
||||
const SceneKeyFrameCropSummary& scene_summary,
|
||||
const std::vector<int64>& scene_timestamps,
|
||||
const std::vector<bool>& is_key_frames,
|
||||
const std::vector<cv::Mat>& scene_frames_or_empty,
|
||||
const std::vector<FocusPointFrame>& focus_point_frames,
|
||||
const std::vector<FocusPointFrame>& prior_focus_point_frames,
|
||||
int top_static_border_size, int bottom_static_border_size,
|
||||
std::vector<cv::Rect>* crop_from_location,
|
||||
std::vector<cv::Mat>* cropped_frames) const {
|
||||
const bool continue_last_scene, std::vector<cv::Rect>* crop_from_location,
|
||||
std::vector<cv::Mat>* cropped_frames) {
|
||||
const int num_scene_frames = scene_timestamps.size();
|
||||
RET_CHECK_GT(num_scene_frames, 0) << "No scene frames.";
|
||||
RET_CHECK_EQ(focus_point_frames.size(), num_scene_frames)
|
||||
<< "Wrong size of FocusPointFrames.";
|
||||
@@ -46,26 +105,50 @@ namespace autoflip {
|
||||
RET_CHECK_LE(crop_height, frame_height)
|
||||
<< "Crop height exceeds frame height.";
|
||||
|
||||
RET_CHECK(camera_motion_options_.has_polynomial_path_solver() ||
|
||||
camera_motion_options_.has_kinematic_options())
|
||||
<< "No camera motion model selected.";
|
||||
|
||||
// Computes transforms.
|
||||
std::vector<cv::Mat> all_xforms;
|
||||
|
||||
PolynomialRegressionPathSolver solver;
|
||||
RET_CHECK_OK(solver.ComputeCameraPath(
|
||||
focus_point_frames, prior_focus_point_frames, frame_width, frame_height,
|
||||
crop_width, crop_height, &all_xforms));
|
||||
std::vector<cv::Mat> scene_frame_xforms;
|
||||
int num_prior = 0;
|
||||
if (camera_motion_options_.has_polynomial_path_solver()) {
|
||||
num_prior = prior_focus_point_frames.size();
|
||||
std::vector<cv::Mat> all_xforms;
|
||||
PolynomialRegressionPathSolver solver;
|
||||
RET_CHECK_OK(solver.ComputeCameraPath(
|
||||
focus_point_frames, prior_focus_point_frames, frame_width, frame_height,
|
||||
crop_width, crop_height, &all_xforms));
|
||||
|
||||
const int num_prior = prior_focus_point_frames.size();
|
||||
std::vector<cv::Mat> scene_frame_xforms(all_xforms.begin() + num_prior,
|
||||
all_xforms.end());
|
||||
scene_frame_xforms =
|
||||
std::vector<cv::Mat>(all_xforms.begin() + num_prior, all_xforms.end());
|
||||
|
||||
// Convert the matrix from center-aligned to upper-left aligned.
|
||||
for (cv::Mat& xform : scene_frame_xforms) {
|
||||
cv::Mat affine_opencv = cv::Mat::eye(2, 3, CV_32FC1);
|
||||
affine_opencv.at<float>(0, 2) =
|
||||
-(xform.at<float>(0, 2) + frame_width / 2 - crop_width / 2);
|
||||
affine_opencv.at<float>(1, 2) =
|
||||
-(xform.at<float>(1, 2) + frame_height / 2 - crop_height / 2);
|
||||
xform = affine_opencv;
|
||||
// Convert the matrix from center-aligned to upper-left aligned.
|
||||
for (cv::Mat& xform : scene_frame_xforms) {
|
||||
cv::Mat affine_opencv = cv::Mat::eye(2, 3, CV_32FC1);
|
||||
affine_opencv.at<float>(0, 2) =
|
||||
-(xform.at<float>(0, 2) + frame_width / 2 - crop_width / 2);
|
||||
affine_opencv.at<float>(1, 2) =
|
||||
-(xform.at<float>(1, 2) + frame_height / 2 - crop_height / 2);
|
||||
xform = affine_opencv;
|
||||
}
|
||||
} else if (camera_motion_options_.has_kinematic_options()) {
|
||||
num_prior = 0;
|
||||
MP_RETURN_IF_ERROR(ProcessKinematicPathSolver(
|
||||
scene_summary, scene_timestamps, is_key_frames, focus_point_frames,
|
||||
continue_last_scene, &scene_frame_xforms));
|
||||
}
|
||||
|
||||
// Store the "crop from" location on the input frame for use with an external
|
||||
// renderer.
|
||||
for (int i = 0; i < num_scene_frames; i++) {
|
||||
const int left = -(scene_frame_xforms[i].at<float>(0, 2));
|
||||
const int right = left + crop_width;
|
||||
const int top = top_static_border_size;
|
||||
const int bottom = frame_height_ - bottom_static_border_size;
|
||||
crop_from_location->push_back(
|
||||
cv::Rect(left, top, right - left, bottom - top));
|
||||
}
|
||||
|
||||
// If no cropped_frames is passed in, return directly.
|
||||
@@ -81,20 +164,6 @@ namespace autoflip {
|
||||
(*cropped_frames)[i] = cv::Mat::zeros(crop_height, crop_width,
|
||||
scene_frames_or_empty[i].type());
|
||||
}
|
||||
|
||||
// Store the "crop from" location on the input frame for use with an external
|
||||
// renderer.
|
||||
for (int i = 0; i < num_scene_frames; i++) {
|
||||
const int left = scene_frame_xforms[i].at<float>(0, 2);
|
||||
const int right = left + crop_width;
|
||||
const int top = top_static_border_size;
|
||||
const int bottom =
|
||||
top_static_border_size +
|
||||
(crop_height - top_static_border_size - bottom_static_border_size);
|
||||
crop_from_location->push_back(
|
||||
cv::Rect(left, top, right - left, bottom - top));
|
||||
}
|
||||
|
||||
return AffineRetarget(cv::Size(crop_width, crop_height),
|
||||
scene_frames_or_empty, scene_frame_xforms,
|
||||
cropped_frames);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "mediapipe/examples/desktop/autoflip/quality/cropping.pb.h"
|
||||
#include "mediapipe/examples/desktop/autoflip/quality/focus_point.pb.h"
|
||||
#include "mediapipe/examples/desktop/autoflip/quality/kinematic_path_solver.h"
|
||||
#include "mediapipe/framework/port/opencv_core_inc.h"
|
||||
#include "mediapipe/framework/port/ret_check.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
@@ -45,7 +46,12 @@ namespace autoflip {
|
||||
// prior_focus_point_frames, &cropped_frames));
|
||||
class SceneCropper {
|
||||
public:
|
||||
SceneCropper() {}
|
||||
SceneCropper(const CameraMotionOptions& camera_motion_options,
|
||||
const int frame_width, const int frame_height)
|
||||
: path_solver_initalized_(false),
|
||||
camera_motion_options_(camera_motion_options),
|
||||
frame_width_(frame_width),
|
||||
frame_height_(frame_height) {}
|
||||
~SceneCropper() {}
|
||||
|
||||
// Computes transformation matrix given SceneKeyFrameCropSummary,
|
||||
@@ -55,13 +61,29 @@ class SceneCropper {
|
||||
// |scene_frames_or_empty| isn't empty.
|
||||
// TODO: split this function into two separate functions.
|
||||
::mediapipe::Status CropFrames(
|
||||
const SceneKeyFrameCropSummary& scene_summary, const int num_scene_frames,
|
||||
const SceneKeyFrameCropSummary& scene_summary,
|
||||
const std::vector<int64>& scene_timestamps,
|
||||
const std::vector<bool>& is_key_frames,
|
||||
const std::vector<cv::Mat>& scene_frames_or_empty,
|
||||
const std::vector<FocusPointFrame>& focus_point_frames,
|
||||
const std::vector<FocusPointFrame>& prior_focus_point_frames,
|
||||
int top_static_border_size, int bottom_static_border_size,
|
||||
std::vector<cv::Rect>* all_scene_frame_xforms,
|
||||
std::vector<cv::Mat>* cropped_frames) const;
|
||||
const bool continue_last_scene, std::vector<cv::Rect>* crop_from_location,
|
||||
std::vector<cv::Mat>* cropped_frames);
|
||||
|
||||
::mediapipe::Status ProcessKinematicPathSolver(
|
||||
const SceneKeyFrameCropSummary& scene_summary,
|
||||
const std::vector<int64>& scene_timestamps,
|
||||
const std::vector<bool>& is_key_frames,
|
||||
const std::vector<FocusPointFrame>& focus_point_frames,
|
||||
const bool continue_last_scene, std::vector<cv::Mat>* all_xforms);
|
||||
|
||||
private:
|
||||
bool path_solver_initalized_;
|
||||
std::unique_ptr<KinematicPathSolver> kinematic_path_solver_;
|
||||
CameraMotionOptions camera_motion_options_;
|
||||
int frame_width_;
|
||||
int frame_height_;
|
||||
};
|
||||
|
||||
} // namespace autoflip
|
||||
|
||||
@@ -71,30 +71,53 @@ std::vector<FocusPointFrame> GetDefaultFocusPointFrames() {
|
||||
return GetFocusPointFrames(kNumSceneFrames);
|
||||
}
|
||||
|
||||
std::vector<int64> GetTimestamps(const int num_frames) {
|
||||
std::vector<int64> timestamps;
|
||||
for (int i = 0; i < num_frames; ++i) {
|
||||
timestamps.push_back(i * 100000);
|
||||
}
|
||||
return timestamps;
|
||||
}
|
||||
|
||||
std::vector<bool> GetIsKeyframe(const int num_frames) {
|
||||
std::vector<bool> is_keyframe;
|
||||
for (int i = 0; i < num_frames; ++i) {
|
||||
is_keyframe.push_back(false);
|
||||
}
|
||||
return is_keyframe;
|
||||
}
|
||||
|
||||
// Checks that CropFrames checks that scene frames size is positive.
|
||||
TEST(SceneCropperTest, CropFramesChecksSceneFramesSize) {
|
||||
SceneCropper scene_cropper;
|
||||
CameraMotionOptions options;
|
||||
options.mutable_polynomial_path_solver()->set_prior_frame_buffer_size(30);
|
||||
SceneCropper scene_cropper(options, kSceneWidth, kSceneHeight);
|
||||
std::vector<cv::Mat> scene_frames(0);
|
||||
std::vector<cv::Mat> cropped_frames;
|
||||
std::vector<cv::Rect> crop_from_locations;
|
||||
const auto status = scene_cropper.CropFrames(
|
||||
GetDefaultSceneKeyFrameCropSummary(), scene_frames.size(), scene_frames,
|
||||
GetDefaultFocusPointFrames(), GetFocusPointFrames(0), 0, 0,
|
||||
GetDefaultSceneKeyFrameCropSummary(), GetTimestamps(scene_frames.size()),
|
||||
GetIsKeyframe(scene_frames.size()), scene_frames,
|
||||
GetDefaultFocusPointFrames(), GetFocusPointFrames(0), 0, 0, false,
|
||||
&crop_from_locations, &cropped_frames);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.ToString(), HasSubstr("No scene frames."));
|
||||
}
|
||||
|
||||
// Checks that CropFrames checks that FocusPointFrames has the right size.
|
||||
|
||||
TEST(SceneCropperTest, CropFramesChecksFocusPointFramesSize) {
|
||||
SceneCropper scene_cropper;
|
||||
CameraMotionOptions options;
|
||||
options.mutable_polynomial_path_solver()->set_prior_frame_buffer_size(30);
|
||||
SceneCropper scene_cropper(options, kSceneWidth, kSceneHeight);
|
||||
std::vector<cv::Mat> cropped_frames;
|
||||
std::vector<cv::Rect> crop_from_locations;
|
||||
const auto& scene_frames = GetDefaultSceneFrames();
|
||||
const auto status = scene_cropper.CropFrames(
|
||||
GetDefaultSceneKeyFrameCropSummary(), scene_frames.size(), scene_frames,
|
||||
GetDefaultSceneKeyFrameCropSummary(), GetTimestamps(kNumSceneFrames),
|
||||
GetIsKeyframe(kNumSceneFrames), scene_frames,
|
||||
GetFocusPointFrames(kNumSceneFrames - 1), GetFocusPointFrames(0), 0, 0,
|
||||
&crop_from_locations, &cropped_frames);
|
||||
false, &crop_from_locations, &cropped_frames);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.ToString(), HasSubstr("Wrong size of FocusPointFrames"));
|
||||
}
|
||||
@@ -103,13 +126,16 @@ TEST(SceneCropperTest, CropFramesChecksFocusPointFramesSize) {
|
||||
TEST(SceneCropperTest, CropFramesChecksCropSizePositive) {
|
||||
auto scene_summary = GetDefaultSceneKeyFrameCropSummary();
|
||||
scene_summary.set_crop_window_width(-1);
|
||||
SceneCropper scene_cropper;
|
||||
CameraMotionOptions options;
|
||||
options.mutable_polynomial_path_solver()->set_prior_frame_buffer_size(30);
|
||||
SceneCropper scene_cropper(options, kSceneWidth, kSceneHeight);
|
||||
std::vector<cv::Mat> cropped_frames;
|
||||
std::vector<cv::Rect> crop_from_locations;
|
||||
const auto& scene_frames = GetDefaultSceneFrames();
|
||||
const auto status = scene_cropper.CropFrames(
|
||||
scene_summary, scene_frames.size(), scene_frames,
|
||||
GetDefaultFocusPointFrames(), GetFocusPointFrames(0), 0, 0,
|
||||
scene_summary, GetTimestamps(kNumSceneFrames),
|
||||
GetIsKeyframe(kNumSceneFrames), scene_frames,
|
||||
GetDefaultFocusPointFrames(), GetFocusPointFrames(0), 0, 0, false,
|
||||
&crop_from_locations, &cropped_frames);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.ToString(), HasSubstr("Crop width is non-positive."));
|
||||
@@ -119,13 +145,16 @@ TEST(SceneCropperTest, CropFramesChecksCropSizePositive) {
|
||||
TEST(SceneCropperTest, InitializesRetargeterChecksCropSizeNotExceedFrameSize) {
|
||||
auto scene_summary = GetDefaultSceneKeyFrameCropSummary();
|
||||
scene_summary.set_crop_window_height(kSceneHeight + 1);
|
||||
SceneCropper scene_cropper;
|
||||
CameraMotionOptions options;
|
||||
options.mutable_polynomial_path_solver()->set_prior_frame_buffer_size(30);
|
||||
SceneCropper scene_cropper(options, kSceneWidth, kSceneHeight);
|
||||
std::vector<cv::Mat> cropped_frames;
|
||||
std::vector<cv::Rect> crop_from_locations;
|
||||
const auto& scene_frames = GetDefaultSceneFrames();
|
||||
const auto status = scene_cropper.CropFrames(
|
||||
scene_summary, scene_frames.size(), scene_frames,
|
||||
GetDefaultFocusPointFrames(), GetFocusPointFrames(0), 0, 0,
|
||||
scene_summary, GetTimestamps(kNumSceneFrames),
|
||||
GetIsKeyframe(kNumSceneFrames), scene_frames,
|
||||
GetDefaultFocusPointFrames(), GetFocusPointFrames(0), 0, 0, false,
|
||||
&crop_from_locations, &cropped_frames);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.ToString(),
|
||||
@@ -134,13 +163,16 @@ TEST(SceneCropperTest, InitializesRetargeterChecksCropSizeNotExceedFrameSize) {
|
||||
|
||||
// Checks that CropFrames works when there are not any prior FocusPointFrames.
|
||||
TEST(SceneCropperTest, CropFramesWorksWithoutPriorFocusPointFrames) {
|
||||
SceneCropper scene_cropper;
|
||||
CameraMotionOptions options;
|
||||
options.mutable_polynomial_path_solver()->set_prior_frame_buffer_size(30);
|
||||
SceneCropper scene_cropper(options, kSceneWidth, kSceneHeight);
|
||||
std::vector<cv::Mat> cropped_frames;
|
||||
std::vector<cv::Rect> crop_from_locations;
|
||||
const auto& scene_frames = GetDefaultSceneFrames();
|
||||
MP_ASSERT_OK(scene_cropper.CropFrames(
|
||||
GetDefaultSceneKeyFrameCropSummary(), scene_frames.size(), scene_frames,
|
||||
GetDefaultFocusPointFrames(), GetFocusPointFrames(0), 0, 0,
|
||||
GetDefaultSceneKeyFrameCropSummary(), GetTimestamps(kNumSceneFrames),
|
||||
GetIsKeyframe(kNumSceneFrames), scene_frames,
|
||||
GetDefaultFocusPointFrames(), GetFocusPointFrames(0), 0, 0, false,
|
||||
&crop_from_locations, &cropped_frames));
|
||||
ASSERT_EQ(cropped_frames.size(), kNumSceneFrames);
|
||||
for (int i = 0; i < kNumSceneFrames; ++i) {
|
||||
@@ -151,13 +183,16 @@ TEST(SceneCropperTest, CropFramesWorksWithoutPriorFocusPointFrames) {
|
||||
|
||||
// Checks that CropFrames works when there are prior FocusPointFrames.
|
||||
TEST(SceneCropperTest, CropFramesWorksWithPriorFocusPointFrames) {
|
||||
SceneCropper scene_cropper;
|
||||
CameraMotionOptions options;
|
||||
options.mutable_polynomial_path_solver()->set_prior_frame_buffer_size(30);
|
||||
SceneCropper scene_cropper(options, kSceneWidth, kSceneHeight);
|
||||
std::vector<cv::Mat> cropped_frames;
|
||||
std::vector<cv::Rect> crop_from_locations;
|
||||
const auto& scene_frames = GetDefaultSceneFrames();
|
||||
MP_EXPECT_OK(scene_cropper.CropFrames(
|
||||
GetDefaultSceneKeyFrameCropSummary(), scene_frames.size(), scene_frames,
|
||||
GetDefaultFocusPointFrames(), GetFocusPointFrames(3), 0, 0,
|
||||
GetDefaultSceneKeyFrameCropSummary(), GetTimestamps(scene_frames.size()),
|
||||
GetIsKeyframe(scene_frames.size()), scene_frames,
|
||||
GetDefaultFocusPointFrames(), GetFocusPointFrames(3), 0, 0, false,
|
||||
&crop_from_locations, &cropped_frames));
|
||||
EXPECT_EQ(cropped_frames.size(), kNumSceneFrames);
|
||||
for (int i = 0; i < kNumSceneFrames; ++i) {
|
||||
|
||||
@@ -133,6 +133,42 @@ const cv::Scalar kWhite = cv::Scalar(255.0, 255.0, 255.0); // others
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
namespace {
|
||||
cv::Rect LimitBounds(const cv::Rect& rect, const int max_width,
|
||||
const int max_height) {
|
||||
cv::Rect result;
|
||||
result.x = fmax(rect.x, 0);
|
||||
result.y = fmax(rect.y, 0);
|
||||
result.width =
|
||||
result.x + rect.width >= max_width ? max_width - result.x : rect.width;
|
||||
result.height = result.y + rect.height >= max_height ? max_height - result.y
|
||||
: rect.height;
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
::mediapipe::Status DrawDetectionAndFramingWindow(
|
||||
const std::vector<cv::Mat>& org_scene_frames,
|
||||
const std::vector<cv::Rect>& crop_from_locations,
|
||||
const ImageFormat::Format image_format, const float overlay_opacity,
|
||||
std::vector<std::unique_ptr<ImageFrame>>* viz_frames) {
|
||||
for (int i = 0; i < org_scene_frames.size(); i++) {
|
||||
const auto& scene_frame = org_scene_frames[i];
|
||||
auto viz_frame = absl::make_unique<ImageFrame>(
|
||||
image_format, scene_frame.cols, scene_frame.rows);
|
||||
cv::Mat darkened = formats::MatView(viz_frame.get());
|
||||
scene_frame.copyTo(darkened);
|
||||
cv::Mat overlay = cv::Mat::zeros(darkened.size(), darkened.type());
|
||||
cv::addWeighted(overlay, overlay_opacity, darkened, 1 - overlay_opacity, 0,
|
||||
darkened);
|
||||
const auto& crop_from_bounded =
|
||||
LimitBounds(crop_from_locations[i], scene_frame.cols, scene_frame.rows);
|
||||
scene_frame(crop_from_bounded).copyTo(darkened(crop_from_bounded));
|
||||
viz_frames->push_back(std::move(viz_frame));
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status DrawFocusPointAndCropWindow(
|
||||
const std::vector<cv::Mat>& scene_frames,
|
||||
const std::vector<FocusPointFrame>& focus_point_frames,
|
||||
|
||||
@@ -55,6 +55,14 @@ namespace autoflip {
|
||||
const mediapipe::ImageFormat::Format image_format,
|
||||
std::vector<std::unique_ptr<ImageFrame>>* viz_frames);
|
||||
|
||||
// Draws the final smoothed path of the camera retargeter by darkening the
|
||||
// removed areas.
|
||||
::mediapipe::Status DrawDetectionAndFramingWindow(
|
||||
const std::vector<cv::Mat>& org_scene_frames,
|
||||
const std::vector<cv::Rect>& crop_from_locations,
|
||||
const ImageFormat::Format image_format, const float overlay_opacity,
|
||||
std::vector<std::unique_ptr<ImageFrame>>* viz_frames);
|
||||
|
||||
} // namespace autoflip
|
||||
} // namespace mediapipe
|
||||
|
||||
|
||||
@@ -203,7 +203,6 @@ void RectUnion(const Rect& rect_to_add, Rect* rect) {
|
||||
}
|
||||
|
||||
::mediapipe::Status AggregateKeyFrameResults(
|
||||
const std::vector<KeyFrameInfo>& key_frame_infos,
|
||||
const KeyFrameCropOptions& key_frame_crop_options,
|
||||
const std::vector<KeyFrameCropResult>& key_frame_crop_results,
|
||||
const int scene_frame_width, const int scene_frame_height,
|
||||
@@ -211,11 +210,7 @@ void RectUnion(const Rect& rect_to_add, Rect* rect) {
|
||||
RET_CHECK_NE(scene_summary, nullptr)
|
||||
<< "Output SceneKeyFrameCropSummary is null.";
|
||||
|
||||
const int num_key_frames = key_frame_infos.size();
|
||||
RET_CHECK_EQ(num_key_frames, key_frame_crop_results.size())
|
||||
<< "Inconsistent number of key frames:"
|
||||
<< " num_key_frames = " << num_key_frames
|
||||
<< " key_frame_crop_results.size() = " << key_frame_crop_results.size();
|
||||
const int num_key_frames = key_frame_crop_results.size();
|
||||
|
||||
RET_CHECK_GT(scene_frame_width, 0) << "Non-positive frame width.";
|
||||
RET_CHECK_GT(scene_frame_height, 0) << "Non-positive frame height.";
|
||||
@@ -255,8 +250,8 @@ void RectUnion(const Rect& rect_to_add, Rect* rect) {
|
||||
std::unique_ptr<Rect> required_crop_region_union = nullptr;
|
||||
for (int i = 0; i < num_key_frames; ++i) {
|
||||
auto* key_frame_compact_info = scene_summary->add_key_frame_compact_infos();
|
||||
key_frame_compact_info->set_timestamp_ms(key_frame_infos[i].timestamp_ms());
|
||||
const auto& result = key_frame_crop_results[i];
|
||||
key_frame_compact_info->set_timestamp_ms(result.timestamp_ms());
|
||||
if (result.are_required_regions_covered_in_target_size()) {
|
||||
num_success_frames++;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ namespace autoflip {
|
||||
// Aggregates information from KeyFrameInfos and KeyFrameCropResults into
|
||||
// SceneKeyFrameCropSummary.
|
||||
::mediapipe::Status AggregateKeyFrameResults(
|
||||
const std::vector<KeyFrameInfo>& key_frame_infos,
|
||||
const KeyFrameCropOptions& key_frame_crop_options,
|
||||
const std::vector<KeyFrameCropResult>& key_frame_crop_results,
|
||||
const int scene_frame_width, const int scene_frame_height,
|
||||
|
||||
@@ -113,16 +113,6 @@ void AddDetectionFromScoreAndIsRequired(const double score,
|
||||
detection->set_is_required(is_required);
|
||||
}
|
||||
|
||||
// Returns default values for KeyFrameInfos. Populates timestamps using the
|
||||
// default spacing kKeyFrameTimestampDiff starting from 0.
|
||||
std::vector<KeyFrameInfo> GetDefaultKeyFrameInfos() {
|
||||
std::vector<KeyFrameInfo> key_frame_infos(kNumKeyFrames);
|
||||
for (int i = 0; i < kNumKeyFrames; ++i) {
|
||||
key_frame_infos[i].set_timestamp_ms(kKeyFrameTimestampDiff * i);
|
||||
}
|
||||
return key_frame_infos;
|
||||
}
|
||||
|
||||
// Returns default settings for KeyFrameCropOptions. Populates target size to be
|
||||
// the default target size.
|
||||
KeyFrameCropOptions GetDefaultKeyFrameCropOptions() {
|
||||
@@ -148,6 +138,7 @@ std::vector<KeyFrameCropResult> GetDefaultKeyFrameCropResults() {
|
||||
*(key_frame_crop_results[i].mutable_required_region()) =
|
||||
MakeRect(10, 10, 20, 20);
|
||||
key_frame_crop_results[i].set_region_score(1.0);
|
||||
key_frame_crop_results[i].set_timestamp_ms(kKeyFrameTimestampDiff * i);
|
||||
}
|
||||
return key_frame_crop_results;
|
||||
}
|
||||
@@ -502,9 +493,8 @@ TEST(UtilTest, SetKeyFrameCropTargetSetsTargetSizeCorrectly) {
|
||||
// Checks that AggregateKeyFrameResults checks output pointer is not null.
|
||||
TEST(UtilTest, AggregateKeyFrameResultsChecksOutputNotNull) {
|
||||
const auto status = AggregateKeyFrameResults(
|
||||
GetDefaultKeyFrameInfos(), GetDefaultKeyFrameCropOptions(),
|
||||
GetDefaultKeyFrameCropResults(), kOriginalWidth, kOriginalHeight,
|
||||
nullptr);
|
||||
GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(),
|
||||
kOriginalWidth, kOriginalHeight, nullptr);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.ToString(),
|
||||
HasSubstr("Output SceneKeyFrameCropSummary is null."));
|
||||
@@ -512,36 +502,20 @@ TEST(UtilTest, AggregateKeyFrameResultsChecksOutputNotNull) {
|
||||
|
||||
// Checks that AggregateKeyFrameResults handles the case of no key frames.
|
||||
TEST(UtilTest, AggregateKeyFrameResultsHandlesNoKeyFrames) {
|
||||
std::vector<KeyFrameInfo> key_frame_infos(0);
|
||||
std::vector<KeyFrameCropResult> key_frame_crop_results(0);
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(
|
||||
key_frame_infos, GetDefaultKeyFrameCropOptions(), key_frame_crop_results,
|
||||
kOriginalWidth, kOriginalHeight, &scene_summary));
|
||||
}
|
||||
|
||||
// Checks that AggregateKeyFrameResults checks that number of key frames is
|
||||
// consistent between KeyFrameInfos and KeyFrameCropResults.
|
||||
TEST(UtilTest, AggregateKeyFrameResultsChecksNumKeyFramesConsistent) {
|
||||
std::vector<KeyFrameInfo> key_frame_infos(kNumKeyFrames);
|
||||
std::vector<KeyFrameCropResult> key_frame_crop_results(kNumKeyFrames + 1);
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
|
||||
const auto status = AggregateKeyFrameResults(
|
||||
key_frame_infos, GetDefaultKeyFrameCropOptions(), key_frame_crop_results,
|
||||
kOriginalWidth, kOriginalHeight, &scene_summary);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.ToString(),
|
||||
HasSubstr("Inconsistent number of key frames"));
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth,
|
||||
kOriginalHeight, &scene_summary));
|
||||
}
|
||||
|
||||
// Checks that AggregateKeyFrameResults checks that frame size is valid.
|
||||
TEST(UtilTest, AggregateKeyFrameResultsChecksFrameSizeValid) {
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
const auto status = AggregateKeyFrameResults(
|
||||
GetDefaultKeyFrameInfos(), GetDefaultKeyFrameCropOptions(),
|
||||
GetDefaultKeyFrameCropResults(), kOriginalWidth, 0, &scene_summary);
|
||||
GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(),
|
||||
kOriginalWidth, 0, &scene_summary);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.ToString(), HasSubstr("Non-positive frame height."));
|
||||
}
|
||||
@@ -553,9 +527,8 @@ TEST(UtilTest, AggregateKeyFrameResultsChecksTargetSizeValid) {
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
|
||||
const auto status = AggregateKeyFrameResults(
|
||||
GetDefaultKeyFrameInfos(), key_frame_crop_options,
|
||||
GetDefaultKeyFrameCropResults(), kOriginalWidth, kOriginalHeight,
|
||||
&scene_summary);
|
||||
key_frame_crop_options, GetDefaultKeyFrameCropResults(), kOriginalWidth,
|
||||
kOriginalHeight, &scene_summary);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.ToString(), HasSubstr("Non-positive target width."));
|
||||
}
|
||||
@@ -568,9 +541,8 @@ TEST(UtilTest, AggregateKeyFrameResultsChecksTargetSizeNotExceedFrameSize) {
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
|
||||
const auto status = AggregateKeyFrameResults(
|
||||
GetDefaultKeyFrameInfos(), key_frame_crop_options,
|
||||
GetDefaultKeyFrameCropResults(), kOriginalWidth, kOriginalHeight,
|
||||
&scene_summary);
|
||||
key_frame_crop_options, GetDefaultKeyFrameCropResults(), kOriginalWidth,
|
||||
kOriginalHeight, &scene_summary);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.ToString(),
|
||||
HasSubstr("Target width exceeds frame width."));
|
||||
@@ -578,19 +550,19 @@ TEST(UtilTest, AggregateKeyFrameResultsChecksTargetSizeNotExceedFrameSize) {
|
||||
|
||||
// Checks that AggregateKeyFrameResults packs KeyFrameCompactInfos.
|
||||
TEST(UtilTest, AggregateKeyFrameResultsPacksKeyFrameCompactInfos) {
|
||||
const auto key_frame_infos = GetDefaultKeyFrameInfos();
|
||||
const auto key_frame_crop_results = GetDefaultKeyFrameCropResults();
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(
|
||||
key_frame_infos, GetDefaultKeyFrameCropOptions(), key_frame_crop_results,
|
||||
kOriginalWidth, kOriginalHeight, &scene_summary));
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth,
|
||||
kOriginalHeight, &scene_summary));
|
||||
|
||||
EXPECT_EQ(scene_summary.num_key_frames(), kNumKeyFrames);
|
||||
EXPECT_EQ(scene_summary.key_frame_compact_infos_size(), kNumKeyFrames);
|
||||
for (int i = 0; i < kNumKeyFrames; ++i) {
|
||||
const auto& compact_info = scene_summary.key_frame_compact_infos(i);
|
||||
EXPECT_EQ(compact_info.timestamp_ms(), key_frame_infos[i].timestamp_ms());
|
||||
EXPECT_EQ(compact_info.timestamp_ms(),
|
||||
key_frame_crop_results[i].timestamp_ms());
|
||||
const auto center = RectCenter(key_frame_crop_results[i].region());
|
||||
EXPECT_FLOAT_EQ(compact_info.center_x(), center.first);
|
||||
EXPECT_FLOAT_EQ(compact_info.center_y(), center.second);
|
||||
@@ -602,7 +574,6 @@ TEST(UtilTest, AggregateKeyFrameResultsPacksKeyFrameCompactInfos) {
|
||||
// Checks that AggregateKeyFrameResults ensures the centered region of target
|
||||
// size fits in frame bound.
|
||||
TEST(UtilTest, AggregateKeyFrameResultsEnsuresCropRegionFitsInFrame) {
|
||||
std::vector<KeyFrameInfo> key_frame_infos(1);
|
||||
std::vector<KeyFrameCropResult> key_frame_crop_results(1);
|
||||
auto* crop_region = key_frame_crop_results[0].mutable_region();
|
||||
crop_region->set_x(0);
|
||||
@@ -611,9 +582,9 @@ TEST(UtilTest, AggregateKeyFrameResultsEnsuresCropRegionFitsInFrame) {
|
||||
crop_region->set_height(10);
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(
|
||||
key_frame_infos, GetDefaultKeyFrameCropOptions(), key_frame_crop_results,
|
||||
kOriginalWidth, kOriginalHeight, &scene_summary));
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth,
|
||||
kOriginalHeight, &scene_summary));
|
||||
|
||||
EXPECT_EQ(scene_summary.crop_window_width(), kTargetWidth);
|
||||
EXPECT_EQ(scene_summary.crop_window_height(), kTargetHeight);
|
||||
@@ -638,14 +609,13 @@ TEST(UtilTest, AggregateKeyFrameResultsEnsuresCropRegionFitsInFrame) {
|
||||
// frames with empty regions.
|
||||
TEST(UtilTest,
|
||||
AggregateKeyFrameResultsSetsMinusOneForKeyFramesWithEmptyRegions) {
|
||||
std::vector<KeyFrameInfo> key_frame_infos(1);
|
||||
std::vector<KeyFrameCropResult> key_frame_crop_results(1);
|
||||
key_frame_crop_results[0].set_region_is_empty(true);
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(
|
||||
key_frame_infos, GetDefaultKeyFrameCropOptions(), key_frame_crop_results,
|
||||
kOriginalWidth, kOriginalHeight, &scene_summary));
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth,
|
||||
kOriginalHeight, &scene_summary));
|
||||
|
||||
const auto& compact_info = scene_summary.key_frame_compact_infos(0);
|
||||
EXPECT_FLOAT_EQ(compact_info.center_x(), -1.0f);
|
||||
@@ -661,8 +631,8 @@ TEST(UtilTest, AggregateKeyFrameResultsRejectsNegativeCenter) {
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
|
||||
const auto status = AggregateKeyFrameResults(
|
||||
GetDefaultKeyFrameInfos(), GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth, kOriginalHeight, &scene_summary);
|
||||
GetDefaultKeyFrameCropOptions(), key_frame_crop_results, kOriginalWidth,
|
||||
kOriginalHeight, &scene_summary);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.ToString(), HasSubstr("Negative vertical center."));
|
||||
}
|
||||
@@ -674,8 +644,8 @@ TEST(UtilTest, AggregateKeyFrameResultsRejectsNegativeScore) {
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
|
||||
const auto status = AggregateKeyFrameResults(
|
||||
GetDefaultKeyFrameInfos(), GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth, kOriginalHeight, &scene_summary);
|
||||
GetDefaultKeyFrameCropOptions(), key_frame_crop_results, kOriginalWidth,
|
||||
kOriginalHeight, &scene_summary);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.ToString(), HasSubstr("Negative score."));
|
||||
}
|
||||
@@ -693,9 +663,9 @@ TEST(UtilTest, AggregateKeyFrameResultsSetsCenterRanges) {
|
||||
}
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(
|
||||
GetDefaultKeyFrameInfos(), GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth, kOriginalHeight, &scene_summary));
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth,
|
||||
kOriginalHeight, &scene_summary));
|
||||
|
||||
EXPECT_FLOAT_EQ(scene_summary.key_frame_center_min_x(), 25.0f);
|
||||
EXPECT_FLOAT_EQ(scene_summary.key_frame_center_max_x(), 45.0f);
|
||||
@@ -712,9 +682,9 @@ TEST(UtilTest, AggregateKeyFrameResultsSetsScoreRange) {
|
||||
}
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(
|
||||
GetDefaultKeyFrameInfos(), GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth, kOriginalHeight, &scene_summary));
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth,
|
||||
kOriginalHeight, &scene_summary));
|
||||
|
||||
EXPECT_FLOAT_EQ(scene_summary.key_frame_min_score(),
|
||||
*std::min_element(scores.begin(), scores.end()));
|
||||
@@ -727,9 +697,8 @@ TEST(UtilTest, AggregateKeyFrameResultsSetsScoreRange) {
|
||||
TEST(UtilTest, AggregateKeyFrameResultsSetsCropWindowSizeToTargetSize) {
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(
|
||||
GetDefaultKeyFrameInfos(), GetDefaultKeyFrameCropOptions(),
|
||||
GetDefaultKeyFrameCropResults(), kOriginalWidth, kOriginalHeight,
|
||||
&scene_summary));
|
||||
GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(),
|
||||
kOriginalWidth, kOriginalHeight, &scene_summary));
|
||||
EXPECT_EQ(scene_summary.crop_window_width(), kTargetWidth);
|
||||
EXPECT_EQ(scene_summary.crop_window_height(), kTargetHeight);
|
||||
}
|
||||
@@ -741,9 +710,9 @@ TEST(UtilTest, AggregateKeyFrameResultsSetsCropWindowSizeExceedingTargetSize) {
|
||||
key_frame_crop_results[0].mutable_region()->set_width(kTargetWidth + 1);
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(
|
||||
GetDefaultKeyFrameInfos(), GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth, kOriginalHeight, &scene_summary));
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth,
|
||||
kOriginalHeight, &scene_summary));
|
||||
EXPECT_EQ(scene_summary.crop_window_width(), kTargetWidth + 1);
|
||||
EXPECT_EQ(scene_summary.crop_window_height(), kTargetHeight);
|
||||
}
|
||||
@@ -753,9 +722,8 @@ TEST(UtilTest, AggregateKeyFrameResultsSetsCropWindowSizeExceedingTargetSize) {
|
||||
TEST(UtilTest, AggregateKeyFrameResultsSetsHasSalientRegionTrue) {
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(
|
||||
GetDefaultKeyFrameInfos(), GetDefaultKeyFrameCropOptions(),
|
||||
GetDefaultKeyFrameCropResults(), kOriginalWidth, kOriginalHeight,
|
||||
&scene_summary));
|
||||
GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(),
|
||||
kOriginalWidth, kOriginalHeight, &scene_summary));
|
||||
EXPECT_TRUE(scene_summary.has_salient_region());
|
||||
}
|
||||
|
||||
@@ -768,9 +736,9 @@ TEST(UtilTest, AggregateKeyFrameResultsSetsHasSalientRegionFalse) {
|
||||
}
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(
|
||||
GetDefaultKeyFrameInfos(), GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth, kOriginalHeight, &scene_summary));
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth,
|
||||
kOriginalHeight, &scene_summary));
|
||||
EXPECT_FALSE(scene_summary.has_salient_region());
|
||||
}
|
||||
|
||||
@@ -779,9 +747,8 @@ TEST(UtilTest, AggregateKeyFrameResultsSetsHasSalientRegionFalse) {
|
||||
TEST(UtilTest, AggregateKeyFrameResultsSetsHasRequiredSalientRegionTrue) {
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(
|
||||
GetDefaultKeyFrameInfos(), GetDefaultKeyFrameCropOptions(),
|
||||
GetDefaultKeyFrameCropResults(), kOriginalWidth, kOriginalHeight,
|
||||
&scene_summary));
|
||||
GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(),
|
||||
kOriginalWidth, kOriginalHeight, &scene_summary));
|
||||
EXPECT_TRUE(scene_summary.has_required_salient_region());
|
||||
}
|
||||
|
||||
@@ -794,9 +761,9 @@ TEST(UtilTest, AggregateKeyFrameResultsSetsHasRequiredSalientRegionFalse) {
|
||||
}
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(
|
||||
GetDefaultKeyFrameInfos(), GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth, kOriginalHeight, &scene_summary));
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth,
|
||||
kOriginalHeight, &scene_summary));
|
||||
EXPECT_FALSE(scene_summary.has_required_salient_region());
|
||||
}
|
||||
|
||||
@@ -810,9 +777,9 @@ TEST(UtilTest, AggregateKeyFrameResultsSetsKeyFrameRequiredCropRegionUnion) {
|
||||
}
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(
|
||||
GetDefaultKeyFrameInfos(), GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth, kOriginalHeight, &scene_summary));
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth,
|
||||
kOriginalHeight, &scene_summary));
|
||||
const auto& required_crop_region_union =
|
||||
scene_summary.key_frame_required_crop_region_union();
|
||||
EXPECT_EQ(required_crop_region_union.x(), 0);
|
||||
@@ -832,9 +799,9 @@ TEST(UtilTest, AggregateKeyFrameResultsSetsFrameSuccessRate) {
|
||||
}
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(
|
||||
GetDefaultKeyFrameInfos(), GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth, kOriginalHeight, &scene_summary));
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth,
|
||||
kOriginalHeight, &scene_summary));
|
||||
EXPECT_FLOAT_EQ(scene_summary.frame_success_rate(), success_rate);
|
||||
}
|
||||
|
||||
@@ -853,9 +820,9 @@ TEST(UtilTest, AggregateKeyFrameResultsSetsMotion) {
|
||||
}
|
||||
SceneKeyFrameCropSummary scene_summary;
|
||||
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(
|
||||
GetDefaultKeyFrameInfos(), GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth, kOriginalHeight, &scene_summary));
|
||||
MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(),
|
||||
key_frame_crop_results, kOriginalWidth,
|
||||
kOriginalHeight, &scene_summary));
|
||||
EXPECT_FLOAT_EQ(scene_summary.horizontal_motion_amount(), motion_x);
|
||||
EXPECT_FLOAT_EQ(scene_summary.vertical_motion_amount(), motion_y);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,23 @@ mediapipe_simple_subgraph(
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_simple_subgraph(
|
||||
name = "autoflip_front_face_detection_subgraph",
|
||||
graph = "front_face_detection_subgraph.pbtxt",
|
||||
register_as = "AutoFlipFrontFaceDetectionSubgraph",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//mediapipe/calculators/image:image_transformation_calculator",
|
||||
"//mediapipe/calculators/tflite:ssd_anchors_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_converter_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_inference_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_tensors_to_detections_calculator",
|
||||
"//mediapipe/calculators/util:detection_label_id_to_text_calculator",
|
||||
"//mediapipe/calculators/util:detection_letterbox_removal_calculator",
|
||||
"//mediapipe/calculators/util:non_max_suppression_calculator",
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_simple_subgraph(
|
||||
name = "autoflip_object_detection_subgraph",
|
||||
graph = "autoflip_object_detection_subgraph.pbtxt",
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
# MediaPipe graph that performs face detection with TensorFlow Lite on CPU. Model paths setup for web use.
|
||||
# TODO: parameterize input paths to support desktop use.
|
||||
input_stream: "VIDEO:input_video"
|
||||
output_stream: "DETECTIONS:output_detections"
|
||||
|
||||
# Transforms the input image on CPU to a 128x128 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:input_video"
|
||||
output_stream: "IMAGE:transformed_input_video_cpu"
|
||||
output_stream: "LETTERBOX_PADDING:letterbox_padding"
|
||||
options: {
|
||||
[mediapipe.ImageTransformationCalculatorOptions.ext] {
|
||||
output_width: 128
|
||||
output_height: 128
|
||||
scale_mode: FIT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Converts the transformed input image on CPU into an image tensor stored as a
|
||||
# TfLiteTensor.
|
||||
node {
|
||||
calculator: "TfLiteConverterCalculator"
|
||||
input_stream: "IMAGE:transformed_input_video_cpu"
|
||||
output_stream: "TENSORS:image_tensor"
|
||||
}
|
||||
|
||||
# Runs a TensorFlow Lite model on CPU 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:image_tensor"
|
||||
output_stream: "TENSORS:detection_tensors"
|
||||
options: {
|
||||
[mediapipe.TfLiteInferenceCalculatorOptions.ext] {
|
||||
model_path: "face_detection_front.tflite"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Generates a single side packet containing a vector of SSD anchors based on
|
||||
# the specification in the options.
|
||||
node {
|
||||
calculator: "SsdAnchorsCalculator"
|
||||
output_side_packet: "anchors"
|
||||
options: {
|
||||
[mediapipe.SsdAnchorsCalculatorOptions.ext] {
|
||||
num_layers: 4
|
||||
min_scale: 0.1484375
|
||||
max_scale: 0.75
|
||||
input_size_height: 128
|
||||
input_size_width: 128
|
||||
anchor_offset_x: 0.5
|
||||
anchor_offset_y: 0.5
|
||||
strides: 8
|
||||
strides: 16
|
||||
strides: 16
|
||||
strides: 16
|
||||
aspect_ratios: 1.0
|
||||
fixed_anchor_size: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Decodes the detection tensors generated by the TensorFlow Lite model, based on
|
||||
# the SSD anchors and the specification in the options, into a vector of
|
||||
# detections. Each detection describes a detected object.
|
||||
node {
|
||||
calculator: "TfLiteTensorsToDetectionsCalculator"
|
||||
input_stream: "TENSORS:detection_tensors"
|
||||
input_side_packet: "ANCHORS:anchors"
|
||||
output_stream: "DETECTIONS:detections"
|
||||
options: {
|
||||
[mediapipe.TfLiteTensorsToDetectionsCalculatorOptions.ext] {
|
||||
num_classes: 1
|
||||
num_boxes: 896
|
||||
num_coords: 16
|
||||
box_coord_offset: 0
|
||||
keypoint_coord_offset: 4
|
||||
num_keypoints: 6
|
||||
num_values_per_keypoint: 2
|
||||
sigmoid_score: true
|
||||
score_clipping_thresh: 100.0
|
||||
reverse_output_order: true
|
||||
x_scale: 128.0
|
||||
y_scale: 128.0
|
||||
h_scale: 128.0
|
||||
w_scale: 128.0
|
||||
min_score_thresh: 0.75
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Performs non-max suppression to remove excessive detections.
|
||||
node {
|
||||
calculator: "NonMaxSuppressionCalculator"
|
||||
input_stream: "detections"
|
||||
output_stream: "filtered_detections"
|
||||
options: {
|
||||
[mediapipe.NonMaxSuppressionCalculatorOptions.ext] {
|
||||
min_suppression_threshold: 0.3
|
||||
overlap_type: INTERSECTION_OVER_UNION
|
||||
algorithm: WEIGHTED
|
||||
return_empty_detections: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Maps detection label IDs to the corresponding label text ("Face"). The label
|
||||
# map is provided in the label_map_path option.
|
||||
node {
|
||||
calculator: "DetectionLabelIdToTextCalculator"
|
||||
input_stream: "filtered_detections"
|
||||
output_stream: "labeled_detections"
|
||||
options: {
|
||||
[mediapipe.DetectionLabelIdToTextCalculatorOptions.ext] {
|
||||
label_map_path: "face_detection_front_labelmap.txt"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Adjusts detection locations (already normalized to [0.f, 1.f]) on the
|
||||
# letterboxed image (after image transformation with the FIT scale mode) to the
|
||||
# corresponding locations on the same image with the letterbox removed (the
|
||||
# input image to the graph before image transformation).
|
||||
node {
|
||||
calculator: "DetectionLetterboxRemovalCalculator"
|
||||
input_stream: "DETECTIONS:labeled_detections"
|
||||
input_stream: "LETTERBOX_PADDING:letterbox_padding"
|
||||
output_stream: "DETECTIONS:output_detections"
|
||||
}
|
||||
Reference in New Issue
Block a user