经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
C++ OpenCV单峰三角阈值法Thresh_Unimodal详解
来源:jb51  时间:2021/12/9 11:45:36  对本文有异议

需求说明

在对图像进行处理时,经常会有这类需求:想通过阈值对图像进行二值化分割,以提取自己感兴趣的区域,常见的阈值分割方法有常数分割、最大类间方差法、双峰分割、三角法等等,不同的场景应用不同的阈值方法。

今天要讲的方法,适合当图像的直方图具有明显单峰特征时使用,结合了三角法的原理而设计,相比较OpenCV自带的三角法,好处是可以根据自身需求合理修改函数;如果用OpenCV库的函数,只有一个接口,若不能达到较理想的应用效果,就束手无策了。

下面介绍具体实现流程。

具体流程

1)取图像的灰度图,并遍历统计0-255各个灰度值所出现的次数。

  1. cv::Mat src = imread("test.jpg", 0);
  2. cv::Mat hist = cv::Mat::zeros(1, 256, CV_32FC1);
  3. for (int i = 0; i < src.rows; ++i)
  4. {
  5. for (int j = 0; j < src.cols; ++j)
  6. {
  7. hist.at<float>(0, src.at <uchar>(i, j))++;
  8. }
  9. }

2)去除0和255的直方图数据,这一步就是OpenCV三角法所没有的。很多人可能不理解为什么要这一步,在你对图像进行阈值化时如果提前进行了相关的运算,可能导致结果大于255的数值全部变为255,或者数值低于0的数值全部变为0,这就使得0和255的数值其实涵盖了许多数值,呈累加态,很容易形成双峰,这样就很难找到我们真正想要的峰。例如0和255的数值都是10000左右,0略大一些,而我们的真峰是在250左右的灰度值,数值只有8000多,那么在后续阈值计算时就会因为峰的方向错了而带来毁灭性打击。别觉得我说夸张了,只有自己去碰碰壁才能深刻领悟我说的。

  1. hist.at<float>(0, 255) = 0;
  2. hist.at<float>(0, 0) = 0;

3)确认峰值位置,maxidx是峰值对应的灰度值,max是峰值高度,也是灰度值对应数据的个数。

  1. float max = 0;
  2. int maxidx = 0;
  3. for (int i = 0; i < 256; ++i)
  4. {
  5. if (hist.at<float>(0, i) > max)
  6. {
  7. max = hist.at<float>(0, i);
  8. maxidx = i;
  9. }
  10. }

4)判断峰值在左侧还是右侧,true为左侧,false为右侧。

  1. bool lr = maxidx < 127;

5)当在左侧时,连接峰值(maxidx,max)和(255,0)点,用两点建立直线公式,如下图所示公式。 L的表达式可以转换为Ax+By+C=0的形式,A是-max,B是maxidx-255,C是max*255,在结合距离公式可以计算出直方图曲线上每个点到直线的距离,取距离最长的那个点作为阈值。

  1. if (lr)
  2. {
  3. float A = float(-max);
  4. float B = float(maxidx - 255);
  5. float C = float(max * 255);
  6. for (int i = maxidx + 1; i < 256; ++i)
  7. {
  8. float x0 = float(i);
  9. float y0 = hist.at<float>(0, i);
  10. float d = abs(A * x0 + B * y0 + C) / std::sqrt(A * A + B * B);
  11. if (d > maxd)
  12. {
  13. maxd = d;
  14. maxdidx = i;
  15. }
  16. }
  17. }

6)右侧同理,连接峰值(maxidx,max)和(0,0)点,公式ABC如代码所示。

  1. else {
  2. float A = float(-max);
  3. float B = float(maxidx);
  4. float C = 0.0f;
  5. for (int i = 0; i < maxidx; ++i)
  6. {
  7. float x0 = float(i);
  8. float y0 = hist.at<float>(0, i);
  9. float d = abs(A * x0 + B * y0 + C) / std::sqrt(A * A + B * B);
  10. if (d > maxd)
  11. {
  12. maxd = d;
  13. maxdidx = i;
  14. }
  15. }
  16. }

