yolo卷積源碼解讀找了好久,但網(wǎng)上好多的解讀,有點誤人子弟,
之前還找到一片注釋寫的比源代碼還多幾倍的,以為我就這樣看懂了?
不存在的 QWQ,后來直接自己看懂了源碼,卻看不懂了注釋
無奈.jpg
于是有了下面的自己理解:
先放出源碼:
#include "im2col.h"
#include <stdio.h>
/*
** 從輸入的多通道數(shù)組im(存儲圖像數(shù)據(jù))中獲取指定行、列、、通道數(shù)處的元素值
** 輸入: im 輸入,所有數(shù)據(jù)存成一個一維數(shù)組,例如對于3通道的二維圖像而言,
** 每一通道按行存儲(每一通道所有行并成一行),三通道依次再并成一行
** height 每一通道的高度(即輸入圖像的真正的高度,補0之前)
** width 每一通道的寬度(即輸入圖像的寬度,補0之前)
** channels 輸入im的通道數(shù),比如彩色圖為3通道,之后每一卷積層的輸入的通道數(shù)等于上一卷積層卷積核的個數(shù)
** row 要提取的元素所在的行(二維圖像補0之后的行數(shù))
** col 要提取的元素所在的列(二維圖像補0之后的列數(shù))
** channel 要提取的元素所在的通道
** pad 圖像左右上下各補0的長度(四邊補0的長度一樣)
** 返回: float類型數(shù)據(jù),為im中channel通道,row-pad行,col-pad列處的元素值
** 注意:在im中并沒有存儲補0的元素值,因此height,width都是沒有補0時輸入圖像真正的
** 高、寬;而row與col則是補0之后,元素所在的行列,因此,要準(zhǔn)確獲取在im中的元素值,
** 首先需要減去pad以獲取在im中真實的行列數(shù)
*/
float im2col_get_pixel(float *im, int height, int width, int channels,
int row, int col, int channel, int pad)
{
// 減去補0長度,獲取元素真實的行列數(shù)
row -= pad;
col -= pad;
// 如果行列數(shù)小于0,則返回0(剛好是補0的效果)
if (row < 0 || col < 0 ||
row >= height || col >= width) return 0;
// im存儲多通道二維圖像的數(shù)據(jù)的格式為:各通道所有行并成一行,再多通道依次并成一行,
// 因此width*height*channel首先移位到所在通道的起點位置,加上width*row移位到
// 所在指定通道所在行,再加上col移位到所在列
// im[col + width*(row + height*channel)]=im[col+width*row+width*height*channel]
return im[col + width*(row + height*channel)];
}
//From Berkeley Vision's Caffe!
//https://github.com/BVLC/caffe/blob/master/LICENSE
/*
將輸入圖片轉(zhuǎn)為便于計算的數(shù)組格式,可以參考https://petewarden.com/2015/04/20/why-gemm-is-at-the-heart-of-deep-learning/
進行輔助理解(但執(zhí)行方式并不同,只是用于概念上的輔助理解),由作者的注釋可知,這是直接從caffe移植過來的
輸入: data_im 輸入圖像
channels 輸入圖像的通道數(shù)(對于第一層,一般是顏色圖,3通道,中間層通道數(shù)為上一層卷積核個數(shù))
height 輸入圖像的高度(行)
width 輸入圖像的寬度(列)
ksize 卷積核尺寸
stride 卷積核跨度
pad 四周補0長度
data_col 相當(dāng)于輸出,為進行格式重排后的輸入圖像數(shù)據(jù)
注:
data_col還是按行排列,
行數(shù)為channels*ksize*ksize,
列數(shù)為height_col*width_col,即一張?zhí)卣鲌D總的元素個數(shù),
*/
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;
// 卷積后的尺寸計算,這里width_col=width
int height_col = (height + 2*pad - ksize) / stride + 1;
int width_col = (width + 2*pad - ksize) / stride + 1;
/// 卷積核大?。簁size*ksize是一個卷積核的大小,通道數(shù)channels
int channels_col = channels * ksize * ksize;
// 獲取channels_col個對應(yīng)像素核
for (c = 0; c < channels_col; ++c) {
// 卷積核上的坐標(biāo):(w_offset,h_offset)
int w_offset = c % ksize;
int h_offset = (c / ksize) % ksize;
int c_im = c / ksize / ksize;
for (h = 0; h < height_col; ++h) {
// 內(nèi)循環(huán)等于該層輸出圖像列數(shù)width_col,說明最終得到的data_col總有channels_col行,height_col*width_col列
for (w = 0; w < width_col; ++w) {
// 獲取輸入圖像的對應(yīng)像素坐標(biāo)
int im_row = h_offset + h * stride;
int im_col = w_offset + w * stride;
// col_index為重排后圖像中的像素索引,等于c * height_col * width_col + h * width_col +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);
}
}
}
}
看不懂?,莫方,莫方,看例子:
例子:
假設(shè)有輸入data_im和卷積核如下:
這里寫圖片描述
那么卷積核每次劃過的像素:
這里寫圖片描述
可知相關(guān)參數(shù):
height=4
width=4
channels=1//單通道
ksize=3
pad=1
stride=1 //這里假設(shè)為1
所以:
height_col = (height + 2*pad - ksize) / stride + 1=4
width_col = (width + 2*pad - ksize) / stride + 1=4
channels_col = channels * ksize * ksize=9
那么接下來怎么運算呢?
當(dāng)c==1:
先獲取輸入(加了pading的)的第一行前4個數(shù):
0 0 0 0
想下移動Stride行(這里例子取1),再取四位,即:
0 1 2 3
按上操作,往下移,取值:
0 5 6 7
0 9 10 11
最終得到data_col的第一行所有像素值
c不斷自加,循環(huán),最后得到data_col像素分布,最終結(jié)果如下:
這里寫圖片描述
最終的卷積運算:
由上知:
卷積核的ksize=3,展開后形狀為channelsx(ksizexksize), 即1x9,
而data_col形狀為channels_colx(height_colxheight_col),即9x16,
所以最終yolo會在通過convolutional_layer.c里的forward_convolutional_layer函數(shù)里的gemm函數(shù)計算卷積核l.weights和data_col的矩陣乘積,完成卷積操作
好繞~