e * ConvH;
int X, Y, IndexD, IndexE, IndexK, ExpStride;
float *CurKer, Inv, Sum = 0;
unsigned char *PtExp, *PtDest;
TImage *Expand;
IS_RET Ret = GetPadImage(Src, &Expand, Left, Right, Top, Bottom, Edge); // 得到扩展后的数据,可以提速和方便编程,但是多占用一份内存
if (Ret != IS_RET_OK) return Ret;
PtExp = Expand->Scan0; PtDest = Dest->Scan0; ExpStride = Expand->Stride;
for (X = 0; X < ConvH * ConvW; X ++) Sum += Conv->Data.F[X];
Inv = (Sum == 0 ? 1: 1 / Sum); // 如果卷积举证的和为0,则设置为1
float *Conv16 = (float *)_mm_malloc(PadConvLine * ConvH * sizeof(float), 16); // 保存16字节对齐的卷积矩阵,以方便使用SSE
float *Kernel = (float *)_mm_malloc(PadConvLine * ExpHeight * sizeof(float), 16); // 保存16字节对齐的卷积核矩阵,以方便使用SSE
for(Y = 0; Y < ConvH; Y++)
{
memcpy (Conv16 + Y * PadConvLine, Conv->Data.F + Y * ConvW , ConvW * sizeof(float)); // 复制卷积矩阵的数据
memset(Conv16 + Y * PadConvLine + ConvW, 0, (PadConvLine - ConvW) * sizeof(float)); // 把冗余部分的卷积数据设置为0
}
for (Y = 0; Y < ExpHeight; Y++)
{
IndexE = Y * ExpStride;
CurKer = Kernel + Y * PadConvLine; // 计算第一列所有像素将要取样的卷积核数据
for (X = 0; X < ConvW; X++)
{
CurKer[X] = PtExp[IndexE++];
}
}
for (X = 0 ; X < Width ; X ++)
{
if (X != 0) // 如果不是第一列,需要更新卷积核的数据
{
memcpy(Kernel, Kernel + 1, (PadConvLine * ExpHeight - 1) * sizeof(float)); // 往前移动一个数据
IndexK = ConvW - 1 ;
IndexE = IndexK + X;
for (Y = 0; Y < ExpHeight; Y++)
{
Kernel[IndexK] = PtExp[IndexE]; // 只要刷新下一个元素
IndexK += PadConvLine;
IndexE += ExpStride;
}
}
CurKer = Kernel; IndexD = X;
for (Y = 0; Y < Height; Y ++) // 沿列的方向进行更新
{
PtDest[IndexD] = Clamp((int)( MultiplySSE(Conv16, CurKer, Length) * Inv + 0.5)); // 直接把函数放在这里也没有啥提速的,注意改函数不会被内联的
CurKer += PadConvLine;
IndexD += Stride;
}
}
_mm_free(Conv16);
_mm_free(Kernel);
FreeImage(Expand);
return IS_RET_OK;
}
}
对于第一个问题,解决的方式很简答,即用空间换时间,新建一副(Width + ConvW - 1, Height + ConvH -1)大小的图像,然后四周的ConvW及ConvH的像素用边缘的值或者边缘镜像的值填充,正中间的则用原来的图复制过来,这样操作后进行取样时不再原图取样,而在这福扩展的图中取样,就避免了坐标判断等if语句的跳转耗时了,上GetPadImage即实现了改功能。
第二个问题则需要有一定的实现技巧,我们分配一块PadConvLine * (Height + ConvH - 1) 大小的内存,然后计算原图第一列像素串联起来的需要卷积的部分的数据,这一部分代码如上述44-52行所示。有了这样的数据,如果需要计算第一列的卷积结果,则很简单了,每跳过一列则把被卷积的数据起点增加PadConvLine个元素,在调用上述MultiplySSE函数获得卷积结果。接着则计算第二列像素的卷积值,此时需要整体更新这一列像素串联起来的需要被卷积的数据,更新也很简单,就是把原来的数据整体向左移动一个像素,这个可以用memcpy快速实现,然后在填充入新进来的那个元素,就ok了,接着就是再次调用MultiplySSE函数,如此重复下去。
经过编码测试,对于3000*3000的灰度图,15*15的核在I5的CPU上的测试平均结果为360ms,比matlab的慢了一半。
最后说明一点,很多人都说用FFT可以快速的实现卷积,并且是O(1)的,我比较同意后半句,但是前面半句是绝对的有问题的,至少在核小于50*50时,FFT实现的卷积不会比直接实现块。要知道FFT的计算量其实是很大的。
****************************基本上我不提供源代码,但是我会尽量用文字把对应的算法描述清楚或提供参考文档************************
*************************************因为靠自己的努力和实践写出来的效果才真正是自己的东西,人一定要靠自己*******************
****************************作者: laviewpbt 时间: 2014.11.27 联系QQ: 33184777 转载请保留本行信息**********************