前言在上一篇文章中,我们解释了图像的虚拟边缘,这篇文章开始平滑(即模糊)。这里的基本原理直接参考OpenCV 2.4 C平滑和OpenCV 2.4 C边缘梯度计算的相关内容:平滑也叫模糊,是一种简单常用的图像处理方法。平滑需要一个滤波器。最常用的过滤器是线性过滤器,用于过滤输出像素值(例如:)。
)是输入像素值(例如:。
的加权平均值):。
叫做核,只是一个加权系数。这涉及到一个叫做卷积的运算,那么什么是卷积呢?卷积是每个图像块和一个算子(核)之间的运算。核能?nbsp。DS内核是一个固定大小的数字数组。数组有一个锚点,通常位于数组的中心。
但是这是如何计算的呢?如果想要得到图像特定位置的卷积值,可以通过以下方法计算:将核的锚点放在特定位置的像素上,同时核中的其他值与像素邻域中的每个像素重合;将内核中的每个值与对应的像素值相乘,并将乘积相加;将结果放在锚点对应的像素上;对图像的所有像素重复上述过程。上述过程用公式表示如下:。
图像边缘的卷积呢?在卷积计算之前,需要通过复制源图像的边界来创建虚拟像素,这样边缘就有足够的像素来计算卷积。这就是为什么我们在上一篇文章中需要做虚边函数。均值平滑均值平滑实际上是一种卷积运算,所有的核元素都是1,然后除以核的大小,用数学表达式表示:。
我们来实现均值平滑函数blur:复制代码如下:函数blur(_ src,_ _ size1,_ _ size2,_ _ bordertype,_ _ dst){ if(_ src . type _ _ src . type==' CV_RGBA '){ var height=_ _ src . row,width=__src.col,dst=__dst || new Mat(height,width,CV _ RGBA),dstData=dst.datavar size1=__size1 || 3,size2=__size2 || size1,size=size1 * size2if(size1 % 2!==1 || size2 % 2!==1){ console.error('大小必须是奇数');return _ _ src} var startX=Math.floor(size1/2),startY=math . floor(size 2/2);var with border mat=copymakedborder(_ src,startY,startX,0,0,__borderType),mData=withBorderMat.data,mWidth=with border mat . col;var newValue,nowX,offsection,offsetIvar i,j,c,y,x;for(i=高度;I-;){ offsetI=i *宽度;for(j=宽度;j-;){ for(c=3;c-;){ NewValue=0;for(y=size 2;y-;){ offsecty=(y I)* mWidth * 4;for(x=size 1;x-;){ NoWx=(x j)* 4c;new value=MData[offsection NoWx];} } DsDATa[(j offsetI)* 4 c]=NewVaLue/size;} dst data[(j offset ti)* 4 3]=mData[offset startY * mWidth * 4(j startX)* 4 3];}} }else{ console.error('类型不受支持。);}返回dst}其中size1和size2是核心的水平和垂直尺寸,并且必须是正奇数。高斯平滑是最有用的滤波器(虽然不是最快的)。高斯滤波是用高斯核对输入阵列中的每个像素进行卷积,并将卷积和作为输出像素值。
参考一维高斯函数,我们可以看到它是一个中心大、边小的函数。因此,高斯滤波器的权重中间大,周围小。它的二维高斯函数是:。
其中
是平均值(峰值对应位置),。
表示标准差(变量)。
和变量。
各有一个均值,也各有一个标准差)。这里参考OpenCV的实现,不过应该还有优化空间,因为还没用到分离滤波器。首先我们做一个getGaussianKernel来返回高斯滤波器的一维数组。复制代码代码如下:函数getGaussianKernel(_ n,_ _ sigma){ var SMALL _ GAUSSIAN _ SIZE=7,smallGaussianTab=[[1],[0.25,0.5,0.25],[0.0625,0.25,0.375,0.25,0.0625],[0.03125,0.109375,0.21875,0.28125,0.2:smallGaussianTab[_ _ n 1]: 0;var sigmaX=__sigma 0?_ _ sigma :((_ _ n-1)* 0.5-1)* 0.3 0.8,scale2X=-0.5/(sigmaX * sigmaX),sum=0;var i,x,t,kernel=[];for(I=0;I _ _ n;I){ x=I-(_ _ n-1)* 0.5;t=fixedKernel?修正了内核[I]:数学。exp(Scale2x * x * x);内核[I]=t;总和=t;}总和=1 /总和;for(I=_ _ n;我-;){内核[I]*=sum;}返回内核;};然后通过两个这个一维数组,便可以计算出一个完整的高斯内核,再利用虚化里面用到的循环方法,就可以算出高斯平滑后的矩阵了。复制代码代码如下:函数gaussianbull(_ src,__size1,__size2,__sigma1,__sigma2,__borderType,_ _ dst){ if(_ src。键入_ _ src。type=' CV _ RGBA '){ var height=_ _ src。row,width=__src.col,dst=__dst || new Mat(高度、宽度、CV _ RGBA),dst data=dst . datavar sigma 1=_ _ sigma 1 | | 0,sigma 2=_ _ sigma 2 | | _ _ sigma 1 var size 1=_ _ size 1 | | math。round(sigma 1 * 6 1)| 1,size2=_ _ size2 | | math。round(sigma 2 * 6 1)| 1,size=size1 * size2if(size1 % 2!==1 || size2 % 2!==1){ console.error('size必须是奇数。');return _ _ src } var startX=Math . floor(size 1/2),startY=math。地板(尺寸2/2);var带边框mat=copymakedborder(_ src,startY,startX,0,0,__borderType),mData=withBorderMat.data,mWidth=带边框mat。colvar内核1=getGaussianKernel(大小1,sigma1),kernel2,内核=new Array(大小1 *大小2);if(size 1===size 21===2)内核2=内核1;else内核2=GetGaussiankernel(size 2,sigma 2);var i,j,c,y,x;for(I=内核2。长度;我-;){ for(j=内核1。长度;j-;){ kernel[I * size 1j]=kernel 2[I]* kernel 1[j];} } var newValue,nowX,offsetY,offset for(I=高度;我-;){ offsetI=i *宽度;对于(j=宽度;j-;){ for(c=3;c-;){ NewValue=0;for(y=尺寸2;y-;){ offsecty=(y I)* mWidth * 4;for(x=size 1;x-;){ NoWx=(x j)* 4c;新值=(mData[offset nowX]*内核[y * size 1x]);} } DSDATa[(j offsetI)* 4 c]=NewValue;} dst数据[(j offset ti)* 4 ^ 3]=mData[offset startY * mWidth * 4(j startX)* 4 ^ 3];} } }else{ console.error('不支持的类型');}返回dst}中值平滑中值滤波将图像的每个像素用邻域(以当前像素为中心的正方形区域)像素的中值代替。依然使用虚化里面用到的循环,只要得到核中的所有值,再通过分类排序便可以得到中值,然后锚点由该值替代。
复制代码代码如下:函数medianBlur(_ src,__size1,__size2,__borderType,_ _ dst){ if(_ src。键入_ _ src。type==' CV _ RGBA '){ var height=_ _ src。行,宽度=__src.col,dst=__dst || new Mat(高度,宽度,CV _ RGBA),dst data=dst . datavar size1=_ _ size1 | | 3,size2=__size2 || size1,size=size1 * size2if(size1 % 2!==1 || size2 % 2!==1){ console.error('size必须是奇数');return _ _ src } var startX=Math . floor(size 1/2),startY=math。地板(尺寸2/2);var带边框mat=copymakedborder(_ src,startY,startX,0,0,__borderType),mData=withBorderMat.data,mWidth=带边框mat。colvar newValue=[],nowX,offsetY,offsetIvar i,j,c,y,x;for(i=高度;我-;){ offsetI=i *宽度;对于(j=宽度;j-;){ for(c=3;c-;){ for(y=size 2;y-;){ offsecty=(y I)* mWidth * 4;for(x=size 1;x-;){ NoWx=(x j)* 4c;新值[y * size1 x]=MData[offset NoWx];} } NewVaLue。sort();dst数据[(j offsetI)* 4c]=新值[数学。圆形(尺寸/2)];} dst数据[(j offset ti)* 4 ^ 3]=mData[offset startY * mWidth * 4(j startX)* 4 ^ 3];} } }else{ console.error('类型不支持');}返回dst };