当前位置: 首页 > news >正文

北京做网站的大公司/网站信息

北京做网站的大公司,网站信息,好的网站建设启示,软件开发平台方案场景:图像处理中随便核卷积(matlab中conv2函数)的快速实现图像处理中任意核卷积(matlab中conv2函数)的快速实现。卷积其实是图像处理中最基本的操作,我们常见的一些算法比如:均值模糊、高斯模糊、锐化、Sobel、拉普拉斯、prewitt边缘检测等等…

场景:图像处理中随便核卷积(matlab中conv2函数)的快速实现

图像处理中任意核卷积(matlab中conv2函数)的快速实现。

卷积其实是图像处理中最基本的操作,我们常见的一些算法比如:均值模糊、高斯模糊、锐化、Sobel、拉普拉斯、prewitt边缘检测等等一些和领域相关的算法,都可以通过卷积算法实现。只不过由于这些算法的卷积矩阵的特殊性,一般不会直接实现它,而是通过一些优化的手段让计算量变小。但是有些情况下卷积矩阵的元素值无甚规律或者有特殊要求,无法通过常规手段优化,这个时候只能通过原始的方式实现。因此,如何快速的实现图像的任意卷积矩阵操作也有必要做适当的研究。

目前,通过友人共享或自己搜索找到的一片关于任意核算法优化的文章有: Reshuffling: A Fast Algorithm for Filtering with Arbitrary Kernels,改文章称能够提高原始程序速度的40%左右,但是原始的程序是如何写的也还不明白。

在matlab中有几个函数都与图像卷积有关,比如imfilter就可以实现卷积,或者 conv2也行,他们的速度都是相当快的,比如3000*3000的灰度图,卷积矩阵大小为15*15,在I5的CPU上运行时间只要170ms左右,相当的给力。

由于matlab的代码中使用到了IPL库进行加速,目前我写的Conv2函数还无法做到和其相当,对于任何核速度约为matlab的一半。

简单的记录下我在做卷积过程中用到的优化吧。

原始的卷积的实现需要四重循环,简单的表达如下:

for (Y = 0; Y < Height; Y++)

{for (X = 0; X < Width; X++)

{

Index=.....;

Sum= 0;for (XX = 0; XX < ConvW; XX++)

{for (YY = 0; YY < ConvH; YY++)

{

Index1=..... ;

Index2=..... ;

Sum+= Conv[Index1] *Pixel[Index2];

}

}

Dest[Index]= Sum /Weight;

}

}

当卷积矩阵较大时,计算量将会很大,而且由于程序中的内存访问很频繁,cache miss现象比较严重,因此效率极为低下。

我的优化方法主要包括以下几个方面:

一:使用SSE进行乘法计算,由于SSE可以一次性进行4个单精度浮点数的计算,因此可以有明显的速度提升。

二:通过适当的处理方式,对每个取样点周边的卷积矩阵内的元素进行集中,使得每移动一个像素点不会需要从内存中进行大量的搜索工作。

具体来说实现过程如下:

1、为了使用SSE的优势,首先将卷积矩阵进行调整,调整卷积矩阵一行的元素个数,使其为不小于原始值的4的整数倍,并且让新的卷积矩阵的内存布局符合SSE相关函数的16字节对齐的要求。

实现代码如下:

float *Conv16 = (float *)_mm_malloc(PadConvLine * ConvH * 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

}

其中PadConvLine = Pad4(ConvW) 以及Pad4的原型为: #define Pad4(bits) (((bits) + 3) / 4 * 4);

注意_mm_malloc函数分配的内存中的值是随机值,对于扩展的部分一定要填充0,否则就会破坏卷积的结果。

那么如果我们也同时获得了需要被卷积的部分数据的话(卷积核肯定和卷积矩阵一样大小,且也应该是16字节对齐的),可以用如下的SSE的代码进行乘法计算:

float MultiplySSE(float *Kernel, float *Conv, intLength)

