图像处理一(大津二值化、膨胀、腐蚀)

1:大津二值化

二值化:二值化是将图像使用黑和白两种颜色表示的方法,可以凸显图像的轮廓。
我们将灰度的阈值设置为N来进行二值化,即:
在这里插入图片描述
一张图片采用不同的阀值进行二值化后得到的图像差距很大,如下图所示:
原图:
原图
以下分别是阀值为50、 70、150时二值化的结果:
50
70
150
可以看到只有合适的阀值才能很好的二值化图像,如何自动的确定阀值呢?
大津二值化算法(OTSU)算法是由日本学者OTSU于1979年提出的一种对图像进行二值化的高效算法,作用是确定将图像分成黑白两个部分的阈值。
当取最佳阈值时,背景应该与前景差别最大,关键在于如何选择衡量差别的标准,而在otsu算法中这个衡量差别的标准就是最大类间方差。
大津算法,也被称作最大类间方差法,是一种可以自动确定二值化中阈值的算法:
g:类间方差(那个灰度的g最大,哪个灰度就是需要的阈值t)

g = w0 * (u0 - u)^2 + w1 * (u1 - u)^2
根据上面的关系,可以推出:
g = w0 * w1 * (u0 - u1) ^ 2

然后,遍历每一个灰度值,找到这个灰度值对应的 g,找到最大的 g 对应的 t。
w0:前景像素数量占总像素数量的比例
w1:背景像素数量占总像素数量的比例
u0:前景平均灰度
u1:背景平均灰度
u:平均灰度

大津二值化算法后的图像:
大津二值化
阀值:103
可以明显感受到该阀值能显著的分离前景和背景。

大津二值化代码实现:

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
using namespace std;

// 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;
    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[])
{
	if(argc <2)
	{
		cout << "input error" << endl;
		return 0;
	}
  // read image
  cv::Mat img = cv::imread(argv[1], 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;
}

2:膨胀

腐蚀和膨胀是数学形态学上的名词,如果用于图像处理上则就称为图像二值形态学。
腐蚀和膨胀是对白色部分(高亮部分)而言的,需要先对图像进行二值化。膨胀就是图像中的高亮部分进行膨胀,“领域扩张”,效果图拥有比原图更大的高亮区域。腐蚀就是原图中的高亮部分被腐蚀,“领域被蚕食”,效果图拥有比原图更小的高亮区域。
形态学处理理中的膨胀算法如下。对于待操作的像素(x,y),如果(x+1, y)、(x-1, y)、(x, y+1)、(x, y-1)中不论哪一个为255.则像素(x,y)为255。
在这里插入图片描述
换句话说,如果将上面的操作执行两次,则可以扩大两格。
在实际进行形态学处理的时候,待操作的像素与矩阵
在这里插入图片描述
相乘,结果大于255的话,将中心像素设为255.
膨胀的原图、二值化图与膨胀后的图:
原图
大津二值化图
膨胀图
代码实现:

#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;
    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;
}

// Morphology erode
cv::Mat Morphology_Erode(cv::Mat img, int Erode_time){
  int height = img.cols;
  int width = img.rows;

  // output image
  cv::Mat tmp_img;
  cv::Mat out = img.clone();

  // for erode time
  for (int i = 0; i < Erode_time; i++){
    tmp_img = out.clone();

    // each pixel
    for (int y = 0; y < height; y++){
      for (int x = 0; x < width; x++){
        // check left pixel
        if ((x > 0) && (tmp_img.at<uchar>(y, x - 1) == 255)){
          out.at<uchar>(y, x) = 255;
          continue;
        } 

        // check up pixel
        if ((y > 0) && (tmp_img.at<uchar>(y - 1, x) == 255)){
          out.at<uchar>(y, x) = 255;
          continue;
        }

        // check right pixel
        if ((x < width - 1) && (tmp_img.at<uchar>(y, x + 1) == 255)){
          out.at<uchar>(y, x) = 255;
          continue;
        }

        // check left pixel
        if ((y < height - 1) && (tmp_img.at<uchar>(y + 1, x) == 255)){
          out.at<uchar>(y, x) = 255;
          continue;
        }
      }
    }
  }

  return out;
}


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

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

  // Gray -> Binary
  cv::Mat bin = Binarize_Otsu(gray);
  cv::imwrite("out_bin.jpg", bin);

  // Morphology Erode
  cv::Mat out = Morphology_Erode(bin, 2);

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

  return 0;
}

3:腐蚀

腐蚀和膨胀相反。
形态学处理理中的膨胀算法如下。对于待操作的像素(x,y),如果(x+1, y)、(x-1, y)、(x, y+1)、(x, y-1)中不论哪一个不为255.则像素(x,y)为0。
在这里插入图片描述
在实际进行形态学处理的时候,待操作的像素与矩阵
在这里插入图片描述
相乘,结果小于255*4的话,将中心像素设为0.
腐蚀的原图、二值化图与腐蚀后的图:
原图
大津二值化图
腐蚀图
代码实现:

#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;
    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;
}

// Morphology Dilate
cv::Mat Morphology_Dilate(cv::Mat img, int Dilate_time){
  int height = img.cols;
  int width = img.rows;

  // output image
  cv::Mat tmp_img;
  cv::Mat out = img.clone();

  // for erode time
  for (int i = 0; i < Dilate_time; i++){
    tmp_img = out.clone();

    // each pixel
    for (int y = 0; y < height; y++){
      for (int x = 0; x < width; x++){
        // check left pixel
        if ((x > 0) && (tmp_img.at<uchar>(y, x - 1) == 0)){
          out.at<uchar>(y, x) = 0;
          continue;
        } 

        // check up pixel
        if ((y > 0) && (tmp_img.at<uchar>(y - 1, x) == 0)){
          out.at<uchar>(y, x) = 0;
          continue;
        }

        // check right pixel
        if ((x < width - 1) && (tmp_img.at<uchar>(y, x + 1) == 0)){
          out.at<uchar>(y, x) = 0;
          continue;
        }

        // check left pixel
        if ((y < height - 1) && (tmp_img.at<uchar>(y + 1, x) == 0)){
          out.at<uchar>(y, x) = 0;
          continue;
        }
      }
    }
  }

  return out;
}


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

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

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

  // Morphology Dilate
  cv::Mat out = Morphology_Dilate(bin, 2);

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

  return 0;
}

开运算:先进行N次腐蚀再进行N次膨胀,开运算可以用来去除仅存的小块像素。
下面进行一次开运算:
二值化图:
二值化
一次腐蚀后:
erode
接着一次膨胀后:
在这里插入图片描述
闭运算:先进行N次膨胀再进行N次腐蚀,闭运算能够将中断的像素连接起来。
Canny边缘检测之后:
在这里插入图片描述
进行一次闭运算(N=1),先膨胀一次:
在这里插入图片描述
接着腐蚀一次:
在这里插入图片描述

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值