(三)OpenCV-Python學習—圖像平滑


  由於種種原因,圖像中難免會存在噪聲,需要對其去除。噪聲可以理解為灰度值的隨機變化,即拍照過程中引入的一些不想要的像素點。噪聲可分為椒鹽噪聲,高斯噪聲,加性噪聲和乘性噪聲等,參見: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()
GaussianBlur()  

 

   對於上面的高斯卷積核,可以由如下兩個矩陣相乘進行構建,說明高斯核是可分離卷積核,因此高斯卷積操作可以分成先進行垂直方向的一維卷積,再進行一維水平方向卷積。

          

     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()
blur()和boxFilter()

  

   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()
medianBlur()

 中值濾波自己實現代碼如下:

#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()
                
            
Medain Filter算法實現

 

   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()
bilateralFilter

    同樣,利用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()
python實現雙邊濾波

 

    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()
cv2.ximgproc.jointBilateralFilter()

     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()
cv2.ximgproc.guidedFilter

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM