上文说到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将从该文件中读取标签值。
后续内容将在后续博文中继续解读