图像对比度计算公式_图像算法原理与实践——单通道变换

本文详细介绍了图像处理中的单通道变换,包括色彩基础数学理论、图像反色、对数变换、指数(伽马)变换、分段线性变换和直方图处理。通过对图像灰度值的变换,可以调节图像的对比度、亮度和细节,以达到增强图像视觉效果的目的。直方图均衡化作为提升对比度的有效方法,通过统计和变换灰度值分布,使图像看起来更加清晰。

一、色彩基础数学理论

对于灰度图像,我们可以使用3D坐标系来形象的描述整个图像所有像素值。使用X-Y-Z三维坐标来表示图像信息,其中 X-O-Y平面表示图像的本身的平面,Z轴的值是对应像素点的颜色值,使用二元函数表达式可以方便的表达函数的 z=f(x,y),其中x表示横轴像素坐标位置,y表示是纵轴像素坐标位置,z表示对应像素点的灰度值。在实际应用场景中,(x,y)是离散的像素点,当我们将x和y的坐标位置进行归一化,同时将像素灰度值z进行归一化处理后,再来看归一化后的连续3D灰度坐标系,可以发现整个坐标系非常类似一个山丘的坐标图。山丘的平地就相当于纯黑色像素点,山峰就相当于亮度比较高的像素点,山谷就相当于灰度值比较小亮度比较暗的像素点,通过这样的3D坐标系,我们可以清楚地将图像像素内容形象化的表达出来。

目前大部分的基础算法和空域算法都是对各个像素的灰度值进行变换处理。例如:调节亮度的处理就是 z’= z * 1.2 或者 z’= z * 0.8,直接调节像素的灰度值即可。而对于RGB色彩空间的处理,也是采用同样的处理方式,只是将R、G、B各个通道分别单独处理即可。同理,在RGB色域空间调节一个彩色图像的亮度,可以使用相同的方法:

R’= R * 1.2; G’= G * 1.2; B’= B * 1.2; 变换后生成的 (R’, G’, B’)的颜色值就是调亮后的彩色图像。

b6de3278392081cfa783d428ad25ee5b.png
灰度图像原始图像

f6441808f9e980cc73a2f4818d198e2c.png
RGB原始图像

70a91258c4bd3e45b3a58ab637509557.png
灰度图像的3D坐标系

二、图像反色

所谓图像反色,就是将原先图像将原先的颜色使用相反的色彩来替换。例如:纯黑色使用纯白色替换; 深灰色色使用浅灰色来替换。对于归一化的颜色值,其最大值是1.0,那么对应的反色值就是 (1.0 - color),因此各个反色值计算:

6a9a8654c9c9b6ce24c961a5a116d85a.png
归一化值的反色计算公式

而在实际开发中我们的灰度值或者R、G、B通道值都是使用单个字节来表示,也即每个通道颜色值的正常表示范围是[0, 255],因此我们在实际开发中使用的反色公式

6efe7cfa53b51022f9cd90a8d9dd754b.png
针对单通道1字节的反色公式

对应的变换代码

//
// 功能:进行单通道反色变换,对于RGB彩色颜色值,各个通道分别调用该函数
// 输入: nInputData : 单通道颜色值,值在[0, 255]
// 返回: 变换后的颜色值,值在[0, 255]
//
uint8_t Trans_Reverse(uint8_t nInputData)
{
  // 归一化输入的颜色值  
  double fInData = (double)nInputData / 255.0;

  // 进行反色变换
  double fTransVal = 1.0 - fInData;
    
  // 将变换后的值重新还原到 [0, 255] 区间
  fTransVal = MIN( MAX(0, fTransVal ), 1.0);  
  uint8_t nRetVal = (uint8_t)(fTransVal * 255);

  return nRetVal;
}  

uint8_t Trans_ReverseFast(uint8_t nInputData)
{
  // 直接反色变换,这个计算最快
  return (255-nInputData);
}  

变换后的效果

1ebb49813e743e07d4e0ff2b2ef7bd87.png
反色后的灰度图像

870a844a6be42fec65565f22a0e87c65.png
反色后的RGB图像

三、对数变换

对数变换可以调节图像中低灰度值范围 或者 高灰度 值范围,使得低(高)灰度值范围更宽一些,这样可以将图像中 较暗区域(低灰度值范围)或者 较亮区域(高灰度值范围)的细节进行增加,将相关区域的细节更好的表现出来。对数变换是使用如下的变换公式:

