本文共 9173 字,大约阅读时间需要 30 分钟。
文章全部
0x00 写在开头
写这一系列文章主要是想解析yolov2的具体实现,因为在作者的论文中有很多地方没有进行详细表述,所以不看源代码的话很难知道幕后具体做了什么。另一点是学习一下别人写一个网络的思路,因为你要知道作者的代码相当于自己写了一个小型框架(函数的接口设计的可能不是非常好)。
0x01 从main函数开始
int main(int argc, char **argv){ //test_resize("data/bad.jpg"); //test_box(); //test_convolutional_layer(); if(argc < 2){ fprintf(stderr, "usage: %s\n", argv[0]);//如果参数小于2就打印出错信息 return 0;//出错后返回 } gpu_index = find_int_arg(argc, argv, "-i", 0);
接着看到find_int_arg
函数
int find_int_arg(int argc, char **argv, char *arg, int def){ int i; for(i = 0; i < argc-1; ++i){ if(!argv[i]) continue; if(0==strcmp(argv[i], arg)){ def = atoi(argv[i+1]); del_arg(argc, argv, i); del_arg(argc, argv, i); break; } } return def;}
find_int_arg
这个函数本身的目的是要找出参数中的int
值。在这里主要任务就是判断输入参数是不是有-i
,将-i
后一位的数值转化为int
,然后返回这个值。其中又出现了两次del_arg
函数
void del_arg(int argc, char **argv, int index){ int i; for(i = index; i < argc-1; ++i) argv[i] = argv[i+1]; argv[i] = 0;}
这个函数作用是删除index
位置的参数。此处调用两次的作用是将-i
和其后的数值去除,类似于一个列表前移操作,后面的项补0。
接着看main函数后面的
if(find_arg(argc, argv, "-nogpu")) { gpu_index = -1; }
这里调用了一个find_arg
函数
int find_arg(int argc, char* argv[], char *arg){ int i; for(i = 0; i < argc; ++i) { if(!argv[i]) continue; if(0==strcmp(argv[i], arg)) { del_arg(argc, argv, i); return 1; } } return 0;}
这个函数的作用就是查看参数中是否有arg
指向的字符串。在这里如果参数中出现了-nogpu
则我们gpu_index
设置为-1
,也就是不使用gpu
接着往后
#ifndef GPU gpu_index = -1;#else if(gpu_index >= 0){ cuda_set_device(gpu_index); }#endif
如果没有定义GPU这个宏,那么将 gpu_index
设置为 -1
。如果设置了,并且我们前面也没有关闭gpu
选项的话,那么调用cuda_set_device
这个函数
void cuda_set_device(int n){ gpu_index = n; cudaError_t status = cudaSetDevice(n);//这是cuda编程里面的,不详细说。设置显卡编号 check_error(status);//判断返回信息,设置显卡成功了,还是失败了}
接着往后
else if (0 == strcmp(argv[1], "yolo")){ run_yolo(argc, argv); }
这里有很多选项,我先看我最感兴趣的yolo
选项
到这里main函数中的所有问题就理清楚了,接着就是run_yolo
函数中问题了
0x02 run_yolo
void run_yolo(int argc, char **argv){ char *prefix = find_char_arg(argc, argv, "-prefix", 0); float thresh = find_float_arg(argc, argv, "-thresh", .2); int cam_index = find_int_arg(argc, argv, "-c", 0); int frame_skip = find_int_arg(argc, argv, "-s", 0); if(argc < 4){ //如果参数小于4,打印出错信息 fprintf(stderr, "usage: %s %s [train/test/valid] [cfg] [weights (optional)]\n", argv[0], argv[1]); return; } int avg = find_int_arg(argc, argv, "-avg", 1); char *cfg = argv[3]; char *weights = (argc > 4) ? argv[4] : 0; char *filename = (argc > 5) ? argv[5]: 0; //根据第三个参数选择调用的函数 if(0==strcmp(argv[2], "test")) test_yolo(cfg, weights, filename, thresh); else if(0==strcmp(argv[2], "train")) train_yolo(cfg, weights); else if(0==strcmp(argv[2], "valid")) validate_yolo(cfg, weights); else if(0==strcmp(argv[2], "recall")) validate_yolo_recall(cfg, weights); else if(0==strcmp(argv[2], "demo")) demo(cfg, weights, thresh, cam_index, filename, voc_names, 20, frame_skip, prefix, avg, .5, 0,0,0,0);}
这里有find_char_arg
,find_float_arg
函数,这里就不再赘述了,按照前面解析find_int_arg
的思路去做。
首先cfg
这个指针指向cfg
文件名字符串,weight
指向了权重文件名字符串。别的变量暂时不管,因为我们先关注train_yolo
这个函数。
void train_yolo(char *cfgfile, char *weightfile){ char *train_images = "/data/voc/train.txt";//train_images指向train.txt路径字符串 char *backup_directory = "/home/pjreddie/backup/";//backup_directory指向保存权重文件的路径 srand(time(0));//设置随机数种子 char *base = basecfg(cfgfile);//cfgfile就是上面说的cfg指向的字符串 printf("%s\n", base);
好的,这里出现了一个basecfg
函数
char *basecfg(char *cfgfile){ char *c = cfgfile; char *next; while((next = strchr(c, '/'))) { c = next+1; } c = copy_string(c); next = strchr(c, '.'); if (next) *next = 0; return c;}char *copy_string(char *s){ char *copy = malloc(strlen(s)+1); strncpy(copy, s, strlen(s)+1); return copy;}
先看传入的参数cfgfile
,是一个cfg
文件的路径字符串。接着strchr
,这个函数的作用是去第一个参数中,第二个参数以后的字符包括第二个参数(abc/ab.cfg—>/ab.cfg),接着c=next+1
,也就是c
指向了这个cfg
文件名字符串。
copy_string
函数的作用,就是重新分配一块内存,并且内容保留。那这里next
后的操作就很清楚了,就是把.cfg
后缀去掉。
这个函数是有缺陷的,因为这里没有考虑到window用户的需求,应该增加\\
的处理。
接着回到train_yolo
函数
//train_yolo float avg_loss = -1; network net = parse_network_cfg(cfgfile);
这里出现了parse_network_cfg
函数
0x03 parse_network_cfg
network *parse_network_cfg(char *filename){ list *sections = read_cfg(filename);
出现了一个read_cfg
函数
0x0301 read_cfg
list *read_cfg(char *filename){ FILE *file = fopen(filename, "r"); if(file == 0) file_error(filename);
void file_error(char *s){ fprintf(stderr, "Couldn't open file: %s\n", s); exit(0);}
file_error
判断cfg
文件有没有打开失败。接着往后
//read_cfg char *line; int nu = 0; list *options = make_list();//创建一个链表 section *current = 0; while((line=fgetl(file)) != 0){
这里出现了一个fgetl
函数
char *fgetl(FILE *fp)//fp指向打开后的cfg文件{ if(feof(fp)) return 0;//如果文件结尾,退出 size_t size = 512; char *line = malloc(size*sizeof(char));//分配512字节内存 //从fp中读取一行数据到line中,数据最大为size。 //注意,如果碰到换行或文件eof会停止读入。读取失败返回NULL if(!fgets(line, size, fp)){ free(line);//失败就释放内存 return 0; } size_t curr = strlen(line);//返回line的长度,也就是读入的字符个数 //这里的代码是为了处理size不够的情况 while((line[curr-1] != '\n') && !feof(fp)){ if(curr == size-1){ //size不够我们就变大两倍 size *= 2; line = realloc(line, size*sizeof(char)); if(!line) { printf("%ld\n", size); malloc_error(); } } //line不够,也就是一行没有读全,那么不会再从开始,而是接着上一次没有读完的信息 size_t readsize = size-curr; if(readsize > INT_MAX) readsize = INT_MAX-1; fgets(&line[curr], readsize, fp); curr = strlen(line); } if(line[curr-1] == '\n') line[curr-1] = '\0'; return line;}
这个函数的作用,简单理解就是读取文件的一行。其实用c++中的getline
函数就可以解决了。同样的python中的readline
也可以做到。
接着回到read_cfg
函数
//read_cfg ++ nu; strip(line);
出现一个strip
函数
void strip(char *s)//传入我们前面读入的行{ size_t i; size_t len = strlen(s); size_t offset = 0; //这里的做法和list前移一样,出现空格符,则其后的所有项前移 for(i = 0; i < len; ++i){ char c = s[i]; if(c==' '||c=='\t'||c=='\n') ++offset; else s[i-offset] = c; } s[len-offset] = '\0';}
这个函数的作用就是删除字符串中的空格符(’\n’,’\t’,’ ‘)
回到read_cfg
函数
switch(line[0]){ case '['://这里就是看读入的行的第一个字符是'['也就是对于cfg文件中[net],[maxpool]这种东西 current = malloc(sizeof(section));//创建一个current list_insert(options, current);//将current插入之前建立的options链表 current->options = make_list();//给current创建链表 current->type = line;//将读入的[net],[maxpool]读入type break; case '\0': case '#': case ';': free(line); break; default: if(!read_option(line, current->options)){ fprintf(stderr, "Config file error line %d, could parse: %s\n", nu, line); free(line); } break; } } fclose(file); return options;}
先看一下section
这个结构体的定义
typedef struct{ char *type; list *options;}section;
它的内部包含一个链表。这里作者的list_insert(options, current);
中options和后面的current->options = make_list();
中的options存在歧义。其实两者一毛钱关系都没有。
分析一下这个read_option
函数
int read_option(char *s, list *options)//s指向读取的行,list就是一个section中的list{ size_t i; size_t len = strlen(s); char *val = 0; for(i = 0; i < len; ++i){ if(s[i] == '='){ s[i] = '\0'; val = s+i+1;//val指向=后面的字符串 break; } } if(i == len-1) return 0; char *key = s;//这个时候key指向的是=前面的字符串 option_insert(options, key, val); return 1;}typedef struct{ char *key; char *val; int used;} kvp;void option_insert(list *l, char *key, char *val){ kvp *p = malloc(sizeof(kvp)); p->key = key; p->val = val; p->used = 0; list_insert(l, p);//将一个kvp结构插入section中的list}
回头再看这个switch
,他在这里的作用就是将cfg
文件中的不同内容(’[net]’,’[maxpool]’)区分开,然后存到一个列表中。
举个例子
[convolutional]batch_normalize=1filters=32size=3stride=1pad=1activation=leaky
这是yolo9000.cfg
中的一个片段,我们先看第一行,他是一个’[]’,所以进入第一个判断,我们首先将[convolutional]
字符串,存入一个section
对象的type
中,并且将这个section
对象插入到一个列表中。接着读取第二行batch_normalize=1
,将=
前后内容拆开存储到kvp
结构中,再将这个kvp
插入到section
的list
中。
总览整个read_cfg
函数
list *read_cfg(char *filename){ FILE *file = fopen(filename, "r"); if(file == 0) file_error(filename); char *line; int nu = 0; list *options = make_list(); section *current = 0; while((line=fgetl(file)) != 0){ ++ nu; strip(line); switch(line[0]){ case '[': current = malloc(sizeof(section)); list_insert(options, current); current->options = make_list(); current->type = line; break; case '\0': case '#': case ';': free(line); break; default: if(!read_option(line, current->options)){ fprintf(stderr, "Config file error line %d, could parse: %s\n", nu, line); free(line); } break; } } fclose(file); return options;}
作者做了一种数据结构来存放cfg
的文件数据。
觉得不错,点个赞吧b( ̄▽ ̄)d
由于本人水平有限,文中有不对之处,希望大家指出,谢谢^_^!
下一篇继续分析parse_network_cfg
这个函数后面的部分,敬请关注。
转载地址:https://coordinate.blog.csdn.net/article/details/78831394 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!