影象二值化方法及適用場景分析(OTSU Trangle 自適應閾值分割)
影象二值化
應用場景
二值影象處理與分析在機器視覺與機器人視覺中非常重要,涉及到非常多的影象處理相關的知識,常見的二值影象分析包括輪廓分析、物件測量、輪廓匹配與識別、形態學處理與分割、各種形狀檢測與擬合、投影與邏輯操作、輪廓特徵提取與編碼等。二值化方法
通常可以分為全域性二值化和區域性二值化方法兩類,前者將一個固定的閾值應用於整幅影象,簡單易行,但在光照不均勻的條件難以應用,如 Otsu 法;後者則針對影象的不同部分採用不同的閾值來解決光照問題,其閾值實際是一個隨畫素變化的曲面。本文將著重介紹如何從一幅影象中獲取有目的性的二值影象。
二值影象定義
二值影象就是隻有黑白兩種顏色表示的影象,在數字上用0 表示黑色(0),1表示白色(255) 。在實際場景中,二值影象的獲得一般需要經過如下過程。
從灰度影象到二值影象,本質上是對資料的二分類分割,所以很多資料處理的方法都可以使用,但是影象是特殊型別的資料,它有很多限制條件,決定了只有一些合適的方法才會取得比較好的效果。這些演算法的最主要的一個任務就是尋找合理的分割閾值T。
閾值獲取的方法
二值化分割可分為手動閾值分割和自動閾值分割,前者是根據整幅影象的特徵進行分析進而確定,目前常用的方法包括手動閾值法及自動閾值法,
- 手動閾值分割
- 自動閾值分割
- 基於灰度圖均值的自動分割
- 基於直方圖的自動分割
- OTSU(直方圖出現雙峰)
- Triangle(直方圖出現單峰)
- 自適應閾值分割
下面以下圖為例,對上述幾種閾值分割方法進行分析和對比。
手動閾值法
該閾值T需要人為給定,取值範圍為0-255,常規的定義為灰度影象上某點畫素值 P(x, y) > T ? 255 : 0,除此之外,還有幾種變式如下:
- P(x, y) > T ? 0: 255,表示畫素值大於閾值時,為0,否則為255;
- P(x, y) > T ? T : 0 ,表示畫素值大於閾值時,為0,否則為255;
- P(x, y) > T ? T : P(x, y) ,表示畫素值大於閾值時,為閾值,否則為原畫素;
- P(x, y) > T ? P(x, y) : 0 ,表示畫素值大於閾值時,為原畫素,否則為0;
- P(x, y) > T ? 0 : P(x, y) ,表示畫素值大於閾值時,為0,否則為原畫素;
圖解表示為:
在OPENCV中有相應的API可以使用,可以根據任務需求,選擇不同的type值。
函式原型:
double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)
引數含義:
InputArray src -輸入原影象,需為灰度影象
OutputArray dst -輸出影象
double thresh -閾值大小 (即閾值T)
double maxval -最大值 (一般指定為255,也可指定為其他數值)
int type -閾值模式(很重要,它決定這個函式的變式,0為常規型的,1-4均為變式)
其中,type可以取0-4中的任一個值,對應的含義如下:
0: THRESH_BINARY -當前點值大於閾值時,取Maxval,否則設定為0 ;
1: THRESH_BINARY_INV -當前點值大於閾值時,設定為0,否則設定為Maxval;
2: THRESH_TRUNC -當前點值大於閾值時,設定為閾值,否則不改變;
3: THRESH_TOZERO -當前點值大於閾值時,不改變,否則設定為0 ;
4: THRESH_TOZERO_INV -當前點值大於閾值時,設定為0,否則不改變。
設閾值T = 120 ,type分別取0-4,可以得到以下結果:
1) type = 0
2)type = 1
3)type = 2
4) type = 3
5) type = 4
總結:
要使用手動閾值分割法獲取理想的二值圖,很關鍵的兩個點是T與type,需要多次嘗試,以經驗加列舉的方式挨個測試,最終確定一個合適的閾值。但此閾值僅適用於這一特定的場景,光照等因素的改變可能導致閾值不再適用。所以,此方法適用與場景單一、固定的場合,如工業車間、流水線等機器人視覺上。
自動閾值法
可以看出手閾值法需要多次嘗試且應用場景單一,侷限性較大。自動閾值分割方法可以根據環境進行閾值調整,適應性也更廣。自動閾值分割法包括基於灰度均值的自動分割、基於直方圖的自動分割和自適應閾值分割。下面分別對這幾種方法進行分析:
灰度均值法
此方法較為簡單,即將灰度影象的畫素均值作為閾值T,得到的影象如下圖所示,關鍵程式碼為:
Mat src = imread("E:/images/aaa.jpg");
Mat gray, gray_mean;
cvtColor(src, gray, COLOR_BGR2GRAY);
meanStdDev(gray, gray_mean, mat_stddev);
double m;
m = mat_mean.at<double>(0, 0);
binary = Mat::zeros(src.size(), CV_8UC1);
int height = gray.rows;
int width = gray.cols;
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
int pv = gray.at<uchar>(row, col);
if (pv > m) {
binary.at<uchar>(row, col) = 255;
}
else {
binary.at<uchar>(row, col) = 0;
}
}
}
imshow("binary", binary);
waitKey(0);
return 0;
}
基於直方圖均值法
此方法是根據直方圖的特徵進行閾值的選擇和求取,在此之前,我們先獲取下灰度影象的直方圖分佈圖如圖所示,關鍵程式碼為如下,這個過程在使用直方圖均值法是不需要的,為了說明問題,單獨顯示下直方圖:
// 本段程式碼,在二值化中不需要,opencv中API中已包括,只是為了說明問題
Mat src = imread("E:/images/aaa.jpg");
Mat gray, gray_mean;
cvtColor(src, gray, COLOR_BGR2GRAY);
//定義變數
Mat dstHist;
int dims = 1;
float hranges[] = {0, 256};
const float *ranges[] = {hranges}; // 這裡需要為const型別
int size = 256;
int channels = 0; //計算影象的直方圖
calcHist(&gray, 1, &channels, Mat(), dstHist, dims, &size, ranges);
Mat dstImage(size, size, CV_8U, Scalar(0)); //獲取最大值和最小值
double minValue = 0;
double maxValue = 0;
minMaxLoc(dstHist,&minValue, &maxValue, 0, 0); // 在cv中用的是cvGetMinMaxHistValue //繪製出直方圖 //saturate_cast函式的作用即是:當運算完之後,結果為負,則轉為0,結果超出255,則為255。
int hpt = saturate_cast<int>(0.9 * size);
for(int i = 0; i < 256; i++)
{
float binValue = dstHist.at<float>(i); // 注意hist中是float型別
int realValue = saturate_cast<int>(binValue * hpt/maxValue);
line(dstImage,Point(i, size - 1),Point(i, size - realValue),Scalar(255));
}
imshow("一維直方圖", dstImage);
}
可以從直方圖分佈圖中看出,灰度多集中在靠近白色的一端,即只有一個明顯的峰值,整體影象偏白。在opencv中基於直方圖獲取閾值並分割的功能,實現方式仍然是使用threshold函式,只是將type型別宣告為THRESH_OTSU或THRESH_RRIANGLE即可。
函式原型:
double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)
引數含義:
InputArray src -輸入原影象,需為灰度影象
OutputArray dst -輸出影象
double thresh -閾值大小,取 0
double maxval -最大值,取 255
int type -閾值模式
其中,type可以取0-4中的任一個值,對應的含義如下:
THRESH_BINARY | THRESH_OTSU 使用最大類間差分法獲取閾值,然後再用該閾值進行二分 ;
THRESH_BINARY | THRESH_TRIANGLE 使用三角法獲取閾值,然後再用該閾值進行二分 ;
OTSU演算法對直方圖有兩個峰,中間有明顯波谷的直方圖對應影象二值化效果比較好,而對於只有一個單峰的直方圖對應的影象分割效果沒有雙峰的好。
OTSU
OTSU的是通過計算類間最大方差來確定分割閾值的閾值選擇演算法,它的原理是不斷地求前景和背景的類件方差:
如圖所示,在灰度直方圖中,先將0,1,2三個灰度作為背景,求取背景類內方差,將3,4,5作為前景,求取前景類內方差。然後根據兩個類內方差,求取兩個類間的類間方差。如此,依次求 0 與1,2,3,4,5之間,0,1,與2,3,4,5之間,…直到遍歷完整個灰度組數。然後找類間方差最大的,則灰度分界也就找到了,此分界即為所要的閾值。在opencv中實現的方法如下:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat src = imread("E:/images/aaa.jpg");
Mat gray, binary;
cvtColor(src, gray, COLOR_BGR2GRAY);
double T = threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
cout<<"threshold : %.2f\n"<< T <<endl;
imshow("binary", binary);
waitKey(0);
return 0;
}
此時返回的閾值T = 157,即採用閾值157進行常規的二值分割,其分割的結果如下圖所示:
Triangle
在但是有時候影象的直方圖只有一個波峰,這個時候使用TRIANGLE方法尋找閾值是比較好的一個選擇。
OpenCV中TRIANGLE演算法使用只需要在 threshold函式的type型別宣告THRESH_TRIANGLE即可。程式如下:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat src = imread("E:/images/aaa.jpg");
Mat gray, binary;
cvtColor(src, gray, COLOR_BGR2GRAY);
double T = threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_TRIANGLE);
cout<<"threshold : %.2f\n"<< T <<endl;
imshow("binary", binary);
waitKey(0);
return 0;
}
此時返回的閾值T = 215,即採用閾值215進行常規的二值分割,其分割的結果如下圖所示:
自適應均值閾值分割方法
OpenCV中的自適應閾值演算法主要是基於均值實現,根據計算均值的方法不同分為box-filter模糊均值與高斯模糊均值,其一般步驟為:
在opencv中有相關的API可以呼叫,如下:
void cv::adaptiveThreshold( InputArray src, OutputArray dst, double maxValue, int
adaptiveMethod, int thresholdType, int blockSize, double C )
其中:
引數 | 取值 |
---|---|
blockSize | 取值必須是奇數,如果輸入影象較大,取127左右,對於小影象取25左右 |
C | 取值多少與效果有很大關係,不能取高,也不能取低,一般取值在10/15/25 |
adaptiveMethod | ADAPTIVE_THRESH_GAUSSIAN_C = 1 , ADAPTIVE_THRESH_MEAN_C = 0 |
thresholdType | THRESH_BINARY 二值影象 = 原圖 – 均值影象 > -C ? 255 : 0 ,THRESH_BINARY_INV 二值影象 = 原圖 – 均值影象 > -C ? 0 : 255 |
Mat src = imread("E:/images/aaa.jpg");
Mat gray, binary;
cvtColor(src, gray, COLOR_BGR2GRAY);
adaptiveThreshold(gray, binary, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 25, 10);
imshow("binary", binary);
waitKey(0);
return 0;
執行效果如下:
總結
在實際二值化應用中,閾值要根據實際場景和目標物特徵進行選擇,如果對於光照不均勻的場合應該選擇自適應閾值分割,對於直方圖出現明顯雙峰的應該選擇OTSU,對與直方圖出現單峰的應該選擇Triangle,雙峰還是單峰的分辨,可以看偏黑或偏白的佔比,也可嘗試執行兩種方法,觀察效果。
參考文獻
[1]: 賈志剛,《OpenCV Android開發實戰》