mediapipe/mediapipe2/examples/desktop/autoflip/quality/utils_test.cc
2021-06-10 23:01:19 +00:00

1040 lines
44 KiB
C++

// 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/utils.h"
#include <random>
#include <vector>
#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h"
#include "mediapipe/examples/desktop/autoflip/quality/cropping.pb.h"
#include "mediapipe/framework/port/gmock.h"
#include "mediapipe/framework/port/gtest.h"
#include "mediapipe/framework/port/integral_types.h"
#include "mediapipe/framework/port/opencv_core_inc.h"
#include "mediapipe/framework/port/status_matchers.h"
namespace mediapipe {
namespace autoflip {
namespace {
using ::testing::HasSubstr;
const int64 kTimestamp = 0;
const int kOriginalWidth = 100;
const int kOriginalHeight = 100;
const double kTargetAspectRatio = 1.5;
const int kNumKeyFrames = 5;
const int64 kKeyFrameTimestampDiff = 1e6 / kNumKeyFrames;
const int kTargetWidth = 50;
const int kTargetHeight = 50;
// Makes a rectangle given the corner (x, y) and the size (width, height).
Rect MakeRect(const int x, const int y, const int width, const int height) {
Rect rect;
rect.set_x(x);
rect.set_y(y);
rect.set_width(width);
rect.set_height(height);
return rect;
}
// Makes a rectangle given the center (center_x, center_y) and the half size
// (half_width, half_height).
Rect MakeRectFromCenterAndHalfSize(const float center_x, const float center_y,
const float half_width,
const float half_height) {
Rect rect;
rect.set_x(center_x - half_width);
rect.set_y(center_y - half_height);
rect.set_width(half_width * 2);
rect.set_height(half_height * 2);
return rect;
}
// Computes the center of a rectangle as a pair (center_x, center_y).
std::pair<float, float> RectCenter(const Rect& rect) {
return std::make_pair(rect.x() + rect.width() / 2.0,
rect.y() + rect.height() / 2.0);
}
// Makes a normalized rectangle given the corner (x, y) and the size (width,
// height).
RectF MakeRectF(const double x, const double y, const double width,
const double height) {
RectF rectf;
rectf.set_x(x);
rectf.set_y(y);
rectf.set_width(width);
rectf.set_height(height);
return rectf;
}
// Adds a detection to the detection set given location.
void AddDetectionFromLocation(const Rect& loc, DetectionSet* detections) {
auto* detection = detections->add_detections();
*(detection->mutable_location()) = loc;
}
// Adds a detection to the detection set given normalized location.
void AddDetectionFromNormalizedLocation(const RectF& normalized_loc,
DetectionSet* detections) {
auto* detection = detections->add_detections();
*(detection->mutable_location_normalized()) = normalized_loc;
}
// Checks whether two rectangles are the same, with an optional scaling factor.
bool CheckRectsEqual(const Rect& rect1, const Rect& rect2,
const double scale_x = 1.0, const double scale_y = 1.0) {
return (static_cast<int>(round(scale_x * rect2.x())) == rect1.x() &&
static_cast<int>(round(scale_y * rect2.y())) == rect1.y() &&
static_cast<int>(round(scale_x * rect2.width())) == rect1.width() &&
static_cast<int>(round(scale_y * rect2.height())) == rect1.height());
}
// Adds a detection to the detection set given its score and whether it is
// required.
void AddDetectionFromScoreAndIsRequired(const double score,
const bool is_required,
DetectionSet* detections) {
auto* detection = detections->add_detections();
detection->set_score(score);
detection->set_is_required(is_required);
}
// Returns default settings for KeyFrameCropOptions. Populates target size to be
// the default target size.
KeyFrameCropOptions GetDefaultKeyFrameCropOptions() {
KeyFrameCropOptions key_frame_crop_options;
key_frame_crop_options.set_target_width(kTargetWidth);
key_frame_crop_options.set_target_height(kTargetHeight);
return key_frame_crop_options;
}
// Returns default values for KeyFrameCropResults. Sets each frame to have
// covered all the required regions and non-required regions, and have required
// crop region (10, 10+20) x (10, 10+20), (full) crop region (0, 50) x (0, 50),
// and region score 1.0.
std::vector<KeyFrameCropResult> GetDefaultKeyFrameCropResults() {
std::vector<KeyFrameCropResult> key_frame_crop_results(kNumKeyFrames);
for (int i = 0; i < kNumKeyFrames; ++i) {
key_frame_crop_results[i].set_are_required_regions_covered_in_target_size(
true);
key_frame_crop_results[i].set_fraction_non_required_covered(1.0);
key_frame_crop_results[i].set_region_is_empty(false);
key_frame_crop_results[i].set_required_region_is_empty(false);
*(key_frame_crop_results[i].mutable_region()) = MakeRect(0, 0, 50, 50);
*(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;
}
// Checks that ScaleRect properly scales Rect and RectF objects.
TEST(UtilTest, ScaleRect) {
Rect scaled_rect;
ScaleRect(MakeRect(10, 10, 20, 30), 1.5, 2.0, &scaled_rect);
EXPECT_TRUE(CheckRectsEqual(scaled_rect, MakeRect(15, 20, 30, 60)));
ScaleRect(MakeRectF(0.5, 0.9, 1.36, 0.748), 100, 50, &scaled_rect);
EXPECT_TRUE(CheckRectsEqual(scaled_rect, MakeRect(50, 45, 136, 37)));
}
// Checks that NormalizedRectToRect properly converts a RectF object to a Rect
// object given width and height.
TEST(UtilTest, NormalizedRectToRect) {
const RectF normalized_rect = MakeRectF(0.1, 1.0, 2.5, 0.9);
Rect rect;
NormalizedRectToRect(normalized_rect, 100, 100, &rect);
EXPECT_TRUE(CheckRectsEqual(rect, MakeRect(10, 100, 250, 90)));
}
// Checks that ClampRect properly clamps a Rect object in [x0, y0] and [x1, y1].
TEST(UtilTest, ClampRect) {
// Overlaps at a corner.
Rect rect = MakeRect(-10, -10, 80, 20);
MP_EXPECT_OK(ClampRect(0, 0, 100, 100, &rect));
EXPECT_TRUE(CheckRectsEqual(rect, MakeRect(0, 0, 70, 10)));
// Overlaps on a side.
rect = MakeRect(10, -10, 80, 20);
MP_EXPECT_OK(ClampRect(0, 0, 100, 100, &rect));
EXPECT_TRUE(CheckRectsEqual(rect, MakeRect(10, 0, 80, 10)));
// Inside.
rect = MakeRect(10, 10, 80, 10);
MP_EXPECT_OK(ClampRect(0, 0, 100, 100, &rect));
EXPECT_TRUE(CheckRectsEqual(rect, MakeRect(10, 10, 80, 10)));
// Outside.
rect = MakeRect(-10, 10, 0, 0);
EXPECT_FALSE(ClampRect(0, 0, 100, 100, &rect).ok());
}
// Checks that ClampRect properly clamps a Rect object in [0, 0] and [width,
// height].
TEST(UtilTest, ClampRectConvenienceFunction) {
Rect rect = MakeRect(-10, 0, 80, 10);
MP_EXPECT_OK(ClampRect(kOriginalWidth, kOriginalHeight, &rect));
EXPECT_TRUE(CheckRectsEqual(rect, MakeRect(0, 0, 70, 10)));
rect = MakeRect(-10, 0, 120, 10);
MP_EXPECT_OK(ClampRect(kOriginalWidth, kOriginalHeight, &rect));
EXPECT_TRUE(CheckRectsEqual(rect, MakeRect(0, 0, 100, 10)));
rect = MakeRect(10, 0, 70, 120);
MP_EXPECT_OK(ClampRect(kOriginalWidth, kOriginalHeight, &rect));
EXPECT_TRUE(CheckRectsEqual(rect, MakeRect(10, 0, 70, 100)));
}
// Checks that RectUnion properly takes the union of two Rect objects.
TEST(UtilTest, RectUnion) {
// Base rectangle and new rectangle are partially overlapping.
Rect base_rect = MakeRect(40, 40, 40, 40);
RectUnion(MakeRect(20, 30, 40, 40), &base_rect);
EXPECT_TRUE(CheckRectsEqual(base_rect, MakeRect(20, 30, 60, 50)));
// Base rectangle contains new rectangle.
base_rect = MakeRect(40, 40, 40, 40);
RectUnion(MakeRect(50, 50, 10, 10), &base_rect);
EXPECT_TRUE(CheckRectsEqual(base_rect, MakeRect(40, 40, 40, 40)));
// Base rectangle is contained by new rectangle.
base_rect = MakeRect(40, 40, 40, 40);
RectUnion(MakeRect(30, 30, 50, 50), &base_rect);
EXPECT_TRUE(CheckRectsEqual(base_rect, MakeRect(30, 30, 50, 50)));
// Base rectangle and new rectangle are disjoint.
base_rect = MakeRect(40, 40, 40, 40);
RectUnion(MakeRect(15, 25, 20, 5), &base_rect);
EXPECT_TRUE(CheckRectsEqual(base_rect, MakeRect(15, 25, 65, 55)));
}
// Checks that PackCropFrame fails on nullptr return object.
TEST(UtilTest, PackKeyFrameInfoFailsOnNullObject) {
DetectionSet detections;
const int feature_width = kOriginalWidth, feature_height = kOriginalHeight;
KeyFrameInfo* key_frame_info_ptr = nullptr;
const auto status =
PackKeyFrameInfo(kTimestamp, detections, kOriginalWidth, kOriginalHeight,
feature_width, feature_height, key_frame_info_ptr);
EXPECT_FALSE(status.ok());
}
// Checks that PackCropFrame fails on invalid frame size.
TEST(UtilTest, PackKeyFrameInfoFailsOnInvalidFrameSize) {
DetectionSet detections;
const int feature_width = -1, feature_height = 0;
KeyFrameInfo key_frame_info;
const auto status =
PackKeyFrameInfo(kTimestamp, detections, kOriginalWidth, kOriginalHeight,
feature_width, feature_height, &key_frame_info)
.ToString();
EXPECT_THAT(status, testing::HasSubstr("Invalid frame size"));
}
// Checks that PackCropFrame correctly packs timestamp.
TEST(UtilTest, PackKeyFrameInfoPacksTimestamp) {
DetectionSet detections;
const int feature_width = kOriginalWidth, feature_height = kOriginalHeight;
KeyFrameInfo key_frame_info;
const auto status =
PackKeyFrameInfo(kTimestamp, detections, kOriginalWidth, kOriginalHeight,
feature_width, feature_height, &key_frame_info);
MP_EXPECT_OK(status);
EXPECT_EQ(key_frame_info.timestamp_ms(), kTimestamp);
}
// Checks that PackCropFrame correctly packs detections.
TEST(UtilTest, PackKeyFrameInfoPacksDetections) {
DetectionSet detections;
AddDetectionFromLocation(MakeRect(0, 0, 10, 10), &detections);
AddDetectionFromLocation(MakeRect(20, 20, 30, 10), &detections);
const int feature_width = kOriginalWidth, feature_height = kOriginalHeight;
KeyFrameInfo key_frame_info;
const auto status =
PackKeyFrameInfo(kTimestamp, detections, kOriginalWidth, kOriginalHeight,
feature_width, feature_height, &key_frame_info);
MP_EXPECT_OK(status);
EXPECT_EQ(key_frame_info.detections().detections_size(),
detections.detections_size());
for (int i = 0; i < detections.detections_size(); ++i) {
const auto& original_rect = detections.detections(i).location();
const auto& rect = key_frame_info.detections().detections(i).location();
EXPECT_TRUE(CheckRectsEqual(rect, original_rect));
}
}
// Checks that PackCropFrame correctly converts normalized location to location.
TEST(UtilTest, PackKeyFrameInfoUnnormalizesLocations) {
DetectionSet detections;
AddDetectionFromNormalizedLocation(MakeRectF(0.1, 0.1, 0.1, 0.1),
&detections);
AddDetectionFromNormalizedLocation(MakeRectF(0.242, 0.256, 0.378, 0.399),
&detections);
const int feature_width = kOriginalWidth, feature_height = kOriginalHeight;
KeyFrameInfo key_frame_info;
const auto status =
PackKeyFrameInfo(kTimestamp, detections, kOriginalWidth, kOriginalHeight,
feature_width, feature_height, &key_frame_info);
MP_EXPECT_OK(status);
const auto& out_rect1 = key_frame_info.detections().detections(0).location();
const auto& out_rect2 = key_frame_info.detections().detections(1).location();
EXPECT_TRUE(CheckRectsEqual(out_rect1, MakeRect(10, 10, 10, 10)));
EXPECT_TRUE(CheckRectsEqual(out_rect2, MakeRect(24, 26, 38, 40)));
}
// Checks that PackCropFrame correctly scales location.
TEST(UtilTest, PackKeyFrameInfoScalesLocations) {
DetectionSet detections;
AddDetectionFromLocation(MakeRect(10, 10, 10, 10), &detections);
AddDetectionFromLocation(MakeRect(20, 20, 30, 30), &detections);
const double scaling = 2.0;
const int feature_width = kOriginalWidth / scaling;
const int feature_height = kOriginalHeight / scaling;
KeyFrameInfo key_frame_info;
const auto status =
PackKeyFrameInfo(kTimestamp, detections, kOriginalWidth, kOriginalHeight,
feature_width, feature_height, &key_frame_info);
MP_EXPECT_OK(status);
EXPECT_EQ(key_frame_info.detections().detections_size(),
detections.detections_size());
for (int i = 0; i < detections.detections_size(); ++i) {
const auto& original_rect = detections.detections(i).location();
const auto& rect = key_frame_info.detections().detections(i).location();
EXPECT_TRUE(CheckRectsEqual(rect, original_rect, scaling, scaling));
}
}
// Checks that PackCropFrame correctly clamps location to be within frame size.
TEST(UtilTest, PackKeyFrameInfoClampsLocations) {
DetectionSet detections;
AddDetectionFromLocation(MakeRect(10, 10, 100, 10), &detections);
AddDetectionFromLocation(MakeRect(0, -10, 110, 100), &detections);
const int feature_width = kOriginalWidth, feature_height = kOriginalHeight;
KeyFrameInfo key_frame_info;
const auto status =
PackKeyFrameInfo(kTimestamp, detections, kOriginalWidth, kOriginalHeight,
feature_width, feature_height, &key_frame_info);
MP_EXPECT_OK(status);
EXPECT_EQ(key_frame_info.detections().detections_size(),
detections.detections_size());
const auto& out_rect1 = key_frame_info.detections().detections(0).location();
const auto& out_rect2 = key_frame_info.detections().detections(1).location();
EXPECT_TRUE(CheckRectsEqual(out_rect1, MakeRect(10, 10, 90, 10)));
EXPECT_TRUE(CheckRectsEqual(out_rect2, MakeRect(0, 0, 100, 90)));
}
// Checks that PackCropFrame correctly clamps normalized location to be within
// frame size.
TEST(UtilTest, PackKeyFrameInfoClampsNormalizedLocations) {
DetectionSet detections;
AddDetectionFromNormalizedLocation(MakeRectF(-0.05, 0.3, 0.4, 0.8),
&detections);
AddDetectionFromNormalizedLocation(MakeRectF(0.05, -0.1, 1.0, 1.1),
&detections);
const int feature_width = kOriginalWidth, feature_height = kOriginalHeight;
KeyFrameInfo key_frame_info;
const auto status =
PackKeyFrameInfo(kTimestamp, detections, kOriginalWidth, kOriginalHeight,
feature_width, feature_height, &key_frame_info);
MP_EXPECT_OK(status);
EXPECT_EQ(key_frame_info.detections().detections_size(),
detections.detections_size());
const auto& out_rect1 = key_frame_info.detections().detections(0).location();
const auto& out_rect2 = key_frame_info.detections().detections(1).location();
EXPECT_TRUE(CheckRectsEqual(out_rect1, MakeRect(0, 30, 35, 70)));
EXPECT_TRUE(CheckRectsEqual(out_rect2, MakeRect(5, 0, 95, 100)));
}
// Checks that SortDetections correctly handles empty regions.
TEST(UtilTest, SortDetectionsHandlesEmptyRegions) {
DetectionSet detections;
std::vector<SalientRegion> required, non_required;
MP_EXPECT_OK(SortDetections(detections, &required, &non_required));
EXPECT_EQ(detections.detections_size(),
required.size() + non_required.size());
}
// Checks that SortDetections correctly separates required and non-required
// salient regions.
TEST(UtilTest, SortDetectionsSeparatesRequiredAndNonRequiredRegions) {
DetectionSet detections;
auto gen_bool = std::bind(std::uniform_int_distribution<>(0, 1),
std::default_random_engine());
for (int i = 0; i < 100; ++i) {
const bool is_required = gen_bool();
AddDetectionFromScoreAndIsRequired(1.0, is_required, &detections);
}
std::vector<SalientRegion> required, non_required;
MP_EXPECT_OK(SortDetections(detections, &required, &non_required));
EXPECT_EQ(detections.detections_size(),
required.size() + non_required.size());
for (int i = 0; i < required.size(); ++i) {
EXPECT_TRUE(required[i].is_required());
}
for (int i = 0; i < non_required.size(); ++i) {
EXPECT_FALSE(non_required[i].is_required());
}
}
// Checks that SortDetections correctly sorts regions based on scores.
TEST(UtilTest, SortDetectionsSortsRegions) {
DetectionSet detections;
auto gen_score = std::bind(std::uniform_real_distribution<>(0.0, 1.0),
std::default_random_engine());
auto gen_bool = std::bind(std::uniform_int_distribution<>(0, 1),
std::default_random_engine());
for (int i = 0; i < 100; ++i) {
const double score = gen_score();
const bool is_required = gen_bool();
AddDetectionFromScoreAndIsRequired(score, is_required, &detections);
}
std::vector<SalientRegion> required, non_required;
MP_EXPECT_OK(SortDetections(detections, &required, &non_required));
EXPECT_EQ(detections.detections_size(),
required.size() + non_required.size());
for (int i = 0; i < required.size() - 1; ++i) {
EXPECT_GE(required[i].score(), required[i + 1].score());
}
for (int i = 0; i < non_required.size() - 1; ++i) {
EXPECT_GE(non_required[i].score(), non_required[i + 1].score());
}
}
// Checks that SetKeyFrameCropTarget checks KeyFrameCropOptions is not null.
TEST(UtilTest, SetKeyFrameCropTargetChecksKeyFrameCropOptionsNotNull) {
const auto status = SetKeyFrameCropTarget(kOriginalWidth, kOriginalHeight,
kTargetAspectRatio, nullptr);
EXPECT_FALSE(status.ok());
EXPECT_THAT(status.ToString(),
testing::HasSubstr("KeyFrameCropOptions is null."));
}
// Checks that SetKeyFrameCropTarget checks frame size and target aspect ratio
// are valid.
TEST(UtilTest, SetKeyFrameCropTargetChecksFrameSizeAndTargetAspectRatioValid) {
KeyFrameCropOptions crop_options;
auto status = SetKeyFrameCropTarget(0, kOriginalHeight, kTargetAspectRatio,
&crop_options);
EXPECT_FALSE(status.ok());
EXPECT_THAT(status.ToString(),
testing::HasSubstr("Frame width is non-positive."));
status =
SetKeyFrameCropTarget(kOriginalWidth, kOriginalHeight, 0, &crop_options);
EXPECT_FALSE(status.ok());
EXPECT_THAT(status.ToString(),
testing::HasSubstr("Target aspect ratio is non-positive."));
}
// Checks that SetKeyFrameCropTarget correctly sets the target crop size.
TEST(UtilTest, SetKeyFrameCropTargetSetsTargetSizeCorrectly) {
KeyFrameCropOptions crop_options;
// 1a) square -> square.
MP_EXPECT_OK(SetKeyFrameCropTarget(101, 101, 1.0, &crop_options));
EXPECT_EQ(crop_options.target_width(), 101);
EXPECT_EQ(crop_options.target_height(), 101);
// 1b) square -> landscape.
MP_EXPECT_OK(SetKeyFrameCropTarget(101, 101, 1.5, &crop_options));
EXPECT_EQ(crop_options.target_width(), 101);
EXPECT_EQ(crop_options.target_height(), 67);
// 1c) square -> vertical.
MP_EXPECT_OK(SetKeyFrameCropTarget(101, 101, 0.5, &crop_options));
EXPECT_EQ(crop_options.target_width(), 51);
EXPECT_EQ(crop_options.target_height(), 101);
// 2a) landscape -> square.
MP_EXPECT_OK(SetKeyFrameCropTarget(128, 72, 1.0, &crop_options));
EXPECT_EQ(crop_options.target_width(), 72);
EXPECT_EQ(crop_options.target_height(), 72);
// 2b) landscape -> more landscape.
MP_EXPECT_OK(SetKeyFrameCropTarget(128, 72, 2.0, &crop_options));
EXPECT_EQ(crop_options.target_width(), 128);
EXPECT_EQ(crop_options.target_height(), 64);
// 2c) landscape -> vertical.
MP_EXPECT_OK(SetKeyFrameCropTarget(128, 72, 0.7, &crop_options));
EXPECT_EQ(crop_options.target_width(), 50);
EXPECT_EQ(crop_options.target_height(), 72);
// 3a) vertical -> square.
MP_EXPECT_OK(SetKeyFrameCropTarget(90, 160, 1.0, &crop_options));
EXPECT_EQ(crop_options.target_width(), 90);
EXPECT_EQ(crop_options.target_height(), 90);
// 3b) vertical -> more vertical.
MP_EXPECT_OK(SetKeyFrameCropTarget(90, 160, 0.36, &crop_options));
EXPECT_EQ(crop_options.target_width(), 58);
EXPECT_EQ(crop_options.target_height(), 160);
// 3c) vertical -> landscape.
MP_EXPECT_OK(SetKeyFrameCropTarget(90, 160, 1.2, &crop_options));
EXPECT_EQ(crop_options.target_width(), 90);
EXPECT_EQ(crop_options.target_height(), 75);
}
// Checks that AggregateKeyFrameResults checks output pointer is not null.
TEST(UtilTest, AggregateKeyFrameResultsChecksOutputNotNull) {
const auto status = AggregateKeyFrameResults(
GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(),
kOriginalWidth, kOriginalHeight, nullptr);
EXPECT_FALSE(status.ok());
EXPECT_THAT(status.ToString(),
HasSubstr("Output SceneKeyFrameCropSummary is null."));
}
// Checks that AggregateKeyFrameResults handles the case of no key frames.
TEST(UtilTest, AggregateKeyFrameResultsHandlesNoKeyFrames) {
std::vector<KeyFrameCropResult> key_frame_crop_results(0);
SceneKeyFrameCropSummary scene_summary;
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(
GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(),
kOriginalWidth, 0, &scene_summary);
EXPECT_FALSE(status.ok());
EXPECT_THAT(status.ToString(), HasSubstr("Non-positive frame height."));
}
// Checks that AggregateKeyFrameResults checks that target size is valid.
TEST(UtilTest, AggregateKeyFrameResultsChecksTargetSizeValid) {
KeyFrameCropOptions key_frame_crop_options;
key_frame_crop_options.set_target_width(0);
SceneKeyFrameCropSummary scene_summary;
const auto status = AggregateKeyFrameResults(
key_frame_crop_options, GetDefaultKeyFrameCropResults(), kOriginalWidth,
kOriginalHeight, &scene_summary);
EXPECT_FALSE(status.ok());
EXPECT_THAT(status.ToString(), HasSubstr("Non-positive target width."));
}
// Checks that AggregateKeyFrameResults checks that target size does not exceed
// frame size.
TEST(UtilTest, AggregateKeyFrameResultsChecksTargetSizeNotExceedFrameSize) {
auto key_frame_crop_options = GetDefaultKeyFrameCropOptions();
key_frame_crop_options.set_target_width(kOriginalWidth + 1);
SceneKeyFrameCropSummary scene_summary;
const auto status = AggregateKeyFrameResults(
key_frame_crop_options, GetDefaultKeyFrameCropResults(), kOriginalWidth,
kOriginalHeight, &scene_summary);
EXPECT_FALSE(status.ok());
EXPECT_THAT(status.ToString(),
HasSubstr("Target width exceeds frame width."));
}
// Checks that AggregateKeyFrameResults packs KeyFrameCompactInfos.
TEST(UtilTest, AggregateKeyFrameResultsPacksKeyFrameCompactInfos) {
const auto key_frame_crop_results = GetDefaultKeyFrameCropResults();
SceneKeyFrameCropSummary 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_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);
EXPECT_FLOAT_EQ(compact_info.score(),
key_frame_crop_results[i].region_score());
}
}
// Checks that AggregateKeyFrameResults ensures the centered region of target
// size fits in frame bound.
TEST(UtilTest, AggregateKeyFrameResultsEnsuresCropRegionFitsInFrame) {
std::vector<KeyFrameCropResult> key_frame_crop_results(1);
auto* crop_region = key_frame_crop_results[0].mutable_region();
crop_region->set_x(0);
crop_region->set_y(0);
crop_region->set_width(10);
crop_region->set_height(10);
SceneKeyFrameCropSummary 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);
const auto& compact_info = scene_summary.key_frame_compact_infos(0);
const float left = compact_info.center_x() - kTargetWidth / 2.0f;
const float right = compact_info.center_x() + kTargetWidth / 2.0f;
const float top = compact_info.center_y() - kTargetWidth / 2.0f;
const float bottom = compact_info.center_y() + kTargetWidth / 2.0f;
// Crop window is in the frame.
EXPECT_GE(left, 0);
EXPECT_LE(right, kOriginalWidth);
EXPECT_GE(top, 0);
EXPECT_LE(bottom, kOriginalHeight);
// Crop window covers input crop region.
EXPECT_LE(left, crop_region->x());
EXPECT_GE(right, crop_region->x() + crop_region->width());
EXPECT_LE(top, crop_region->y());
EXPECT_GE(bottom, crop_region->y() + crop_region->height());
}
// Checks that AggregateKeyFrameResults sets centers and scores to -1.0 for key
// frames with empty regions.
TEST(UtilTest,
AggregateKeyFrameResultsSetsMinusOneForKeyFramesWithEmptyRegions) {
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(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);
EXPECT_FLOAT_EQ(compact_info.center_y(), -1.0f);
EXPECT_FLOAT_EQ(compact_info.score(), -1.0f);
}
// Checks that AggregateKeyFrameResults rejects negative center.
TEST(UtilTest, AggregateKeyFrameResultsRejectsNegativeCenter) {
auto key_frame_crop_results = GetDefaultKeyFrameCropResults();
auto* region = key_frame_crop_results[0].mutable_region();
*region = MakeRectFromCenterAndHalfSize(10, -1.0, 10, 10);
SceneKeyFrameCropSummary scene_summary;
const auto status = AggregateKeyFrameResults(
GetDefaultKeyFrameCropOptions(), key_frame_crop_results, kOriginalWidth,
kOriginalHeight, &scene_summary);
EXPECT_FALSE(status.ok());
EXPECT_THAT(status.ToString(), HasSubstr("Negative vertical center."));
}
// Checks that AggregateKeyFrameResults rejects negative score.
TEST(UtilTest, AggregateKeyFrameResultsRejectsNegativeScore) {
auto key_frame_crop_results = GetDefaultKeyFrameCropResults();
key_frame_crop_results[0].set_region_score(-1.0);
SceneKeyFrameCropSummary scene_summary;
const auto status = AggregateKeyFrameResults(
GetDefaultKeyFrameCropOptions(), key_frame_crop_results, kOriginalWidth,
kOriginalHeight, &scene_summary);
EXPECT_FALSE(status.ok());
EXPECT_THAT(status.ToString(), HasSubstr("Negative score."));
}
// Checks that AggregateKeyFrameResults properly sets center ranges.
TEST(UtilTest, AggregateKeyFrameResultsSetsCenterRanges) {
auto key_frame_crop_results = GetDefaultKeyFrameCropResults();
const std::vector<float> centers_x = {30.0, 20.0, 45.0, 3.0, 10.0};
const std::vector<float> centers_y = {10.0, 0.0, 5.0, 30.0, 20.0};
const int half_width = 25, half_height = 25;
for (int i = 0; i < kNumKeyFrames; ++i) {
auto* region = key_frame_crop_results[i].mutable_region();
*region = MakeRectFromCenterAndHalfSize(centers_x[i], centers_y[i],
half_width, half_height);
}
SceneKeyFrameCropSummary 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);
EXPECT_FLOAT_EQ(scene_summary.key_frame_center_min_y(), 25.0f);
EXPECT_FLOAT_EQ(scene_summary.key_frame_center_max_y(), 30.0f);
}
// Checks that AggregateKeyFrameResults properly sets score range.
TEST(UtilTest, AggregateKeyFrameResultsSetsScoreRange) {
auto key_frame_crop_results = GetDefaultKeyFrameCropResults();
const std::vector<float> scores = {0.9, 0.1, 1.2, 2.0, 0.5};
for (int i = 0; i < kNumKeyFrames; ++i) {
key_frame_crop_results[i].set_region_score(scores[i]);
}
SceneKeyFrameCropSummary 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()));
EXPECT_FLOAT_EQ(scene_summary.key_frame_max_score(),
*std::max_element(scores.begin(), scores.end()));
}
// Checks that AggregateKeyFrameResults sets crop window size to target size
// when the crop regions fit in target size.
TEST(UtilTest, AggregateKeyFrameResultsSetsCropWindowSizeToTargetSize) {
SceneKeyFrameCropSummary scene_summary;
MP_EXPECT_OK(AggregateKeyFrameResults(
GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(),
kOriginalWidth, kOriginalHeight, &scene_summary));
EXPECT_EQ(scene_summary.crop_window_width(), kTargetWidth);
EXPECT_EQ(scene_summary.crop_window_height(), kTargetHeight);
}
// Checks that AggregateKeyFrameResults properly sets crop window size when the
// crop regions exceed target size.
TEST(UtilTest, AggregateKeyFrameResultsSetsCropWindowSizeExceedingTargetSize) {
auto key_frame_crop_results = GetDefaultKeyFrameCropResults();
key_frame_crop_results[0].mutable_region()->set_width(kTargetWidth + 1);
SceneKeyFrameCropSummary 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);
}
// Checks that AggregateKeyFrameResults sets has salient region to true when
// there are salient regions.
TEST(UtilTest, AggregateKeyFrameResultsSetsHasSalientRegionTrue) {
SceneKeyFrameCropSummary scene_summary;
MP_EXPECT_OK(AggregateKeyFrameResults(
GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(),
kOriginalWidth, kOriginalHeight, &scene_summary));
EXPECT_TRUE(scene_summary.has_salient_region());
}
// Checks that AggregateKeyFrameResults sets has salient region to false when
// there are no salient regions.
TEST(UtilTest, AggregateKeyFrameResultsSetsHasSalientRegionFalse) {
std::vector<KeyFrameCropResult> key_frame_crop_results(kNumKeyFrames);
for (int i = 0; i < kNumKeyFrames; ++i) {
key_frame_crop_results[i].set_region_is_empty(true);
}
SceneKeyFrameCropSummary scene_summary;
MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(),
key_frame_crop_results, kOriginalWidth,
kOriginalHeight, &scene_summary));
EXPECT_FALSE(scene_summary.has_salient_region());
}
// Checks that AggregateKeyFrameResults sets has required salient region to true
// when there are required salient regions.
TEST(UtilTest, AggregateKeyFrameResultsSetsHasRequiredSalientRegionTrue) {
SceneKeyFrameCropSummary scene_summary;
MP_EXPECT_OK(AggregateKeyFrameResults(
GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(),
kOriginalWidth, kOriginalHeight, &scene_summary));
EXPECT_TRUE(scene_summary.has_required_salient_region());
}
// Checks that AggregateKeyFrameResults sets has required salient region to
// false when there are no required salient regions.
TEST(UtilTest, AggregateKeyFrameResultsSetsHasRequiredSalientRegionFalse) {
std::vector<KeyFrameCropResult> key_frame_crop_results(kNumKeyFrames);
for (int i = 0; i < kNumKeyFrames; ++i) {
key_frame_crop_results[i].set_required_region_is_empty(true);
}
SceneKeyFrameCropSummary scene_summary;
MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(),
key_frame_crop_results, kOriginalWidth,
kOriginalHeight, &scene_summary));
EXPECT_FALSE(scene_summary.has_required_salient_region());
}
// Checks that AggregateKeyFrameResults sets key frame required crop region
// union.
TEST(UtilTest, AggregateKeyFrameResultsSetsKeyFrameRequiredCropRegionUnion) {
auto key_frame_crop_results = GetDefaultKeyFrameCropResults();
for (int i = 0; i < kNumKeyFrames; ++i) {
*key_frame_crop_results[i].mutable_required_region() =
MakeRect(i, 0, 50, 50);
}
SceneKeyFrameCropSummary 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);
EXPECT_EQ(required_crop_region_union.width(), 50 + kNumKeyFrames - 1);
}
// Checks that AggregateKeyFrameResults properly sets frame success rate.
TEST(UtilTest, AggregateKeyFrameResultsSetsFrameSuccessRate) {
const int num_success_frames = 3;
const float success_rate =
static_cast<float>(num_success_frames) / kNumKeyFrames;
auto key_frame_crop_results = GetDefaultKeyFrameCropResults();
for (int i = 0; i < kNumKeyFrames; ++i) {
const bool successful = i < num_success_frames ? true : false;
key_frame_crop_results[i].set_are_required_regions_covered_in_target_size(
successful);
}
SceneKeyFrameCropSummary 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);
}
// Checks that AggregateKeyFrameResults properly sets motion.
TEST(UtilTest, AggregateKeyFrameResultsSetsMotion) {
auto key_frame_crop_results = GetDefaultKeyFrameCropResults();
const std::vector<float> centers_x = {30.0, 55.0, 45.0, 30.0, 60.0};
const std::vector<float> centers_y = {30.0, 40.0, 50.0, 45.0, 25.0};
const float motion_x = (60.0 - 30.0) / kOriginalWidth;
const float motion_y = (50.0 - 25.0) / kOriginalHeight;
const int half_width = 25, half_height = 25;
for (int i = 0; i < kNumKeyFrames; ++i) {
auto* region = key_frame_crop_results[i].mutable_region();
*region = MakeRectFromCenterAndHalfSize(centers_x[i], centers_y[i],
half_width, half_height);
}
SceneKeyFrameCropSummary 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);
}
// Checks that ComputeSceneStaticBordersSize checks output not null.
TEST(UtilTest, ComputeSceneStaticBordersSizeChecksOutputNotNull) {
std::vector<StaticFeatures> static_features;
int bottom_border_size = 0;
const auto status = ComputeSceneStaticBordersSize(static_features, nullptr,
&bottom_border_size);
EXPECT_FALSE(status.ok());
EXPECT_THAT(status.ToString(),
testing::HasSubstr("Output top border size is null."));
}
// Checks that ComputeSceneStaticBordersSize returns 0 when there are no static
// borders.
TEST(UtilTest, ComputeSceneStaticBordersSizeNoBorder) {
std::vector<StaticFeatures> static_features(10);
int top_border_size = 0, bottom_border_size = 0;
MP_EXPECT_OK(ComputeSceneStaticBordersSize(static_features, &top_border_size,
&bottom_border_size));
EXPECT_EQ(top_border_size, 0);
EXPECT_EQ(bottom_border_size, 0);
}
// Checks that ComputeSceneStaticBordersSize correctly computes static border
// size.
TEST(UtilTest, ComputeSceneStaticBordersSizeHasBorders) {
const int num_frames = 6;
const std::vector<int> top_borders = {10, 9, 8, 9, 10, 5};
const std::vector<int> bottom_borders = {7, 7, 7, 7, 6, 7};
std::vector<StaticFeatures> static_features(num_frames);
for (int i = 0; i < num_frames; ++i) {
auto& features = static_features[i];
auto* top_part = features.add_border();
top_part->set_relative_position(Border::TOP);
top_part->mutable_border_position()->set_height(top_borders[i]);
auto* bottom_part = features.add_border();
bottom_part->set_relative_position(Border::BOTTOM);
bottom_part->mutable_border_position()->set_height(bottom_borders[i]);
}
int top_border_size = 0, bottom_border_size = 0;
MP_EXPECT_OK(ComputeSceneStaticBordersSize(static_features, &top_border_size,
&bottom_border_size));
EXPECT_EQ(top_border_size, 5);
EXPECT_EQ(bottom_border_size, 6);
}
// Checks that FindSolidBackgroundColor checks output not null.
TEST(UtilTest, FindSolidBackgroundColorChecksOutputNotNull) {
std::vector<StaticFeatures> static_features;
std::vector<int64> static_features_timestamps;
const double min_fraction_solid_background_color = 0.8;
bool has_solid_background_color;
PiecewiseLinearFunction l_function, a_function, b_function;
auto status =
FindSolidBackgroundColor(static_features, static_features_timestamps,
min_fraction_solid_background_color, nullptr,
&l_function, &a_function, &b_function);
EXPECT_FALSE(status.ok());
EXPECT_THAT(status.ToString(), testing::HasSubstr("Output boolean is null."));
status = FindSolidBackgroundColor(static_features, static_features_timestamps,
min_fraction_solid_background_color,
&has_solid_background_color, nullptr,
&a_function, &b_function);
EXPECT_FALSE(status.ok());
EXPECT_THAT(status.ToString(),
testing::HasSubstr("Output color l function is null."));
}
// Checks that FindSolidBackgroundColor returns true when there is solid
// background color.
TEST(UtilTest, FindSolidBackgroundColorReturnsTrue) {
std::vector<StaticFeatures> static_features(1);
auto* color = static_features[0].mutable_solid_background();
color->set_r(255);
color->set_g(255);
color->set_b(255);
std::vector<int64> static_features_timestamps(1);
static_features_timestamps[0] = 0;
const double min_fraction_solid_background_color = 0.8;
bool has_solid_background_color;
PiecewiseLinearFunction l_function, a_function, b_function;
MP_EXPECT_OK(FindSolidBackgroundColor(
static_features, static_features_timestamps,
min_fraction_solid_background_color, &has_solid_background_color,
&l_function, &a_function, &b_function));
EXPECT_TRUE(has_solid_background_color);
}
// Checks that FindSolidBackgroundColor returns false when there is no solid
// background color.
TEST(UtilTest, FindSolidBackgroundColorReturnsFalse) {
std::vector<StaticFeatures> static_features(1);
std::vector<int64> static_features_timestamps(1);
static_features_timestamps[0] = 0;
const double min_fraction_solid_background_color = 0.8;
bool has_solid_background_color;
PiecewiseLinearFunction l_function, a_function, b_function;
MP_EXPECT_OK(FindSolidBackgroundColor(
static_features, static_features_timestamps,
min_fraction_solid_background_color, &has_solid_background_color,
&l_function, &a_function, &b_function));
EXPECT_FALSE(has_solid_background_color);
}
// Checks that FindSolidBackgroundColor sets the interpolation functions.
TEST(UtilTest, FindSolidBackgroundColorSetsInterpolationFunctions) {
const uint8 rgb1[] = {255, 255, 0}; // cyan in bgr
const double lab1[] = {91.1133, -48.0938, -14.125};
const int64 time1 = 0;
const uint8 rgb2[] = {255, 0, 255}; // magenta in bgr
const double lab2[] = {60.321, 98.2344, -60.8281};
const int64 time2 = 2000;
std::vector<StaticFeatures> static_features(2);
auto* color1 = static_features[0].mutable_solid_background();
color1->set_r(rgb1[0]);
color1->set_g(rgb1[1]);
color1->set_b(rgb1[2]);
auto* color2 = static_features[1].mutable_solid_background();
color2->set_r(rgb2[0]);
color2->set_g(rgb2[1]);
color2->set_b(rgb2[2]);
std::vector<int64> static_features_timestamps(2);
static_features_timestamps[0] = time1;
static_features_timestamps[1] = time2;
const double min_fraction_solid_background_color = 0.8;
bool has_solid_background_color;
PiecewiseLinearFunction l_function, a_function, b_function;
MP_EXPECT_OK(FindSolidBackgroundColor(
static_features, static_features_timestamps,
min_fraction_solid_background_color, &has_solid_background_color,
&l_function, &a_function, &b_function));
EXPECT_TRUE(has_solid_background_color);
EXPECT_LE(std::fabs(l_function.Evaluate(time1) - lab1[0]), 1e-2f);
EXPECT_LE(std::fabs(a_function.Evaluate(time1) - lab1[1]), 1e-2f);
EXPECT_LE(std::fabs(b_function.Evaluate(time1) - lab1[2]), 1e-2f);
EXPECT_LE(std::fabs(l_function.Evaluate(time2) - lab2[0]), 1e-2f);
EXPECT_LE(std::fabs(a_function.Evaluate(time2) - lab2[1]), 1e-2f);
EXPECT_LE(std::fabs(b_function.Evaluate(time2) - lab2[2]), 1e-2f);
EXPECT_LE(std::fabs(l_function.Evaluate((time1 + time2) / 2.0) -
(lab1[0] + lab2[0]) / 2.0),
1e-2f);
EXPECT_LE(std::fabs(a_function.Evaluate((time1 + time2) / 2.0) -
(lab1[1] + lab2[1]) / 2.0),
1e-2f);
EXPECT_LE(std::fabs(b_function.Evaluate((time1 + time2) / 2.0) -
(lab1[2] + lab2[2]) / 2.0),
1e-2f);
}
TEST(UtilTest, TestAffineRetargeterPass) {
std::vector<cv::Mat> transforms;
std::vector<cv::Mat> frames;
std::vector<cv::Mat> results;
for (int i = 0; i < 5; i++) {
cv::Mat transform = cv::Mat(2, 3, CV_32FC1);
transform.at<float>(0, 0) = 1;
transform.at<float>(0, 1) = 0;
transform.at<float>(1, 0) = 0;
transform.at<float>(1, 1) = 1;
transform.at<float>(0, 2) = -i * 50;
transform.at<float>(1, 2) = 0;
transforms.push_back(transform);
cv::Mat image = cv::Mat::zeros(1080, 1920, CV_8UC3);
cv::Vec3b val;
val[0] = 255;
val[1] = 255;
val[2] = 255;
image(cv::Rect(0, 0, 395, 1080)).setTo(val);
frames.push_back(image);
cv::Mat image_out = cv::Mat::zeros(500, 1920, CV_8UC3);
results.push_back(image_out);
}
MP_ASSERT_OK(
AffineRetarget(cv::Size(500, 1080), frames, transforms, &results));
ASSERT_EQ(results.size(), 5);
for (int i = 0; i < 5; i++) {
EXPECT_GT(results[i].at<cv::Vec3b>(540, 390 - i * 50)[0], 250);
EXPECT_GT(results[i].at<cv::Vec3b>(540, 390 - i * 50)[1], 250);
EXPECT_GT(results[i].at<cv::Vec3b>(540, 390 - i * 50)[2], 250);
EXPECT_LT(results[i].at<cv::Vec3b>(540, 400 - i * 50)[0], 5);
EXPECT_LT(results[i].at<cv::Vec3b>(540, 400 - i * 50)[1], 5);
EXPECT_LT(results[i].at<cv::Vec3b>(540, 400 - i * 50)[2], 5);
}
}
TEST(UtilTest, TestAffineRetargeterFail) {
std::vector<cv::Mat> transforms;
std::vector<cv::Mat> frames;
std::vector<cv::Mat> results;
cv::Mat dummy;
transforms.push_back(dummy);
frames.push_back(dummy);
EXPECT_THAT(
AffineRetarget(cv::Size(500, 1080), frames, transforms, &results)
.ToString(),
testing::HasSubstr("Output vector cropped_frames must be populated"));
}
} // namespace
} // namespace autoflip
} // namespace mediapipe