OpenCV中的图像处理 —— 直方图大章(查找、绘制及分析+均衡化+二维直方图+直方图反投影)

举报
ErrorError! 发表于 2022/04/27 16:52:33 2022/04/27
【摘要】 我们可以将直方图视为图形或绘图,从而可以总体了解图像的强度分布,它是在X轴具有像素值(不一定是0~255),在Y轴上具有图像中相应像素数的图,通过直方图我们可以直观地了解图像的对比度、明暗程度和强度分布等信息

OpenCV中的图像处理 —— 直方图大章(查找、绘制及分析+均衡化+二维直方图+直方图反投影)

关于直方图在计算机视觉领域的地位、基本介绍和应用范围在我的这篇文章中曾有提及,感兴趣的小伙伴可以去康康:进入Computer Vision世界 —— 数字图像 + 插值算法 + 直方图 + 卷积&滤波

我们可以将直方图视为图形或绘图,从而可以总体了解图像的强度分布,它是在X轴具有像素值(不一定是0~255),在Y轴上具有图像中相应像素数的图,通过直方图我们可以直观地了解图像的对比度、明暗程度和强度分布等信息

1. 直方图的查找、绘制及分析

1.1 寻找直方图

关于直方图的查找,不管是OpenCV库还是Numpy都提供了相应的方法

OpenCV和Numpy中的直方图计算

OpenCV提供了一个cv.calcHist()函数用来查找直方图,其中一般需要传入5个参数:cv.calcHist(images,channels,mask,histSize,ranges [)

  1. 第一个参数img是uint8或float3类型的源图像
  2. 第二个参数指的是计算直方图的通道索引,如果输入的是灰度图,我们另其为[0]即可,对于彩色图像[0/1/2]分别表示计算蓝、绿和红色通道的直方图
  3. 第三个参数是图像掩码,如果我们需要的是完整图像的直方图,则将其定义为None即可,如果我们需要查找的是特定区域内的直方图,我们就必须为此创建一个掩码图像并将其作为掩码传入函数
  4. 第四个参数histSize指的是直方图中每个像素值的像素类型个数,对于全尺寸传入[256]即可
  5. 第五个参数ranges,顾名思义就是像素值范围,全范围即[0,256]

Numpy提供了一个np.histogram()函数用于计算直方图

hist,bins = np.histogram(img.ravel(),256,[0,256])

hist与我们之前计算的相同。但是bin将具有257个元素,因为Numpy计算出bin的范围为 0-0.99 、1-1.99 、 2-2.99 等。因此最终范围为 255-255.99 。为了表示这一点,他们还在最后添加了256。但是我们不需要256,最多255就足够了

除此之外Numpy还提供了一个更快的函数np.bincount(),它比np.histogram()快了十倍,对于一维直方图这种方法无疑是更好的选择,在使用的时候不要忘记在np.bincount中设置minlength = 256

hist = np.bincount(img.ravel(),minlength = 256)

说起运算速度我们不得不说OpenCV函数比np.histogram()大约快了40倍,因此我们要尽可能地使用OpenCV内置的函数

1.2 绘制直方图

在前一块儿我们说了用OpenCV的calcHist()函数和Numpy的np.bincount()或np.histogram()函数查找直方图,在查找完毕之后我们还要把它绘制出来,使我们能更清晰地看到图像的信息,关于直方图的绘制有两种方法供我们使用,分别是matplotlib库的绘图功能和OpenCV的绘图功能,使用OpenCV提供的cv.line()函数和cv.ployline()可以帮助我们绘制直方图,但是这种方法太过于繁琐了,所以不建议使用

Matplotlib绘制直方图

Matplotlib带有直方图绘图功能: matplotlib.pyplot.hist() 它直接找到直方图并将其绘制,我们甚至无需使用calcHist()或np.histogram()函数来查找直方图便可直接绘制

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('home.jpg',0)
plt.hist(img.ravel(),256,[0,256]); plt.show()

对于BGR图像,我们可通过Matpllotlib的法线图来绘制

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('home.jpg')
color = ('b','g','r')
for i,col in enumerate(color):
     histr = cv.calcHist([img],[i],None,[256],[0,256])
     plt.plot(histr,color = col)
     plt.xlim([0,256])
plt.show()

在这里插入图片描述

老规矩为了更深刻地理解,我们先来分析一下代码,1-4行就不用说了,不明白的同学可以去我的主页翻翻之前的内容,第5行是定义了一组枚举类型的数据,分别表示三个颜色通道,然后用了一个for循环来完成直方图的查找和绘制

第7行我们见到了cv.calchist()函数的返回值histr,它表示的是一个256*1的数组,每一个元素保存的是相应像素值的像素点个数,第8行的plt.plot()函数就值得一说了:

它的原型是这样的plt.plot(x, y, format_string, **kwargs) ,它用于对绘制的图像作出一些修改,x和y分别表示轴上的数据,通常传入列表或数组,format_string表示控制曲线的格式字符串,可选,由颜色字符、风格字符和标记字符组成,最后一个就是一些公共属性了,比如颜色、粗细和线条风格等

掩码的应用

上面我们无论是查找直方图还是绘制直方图,针对的都是完整的图像,但是如果我们想要特定区域内的一块儿图像的直方图呢?这个时候就会用到掩码,我们需要先创建一个掩码图像,我们要找的直方图为白色,其他地方为黑色

img = cv.imread('home.jpg',0)
# create a mask
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:400] = 255
masked_img = cv.bitwise_and(img,img,mask = mask)
# 计算掩码区域和非掩码区域的直方图
# 检查作为掩码的第三个参数
hist_full = cv.calcHist([img],[0],None,[256],[0,256])
hist_mask = cv.calcHist([img],[0],mask,[256],[0,256])
plt.subplot(221), plt.imshow(img, 'gray')
plt.subplot(222), plt.imshow(mask,'gray')
plt.subplot(223), plt.imshow(masked_img, 'gray')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask)
plt.xlim([0,256])
plt.show()