ba287e254c83da1f5e1cf4afb25f2697.png
对数变换公式

98932d58478fcc9badae68c3615a207d.png

从这个变换曲线中可以看到:

当原始灰度颜色值在 [0.0, 0.3]之间的时候,输出灰度颜色值要明显大于[0.0, 0.3]的值,通过这个变换可以将原始图像中过暗的像素点快速提亮

当原始灰度颜色值在 [0.7, 1.0]之间的时候,输出灰度颜色值要明显小于[0.7, 1.0]的值,通过这个变换可以将原始图像中比较亮的像素点快速降低亮度

通过这样的简单变换,可以快速调节过暗或者过亮区域的像素值。

另外:这个公式的来源主要是来源于 Y=lnX的变形,变形为 Y = c*ln(X+offset)

207af2efff8b686082a448382c6987fb.png
Y= ln(x)函数曲线

b2c70ddef4f00fdc08d534d4387bec93.png
Y=1.2*ln(X+1.0)函数曲线

对应的变换代码

//
// 功能:进行单通道对数变换,对于RGB彩色颜色值,各个通道分别调用该函数
// 输入: nInputData : 单通道颜色值,值在[0, 255]
// 返回: 变换后的颜色值,值在[0, 255]
//
uint8_t Trans_Log(uint8_t nInputData)
{
  // 归一化输入的颜色值  
  double fInData = (double)nInputData / 255.0;

  // 进行指数变换
  double fAmplitude = 1.2;   // 幅度变换系数
  double fTransVal = fAmplitude * ln(fInData + 1.0);
    
  // 将变换后的值重新还原到 [0, 255] 区间
  fTransVal = MIN( MAX(0, fTransVal ), 1.0);  
  uint8_t nRetVal = (uint8_t)(fTransVal * 255);

  return nRetVal;
}  

下图中是经过对数变换 dest = 1.2*log(src + 1.0) 变换后的结果,在这个变换中将低灰度区间扩展得更多一些,这样可以将低灰度区域细节表现得更多一些

7e6c429d80b29c7e852eb9f71f0cdd96.png
Y=1.2*ln(X+1.0)灰度变换后的效果

四、指数(伽马)变换

指数变换又称为伽马变换,通常用于将漂白(相机过曝)的图片或者过暗(曝光不足)的图片进行校正(过暴的照片高光部分调低;过暗照片低光部分调亮)。在很多显示设备上也提供伽马校正就是这种指数变换。其具体变换公式如下:

cf5a5a90d8acfaf137a5155ecc7dae7e.png
指数变换公式

变换曲线曲线如下,当 γ 值大于1.0和小于1.0时有两个完全相反的变换曲线。

当γ>1.0时,变换后的图像中暗灰度像素会变得更多,亮灰度像素数量被减少,图像整体变暗

当γ<1.0时,变换后的图像中亮灰度像素会变得更多,暗灰度像素数量被减少,图像整体变凉

676e20029447e1e45232a1ee41570951.png

对应的变换代码

//
// 功能:进行单通道指数变换,对于RGB彩色颜色值,各个通道分别调用该函数
// 输入: nInputData : 单通道颜色值,值在[0, 255]
// 返回: 变换后的颜色值,值在[0, 255]
//
uint8_t Trans_Index(uint8_t nInputData)
{
  // 归一化输入的颜色值  
  double fInData = (double)nInputData / 255.0;

  // 进行指数变换
  double fAmplitude = 1.0;   // 幅度变换系数
  double fGama = 0.4;        // 指数系数
  double fTransVal = fAmplitude * power(fInData, fGama);
    
  // 将变换后的值重新还原到 [0, 255] 区间
  fTransVal = MIN( MAX(0, fTransVal ), 1.0);  
  uint8_t nRetVal = (uint8_t)(fTransVal * 255);

  return nRetVal;
}  


经过不同γ值变换后的灰度图像

55b8eef2378956ad66f922490c444f2a.png
Y = X ^ 0.4 指数变换后的灰度图

9c785bde93597d388e8008db7c1ea65e.png
Y = X ^ 2.5 指数变换后的灰度图

五、分段线性变换

作为灰度图高低光区域调节的补充,分段线性变换通常用于增加对比度调节,使得暗灰度区域显得更暗,亮灰度区域显得更亮,这样整张图像的中亮和暗两个区域的区别显得更加明显。在数学曲线表达上,Sigmoid函数曲线比较符合我们的期望,因此我们通常使用Sigmoid函数变换灰度值来进行增加对比度。

