im2col的原理和实现

1、矩阵卷积

卷积就是卷积核跟图像矩阵的运算。卷积核是一个小窗口,记录的是权重。卷积核在输入图像上按步长滑动,每次操作卷积核对应区域的输入图像,将卷积核中的权值和对应的输入图像的值相乘再相加,赋给卷积核中心所对应的输出特征图的一个值,如下图所示(这里卷积核要旋转180 °):

2、im2col的实现

2.1 im2col有什么作用

以上我们已经知道了卷积是如何操作的,im2col的作用就是优化卷积运算,如何优化呢,我们先学习一下这个函数的原理。
我们假设卷积核的尺寸为2*2,输入图像尺寸为3*3.im2col做的事情就是对于卷积核每一次要处理的小窗,将其展开到新矩阵的一行(列),新矩阵的列(行)数,就是对于一副输入图像,卷积运算的次数(卷积核滑动的次数),如下图所示:

以最右侧一列为例,卷积核为2*2,所以新矩阵的列数就为4;步长为一,卷积核共滑动4次,行数就为4.再放一张图应该看得更清楚。

输入为4*4,卷积核为3*3,则新矩阵为9*4 。看到这里我就产生了一个疑问:我们把一个卷积核对应的值展开,到底应该展开为行还是列呢?卷积核的滑动先行后列还是相反?区别在哪?
这其实主要取决于我们使用的框架访存的方式。 计算机一次性读取相近的内存是最快的,尤其是当需要把数据送到GPU去计算的时候,这样可以节省访存的时间,以达到加速的目的。不同框架的访存机制不一样,所以会有行列相反这样的区别。在caffe框架下,im2col是将一个小窗的值展开为一行,而在matlab中则展开为列。所以说,行列的问题没有本质区别,目的都是为了在计算时读取连续的内存。
这也解释了我们为什么要通过这个变化来优化卷积。如果按照数学上的步骤做卷积读取内存是不连续的,这样就会增加时间成本。同时我们注意到做卷积对应元素相乘再相加的做法跟向量内积很相似,所以通过im2col将矩阵卷积转化为矩阵乘法来实现。

2.2 一个简单实现

基于以上对于im2col的理解,自己写了一个简单实现(c++ opencv3.2)。
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main()
{
	Mat img =imread("1.jpg");
	Mat kernel=Mat::ones(9,9,CV_8UC1);
	int stride=5;
	int kernum_w,kernum_h;
	if(img.rows%stride==0)
		kernum_w=img.rows/stride;
	else
		kernum_w=img.rows/stride+1;
	if(img.cols%stride==0)
		kernum_h=img.cols/stride;
	else
		kernum_h=img.cols/stride+1;
	int out_w=kernum_w*kernum_h;
	int out_h=kernel.cols*kernel.rows;
	int channel_size=img.cols*img.rows;
	//初始化输入、卷积核并计算新矩阵的尺寸
	Mat C=Mat::zeros(out_w,out_h,CV_8UC3);

	for (int channel=img.channels();channel--;channel>0)
	{	
		int i=0;
		for (int im_row=0;im_row<img.rows;im_row+=stride)
		{
		for (int im_col=0;im_col<img.cols;im_col+=stride)
		{	
			
			for (int ker_row=0;ker_row<kernel.rows;ker_row++)
			{
			for(int ker_col=0;ker_col<kernel.cols;ker_col++)
				{
				int input_row=im_row-(kernel.rows-1)/2+ker_row;
				int input_col=im_col-(kernel.cols-1)/2+ker_col;
				//索引当前小窗内元素对应到输入的行列值
				if(input_row<0||input_col<0||input_row>=img.rows||input_col>=img.cols)
					C.at<Vec3b>(i,ker_col+kernel.cols*ker_row)[channel]=0;  
				else    
					C.at<Vec3b>(i,ker_col+kernel.cols*ker_row)[channel]=img.at<Vec3b>(input_row,input_col)[channel];
				//超出原图的范围,则新矩阵对应的位置赋零,否则将输入赋给对应的位置
				}
			}
			i++;
		}
		}	
	}
	imshow("im2col",C);
	waitKey(0);
	return 0;
}
对于c++我也只是初学者,关于指针,我还要多加学习,这里对图像的遍历使用了Mat类的成员函数at(),也是一种比较低效率的方法。高效的方法(指针方法)参考这篇 http://blog.csdn.net/daoqinglin/article/details/23628125

参考文献
http://lib.csdn.net/article/aiframework/62849(这里有caffe中的源码解读)

  • 79
    点赞
  • 180
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
使用im2col实现卷积操作可以将输入图像转换为一个二维矩阵,然后利用矩阵乘法进行卷积计算。下面是使用im2col实现卷积的步骤: 1. 定义输入图像的大小和卷积核的大小。假设输入图像的大小为(H, W, C),其中H为高度,W为宽度,C为通道数;卷积核的大小为(KH, KW, C, F),其中KH为卷积核的高度,KW为卷积核的宽度,C为输入通道数,F为输出通道数。 2. 将输入图像转换为im2col矩阵。im2col矩阵的每一列对应于卷积核在输入图像上的一个局部区域。具体实现时,需要定义一个滑动窗口,通过滑动窗口在输入图像上遍历,并将每个窗口内的像素值按列排列到im2col矩阵中。 3. 将卷积核展开为一个二维矩阵。将卷积核的每个通道展开成一列,并将所有通道按行排列成一个二维矩阵。 4. 将im2col矩阵与卷积核矩阵进行矩阵乘法。将im2col矩阵和卷积核矩阵相乘,得到一个结果矩阵,其中每一列对应于卷积核在输入图像上的一个局部区域的卷积结果。 5. 将结果矩阵转换为输出图像。将结果矩阵重新排列为输出图像的形状,即(H', W', F),其中H'为输出图像的高度,W'为输出图像的宽度,F为输出通道数。 这样就得到了卷积操作的输出图像结果。需要注意的是,对于多通道的输入图像和卷积核,需要对每个通道分别进行卷积操作,并将结果进行叠加得到最终的输出图像。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值