马老师带你学opencv(大津二值化算法)


opencv学习第四弹

问题

使用大津算法来二值化图像吧。
大津算法,也被称作最大类间方差法,是一种可以自动确定二值化中阈值的算法。
从类内方差和类间方差的比值计算得来:

1.小于阈值t的类记作0,大于阈值t的类记作1;
2. w 0 w_0 w0 w 1 w_1 w1是被阈值t分开的两个类中的像素数占总像素数的比率(满足 w 0 w_0 w0+ w 1 w_1 w1=1);
3. S 0 2 S_0{}^2{} S02 S 1 2 S_1{}^2{} S12是这两个类中像素值的方差;
4. M 0 M_0 M0 M 1 M_1 M1是这两个类的像素值的平均值;
即:
1.类内方差: S w 2 = w 0 ⋅ S 0 2 + w 1 ⋅ S 1 2 S_w{}^2{} =w0_{} \cdot S_0{}^2{} +w_1{} \cdot S_1{}^2{} Sw2=w0S02+w1S12
2.类间方差: S b 2 = w 0 ⋅ ( M 0 − M t ) 2 + w 1 ⋅ ( M 1 − M t ) 2 = w 0 ⋅ w 1 ⋅ ( M 0 − M 1 ) 2 S_{b}{ }^{2}=w_{0} \cdot\left(M_{0}-M_{t}\right)^{2}+w_{1} \cdot\left(M_{1}-M_{t}\right)^{2}=w_{0} \cdot w_{1} \cdot\left(M_{0}-M_{1}\right)^{2} Sb2=w0(M0Mt)2+w1(M1Mt)2=w0w1(M0M1)2
3.图像所有像素的方差: S t 2 = S w 2 + S b 2 = 常 数 S_{t}^{2}=S_{w}^{2}+S_{b}^{2}=常数 St2=Sw2+Sb2=

根据以上的式子,我们用以下的式子计算分离度X:
X = S b 2 S w 2 = S b 2 S t 2 − S b 2 X=\frac{S_{b}^{2}}{S_{w}{ }^{2}}=\frac{S_{b}^{2}}{S_{t}^{2}-S_{b}^{2}} X=Sw2Sb2=St2Sb2Sb2

也就是说:
arg ⁡ max ⁡ t X = arg ⁡ max ⁡ t S b 2 \arg \max _{t} X=\arg \max _{t} S_{b}^{2} argtmaxX=argtmaxSb2
换言之,如果使 S b 2 = w 0 ⋅ w 1 ⋅ ( M 0 − M 1 ) 2 S_{b}^{2}=w_{0} \cdot w_{1} \cdot\left(M_{0}-M_{1}\right)^{2} Sb2=w0w1(M0M1)2最大,就可以得到最好的二值化阈值t。

代码

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>

// BGR -> Gray
cv::Mat BGR2GRAY(cv::Mat img){
  // get height and width
  int width = img.cols;
  int height = img.rows;

  // prepare output
  cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);

  // each y, x
  for (int y = 0; y < height; y++){
    for (int x = 0; x < width; x++){
      // BGR -> Gray
      out.at<uchar>(y, x) = 0.2126 * (float)img.at<cv::Vec3b>(y, x)[2] \
        + 0.7152 * (float)img.at<cv::Vec3b>(y, x)[1] \
        + 0.0722 * (float)img.at<cv::Vec3b>(y, x)[0];
    }
  }

  return out;
}

// Gray -> Binary
cv::Mat Binarize_Otsu(cv::Mat gray){
  int width = gray.cols;
  int height = gray.rows;

  // determine threshold
  double w0 = 0, w1 = 0;
  double m0 = 0, m1 = 0;
  double max_sb = 0, sb = 0;
  int th = 0;
  int val;

  // Get threshold
  for (int t = 0; t < 255; t++){
    w0 = 0;//每改变一次t的值,这四个参数都要重新初始化。
    w1 = 0;
    m0 = 0;
    m1 = 0;
    for (int y = 0; y < height; y++){
      for (int x = 0; x < width; x++){
        val = (int)(gray.at<uchar>(y, x));

        if (val < t){
          w0++;
          m0 += val;
        } else {
          w1++;
          m1 += val;
        }
      }
    }

    m0 /= w0;
    m1 /= w1;
    w0 /= (height * width);
    w1 /= (height * width);
    sb = w0 * w1 * pow((m0 - m1), 2);
    
    if(sb > max_sb){
      max_sb = sb;
      th = t;
    }
  }

  std::cout << "threshold:" << th << std::endl;

  // prepare output
  cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);

  // each y, x
  for (int y = 0; y < height; y++){
    for (int x = 0; x < width; x++){
      // Binarize
      if (gray.at<uchar>(y, x) > th){
        out.at<uchar>(y, x) = 255;
      } else {
        out.at<uchar>(y, x) = 0;
      }
    
    }
  }

  return out;
}


int main(int argc, const char* argv[]){
  // read image
  cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);

  // BGR -> Gray
  cv::Mat gray = BGR2GRAY(img);

  // Gray -> Binary
  cv::Mat out = Binarize_Otsu(gray);

  //cv::imwrite("out.jpg", out);
  cv::imshow("sample", out);
  cv::waitKey(0);
  cv::destroyAllWindows();

  return 0;
}

结果图

结果图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值