OpenCV-单峰三角阈值法Thresh_Unimodal

OpenCV-单峰三角阈值法Thresh_UnimodalOpenCV amp C 代码实现单峰三角阈值法 Thresh Unimodal

需求说明

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

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

       下面介绍具体实现流程。

具体流程

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

cv::Mat src = imread("test.jpg", 0); cv::Mat hist = cv::Mat::zeros(1, 256, CV_32FC1); for (int i = 0; i < src.rows; ++i) { for (int j = 0; j < src.cols; ++j) { hist.at 
  
    (0, src.at 
   
     (i, j))++; } } 
    
  

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

hist.at 
  
    (0, 255) = 0; hist.at 
   
     (0, 0) = 0; 
    
  

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

float max = 0; int maxidx = 0; for (int i = 0; i < 256; ++i) { if (hist.at 
  
    (0, i) > max) { max = hist.at 
   
     (0, i); maxidx = i; } } 
    
  

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

bool lr = maxidx < 127;

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

d=\left | \frac{Ax_{0} +By_{0}+C}{\sqrt{A^{2}+B^{2} } } \right |

OpenCV-单峰三角阈值法Thresh_Unimodal

if (lr) { float A = float(-max); float B = float(maxidx - 255); float C = float(max * 255); for (int i = maxidx + 1; i < 256; ++i) { float x0 = float(i); float y0 = hist.at 
  
    (0, i); float d = abs(A * x0 + B * y0 + C) / std::sqrt(A * A + B * B); if (d > maxd) { maxd = d; maxdidx = i; } } } 
  

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

else { float A = float(-max); float B = float(maxidx); float C = 0.0f; for (int i = 0; i < maxidx; ++i) { float x0 = float(i); float y0 = hist.at 
  
    (0, i); float d = abs(A * x0 + B * y0 + C) / std::sqrt(A * A + B * B); if (d > maxd) { maxd = d; maxdidx = i; } } } 
  

          7)二值化,完成。

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

 

功能函数

// 单峰三角阈值法 cv::Mat Thresh_Unimodal(cv::Mat &src, int& idx) { cv::Mat result = cv::Mat::zeros(src.size(), CV_8UC1); // 统计直方图 cv::Mat hist = cv::Mat::zeros(1, 256, CV_32FC1); for (int i = 0; i < src.rows; ++i) { for (int j = 0; j < src.cols; ++j) { hist.at 
  
    (0, src.at 
   
     (i, j))++; } } hist.at 
    
      (0, 255) = 0; hist.at 
     
       (0, 0) = 0; // 搜索最大值位置 float max = 0; int maxidx = 0; for (int i = 0; i < 256; ++i) { if (hist.at 
      
        (0, i) > max) { max = hist.at 
       
         (0, i); maxidx = i; } } // 判断最大点在哪一侧,true为左侧,false为右侧 bool lr = maxidx < 127; float maxd = 0; int maxdidx = 0; // 假设在左侧 if (lr) { float A = float(-max); float B = float(maxidx - 255); float C = float(max * 255); for (int i = maxidx + 1; i < 256; ++i) { float x0 = float(i); float y0 = hist.at 
        
          (0, i); float d = abs(A * x0 + B * y0 + C) / std::sqrt(A * A + B * B); if (d > maxd) { maxd = d; maxdidx = i; } } } // 假设在右侧 else { float A = float(-max); float B = float(maxidx); float C = 0.0f; for (int i = 0; i < maxidx; ++i) { float x0 = float(i); float y0 = hist.at 
         
           (0, i); float d = abs(A * x0 + B * y0 + C) / std::sqrt(A * A + B * B); if (d > maxd) { maxd = d; maxdidx = i; } } } // 二值化 result.setTo(255, src > maxdidx); idx = maxdidx; return result; } 
          
         
        
       
      
     
    
  

C++测试代码

