因爲種種緣由,圖像中不免會存在噪聲,須要對其去除。噪聲能夠理解爲灰度值的隨機變化,即拍照過程當中引入的一些不想要的像素點。噪聲可分爲椒鹽噪聲,高斯噪聲,加性噪聲和乘性噪聲等,參見:https://zhuanlan.zhihu.com/p/52889476python
噪聲主要經過平滑進行抑制和去除,包括基於二維離散卷積的高斯平滑,均值平滑,基於統計學的中值平滑,以及可以保持圖像邊緣的雙邊濾波,導向濾波算法等。下面介紹其具體使用算法
1. 二維離散卷積dom
理解卷積:https://www.zhihu.com/question/22298352/answer/637156871ide
https://www.zhihu.com/question/22298352/answer/228543288函數
學習圖像平滑前,有必要了解下卷積的知識,看完上述鏈接,對於圖像處理中卷積應該瞭解幾個關鍵詞:卷積核,錨點,步長,內積,卷積模式學習
卷積核(kernel):用來對圖像矩陣進行平滑的矩陣,也稱爲過濾器(filter)ui
錨點:卷積核和圖像矩陣重疊,進行內積運算後,錨點位置的像素點會被計算值取代。通常選取奇數卷積核,其中心點做爲錨點spa
步長:卷積核沿着圖像矩陣每次移動的長度.net
內積:卷積核和圖像矩陣對應像素點相乘,而後相加獲得一個總和,以下圖所示。(不要和矩陣乘法混淆)3d
卷積模式:卷積有三種模式,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()
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()