load_data_detection是数据加载过程的最后一步。下面就来对他进行详细分析。
首先是空间分配过程:
d.X.rows = n;
d.X.vals = calloc(d.X.rows, sizeof(float*));
d.X.cols = h*w*3;
d.y = make_matrix(n, 5*boxes);
d是data类型数据, data定义如下:
typedef struct{
int w, h;
matrix X;
matrix y;
int shallow;
int *num_boxes;
box **boxes;
} data;
根据以上代码可以发现,加载检测数据,仅使用了X与y变量,X与y又是matrix类型的变量,下面来看matrix类型的定义:
typedef struct matrix{
int rows, cols;
float **vals;
} matrix;
其中X.rows为1,X.vals分配的空间为1个float*类型的地址空间,X.cols 的值为h*w*3为602112。
d.y使用make_matrix(src/matrix.c)进行初始化。
matrix make_matrix(int rows, int cols)
{
int i;
matrix m;
m.rows = rows;
m.cols = cols;
m.vals = calloc(m.rows, sizeof(float *));
for(i = 0; i < m.rows; ++i){
m.vals[i] = calloc(m.cols, sizeof(float));
}
return m;
}
其中rows为1,cols为450,vals分配的空间共1×450个float的空间。
此处X.rows与y.rows的值都是1,应该是表示一组数据。
接下来就是数据加载的详细过程,上次没有分析清楚,这次做详细分析。
image orig = load_image_color(random_paths[i], 0, 0);
image sized = make_image(w, h, orig.c);
fill_image(sized, .5);
load_image_color就是加载原图片,sized是网络的输入图片,448*448*3大小的,fill_image是用0.5填充sized图片。
float dw = jitter * orig.w;
float dh = jitter * orig.h;
float new_ar = (orig.w + rand_uniform(-dw, dw)) / (orig.h + rand_uniform(-dh, dh));
dw、dy的值分别为原图w、h的0.2。new_ar的值是随机生成的,之后会以1为界进行调整。
float scale = 1;
float nw, nh;
if(new_ar < 1){
nh = scale * h;
nw = nh * new_ar;
} else {
nw = scale * w;
nh = nw / new_ar;
}
float dx = rand_uniform(0, w - nw);
float dy = rand_uniform(0, h - nh);
若new_ar小于1,则nh为448,nw为小于448的一个值,dx为一个随机值,dy为0;若new_ar大于1,则nw为448,nh为小于448的一个值,dx为0,dy为一个随机值。
place_image(orig, nw, nh, dx, dy, sized);
接下来的place_image、random_distort_image、fill_truth_detection的功能分析我主要是根据https://github.com/hgpvision/darknet/
看了他的解释后我还是没有完全理解。总之一句话place_image就是将orig的一部分放到sized上,nw、nh分别是中间图的宽度与高度,dx、dy分别是中间图插入到sized的x、y方向上的偏移。至于为什么需要插值运算计算中间图就不太清晰了。
过程可能不太理解,但可以通过实验的方式对程序运行结果进行验证。place_image的功能就是将图片调整为448*448大小,同时具有一定的平移功能。
为了进行上述实验,需要将example/detector.c文件中train_detector函数中的
args.threads = 64;
改为
args.threads = 1;
同时在src/data.c文件中load_data_detection函数中的place_image,后添加
save_image(sized, "test");
将处理的中间结果保存下来。之所以要将args.threads改为1,是因为同时启动64个线程,结果会反复写入test.jpg中。
place_image效果如下:
原图片,大小为375*500:
经过place_image函数后的图片,大小为448*488:
如此就可以比较直观的place_image的功能了,首先是对图片进行了一定的比例缩放,然后还有一定的平移操作,最后图片的大小是448*448。
random_distort_image是对图片进行随机扭曲。如法炮制,对random_distort_image的结果进行绘制。
经过random_distort_image函数后的图片:
可以发现图片颜色有一些变化,但不是很明显。
fill_truth_detection是将标注框(ground truth)经过调整后存储到d.y.vals[i]。其中起主要作用的就是correct_boxes函数,该函数的作用就是调整标注框为调整后的图片的标注框。
在fill_truth_detection函数中,nw、nh分别代表调整后的图片大小,例如nw、nh的值分别为448、353。dx、dy的值分别为0、47,其中dy是0~(h-nh)的一个任意值。dx、dy表示调整后的图在x轴、y轴方向上的偏移。在本例中原图被缩放为448*353大小后,在y轴方向上还有一个47像素的偏移。理解了这部分信息也就基本理解了fill_truth_detection函数的参数dx、dy、sx、sy的含义,sx、sy分别代表缩放比例,而dx、dy分别代表距离图片左上角的偏移。
经过上述三个函数后一组图片与其对应的标注就加载完成了。