1. 程式人生 > >影象二值化方法及適用場景分析(OTSU Trangle 自適應閾值分割)

影象二值化方法及適用場景分析(OTSU Trangle 自適應閾值分割)

影象二值化

應用場景

二值影象處理與分析在機器視覺與機器人視覺中非常重要,涉及到非常多的影象處理相關的知識,常見的二值影象分析包括輪廓分析、物件測量、輪廓匹配與識別、形態學處理與分割、各種形狀檢測與擬合、投影與邏輯操作、輪廓特徵提取與編碼等。二值化方法

是一種應用廣泛的影象分割方法,恰當的二值化結果對於 文件影象分析、OCR以及醫學影象中對 DNA 點陣影象分析等起著至關重要的作用。

通常可以分為全域性二值化和區域性二值化方法兩類,前者將一個固定的閾值應用於整幅影象,簡單易行,但在光照不均勻的條件難以應用,如 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,否則設定為01: THRESH_BINARY_INV -當前點值大於閾值時,設定為0,否則設定為Maxval; 
2: THRESH_TRUNC -當前點值大於閾值時,設定為閾值,否則不改變; 
3: THRESH_TOZERO -當前點值大於閾值時,不改變,否則設定為04: THRESH_TOZERO_INV -當前點值大於閾值時,設定為0,否則不改變。 

設閾值T = 120 ,type分別取0-4,可以得到以下結果:
1) type = 0
在這裡插入圖片描述

2)type = 1
在這裡插入圖片描述
3)type = 2
在這裡插入圖片描述
4) type = 3
在這裡插入圖片描述
5) type = 4
在這裡插入圖片描述

總結:
要使用手動閾值分割法獲取理想的二值圖,很關鍵的兩個點是Ttype,需要多次嘗試,以經驗加列舉的方式挨個測試,最終確定一個合適的閾值。但此閾值僅適用於這一特定的場景,光照等因素的改變可能導致閾值不再適用。所以,此方法適用與場景單一、固定的場合,如工業車間、流水線等機器人視覺上。

自動閾值法

可以看出手閾值法需要多次嘗試且應用場景單一,侷限性較大。自動閾值分割方法可以根據環境進行閾值調整,適應性也更廣。自動閾值分割法包括基於灰度均值的自動分割、基於直方圖的自動分割和自適應閾值分割。下面分別對這幾種方法進行分析:

灰度均值法

此方法較為簡單,即將灰度影象的畫素均值作為閾值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開發實戰》