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

作者: 蜜丝特潮 | 来源:发表于2018-05-12 12:17 被阅读38次

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

    好绕~

    相关文章

      网友评论

        本文标题:Yolo的卷积运算源码图解之im2col.c

        本文链接:https://www.haomeiwen.com/subject/yzwcdftx.html