若想提升图像的对比度,可用直方图均衡化。
22.1 计算直方图
何为直方图?
直方图是对图像像素的统计分布,它统计了每个像素(0到L-1)的数量。讲到这里不得不引入累计分布函数 CDF,其定义为:
对于连续函数,所有小于等于a 的值,其出现概率的和为:
针对于灰度级从 的图像,则是统计各个灰度级的累计分布概率,灰度值
的累计概率是
之和。
1. 直方图是对数据的集合 统计 ,并将统计结果分布于一系列预定义的 bins 中。
2. 这里的 数据 不仅仅指的是灰度值 (如上一篇您所看到的), 统计数据可能是任何能有效描述图像的特征。
先看一个例子吧。 假设有一个矩阵包含一张图像的信息 (灰度值 0-255):
如果我们按照某种方式去 统计 这些数字,会发生什么情况呢? 既然已知数字的 范围 包含 256 个值, 我们可以将这个范围分割成子区域(称作 bins), 如:
然后再统计掉入每一个 的像素数目。采用这一方法来统计上面的数字矩阵,我们可以得到下图( x轴表示 bin , y轴表示各个bin 中的像素个数)。
以上只是一个说明直方图如何工作以及它的用处的简单示例。直方图可以统计的不仅仅是颜色灰度, 它可以统计任何图像特征 (如 梯度, 方向等等)。
让我们再来搞清楚直方图的一些具体细节:
- dims: 需要统计的特征的数目, 在上例中, dims = 1 因为我们仅仅统计了灰度值(灰度图像)。
- bins: 每个特征空间 子区段 的数目,在上例中, bins = 16
- range: 每个特征空间的取值范围,在上例中, range = [0,255]
怎样去统计两个特征呢? 在这种情况下, 直方图就是3维的了,x轴和y轴分别代表一个特征, z轴是掉入( )组合中的样本数目。 同样的方法适用于更高维的情形 (当然会变得很复杂)。
cv2.calcHist()计算图像阵列的直方图
函数原型:result=cv2.calcHist(iamge,channels,mask,histSize,ranges)
参数:
- image: 源图像。图像格式为unit8或float32,当传入函数时应用中括号[]括起来,如:[img]。
- channels:图像通道。同样用中括号括起来。如果传入的图像时灰度图,其值为[0];如果传入的图像时彩色图,其值为可以为[0]、[1]、[2],分别对应着BGR;
- mask:掩膜图像。统计整幅图像的直方图,其为None;统计整幅图像中的部分图像的直方图,就制作一个mask图像,并使用它
- histSize:BIN的数码,也要用中括号括起来
- ranges:像素值范围,常为[0,256]。
cv2.normalize()归一化数组
原型:cv2.normalize(src[, dst[, alpha[, beta[, norm_type[, dtype[, mask]]]]]]) → dst
参数:
- src-输⼊数组。
- dst-与src⼤⼩相同的输出数组。
- alpha-范数值在范围归⼀化的情况下归⼀化到较低的范围边界。
- beta-上限范围在范围归⼀化的情况下;它不⽤于范数归⼀化。
- momal-规范化类型(见下⾯的细节)。NORM_MINMAX:数组的数值被平移或缩放到⼀个指定的范围,线性归⼀化:
;NORM_INF: 归⼀化数组的(切⽐雪夫距离)L∞范数(绝对值的最⼤值:
;NORM_L1 : 归⼀化数组的(曼哈顿距离)L1-范数(绝对值的和
;NORM_L2: 归⼀化数组的(欧⼏⾥德距离)L2-范数
- dtype——当输出为负时,输出数组具有与SRC相同的类型;否则,它具有与SRC相同的信道数和深度=CVH-MatthAsHead(DyType)。
- mask-掩膜。
示例:绘制RGB图像的三通道直方图
import numpy as np
import matplotlib.pyplot as plt
import cv2
# 1、读入图像
img = cv2.imread('C:/Users/xxx/Downloads/lena.jpg')
fig = plt.figure(figsize=(12.8,4.8))
# 2 plt绘制原图
img = cv2.merge((img[:,:,2],img[:,:,1],img[:,:,0]))
plt.subplot(1,2,1),plt.imshow(img)
plt.title('Original',fontsize=20), plt.xticks([]), plt.yticks([])
# 3 绘制直方图
plt.subplot(1,2,2)
plt.title('R,G,B Hist',fontsize=20)
color =('b','g','r')
for i,c in enumerate(color):
hist = cv2.calcHist([img],[i],None,[256],[0,256])
plt.plot(hist,color=c)
plt.xlim([0,256])
22.2 直方图均衡化
原理
直方图均衡化就是将原始的直方图拉伸,使之均匀分布在全部灰度范围内,从而增强图像的对比度。直方图均衡化的中心思想是把原始图像的的灰度直方图从比较集中的某个区域变成在全部灰度范围内的均匀分布。
在一幅图像中,明亮图像的直方图倾向于灰度级高的一侧,灰暗图像的直方图倾向于灰度级低的一侧,如果一副图像占有全部可能的灰度级并且分布均匀,则这样的图像有高对比度和多变的灰度色调。直方图均衡化这种方法通常用来增加图像的局部对比度。所以这种方法对于图像前景和背景都太亮或者太暗的情况非常有用,使目标区域从背景脱离出来。
直方图均衡化:一般可以用来提升图片的亮度,频数均衡化指的是让频数的分布看起来更加均匀一些。
上图中的左边的图是原始数据, 右边的图是进行函数映射后的灰度值
首先对各个灰度值做频数统计,计算其概率,根据像素的灰度值计算出累积概率,最后将累积概率 * (255-0) 做为函数映射后的灰度值,
这样做的目的,可以使得灰度值之间的间隔更小,即一些频数较大的灰度值补充给了频数较小的灰度值,从而实现了灰度值的均衡化。
相关函数
cv2.equalizeHist() 完整图像直方图均衡化
函数原型:result=cv2.equalizeHist(iamge)
参数:
- image: 输入的图片
cv2.createCLAHE() 自适应图像直方图均衡化
函数原型:cv2.createCLAHE(clipLimit=8.0,titleGridSize=(8,8))
参数:
- clipLimit颜色对比度的阈值
- titleGridSize进行像素均衡化的网格大小,即在多少网格下进行直方图的均衡化操作
颜色直方图均衡化
上述函数都是对灰度图的均衡化,如何对彩色图实现均衡化?
- 使用
cv2.split()
分割BGR
图像 - 并将
cvequalizeHist()
函数应用于每个通道, - 最后,使用
cv2.merge()
合并结果通道
示例1:cv2.equalizeHist() 完整图像直方图均衡化
"""
第一步:读入图片
第二步:使用cv2.equalizeHist(img)均衡化像素
第三步:使用plt.hist 画出均衡化的直方图
第四步:使用plt.imshow 画出均衡化后的图像
这种全局的均衡化也会存在一些问题,由于整体亮度的提升,也会使得
局部图像的细节变得模糊,因为我们需要进行分块的局部均衡化操作
"""
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 【解决plt显示汉字乱码的临时设置】
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 【第一步:读入图片】
img = cv2.imread('C:/Users/xxx/Downloads/lena.jpg')
img = cv2.merge((img[:,:,2],img[:,:,1],img[:,:,0]))
imgR,imgG,imgB =cv2.split(img)
# 【第二步: 使用cv2.equalizeHist实现像素点的均衡化】
retR = cv2.equalizeHist(imgR)
retG = cv2.equalizeHist(imgG)
retB = cv2.equalizeHist(imgB)
ret = cv2.merge([retR,retG,retB])
# 【第三步:使用plt.hist绘制像素直方图和图像显示】
# 【绘制原图】
fig = plt.figure(figsize=(25.6,4.8))
plt.subplot(1,4,1),plt.imshow(img)
plt.title('原图',fontsize=20), plt.xticks([]), plt.yticks([])
# 【绘制直方图均衡化后的图像】
plt.subplot(1,4,3),plt.imshow(ret)
plt.title('直方图均衡化',fontsize=20), plt.xticks([]), plt.yticks([])
# 【绘制原图的R,G,B直方图】
plt.subplot(1,4,2)
plt.title('R,G,B 原图直方图',fontsize=20)
color =('r','g','b')
for i,c in enumerate(color):
hist = cv2.calcHist([img],[i],None,[256],[0,256])
plt.plot(hist,color=c)
plt.xlim([0,256])
# 【绘制直方图均衡化后的图像的R,G,B直方图】
plt.subplot(1,4,4)
plt.title('R,G,B 均衡化后直方图',fontsize=20)
color =('r','g','b')
for i,c in enumerate(color):
hist = cv2.calcHist([ret],[i],None,[256],[0,256])
plt.plot(hist,color=c)
plt.xlim([0,256])
示例2:cv2.createCLAHE()自适应图像直方图均衡化
"""
第一步:使用cv2.createCLAHE(clipLimit=2.0, titleGridSize=(8, 8)) 实例化均衡直方图函数
第二步:使用.apply进行均衡化操作
第三步:进行画图操作
可以看出自适应均衡化没有使得人物脸部的细节消失
"""
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 【读入图片】
img = cv2.imread('C:/Users/xxx/Downloads/lena.jpg')
img = cv2.merge((img[:,:,2],img[:,:,1],img[:,:,0]))
imgR,imgG,imgB =cv2.split(img)
# 【使用自适应直方图均衡化】
# 【第一步:实例化自适应直方图均衡化函数】
clahe = cv2.createCLAHE(clipLimit=2.0,
tileGridSize=(8, 8))
# 【第二步:进行自适应直方图均衡化】
retR = clahe.apply(imgR)
retG = clahe.apply(imgG)
retB = clahe.apply(imgB)
ret = cv2.merge([retR,retG,retB])
# 【第三步:使用plt.hist绘制像素直方图和图像显示】
# 【绘制原图】
fig = plt.figure(figsize=(25.6,4.8))
plt.subplot(1,4,1),plt.imshow(img)
plt.title('原图',fontsize=20), plt.xticks([]), plt.yticks([])
# 【绘制直方图均衡化后的图像】
plt.subplot(1,4,3),plt.imshow(ret)
plt.title('直方图均衡化',fontsize=20), plt.xticks([]), plt.yticks([])
# 【绘制原图的R,G,B直方图】
plt.subplot(1,4,2)
plt.title('R,G,B 原图直方图',fontsize=20)
color =('r','g','b')
for i,c in enumerate(color):
hist = cv2.calcHist([img],[i],None,[256],[0,256])
plt.plot(hist,color=c)
plt.xlim([0,256])
# 【绘制直方图均衡化后的图像的R,G,B直方图】
plt.subplot(1,4,4)
plt.title('R,G,B 均衡化后直方图',fontsize=20)
color =('r','g','b')
for i,c in enumerate(color):
hist = cv2.calcHist([ret],[i],None,[256],[0,256])
plt.plot(hist,color=c)
plt.xlim([0,256])
plt.show()
20.3 直方图对比
图像直方图是反映一个图像像素分布的统计表,其实横坐标代表了图像像素的种类,可以是灰度的,也可以是彩色的。纵坐标代表了每一种颜色值在图像中的像素总数或者占所有像素个数的百分比。
图像是由像素构成,因为反映像素分布的直方图往往可以作为图像一个很重要的特征。在实际工程中,图像直方图在特征提取、图像匹配等方面都有很好的应用。
直方图比较
- 图像相似度比较:如果我们有两张图像,并且这两张图像的直方图一样,或者有极高的相似度,那么在一定程度上,我们可以认为这两幅图是一样的,这就是直方图比较的应用之一。
- 分析图像之间关系:两张图像的直方图反映了该图像像素的分布情况,可以利用图像的直方图,来分析两张图像的关系。
原理
要比较两个直方图( 和
), 首先必须要选择一个衡量直方图相似度的 对比标准
。
OpenCV 函数compareHist执行了了具体的直方图对比的任务。该函数提供了4种对比标准来计算相似度:
1. 相关性(Correlation)比较 ( cv2. HISTCMP_CORREL )
2. 卡方(Chi-Square)比较(cv2.HISTCMP_CHISQR)
3. (Intersection)比较(cv2. HISTCMP_INTERSECT)
4. 巴氏(Bhattacharyya)距离比较( cv2_ HISTCMP_BHATTACHARYYA )
cv2.compareHist()函数
原型:cv2.compareHist(H1, H2, method)
参数:
- H1,H2:分别为要比较图像的直方图
- method:比较方式。cv2.HISTCMP_CORREL——相关性比较,值越大,相关度越高,最大值为1,最小值为0; cv2.HISTCMP_CHISQR——卡方比较,值越小,相关度越高,最大值无上界,最小值0;cv2.HISTCMP_BHATTACHARYYA——巴氏距离比较,值越小,相关度越高,最大值为1,最小值为0;cv2. HISTCMP_INTERSECT
示例
def create_rgb_hist(image):
""""创建 RGB 三通道直方图(直方图矩阵)"""
h, w, c = image.shape
# 创建一个(16*16*16,1)的初始矩阵,作为直方图矩阵
# 16*16*16的意思为三通道每通道有16个bins
rgbhist = np.zeros([16 * 16 * 16, 1], np.float32)
bsize = 256 / 16
for row in range(h):
for col in range(w):
r = image[row, col, 0]
g = image[row, col, 1]
b = image[row, col, 2]
# 人为构建直方图矩阵的索引,该索引是通过每一个像素点的三通道值进行构建
index = int(b / bsize) * 16 * 16 + int(g / bsize) * 16 + int(r / bsize)
# 该处形成的矩阵即为直方图矩阵
rgbhist[int(index), 0] += 1
plt.ylim([0, 10000])
plt.grid(color='r', linestyle='--', linewidth=0.5, alpha=0.3)
return rgbhist
def hist_compare(image1, image2):
"""直方图比较函数"""
# 创建第一幅图的rgb三通道直方图(直方图矩阵)
hist1 = create_rgb_hist(image1)
# 创建第二幅图的rgb三通道直方图(直方图矩阵)
hist2 = create_rgb_hist(image2)
# 进行三种方式的直方图比较
match1 = cv.compareHist(hist1, hist2, cv.HISTCMP_BHATTACHARYYA)
match2 = cv.compareHist(hist1, hist2, cv.HISTCMP_CORREL)
match3 = cv.compareHist(hist1, hist2, cv.HISTCMP_CHISQR)
print("巴氏距离:%s, 相关性:%s, 卡方:%s" %(match1, match2, match3))
img1 = cv.imread("C:/Users/xxx/Downloads/lena.jpg")
img1 = cv2.merge((img[:,:,2],img[:,:,1],img[:,:,0]))
imgR,imgG,imgB =cv2.split(img1)
# 【形成img2(使用自适应直方图均衡化)】
# 【实例化自适应直方图均衡化函数】
clahe = cv2.createCLAHE(clipLimit=2.0,
tileGridSize=(8, 8))
# 【进行自适应直方图均衡化】
retR = clahe.apply(imgR)
retG = clahe.apply(imgG)
retB = clahe.apply(imgB)
img2 = cv2.merge([retR,retG,retB])
# 【使用plt.hist绘制像素直方图和图像显示】
# 【绘制原图】
fig = plt.figure(figsize=(25.6,4.8))
plt.subplot(1,4,1),plt.imshow(img1)
plt.title('原图',fontsize=20), plt.xticks([]), plt.yticks([])
# 【绘制直方图均衡化后的图像】
plt.subplot(1,4,3),plt.imshow(img2)
plt.title('直方图均衡化',fontsize=20), plt.xticks([]), plt.yticks([])
# 【绘制原图的R,G,B直方图】
plt.subplot(1,4,2)
plt.title('R,G,B 原图直方图',fontsize=20)
plt.plot(create_rgb_hist(img1))
# 【绘制直方图均衡化后的图像的R,G,B直方图】
plt.subplot(1,4,4)
plt.title('R,G,B 均衡化后的直方图',fontsize=20)
plt.plot(create_rgb_hist(img2))
# 【比较直方图】
hist_compare(img1, img2)
plt.show()
20.4 反向投影
什么是反向投影?
- 反向投影是一种记录给定图像中的像素点如何适应直方图模型像素分布的方式。
- 简单的讲, 所谓反向投影就是首先计算某一特征的直方图模型,然后使用模型去寻找图像中存在的该特征。
- 例如, 你有一个肤色直方图 ( Hue-Saturation 直方图 ),你可以用它来寻找图像中的肤色区域:
反向投影的工作原理
我们使用肤色直方图为例来解释反向投影的工作原理:
假设你已经通过下图得到一个肤色直方图(Hue-Saturation), 旁边的直方图就是 模型直方图 ( 代表手掌的皮肤色调).你可以通过掩码操作来抓取手掌所在区域的直方图:
下图是另一张手掌图(测试图像) 以及对应的整张图像的直方图:
我们要做的就是使用 模型直方图 (代表手掌的皮肤色调) 来检测测试图像中的皮肤区域。以下是检测的步骤
1. 对测试图像中的每个像素p(i,j) ,获取色调数据并找到该色调(hi,j,si,j)
在直方图中的bin的位置。
2. 查询 模型直方图 中对应的bin - (hi,j,si,j) - 并读取该bin的数值。
3. 将此数值储存在新的图像中(BackProjection)。 你也可以先归一化 模型直方图 ,这样测试图像的输出就可以在屏幕显示了。
4. 通过对测试图像中的每个像素采用以上步骤, 我们得到了下面的 BackProjection 结果图:
5. 使用统计学的语言, BackProjection 中储存的数值代表了测试图像中该像素属于皮肤区域的 概率 。比如以上图为例, 亮起的区域是皮肤区域的概率更大(事实确实如此),而更暗的区域则表示更低的概率(注意手掌内部和边缘的阴影影响了检测的精度)。
反向投影查找原理
查找的方式就是不断的在输入图像中切割跟模板图像大小一致的图像块,并用直方图对比的方式与模板图像进行比较。
假设我们有一张100x100的输入图像,有一张10x10的模板图像,查找的过程是这样的:
(1)从输入图像的左上角(0,0)开始,切割一块(0,0)至(10,10)的临时图像;
(2)生成临时图像的直方图;
(3)用临时图像的直方图和模板图像的直方图对比,对比结果记为c;
(4)直方图对比结果c,就是结果图像(0,0)处的像素值;
(5)切割输入图像从(0,1)至(10,11)的临时图像,对比直方图,并记录到结果图像;
(6)重复(1)~(5)步直到输入图像的右下角。
反向投影的应用
用于图像分割或查找图像中感兴趣的对象。它的输出结果是与输入图像大小相同的图像,每一个像素值代表了输入图像上对应点属于目标对象的概率。换言之,输出图像中像素值越高的点越可能代表想要查找的目标。直方图投影经常与camshift(追踪算法)算法一起使用。
相关函数
cv2.calcBackProject()计算反向投影
原型:cv2.calcBackProject(images, channels, hist, ranges, scale[, dst=None])
参数:
- images:输入图像,是一个图像集合,可以是包含多通道彩色图像的list或tuple,也可以是多个灰度图组成的list或者tuple;list或tuple形式的输入
- channels:输入通道。根据images确定,指明要用images里的哪个通道号,根据images的形式确定;list或tuple形式的输入;
- hist:输入直方图。模板的直方图,这个直方图的通道要与第二个参数channels是一样的
- ranges:图像元素取值的范围;包含2个元素的list或tuple;均匀bin, ranges只需要最小最大边界;H 范围为[0,180],S 范围[0,256]
- scale:输出结果的缩放因子,默认为1
返回值:
- dst:目标图像,单通道,和images[0]同样的尺寸和depth。包含了以每个输入图像像素点为起点的直方图对比结果;可以将其看做是一个二维的浮点型数组、二维矩阵,或者单通道的浮点型图像。
cv2.mixChannels()组合图像的不同通道
示例
"""
反向投影
用于图像分割或查找图像中感兴趣的对象
输入图像为两张三通道图片
"""
import cv2
import numpy as np
# 【BGR转HSV图像函数】
def bgr2hsv(src):
"""
将原图由BGR色彩空间转换至HSV色彩空间
:param src:三通道图片
:return:返回转换后图片
"""
dst = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)
return dst
# 【获取图像特征函数】
def getCharacterPic(roi, target):
"""
分离特征图片
:param roi: 模板,三通道HSV图片
:param target: 待检测图片(目标图片),三通道HSV图片
:return: 无
"""
# 【显示图像】
cv2.imshow('target', target)
cv2.imshow('roi', roi)
# 【将BGR图像转换为HSV图像】
target = cv2.bilateralFilter(target, 13, 70, 50) # 双边滤波 - 保边去噪
roiHsv = bgr2hsv(roi)
targetHsv = bgr2hsv(target)
# 【计算模板roiHsv的直方图】
roiHist = cv2.calcHist([roiHsv], [0, 1], None, [180, 256], [0, 180, 0, 256]) # H 范围为[0,180],S 范围[0,256]
cv2.normalize(roiHist, roiHist, 0, 255, cv2.NORM_MINMAX) # 将roihist归一化至[0, 255]范围
# 【利用反向投影cv2.calcBackProject】
# dst包含了以每个输入图像像素点为起点的直方图比对结果
# 可以将其看成是一个二维的浮点型数组、二维矩阵,或者单通道的浮点型图片
dst = cv2.calcBackProject([targetHsv], [0, 1], roiHist, [0, 180, 0, 256], 1) # 将模板的直方图投影到原图的hsv空间得到dst
cv2.imshow("calcBackProject", dst) # 显示图片
# 【Now convolute with circular disc】
disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) # 构造一个5*5椭圆形的kernel
cv2.filter2D(dst, -1, disc, dst) # -1代表卷积padding,连接分散的点
cv2.imshow("dst after conv", dst) # 显示图片
# 【threshold and binary AND】
ret, thresh = cv2.threshold(dst, 50, 255, 0) # 阈值需要自己调整
# thresh是单通道的浮点型图片,原始图是三通道的,所以需要合并thresh为三通道图方便后续操作
thresh = cv2.merge((thresh, thresh, thresh))
cv2.imshow("thresh", thresh)
res = cv2.bitwise_and(target, thresh)
cv2.imshow('result', res)
cv2.imwrite('res.jpg', res)
cv2.waitKey(0) # 等待用户输入,按任意键即可
cv2.destroyAllWindows()
if __name__ == '__main__':
roi = cv2.imread("C:/Users/xxx/Downloads/roi.png")
target = cv2.imread("C:/Users/xxx/Downloads/target.png") # 原图 - 模板图片从其抠出
getCharacterPic(roi, target)