天天看点

YOLO中参数和函数说明

YOLO:you only look once中参数和函数说明 1、画出的Box:为包围物体的ground truth。 2、lable标签 : x y w h,(x,y)是box的位置,w,h是为box的宽度和高度是图片的宽度,高度的比值。在生成的lable中是这样的

YOLO中参数和函数说明

第一列是类别信息,0、1、2表示你的三个类别,第二列和第三列是x、y的坐标,第四和五列就是上述的w,h.

3、训练时候利用如下命令: 

./darknet detector train ./cfg/voc.data cfg/tiny-yolo-voc.cfg
           

darknet 是Gcc makefile的可执行文件,进入darknet.c 的主函数  

YOLO中参数和函数说明

可以看到检测到上述命令行的地一个参数是detector

进入detector.c

YOLO中参数和函数说明

命令行的第二个参数是train,于是执行高亮语句,train_detector,接下来我们看一下 train_detector的定义,在截图文件的上部分。

YOLO中参数和函数说明

第三个参数是VOC_DATA

YOLO中参数和函数说明

第一行是类别的个数,第二和第三行是训练和验证集的文本位置,文本中包含了图片路径和名字,

/home/***/darknet/scripts/VOCdevkit/VOC2017/JPEGImages/0006.png
/home/***/darknet/scripts/VOCdevkit/VOC2017/JPEGImages/0007.png
/home/***/darknet/scripts/VOCdevkit/VOC2017/JPEGImages/0008.png
           

voc.names中是三类物品的名字

backup保存训练时候的权重,

if(i%1000==0 || (i < 1000 && i%100 == 0)){
#ifdef GPU
            if(ngpus != 1) sync_nets(nets, ngpus, 0);
#endif
            char buff[256];
            sprintf(buff, "%s/%s_%d.weights", backup_directory, base, i);
            save_weights(net, buff);
        }
           

每一百次和最后会保存一次权重的值。保存在backup中。

在yolo_voc.cfg中定义了网络的参数和网络的结构

[net]
batch=64
subdivisions=8//表示加载一次数据v可以训练几次
height=416 //图像缩放后的大小
width=416
channels=3//彩色图像
momentum=0.9
decay=0.0005
angle=0
saturation = 1.5
exposure = 1.5
hue=.1

learning_rate=0.0001
max_batches = 32000//最大训练次数
policy=steps
steps=100,20000,30000
scales=10,.1,.1
           

中间的网络结构不列出了,看最后一个卷积层

