上一节讲到,在使用load_param接口载入参数时,需要用参数字典ParamDict来解析.param文件中的特定参数,如:
0=64 1=3 11=3 5=1 6=1728
那么参数字典具体如何进行解析的?我们首先看一下paramdict.h文件中定义的数据成员变量:
// parameters
struct
{
// 是否已经被载入:1表示已载入
int loaded;
// 单个值可能为整形也有可能为浮点型
union { int i; float f; };
// 还有可能是数组
Mat v;
} params[NCNN_MAX_PARAM_COUNT];
这里,NCNN_MAX_PARAM_COUNT大小为32,params是一个大小为32的结构体数组,即一行中特定参数数量不能超过32,当然,一般情况下也不会超过32。
然后,我们看一下paramdict.cpp源码,可以看到,这里会解析出当前行的index(id),也即是等号左边部分:
while (fscanf(fp, "%d=", &id) == 1)
{
...
}
再结合ncnn关于param和bin文件的wiki,链接为参考资料[1],有:
index:0~19 对应整形或浮点型数据
index:-23000 减去0~19 对应整形或浮点型数组
具体代码如下:
(1)根据读取的index判断,如果小于-23300,表示为数组,那么等号右边第一个参数就是数组长度,后面顺序就是数组内容,[array size],int,int,...,int或[array size],float,float,...,float,例如:
0=1 1=2.5 -23303=2,2.0,3.0
index为-23303,表明当前参数为数组,等号右边第一个参数为2,表明数组长度为2,后面2.0,3.0就是数组的内容:
// index <= -23300:数组
bool is_array = id <= -23300;
// 如果是数组
if (is_array)
{
// 计算id
id = -id - 23300;
}
// 如果当前参数是数组类型
if (is_array)
{
int len = 0;
// 数组长度
int nscan = fscanf(fp, "%d", &len);
// 等于1才表示读取成功
if (nscan != 1)
{
fprintf(stderr, "ParamDict read array length failed\n");
return -1;
}
// 创建数组:就是一个Mat
params[id].v.create(len);
for (int j = 0; j < len; j++)
{
char vstr[16];
// 从二值文件中读取string
nscan = fscanf(fp, ",%15[^,\n ]", vstr);
// 如果读取失败
if (nscan != 1)
{
fprintf(stderr, "ParamDict read array element failed\n");
return -1;
}
// 是否为浮点型:看解析的字符串中是否存在'.'或'e'
// 小数点计数法和科学计数法
bool is_float = vstr_is_float(vstr);
// 如果是浮点数
if (is_float)
{
// vstr赋值给params[id].v[j]
float* ptr = params[id].v;
nscan = sscanf(vstr, "%f", &ptr[j]);
}
else
{
// vstr赋值给params[id].v[j]
int* ptr = params[id].v;
nscan = sscanf(vstr, "%d", &ptr[j]);
}
// 赋值失败
if (nscan != 1)
{
fprintf(stderr, "ParamDict parse array element failed\n");
return -1;
}
}
}
这里有个vstr_is_float函数,原理很简单,就是判断数字对应字符串中是否存在小数点'.'或字母'e',对应小数的两种写法,一种正常的小数点表示法,一种是科学计数法。
如果不是数组,直接读取即可:
else
{
// 不是数组
char vstr[16];
// 直接将字符串赋值给vstr
int nscan = fscanf(fp, "%15s", vstr);
// 赋值失败
if (nscan != 1)
{
fprintf(stderr, "ParamDict read value failed\n");
return -1;
}
// 判断是否为浮点数
bool is_float = vstr_is_float(vstr);
// 将字符串中的值赋给参数字典
if (is_float)
nscan = sscanf(vstr, "%f", ¶ms[id].f);
else
nscan = sscanf(vstr, "%d", ¶ms[id].i);
// 赋值失败
if (nscan != 1)
{
fprintf(stderr, "ParamDict parse value failed\n");
return -1;
}
}
// 载入成功
params[id].loaded = 1;
这里参数字典解析特定参数的流程就走完了。
参考资料:
[1] https://github.com/Tencent/ncnn/wiki/param-and-model-file-structure