2015-04-17 11:03:10 hdanbang 阅读数 783
  • OpenCV图像分割实战视频教程

    基于OpenCV新版本3.2 讲述,详细解释了KMeans、高斯混合模型(GMM)、分水岭变换、Grabcut等算法基本原理与在图像分割中的应用,基于OpenCV相关API演示每种图像分割方法,通过证件照背景融合替换与视频背景融合替换两个真实案例,讲述了图像分割在实际应用场景中的实现与演示。

    2681 人正在学习 去看看 贾志刚

注:博客中图表的大小难以调整,导致阅读不便,这里有其pdf版本:高斯模糊实现小结.pdf

 

高斯模糊是一种图像滤波器,它使用正态分布(高斯函数)计算模糊模板,并使用该模板与原图像做卷积运算,达到模糊图像的目的。

N维空间正态分布方程为:

其中,σ是正态分布的标准差,σ值越大,图像越模糊(平滑)r为模糊半径,模糊半径是指模板元素到模板中心的距离。如二维模板大小为m*n,则模板上的元素(x,y)对应的高斯计算公式为:

在二维空间中,这个公式生成的曲面的等高线是从中心开始呈正态分布的同心圆。分布不为零的像素组成的卷积矩阵与原始图像做变换。每个像素的值都是周围相邻像素值的加权平均。原始像素的值有最大的高斯分布值,所以有最大的权重,相邻像素随着距离原始像素越来越远,其权重也越来越小。这样进行模糊处理比其它的均衡模糊滤波器更高地保留了边缘效果。

理论上来讲,图像中每点的分布都不为零,这也就是说每个像素的计算都需要包含整幅图像。在实际应用中,在计算高斯函数的离散近似时,在大概3σ距离之外的像素都可以看作不起作用,这些像素的计算也就可以忽略。通常,图像处理程序只需要计算(6σ+1)*(6σ+1)的矩阵就可以保证相关像素影响。

1、使用给定高斯模板平滑图像函数

σ=0.84089642的77列高斯模糊矩阵为:

现使用该模板对源图像做模糊处理,其函数如下:

  1. //高斯平滑  
  2. //未使用sigma,边缘无处理  
  3. void GaussianTemplateSmooth(const Mat &src, Mat &dst, double sigma)  
  4. {  
  5.     //高斯模板(7*7),sigma = 0.84089642,归一化后得到  
  6.     static const double gaussianTemplate[7][7] =   
  7.     {  
  8.         {0.00000067, 0.00002292, 0.00019117, 0.00038771, 0.00019117, 0.00002292, 0.00000067},  
  9.         {0.00002292, 0.00078633, 0.00655965, 0.01330373, 0.00655965, 0.00078633, 0.00002292},  
  10.         {0.00019117, 0.00655965, 0.05472157, 0.11098164, 0.05472157, 0.00655965, 0.00019117},  
  11.         {0.00038771, 0.01330373, 0.11098164, 0.22508352, 0.11098164, 0.01330373, 0.00038771},  
  12.         {0.00019117, 0.00655965, 0.05472157, 0.11098164, 0.05472157, 0.00655965, 0.00019117},  
  13.         {0.00002292, 0.00078633, 0.00655965, 0.01330373, 0.00655965, 0.00078633, 0.00002292},  
  14.         {0.00000067, 0.00002292, 0.00019117, 0.00038771, 0.00019117, 0.00002292, 0.00000067}  
  15.     };  
  16.   
  17.     dst.create(src.size(), src.type());  
  18.     uchar* srcData = src.data;  
  19.     uchar* dstData = dst.data;  
  20.   
  21.     for(int j = 0; j < src.cols-7; j++)  
  22.     {  
  23.         for(int i = 0; i < src.rows-7; i++)  
  24.         {  
  25.             double acc = 0;  
  26.             double accb = 0, accg = 0, accr = 0;   
  27.             for(int m = 0; m < 7; m++)  
  28.             {  
  29.                 for(int n = 0; n < 7; n++)  
  30.                 {  
  31.                     if(src.channels() == 1)  
  32.                         acc += *(srcData + src.step * (i+n) + src.channels() * (j+m)) * gaussianTemplate[m][n];  
  33.                     else  
  34.                     {  
  35.                         accb += *(srcData + src.step * (i+n) + src.channels() * (j+m) + 0) * gaussianTemplate[m][n];  
  36.                         accg += *(srcData + src.step * (i+n) + src.channels() * (j+m) + 1) * gaussianTemplate[m][n];  
  37.                         accr += *(srcData + src.step * (i+n) + src.channels() * (j+m) + 2) * gaussianTemplate[m][n];  
  38.                     }  
  39.                 }  
  40.             }  
  41.             if(src.channels() == 1)  
  42.                 *(dstData + dst.step * (i+3) + dst.channels() * (j+3))=(int)acc;  
  43.             else  
  44.             {  
  45.                 *(dstData + dst.step * (i+3) + dst.channels() * (j+3) + 0)=(int)accb;  
  46.                 *(dstData + dst.step * (i+3) + dst.channels() * (j+3) + 1)=(int)accg;  
  47.                 *(dstData + dst.step * (i+3) + dst.channels() * (j+3) + 2)=(int)accr;  
  48.             }  
  49.         }  
  50.     }  
  51.       
  52. }  

其效果如图1所示,7*7的高斯模板与源图像做卷积运算时,会产生半径为3的边缘,在不精确的图像处理中,可用源图像像素填充,或者去掉边缘。

2、二维高斯模糊函数

上述的例子中,如何求得高斯模糊矩阵是高斯模糊的关键。根据高斯函数的性质,图像处理程序只需要计算(6σ+1)*(6σ+1)的矩阵就可以保证相关像素影响。因此,可根据σ的值确定高斯模糊矩阵的大小。高斯矩阵可利用公式(1-2)计算,并归一化得到。归一化是保证高斯矩阵的值在[0,1]之间,

