空间域滤波器(1)--使用GDI+(C++)实现
Spatial Filtering
(newsuppy,本文为图像处理课程的作业)
1平滑线性滤波器(或称均值滤波器) smoothing(averaging) filter
使用一个m*n的掩模(注意:m和n均必须为大于1奇数)依次覆盖在要处理图像的各个像素上,掩模中心与该像素重合。掩模上的各个元素均有其自身的权值(一种特殊的均值滤波器其掩模权值全为1,也可称为盒滤波器)。将掩模各元素权值与覆盖在其下的像素值分别相乘后求和,在除以掩模权值之和,所得结果称为响应,赋给当前处理的像素。
2中值滤波器 median filter
同样使用一个m*n的掩模依次处理各个像素,但是掩模没有权值,而是将掩模覆盖下的像素值排序后取中间值,将这个中值赋值给当前处理的像素。
3处理细节
掩模在图像边缘时,会遇到覆盖了非图像部分的情况,这里可以有多种方式处理,比较常见的有部分滤波和补零法(zero-padding)。部分滤波为在遇到图像边界时,忽略超出图像边界的部分,采用部分掩模处理边界像素。补零法是将图像边缘部分补上相应于掩模大小的零值(也可以为其它常值),处理完成后再将这部分切除,恢复至原图像大小,但是这样图像边缘往往会出现灰度值偏小的情况。一般来说图像边缘往往是不重要的部分,所以这些处理带来的缺点是可以容忍的。
4 图示处理过程(程序实现过程)
(1)获取一个准备滤波的原始图像,大小为Width*Height;准备一个m*n大小的滤波器掩模 要求m,n必须为大于1的奇数,如果是偶数,自动加1改为奇数 (2)复制原图像并使用补零法(zero-padding)在复制后的图像边缘补零,宽度两边补上(m-1)/2个像素,高度两边补上(n-1)/2个像素, 则复制后的图像尺寸变为(Width+m-1)*(Height+n-1)。这一步不在原图像上直接补零是因为,图像处理时取各个像素的邻域的原 始值,所以前一个像素处理后,要保留其原始值为下一个像素的处理所用。 (3)将掩模覆盖在复制图像上,依次处理各个像素(非补零像素)。 (a)对于均值滤波器,将掩模覆盖下的像素值分别与其对应的掩模的权值相乘,最后将这些乘积的和除以掩模的权值和所得既为 响应,赋给当前处理像素。遇到特殊的全1权值掩模,为方便计算只需将掩模覆盖下的像素值求和除以m*n,即为响应。 (b)对于中值滤波器,将掩模覆盖下的像素值取出成为一有m*n个元素的序列,排序后取中值,即为响应,赋给当前处理像素。 (4)完成处理,删除复制后图像。 |
5 程序实现
程序使用GDI+(C++)实现,在GDI+中Bitmap类提供了LockBits和UnlockBits方法操作BitmapData类,这样就可以直接操作图像的字节流,获得较高性能。
代码如下:
// Author: newsuppy // E-mail: [email protected] // Date: Nov. 1, 2004 // Version: v0.1.0 (it include in newsuppy's image process toolkit) // Declaration: You can copy&rewirte the code yourself, but please put me in your acknowledgments
#ifndef SPATIALFILTER_H #define SPATIALFILTER_H
#include <vector> #include <numeric> #include <algorithm> #include <gdiplus.h>
namespace nsimgtk { // function: filter a width*height rectangle portion of a bitmap one pixel by one pixel with the filterMask // template parameter: // pixelType: pixel's type depend on pixelFormat // pixelFormat: pixel's format, but this function doesn't support all the PixelFormat define in Gdiplus // | supported PixelFormat | pixel type | // 1, PixelFormat8bppIndexed ----------------------- unsigned char(8bit) // 2, PixelFormat16bppARGB1555 ---------------- unsigned short int(16bit) // 3, PixelFormat16bppGrayScale ---------------- unsigned short int(16bit) // 4, PixelFormat16bppRGB555 ---------------- unsigned short int(16bit) // 5, PixelFormat16bppRGB565 ---------------- unsigned short int(16bit) // 6, PixelFormat32bppARGB ----------------- unsigned int(32bit) // 6, PixelFormat32bppPARGB ----------------- unsigned int(32bit) // 6, PixelFormat32bppRGB ----------------- unsigned int(32bit) // FilterMask: FilterMask is a functor, it should inherit class "__filterMask" // and it must support member function "response" like this: // pixelType::FilterMask response(); // function parameter: // p_bitmap: a pointer to Gdiplus::Bitmap class // filterMask: the filterMask's instance // x, y, width, height: they are the parameters of the rectangle which should be process // (x,y) are the left-up point of the rectangle // return value: if failed,return false; else if success, return true; template <typename pixelType, Gdiplus::PixelFormat pixelFormat, class FilterMask> bool SpatialFilterAlgo(Gdiplus::Bitmap* const p_bitmap, FilterMask filterMask, unsigned int x, unsigned int y, unsigned int width, unsigned int height) { if (p_bitmap == NULL) { return false; }
if ((width + x > p_bitmap->GetWidth()) || (height + y >p_bitmap->GetHeight())) { return false; }
Gdiplus::BitmapData bitmapData; Gdiplus::Rect rect(x, y, width,height);
if (p_bitmap->LockBits(&rect, Gdiplus::ImageLockModeWrite, pixelFormat, &bitmapData) != Gdiplus::Ok) { return false; }
pixelType *pixels = (pixelType*)bitmapData.Scan0;
const unsigned int m = filterMask.d_m; // mask's width const unsigned int n = filterMask.d_n; // mask's height std::vector<pixelType> tmpImage((m-1+width)*(n-1+height)); // extend image to use zero-padding
// copy original bitmap to extended image with zero-padding method for (unsigned int row=0; row<height; ++row) { for (unsigned int col=0; col<width; ++col) { tmpImage[(col+m/2)+(row+n/2)*(bitmapData.Stride/sizeof(pixelType)+m-1)] = pixels[col+row*bitmapData.Stride/sizeof(pixelType)]; } }
// process every pixel with filterMask for (unsigned int row=0; row<height; ++row) { for (unsigned int col=0; col<width; ++col) { // fill the "m*n" mask with the current pixel's neighborhood for (unsigned int i=0; i<n; ++i) { for (unsigned int j=0; j<m; ++j) { filterMask.d_mask[i*m+j] = tmpImage[(col+j)+(row+i)*(bitmapData.Stride/sizeof(pixelType)+m-1)]; } }
// replace the current pixel with filter mask's response pixels[col+row*bitmapData.Stride/sizeof(pixelType)] = filterMask.response(); } }
if (p_bitmap->UnlockBits(&bitmapData) != Gdiplus::Ok) { return false; }
return true; }
// base class for filterMask, be only used for the library // others filterMask should inherit it template <typename pixelType> struct __filterMask { const unsigned int d_m; const unsigned int d_n;
std::vector<pixelType> d_mask;
// filter mask's width and heigh must be a odd, if not, it will plus one for the width or the height __filterMask(unsigned int m, unsigned int n) : d_m(m%2 ? m:m+1), d_n(n%2 ? n:n+1), d_mask(d_m*d_n) { } };
// special averaging(smoothing) filter mask, its' weights are all 1 template <typename pixelType> class averagingFilterMaskSp : public __filterMask<pixelType> { public: averagingFilterMaskSp(unsigned int m, unsigned int n) : __filterMask<pixelType>(m, n) { }
pixelType response() { return std::accumulate(d_mask.begin(), d_mask.end(), 0) / (d_m * d_n); } };
// averaging(smoothing) filter mask template <typename pixelType> class averagingFilterMask : public __filterMask<pixelType> { private: std::vector<pixelType> d_weight; // weights' vector(m*n) int d_weight_sum; // all weights' sum
public: averagingFilterMask(unsigned int m, unsigned int n, const std::vector<pixelType>& weightVec) : __filterMask<pixelType>(m, n), d_weight(weightVec) { if (weightVec.size() != d_mask.size()) { // if weight's size isn't equal to mask's size, it will change filter mask as a special filter mask d_weight.resize(d_mask.size(), 1); }
d_weight_sum = std::accumulate(d_weight.begin(), d_weight.end(), 0); }
pixelType response() { return std::inner_product(d_mask.begin(), d_mask.end(), d_weight.begin(), 0) / d_weight_sum; } };
// median filter mask template <typename pixelType> class medianFilterMask : public __filterMask<pixelType> { public: medianFilterMask(unsigned int m, unsigned int n) : __filterMask<pixelType>(m, n) { }
pixelType response() { std::sort(d_mask.begin(), d_mask.end()); return d_mask[d_mask.size()/2]; } }; }
#endif |
说明: SpatialFilterAlgo函数是滤波处理算法框架;averagingFilterMask和averagingFilterMaskSp为均值滤波器,后者掩模权值为全1;medianFilterMask是中值滤波器。
算法包括在namespace nsimgtk中。
使用方法如下:
// 创建一个Bitmap类实例 Bitmap bitmapWith3x3MaskSp(L"image/Fig3.35(a).jpg"); // 创建一个3x3均值滤波器 // 注意对于averagingFilterMask类,其构造函数必须为其第三参数传入一个std::vector作为掩模权值,大小和掩模大小相同。 // 如: std::vector<unsigned char> weight(9); // weight[0] = 1, weight[1] = 2, weight[2] = 1, // weight[0] = 2, weight[1] = 4, weight[2] = 2, // weight[0] = 1, weight[1] = 2, weight[2] = 1; // averagingFilterMask<unsigned char> avgfilterMask3x3(3,3,weight); averagingFilterMaskSp<unsigned char> avgfilterMaskSp3x3(3,3); // 使用SpatialFilterAlgo算法,第一参数为上述Bitmap类实例,由于该图像为8阶灰度图,模版参数的第一为unsigned char,一般为8位 // 第三模版参数为PixelFormat8bppIndexed(参考GDI+文档),函数第二参数为滤波器掩模对象,其余参数分别为图像起始点坐标及宽度和高度 SpatialFilterAlgo<unsigned char,PixelFormat8bppIndexed>(&bitmapWith3x3MaskSp, avgfilterMaskSp3x3, 0, 0, bitmapWith3x3MaskSp.GetWidth(), bitmapWith3x3MaskSp.GetHeight())==false)
|
图例:
(1) 下面图像分别为左图---原图像,右图---9x9均值滤波器(盒滤波器)处理的图像。图中细节部分被模糊后与背景混合。
|
(2) 下面图像分别为左图---原图像,右图---3x3中值滤波器处理的图像。处理后你可以明显地看到椒盐噪声被滤去。
|
|
6 结论
均值滤波器的作用在于为了对感兴趣的物体得到一个大概的描述而模糊图像。其原因在于每一个像素都是其邻域像素加权和的平均值,使得各个像素和其他邻域像素相融合,往往随着掩模的增大,模糊程度也增大。
中值滤波器的作用主要在于消除椒盐噪声,所谓椒盐噪声就是图像中分布广泛的黑白点,通过取邻域内的中值,有效地将灰度的极大极小值消去。另外不宜取太大的掩模,这样会在滤去椒盐噪声的同时使得图像细节被模糊化。
7 说明
这是参考 <数字图像处理>2nd 中译本完成的作业,本书原著Rafael C. Gonzalez和Richard E. Woods,译者为阮秋琦 阮宇智,机械工业出版社。感谢他们编著,翻译出版了这样出色的书籍。