Published by orzz.org(). (http://orzz.org/win32%e4%b8%8b%e7%9a%84c%e9%ab%98%e6%96%af%e6%a8%a1%e7%b3%8a%e7%ae%97%e6%b3%95%e5%ae%9e%e4%be%8b/)
下文内容较旧,最新的相关文章请参看:高斯模糊算法的实现和优化
高斯模糊算法的基本原理,是利用一个基于正态分布特征权重的矩阵,对图像中每个像素区域进行采样,并做卷积运算;之后将得到的结果赋值给采样区域的中心点像素.
这样每个像素都将被重新计算,并且计算结果考虑到了以待赋值像素为中心的采样区域里的所有像素.采样区域中每个像素分别与高斯矩阵中的对应权值相乘,并将得到的结果依次相加.最终的结果就是考虑到所有采样区域像素的一个像素值.
详细介绍及数学方程请参考:http://zh.wikipedia.org/zh/%E9%AB%98%E6%96%AF%E6%A8%A1%E7%B3%8A
下面给出Win32下的C++(下文的例子基于VS2008)算法实现.
首先,我们创建一个测试工程,例如:TestGauss.
之后在工作目录中放入一个24位的bmp(这里为了凸显算法本身,只处理较为容易解析的24位位图):
下面,我们需要加载这张bmp图片,以便得到内存中的像素数据:
1 2 3 4 5 6 |
// 加载bmp hBitmap = (HBITMAP)LoadImage ( NULL, _T("ground.bmp"), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE ); |
然后在TestGauss.h中定义一个简单的bitmap结构,用于存放纯粹的像素指针及基本信息:
1 2 3 4 5 6 7 8 9 10 |
struct bitmap_t { struct pix_t { BYTE g; BYTE b; BYTE r; } *m_bits; long m_w, m_h; }; |
之后,写一个函数用于得到我们需要的bmp信息,并填充此结构:
1 2 3 4 5 6 7 8 9 10 11 12 |
bool GetBitmap(HBITMAP hBmp, bitmap_t* pBmp) { if (!hBmp || !pBmp) return false; BITMAP bm = {0}; GetObject(hBmp, sizeof(bm), &bm); if (!bm.bmBits) return false; if (bm.bmBitsPixel != 24) return false; // 仅处理24位位图 pBmp->m_bits = (bitmap_t::pix_t*)bm.bmBits; pBmp->m_w = bm.bmWidth; pBmp->m_h = bm.bmHeight; return true; } |
声明处理函数:
1 |
bool Gauss(bitmap_t* pBmp, long nRadius); |
定义外部调用的接口函数,实际上就是一个简单的包裹函数:
1 2 3 4 5 6 |
bool GaussBlur(HBITMAP hBmp, long nRadius = 5) { bitmap_t bmp = {0}; if (!GetBitmap(hBmp, &bmp)) return false; return Gauss(&bmp, nRadius); } |
这里我们选取的取样区域半径为5(此半径不包括取样中心点).
下面我们开始实现Gauss函数,对得到的像素矩阵做高斯模糊运算.首先,我们需要定义并计算出一些必须的变量:
1 2 3 4 5 6 7 8 |
// 定义变量 long diamet = (nRadius << 1) + 1; // 采样区域直径,或者方阵的边长 double s = (double)nRadius / 3.0; // 正态分布的标准偏差σ double sigma2 = 2.0 * s * s; // 2倍的σ平方,参考N维空间正态分布方程 double nuclear = 0.0; // 高斯卷积核 double* matrix = new double[diamet * diamet]; // 高斯矩阵定义 bitmap_t::pix_t* bits = pBmp->m_bits; // 像素内存块 long w = pBmp->m_w, h = pBmp->m_h; // 像素矩阵的宽与高 |
这里根据采样区域半径反算σ.由公式:d = 6σ + 1,可以得到σ = r / 3.
下面计算高斯矩阵:
1 2 3 4 5 6 7 8 9 10 |
// 计算高斯矩阵 int i = 0; for(long y = -nRadius; y <= nRadius; ++y) for(long x = -nRadius; x <= nRadius; ++x) { matrix[i] = exp(-(double)(x * x + y * y) / sigma2); nuclear += matrix[i]; ++i; } |
参考公式: .实际上若使用此公式生成矩阵,算法应该是这样:
1 2 3 4 5 6 |
// 计算高斯矩阵 int i = 0; for(long y = -nRadius; y <= nRadius; ++y) for(long x = -nRadius; x <= nRadius; ++x) matrix[i++] = exp(-(double)(x * x + y * y) / sigma2) / (sigma2 * 3.14159265); |
不过这样计算的矩阵,在nRadius == 1时会产生较大的偏差,因此这里我用nuclear保存并累加矩阵的所有乘积,在后面对像素处理之前用矩阵中的值除以nuclear,得到一个比较合适的权重值.
接下来,利用此矩阵对像素点进行区域采样并做卷积运算:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// 遍历并处理像素 for(long y_s = 0; y_s < h; ++y_s) { for(long x_s = 0; x_s < w; ++x_s) { // 分析取样区域 double r = 0.0, g = 0.0, b = 0.0; int i_m = 0; for (long m = -nRadius; m <= nRadius; ++m) { long y = y_s + m; if (y >= 0 && y < h) for (long n = -nRadius; n <= nRadius; ++n) { long x = x_s + n; if (x >= 0 && x < w) { double weight = matrix[i_m] / nuclear; long i = (h - y - 1) * w + x; r += weight * bits[i].r; g += weight * bits[i].g; b += weight * bits[i].b; } ++i_m; } else i_m += diamet; } // 保存处理结果 long i_s = (h - y_s - 1) * w + x_s; bits[i_s].r = (BYTE)(r > (BYTE)~0 ? (BYTE)~0 : r); bits[i_s].g = (BYTE)(g > (BYTE)~0 ? (BYTE)~0 : g); bits[i_s].b = (BYTE)(b > (BYTE)~0 ? (BYTE)~0 : b); } } |
此时图像已经被我们成功的模糊处理完毕了.
下面给出处理效果对比:
其中左边为原图,右边为经过半径为5的高斯模糊处理之后的效果图.
完整示例下载:TestGauss.zip
对高斯模糊更详细的讲解请参考此文:高斯模糊算法的实现和优化
生成的图像居然是全黑的图像 我晕
@st 不会吧,你确定你运行的是我附带的例子程序?
[…] 前两年我发过一文:Win32下的C++高斯模糊算法实例,里面给出了一个高斯模糊的实现,并写了粗略的简介。 […]