其处理函数如下:

 

  1. void GaussianSmooth2D(const Mat &src, Mat &dst, double sigma)  
  2. {  
  3.     if(src.channels() != 1)  
  4.         return;  
  5.   
  6.     //确保sigma为正数   
  7.     sigma = sigma > 0 ? sigma : 0;  
  8.     //高斯核矩阵的大小为(6*sigma+1)*(6*sigma+1)  
  9.     //ksize为奇数  
  10.     int ksize = cvRound(sigma * 3) * 2 + 1;  
  11.       
  12. //  dst.create(src.size(), src.type());  
  13.     if(ksize == 1)  
  14.     {  
  15.         src.copyTo(dst);      
  16.         return;  
  17.     }  
  18.   
  19.     dst.create(src.size(), src.type());  
  20.   
  21.     //计算高斯核矩阵  
  22.     double *kernel = new double[ksize*ksize];  
  23.   
  24.     double scale = -0.5/(sigma*sigma);  
  25.     const double PI = 3.141592653;  
  26.     double cons = -scale/PI;  
  27.   
  28.     double sum = 0;  
  29.   
  30.     for(int i = 0; i < ksize; i++)  
  31.     {  
  32.         for(int j = 0; j < ksize; j++)  
  33.         {  
  34.             int x = i-(ksize-1)/2;  
  35.             int y = j-(ksize-1)/2;  
  36.             kernel[i*ksize + j] = cons * exp(scale * (x*x + y*y));  
  37.   
  38.             sum += kernel[i*ksize+j];  
  39. //          cout << " " << kernel[i*ksize + j];  
  40.         }  
  41. //      cout <<endl;  
  42.     }  
  43.     //归一化  
  44.     for(int i = ksize*ksize-1; i >=0; i--)  
  45.     {  
  46.         *(kernel+i) /= sum;  
  47.     }  
  48.     //图像卷积运算,无边缘处理  
  49.     for(int j = 0; j < src.cols-ksize; j++)  
  50.     {  
  51.         for(int i = 0; i < src.rows-ksize; i++)  
  52.         {  
  53.             double acc = 0;  
  54.   
  55.             for(int m = 0; m < ksize; m++)  
  56.             {  
  57.                 for(int n = 0; n < ksize; n++)  
  58.                 {  
  59.                     acc += *(srcData + src.step * (i+n) + src.channels() * (j+m)) * kernel[m*ksize+n];   
  60.                 }  
  61.             }  
  62.   
  63.           
  64.             *(dstData + dst.step * (i + (ksize - 1)/2) + (j + (ksize -1)/2)) = (int)acc;  
  65.         }  
  66.     }  
  67.     delete []kernel;  
  68. }  

利用该函数,取σ=0.84089642即可得到上例中7*7的模板,该模板数据存在kernel中。利用该函数计算的归一化后的3*35*5阶高斯模板如表2,3所示:

由上表可以看出,高斯模板是中心对称的。

模糊效果如图2所示。

 

对图2中边缘的处理:

  1. ...  
  1. int center = (ksize-1) /2;  
  2. //图像卷积运算,处理边缘  
  3. for(int j = 0; j < src.cols; j++)  
  4. {  
  5.     for(int i = 0; i < src.rows; i++)  
  6.     {  
  7.         double acc = 0;  
  8.   
  9.         for(int m = -center, c = 0; m <= center; m++, c++)  
  10.         {  
  11.             for(int n = -center, r = 0; n <= center; n++, r++)  
  12.             {  
  13.                 if((i+n) >=0 && (i+n) < src.rows && (j+m) >=0 && (j+m) < src.cols)  
  14.                 {  
  15.                       
  16.                     acc += *(srcData + src.step * (i+n) + src.channels() * (j+m)) * kernel[r*ksize+c];   
  17.               
  18.                 }  
  19.             }  
  20.         }  
  21.   
  22.   
  23.         *(dstData + dst.step * (i) + (j)) = (int)acc;  
  24.     }  
  25. }  
  1. ...  


结果图为:

如上图所示,边缘明显变窄,但是存在黑边。

3、改进的高斯模糊函数

上述的二维高斯模糊函数GaussianSmooth2D达到了高斯模糊图像的目的,但是如图2所示,会因模板的关系而造成边缘图像缺失,σ越大,缺失像素越多,额外的边缘处理会增加计算量。并且当σ变大时,高斯模板(高斯核)和卷积运算量将大幅度提高。根据高斯函数的可分离性,可对二维高斯模糊函数进行改进。

高斯函数的可分离性是指使用二维矩阵变换得到的效果也可以通过在水平方向进行一维高斯矩阵变换加上竖直方向的一维高斯矩阵变换得到。从计算的角度来看,这是一项有用的特性,因为这样只需要O(n*M*n)+O(m*M*N)次计算,而二维不可分的矩阵则需要O(m*n*M*n)次计算,其中,m,n为高斯矩阵的维数,M,N为二维图像的维数。

另外,两次一维的高斯卷积将消除二维高斯矩阵所产生的边缘。

(关于消除边缘的论述如下图2.4所示, 对用模板矩阵超出边界的部分——虚线框,将不做卷积计算。如图2.4中x方向的第一个模板1*5,将退化成1*3的模板,只在图像之内的部分做卷积。)


