Opencv之图像二值化

二值化图像分割的一种方法。在二值化图象的时候把大于某个临界灰度值的像素灰度设为灰度极大值,把小于这个值的像素灰度设为灰度极小值,从而实现二值化。
根据阈值选取的不同,二值化的算法分为固定阈值自适应阈值。 比较常用的二值化方法则有:双峰法P参数法迭代法OTSU法等。

1、双峰法

在一些简单的图像中,物体的灰度分布比较有规律,背景与目标在图像的直方图各自形成一个波峰,即区域与波峰一一对应,每两个波峰之间形成一个波谷。那么,选择双峰之间的波谷所代表的灰度值T作为阈值,即可实现两个区域的分割。如图所示


灰度直方图

在直方图中我们可以明显的看到两个山峰状的图像分布,山峰的顶点我们记为Hmax1和Hmax2,他们对应的灰度值分别为T1和T2,那么双峰法图像分割的思想就是找到图像两个山峰之间的谷地最低值,即在[T1,T2]的灰度范围内寻找阈值T,使其满足对应的像素数目最少,表现在图像上就是高度最低,用T对图像进行分割或二值化。

2、P参数法

2.1 原理

所谓P分位法图像分割,就是在知道图像中目标所占的比率Ratio时,循环不同的灰度值对图像进行分割,并计算对应的目标所占的比率,如果该比率与Ratio的差值足够小,那么该阈值就是所求的最佳分割阈值。

2.2 算法

算法过程如下:

  1. 已知目标图像所占比率P;
  2. 设定一阈值Th,它将图像分割为两部分,目标部分A和背景部分B,统计两部分所包含的像素数目分别为Na和Nb;
  3. 将Th从1-254迭代,每改变一次Th ,计算一次Na,Nb,根据Na,Nb计算目标所占的比率P,公式如下:


  4. 计算当前阈值对应的分割比率与已知比率的差值,若小于某阈值则停止迭代,否则,转至3继续进行,公式如下:


    其中T为某一小数

3、迭代法

3.1 原理

迭代选择法是首先猜测一个初始阈值,然后再通过对图像的多趟计算对阈值进行改进的过程。重复地对图像进行阈值操作,将图像分割为对象类和背景类,然后来利用每一个类中的灰阶级别对阈值进行改进。

3.2 算法

  1. 为全局阈值选择一个初始估计值T(图像的平均灰度)。
  2. 用T分割图像。产生两组像素:G1有灰度值大于T的像素组成,G2有小于等于T像素组成。
  3. 计算G1和G2像素的平均灰度值m1和m2;
  4. 计算一个新的阈值:T = (m1 + m2) / 2;
  5. 重复步骤2和4,直到连续迭代中的T值间的差小于一个预定义参数为止。

3.3 代码

/**
 *   迭代法
 *
 *  @param srcImg  灰度图像
 *  @param maxIter 最大迭代次数
 */
+ (int)detechThreshold:(IplImage *)srcImg maxIterat:(int)maxIter{
    //图像信息
    int height = srcImg->height;
    int width = srcImg->width;
    int step = srcImg->widthStep/sizeof(uchar);
    uchar *data = (uchar*)srcImg->imageData;
    
    int iDiffRec =0;       //使用给定阀值确定的亮区与暗区平均灰度差异值, 根据需要返回
    int F[256]={ 0 }; //直方图数组
    int iTotalGray=0;//灰度值和
    int iTotalPixel =0;//像素数和
    Byte bt;//某点的像素值
    
    uchar iThrehold,iNewThrehold;//阀值、新阀值
    uchar iMaxGrayValue=0,iMinGrayValue=255;//原图像中的最大灰度值和最小灰度值
    uchar iMeanGrayValue1,iMeanGrayValue2;
    
    //获取(i,j)的值,存于直方图数组F
    for(int i=0;i<width;i++){
        for(int j=0;j<height;j++){
            bt = data[i*step+j];
            if(bt<iMinGrayValue)
                iMinGrayValue = bt;
            if(bt>iMaxGrayValue)
                iMaxGrayValue = bt;
            F[bt]++;
        }
    }
    
    iThrehold =0;
    iNewThrehold = (iMinGrayValue+iMaxGrayValue)/2;//初始阀值
    iDiffRec = iMaxGrayValue - iMinGrayValue;
    
    for(int a=0;(abs(iThrehold-iNewThrehold)>0.5)&&a<maxIter;a++){      //迭代中止条件
        iThrehold = iNewThrehold;
        //小于当前阀值部分的平均灰度值
        for(int i=iMinGrayValue;i<iThrehold;i++){
            iTotalGray += F[i]*i;//F[]存储图像信息
            iTotalPixel += F[i];
        }
        
        iMeanGrayValue1 = (uchar)(iTotalGray/iTotalPixel);
        //大于当前阀值部分的平均灰度值
        iTotalPixel =0;
        iTotalGray =0;
        for(int j=iThrehold+1;j<iMaxGrayValue;j++){
            iTotalGray += F[j]*j;//F[]存储图像信息
            iTotalPixel += F[j];
        }
        iMeanGrayValue2 = (uchar)(iTotalGray/iTotalPixel);
        
        iNewThrehold = (iMeanGrayValue2+iMeanGrayValue1)/2; //新阀值
        iDiffRec = abs(iMeanGrayValue2 - iMeanGrayValue1);
    }
    
    return iThrehold;
}

4. OTSU法

4.1 原理

