前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++ OpenCV使用大津法求自适应阈值

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

作者头像
Vaccae
发布2021-07-07 19:11:33
1.4K0
发布2021-07-07 19:11:33
举报
文章被收录于专栏:微卡智享微卡智享

前言

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

实现效果

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

大津法简介

微卡智享

大津法(OTSU)又名最大类间差法,由日本学者大津于1979年提出。被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响。

大津法是按图像的灰度特征,把图像分成前景和背景两部分。因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小。因此使用类间方差最大的分割意味着错分概率最小。

原理

代码语言:javascript
复制
对于图像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,即为所求。

代码实现

微卡智享

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

核心代码

代码语言:javascript
复制
//使用大津法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;
}

调用方法

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

代码语言:javascript
复制
//求自适应阈值的最小和最大值
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);
}

输出参数对比

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-06-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微卡智享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 原理
  • 调用方法
  • 输出参数对比
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档