改进的高斯模糊函数如下:

  1. void GaussianSmooth(const Mat &src, Mat &dst, double sigma)  
  2. {  
  3.     if(src.channels() != 1 && src.channels() != 3)  
  4.         return;  
  5.   
  6.     //  
  7.     sigma = sigma > 0 ? sigma : -sigma;  
  8.     //高斯核矩阵的大小为(6*sigma+1)*(6*sigma+1)  
  9.     //ksize为奇数  
  10.     int ksize = ceil(sigma * 3) * 2 + 1;  
  11.   
  12.     //cout << "ksize=" <<ksize<<endl;  
  13.     //  dst.create(src.size(), src.type());  
  14.     if(ksize == 1)  
  15.     {  
  16.         src.copyTo(dst);      
  17.         return;  
  18.     }  
  19.   
  20.     //计算一维高斯核  
  21.     double *kernel = new double[ksize];  
  22.   
  23.     double scale = -0.5/(sigma*sigma);  
  24.     const double PI = 3.141592653;  
  25.     double cons = 1/sqrt(-scale / PI);  
  26.   
  27.     double sum = 0;  
  28.     int kcenter = ksize/2;  
  29.     int i = 0, j = 0;  
  30.     for(i = 0; i < ksize; i++)  
  31.     {  
  32.         int x = i - kcenter;  
  33.         *(kernel+i) = cons * exp(x * x * scale);//一维高斯函数  
  34.         sum += *(kernel+i);  
  35.   
  36. //      cout << " " << *(kernel+i);  
  37.     }  
  38. //  cout << endl;  
  39.     //归一化,确保高斯权值在[0,1]之间  
  40.     for(i = 0; i < ksize; i++)  
  41.     {  
  42.         *(kernel+i) /= sum;  
  43. //      cout << " " << *(kernel+i);  
  44.     }  
  45. //  cout << endl;  
  46.   
  47.     dst.create(src.size(), src.type());  
  48.     Mat temp;  
  49.     temp.create(src.size(), src.type());  
  50.   
  51.     uchar* srcData = src.data;  
  52.     uchar* dstData = dst.data;  
  53.     uchar* tempData = temp.data;  
  54.   
  55.     //x方向一维高斯模糊  
  56.     for(int y = 0; y < src.rows; y++)  
  57.     {  
  58.         for(int x = 0; x < src.cols; x++)  
  59.         {  
  60.             double mul = 0;  
  61.             sum = 0;  
  62.             double bmul = 0, gmul = 0, rmul = 0;  
  63.             for(i = -kcenter; i <= kcenter; i++)  
  64.             {  
  65.                 if((x+i) >= 0 && (x+i) < src.cols)  
  66.                 {  
  67.                     if(src.channels() == 1)  
  68.                     {  
  69.                         mul += *(srcData+y*src.step+(x+i))*(*(kernel+kcenter+i));  
  70.                     }  
  71.                     else   
  72.                     {  
  73.                         bmul += *(srcData+y*src.step+(x+i)*src.channels() + 0)*(*(kernel+kcenter+i));  
  74.                         gmul += *(srcData+y*src.step+(x+i)*src.channels() + 1)*(*(kernel+kcenter+i));  
  75.                         rmul += *(srcData+y*src.step+(x+i)*src.channels() + 2)*(*(kernel+kcenter+i));  
  76.                     }  
  77.                     sum += (*(kernel+kcenter+i));  
  78.                 }  
  79.             }  
  80.             if(src.channels() == 1)  
  81.             {  
  82.                 *(tempData+y*temp.step+x) = mul/sum;  
  83.             }  
  84.             else  
  85.             {  
  86.                 *(tempData+y*temp.step+x*temp.channels()+0) = bmul/sum;  
  87.                 *(tempData+y*temp.step+x*temp.channels()+1) = gmul/sum;  
  88.                 *(tempData+y*temp.step+x*temp.channels()+2) = rmul/sum;  
  89.             }  
  90.         }  
  91.     }  
  92.   
  93.       
  94.     //y方向一维高斯模糊  
  95.     for(int x = 0; x < temp.cols; x++)  
  96.     {  
  97.         for(int y = 0; y < temp.rows; y++)  
  98.         {  
  99.             double mul = 0;  
  100.             sum = 0;  
  101.             double bmul = 0, gmul = 0, rmul = 0;  
  102.             for(i = -kcenter; i <= kcenter; i++)  
  103.             {  
  104.                 if((y+i) >= 0 && (y+i) < temp.rows)  
  105.                 {  
  106.                     if(temp.channels() == 1)  
  107.                     {  
  108.                         mul += *(tempData+(y+i)*temp.step+x)*(*(kernel+kcenter+i));  
  109.                     }  
  110.                     else  
  111.                     {  
  112.                         bmul += *(tempData+(y+i)*temp.step+x*temp.channels() + 0)*(*(kernel+kcenter+i));  
  113.                         gmul += *(tempData+(y+i)*temp.step+x*temp.channels() + 1)*(*(kernel+kcenter+i));  
  114.                         rmul += *(tempData+(y+i)*temp.step+x*temp.channels() + 2)*(*(kernel+kcenter+i));  
  115.                     }  
  116.                     sum += (*(kernel+kcenter+i));  
  117.                 }  
  118.             }  
  119.             if(temp.channels() == 1)  
  120.             {  
  121.                 *(dstData+y*dst.step+x) = mul/sum;  
  122.             }  
  123.             else  
  124.             {  
  125.                 *(dstData+y*dst.step+x*dst.channels()+0) = bmul/sum;  
  126.                 *(dstData+y*dst.step+x*dst.channels()+1) = gmul/sum;  
  127.                 *(dstData+y*dst.step+x*dst.channels()+2) = rmul/sum;  
  128.             }  
  129.           
  130.         }  
  131.     }  
  132.       
  133.     delete[] kernel;  
  134. }  

该函数中使用的水平方向和垂直方向的高斯矩阵为同一矩阵,实际计算时可根据需要取不同。

