中值滤波是一种非线性滤波,在处理脉冲噪声以及椒盐噪声时效果极佳,能够有效的保护好图像的边缘信息。

中值滤波的处理思路很简单,取卷积核当中所覆盖像素中的中值作为锚点的像素值即可。

如果按照遍历所有像素,再对卷积核中的像素排序取中值,那么时间复杂度会很高,需要对中值滤波进行改进。

中值滤波的改进实际上很是很好想的,无非就是一个滑动窗口取中值的问题,每次向右滑动的过程中等于在窗口中新添加添加一列窗口像素,同时减去一列窗口像素,考虑维护这个窗口中的像素信息变化即可。

这里面使用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