[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=1024 //卷积核的个数
activation=leaky//leaky激活函数,大于0的为x,小于0的为0

[convolutional]
size=1
stride=1
pad=1
filters=40//用3类训练,所以5*(3+4+1)
activation=linear

[region]
anchors = 1.08,1.19,  3.42,4.41,  6.63,11.38,  9.42,5.11,  16.62,10.52
bias_match=1
classes=3//类别数
coords=4
num=5
softmax=1
jitter=.2
rescore=1
           

下文参考博客:http://blog.csdn.net/u014540717/article/details/53667416

如何把图像分为7*7个网格,每个网格推荐两个框是什么意思,

我们来看一下

truth_index

是怎么定义的:

int truth_index = (b*locations + i)*(+l.coords+l.classes);
           

这里参数意义如下:

locations:7*7

b :batch size的索引

i :locations的索引

1 :置信度

l.coords :值为4,分别表示x,y,w,h

l.classes : 20

然后在下面我们可以看到如下代码段

//l.n就是一个网格要推荐几个框,论文中l.n=2
for(j = ; j < l.n; ++j){
                    int box_index = index + locations*(l.classes + l.n) + (i*l.n + j) * l.coords;
                    box out = float_to_box(l.output + box_index);
                    out.x /= l.side;
                    out.y /= l.side;

                    if (l.sqrt){
                        out.w = out.w*out.w;
                        out.h = out.h*out.h;
                    }

                    //计算iou的值
                    float iou  = box_iou(out, truth);
                    //iou = 0;
                    //计算均方根误差(root-mean-square error)
                    float rmse = box_rmse(out, truth);
                    //选出iou最大或者均方根误差最小的那个框作为最后预测框~
                    if(best_iou >  || iou > ){
                        if(iou > best_iou){
                            best_iou = iou;
                            best_index = j;
                        }
                    }else{
                        if(rmse < best_rmse){
                            best_rmse = rmse;
                            best_index = j;
                        }
                    }
                }
           

上述代码中最重要的是

box_iou(out, truth);

这句代码,这句代码是要计算你输出的框和真实框的IOU,truth的定义如下:

box truth = float_to_box(state.truth + truth_index +  + l.classes);
           

从定义我们可以得到,真实框的坐标来自state.truth,我们来追本溯源

state.truth最初是在network.c中赋值的

//network.c
state.truth = y
           

y又从哪里来呢?最初的y是从

load_data_in_thread(args);

这个函数中获得的,我们来剖析下该函数

//data.c
pthread_t load_data_in_thread(load_args args)
{
    pthread_t thread;
    struct load_args *ptr = calloc(, sizeof(struct load_args));
    *ptr = args;
    //调用load_thread这个函数
    if(pthread_create(&thread, , load_thread, ptr)) error("Thread creation failed");
    return thread;
}
           
//data.c
void *load_thread(void *ptr)
{
    //printf("Loading data: %d\n", rand());
    load_args a = *(struct load_args*)ptr;
    if(a.exposure == ) a.exposure = ;
    if(a.saturation == ) a.saturation = ;
    if(a.aspect == ) a.aspect = ;

    if (a.type == OLD_CLASSIFICATION_DATA){
        *a.d = load_data_old(a.paths, a.n, a.m, a.labels, a.classes, a.w, a.h);
    } else if (a.type == CLASSIFICATION_DATA){
        *a.d = load_data_augment(a.paths, a.n, a.m, a.labels, a.classes, a.hierarchy, a.min, a.max, a.size, a.angle, a.aspect, a.hue, a.saturation, a.exposure);
    } else if (a.type == SUPER_DATA){
        *a.d = load_data_super(a.paths, a.n, a.m, a.w, a.h, a.scale);
    } else if (a.type == WRITING_DATA){
        *a.d = load_data_writing(a.paths, a.n, a.m, a.w, a.h, a.out_w, a.out_h);
    } else if (a.type == REGION_DATA){
        //因为a.type == REGION_DATA,所以调用这个函数,我们继续追~
        *a.d = load_data_region(a.n, a.paths, a.m, a.w, a.h, a.num_boxes, a.classes, a.jitter, a.hue, a.saturation, a.exposure);
.
.
           
//data.c
data load_data_region(int n, char **paths, int m, int w, int h, int size, int classes, float jitter, float hue, float saturation, float exposure)
{
    char **random_paths = get_random_paths(paths, n, m);
    int i;
    data d = {};
    d.shallow = ;
    //n就是batch size啦
    d.X.rows = n;
    //给X(也就是图像数据)分配内存
    d.X.vals = calloc(d.X.rows, sizeof(float*));
    d.X.cols = h*w*;


    int k = size*size*(+classes);
    //终于找到你啦~\(≧▽≦)/~。这里先给y分配了内存,注意一共分配了n*k个float类型的内存块,为什么分配这么多呢?慢慢往下看~
    d.y = make_matrix(n, k);
    for(i = ; i < n; ++i){
        //读取图像
        image orig = load_image_color(random_paths[i], , );

        int oh = orig.h;
        int ow = orig.w;

        //这里jitter=0.2(cfg文件中有写),这就是所谓的抖动了,其实就是crop(数据增广的一种)
        //剪掉的不能太多,这里设置图像的左边和右边最多剪掉dw(整幅图像宽度的1/5),上边和下边最多剪掉dh(整幅图像高度的1/5)
        int dw = (ow*jitter);
        int dh = (oh*jitter);
        //rand_uniform生成(-dw, dw)的一个随机数
        int pleft  = rand_uniform(-dw, dw);
        int pright = rand_uniform(-dw, dw);
        int ptop   = rand_uniform(-dh, dh);
        int pbot   = rand_uniform(-dh, dh);

        //swidth是图像剪完后的宽度,sheight是图像剪完后的高度
        int swidth =  ow - pleft - pright;
        int sheight = oh - ptop - pbot;

        //sx是图像剪完后宽度和原始图像的宽度比,同理sy
        float sx = (float)swidth  / ow;
        float sy = (float)sheight / oh;

        //设置图像随机翻转
        int flip = rand()%;
        //开始剪切图像,咔咔咔,具体代码不看了,很简单~
        image cropped = crop_image(orig, pleft, ptop, swidth, sheight);

        //dx=pleft/swidth,dy=ptop/sheight
        float dx = ((float)pleft/ow)/sx;
        float dy = ((float)ptop /oh)/sy;

        //都剪完了,当然要把图像重新resize到448*448(论文中说了,输入图像是448*448)
        image sized = resize_image(cropped, w, h);
        //翻转图像~
        if(flip) flip_image(sized);
        //图像随机排序
        random_distort_image(sized, hue, saturation, exposure);
        //最终d.X.vals[]存储的就是要输入的数据啦,准备好X了,我们去准备下y
        d.X.vals[i] = sized.data;

        //开始追y,追追追~
        fill_truth_region(random_paths[i], d.y.vals[i], classes, size, flip, dx, dy, /sx, /sy);

           
//data.c
void fill_truth_region(char *path, float *truth, int classes, int num_boxes, int flip, float dx, float dy, float sx, float sy)
{
    char labelpath[];
    //有人一直不知道labels怎么来的,说源码都没设置labels的路径啊,怎么读的labels啊,那不是成了无监督学习?其实源码只是没直接设置labels的路径而已,把images替换为labels,在把.jpg替换为.txt,labels的路径就有了~
    find_replace(path, "images", "labels", labelpath);
    find_replace(labelpath, "JPEGImages", "labels", labelpath);

    find_replace(labelpath, ".jpg", ".txt", labelpath);
    find_replace(labelpath, ".png", ".txt", labelpath);
    find_replace(labelpath, ".JPG", ".txt", labelpath);
    find_replace(labelpath, ".JPEG", ".txt", labelpath);
    int count = ;
    //从.txt中读取labels值,count记录框的个数
    box_label *boxes = read_boxes(labelpath, &count);
    //把框随机排序~
    randomize_boxes(boxes, count);
    //因为图像已经被修剪了,所以框的坐标也要改一改,correct_boxes函数就是把框在原始图像下的坐标转到修剪后图像下的坐标
    correct_boxes(boxes, count, dx, dy, sx, sy, flip);
    float x,y,w,h;
    int id;
    int i;
    for (i = ; i < count; ++i) {
        x =  boxes[i].x;
        y =  boxes[i].y;
        w =  boxes[i].w;
        h =  boxes[i].h;
        id = boxes[i].id;

        //修剪后,太小的框就不作为正样本了
        if (w <  || h < ) continue;

        //这里x的值为0~1之间(不一定能取到0和1,因为图像被修剪过了,坐标的范围也变了),num_boxes=7,所以col和row都是0~6之间的整数
        int col = (int)(x*num_boxes);
        int row = (int)(y*num_boxes);

        //x和y又被打回原型,又变成0~1之间的数了
        x = x*num_boxes - col;
        y = y*num_boxes - row;

        //index就懂了吧,一共7*7个网格,每个网格的索引是0~6
        int index = (col+row*num_boxes)*(+classes);
        if (truth[index]) continue;
        //如果第i个框落在这个网格里,就把相应的置信度赋1
        truth[index++] = ;
        //然后看标签id是几,就把对应的类别处赋值为1
        if (id < classes) truth[index+id] = ;
        index += classes;
        //再赋值框的x,y,w,h到truth
        truth[index++] = x;
        truth[index++] = y;
        truth[index++] = w;
        truth[index++] = h;
    }
    free(boxes);
}
           

y值追完了,我们再回过头来看

float iou  = box_iou(out, truth)
           

out(每个网格一共l.n个out,论文中l.n=2)就是网络回归出来的值,然后把out的值和truth中的值对应比较,计算出iou,然后从

l.n

个iou中挑出iou最高的一个,作为最后的预测框,说白了就是:只有该框会对loss function产生影响,其他框不产生影响

正在阅读yolo代码,理解有误的地方欢迎指正。随着后续的理解,笔记不断更新纠正。。。。。

继续阅读