模糊效果如图3所示:

比较

使用GetTickCount()进行比较,GetTickCount()函数的精度为1ms。

以下表格中的数据均为作者机器上的某两次运行结果取均值,编程环境为vs2010+opencv2.2


上表中Debug版本的GaussianTemplateSmooth竟然比GaussianSmooth2D运行时间长,难道是二维数组比不上一维指针,或者是Debug版本的问题?实验结果确实如上。

将本文所写函数与opencv2.2提供的高斯模糊函数GaussianBlur一起进行比较。

结论

如上表4,5所示,对GaussianSmooth2D的改进函数GaussianSmooth,越大时,提速效果越明显,这种速度的改进在Debug模式下尤为明显。无论是在Debug,还是在Release模式下,Opencv2.2提供的GaussianBlur完胜本文所用的函数。建议在学习算法时参考本文内容,实际项目中使用GaussianBlur

 

本例代码键连接:http://download.csdn.net/detail/zddmail/4217704

 

转载请注明出处,如有问题,请联系zddmail@gmail.com.

zdd

201241111:53:40


2017-10-13 21:34:51 shiyongraow 阅读数 513
  • OpenCV图像分割实战视频教程

    基于OpenCV新版本3.2 讲述,详细解释了KMeans、高斯混合模型(GMM)、分水岭变换、Grabcut等算法基本原理与在图像分割中的应用,基于OpenCV相关API演示每种图像分割方法,通过证件照背景融合替换与视频背景融合替换两个真实案例,讲述了图像分割在实际应用场景中的实现与演示。

    2681 人正在学习 去看看 贾志刚

图像处理的基本知识和学习SIFT的先验知识,总结一下


温故而知新,学习的知识整理出来时常翻一下还是好的。下面将从高斯模糊的定义和应用上来说明。

高斯模糊定义

高斯模糊其实是一个低通滤波器,它的核心在于使用高斯函数作为模糊模板与输入图像做卷积运算,去除图像的高频分量,达到模糊图像的目的(其实就是滤波)。

经过高斯模糊处理的图片视觉效果类似于通过透过半透明的屏幕去查看,高斯模糊在图像预处理阶段用的非常广泛,就比如在SIFT算法中构建尺度空间中就要用到。

如上,高斯模糊的核心在于高斯函数,下面将贴出高斯函数的具体表达式:
一维:

 G(x)=12πσ2ex22σ2 

二维:

 G(x,y)=12πσ2ex2+y22σ2 

其中: xy 分别是原点到 x 轴或 y 轴之间的距离。σ 为正态分布的标准差。

当应用于二维时,这个公式生成的曲面的等高线是从中心开始呈正态分布的同心圆。该分布的值用于构建一个应用于原始图像的卷积矩阵。
每个像素的新值被设置为周围像素值的加权平均值。原来的像素值具有最高的高斯值,因此具有最大的权重。而相邻的像素随着与中心距离的增加,则权重越来越小。所以经过高斯模糊处理后更好地保留了边缘效果。

理论上,图像上每个点的高斯分布都不为0,也就是说每个像素的计算都需要包含整幅图像。在实际应用中,在计算高斯函数的离散近似时,超过 3σ 的像素是非常小甚至接近0,这些点可看作不起作用的点。在计算时可以忽略。一般在图像处理中我们只需计算 (6σ+1)×(6σ+1) 的像素矩阵就可以确保得到的结果接近高斯分布。

还有最重要一点,我们对同一图像先后作 σ=σ1  σ=σ2 的高斯平滑和直接作 σ=σ21+σ22 高斯平滑的结果是一样的(也叫高斯核的半群性质)。

wiki中给出的高斯模糊矩阵如下,其中 σ=0.84089642 ,中心元素具有最大值,随着离中心距离的增加,元素对称减小。

模板矩阵

注意中心元素0.22508352是离中心 3σ 处0.00019117的1177倍,所以前面说 3σ 外的点可以忽略不计。


高斯模糊的应用

在图像处理上运用的非常多,下面先贴一个具体用例

clear;close;clc;
img1=imread('2.png');
w=fspecial('gaussian',[7 7],0.84089642);
img2=imfilter(img1,w);
w=fspecial('gaussian',[7 7],2);
img3=imfilter(img1,w);
w=fspecial('gaussian',[7 7],10);
img4=imfilter(img1,w);

subplot(2,2,1)
imshow(img1);
subplot(2,2,2)
imshow(img2);
subplot(2,2,3)
imshow(img3);
subplot(2,2,4)
imshow(img4);

σ=0.84088642 时,上面代码中的 w 结果就是上面提到的模板矩阵。模糊结果如下:
这里写图片描述
可以看到随着 σ 的不断增大,图片越来越模糊。

2019-08-20 17:31:29 yeweij226 阅读数 50
  • OpenCV图像分割实战视频教程

    基于OpenCV新版本3.2 讲述,详细解释了KMeans、高斯混合模型(GMM)、分水岭变换、Grabcut等算法基本原理与在图像分割中的应用,基于OpenCV相关API演示每种图像分割方法,通过证件照背景融合替换与视频背景融合替换两个真实案例,讲述了图像分割在实际应用场景中的实现与演示。

    2681 人正在学习 去看看 贾志刚

本文用到了卷积的内容,如有了解较少的同学建议转到java图像处理(卷积,强调边缘,平滑与高斯模糊)先了解一下

预备知识

高斯模糊的概念

高斯模糊(Gaussian Blur),是常用的用来减少噪声的算法。其实质上是图像与正态分布的卷积,而正态分布又叫做高斯分布,故而名为高斯模糊

在这里插入图片描述

高斯模糊的原理
像素重置

