常用圖像閾值分割算法

2021-02-13 大勤喵

 閾值分割法可以說是圖像分割中的經典方法,它利用圖像中要提取的目標與背景在灰度上的差異,通過設置閾值來把像素級分成若干類,從而實現目標與背景的分離。

    一般流程:通過判斷圖像中每一個像素點的特徵屬性是否滿足閾值的要求,來確定圖像中的該像素點是屬於目標區域還是背景區域,從而將一幅灰度圖像轉換成二值圖像。

用數學表達式來表示,則可設原始圖像f(x,y),T為閾值,分割圖像時則滿足下式:

                         

按照閾值確定的來源,可以分成:

人工經驗選擇法;直方圖方法;類間方差法;自適應閾值等。

按照閾值確定中的運算範圍,可以分為全局閾值法,局部閾值法。

下面對各種方法進行混合展示:

第一類:全局閾值處理

圖像閾值化分割是一種傳統的最常用的圖像分割方法,因其實現簡單、計算量小、性能較穩定而成為圖像分割中最基本和應用最廣泛的分割技術。它特別適用於目標和背景佔據不同灰度級範圍的圖像。難點在於如何選擇一個合適的閾值實現較好的分割。

0.直接按照經驗給定閾值

我們自己根據需要處理的圖像的先驗知識,對圖像中的目標與背景進行分析。

*PDB文件問題

程序所依賴的所有動態連結庫(dll 文件)也會被編譯,編譯過程中每個 dll 都會產生一個pdb文件,又稱為「符號文件」,是一個存儲數據的信息文件,其包含 dll 庫在編譯過程的某些調試信息,例如程序中所用到的全局變量、局部變量、函數名以及他們的入口地址等。

http://c.biancheng.net/view/474.html

1.基本全局閾值

這個也是直方圖的方法:利用直方圖進行分析,並根據直方圖的波峰和波谷之間的關係,選擇出一個較好的閾值。這樣方法,準確性較高,但是只對於存在一個目標和一個背景的,且兩者對比明顯的圖像,且直方圖是雙峰的那種最有價值。

2. 邊緣改進全局閾值

3.用拉普拉斯邊緣信息改進全局閾值處理

4. 最大類間方差法(OTSU)

    OTSU是一種使用最大類間方差的自動確定閾值的方法。是一種基於全局的二值化算法,它是根據圖像的灰度特性,將圖像分為前景和背景兩個部分。當取最佳閾值時,兩部分之間的差別應該是最大的,在OTSU算法中所採用的衡量差別的標準就是較為常見的最大類間方差。前景和背景之間的類間方差如果越大,就說明構成圖像的兩個部分之間的差別越大,當部分目標被錯分為背景或部分背景被錯分為目標,都會導致兩部分差別變小,當所取閾值的分割使類間方差最大時就意味著錯分概率最小。

 

    記T為前景與背景的分割閾值,前景點數佔圖像比例為w0,平均灰度為u0;背景點數佔圖像比例為w1,平均灰度為u1,圖像的總平均灰度為u,前景和背景圖象的方差g,則有:

                                              

 

聯立上式得:

                                                             

或:

                                                                       

 

        當方差g最大時,可以認為此時前景和背景差異最大,此時的灰度T是最佳閾值。類間方差法對噪聲以及目標大小十分敏感,它僅對類間方差為單峰的圖像產生較好的分割效果。當目標與背景的大小比例懸殊時(例如受光照不均、反光或背景複雜等因素影響),類間方差準則函數可能呈現雙峰或多峰,此時效果不好。

5.最大熵閾值分割法(KSW熵算法)

最大熵閾值分割法和OTSU算法類似,假設將圖像分為背景和前景兩個部分。熵代表信息量,圖像信息量越大,熵就越大,最大熵算法就是找出一個最佳閾值使得背景與前景兩個部分熵之和最大。

 熵的公式:

                                

 

第二類:自適應閾值分割

