您的位置:首页 > 编程语言 > C语言/C++

中值滤波,C语言实现

2017-03-19 20:41 246 查看

中值滤波,C语言实现

本来先注册的是cnblogs,发出来一看,代码段的风格实在是有些丑陋,不符合我的审美,便注册了CSDN。在企图写的时候无意中发现居然还有MarkDown编辑器!听说这是个神器,刚好可以在写博客的时候学习MarkDown的用法,一举两得,果断放弃了cnblogs转投CSDN了!

在学习的道路上,看了许多博客,受益良多。随着看过的内容越来越多,有时很难再找到之前看过的内容,遂决定自己也开一个博客,记录学习的历程。主要是为了方便自己查阅,也许某一天,也能帮助到别人。

最近在做毕业设计,需要使用到中值滤波这样的常规图像处理算法,往常都是使用OpenCV中自带的函数进行滤波,非常方便。然而,这次滤波的对象不是一个“正经”的图,它是内存中的一块连续数据,转换成OpenCV中的Mat并不方便,也不搞笑。只好采用直接读取数据进行滤波,需要自己写一段中值滤波。

好在中值滤波思想很简单,很容易就能想通是怎么做的,简单的来说就是以一个点的一定邻域内数据的中值来代替这个点的数值。

以下内容来自foreverhuylee的博客:关于中值滤波算法,以及C语言实现

什么是中值滤波?

中值滤波是对一个滑动窗口内的诸像素灰度值排序,用其中值代替窗口中心象素的原来灰度值,它是一种非线性的图像平滑法,它对脉冲干扰级椒盐噪声的抑制效果好,在抑制随机噪声的同时能有效保护边缘少受模糊。

中值滤波可以过滤尖峰脉冲。目的在于我们对于滤波后的数据更感兴趣。滤波后的数据保留的原图像的变化趋势,同时去除了尖峰脉冲对分析造成的影响。

以一维信号的中值滤波举例。对灰度序列80、120、90、200、100、110、70,如果按大小顺序排列,其结果为70、80、90、10O、110、120、200,其中间位置上的灰度值为100,则该灰度序列的中值即为100。一维信号中值滤波实际上就是用中值代替规定位置(一般指原始信号序列中心位置)的信号值。对前面所举的序列而言,中值滤波的结果是用中值100替代序列80、120、90、200、100、110、70中的信号序列中心位置值200,得到的滤波序列就是80、120、90、100、100、110、70。如果在此序列中200是一个噪声信号,则用此方法即可去除这个噪声点。

二维中值滤波算法是:对于一幅图像的象素矩阵,取以目标象素为中心的一个子矩阵窗口,这个窗口可以是3*3 ,5*5 等根据需要选取,对窗口内的象素灰度排序,取中间一个值作为目标象素的新灰度值。窗口示例如ooooxoooo上面x为目标象素,和周围o组成3*3矩阵Array,然后对这9个元素的灰度进行排序,以排序后的中间元素Array[4]为x的新灰度值,如此就完成对象素x的中值滤波,再迭代对其他需要的象素进行滤波即可。

图像处理中,中值滤波的实现方法:

通过从图像中的某个采样窗口取出奇数个数据进行排序

用排序后的中值取代要处理的数据即可

中值滤波的算法实现过程,重点是排序,最常用的冒泡排序~~

把滤波区间的数据从小到大进行排序,然后取中值,(如果是奇数个数据,那么中值就只有一个了,如果偶数个数据,中值有两个,可以对两个数据再求平均)

下面是一个C语言实现中值滤波的函数:

unsigned char GetMedianNum(int * bArray, int iFilterLen)
{
int i,j;// 循环变量
unsigned char bTemp;

// 用冒泡法对数组进行排序
for (j = 0; j < iFilterLen - 1; j ++)
{
for (i = 0; i < iFilterLen - j - 1; i ++)
{
if (bArray[i] > bArray[i + 1])
{
// 互换
bTemp = bArray[i];
bArray[i] = bArray[i + 1];
bArray[i + 1] = bTemp;
}
}
}

// 计算中值
if ((iFilterLen & 1) > 0)
{
// 数组有奇数个元素,返回中间一个元素
bTemp = bArray[(iFilterLen + 1) / 2];
}
else
{
// 数组有偶数个元素,返回中间两个元素平均值
bTemp = (bArray[iFilterLen / 2] + bArray[iFilterLen / 2 + 1]) / 2;
}

return bTemp;
}


注:bArray 是一个整形指针,我们传入的一般是一个数组,用来存储待排序的数据

iFilterLen 是滤波器的长度

用在图像处理中时,由于像素的取值范围是0~255,刚好是unsigned char 的范围,所以函数的返回值是unsigned char,如果我们要处理的数是float型,或其他类型,返回值也可以更改~~返回值是bTemp,也即是我们想得到的中值

/*************************************************************************
* 函数名称:
*   MedianFilter()
* 参数:
*   int   iFilterH         - 滤波器的高度
*   int   iFilterW         - 滤波器的宽度
*   int   iFilterMX        - 滤波器的中心元素X坐标
*   int   iFilterMY        - 滤波器的中心元素Y坐标
* 说明:
*   该函数对DIB图像进行中值滤波。
************************************************************************/
#define iFilterW 1
#define iFilterH 1
#define iFilterMX 1
#define iFilterMY 1
#define WIDTHBYTES(bits)    (((bits) + 31) / 32 * 4)