模糊的过程即将每个像素值进行重置的过程,重置的一般过程即为将每一个像素设置成周围像素的平均值(算数平均或加权平均),如以下像素组
在这里插入图片描述
这个像素组的中间为8,其余周围为1
假定中心点的元素对周围元素无影响,那么我们需要用来求平均值的卷积核为
在这里插入图片描述
卷积一下
在这里插入图片描述
这样,中间的值就从较大的8变成了较小的16/9,相当于是平滑了中间值,因此使用上面卷积核来进行模糊的算法也叫做平滑模糊
当然我们也可以以卷积核作为权来进行加权平均,如下面我们使用的高斯模糊的卷积核就是加权平均的一种

使用范围为3,5,7,9的平均值卷积核来进行平滑模糊的效果如图所示
在这里插入图片描述
很明显,随着范围的增大,模糊的效果越来越明显,但是平滑模糊由于权重一致,虽然有一定的模糊效果,但是对噪声的处理不是很好,如果我们找到了一种权重的定义的话,可能会对我们的模糊处理有着更有益的效果。

很容易想到的是将权重与距离联系起来,距离中心元越近权重越高,距离中心元越远权重越低。
如何分辨中心元素和周围元素的距离关系?
一种常见的衡量方法为

正态分布(也叫高斯分布)

一维的正态分布我们已经很常见了
在这里插入图片描述
在这里插入图片描述
这种中心高,两侧低的算法正好可以用于我们对距离的判定
但是,由于图像是二维的,因此一维的正态分布在这里不适用

我们将x换成(x^2 + y^2)的开方,即可将一维的正态曲线旋转画到二维中
在这里插入图片描述
由于我们将(0,0)点定义成中心点,这样公式中的μ值取0,公式即为在这里插入图片描述
有了这个公式,我们就可以根据公式生成一个卷积核,然后去和像素区域进行卷积,这样得到的就是加权平均过后的中心值了
根据正态分布产生的卷积核我们叫做高斯模板

高斯模板代码(java)
		 //设置一个数组存储高斯模板,其中size为模板的大小(也叫做卷积核范围)
		 double[][] gaussModel = new double[size][size]; 
		 //设置图片中心参数
		 int CenterX = (size-1)/2;
		 int CenterY = CenterX;  
		 //临时中间量
		 double part;
		 //高斯模板的总和
		 double sum = 0;
		 //定义一个合适的sigma值
		 double sigma = size / 3.0f;
		 for(int i = 0; i< size; ++i){
			 //根据高斯公式计算高斯模板
			 	for(int j = 0; j < size; ++j){
			 	   //首先产生指数值中有关x,y的内容
			 	   part = (i - CenterX) * (i - CenterX) + (j  - CenterY) * (j - CenterY);
			 	   //然后进行高斯值的计算,并赋到高斯模板的数组中
				   gaussModel[i][j] = (double)(Math.exp( - part / (2 * sigma * sigma) ) 
						   / Math.sqrt(2 * Math.PI * sigma * sigma));
				   //将高斯值加到总和中
				   sum += gaussModel[i][j];
			 	}
		 }
		 // 归一化,为了使处理后的图像与原图亮度相同
		 for(int i = 0; i< size; ++i){
			 for(int j = 0; j < size; ++j){
				 //将所得高斯值除以sum值,使其缩到1内,即为归一化
				 gaussModel[i][j] = (double) (gaussModel[i][j] / sum);
			 }
		 }		 

附:生成的3 * 3与5 * 5的高斯模板
3x3高斯模板
5x5高斯模板

边缘处理

暂时想到的是可以将原图先四周围临时补上一圈无意义的像素点,但是补什么样的点暂时我还没有想出来,在这里先补上了黑色点,像素点宽度为size/2(例如3 * 3的卷积核补充一圈宽度为1的像素点),这样在处理图像时就可以将整个图像进行处理,最后再去掉补上的点即可。

代码请见下面高斯模板内的代码

下面是效果
处理之前的边缘,是有很明显的没有处理的边界的
在这里插入图片描述
处理之后的边缘,很明显是被处理过的,补的是黑色边框(根据周围颜色来补边框的算法暂时还没研究出来,如果有好的解决方法欢迎各位支援!)
在这里插入图片描述

高斯模糊的代码实现