7)二值化,完成。

  1. result.setTo(255, src > maxdidx);
  2. idx = maxdidx;
  3. return result;

功能函数

  1. // 单峰三角阈值法
  2. cv::Mat Thresh_Unimodal(cv::Mat &src, int& idx)
  3. {
  4. cv::Mat result = cv::Mat::zeros(src.size(), CV_8UC1);
  5. // 统计直方图
  6. cv::Mat hist = cv::Mat::zeros(1, 256, CV_32FC1);
  7. for (int i = 0; i < src.rows; ++i)
  8. {
  9. for (int j = 0; j < src.cols; ++j)
  10. {
  11. hist.at<float>(0, src.at<uchar>(i, j))++;
  12. }
  13. }
  14. hist.at<float>(0, 255) = 0;
  15. hist.at<float>(0, 0) = 0;
  16. // 搜索最大值位置
  17. float max = 0;
  18. int maxidx = 0;
  19. for (int i = 0; i < 256; ++i)
  20. {
  21. if (hist.at<float>(0, i) > max)
  22. {
  23. max = hist.at<float>(0, i);
  24. maxidx = i;
  25. }
  26. }
  27. // 判断最大点在哪一侧,true为左侧,false为右侧
  28. bool lr = maxidx < 127;
  29. float maxd = 0;
  30. int maxdidx = 0;
  31. // 假设在左侧
  32. if (lr)
  33. {
  34. float A = float(-max);
  35. float B = float(maxidx - 255);
  36. float C = float(max * 255);
  37. for (int i = maxidx + 1; i < 256; ++i)
  38. {
  39. float x0 = float(i);
  40. float y0 = hist.at<float>(0, i);
  41. float d = abs(A * x0 + B * y0 + C) / std::sqrt(A * A + B * B);
  42. if (d > maxd)
  43. {
  44. maxd = d;
  45. maxdidx = i;
  46. }
  47. }
  48. }
  49. // 假设在右侧
  50. else {
  51. float A = float(-max);
  52. float B = float(maxidx);
  53. float C = 0.0f;
  54. for (int i = 0; i < maxidx; ++i)
  55. {
  56. float x0 = float(i);
  57. float y0 = hist.at<float>(0, i);
  58. float d = abs(A * x0 + B * y0 + C) / std::sqrt(A * A + B * B);
  59. if (d > maxd)
  60. {
  61. maxd = d;
  62. maxdidx = i;
  63. }
  64. }
  65. }
  66. // 二值化
  67. result.setTo(255, src > maxdidx);
  68. idx = maxdidx;
  69. return result;
  70. }

