REGISTER_CALCULATOR是MediaPipe中注册Calculator的宏,每个Calculator都通过它来注册的。Calculator是MediaPipe中的基本元素,其地位不言而喻。REGISTER_CALCULATOR功能强大,但是比较复杂:用了多层可变参数宏、多层可变参数模板,想要把它展开真心不是件容易的事。
它到底干了啥呢?本文以TfLiteConverterCalculator为例进行分析。
---------------------------------------------------------------------------------------------------------------------------------
【正文】
\mediapipe\mediapipe\calculators\tflite\tflite_converter_calculator.cc:
..
class TfLiteConverterCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc);
::mediapipe::Status Open(CalculatorContext* cc) override;
::mediapipe::Status Process(CalculatorContext* cc) override;
::mediapipe::Status Close(CalculatorContext* cc) override;
..
};
REGISTER_CALCULATOR(TfLiteConverterCalculator);
::mediapipe::Status TfLiteConverterCalculator::Open(CalculatorContext* cc) {
..
}
::mediapipe::Status TfLiteConverterCalculator::Process(CalculatorContext* cc) {
..
}
::mediapipe::Status TfLiteConverterCalculator::Close(CalculatorContext* cc) {
..
}
其中有宏如下:
REGISTER_CALCULATOR(TfLiteConverterCalculator);
#define REGISTER_CALCULATOR(name) \
REGISTER_FACTORY_FUNCTION_QUALIFIED(::mediapipe::CalculatorBaseRegistry, \
calculator_registration, name, \
absl::make_unique<name>); \
REGISTER_FACTORY_FUNCTION_QUALIFIED(::mediapipe::internal::StaticAccessToCalculatorBaseRegistry, \
access_registration, name, \
absl::make_unique<::mediapipe::internal::StaticAccessToCalculatorBaseTyped<name>>)
分两段分析,先来前半段:
【1】
#define REGISTER_CALCULATOR(name) \
REGISTER_FACTORY_FUNCTION_QUALIFIED(::mediapipe::CalculatorBaseRegistry, \
calculator_registration, name, \
absl::make_unique<name>); \
分两点:1.REGISTER_FACTORY_FUNCTION_QUALIFIED;2.::mediapipe::CalculatorBaseRegistry
【1.1】REGISTER_FACTORY_FUNCTION_QUALIFIED:
#define REGISTER_FACTORY_FUNCTION_QUALIFIED(RegistryType, var_name, name, ...) \
static auto* REGISTRY_STATIC_VAR(var_name, __LINE__) = \
new ::mediapipe::RegistrationToken( \
RegistryType::Register(#name, __VA_ARGS__))
REGISTER_FACTORY_FUNCTION_QUALIFIED是一个可变参数宏,前三个必选,第4个开始可选,其中__VA_ARGS__代表第四个及后面的参数。然而,【1】的调用中传了4个实参,分别是:
- ::mediapipe::CalculatorBaseRegistry,
- calculator_registration,
- name,
- absl::make_unique<name>);
由此可知__VA_ARGS__实际上只有一个参数,它就是absl::make_unique<TfLiteConverterCalculator>
咦!这里的absl::make_unique是什么鬼?
先说absl,absl是Abseil的缩写,是 Google 从代码库中抽取出的一组C++公共库。它是 Google 内部最基本的构建块,经过了充分的测试和优化,像 gRPC、Protobuf 和 TensorFlow 等很多项目都有应用。我们经常看到std::cout、std::vector等,并且知道它是标准模板库STL。类似地,absl::make_unique的意思就比较明显了:意思是构建一个独享智能指针。absl::make_unique<TfLiteConverterCalculator>完全可以用std::make_unique<TfLiteConverterCalculator>来代替。
【1.1.1】REGISTRY_STATIC_VAR
#define REGISTRY_STATIC_VAR_INNER(var_name, line) var_name##_##line##__
#define REGISTRY_STATIC_VAR(var_name, line) \
REGISTRY_STATIC_VAR_INNER(var_name, line)
根据传进来的参数将REGISTRY_STATIC_VAR_INNER展开,则【1.1】的表达式为:
(REGISTER_CALCULATOR(TfLiteConverterCalculator)在tflite_converter_calculator.cc中的第163行)
#define REGISTER_FACTORY_FUNCTION_QUALIFIED(RegistryType, var_name, name, ...) \
static auto* calculator_registration_163__ = \
new ::mediapipe::RegistrationToken( \
RegistryType::Register(#name, __VA_ARGS__))
这里定义了一个static变量calculator_registration_163__,类型为auto。auto并非真实的某一种类型,而是其类型是根据等号右边返回的数据来定。这里等号右边是new了一个RegistrationToken对象,因此calculator_registration_163__的类型就是RegistrationToken。
【calculator_registration_163__为什么要声明为static?】
这里有个小疑问:
calculator_registration_163__中的163是调用宏REGISTER_CALCULATOR所在文件中的的行数,那有没有这种可能:两个不同的Calculator分别声明在两个文件中,而调用这个宏所在的行数刚好相同呢?我的答案是:完全有这种可能!系统中用REGISTER_CALCULATOR注册的Calculator越多,这种可能性越大!此时我们不禁要发问:声明两个相同名字的变量难道没有问题吗?
答案是:没有问题。原因是这个变量的类型是static,是文件专属的变量,如果把前面的static去掉,那么就会变成全局变量,那么编译会报错(变量重定义)。笔者亲自验证这个想法,把auto前面的static去掉:
果然编译报错!原来,文件tflite_tensors_to_detections_calculator.cc的163行也调用了REGISTER_CALCULATOR这个宏:
REGISTER_CALCULATOR(TfLiteTensorsToDetectionsCalculator);
这就很好地解释了calculator_registration_163__为什么要声明为static。
【1.1.2】new RegistrationToken(参数)
RegistrationToken定义在mediapipe\mediapipe\framework\deps\registration_token.h中:
class RegistrationToken {
public:
explicit RegistrationToken(std::function<void()> unregisterer);
// It is useful to have an empty constructor for when we want to declare a
// token, and assign it later.
RegistrationToken() {}
RegistrationToken(const RegistrationToken&) = delete;
RegistrationToken& operator=(const RegistrationToken&) = delete;
RegistrationToken(RegistrationToken&& rhs);
RegistrationToken& operator=(RegistrationToken&& rhs);
..
};
此处很容易猜到调用的是其移动构造函数:RegistrationToken(RegistrationToken&& rhs);那么构造函数的参数的类型也就可以确定为RegistrationToken&&,这是一个右引用,如果不清楚什么是右引用,可点这里。
也就是说RegistryType::Register(#name, __VA_ARGS__)返回的类型是RegistrationToken&&
而根据【1】可知,这里的RegistryType是::mediapipe::CalculatorBaseRegistry,而又有:
using CalculatorBaseRegistry = GlobalFactoryRegistry<std::unique_ptr<CalculatorBase>>;
这里有几个重要信息:
1)CalculatorBaseRegistry是一个GlobalFactoryRegistry为模板的模板类;
2)这个模板类的实参类型是指向CalculatorBase的智能独享指针;
(注意,TfLiteConverterCalculator正是CalculatorBase的子类)
【1.1.3】RegistryType::Register(#name, __VA_ARGS__)
根据外层传进来的参数,可知,它实际是GlobalFactoryRegistry::Register()
GlobalFactoryRegistry定义在mediapipe\mediapipe\framework\deps\registration.h:
template <typename R, typename... Args>
class GlobalFactoryRegistry {
using Functions = FunctionRegistry<R, Args...>;
public:
static RegistrationToken Register(const std::string& name,typename Functions::Function func) {
return functions()->Register(name, std::move(func));
}
...
// Returns the factory function registry singleton.
static Functions* functions() {
static auto* functions = new Functions();
return functions;
}
...
};
只要记住,传到RegistryType::Register(name, func)的实参func是一个指向TfLiteConverterCalculator的智能指针。
【1.1.3.1】GlobalFactoryRegistry::Register()的形参
static RegistrationToken Register(const std::string& name,typename Functions::Function func)
两个疑问:1.这里的typename是什么鬼,为什么出现在这里?
2.Functions::Function func到底是什么?
对于typename,它原本是模板定义中引入的关键字,作用与class相似,但比class更强,模板定义中某些场景下表达的具体含义有歧义,无法判断一个表达式是指代一个变量还是指代一个数据类型,这时就要加上typename来指明该表达式是指一个数据类型。简单地说,这里的typename是为了指明Functions::Function是一个数据类型。关于typename的详细解释可以参考这里。
Functions::Function有点复杂,相关代码简化如下:
template <typename R, typename... Args>
class FunctionRegistry {
public:
using Function = std::function<R(Args...)>;
...
}
...
template <typename R, typename... Args>
class GlobalFactoryRegistry {
using Functions = FunctionRegistry<R, Args...>;
public:
static RegistrationToken Register(const std::string& name,typename Functions::Function func) {
return functions()->Register(name, std::move(func));
}
...
}
- template <typename R, typename... Args>的写法叫可变参数模板;
- GlobalFactoryRegistry和FunctionRegistry都是可变参数模板;
- using的意思可以简单地解理为typedef,但比typedef更强;
- GlobalFactoryRegistry和FunctionRegistry都有Functions的定义:using Functions = xxxxxx;但它们的含义不同。
可变参数模板,顾名思义,就是模板的参数个数是可变的(有点类似于可变参数函数printf),这里我们根据
using CalculatorBaseRegistry = GlobalFactoryRegistry<std::unique_ptr<CalculatorBase>>;
可知GlobalFactoryRegistry的实参只有一个,就是std::unique_ptr<CalculatorBase>。
再看GlobalFactoryRegistry::Functions的定义:
using Functions = FunctionRegistry<R, Args...>;
它是另一个可变参数模板FunctionRegistry 的别名,实参是R,也就是std::unique_ptr<CalculatorBase>
而FunctionRegistry ::Functions的定义:
using Function = std::function<R(Args...)>;
将前面确定的实参R代入得:
using Function = std::function<std::unique_ptr<CalculatorBase>()>;
意思是Function是一个函数类型,该函数没有参数,返回std::unique_ptr<CalculatorBase>。
现在终于可以知道第二个问题了:2.Functions::Function func到底是什么?答案是:一个没有参数、返回一个指向CalculatorBase智能指针的函数。当然,这里要注意这个作案正确的前提是:
a)GlobalFactoryRegistry的实参个数是1
using CalculatorBaseRegistry = GlobalFactoryRegistry<std::unique_ptr<CalculatorBase>>;
如果实参个数多于1个,那么Functions::Function所指代的函数则是有参数的,其参数为第2个及后面的实参。
b)GlobalFactoryRegistry的实参为std::unique_ptr<CalculatorBase>
using CalculatorBaseRegistry = GlobalFactoryRegistry<std::unique_ptr<CalculatorBase>>;
如果模板GlobalFactoryRegistry的实参变成了其它,那么其返回的数据类型将变成对应的,例如:
using StaticAccessToCalculatorBaseRegistry = GlobalFactoryRegistry<std::unique_ptr<StaticAccessToCalculatorBase>>;
那么Functions::Function func指的则是:一个没有参数、返回一个指向StaticAccessToCalculatorBase智能指针的函数。
【1.1.3.2】GlobalFactoryRegistry::Register()的实参
前面已经知道::Register(name, func)的实参func是一个指向TfLiteConverterCalculator的智能指针,即absl::make_unique<TfLiteConverterCalculator>,也就是absl::unique<TfLiteConverterCalculator>。
现在问题来了,Register的形参是std::function<std::unique_ptr<CalculatorBase>()>,是一个函数指针,而实参是absl::unique<TfLiteConverterCalculator>是一个对象指针,类型不同!这怎么行呢。
首先,TfLiteConverterCalculator继承了CalculatorBase,是CalculatorBase的子类,因此,如果形参是absl::unique<CalculatorBase>,是可以接受absl::unique<TfLiteConverterCalculator>实参的。
但是,形参需要的是一个可调用的实体,那就看TfLiteConverterCalculator是否具有“可调用实体”的属性了。——我的判断是没有!因为它既不是函数,也不是实现了Operator()接口的类。在此先不追究,且先往下分析看看有什么转机没有。
接着看GlobalFactoryRegistry::Register()
using Functions = FunctionRegistry<R, Args...>;
...
static RegistrationToken Register(const std::string& name,typename Functions::Function func) {
return functions()->Register(name, std::move(func));
}
...
// Returns the factory function registry singleton.
static Functions* functions() {
static auto* functions = new Functions();
return functions;
}
Functions只是一个数据类型,但不是对象,不可以直接调用。而functions中用new Functions()创建了一个实例,并把指针返回给Register()。然后调用这个实例的Register()函数;
std::move的写法很怪异,其实在C++中很常用,简单地说就是把左引用强转换为右引用,不熟悉的可以点这里。
在进一步分析FunctionRegistry::Registry之前,重新明晰一下传进去的两个实参分别是:
name: TfLiteConverterCalculator
func: absl::make_unique<TfLiteConverterCalculator> //相当于std::unique<TfLiteConverterCalculator>
【1.1.4】FunctionRegistry::Register()
using Function = std::function<R(Args...)>;
...
RegistrationToken Register(const std::string& name, Function func)
LOCKS_EXCLUDED(lock_) {
//p1
std::string normalized_name = GetNormalizedName(name);
absl::WriterMutexLock lock(&lock_);
//p2
std::string adjusted_name = GetAdjustedName(normalized_name);
if (adjusted_name != normalized_name) {
//p3
functions_.insert(std::make_pair(adjusted_name, func));
}
if (functions_.insert(std::make_pair(normalized_name, std::move(func)))
.second) {
return RegistrationToken(
[this, normalized_name]() { Unregister(normalized_name); });
}
LOG(FATAL) << "Function with name " << name << " already registered.";
//p4
return RegistrationToken([]() {});
}
上面标了4个地方p1/p2/p3/p4,下面分别分析:
【p1】:
// Normalizes a C++ qualified name. Validates the name qualification.
// The name must be either unqualified or fully qualified with a leading "::".
// The leading "::" in a fully qualified name is stripped.
std::string GetNormalizedName(const std::string& name) {
constexpr auto kCxxSep = registration_internal::kCxxSep;
std::vector<std::string> names = absl::StrSplit(name, kCxxSep); //kCxxSep的值是"::"
if (names[0].empty()) {
names.erase(names.begin());
} else {
CHECK_EQ(1, names.size())
<< "A registered class name must be either fully qualified "
<< "with a leading :: or unqualified, got: " << name << ".";
}
return absl::StrJoin(names, kCxxSep);
}
std::vector<std::string> names = absl::StrSplit(name, kCxxSep)这一行,其中kCxxSep的值是"::",根据函数名称猜测这行的作用是:把name按"::"进行分割,把结果放在向量names里面。
接着判断如果names[0]为空,那么擦掉第一个成员。什么情况names为空呢?比如,name以"::"开头的时候,如:
::mediapipe::CalculatorBaseRegistry
接着判断,如果names的长度如果是1,那么打印一行警告。我们传进来的参数是TfLiteConverterCalculator,因此按逻辑应该会打印这行警告信息。
go on:
absl::StrJoin(names, kCxxSep);
按函数名称很容易猜测到是把向量names中的所有成员用"::"重新连接起来。
总结p1的作用就是把name中前面的::去掉。
【p2】:
// For names included in NamespaceWhitelist, strips the namespace.
std::string GetAdjustedName(const std::string& name) {
constexpr auto kCxxSep = registration_internal::kCxxSep;
std::vector<std::string> names = absl::StrSplit(name, kCxxSep);
std::string base_name = names.back();
names.pop_back();
std::string ns = absl::StrJoin(names, kCxxSep);
if (NamespaceWhitelist::TopNamespaces().count(ns)) {
return base_name;
}
return name;
}
names与p1的情况一样,
然后 :
std::string base_name = names.back()
name.back()的作用是(官方描述):Returns a reference to the last element in the vecotr,即返回向量最后一个元素的引用。我们传进来的name是"TfLiteConverterCalculator",最后一个元素也是它了,因此base_name="TfLiteConverterCalculator"。
然后:
names.pop_back() //Removes the last element in the vector。
(此时names里的成员数应该是0了)。
然后:
NamespaceWhitelist::TopNamespaces()
constexpr char const* kTopNamespaces[] = {
"mediapipe",
};
/*static*/
const std::unordered_set<std::string>& NamespaceWhitelist::TopNamespaces() {
static std::unordered_set<std::string>* result =
new std::unordered_set<std::string>(
kTopNamespaces, kTopNamespaces + array_size(kTopNamespaces));
return *result;
}
可见NamespaceWhitelist::TopNamespaces()返回的只包含[“mediapipe"]的集合。如果光按名称来理解,NamespaceWhitelist可知道它是命名空间的白名单,这个名单中只有“mediapipe”。
返回值数据类型是std::unsordered_set,而std::unordered_set.count(const key_type k)的作用是:搜索集合中值为k的成员个数。那么下面的代码就很理解了
if (NamespaceWhitelist::TopNamespaces().count(ns)) {
return base_name;
}
return name;
如果ns在白名单中,就返回base_name,否则返回 name。然后此时name和base_name的值都是“TfLiteConverterCalculator”,反正结果都一样!
【p3】:
好习惯:先明晰一下两个参数:
- adjusted_name: "TfLiteConverterCalculator"
- func: unique<TfLiteConverterCalculator>
functions_.insert(std::make_pair(adjusted_name, func));
做了两件事情:
1.创建了一个键值对: 以“TfLiteConverterCalculator”为键,unique<TfLiteConverterCalculator>为值
2.把这个键值对插入functions_中
functions_是什么东西?
请看定义:
private:
mutable absl::Mutex lock_;
std::unordered_map<std::string, Function> functions_ GUARDED_BY(lock_);
了然了:functions_是一个无序的键值对图。
但是后面的GUARDED_BY是什么鬼?其实也是C++中的常见用法,GUARDED_BY是一个应用在数据成员上的属性,它声明了数据成员被给定的监护权保护。对于数据的读操作需要共享的访问权限,而写操作需要独占的访问权限。
【p4】
return RegistrationToken([]() {});
首先,[](){}是一个Lambda表达式,其声明了一个匿名函数,这个函数没有参数,并且函数体没有执行语句。
然后看RegistrationToken对应构造函数和析构函数:
RegistrationToken::RegistrationToken(std::function<void()> unregisterer)
: unregister_function_(std::move(unregisterer)) {}
void RegistrationToken::Unregister() {
if (unregister_function_ != nullptr) {
unregister_function_();
unregister_function_ = nullptr;
}
}
其中unregister_function_的定义:
private:
std::function<void()> unregister_function_ = nullptr;
发现RegistrationToken其实什么事都没做,只是保存了一个std::functoin<void()>函数,在析构函数中调用一下。也就是在New RegistrationToken实例的时候指定一个函数,然后在销毁这个实例前会调用一下。而这里我们指定的这个函数体是[](){},也就是啥都没干。
弱弱地问一下,这个实例什么时候销毁呢?问得好,我们回到【1.1.1】化简的宏
#define REGISTER_FACTORY_FUNCTION_QUALIFIED(RegistryType, var_name, name, ...) \
static auto* calculator_registration_163__ = \
new ::mediapipe::RegistrationToken( \
RegistryType::Register(#name, __VA_ARGS__))
再抛开RegistrationToken的构造过程再化简如下:
#define REGISTER_FACTORY_FUNCTION_QUALIFIED(RegistryType, var_name, name, ...) \
static auto* calculator_registration_163__ = new ::mediapipe::RegistrationToken()
请注意calculator_registration_163__ 的类型是static, 根本没有人会销毁这个变量!——至少当前的版本没有。这也很好理解为什么把[](){}作为其析构函数的回调。其实这个变量目前无关紧要,因为最关键的东西是TfLiteConverterCalculator,已经用键值对的方式保存好了,后面的操作都可以通过“TfLiteConverterCalculator”这个名字找到对应的TfLiteConverterCalculator。
-------------------------------------------------------------------------------------------------------------------------
那么到这里就很清楚了,REGISTER_CALCULATOR这个宏前半部分搞了半天就是把一个函数体用键值对的方式保存在一个变量中。
但还有一个问题:按常理,如果这个functions_如果是一个全局变量那就好解理了。但是这个functions_是FunctionRegistry的一个私有成员,那这个FunctionRegistry的对象是谁呢?又是在哪里声明的呢?
其实玄机在下面这个函数中:
【GlobalFactoryRegistry::functions()】
static Functions* functions() {
static auto* functions = new Functions();
return functions;
}
请注意:fcunstions的类型为static!说明functions只会new一次,其作用跟全局变量是一样一样的!
那么至此只剩下一个遗留的疑问了,也就是前面分析过程中遇到的参数类型不一致的问题。
无论是沿着参数传输的路径分析还是直接看functions_的定义
using Function = std::function<R(Args...)>;
...
std::unordered_map<std::string, Function> functions_
...
都可以确定Function是一个函数体:无参数,返回CalculatorBase:
std::function<CalculatorBase()>
但是实参却是:absl::make_unique<TfLiteConverterCalculator>
make_unique<TfLiteConverterCalculator>的执行过程是:先new一个TfLiteConverterCalculator,然后返回指向这个对象的std::unique_ptr
template <typename T, typename... Args>
typename memory_internal::MakeUniqueResult<T>::scalar make_unique(
Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
哎,等等!在【1】中,absl::make_unique<name>是宏的实参,前面的分析中一直把它当作函数的实参,这是不对的。当它作为函数实参的时候是把执行的结果传给内层,而当它作为宏的实参时,是把展开后的代码传给内层。它展开后就是一个函数呀!而且这个函数返回的数据类型是TfLiteConverterCalculator,而TfLiteConverterCalculator是CalculatorBase的子类,正是functions_所要的类型!
【2】REGISTER_CALCULATOR的后半部分
REGISTER_FACTORY_FUNCTION_QUALIFIED(::mediapipe::internal::StaticAccessToCalculatorBaseRegistry, \
access_registration, name, \
absl::make_unique<::mediapipe::internal::StaticAccessToCalculatorBaseTyped<name>>)
这跟前半部分结构是相似,都是调用REGISTER_FACTORY_FUNCTION_QUALIFIED宏,类比前半部分,可以知道这里也将是用键值对的方式把一个函数注册到某个static变量里面,这个函数返回的数据类型是std::unique_ptr<StaticAccessToCalculatorBase>。主要区别在第4个实参,这次多了层模板。
absl::make_unique<::mediapipe::internal::StaticAccessToCalculatorBaseTyped<name>>
将REGISTER_CALCULATOR的实参TfLiteConverterCalculator代入得:
absl::make_unique<::mediapipe::internal::StaticAccessToCalculatorBaseTyped<TfLiteConverterCalculator>>
我们来看看StaticAccessToCalculatorBaseTyped这个模板:
// Provides access to the static functions within a specific subclass
// of CalculatorBase.
template <typename CalculatorBaseSubclass>
class StaticAccessToCalculatorBaseTyped : public StaticAccessToCalculatorBase {
public:
static_assert(std::is_base_of<::mediapipe::CalculatorBase,
CalculatorBaseSubclass>::value,
"Classes registered with REGISTER_CALCULATOR must be "
"subclasses of ::mediapipe::CalculatorBase.");
static_assert(CalculatorHasGetContract<CalculatorBaseSubclass>(nullptr),
"GetContract() must be defined with the correct signature in "
"every calculator.");
// Provides access to the static function GetContract within a specific
// subclass of CalculatorBase.
::mediapipe::Status GetContract(CalculatorContract* cc) final {
// CalculatorBaseSubclass must implement this function, since it is not
// implemented in the parent class.
return CalculatorBaseSubclass::GetContract(cc);
}
};
StaticAccessToCalculatorBaseTyped<TfLiteConverterCalculator>将得到一个模板类,这个类继承了StaticAccessToCalculatorBase。其中有个重要的函数:GetContract()。请注意,现在我们的模板实参是TfLiteConverterCalculator,代入GetContract()得:
::mediapipe::Status GetContract(CalculatorContract* cc) final {
// CalculatorBaseSubclass must implement this function, since it is not
// implemented in the parent class.
return TfLiteConverterCalculator::GetContract(cc);
}
它会调用TfLiteConverterCalculator::GetContract(cc)
我们来看下它的实现(已根据Android版本化简):
::mediapipe::Status TfLiteConverterCalculator::GetContract(
CalculatorContract* cc) {
const bool has_image_tag = cc->Inputs().HasTag("IMAGE");
const bool has_image_gpu_tag = cc->Inputs().HasTag("IMAGE_GPU");
const bool has_matrix_tag = cc->Inputs().HasTag("MATRIX");
// Confirm only one of the input streams is present.
RET_CHECK(has_image_tag ^ has_image_gpu_tag ^ has_matrix_tag &&
!(has_image_tag && has_image_gpu_tag && has_matrix_tag));
// Confirm only one of the output streams is present.
RET_CHECK(cc->Outputs().HasTag("TENSORS") ^
cc->Outputs().HasTag("TENSORS_GPU"));
if (cc->Inputs().HasTag("IMAGE")) cc->Inputs().Tag("IMAGE").Set<ImageFrame>();
if (cc->Inputs().HasTag("MATRIX")) cc->Inputs().Tag("MATRIX").Set<Matrix>();
if (cc->Inputs().HasTag("IMAGE_GPU"))
cc->Inputs().Tag("IMAGE_GPU").Set<mediapipe::GpuBuffer>();
if (cc->Outputs().HasTag("TENSORS"))
cc->Outputs().Tag("TENSORS").Set<std::vector<TfLiteTensor>>();
if (cc->Outputs().HasTag("TENSORS_GPU"))
cc->Outputs().Tag("TENSORS_GPU").Set<std::vector<GpuTensor>>();
MP_RETURN_IF_ERROR(mediapipe::GlCalculatorHelper::UpdateContract(cc));
// Assign this calculator's default InputStreamHandler.
cc->SetInputStreamHandler("FixedSizeInputStreamHandler");
return ::mediapipe::OkStatus();
}
它干的事情大概是输入输出相关:依次判断输入、输出中是否有存在某个TAG,如果有,则把设置该TAG数据类型。
怎么知道哪个TAG是否存在呢?这个在MediaPipe的graph文件中定义,以人脸检测的demo为例,
\mediapipe\mediapipe\graphs\face_detection\face_detection_mobile_gpu.pbtxt:
...
# Converts the transformed input image on GPU into an image tensor stored as a
# TfLiteTensor.
node {
calculator: "TfLiteConverterCalculator"
input_stream: "IMAGE_GPU:transformed_input_video"
output_stream: "TENSORS_GPU:image_tensor"
}
...
此例中对输入输出分别定义了TAG:IMAGE_GPU和TENSORS_GPU。
关于graph文件是如何关联到Calculator的内容比较多,一两句说不清楚,往后有时间的话将另行分析。这里当是为了更加直观地解理GetContext()的功能。
再顺便看一眼模板StaticAccessToCalculatorBaseTyped 中剩下的代码:
static_assert(std::is_base_of<::mediapipe::CalculatorBase, CalculatorBaseSubclass>::value,
"Classes registered with REGISTER_CALCULATOR must be "
"subclasses of ::mediapipe::CalculatorBase.");
static_assert(CalculatorHasGetContract<CalculatorBaseSubclass>(nullptr),
"GetContract() must be defined with the correct signature in "
"every calculator.");
- static_assert是C++中的关键字,用来做编译期间的断言,因此叫静态断言。其语法:static_assert(常量表达式,提示字符串)。
如果第一个参数常量表达式的值为false,会产生一条编译错误,错误位置就是该static_assert语句所在行,第二个参数就是错误提示字符串。
- std::is_base_of是检测第二个类是否为有继承关系。
由此可知:第一个断言的用途是检测模板实参是否为CalculatorBase的子类。
对于 第二个断言,代入实参稍微分析一下:
CalculatorHasGetContract<TfLiteConverterCalculator>(nullptr)
CalculatorHasGetContract定义在mediapipe\mediapipe\framework\calculator_base.h中,是一个函数模板
// Functions for checking that the calculator has the required GetContract.
template <class T>
constexpr bool CalculatorHasGetContract(decltype(&T::GetContract) /*unused*/) {
typedef ::mediapipe::Status (*GetContractType)(CalculatorContract * cc);
return std::is_same<decltype(&T::GetContract), GetContractType>::value;
}
这里有点意思,这个模板得到的是一个函数,而传入第一个静态断言的值却是调用这个模板函数,以nullptr为参数所返回的值。
继续将实参TfLiteConverterCalculator代入得:
constexpr bool CalculatorHasGetContract(decltype(&TfLiteConverterCalculator::GetContract) /*unused*/) {
typedef ::mediapipe::Status (*GetContractType)(CalculatorContract * cc);
return std::is_same<decltype(&TfLiteConverterCalculator::GetContract), GetContractType>::value;
}
判断TfLiteConverterCalculator::GetContract的是不是以CalculatorContract为参数并返回::mediapipe::Status的函数。
【总结】REGISTER_CALCULATOR(TfLiteConverterCalculator)
前半部分的作用:
1.将TfLiteConverterCalculator封装在一个模板类中;
2.将获取这个模板类指针的函数以键值对的方式注册到某个static变量
后半部分的作用:
1. 检查TfLiteConverterCalculator是否继承了CalculatorBase;
2.检查TfLiteConverterCalculator是否实现了GetContract函数;
3.将GetContract回调封装在一个模板类中;
4.把获取这个模板类指针的函数以键值对的方式注册到某个static变量中。
【最后感叹一句】:C++的奇淫异巧真TM多!