原理
中值滤波是空域中常用的一种滤波方式,是一种非线性的滤波。它的原理就是将窗口像素排序,取中值,然后移动窗口,不断重复取中值的过程。
但是,可以发现,每次移动窗口,都需要对像素点进行排序,从而选取中间的那个像素点。每次重新排序的效率特别低,为了优化这个排序,便有了快速中值滤波。
快速中值滤波是中值滤波的优化版,它利用了窗口每次平移时,没有移出窗口的像素点还是排好序的,因此,只需要把新加入的像素点插入到其中即可完成排序。此外,由于我们并不需要一个完整的排序数列, 只需要找到中值就可以了。
基于上面两点,快速中值滤波采用的是直方图的方式来统计像素点,横坐标是像素点的值,纵坐标是窗口中像素点的个数。设置一个“光标”,在横轴上左右移动,当两边像素点相等时,对应的像素点就是要找的中值。然后移动窗口,更新直方图,再次移动“光标”,找到中值,不断反复。
需要注意的是,“光标”的每次移动,都只能移动在数目不为 0 的像素点上,否则,取到的中值很可能不在这个窗口中。
步骤
-
第一步:
设置门限th = N*M/2。门限是用来判断像素点是否是中值的,它是窗口大小的一半,如果不知道有啥用,看第五步的用法。 -
第二步:
将窗口移动到一个新行的开始,建立窗口像素的直方图,通过直方图确定中值 med,记下亮度小于或等于 med 的像素数目到变量 n 中。 -
第三步:
对于最左列的每个像素,去掉每一个元素,并将直方图中的相应的数值更新,然后更新n的值。 -
第四步:
同理,与第三步一样,对于最右列的每个像素,增加每一个元素的值,并将直方图中的相应的数值进行更新。 -
第五步:
然后判断n的值与门限 th 的大小。如果 n > th,则将 med 进行递减操作;如果n < th 则将 med 进行递增操作。直到 n 超过 th 为止。得到的 med 就是需要的中值。
代码
R、G、B 三个通道分别计算(可以一起计算,但单通道计算更清晰)
窗口大小为 3*3 的代码:
# -*- coding: UTF-8 -*-
import numpy as np
import cv2
def quickMedianFiltering(img) :
B, G, R = cv2.split(img)
# 对 蓝色通道 进行中值滤波
H = np.zeros(256, dtype=int) # 直方图
for row in range(1, len(B) - 1) :
# 到达一个新的行 初始化
H = np.zeros(256, dtype=int) # 直方图
# 求中值
med = np.uint8(np.median(B[row - 1 : row + 2, 0:3]))
n = 0
for i in range(-1, 2) :
for j in range(0, 3) :
H[B[row+i][j]] = H[B[row+i][j]] + 1
if B[row+i][j] <= med :
n = n + 1
for col in range(1, len(B[row]) - 1) :
if col == 1 :
None
# 移到下一列
else :
# 更新直方图 并计算 n 的值
for i in range(-1, 2) :
# 对左列元素 值减一
H[B[row+i][col-2]] = H[B[row+i][col-2]] - 1
if B[row+i][col-2] <= med :
n = n - 1
# 对右列元素 值加一
H[B[row+i][col+1]] = H[B[row+i][col+1]] + 1
if B[row+i][col+1] <= med :
n = n + 1
# 重新计算中值
if n > 5 :
while n > 5 :
if med == 0 :
break
n = n - H[med]
med = med - 1
elif n < 5 :
while n < 5 :
med = med + 1
n = n + H[med]
sum = 0
for k in range(med + 1) :
sum = sum + H[k]
# 更新中值后的直方图
H[B[row][col]] = H[B[row][col]] - 1
if med < B[row][col] :
n = n + 1
B[row][col] = med
H[med] = H[med] + 1
# 对 绿色通道 进行中值滤波
H = np.zeros(256, dtype=int) # 直方图
for row in range(1, len(G) - 1) :
# 到达一个新的行 初始化
H = np.zeros(256, dtype=int) # 直方图
# 求中值
med = np.uint8(np.median(G[row - 1 : row + 2, 0:3]))
if med == -128 :
print(G[row - 1 : row + 2, 0:3])
n = 0
for i in range(-1, 2) :
for j in range(0, 3) :
H[G[row+i][j]] = H[G[row+i][j]] + 1
if G[row+i][j] <= med :
n = n + 1
for col in range(1, len(G[row]) - 1) :
if col == 1 :
None
# 移到下一列
else :
# 更新直方图 并计算 n 的值
for i in range(-1, 2) :
# 对左列元素 值减一
H[G[row+i][col-2]] = H[G[row+i][col-2]] - 1
if G[row+i][col-2] <= med :
n = n - 1
# 对右列元素 值加一
H[G[row+i][col+1]] = H[G[row+i][col+1]] + 1
if G[row+i][col+1] <= med :
n = n + 1
# 重新计算中值
if n > 5 :
while n > 5 :
if med == 0 :
break
n = n - H[med]
med = med - 1
elif n < 5 :
while n < 5 :
med = med + 1
n = n + H[med]
# 更新中值后的直方图
H[G[row][col]] = H[G[row][col]] - 1
if med < G[row][col] :
n = n + 1
G[row][col] = med
H[med] = H[med] + 1
# 对 红色通道 进行中值滤波
H = np.zeros(256, dtype=int) # 直方图
for row in range(1, len(R) - 1) :
# 到达一个新的行 初始化
H = np.zeros(256, dtype=int) # 直方图
# 求中值
med = np.uint8(np.median(R[row - 1 : row + 2, 0:3]))
if med == -128 :
print(R[row - 1 : row + 2, 0:3])
n = 0
for i in range(-1, 2) :
for j in range(0, 3) :
H[R[row+i][j]] = H[R[row+i][j]] + 1
if R[row+i][j] <= med :
n = n + 1
for col in range(1, len(R[row]) - 1) :
if col == 1 :
None
# 移到下一列
else :
# 更新直方图 并计算 n 的值
for i in range(-1, 2) :
# 对左列元素 值减一
H[R[row+i][col-2]] = H[R[row+i][col-2]] - 1
if R[row+i][col-2] <= med :
n = n - 1
# 对右列元素 值加一
H[R[row+i][col+1]] = H[R[row+i][col+1]] + 1
if R[row+i][col+1] <= med :
n = n + 1
# 重新计算中值
if n > 5 :
while n > 5 :
if med == 0 :
break
n = n - H[med]
med = med - 1
elif n < 5 :
while n < 5 :
med = med + 1
n = n + H[med]
sum = 0
# 更新中值后的直方图
H[R[row][col]] = H[R[row][col]] - 1
if med < R[row][col] :
n = n + 1
R[row][col] = med
H[med] = H[med] + 1
return cv2.merge([B,G,R])
最后
快速中值滤波主要是利用了直方图的思想,虽然思路很简单,但写代码的时候,也遇到不少bug,所幸最后写完了。
如果有任何错误的地方,请联系我改正,非常感谢。