Task05 OpenCV框架與圖像分割/二值化

計算機視覺基礎-圖像處理(上)-Task05 圖像分割/二值化

5.1 簡介

該部分的學習內容是對經典的閾值分割算法進行回顧,圖像閾值化分割是一種傳統的最常用的圖像分割方法,因其實現簡單、計算量小、性能較穩定而成爲圖像分割中最基本和應用最廣泛的分割技術。它特別適用於目標和背景佔據不同灰度級範圍的圖像。它不僅可以極大的壓縮數據量,而且也大大簡化了分析和處理步驟,因此在很多情況下,是進行圖像分析、特徵提取與模式識別之前的必要的圖像預處理過程。圖像閾值化的目的是要按照灰度級,對像素集合進行一個劃分,得到的每個子集形成一個與現實景物相對應的區域,各個區域內部具有一致的屬性,而相鄰區域不具有這種一致屬性。這樣的劃分可以通過從灰度級出發選取一個或多個閾值來實現。

5.2 學習目標

瞭解閾值分割基本概念

理解最大類間方差法(大津法)、自適應閾值分割的原理

掌握OpenCV框架下上述閾值分割算法API的使用

5.3 內容介紹

1、最大類間方差法、自適應閾值分割的原理

2、OpenCV代碼實踐

3、動手實踐並打卡(讀者完成)

5.4 算法理論介紹

5.4.1 最大類間方差法(大津閾值法)

大津法(OTSU)是一種確定圖像二值化分割閾值的算法,由日本學者大津於1979年提出。從大津法的原理上來講,該方法又稱作最大類間方差法,因爲按照大津法求得的閾值進行圖像二值化分割後,前景與背景圖像的類間方差最大。
它被認爲是圖像分割中閾值選取的最佳算法,計算簡單,不受圖像亮度和對比度的影響,因此在數字圖像處理上得到了廣泛的應用。它是按圖像的灰度特性,將圖像分成背景和前景兩部分。因方差是灰度分佈均勻性的一種度量,背景和前景之間的類間方差越大,說明構成圖像的兩部分的差別越大,當部分前景錯分爲背景或部分背景錯分爲前景都會導致兩部分差別變小。因此,使類間方差最大的分割意味着錯分概率最小。

應用: 是求圖像全局閾值的最佳方法,應用不言而喻,適用於大部分需要求圖像全局閾值的場合。
優點: 計算簡單快速,不受圖像亮度和對比度的影響。
缺點: 對圖像噪聲敏感;只能針對單一目標分割;當目標和背景大小比例懸殊、類間方差函數可能呈現雙峯或者多峯,這個時候效果不好。

原理非常簡單,涉及的知識點就是均值、方差等概念和一些公式推導。爲了便於理解,我們從目的入手,反推一下這著名的OTSU算法。

求類間方差:
OTSU算法的假設是存在閾值TH將圖像所有像素分爲兩類C1(小於TH)和C2(大於TH),則這兩類像素各自的均值就爲m1、m2,圖像全局均值爲mG。同時像素被分爲C1和C2類的概率分別爲p1、p2。因此就有:
在這裏插入圖片描述
根據原文,式(4)還可以進一步變形:
在這裏插入圖片描述
分割:
這個分割就是二值化,OpenCV給了以下幾種方式,很簡單,可以參考:
在這裏插入圖片描述

5.4.2 自適應閾值

前面介紹了OTSU算法,但這算法屬於全局閾值法,所以對於某些光照不均的圖像,這種全局閾值分割的方法會顯得蒼白無力,如下圖:
在這裏插入圖片描述
顯然,這樣的閾值處理結果不是我們想要的,那麼就需要一種方法來應對這樣的情況。

這種辦法就是自適應閾值法(adaptiveThreshold),它的思想不是計算全局圖像的閾值,而是根據圖像不同區域亮度分佈,計算其局部閾值,所以對於圖像不同區域,能夠自適應計算不同的閾值,因此被稱爲自適應閾值法。(其實就是局部閾值法)

如何確定局部閾值呢?可以計算某個鄰域(局部)的均值、中值、高斯加權平均(高斯濾波)來確定閾值。值得說明的是:如果用局部的均值作爲局部的閾值,就是常說的移動平均法。

5.5 基於OpenCV的實現

