当前位置:   article > 正文

循序渐进之(五)空间域图像增强之自适应直方图均衡化(AHE)

自适应直方图均衡

                      循序渐进之(五)空间域图像增强之自适应直方图均衡化(AHE)

 

文字摘自:对比度受限的自适应直方图均衡化(CLAHE)

直方图均衡化(HE)是一种很常用的直方图类方法,基本思想是通过图像的灰度分布直方图确定一条映射曲线,用来对图像进行灰度变换,以达到提高图像 对比度的目的。该映射曲线其实就是图像的累计分布直方图(CDF)(严格来说是呈正比例关系)。然而HE是对图像全局进行调整的方法,不能有效地提高局部 对比度,而且某些场合效果会非常差。如:

上述原图和HE结果图的直方图分别为:

因为从原图的直方图中求取的映射函数(CDF)形状为:

将它作用于原图像会导致直方图被整体右移,没有充分利用整个灰度动态范围。

为了提高图像的局部对比度,有人提出将图像分成若干子块,对子块进行HE处理,这便是AHE(自适应直方图均衡化),使用AHE处理上图得到:

 

结果直方图:

可 以看出结果图像的灰度较好地分布在了全部动态范围上。从结果图像上也可以看出,局部对比度的确得到了提高,视觉效果要优于HE。但是仍然有个问题:AHE 对局部对比度提高过大,导致图像失真。看看背景区,本来的黑色背景现在已经变成白色了,原因是因为背景区中的局部子块统计得到的直方图在0灰度处幅值太高 (实际上全黑子图基本上就集中在0灰度处),这样导致映射曲线斜率过高,将所有灰度值都映射到整个灰度轴的右侧,所以结果图中背景偏白。

上边的AHE算法实现的图像处理效果为添加了线性插值功能,这里代码实现的AHE,暂时没有加入线性插值功能,所以可以看出图像分块

代码参考:CLAHE的实现和研究   中的AHE算法部分。

  1. #include<iostream>
  2. #include<opencv.hpp>
  3. using namespace std;
  4. using namespace cv;
  5. int main()
  6. {
  7. Mat originImage = imread("E://匹配.jpg", 0);
  8. imshow("原图", originImage);
  9. Mat src = originImage.clone();
  10. const int blockNumber = 8;//把图像分成block数量
  11. int width = src.cols;
  12. int height = src.rows;
  13. int singleBlockWidth = src.cols / 8;//每个block大小
  14. int singleBlockHeight = src.rows / 8;
  15. int pixelNumber[blockNumber *blockNumber][256] = { 0 };//存储不同block中各个不同灰度像素数量
  16. float total[blockNumber *blockNumber][256] = { 0.0 };//累计直方图
  17. for (int i = 0; i < blockNumber; i++)
  18. {
  19. for (int j = 0; j < blockNumber; j++)
  20. {
  21. int startPixelW = (i)*singleBlockWidth;
  22. int endPixelW = (i+1)*singleBlockWidth;
  23. int startPixelH = (j)*singleBlockHeight;
  24. int endPixelH = (j+1)*singleBlockHeight;
  25. int number = i + 8 * j;//统计运算到哪一个block了
  26. int singleBlockPixelNumber = singleBlockWidth*singleBlockHeight;
  27. for (int x = startPixelW; x < endPixelW; x++)//统计不同block中各个不同灰度像素数量
  28. for (int y = startPixelH; y < endPixelH; y++)
  29. {
  30. int pixelValue = src.at<uchar>(y, x);
  31. pixelNumber[number][pixelValue]++;
  32. }
  33. for (int k = 0; k < 256; k++)//计算累计直方图
  34. {
  35. if (k == 0)
  36. total[number][k] = 1.0*pixelNumber[number][k] / singleBlockPixelNumber;
  37. else
  38. total[number][k] = total[number][k - 1] + 1.0*pixelNumber[number][k] / singleBlockPixelNumber;
  39. }
  40. }
  41. }
  42. for (int i = 0; i < blockNumber; i++)//利用累计直方图对于原像素灰度在各自block中进行映射
  43. {
  44. for (int j = 0; j < blockNumber; j++)
  45. {
  46. int startPixelW = (i)*singleBlockWidth;
  47. int endPixelW = (i+1)*singleBlockWidth;
  48. int startPixelH = (j)*singleBlockHeight;
  49. int endPixelH = (j+1)*singleBlockHeight;
  50. int number = i + 8 * j;
  51. int singleBlockPixelNumber = singleBlockWidth*singleBlockHeight;
  52. for (int x = startPixelW; x < endPixelW; x++)
  53. for (int y = startPixelH; y < endPixelH; y++)
  54. {
  55. int pixelValue = src.at<uchar>(y, x);
  56. src.at<uchar>(y, x) = total[number][pixelValue] * 255;
  57. }
  58. }
  59. }
  60. imshow("均衡图", src);
  61. waitKey(0);
  62. return 0;
  63. }