unsigned char GetMedianNum(int * bArray, int iFilterLen);
void MedianFilter(unsigned char *pImg1,unsigned char *pImg,int nWidth,int nHeight)
{
unsigned char   *lpSrc;                         // 指向源图像的指针
unsigned char   *lpDst;                         // 指向要复制区域的指针
int         aValue[iFilterH*iFilterW];          // 指向滤波器数组的指针
int         i,j,k,l;                            // 循环变量
int         lLineBytes;                         // 图像每行的字节数
lLineBytes = WIDTHBYTES(nWidth * 8);
for ( i=0;i<nWidth;i++,pImg++ )
(*pImg)=0;
// 开始中值滤波
// 行(除去边缘几行)
for(i = iFilterMY; i < nHeight - iFilterH + iFilterMY + 1; i++)
{
// 列(除去边缘几列)
for(j = iFilterMX; j < nWidth - iFilterW + iFilterMX + 1; j++)
{
// 指向新DIB第i行,第j个象素的指针
lpDst = pImg + lLineBytes * (nHeight - 1 - i) + j;

// 读取滤波器数组
for (k = 0; k < iFilterH; k++)
{
for (l = 0; l < iFilterW; l++)
{
// 指向DIB第i - iFilterMY + k行,第j - iFilterMX + l个象素的指针
lpSrc = pImg1 + lLineBytes * (nHeight - 1 - i + iFilterMY - k) + j - iFilterMX + l;

// 保存象素值
aValue[k * iFilterW + l] = *lpSrc;
}
}

// 获取中值
* lpDst = GetMedianNum(aValue, iFilterH * iFilterW);
}
}

}

unsigned char GetMedianNum(int * bArray, int iFilterLen)
{
int     i,j;            // 循环变量
unsigned char bTemp;

// 用冒泡法对数组进行排序
for (j = 0; j < iFilterLen - 1; j ++)
{
for (i = 0; i < iFilterLen - j - 1; i ++)
{
if (bArray[i] > bArray[i + 1])
{
// 互换
bTemp = bArray[i];
bArray[i] = bArray[i + 1];
bArray[i + 1] = bTemp;
}
}
}

// 计算中值
if ((iFilterLen & 1) > 0)
{
// 数组有奇数个元素,返回中间一个元素
bTemp = bArray[(iFilterLen + 1) / 2];
}
else
{
// 数组有偶数个元素,返回中间两个元素平均值
bTemp = (bArray[iFilterLen / 2] + bArray[iFilterLen / 2 + 1]) / 2;
}

return bTemp;
}


做人不能光伸手,自己也得动动脑

他的代码中排序使用了一个冒泡排序,虽然说可能提升不大,但是我还是想把它改成快排,万一能快一点是一点。刚好前一阵LeetCode上写过快排的代码,拿来改一改即可使用了。由于我的图数据格式是uint16_t,窗口又是5x5之类的,所以做了一些修改。

// 快速排序,输入数组为nums,起点下标left,终点right
void sort(uint16_t* nums, int left, int right)
{
if(left >= right)
return;

uint16_t temp;
int i = left;
int j = right;

temp = nums[left];
while(i < j)
{
while(i < j && nums[j] >= temp)
--j;
nums[i] = nums[j];
while(i < j && nums[i] <= temp)
++i;
nums[j] = nums[i];
}
nums[i] = temp;

sort(nums, left, i - 1);
sort(nums, i + 1, right);
}


再用一个函数把中间值挑出来即可,听说inline可以内联函数令运行速度加快一些,试一下不亏,反正只是建议编译器这么做:

inline uint16_t GetMedianNum(uint16_t* array, int length)
{
uint16_t ans = 0;

sort(array, 0, length - 1);
// 由于我的中值滤波器总是3x3或者5x5这样的,只会有奇数个数,不需要判断奇偶
ans = array[length / 2];

return ans;
}


有了这些函数以后,主函数其实就非常简单了:

void MidFilter(uint16_t* src, uint16_t* dst, int Height, int Width, int size)
{
int semi_size = size / 2;   // 半个窗口大小,用来确定边界
int length = size * size;
uint16_t array[length] = {0};   // 存放窗口内数据

// memcpy(dst, src, Height * Width * sizeof(uint16_t));     // 额外复制了很多数据,并不需要

for(int x = 0;x < Height;++x)
{
for(int y = 0;y < Width;++y)
{
// 边界检测,若越界则直接将原图赋值过去
if((x < semi_size) || (x >= Height - semi_size) || (y < semi_size)
|| (y >= Width - semi_size))
{
*(dst + x * Width + y) = *(src + x * Width + y);
continue;
}
int count = 0;
// 将窗口内的数据存入array中
for(int i = x - semi_size;i < x + semi_size + 1;++i)
for(int j = y - semi_size;j < y + semi_size + 1;++j)
array[count++] = *(src + i * Width + j);
*(dst + x * Width + y) = getMedianNum(array, length);
}
}
}


等我去实验室跑一下这段代码,再回来确认是否可行。

没有跑过果然是看不出问题的,居然用break代替了continue,修改过后可以正常运行了。

又打脸了,发现还是有些错误,边界啊边界,这些地方还是得细心一些,没有越界所有不会报错,反而更难发现,要不是对比发现输出图像有些不对,真的以为做对了呢。再次更正。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c语言 中值滤波 c++