Yolo的卷积运算源码图解之im2col.c

yolo卷积源码解读找了好久,但网上好多的解读,有点误人子弟,
之前还找到一片注释写的比源代码还多几倍的,以为我就这样看懂了?
不存在的 QWQ,后来直接自己看懂了源码,却看不懂了注释

           无奈.jpg

于是有了下面的自己理解:

先放出源码:

#include "im2col.h"
#include <stdio.h>
/*
**  从输入的多通道数组im(存储图像数据)中获取指定行、列、、通道数处的元素值
**  输入: im      输入,所有数据存成一个一维数组,例如对于3通道的二维图像而言,
**                每一通道按行存储(每一通道所有行并成一行),三通道依次再并成一行
**        height  每一通道的高度(即输入图像的真正的高度,补0之前)
**        width   每一通道的宽度(即输入图像的宽度,补0之前)
**        channels 输入im的通道数,比如彩色图为3通道,之后每一卷积层的输入的通道数等于上一卷积层卷积核的个数
**        row     要提取的元素所在的行(二维图像补0之后的行数)
**        col     要提取的元素所在的列(二维图像补0之后的列数)
**        channel 要提取的元素所在的通道
**        pad     图像左右上下各补0的长度(四边补0的长度一样)
**  返回: float类型数据,为im中channel通道,row-pad行,col-pad列处的元素值
**  注意:在im中并没有存储补0的元素值,因此height,width都是没有补0时输入图像真正的
**       高、宽;而row与col则是补0之后,元素所在的行列,因此,要准确获取在im中的元素值,
**       首先需要减去pad以获取在im中真实的行列数
*/
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存储多通道二维图像的数据的格式为:各通道所有行并成一行,再多通道依次并成一行,
    // 因此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
/*
 将输入图片转为便于计算的数组格式,可以参考https://petewarden.com/2015/04/20/why-gemm-is-at-the-heart-of-deep-learning/
 进行辅助理解(但执行方式并不同,只是用于概念上的辅助理解),由作者的注释可知,这是直接从caffe移植过来的
 输入: data_im    输入图像
       channels   输入图像的通道数(对于第一层,一般是颜色图,3通道,中间层通道数为上一层卷积核个数)
       height     输入图像的高度(行)
       width      输入图像的宽度(列)
       ksize      卷积核尺寸
       stride     卷积核跨度
       pad        四周补0长度
       data_col   相当于输出,为进行格式重排后的输入图像数据

注:
   data_col还是按行排列,
       行数为channels*ksize*ksize,
       列数为height_col*width_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;
    // 卷积后的尺寸计算,这里width_col=width
    int height_col = (height + 2*pad - ksize) / stride + 1;
    int width_col = (width + 2*pad - ksize) / stride + 1;

    /// 卷积核大小:ksize*ksize是一个卷积核的大小,通道数channels
    int channels_col = channels * ksize * ksize;
    
  // 获取channels_col个对应像素核
    for (c = 0; c < channels_col; ++c) {
        // 卷积核上的坐标:(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) {
            // 内循环等于该层输出图像列数width_col,说明最终得到的data_col总有channels_col行,height_col*width_col列
            for (w = 0; w < width_col; ++w) {
 
                // 获取输入图像的对应像素坐标
                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);
            }
        }
    }
}

看不懂?,莫方,莫方,看例子:

例子:

假设有输入data_im和卷积核如下:


这里写图片描述

那么卷积核每次划过的像素:


这里写图片描述

可知相关参数:

height=4
width=4
channels=1//单通道
ksize=3
pad=1
stride=1 //这里假设为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

那么接下来怎么运算呢?
c==1:
先获取输入(加了pading的)的第一行前4个数:

0 0 0 0

想下移动Stride行(这里例子取1),再取四位,即:

0 1 2 3

按上操作,往下移,取值:

0 5 6 7
0 9 10 11

最终得到data_col的第一行所有像素值
c不断自加,循环,最后得到data_col像素分布,最终结果如下:

这里写图片描述

最终的卷积运算:

由上知:
卷积核的ksize=3,展开后形状为channelsx(ksizexksize), 即1x9,
data_col形状为channels_colx(height_colxheight_col),即9x16,

所以最终yolo会在通过convolutional_layer.c里的forward_convolutional_layer函数里的gemm函数计算卷积核l.weights和data_col的矩阵乘积,完成卷积操作

好绕~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容