Hexo

  • Home

  • Archives

CPP-Primer-Chapter-6-Functions

Posted on 2020-01-15 Edited on 2020-01-22

Chapter 6 函数

6.1 函数基础

6.1.1 局部变量

自动对象: 只存在于块执行期间的对象称为自动对象(automatic object)。形参 是一种自动对象。函数开始时为形参申请存储空间,因为形参定义在函数体作用域之内,所以一旦函数终止,形参也就被销毁。

局部静态对象: 局部静态对象(local static object)在程序执行路经第一次经过对象语句时初始化,并且直到程序终止才被销毁。

6.1.2 函数声明

如果一个函数永远不会被用到,那么它可以只有声明没有定义。函数声明无须函数体,用一个分号代替即可。

同时函数的声明不包含函数体,所以也就无须形参的名字。

函数三要素(返回类型、函数名、形参类型)描述了函数接口,说明了调用该函数所需的全部信息。函数声明也称作函数原型(function prototype)。

6.1.3 分离式编译

请参见CMake的使用教程。

6.2 参数传递

NOTES:每次调用函数时都会重新创建它的形参,并用传入的实参对形参进行初始化。形参初始化的机理与变量初始化一样。

当形参是引用类型时,我们说它对应的实参被引用传递,或者函数被引用调用。

当形参是被拷贝给形参时,形参和实参是两个相互独立的对象。这样的实参被值传递或者函数被传值调用。

指针形参:指针的行为和其他非引用类型一样,拷贝的是指针的值。拷贝之后,两个指针是不同的指针,指向同一对象,通过指针可以修改它所指对象的值。

在C++中,建议使用引用类型的形参代替指针。

6.2.2 传引用参数

使用引用避免拷贝:拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型(包括IO类型在内)不支持拷贝操作,函数只能通过引用形参访问该类型对象。如果函数无须改变引用形参的值,最好将其声明为常量引用。

使用形参返回额外信息:给函数传入额外的引用实参,来达到隐式的返回额外参数的目的。

6.2.3 const形参和实参

指针或引用形参与const:形参的初始化方式和变量的初始化方式是一样的,所以可以使用非常量初始化一个底层const对象,但是反过来不行。同时一个普通的引用必须用同类型的对象初始化。

6.2.4 数组形参

数组的两个特殊性质对定义和使用作用在数组的函数有影响:

  • 不允许拷贝数组;
  • 使用数组时通常会将其转化为指针;
1
2
3
void print(const int*);
void print(const int[]);
void print(const int[10]);

以上三种函数都是等价的。因为数组是以指针的形式传递给函数的,所以一开始函数并不知道数组的确切尺寸,调用者应该为此提供一些额外的信息。管理指针形参有三种常用的技术。

使用标记指定数组长度:要求数组本身包含一个结束标记,比如C风格字符串。C风格字符串存储的字符串数组中,在最后一个字符后面跟着一个空字符。函数在处理C风格字符串时遇到空字符停止:

1
2
3
4
5
6
void print(const char *cp)
{
if (cp)
while (*cp)
cout << *cp++;
}

该方法仅适用于那些有明显结束标记且该标记不会与普通数据混淆的情况。

使用标准库规范: 传递指向数组首元素和尾后元素的指针,该方法收到了标准库技术的启发。

1
2
3
4
5
6
7
8
9
10
void print(const int *beg, const int *end)
{
// 输出beg到end之间(不含end)的所有元素
while (beg != end)
cout << *beg++ << endl;
}

int j[3] = {0, 1, 2};

print(begin(j), end(j));

显式传递一个表示数组大小的形参:专门指定一个表示数组大小的形参,在C程序和过去的C++程序中常常采用这种方法。

1
2
3
4
5
6
// const int ia[] 等价于const int* ia
// size 表示数组的大小,将它显式地传给函数用于控制对ia元素的访问
void print(const int ia[], size_t size)
{
for (size_t i = 0; i != size; ++i) { cout << ia[i] << endl; }
}

只要传递给函数的size值不超过数组实际的大小,函数就是安全的。

数组形参和const: 当函数不需要对数组元素执行写操作的时候,数组形参应该是指向const的指针。只有当函数确实要改变元素值的时候,才把形参定义成指向非常量的指针。

数组引用形参:C++语言允许将变量定义成数组的引用,基于同样的道理,形参也可以是数组的引用。此时,引用形参绑定到对应的实参上,也就是绑定到数组上。

1
2
3
4
5
6
7
8
void print(int (&arr)[10])
{
for (auto elem : arr)
cout << elem << endl;
}

// f(int &arr[10]) // error: 将arr声明为了引用数组
// f(int (&arr)[10]) // right: arr是具有10个整数的整型数组的引用

传递多维数组:数组的数组,其首元素本身就是一个数组,指针就是一个指向数组的指针。数组第二维的大小都是数组类型的一部分。

1
2
3
4
5
6
void print(int (*matrix)[10], int rowSize) { /* ... */ }
void print(int matrix[][10], int rowSize) { /* ... */ } // 与上等价


// int *matrix[10]; // 10个指针构成的数组
// int (*matrix)[10]; // 指向含有10个整数数组的指针

6.2.5 main: 处理命令行选项

1
int main(int argc, char** argv) { /* ... */ }
  • 第二个形参argv是一个数组,它的元素是指向C风格字符串的指针;
  • 第一个形参argc表示数组中字符串的数量;

WARNING:当使用argv中的实参数时,一定要记得可选的实参从argv[1]开始,argv[0]保存程序的名字,而非用户的输入。

6.2.6 含有可变形参的函数

为了编写能处理不同数量实参的函数,C++11新标准提供了两种主要的方法:

  1. 如果所有的实参类型相同,可以传递一个名为initializer_list标准库类型;
  2. 如果实参的类型不同,可以编写一种特殊的函数,也就是所谓的可变参数模板;

initializer_list形参:是一种标准库类型,用于表示某种特定类型的值的数组。

1
2
3
4
5
6
7
8
9
initializer_list<T> lst;  // 默认初始化 T类型元素的空列表
initializer_list<T> lst{a, b, c, ...}; // lst的元素数量和初始值一样多;lst的元素是对应初始值的副本;列表中的元素是const

lst2(lst);
lst2 = lst; // 拷贝或赋值一个initializer_list对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素

lst.size(); // 列表中的元素数量
lst.begin(); // 返回指向lst中首元素的指针
lst.end(); // 返回指向lst中尾元素下一位置的指针

和vector不一样的是initializer_list中的元素永远是常量值,无法改变其中的值。使用如下的形式编写输出错误信息的函数,使其可以作用于可变数量的实参。

1
2
3
4
5
6
7
void error_msg(initializer_list<string> il)  {
for (auto beg = il.begin(); beg != il.end(); ++beg) {
cout << *beg << " ";
}

cout << endl;
}

如果想向initializer_list形参中传递一个值的序列,则必须把序列放在一对花括号内:

1
2
3
4
5
// excepted 和 actual是string 对象
if (excepted != actual)
error_msg({"functionX", excepted, actual});
else
error_msg({"functionX", "okay"});

含有initializer_list形参的函数也可以同时拥有其他形参。例如,调试系统可能有个名为ErrCode的用来表示不同类型的错误,之前的程序可以改写为:

1
2
3
4
5
6
7
void error_msg(ErrCode e, initializer_list<string> il) {
cout << e.msg() << ": ";
for (const auto &elem : il)
cout << elem << " ";

cout << endl;
}

省略符形参:为了便于C++程序访问某些特殊的C代码而设置的,这些代码使用了名为varargs的C标准库功能。

WARNING:省略符形参应该仅仅用与C和C++通用的类型。特别应该注意的是,大多数类类型的对象在传递给省略符形参时都无法正确拷贝。

省略符形参只能出现在形参列表的最后一个位置,它的形式无外乎以下两种:

1
2
void foo(param_list, ...);
void foo(...);

第一种形式指定了foo函数的部分形参的类型,对应于这些形参的实参将会执行正常的类型检查。省略符形参所对应的实参无须类型检查。在第一种形式中,形参声明后面的逗号是可选的。

6.3 返回类型和return语句

函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值,其他返回类型得到右值。可以像使用其他左值这样来使用返回引用的函数调用,特别是,我们能为返回类型是非常量引用的函数的结果赋值。

