参考文献依然是放前面:https://blog.csdn.net/caicaiatnbu/category_9096319.html
darknet版本: https://github.com/AlexeyAB/darknet,与原始的版本还是有一点区别的。
这个博客解释的超详细:https://blog.csdn.net/caicaiatnbu/article/details/100515321
进入代码:
im2col.h
#ifndef IM2COL_H
#define IM2COL_H
//防止头文件重复定义
#include <stddef.h>
#include <stdint.h>
#include "darknet.h"
#ifdef __cplusplus
extern "C" {
#endif
void im2col_cpu(float* data_im,
int channels, int height, int width,
int ksize, int stride, int pad, float* data_col);
float im2col_get_pixel(float* im, int height, int width, int channels,
int row, int col, int channel, int pad);
void im2col_cpu_ext(const float* data_im, const int channels,
const int height, const int width, const int kernel_h, const int kernel_w,
const int pad_h, const int pad_w,
const int stride_h, const int stride_w,
const int dilation_h, const int dilation_w,
float* data_col);
#ifdef GPU
void im2col_ongpu(float *im,
int channels, int height, int width,
int ksize, int stride, int pad,float *data_col);
void im2col_gpu_ext(const float* data_im, const int channels,
const int height, const int width, const int kernel_h, const int kernel_w,
const int pad_h, const int pad_w,
const int stride_h, const int stride_w,
const int dilation_h, const int dilation_w,
float* data_col);
void im2col_align_ongpu(float *im,
int channels, int height, int width,
int ksize, int stride, int pad, float *data_col, int bit_align);
void im2col_align_bin_ongpu(float *im,
int channels, int height, int width,
int ksize, int stride, int pad, float *data_col, int bit_align);
void float_to_bit_gpu(float *src, unsigned char *dst, size_t size);
void transpose_bin_gpu(unsigned char *A, unsigned char *B, const int n, const int m,
const int lda, const int ldb, const int block_size);
void transpose_uint32_gpu(uint32_t *src, uint32_t *dst, int src_h, int src_w, int src_align, int dst_align);
void transpose_uint32_gpu_2(uint32_t *src, uint32_t *dst, int src_h, int src_w, int src_align, int dst_align);
void repack_input_gpu(float *input, float *re_packed_input, int w, int h, int c);
void repack_input_gpu_2(float *input, float *re_packed_input, int w, int h, int c);
void repack_input_gpu_bin(float *input, uint32_t *re_packed_input_bin, int w, int h, int c);
void fill_int8_gpu(unsigned char *src, unsigned char val, size_t size);
// shared_memory + partial coalescing = GOOD
void gemm_nn_custom_bin_mean_transposed_gpu(int M, int N, int K,
unsigned char *A, int lda,
unsigned char *B, int ldb,
float *C, int ldc, float *mean_arr, float *bias, int leaky_activation,
float *shortcut_in_gpu, float *shortcut_out_gpu);
// sequentially - BAD
void gemm_nn_custom_bin_mean_transposed_sequentially_gpu(int M, int N, int K,
unsigned char *A, int lda,
unsigned char *B, int ldb,
float *C, int ldc, float *mean_arr);
void convolve_gpu(float *input, float *weights, float *output, int in_w, int in_h, int in_c, int n, int size, int pad);
void convolve_bin_gpu(float *input, float *weights, float *output, int in_w, int in_h, int in_c, int n, int size, int pad,
int new_lda, float *mean_arr_gpu);
//void convolve_bin_cpu(float *input, float *weights, float *output, int in_w, int in_h, int in_c, int n, int size, int pad, int new_lda, float *mean_arr_gpu);
//void convolve_cpu(float *input, float *weights, float *output, int in_w, int in_h, int in_c, int n, int size, int pad);
#endif
#ifdef __cplusplus
}
#endif
#endif
im2col.c
#include "im2col.h"
#include <stdio.h>
//得到图像对应位置的像素值
float im2col_get_pixel(float *im, int height, int width, int channels,
int row, int col, int channel, int pad)
{ // 减去补0的长度,得到真实的行数和列数
row -= pad;
col -= pad;
//判断是否超出边界
// 如果行列数小于0,则返回0(刚好是补0的效果)
if (row < 0 || col < 0 ||
row >= height || col >= width) return 0;
// im存储多通道二维图像的数据格式为:各通道所有行并后成一行,再多通道一次并成一行;
// 所在指定通道所在行,再加上col移位到所在列
return im[col + width*(row + height*channel)];
}
//From Berkeley Vision's Caffe!
//https://github.com/BVLC/caffe/blob/master/LICENSE
//从caffe移植过来
//功能:将image转换为数组格式,要弄明白一点,这里将图片转换为数组的目的是为了更好的进行卷积操作。
//所以输出的应该是能够与卷积核直接进行卷积操作的矩阵,而不是原来的BGR存储格式
//那么他的尺寸大小就是关键
/*
*float* data_im 输入图片
*int channels 图像通道数
*int height 图像的高
*int width 图像的宽
*int ksize 卷积核的尺寸
*int stride 卷积的步长
*int pad 卷积的pading尺寸
*float* data_col 存放输出数据的数组
*/
void im2col_cpu(float* data_im,
int channels, int height, int width,
int ksize, int stride, int pad, float* data_col)
{
int c,h,w;
int height_col = (height + 2*pad - ksize) / stride + 1;
int width_col = (width + 2*pad - ksize) / stride + 1;
//以上获得卷积之后的特征长宽值
int channels_col = channels * ksize * ksize;
//以上确认转换时,im2col后的输出矩阵行数
//举个例子,如果是3*25*25的图片A遇上一组3*3*3的卷积核,
//通过卷积操作可以知道应该是A[0:3,0:3,0:3]与卷积核做点乘并相加,
//得到卷积后输出的特征F的第一个值 F[0,0]
//
//所以可以知道,为了方便进行卷积操作
//这里输出的每一列就代表一组与卷积核进行卷积操作的像素值,
//大小为一组卷积核参数总数:channels * ksize * ksize[点乘相加,每点对应]
//上述例子中,就应该为3*3*3=27行,因为每个输出特征值都是通过两组27个值
//点乘相加得到
//输出的是一维数组,所以做几次点乘相加的操作要用到的像素值
//直接按顺序往后面排就行,关键是知道,一次要取几个像素出来做点乘就行
//接下来做转换操作
//1.第一层循环,循环次数是一次点乘操作中,要用到的像素值数目
//也就是卷积的的参数总数
for (c = 0; c < channels_col; ++c) {
//以下 计算需要的像素分别在什么位置
//这里就要明确一点,做图像的大家都知道,在图像的一维数组中是按照如下
//顺序存放数据的[假设图像大小为w,h]:
//data_im[0:3w]...data_im[3w:6w]...data_im[3(h-1)w:3hw],
//一组为bgr的一行,按照b--data_im[0],g--data_im[1],r--data_im[2]的顺序放好。
//以下计算存放像素值对应的原始位置
//3*25*25的图片A遇上一组3*3*3的卷积核,stride=2时,得到的卷积后的特征应该为2*2
//且知道,一次卷积的点乘计算要用到27个像素,总共做4次卷积的点乘计算,
//所以,转换后的图像数组应该是27*4的大小
//图像转换后的数组第一列应该是:第一次做卷积点乘计算的27个像素值
/*
B--A[0,0,0],A[0,0,1],A[0,0,2],A[0,1,0],A[0,1,1],A[0,1,2],A[0,2,0],A[0,2,1],A[0,2,2]
G--A[1,0,0],A[1,0,1],A[1,0,2],A[1,1,0],A[1,1,1],A[1,1,2],A[1,2,0],A[1,2,1],A[1,2,2],
R--A[2,0,0],A[2,0,1],A[2,0,2],A[2,1,0],A[2,1,1],A[2,1,2],A[2,2,0],A[2,2,1],A[2,2,2]
*/
/*
对应data_img中的值:
B--data_img[0],data_img[3],data_img[6],data_img[15],data_img[18],data_img[21],data_img[30],data_img[33],data_img[36]
G--data_img[1],data_img[4],data_img[7],data_img[16],data_img[19],data_img[22],data_img[31],data_img[34],data_img[37]
R--data_img[2],data_img[5],data_img[8],data_img[17],data_img[20],data_img[23],data_img[32],data_img[35],data_img[38]
刚好一组27个像素点
*/
//图像转换后的数组第一行应该是:根据步长判定的所有与卷积核做点乘的第一个像素,
/*
A[0,0,0],A[0,0,2],A[0,2,0],A[0,2,2]
*/
/*
对应data_img中的值:
data_img[0],data_img[6],data_img[30],data_img[36]
F[0],F[1],F[2],F[3]
刚好是4次卷积点乘计算操作的第一个像素值
...以此类推,第二行
data_img[3],data_img[9],data_img[33],data_img[39]
F[4],F[5],F[6],F[7]
*/
int w_offset = c % ksize;//列偏移,w_offset的意义是改变c时,得到在卷积核上相应的列数
// 3*3 的卷积核(3通道),当c=0,显然在第一列,当c=5,显示在第3列,w_offset =2
//当c=9时,在第二通道的卷积核的第一列,w_offset =0
//当c=26,在第三列(第三通道),w_offset =2。
int h_offset = (c / ksize) % ksize;
//行偏移,h_offset的意义是改变c时,得到在卷积核上相应的行数
// 3*3 的卷积核(3通道),当c=0,显然在第一行,当c=5,显示在第二行,w_offset =1
//当c=9时,在第二通道的卷积核的第一行,w_offset =0
//当c=26,在第三第一行(第三通道),w_offset =2。
int c_im = c / ksize / ksize;
//对应通道数,c_im 的意义是改变c时,得到在卷积核上相应的通道数,也变相的是得到图像BGR层数
//第二层循环,循环次数是需要进行卷积的输入特征图(或是输入图像)的高值
for (h = 0; h < height_col; ++h) {
//第三层循环,循环次数是需要进行卷积的输入特征图(或是输入图像)的宽值
for (w = 0; w < width_col; ++w) {
//获得输入图像对应的像素位置
//由上面可知,对于3*3的卷积核,h_offset取值为0,1,2,对应卷积核的行
//当h_offset=0时,会提取出所有与卷积核第一行元素进行运算的像素,
//依次类推;加上h×stride是对卷积核进行行移位操作,
//比如卷积核从图像(0,0)位置开始做卷积,那么最先涉及(0,0)——(3,3)
//之间的像素值,若stride=2,那么卷积核进行一次行移位时,
//下一行的卷积操作是从元素(2, 0)(2为图像行号,0为列号)开始
int im_row = h_offset + h * stride;
// 对于3*3的卷积核,w_offset取值也为0,1,2,对应卷积核的列
// 当w_offset=1,会提取所有与卷积核中第2列元素进行运算的像素,
// 比如前一次卷积其实像素元素(0,0),若stride=2,
//那么下次卷积元素是从元素(2,0)(0为行号,2为列号)
int im_col = w_offset + w * stride;
// im_row,im_col 为按照卷积点乘计算顺序得到的对应像素宽高
/*
举个栗子,
3*25*25的图片A遇上一组3*3*3的卷积核,stride=2时,得到的卷积后的特征应该为2*2
且知道,一次卷积的点乘计算要用到27个像素,总共做4次卷积的点乘计算,
/所以,转换后的图像数组应该是27*4的大小
第一次卷积点乘计算时,c循环27次,分别对应卷积核点乘的27个位置
第一层循环的第一次循环c=0,也即col_c=0时【为了避免与通道数c混淆,这里用col_c代替循环次数c
col_c为卷积点乘计算的第c个点】,表示我现在要取出所有与卷积核第一通道第一行第一列的值进行点乘的像素值:
---------------------------------------------------------
此时计算有h_offset =0,w_offset =0,height_col=2,width_col =2
im_row=0+0*2 0+0*2 0+1*2 0+1*2
0 0 2 2
im_col=0+0*2 0+1*2 0+0*2 0+1*2
0 2 0 2
--------------------------------------------------------
分别对应:几何上,图像A中
A[0,0,0],A[0,0,2],A[0,2,0],A[0,2,2]
A[c,h,w]
---------------------------------------------------------
一维数组中data_img中:
im_index=(h*im_w+w)*3+c
im_index=(0*5+0)*3+0 (0*5+2)*3+0 (2*5+0)*3+0 (2*5+2)*3+0
0 6 30 36
data_img[0],data_img[6],data_img[30],data_img[36]
------------------------------------------------------------
转换矩阵中
col_index = (col_c*height_col+h)*width_col+w=(col_c*2+h)*2+w
col_index=(0*2+0)2+0 (0*2+0)2+1 (0*2+1)2+0 (0*2+1)2+1
F[0],F[1],F[2],F[3]
差不多就是这样,好啰嗦,我能看懂就行QAQ
*/
// col_index为重排后,该像素值在新图像中的像素索引。
//等于c * height_col * width_col * w(还是按行存储,所有通道在合并成一行)
// 对应第c通道,h行,w列元素
int col_index = (c * height_col + h) * width_col + w;
//赋像素值
data_col[col_index] = im2col_get_pixel(data_im, height, width, channels,im_row, im_col, c_im, pad);
}
}
}
}
// Function uses casting from int to unsigned to compare if value of
// parameter a is greater or equal to zero and lower than value of
// parameter b. The b parameter is of type signed and is always positive,
// therefore its value is always lower than 0x800... where casting
// negative value of a parameter converts it to value higher than 0x800...
// The casting allows to use one condition instead of two.
inline static int is_a_ge_zero_and_a_lt_b(int a, int b) {
return (unsigned)(a) < (unsigned)(b);
}
// https://github.com/BVLC/caffe/blob/master/src/caffe/util/im2col.cpp
void im2col_cpu_ext(const float* data_im, const int channels,
const int height, const int width, const int kernel_h, const int kernel_w,
const int pad_h, const int pad_w,
const int stride_h, const int stride_w,
const int dilation_h, const int dilation_w,
float* data_col)
{
const int output_h = (height + 2 * pad_h -
(dilation_h * (kernel_h - 1) + 1)) / stride_h + 1;
const int output_w = (width + 2 * pad_w -
(dilation_w * (kernel_w - 1) + 1)) / stride_w + 1;
const int channel_size = height * width;
int channel, kernel_row, kernel_col, output_rows, output_col;
for (channel = channels; channel--; data_im += channel_size) {
for (kernel_row = 0; kernel_row < kernel_h; kernel_row++) {
for (kernel_col = 0; kernel_col < kernel_w; kernel_col++) {
int input_row = -pad_h + kernel_row * dilation_h;
for (output_rows = output_h; output_rows; output_rows--) {
if (!is_a_ge_zero_and_a_lt_b(input_row, height)) {
for (output_col = output_w; output_col; output_col--) {
*(data_col++) = 0;
}
}
else {
int input_col = -pad_w + kernel_col * dilation_w;
for (output_col = output_w; output_col; output_col--) {
if (is_a_ge_zero_and_a_lt_b(input_col, width)) {
*(data_col++) = data_im[input_row * width + input_col];
}
else {
*(data_col++) = 0;
}
input_col += stride_w;
}
}
input_row += stride_h;
}
}
}
}
}