Project import generated by Copybara.
GitOrigin-RevId: ff83882955f1a1e2a043ff4e71278be9d7217bbe
This commit is contained in:
@@ -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"],
|
||||
|
||||
@@ -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_
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user