画像処理閾値分割のOTSU/大津閾値原理とその実現


画像処理閾値分割の最大クラス間分散法/大津法/OTSU
概要:
最大類間分散法は日本の学者大津が1979年に提案したもので、適応しきい値決定の方法であり、大津法とも呼ばれ、OTSUと略称されている.画像の階調特性に応じて、画像を背景とターゲットの2つの部分に分けます.背景とターゲットとの間のクラス間分散が大きいほど、画像を構成する2つの部分の差が大きいほど、部分的なターゲットが背景にずれたり、部分的な背景が間違ってターゲットに分かれたりすると、2つの部分の差が小さくなることを示します.したがって,クラス間分散を最大にする分割は,誤分確率が最小であることを意味する.
サイズM×Nの画像I(x,y)は、前景(すなわち、目標)と背景の分割閾値をTとし、前景に属する画素点数が全体の画像に占める割合をTとするω0、平均階調μ0;背景画素の点数が全体の画像に占める割合はω1、その平均階調はμ1.画像の総平均階調はμ,クラス間分散をgと記す.画像中の画素の階調値が閾値Tより小さい画素個数をN 0、画素階調が閾値Tより大きい画素個数をN 1とすると、ω0=N0/M×N       (1)       ω1=N1/M×N       (2)       N0+N1=M×N       (3)       ω0+ω1=1            (4)       μ=ω0*μ0+ω1*μ1                          (5)       g=ω0(μ0-μ)^2+ω1(μ1-μ)^2          (6)  
1、アルゴリズムの原理
OTSUの式は、現在のしきい値がtである場合、
w 0:前景点の割合
w 1:背景点の割合、w 1=1-w 0
u 0:前景階調平均値
u 1:背景階調平均値
u:グローバル階調平均、u=w 0*u 0+w 1*u 1
g:クラス間最大分散、g=w 0(u 0-u)*(u 0-u)+w 1(u 1-u)*(u 1-u)=w 0*(1–w 0)*(u 0-u 1)*(u 0-u 1)
ターゲット関数はgであり,gが大きいほどtは良い閾値である.なぜこの関数を判別の根拠とするのか、直感的にはこの関数が前景と背景の差を反映しているのか、
差が大きいほど、前景と背景の差が大きくなり、しきい値が高くなります.
 
2、アルゴリズム実現疑似コード(C/C++/Python実現)
 
 
 
3、アルゴリズム実装コード(C/C++/Python実装)
(1)C/C++OTSU/大津閾値を実現し、コード:
/// 
///   OTSU  
/// 
///        >
///     >
///     >
/// 
int Otsu_byte(const uchar* src, const int width, const int height)
{
	int i = 0;
	float histogram[256];
	//unsigned char* temp=new uchar[width*height];

	for (i = 0; i < 256; i++)
	{
		histogram[i] = 0;
	}
	
	//histogram  
	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < width; j++)
		{
			histogram[src[i * width + j]]++;//       .
		}
	}
	//normalize histogram    
	int size = height * width;
	for (int i = 0; i < 256; i++)
	{
		histogram[i] = histogram[i] / size;//      .
	}

	//average pixel value    
	float avgValue = 0;
	for (int i = 0; i < 256; i++)
	{
		avgValue += i * histogram[i];//         .
	}

	int threshold;
	float maxVariance = 0;
	float w = 0, u = 0;
	for (int i = 0; i < 256; i++)
	{
		w += histogram[i];//      i   , 0~i      (                  )          .
		u += i * histogram[i];//   i      (0~i)      :          .

		float t = avgValue * w - u;
		float variance = t * t / (w * (1 - w));
		if (variance > maxVariance)
		{
			maxVariance = variance;
			threshold = i;
		}
	}

	//delete temp;
	return threshold;
}

 
 
