博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
YOLOv2源码分析(一)
阅读量:1886 次
发布时间:2019-04-26

本文共 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_argfind_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插入到sectionlist中。

总览整个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这个函数后面的部分,敬请关注。

转载地址:http://yhkdf.baihongyu.com/

你可能感兴趣的文章
spark的shell脚本分析
查看>>
推荐算法:基于物品的协同过滤算法
查看>>
ubuntu 16:使用系统自带的中文输入法
查看>>
docker系列6 :k8s集群[ 解压安装 ]
查看>>
maven- idea: 打包可执行jar
查看>>
windows server 2012设置远程连接断开后自动注销
查看>>
python基础:list,map,open()文件读写
查看>>
Go面向对象-接口
查看>>
Go-多路选择和超时控制
查看>>
centos7 基于rsync+inotify 实现定时备份
查看>>
指定IP进行 文件的分发
查看>>
基于http搭建本地yum仓库
查看>>
常规邮件基础
查看>>
邮件基础之 -- SMTP协议
查看>>
Java设计模式--责任链模式
查看>>
OpenCV的cvLoadImage函数
查看>>
OpenCV中与matlab中相对应的函数
查看>>
C/C++中二维数组作函数形参时,调用函数时,可传递的实参类型的小结
查看>>
cvGetSubRect与cvMul用法
查看>>
opencv图像处理梯度边缘和角点
查看>>