C++ OpenCV单峰三角阈值法Thresh_Unimodal详解

 更新时间:2021年12月09日 10:40:54   作者:翟天保Steven  
本文主要介绍了适合当图像的直方图具有明显单峰特征时使用,结合了三角法的原理而设计的图像分割方法,感兴趣的小伙伴可以了解一下

需求说明

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

今天要讲的方法,适合当图像的直方图具有明显单峰特征时使用,结合了三角法的原理而设计,相比较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<float>(0, src.at <uchar>(i, j))++;
	}
}

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

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

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

float max = 0;
int maxidx = 0;
for (int i = 0; i < 256; ++i)
{
	if (hist.at<float>(0, i) > max)
	{
		max = hist.at<float>(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,在结合距离公式可以计算出直方图曲线上每个点到直线的距离,取距离最长的那个点作为阈值。

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<float>(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<float>(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<float>(0, src.at<uchar>(i, j))++;
		}
	}
	hist.at<float>(0, 255) = 0;
	hist.at<float>(0, 0) = 0;
	// 搜索最大值位置
	float max = 0;
	int maxidx = 0;
	for (int i = 0; i < 256; ++i)
	{
		if (hist.at<float>(0, i) > max)
		{
			max = hist.at<float>(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<float>(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<float>(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 <iostream>
#include <time.h>
#include <opencv2/opencv.hpp>
 
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<float>(0, src.at <uchar>(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<float>(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<float>(0, src.at<uchar>(i, j))++;
		}
	}
	hist.at<float>(0, 255) = 0;
	hist.at<float>(0, 0) = 0;
	// 搜索最大值位置
	float max = 0;
	int maxidx = 0;
	for (int i = 0; i < 256; ++i)
	{
		if (hist.at<float>(0, i) > max)
		{
			max = hist.at<float>(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<float>(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<float>(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;
}

测试效果

图1 原图灰度图

图2 直方图

图3 阈值图

图4 阈值结果

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

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

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

相关文章

  • 字符串的模式匹配详解--BF算法与KMP算法

    字符串的模式匹配详解--BF算法与KMP算法

    这篇文章记录一下串里面的模式匹配,模式匹配,顾名思义就是给定一个被匹配的字符串,然后用一个字符串模式(模型)去匹配上面说的字符串,看后者是否在前者里面出现。常用的有2种算法可以实现,下面我们来具体探讨下
    2014-08-08
  • C语言实现栈的示例详解

    C语言实现栈的示例详解

    栈是一种特殊的线性表,只允许从一端进出数据,称为后进先出,先进后出。本文主要为大家介绍了C语言实现栈的示例代码,感兴趣的可以了解一下
    2022-06-06
  • c++ base64编解码使用示例

    c++ base64编解码使用示例

    这篇文章主要介绍了c++的base64编解码使用示例,需要的朋友可以参考下
    2014-02-02
  • C++ 中this指针的用途详解

    C++ 中this指针的用途详解

    这篇文章主要给大家介绍了关于C++ 中this指针的用途,文中通过示例代码介绍的非常详细,对大家学习或者使用C++具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2021-09-09
  • C++编译器和链接器工作原理及使用方法完全指南

    C++编译器和链接器工作原理及使用方法完全指南

    本文将详细介绍C++中的编译器和链接器以及它们的工作原理及使用方法全面详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • C语言多文件编程问题解析

    C语言多文件编程问题解析

    在某些场景中,考虑到编译效率和可移植性,#pragma once 和 #ifndef 经常被结合使用来避免头文件被 重复引入,这里介绍用 _Pragma 操作符避免头文件重复引入的问题,感兴趣的朋友跟随小编一起看看吧
    2022-12-12
  • VC++ 中ListCtrl经验总结

    VC++ 中ListCtrl经验总结

    这篇文章主要介绍了VC++ 中ListCtrl经验总结的相关资料,需要的朋友可以参考下
    2015-06-06
  • 使用C语言操作文件的基本函数整理

    使用C语言操作文件的基本函数整理

    这篇文章主要介绍了使用C语言操作文件的基本函数整理,包括创建和打开以及关闭文件的操作方法,需要的朋友可以参考下
    2015-08-08
  • C++设计模式之抽象工厂模式

    C++设计模式之抽象工厂模式

    这篇文章主要介绍了C++设计模式之抽象工厂模式,本文要讲的抽象工厂模式,就是工厂方法模式的扩展和延伸,需要的朋友可以参考下
    2014-09-09
  • C/C++判断传入的UTC时间是否当天的实现方法

    C/C++判断传入的UTC时间是否当天的实现方法

    在项目中经常会显示一个时间,如果这个时间在今日内就显示为时分秒,否则显示为年月日,有需要的朋友可以参考一下
    2014-01-01

最新评论