当前位置:   article > 正文

C++手敲基于梯度图和像素数量数组的OTSU阈值分割_1.编程实现otsu阈值分割算法

1.编程实现otsu阈值分割算法

 一、OTSU算法原理

OTSU法(最大类间方差法,有时也称之为大津算法)

➢ 使用聚类的思想,把图像的灰度数按灰度级分成2个部分, 使得两个部分之间的灰度值差异最大,每个部分之间的灰 度差异最小

➢ 通过方差的计算来寻找一个合适的灰度级别来划分。

➢ 可以在二值化的时候采用OTSU算法来自动选取阈值进行 二值化。

➢ OTSU算法被认为是图像分割中阈值选取的最佳算法,计 算简单,不受图像亮度和对比度的影响。

➢ 因此,使类间方差最大的分割意味着错分概率最小。

二、OTSU算法步骤:

全局阈值T可以按如下计算:

➢选择一个初时估计值T (一般为图像的平均灰度值)

➢使用T分割图像,产生两组像素:G1包括灰度级大于 T的像素,G2包括灰度级小于等于T的像素

➢计算G1 中像素的平均值并赋值给μ1,计算G2 中像素 的平均值并赋值给μ2

➢计算一个新的阈值:

➢重复步骤 2 ~ 4,一直到两次连续的T之间的差小于 预先给定的上界T

三、代码实现 

当我们用边缘梯度算子(如Roberts、Soberl、Prewitt等)处理原始图像拿到梯度图之后,可以创建一个1*256维的数组来存放梯度图中每个像素的个数,其具体的实现方式如下:

  1. int pixel_num[256] = { 0 }; //数组记得初始化为0,即未统计数量之前各个像素的个数都是0
  2. for (int row = 0; row < img.rows; row++) {
  3. for (int col = 0; col < img.cols; col++) {
  4. pixel_num01[Sobel_City.at<uchar>(row, col)] += 1; //遍历到的像素值作为索引,次数+1
  5. }
  6. }

以下是OTSU的实现方法:
①由传入的梯度图拿到图像的维度,创建一个空白图像用于保留运算得到的结果,并且拿到梯度图像的总像素个数。

  1. Mat OTSU = Mat::zeros(Size(img.cols, img.rows), img.type());
  2. int N = img.rows * img.cols;

②由像素数量数组,得到每一个像素对应的概率:

  1. double pro[256] = { 0 }; // 定义概率数组
  2. for (int i = 0; i < 256; i++) {
  3. pro[i] = arr[i] / N; // 得到每个像素值的概率
  4. }

注意创建的概率数组是浮点型,定义为整形的话会出现全部像素的概率都为零的情况。

③依次创建OTSU计算的过程量,包括两类的概率、均值,还有阈值T以及方差v等等;先计算得到两类的均值以及概率,根据公式v = w0 * w1 * pow(u1 - u0, 2);就可以得到此次遍历的像素作为阈值得到的方差。

  1. double delta = 0, w0 = 0, w1 = 0, u0 = 0, u1 = 0, v = 0; // 变量初始化为0
  2. int T = 0, thresh = 0;
  3. while (T <= 255) {
  4. for (int i = 0; i <= T; i++) {
  5. w0 += pro[i]; // C0的概率
  6. }
  7. w1 = 1 - w0; // C1的概率
  8. for (int i = 0; i <= 255; i++) {
  9. if (i <= T) {
  10. u0 += pro[i] * i; // C0的平均值
  11. }
  12. else {
  13. u1 += pro[i] * i; // C1的平均值
  14. }
  15. v = w0 * w1 * pow(u1 - u0, 2);
  16. if (v > delta) {
  17. delta = v;
  18. thresh = T;
  19. }
  20. T += 1;
  21. }
  22. }

遍历循环的过程中要对方差进行判断,如果此次计算的方差要比之前的最大方差delta要大,那么对delta进行更新,同时也用thresh记录下历史最大方差对应的像素值,作为分割的阈值T。这样一来,遍历的结果就可以得到我们想要的,能使两类差异最大的阈值T。