在分割過程中對圖像上的每個像素都使用了相等的閾值。但在實際情況中,當照明不均勻、有突發噪聲或者背景變化較大時,整幅圖像分割時將沒有合適的單一閾值,如果仍採用單一的閾值去處理每一個像素,可能會將目標和背景區域錯誤劃分。而自適應閾值分割的思想,將圖像中每個像素設置可能不一樣的閾值。

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

基本原理:

    一種較為簡單的自適應閾值選取方法是對每個像素確定以其自身為中心的一個領域窗口,尋找窗口內像素的最大值與最小值,並取二者的平均值作為閾值,或者將窗口內所有像素的平均值作為閾值,亦或者將窗口內的所有像素的高斯卷積作為閾


當背景不同的時候,局部閾值的還是效果好一些。

下面對幾種代表性的方法進行對比:

函數如下:

// 大津閾值

Mat OtsuAlgThreshold(Mat &image)

{

if (image.channels() != 1)

{

cout << "Please input Gray-image!" << endl;

}

int T = 0; //Otsu算法閾值  

double varValue = 0; //類間方差中間值保存

double w0 = 0; //前景像素點數所佔比例  

double w1 = 0; //背景像素點數所佔比例  

double u0 = 0; //前景平均灰度  

double u1 = 0; //背景平均灰度  

double Histogram[256] = { 0 }; //灰度直方圖,下標是灰度值,保存內容是灰度值對應的像素點總數  

uchar *data = image.data;

double totalNum = image.rows*image.cols; //像素總數

for (int i = 0; i < image.rows; i++)

{

for (int j = 0; j < image.cols; j++)

{

if (image.at<uchar>(i, j) != 0) Histogram[data[i*image.step + j]]++;

}

}

int minpos, maxpos;

for (int i = 0; i < 255; i++)

{

if (Histogram[i] != 0)

{

minpos = i;

break;

}

}

for (int i = 255; i > 0; i--)

{

if (Histogram[i] != 0)

{

maxpos = i;

break;

}

}

for (int i = minpos; i <= maxpos; i++)

{

//每次遍歷之前初始化各變量  

w1 = 0;       u1 = 0;       w0 = 0;       u0 = 0;

//***********背景各分量值計算**************************  

for (int j = 0; j <= i; j++) //背景部分各值計算  

{

w1 += Histogram[j];   //背景部分像素點總數  

u1 += j * Histogram[j]; //背景部分像素總灰度和  

}

if (w1 == 0) //背景部分像素點數為0時退出  

{

break;

}

u1 = u1 / w1; //背景像素平均灰度  

w1 = w1 / totalNum; // 背景部分像素點數所佔比例

//***********背景各分量值計算**************************  

//***********前景各分量值計算**************************  

for (int k = i + 1; k < 255; k++)

{

w0 += Histogram[k];  //前景部分像素點總數  

u0 += k * Histogram[k]; //前景部分像素總灰度和  

}

if (w0 == 0) //前景部分像素點數為0時退出  

{

break;

}

u0 = u0 / w0; //前景像素平均灰度  

w0 = w0 / totalNum; // 前景部分像素點數所佔比例  

//***********前景各分量值計算**************************  

//***********類間方差計算******************************  

double varValueI = w0 * w1*(u1 - u0)*(u1 - u0); //當前類間方差計算  

if (varValue < varValueI)

{

varValue = varValueI;

T = i;

}

}

Mat dst;

threshold(image, dst, T, 255, CV_THRESH_OTSU);

return dst;

}

// 自適應閾值

void myadaptive(InputArray _src, OutputArray _dst, double maxValue,

int method, int type, int blockSize, double delta)

