系列文章目錄
文章目錄
- 系列文章目錄
- 前言
- 一、圖像門檻值
-
- 1. 簡單門檻值
- 2. 自适應門檻值
- 3. OTSU二值化
-
- OTSU原理
- OTSU python實作
- 4. 參考資料
- 二、圖檔光滑
-
- 1. 2D卷積
- 2. 圖像模糊
-
- 均值模糊
- 高斯模糊
- 中值模糊
- 雙邊濾波
- 3. 擴充資料
前言
本教程将學習簡單門檻值,自适應門檻值和Otsu的門檻值:cv.threshold 和 cv.adaptiveThreshold. 以及使用低通濾波器模糊圖像、2D卷積等等
一、圖像門檻值
函數cv.threshold和cv.adaptiveThreshold
1. 簡單門檻值
對于每個像素,應用相同的門檻值。如果像素值小于門檻值,則設定為0,否則設定為最大值。函數cv.Threshold用于應用門檻值。第一個參數是源圖像,它應該是灰階圖像。第二個參數是用于對像素值進行分類的門檻值。第三個參數是配置設定給超過門檻值的像素值的最大值。OpenCV提供了不同類型的門檻值,由第四個參數給出。上面描述的基本門檻值是通過使用類型cv.THRESH_BINARY來實作的。所有簡單門檻值類型為:
- 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',vmin=0,vmax=255)
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
注意:為了繪制多個圖像,我們使用了plt.subplot()函數。請檢視matplotlib文檔以獲得更多細節。
2. 自适應門檻值
在前一節中,我們使用一個全局值作為門檻值。但這可能不是在所有情況下都是好的,例如,如果一個圖像在不同的區域有不同的照明條件。在這種情況下,自适應門檻值可以有所幫助。這裡,算法根據像素周圍的小區域來确定門檻值。是以,我們對同一幅圖像的不同區域得到不同的門檻值,進而使不同光照下的圖像得到更好的效果。
除上述參數外,函數cv.adaptiveThreshold接受三個輸入參數:
-
adaptiveMethod決定如何計算門檻值:cv.ADAPTIVE_THRESH_MEAN_C:門檻值是鄰域面積的平均值減去常數C。
cv.ADAPTIVE_THRESH_GAUSSIAN_C:門檻值是鄰域值減去常數C的高斯權重和。
- blockSize決定了鄰域面積的大小,
- C是一個常量,從鄰域像素的平均值或權重和中減去。
下面的代碼對不同光照下的圖像進行全局門檻值和自适應門檻值的比較:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('sudoku.png',0)
img = cv.medianBlur(img,5)
ret,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
th2 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_MEAN_C,\
cv.THRESH_BINARY,11,2)
th3 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv.THRESH_BINARY,11,2)
titles = ['Original Image', 'Global Thresholding (v = 127)',
'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
images = [img, th1, th2, th3]
for i in range(4):
plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
3. OTSU二值化
在全局門檻值中,我們使用任意選擇的值作為門檻值。相比之下,Otsu的方法避免了選擇一個值,并自動确定它。
考慮一個隻有兩個不同圖像值的圖像(雙峰圖像),其中直方圖隻包含兩個峰。一個好的門檻值應該位于這兩個值的中間。類似地,Otsu的方法從圖像直方圖中确定一個最優的全局門檻值。
為了做到這一點,使用了cv.threshold()函數。THRESH_OTSU作為一個額外的标志傳遞。門檻值可以任意選擇。然後算法找到最優門檻值,作為第一個輸出傳回。
看看下面的例子。輸入圖像是一個噪聲圖像。在第一種情況下,應用值為127的全局門檻值。在第二種情況下,直接應用Otsu的門檻值。在第三種情況下,首先對圖像進行5x5高斯核濾波去噪,然後進行Otsu門檻值分割。看看噪聲濾波如何改善結果。
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('noisy2.png',0)
# global thresholding
ret1,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
# Otsu's thresholding
ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# Otsu's thresholding after Gaussian filtering
blur = cv.GaussianBlur(img,(5,5),0)
ret3,th3 = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# plot all the images and their histograms
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([])
OTSU原理
Otsu的算法試圖找到一個門檻值(t),使由關系給出的類内權重方差最小:
它實際上找到了一個值t,它位于兩個峰值之間,這樣兩個類别的方差都是最小的。它可以簡單地用Python實作如下:
OTSU python實作
img = cv.imread('noisy2.png',0)
blur = cv.GaussianBlur(img,(5,5),0)
# find normalized_histogram, and its cumulative distribution function
hist = cv.calcHist([blur],[0],None,[256],[0,256])
hist_norm = hist.ravel()/hist.sum()
Q = hist_norm.cumsum()
bins = np.arange(256)
fn_min = np.inf
thresh = -1
for i in range(1,256):
p1,p2 = np.hsplit(hist_norm,[i]) # probabilities
q1,q2 = Q[i],Q[255]-Q[i] # cum sum of classes
if q1 < 1.e-6 or q2 < 1.e-6:
continue
b1,b2 = np.hsplit(bins,[i]) # weights
# finding means and variances
m1,m2 = np.sum(p1*b1)/q1, np.sum(p2*b2)/q2
v1,v2 = np.sum(((b1-m1)**2)*p1)/q1,np.sum(((b2-m2)**2)*p2)/q2
# calculates the minimization function
fn = v1*q1 + v2*q2
if fn < fn_min:
fn_min = fn
thresh = i
# find otsu's threshold value with OpenCV function
ret, otsu = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
print( "{} {}".format(thresh,ret) )
4. 參考資料
Digital Image Processing, Rafael C. Gonzalez
二、圖檔光滑
1. 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()
2. 圖像模糊
圖像模糊是通過将圖像與低通濾波器核進行卷積來實作的。這對消除噪聲很有用。它實際上從圖像中去除高頻内容(如:噪聲,邊緣)。是以在這個操作中邊緣會被模糊一點(也有一些模糊技術不會模糊邊緣)。OpenCV提供了四種主要的模糊技術。
均值模糊
這是通過将圖像與标準化的框過濾器進行卷積來實作的。它隻是取kernel下所有像素的平均值,然後替換中心元素。這是由函數cv.blur()或cv.boxFilter()完成的。關于kernel的更多細節請檢視文檔。我們應該指定核的寬度和高度。一個3x3的歸一化盒子濾波器看起來如下所示:
注意:如果你不想使用歸一化盒子濾波器,可以使用cv.boxFilter()。向函數傳遞一個參數normalize=False。
下面是一個5x5大小的核心示例:
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()
高斯模糊
該方法采用高斯核代替盒子濾波器。這是通過函數cv.GaussianBlur()完成的。我們應該指定核的寬度和高度,并且應該是正奇數。我們還應該分别指定X和Y方向上的标準差sigmaX和sigmaY。如果隻指定了sigmaX,則sigmaY與sigmaX相同。如果兩者都以0表示,則從核心大小計算它們。高斯模糊是去除圖像高斯噪聲的有效方法。
如果需要,可以使用函數cv.getGaussianKernel()建立高斯核。
中值模糊
函數cv.medianBlur()取核心區域下所有像素的中值,中心元素被這個中值替換。這是非常有效的對抗椒鹽噪聲的圖像。有趣的是,在上面的濾波中,中心元素是一個新計算的值,可能是圖像中的像素值,也可能是一個新值。但在中值模糊中,中心元素總是被圖像中的某個像素值所替代。有效地降低了噪聲。它的核大小應該是一個正奇數。
下面給原始圖像添加50%的噪聲,并應用中值模糊。
雙邊濾波
cv.bilateralFilter()在去除噪聲的同時保持邊緣的銳利。但與其他過濾器相比,操作要慢一些。我們已經看到,高斯濾波器取像素周圍的鄰域并求其高斯權重平均值。該高斯濾波器是一個單獨的空間函數,即在濾波時考慮附近的像素。它沒有考慮像素是否具有幾乎相同的強度,也沒有考慮一個像素是否為邊緣像素。是以它也模糊了邊緣,這是我們不想做的。
雙邊濾波在空間上也采用高斯濾波器,但多了一個高斯濾波器,它是像素差的函數。空間的高斯函數保證了隻考慮附近的像素進行模糊處理,而強度差的高斯函數保證了隻考慮與中心像素強度相近的像素進行模糊處理。是以它保留了邊緣,因為邊緣上的像素會有很大的強度變化。
下面的示例展示了雙邊過濾器的使用(有關參數的詳細資訊,請通路docs)。
看,表面的紋理消失了,但邊緣還在。
3. 擴充資料
- Details about the bilateral filtering
參考:https://docs.opencv.org/4.5.2/d6/d00/tutorial_py_root.html