④通过对与梯度图同维大小的空白图片遍历,给每一个像素进行二值化。若遍历到的像素值大于等于T,则赋值为255,否则为0。

  1. for (int row = 0; row < img.rows; row++) {
  2. for (int col = 0; col < img.cols; col++) {
  3. if (img.at<uchar>(row, col) > thresh) { //遍历判断
  4. OTSU.at<uchar>(row, col) = 255;
  5. }
  6. }
  7. }

⑤显示OTSU图像,得到阈值分割结果:

  1. imshow("OTSU阈值分割", OTSU);
  2. waitKey(0);
  3. destroyAllWindows();

4a9f558cd76f4273a9d39f8cce16d4a7.png
四、代码汇总

  1. #include<opencv2/opencv.hpp>
  2. #include<iostream>
  3. #include<math.h>
  4. using namespace cv;
  5. using namespace std;
  6. void Sobel(Mat img);
  7. void OTSU(Mat& img, int arr[]);
  8. int main() {
  9. Mat Gray = imread("C:\\Users\\Czhannb\\Desktop\\gray.png", IMREAD_GRAYSCALE);
  10. if (Gray.empty()) {
  11. cout << "读取图片错误!" << endl;
  12. }
  13. else {
  14. imshow("未动工之前:", Gray);
  15. }
  16. Sobel(Gray);
  17. return 0;
  18. }
  19. void OTSU(Mat& img, int arr[]) { // 需要输入待处理的图像 和 直方图像素个数数组
  20. int N = img.rows * img.cols; // 统计输入图像的像素个数N
  21. double pro[256] = { 0 }; // 定义概率数组
  22. for (int i = 0; i < 256; i++) {
  23. pro[i] = arr[i] / N; // 得到每个像素值的概率
  24. }
  25. double delta = 0, w0 = 0, w1 = 0, u0 = 0, u1 = 0, v = 0; // 变量初始化为0
  26. int T = 0, thresh = 0;
  27. while (T <= 255) {
  28. for (int i = 0; i <= T; i++) {
  29. w0 += pro[i]; // C0的概率
  30. }
  31. w1 = 1 - w0; // C1的概率
  32. for (int i = 0; i <= 255; i++) {
  33. if (i <= T) {
  34. u0 += pro[i] * i; // C0的平均值
  35. }
  36. else {
  37. u1 += pro[i] * i; // C1的平均值
  38. }
  39. v = w0 * w1 * pow(u1 - u0, 2);
  40. if (v > delta) {
  41. delta = v;
  42. thresh = T;
  43. }
  44. T += 1;
  45. }
  46. }
  47. Mat OTSU = Mat::zeros(Size(img.cols, img.rows), img.type());
  48. for (int row = 0; row < img.rows; row++) {
  49. for (int col = 0; col < img.cols; col++) {
  50. if (img.at<uchar>(row, col) > int(thresh)) {
  51. OTSU.at<uchar>(row, col) = 255;
  52. }
  53. }
  54. }
  55. imshow("OTSU阈值分割", OTSU);
  56. waitKey(0);
  57. destroyAllWindows();
  58. }
  59. void Sobel(Mat img) { // 基于Prewitt算子的阈值分割
  60. Mat Sobel_City = Mat::zeros(Size(img.cols, img.rows), img.type()); //用于计算城市距离的空白图像
  61. Mat Sobel_Ojld = Mat::zeros(Size(img.cols, img.rows), img.type()); //用于计算欧几里得距离的空白图像
  62. for (int row = 1; row < img.rows - 1; row++) {
  63. for (int col = 1; col < img.cols - 1; col++) { // 由于像素之间的加减可能会小于零,因此记得加上绝对值函数fabs()
  64. Sobel_City.at<uchar>(row, col) = saturate_cast<uchar>(fabs(img.at<uchar>(row - 1, col + 1) - img.at<uchar>(row - 1, col - 1) + 2*img.at<uchar>(row, col + 1) - 2*img.at<uchar>(row, col - 1) + img.at<uchar>(row + 1, col + 1) - img.at<uchar>(row + 1, col - 1)) + fabs(img.at<uchar>(row + 1, col - 1) - img.at<uchar>(row - 1, col - 1) + 2*img.at<uchar>(row + 1, col) - 2*img.at<uchar>(row - 1, col) + img.at<uchar>(row + 1, col + 1) - img.at<uchar>(row - 1, col + 1)));
  65. }
  66. }
  67. for (int row = 1; row < img.rows - 1; row++) {
  68. for (int col = 1; col < img.cols - 1; col++) {
  69. Sobel_Ojld.at<uchar>(row, col) = saturate_cast<uchar>(sqrt(pow(img.at<uchar>(row - 1, col + 1) - img.at<uchar>(row - 1, col - 1) + 2*img.at<uchar>(row, col + 1) - 2*img.at<uchar>(row, col - 1) + img.at<uchar>(row + 1, col + 1) - img.at<uchar>(row + 1, col - 1), 2) + pow(img.at<uchar>(row + 1, col - 1) - img.at<uchar>(row - 1, col - 1) + 2*img.at<uchar>(row + 1, col) - 2*img.at<uchar>(row - 1, col) + img.at<uchar>(row + 1, col + 1) - img.at<uchar>(row - 1, col + 1), 2)));
  70. }
  71. }
  72. imshow("Sobel图像(街区距离)", Sobel_City);
  73. imshow("Sobel图像(欧几里得距离)", Sobel_Ojld);
  74. int pixel_num01[256] = { 0 }; //数组记得初始化为0,即未统计数量之前各个像素的个数都是0
  75. for (int row = 0; row < img.rows; row++) {
  76. for (int col = 0; col < img.cols; col++) {
  77. pixel_num01[Sobel_Ojld.at<uchar>(row, col)] += 1; //遍历到的像素值作为索引,次数+1
  78. }
  79. }
  80. int times = 0; //定义遍历数组的变量,从0开始,依次输出0到255每个像素值的数目
  81. while (times <= 255) {
  82. cout << "像素值" << times << "的数目为: " << pixel_num01[times] << endl; // 遍历输出
  83. times++; // 不要忘了自增
  84. }
  85. //得到10作为分割阈值
  86. for (int row = 0; row < img.rows; row++) {
  87. for (int col = 0; col < img.cols; col++) { //遍历所有像素点进行判断
  88. if (Sobel_Ojld.at<uchar>(row, col) < 70) {
  89. Sobel_Ojld.at<uchar>(row, col) = 0; // 小于阈值赋值为0,否则赋值255
  90. }
  91. else {
  92. Sobel_Ojld.at<uchar>(row, col) = 255;
  93. }
  94. }
  95. }
  96. imshow("欧几里得阈值分割", Sobel_Ojld);
  97. int pixel_num02[256] = { 0 }; //数组记得初始化为0,即未统计数量之前各个像素的个数都是0
  98. for (int row = 0; row < img.rows; row++) {
  99. for (int col = 0; col < img.cols; col++) {
  100. pixel_num01[Sobel_City.at<uchar>(row, col)] += 1; //遍历到的像素值作为索引,次数+1
  101. }
  102. }
  103. int time = 0; //定义遍历数组的变量,从0开始,依次输出0到255每个像素值的数目
  104. while (times <= 255) {
  105. cout << "像素值" << time << "的数目为: " << pixel_num02[time] << endl; // 遍历输出
  106. time++; // 不要忘了自增
  107. }
  108. OTSU(Sobel_Ojld, pixel_num01); // OTSU阈值分割
  109. OTSU(Sobel_City, pixel_num02);
  110. }

 

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/481168
推荐阅读
相关标签
  

闽ICP备14008679号