7b5acca8a3c993b5d1d1d68f48776bfb.png
原始Sigmoid函数曲线

不过由于我们的输入灰度值x范围在[0.0, 1.0]之间,输出灰度值y的范围也在[0.0, 1.0]之间,因此我们需要将Sigmoid函数做一个变形来满足我们定义域和值域的范围

82171af911d6f80f635563c53a766f74.png
调整后的Sigmoid函数曲线

对应的变换代码

//
// 功能:进行单通道Sigmoid变换,对于RGB彩色颜色值,各个通道分别调用该函数
// 输入: nInputData : 单通道颜色值,值在[0, 255]
// 返回: 变换后的颜色值,值在[0, 255]
//
uint8_t Trans_Sigmoid(uint8_t nInputData)
{
  // 归一化输入的颜色值  
  double fInData = (double)nInputData / 255.0;

  // 进行Sigmoid变换
  double fTransVal = 1.0 / (1.0 + exp(-10.0*(fInData-0.5)));
    
  // 将变换后的值重新还原到 [0, 255] 区间
  fTransVal = MIN( MAX(0, fTransVal ), 1.0);  
  uint8_t nRetVal = (uint8_t)(fTransVal * 255);

  return nRetVal;
}  


通过分段线性变换后的效果图

4d69e1c8f706d628a4e040f9af8961de.png
Sigmoid变换后的灰度图

75d88325eac59c4096bf9c4fd140e949.png
灰度变换后的彩色图

六、直方图处理

通过前面的各种变换我们可以看到,无论是灰度图还是彩色图像,其中的变换总体来说就是把输入灰度值(颜色值)通过特定的映射函数变换成另外的一个灰度值(颜色值)。

1、直方图统计

从变换结果来看,变换后的图像明暗程度,完全由各个像素点的灰度值来决定,如果低灰度值像素所占比例比较高,那么图像整体色调比较暗淡;反之,高灰度值像素所占比例较高的话,图像整体比较明亮。因此,统计图像中各个灰度值像素所占的比例就比较重要,因此需要统计各个灰度值所占比例的直方图。

定义:图像的直方图是指图像中 0~255总计256个灰度值的像素所占的比例

对于灰度图像,就是统计0~255各个灰度值的像素的所占的比例;

对于彩色图像,R、G、B三个通道的直方图需要分别单独计算。

下图中就是灰度图像及其直方图的统计结果,通过左边的直方图我们可以清晰的看到,整个图像中绝大部分像素的灰度值集中在 75~150之间,而更暗像素和更亮的像素都比较少,这样整个图像看起来对比度比较小、图像模糊不清晰。

7c5d265745163830b4b1084150b69b8e.png

如下是统计图像直方图的代码

//
// 功能:计算直方图
// 参数:pBitmap : 输入要统计的位图数据
//      szHistogramR: 输出Red通道的直方图,每个颜色值的像素个数
//      szHistogramG: 输出Green通道的直方图,每个颜色值的像素个数
//      szHistogramB: 输出Blue通道的直方图,每个颜色值的像素个数
//      szHistNormalR: 输出Red通道的直方图,每个颜色值的像素比例,值在[0.0, 1.0]
//      szHistNormalG: 输出Green通道的直方图,每个颜色值的像素比例,值在[0.0, 1.0]
//      szHistNormalB: 输出Blue通道的直方图,每个颜色值的像素比例,值在[0.0, 1.0]
// 返回:错误值
//
XRESULT XBmp_CalcHistogram(
  XBITMAP*  pBitmap, 
  XLONG      szHistogramR[256],
  XLONG      szHistogramG[256],
  XLONG      szHistogramB[256],
  XFLOAT    szHistNormalR[256],
  XFLOAT    szHistNormalG[256],
  XFLOAT    szHistNormalB[256]    )
{
  XBYTE    *pInLine = XNULL;
  XLONG    lInPxlBytes;
  XLONG   i, j;
  XRESULT  res = XOK;



  if (XNULL == pBitmap)
    return XERR_INVALID_PARAM;

  for (i = 0; i < 256; i++)
  {
    szHistogramR[i] = 0;
    szHistogramG[i] = 0;
    szHistogramB[i] = 0;
  }

  lInPxlBytes = XBmpGetPxlBytes(pBitmap->dwPixelArrayFormat);

  if (lInPxlBytes == 1) // 灰度图
  {
    pInLine = pBitmap->pPlane[0];
    for (i = 0; i < pBitmap->lHeight; i++)
    {
      XBYTE* pData = pInLine;

      for (j = 0; j < pBitmap->lWidth; j++)
      {
        XBYTE gray = pData[0];

        // 对应通道的像素个数分别增加,灰度图三个通道值一样
        szHistogramR[gray]++;
        szHistogramG[gray]++;
        szHistogramB[gray]++;

        pData += lInPxlBytes;
      }

      pInLine += pBitmap->lPitch[0];
    }
  }
  else // 彩色图
  {
    pInLine = pBitmap->pPlane[0];
    for (i = 0; i < pBitmap->lHeight; i++)
    {
      XBYTE* pData = pInLine;

      for (j = 0; j < pBitmap->lWidth; j++)
      {
        XBYTE R = pData[0];
        XBYTE G = pData[1];
        XBYTE B = pData[1];

        // 对应通道的像素个数分别增加
        szHistogramR[R]++;
        szHistogramG[G]++;
        szHistogramB[B]++;

        pData += lInPxlBytes;
      }

      pInLine += pBitmap->lPitch[0];
    }
  }

  // 将比例归一化到 [0.0, 1.0] 区间
  XLONG lPxlCount = pBitmap->lWidth * pBitmap->lHeight;
  for (i = 0; i < 256; i++)
  {
    szHistNormalR[i] = (XFLOAT)(szHistogramR[i]) * 1.0f / (XFLOAT)lPxlCount;
    szHistNormalG[i] = (XFLOAT)(szHistogramG[i]) * 1.0f / (XFLOAT)lPxlCount;
    szHistNormalB[i] = (XFLOAT)(szHistogramB[i]) * 1.0f / (XFLOAT)lPxlCount;
  }

  return XOK;
}

