Otsu算法原理及实现

OTSU算法

OTSU算法也称最大类间差法,有时也称之为大津算法,由大津于1979年提出,被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,因此在数字图像处理上得到了广泛的应用。它是按图像的灰度特性,将图像分成背景和前景两部分。因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。

原理:

对于图像I(x,y),前景(即目标)和背景的分割阈值记作T,属于前景的像素点数占整幅图像的比例记为ω0,其平均灰度μ0;背景像素点数占整幅图像的比例为ω1,其平均灰度为μ1。图像的总平均灰度记为μ,类间方差记为g。

假设图像的背景较暗,并且图像的大小为M×N,图像中像素的灰度值小于阈值T的像素个数记作N0,像素灰度大于阈值T的像素个数记作N1,则有:
      ω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)
将式(5)代入式(6),得到等价公式:
      g=ω0ω1(μ0-μ1)^2          (7) 

这就是类间方差。采用遍历的方法得到使类间方差g最大的阈值T,即为所求。

Ostu方法可以形象地理解为:求取直方图有两个峰值的图像中那两个峰值之间的低谷值 T 。

Otsu算法实现:

opencv函数实现:
po下opencv的源码:

 getThreshVal_Otsu_8u( const Mat& _src )  
    {  
        Size size = _src.size();  
        if( _src.isContinuous() )  
        {  
            size.width *= size.height;  
            size.height = 1;  
        }  
        const int N = 256;  
        int i, j, h[N] = {0};  
        for( i = 0; i < size.height; i++ )  
        {  
            const uchar* src = _src.data + _src.step*i;  
            j = 0;  
            #if CV_ENABLE_UNROLLED  
            for( ; j <= size.width - 4; j += 4 )  
            {  
                int v0 = src[j], v1 = src[j+1];  
                h[v0]++; h[v1]++;  
                v0 = src[j+2]; v1 = src[j+3];  
                h[v0]++; h[v1]++;  
            }  
            #endif  
            for( ; j < size.width; j++ )  
                h[src[j]]++;  
        }  
      
        double mu = 0, scale = 1./(size.width*size.height);  
        for( i = 0; i < N; i++ )  
            mu += i*(double)h[i];  
      
        mu *= scale;  
        double mu1 = 0, q1 = 0;  
        double max_sigma = 0, max_val = 0;  
      
        for( i = 0; i < N; i++ )  
        {  
            double p_i, q2, mu2, sigma;  
      
            p_i = h[i]*scale;  
            mu1 *= q1;  
            q1 += p_i;  
            q2 = 1. - q1;  
      
            if( std::min(q1,q2) < FLT_EPSILON || std::max(q1,q2) > 1. - FLT_EPSILON )  
                continue;  
      
            mu1 = (mu1 + i*p_i)/q1;  
            mu2 = (mu - q1*mu1)/q2;  
            sigma = q1*q2*(mu1 - mu2)*(mu1 - mu2);  
            if( sigma > max_sigma )  
            {  
                max_sigma = sigma;  
                max_val = i;  
            }  
        }  
      
        return max_val;  
    } 

上面的应该是opencv 2以后的版本,之前的版本更好理解一些,这里也po一下, 

int MyAutoFocusDll::otsuThreshold(IplImage *frame)
{
    const int GrayScale = 256;
    int width = frame->width;
    int height = frame->height;
    int pixelCount[GrayScale];
    float pixelPro[GrayScale];
    int i, j, pixelSum = width * height, threshold = 0;
    uchar* data = (uchar*)frame->imageData;  //指向像素数据的指针
    for (i = 0; i < GrayScale; i++)
    {
        pixelCount[i] = 0;
        pixelPro[i] = 0;
    }

    //统计灰度级中每个像素在整幅图像中的个数  
    for (i = 0; i < height; i++)
    {
        for (j = 0; j < width; j++)
        {
            pixelCount[(int)data[i * width + j]]++;  //将像素值作为计数数组的下标
        }
    }

    //计算每个像素在整幅图像中的比例  
    float maxPro = 0.0;
    int kk = 0;
    for (i = 0; i < GrayScale; i++)
    {
        pixelPro[i] = (float)pixelCount[i] / pixelSum;
        if (pixelPro[i] > maxPro)
        {
            maxPro = pixelPro[i];
            kk = i;
        }
    }

    //遍历灰度级[0,255]  
    float w0, w1, u0tmp, u1tmp, u0, u1, u, deltaTmp, deltaMax = 0;
    for (i = 0; i < GrayScale; i++)     // i作为阈值
    {
        w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0;
        for (j = 0; j < GrayScale; j++)
        {
            if (j <= i)   //背景部分  
            {
                w0 += pixelPro[j];
                u0tmp += j * pixelPro[j];
            }
            else   //前景部分  
            {
                w1 += pixelPro[j];
                u1tmp += j * pixelPro[j];
            }
        }
        u0 = u0tmp / w0;
        u1 = u1tmp / w1;
        u = u0tmp + u1tmp;
        deltaTmp = w0 * pow((u0 - u), 2) + w1 * pow((u1 - u), 2);
        if (deltaTmp > deltaMax)
        {
            deltaMax = deltaTmp;
            threshold = i;
        }
    }

    return threshold;
}

参考:

https://en.wikipedia.org/wiki/Otsu%27s_method

http://www.cnblogs.com/ranjiewen/p/6385564.html

http://blog.csdn.net/glouds/article/details/38976573
————————————————
版权声明:本文为CSDN博主「Naruto_Q」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/piaoxuezhong/article/details/78302893

你可能感兴趣的:(opencv,otsu)