opencv-阈值分割

 

关于自适应阈值,可参考:Wellner 自适应阈值二值化算法

 

一、大津法OTSU(最大类间方差法)

 

参考:非黑即白——图像分割入门篇之Otsu阈值

自适应阈值分割—大津法(OTSU算法)C++实现

灰度图像的自动阈值分割(Otsu 法)

 

在实际运用过程中,大津法表现得最稳定,且无需参数,对于现实图像保持了最好的均匀性和形状特性,而且被商业软件GIMP 和学术软件Matlab采纳为自动阈值法。

 

原理:

Otsu分割方法求取阈值是求得使类间方差最大的阈值:

假设待分割图像的像素数为N(就是常说的几百万像素了),它有L个灰度级(0,1,…,L-1),灰度级为i的像素数为ni,那么直方图概率密度pi=ni/N(通俗说法就是这一部分像素所占的比例)。假设阈值t将图像分成两类C0和C1,则C0和C1分别对应具有灰度级{0,1,…,k}和{k+1,k+2,…,L-1}的像素。设σB(k)表示阈值为k时的类间方差,则最佳阈值T可以通过求其最大值而得到,

这是什么意思呢?σB(k)是随着k而变化的,那么在k的变化过程中,σB(k)总有一个最大值,T就等于那个对应的k。

 

上面一直憋着类间方差没有说,它的定义是这样的:

 

其中p1(k),p2(k)分别是两类的概率密度,m1(k),m2(k)分别是C0和C1的均值(C0和C1都对应一堆像素值,直接求数学平均值),mg是图像的全局均值。

 

Otsu方法加权地使用了两类的灰度均值信息和概率密度信息,考虑了两类的分布,在实际的应用中取得了很好的效果,彩色分割都可以哦,不过是经过了灰度变换了,不仅仅是简单的灰度变换,下面分割结果通过叠加轮廓展示。

它尤其适用于前背景的方差相差不大的情况。

 

Otsu实现思路

1. 计算0~255各灰阶对应的像素个数,保存至一个数组中,该数组下标是灰度值,保存内容是当前灰度值对应像素数;

2. 计算背景图像的平均灰度、背景图像像素数所占比例;

3. 计算前景图像的平均灰度、前景图像像素数所占比例;

4. 遍历0~255各灰阶,计算并寻找类间方差极大值;

 

 

使用时可直接调用opencv中的threshold函数,将第五个参数设置为CV_THRESH_OTSU

C++代码实现:

int OtsuAlgThreshold(const Mat image)  
{  
    if(image.channels()!=1)  
    {  
        cout<<"Please input Gray-image!"<<endl;  
        return 0;  
    }  
    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; //像素总数  ;

    //计算灰度直方图分布,Histogram数组下标是灰度值,保存内容是灰度值对应像素点数  ;

    for(int i=0;i<image.rows;i++)   //为表述清晰,并没有把rows和cols单独提出来  ;
    {  
        for(int j=0;j<image.cols;j++)  
        {  
            Histogram[data[i*image.step+j]]++;  
        }  
    }  

    for(int i=0;i<255;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;  
        }  
    }  
    return T;  
} 

 

大津算法可以从图像直方图上有一个更为直观的理解:大津阈值大致上是直方图两个峰值之间低谷的值。

Otsu 方法也不是万能的。当目标与背景的大小比例悬殊时,类间方差准则函数可能呈现双峰或多峰,此时效果不好。这时就要考虑其他的办法了。

 

二、最大熵阈值分割

 

参考:最大熵阈值分割法

OpenCV学习笔记(二)之最大熵阈值分割

 

 

原理:

 

1.频率和概率

 

    直方图每个矩形框的数值描述的是图像中相应灰度值的频率。因此,可以说直方图是一种离散的频率分布。给定一个大小为M*N的图像I,直方图中所有矩形框所代表的数值之和,即为图像中的像素数量,即:

 

 

    相对应的归一化直方图表示为:

 

 

   0<=i<K 通常被解释为一个随机过程的概率分布或概率密度函数,表示的是图像中像素灰度值为i所出现的概率。i的累积概率值为1,即概率分布p必须满足以下关系:

 

 

    与累积概率所所对应的累积直方图H是一个离散的分布函数P(),(通常也称为累积分布函数或cdf):

 

 