效果
1.簡單閥值
cv2.threshold , cv2.adaptiveThreshold
當像素值高於閥值時,我們給這個像素賦予一個新值(可能是白色),否則我們給它賦予另外一種顏色(也許是黑色)。這個函數就是cv2.threshold()。這個函數的第一個參數就是原圖像,原圖像應該是灰度圖。第二個參數就是用來對像素值進行分類的閥值,第三個參數就是當像素值高於(或者小於)閥值時,應該被賦予新的像素值。OpenCV提供了多種不同的閥值方法,這是有第四個參數來決定的。方法包括:
cv2.THRESH_BINARY
cv2.THRESH_BINARY_INV
cv2.THRESH_TRUNC
cv2.THRESH_TOZERO
cv2.THRESH_TOZERO_INV

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('storm.png',0)
ret , thresh1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
ret , thresh2 = cv2.threshold(img,127,255,cv2.THRESH_BINARY_INV)
ret , thresh3 = cv2.threshold(img,127,255,cv2.THRESH_TRUNC)
ret , thresh4 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO)
ret , thresh5 = cv2.threshold(img,127,255,cv2.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()

在這裏插入圖片描述
2.自適應閥值

根據圖像上的每一個小區域計算與其對應的閥值。因此在同一幅圖像上的不同區域採用的是不同的閥值,從而使我們能在亮度不同的情況下得到更好的結果。
這種方法需要我們指定三個參數,返回值只有一個。
Adaptive Method 指定計算閥值的方法
-cv2.ADAPTIVE_THRESH_MEAN_C:閥值取自相鄰區域的平均值
-cv2.ADAPTIVE_THRESH_GAUSSIAN_C:閥值取自相鄰區域的加權和,權重爲一個高斯窗口
Block Size 鄰域大小(用來計算閥值的區域大小)
C這就是一個常數,閥值就等於的平均值或者加權平均值減去這個常數

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('storm.png',0)
#中值濾波
img = cv2.medianBlur(img,5)

ret , th1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
# 11爲block size,2爲C值
th2 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C , cv2.THRESH_BINARY,11,2 )
th3 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C , cv2.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’s二值化

我們前面說到,cv2.threshold函數是有兩個返回值的,前面一直用的第二個返回值,也就是閾值處理後的圖像,那麼第一個返回值(得到圖像的閾值)將會在這裏用到。
前面對於閾值的處理上,我們選擇的閾值都是127,那麼實際情況下,怎麼去選擇這個127呢?有的圖像可能閾值不是127得到的效果更好。那麼這裏我們需要算法自己去尋找到一個閾值,而Otsu’s就可以自己找到一個認爲最好的閾值。並且Otsu’s非常適合於圖像灰度直方圖具有雙峯的情況,他會在雙峯之間找到一個值作爲閾值,對於非雙峯圖像,可能並不是很好用。那麼經過Otsu’s得到的那個閾值就是函數cv2.threshold的第一個參數了。因爲Otsu’s方法會產生一個閾值,那麼函數cv2.threshold的的第二個參數(設置閾值)就是0了,並且在cv2.threshold的方法參數中還得加上語句cv2.THRESH_OTSU。

import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('storm.png',0)

ret1,th1=cv2.threshold(img,127,255,cv2.THRESH_BINARY)

ret2,th2=cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
#(5,5)爲高斯核的大小,0爲標準差
blur= cv2.GaussianBlur(img,(5,5),0)

#閥值一定要設爲0
ret3,th3=cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)

images=[img,0,th1,
         img,0,th2,
         img,0,th3]
titles =['original noisy image','histogram','global thresholding(v=127)',
          'original noisy image','histogram',"otsu's thresholding",
          'gaussian giltered image','histogram',"otus's thresholding"]
#這裏使用了pyplot中畫直方圖的方法,plt.hist要注意的是他的參數是一維數組
#所以這裏使用了(numpy)ravel方法,將多維數組轉換成一維,也可以使用flatten方法
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文檔: https://docs.opencv.org/3.1.0/d7/d1b/groupimgprocmisc.html#gae8a4a146d1ca78c626a53577199e9c57

博客:
https://blog.csdn.net/weixin_40647819/article/details/90179953
https://blog.csdn.net/weixin_40647819/article/details/90213858

python版本:
https://www.kancloud.cn/aollo/aolloopencv/267591 http://www.woshicver.com/FifthSection/43%E5%9B%BE%E5%83%8F%E9%98%88%E5%80%BC/
5.6 總結
該部分對兩種經典閾值分割方法進行了介紹,讀者可根據提供的資料進行學習,然後參考示例代碼自行實現。Otsu的二值化有一些優化方法,讀者可以嘗試學習並實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章