列表初始化返回值:C++11新规定函数可以返回花括号包围的值的列表。

main函数的返回值:为了使返回值与机器无关,cstlib头文件定义了两个预处理变量:

1
2
3
4
5
6
int main() {
if (some_failure)
return EXIT_FAILURE;
else
return EXIT_SUCCESS;
}

递归:如果一个函数调用了它自身,不管这种调用是直接的还是间接的,都称该函数递归函数。

1
2
3
4
5
6
int factorial(int val) {
if (val > 1)
return factorial(val - 1) * val;

return 1;
}

6.3.3 返回数组指针

1
int (*func(int i))[10]; // a function returns a pointer pointing to a 10-dimension array

使用尾置返回类型:

1
2
// func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组
auto func(int i) -> int(*)[10];

使用decltype:

1
2
3
4
5
6
int odd[] = {1, 3, 5, 7, 9};
int even[] = {0, 2, 4, 6, 8};

decltype(odd) *arrPtr(int i) {
return (i % 2) ? &odd : &even; // 返回一个指向数组的指针
}

delctype并不负责把数组类型转换成对应的指针,所以delctype的结果是个数组,要想表示arrPtr返回指针还必须在函数声明时加一个*符号。

6.4 函数重载

NOTE:main函数不能重载。

重载和const形参:一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。但是如果形参是某种类型的指针或引用,怎通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层的。

1
2
3
4
5
6
7
// 对于接受引用或指针的函数来说,对象是常量还是非常量对应的形参不同。
// 定义了4个独立的重载函数
Record lookup(Account&);
Record lookup(const Account&);

Record lookup(Account*);
Record lookup(const Account*);

const_cast和重载:const_cast在重载函数的情景中最有用。比如如下函数:

1
2
3
const string &shorterString(const string &s1, const string &s2) {
return s1.size() <= s2.size() ? s1 : s2;
}

函数的参数和返回类型都是const string的引用。需要一个新的shorterString函数,当它的实参不是常量时,得到的结果是一个普通的引用,如下所示:

1
2
3
4
5
string &shorterString(string &s1, string &s2) {
auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));

return const_cast<string&>(r);
}

调用重载函数:函数匹配是指一个过程,这个过程中函数调用与一组重载函数中的某一个关联起来,函数匹配也叫做重载解析(overload resolution)。编译器首先将调用的实参与重载。

当调用重载函数时有三种可能情况:

  • 编译器找到一个与实参最佳匹配的函数,并生成调用该函数的代码;
  • 找不到任何一个函数与调用 的实参匹配,此时编译器发出无匹配的错误代码;
  • 有多于一个函数可以匹配,但是每一个都不是明显的最佳选择。此时也将发生错误,称为二义性调用;

6.4.1 重载与作用域

当user调用print函数时,编译器首先寻找对该函数名的声明,找到的是接受int值的那个局部声明。一旦在当前作用域中找到了所需的名字,编译器就会忽略掉外层作用域中的同名实体。

NOTE:在C++语言中,名字查找发生在类型检查之前。

6.5 特殊用途语言特性

6.5.1 默认实参

函数在多次调用中被赋予同一个值的参数,称为函数的默认实参。

1
2
3
typedef string::size_type sz;

string screen(sz ht = 25, sz wid = 80, char bachgrnd = ' ');

6.5.2 内联函数和constexpr函数

调用函数存在潜在的缺陷:调用函数一般比求等价表达式要慢一些。在大多数机器上,一次函数调用包含着一系列的工作:调用前要先保存寄存器,并在返回时回复;可能需要拷贝实参;程序转向一个新的位置继续执行。

内联函数可以避免函数调用的开销:将函数在每个调用点上“内联地”展开。如下所示:

1
2
3
4
5
6
7
8
9
10
11
  cout << shorterStirng(s1, s2)  << endl; 
cout << (s1.size() < s2.size() ? s1 : s2) << endl;
``

通过在返回类型前加上`inline`,这样就可以将其声明成内联函数。

