1.目的
在本教程中:
- 你會學到簡單閾值法,自適應閾值法,以及 Otsu 閾值法(俗稱大津法)等。
- 你會學到如下函數:**cv.threshold,cv.adaptiveThreshold** 等。
2.簡單閾值法
此方法是直截了當的。如果像素值大于閾值,則會被賦為一個值(可能為白色),否則會賦為另一個值(可能為黑色)。使用的函數是 cv.threshold。第一個參數是源圖像,它應該是灰度圖像。第二個參數是閾值,用于對像素值進行分類。第三個參數是 maxval,它表示像素值大于(有時小于)閾值時要給定的值。opencv 提供了不同類型的閾值,由函數的第四個參數決定。不同的類型有:
- cv.THRESH_BINARY
- cv.THRESH_BINARY_INV
- cv.THRESH_TRUNC
- cv.THRESH_TOZERO
- cv.THRESH_TOZERO_INV
文檔清楚地解釋了每種類型的含義。請查看文檔。
獲得兩個輸出。第一個是 retval,稍后將解釋。第二個輸出是我們的閾值圖像。
代碼:
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 xrange(6):plt.subplot(2,3,i+1),plt.imshow(images[i],'gray')plt.title(titles[i])plt.xticks([]),plt.yticks([])
plt.show()
在 OpenCV 中,這些是閾值化操作的類型,用于將灰度圖像轉換為二值圖像或將灰度圖像中的像素值根據閾值進行分割。閾值化是一種常見的圖像處理技術,它根據像素值與設定閾值的比較結果,將像素值設置為0或最大值。
以下是這些閾值的類型及其作用:
cv.THRESH_BINARY
:如果像素值大于閾值,則將其設置為最大值(例如255),否則設置為0。這種類型的閾值化產生了一個完全黑白的二值圖像。
cv.THRESH_BINARY_INV
:與cv.THRESH_BINARY
相反,如果像素值大于閾值,則將其設置為0,否則設置為最大值。這種類型的閾值化也會產生一個完全黑白的二值圖像,但與cv.THRESH_BINARY
相比,黑色和白色的區域會互換。
cv.THRESH_TRUNC
:如果像素值大于閾值,則將其截斷為閾值,否則保持不變。這種類型的閾值化不會產生二值圖像,而是將閾值以上的像素值都設置為相同的值。
cv.THRESH_TOZERO
:如果像素值大于閾值,則保持不變,否則將其設置為0。這種類型的閾值化會將低于閾值的像素值變為黑色,而高于閾值的像素值保持其原始灰度。
cv.THRESH_TOZERO_INV
:與cv.THRESH_TOZERO
相反,如果像素值大于閾值,則將其設置為0,否則保持不變。這種類型的閾值化會將高于閾值的像素值變為黑色,而低于閾值的像素值保持其原始灰度。在使用這些閾值類型時,通常還會指定一個閾值值和一個最大值。閾值值是用于比較的值,而最大值是用于
cv.THRESH_BINARY
和cv.THRESH_BINARY_INV
類型的輸出圖像中的最大像素值。?
3.自適應閾值?
在前一節中,我們使用一個全局變量作為閾值。但在圖像在不同區域具有不同照明條件的條件下,這可能不是很好。在這種情況下,我們采用自適應閾值。在此,算法計算圖像的一個小區域的閾值。因此,我們得到了同一圖像不同區域的不同閾值,對于不同光照下的圖像,得到了更好的結果。
它有三個“特殊”輸入參數,只有一個輸出參數。
Adaptive Method-它決定如何計算閾值。
- cv.ADAPTIVE_THRESH_MEAN_C?閾值是指鄰近地區的平均值。
- cv.ADAPTIVE_THRESH_GAUSSIAN_C?閾值是權重為高斯窗的鄰域值的加權和。
Block Size-它決定了計算閾值的窗口區域的大小。
C-它只是一個常數,會從平均值或加權平均值中減去該值。
代碼:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# img = cv.imread('gradient.png',0)
img = r'D:\study\EmotionDetection_RealTime-master\data\data\te\04.jpg'
img = cv.imread(img,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()
img = cv.medianBlur(img,5)
在 OpenCV 中,
cv.medianBlur
?函數用于對圖像進行中值濾波。中值濾波是一種非線性的數字圖像濾波技術,它用像素點鄰域內的中值來代替該像素點的值,從而消除圖像中的椒鹽噪聲和斑點噪聲,同時保持圖像邊緣清晰。?在 OpenCV 中,可以使用?
cv.adaptiveThreshold
?函數來實現自適應閾值化。該函數的原型如下:cv.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
參數說明:
src
:輸入的灰度圖像。maxValue
:用于閾值化操作的最大值,通常設置為 255。adaptiveMethod
:自適應方法,可以是?cv.ADAPTIVE_THRESH_MEAN_C
?或?cv.ADAPTIVE_THRESH_GAUSSIAN_C
。
cv.ADAPTIVE_THRESH_MEAN_C
:計算局部區域的平均值作為閾值。cv.ADAPTIVE_THRESH_GAUSSIAN_C
:計算局部區域的加權平均值作為閾值,權重是一個高斯窗口。thresholdType
:閾值類型,與?cv.threshold
?函數中的閾值類型相同,例如?cv.THRESH_BINARY
。blockSize
:局部區域的大小,必須是奇數,如 3、5、7 等。C
:從平均值或加權平均值中減去的常數,通常是一個正值。
輸出圖像:?
4.otus二值化
在第一部分中,我告訴過您有一個參數 retval。當我們進行 Otsu 二值化時,它的用途就來了。那是什么?
在全局閾值化中,我們使用一個任意的閾值,對嗎?那么,我們如何知道我們選擇的值是好的還是不好的呢?答案是,試錯法。但是考慮一個雙峰圖像(簡單來說,雙峰圖像是一個直方圖有兩個峰值的圖像)。對于那個圖像,我們可以近似地取這些峰值中間的一個值作為閾值,對嗎?這就是 Otsu 二值化所做的。所以簡單來說,它會自動從雙峰圖像的圖像直方圖中計算出閾值。(對于非雙峰圖像,二值化將不準確。)
為此,我們使用了?cv.threshold?函數,但傳遞了一個額外的符號?cv.THRESH_OTSU?。對于閾值,只需傳入零。然后,該算法找到最佳閾值,并作為第二個輸出返回 retval。如果不使用 otsu 閾值,則 retval 與你使用的閾值相同。
查看下面的示例。輸入圖像是噪聲圖像。在第一種情況下,我應用了值為 127 的全局閾值。在第二種情況下,我直接應用 otsu 閾值。在第三種情況下,我使用 5x5 高斯核過濾圖像以去除噪聲,然后應用 otsu 閾值。查看噪聲過濾如何改進結果。
代碼:
import cv2 as cv
import numpy as np
from skimage import util
from matplotlib import pyplot as plt
# img = cv.imread('gradient.png',0)
img = r'D:\study\EmotionDetection_RealTime-master\data\data\te\01.jpg'
img = cv.imread(img,cv.IMREAD_GRAYSCALE)
cv.imshow('img',img)# 全局閾值
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()
?在您的代碼中,您使用了 OpenCV 的 `cv.threshold` 函數來應用不同的閾值化方法。這里簡要解釋一下每個步驟:
1. `ret1, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)`:
? ?- `img` 是輸入圖像。
? ?- `127` 是閾值。
? ?- `255` 是最大值。
? ?- `cv.THRESH_BINARY` 是閾值類型,它將像素值設置為 0 或 255,具體取決于它們是否大于閾值。
? ?- 返回值 `ret1` 是一個布爾值,表示閾值化操作是否成功。
? ?- `th1` 是閾值化后的輸出圖像。
2. `ret2, th2 = cv.threshold(img, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)`:
? ?- 與第一個步驟類似,但這次您使用了 `cv.THRESH_OTSU`。
? ?- `cv.THRESH_OTSU` 是一個特殊的閾值類型,它會自動選擇一個閾值,使得前景和背景之間的類間方差最大。
? ?- `th2` 是應用 Otsu 閾值后的輸出圖像。
3. `blur = cv.GaussianBlur(img, (5, 5), 0)`:
? ?- `img` 是輸入圖像。
? ?- `(5, 5)` 是高斯濾波器的尺寸,它定義了濾波器的寬度和高度。
? ?- `0` 是高斯核的標準差,它決定了濾波器的模糊程度。
? ?- `blur` 是應用高斯濾波后的輸出圖像。
4. `ret3, th3 = cv.threshold(blur, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)`:
? ?- 與第二個步驟類似,但這次您使用了經過高斯濾波的圖像 `blur`。
? ?- `th3` 是應用 Otsu 閾值后的輸出圖像。
請注意,`cv.threshold` 函數的第二個參數 `0` 通常是不必要的,因為它是默認值。您可以直接使用 `cv.threshold(img, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)`。
最后,`ret1`、`ret2` 和 `ret3` 都是布爾值,表示閾值化操作是否成功。`th1`、`th2` 和 `th3` 是閾值化后的輸出圖像。
可以看到是沒什么區別,因為圖像噪聲很小,進行高斯濾波后變化不大?
添加高斯噪聲后,可以看到有明顯的效果?
加入噪聲的代碼: img = util.random_noise(img, mode='gaussian', mean=0, var=0.05) img = np.uint8(img * 255)在 OpenCV 中,
util.random_noise(img, mode='gaussian', mean=0, var=0.05)
?函數的?img
?參數是一個圖像數據,而?util.random_noise
?函數是添加高斯噪聲的函數。當您調用?
util.random_noise
?函數并將?img
?作為第一個參數傳遞時,它會根據您指定的參數(在這個例子中是?mode='gaussian'
、mean=0
?和?var=0.05
)為圖像添加高斯噪聲。
mode='gaussian'
?指定使用高斯噪聲模式。mean=0
?指定噪聲的均值(中心點),這里設置為 0,意味著噪聲的平均值是 0。var=0.05
?指定噪聲的方差,它決定了噪聲的強度。方差值越大,噪聲越強。在這個例子中,方差為 0.05,意味著噪聲強度較小。添加噪聲后,
util.random_noise
?函數將返回一個包含噪聲的圖像。這個圖像的數據類型將與輸入圖像的數據類型相同。如果輸入圖像是一個浮點數圖像,那么添加噪聲后的輸出也將是一個浮點數圖像。如果輸入圖像是一個整數圖像,那么添加噪聲后的輸出將是一個浮點數圖像。因此,
util.random_noise(img, mode='gaussian', mean=0, var=0.05)
?的輸出結果將是一個浮點數圖像,其中包含了根據指定參數添加的高斯噪聲。
5. 二值化原理
?由于我們使用的是雙峰圖像,因此 Otsu 的算法試圖找到一個閾值(t),該閾值將由下式計算得到的類內加權方差最小化。
它實際上找到一個 T 值,它位于兩個峰值之間,使得兩個類的方差最小。它可以簡單地在 python 中實現,如下所示:
img = cv.imread('noisy2.png',0)
blur = cv.GaussianBlur(img,(5,5),0)
# 找到歸一化直方圖還有累計分布函數
hist = cv.calcHist([blur],[0],None,[256],[0,256])
hist_norm = hist.ravel()/hist.max()
Q = hist_norm.cumsum()
bins = np.arange(256)
fn_min = np.inf
thresh = -1
for i in xrange(1,256):p1,p2 = np.hsplit(hist_norm,[i]) # 概率q1,q2 = Q[i],Q[255]-Q[i] # 類別總和b1,b2 = np.hsplit(bins,[i]) # 權重# f 找到均值與方差m1,m2 = np.sum(p1*b1)/q1, np.sum(p2*b2)/q2v1,v2 = np.sum(((b1-m1)**2)*p1)/q1,np.sum(((b2-m2)**2)*p2)/q2# 計算最小函數fn = v1*q1 + v2*q2if fn < fn_min:fn_min = fnthresh = i
# 用 OpenCV 函數的 otsu'閾值
ret, otsu = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
print( "{} {}".format(thresh,ret) )
Q = hist_norm.cumsum()
在您提供的代碼片段中,您正在計算直方圖的累積分布函數(CDF)。累積分布函數(CDF)是概率論中的一個概念,它給出了隨機變量小于或等于某個值的概率。在圖像處理中,累積分布函數通常用于閾值選擇,特別是在 Otsu 閾值化方法中。
在您的代碼中,`Q = hist_norm.cumsum()` 意味著:
- `hist_norm` 是歸一化的直方圖,其值范圍在 [0, 1]。
- `hist_norm.cumsum()` 計算直方圖的累積和,即從左到右遍歷直方圖,計算累積的概率。
這個累積和形成了一個新的數組 `Q`,其中 `Q[i]` 表示隨機變量小于或等于 `i` 的概率。這個數組 `Q` 就是累積分布函數(CDF)。
累積分布函數在 Otsu 閾值化方法中的作用是幫助選擇一個閾值,使得背景和前景之間的類間方差最大。Otsu 閾值化的基本思想是找到一個閾值,使得前景和背景之間的類間方差最大。在這個上下文中,類間方差可以通過計算前景和背景的概率分布的方差來估計。
請注意,Otsu 閾值化通常使用 OpenCV 的 `cv.threshold(blur, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)` 函數來實現,而不是手動計算累積分布函數。手動計算可能會導致不準確的結果,并且通常不如 OpenCV 的內置函數高效。?
p1,p2 = np.hsplit(hist_norm,[i]) # 概率
在您提供的代碼片段中,您似乎正在嘗試實現一個自定義的 Otsu 閾值選擇方法。在這個方法中,您使用了 NumPy 的 `np.hsplit` 函數來分割直方圖。
`np.hsplit` 函數用于將一個數組水平分割成多個子數組。它接受一個數組和一個或多個分割點作為參數,并將數組分割成多個子數組。
在您的代碼中,`p1, p2 = np.hsplit(hist_norm, [i])` 意味著:
- `hist_norm` 是歸一化的直方圖。
- `[i]` 是分割點,表示您想要將直方圖分割成兩部分,其中一部分包含小于或等于 `i` 的像素,另一部分包含大于 `i` 的像素。
函數 `np.hsplit` 返回兩個子數組,分別對應于小于或等于 `i` 的像素和大于 `i` 的像素。這兩個子數組分別被賦值給 `p1` 和 `q2`。
請注意,如果您想要使用 OpenCV 的 `cv.threshold` 函數來應用 Otsu 閾值,您應該使用 `cv.threshold(blur, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)`,而不是手動計算閾值。手動計算閾值可能會導致不準確的結果,并且通常不如 OpenCV 的內置函數高效。
q1,q2 = Q[i],Q[255]-Q[i]?
在您提供的代碼片段中,您似乎正在嘗試實現一個自定義的 Otsu 閾值選擇方法。在這個方法中,您使用了累積分布函數(CDF)來分割直方圖,并計算兩個類別的概率。
在 Otsu 閾值化方法中,累積分布函數(CDF)被用來分割直方圖,從而得到兩個類別的概率分布。具體來說,您正在計算兩個類別(前景和背景)的概率。
在您的代碼中,`q1, q2 = Q[i], Q[255] - Q[i]` 意味著:
- `Q` 是累積分布函數(CDF)。
- `i` 是分割點,表示您想要將直方圖分割成兩部分,其中一部分包含小于或等于 `i` 的像素,另一部分包含大于 `i` 的像素。
- `Q[i]` 表示隨機變量小于或等于 `i` 的概率,即背景的概率。
- `Q[255] - Q[i]` 表示隨機變量大于 `i` 的概率,即前景的概率。
這兩個概率值 `q1` 和 `q2` 用于后續的計算,以估計前景和背景的方差。Otsu 閾值化的基本思想是找到一個閾值,使得前景和背景之間的類間方差最大。類間方差可以通過計算前景和背景的概率分布的方差來估計。
請注意,Otsu 閾值化通常使用 OpenCV 的 `cv.threshold(blur, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)` 函數來實現,而不是手動計算累積分布函數。手動計算可能會導致不準確的結果,并且通常不如 OpenCV 的內置函數高效。
b1,b2 = np.hsplit(bins,[i]) # 權重?
在您提供的代碼片段中,您似乎正在嘗試實現一個自定義的 Otsu 閾值選擇方法。在這個方法中,您使用了 NumPy 的 `np.hsplit` 函數來分割一個包含像素值的數組。
在您的代碼中,`b1, b2 = np.hsplit(bins, [i])` 意味著:
- `bins` 是一個包含像素值的數組,這些像素值代表圖像的灰度級。
- `[i]` 是分割點,表示您想要將像素值數組分割成兩部分,其中一部分包含小于或等于 `i` 的像素值,另一部分包含大于 `i` 的像素值。
函數 `np.hsplit` 返回兩個子數組,分別對應于小于或等于 `i` 的像素值和大于 `i` 的像素值。這兩個子數組分別被賦值給 `b1` 和 `b2`。
在 Otsu 閾值化方法中,`b1` 和 `b2` 通常被稱為權重,它們代表圖像中不同灰度級像素的數量。這些權重被用來計算前景和背景的加權方差,從而找到最佳的閾值。
請注意,Otsu 閾值化通常使用 OpenCV 的 `cv.threshold(blur, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)` 函數來實現,而不是手動計算權重。手動計算可能會導致不準確的結果,并且通常不如 OpenCV 的內置函數高效。
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
在您提供的代碼片段中,您似乎正在嘗試實現一個自定義的 Otsu 閾值選擇方法。在這個方法中,您正在計算兩個類別(前景和背景)的均值和方差。
在 Otsu 閾值化方法中,均值和方差被用來估計前景和背景的加權方差,從而找到最佳的閾值。加權方差是類內方差和類間方差的組合,用于評估不同閾值下的分割質量。
在您的代碼中,`m1, m2 = np.sum(p1*b1)/q1, np.sum(p2*b2)/q2` 意味著:
- `p1` 和 `p2` 是前景和背景的概率分布。
- `b1` 和 `b2` 是前景和背景的權重,即對應灰度級的像素數量。
- `q1` 和 `q2` 是前景和背景的概率。
這些概率和權重被用來計算兩個類別的均值:
- `m1` 是前景的均值,計算方法是將前景的概率分布 `p1` 乘以前景的權重 `b1`,然后除以前景的概率 `q1`。
- `m2` 是背景的均值,計算方法是將背景的概率分布 `p2` 乘以背景的權重 `b2`,然后除以背景的概率 `q2`。
接著,您計算了兩個類別的方差:
- `v1` 是前景的方差,計算方法是將前景的權重 `b1` 與前景的均值 `m1` 的差的平方乘以前景的概率分布 `p1`,然后除以前景的概率 `q1`。
- `v2` 是背景的方差,計算方法是將背景的權重 `b2` 與背景的均值 `m2` 的差的平方乘以背景的概率分布 `p2`,然后除以背景的概率 `q2`。
這些方差和均值值用于后續的計算,以估計前景和背景的加權方差,從而找到最佳的閾值。
請注意,Otsu 閾值化通常使用 OpenCV 的 `cv.threshold(blur, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)` 函數來實現,而不是手動計算均值和方差。手動計算可能會導致不準確的結果,并且通常不如 OpenCV 的內置函數高效。?