在这里插入图片描述

2. 直方图均衡

2.1 了解均衡化

有这样一副图像,它的像素值仅局限于某个特定区域的范围,较亮的图像将把所有像素限制在高值上,但是一幅好的图像会有来自图像所有区域的像素,因此您需要将这个直方图拉伸到两端,这就是直方图均衡化的作用(简单来说),这通常会提高图像的对比度,也就是说我们需要一个转换函数,将亮的区域的输入像素映射到整个区域的输出像素

即使图像是一个较暗的图像,而不是我们使用的一个较亮的图像,经过均衡后,我们将得到几乎相同的图像,因此,这是作为一个“参考工具”,使所有的图像具有相同的照明条件

在人脸识别中,在对人脸数据进行训练之前,对人脸图像进行直方图均衡化处理,使其具有相同的光照条件

2.2 OpenCV中的直方图均衡

OpenCV提供了了cv.equalizeHist()函数,它的输入只是灰度图像,输出是我们的直方图均衡图像

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread(r'E:\image\test07.png', 0)
equ = cv.equalizeHist(img)
res = np.hstack((img, equ))  # stacking images side-by-side
cv.imshow('res.png', res)
cv.waitKey(0)
cv.destroyWindow()

在这里插入图片描述

这个例子就很明显啦,原图像由于天空亮度太高,我们甚至是看不到云的,但是在使用直方图均衡化后,图像的对比度明显增强了,我们也可以很轻易的看到更多的图像信息了

当图像的直方图限制在特定区域时,直方图均衡化效果很好。在直方图覆盖较大区域(即同时存在亮像素和暗像素)的强度变化较大的地方,效果并不理想

2.3 CLAHE对比度受限的自适应均衡

刚才我们演示的直方图均衡化是针对的是整个图像,然而在明暗差距较大时图像处理效果并不好,也就是说有时候输入图像及其在全局直方图均衡化的结果会导致亮度过亮或暗度过暗而导致一些图像信息的丢失

为了解决这个问题,我们会用到自适应直方图均衡,在这种情况下图像被分成称为为“tiles”的小块(在OpenCV中,tileSize默认为 8x8 )。然后,像往常一样对这些块中的每一个进行直方图均衡,因此在较小的区域中,直方图将限制在一个较小的区域中(除非存在噪声),如果有噪声的存在,直方图均衡化操作会把噪声放大,为了限制这种错误我们使用对比度限制,如果任何直方图bin超出指定的对比度限制(在OpenCV中默认为40),则在应用直方图均衡之前,将这些像素裁剪并均匀地分布到其他bin,均衡后,要消除图块边界中的伪影,请应用双线性插值

