Project import generated by Copybara.

GitOrigin-RevId: ff83882955f1a1e2a043ff4e71278be9d7217bbe
This commit is contained in:
MediaPipe Team
2021-05-05 14:56:16 -04:00
committed by chuoling
parent ecb5b5f44a
commit a9b643e0f5
210 changed files with 5312 additions and 3838 deletions
+19
View File
@@ -199,6 +199,25 @@ cc_library(
}),
)
cc_library(
name = "resource_cache",
hdrs = ["resource_cache.h"],
visibility = ["//visibility:public"],
deps = [
"//mediapipe/framework/port:logging",
"@com_google_absl//absl/functional:function_ref",
],
)
cc_test(
name = "resource_cache_test",
srcs = ["resource_cache_test.cc"],
deps = [
":resource_cache",
"//mediapipe/framework/port:gtest_main",
],
)
cc_library(
name = "tensor_to_detection",
srcs = ["tensor_to_detection.cc"],
+181
View File
@@ -0,0 +1,181 @@
// 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_UTIL_RESOURCE_CACHE_H_
#define MEDIAPIPE_UTIL_RESOURCE_CACHE_H_
#include <unordered_map>
#include "absl/functional/function_ref.h"
#include "mediapipe/framework/port/logging.h"
namespace mediapipe {
// Maintains a cache for resources of type `Value`, where the type of the
// resource (e.g., image dimension for an image pool) is described bye the `Key`
// type. The `Value` type must include an unset value, with implicit conversion
// to bool reflecting set/unset state.
template <typename Key, typename Value, typename KeyHash>
class ResourceCache {
public:
Value Lookup(
const Key& key,
absl::FunctionRef<Value(const Key& key, int request_count)> create) {
auto map_it = map_.find(key);
Entry* entry;
if (map_it == map_.end()) {
std::tie(map_it, std::ignore) =
map_.emplace(std::piecewise_construct, std::forward_as_tuple(key),
std::forward_as_tuple(key));
entry = &map_it->second;
CHECK_EQ(entry->request_count, 0);
entry->request_count = 1;
entry_list_.Append(entry);
if (entry->prev != nullptr) CHECK_GE(entry->prev->request_count, 1);
} else {
entry = &map_it->second;
++entry->request_count;
Entry* larger = entry->prev;
while (larger != nullptr &&
larger->request_count < entry->request_count) {
larger = larger->prev;
}
if (larger != entry->prev) {
entry_list_.Remove(entry);
entry_list_.InsertAfter(entry, larger);
}
}
if (!entry->value) {
entry->value = create(entry->key, entry->request_count);
}
++total_request_count_;
return entry->value;
}
std::vector<Value> Evict(int max_count, int request_count_scrub_interval) {
std::vector<Value> evicted;
// Remove excess entries.
while (entry_list_.size() > max_count) {
Entry* victim = entry_list_.tail();
evicted.emplace_back(std::move(victim->value));
entry_list_.Remove(victim);
map_.erase(victim->key);
}
// Every request_count_scrub_interval, halve the request counts, and
// remove entries which have fallen to 0.
// This keeps sporadic requests from accumulating and eventually exceeding
// the minimum request threshold for allocating a pool. Also, it means that
// if the request regimen changes (e.g. a graph was always requesting a
// large size, but then switches to a small size to save memory or CPU), the
// pool can quickly adapt to it.
bool scrub = total_request_count_ >= request_count_scrub_interval;
if (scrub) {
total_request_count_ = 0;
for (Entry* entry = entry_list_.head(); entry != nullptr;) {
entry->request_count /= 2;
Entry* next = entry->next;
if (entry->request_count == 0) {
evicted.emplace_back(std::move(entry->value));
entry_list_.Remove(entry);
map_.erase(entry->key);
}
entry = next;
}
}
return evicted;
}
private:
struct Entry {
Entry(const Key& key) : key(key) {}
Entry* prev = nullptr;
Entry* next = nullptr;
int request_count = 0;
Key key;
Value value;
};
// Unlike std::list, this is an intrusive list, meaning that the prev and next
// pointers live inside the element. Apart from not requiring an extra
// allocation, this means that once we look up an entry by key in the pools_
// map we do not need to look it up separately in the list.
//
class EntryList {
public:
void Prepend(Entry* entry) {
if (head_ == nullptr) {
head_ = tail_ = entry;
} else {
entry->next = head_;
head_->prev = entry;
head_ = entry;
}
++size_;
}
void Append(Entry* entry) {
if (tail_ == nullptr) {
head_ = tail_ = entry;
} else {
tail_->next = entry;
entry->prev = tail_;
tail_ = entry;
}
++size_;
}
void Remove(Entry* entry) {
if (entry == head_) {
head_ = entry->next;
} else {
entry->prev->next = entry->next;
}
if (entry == tail_) {
tail_ = entry->prev;
} else {
entry->next->prev = entry->prev;
}
entry->prev = nullptr;
entry->next = nullptr;
--size_;
}
void InsertAfter(Entry* entry, Entry* after) {
if (after != nullptr) {
entry->next = after->next;
if (entry->next) entry->next->prev = entry;
entry->prev = after;
after->next = entry;
++size_;
} else {
Prepend(entry);
}
}
Entry* head() { return head_; }
Entry* tail() { return tail_; }
size_t size() { return size_; }
private:
Entry* head_ = nullptr;
Entry* tail_ = nullptr;
size_t size_ = 0;
};
std::unordered_map<Key, Entry, KeyHash> map_;
EntryList entry_list_;
int total_request_count_ = 0;
};
} // namespace mediapipe
#endif // MEDIAPIPE_UTIL_RESOURCE_CACHE_H_
+150
View File
@@ -0,0 +1,150 @@
// Copyright 2021 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/util/resource_cache.h"
#include "mediapipe/framework/port/gmock.h"
#include "mediapipe/framework/port/gtest.h"
#define EXPECT_BETWEEN(low, high, value) \
do { \
EXPECT_LE((low), (value)); \
EXPECT_GE((high), (value)); \
} while (0)
namespace mediapipe {
namespace {
using ::testing::_;
using ::testing::MockFunction;
using ::testing::Return;
using IntCache = ResourceCache<int, std::shared_ptr<int>, std::hash<int>>;
using MockCreate =
MockFunction<std::shared_ptr<int>(const int& key, int request_count)>;
TEST(ResourceCacheTest, ReturnsNull) {
IntCache cache;
MockCreate create;
EXPECT_CALL(create, Call(1, 1)).WillOnce(Return(nullptr));
EXPECT_EQ(nullptr, cache.Lookup(1, create.AsStdFunction()));
}
TEST(ResourceCacheTest, CountsRequests) {
IntCache cache;
MockCreate create11;
MockCreate create12;
MockCreate create21;
EXPECT_CALL(create11, Call(1, 1)).WillOnce(Return(nullptr));
EXPECT_CALL(create12, Call(1, 2)).WillOnce(Return(nullptr));
EXPECT_CALL(create11, Call(2, 1)).WillOnce(Return(nullptr));
// Verify that request counts are updated, and separate by key.
EXPECT_EQ(nullptr, cache.Lookup(1, create11.AsStdFunction()));
EXPECT_EQ(nullptr, cache.Lookup(1, create12.AsStdFunction()));
EXPECT_EQ(nullptr, cache.Lookup(2, create11.AsStdFunction()));
}
TEST(ResourceCacheTest, CachesValues) {
IntCache cache;
auto value1 = std::make_shared<int>(1);
auto value2 = std::make_shared<int>(2);
MockCreate create1;
MockCreate create2;
MockCreate no_create;
EXPECT_CALL(create1, Call(1, 1)).WillOnce(Return(value1));
EXPECT_CALL(create2, Call(2, 1)).WillOnce(Return(value2));
EXPECT_CALL(no_create, Call(_, _)).Times(0);
// Calls creating the values.
EXPECT_EQ(value1, cache.Lookup(1, create1.AsStdFunction()));
EXPECT_EQ(value2, cache.Lookup(2, create2.AsStdFunction()));
// Calls returning existing values.
EXPECT_EQ(value1, cache.Lookup(1, no_create.AsStdFunction()));
EXPECT_EQ(value2, cache.Lookup(2, no_create.AsStdFunction()));
}
TEST(ResourceCacheTest, EvictToMaxSize) {
IntCache cache;
MockCreate create;
EXPECT_CALL(create, Call(_, 1))
.WillRepeatedly([](int key, int request_count) {
return std::make_shared<int>(key);
});
// Add three entries.
EXPECT_NE(nullptr, cache.Lookup(1, create.AsStdFunction()));
EXPECT_NE(nullptr, cache.Lookup(2, create.AsStdFunction()));
EXPECT_NE(nullptr, cache.Lookup(3, create.AsStdFunction()));
// Keep only two.
auto evicted = cache.Evict(/*max_count=*/2,
/*request_count_scrub_interval=*/4);
ASSERT_EQ(1, evicted.size());
int evicted_entry = *evicted[0];
EXPECT_BETWEEN(1, 3, evicted_entry);
MockCreate no_create;
EXPECT_CALL(no_create, Call(_, 1)).WillOnce(Return(nullptr));
EXPECT_EQ(nullptr, cache.Lookup(evicted_entry, no_create.AsStdFunction()));
for (int key = 1; key <= 3; key++) {
if (key != evicted_entry) {
EXPECT_NE(nullptr, cache.Lookup(key, no_create.AsStdFunction()));
}
}
}
TEST(ResourceCacheTest, EvictWithScrub) {
IntCache cache;
MockCreate create;
EXPECT_CALL(create, Call(_, 1))
.WillRepeatedly([](int key, int request_count) {
return std::make_shared<int>(key);
});
EXPECT_NE(nullptr, cache.Lookup(1, create.AsStdFunction()));
EXPECT_NE(nullptr, cache.Lookup(2, create.AsStdFunction()));
EXPECT_NE(nullptr, cache.Lookup(3, create.AsStdFunction()));
// 3 entries, total request count 4, so nothing evicted from this call.
EXPECT_TRUE(
cache.Evict(/*max_count=*/3, /*request_count_scrub_interval=*/4).empty());
// Increment request counts.
EXPECT_NE(nullptr, cache.Lookup(1, create.AsStdFunction()));
EXPECT_NE(nullptr, cache.Lookup(3, create.AsStdFunction()));
// Expected to evict entry 2, and halve request counts for the other two
// entries.
auto evicted =
cache.Evict(/*max_count=*/3, /*request_count_scrub_interval=*/5);
ASSERT_EQ(1, evicted.size());
EXPECT_EQ(2, *evicted[0]);
// Increment request count.
EXPECT_NE(nullptr, cache.Lookup(3, create.AsStdFunction()));
// Expected to evict entry 1.
evicted = cache.Evict(/*max_count=*/3, /*request_count_scrub_interval=*/1);
ASSERT_EQ(1, evicted.size());
EXPECT_EQ(1, *evicted[0]);
}
} // namespace
} // namespace mediapipe
@@ -392,7 +392,7 @@ struct RegionFlowComputation::FrameTrackingData {
void BuildPyramid(int levels, int window_size, bool with_derivative) {
if (use_cv_tracking) {
#if CV_MAJOR_VERSION == 3
#if CV_MAJOR_VERSION >= 3
// No-op if not called for opencv 3.0 (c interface computes
// pyramids in place).
// OpenCV changed how window size gets specified from our radius setting
@@ -761,7 +761,7 @@ RegionFlowComputation::RegionFlowComputation(
// Tracking algorithm dependent on cv support and flag.
use_cv_tracking_ = options_.tracking_options().use_cv_tracking_algorithm();
#if CV_MAJOR_VERSION != 3
#if CV_MAJOR_VERSION < 3
if (use_cv_tracking_) {
LOG(WARNING) << "Compiled without OpenCV 3.0 but cv_tracking_algorithm "
<< "was requested. Falling back to older algorithm";
@@ -2577,7 +2577,7 @@ void RegionFlowComputation::TrackFeatures(FrameTrackingData* from_data_ptr,
input_mean, gain_image_.get());
}
#if CV_MAJOR_VERSION == 3
#if CV_MAJOR_VERSION >= 3
// OpenCV changed how window size gets specified from our radius setting
// < 2.2 to diameter in 2.2+.
const cv::Size cv_window_size(track_win_size * 2 + 1, track_win_size * 2 + 1);
@@ -2599,7 +2599,7 @@ void RegionFlowComputation::TrackFeatures(FrameTrackingData* from_data_ptr,
feature_track_error_.resize(num_features);
feature_status_.resize(num_features);
if (use_cv_tracking_) {
#if CV_MAJOR_VERSION == 3
#if CV_MAJOR_VERSION >= 3
if (gain_correction) {
if (!frame1_gain_reference) {
input_frame1 = cv::_InputArray(*gain_image_);
@@ -2788,7 +2788,7 @@ void RegionFlowComputation::TrackFeatures(FrameTrackingData* from_data_ptr,
feature_status_.resize(num_to_verify);
if (use_cv_tracking_) {
#if CV_MAJOR_VERSION == 3
#if CV_MAJOR_VERSION >= 3
cv::calcOpticalFlowPyrLK(input_frame2, input_frame1, verify_features,
verify_features_tracked, feature_status_,
verify_track_error, cv_window_size,