// 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 #include #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 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(round(scale_x * rect2.x())) == rect1.x() && static_cast(round(scale_y * rect2.y())) == rect1.y() && static_cast(round(scale_x * rect2.width())) == rect1.width() && static_cast(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 GetDefaultKeyFrameCropResults() { std::vector 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 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 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 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 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 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 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 centers_x = {30.0, 20.0, 45.0, 3.0, 10.0}; const std::vector 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 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 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 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(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 centers_x = {30.0, 55.0, 45.0, 30.0, 60.0}; const std::vector 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 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 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 top_borders = {10, 9, 8, 9, 10, 5}; const std::vector bottom_borders = {7, 7, 7, 7, 6, 7}; std::vector 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 static_features; std::vector 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 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 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 static_features(1); std::vector 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 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 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 transforms; std::vector frames; std::vector results; for (int i = 0; i < 5; i++) { cv::Mat transform = cv::Mat(2, 3, CV_32FC1); transform.at(0, 0) = 1; transform.at(0, 1) = 0; transform.at(1, 0) = 0; transform.at(1, 1) = 1; transform.at(0, 2) = -i * 50; transform.at(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(540, 390 - i * 50)[0], 250); EXPECT_GT(results[i].at(540, 390 - i * 50)[1], 250); EXPECT_GT(results[i].at(540, 390 - i * 50)[2], 250); EXPECT_LT(results[i].at(540, 400 - i * 50)[0], 5); EXPECT_LT(results[i].at(540, 400 - i * 50)[1], 5); EXPECT_LT(results[i].at(540, 400 - i * 50)[2], 5); } } TEST(UtilTest, TestAffineRetargeterFail) { std::vector transforms; std::vector frames; std::vector 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