为什么我们用全局均衡化就不行,而分割成小块就可以了呢?聪明的同学已经猜到了,说明在执行均衡化处理的时候,也就是把原图像的像素值映射到新图像的时候,肯定是有操作涉及到全部像素的处理,这个时候运算可能就顾及不到所有像素的感受,因此才会出现一些图像信息丢失的结果,关于直方图均衡化和双线性插值的原理可以看看我的这篇文章:进入Computer Vision世界 —— 数字图像 + 插值算法 + 直方图 + 卷积&滤波

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread(r'E:\image\test15.png', 0)
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
cl1 = clahe.apply(img)
img2 = cv.equalizeHist(img)
plt.subplot(1, 3, 1), plt.imshow(img, cmap='gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks()
plt.subplot(1, 3, 2), plt.imshow(img2, cmap='gray')
plt.title('Equalize Hist'), plt.xticks([]), plt.yticks()
plt.subplot(1, 3, 3), plt.imshow(cl1, cmap='gray')
plt.title('CLAHE Image'), plt.xticks([]), plt.yticks()
plt.show()

在这里插入图片描述

这样效果就很明显啦,使用全局直方图均衡化会导致第二张图的情况,人脸太亮了都看不清了,但是使用自适应均衡化的话呈现了第三张图的效果,嘎嘎猛

3. 二维直方图

上面两部分我们学习的是一维直方图,为什么呢?因为我们只涉及到了一个特征鸭,那就是像素的灰度强度值,但是在二维直方图中我们需要考虑两个特征,通过它用于查找颜色直方图,这两个特征而分别是每个像素的色相和饱和度值

3.1 OpenCV中的二维直方图

嘿嘿,是不是正迷惑HSV呢?玩儿了太久的BGR和RGB都快把HSV颜色空间忘了吧,我们所需要的两个特征色相和饱和度就正是其中的两个通道,如果想复习一下HSV那就来看看这篇文章吧:HSVy颜色空间

使用OpenCV查找二维直方图依旧使用cv.calcHist()函数,只是需要改变其中传入的参数而已,对于颜色直方图当然要把BGR转换为HSV咯

第一个参数是HSV颜色通道的图像,第二个参数是通道channel,此时我们需要传入[0, 1],因为我们需要同时处理H和S平面,第三个参数是掩码就不多说了,第四个参数bins是某像素值的像素类型个数,此时传入[180, 256],第五个参数range传入[0, 180, 0, 256]

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread(r'E:\image\test15.png', 0)
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
hist = cv.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256])

3.2 Numpy中的二维直方图

Numpy还为此提供了一个特定的函数:np.histogram2d(),第一个参数是H平面,第二个是S平面,第三个是每个箱子的数量,第四个是它们的范围

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]])

3.3 二维直方图的绘制

使用matplotlib绘制

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()

在这里插入图片描述

4. 直方图反投影

4.1 了解直方图反投影

先来看看直方图反投影是个啥,它用于图像分割或在图像中查找感兴趣的对象,也就是是它创建的图像大小与输入图像一致,但只有一个通道,其中每个像素对应于该像素属于我们物体的概率

我们创建一个图像的直方图,其中包含我们感兴趣的对象,对象应尽可能填充图像以获得更好的效果,而且颜色直方图比灰度直方图更可取,因为对象的颜色对比灰度强度是定义对象的好方法

然后,我们将该直方图“反投影”到需要找到对象的测试图像上,换句话说,我们计算出属于背景的每个像素的概率并将其显示出来,在适当的阈值下产生的输出使我们仅获得背景

4.2 OpenCV中的反投影

OpenCV提供了一个内建的函数cv.calcBackProject(),它的参数几乎与cv.calchist()函数相同,它的一个参数是直方图,也就是物体的直方图,所以我们要查找直方图,另外,在传递给backproject函数之前,应该对对象直方图进行归一化。它返回概率图像。然后我们用圆盘内核对图像进行卷积并应用阈值

import numpy as np
import cv2 as cv
roi = cv.imread('rose_red.png')
hsv = cv.cvtColor(roi,cv.COLOR_BGR2HSV)
target = cv.imread('rose.png')
hsvt = cv.cvtColor(target,cv.COLOR_BGR2HSV)
# 计算对象的直方图
roihist = cv.calcHist([hsv],[0, 1], None, [180, 256], [0, 180, 0, 256] )
# 直方图归一化并利用反传算法
cv.normalize(roihist,roihist,0,255,cv.NORM_MINMAX)
dst = cv.calcBackProject([hsvt],[0,1],roihist,[0,180,0,256],1)
# 用圆盘进行卷积
disc = cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5))
cv.filter2D(dst,-1,disc,dst)
# 应用阈值作与操作
ret,thresh = cv.threshold(dst,50,255,0)
thresh = cv.merge((thresh,thresh,thresh))
res = cv.bitwise_and(target,thresh)
res = np.vstack((target,thresh,res))
cv.imwrite('res.jpg',res)

在这里插入图片描述


版权声明:以上学习内容及配图来自或参考自——八斗人工智能 王小天
如果文章对您有所帮助,记得一键三连支持一下哦~

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。