C++ OpenCV使用大津法求自适应阈值

学更好的别人,

做更好的自己。

——《微卡智享》

ad4a470a6932f091c50c0af65fc4ec20.png

本文长度为1245,预计阅读3分钟

前言

上篇《C++ OpenCV自适应阈值Canny边缘检测》中,使用的求中值的方式来获取自适应阈值,有小伙伴留言说一般用大津法OTSU来求自适应阈值,所以这篇就来说说大津法,及两个效果的对比。

0912601ca8b2537d40e13efd28fb5b47.png

实现效果

fb767ba0930d65a9882612e3964c0a62.gif

be210e85009564fd335a88ad646e73d6.png

eb2008001882ea6cdd2d2a875de4edad.png

201648f77aefd876b8c0e8453e7316a3.png

从上图中可以看出,除了书的那张图两个求出的阈值是完全一样,效果也一样,用大津(OTSU)法的阈值效果会更完整一些,原来的中值过滤掉的东西会更多一些。最后一张手机比较明显。

大津法简介

669eb90337761238cd9e93f957d63406.png

微卡智享

大津法(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,即为所求。

代码实现

803491057ee749ef0f49b655aef01caa.png

微卡智享

大津法主要函数前半部分和上一篇中求中值的是一样,后半部分就要去计算前景和背景的比例后,再求出类间方差。

核心代码

//使用大津法Mat的阈值
int CvUtils::GetMatOTSU(Mat& img)
{
  //判断如果不是单通道直接返回128
  if (img.channels() > 1) return 128;
  int rows = img.rows;
  int cols = img.cols;
  //定义数组
  float mathists[256] = { 0 };
  //遍历计算0-255的个数
  for (int row = 0; row < rows; ++row) {
    for (int col = 0; col < cols; ++col) {
      int val = img.at<uchar>(row, col);
      mathists[val]++;
    }
  }


  //定义灰度级像素在整个图像中的比例
  float grayPro[256] = { 0 };
  int matSize = rows * cols;
  for (int i = 0; i < 256; ++i) {
    grayPro[i] = (float)mathists[i] / (float)matSize;
  }


  //大津法OTSU,前景与背景分割,计算出方差最大的灰度值
  int calcval;
  int calcMax = 0;
  for (int i = 0; i < 256; ++i) {
    float w0 = 0, w1 = 0, u0tmp = 0, u1tmp = 0, u0 = 0, u1 = 0, u = 0, calctmp = 0;
      
    for (int k = 0; k < 256; ++k) {
      float curGray = grayPro[k];
      //计算背景部分
      if (k <= i) {
        //以i为阈值分类,第一类总的概率
        w0 += curGray;
        u0tmp += curGray * k;
      }
      //计算前景部分
      else {
        //以i为阈值分类,第一类总的概率
        w1 += curGray;
        u1tmp += curGray * k;
      }
    }


    //求出第一类和第二类的平均灰度
    u0 = u0tmp / w0;
    u1 = u1tmp / w1;
    //求出整幅图像的平均灰度
    u = u0tmp + u1tmp;


    //计算类间方差
    calctmp = w0 * pow((u0 - u), 2) + w1 * pow((u1 - u), 2);


    //更新最大类间方差,并设置阈值
    if (calctmp > calcMax) {
      calcMax = calctmp;
      calcval = i;
    }
  }


  return calcval;
}

调用方法

为了做一下两个自适应阈值的对比,把原来的调用方法做了一下改造,加入一个参数来判断调用的什么方法。

a9ccd3dda5f4c22f00f4be0d70089844.png

//求自适应阈值的最小和最大值
void CvUtils::GetMatMinMaxThreshold(Mat& img, int& minval, int& maxval, int calctype, float sigma)
{
  int midval;
  switch (calctype)
  {
  case 1: {
    midval = GetMatOTSU(img);
    break;
  }
  default:
    midval = GetMatMidVal(img);
    break;
  }
  cout << "midval:" << midval << endl;
  // 计算低阈值
  minval = saturate_cast<uchar>((1.0 - sigma) * midval);
  //计算高阈值
  maxval = saturate_cast<uchar>((1.0 + sigma) * midval);
}


5be60f37e66b6a8f4380fba2da76f858.png

输出参数对比

0c266f760020b386d17a6ea03b632e4e.png

上图中可以看出,还是上一篇中那几个图,倒数第二张求的结果是一致的,有个别差异还挺大,总结来说,保留边缘的完整性上大津法的效果要好很多,中值里面过滤掉的会更多一些

31b7b240b51f7f454442bb8be8ca5bdc.png

扫描二维码

获取更多精彩

微卡智享

e834370aa0325aecd24c702dd43a52f1.png

「 往期文章 」

C++ OpenCV自适应阈值Canny边缘检测

C++ OpenCV4.5版本SIFT特征检测及匹配

趣玩算法--OpenCV华容道AI自动解题

 

  • 5
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vaccae

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值