本系列为darknet源码解析,本次解析src/yolo_layer.h 与 src/yolo_layer.c 两个。yolo_layer主要完成了yolo v3中的三层的detection,分别是52*52*75,26*26*75,13*13*75是yolo v3这篇论文的核心部分。
在阅读本节源码之前,请先了解一下 52*52*75,26*26*75,13*13*75 是什么样子的逻辑存储形式,在物体存储是一维数组;以及yolov3中bbox的[x, y, w, h]是如何进行表示的,本节只解析了yolov3的训练阶段的源码,inference阶段未进行解析;配对的cfg文件为cfg/yolov3-voc.cfg
yolo v3中用多个独立的逻辑回归替代了yolo v2的softmax,对每个标签使用使用二元交叉熵损失,避免使用softmax函数而降低计算复杂度;对[x, y] 和confidence进行逻辑回归;
yolo_layer.h 的定义如下:
#ifndef YOLO_LAYER_H
#define YOLO_LAYER_H
#include "darknet.h"
#include "layer.h"
#include "network.h"
// 构造yolo v3 yolo层
layer make_yolo_layer(int batch, int w, int h, int n, int total, int *mask, int classes);
// yolo层前向传播函数
void forward_yolo_layer(const layer l, network net);
// yolo层反向传播函数
void backward_yolo_layer(const layer l, network net);
void resize_yolo_layer(layer *l, int w, int h);
int yolo_num_detections(layer l, float thresh);
#ifdef GPU
void forward_yolo_layer_gpu(const layer l, network net);
void backward_yolo_layer_gpu(layer l, network net);
#endif
#endif
yolo_layer.c 的详细解释如下:
#include "yolo_layer.h"
#include "activations.h"
#include "blas.h"
#include "box.h"
#include "cuda.h"
#include "utils.h"
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
/**
* 构建yolo v3层中的yolo层
* @param batch 一个batch中包含图片的张数
* @param w 输入图片的宽度
* @param h 输入图片的高度
* @param n 一个cell预测多少个bbox
* @param total Anchor bbox的数目
* @param mask 使用的是0,1,2 还是
* @param classes 网络需要识别的物体类别数
* @return
*/
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; // 一个cell预测多少个bbox
l.total = total; // anchors的数目, 为9
l.batch = batch; // 一个batch包含图片的张数
l.h = h; // 输入图片的宽度
l.w = w; // 输入图片的高度
l.c = n*(classes + 4 + 1); // 输入图片的通道数, 3*(20 + 5)
l.out_w = l.w; // 输出图片的宽度
l.out_h = l.h; // 输出图片的高度
l.out_c = l.c; // 输出图片的通道数
l.classes = classes; // 网络需要识别的网络
l.cost = calloc(1, sizeof(float)); // yolo层的总损失
l.biases = calloc(total*2, sizeof(float)); // 存储bbox的Anchor box的[w,h]
if(mask) l.mask = mask; // 52*52的时候,mask=[6,7,8] 26*26的时候,mask=[3,4,5], 13*13的时候,mask=[0,1,2]
else{ // yolo v3 有mask传入,
l.mask = calloc(n, sizeof(int));
for(i = 0; i < n; ++i){
l.mask[i] = i;
}
}
l.bias_updates = calloc(n*2, sizeof(float)); //存储bbox的Anchor box的[w,h]的更新值
l.outputs = h*w*n*(classes + 4 + 1); // yolo层对应输入type的输出元素个数,yolo层输入输出元素不发生变化
l.inputs = l.outputs; // yolo层一张输入图片的元素个数
l.truths = 90*(4 + 1); // GT: 存储90个bbox的信息, 这里是假设图片中GT bbox的数量是小于30的, 这里是写死的;此处与yolov1不同
l.delta = calloc(batch*l.outputs, sizeof(float)); // yolo层误差项(包含整个batch的)
l.output = calloc(batch*l.outputs, sizeof(float)); // yolo层所有输出(包含整个batch的)
// 存储bbox的Anchor box的[w,h]的初始化,在src/parse.c中parse_yolo函数会加载cfg中Anchor尺寸
for(i = 0; i < total*2; ++i){
l.biases[i] = .5;
}
l.forward = forward_yolo_layer; // yolo层的前向传播
l.backward = backward_yolo_layer; // yolo层的反向传播
#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, "yolo\n");
srand(0);
return