[圖像處理] 二值化閥值自動化篩選 - Otsu, 多重門檻值, 直方圖

Ben Hsu
10 min readApr 19, 2023

--

影像分割是圖像處理的基本技巧,本文將說明二值化閥值方法的基本原理,以及幾種進階的閥值定義方法,包括Otsu’s method、多重閥值、自適應閥值等數種算法,讓讀者在設計圖像分割算法的流程時,可以提升流程對新進圖片的適應程度。

目錄

  1. 為什麼需要自動化閥值?
  2. 二值化閥值篩選的基礎原理
  3. 自動定閥值的範例
  4. 用變異數決定二值化閥值的方法
  5. 多重閥值篩選的方法
  6. 結論

1. 為什麼需要自動化閥值?

圖像分割的二值化是一個常見的應用,但許多人對二值化的處理,是用土法煉鋼的方法,先人工選擇不同閥值進行測試,再挑選結果表現最好的當作閥值。

這樣的做法可以應付大部分的情況,但當我們想要大量使用在不同情境時,就需要減少人工決策的部分。並且,我們會希望設計的圖像處理流程對不同圖像有良好的適應性,而人工選擇的閥值會產生以下問題:

  1. 以人工進行閥值篩選時,不同人會出現不同的標準
  2. 多張圖像的閥值:通常會找一個最佳閥值用在所有圖像,但當多張圖像彼此的灰度值差異很大時,設定單一閥值就會導致分割效果不好。例如:多個鏡頭拍攝時,不同鏡頭間可能會有色調差異。

下面將說明如何將閥值篩選自動化的方法。

2. 二值化閥值篩選的基礎原理

透過繪製圖片像素的分布圖,找到灰度值不連續的位置作為閥值

圖 1(a) 是一張帶有漸層的圓形,而中間圓形的顏色較周遭顏色來的深,我們該怎樣把中間的圓提取出來?首先,我們可以將灰度值繪製成直方圖,看出哪些顏色在整張圖形占的比重較大,像圖 1(b) 的 0 到 60 附近有一個高峰,會對應到 圖1(a) 中圓內深色的部分;而90到220,則是 圖1(a) 圓外淺色的部分。所以只要將圖像分割的閥值,定在60至90之間,就可以完美的將中間的圓切開來,如 圖1(c) 是以75為閥值切割的結果。

圖1. 由左而右依序為 (a) 圓形;(b) 灰度值分布圖;(c) 圖像分割結果
### 執行環境 colab ###
import urllib.request
import numpy as np
from google.colab.patches import cv2_imshow
import matplotlib.pyplot as plt

###檔案讀取
req = urllib.request.urlopen('https://imgur.com/AX3EH43.jpg')
arr = np.asarray(bytearray(req.read()), dtype=np.uint8)

###彩色圖
img = cv2.imdecode(arr, -1)
cv2_imshow(img)

### 灰階圖:圖1a
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2_imshow(img)

### 畫圖直方圖:圖1b
plt.figure(figsize=(10, 10))
plt.hist(img.ravel(),256,[0,256])
plt.show()

### 繪製 mask:圖1c
ret, dst = cv2.threshold(img, 75, 255, cv2.THRESH_BINARY)
cv2_imshow(255-dst)

上述的方法是手動觀察、找到最佳切割點的方法,但許多時候圖片的分布很難靠著觀察找到合適的切割點。舉例而言,圖2(a) 是好吃好吃流沙包,圖2(b) 是他的分布圖,從圖上來看可能會切在80、160、甚至想切在200也不能說錯,但我們需要每次判斷時標準都是一樣的;另外,當手上的圖像不是一張,而是百張、千張時,很難人工逐張判讀。此時自動化閥值的決策方法就非常重要,接著先示範一個簡單決定閥值的方法。

圖2. 由左而右依序為 (a) 好吃好吃流沙包;(b) 流沙包的灰度值分布圖

3. 自動定閥值的範例

以灰度值中位數附近的最低點為閥值

我們嘗試將閥值篩選過程自動化。圖1(a) 目標是要提取圖片中間的深色圓型,可以預期圖像的直方圖會是一個雙峰分布,而我們只要找到雙峰間的低點,就可以完成切割。這裡示範自動化設定的方法:

Step1:繪製圖像灰度值的直方圖

Step2:找到分布圖的中位數,如下圖紅線

Step3:透過中位數設定一個尋找閥值的區域,如下圖藍線間的區域

