Yolo的卷積運算源碼圖解之im2col.c

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的矩陣乘積,完成卷積操作

好繞~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容