赞
踩
img = cv2.imread(‘home.jpg’,0)
#别忘了中括号 [img],[0],None,[256],[0,256],只有 mask 没有中括号
hist = cv2.calcHist([img],[0],None,[256],[0,256])
hist 是一个 256x1 的数组,每一个值代表了与次灰度值对应的像素点数目。
#img.ravel() 将图像转成一维数组,这里没有中括号。
hist,bins = np.histogram(img.ravel(),256,[0,256])
Numpy 还 有 一 个 函 数 np.bincount(), 它 的 运 行 速 度 是
np.histgram 的 十 倍。 所 以 对 于 一 维 直 方 图, 我 们 最 好 使 用 这 个函 >数。 使 用 np.bincount 时 别 忘 了 设 置 minlength=256。
hist=np.bincount(img.ravel(), minlength=256)
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('./resource/opencv/image/leuvenA.jpg', cv2.IMREAD_GRAYSCALE)
plt.hist(img.ravel(), 256, [0, 256])
plt.show()
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('./resource/opencv/image/leuvenA.jpg', cv2.IMREAD_COLOR)
color = ('b', 'g', 'r')
for i, col in enumerate(color):
histr = cv2.calcHist([img], [i], None, [256], [0, 256])
plt.plot(histr, color = col)
plt.xlim([0, 256])
plt.show()
要统计图像某个局部区域的直方图只需要构建一副掩模图像。将要统计的部分设置成白色,其余部分为黑色,就构成了一副掩模图像。然后把这个掩模图像传给函数就可以了。
import numpy as np import cv2 from matplotlib import pyplot as plt img = cv2.imread('./resource/opencv/image/leuvenA.jpg', cv2.IMREAD_GRAYSCALE) h,w = img.shape print(h,w) # create mask mask = np.zeros(img.shape[:2], np.uint8) mask[100:400, 100:500] = 255 masked_img = cv2.bitwise_and(img, img, mask = mask) hist_full = cv2.calcHist([img], [0], None, [256], [0, 256]) hist_mask = cv2.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, 'r'), plt.plot(hist_mask, 'b') plt.xlim([0, 256]) plt.show()
红色线是整幅图的直方图,蓝色线是掩膜之后的直方图:
如果一副图像中的大多是像素点的像素值都集中在一个像素值范围之内会怎样呢?例如,如果一幅图片整体很亮,那所有的像素值应该都会很高。但是一副高质量的图像的像素值分布应该很广泛。所以你应该把它的直方图做一个横向拉伸(如下图),这就是直方图均衡化要做的事情。通常情况下这种操作会改善图像的对比度。
直方图均衡化处理可以提高图像的清晰度
import numpy as np import cv2 from matplotlib import pyplot as plt img = cv2.imread('./resource/opencv/image/leuvenA.jpg', cv2.IMREAD_GRAYSCALE) # 1.使用Numpy统计原图直方图 # flatten() 将数组变成一维 hist, bins = np.histogram(img.flatten(), 256, [0, 256]) # 计算累积分布图 cdf = hist.cumsum() cdf_normalized = cdf * hist.max() / cdf.max() # 2.使用Numpy直方图均衡化处理 # 构建 Numpy 掩模数组, cdf 为原数组,当数组元素为 0 时,掩盖(计算时被忽略)。 cdf_m = np.ma.masked_equal(cdf, 0) cdf_m = (cdf_m - cdf_m.min()) *255/(cdf_m.max() - cdf_m.min()) # 对被掩盖的元素赋值,这里赋值为 0 cdf = np.ma.filled(cdf_m, 0).astype('uint8') # 现在就获得了一个表,我们可以通过查表得知与输入像素对应的输出像素的值。我们只需要把这种变换应用到图像上就可以了 img2 = cdf[img] # 3. 绘制原图直方图 plt.subplot(221), plt.imshow(cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)) plt.subplot(222) 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') # 4.绘制均衡化直方图 plt.subplot(223), plt.imshow(cv2.cvtColor(img2, cv2.COLOR_GRAY2RGB)) plt.subplot(224) plt.plot(cdf_m, color='g') plt.hist(img2.flatten(), 256, [0, 256], color='r') plt.xlim([0, 256]) plt.legend(('cdf', 'histogram'), loc='upper left') plt.show()
OpenCV 中的直方图均衡化函数为 cv2.equalizeHist()。这个函数的输入图片仅仅是一副灰度图像,输出结果是直方图均衡化之后的图像。
import numpy as np
import cv2
img = cv2.imread('./resource/opencv/image/leuvenA.jpg', cv2.IMREAD_GRAYSCALE)
# 直方图均衡化
equ = cv2.equalizeHist(img)
# 图像拼接,左边原图,右边直方图均衡化之后的图像
res = np.hstack((img, equ))
cv2.imshow('img', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
左边原图,右边直方图均衡化之后的图像
文章上边做的直方图均衡化会改变整个图像的对比度,但是在很多情况下,这样做的效果并不好。例如,下图分别是输入图像和进行直方图均衡化之后的输出图像。的确在进行完直方图均衡化之后,图片背景的对比度被改变了。但是你再
对比一下两幅图像中雕像的面图,由于太亮我们丢失了很多信息。
为了解决这个问题,我们需要使用自适应的直方图均衡化。这种情况下,整幅图像会被分成很多小块,这些小块被称为“tiles”(在 OpenCV 中 tiles 的大小默认是 8x8),然后再对每一个小块分别进行直方图均衡化(跟前面类似)。所以在每一个的区域中,直方图会集中在某一个小的区域中(除非有噪声干扰)。如果有噪声的话,噪声会被放大。为了避免这种情况的出现要使用对比度限制。对于每个小块来说,如果直方图中的 bin 超过对比度的上限的话,就把其中的像素点均匀分散到其他 bins 中,然后在进行直方图均衡化。最后,为了去除每一个小块之间“人造的”(由于算法造成)边界,再使用双线性差值,对小块进行缝合。
import numpy as np import cv2 from matplotlib import pyplot as plt img = cv2.imread('./resource/opencv/image/clahe_2.jpg', cv2.IMREAD_GRAYSCALE) # 均衡化处理 equ = cv2.equalizeHist(img) # 自适应均衡化处理 clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) cl1 = clahe.apply(img) # 绘制图像 plt.subplot(131), plt.imshow(cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)), plt.title('img') plt.subplot(132), plt.imshow(cv2.cvtColor(equ, cv2.COLOR_GRAY2RGB)), plt.title('equ') plt.subplot(133), plt.imshow(cv2.cvtColor(cl1, cv2.COLOR_GRAY2RGB)), plt.title('cl1') plt.show()
在前面的文章介绍了如何绘制一维直方图,之所以称为一维,是因为我们只考虑了图像的一个特征:灰度值。但是在 2D 直方图中我们就要考虑两个图像特征。对于彩色图像的直方图通常情况下我们需要考虑每个的颜色( Hue)和饱和度( Saturation)。根据这两个特征绘制 2D 直方图。
使用函数 cv2.calcHist() 来计算直方图既简单又方便。如果要绘制颜色直方图的话,我们首先需要将图像的颜色空间从 BGR 转换到 HSV。(记住,计算一维直方图,要从 BGR 转换到 HSV)。计算 2D 直方图,函数的参数要做如下修改:
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('./resource/opencv/image/home.jpg', cv2.IMREAD_COLOR)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
hist = cv2.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256])
plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB), 'gray')
plt.subplot(122), plt.imshow(hist, interpolation = 'nearest')
plt.show()
Numpy 同样提供了绘制 2D 直方图的函数:
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('./resource/opencv/image/home.jpg', cv2.IMREAD_COLOR)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)#分离通道
hist, xbins, ybins = np.histogram2d(h.ravel(),s.ravel(),[180,256],[[0,180],[0,256]])
plt.subplot(131), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.subplot(132), plt.imshow(hist)
plt.show()
首先,我们要创建两幅颜色直方图,目标图像的直方图( ‘M’),(待搜索)输入图像的直方图( ‘I’)。
import numpy as np import cv2 from matplotlib import pyplot as plt roi = cv2.imread('./resource/opencv/image/target.jpg', cv2.IMREAD_COLOR) hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) target = cv2.imread('./resource/opencv/image/messi5.jpg', cv2.IMREAD_COLOR) hsvt = cv2.cvtColor(target, cv2.COLOR_BGR2HSV) M = cv2.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256]) I = cv2.calcHist([hsvt], [0, 1], None, [180, 256], [0, 180, 0, 256]) # 计算比值: R = M/I 。反向投影 R,也就是根据 R 这个”调色板“创建一 # 副新的图像,其中的每一个像素代表这个点就是目标的概率。 # 例如 B (x; y) = R [h (x; y) ; s (x; y)], # 其中 h 为点( x, y)处的 hue 值, s 为点( x, y)处的 # saturation 值。最后加入再一个条件 B (x; y) = min [B (x; y) ; 1] R = M/I h, s, v = cv2.split(hsvt) B = R[h.ravel(), s.ravel()] B = np.minimum(B, 1) B = B.reshape(hsvt.shape[:2]) # 现在使用一个圆盘算子做卷积, B = D × B,其中 D 为卷积核 disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)) B = cv2.filter2D(B, -1, disc) B = np.uint8(B) # 归一化处理 cv2.normalize(B,B,0,255,cv2.NORM_MINMAX) ret, thresh = cv2.threshold(B, 50, 255, 0) # 别忘了是三通道图像,因此这里使用 merge 变成 3 通道 thresh = cv2.merge((thresh,thresh,thresh)) res = cv2.bitwise_and(target, thresh) res = np.hstack((target, thresh, res)) cv2.imshow('img', res) cv2.waitKey(0) cv2.destroyAllWindows()
程序运行结果:
target.jpg:是另外一副图草地区域的一块截图
messi5.jpg:
OpenCV 提供的函数 cv2.calcBackProject() 可以用来做直方图反向投影。它的参数与函数 cv2.calcHist 的参数基本相同。其中的一个参数是我们要查找目标的直方图。同样再使用目标的直方图做反向投影之前我们应该先对其做归一化处理。返回的结果是一个概率图像,我们再使用一个圆盘形卷积核对其做卷操作,最后使用阈值进行二值化。
import cv2 import numpy as np roi = cv2.imread('./resource/opencv/image/target.jpg', cv2.IMREAD_COLOR) hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) target = cv2.imread('./resource/opencv/image/messi5.jpg', cv2.IMREAD_COLOR) hsvt = cv2.cvtColor(target, cv2.COLOR_BGR2HSV) # calculating object histogram roihist = cv2.calcHist([hsv], [0,1], None, [180, 256], [0, 180, 0, 256]) # normalize histogram and apply backprojection # 归一化:原始图像,结果图像,映射到结果图像中的最小值,最大值,归一化类型 #cv2.NORM_MINMAX 对数组的所有值进行转化,使它们线性映射到最小值和最大值之间 # 归一化之后的直方图便于显示,归一化之后就成了 0 到 255 之间的数了 cv2.normalize(roihist, roihist, 0, 255, cv2.NORM_MINMAX) dst = cv2.calcBackProject([hsvt], [0, 1], roihist, [0, 180, 0, 256], 1) # Now convolute with circular disc # 此处卷积可以把分散的点连在一起 disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)) dst = cv2.filter2D(dst, -1, disc) # threshold and binary AND ret, thresh = cv2.threshold(dst, 50, 255, 0) # 别忘了是三通道图像,因此这里使用 merge 变成 3 通道 thresh = cv2.merge((thresh,thresh,thresh)) res = cv2.bitwise_and(target, thresh) res = np.hstack((target, thresh, res)) cv2.imshow('img', res) cv2.waitKey(0) cv2.destroyAllWindows()
程序运行结果:
target.jpg:是另外一副图草地区域的一块截图
messi5.jpg:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。