效果如下:

加入线性插值功能,代码参考:CLAHE的实现和研究 中的CLAHE算法部分(这里的线性插值代码好像较为复杂,后续看有没有更简便的实现手段)。。

  1. #include<iostream>
  2. #include<opencv.hpp>
  3. using namespace std;
  4. using namespace cv;
  5. int main()
  6. {
  7. Mat originImage = imread("E://匹配.jpg", 0);
  8. imshow("原图", originImage);
  9. Mat src = originImage.clone();
  10. Mat src1 = originImage.clone();
  11. const int blockNumber = 8;//把图像分成block数量
  12. int width = src.cols;
  13. int height = src.rows;
  14. int singleBlockWidth = src.cols / 8;//每个block大小
  15. int singleBlockHeight = src.rows / 8;
  16. int pixelNumber[blockNumber *blockNumber][256] = { 0 };//存储不同block中各个不同灰度像素数量
  17. float total[blockNumber *blockNumber][256] = { 0.0 };//累计直方图
  18. for (int i = 0; i < blockNumber; i++)
  19. {
  20. for (int j = 0; j < blockNumber; j++)
  21. {
  22. int startPixelW = (i)*singleBlockWidth;
  23. int endPixelW = (i + 1)*singleBlockWidth;
  24. int startPixelH = (j)*singleBlockHeight;
  25. int endPixelH = (j + 1)*singleBlockHeight;
  26. int number = i + 8 * j;//统计运算到哪一个block了
  27. int singleBlockPixelNumber = singleBlockWidth*singleBlockHeight;
  28. for (int x = startPixelW; x < endPixelW; x++)//统计不同block中各个不同灰度像素数量
  29. for (int y = startPixelH; y < endPixelH; y++)
  30. {
  31. int pixelValue = src.at<uchar>(y, x);
  32. pixelNumber[number][pixelValue]++;
  33. }
  34. for (int k = 0; k < 256; k++)//计算累计直方图
  35. {
  36. if (k == 0)
  37. total[number][k] = 1.0*pixelNumber[number][k] / singleBlockPixelNumber;
  38. else
  39. total[number][k] = total[number][k - 1] + 1.0*pixelNumber[number][k] / singleBlockPixelNumber;
  40. }
  41. }
  42. }
  43. for (int i = 0; i < blockNumber; i++)//利用累计直方图对于原像素灰度在各自block中进行映射
  44. {
  45. for (int j = 0; j < blockNumber; j++)
  46. {
  47. int startPixelW = (i)*singleBlockWidth;
  48. int endPixelW = (i + 1)*singleBlockWidth;
  49. int startPixelH = (j)*singleBlockHeight;
  50. int endPixelH = (j + 1)*singleBlockHeight;
  51. int number = i + 8 * j;
  52. int singleBlockPixelNumber = singleBlockWidth*singleBlockHeight;
  53. for (int x = startPixelW; x < endPixelW; x++)
  54. for (int y = startPixelH; y < endPixelH; y++)
  55. {
  56. int pixelValue = src1.at<uchar>(y, x);
  57. src1.at<uchar>(y, x) = total[number][pixelValue] * 255;
  58. }
  59. }
  60. }
  61. imshow("均衡图无线性差值", src1);
  62. for (int i = 0; i < width; i++)
  63. {
  64. for (int j = 0; j < height; j++)
  65. {
  66. //four coners
  67. if (i <= singleBlockWidth / 2 && j <= singleBlockHeight / 2)
  68. {
  69. int num = 0;
  70. src.at<uchar>(j, i) = (int)(total[num][src.at<uchar>(j, i)] * 255);
  71. }
  72. else if (i <= singleBlockWidth / 2 && (j >= ((blockNumber - 1)*singleBlockHeight +singleBlockHeight / 2))) {
  73. int num = blockNumber*(blockNumber - 1);
  74. src.at<uchar>(j, i) = (int)(total[num][src.at<uchar>(j, i)] * 255);
  75. }
  76. else if (i >= ((blockNumber - 1)*singleBlockWidth + singleBlockHeight / 2) && j <=singleBlockHeight / 2) {
  77. int num = blockNumber - 1;
  78. src.at<uchar>(j, i) = (int)(total[num][src.at<uchar>(j, i)] * 255);
  79. }
  80. else if (i >= ((blockNumber - 1)*singleBlockWidth + singleBlockWidth / 2) && j >= ((blockNumber - 1)*singleBlockHeight + singleBlockHeight / 2)) {
  81. int num = blockNumber*blockNumber - 1;
  82. src.at<uchar>(j, i) = (int)(total[num][src.at<uchar>(j, i)] * 255);
  83. }
  84. //four edges except coners
  85. else if (i <= singleBlockWidth/ 2)
  86. {
  87. //线性插值
  88. int num_i = 0;
  89. int num_j = (j -singleBlockHeight / 2) /singleBlockHeight;
  90. int num1 = num_j*blockNumber + num_i;
  91. int num2 = num1 + blockNumber;
  92. float p = (j - (num_j*singleBlockHeight +singleBlockHeight / 2)) / (1.0f*singleBlockHeight);
  93. float q = 1 - p;
  94. src.at<uchar>(j, i) = (int)((q*total[num1][src.at<uchar>(j, i)] + p*total[num2][src.at<uchar>(j, i)]) * 255);
  95. }
  96. else if (i >= ((blockNumber - 1)*singleBlockWidth+ singleBlockWidth/ 2)) {
  97. //线性插值
  98. int num_i = blockNumber - 1;
  99. int num_j = (j -singleBlockHeight / 2) /singleBlockHeight;
  100. int num1 = num_j*blockNumber + num_i;
  101. int num2 = num1 + blockNumber;
  102. float p = (j - (num_j*singleBlockHeight +singleBlockHeight / 2)) / (1.0f*singleBlockHeight);
  103. float q = 1 - p;
  104. src.at<uchar>(j, i) = (int)((q*total[num1][src.at<uchar>(j, i)] + p*total[num2][src.at<uchar>(j, i)]) * 255);
  105. }
  106. else if (j <=singleBlockHeight / 2) {
  107. //线性插值
  108. int num_i = (i - singleBlockWidth/ 2) / singleBlockWidth;
  109. int num_j = 0;
  110. int num1 = num_j*blockNumber + num_i;
  111. int num2 = num1 + 1;
  112. float p = (i - (num_i*singleBlockWidth+ singleBlockWidth/ 2)) / (1.0f*singleBlockWidth);
  113. float q = 1 - p;
  114. src.at<uchar>(j, i) = (int)((q*total[num1][src.at<uchar>(j, i)] + p*total[num2][src.at<uchar>(j, i)]) * 255);
  115. }
  116. else if (j >= ((blockNumber - 1)*singleBlockHeight +singleBlockHeight / 2)) {
  117. //线性插值
  118. int num_i = (i - singleBlockWidth/ 2) / singleBlockWidth;
  119. int num_j = blockNumber - 1;
  120. int num1 = num_j*blockNumber + num_i;
  121. int num2 = num1 + 1;
  122. float p = (i - (num_i*singleBlockWidth+ singleBlockWidth/ 2)) / (1.0f*singleBlockWidth);
  123. float q = 1 - p;
  124. src.at<uchar>(j, i) = (int)((q*total[num1][src.at<uchar>(j, i)] + p*total[num2][src.at<uchar>(j, i)]) * 255);
  125. }
  126. //双线性插值
  127. else {
  128. int num_i = (i - singleBlockWidth/ 2) / singleBlockWidth;
  129. int num_j = (j -singleBlockHeight / 2) /singleBlockHeight;
  130. int num1 = num_j*blockNumber + num_i;
  131. int num2 = num1 + 1;
  132. int num3 = num1 + blockNumber;
  133. int num4 = num2 + blockNumber;
  134. float u = (i - (num_i*singleBlockWidth+ singleBlockWidth/ 2)) / (1.0f*singleBlockWidth);
  135. float v = (j - (num_j*singleBlockHeight +singleBlockHeight / 2)) / (1.0f*singleBlockHeight);
  136. src.at<uchar>(j, i) = (int)((u*v*total[num4][src.at<uchar>(j, i)] +
  137. (1 - v)*(1 - u)*total[num1][src.at<uchar>(j, i)] +
  138. u*(1 - v)*total[num2][src.at<uchar>(j, i)] +
  139. v*(1 - u)*total[num3][src.at<uchar>(j, i)]) * 255);
  140. }
  141. //最后这步,类似高斯平滑
  142. src.at<uchar>(j, i) = src.at<uchar>(j, i) + (src.at<uchar>(j, i) << 8) + (src.at<uchar>(j, i) << 16);
  143. }
  144. }
  145. imshow("均衡图线性差值", src);
  146. waitKey(0);
  147. return 0;
  148. }

效果如下:

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/78873
推荐阅读
相关标签
  

闽ICP备14008679号