{

Mat src = _src.getMat();

CV_Assert(src.type() == CV_8UC1);

CV_Assert(blockSize % 2 == 1 && blockSize > 1);

Size size = src.size();

_dst.create(size, src.type());

Mat dst = _dst.getMat();

if (maxValue < 0)

{

dst = Scalar(0);

return;

}

Mat mean;

if (src.data != dst.data)

mean = dst;

if (method == ADAPTIVE_THRESH_GAUSSIAN_C)

{

GaussianBlur(src, mean, Size(blockSize, blockSize), 0, 0, BORDER_REPLICATE);

}

else if (method == ADAPTIVE_THRESH_MEAN_C)

{

boxFilter(src, mean, src.type(), Size(blockSize, blockSize),

Point(-1, -1), true, BORDER_REPLICATE);

}

else

{

CV_Error(CV_StsBadFlag, "Unknown/unsupported adaptive threshold method");

}

int i, j;

uchar imaxval = saturate_cast<uchar>(maxValue);

int idelta = type == THRESH_BINARY ? cvCeil(delta) : cvFloor(delta);

uchar tab[768];

if (type == CV_THRESH_BINARY)

for (i = 0; i < 768; i++)

tab[i] = (uchar)(i - 255 > -idelta ? imaxval : 0);

else if (type == CV_THRESH_BINARY_INV)

for (i = 0; i < 768; i++)

tab[i] = (uchar)(i - 255 <= -idelta ? imaxval : 0);

else

{

CV_Error(CV_StsBadFlag, "Unknown/unsupported threshold type");

}

if (src.isContinuous() && mean.isContinuous() && dst.isContinuous())

{

size.width *= size.height;

size.height = 1;

}

for (i = 0; i < size.height; i++)

{

const uchar* sdata = src.data + src.step*i;

const uchar* mdata = mean.data + mean.step*i;

uchar* ddata = dst.data + dst.step*i;

for (j = 0; j < size.width; j++)

// 將[-255, 255] 映射到[0, 510]然後查表

ddata[j] = tab[sdata[j] - mdata[j] + 255];

}

}

// 最大熵閾值

Mat EntropySeg(Mat src)

{

int tbHist[256] = { 0 };

int index = 0;

double Property = 0.0;

double maxEntropy = -1.0;

double frontEntropy = 0.0;

double backEntropy = 0.0;

int TotalPixel = 0;

int nCol = src.cols*src.channels();

for (int i = 0; i < src.rows; i++)

{

uchar* pData = src.ptr<uchar>(i);

for (int j = 0; j < nCol; j++)

{

++TotalPixel;

tbHist[pData[j]] += 1;

}

}

for (int i = 0; i < 256; i++)

{

double backTotal = 0;

for (int j = 0; j < i; j++)

{

backTotal += tbHist[j];

}

for (int j = 0; j < i; j++)

{

if (tbHist[j] != 0)

{

Property = tbHist[j] / backTotal;

backEntropy += -Property * logf((float)Property);

}

}

for (int k = i; k < 256; k++)

{

if (tbHist[k] != 0)

{

Property = tbHist[k] / (TotalPixel - backTotal);

frontEntropy += -Property * logf((float)Property);

}

}

if (frontEntropy + backEntropy > maxEntropy)

{

maxEntropy = frontEntropy + backEntropy;

index = i;

}

frontEntropy = 0.0;

backEntropy = 0.0;

}

Mat dst;

threshold(src, dst, index, 255, 0);

return dst;

}

//迭代閾值

Mat IterationThreshold(Mat src)

{

int width = src.cols;

int height = src.rows;

int hisData[256] = { 0 };

for (int j = 0; j < height; j++)

{

uchar* data = src.ptr<uchar>(j);

for (int i = 0; i < width; i++)

hisData[data[i]]++;

}

int T0 = 0;

for (int i = 0; i < 256; i++)

{

T0 += i * hisData[i];

}

T0 /= width * height;

int T1 = 0, T2 = 0;

int num1 = 0, num2 = 0;

int T = 0;

while (1)

{

for (int i = 0; i < T0 + 1; i++)

{

T1 += i * hisData[i];

num1 += hisData[i];

}

if (num1 == 0)

continue;

for (int i = T0 + 1; i < 256; i++)

{

T2 += i * hisData[i];

num2 += hisData[i];

}

if (num2 == 0)

continue;

T = (T1 / num1 + T2 / num2) / 2;

if (T == T0)

break;

else

T0 = T;

}

Mat dst;

threshold(src, dst, T, 255, 0);

return dst;

}

