中值滤波是一种非线性滤波,在处理脉冲噪声以及椒盐噪声时效果极佳,能够有效的保护好图像的边缘信息。
中值滤波的处理思路很简单,取卷积核当中所覆盖像素中的中值作为锚点的像素值即可。
如果按照遍历所有像素,再对卷积核中的像素排序取中值,那么时间复杂度会很高,需要对中值滤波进行改进。
中值滤波的改进实际上很是很好想的,无非就是一个滑动窗口取中值的问题,每次向右滑动的过程中等于在窗口中新添加添加一列窗口像素,同时减去一列窗口像素,考虑维护这个窗口中的像素信息变化即可。
这里面使用huang算法,该思路是这个人提出来的,算法思路如下:
在计算中值的办法中,不使用排序,而是使用像素直方图,也就是记录像素值的哈希。首先设定阈值threshold,这个threshold就是窗口的中心位置,即ksize×ksize/2+1,kisze为窗口尺寸。
每次在计算中值的过程中,从小到大累加像素直方图的值,如果该值大于等于,此时对应的像素值就是中值了。
例如ksize=3的窗口如下:
⎡⎣⎢122235154⎤⎦⎥(3) \left[\begin{matrix}1 & 2 & 1 \\2 & 3 & 5 \\2 & 5 & 4\end{matrix}\right] \tag{3}
⎣
⎡
1
2
2
2
3
5
1
5
4
⎦
⎤
(3)
对该窗口中的值计算像素直方图如下,threshold=3×3/2+1=5
1:2(表示像素值为1的有2个)
2:3
3:1
4:1
5:2
因为2+3≥5,所以中值为2
每次滑动窗口的过程中,如果窗口在第一列,那么直接计算直方图。否则向右移动,在直方图中减去左侧离开窗口中像素,在右侧添加进入窗口中的像素。
此外,也可以让窗口按照蛇形来移动,这样也会避免每次窗口在第一列时需要重新计算的问题。
1 void AddSultPapperNoise(const Mat &src, Mat &dst,int num)//添加椒盐噪声
2 {
3 dst = src.clone();
4 uchar *pd=dst.data;
5 int row, col, cha;
6 srand((unsigned)time(NULL));
7 while (num--)
8 {
9 row = rand() % dst.rows;
10 col = rand() % dst.cols;
11 cha = rand() % dst.channels();
12 pd[(row*dst.cols + col)*dst.channels() + cha] = 0;
13 }
14 }
15
16
17 int GetMediValue(const int histogram[], int thresh)//计算中值
18 {
19 int sum = 0;
20 for (int i = 0; i < (1 << 16); i++)
21 {
22 sum += histogram[i];
23 if (sum >= thresh)
24 return i;
25 }
26 return (1 << 16);
27 }
28
29 void MyFastMediFilter(const Mat &src, Mat &dst, int ksize)
30 {
31 CV_Assert(ksize % 2 == 1);
32
33 Mat tmp;
34 int len = ksize / 2;
35 tmp.create(Size(src.cols + len, src.rows + len), src.type());//添加边框
36 dst.create(Size(src.cols, src.rows), src.type());
37
38
39 int channel = src.channels();
40 uchar *ps = src.data;
41 uchar *pt = tmp.data;
42 for (int row = 0; row < tmp.rows; row++)//添加边框的过程
43 {
44 for (int col = 0; col < tmp.cols; col++)
45 {
46 for (int c = 0; c < channel; c++)
47 {
48 if (row >= len && row < tmp.rows - len && col >= len && col < tmp.cols - len)
49 pt[(tmp.cols * row + col) * channel + c] = ps[(src.cols * (row - len) + col - len) * channel + c];
50 else
51 pt[(tmp.cols * row + col) * channel + c] = 0;
52 }
53 }
54 }
55 int Hist[(1 << 16)] = { 0 };
56 uchar *pd = dst.data;
57 ushort val = 0;
58 pt = tmp.data;
59 for (int c = 0; c < channel; c++)//每个通道单独计算
60 {
61 for (int row = len; row < tmp.rows - len; row++)
62 {
63 for (int col = len; col < tmp.cols - len; col++)
64 {
65
66 if (col == len)
67 {
68 memset(Hist, 0, sizeof(Hist));
69 for (int x = -len; x <= len; x++)
70 {
71 for (int y = -len; y <= len; y++)
72 {
73 val = pt[((row + x) * tmp.cols + col + y) * channel + c];
74 Hist[val]++;
75 }
76 }
77 }
78 else
79 {
80 int L = col - len - 1;
81 int R = col + len;
82 for (int y = -len; y <= len; y++)
83 {
84 int leftInd = ((row + y) * tmp.cols + L) * channel + c;
85 int rightInd = ((row + y) * tmp.cols + R) * channel + c;
86 Hist[pt[leftInd]]--;
87 Hist[pt[rightInd]]++;
88 }
89 }
90 val = GetMediValue(Hist, ksize*ksize / 2 + 1);
91 pd[(dst.cols * (row - len) + col - len) * channel + c] = val;
92
93 }
94 }
95 }
96 }
97