2.最大熵阈值分割

    熵是信息理论中一个重要的概念,这种方法常用于数据压缩领域。熵是一种统计测量方法,用以确定随机数据源中所包含的信息数量。例如,包含有N个像素的图像I,可以解释为包含有N个符号的信息,每一个符号的值都独立获取于有限范围K(e.g.,256)中的不同灰度值。

   将数字图像建模为一种随机信号处理,意味着必须知道图像灰度中每一个灰度g所发生的概率,即:

 

  因为所有概率应该事先知道,所以这些概率也称为先验概率。对于K个不同灰度值g=0,…,K-1的概率向量可以表示为:

 上述概率向量也称为概率分布或称为概率密度函数(pdf)。实际数字图像处理应用当中,先验的概率通常是不知道的,但是这些概率可以通过在一幅图像或多幅图像中观察对应灰度值所发生的频率,从而估算出其概率。图像概率密度函数p(g)可以通过归一化其对应的直方图获得其概率,即:

 

3.最大熵

    数字图像中给定一个估算的概率密度函数p(g),数字图像中的熵定义为:

4.用图像熵进行图像分割

    利用图像熵为准则进行图像分割有一定历史了,学者们提出了许多以图像熵为基础进行图像分割的方法。我们介绍一种由Kapuret al提出来,现在仍然使用较广的一种图像熵分割方法。

给定一个特定的阈值q(0<=q<K-1),对于该阈值所分割的两个图像区域C0,C1,其估算的概率密度函数可表示为:

 

 同样的,背景熵可以改写为:


代码:

//一维最大熵阈值分割;
float caculateCurrentEntropy(Mat hist, int threshold)
{
    float backgroundSum=0,targetSum=0;
    float * pDataHist=hist.ptr<float>(0);
    for (int i=0;i<256;i++)
    {
        //累积背景值;
        if (pDataHist[i]<threshold)
        {
            backgroundSum+=pDataHist[i];
        }
        //累积目标值;
        else
        {
            targetSum+=pDataHist[i];
        }
    }
    float BackgroundEntropy = 0, targetEntropy = 0;
    for (int i=0;i<256;i++)
    {
        //计算背景熵;
        if (i < threshold)
        {
            if (pDataHist[i] == 0)
                continue;
            float ratio1 = pDataHist[i] / backgroundSum;
            //计算当前能量熵;
            BackgroundEntropy += -ratio1*logf(ratio1);
        }
        else  //计算目标熵;
        {
            if (pDataHist[i] == 0)
                continue;
            float ratio2 = pDataHist[i] / targetSum;
            targetEntropy += -ratio2*logf(ratio2);
        }

    }

    return (BackgroundEntropy+targetEntropy);
}

void tseg(Mat img,Mat &res)
{//计算原图一维灰度直方图;
    int nimages=1;
    int channels[1]={0};
    Mat outputHist; 
    int dims=1;
    int histSize[1]={256};
    float hranges[2]={0,255};
    const float *ranges[1]={hranges};

    calcHist(&img,nimages,channels,Mat(),outputHist,dims,histSize,ranges);
    
    float maxentropy = 0;
    int max_index = 0;
    for (int i = 0; i < 256; i++)
    {
        float cur_entropy = caculateCurrentEntropy(outputHist, i);
        if (cur_entropy > maxentropy)
        {
            maxentropy = cur_entropy;
            max_index = i;
        }
    }
    threshold(img, res, max_index, 255, CV_THRESH_BINARY);

}

 

三、区域生长法

 

参考:OpenCV - 区域生长算法

 

 

 

其它参考:

七种常见阈值分割代码(Otsu、最大熵、迭代法、自适应阀值、手动、迭代法、基本全局阈值法)

OpenCV—使用积分图像统计像素     利用积分图像可以进行自适应阈值分割。

 

 

 

 

 

posted @ 2018-08-04 22:20  eeeeeeee鹅  阅读(7557)  评论(0编辑  收藏  举报