C++测试代码

  1. #include <iostream>
  2. #include <time.h>
  3. #include <opencv2/opencv.hpp>
  4. using namespace std;
  5. using namespace cv;
  6. cv::Mat DrawHistImg(cv::Mat &hist);
  7. cv::Mat Thresh_Unimodal(cv::Mat &src, int& idx);
  8. int main()
  9. {
  10. cv::Mat src = imread("test.jpg", 0);
  11. // 绘制均衡化后直方图
  12. cv::Mat hrI = DrawHistImg(src);
  13. // 单峰三角阈值法
  14. int thresh;
  15. cv::Mat result = Thresh_Unimodal(src, thresh);
  16. cout << " thresh: " << thresh << endl;
  17. imshow("original", src);
  18. imshow("hist", hrI);
  19. imshow("result", result);
  20. waitKey(0);
  21. return 0;
  22. }
  23. // 绘制简易直方图
  24. cv::Mat DrawHistImg(cv::Mat &src)
  25. {
  26. cv::Mat hist = cv::Mat::zeros(1, 256, CV_32FC1);
  27. for (int i = 0; i < src.rows; ++i)
  28. {
  29. for (int j = 0; j < src.cols; ++j)
  30. {
  31. hist.at<float>(0, src.at <uchar>(i, j))++;
  32. }
  33. }
  34. cv::Mat histImage = cv::Mat::zeros(540, 1020, CV_8UC1);
  35. const int bins = 255;
  36. double maxValue;
  37. cv::Point2i maxLoc;
  38. cv::minMaxLoc(hist, 0, &maxValue, 0, &maxLoc);
  39. int scale = 4;
  40. int histHeight = 540;
  41. for (int i = 0; i < bins; i++)
  42. {
  43. float binValue = (hist.at<float>(i));
  44. int height = cvRound(binValue * histHeight / maxValue);
  45. cv::rectangle(histImage, cv::Point(i * scale, histHeight),
  46. cv::Point((i + 1) * scale - 1, histHeight - height), cv::Scalar(255), -1);
  47. }
  48. return histImage;
  49. }
  50. // 单峰三角阈值法
  51. cv::Mat Thresh_Unimodal(cv::Mat &src, int& idx)
  52. {
  53. cv::Mat result = cv::Mat::zeros(src.size(), CV_8UC1);
  54. // 统计直方图
  55. cv::Mat hist = cv::Mat::zeros(1, 256, CV_32FC1);
  56. for (int i = 0; i < src.rows; ++i)
  57. {
  58. for (int j = 0; j < src.cols; ++j)
  59. {
  60. hist.at<float>(0, src.at<uchar>(i, j))++;
  61. }
  62. }
  63. hist.at<float>(0, 255) = 0;
  64. hist.at<float>(0, 0) = 0;
  65. // 搜索最大值位置
  66. float max = 0;
  67. int maxidx = 0;
  68. for (int i = 0; i < 256; ++i)
  69. {
  70. if (hist.at<float>(0, i) > max)
  71. {
  72. max = hist.at<float>(0, i);
  73. maxidx = i;
  74. }
  75. }
  76. // 判断最大点在哪一侧,true为左侧,false为右侧
  77. bool lr = maxidx < 127;
  78. float maxd = 0;
  79. int maxdidx = 0;
  80. // 假设在左侧
  81. if (lr)
  82. {
  83. float A = float(-max);
  84. float B = float(maxidx - 255);
  85. float C = float(max * 255);
  86. for (int i = maxidx + 1; i < 256; ++i)
  87. {
  88. float x0 = float(i);
  89. float y0 = hist.at<float>(0, i);
  90. float d = abs(A * x0 + B * y0 + C) / std::sqrt(A * A + B * B);
  91. if (d > maxd)
  92. {
  93. maxd = d;
  94. maxdidx = i;
  95. }
  96. }
  97. }
  98. // 假设在右侧
  99. else {
  100. float A = float(-max);
  101. float B = float(maxidx);
  102. float C = 0.0f;
  103. for (int i = 0; i < maxidx; ++i)
  104. {
  105. float x0 = float(i);
  106. float y0 = hist.at<float>(0, i);
  107. float d = abs(A * x0 + B * y0 + C) / std::sqrt(A * A + B * B);
  108. if (d > maxd)
  109. {
  110. maxd = d;
  111. maxdidx = i;
  112. }
  113. }
  114. }
  115. // 二值化
  116. result.setTo(255, src > maxdidx);
  117. idx = maxdidx;
  118. return result;
  119. }

测试效果

图1 原图灰度图

图2 直方图

图3 阈值图

图4 阈值结果

通过imagewatch插件可以观察阈值203是不是在距离最远的位置,答案是肯定的。

如果函数有什么可以改进完善的地方,非常欢迎大家指出,一同进步何乐而不为呢~?

以上就是C++ OpenCV单峰三角阈值法Thresh_Unimodal详解的详细内容,更多关于C++ OpenCV单峰三角阈值法的资料请关注w3xue其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号