由於種種原因,圖像中難免會存在噪聲,需要對其去除。噪聲可以理解為灰度值的隨機變化,即拍照過程中引入的一些不想要的像素點。噪聲可分為椒鹽噪聲,高斯噪聲,加性噪聲和乘性噪聲等,參見:https://zhuanlan.zhihu.com/p/52889476
噪聲主要通過平滑進行抑制和去除,包括基於二維離散卷積的高斯平滑,均值平滑,基於統計學的中值平滑,以及能夠保持圖像邊緣的雙邊濾波,導向濾波算法等。下面介紹其具體使用
1. 二維離散卷積
理解卷積:https://www.zhihu.com/question/22298352/answer/637156871
https://www.zhihu.com/question/22298352/answer/228543288
學習圖像平滑前,有必要了解下卷積的知識,看完上述連接,對於圖像處理中卷積應該了解幾個關鍵詞:卷積核,錨點,步長,內積,卷積模式
卷積核(kernel):用來對圖像矩陣進行平滑的矩陣,也稱為過濾器(filter)
錨點:卷積核和圖像矩陣重疊,進行內積運算后,錨點位置的像素點會被計算值取代。一般選取奇數卷積核,其中心點作為錨點
步長:卷積核沿着圖像矩陣每次移動的長度
內積:卷積核和圖像矩陣對應像素點相乘,然后相加得到一個總和,如下圖所示。(不要和矩陣乘法混淆)
卷積模式:卷積有三種模式,FULL, SAME,VALID,實際使用注意區分使用的那種模式。(參考:https://zhuanlan.zhihu.com/p/62760780)
Full:全卷積,full模式的意思是,從filter和image剛相交開始做卷積,白色部分為填0,橙色部分為image, 藍色部分為filter,filter的運動范圍如圖所示。
Same卷積:當filter的錨點(K)與image的邊角重合時,開始做卷積運算,可見filter的運動范圍比full模式小了一圈,same mode為full mode 的子集,即full mode的卷積結果包括same mode。
valid卷積:當filter全部在image里面的時候,進行卷積運算,可見filter的移動范圍較same更小了,同樣valid mode為same mode的子集。valid mode的卷積計算,填充邊界中的像素值不會參與計算,即無效的填充邊界不影響卷積,所以稱為valid mode。
python的scipy包中提供了convolve2d()函數來實現卷積運算,其參數如下:
from scipy import signal signal.convolve2d(src,kernel,mode,boundary,fillvalue) src: 輸入的圖像矩陣,只支持單通的(即二維矩陣) kernel:卷積核 mode:卷積類型:full, same, valid boundary:邊界填充方式:fill,wrap, symm fillvalue: 當boundary為fill時,邊界填充的值,默認為0
opencv中提供了flip()函數翻轉卷積核,filter2D進行same 卷積, 其參數如下:
dst = cv2.flip(src,flipCode) src: 輸入矩陣 flipCode:0表示沿着x軸翻轉,1表示沿着y軸翻轉,-1表示分別沿着x軸,y軸翻轉 dst:輸出矩陣(和src的shape一樣) cv2.filter2D(src,dst,ddepth,kernel,anchor=(-1,-1),delta=0,borderType=cv2.BORDER_DEFAULT) src: 輸入圖像對象矩陣 dst:輸出圖像矩陣 ddepth:輸出矩陣的數值類型 kernel:卷積核 anchor:卷積核錨點,默認(-1,-1)表示卷積核的中心位置 delat:卷積完后相加的常數 borderType:填充邊界類型
2 圖像平滑
2.1 高斯平滑
高斯平滑即采用高斯卷積核對圖像矩陣進行卷積操作。高斯卷積核是一個近似服從高斯分布的矩陣,隨着距離中心點的距離增加,其值變小。這樣進行平滑處理時,圖像矩陣中錨點處像素值權重大,邊緣處像素值權重小,下為一個3*3的高斯卷積核:
opencv中提供了GaussianBlur()函數來進行高斯平滑,其對應參數如下:
dst = cv2.GaussianBlur(src,ksize,sigmaX,sigmay,borderType)
src: 輸入圖像矩陣,可為單通道或多通道,多通道時分別對每個通道進行卷積
dst:輸出圖像矩陣,大小和數據類型都與src相同
ksize:高斯卷積核的大小,寬,高都為奇數,且可以不相同
sigmaX: 一維水平方向高斯卷積核的標准差
sigmaY: 一維垂直方向高斯卷積核的標准差,默認值為0,表示與sigmaX相同
borderType:填充邊界類型
代碼使用示例和效果如下:(相比於原圖,平滑后圖片變模糊)

#coding:utf-8 import cv2 as cv import matplotlib.pyplot as plt import numpy as np img = cv.imread(r"C:\Users\Administrator\Desktop\timg.jpg") img_gauss = cv.GaussianBlur(img,(3,3),1) cv.imshow("img",img) cv.imshow("img_gauss",img_gauss) cv.waitKey(0) cv.destroyAllWindows()
對於上面的高斯卷積核,可以由如下兩個矩陣相乘進行構建,說明高斯核是可分離卷積核,因此高斯卷積操作可以分成先進行垂直方向的一維卷積,再進行一維水平方向卷積。
opencv中getGaussianKernel()能用來產生一維的高斯核,分別獲得水平和垂直的高斯核,分兩步也能完成高斯卷積,獲得和GaussianBlur一樣的結果。其參數如下:
cv2.getGaussianKernel(ksize,sigma,ktype)
ksize:奇數,一維核長度
sigma:標准差
ktype:數據格式,應該為CV_32F 或者 CV_64F
返回矩陣如下:垂直的矩陣
[[ 0.27406862]
[ 0.45186276]
[ 0.27406862]

#coding:utf-8 import cv2 as cv import matplotlib.pyplot as plt import numpy as np from scipy import signal #convolve2d只是對單通道進行卷積,若要實現cv.GaussianBlur()多通道高斯卷積,需要拆分三個通道進行,再合並 def gaussianBlur(img,h,w,sigma,boundary="fill",fillvalue=0): kernel_x = cv.getGaussianKernel(w,sigma,cv.CV_64F) #默認得到的為垂直矩陣 kernel_x = np.transpose(kernel_x) #轉置操作,得到水平矩陣 #水平方向卷積 gaussian_x = signal.convolve2d(img,kernel_x,mode="same",boundary=boundary,fillvalue=fillvalue) #垂直方向卷積 kernel_y = cv.getGaussianKernel(h,sigma,cv.CV_64F) gaussian_xy = signal.convolve2d(gaussian_x,kernel_y,mode="same",boundary=boundary,fillvalue=fillvalue) #cv.CV_64F數據轉換為uint8 gaussian_xy = np.round(gaussian_xy) gaussian_xy = gaussian_xy.astype(np.uint8) return gaussian_xy if __name__=="__main__": img = cv.imread(r"C:\Users\Administrator\Desktop\timg.jpg",0) img_gauss = gaussianBlur(img,3,3,1) cv.imshow("img",img) cv.imshow("img_gauss",img_gauss) cv.waitKey(0) cv.destroyAllWindows()
2.2 均值平滑
高斯卷積核,對卷積框中像素值賦予不同權重,而均值平滑賦予相同權重,一個3*5的均值卷積核如下,均值卷積核也是可分離的。
opencv的boxFilter()函數和blur()函數都能用來進行均值平滑,其參數如下:
cv2.boxFilter(src,ddepth,ksize,dst,anchor,normalize,borderType) src: 輸入圖像對象矩陣, ddepth:數據格式,位深度 ksize:高斯卷積核的大小,格式為(寬,高) dst:輸出圖像矩陣,大小和數據類型都與src相同 anchor:卷積核錨點,默認(-1,-1)表示卷積核的中心位置 normalize:是否歸一化 (若卷積核3*5,歸一化卷積核需要除以15) borderType:填充邊界類型 cv2.blur(src,ksize,dst,anchor,borderType) src: 輸入圖像對象矩陣,可以為單通道或多通道 ksize:高斯卷積核的大小,格式為(寬,高) dst:輸出圖像矩陣,大小和數據類型都與src相同 anchor:卷積核錨點,默認(-1,-1)表示卷積核的中心位置 borderType:填充邊界類型
示例代碼和使用效果如下:

#coding:utf-8 import cv2 as cv import matplotlib.pyplot as plt import numpy as np img = cv.imread(r"C:\Users\Administrator\Desktop\timg.jpg") img_blur = cv.blur(img,(3,5)) # img_blur = cv.boxFilter(img,-1,(3,5)) cv.imshow("img",img) cv.imshow("img_blur",img_blur) cv.waitKey(0) cv.destroyAllWindows()
2.3 中值平滑
中值平滑也有核,但並不進行卷積計算,而是對核中所有像素值排序得到中間值,用該中間值來代替錨點值。opencv中利用medianBlur()來進行中值平滑,中值平滑特別適合用來去除椒鹽噪聲,其參數如下:
cv2.medianBlur(src,ksize,dst) src: 輸入圖像對象矩陣,可以為單通道或多通道 ksize:核的大小,格式為 3 #注意不是(3,3) dst:輸出圖像矩陣,大小和數據類型都與src相同
其使用代碼及效果如下:(加上的白點噪聲都被平滑掉了)

#coding:utf-8 import cv2 as cv import matplotlib.pyplot as plt import numpy as np import random img = cv.imread(r"C:\Users\Administrator\Desktop\timg.jpg") rows,cols = img.shape[:2] #加入椒鹽噪聲 for i in range(100): r = random.randint(0,rows-1) c = random.randint(0,cols-1) img[r,c]=255 img_medianblur = cv.medianBlur(img,5) cv.imshow("img",img) cv.imshow("img_medianblur",img_medianblur) cv.waitKey(0) cv.destroyAllWindows()
中值濾波自己實現代碼如下:

#coding:utf-8 import heapq import cv2 #實現圖片的中值濾波算法 # 2D median filter with no stride, zero padding def medain_filter(img, kernel_w, kernel_h): """ img: cv2 matrix kernel_w: kernel width kernel_h: kernel height """ rows, cols = img.shape[:2] for i in range(rows): for j in range(cols): left = max(0, j-kernel_w//2) right = min(cols-1, j+kernel_w//2) upper = max(0, i-kernel_h//2) lower = min(rows-1, i+kernel_h//2) #kernel中的像素點放入堆中 hq = [] for m in range(upper, lower+1): for n in range(left, right+1): heapq.heappush(hq, img[m, n]) size = (right-left+1)*(lower-upper+1) if size&1: #奇數個元素,取中間值 for k in range(0, (size+1)//2): median = heapq.heappop(hq) img[i, j] = median else: #偶數個元素,取中間兩個元素平均值 for k in range(0, (size+1)//2): median1 = heapq.heappop(hq) median2 = heapq.heappop(hq) img[i, j] = (median1+median2)//2 return img if __name__ == "__main__": img = cv2.imread(r"C:\Users\Administrator\Desktop\dog.jpg", 0) img2 = medain_filter(img, 3, 3) cv2.imshow("img", img) cv2.imshow("img2", img2) cv2.waitKey(0) cv2.destroyAllWindows()
2.4 雙邊濾波
相比於上面幾種平滑算法,雙邊濾波在平滑的同時還能保持圖像中物體的輪廓信息。雙邊濾波在高斯平滑的基礎上引入了灰度值相似性權重因子,所以在構建其卷積核核時,要同時考慮空間距離權重和灰度值相似性權重。在進行卷積時,每個位置的鄰域內,根據和錨點的距離d構建距離權重模板,根據和錨點灰度值差異r構建灰度值權重模板,結合兩個模板生成該位置的卷積核。opencv中的bilateralFilter()函數實現了雙邊濾波,其參數對應如下:
dst = cv2.bilateralFilter(src,d,sigmaColor,sigmaSpace,borderType) src: 輸入圖像對象矩陣,可以為單通道或多通道 d:用來計算卷積核的領域直徑,如果d<=0,從sigmaSpace計算d sigmaColor:顏色空間濾波器標准偏差值,決定多少差值之內的像素會被計算(構建灰度值模板) sigmaSpace:坐標空間中濾波器標准偏差值。如果d>0,設置不起作用,否則根據它來計算d值(構建距離權重模板)
其使用代碼及效果如下:

#coding:utf-8 import cv2 as cv import matplotlib.pyplot as plt import numpy as np import random import math img = cv.imread(r"C:\Users\Administrator\Desktop\timg.jpg") img_bilateral = cv.bilateralFilter(img,0,0.2,40) cv.imshow("img",img) cv.imshow("img_bilateral",img_bilateral) cv.waitKey(0) cv.destroyAllWindows()
同樣,利用numpy也可以自己實現雙邊濾波算法,同樣需要對每個通道進行雙邊濾波,最后進行合並,下面代碼只對單通道進行了雙邊濾波,代碼和效果如下圖:

#coding:utf-8 import cv2 as cv import matplotlib.pyplot as plt import numpy as np import random import math def getDistanceWeight(sigmaSpace,H,W): r,c = np.mgrid[0:H:1,0:W:1] r = r-(H-1)/2 c =c-(W-1)/2 distanceWeight = np.exp(-0.5*(np.power(r,2)+np.power(c,2))/math.pow(sigmaSpace,2)) return distanceWeight def bilateralFilter(img,H,W,sigmaColor,sigmaSpace): distanceWeight = getDistanceWeight(sigmaSpace,H,W) cH = (H-1)/2 cW = (W-1)/2 rows,cols = img.shape[:2] bilateralImg = np.zeros((rows,cols),np.float32) for r in range(rows): for c in range(cols): pixel = img[r,c] rTop = 0 if r-cH<0 else r-cH rBottom = rows-1 if r+cH>rows-1 else r+cH cLeft = 0 if c-cW<0 else c-cW cRight = cols-1 if c+cW>cols-1 else c+cW #權重模板作用區域 region=img[rTop:rBottom+1,cLeft:cRight+1] #灰度值差異權重 colorWeight = np.exp(0.5*np.power(region-pixel,2.0)/math.pow(sigmaColor,2)) print(colorWeight.shape) #距離權重 distanceWeightTemp = distanceWeight[cH-(r-rTop):rBottom-r+cH+1,cW-(c-cLeft):cRight-c+cW+1] print(distanceWeightTemp.shape) #權重相乘並歸一化 weightTemp = colorWeight*distanceWeightTemp weightTemp = weightTemp/np.sum(weightTemp) bilateralImg[r][c]=np.sum(region*weightTemp) return bilateralImg if __name__=="__main__": img = cv.imread(r"C:\Users\Administrator\Desktop\timg.jpg",0) img_temp = img/255.0 img_bilateral = bilateralFilter(img_temp,3,3,0.2,19)*255 img_bilateral[img_bilateral>255] = 255 img_bilateral = img_bilateral.astype(np.uint8) cv.imshow("img",img) cv.imshow("img_bilateral",img_bilateral) cv.waitKey(0) cv.destroyAllWindows()
2.5 聯合雙邊濾波
雙邊濾波是根據原圖中不同位置灰度相似性來構建相似性權重模板,而聯合濾波是先對原圖進行高斯平滑,然后根據平滑后的圖像灰度值差異建立相似性模板,再與距離權重模板相乘得到最終的卷積核,最后再對原圖進行處理。所以相比於雙邊濾波,聯合雙邊濾波只是建立灰度值相似性模板的方法不一樣。
聯合雙邊濾波作為邊緣保留濾波算法時,進行joint的圖片即為自身原圖片,如果將joint換為其他引導圖片,聯合雙邊濾波算法還可以用來實現其他功能。opencv 2中不支持聯合雙邊濾波,opencv 3中除了主模塊,還引入了contrib,其中的ximgproc模塊包括了聯合雙邊濾波的算法。因此如果需要使用opencv的聯合雙邊濾波,需要安裝opencv-contrib-python包。
安裝opencv主模塊和contrib附加模塊步驟: pip uninstall opencv-python (如果已經安裝opencv-python包,先卸載) pip install opencv-contrib-python
聯合雙邊濾波: cv2.xmingproc.jointBilateralFilter(), 其相關參數如下:
dst = cv2.xmingproc.jointBilateralFilter(joint,src,d,sigmaColor,sigmaSpace,borderType) joint: 進行聯合濾波的導向圖像,可以為單通道或多通道,保持邊緣的濾波算法時常采用src src: 輸入圖像對象矩陣,可以為單通道或多通道 d:用來計算卷積核的領域直徑,如果d<0,從sigmaSpace計算d sigmaColor:顏色空間濾波器標准偏差值,決定多少差值之內的像素會被計算(構建灰度值模板) sigmaSpace:坐標空間中濾波器標准偏差值。如果d>0,設置不起作用,否則根據它來計算d值(構建距離權重模板)
下面是聯合雙邊濾波的使用代碼和效果:(采用src的高斯平滑圖片作為joint)

#coding:utf-8 import cv2 as cv import matplotlib.pyplot as plt import numpy as np import random import math src = cv.imread(r"C:\Users\Administrator\Desktop\timg.jpg") joint = cv.GaussianBlur(src,(7,7),1,0) dst = cv.ximgproc.jointBilateralFilter(joint,src,33,2,0) # dst = cv.ximgproc.jointBilateralFilter(src,src,33,2,0) #采用src作為joint cv.imshow("img",src) cv.imshow("joint",joint) cv.imshow("dst",dst) cv.waitKey(0) cv.destroyAllWindows()
2.6 導向濾波
導向濾波也是需要一張圖片作為引導圖片,來表明邊緣,物體等信息,作為保持邊緣濾波算法,可以采用自身作為導向圖片。opencv 2中也暫不支持導向濾波, 同樣在opencv-contrib-python包的ximgproc模塊提供了導向濾波函。
導向濾波具體原理可以參考:https://blog.csdn.net/baimafujinji/article/details/74750283
opencv中導向濾波cv2.ximgproc.guidedFilter()的參數如下:
導向濾波 cv2.ximgproc.guidedFilter(guide,src,radius,eps,dDepth) guide: 導向圖片,單通道或三通道 src: 輸入圖像對象矩陣,可以為單通道或多通道 radius:用來計算卷積核的領域直徑 eps:規范化參數, eps的平方類似於雙邊濾波中的sigmaColor(顏色空間濾波器標准偏差值) (regularization term of Guided Filter. eps2 is similar to the sigma in the color space into bilateralFilter.) dDepth: 輸出圖片的數據深度
其代碼使用和效果如下:

#coding:utf-8 import cv2 as cv src = cv.imread(r"C:\Users\Administrator\Desktop\timg.jpg") dst = cv.ximgproc.guidedFilter(src,src,33,2,-1) cv.imshow("img",src) cv.imshow("dst",dst) cv.waitKey(0) cv.destroyAllWindows()