目标
在本节中, - 我们将学习直方图均衡化的概念,并利用它来提高图像的对比度。
理论
考虑这样一个图像,它的像素值仅局限于某个特定的值范围。例如,较亮的图像将把所有像素限制在高值上。但是一幅好的图像会有来自图像所有区域的像素。因此,您需要将这个直方图拉伸到两端(如下图所示,来自wikipedia),这就是直方图均衡化的作用(简单来说)。这通常会提高图像的对比度。
我建议您阅读直方图均衡化上的Wikipedia页面,以获取有关它的更多详细信息。它很好地解释了示例,使您在阅读完之后几乎可以理解所有内容。相反,在这里我们将看到其Numpy实现。之后,我们将看到OpenCV功能。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('wiki.jpg',0)
hist,bins = np.histogram(img.flatten(),256,[0,256])
cdf = hist.cumsum()#求和函数
cdf_normalized = cdf * float(hist.max()) / cdf.max()
plt.plot(cdf_normalized, color = 'b')
plt.hist(img.flatten(),256,[0,256], color = 'r')
plt.xlim([0,256])
plt.legend(('cdf','histogram'), loc = 'upper left')#plt.legend用法在后面
plt.show()
'''
plt.legend()的几种用法
**
(1)设置图列位置
python
plt.legend(loc=' ')
![](https://img-blog.csdnimg.cn/20200830165252486.png#pic_center)
**(2)设置图例字体大小**
fontsize : int or float or {‘xx-small’, ‘x-small’, ‘small’, ‘medium’, ‘large’, ‘x-large’, ‘xx-large’}
(3)设置图例边框及背景
plt.legend(loc='best',frameon=False) #去掉图例边框
plt.legend(loc='best',edgecolor='blue') #设置图例边框颜色
plt.legend(loc='best',facecolor='blue') #设置图例背景颜色,若无边框,参数无效
对于边框还可以采用面向对象方式:
legend = plt.legend(["First", "Second"])
frame = legend.get_frame()
frame.set_facecolor('blue')
(4)设置图例标题
legend = plt.legend(["BJ", "SH"], title='Beijing VS Shanghai')
或者
plt.plot(["BJ", "SH"],loc='upper left',title='Beijing VS Shanghai')
(5)设置图例名字及对应关系
legend = plt.legend([p1, p2], ["BJ", "SH"])
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(0,10,1)
plt.plot(x,x,'r--',x,np.cos(x),'g--',marker='*')
plt.xlabel('row')
plt.ylabel('cow')
plt.legend(["BJ","SH"],loc='upper left',loc='upper left')
plt.show()'''
你可以看到直方图位于较亮的区域。我们需要全频谱。为此,我们需要一个转换函数,将亮区域的输入像素映射到整个区域的输出像素。这就是直方图均衡化的作用。
现在我们找到最小的直方图值(不包括0),并应用wiki页面中给出的直方图均衡化方程。但我在这里用过,来自Numpy的掩码数组概念数组。对于掩码数组,所有操作都在非掩码元素上执行。您可以从Numpy文档中了解更多关于掩码数组的信息。
cdf_m = np.ma.masked_equal(cdf,0)
cdf_m = (cdf_m - cdf_m.min())*255/(cdf_m.max()-cdf_m.min())
cdf = np.ma.filled(cdf_m,0).astype('uint8')#astype 转换数据的类型为这个
现在我们有了查找表,该表为我们提供了有关每个输入像素值的输出像素值是什么的信息。因此,我们仅应用变换。
img2 = cdf[img]
现在,我们像以前一样计算其直方图和cdf(您这样做),结果如下所示:
另一个重要的特征是,即使图像是一个较暗的图像(而不是我们使用的一个较亮的图像),经过均衡后,我们将得到几乎相同的图像。因此,这是作为一个“参考工具”,使所有的图像具有相同的照明条件。这在很多情况下都很有用。例如,在人脸识别中,在对人脸数据进行训练之前,对人脸图像进行直方图均衡化处理,使其具有相同的光照条件。
OpenCV中的直方图均衡
OpenCV具有执行此操作的功能cv.equalizeHist()。它的输入只是灰度图像,输出是我们的直方图均衡图像。 下面是一个简单的代码片段,显示了它与我们使用的同一图像的用法:
在这里np.hstack((img,equ))是把2个图水平合一起
import cv2 as cv
import numpy as np
img = cv.imread('1.jpg',0)
equ = cv.equalizeHist(img)
res = np.hstack((img,equ)) #stacking images side-by-side
cv.imshow('img',res)
cv.waitKey(0)
'''np.hstack将参数!元组!的元素数组按水平方向进行叠加
因为是元组,如果你要合一起只能:(a1,a2,a3)再放入函数
import numpy as np
arr1 = np.array([[1,3], [2,4] ])
arr2 = np.array([[1,4], [2,6] ])
res = np.hstack((arr1, arr2))
print (res)
#
[[1 3 1 4]
[2 4 2 6]]
import numpy as np
arr1 = [1,2,3]
arr2 = [4,5]
arr3 = [6,7]
res = np.hstack((arr1, arr2,arr3))
print (res)
#
[1 2 3 4 5 6 7]
'''
因此,现在您可以在不同的光照条件下拍摄不同的图像,对其进行均衡并检查结果。
当图像的直方图限制在特定区域时,直方图均衡化效果很好。在直方图覆盖较大区域(即同时存在亮像素和暗像素)的强度变化较大的地方,效果不好。请检查其他资源中的SOF链接。
CLAHE(对比度受限的自适应直方图均衡)
我们刚刚看到的第一个直方图均衡化考虑了图像的整体对比度。在许多情况下,这不是一个好主意。例如,下图显示了输入图像及其在全局直方图均衡后的结果。
直方图均衡后,背景对比度确实得到了改善。但是在两个图像中比较雕像的脸。由于亮度过高,我们在那里丢失了大多数信息。这是因为它的直方图不像我们在前面的案例中所看到的那样局限于特定区域(尝试绘制输入图像的直方图,您将获得更多的直觉)。
因此,为了解决这个问题,使用了**自适应直方图均衡**。在这种情况下,图像被分成称为“tiles”的小块(在OpenCV中,tileSize默认为8x8
)。然后,像往常一样对这些块中的每一个进行直方图均衡。因此,在较小的区域中,直方图将限制在一个较小的区域中(除非存在噪声)。如果有噪音,它将被放大。为了避免这种情况,应用了对比度限制。如果任何直方图bin超出指定的对比度限制(在OpenCV中默认为40),则在应用直方图均衡之前,将这些像素裁剪并均匀地分布到其他bin。均衡后,要消除图块边界中的伪影,请应用双线性插值。
下面的代码片段显示了如何在OpenCV中应用CLAHE:
import numpy as np
import cv2 as cv
img = cv.imread('tsukuba_l.png',0)
# create a CLAHE object (Arguments are optional).
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl1 = clahe.apply(img)
cv.imwrite('clahe_2.jpg',cl1)
查看下面的结果,并将其与上面的结果进行比较,尤其是雕像区域:
介绍
在第一篇文章中,我们计算并绘制了一维直方图。 之所以称为一维,是因为我们仅考虑一个特征,即像素的灰度强度值。 但是在二维直方图中,您要考虑两个特征。 通常,它用于查找颜色直方图,其中两个特征是每个像素的色相和饱和度值。
已经有一个python示例(samples / python / color_histogram.py)用于查找颜色直方图。 我们将尝试了解如何创建这种颜色直方图,这对于理解诸如直方图反向投影之类的更多主题将很有用。
OpenCV中的二维直方图
它非常简单,并且使用相同的函数**cv.calcHist**()进行计算。 对于颜色直方图,我们需要将图像从BGR转换为HSV。(Hue, Saturation, Value)分别是色调(H)、饱和度(S)和明度(V)(请记住,对于一维直方图,我们从BGR转换为灰度)。对于二维直方图,其参数将进行如下修改:
- channel = [0,1],因为我们需要同时处理H和S平面。
- bins = [180,256] 对于H平面为180,对于S平面为256。
- range = [0,180,0,256] 色相值介于0和180之间,饱和度介于0和256之间
现在检查以下代码:
import numpy as np
import cv2 as cv
img = cv.imread('home.jpg')
hsv = cv.cvtColor(img,cv.COLOR_BGR2HSV)
hist = cv.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256])
就是这样。
Numpy中的二维直方图
Numpy还为此提供了一个特定的函数:np.histogram2d()。(记住,对于一维直方图我们使用了**np.histogram**())。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('home.jpg')
hsv = cv.cvtColor(img,cv.COLOR_BGR2HSV)
hist, xbins, ybins = np.histogram2d(h.ravel(),s.ravel(),[180,256],[[0,180],[0,256]])
第一个参数是H平面,第二个是S平面,第三个是每个箱子的数量,第四个是它们的范围。
现在我们可以检查如何绘制这个颜色直方图。
绘制二维直方图
方法1:使用 cv.imshow()
我们得到的结果是尺寸为80x256
的二维数组。因此,可以使用**cv.imshow**()函数像平常一样显示它们。它将是一幅灰度图像,除非您知道不同颜色的色相值,否则不会对其中的颜色有太多了解。
方法2:使用Matplotlib
我们可以使用matplotlib.pyplot.imshow()函数绘制具有不同颜色图的2D直方图。它使我们对不同的像素密度有了更好的了解。但是,除非您知道不同颜色的色相值,否则乍一看并不能使我们知道到底是什么颜色。我还是更喜欢这种方法。它简单而更好。
注意 使用此功能时,请记住,插值法应采用最近邻以获得更好的结果。
考虑下面的代码:
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('home.jpg')
hsv = cv.cvtColor(img,cv.COLOR_BGR2HSV)
hist = cv.calcHist( [hsv], [0, 1], None, [180, 256], [0, 180, 0, 256] )
plt.imshow(hist,interpolation = 'nearest')
plt.show()
下面是输入图像及其颜色直方图。X轴显示S值,Y轴显示色相。
在直方图中,您可以在H = 100和S = 200附近看到一些较高的值。它对应于天空的蓝色。同样,在H = 25和S = 100附近可以看到另一个峰值。它对应于宫殿的黄色。您可以使用GIMP等任何图像编辑工具进行验证。
方法3:OpenCV示例样式
OpenCV-Python2示例中有一个颜色直方图的示例代码(samples / python / color_histogram.py)。如果运行代码,则可以看到直方图也显示了相应的颜色。或者简单地,它输出颜色编码的直方图。其结果非常好(尽管您需要添加额外的线束)。
在该代码中,作者在HSV中创建了一个颜色图。然后将其转换为BGR。将所得的直方图图像与此颜色图相乘。他还使用一些预处理步骤来删除小的孤立像素,从而获得良好的直方图。
我将其留给读者来运行代码,对其进行分析并拥有自己的解决方法。下面是与上面相同的图像的代码输出:
您可以在直方图中清楚地看到存在什么颜色,那里是蓝色,那里是黄色,并且由于棋盘的存在而有些白色。很好!