調試過程中:

*PDB文件問題

程序所依賴的所有動態連結庫(dll 文件)也會被編譯,編譯過程中每個 dll 都會產生一個pdb文件,又稱為「符號文件」,是一個存儲數據的信息文件,其包含 dll 庫在編譯過程的某些調試信息,例如程序中所用到的全局變量、局部變量、函數名以及他們的入口地址等。

http://c.biancheng.net/view/474.html

* 內存溢出問題通過修改絕對路徑或者將cpp與圖片放在同一層解決。

可見廣泛使用的大津法其實效果不是太好, 二值化後一些細節部分難以通過前景和背景的差異表現出來。最大熵法通過信息量最大化進行分割,有良好的的效果,但是在青蛙的帽子上面有誤差。局部自適應閾值法對深色的蝴蝶翅膀提取效果不好。

這兩個都效果還好,最大熵使得全局信息門限值最大化,但是畢竟是全局閾值的方法;自適應方法很明顯窗口大小太重要了,平緩背景下分割效果不好,對窗口大小進行調整:

OpenCV提供的API,對窗口大小進行調整後進一步實驗:

說明下各參數:

InputArray src:源圖像

OutputArray dst:輸出圖像,與源圖像大小一致

int adaptiveMethod:在一個鄰域內計算閾值所採用的算法,有兩個取值,分別為 ADAPTIVE_THRESH_MEAN_C 和 ADAPTIVE_THRESH_GAUSSIAN_C 。

ADAPTIVE_THRESH_MEAN_C的計算方法是計算出領域的平均值再減去第七個參數double C的值。

ADAPTIVE_THRESH_GAUSSIAN_C的計算方法是計算出領域的高斯均值再減去第七個參數double C的值。

int thresholdType:這是閾值類型,只有兩個取值,分別為 THRESH_BINARY 和THRESH_BINARY_INV  具體的請看官方的說明,這裡不多做解釋。

int blockSize:adaptiveThreshold的計算單位是像素的鄰域塊,這是局部鄰域大小,3、5、7等。

double C:這個參數實際上是一個偏移值調整量,用均值和高斯計算閾值後,再減或加這個值就是最終閾值

將閾值從15(左圖)改成35(右圖)後好多了,但是又有點過了.怎麼樣才能自動確定出最佳窗口大小呢,如果 運行成本低又適應不同粒度區域的方法的話就很好了..

* 不同粒度程度用不同的窗口,是不是會出現邊緣提取不連接的情況?林洪磊師兄的那個長方形窗口好處是啥/