这里实现的是彩色图像的高斯模糊,因此我们需要对原图的rgb值进行提取,然后分别对r,g,b进行卷积处理后绘出(也可以理解成使用了3 * 3 * 3的一个三维卷积核)
请仔细阅读注释内容,注释中有详细的代码解释

 		 //设置临时边缘的宽度
		 int tempWidth = size/2;
		 //设置一个数组存储含边缘的图片数据,其中边缘因为是上下左右均要补边,因此是2*tempWidth
		 int[][] tempImg = new int[width+2*tempWidth][height+2*tempWidth]; 
		 //将横着的边缘补上
		 for(int x = 0;x < width+2*tempWidth;x++){
			 for(int y = 0;y < tempWidth;y++){
				 //将上下的边缘补上
				 tempImg[x][y] =0;
				 //height+2*tempWidth-y-1计算下面需要补充的边缘的y值位置,下同,不再赘述
				 tempImg[x][height+2*tempWidth-y-1] =0;
			 }
		 }
		 //将竖着的边缘补上
		 for(int y = 0;y < height+2*tempWidth;y++){
			 for(int x = 0;x < tempWidth;x++){
				 tempImg[x][y] = 0;
				 tempImg[width+2*tempWidth-x-1][y] =0;
			 }
		 }
		 //将原有图像传到新数组中等待处理,其中将原有图像向右下角移动tempWidth个单位即可将原图赋到新图中
		 for(int x = 0;x < width;x++){
			 for(int y = 0;y < height;y++){
				 tempImg[x+tempWidth][y+tempWidth] = rgbOfImg[x][y];
			 }
		 }
		 //下面开始搞卷积算法
		//初始化rgb数组
		R = new int[size][size];
		G = new int[size][size];
		B = new int[size][size];
		//遍历图像的二维数组进行处理
		for(int x = 0;x < width+2*tempWidth-size+1;x++){
			for(int y = 0;y < height+2*tempWidth-size+1;y++){
				//一定要在循环内部进行初始化0,这样才能每次有不同的值
				int resultOfR = 0;
				int resultOfG = 0;
				int resultOfB = 0;
				//将格子的rgb值都取出,便于之后的卷积操作
				for(int i = 0;i < size;i++){
					for(int j = 0;j <size;j++){
						//将该点的ARGB信息取出,放到变量中待操作
						int argb = tempImg[x+i][y+j];
						//分段获取其R,G,B信息
						//int变量共4位32字节,0位对应透明度(A),1位对应R值,2位对应G值,3位对应B值
						//>>操作:将二进制代码向右移动,左边空位根据符号补充,正号为0,负号为1,右边超过范围的全部舍弃
						//&:二进制位与运算符,只有两个变量对应值均为1时该位才返回1,0xff表示全为1的十六进制数(11111111),因此任何与0xff进行位与的结果均为其本身
						//先移位后取位与可以将不同值对应的位信息取出,位与的意义是只取32字节的后8字节
						R[i][j] = argb>>16 & 0xff;
						G[i][j] = argb>>8  & 0xff;
						B[i][j] = argb     & 0xff;
					}
				}
				//分别对R,G,B进行卷积操作,对应相乘后加起来
				for(int i = 0;i < size;i++){
					for(int j = 0;j < size;j++){
						resultOfR += (int)(gaussModel[i][j]*R[i][j]);
					}
				}
				for(int i = 0;i < size;i++){
					for(int j = 0;j < size;j++){
						resultOfG += (int)(gaussModel[i][j]*G[i][j]);

					}
				}				
				for(int i = 0;i < size;i++){
					for(int j = 0;j < size;j++){
						resultOfB += (int)(gaussModel[i][j]*B[i][j]);
					}
				}
				//如果超过了界限,将其按照最大值或最小值处理
				if(resultOfR > 255)resultOfR = 255;
				if(resultOfR < 0)resultOfR = 0;
				if(resultOfG > 255)resultOfG = 255;
				if(resultOfG < 0)resultOfG = 0;
				if(resultOfB > 255)resultOfB = 255;
				if(resultOfB < 0)resultOfB = 0;
				
				//将卷积得出的高斯模糊的灰度值传到中间元中
				//根据该argb值创建颜色对象
				Color color = new Color(resultOfR, resultOfG, resultOfB);
				//设置颜色
				graphics2.setColor(color);
				//画像素点,将点向左上平移以实现边框的去除
				graphics2.drawLine(x+size/2-tempWidth, y+size/2-tempWidth, x+size/2-tempWidth, y+size/2-tempWidth);
			}
		}

效果(左为3 * 3,右为5 * 5)
在这里插入图片描述
在这里插入图片描述
附原图
在这里插入图片描述
在这里插入图片描述

2013-04-09 22:09:46 g_idea 阅读数 1263
  • OpenCV图像分割实战视频教程

    基于OpenCV新版本3.2 讲述,详细解释了KMeans、高斯混合模型(GMM)、分水岭变换、Grabcut等算法基本原理与在图像分割中的应用,基于OpenCV相关API演示每种图像分割方法,通过证件照背景融合替换与视频背景融合替换两个真实案例,讲述了图像分割在实际应用场景中的实现与演示。

    2681 人正在学习 去看看 贾志刚

图像平滑、是用于消除噪音,降低细节层次,模糊图像的一种处理技术。

由于图像中,距离被处理的对象越近,其对该像素的影响也越大,所以图形平滑时,多采用加权平均的方法。

如采用加权平均模板为:

即用矩阵对图像进行卷积,得到平滑后的图像。


 

高斯模糊也属于加权平均法的一种模板,只是他采用了正太分布来作为模板,进行像素映射工作。

一维高斯:

                  



二维高斯函数:

                


由于高斯函数具有线性可分的特性,所以高斯模糊即可以由一维高斯函数获得,也可由二维高斯函数获得。

其中,一维的方法为:首先沿横向对图像进行处理,然后在上一处理的基础上沿着纵向进行处理。



http://zh.wikipedia.org/wiki/%E9%AB%98%E6%96%AF%E6%A8%A1%E7%B3%8A

高斯矩阵示例

这是一个计算 σ = 0.84089642 的高斯分布生成的示例矩阵。注意中心元素 (4,4) 处有最大值,随着距离中心越远数值对称地减小。

0.00000067 0.00002292 0.00019117 0.00038771 0.00019117 0.00002292 0.00000067
0.00002292 0.00078633 0.00655965 0.01330373 0.00655965 0.00078633 0.00002292
0.00019117 0.00655965 0.05472157 0.11098164 0.05472157 0.00655965 0.00019117
0.00038771 0.01330373 0.11098164 0.22508352 0.11098164 0.01330373 0.00038771
0.00019117 0.00655965 0.05472157 0.11098164 0.05472157 0.00655965 0.00019117
0.00002292 0.00078633 0.00655965 0.01330373 0.00655965 0.00078633 0.00002292
0.00000067 0.00002292 0.00019117 0.00038771 0.00019117 0.00002292 0.00000067

变化前图像:

 

高斯模糊后:

 

代码:


 