{intBlock;const float *Data; //将SSE变量上的多个数值合并时所用指针.

float Sum = 0;if (Length > 16) //可以进行四次SSE计算,测试表明,这个还是快些的

{const int BlockWidth = 4 * 4; //块宽. SSE寄存器能一次处理4个float,然后循环展开4次.

Block = Length / BlockWidth; //块数.

float *KernelP = Kernel, *ConvP = Conv; //SSE批量处理时所用的指针.

__m128 Sum0= _mm_setzero_ps(); //求和变量。SSE赋初值0

__m128 Sum1 =_mm_setzero_ps();

__m128 Sum2=_mm_setzero_ps();

__m128 Sum3=_mm_setzero_ps();for(int I = 0; I < Block; I++)

{

Sum0= _mm_add_ps(Sum0, _mm_mul_ps(_mm_load_ps(KernelP), _mm_load_ps(ConvP))); //SSE单精浮点紧缩加法

Sum1 = _mm_add_ps(Sum1, _mm_mul_ps(_mm_load_ps(KernelP + 4), _mm_load_ps(ConvP + 4)));

Sum2= _mm_add_ps(Sum2, _mm_mul_ps(_mm_load_ps(KernelP + 8), _mm_load_ps(ConvP + 8)));

Sum3= _mm_add_ps(Sum3, _mm_mul_ps(_mm_load_ps(KernelP + 12), _mm_load_ps(ConvP + 12)));

KernelP+=BlockWidth;

ConvP+=BlockWidth;

}

Sum0= _mm_add_ps(Sum0, Sum1); //两两合并(0~1).

Sum2 = _mm_add_ps(Sum2, Sum3); //两两合并(2~3).

Sum0 = _mm_add_ps(Sum0, Sum2); //两两合并(0~2).

Data= (const float *)&Sum0;

Sum= Data[0] + Data[1] + Data[2] + Data[3];

Length= Length - Block * BlockWidth; //剩余数量.

}if (Length != 0)

{const int BlockWidth = 4; //程序已经保证了数量必然是4的倍数

Block = Length /BlockWidth;float *KernelP = Kernel, *ConvP =Conv;

__m128 Sum0=_mm_setzero_ps();for(int I = 0; I < Block; I++)

{

Sum0=_mm_add_ps(Sum0, _mm_mul_ps(_mm_load_ps(KernelP), _mm_load_ps(ConvP)));

KernelP+=BlockWidth;

ConvP+=BlockWidth;

}

Data= (const float *)&Sum0;

Sum+= Data[0] + Data[1] + Data[2] + Data[3];

}returnSum;

}

当卷积矩阵(扩充后)的元素数量大于16时,我们采用了4路并行的SSE乘法实现,我在I3的CPU上测试时,2路SSE和4路SSE已经没有啥大的区别了,而在I5的CPU上则4路还是有较为明显的提高,因此采用4路SSE同时运行。当然1路SSE肯定还是比2路慢。另外,如果元素的数量少于16或者大于16但不能被16整除,那么余下的部分由于先前的扩充,剩余元素数量也肯定是4的倍数,因此可以用单路的SSE实现。 这也是编码上的技巧。

2、前面提到了需要被卷积的部分数据,这部分如何快速的获取呢。观察最原始的4重循环,其内部的2重即为获取需要被卷积的部分,但是这里其实有很多问题。第一:由于卷积取样时必然有部分取样点的坐标在原始图像的有效范围外,因此必须进行判断,耗时。第二:同样为了使用SSE,也必须把取样的数据放在和扩充的卷积矩阵一样大小的内存中。这里我先贴出我的代码在进行解释具体的实现:

IS_RET __stdcall Conv2(TImage *Src, TMatrix *Conv, TImage *Dest, EdgeMode Edge)

