图像最大熵阈值分割的详细原理及基于OpenCV的C++代码实现
前面已经写了四篇博文介绍图像的阈值化技术了,四篇博文的标题和链接如下:
使用OpenCV的函数threshold()对图像进行二值化或阈值化处理
https://www.hhai.cc/thread-162-1-1.html
使用OpenCV的函数createTrackbar()创建窗口滑动条查找图像二值化的最优阈值
https://www.hhai.cc/thread-228-1-1.html
图像的OTSU阈值化、双阈值化、半阈值化的原理及OpenCV代码实现
https://www.hhai.cc/thread-165-1-1.html
详解OpenCV的函数adaptiveThreshold(),并利用它实现图像的自适应二值化阈值分割
https://www.hhai.cc/thread-166-1-1.html
从上面四篇博文中我们可以看出,图像的阈值化技术的关键在于找寻合适的阈值然后用这个阈值对图像进行二值化处理。
找寻阈值的方法有很多,上面三篇博文就提供了四种方法。
本文介绍利用图像图像直方图的最大熵找寻阈值的方法,并附相关代码。
先介绍原理:
1、要理解最大熵就不得不先了解熵的概念。熵的概念用于表示系统的不确定性,系统的熵越大则系统的不确定性越大。所以取系统的最大熵就是取系统的最大不确定性,从信息论的角度来讲就是取最大的信息量。
2、熵的数学定义如下:
![](http://pic1.hhai.cc/pic1/2023/2023-03/001/10.png)
对数的底数a决定了熵的单位。当a=2时,熵的单位为bit;当a=时,熵的单位为nat;当a=10时,熵的单位为Harteley。
![](http://pic1.hhai.cc/pic1/2023/2023-03/001/11.png)
也就是说,当随机变量的所有事件等概率分布时,系统的不确定性最大。
这个很好理解嘛,比如下面这两种情况:
第一种情况:
A股票涨的概率为0.3
B股票涨的概率为0.5
C股票涨的概率为0.1
D股票涨的概率为0.1
第二种情况:
A股票、B股票、C股票、D股票涨的概率都为0.25
现在让你从这四支股票中选一支,你觉得是哪种情况好选呢?
显然是第一种情况好选嘛。
本文前面说熵用来衡量系统的不确定性,熵越大系统的不确定性越高,理解了这个数学定义,也就更加理解这句话了。
3、下面介绍利用最大熵进行图像阈值化的原理和思路。
我们对图像进行阈值化操作,实际上就是通过某个阈值将图像分成两部分。具体在本文的算法中,我们通过某个阈值将图像分为前景和背景,前景是图像中像素灰度值大于等于阈值的部分,背景是图像中像素灰度值小于等于阈值的部分。
如果我们把图像按阈值分为前景和背景,那么对于图像的某个像素,要么归属于前景,要么归属于背景。如果我们知道这个像素归属于前景的概率,又知道其归属于背景的概率,那么我们便可以计算出图像按前景和背景进行划分的熵值了。按照上面对熵的数学原理的介绍,我们可以写出此种情况下图像的熵值为:
![](http://pic1.hhai.cc/pic1/2023/2023-03/001/12.png)
所以求上面情况下图像的熵值关键是计算在某个阈值下的值和值。怎么计算这两个概率值呢?我们可以用后验概率来作为这两个概率值。
具体的过程通过下面这个例子说明。
![](http://pic1.hhai.cc/pic1/2023/2023-03/001/13.png)
![](http://pic1.hhai.cc/pic1/2023/2023-03/001/14.png)
从上面的计算过程中可以看出,我们在具体实现这个算法时需要统计各灰度值出现的次数,图像的直方图刚好统计了各灰度值出现的次数,所以我们便可利用直方图的统计结果来计算相应的熵值。
将阈值在图像的灰度范围0~255内遍历一次,并计算出各个阈值下图像的熵值,我们便可找到在哪个阈值情况下图像的熵值最大。进而用这个熵值实现图像的二值化分割。
原理和思路就介绍完了,接下来上代码:
借助图像直方图计算图像阈值分割最大熵,并对图像进行阈值分割的C++代码如下(基于OpenCV写成):
代码中用到的图像下载链接:
https://pan.baidu.com/s/1k13r2DdhEXuXWlxV-IbxNA 提取码:kwgo
[C++] 纯文本查看 复制代码 //出处:昊虹AI笔记网(hhai.cc)
//用心记录计算机视觉和AI技术
//博主微信/QQ 2487872782
//QQ群 271891601
//欢迎技术交流与咨询
//OpenCV版本 OpenCV3.0
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
// 利用图像的直方图计算各阈值分割图像时图像的熵值
float caculateCurrentEntropy(cv::Mat hist, int threshold)
{
float BackgroundSum = 0, targetSum = 0;
const float* pDataHist = (float*) hist.ptr<float>(0);
for (int i = 0; i < 256; i++)
{
// 累计背景值
if( i < threshold )
{
BackgroundSum += pDataHist[i];
}else // 累计前景值
{
targetSum += pDataHist[i];
}
}
//接下来计算图像在阈傎threshold分割情况下的熵值
float BackgroundEntropy = 0, targetEntropy = 0;
for (int i = 0; i < 256; i++)
{
// 计算图像熵的背景熵分量
if( i < threshold )
{
if( pDataHist[i] == 0 )
continue;
float ratio1 = pDataHist[i] / BackgroundSum;
// 当循环完成后,BackgroundEntropy中存储的便是图像熵的背景熵分量
BackgroundEntropy += - ratio1 * logf(ratio1);
}else // 计算图像熵的前景熵分量
{
if( pDataHist[i] == 0 )
continue;
float ratio2 = pDataHist[i] / targetSum;
// 当循环完成后,targetEntrop中存储的便是图像熵的前景熵分量
targetEntropy += -ratio2 * logf(ratio2);
}
}
return ( targetEntropy + BackgroundEntropy );
}
// 找寻最大熵及最大熵对应的阈值
cv::Mat maxEntropySegMentation(cv::Mat inputImage)
{
// 初始化直方图参数
const int channels[1]={0};
const int histSize[1]={256};
float pranges[2]={0, 256};
const float* ranges[1]={pranges};
cv::Mat hist;
// 计算直方图
cv::calcHist(&inputImage,1,channels,
cv::Mat(),hist,1,histSize,ranges);
float maxentropy = 0;
int max_index = 0;
cv::Mat result;
// 遍历得到最大熵阈值分割的最佳阈值
for(int i = 0; i < 256; i++)
{
float cur_entropy =
caculateCurrentEntropy(hist,i);
// 计算当前最大值的位置
if(cur_entropy > maxentropy)
{
maxentropy = cur_entropy;
max_index = i;
}
}
cout<<"利用最大熵法找到的阈值为:"<<max_index<<endl;
// 二值化分割
threshold(inputImage, result, max_index, 255,
CV_THRESH_BINARY); //max_index就是满足最大熵情况下的阈值
return result;
}
int main()
{
//cv::Mat srcImage=cv::imread("hand1.jpg");
cv::Mat srcImage=cv::imread("E:/material/images/P0027-coins-01.png");
if(!srcImage.data)
return 0;
cv::Mat grayImage;
cv::cvtColor(srcImage, grayImage, CV_BGR2GRAY);
// 最大熵阈值分割实现
cv::Mat result = maxEntropySegMentation(grayImage);
cv::imshow("grayImage", grayImage);
cv::imshow("result" , result);
cv::waitKey(0);
return 0;
}
运行结果如下图所示:
![](http://pic1.hhai.cc/pic1/2023/2023-03/001/15.png) |