```c++
inline const string &
shorterString(const string &s1, const string &s2) {
return s1.size() <= s2.size() ? s1 : s2;
}

constexpr函数:能用与常量表达式的函数。需遵循一下规定:函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句。

1
2
constexpr int new_sz() { return 42; }
constexpr int foo = new_sz(); // 正确:foo是一个常量表达式

执行初始化任务时,编译器把对constexpr的调用替换成其结果值。为了能在编译过程中随时展开,constexpr函数被隐式的指定为内联函数。和其他函数不一样,内联函数和constexpr函数可以在程序中多次定义,因为编译器想要展开函数仅有函数声明是不够的,它的多个定义必须完全一致。基于这个原因,内联函数和constexpr函数通常定义在头文件中。

6.5.3 调试帮助

assert预处理宏:预处理宏其实是一个预处理变量,它的行为类似于内联函数。使用一个表达式作为它的条件:assert(expr)。预处理名字由预处理器而非编译器管理。一次我们可以直接使用预处理名字而无需提供using声明。也就是说我们应该使用assert而不是std::assert。

NDEBUG预处理变量:如果定义了NDEBUG,那么assert则什么也不做。我们可以通过#define定义NDEBUG,从而关闭调试状态。

1
CC -D NDEBUG main.cc

该命令等价于在main.cc文件的一开始写#define NDEBUG。如果NDEBUG没有定义,将执行#ifndef和#endif之间的代码;如果定义了NDEBUG,这些代码将被忽略。

除了__func__之外,预处理器还提供了另外4个对于程序调试很有用的名字:

1
2
3
4
__FILE__; // 存放文件名的字符串字面值
__LINE__; // 存放当前行号的整型字面值
__TIME__; // 存放文件编译时间的字符串字面值
__DATE__; // 存放文件编译日期的字符串字面值

6.6 函数匹配

确定候选函数和可行函数:函数匹配的第一步是选定本次调用对应的重载函数集,集合中的函数称为候选函数。候选函数具备两个特征:1. 与被调函数同名,2. 其声明在调用点可见。

第二步考察本次调用提供的实参,然后选择能被这组实参调用的函数,这些新的函数称为可行函数。

第三步是从可行函数中选择与本次调用匹配最佳的函数。基本思想是实参类型与形参类型越接近,它们的匹配度越高。

含有多个形参的函数匹配:编译器依次检查每个实参以确定哪个函数是最佳匹配。如果有且只有一个函数满足下列条件,则匹配成功:

  • 该函数每个实参的匹配都不劣于其他可行函数需要的匹配;
  • 至少有一个实参的匹配由于其他可行函数提供的匹配;

如果检查了所有实参之后,没有任何一个函数脱颖而出,则该调用是错误的,编译器将报告二义性调用。

6.6.1 实参类型转换

编译器将实参类型到形参类型的转换分为以下几个等级:

  1. 精确匹配:
    • 实参类型和形参类型相同;
    • 实参从数组类型或函数类型转换成对应的指针类型;
    • 向实参添加顶层const或者从实参中删除顶层const;
  2. 通过const转换实现的匹配;
  3. 通过类型提升实现的匹配;
  4. 通过算数类型转换或指针转换实现的匹配;
  5. 通过类类型转换实现的匹配;

需要类型提升和算术类型转换的匹配:小整型一般都会提升到int或更大的整数类型。且所有算术类型的转换的级别都一样,从int向unsigned int的转换并不比从int向double的转换级别高。

函数匹配和const实参:如果重载函数的区别在于它们的引用类型的形参是否引用了const,或者指针类型的形参是否指向const,则当调用发生时编译器通过实参是否是常量来决定选择哪个函数。

6.7 函数指针

函数指针指向的是函数而非对象。和其他指针一样,函数指针指向某种特定类型。函数的类型由它的返回类型和形参共同决定,与函数名无关。

1
2
3
4
5
bool length_compare(const string &, const string &);

// pf 指向一个函数,该函数的参数是两个const string的引用,返回值是bool类型
// pf 两端的括号不可少,否则pf是一个返回值为bool指针的函数
bool (*pf)(const string &, const string &); // 未初始化

lenght_compare的类型是bool(const string&, const string &)。

使用函数指针:当我们把函数名作为一个值使用时,该函数自动地转换成指针。

1
2
3
4
5
6
7
pf = length_compare;    // pf指向名为length_compare的函数

pf = &length_compare; // 等价的赋值语句:取地址符是可以的

bool b1 = pf("hello", "world"); // 调用length_compare而无须提前解引指针
bool b2 = (*pf)("hello", "world"); // 一个等价的调用
bool b3 = lenght_compare("hello", "world"); // 另一个等价的调用

在指向不同函数类型的指针间不存在转换规则。但是可以为函数指针赋也各nullptr或者值为0的整型常量表达式,表示该指针没有指向任何一个函数。

重载函数的指针:当使用重载函数时,上下文必须清晰地界定到底应该引用哪个函数。

函数指针形参:和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。此时,形参看起来是函数类型,实际上却是当成指针使用。

1
2
3
void useBigger(const string &s1, const string &s2, bool pf(const string &, const string &));

void useBigger(const string &s1, const string &s2, bool (*pf)(const string &, const string &));

Detectron2: Code Notes

Posted on 2019-11-25 Edited on 2020-01-03

Detectron 2 源码阅读笔记

Most part of the begining of this notes is inherited from https://www.cnblogs.com/marsggbo/p/11677086.html.

1. 代码结构概览

  1. 核心部分

    • configs: 储存各种网络的yaml配置文件;
    • datasets: 存放数据集的地方;
    • deteectron2:运行代码的核心组建;
    • tools:提供了运行代码的入口以及一切可视化的代码问价。
  2. Tutorial部分

    • demo:demo;
    • docs:docs;
    • tests:tests;
    • projects:提供了真实的项目代码示例;

2. 代码逻辑分析

2.1 超参数配置

Detectron2中的参数配置使用了yacs这个库,这个库能够很好地重用和拼接超参数文件配置。我们先看一下detrctron2/config/的文件结构:

  • compat.py: 应该是对之前的Detectron库的兼容吧,可忽略。
  • config.py: 定义了一个CfgNode类,这个类继承自fvcore库(fb写的一个共公共库,提供一些共享的函数,方便各种不同项目使用)中定义的CfgNode,总之就是不断继承。。。继承关系是这样的:detrctron2.config.CfgNode->fcvore.common.config.CfgNode->yacs.config.CfgNode->dict。另外该文件还提供了get_cfg()方法,该方法会返回一个含有默认配置的CfgNode,而这些默认的配置值在下面的default.py中定义了,之所以这样做是因为要配置的默认值太多了,所以为了文档清晰才写到了一个新的文件中去,不过,yacs库的作者也建议这样做。
  • default.py: 如上面所说,该文件定义了各种参数的默认值。

了解配置函数的方法后我们再回到tools/train_net.py,我们一行一行的来理解。

  • tools/train_net.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from detectron2.config import get_cfg
from detectron2.engine import DefaultTrainer, default_argument_parser, default_setup, hooks, launch
...

def setup(args):
"""
Create configs and perform basic setups.
"""
cfg = get_cfg()
cfg.merge_from_file(args.config_file)
cfg.merge_from_list(args.opts)
cfg.freeze()
default_setup(cfg, args)
return cfg
  • cfg = get_cfg(): 获取已经配置好默认参数的cfg
  • cfg.merge_from_file(args.config_file):config_file是指定的yaml配置文件,通过merge_from_file这个函数会将yaml文件中指定的超参数对默认值进行覆盖。
  • cfg.merge_from_list(args.opts):merge_from_list作用同上面的类似,只不过是通过命令行的方式覆盖。
  • cfg.freeze(): freeze函数的作用是将超参数值冻结,避免被程序不小心修改。
  • default_setup(cfg, args):default_setup是detectron2/engine/default.py中提供的一个默认配置函数,具体是怎么配置的这里不详细说明了。不过需要知道的值这个文件中还提供了很多其他的配置函数,例如还提供了两个类:DefaultPredictor和DefaultTrainer。

2.2 Trainer结构

既然上面提到了DefaultTrainer,那么我们就从这个类入手了解一下detectron2.engine,其代码结构如下:

  • train_loop.py:这个函数主要作用是提供了三个重要的类:

    • HookBase: 这是一个Hook的基类,用于指定在训练前后或者每一个step前后需要做什么事情,所以根据特定的需求需要对如下四种方法做不同的定义:before_train, after_train, before_step, after_step。以before_step。
    • TrainerBase: 该类中定义的函数可以归纳成三种,并初始化_hooks为[]:
      • register_hooks:这个很好理解,就是将用户定义的一些hooks进行注册,说大白话就是把若干个Hook放在一个list里面去。之后只需要遍历这个list依次执行就可以了。
        • register_hooks(hooks)会将输入的hooks中的trainer设置为当前的类实例的弱引用,并将其添加到当前类实例的self._hooks中去;
      • 第二类其实就是上面提到的遍历hook list并执行hook,不过这个遍历有四种,分别是before_train,after_train,before_step,after_step。还有一个就是run_step,这个函数其实就是平常我们在编写训练过程的代码,例如读数据,训练模型,获取损失值,求导数,反向梯度更新等,只不过在这个类里面没有定义。
      • 第三类就是train函数,它有两个参数,分别是开始的迭代数和最大的迭代数。之后就是重复依次执行第二类中的函数指定迭代次数。
    • SimpleTrainer:其实就是继承自TrainerBase,然后定义了run_step等方法。我们后面也可以继承这个类做进一步的自定义。
      • 初始化基类,并赋予其model、data_loader、_data_loader_iter、optimizer;
      • 这在随后的训练过程中会被继承为默认的训练类DefaultTrainer。
  • defaults.py: 上面已介绍,提供了两个类:DefaultPredictor和DefaultTrainer,这个DefaultTrainer就继承自SimpleTrainer,所以存在如下继承关系:detectron2.engine.default.DefaultTrainer->detectron2.engine.train_loop.SimpleTrainer->detectron2.engine.train_loop.TrainerBase

  • hooks.py:定义了很多继承自train_loop.HookBase的Hook。

    • CallBackHook: Create a hook using callback functions provided by the user.
    • IterationTimer:Track the time spent for each iteration (each run_step call in the trainer). Print a summary in the end of training.;
    • PeriodicWriter:Write events to EventStorage periodically.
    • PeriodicCheckpointer:Same as :class:detectron2.checkpoint.PeriodicCheckpointer, but as a hook.
    • LRScheduler:A hook which executes a torch builtin LR scheduler and summarizes the LR. It is executed after every iteration.
    • AutogradProfiler:A hook which runs torch.autograd.profiler.profile.
    • EvalHook: Run an evaluation function periodically, and at the end of training.
    • PreciseBN: The standard implementation of BatchNorm uses EMA in inference, which is sometimes suboptimal. This class computes the true average of statistics rather than the moving average, and put true averages to every BN layer in the given model.
  • launch.py: 前面提到过,可以理解成代码启动器,可以根据命令决定是否采用分布式训练(或者单机多卡)或者单机单卡训练。

整个训练过程按照如下流程进行:

1
2
3
4
5
6
7
self.before_train()
for self.iter in range(start_iter, max_iter):
self.before_step()
self.run_step()
self.after_step()
finally:
self.after_train()

小结:
fig-1

2.3 Trainer解析

1
2
3
4
5
6
7
8
9
10
11
12
def main(args):
cfg = setup(args)

if args.eval_only:
...
trainer = Trainer(cfg)
trainer.resume_or_load(resume=args.resume)
if cfg.TEST.AUG.ENABLED:
trainer.register_hooks(
[hooks.EvalHook(0, lambda: trainer.test_with_TTA(cfg, trainer.model))]
)
return trainer.train()

2.3.1 build_*方法

build_*主要来自于engine.defaults.DefaultTrainer类,继承自engine.train_loop.SimpleTrainer类。而detectron2.engine.default.DefaultTrainer在其__init__(self, cfg)函数中定义了解析cfg。如下面代码所示,cfg会作为参数倍若干个build_*方法解析,得到解析后的model,optimizer,data_loader等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from detectron2.modeling import build_model
class DefaultTrainer(SimpleTrainer):
def __init__(self, cfg):
"""
Args:
cfg (CfgNode):
"""
# Assume these objects must be constructed in this order.
model = self.build_model(cfg)
optimizer = self.build_optimizer(cfg, model)
data_loader = self.build_train_loader(cfg)

...

self.register_hooks(self.build_hooks())

@classmethod
def build_model(cls, cfg):
"""
Returns:
torch.nn.Module:
"""
model = build_model(cfg)
logger = logging.getLogger(__name__)
logger.info("Model:\n{}".format(model))
return model

下面我们以DefaultTrainer.build_model为例来介绍注册机制,该方法调用了detectron2/modeling/meta_arch/build.py的build_model函数,其源代码如下:

1
2
3
4
5
6
7
8
9
10
11
from detectron2.utils.registry import Registry

META_ARCH_REGISTRY = Registry("META_ARCH")
META_ARCH_REGISTRY.__doc__ = """..."""

def build_model(cfg):
"""
Built the whole model, defined by `cfg.MODEL.META_ARCHITECTURE`.
"""
meta_arch = cfg.MODEL.META_ARCHITECTURE
return META_ARCH_REGISTRY.get(meta_arch)(cfg)
  • meta_arch = cfg.MODEL.META_ARCHITECTURE: 根据超参数获得网络结构的名字
  • return META_ARCH_REGISTRY.get(meta_arch)(cfg):META_ARCH_REGISTRY是一个Registry类(这个在后面会详细介绍),可以将这一行代码拆成如下几个步骤:
1
2
model = META_ARCH_REGISTRY.get(meta_arch)
return model(cfg)

2.3.2 注册机制Registry

假如你想自己实现一个新的backbone网络,那么你可以这样做:

首先在detectron2中定义好如下(实际上已经定义了):

1
2
3
# detectron2/modeling/backbone/build.py

BACKBONE_REGISTRY = Registry('BACKBONE')

之后在你创建的新的文件下按如下方式创建你的backbone

1
2
3
4
5
6
7
8
9
10
11
12
# detectron2/modeling/backbone/your_backbone.py
from .build import BACKBONE_REGISTRY

# 方式1
@BACKBONE_REGISTRY.register()
class MyBackbone():
...

# 方式2
class MyBackbone():
...
BACKBONE_REGISTRY.register(MyBackbone)

Registry源代码如下(有删减):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Registry(object):
def __init__(self, name):
self._name = name
self._obj_map = {}

def _do_register(self, name, obj):
assert (
name not in self._obj_map
), "An object named '{}' was already registered in '{}' registry!".format(name, self._name)
self._obj_map[name] = obj

def register(self, obj=None):
if obj is None:
# used as a decorator
def deco(func_or_class):
name = func_or_class.__name__
self._do_register(name, func_or_class)
return func_or_class

return deco

# used as a function call
name = obj.__name__
self._do_register(name, obj)

def get(self, name):
ret = self._obj_map.get(name)
if ret is None:
raise KeyError("No object named '{}' found in '{}' registry!".format(name, self._name))
return ret
  • 首先是__init__部分:
    • self._name则是你要注册的名字,例如对于完整的模型而言,name一般取META_ARCH。当然如果你需要自定义backbone网络,你也可以定义一个Registry('BACKBONE');
    • self._obj_map:其实就是一个字典。以模型为例,key就是你的模型名字,而value就是对应的模型类。这样你在传参时只需要修改一下模型名字就能使用不同的模型了。具体实现方法就是后面这几个函数;
  • register: 可以看到该方法定义了注册的两种方式,一种是当obj==None的时候,使用装饰器的方式注册,另外一种就是直接将obj作为参数调用_do_register进行注册。
  • _do_register:真正注册的函数,可以看到它首先会判断name是否已经存在于self._obj_map了。什么意思呢?还是以backbone为例,我们定义了一个BACKBONE_REGISTRY = Registry('BACKBONE'),然后又定义了很多种backbone,而这些backbone都使用@BACKBONE_REGISTRY.register()的方式注册到了BACKBONE_REGISTRY._obj_map中了,所以才取名为Registry。

2.4 Detectron2 整体代码框架

整体框架如下图所示:

fig-2

3. Dataset

构建data_loader原理步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# engine/default.py
from detectron2.data import (
MetadataCatalog,
build_detection_test_loader,
build_detection_train_loader,
)

class DefaultTrainer(SimpleTrainer):
def __init__(self, cfg):
# Assume these objects must be constructed in this order.
data_loader = self.build_train_loader(cfg)

...

@classmethod
def build_train_loader(cls, cfg):
"""
Returns:
iterable
"""
return build_detection_train_loader(cfg)

函数调用关系如下图:

fig-3

结合前面两篇文章的内容可以看到detectron2在构建model,optimizer和data_loader的时候都是在对应的build.py文件里实现的。我们看一下build_detection_train_loader是如何定义的(对应上图中紫色方框内的部分(自下往上的顺序)):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def build_detection_train_loader(cfg, mapper=None):
"""
A data loader is created by the following steps:

1. Use the dataset names in config to query :class:`DatasetCatalog`, and obtain a list of dicts.
2. Start workers to work on the dicts. Each worker will:
* Map each metadata dict into another format to be consumed by the model.
* Batch them by simply putting dicts into a list.
The batched ``list[mapped_dict]`` is what this dataloader will return.

Args:
cfg (CfgNode): the config
mapper (callable): a callable which takes a sample (dict) from dataset and
returns the format to be consumed by the model.
By default it will be `DatasetMapper(cfg, True)`.

Returns:
a torch DataLoader object
"""
# 1. 获得dataset_dicts
dataset_dicts = get_detection_dataset_dicts(
cfg.DATASETS.TRAIN,
filter_empty=True,
min_keypoints=cfg.MODEL.ROI_KEYPOINT_HEAD.MIN_KEYPOINTS_PER_IMAGE
if cfg.MODEL.KEYPOINT_ON
else 0,
proposal_files=cfg.DATASETS.PROPOSAL_FILES_TRAIN if cfg.MODEL.LOAD_PROPOSALS else None,
)

# 2. 将dataset_dicts转化成torch.utils.data.Dataset
dataset = DatasetFromList(dataset_dicts, copy=False)

# 3. 进一步转化成MapDataset,每次读取数据时都会调用mapper来对dict进行解析
if mapper is None:
mapper = DatasetMapper(cfg, True)
dataset = MapDataset(dataset, mapper)

# 4. 采样器
sampler_name = cfg.DATALOADER.SAMPLER_TRAIN
if sampler_name == "TrainingSampler":
sampler = samplers.TrainingSampler(len(dataset))

...

batch_sampler = build_batch_data_sampler(
sampler, images_per_worker, group_bin_edges, aspect_ratios
)

# 5. 数据迭代器 data_loader
data_loader = torch.utils.data.DataLoader(
dataset,
num_workers=cfg.DATALOADER.NUM_WORKERS,
batch_sampler=batch_sampler,
collate_fn=trivial_batch_collator,
worker_init_fn=worker_init_reset_seed,
)

return data_loader

后面的采样器和data_loader可以参阅https://www.cnblogs.com/marsggbo/p/11308889.html一文弄懂Pytorch的DataLoader, DataSet, Sampler之间的关系。

3.1 获取dataset_dicts

get_detection_dataset_dicts(dataset_names)函数需要传递的一个重要参数是dataset_names,这个参数其实就是一个字符串,用来指定数据集的名称。通过这个字符串,该函数会调用data/catalog.py的DatasetCatalog类来进行解析得到一个包含数据信息的字典。

解析的原理是:DatasetCatalog有一个字典_REGISTERED,默认已经注册好了例如coco, voc这些数据集的信息。如果你想要使用你自己的数据集,那么你需要在最开始前你需要定义你的数据集名字以及定义一个函数(这个函数不需要传参,而且最后会返回一个dict,该dict包含你的数据集信息),举个栗子:

1
2
3
4
5
6
7
from detectron2.data import DatasetCatalog
my_dataset_name = 'apple'
def get_dicts():
...
return dict

DatasetCatalog.register(my_dataset_name, get_dicts)

当然,如果你的数据集已经是COCO的格式了,那么你也可以使用如下方法进行注册:

1
2
3
from detectron2.data.datasets import register_coco_instances
my_dataset_name = 'apple'
register_coco_instances(my_dataset_name, {}, "json_annotation.json", "path/to/image/dir")

最后,get_detection_dataset_dicts会返回一个包含若干个dict的list,之所以是list是因为参数dataset_names也是一个list,这样我们就可以制定多个names来同时对数据进行读取。

3.2 解析成DatasetFromList

DatasetFromList(dataset_dict)函数定义在detectron2/data/common.py中,它其实就是一个torch.utils.data.Dataset类,其源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class DatasetFromList(data.Dataset):
"""
Wrap a list to a torch Dataset. It produces elements of the list as data.
"""

def __init__(self, lst: list, copy: bool = True):
"""
Args:
lst (list): a list which contains elements to produce.
copy (bool): whether to deepcopy the element when producing it,
so that the result can be modified in place without affecting the
source in the list.
"""
self._lst = lst
self._copy = copy

def __len__(self):
return len(self._lst)

def __getitem__(self, idx):
if self._copy:
return copy.deepcopy(self._lst[idx])
else:
return self._lst[idx]

3.3 将DatasetFromList转化为MapDataset

其实DatsetFromList和MapDataset都是torch.utils.data.Dataset的子类,那他们的区别是什么呢?很简单,区别就是后者使用了mapper。

在解释mapper是什么之前我们首先要知道的是,在detectron2中,一张图片对应的是一个dict,那么整个数据集就是listdict。之后我们再看DatsetFromList,它的__getitem__函数非常简单,它只是简单粗暴地就返回了指定idx的元素。显然这样是不行的,因为在把数据扔给模型训练之前我们肯定还要对数据做一定的处理,而这个工作就是由mapper来做的,默认情况下使用的是detectron2/data/dataset_mapper.py中定义的DatasetMapper,如果你需要自定义一个mapper也可以参考这个写。

1
DatasetMapper(cfg, is_train=True)

我们继续了解一下DatasetMapper的实现原理,首先看一下官方给的定义:

1
2
3
4
"""
A callable which takes a dataset dict in Detectron2 Dataset format,
and map it into a format used by the model.
"""

简单概括就是这个类是可调用的(callable),所以在下面的源码中可以看到定义了__call__方法。

该类主要做了这三件事:

1
2
3
4
The callable currently does the following:
1. Read the image from "file_name"
2. Applies cropping/geometric transforms to the image and annotations
3. Prepare data and annotations to Tensor and :class:`Instances`

其源码如下(有删减):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class DatasetMapper:
def __init__(self, cfg, is_train=True):
# 读取cfg的参数
...

def __call__(self, dataset_dict):
"""
Args:
dataset_dict (dict): Metadata of one image, in Detectron2 Dataset format.

Returns:
dict: a format that builtin models in detectron2 accept
"""
dataset_dict = copy.deepcopy(dataset_dict) # it will be modified by code below

# 1. 读取图像数据
image = utils.read_image(dataset_dict["file_name"], format=self.img_format)

# 2. 对image和box等做Transformation
if "annotations" not in dataset_dict:
image, transforms = T.apply_transform_gens(
([self.crop_gen] if self.crop_gen else []) + self.tfm_gens, image
)
else:
...
image, transforms = T.apply_transform_gens(self.tfm_gens, image)
if self.crop_gen:
transforms = crop_tfm + transforms

image_shape = image.shape[:2] # h, w

# 3.将数据转化成tensor格式
dataset_dict["image"] = torch.as_tensor(image.transpose(2, 0, 1).astype("float32"))
...

return dataset_dict

MapDataset:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class MapDataset(data.Dataset):
def __init__(self, dataset, map_func):
self._dataset = dataset
self._map_func = PicklableWrapper(map_func) # wrap so that a lambda will work

self._rng = random.Random(42)
self._fallback_candidates = set(range(len(dataset)))

def __len__(self):
return len(self._dataset)

def __getitem__(self, idx):
retry_count = 0
cur_idx = int(idx)

while True:
data = self._map_func(self._dataset[cur_idx])
if data is not None:
self._fallback_candidates.add(cur_idx)
return data

# _map_func fails for this idx, use a random new index from the pool
retry_count += 1
self._fallback_candidates.discard(cur_idx)
cur_idx = self._rng.sample(self._fallback_candidates, k=1)[0]

if retry_count >= 3:
logger = logging.getLogger(__name__)
logger.warning(
"Failed to apply `_map_func` for idx: {}, retry count: {}".format(
idx, retry_count
)
)
  • self._fallback_candidates是一个set,它的特点是其中的元素是独一无二的,定义这个的作用是记录可正常读取的数据索引,因为有的数据可能无法正常读取,所以这个时候我们就可以把这个坏数据的索引从_fallback_candidates中剔除,并随机采样一个索引来读取数据。
  • __getitem__中的逻辑就是首先读取指定索引的数据,如果正常读取就把该所索引值加入到_fallback_candidates中去;反之,如果数据无法读取,则将对应索引值删除,并随机采样一个数据,并且尝试3次,若3次后都无法正常读取数据,则报错,但是好像也没有退出程序,而是继续读数据,可能是以为总有能正常读取的数据吧;

3. Debug

3.1 Using ground.py

4. Training

4.1 在COCO数据集上训练

Using 1 image per batch, cause the desktop can’t afford 2 images per batch.

1
2
3
python tools/train_net.py \
--config-file configs/COCO-Keypoints/keypoint_rcnn_R_50_FPN_1x.yaml \
SOLVER.IMS_PER_BATCH 1 SOLVER.BASE_LR 0.0025

Training parameters:

  • max_mem: 2638M;
  • 89999 iterations;
  • The training log is saved to log_1127_first_training.txt;

4.2 训练流程

主要训练函数为tools/train_net.py中的main函数,launch函数将训练过程分布化,在当前阶段暂时不予深究。

5. Validation

python demo/demo.py
–config-file configs/COCO-Keypoints/keypoint_rcnn_R_50_FPN_1x.yaml
–input comunity/1574912817253.png
–opts MODEL.WEIGHTS weights/COCO-Keypoints/model_final_04e291.pkl

CPP Primer Chapter 5 Statements

Posted on 2019-11-16 Edited on 2020-01-15

Chapter 5 语句

和大多数语言一样,C++提供了条件执行语句、重复执行相同代码的循环语句和用于中断当前控制流的跳转语句。

5.1 简单语句

空语句

BEST PRACTICE:使用空语句时最好是加上注释,从而令独这段代码的人知道该语句是有意省略的。

WARNING:多余的空语句并非总是无害的。

复合语句(块)

复合语句块是指被{}括起来的语句和声明序列,也被成为block。

5.2 语句作用域

5.3 条件语句

悬挂else:C++规定else与离它最近的尚未匹配的if匹配。

5.3.2 switch 语句

switch 内部的控制流:如果某个case匹配成功,将错哦你该标签开始往后顺序执行所有case分支,除非程序显式地中断了这一过程。

1
2
3
4
5
6
7
8
9
10
11
12
unsigned vowelCnt = 0;

switch (ch)
{
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
++vowelCnt;
break;
}

case 标签之后不一定非得换行。

1
2
3
4
5
switch (ch) {
case 'a': case 'e': case 'i': case 'o': case 'u':
++vowelCnt;
break;
}

WARNING:常见的错觉是程序只执行匹配成功的那个case分支语句。因此在每个标签后写上break,是最好的应用。这样新增加新的分支也不用在前面加上break了。

default 标签:当没有任何一个case标签能匹配上switch表达式的值时,程序将执行紧跟在default后面的语句。每一个switch语句。

5.4 迭代语句

5.4.3 范围for语句

序列:可以返回迭代器begin和end的成员。采用范围for语句时,在迭代过程中修改序列结果,可能会导致循环体的尾指针失效。

5.5 跳转语句

C++ 提供了4种跳转语句:break、continue、goto、return。

5.5.1 break语句

break语句的作用范围仅限于最近的循环或者switch。

5.5.2 continue语句

continue语句只出现在for、while、do while循环内,或者嵌套在此类循环的语句或块的内部。

5.5.3 goto语句

hmmmm,没什么可说的。

5.6 try语句

  • throw表达式;
  • try语句块;
  • 一套异常类。

5.6.1 throw表达式

见练习5.22.cpp。

5.6.2 try语句块

Pytorch Notes

Posted on 2019-09-12

Pytorch Notes

This notes mostly comes from source code from pytorch just to summarize the most import part of those most frequently used functions.

torchvision

transforms

..ToTensor()

torchvision.tansforms.functional(pic): Convert a PIL Image or numpy.ndarray to tensor.

1
2
3
4
5
6
# This will divide image pixel value by 255.0 to normalize it into [0, 1.0]
img = img.transpose(0, 1).transpose(0, 2).contiguous()
if isinstance(img, torch.ByteTensor):
return img.float().div(255)
else:
return img

..Normalize()

Using *.*.functional.normalize() function: Normalize a tensor image with mean and standare deviation:

1
2
3
mean = torch.as_tensor(mean, dtype=dtype, device=tensor.device)
std = torch.as_tensor(std, dtype=dtype, device=tensor.device)
tensor.sub_(mean[:, None, None]).div_(std[:, None, None])

Hello World

Posted on 2019-09-10 Edited on 2019-08-14

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

Python Notes

Posted on 2019-09-06 Edited on 2019-09-09

Python Notes

This is the place to take some regular notes for python.

Numpy

1
numpy.unique(ar, return_index=False, return_inverse=False, return_counts=False, axis=None)

Pandas

CPP Primer Chapter 4: Expressions

Posted on 2019-08-27 Edited on 2019-11-16

Expressions

4.1 Fundamentals

There ar both unary operators and binary operators.

  1. Unary operators: address-of (&) and dereference (*), act on one operand;
  2. Binary operators: equality (==) and multiplication (*), act on two operands;
  3. Tenrary operator: takes three operands;
  4. Function call: takes unlimited number of operands;

Operand Conversions: The small integral type operands (e.g., bool, char, short, etc) are generally promoted to a larger integraltype, typically int.

Overloaded Operators:The IO library >> and << operators we used with strings, vectors, and iterators are all overloaded operators.

Lvalue and Rvalue: Iherited from C. Some lvalues, such as const objects, may not be the left-hand operand of an assignment. Moreover, some expression yield objects but return them as rvalues, not lvalues.

Roughly speaking:

  • When we use an object as an rvalue, we use the object`s value (its content);
  • When we use an object as an lvalue, we use the object’s identity (its location in memory);