最大类间方差法是由日本学者大津于1979年提出的,是一种自适应的阈值确定的方法,又叫大津法,简称OTSU。它是按图像的灰度特性,将图像分成背景和目标2部分。背景和目标之间的类间方差越大,说明构成图像的2部分的差别越大,当部分目标错分为背景或部分背景错分为目标都会导致2部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。对于图像I(x,y),前景(即目标)和背景的分割阈值记作T,属于前景的像素点数占整幅图像的比例记为ω0,其平均灰度μ0;背景像素点数占整幅图像的比例为ω1,其平均灰度为μ1。图像的总平均灰度记为μ,类间方差记为g。

4.2 算法

假设图像的背景较暗,并且图像的大小为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)采用遍历的方法得到使类间方差最大的阈值T,即为所求。

4.3 代码实现

代码实现如下:

int  OTSU(unsigned char* pGrayImg , int iWidth , int iHeight)
{
    if((pGrayImg==0)||(iWidth<=0)||(iHeight<=0))return -1;
    int ihist[256];
    int thresholdValue=0; // „–÷µ
    int n, n1, n2 ;
    double m1, m2, sum, csum, fmax, sb;
    int i,j,k;
    memset(ihist, 0, sizeof(ihist));
    n=iHeight*iWidth;
    sum = csum = 0.0;
    fmax = -1.0;
    n1 = 0;
    for(i=0; i < iHeight; i++)
    {
        for(j=0; j < iWidth; j++)
        {
            ihist[*pGrayImg]++;
            pGrayImg++;
        }
    }
    pGrayImg -= n;
    for (k=0; k <= 255; k++)
    {
        sum += (double) k * (double) ihist[k];
    }
    for (k=0; k <=255; k++)
    {
        n1 += ihist[k];
        if(n1==0)continue;
        n2 = n - n1;
        if(n2==0)break;
        csum += (double)k *ihist[k];
        m1 = csum/n1;
        m2 = (sum-csum)/n2;
        sb = (double) n1 *(double) n2 *(m1 - m2) * (m1 - m2);
        if (sb > fmax)
        {
            fmax = sb;
            thresholdValue = k;
        }
    }
    return(thresholdValue);
}

5、一维最大熵法

5.1 原理

一维最大熵法图像分割就是利用图像的灰度分布密度函数定义图像的信息熵,通过优化一定的熵准则得到熵最大时对应的阈值,从而进行图像分割的方法。

5.2 算法

算法过程:

  1. 对于一幅灰度图像,灰度范围为[0,L-1],求取图像的最小灰度级min,最大灰度级max;
  2. 按照如下熵的公式求取灰度t对应的熵值;


    其中,pi表示灰度级i出现的概率。

  3. 计算t从最小灰度min到最大灰度max之间不同灰度级所对应的熵值E(t),求取E(t)最大时所对应的灰度级t,该灰度级即为所求的阈值Th。

5.3 代码

代码实现如下:

// 计算当前位置的能量熵
#define cvQueryHistValue_1D( hist, idx0 )  \((float)cvGetReal1D( (hist)->bins, (idx0)))
+ (double)caculateCurrentEntropy:(CvHistogram *)histogram currentThreshold:(int)threshold state:(EntropyState)state{
    int start,end;
    int total =0;
    double cur_entropy =0.0;
    if(state == Back){
        start =0;
        end = threshold;
    }else{
        start = threshold;
        end =256;
    }
    
    for(int i=start;i<end;i++){
        total += (int)cvQueryHistValue_1D(histogram,i);//查询直方块的值 P304
    }
    
    for(int j=start;j<end;j++)
    {
        if((int)cvQueryHistValue_1D(histogram,j)==0)
            continue;
        double percentage = cvQueryHistValue_1D(histogram,j)/total;
        /*熵的定义公式*/
        cur_entropy +=-percentage*logf(percentage);
        /*根据泰勒展式去掉高次项得到的熵的近似计算公式
         cur_entropy += percentage*percentage;*/
    }
    return cur_entropy;
}

//寻找最大熵阈值并分割
+ (int)maxEntropy:(IplImage *)srcImg{
    /**
     *  创建直方图
     *
     *  @param dims             直方图维数的数目
     *  @param sizes      直方图维数尺寸的组数
     *  @param type 直方图的表示格式: CV_HIST_ARRAY 意味着直方图数据表示为多维密集数组 CvMatND; CV_HIST_TREE 意味着直方图数据表示为多维稀疏数组 CvSparseMat.
     */
    int hist_size = 256;
    CvHistogram *hist = cvCreateHist(1, &hist_size, CV_HIST_ARRAY);
    cvCalcHist(&srcImg, hist);
    double maxentropy = -1.0;
    int max_index = -1;
    // 循环测试每个分割点,寻找到最大的阈值分割点
    for (int i=0; i < hist_size; i++) {
       double backValue = [[self class] caculateCurrentEntropy:hist currentThreshold:i state:Back];
        double frontValue = [[self class] caculateCurrentEntropy:hist currentThreshold:i state:Object];
        double cur_entropy = backValue + frontValue;
        
        if(cur_entropy>maxentropy){
            maxentropy = cur_entropy;
            max_index = i;
        }
    }
    
    return max_index;
}

参考文章:
图像二值化----otsu(最大类间方差法、大津算法)
图像基本变换---图像二值化(包含OSTU/迭代法/统计法/双峰法/P分位法/最大熵法)
七种常见阈值分割代码(Otsu、最大熵、迭代法、自适应阀值、手动、迭代法、基本全局阈值法)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,814评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,124评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,814评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,924评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,815评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,562评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,944评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,582评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,859评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,881评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,700评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,493评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,943评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,115评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,413评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,978评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,182评论 2 339

推荐阅读更多精彩内容