2007-08-22 11:21:00 iteye_6236 阅读数 52
  • OpenCV图像分割实战视频教程

    基于OpenCV新版本3.2 讲述,详细解释了KMeans、高斯混合模型(GMM)、分水岭变换、Grabcut等算法基本原理与在图像分割中的应用,基于OpenCV相关API演示每种图像分割方法,通过证件照背景融合替换与视频背景融合替换两个真实案例,讲述了图像分割在实际应用场景中的实现与演示。

    2681 人正在学习 去看看 贾志刚

在图像的处理过程中,经常要用到卷积模板,如图像锐化、图像平滑、高斯模糊、Hough变换等,为此,本人使用Delphi编写了图像通用卷积处理过程和高斯模糊过程,代码如下:

过程定义: // 卷积图像。参数: // Dest输出图,Source原图,Data自身操作图像 // ConvolTemplate卷积模板,必须为奇数矩阵 // Callback回调函数,返回True终止操作,CallbackData回调函数参数地址 procedure ImageConvolution(var Data: TImageData; ConvolTemplate: array of Integer); overload; procedure ImageConvolution(var Dest: TImageData; const Source: TImageData; ConvolTemplate: array of Integer); overload; // 无效参数或者被回调函数终止操作返回False。 function ImageConvolution(var Dest: TImageData; const Source: TImageData; ConvolTemplate: array of Integer; Callback: TImageAbort; CallbackData: Pointer): Boolean; overload; 实现代码: procedure ConvolutionMMX(Template: Pointer; Size, TemplateOffset: Integer); pascal; var width, height: Integer; dstOffset, srcOffset: Integer; asm mov width, ecx mov height, edx mov dstOffset, ebx mov srcOffset, eax mov eax, TemplateOffset pxor mm7, mm7 @@yLoop: // for (y = 0; y < Dst.Height; y ++) push width // { @@xLoop: // for (x = 0; x < Dst.Width; x ++) push esi // { pxor mm0, mm0 // mm0 = 0 mov ebx, Template mov edx, Size // for (I = 0; I < TemplateSize; I ++) @@iLoop: // { mov ecx, Size // for (J = 0; J <= TemplateSize; J ++) @@jLoop: // { movd mm1, [esi] punpcklbw mm1, mm7 pmullw mm1, [ebx] // mm1 = pixels[I, J] * Template[i * j]; paddsw mm0, mm1 // mm0 += mm1 add esi, 4 // esi += 4 add ebx, 8 // edx += 8 loop @@jLoop // } add esi, eax // esi += TemplateOffset dec edx jnz @@iLoop // } packuswb mm0, mm7 mov dl, [edi].TARGBQuad.Alpha movd [edi], mm0 // *(ARGB*)edi = mm0 mov [edi].TARGBQuad.Alpha, dl pop esi add esi, 4 // esi += 4 add edi, 4 // edi += 4 dec width jnz @@xLoop // } add esi, srcOffset // esi += srcOffset add edi, dstOffset // edi += dstOffset pop width dec height jnz @@yLoop // } emms end; procedure Convolution(Template: Pointer; Size, TemplateOffset: Integer); pascal; var width, height: Integer; dstOffset, srcOffset: Integer; reds, greens, blues: Integer; asm mov width, ecx mov height, edx mov dstOffset, ebx mov srcOffset, eax @@yLoop: push width @@xLoop: push esi push edi mov edi, Template xor ebx, ebx mov reds, ebx mov greens, ebx mov blues, ebx mov ecx, Size @@iLoop: push ecx mov ecx, Size @@jLoop: movzx eax, [esi].TARGBQuad.Blue movzx edx, [esi].TARGBQuad.Green imul eax, [edi] imul edx, [edi] add blues, eax add greens, edx movzx eax, [esi].TARGBQuad.Red movzx edx, [esi].TARGBQuad.Alpha imul eax, [edi] imul edx, [edi] add reds, eax add ebx, edx add esi, 4 add edi, 4 loop @@jLoop add esi, TemplateOffset pop ecx loop @@iLoop pop edi pop esi mov eax, blues mov edx, greens mov ecx, reds shr eax, 16 shr edx, 16 shr ecx, 16 shr ebx, 16 mov [edi].TARGBQuad.Blue, al mov [edi].TARGBQuad.Green, dl mov [edi].TARGBQuad.Red, cl mov [edi].TARGBQuad.Alpha, bl add esi, 4 add edi, 4 dec width jnz @@xLoop add esi, srcOffset add edi, dstOffset pop width dec height jnz @@yLoop end; function ImageConvolution(var Dest: TImageData; const Source: TImageData; ConvolTemplate: array of Integer; Callback: TImageAbort; CallbackData: Pointer): Boolean; var i, j, n: Integer; p: PInteger; Dst, Src: TImageData; Template: Pointer; TemplateSize, Radius: Integer; TemplateOffset: Integer; Nuclear: Integer; Proc: Pointer; begin Result := False; n := Length(ConvolTemplate); if ImageEmpty(Dest) or ImageEmpty(Source) or (n = 0) then Exit; TemplateSize := Trunc(Sqrt(n)); Radius := TemplateSize shr 1; GetExpandData(Dest, Source, Radius, Dst, Src); TemplateOffset := Src.Stride - (TemplateSize shl 2); Nuclear := 0; for i := 0 to n - 1 do Inc(Nuclear, ConvolTemplate[i]); if Nuclear <= 1 then begin GetMem(Template, n * Sizeof(int64)); j := 0; for i := 0 to n - 1 do begin TWordArray(Template^)[j] := ConvolTemplate[i]; TWordArray(Template^)[j + 1] := ConvolTemplate[i]; TWordArray(Template^)[j + 2] := ConvolTemplate[i]; TWordArray(Template^)[j + 3] := ConvolTemplate[i]; Inc(j, 4); end; Proc := @ConvolutionMMX; if @Dest <> @Source then CopyAlpha(Dst, Source); end else begin GetMem(Template, n * Sizeof(Integer)); p := Template; for i := 0 to n - 1 do begin p^ := ConvolTemplate[i] * 65536 div Nuclear; Inc(p); end; Proc := @Convolution; end; try if Assigned(Callback) then Result := ExecuteAbort(Dst, Src, Proc, [Template, TemplateSize, TemplateOffset], Callback, CallbackData) else Result := ExecuteProc(Dst, Src, Proc, [Template, TemplateSize, TemplateOffset]); finally FreeImageData(Src); FreeMem(Template); end; end; procedure ImageConvolution(var Data: TImageData; ConvolTemplate: array of Integer); begin ImageConvolution(Data, Data, ConvolTemplate, nil, nil); end; procedure ImageConvolution(var Dest: TImageData; const Source: TImageData; ConvolTemplate: array of Integer); begin ImageConvolution(Dest, Source, ConvolTemplate, nil, nil); end;