Important: We can use an lvalue when an rvalue is required, but we ccannot use an rvalue when an lvalue (i.e.., a location) is required.

  • Assignment requiers a lvalue as itts left-hand operand and yields its left-hand operand as an lvalue;
  • The address-of operator requiers an lvalue operand and returns a pointer to its operand as an rvalue;
  • The built-in dereferenect and subscript operators and the iterator dereference and string and vector subscript operators all yield lvalues;
  • The built-in and iterator increment and decrement operators require lvalue operands and the prefix versions also yield lvalues.
  • When we apply decltype to an expression, the result is a reference type if the expression yields an lvalue.

4.1.2 Precedence and Associativity

Table 4.12 (p. 166) lists all the operators organized into segmentes separated by double lines.

4.1.3 Order of Evaluation

In most cases, the order of how the operands are evaluated is largely unspecified. There are four operators that do guarantee the order in which operands are evaluated.

  • The logical AND (&&) guarantees thta its left-hand operand is evaluated first. It is also guaranteed that the right -hand operand is evaluated only if the left-hand operand is true.
  • The logical OR (||);
  • The condition (? :) operator, the tenary operator;
  • The comma (,) operator;

4.3 Logical and Relational Operatiors

Short-circuit evaluation: The logical AND and OR operators always evaluate their left operand before the right. Moreover, the right operands is evalutaed if and only if the left operand does not determine the result:

  • The right side of an && is evalutaed if and only if the left side is true;
  • The right side of an || is evaluated if and only if the left side is false;

