只看一眼学习笔记(一)

yolo的作者不久前发表了yolov3,网络结构变深了,但是速度依然非常快,准确度也得到了提升。

yolov项目地址:https://pjreddie.com/darknet/yolo/

自己的工作需要使用yolo检测目标,记录一下学习内容以备日后翻阅。查看了新的yolov3.cfg,其中有一些新的参数,如[upsample]和[yolo]等,根据AlexeyAB在github上的解答(https://github.com/AlexeyAB/darknet/issues/504#issuecomment-377290060)upsample层实现的是类似yolov2中reorg层的功能,查看代码后,我的理解是当forward输入的一个特征图上每个点用它的scale倍(默认为1,可以在cfg里设置)扩充为4个点,最后输出的w和h都成为原来的2倍。这里是为了使高层特征的维度和低层特征的维度相同,然后concatenate起来提高细粒度特征的提取能力。

yolo层是新的检测器层,替代region层使用了多尺度预测的方法。下面是解析cfg文件中yolo层的代码。

layer parse_yolo(list *options, size_params params)
{
	int classes = option_find_int(options, "classes", 20);    // 检测任务的目标类别数
	int total = option_find_int(options, "num", 1);            // anchor的总个数
	int num = total;                                

	char *a = option_find_str(options, "mask", 0);         // 该层使用的anchor的索引,yolov3使用多尺度预测,每个尺度使用3个anchor
	int *mask = parse_yolo_mask(a, &num);              // 把a字符串中以,分隔得数字转换为整形保存在mask数组中,num更新为a中数字的个数
	layer l = make_yolo_layer(params.batch, params.w, params.h, num, total, mask, classe);
	assert(l.outputs == params.inputs);

	l.max_boxes = option_find_int_quiet(options, "max", 90);
	l.jitter = option_find_float(options, "jitter", .2);        // 数据抖动范围,默认0~0.2

	l.ignore_thresh = option_find_float(options, "ignore_thresh", .5);    // 决定是否需要计算IOU误差的参数
	l.truth_thresh = option_find_float(options, "truth_thresh", 1);
	l.random = option_find_int_quiet(options, "random", 0);    // 如果为1每次迭代图片大小随机,为0训练大小与输入大小一致

	char *map_file = option_find_str(options, "map", 0);
	if (map_file) l.map = read_map(map_file);

	a = option_find_str(options, "anchors", 0);
	if (a) {
		int len = strlen(a);
		int n = 1;
		int i;
		for (i = 0; i < len; ++i) {
			if (a[i] == ',') ++n;        // 统计anchor宽和高的总和
		}
		for (i = 0; i < n; ++i) {
			float bias = atof(a);
			l.biases[i] = bias;
			a = strchr(a, ',') + 1;     // 定位a到下一个,后面第一个位置
		}
	}
	return l;
}

下面是make_yolo_layer函数的代码:

layer make_yolo_layer(int batch, int w, int h, int n, int total, int *mask, int classes)
{
    int i;
    layer l = {0};
    l.type = YOLO;

    l.n = n;                        // 该层使用的anchor数
    l.total = total;                // yolov3的总anchor数
    l.batch = batch;
    l.h = h;
    l.w = w;
    l.c = n*(classes + 4 + 1);    
    l.out_w = l.w;
    l.out_h = l.h;
    l.out_c = l.c;
    l.classes = classes;
    l.cost = calloc(1, sizeof(float));
    l.biases = calloc(total*2, sizeof(float));
    if(mask) l.mask = mask;
    else{
        l.mask = calloc(n, sizeof(int));
        for(i = 0; i < n; ++i){
            l.mask[i] = i;
        }
    }
    l.bias_updates = calloc(n*2, sizeof(float));
    l.outputs = h*w*n*(classes + 4 + 1);            
    l.inputs = l.outputs;
    l.truths = 90*(4 + 1);            // max boxes默认90,即每张图像中目标的最大个数
    l.delta = calloc(batch*l.outputs, sizeof(float));
    l.output = calloc(batch*l.outputs, sizeof(float));
    for(i = 0; i < total*2; ++i){
        l.biases[i] = .5;
    }

    l.forward = forward_yolo_layer;
    l.backward = backward_yolo_layer;
#ifdef GPU
    l.forward_gpu = forward_yolo_layer_gpu;
    l.backward_gpu = backward_yolo_layer_gpu;
    l.output_gpu = cuda_make_array(l.output, batch*l.outputs);
    l.delta_gpu = cuda_make_array(l.delta, batch*l.outputs);
#endif

    fprintf(stderr, "detection\n");
    srand(0);

    return l;
}

定义了一些输入输出维度和anchor个数以及分配内存后,主要还是forward和backward函数,首先需要了解一些存储结构,才能读懂源码中的索引。

首先是net.truth,其实是target的存储格式:x,y,w,h,class,x,y,w,h,class...

然后是output数组的存储格式:w×h×entry×n×batch,entry对应每个anchor生成的向量维度,长度是(4+1+classes),依次存储box的(x,y,w,h)和有无目标的confidence以及类别class。

void forward_yolo_layer(const layer l, network net)
{
    int i,j,b,t,n;
    memcpy(l.output, net.input, l.outputs*l.batch*sizeof(float));

#ifndef GPU
    for (b = 0; b < l.batch; ++b){
        for(n = 0; n < l.n; ++n){
            int index = entry_index(l, b, n*l.w*l.h, 0);
            activate_array(l.output + index, 2*l.w*l.h, LOGISTIC);
            index = entry_index(l, b, n*l.w*l.h, 4);
            activate_array(l.output + index, (1+l.classes)*l.w*l.h, LOGISTIC);
        }
    }
#endif

    memset(l.delta, 0, l.outputs * l.batch * sizeof(float));            // 梯度清零
    if(!net.train) return;                
    float avg_iou = 0;            // 平均IOU
    float recall = 0;            // 召回
    float recall75 = 0;            // IOU大于.75的召回
    float avg_cat = 0;            // 平均类别辨识率
    float avg_obj = 0;
    float avg_anyobj = 0;
    int count = 0;                // 检测到的target数
    int class_count = 0;
    *(l.cost) = 0;                // 损失
    for (b = 0; b < l.batch; ++b) {
        for (j = 0; j < l.h; ++j) {
            for (i = 0; i < l.w; ++i) {
                for (n = 0; n < l.n; ++n) {
                    int box_index = entry_index(l, b, n*l.w*l.h + j*l.w + i, 0);    
                    // 带入 entry_index, 由output tensor的存储格式可以知道这里是第n类anchor在(i,j)上对应box的首地址
                    box pred = get_yolo_box(l.output, l.biases, l.mask[n], box_index, i, j, l.w, l.h, net.w, net.h, l.w*l.h);
                    // 在(i,j)上预测的box的x和y相对于l.w和l.h的值及w和h相对于原图w和h的值
                    float best_iou = 0;
                    int best_t = 0;
                    for(t = 0; t < l.max_boxes; ++t){
                        box truth = float_to_box(net.truth + t*(4 + 1) + b*l.truths, 1);
                        // 从gt中提取一个真实目标框
                        if(!truth.x) break;
                        float iou = box_iou(pred, truth);    // 计算IOU
                        if (iou > best_iou) {
                            best_iou = iou;            // 找到与当前预测box的最大IOU
                            best_t = t;
                        }
                    }
                    int obj_index = entry_index(l, b, n*l.w*l.h + j*l.w + i, 4);
                    // 找到第n个anchor在(i,j)处的confidence的首地址
                    avg_anyobj += l.output[obj_index];    // 有目标的概率
                    l.delta[obj_index] = 0 - l.output[obj_index];    
                    if (best_iou > l.ignore_thresh) {
                        l.delta[obj_index] = 0;                // 预测框中有目标
                    }
                    if (best_iou > l.truth_thresh) {            // l.truth_thresh = 1
                        l.delta[obj_index] = 1 - l.output[obj_index];

                        int class = net.truth[best_t*(4 + 1) + b*l.truths + 4];
                        if (l.map) class = l.map[class];
                        int class_index = entry_index(l, b, n*l.w*l.h + j*l.w + i, 4 + 1);
                        delta_yolo_class(l.output, l.delta, class_index, class, l.classes, l.w*l.h, 0);
                        box truth = float_to_box(net.truth + best_t*(4 + 1) + b*l.truths, 1);
                        delta_yolo_box(truth, l.output, l.biases, l.mask[n], box_index, i, j, l.w, l.h, net.w, net.h, l.delta, (2-truth.w*truth.h), l.w*l.h);
                    }
                }
            }
        }
        for(t = 0; t < l.max_boxes; ++t){
            box truth = float_to_box(net.truth + t*(4 + 1) + b*l.truths, 1);
             // 把truth转成box类型,x,y,w,h,都是归一化后的真实值

            if(!truth.x) break;
            float best_iou = 0;
            int best_n = 0;
            i = (truth.x * l.w);            // 计算该truth所在的cell的i,j坐标
            j = (truth.y * l.h);
            box truth_shift = truth;
            truth_shift.x = truth_shift.y = 0;
            for(n = 0; n < l.total; ++n){        
                                            // 找出与该truth的IOU最大的是哪一种anchor
                box pred = {0};
                pred.w = l.biases[2*n]/net.w;
                pred.h = l.biases[2*n+1]/net.h;
                float iou = box_iou(pred, truth_shift);
                if (iou > best_iou){
                    best_iou = iou;
                    best_n = n;
                }
            }

            int mask_n = int_index(l.mask, best_n, l.n);    // 求对应于当前mask中anchor标号的下标,没有返回-1
            if(mask_n >= 0){                                // 如果不是当前层的mask数组中的,不执行
                int box_index = entry_index(l, b, mask_n*l.w*l.h + j*l.w + i, 0);    // 第mask_n个anchor的在i,j位置预测的box 
                float iou = delta_yolo_box(truth, l.output, l.biases, best_n, box_index, i, j, l.w, l.h, net.w, net.h, l.delta, (2-truth.w*truth.h), l.w*l.h);
                // 用第mask_n个anchor得到预测box,然后计算与该truth的IOU,得到IOU同时更新梯度数组:用真正需要平移的量和缩放量减去预测的box

                int obj_index = entry_index(l, b, mask_n*l.w*l.h + j*l.w + i, 4);
                avg_obj += l.output[obj_index];            // obj_index就是i,j位置是否有目标confidence的首地址
                l.delta[obj_index] = 1 - l.output[obj_index];

                int class = net.truth[t*(4 + 1) + b*l.truths + 4];    // target:x,y,w,h,class
                if (l.map) class = l.map[class];
                int class_index = entry_index(l, b, mask_n*l.w*l.h + j*l.w + i, 4 + 1);
                                                        // 第mask_n个anchor在i,j位置预测的class的首地址,4+1是坐标加confidence

                delta_yolo_class(l.output, l.delta, class_index, class, l.classes, l.w*l.h, &avg_cat);
                                                        // 更新预测class的梯度和avg_cat的值

                ++count;
                ++class_count;
                if(iou > .5) recall += 1;
                if(iou > .75) recall75 += 1;
                avg_iou += iou;
            }
        }
    }
    *(l.cost) = pow(mag_array(l.delta, l.outputs * l.batch), 2);        // MSE loss
    printf("Region %d Avg IOU: %f, Class: %f, Obj: %f, No Obj: %f, .5R: %f, .75R: %f,  count: %d\n", net.index, avg_iou/count, avg_cat/class_count, avg_obj/count, avg_anyobj/(l.w*l.h*l.n*l.batch), recall/count, recall75/count, count);
}


参考:

https://github.com/AlexeyAB/darknet

https://www.cnblogs.com/makefile/p/YOLOv3.html

http://www.cnblogs.com/YiXiaoZhou/p/7429481.html

https://www.cnblogs.com/zf-blog/p/7142463.html


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值