(2)OpenCVによるOTSU/大津閾値の実現
OpenCVベースのIplImage:
//OTSU  
int Otsu(IplImage* src)
{
	int height=src->height;    
	int width=src->width;        

	//histogram    
	float histogram[256] = {0};    
	for(int i=0; i < height; i++)  
	{    
		unsigned char* p=(unsigned char*)src->imageData + src->widthStep * i;    
		for(int j = 0; j < width; j++)   
		{    
			histogram[*p++]++;//       .
		}    
	}    
	//normalize histogram    
	int size = height * width;     
	for(int i = 0; i < 256; i++)  
	{    
		histogram[i] = histogram[i] / size;//      .
	}    

	//average pixel value    
	float avgValue=0;    
	for(int i=0; i < 256; i++)  
	{    
		avgValue += i * histogram[i];//         .
	}     

	int threshold;      
	float maxVariance=0;    
	float w = 0, u = 0;    
	for(int i = 0; i < 256; i++)   
	{    
		w += histogram[i];//      i   , 0~i      (                  )          .
		u += i * histogram[i];//   i      (0~i)      :          .

		float t = avgValue * w - u;    
		float variance = t * t / (w * (1 - w) );    
		if(variance > maxVariance)   
		{    
			maxVariance = variance;    
			threshold = i;    
		}    
	}    

	return threshold;    
}

OpenCVベースMat:
#include   
#include 
#include 
#include 

using namespace std;
using namespace cv;

int otsuGray(const Mat src) 
{
    Mat img = src;
    int c = img.cols; //    
    int r = img.rows; //    
    int T = 0; //  
    uchar* data = img.data; //    
    int ftNum = 0; //      
    int bgNum = 0; //      
    int N = c*r; //     
    int ftSum = 0; //      
    int bgSum = 0; //      
    int graySum = 0;
    double w0 = 0; //        
    double w1 = 0; //        
    double u0 = 0; //      
    double u1 = 0; //      
    double Histogram[256] = {0}; //     
    double temp = 0; //      
    double g = 0; //    

    //     
    for(int i = 0; i < r ; i ++) 
    {
        for(int j = 0; j (i,j)]++;
        }
    }
    //     
    for(int i = 0; i < 256; i ++) 
    {
        graySum += Histogram[i]*i;
    }

    for(int i = 0; i < 256; i ++) 
    {
        ftNum += Histogram[i];  //   i     
        bgNum = N - ftNum;      //   i     
        w0 = (double)ftNum/N; //        
        w1 = (double)bgNum/N; //        
        if(ftNum == 0) continue;
        if(bgNum == 0) break;
        //      
        ftSum += i*Histogram[i];
        u0 = ftSum/ftNum;

        //      
        bgSum = graySum - ftSum;
        u1 = bgSum/bgNum;

        g = w0*w1*(u0-u1)*(u0-u1);
        if(g > temp) 
        {
            temp = g;
            T = i;
        }
    }

    return T

   /* for(int i=0; i(i,j)>T)
                img.at(i,j) = 255;
            else
                img.at(i,j) = 0;
        }
    }
    return img;*/
}
//【      :https://blog.csdn.net/a153375250/article/details/50970104?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-1&spm=1001.2101.3001.4242】

 
OpenCVにはこのアルゴリズムが実装されており,直接呼び出すこともできる.
    int value1 = 0;
    cv::Mat dst;
    cv::Mat src = cv::imread("..\\testPicture\\001.jpg", cv::IMREAD_GRAYSCALE);

    value1 = cv::threshold(src, dst, 0,255, CV_THRESH_OTSU);//  OTSU  

 
 
4、最大クラス間分散法(otsu)の性能について:
クラス間分散法はノイズとターゲットサイズに非常に敏感であり,クラス間分散が単一ピークの画像に対してのみ良好な分割効果を生じる.
ターゲットとバックグラウンドの大きさの割合が異なる場合、クラス間分散準則関数は二重ピークまたは多ピークを呈する可能性があります.この場合、効果はよくありませんが、クラス間分散法は使用時間が最も少ないです.
 
 
水に逆らって舟を漕ぎ、力を入れて千尋を退ける.