4.4 Assignment Operatiors

The left-hand operand of an assignment operator must be a modifiable lvalue.

1
2
3
1024 = k;     // error: literals are rvalues
i + j = k; // error: arithmetic expressions are rvalues
ci = k; // error: ci is a const (nonmodifiable) lvalue

Assignment Is Right Associative: Unlike the other binary operators, assignment is right associative.

1
2
3
4
5
int i;
// a better way to write the loop---what the condition does is now clearer
while ((i = get_value()) != 42) {
// do something...
}

Compound Assignment Operators: There are compound assignments for each of these operators.

1
2
+=    -=    *=    /=    %=;    // arithmetci operators
<<= >>= &= ^= |=; // bitwise operators

Each compound operator is essentially equivalent to a = a op b.

4.5 Increment and Decrement Operators

There are two forms of these operators: prefix and postfix.

  • prefix: increments its operand and yields the changed object as its result;
  • postfix: yield a copy of the original, unchanged object as its result;

Advice: Use postfix operators only when necessary.

Combinig Dereference and Increment in a Single Expression: The postfix versions of ++ and – are used when we want to use the current value of a variable and increment it in a single compound expression.

1
2
3
4
auto pbeg = v.begin();
// print elements up to the first negative value
while (pbeg != v.end() && *pbeg >= 0)
cout << *pbeg++ << endl; // print the current value and advance pbeg