相關焦點

  • 數字圖像處理中常用圖像分割算法有哪些?
    擊上方「新機器視覺」,選擇加"星標"或「置頂」重磅乾貨,第一時間送達數字圖像處理中常用圖像分割算法有哪些
  • 【好設計論文】​一種改進的二維Otsu閾值分割算法
    二維Otsu算法是一維Otsu算法的推廣,它充分考慮了圖像的灰度信息和空間鄰域信息,可以有效濾除噪聲影響,但是同樣存在著運算量大、時效性差的問題。對此提出了一種改進的二維Otsu快速閾值分割算法,先將二維Otsu算法分解為兩個一維Otsu算法,併集成類間和類內方差信息構造了一種新的閾值判別函數,同時通過降維,進一步降低計算量。
  • 基於圖像的目標區域分割算法研究
    1 OTSU算法研究  1.1 OTSU算法的閾值分割  OTSU最早是在1979年被提出來,藉助灰度直方圖,通過閾值的方式將圖像進行分類,然後計算各類之間的方差,選取使類間方差最大時閾值作為最優閾值。本文實驗的對象背景單一,只需要進行單閾值就能將目標區域從圖像中分割出來。
  • 光照不均勻圖像分割技巧——分塊閾值
    前言  在數字圖像處理中,圖像分割是很關鍵的一步,當圖像質量較好,光照很均勻的時候只需用全局閾值的方法就能很完美地完成圖像分割任務,但是有些時候會遇到光照不均勻的現象,這個時候就需要用一些技巧才能達到比較好的分割效果,本文要介紹的是一種通過分塊閾值進行分割的方法。
  • 光照不均勻圖像分割技巧1——分塊閾值
    前言  在數字圖像處理中,圖像分割是很關鍵的一步,當圖像質量較好,光照很均勻的時候只需用全局閾值的方法就能很完美地完成圖像分割任務,但是有些時候會遇到光照不均勻的現象,這個時候就需要用一些技巧才能達到比較好的分割效果,本文要介紹的是一種通過分塊閾值進行分割的方法。
  • OpenCV-Python 圖像分割與Watershed算法|三十四
    目標在本章中,我們將學習使用分水嶺算法實現基於標記的圖像分割我們將看到:cv.watershed()理論任何灰度圖像都可以看作是一個地形表面,其中高強度表示山峰,低強度表示山谷。你開始用不同顏色的水(標籤)填充每個孤立的山谷(局部最小值)。隨著水位的上升,根據附近的山峰(坡度),來自不同山谷的水明顯會開始合併,顏色也不同。
  • 圖像分割方法介紹及應用
    一、圖像分割方法介紹(一)基於閾值的分割方法灰度閾值分割法是一種最常用的並行區域技術,它是圖像分割中應用數量最多的一類。閾值分割方法實際上是輸入圖像f到輸出圖像g的如下變換:其中,T為閾值,對於物體的圖像元素,g(i,j)=1,對於背景的圖像元素,g(i,j)=0。
  • 【動手學計算機視覺】第三講:圖像預處理之圖像分割
    非語義分割在圖像分割中所佔比重更高,目前算法也非常多,研究時間較長,而且算法也比較成熟,此類圖像分割目前的算法主要有以下幾種:閾值分割是圖像分割中應用最多的一類,該算法思想比較簡單,給定輸入圖像一個特定閾值,如果這個閾值可以是灰度值,也可以是梯度值,如果大於這個閾值,則設定為前景像素值,如果小於這個閾值則設定為背景像素值。
  • 常用圖像邊緣檢測算法詳解
    當對精度要求不是很高時,是一種較為常用的邊緣檢測方法。Sobel算子並沒有將圖像的主題與背景嚴格地區分開來,換言之就是Sobel算子並沒有基於圖像灰度進行處理,由於Sobel算子並沒有嚴格地模擬人的視覺生理特徵,所以提取的圖像輪廓有時並不能令人滿意。
  • 基於OpenCV的圖像分割處理!
    圖像閾值化分割是一種傳統的最常用的圖像分割方法,因其實現簡單、計算量小、性能較穩定而成為圖像分割中最基本和應用最廣泛的分割技術。通常情況下對於色彩均衡的圖像,直接將閾值設為127即可,但有時圖像灰度級的分布是不均衡的,如果此時還將閾值設為127,那麼閾值處理的結果就是失敗的。所以需要找出圖像的最佳的分割閾值。OTSU就是獲得最佳閾值的方法。OTSU(大津法)是一種確定圖像二值化分割閾值的算法,由日本學者大津於1979年提出。
  • 一文概述用 python 的 scikit-image 模塊進行圖像分割
    圖像分割本質上是將數字圖像分割成多個片段的過程,以簡化或將圖像的表示方式更改為更有意義和更易於分析的內容。在本文中,我們結合監督算法和無監督算法來處理分割過程。scikit-image 庫中可用的一些分割算法 監督分割算法:一些可能來自人類輸入的先驗知識被用來指導算法。無監督分割算法:不需要先驗知識。這些算法試圖將圖像自動細分到有意義的區域。
  • 快速圖像分割的SuperBPD方法
    Boundary-to-Pixel Direction for Fast Image Segmentationarxiv.org代碼下載地址:JianqiangWan/Super-BPDgithub.comAbstract本文提出了一種基於超邊界到像素方向的圖像快速分割方法和自定義分割算法
  • 基於新閾值函數的小波閾值去噪算法
    小波變換以其多解析度分析的特性,在時頻域內良好的表徵信號的能力以及大小固定形狀可變的窗口等特點,廣泛應用於圖像去噪中,並得到了很好的去噪效果。而小波閾值去噪法是小波分析法在圖像去噪眾多應用中最常用的一種方法,利用閾值處理後的小波係數進行小波反變換重構出去噪後的結果圖像。
  • 腦部MR圖像的Sigma-IFCM分割算法分析
    在諸多的圖像分割算法中,模糊C均值(FCM)分割算法是目前應用最廣泛的分割算法之一。最早由Dunn提出,後經Bezdek改進。由於模糊集理論對圖像的不確定性有較好的描述能力,因此FCM算法在醫學圖像分割中取得了良好的分割效果。最早把FCM算法用於醫學腦部圖像分割的是LiC L等人。
  • 基於Sigma-IFCM分割算法的腦部MR圖像
    本文引用地址:http://www.eepw.com.cn/article/199482.htm在諸多的圖像分割算法中,模糊C均值(FCM)分割算法是目前應用最廣泛的分割算法之一。最早由Dunn提出,後經Bezdek改進。由於模糊集理論對圖像的不確定性有較好的描述能力,因此FCM算法在醫學圖像分割中取得了良好的分割效果。
  • 深度學習中的圖像分割:方法和應用
    圖像分割是計算機視覺中的一個關鍵過程。它包括將視覺輸入分割成片段以簡化圖像分析。片段表示目標或目標的一部分,並由像素集或「超像素」組成。圖像分割將像素組織成更大的部分,消除了將單個像素作為觀察單位的需要。
  • 圖像去噪算法的優點和缺點
    圖像去噪算法的優點和缺點 會飛的碼 發表於 2020-05-04 18:36:00 圖像降噪算法總結 分析各種算法的優點和缺點 1、BM3D
  • 浙大博士生劉漢唐:帶你回顧圖像分割的經典算法 | 分享總結
    雷鋒網AI科技評論按:圖像語義分割是 AI 領域中一個重要的分支,是機器視覺技術中關於圖像理解的重要一環。近年的自動駕駛技術中,也需要用到這種技術。車載攝像頭探查到圖像,後臺計算機可以自動將圖像分割歸類,以避讓行人和車輛等障礙。隨著近些年深度學習的火熱,使得圖像分割有了巨大的發展,本文為大家介紹深度學習中圖像分割的經典算法。
  • 基礎圖像處理-常用對比度調整方法(1)
    缺點:   1)變換後圖像的灰度級減少,某些細節消失;   2)某些圖像,如直方圖有高峰,經處理後對比度不自然的過分增強。 算法具體:根據圖像灰度計算灰度概率密度函數PDF(概率密度函數)計算累積概率分布函數CDF(累積分布函數)將CDF歸一化到原圖灰度取值範圍,如[0,255]。
  • 100個深度圖像分割算法,紐約大學UCLA等最新綜述論文
    對近幾年深度學習圖像分割進行了全面綜述,對現有的深度學習圖像分割研究進行梳理使其系統化,並提出6方面挑戰,幫助讀者更好地了解當前的研究現狀和思路。可作為相關領域從業者的必備參考文獻。    基於深度學習的二維圖像分割算法的時間軸。