#include 
  
    #include 
   
     #include 
    
      using namespace std; using namespace cv; cv::Mat DrawHistImg(cv::Mat &hist); cv::Mat Thresh_Unimodal(cv::Mat &src, int& idx); int main() { cv::Mat src = imread("test.jpg", 0); // 绘制均衡化后直方图 cv::Mat hrI = DrawHistImg(src); // 单峰三角阈值法 int thresh; cv::Mat result = Thresh_Unimodal(src, thresh); cout << " thresh: " << thresh << endl; imshow("original", src); imshow("hist", hrI); imshow("result", result); waitKey(0); return 0; } // 绘制简易直方图 cv::Mat DrawHistImg(cv::Mat &src) { cv::Mat hist = cv::Mat::zeros(1, 256, CV_32FC1); for (int i = 0; i < src.rows; ++i) { for (int j = 0; j < src.cols; ++j) { hist.at 
     
       (0, src.at 
      
        (i, j))++; } } cv::Mat histImage = cv::Mat::zeros(540, 1020, CV_8UC1); const int bins = 255; double maxValue; cv::Point2i maxLoc; cv::minMaxLoc(hist, 0, &maxValue, 0, &maxLoc); int scale = 4; int histHeight = 540; for (int i = 0; i < bins; i++) { float binValue = (hist.at 
       
         (i)); int height = cvRound(binValue * histHeight / maxValue); cv::rectangle(histImage, cv::Point(i * scale, histHeight), cv::Point((i + 1) * scale - 1, histHeight - height), cv::Scalar(255), -1); } return histImage; } // 单峰三角阈值法 cv::Mat Thresh_Unimodal(cv::Mat &src, int& idx) { cv::Mat result = cv::Mat::zeros(src.size(), CV_8UC1); // 统计直方图 cv::Mat hist = cv::Mat::zeros(1, 256, CV_32FC1); for (int i = 0; i < src.rows; ++i) { for (int j = 0; j < src.cols; ++j) { hist.at 
        
          (0, src.at 
         
           (i, j))++; } } hist.at 
          
            (0, 255) = 0; hist.at 
           
             (0, 0) = 0; // 搜索最大值位置 float max = 0; int maxidx = 0; for (int i = 0; i < 256; ++i) { if (hist.at 
            
              (0, i) > max) { max = hist.at 
             
               (0, i); maxidx = i; } } // 判断最大点在哪一侧,true为左侧,false为右侧 bool lr = maxidx < 127; float maxd = 0; int maxdidx = 0; // 假设在左侧 if (lr) { float A = float(-max); float B = float(maxidx - 255); float C = float(max * 255); for (int i = maxidx + 1; i < 256; ++i) { float x0 = float(i); float y0 = hist.at 
              
                (0, i); float d = abs(A * x0 + B * y0 + C) / std::sqrt(A * A + B * B); if (d > maxd) { maxd = d; maxdidx = i; } } } // 假设在右侧 else { float A = float(-max); float B = float(maxidx); float C = 0.0f; for (int i = 0; i < maxidx; ++i) { float x0 = float(i); float y0 = hist.at 
               
                 (0, i); float d = abs(A * x0 + B * y0 + C) / std::sqrt(A * A + B * B); if (d > maxd) { maxd = d; maxdidx = i; } } } // 二值化 result.setTo(255, src > maxdidx); idx = maxdidx; return result; } 
                
               
              
             
            
           
          
         
        
       
      
     
    
  

测试效果

OpenCV-单峰三角阈值法Thresh_Unimodal
图1 原图灰度图

OpenCV-单峰三角阈值法Thresh_Unimodal
图2 直方图

OpenCV-单峰三角阈值法Thresh_Unimodal
图3 阈值图

OpenCV-单峰三角阈值法Thresh_Unimodal
图4 阈值结果

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

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

       如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/177366.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午7:41
下一篇 2026年3月26日 下午7:41


相关推荐

  • matlab物理碰撞建模_opencascade 碰撞检测

    matlab物理碰撞建模_opencascade 碰撞检测碰撞检测Note本节暂未进行完全的重写,错误可能会很多。如果可能的话,请对照原文进行阅读。如果有报告本节的错误,将会延迟至重写之后进行处理。当试图判断两个物体之间是否有碰撞发生时,我们通常不使用物体本身的数据,因为这些物体常常会很复杂,这将导致碰撞检测变得很复杂。正因这一点,使用重叠在物体上的更简单的外形(通常有较简单明确的数学定义)来进行碰撞检测成为常用的方法。我们基于这些简单的外形来检测碰撞,…

    2025年7月14日
    5
  • JAVA 实现简单的学生成绩管理系统

    JAVA 实现简单的学生成绩管理系统一、实验目的1.掌握java的类与对象的基本概念;2.掌握简单的信息管理系统的设计与实现。二、实验环境实验建议在安装了以下软件的计算机上完成:1.Windowsxp/win7/win8/win10操作系统2.JDK1.6以上版本3.Eclipse或NetBeansIDE或EditPlus或其它开发工具三、实验内容与要求(一)问题描述要求采用java…

    2022年7月13日
    19
  • MySQL轻快入门2021.3.18(字符集与乱码)[通俗易懂]

    MySQL轻快入门2021.3.18(字符集与乱码)[通俗易懂]输入,查询,展示的字符集编码一致就不会出现乱码。连接器好像对我们没有影响(仅限于gdk,utf-8),连接器字符编码太小转换的时候会造成数据的丢失。校对集就是它们的排序规则

    2022年7月11日
    20
  • Java中instanceof关键字的理解「建议收藏」

    Java中instanceof关键字的理解「建议收藏」Java中instanceof关键字的理解

    2022年4月23日
    57
  • appsettings与connectionstrings

    appsettings与connectionstrings这篇文章源于我在做一个手机号码归属地的例子时用到了配置文件,于是像视频上说的那样,我在app.config中写下了这段代码:可是却出现了在运行中弹出这样的错误提示:    很是郁闷,查了很多资料,反复看自己写的代码,可就是不知道到底错哪儿了,于是找了大神帮我查了,用大话设计模式中的配置文件方法做了修改,结果程序可以跑起来了:

    2022年5月11日
    71
  • huffman编码——原理与实现

    huffman编码——原理与实现

    2021年12月6日
    42

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号