Remember that operands can be evaluated in any order: Most operators give no guarantee as to the order in which operands will be evaluated.

1
2
3
4
5
6
7
// the behavior of the following loop is undefined!
while (beg != s.end() && !isspace(*beg))
\*beg = touper(\*beg++);

// which could be evaluated as:
*beg = toupper(\*beg);
*(beg + 1) = toupper(\*beg);

Or it might evaluate it in yet some other way.

4.6 The Member Access Operators

Th dereference has a lower precedence than dot operator (member access operator).

4.7 The conditional Operator

1
2
// cond ? expr1 : expr2
string grade = (grade < 60) ? "fail" : "pass";

Nesting Conditional Operations: We can nest one conditional operator inside another.

1
2
string grade = (grade > 90) ? "high"
: (grade < 60) : "fail" : "pass";

4.8 The bitwise Operator

The bitwise operators take operands of integral type that they use as a collection of bits. Because there are no guarantees for how the sign bit is handled, we strongly recommend using unsigned types with the bitwise operators.

1
2
3
4
5
6
7
unsigned char bits = 0233;  // char (8 bits, 1 byte)

bits << 8; // bits promoted to int (32 bits, 4 bytes) and then shifted left by 8 bits

bits << 31; // left shift 31 bits, left-most bits discarded

bits >> 3; // rigth shift 3 bits, 3 right-most bits discards

A bitwise operation example:

1
2
quiz1 |= 1UL << 27;     // set the 27th number of quiz1 to be 1
quiz1 &= ~(1UL << 27); // set the 27th number of quiz1 to be 0

Shift Operatiors (aka IO operators) Are Left Associative: The expression:

1
2
cout << "hi" << " there" << endl;
( (cout << "hi") << " there" ) << endl;

4.9 The sizeof Operator

The sizeof operator returns the size, in bytes, of an expression or a type name. Under the new standard, we can use the scope operator to ask for the size of a member of a class type.

1
sizeof Sales_data::revenue;

Because sizeof returns the size of the entire array, we can determine the number of elements in an array by dividing the array size by the element size.

1
2
constexpr size_t sz = sizeof(ia)/sizeof(*ia);
int arr2[sz];

4.10 Comma Operator

The comma operator takes two operands, which it evaluates frmo left to rigtht.

4.11 Type Conversions

Implicit conversions: Some conversions are carried out automatically without programmer intervention–and sometimes without programmer knowledge.

Conwerting a double to an int truncates the double‘s value, discarding the decimal portin.

When Implicit Conversions Occur

  • In arithmetic and relational expressions with operands of mixed types, the types are converted to a common type;
  • Conversions also happen during function calls;

4.11.1 The Arithmetic Conversions

Nothing much, never mind.

4.11.2 Other Implicit Conversions

Array to Pointer Conversions: In most expressions, when we use an array, the array is automatically converted to a pointer.

Pointer Conversions: There are several other pointer conversions.

4.11.3 显式转换

(I’m so fucking tired of taking English notes, English should be my helper not the obstacle)

NOTES 虽然有时不得不使用强制类型转换,但是这种方法本质上是十分危险的。

强制类型转换有如下形式:

cast-name(expression);

cast-name 是static_cast,dynamic_cast,const_cast和reinterpret_cast中的 一种。

static_cast:任何具有明确定义的类型转换,只要不包含底层const,都可以用static_cast。

const_cast:只能改变运算对象的底层const,即去const性质。常用于有函数重载的上下文中。

reinterpret_cast:通常为运算对象的位模式提供较低层次上的重新解释。