因为要进行图像卷积操作,必须拷贝出源图像,本文过程在拷贝源图像时使用ImageGetExpandData过程附带扩展了其边框,这样在卷积图像边界像素时不必再进行坐标范围判断,从而简化了代码和提高了运行速度。

由于本文提供的是通用的卷积过程,尽管使用了BASM代码,但还是比较耗时的,如果对速度有特别要求的卷积操作,建议写专用的处理过程为好,例如Hough变换,其卷积核为1,就没必要在卷积过程中使用耗时的除法运算了。

下面是高斯模糊过程测试代码:

var jpg: TJPEGImage; Data: TImageData; begin jpg := TJPEGImage.Create; jpg.LoadFromFile('D:/VclLib/GdiplusDemo/Media/20041001.jpg'); Canvas.Draw(0, 0, jpg); Data := GetImageData(jpg); ImageGaussiabBlur(Data, 3); ImageDataAssignTo(Data, jpg); Canvas.Draw(0, jpg.Height, jpg); FreeImageData(Data); jpg.Free; end;

下面是采用不同Q值进行高斯模糊的运行结果图:

左边是原始图像,其后分别是采用Q=1、2、3调用GdipGaussiabBlur过程高斯模糊后的图像,最右边一幅是使用Photoshop8.1cs高斯模糊(Q=3)处理的。

可以看出,本过程处理的图片和Photoshop处理的图片效果基本接近,同等Q值(3)情况下,Photoshop处理的模糊程度似乎稍稍强一些,可能是算法上的差异,我的过程自动计算的Radius是Q+2,我用Q=3,Radius=6,也就是采用13*13卷积矩阵的模糊效果似乎更接近Photoshop的处理效果,难道Photoshop的Radius为Q * 2?这样一来,采用的卷积矩阵会更大,应该不是,不过,上面的高斯模糊过程中Radius可以任意给定而不通过自动计算,可以反复试试,以模仿Photoshop的处理;另外,当Q=3时图像好像已经模糊的很厉害了,其实,对于高分辨率(200-300)图像,Q=3的高斯模糊程度并没有这么明显,我采用的图片分辨率为72,而且这张图片已经是经过Photoshop过滤、模糊处理后合成的。

下面给出一个直接调用GdipConvolution过程进行Hough变换的例子作为本文结束:

const // Ray: array[0..8] of Integer = (-1, 0, 1, -1, 0, 1, -1, 0, 1); // Ray: array[0..8] of Integer = (-1, -1, 0, -1, 0, 1, 0, 1, 1); Ray: array[0..8] of Integer = (-1, -1, -1, 0, 0, 0, 1, 1, 1); var bmp: TGpBitmap; Data: TImageData; g: TGpGraphics; begin bmp := TGpBitmap.Create('D:/msn.jpg'); g := TGpGraphics.Create(Canvas.Handle); g.DrawImage(bmp, 0, 0); Data := GetImageData(bmp); ImageConvolution(Data, Ray); ImageDataAssignTo(Data, bmp, False); g.DrawImage(bmp, 0, Data.Height); g.Free; FreeImageData(Data); bmp.Free; end;

运行效果如下图:

左边是原始图像,后面分别为用不同的Hough矩阵的运行效果,见例子代码中的几组Ray数组。作Hough变换,一般应该采用灰度图,本例子只是演示一下,没做图片灰度处理。

文章中所用数据类型及一些过程见《Delphi图像处理 -- 数据类型及内部过程》和《Delphi图像处理 -- 图像像素结构与图像数据转换》。

例子使用GDI+版本下载地址和说明见《GDI+ for VCL基础 -- GDI+ 与 VCL》。

尽管我十分努力,但水平有限,错误在所难免,欢迎指正和指导。邮箱地址:

maozefa@hotmail.com

注:本文已经于2009.10.28重新整理。

本文代码于2010.5.20重新修订过。增加了拷贝形式的调整过程和响应回调函数的调整过程。代码中的ExecuteAbort过程和ExecuteProc过程见《Delphi图像处理 -- 图像像素结构与图像数据转换》。

修订过的卷积过程对于卷积核小于等于1的卷积模板采用了MMX处理,过程速度相当快,当然也要求其模板单个数据项不得超过127,整个模板数据项与255的乘积和不得超过$7fff(一般卷积核小于等于1的卷积模板都远远小于这些值)。对于卷积核大于1的卷积模板,本文代码也作了很大改进,速度比以前有所提高。

另外,高斯模糊处理过程已经另写(见《Delphi图像处理 -- 图像高斯模糊处理(改进版)》),本文的高斯模糊例子就作为遗迹存放吧(代码是有效的,只是处理过程不在本文)!

高斯模糊实现小结

阅读数 46567

没有更多推荐了,返回首页