yolov3源码解析——test_detector(01)

上文说到darknet.c中主函数detect条件分支,即调用test_detector函数

下面对test_detector函数进行逐句分析,该函数在include/darknet.h中声明,在examples/detector.c中实现,源码如下:

void test_detector(char *datacfg, char *cfgfile, char *weightfile, char *filename, float thresh, float hier_thresh, char *outfile, int fullscreen)
{
    list *options = read_data_cfg(datacfg);
    char *name_list = option_find_str(options, "names", "data/names.list");
    char **names = get_labels(name_list);

    image **alphabet = load_alphabet();
    network *net = load_network(cfgfile, weightfile, 0);
    set_batch_network(net, 1);
    srand(2222222);
    double time;
    char buff[256];
    char *input = buff;
    float nms=.45;
    while(1){
        if(filename){
            strncpy(input, filename, 256);
        } else {
            printf("Enter Image Path: ");
            fflush(stdout);
            input = fgets(input, 256, stdin);
            if(!input) return;
            strtok(input, "\n");
        }
        image im = load_image_color(input,0,0);
        image sized = letterbox_image(im, net->w, net->h);
        //image sized = resize_image(im, net->w, net->h);
        //image sized2 = resize_max(im, net->w);
        //image sized = crop_image(sized2, -((net->w - sized2.w)/2), -((net->h - sized2.h)/2), net->w, net->h);
        //resize_network(net, sized.w, sized.h);
        layer l = net->layers[net->n-1];


        float *X = sized.data;
        time=what_time_is_it_now();
        network_predict(net, X);
        printf("%s: Predicted in %f seconds.\n", input, what_time_is_it_now()-time);
        int nboxes = 0;
        detection *dets = get_network_boxes(net, im.w, im.h, thresh, hier_thresh, 0, 1, &nboxes);
        //printf("%d\n", nboxes);
        //if (nms) do_nms_obj(boxes, probs, l.w*l.h*l.n, l.classes, nms);
        if (nms) do_nms_sort(dets, nboxes, l.classes, nms);
        draw_detections(im, dets, nboxes, thresh, names, alphabet, l.classes);
        free_detections(dets, nboxes);
        if(outfile){
            save_image(im, outfile);
        }
        else{
            save_image(im, "predictions");
#ifdef OPENCV
            make_window("predictions", 512, 512, 0);
            show_image(im, "predictions", 0);
#endif
        }

        free_image(im);
        free_image(sized);
        if (filename) break;
    }
}

第一句:list *options = read_data_cfg(datacfg);

list是结构体,定义在darknet.h中,其中使用到另一个结构体Node,也定义在darknet.h中:

typedef struct node{
    void *val;
    struct node *next;
    struct node *prev;
} node;

typedef struct list{
    int size;
    node *front;
    node *back;
} list;

从其命名可以看出,node是节点,list是一个以node为元素的列表

node中的next代表当前节点的后一个节点,prev代表当前节点的前一节点,从而构建了以node为元素的双向链表

list中的front代表该链中的第一个节点,back代表最后一个节点,size代表链中的节点个数

test_detector第一句使用的函数read_data_cfg在darknet.h中声明,在src/option_list.c中实现,代码如下:

list *read_data_cfg(char *filename)
{
    FILE *file = fopen(filename, "r");
    if(file == 0) file_error(filename);
    char *line;
    int nu = 0;

    //创建空list
    list *options = make_list();
    //一个文件有一个list,每行数据是一个node,option_insert将node链接入list
    while((line=fgetl(file)) != 0){
        ++ nu;
        strip(line);
        switch(line[0]){
            case '\0':
            case '#':
            //;作为结束标志
            case ';':
                free(line);
                break;
            default:
                if(!read_option(line, options)){
                    fprintf(stderr, "Config file error line %d, could parse: %s\n", nu, line);
                    free(line);
                }
                break;
        }
    }

    fclose(file);
    return options;
}

 前几句分别打开文件,并定义了存储文件行数据的line,nu用于循环累加,用于输出错误信息行数

list *options = make_list();用于创建新list,在src/list.h中声明,在src/list.c中实现,代码如下:

list *make_list()
{
	list *l = malloc(sizeof(list));
	l->size = 0;
	l->front = 0;
	l->back = 0;
	return l;
}

仅仅用于创建一个新list,其中的参数全部设置为空

重点来了,下面的循环如下:

while((line=fgetl(file)) != 0){
        ++ nu;
        strip(line);
        switch(line[0]){
            case '\0':
            case '#':
            //;作为结束标志
            case ';':
                free(line);
                break;
            default:
                if(!read_option(line, options)){
                    fprintf(stderr, "Config file error line %d, could parse: %s\n", nu, line);
                    free(line);
                }
                break;
        }
    }

