最大熵阈值分割法和OTSU算法类似,假设将图像分为背景和前景两个部分。熵代表信息量,图像信息量越大,熵就越大,最大熵算法就是找出一个最佳阈值使得背景与前景两个部分熵之和最大。
基本原理
频率和概率
直方图每个矩形框的数值描述的是图像中相应灰度值的频率。因此,可以说直方图是一种离散的频率分布。给定一个大小为M*N的图像I,直方图中所有矩形框所代表的数值之和,即为图像中的像素数量,即:
相对应的归一化直方图表示为:
0<=i<K 通常被解释为一个随机过程的概率分布或概率密度函数,表示的是图像中像素灰度值为i所出现的概率。i的累积概率值为1,即概率分布p必须满足以下关系:
与累积概率所所对应的累积直方图H是一个离散的分布函数P(),(通常也称为累积分布函数或cdf):
最大熵阈值分割
熵是信息论中一个重要的概念,这种方法常用于数据压缩领域。熵是一种统计测量方法,用以确定随机数据源中所包含的信息数量。例如,包含有N个像素的图像I,可以解释为包含有N个符号的信息,每一个符号的值都独立获取于有限范围K(e.g.,256)中的不同灰度值。
将数字图像建模为一种随机信号处理,意味着必须知道图像灰度中每一个灰度g所发生的概率,即:
因为所有概率应该事先知道,所以这些概率也称为先验概率。对于K个不同灰度值g=0,…,K-1的概率向量可以表示为:
上述概率向量也称为概率分布或称为概率密度函数(pdf)。实际数字图像处理应用当中,先验的概率通常是不知道的,但是这些概率可以通过在一幅图像或多幅图像中观察对应灰度值所发生的频率,从而估算出其概率。图像概率密度函数p(g)可以通过归一化其对应的直方图获得其概率,即:
最大熵
数字图像中给定一个估算的概率密度函数p(g),数字图像中的熵定义为:
用图像熵进行图像分割
利用图像熵为准则进行图像分割有一定历史了,学者们提出了许多以图像熵为基础进行图像分割的方法。我们介绍一种由Kapuret al提出来,现在仍然使用较广的一种图像熵分割方法。
给定一个特定的阈值q(0<=q<K-1),对于该阈值所分割的两个图像区域C0,C1,其估算的概率密度函数可表示为:
为了计算效率,对上面公式进行优化
其中:
示例演示
用OpenCV实现了两个实现方式,MaxEntropyFast是提升了效率的函数。完整代码
#include <iostream>
#include <opencv2/opencv.hpp>
const double g_EPSINON = 0.00001;
int MaxEntropy(const cv::Mat& src, cv::Mat& dst, int thresh, int factor)
{
const int k_Grayscale = 256;
double hist[k_Grayscale] = { 0.0 };
int r = src.rows;
int c = src.cols;
for (int i = 0; i < r; ++i)
{
const uchar* ptr = src.ptr<uchar>(i);
for (int j = 0; j < c; ++j)
hist[ptr[j]]++;
}
double probability = 0.0; //概率
double maxEntropy = DBL_MIN; //最大熵
int totalpix = r * c;
for (int i = 0; i < k_Grayscale; ++i)
{
//计算背景像素数
double backgroundpix = 0;
for (int j = 0; j <= i; ++j)
backgroundpix += hist[j];
//计算背景熵
double H0 = 0.0; //背景熵
if (backgroundpix > 0)
{
for (int j = 0; j <= i; ++j)
{
probability = hist[j] / backgroundpix;
if(probability > g_EPSINON)
H0 += -probability * log(probability);
}
}
//计算前景像素数
double frontpix = totalpix - backgroundpix;
//计算前景熵
double H1 = 0.0; //前景熵
if (frontpix > 0)
{
for (int k = i + 1; k < k_Grayscale; ++k)
{
probability = hist[k] / frontpix;
if (probability > g_EPSINON)
H1 += -probability * log(probability);
}
}
//计算最大熵
if(H0 + H1 > maxEntropy)
{
maxEntropy = H0 + H1;
thresh = i;
}
}
thresh += factor;
//阈值处理
src.copyTo(dst);
for (int i = 0; i < r; ++i){
uchar* ptr = dst.ptr<uchar>(i);
for (int j = 0; j < c; ++j){
if (ptr[j]> thresh)
ptr[j] = 255;
else
ptr[j] = 0;
}
}
return thresh;
}
void MakeTable(double *S0, double *S1, double *normalizeHist, int histSize)
{
double s0 = 0.0;
for (int i = 0; i < histSize; i++)
{
if(normalizeHist[i] > g_EPSINON)
s0 = s0 + normalizeHist[i] * log(normalizeHist[i]);
S0[i] = s0;
}
double s1 = 0.0;
for (int i = histSize - 1; i >= 0; i--)
{
S1[i] = s1;
if (normalizeHist[i] > g_EPSINON)
s1 = s1 + normalizeHist[i] * log(normalizeHist[i]);
}
}
int MaxEntropyFast(const cv::Mat& src, cv::Mat& dst, int thresh, int factor)
{
const int k_GrayScale = 256;
int hist[k_GrayScale] = { 0 };
int r = src.rows;
int c = src.cols;
for (int i = 0; i < r; ++i) {
const uchar* ptr = src.ptr<uchar>(i);
for (int j = 0; j < c; ++j)
hist[ptr[j]]++;
}
double normalizeHist[256] = { 0.0 }, sum = (double)(r * c);
for (int i = 0; i < k_GrayScale; i++)
normalizeHist[i] = hist[i] / sum ;
double S0[256] = { 0 }, S1[256] = { 0 };
MakeTable(S0, S1, normalizeHist, k_GrayScale);
double H01 = 0.0, H0 = 0.0, H1 = 0.0, P1 = 0.0, P0 = 0.0, Hmax = DBL_MIN;
for (int q = 0; q < k_GrayScale; q++)
{
P0 += normalizeHist[q];
if (P0 > g_EPSINON)
H0 = -(1.0 * S0[q]) / P0 + log(P0);
else
H0 = 0.0;
P1 = 1.0 - P0;
if (P1 > g_EPSINON)
H1 = -(1.0 * S1[q]) / P1 + log(P1);
else
H1 = 0.0;
H01 = H0 + H1;
if (H01 > Hmax)
{
Hmax = H01;
thresh = q;
}
}
thresh += factor;
//阈值处理
src.copyTo(dst);
for (int i = 0; i < r; ++i) {
uchar* ptr = dst.ptr<uchar>(i);
for (int j = 0; j < c; ++j) {
if (ptr[j] > thresh)
ptr[j] = 255;
else
ptr[j] = 0;
}
}
return thresh;
}
int main()
{
cv::Mat src = cv::imread("cameraman.tif");
//cv::Mat src = cv::imread("test120.png");
if (src.empty()){
return -1;
}
if (src.channels() > 1)
cv::cvtColor(src, src, CV_RGB2GRAY);
cv::Mat dst, dstOtsu;
int thresh = 0;
double t2 = (double)cv::getTickCount();
thresh = MaxEntropy(src, dst, thresh, 0); //Max_Entropy
std::cout << "MaxEntropyThresh=" << thresh << std::endl;
t2 = (double)cv::getTickCount() - t2;
double time2 = (t2 *1000.) / ((double)cv::getTickFrequency());
std::cout << "MaxEntropyProcess=" << time2 << " ms. " << std::endl << std::endl;
cv::Mat dstFast;
thresh = 0;
t2 = (double)cv::getTickCount();
thresh = MaxEntropyFast(src, dstFast, thresh, 0); //Max_Entropy
std::cout << "MaxEntropyFastThresh=" << thresh << std::endl;
t2 = (double)cv::getTickCount() - t2;
time2 = (t2 *1000.) / ((double)cv::getTickFrequency());
std::cout << "MaxEntropyFastProcess=" << time2 << " ms. " << std::endl << std::endl;
double Otsu = 0;
Otsu = cv::threshold(src, dstOtsu, Otsu, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
std::cout << "OtsuThresh=" << Otsu << std::endl;
cv::namedWindow("src", CV_WINDOW_NORMAL);
cv::imshow("src", src);
cv::namedWindow("dst", CV_WINDOW_NORMAL);
cv::imshow("dst", dst);
cv::namedWindow("dstOtsu", CV_WINDOW_NORMAL);
cv::imshow("dstOtsu", dstOtsu);
//cv::imwrite("r.jpg",dst);
cv::waitKey(0);
}
运行结果
参考资料
- 最大熵阈值分割法