OpenCV中的圖像處理 —— 圖像閾值+圖像平滑+形態轉換(opencv進行圖像處理)
OpenCV中的圖像處理 —— 圖像閾值+圖像平滑+形態轉換
1. 圖像閾值
關于圖像閾值主要涉及到兩個函數:cv.threshold和cv.adaptiveThreshold(即簡單閾值和自適應閾值)
1.1 簡單閾值
首先我們要了解什么是閾值,閾值能干什么?簡單閾值是我們設置的一個臨界值,這個臨界值的作用就是對應圖像中的每一個像素,如果它小于這個臨界值就將其設置為0,若其大于這個臨界值則將其設置為最大值(一般為255),在使用閾值之后的圖像就會只剩兩個顏色像素:最大值和最小值,在掩膜的運用比較多,我們后續詳細講
我們先說簡單閾值,簡單閾值涉及的函數是cv.threshold(),其中需要傳入4個參數,第一個參數即是我們的圖像對象,需要注意的是一般我們需要在這里傳入一個單通道灰度圖,第二個參數是閾值,用于對整個圖像的像素做一個分類,第三個參數是分配的最大值,即當像素大于我們設置的閾值時,使其等于這個我們設置的最大值即可,第四個參數是一個表示不同類型的標志,其取值可以是:cv.THRESH_BINARY,cv.THRESH_BINARY_INV,cv.THRESH_TRUNC,cv.THRESH_TOZERO,cv.THRESH_TOZERO_INV
下面我們通過一個例子來展示這些不同類型閾值的使用結果
import cv2 as cv import numpy as np from matplotlib import pyplot as plt img = cv.imread('gradient.png',0) ret,thresh1 = cv.threshold(img,127,255,cv.THRESH_BINARY) ret,thresh2 = cv.threshold(img,127,255,cv.THRESH_BINARY_INV) ret,thresh3 = cv.threshold(img,127,255,cv.THRESH_TRUNC) ret,thresh4 = cv.threshold(img,127,255,cv.THRESH_TOZERO) ret,thresh5 = cv.threshold(img,127,255,cv.THRESH_TOZERO_INV) titles = ['Original Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV'] images = [img, thresh1, thresh2, thresh3, thresh4, thresh5] for i in range(6): plt.subplot(2,3,i+1),plt.imshow(images[i],'gray') plt.title(titles[i]) plt.xticks([]),plt.yticks([]) plt.show()
在上面的代碼的顯示圖像過程中出現了一個很重要的plt.subplot()函數,這是由matplotlib庫提供的一個繪圖函數,其使用方法也比較簡單,就是將若干個圖像按照行列的形式展示出來,plt.subplot()傳入的參數有三個,第一二個參數指的是行和列數,第三個指的是顯示的圖片是在第幾個位置
plt還提供了plt.imshow()、pli.title() 和 plt.x/yticks()用來完善我們的圖像展示
1.2 自適應閾值
在簡單閾值中我們指定了一個閾值作為整張圖片的固定閾值,但是有時候一些圖片的各個部分的光照角度乃至角度都不一樣,這個時候我們如果還使用簡單閾值,那效果可想而知
只要思想不滑坡,辦法總比困難多!這個時候我們就可以使用自適應閾值解決這種問題
自適應閾值關系到函數cv.adaptiveThreshold(),關于這個函數需要傳入的參數就有點小復雜了,我們借助一篇文章來了解:圖像的二值化-cv2.threshold()、cv2.adaptiveThreshold()
先來觀察一下cv.adaptiveThredshold()函數使用時的樣子
th3 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 11, 2)
是不是發現這參數有些小多,我們一個一個來看,第一個參數老生常談,就是我們的圖像資源src,第二個參數指的就是像素值上限,第三個參數就有意思了,它指的是自適應方法,而自適應方法可供我們使用的有兩種(不知道還有沒有其他的,感興趣的小伙伴可以去查一查):
cv2.ADAPTIVE_THRESH_MEAN_C :領域內均值
cv2.ADAPTIVE_THRESH_GAUSSIAN_C :領域內像素點加權和,權重為一個高斯窗口
第四個參數只有兩個值可以賦給它:cv2.THRESH_BINARY 和cv2.THRESH_BINARY_INV,第五個參數Block size指的是規定領域大小,BlockSize值越大,參與計算閾值的區域也越大,細節輪廓就變得越少,整體輪廓越粗越明顯,第六個參數是常數C,C越大,每個像素點的N*N鄰域計算出的閾值就越小,中心點大于這個閾值的可能性也就越大,設置成255的概率就越大,整體圖像白色像素就越多,反之亦然
1.3 Otsu的二值化
在全局閾值化中,我們使用任意選擇的值作為閾值。相反,Otsu的方法避免了必須選擇一個值并自動確定它的情況
首先我們來了解一下什么是雙峰圖像,顧名思義就是僅有兩個不同圖像值的圖像,其中直方圖僅包含兩個峰,而一個好的閾值就應該處于這兩個峰之間才能達到最好的圖像處理效果,而Otsu的方法就是從圖像直方圖中確定最佳的全局閾值(跟自適應閾值完全不一樣,自適應閾值是對應不同的區域自動確定閾值,而Otsu方法是根據雙峰圖像來確定一個最佳的全局閾值)
我們依舊通過一個例子來掌握這幾種方法
import cv2 as cv import numpy as np from matplotlib import pyplot as plt img = cv.imread(r"E:\image\test03.png", 0) # 全局閾值 ret1, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY) # Otsu閾值 ret2, th2 = cv.threshold(img, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU) # 高斯濾波后再采用Otsu閾值 blur = cv.GaussianBlur(img, (5, 5), 0) ret3, th3 = cv.threshold(blur, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU) # 繪制所有圖像及其直方圖 images = [img, 0, th1, img, 0, th2, blur, 0, th3] titles = ['Original Noisy Image', 'Histogram', 'Global Thresholding (v=127)', 'Original Noisy Image', 'Histogram', "Otsu's Thresholding", 'Gaussian filtered Image', 'Histogram', "Otsu's Thresholding"] for i in range(3): plt.subplot(3, 3, i * 3 + 1), plt.imshow(images[i * 3], 'gray') plt.title(titles[i * 3]), plt.xticks([]), plt.yticks([]) plt.subplot(3, 3, i * 3 + 2), plt.hist(images[i * 3].ravel(), 256) plt.title(titles[i * 3 + 1]), plt.xticks([]), plt.yticks([]) plt.subplot(3, 3, i * 3 + 3), plt.imshow(images[i * 3 + 2], 'gray') plt.title(titles[i * 3 + 2]), plt.xticks([]), plt.yticks([]) plt.show()
2. 圖像平滑
2.1 2D卷積(圖像過濾)
在學習圖像平滑之前我們要先了解一下2D卷積即圖像過濾,我們可以使用各種低通濾波器(LPF),高通濾波器(HPF)對圖像進行濾波
低通濾波器LPF有助于消除噪聲,而高通濾波器HPF有助于在圖像中找到邊緣
OpenCV提供了一個cv.filter2D()函數用來將內核與圖像進行卷積,在后面的內容我們會著重解除“內核”,而使用什么內核是實現各種圖像模糊技術的關鍵,現在我們先提供一個例子來了解內核與圖像卷積的實現過程,在這個例子中我們通過一個5x5平均濾波器內核來實現
平局是一種重要的圖像平滑技術,下下面我們會做詳細介紹
代碼實質:保持這個內核在一個像素上,將所有低于這個像素的25個像素相加,取其平均值,用新的平均值替換中心像素,它會對所有的像素繼續此操作,直至處理完畢圖像
import numpy as np import cv2 as cv from matplotlib import pyplot as plt img = cv.imread('opencv_logo.png') kernel = np.ones((5,5),np.float32)/25 dst = cv.filter2D(img,-1,kernel) plt.subplot(121),plt.imshow(img),plt.title('Original') plt.xticks([]), plt.yticks([]) plt.subplot(122),plt.imshow(dst),plt.title('Averaging') plt.xticks([]), plt.yticks([]) plt.show()
我們發現在上述代碼中出現了一個陌生的東西:kernel,這是一個5x5且數據類型為float32的數組,我們也可以理解為這就是我們的5x5平均濾波器的內核,這個5x5的數組中存儲了25個低于內核像素的像素,把這個數組傳入cv.filter2D()后會實現最終的圖像平滑
接下來我們再說說cv.filter2D()這個函數,上面說到它是一個用來將圖像和內核進行卷積的函數,而內核由我們自己作為參數傳入,cv.filter2D()有三個必須傳入的參數:src、ddepth和kernel,src當然指的就是我們的圖像資源(原圖像),ddepth是目標圖像深度,這個東西有些不好理解,但是一般情況下我們都設為-1,kernel指的就是我們的卷積內核,它是一個numpy.ndarray 類型的矩陣,這個矩陣可以用numpy函數生成,但是在后續圖像處理技術中,我們需要制造一些很復雜的卷積核,這個時候使用numpy的函數就顯得不夠用了,這個時候我們需要使用OpenCV的內置函數:getStructuringElement、getGaussianKernel等來滿足我們的需求
2.2 圖像平滑(圖像模糊)
通過將圖像與低通濾波器內核進行卷積來實現圖像模糊,低通濾波器LPF對消除噪聲非常有效,它從實際圖片上消除了高頻的部分(例如噪聲和邊緣),當然它對邊緣不太友好,此操作的結果就是邊緣比較模糊,OpenCV提供了四種類型的模糊技術
1、平均
我們上面演示了使用5x5平均濾波器內核來實現操作,但是“平均”這種技術也是有著它自己的函數方便我們操作
它僅獲取內核區域下所有像素的平均值,并替換中心元素,這是通過功能函數**cv.blur()和cv.boxFilter()**完成的,在進行操作時我們需要指定內核的寬度和高度
cv.boxFilter()函數是在我們不行使用標準化的框式過濾器時使用的,并將參數normalize = False傳遞進去
import cv2 as cv import numpy as np from matplotlib import pyplot as plt img = cv.imread('opencv-logo-white.png') blur = cv.blur(img,(5,5)) plt.subplot(121),plt.imshow(img),plt.title('Original') plt.xticks([]), plt.yticks([]) plt.subplot(122),plt.imshow(blur),plt.title('Blurred') plt.xticks([]), plt.yticks([]) plt.show()
2、高斯模糊
高斯模糊代替了盒式濾波器,使用了高斯核來實現圖像平滑,這是通過功能cv.GaussianBlur() 完成的,我們應指定內核的寬度和高度,該寬度和高度應為正數和奇數。我們還應指定X和Y方向的標準偏差,分別為sigmaX和sigmaY
如果僅指定sigmaX,則將sigmaY與sigmaX相同,如果兩個都為零,則根據內核大小進行計算
對于一些特殊的需求我們會使用cv.getGaussianKernel()函數來創建高斯核
# 我們可以通過修改上面的代碼實現高斯模糊 blur = cv.GaussianBlur(img,(5,5),0)
上述代碼中cv.GaussianBlur()中傳入的參數img即是原圖像,(5,5)則是高斯核的大小,0指的是sigmaX和sigmaY都為0,此時其值根據內核大小計算
3、中位模糊
函數cv.medianBlur() 提取內核區域下所有像素的中值,并將中心元素替換為該中值,這對于消除圖像中的椒鹽噪聲非常有效,在平均中,內核中心元素是新計算的平均值,而中位模糊的內核中心元素是圖像中的像素值或新值,但是在中位模糊中中心元素總是被某些像素代替
中位模糊的內核大小也應為整技術整數
median = cv.medianBlur(img,5)
img指圖像資源,5指的是內核大小
4、雙邊濾波
cv.bilateralFilter() 在去除噪聲的同時保持邊緣清晰銳利非常有效,但是,與其他過濾器相比,該操作速度較慢
高斯濾波器采用像素周圍的鄰域并找到其高斯加權平均值,高斯濾波器僅僅是控件的函數,即它在工作時僅考慮附近的像素,而不考慮像素是否具有相同的強度也不考慮像素是否是邊緣像素,所以它對邊緣的清晰銳利保持很不友好
但是雙邊濾波器改善了這種缺陷,它內部有兩個高斯濾波器,一個是上述的一般高斯濾波器,而另一個是像素差的函數,空間的高斯函數確保僅考慮附近元素的模糊,強度差的高斯函數確保僅考慮強度與中心元素相似的像素的模糊(即加了一個顯示,如果像素的強度與中心元素差距較大,就不會令其模糊從而保持邊緣清晰銳利)
blur = cv.bilateralFilter(img,9,75,75)
3. 形態轉換
這一塊兒我們說說OpenCV處理圖像時在形態學的操作,這些操作有:侵蝕和膨脹、開運算和閉運算、形態學梯度、頂帽和黑帽
首先我們要了解什么是形態學變換,形態學變換就是基于圖像形狀的簡單操作,通常在二進制圖像上執行,一般需要兩個輸入:原始圖像和決定操作性質的結構元素或內核
3.1 侵蝕與膨脹
侵蝕:內核滑動通過圖像(在2D卷積中),原始圖像中的一個像素(無論是1還是0)只有當內核下的所有像素都是1時才被認為是1,否則它就會被侵蝕(變成0)
侵蝕的結果就是根據內核的大小,邊界附近的所有像素都會被丟棄,因此,前景物體的厚度或大小減小,或只是圖像中的白色區域減小,它有助于去除小的白色噪聲
import cv2 as cv import numpy as np img = cv.imread('j.png',0) # 創建內核 kernel = np.ones((5,5),np.uint8) # 使用侵蝕函數處理圖像 erosion = cv.erode(img,kernel,iterations = 1) plt.subplot(121),plt.imshow(img),plt.title('Original') plt.xticks([]), plt.yticks([]) plt.subplot(122),plt.imshow(erosion),plt.title('Erosion') plt.xticks([]), plt.yticks([]) plt.show()
膨脹:如果內核下的至少一個像素為“ 1”,則像素元素為“ 1”。因此,它會增加圖像中的白色區域或增加前景對象的大小,通常,在消除噪音的情況下,腐蝕后會膨脹,因為腐蝕會消除白噪聲,但也會縮小物體
dilation = cv.dilate(img,kernel,iterations = 1)
3.2 開運算與閉運算
開放只是“侵蝕后擴張”的另一個名稱,它對于消除噪音很有用,為了實現這個操作我們要使用函數cv.morphologyEx()
閉運算與開運算相反,先擴張然后再侵蝕,在關閉前景對象內部的小孔或對象上的小黑點時很有用
opening = cv.morphologyEx(img, cv.MORPH_OPEN, kernel) # 開運算 opening = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel) # 閉運算
這里的參數我們有必要再留意一下,第一個參數即原圖像,第二個參數指的是進行變化的方式,cv2.MORPH_OPEN 進行開運算,cv2.MORPH_CLOSE 進行閉運算
3.3 頂帽與黑帽
頂帽是輸入圖像和圖像開運算之差,而黑帽是輸入圖像和圖像閉運算之差
tophat = cv.morphologyEx(img, cv.MORPH_TOPHAT, kernel) # 頂帽 blackhat = cv.morphologyEx(img, cv.MORPH_BLACKHAT, kernel) # 黑帽
黑帽是輸入圖像和圖像閉運算之差
3.4 結構元素
在Numpy的幫助下,我們在前面的示例中手動創建了一個結構元素(內核),它是矩形的,但是在某些情況下,我們可能需要橢圓形/圓形的內核,因此,OpenCV提供了函數cv.getStructuringElement(),我們只需傳遞內核的形狀和大小,即可獲得所需的內核
(注:文章內容參考OpenCV4.1中文官方文檔)
如果文章對您有所幫助,記得一鍵三連支持一下哦
AI OpenCV 圖像處理 機器學習 機器視覺
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。