OpenCV中的图像处理 —— 图像阈值+图像平滑+形态转换
OpenCV中的圖像處理 —— 圖像閾值+圖像平滑+形態(tài)轉(zhuǎn)換
目錄
- OpenCV中的圖像處理 —— 圖像閾值+圖像平滑+形態(tài)轉(zhuǎn)換
- 1. 圖像閾值
- 1.1 簡(jiǎn)單閾值
- 1.2 自適應(yīng)閾值
- 1.3 Otsu的二值化
- 2. 圖像平滑
- 2.1 2D卷積(圖像過(guò)濾)
- 2.2 圖像平滑(圖像模糊)
- 3. 形態(tài)轉(zhuǎn)換
- 3.1 侵蝕與膨脹
- 3.2 開(kāi)運(yùn)算與閉運(yùn)算
- 3.3 頂帽與黑帽
- 3.4 結(jié)構(gòu)元素
1. 圖像閾值
關(guān)于圖像閾值主要涉及到兩個(gè)函數(shù):cv.threshold和cv.adaptiveThreshold(即簡(jiǎn)單閾值和自適應(yīng)閾值)
1.1 簡(jiǎn)單閾值
首先我們要了解什么是閾值,閾值能干什么?簡(jiǎn)單閾值是我們?cè)O(shè)置的一個(gè)臨界值,這個(gè)臨界值的作用就是對(duì)應(yīng)圖像中的每一個(gè)像素,如果它小于這個(gè)臨界值就將其設(shè)置為0,若其大于這個(gè)臨界值則將其設(shè)置為最大值(一般為255),在使用閾值之后的圖像就會(huì)只剩兩個(gè)顏色像素:最大值和最小值,在掩膜的運(yùn)用比較多,我們后續(xù)詳細(xì)講
我們先說(shuō)簡(jiǎn)單閾值,簡(jiǎn)單閾值涉及的函數(shù)是cv.threshold(),其中需要傳入4個(gè)參數(shù),第一個(gè)參數(shù)即是我們的圖像對(duì)象,需要注意的是一般我們需要在這里傳入一個(gè)單通道灰度圖,第二個(gè)參數(shù)是閾值,用于對(duì)整個(gè)圖像的像素做一個(gè)分類(lèi),第三個(gè)參數(shù)是分配的最大值,即當(dāng)像素大于我們?cè)O(shè)置的閾值時(shí),使其等于這個(gè)我們?cè)O(shè)置的最大值即可,第四個(gè)參數(shù)是一個(gè)表示不同類(lèi)型的標(biāo)志,其取值可以是:cv.THRESH_BINARY,cv.THRESH_BINARY_INV,cv.THRESH_TRUNC,cv.THRESH_TOZERO,cv.THRESH_TOZERO_INV
下面我們通過(guò)一個(gè)例子來(lái)展示這些不同類(lèi)型閾值的使用結(jié)果
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()在上面的代碼的顯示圖像過(guò)程中出現(xiàn)了一個(gè)很重要的plt.subplot()函數(shù),這是由matplotlib庫(kù)提供的一個(gè)繪圖函數(shù),其使用方法也比較簡(jiǎn)單,就是將若干個(gè)圖像按照行列的形式展示出來(lái),plt.subplot()傳入的參數(shù)有三個(gè),第一二個(gè)參數(shù)指的是行和列數(shù),第三個(gè)指的是顯示的圖片是在第幾個(gè)位置
plt還提供了plt.imshow()、pli.title() 和 plt.x/yticks()用來(lái)完善我們的圖像展示
1.2 自適應(yīng)閾值
在簡(jiǎn)單閾值中我們指定了一個(gè)閾值作為整張圖片的固定閾值,但是有時(shí)候一些圖片的各個(gè)部分的光照角度乃至角度都不一樣,這個(gè)時(shí)候我們?nèi)绻€使用簡(jiǎn)單閾值,那效果可想而知
只要思想不滑坡,辦法總比困難多!這個(gè)時(shí)候我們就可以使用自適應(yīng)閾值解決這種問(wèn)題
自適應(yīng)閾值關(guān)系到函數(shù)cv.adaptiveThreshold(),關(guān)于這個(gè)函數(shù)需要傳入的參數(shù)就有點(diǎn)小復(fù)雜了,我們借助一篇文章來(lái)了解:圖像的二值化-cv2.threshold()、cv2.adaptiveThreshold()
先來(lái)觀察一下cv.adaptiveThredshold()函數(shù)使用時(shí)的樣子
th3 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 11, 2)是不是發(fā)現(xiàn)這參數(shù)有些小多,我們一個(gè)一個(gè)來(lái)看,第一個(gè)參數(shù)老生常談,就是我們的圖像資源src,第二個(gè)參數(shù)指的就是像素值上限,第三個(gè)參數(shù)就有意思了,它指的是自適應(yīng)方法,而自適應(yīng)方法可供我們使用的有兩種(不知道還有沒(méi)有其他的,感興趣的小伙伴可以去查一查):
- cv2.ADAPTIVE_THRESH_MEAN_C :領(lǐng)域內(nèi)均值
- cv2.ADAPTIVE_THRESH_GAUSSIAN_C :領(lǐng)域內(nèi)像素點(diǎn)加權(quán)和,權(quán)重為一個(gè)高斯窗口
第四個(gè)參數(shù)只有兩個(gè)值可以賦給它:cv2.THRESH_BINARY 和cv2.THRESH_BINARY_INV,第五個(gè)參數(shù)Block size指的是規(guī)定領(lǐng)域大小,BlockSize值越大,參與計(jì)算閾值的區(qū)域也越大,細(xì)節(jié)輪廓就變得越少,整體輪廓越粗越明顯,第六個(gè)參數(shù)是常數(shù)C,C越大,每個(gè)像素點(diǎn)的N*N鄰域計(jì)算出的閾值就越小,中心點(diǎn)大于這個(gè)閾值的可能性也就越大,設(shè)置成255的概率就越大,整體圖像白色像素就越多,反之亦然
1.3 Otsu的二值化
在全局閾值化中,我們使用任意選擇的值作為閾值。相反,Otsu的方法避免了必須選擇一個(gè)值并自動(dòng)確定它的情況
首先我們來(lái)了解一下什么是雙峰圖像,顧名思義就是僅有兩個(gè)不同圖像值的圖像,其中直方圖僅包含兩個(gè)峰,而一個(gè)好的閾值就應(yīng)該處于這兩個(gè)峰之間才能達(dá)到最好的圖像處理效果,而Otsu的方法就是從圖像直方圖中確定最佳的全局閾值(跟自適應(yīng)閾值完全不一樣,自適應(yīng)閾值是對(duì)應(yīng)不同的區(qū)域自動(dòng)確定閾值,而Otsu方法是根據(jù)雙峰圖像來(lái)確定一個(gè)最佳的全局閾值)
我們依舊通過(guò)一個(gè)例子來(lái)掌握這幾種方法
import cv2 as cv import numpy as np from matplotlib import pyplot as pltimg = 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卷積(圖像過(guò)濾)
在學(xué)習(xí)圖像平滑之前我們要先了解一下2D卷積即圖像過(guò)濾,我們可以使用各種低通濾波器(LPF),高通濾波器(HPF)對(duì)圖像進(jìn)行濾波
低通濾波器LPF有助于消除噪聲,而高通濾波器HPF有助于在圖像中找到邊緣
OpenCV提供了一個(gè)cv.filter2D()函數(shù)用來(lái)將內(nèi)核與圖像進(jìn)行卷積,在后面的內(nèi)容我們會(huì)著重解除“內(nèi)核”,而使用什么內(nèi)核是實(shí)現(xiàn)各種圖像模糊技術(shù)的關(guān)鍵,現(xiàn)在我們先提供一個(gè)例子來(lái)了解內(nèi)核與圖像卷積的實(shí)現(xiàn)過(guò)程,在這個(gè)例子中我們通過(guò)一個(gè)5x5平均濾波器內(nèi)核來(lái)實(shí)現(xiàn)
平局是一種重要的圖像平滑技術(shù),下下面我們會(huì)做詳細(xì)介紹
代碼實(shí)質(zhì):保持這個(gè)內(nèi)核在一個(gè)像素上,將所有低于這個(gè)像素的25個(gè)像素相加,取其平均值,用新的平均值替換中心像素,它會(huì)對(duì)所有的像素繼續(xù)此操作,直至處理完畢圖像
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()我們發(fā)現(xiàn)在上述代碼中出現(xiàn)了一個(gè)陌生的東西:kernel,這是一個(gè)5x5且數(shù)據(jù)類(lèi)型為float32的數(shù)組,我們也可以理解為這就是我們的5x5平均濾波器的內(nèi)核,這個(gè)5x5的數(shù)組中存儲(chǔ)了25個(gè)低于內(nèi)核像素的像素,把這個(gè)數(shù)組傳入cv.filter2D()后會(huì)實(shí)現(xiàn)最終的圖像平滑
接下來(lái)我們?cè)僬f(shuō)說(shuō)cv.filter2D()這個(gè)函數(shù),上面說(shuō)到它是一個(gè)用來(lái)將圖像和內(nèi)核進(jìn)行卷積的函數(shù),而內(nèi)核由我們自己作為參數(shù)傳入,cv.filter2D()有三個(gè)必須傳入的參數(shù):src、ddepth和kernel,src當(dāng)然指的就是我們的圖像資源(原圖像),ddepth是目標(biāo)圖像深度,這個(gè)東西有些不好理解,但是一般情況下我們都設(shè)為-1,kernel指的就是我們的卷積內(nèi)核,它是一個(gè)numpy.ndarray 類(lèi)型的矩陣,這個(gè)矩陣可以用numpy函數(shù)生成,但是在后續(xù)圖像處理技術(shù)中,我們需要制造一些很復(fù)雜的卷積核,這個(gè)時(shí)候使用numpy的函數(shù)就顯得不夠用了,這個(gè)時(shí)候我們需要使用OpenCV的內(nèi)置函數(shù):getStructuringElement、getGaussianKernel等來(lái)滿足我們的需求
2.2 圖像平滑(圖像模糊)
通過(guò)將圖像與低通濾波器內(nèi)核進(jìn)行卷積來(lái)實(shí)現(xiàn)圖像模糊,低通濾波器LPF對(duì)消除噪聲非常有效,它從實(shí)際圖片上消除了高頻的部分(例如噪聲和邊緣),當(dāng)然它對(duì)邊緣不太友好,此操作的結(jié)果就是邊緣比較模糊,OpenCV提供了四種類(lèi)型的模糊技術(shù)
1、平均
我們上面演示了使用5x5平均濾波器內(nèi)核來(lái)實(shí)現(xiàn)操作,但是“平均”這種技術(shù)也是有著它自己的函數(shù)方便我們操作
它僅獲取內(nèi)核區(qū)域下所有像素的平均值,并替換中心元素,這是通過(guò)功能函數(shù)**cv.blur()和cv.boxFilter()**完成的,在進(jìn)行操作時(shí)我們需要指定內(nèi)核的寬度和高度
cv.boxFilter()函數(shù)是在我們不行使用標(biāo)準(zhǔn)化的框式過(guò)濾器時(shí)使用的,并將參數(shù)normalize = False傳遞進(jìn)去
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、高斯模糊
高斯模糊代替了盒式濾波器,使用了高斯核來(lái)實(shí)現(xiàn)圖像平滑,這是通過(guò)功能cv.GaussianBlur() 完成的,我們應(yīng)指定內(nèi)核的寬度和高度,該寬度和高度應(yīng)為正數(shù)和奇數(shù)。我們還應(yīng)指定X和Y方向的標(biāo)準(zhǔn)偏差,分別為sigmaX和sigmaY
如果僅指定sigmaX,則將sigmaY與sigmaX相同,如果兩個(gè)都為零,則根據(jù)內(nèi)核大小進(jìn)行計(jì)算
對(duì)于一些特殊的需求我們會(huì)使用cv.getGaussianKernel()函數(shù)來(lái)創(chuàng)建高斯核
# 我們可以通過(guò)修改上面的代碼實(shí)現(xiàn)高斯模糊 blur = cv.GaussianBlur(img,(5,5),0)上述代碼中cv.GaussianBlur()中傳入的參數(shù)img即是原圖像,(5,5)則是高斯核的大小,0指的是sigmaX和sigmaY都為0,此時(shí)其值根據(jù)內(nèi)核大小計(jì)算
3、中位模糊
函數(shù)cv.medianBlur() 提取內(nèi)核區(qū)域下所有像素的中值,并將中心元素替換為該中值,這對(duì)于消除圖像中的椒鹽噪聲非常有效,在平均中,內(nèi)核中心元素是新計(jì)算的平均值,而中位模糊的內(nèi)核中心元素是圖像中的像素值或新值,但是在中位模糊中中心元素總是被某些像素代替
中位模糊的內(nèi)核大小也應(yīng)為整技術(shù)整數(shù)
median = cv.medianBlur(img,5)img指圖像資源,5指的是內(nèi)核大小
4、雙邊濾波
cv.bilateralFilter() 在去除噪聲的同時(shí)保持邊緣清晰銳利非常有效,但是,與其他過(guò)濾器相比,該操作速度較慢
高斯濾波器采用像素周?chē)泥徲虿⒄业狡涓咚辜訖?quán)平均值,高斯濾波器僅僅是控件的函數(shù),即它在工作時(shí)僅考慮附近的像素,而不考慮像素是否具有相同的強(qiáng)度也不考慮像素是否是邊緣像素,所以它對(duì)邊緣的清晰銳利保持很不友好
但是雙邊濾波器改善了這種缺陷,它內(nèi)部有兩個(gè)高斯濾波器,一個(gè)是上述的一般高斯濾波器,而另一個(gè)是像素差的函數(shù),空間的高斯函數(shù)確保僅考慮附近元素的模糊,強(qiáng)度差的高斯函數(shù)確保僅考慮強(qiáng)度與中心元素相似的像素的模糊(即加了一個(gè)顯示,如果像素的強(qiáng)度與中心元素差距較大,就不會(huì)令其模糊從而保持邊緣清晰銳利)
blur = cv.bilateralFilter(img,9,75,75)3. 形態(tài)轉(zhuǎn)換
這一塊兒我們說(shuō)說(shuō)OpenCV處理圖像時(shí)在形態(tài)學(xué)的操作,這些操作有:侵蝕和膨脹、開(kāi)運(yùn)算和閉運(yùn)算、形態(tài)學(xué)梯度、頂帽和黑帽
首先我們要了解什么是形態(tài)學(xué)變換,形態(tài)學(xué)變換就是基于圖像形狀的簡(jiǎn)單操作,通常在二進(jìn)制圖像上執(zhí)行,一般需要兩個(gè)輸入:原始圖像和決定操作性質(zhì)的結(jié)構(gòu)元素或內(nèi)核
3.1 侵蝕與膨脹
侵蝕:內(nèi)核滑動(dòng)通過(guò)圖像(在2D卷積中),原始圖像中的一個(gè)像素(無(wú)論是1還是0)只有當(dāng)內(nèi)核下的所有像素都是1時(shí)才被認(rèn)為是1,否則它就會(huì)被侵蝕(變成0)
侵蝕的結(jié)果就是根據(jù)內(nèi)核的大小,邊界附近的所有像素都會(huì)被丟棄,因此,前景物體的厚度或大小減小,或只是圖像中的白色區(qū)域減小,它有助于去除小的白色噪聲
import cv2 as cv import numpy as np img = cv.imread('j.png',0) # 創(chuàng)建內(nèi)核 kernel = np.ones((5,5),np.uint8) # 使用侵蝕函數(shù)處理圖像 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()膨脹:如果內(nèi)核下的至少一個(gè)像素為“ 1”,則像素元素為“ 1”。因此,它會(huì)增加圖像中的白色區(qū)域或增加前景對(duì)象的大小,通常,在消除噪音的情況下,腐蝕后會(huì)膨脹,因?yàn)楦g會(huì)消除白噪聲,但也會(huì)縮小物體
dilation = cv.dilate(img,kernel,iterations = 1)3.2 開(kāi)運(yùn)算與閉運(yùn)算
開(kāi)放只是“侵蝕后擴(kuò)張”的另一個(gè)名稱(chēng),它對(duì)于消除噪音很有用,為了實(shí)現(xiàn)這個(gè)操作我們要使用函數(shù)cv.morphologyEx()
閉運(yùn)算與開(kāi)運(yùn)算相反,先擴(kuò)張然后再侵蝕,在關(guān)閉前景對(duì)象內(nèi)部的小孔或?qū)ο笊系男『邳c(diǎn)時(shí)很有用
opening = cv.morphologyEx(img, cv.MORPH_OPEN, kernel) # 開(kāi)運(yùn)算 opening = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel) # 閉運(yùn)算這里的參數(shù)我們有必要再留意一下,第一個(gè)參數(shù)即原圖像,第二個(gè)參數(shù)指的是進(jìn)行變化的方式,cv2.MORPH_OPEN 進(jìn)行開(kāi)運(yùn)算,cv2.MORPH_CLOSE 進(jìn)行閉運(yùn)算
3.3 頂帽與黑帽
頂帽是輸入圖像和圖像開(kāi)運(yùn)算之差,而黑帽是輸入圖像和圖像閉運(yùn)算之差
tophat = cv.morphologyEx(img, cv.MORPH_TOPHAT, kernel) # 頂帽 blackhat = cv.morphologyEx(img, cv.MORPH_BLACKHAT, kernel) # 黑帽黑帽是輸入圖像和圖像閉運(yùn)算之差
3.4 結(jié)構(gòu)元素
在Numpy的幫助下,我們?cè)谇懊娴氖纠惺謩?dòng)創(chuàng)建了一個(gè)結(jié)構(gòu)元素(內(nèi)核),它是矩形的,但是在某些情況下,我們可能需要橢圓形/圓形的內(nèi)核,因此,OpenCV提供了函數(shù)cv.getStructuringElement(),我們只需傳遞內(nèi)核的形狀和大小,即可獲得所需的內(nèi)核
(開(kāi)閉運(yùn)算還有頂帽和黑帽我們后面細(xì)說(shuō))
(注:文章內(nèi)容參考OpenCV4.1中文官方文檔)
如果文章對(duì)您有所幫助,記得一鍵三連支持一下哦
總結(jié)
以上是生活随笔為你收集整理的OpenCV中的图像处理 —— 图像阈值+图像平滑+形态转换的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: JSP与JavaScript的区别
- 下一篇: 接入AppleID登录 go语言实现