编程艺术剖析 darknet read_data_cfg 接口

Posted 极智视界

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编程艺术剖析 darknet read_data_cfg 接口相关的知识,希望对你有一定的参考价值。

欢迎关注我的公众号 [极智视界],获取我的更多笔记分享

O_o>_<o_OO_o~_~o_O

  本文介绍一下 darknet 中 read_data_cfg 接口实现。

  这个接口在 darknet 中的作用主要是加载 .data 中的数据,功能比较简单。

1、darknet 数据加载例程

  要说这个接口首先需要介绍一下 darknet 目标检测的数据加载流程:

  以 voc 格式数据来说,对于模型推理一般首先需要加载 voc.data 中的 classes 和 names,而其中的 train 和 valid 主要是训练的时候需要的数据集,来看一下 vod.data 的样子:

  通过 voc.data 中的 names 来调 .names,.names 中即为目标检测的类别名称,上面的 classes 为类别数,来看一下 voc.names:

  接着就是 cfg 和 weights,这个没什么好说的,就是模型结构文件和权重文件。


2、read_data_cfg 接口

  来看一下 read_data_cfg 的调用:

list *options = read_data_cfg(datacfg);

  可以看到 read_data_cfg 的返回是指向 list 的指针,而 list 就是之前写的这篇文章《【编程艺术】剖析 darknet C 链表实现》中的链表了。

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

  来看一下 read_data_cfg 的实现:

/// 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 *options = make_list();               // 创建链表
    while((line=fgetl(file)) != 0){            // fgetl(file)逐行读入
        ++nu;
        strip(line);                           // 去除line中的空格、制表和换行符,并在最后加'\\0'结束
        switch(line[0]){                       // 判断line的首字符是否为'\\0'、'#'、';',若是则释放line并跳出
            case '\\0':
            case '#':
            case ';':
                free(line);
                break;
            default:
                if(!read_option(line, options)){  // 将line读入到链表options
                    fprintf(stderr, "Config file error line %d, could parse: %s\\n", nu, line);
                    free(line);
                }
                break;
        }
    }
    fclose(file);
    return options;
}

  上面实现中的三个函数拿出来说一说:fgetl、strip、read_option。

  fgetl 的功能是逐行读入,来看一下 fgetl 的实现:

/// utils.c
// 逐行读取
char *fgetl(FILE *fp)
{
    if(feof(fp)) return 0;                                  // 判断是否读到文件尾
    size_t size = 512;
    char* line = (char*)xmalloc(size * sizeof(char));       // 限定一次最多读512 char*数据
    if(!fgets(line, size, fp)){                             // fgets是从fp流中读取size个字符到line,当读取到size-1个字符或换行符或到达文件尾时,则停止。即逐行读
        free(line);
        return 0;
    }

    size_t curr = strlen(line);                             // 获取行的大小

    while((line[curr-1] != '\\n') && !feof(fp)){             // 防止实际文件行大于size的处理逻辑
        if(curr == size-1){                                 // 将size扩容后再存入
            size *= 2;
            line = (char*)xrealloc(line, size * sizeof(char));  // xrelloc调整已开辟空间大小
        }
        size_t readsize = size-curr;
        if(readsize > INT_MAX) readsize = INT_MAX-1;
        fgets(&line[curr], readsize, fp);
        curr = strlen(line);
    }
    if(curr >= 2)
        if(line[curr-2] == 0x0d) line[curr-2] = 0x00;

    if(curr >= 1)
        if(line[curr-1] == 0x0a) line[curr-1] = 0x00;

    return line;
}

  再来看一下 strip 的实现:

/// 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'||c =='\\r'||c==0x0d||c==0x0a) ++offset;   // 去除' '、'\\t'、'\\n'、'\\r'、0x0d即回车符、0x0a即换行符
        else s[i-offset] = c;
    }
    s[len-offset] = '\\0';          // 尾巴加上'\\0'
}

  最后来看一下 read_option 的实现:

/// option_list.c
// 将字符串读入链表
int read_option(char *s, list *options)
{
    size_t i;
    size_t len = strlen(s);
    char *val = 0;
    for(i = 0; i < len; ++i){           // voc.data中每行字符都有'=', 取'='后数据
        if(s[i] == '='){           
            s[i] = '\\0';
            val = s+i+1;
            break;
        }
    }
    if(i == len-1) return 0;           // 判断'='后是否有数据,若没有则跳出
    char *key = s;                     // 将'='前的键值赋给key
    option_insert(options, key, val);  // 将数据插入链表操作
    return 1;
}

  来看一下将数据插入链表方法 option_insert:

/// option_list.c
void option_insert(list *l, char *key, char *val)
{
    kvp* p = (kvp*)xmalloc(sizeof(kvp));
    p->key = key;
    p->val = val;
    p->used = 0;
    list_insert(l, p);                  // 链表中插入,按顺序插入在尾
}

  关于 list_insert 可查阅我之前写的《【编程艺术】剖析 darknet C 链表实现》中的 list_insert 实现注释,说的比较清楚。


  这里介绍分析了一下 darknet 中 read_data_cfg 接口,也可以帮助温固 C 语言基础。

a4f9e65a5eb08f22307707339c02b5839240d8ab7a0977f&token=277768597&lang=zh_CN#rd)》中的 list_insert 实现注释,说的比较清楚。


  这里介绍分析了一下 darknet 中 read_data_cfg 接口,也可以帮助温固 C 语言基础。


 【公众号传送】

《【编程艺术】剖析 darknet read_data_cfg 接口》



扫描下方二维码即可关注我的微信公众号【极智视界】,获取更多AI经验分享,让我们用极致+极客的心态来迎接AI !

以上是关于编程艺术剖析 darknet read_data_cfg 接口的主要内容,如果未能解决你的问题,请参考以下文章

编程艺术剖析 darknet 链表查找 option_find_xx 接口

编程艺术剖析 darknet parse_network_cfg 接口

编程艺术剖析 darknet C 链表实现

经验分享剖析 darknet entry_index 指针偏移逻辑

darknet源码剖析 模型训练初探

darknet源码剖析box_iou详细分析