循环结束的条件是line不为空,line=fgetl(file),可以看出是文件的一行数据

fgetl函数在src/utils.h中声明,在src/utils.c中实现,代码如下:

//读取文件行,以换行符为界
char *fgetl(FILE *fp)
{
    if(feof(fp)) return 0;
    //line的大小为512个char
    size_t size = 512;
    char *line = malloc(size*sizeof(char));
    if(!fgets(line, size, fp)){
        free(line);
        return 0;
    }

    //获取字符串真实大小,不包含null
    size_t curr = strlen(line);

    while((line[curr-1] != '\n') && !feof(fp)){
        if(curr == size-1){
            size *= 2;
            line = realloc(line, size*sizeof(char));
            if(!line) {
                printf("%ld\n", size);
                malloc_error();
            }
        }
        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;
}

该算法的实现过程在其他博文中解读,该函数读取文件行,返回一行数据

回到循环中,strip(line)去处line中首尾的特定字符(空格等),该函数在src/utils.h中声明,在src/utils.c中实现,源码如下:

//将非法字符去除,重新链接字符串,并以'\0'作为结尾
void strip(char *s)
{
    size_t i;
    size_t len = strlen(s);
    size_t offset = 0;
    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';
}

实现原理将在其他博文中讲解,该算法类似python中的strip,实现对字符串首尾特定字符的删除

再回到循环中,使用switch语句排除特定非法情况,重点在default,read_option(line, options)

read_option函数在include/darknet.h中声明,在src/option_list.c中实现,代码如下:

int read_option(char *s, list *options)
{
    size_t i;
    size_t len = strlen(s);
    char *val = 0;
    //找到‘=’后的内容,即相关参数,找到后就break
    for(i = 0; i < len; ++i){
        if(s[i] == '='){
            s[i] = '\0';
            //指向‘=’后内容的开头
            val = s+i+1;
            break;
        }
    }
    //判断是否找到了‘=‘后的内容,若本行没有相关内容,结束返回0
    if(i == len-1) return 0;
    //使用指针调用字符串s
    char *key = s;
    option_insert(options, key, val);
    return 1;
}

这里需要看看,读取的文件是什么样的,回到test_detector第一个参数使用默认值为cfg/coco.data,该文件内容如下:

classes= 80
train  = /home/pjreddie/data/coco/trainvalno5k.txt
valid  = coco_testdev
#valid = data/coco_val_5k.list
names = data/coco.names
backup = /home/pjreddie/backup/
eval=coco

现在可以看到read_option中,采用判断“=”的方式找到“=”后的内容,指针al指向该内容的首位置,指针key依然是该行数据的首位置,option_insert函数在include/darknet.h中声明,在src/option_list.c中实现,代码如下:

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);
}

 其中list_insert函数在src/list.h中声明,在src/list.c中实现,代码如下:

void list_insert(list *l, void *val)
{
	node *new = malloc(sizeof(node));
	new->val = val;
	new->next = 0;

	if(!l->back){
		l->front = new;
		new->prev = 0;
	}else{
		l->back->next = new;
		new->prev = l->back;
	}
	l->back = new;
	++l->size;
}

可以看出read_data_cfg函数返回的list(option),存储了文件的内容,每个节点的值是指针,该指针指向文件行数据的起始位置,该结点的前一节点是上一行数据对应的节点,后一节点对应后一行数据的节点,list(option)的size实际指文件的行数。

所以read_data_cfg返回的list(option),是读取文件的索引,其中节点以指针形式直接指向数据行的起始位置,可以随意调用

 

让我们回到test_detector函数,read_data_cfg后面调用option_find_str,该函数在include/darknet.h中声明,在src/option_list.c中实现,代码如下:

char *option_find_str(list *l, char *key, char *def)
{
    char *v = option_find(l, key);
    if(v) return v;
    if(def) fprintf(stderr, "%s: Using default '%s'\n", key, def);
    return def;
}

该函数和read_data_cfg函数一起实现了对数据文件指定内容的读取,option_find_str函数参数l是read_data_cfg产生的文件索引链表,参数key指定想要读取的数据,key的值就是文件中“=”前的字符串,而option_find_str返回的值即文件中“=”后面的字符串

classes= 80
train  = /home/pjreddie/data/coco/trainvalno5k.txt
valid  = coco_testdev
#valid = data/coco_val_5k.list
names = data/coco.names
backup = /home/pjreddie/backup/
eval=coco

从调用语句可以看出要获取的是第5行names = data/coco.names,data/coco.names是一个文件,下面get_labels将从该文件中读取标签值。

后续内容将在后续博文中继续解读

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫猫虫(——)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值