2、直方图均衡化原理

在实际的图像效果中,当图像直方图完全均匀分布的时候,此时图像的熵是最大的(随机变量每个值的概率都相同时,概率最大),图像对比度是最大的。所以,理想情况下,图像经过变换函数 Y = trans(X) 将灰度值变换后,各个灰度值的像素能够均匀分布,此时图像对比度最大,相对效果比较好。因此,我们通常进行直方图均衡化变换来提高图像质量。

在提高图像对比度的变换函数 f(x)需要满足两个条件:(这里X是在[0, 255]定义域范围)

1)f(x)在 0 <= X <= 255 上单调递增(不要求严格单调递增)

2)f(x)的值域范围是 [0, 255]


在图像处理中,有一个重要的函数,能够满足上面的条件:

2abbb20a3a30955f30851b0d6b916d1d.png

这里Px(t)表示概率密度函数, 在离散的图像中,表示直方图的每个灰度级的概率(在图像中,灰度级就可以看成是一个随机变量,而直方图就是该随机变量的概率密度函数),由概率论的知识,我们可以知道,变换函数f(x)其实就是连续型随机变量x的分布函数,表示的是函数下方的面积,而分布函数的两个性质:单调不减 和 值域为[0.0, 1.0],正好可以满足提高对比度变换函数的性质。

ba905334d01af54fd95f6efd53226b90.png

接着我们将这个变换函数中的积分部分使用加法和乘法来替换,根据积分的基本定义,可以使用求和代替积分,差分代替微分,所以上述的变换函数就是:

0b905c1b2a0c66640d29669a8995abce.png

其中h(xi)表示直方图中每个灰度级像素的个数w和 h分别表示图像的宽和高

对应直方图均衡化代码如下

//
// 计算分布函数(也就是变换函数f(x))
//
XLONG lPxlCount = pGrayBmp->lWidth * pGrayBmp->lHeight;
XBYTE szColorMap[256] = { 0 };
XLONG lTempVal, lPxlSum;
XLONG  i, j;

lPxlSum = 0;
for (i = 0; i <= 255;  i++)
{
  lPxlSum += szHistogramR[i];

  lTempVal = (XLONG)(lPxlSum * 255 / lPxlCount);
  lTempVal = MAX(0, lTempVal);
  lTempVal = MIN(255, lTempVal);

  szColorMap[i] = (XBYTE)lTempVal;
}

{ // 对灰度图像进行直方图均衡化变换处理
  XBYTE* pSrcLine = XNULL;
  XBYTE* pSrcData = XNULL;
  XLONG  lPxlBytes = 1;


  pSrcLine = pGrayBmp->pPlane[0];
  for (i = 0; i < pGrayBmp->lHeight; i++)
  {
    pSrcData = pSrcLine;
    for (j = 0; j < pGrayBmp->lWidth; j++)
    {
      // 变换处理
      pSrcData[0] = szColorMap[pSrcData[0]];

      pSrcData += lPxlBytes;
    }
    pSrcLine += pGrayBmp->lPitch[0];
  }
}

