图像增强的主要目的是显示隐藏的图像细节,或者用新的动态范围增加图像对比度。直方图均衡(HE)是用于图像对比度增强的最流行的技术之一,因为HE在计算上快速且易于实现。HE通过基于输入灰度级的概率分布重新映射图像的灰度级来执行其操作。然而,HE很少用于视频监控、数码相机和电视等消费电子应用,因为HE往往会引入一些令人讨厌的伪影和不自然的增强,包括强度饱和效应。这个问题的原因之一是HE通常会显著改变图像的亮度,从而使输出图像变得饱和,具有非常亮或暗的强度值。因此,为了增强消费电子产品的图像,亮度保持是需要考虑的一个重要特性。
为了克服HE的局限性并保持图像的亮度,人们提出了几种保持亮度的直方图均衡技术。首先,Kim提出了保持亮度的双直方图均衡(BBHE),BBHE根据输入图像的平均值将输入图像直方图分为两部分,然后独立地对每个部分进行均衡。因此,由于保留了原始平均亮度,所以保留了平均亮度。Wan等人引入了二元子图像直方图均衡(DSIHE),它类似于BBHE,只是输入图像的中值用于直方图分割,而不是平均亮度[5]。Chen和Ramli提出了最小平均亮度误差双直方图均衡(MMBEBHE),这是BBHE方法的扩展,提供了最大的亮度保持。该算法找出原始图像和增强图像之间的最小平均亮度误差。它采用最优点作为分离点,而不是输入图像的平均值或中值。尽管这些方法可以执行良好的对比度增强,但根据直方图中灰度分布的变化,它们也会导致更令人讨厌的副作用。递归平均分离HE(RMSHE)是BBHE的另一个改进版本。该方法递归地将直方图分离为多个子直方图,而不是像BBHE中那样将两个子直方图分离。最初,根据原始直方图的平均亮度创建两个子直方图。随后,来自先前获得的两个子直方图的平均亮度被用作创建更多子直方图的第二和第三分离点。以类似的方式,递归地执行该算法,直到满足所需数量的子直方图。然后,HE方法独立地应用于每个子直方图。前面讨论的方法是基于通过使用中值或平均亮度将原始直方图划分为几个子直方图。尽管上述方法很好地保持了平均亮度,但这些方法不能进一步扩展位于动态范围的最小值或最大值附近的亚直方图区域。然而,它也并非没有副作用,例如褪色的外观、不希望的棋盘效果和图像亮度的显著变化。
为了解决上述问题,Abdullah Al-Wadud等人引入了动态直方图均衡(DHE)技术。DHE基于局部极小值对原始直方图进行分割。然而,DHE并不考虑亮度的保持。为此,Ibrahim和Kong提出了保持亮度的动态直方图均衡(BPDHE)。该方法基于平滑直方图的局部最大值来分割图像直方图。它为每个分区分配一个新的动态范围。最后,对输出强度进行归一化,使所得图像的平均强度等于输入强度。尽管BPHE在平均亮度保持方面表现良好,但亮度归一化的比率起着重要作用。较小的比值会导致不显著的对比度增强。对于大比率(即比率值大于1),最终强度值可能超过输出动态范围的最大强度值。超出的像素将被量化为灰度级的最大强度值,并产生强度饱和问题(在MATLAB环境中)。Sheet等人提出了保持亮度的动态模糊直方图均衡(BPDFHE)。这是BPDFHE的增强版本。BPDFHE技术以这样的方式操纵图像直方图,即不发生直方图峰值的重新映射,而只发生两个连续峰值之间的谷部分中的灰度值的重新分布。使用BPDFHE方法的结果显示出良好的对比度增强和小的伪影。
为了克服不必要的过度增强和噪声放大,提出了基于模糊逻辑的灰度和彩色图像直方图均衡技术。所提出的FHE方法不仅保持了图像的亮度,而且提高了原始图像的局部对比度。首先,利用模糊集理论计算模糊直方图。其次,根据原始图像的中值将模糊直方图一分为二。最后,HE方法被独立地应用于每个子直方图以提高对比度。
using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
namespace Legal.Truffer.ImageProcess
{
public partial static class CVUtility
{
/// <summary>
/// 直方图均衡化
/// 保持亮度的动态直方图均衡化(Brightness Preserving Dynamic Histogram Equalization:BPDHE)
/// </summary>
/// <param name="src"></param>
/// <returns></returns>
public static Mat Equalization_BPDHE(Mat src)
{
double[] B = new double[256];
for (int k = 0; k < 255; k++)
{
int t1 = 0, t2 = 0, t3 = 0;
for (int m = 0; m < src.Rows; m++)
{
for (int n = 0; n < src.Cols; n++)
{
if (src.At<Vec3b>(m, n)[0] == k) t1 = t1 + 1;
if (src.At<Vec3b>(m, n)[1] == k) t2 = t2 + 1;
if (src.At<Vec3b>(m, n)[2] == k) t3 = t3 + 1;
}
}
int t = t1 + t2 + t3;
double b = 1.0f / (src.Rows * src.Cols * 3.0);
double x = t * b;
B[k] = x;
}
double[] T1 = new double[256];
for (int i = 1; i < 256; i++)
{
for (int j = 1; j < i; j++)
{
T1[i - 1] = B[j - 1] + T1[i - 1];
}
}
double[] T2 = new double[256];
for (int i = 1; i < 256; i++)
{
T2[i - 1] = Math.Round((T1[i - 1] * 255) + 0.5);
}
Mat bpdhe = new Mat();
src.CopyTo(bpdhe);
for (int i = 0; i < 255; i++)
{
for (int j = 0; j < src.Rows; j++)
{
for (int k = 0; k < src.Cols; k++)
{
if (bpdhe.At<Vec3b>(j, k)[0] == i)
bpdhe.At<Vec3b>(j, k)[0] = (byte)T2[i];
if (bpdhe.At<Vec3b>(j, k)[1] == i)
bpdhe.At<Vec3b>(j, k)[1] = (byte)T2[i];
if (bpdhe.At<Vec3b>(j, k)[2] == i)
bpdhe.At<Vec3b>(j, k)[2] = (byte)T2[i];
}
}
}
return bpdhe;
}
}
}