WARNING 与命名的强制类型转换相比,旧式的强制类型转换从表现形式上来说不那么清晰明了,容易被看漏,所以一旦转换过程出现问题,追踪起来也更加困难。

CPP Primer: Chapter 1 Getting Started

Posted on 2019-08-26

Cpp Primer Notes

This a implementation of some of the exercises from the book: C++ Primer. And also some notes from the book. The github source could be found here.

Prerequisites

Ubuntu 18.04 LTS
gcc/g++: 5.5
vscode: 1.35.0, x64

Chapter 1 Getting Started

1.4 Flow of control

1.4.1 The While statement

1
2
int b = ++a;  // a + 1 before assigned to b, cal after a + 1;
int c = a++; // a + 1 after assigned to c, cal before a + 1;

1.4.3 Reading an Unknown Number of INputs

  • The end of input is CTRL + d;
  • It is a good practice to recompile the code after each fix – or after making at most a small number of obvious fixes. This is called edit-compile-debug.
  • exercise at input_if.cc

1.5 Introducing Classes

  1. Memeber function: a function that is defined as part of a class, sometimes referred to as methods;
  2. (): the call operator;
  • Uninitialized variables are a rich source of bugs;

CPP Primer: Chapter 3 String, Vectors, and Arrays

Posted on 2019-08-26 Edited on 2019-08-27

Strings, Vectors, And Arrays

The standard library defines a number of additiaonal types of a higher-level nature that computer hardware usually does not implement directly.

3.1 Namespace using Declarations

Headers Should Not Include using Declarations: The contents of a header are copied into the including program’s text. If a header has a using declaration, then it will pollute all scope with such using statement.

3.2 Library string

3.2.1 Defining and Initializeing strings

1
string s4(10, 'c'); //  s4 is cccccccccc

Direct and Copy Forms of Initialization:

1
2
3
string s5 = "hiya"; //  copy initialization
string s6("hiya"); // direct initialization
string s7(10, 'c'); // direct initialization

3.2.2 Operations on strings

Table: String Operations: On page. 86.

1
2
3
4
5
6
7
8
getline(is, s); //  Reads a line of input from is into s, returns is.
s.empty(); //
s.size(); // Returns the number of characters in s
s[n]; // returns a reference to the char at position n in s.
s1 + s2; //
s1 = s2; //
s1 == s2; //
<, <=, >, >=; // Comparisions are case-sensitive and use dictionary ordering

Using getline to Read an Entire Line: if you want to take the input and keep the whitespace in input:

1
2
3
4
5
6
7
int main()
{
string line;
while (getline(cin, line))
cout << line << endl; // we need endl to end the current line and flush the buffer
return 0;
}

The string::size_type Type: string::size_type returns a size_type value. If n ins an int that holds a negative value, then s.size() < n will almost surely evaluate as true. It yields true because the negative value in n will convert to a large unsigned value.

Adding Literals and strings: When we mix strings and string or character literals, at least one operand to each + operator must be of string type.

1
2
string s5 = "hello" + ", ";   //  error: no string operand
string s7 = "hello" + ", " + s2; // error: can't add string literals

3.2.3 Dealing with the Characters in a string

Advice: Use the C++ version of C Library Headers. Headers in C have names of the form name.h. The C++ version of these headers are named cname–they remove the .h suffix and precede the name with the letterc. The c indicates that the header is part of the C library.
cctype has the same contents as ctype.h, but in a form that is approprate for C++ programs. In particular, the names defined in the cname headers are defined inside the std namespace, whereas those defined in the .h versions are not.

Note:

  • string.h contains old functions like strcpy, strlen for C style null-terminated strings, use cstring instead.
  • string primarily contains the std::string, std::wstring and other classes.

Use Range-Based for to process every Character: Introduced in C++ 11, while the expression should represent a sequence.

1
2
3
4
5
6
7
8
for (declaration : expression)
statement

string str("some string");

// fprint the characters in str one character to a line
for (auto c : str)
cout << c << endl;

Example code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
string s("Hello world!!!");
// punct_cunt has the same type that s.size returns;
decltype(s.size()) punct_cnt = 0;

// count the number of punctutation characters in s
for (auto c : s)
if (ispunct(c))
++punct_cnt;

cout << punct_cnt
<< " punctiuation characters in " << s << endl;

for (auto &c :s)
if (isalpha(c))
c = toupper(c);

cout << s << endl;
/* cctype Functions
* isalnum(c): true if c is a letter or a digit
* isalpha(c): true if c is a letter
* iscntrl(c): true if c is a control character
* isdigit(c): true if c is a digit
* isgraph(c): true if c is not a space but is printable
* islower(c): true if c is a lowercase letter
* isprint(c): true if c is a printable character (i.e., a space or a character that has a visible representation)
* ispunct(c): true if c is a punctutation charcter
* isspace(c): true if c is a space
* isupper(c): true if c is an uppercase letter
* isxdigit(c): true if c is a hexadecimal digit
* tolower(c): convert c to its lowercase equivalent if c is a letter
* toupper(c): convert c to its uppercase equivalent if c is a letter
*/

Process Only Some Characters: We can use a subscript or an iterator. Note that the subscript will be converted to unsigned int.

Using a Subscript for Random Access:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const string hexdigits = "0123456789ABCDEF";

cout << "Enter a series of numbers between 0 and 15"
<< " separated by spaces. Hit ENDTER when finished: "
<< endl;

string result;

string::size_type n;
while (cin >> n)
if (n < hexdigits.size())
result += hexdigits[n];

cout << "Your hex number is: " << result << endl;

3.3 Library vector Type

A vector is a class template. C++ has both class and function templates.The process that the compiler uses to create classees or functions from templates is called Instantiation.
Note: vector is a template, not a type. Types generated from vector must include the element type, for example, vector<int>. Some compilers may require the old-style declarations for a vector of vectors, for example, vector<vector<int> >.

3.3.1 Deifining and Initializing vectors

1
2
3
4
5
6
7
vector<T> v1;                   //  vector that holds objects of type T. Default initialization; v1 is empty
vector<T> v2(v1); // v2 has a copy of each element in v1.
vector<T> v2 = v1; // Equivalent to v2(v1)
vector<T> v3(n, val); // v3 has n elements with value val.
vector<T> v4(n); // v4 has n copipes of a value-initialized object
vector<T> v5{a, b, c, ...}; // v5 has as manay elements as there are initializers; elements are initialized by corresponding initializers
vector<T> v5 = {a, b, c, ...}; // Equivalent to v5{a, b, c, ...}

Key Concept: vectors grow efficiently: If differing element values are needed, it is usually more efficient to define an empty vector and add elements as the values we need become known at run time. Moreover, vector offers capabilityes to allow us to further enhance run-time performance when we add elements. Deifne a vector of a specific size could result in poorer performance.

Warning: The body of a range for must not change the size of the sequence over which it is iterating.

3.3.2 Other vector Operations