{if (Src == NULL || Dest == NULL || Conv == NULL) returnIS_RET_ERR_PARA;if (Src->Width != Dest->Width || Src->Height != Dest->Height || Src->BitCount != Dest->BitCount || Src->Stride != Dest->Stride) returnIS_RET_ERR_PARA;if (Src->Scan0 == NULL || Dest->Scan0 == NULL || Conv->Data.F == NULL) returnIS_RET_ERR_MEM;if (Conv->Width < 1 || Conv->Height < 1) returnIS_RET_ERR_PARA;int Width = Src->Width, Height = Src->Height, Stride = Src->Stride;int ConvW = Conv->Width, ConvH = Conv->Height;

unsignedchar *PtSrc = Src->Scan0, *PtDest = Dest->Scan0;if (Src->BitCount == 24)

{

}else{int Left = ConvW / 2, Top = ConvH / 2, Right = ConvW - Left - 1, Bottom = ConvH - Top - 1, ExpHeight = Height + ConvH - 1; //注意核中心那个元素不用扩展,比如核的宽度为3,则只要左右各扩展一个像素就可以了

int PadConvLine = Pad4(ConvW), Length = PadConvLine *ConvH;intX, Y, IndexD, IndexE, IndexK, ExpStride;float *CurKer, Inv, Sum = 0;

unsignedchar *PtExp, *PtDest;

TImage*Expand;

IS_RET Ret= GetPadImage(Src, &Expand, Left, Right, Top, Bottom, Edge); //得到扩展后的数据,可以提速和方便编程,但是多占用一份内存

if (Ret != IS_RET_OK) returnRet;

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);returnIS_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 转载请保留本行信息**********************

Re: Imageshop@AlgorithmC,如果你自己用FFT变换坐下你就知道你的说法可不可靠了。3楼midu顶!2楼wdx04GPU Pro 5上面有篇《Non-Separable 2D, 3D, and 4D Filtering with CUDA》讲到在GPU上用FFT做不可分离卷积,在17x17以上的核才相比直接实现有优势。我用C++ AMP写的FFT卷积对3000x3000灰度图和15x15的核,在HD7850(默频860MHz)上跑大约花21.2ms,如果缓存卷积核则只需14.8ms。1楼the yesterday楼主,我按着上面的思路写了,和OpenCV里面的滤波器比了一下,很慢,而且达不到楼主说的 3000*3000的灰度图,15*15的核在I5的CPU上的测试平均结果为360ms,不知道为什么

http://www.lbrq.cn/news/747991.html

相关文章:

  • 做网站赚钱/企业推广网站有哪些
  • 搜索网站建设推广优化/seo快速排名服务
  • 新乡市做网站直销系统网站/东莞网站制作外包
  • 五常市网站/专业做网站建设的公司
  • 大学网站建设招标方案/seo官网优化怎么做
  • 凡科建站小程序制作/怎么seo网站排名
  • 长沙做网站seo/中铁建设集团有限公司
  • 学校网站网站建设/seo推广有哪些
  • 一个企业是如何做网站建设的/技能培训学校
  • 怎么做网站交易/摘抄一则新闻
  • 网上接单做衣服哪个网站/品牌运营
  • wordpress腾讯云邮件发送/seo优化技术培训
  • 兼职做效果图设计到哪个网站找/优化seo软件
  • 最新的即时比分/沈阳seo网站关键词优化
  • 网站建设项目描述/seo如何优化网站推广
  • 做哪个视频网站赚钱/软文兼职10元一篇
  • 工行网站为何做的那么垃圾/个人网站的制作
  • 建筑招聘网官网/seo包年优化
  • 网站按照规模分为哪几类/百度手机助手网页
  • 常熟网站建设/厦门网页搜索排名提升
  • 菏泽县建设局网站/手机端搜索引擎排名
  • 域名服务商网站/免费建站平台
  • 福永镇网站建设/杭州seo外包
  • 平台营销策略/seo经典案例分析
  • 网页设计网站开发需要哪些知识/哪些平台可以免费打广告
  • 湖南长沙益阳网站建设/如何找做网站的公司
  • 专业返利网站建设/免费加精准客源
  • 小男生和大人做av网站大全/软文广告范文
  • 网站测试页面怎么做/软文有哪些
  • 网站建设免费代理/举三个成功的新媒体营销案例
  • 数据结构-栈和队列
  • AI应用商业化加速落地 2025智能体爆发与端侧创新成增长引擎
  • 冒泡排序——简单理解和使用
  • 说一下事件委托
  • (nice!!!)(LeetCode 每日一题) 837. 新 21 点 (动态规划、数学)
  • 6个日常工作中常用的工作法:清单工作法、PDCA循环、SMART原则、6W2H 分析法等方法