经过直方图均衡化后的图像,可以看到不同的灰度颜色值分布相对均匀,各像素之间的对比度拉大,图像显得更加清晰

59742b88a91d7a825d167dfcf9ec046f.png

3、直方图均衡化变换的特点

如果原始图像整体偏暗或者偏亮,那么直方图均衡化的方法比较合适。但直方图均衡化是一种全局处理方式,它对处理的数据不加选择,可能会增加背景干扰信息的对比度并且降低有用信号的对比度(如果图像某些区域对比度很好,而另一些区域对比度不好,那采用直方图均衡化就不一定适用)。此外,均衡化后图像的灰度级减少,某些细节将会消失;某些图像(如直方图有高峰),经过均衡化后对比度不自然的过分增强。针对直方图均衡化的缺点,已经有局部的直方图均衡化方法出现。

总结

图像的单通道变换是直接调整图像灰度或者单通道颜色值的图像算法。使用数学表达式就是:

newColor = trans(oldColor);

这里的trans是一元变换函数,可以根据应用需要使用任意的变换函数,例如:对数、指数、拟合函数等,本质上就是输入256个整数值,映射为输出的256个整数值。

oldColor 是输入颜色值,定义域范围在[0, 255]内整数

newColor 是输出颜色值,值域范围也是在[0, 255]范围内的整数

通道变换算法的主要特点:

一、变换主要影响图像的灰度值或者颜色值,所以变换后的图像通常会在色调、饱和度、对比度方面有各种变化。

二、变换后图像内容和结构没有变化,图像中的对象位置、形状、结构等通常不会有任何变化。

三、非常方便的使用查询表的方式进行优化。先将0~255的颜色值先使用变换函数变换好对应的输出颜色值,每个像素处理时直接采用查询表的方式,从而加快计算速度。具体查询表优化代码如下:

//
// 功能:采用查询表的方式进行单通道变换示例
//
XRESULT Trans_Process(XBITMAP* pInputBmp, XBITMAP* pOutputBmp) 
{
  XBYTE  szIndexTab[256] = { 0 };  // 指数变换的查询表
  XBYTE  szSigmoid[256] = { 0 };   // 分段变换的查询表
	XBYTE* pSrcLine = XNULL;
	XBYTE* pDstLine = XNULL;
  XLONG  lInPxlBytes, lOutPxlBytes;
  XLONG   i, j;
  XRESULT  res = XOK;
 

  if (XNULL == pInputBmp || XNULL == pOutputBmp)
    return XERR_INVALID_PARAM;
    
  lInPxlBytes = XBmpGetPxlBytes(pInputBmp->dwPixelArrayFormat);
  lOutPxlBytes = XBmpGetPxlBytes(pOutputBmp->dwPixelArrayFormat);
  if (lInPxlBytes < 3 || lOutPxlBytes < 3) // 这里只支持彩色图像
    return XERR_INVALID_PARAM;

  // 将256个颜色值直接进行变换映射
  for (i = 0; i < 256; i++)
  {
    szIndexTab[i] = Trans_Index(i);
    szSigmoid[i]  = Trans_Sigmoid(i);
  }


  pSrcLine = pInputBmp->pPlane[0];
  pDstLine = pOutputBmp->pPlane[0];
  for (i = 0; i < pOutputBmp->lHeight; i++)
  {
    XBYTE* pSrcData = pSrcLine;
    XBYTE* pDstData = pDstLine;
    for (j = 0; j < pOutputBmp->lWidth; j++)
    {
      // 分段变换, 如果是指数变换,就使用 szIndexTab[]表查询
      pDstData[0] = szSigmoid[pSrcData[0]];
      pDstData[1] = szSigmoid[pSrcData[1]];
      pDstData[2] = szSigmoid[pSrcData[2]];
			
      pSrcData += lInPxlBytes;
      pDstData += lOutPxlBytes;
     }

      pSrcLine += pInputBmp->lPitch[0];
      pDstLine += pOutputBmp->lPitch[0];
    }
  }

  return XOK;
}


文章系列目录

华叔-视觉魔术师:图像算法原理与实践——绪论​zhuanlan.zhihu.com
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值