Warning: buffer overflow errors are the result of subscripting elements that don`t exst. Such bugs are the most common cause of security problems in PC and other applications.

3.4 Introducing Iterators

All of the library containers have iterators, but only a few of them support the subscript operator. string is not a container, but it supports most of the container operations, string also supports iterators.

3.4.1 Using Iterators

The iterator returned by end is often referred to as the off-the-end iterator or abbreviated as “the end iterator”.

Iterator Operations: Iterator only supports a few operations.

1
2
3
4
5
*iter;          // Returns a reference to the element denoted by the iterator iter.
iter->mem; // Dereferences iter and fetches the member named mem from the underlying element, Equivalent to (*iter).mem
++iter; // Increments iter to refer to the next element in the container
--iter; // Decrements iter to refer to the previous element in the container
iter1 == iter2; //Compares two iterators for equaility. Two iterators are equal if they denote the same element or if they are the off-the-end iterator for the same container

MMoving Iterators from One Element to Another: Because the iterator returned from end does not denote an element, it may not be incremented or dereferenced.

Key Concept: Generic Programming: Only a few containers have the subscript operator. Most of thoes iterators do not have the < operator. So C++ programmers use != operator in the loop instead of < operator.

The begin and end Operations: The default behavior is often not we want, return iterator for vector and const_iterator for const vector, while we usually want const type to read but not to write. It is suggested to use following new functions in new standard.

1
2
3
vector<int> v;

auto it3 = v.cbegin();

3.4.2 Iterator Arithmetic

1
2
3
4
5
6
7
iter + n;         //
iter - n;
iter1 += n;
iter1 -= n;
iter1 - iter2; // Substracting 2 iterators yiels the number that when added to the right-hande iterator yelds the left-head iterator.

>, >=, <, <=; // One iterator is less than another if it refers to an element that appears in the container before the one referred to by the other iterator.

Arithmetic Operations on Iterators: Compute an iterator te the element nearest the middle of a vector:

1
auto mid = vi.begin() + vi.size() / 2;

Using Iterator Arithmetic: A classic algorithm that uses iterator arithmetic is binary search.

3.5 Arrays

An array is a data structure that is similar to the library vector type but offers a different trade-off between performance and flexibility.

An array is a compound type, which is defined based on other type, like reference and pointer.

1
2
3
4
5
6
7
unsigned cnt = 42;
constexpr unsigned sz = 42;

int arr[10];
int *parr[sz];
string bad[cnt];
string strs[get_size()];

By default, the elements in an array are default initialized. Arrays hold objects, thus , there are no arrays of references.

Explicitly Initializing Array Elements:

1
2
3
4
5
6
const unsigned sz = 3;
int ia1[sz] = {0, 1, 2};
int a2[] = {0, 1, 2};
int a3[5] = {0, 1, 2}; // equivalent ot a3[] = {0, 1, 2, 0, 0}
string a4[3] = {"hi", "bye"};
int a5[2] = {0, 1, 2}; // error: too many arguments

Character Arrays Are Special: String literals end with a null character. That null character is copied into the array along with the characters in the literal.

1
2
3
4
char a1[] = {'C', '+', '+'};
char a2[] = {'C', '+', '+', '\0'}; // list initialization, explicit null
char a3[] = "C++"; // null termintator added automatically
char a4[6] = "Daniel"; // error: nospace for the null

No Copi or Assignment: We cannot initialize an array as a copi of another array, nor is it legal to assign one array to another.

Complicated Array Declarations: Read the declaration from inside out and then right to left.

1
2
3
4
5
int *ptrs[10];                    // ptrs is an array of ten pointers to int
int &refs[10] = /* ? */; // error: no arrays of references
int (*Parray) [10] = &arr; // Parray points to an array of ten ints
int (&arrRef)[10] = arr; // arrRef refers to an array of ten ints
int *(&arry)[10] = ptrs; // arry arry is a reference to an array of ten pointers

3.5.2 Accessing the Elements of an Array

Nothing much, you should be able to accesss elements in arrays like in vectors.

3.5.3 Pointers and Arrays

In C++, when we use an array, the compiler ordinarily converts the array to a pointer. The arrays have a special property–in most places when we use an array, the compiler automatically substitutes a poiter to the first elemet:

1
2
3
4
5
string nums[] = {"one", "two", "three"};

string *p = &nums[0]; // p points the first element in nums

string *p2 = nums; // equivalent to p2 = &nums[0]

Pointers Are Iterators: In particular, pointers to array elements support the same operations as iterators on vector or string.

The Libarary begin and end Functions: begin returns a pointer to the first, and end returns a pointer one past the last element in the given array. These funcgtions are defined in the iterator header.

1
2
3
int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *beg = begin(ia);
int *last = end(ia);

Pointer Arithmetic: Pointers acn use all the iterator operations.

1
2
3
4
5
6
7
8
constexpr size_t sz = 5;
int arr[sz] = {1, 2, 3, 4, 5};
int *ip = arr; // equivalent to int *ip = &arr[0]
int *ip2 = ip + 4; // ip2 points to arr[4], the last element in arr

// ok: arr is converted to a pointer to its first element; p points one past the end of arr
int *p = arr + sz; // do not dereference
int *p2 = arr + 10; // error: arr has only 5 elements; p2 has undefined value

The result of substracting two pointers is a library type named ptrdiff_t. Like size_t, the ptrdiff_t type is a machine-specific type and is defined in the cstddef header.

Interaction between Dereference and Pointer Arithmetic:

1
2
int ia[] = {0, 2, 4, 6, 8};
int last = *(ia + 4);

Subscripts and Pointers: In most places when we use the name of an array, we are really using a pointer to the first element in that array. We can use the subscript operator on any pointer, as long as that pointer points to an element (or one past the last element) in an array.

1
2
3
int *p = &ia[2];      // p points to the element indexed by 2
int j = p[1]; // p[1] is the same element as ia[3]
int k = p[-2]; // p[-2] is the same element as ia[0]

Warning: Unlike subscripts for vector and string, the index of the built-in subscript operator is not an unsigned type.

3.5.4 C-Style Character Strings

C Library String Functions: Included in cstring header file.

1
2
3
4
strlen(p);        // Renturns the length of p, not cunting the null
strcmp(p1, p2); // Compares p1 and p2 for equality. Returns 0 if p1 == p2, a positive value if p1 > p2, a negative value if p1 < p2
strcat(p1, p2); // Appends p2 to p1. Returns p1.
strcpy(p1, p2); // Copies p2 into p1. Returns p1.

Notes: Remember that when we are comparing arraies, we are really using a pointer to the first element in the array.

Caller Is Responsible for Size of a Destination String: For most applications, in addition to being safer, it is also more efficient to use library strings rather than C-style strings.

3.5.5 INterfacing to Older Code

The string member funciton named c_str that we can often use to accomplish what we want.

Using an Array to Initialize a vector: We cannot initialize a built-in array from another array. But we can, and we only can initialize a vector using an array.

1
2
3
4
5
int int_arr[] = {0, 1, 2, 3, 4, 5};
vector<int> ivec(begin(int_arr), end(int_arr));

/* create a sub vector */
vector<int> sub_vec(int_arr + 1, int_arr + 4);

3.6 Multidimensional Arrays

Using a Ranf for with Multidimensional Arrays: The range for loop could be done under the new standard.

1
2
3
4
5
6
size_t cnt = 0;
for (auto &row : ia)
for (auto &col : row){
col = cnt;
++cnt;
}

Notes: To use a multidimensional array in a range for, the loop control variable for all but the innermost array must be references.

1
2
3
4
5
6
7
8
9
10
11
int *ip[4];     // array of pointers to int
int (*ip)[4]; // pointer to an array of four ints

// print the value of each element in ia, with each inner array on its own line
// p points to an array of four ints
for (auto p = ia; p != ia + 3; ++p) {
// q points to the first element of an array of four ints; that is , q points to an int
for (auto q = *p; q != *p + 4; ++q)
cout << *q << '\t';
cout << endl;
}

Defined Terms

  • begin/end: included in <iterator>, return the pointers to an array`s begin and end;
  • buffer overflow: When we use an index that is out-of-range for a container;
  • difference_type: A signed integral type defined by vector and string that can hold the distance between any two iterators;
  • ptrdiff_t/size_t: Machine-dependent signed integral type defined in the cstddef header that is large enough to held the difference between 2 pointers into the largest possible array;

Opencv Matrix Operations

Posted on 2019-08-26 Edited on 2019-08-27

OpenCV Matrix Operations

Since OpenCV is the most widely used image processing library, the matrix operation and basic linear algebra is inevidable.

1. Initialization

Remeber that you have to initialize a Mat from a array.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <opencv2/core/core.hpp>
using namespace std;
using namespace cv;

void addMatrixExample() {
float a[2][2] = {{1, 2},
{3, 4}};
float b[2][2] = {{5, 6},
{7, 8}};

Mat A = Mat(2, 2, CV_32FC1, a);
Mat B = Mat(2, 2, CV_32FC1, b);
Mat C;
C = A + B;
cout << "C =" << endl << " " << C << endl << endl;
}

int main() {
addMatrixExample();
}

2. Point

1
2
3
4
5
6
7
typedef Point_<int> Point2i;
typedef Point2i Point;
typedef Point_<float> Point2f;
typedef Point_<double> Point2d;
typedef Point3_<int> Point3i;
typedef Point3_<float> Point3f;
typedef Point3_<double> Point3d;

From the above we can see that Point, Point2i, Point_<int> are exactly the same.

12

Zepyhrus

12 posts
© 2020 Zepyhrus
Powered by Hexo v3.9.0
|
Theme – NexT.Mist v7.3.0