如下圖中位數在120附近,此時我們可以設定 120 ± 50 的範圍,做為搜尋的區間。

Step4:選擇區域內直方圖數值最小的位置,作為閥值

所以在 120 ± 50 的範圍內,我們找尋灰度值最小的數值,就可以自動找到90為切割位置。

自動化的目的,是希望閥值能自動適應不同的圖像,圖3. 是多張圖型的切割狀況,第一至第四列依序為原始圖像、灰階圖像、分布圖、以及切割結果。

圖4. 不同圓形自動分割結果

4. 用變異數決定二值化閥值的方法

用某個閥值將圖像分成兩群時,使兩群間的變異最大,那就是最佳閥值!

上面的案例是用中位數找峰低點,這裡來講解要如何利用兩群資料的變異來進行分割。先說明組內變異的方法,概念是這樣的,如果我們切開兩群資料時,此時它們各自灰度值的變異數,相較其他切割閥值的變異數小,表示分割的兩群資料各自是很集中的,此時就會有好的分割效果。

  1. 先找一個閥值T,用這個T將像素切成兩群,如圖4. 紅線。
  2. 計算左邊黃色像素的變異數,得到σ₁
  3. 計算左邊黃色像素的變異數,得到σ₂
  4. 將 σ₁ 與 σ₂ 相加,得到σ
圖4. 分布圖分割示意圖

如果你有三個閥值T₁、T₂、T₃,你就可以得到三個變異數,σσσ₃,這時如果 σ₂ 是最小的,那閥值就是 T₂ 。而實際上,,算法會將所有可能的切割點位都掃過一遍,找到一個最佳的分割點 T。這個算法是讓組內變異 (within-group variation, wg)最小。

但除此之外,如果讓切開的兩群,彼此間的離散程度很大,也可以達到相同的目的。而這是使組間變異 (between-groups variance, bg )最大,也是Otsu所使用的算法:

剛剛我們嘗試分割 圖 3. 最右邊圓形時,無法得到好的結果,這裡分別使用組內變異、及組間變異閥值算法。組內變異的結果如 圖5(b);Otsu的結果如圖6(b),可以看到,兩種分割方法均能有效切割。

圖5. (a) 組內變異的切割效果,x軸為灰度值,y軸為組內變異,約在155變異最大;(b) 灰度值分布圖
圖6. (a) 組間變異的切割效果,x軸為灰度值,y軸為組內變異,約在155變異最大;(b) 灰度值分布圖

5. 多重閥值篩選的方法

很多情況,我們不只是想要進行二元切割,而是有多重分割的需求,這時要怎麼找到最佳閥值?以組內變異舉例,2組的閥值是將2組各別的變異數相加;而3組,其實就是3組變異數相加;而10組,就是10組相加。圖7. 為 2、3、4組的分割情形。

圖7. (a) 單一閥值分割;(b) 雙閥值分割;(c) 三閥值分割
### 多元閥值分割

from skimage import data
from skimage.filters import threshold_multiotsu

req = urllib.request.urlopen('https://imgur.com/AX3EH43.jpg')
req = urllib.request.urlopen('https://i.imgur.com/vb8OTNd.jpg')
arr = np.asarray(bytearray(req.read()), dtype=np.uint8)
img = cv2.imdecode(arr, -1)

plt.imshow(img[...,[2, 1, 0]])
plt.show()
plt.hist(img_gray4.ravel(),256,[0,256])
plt.show()


img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

### 你可以修改 threshold_multiotsu 的第二個參數,來決定要分割的群數
thresholds = threshold_multiotsu(img_gray, 2)
print(thresholds)
plt.figure(figsize=(5, 10))
plt.subplot(2, 1, 1)
plt.hist(img_gray.ravel(),256,[0,256])
plt.axvline(thresholds, color='r') # vertical
plt.subplot(2, 1, 2)
regions = np.digitize(img_gray, bins=thresholds)
plt.imshow(regions, cmap='gray'); plt.axis('off')
plt.show()

6. 結論

閥值篩選的自動化是將圖像處理系統化的一個重要元素,本文簡要的說明二值化閥值的基本原理,但上述範例都是在圖像完美的情況之下。但如果今天的圖像帶有雜訊、或是圖片有光源的差異,使用Otsu會發生什麼事,又要怎樣處理?下一篇文章,會說明一些常見汙染的圖像處理,並且說明進階的二值化處理方法。

--

--