Opencv-直方图&直方图均衡化&直方图反射
主要简单介opencv直方图相关操作,参考资料来源《数字图像处理》和opencv官方文档以及wiki。
import cv2
import os
import numpy as np
from matplotlib import pyplot as plt
img=cv2.imread(r"D:\PersonSpace\digital_image_process\data_result\histogram\home.jpg",0)
#opencv计算直方图
hist=cv2.calcHist([img],channels=[0],mask=None,histSize=[256],ranges=[0,256])
# print(hist)
hist_bin16=cv2.calcHist([img],channels=[0],mask=None,histSize=[16],ranges=[0,256])
# for index,i in enumerate(hist):
# if i[0]!=0:
# print(f'{index}:{i[0]}')
#numpy计算一维直方图
np_hist,np_bins=np.histogram(img.flatten(),bins=range(255),range=[0,256])
# print(np_hist)
# print(np_bins)
# print(np.cumsum(np_hist))
plt.subplot(221)
plt.imshow(img,cmap='gray')
plt.subplot(222)
plt.plot(hist)
# print(hist)
plt.xlim([0,256])
plt.subplot(223)
# plt.plot(hist_bin16)
#使用matlib直接计算直方图
plt.hist(img.flatten(),bins=256,range=(0,256))
plt.subplot(224)
plt.plot(np_hist)
plt.show()
#自己实现梯度直方图
temp=img=cv2.imread(r"D:\PersonSpace\digital_image_process\data_result\histogram\home.jpg",0).flatten()
bins=256
#统计图中每个像素出现的数目
pixes_count=np.zeros((256))
for i in temp:
pixes_count[int(i)]+=1
#根据bins值来对像素取值范围划分区间
interval=256//bins if 256%bins==0 else 256//bins+1
hist=[np.sum(pixes_count[i*interval:(i+1)*interval])/len(temp) for i in range(bins) ]
plt.subplot(221)
plt.plot(hist)
plt.xlim([0,256])
plt.show()
划分不同色彩通道的直方图
temp=img=cv2.imread(r"D:\PersonSpace\digital_image_process\data_result\histogram\home.jpg")
print(temp.shape)
plt.subplot(211)
plt.imshow(temp[:,:,::-1])
plt.subplot(212)
#使用plt
color=['b','g','r']
for i,col in enumerate(color):
hist=cv2.calcHist([img],channels=[i],mask=None,histSize=[256],ranges=[0,256])
plt.plot(hist,color=col)
plt.xlim([0,256])
plt.show()
(384, 512, 3)
应用掩码来求解一部分区域的直方图
temp=img=cv2.imread(r"D:\PersonSpace\digital_image_process\data_result\histogram\home.jpg")
# temp=cv2.cvtColor(temp,cv2.COLOR_BGR2RGB)
temp=cv2.cvtColor(temp,cv2.COLOR_BGR2GRAY)
mask=np.zeros(temp.shape[:2],np.uint8)
mask[100:300,100:400]=1
# mask=mask[:,:,None]
mask_img=mask*temp
hist_full=cv2.calcHist([temp],[0],None,[256],[0,256])
hist_mask=cv2.calcHist([temp],[0],mask,[256],[0,256])
plt.subplot(221)
plt.imshow(temp,'gray')
plt.subplot(222)
plt.imshow(mask,'gray')
plt.subplot(223)
plt.imshow(mask_img,'gray')
plt.subplot(224)
plt.plot(hist_full,color='b')
plt.plot(hist_mask,color='g')
plt.xlim([0,256])
plt.show()
直方图均衡化
直方图均衡化可以提高图像的对比度,其相当于将直方图分布从可以偏向某一区间的情况转换成均衡分布的情况
直方图均衡就是让图像的像素个数多的灰度级拉的更宽,对像素个数少的灰度级进行压缩,从而达到提高图像的对比度的目的。
其也可以作为一种中间态来实现从一种转换到另一种分布,具体内容可以看《数字图像处理》
img=cv2.imread('D:\PersonSpace\digital_image_process\data_result\histogram\hist_equal.png',0)
#手动实现直方图均衡化
#1.获取直方图,这里直接使用opencv的内置函数
hist=cv2.calcHist([img],[0],None,[256],[0,256])
#获取均衡化之后的结果
hist_rate=hist/img.size
hist_accumlte_rate=np.cumsum(hist_rate)
hist_color=hist_accumlte_rate*255
temp=np.array([[int(hist_color[j])for j in i]for i in img],dtype=np.uint8)
hist_temp=cv2.calcHist([temp],[0],None,[256],[0,256])
#使用cv2自带均衡化功能
temp2=cv2.equalizeHist(img)
hist_temp2=cv2.calcHist([temp2],[0],None,[256],[0,256])
plt.subplot(231)
plt.imshow(img,'gray')
plt.subplot(232)
plt.imshow(temp,'gray')
plt.subplot(233)
plt.imshow(temp2,'gray')
plt.subplot(234)
plt.plot(hist,color='r')
plt.subplot(235)
plt.plot(hist_temp,color='b')
plt.subplot(236)
plt.plot(hist_temp2,color='g')
plt.savefig(r'D:\PersonSpace\digital_image_process\data_result\histogram\hist_equal_result.png')
plt.show()
2D直方图
前面主要方法都是针对一维直方图的,因此只能先将图像转为灰度图然后再根据灰度值统计直方图,那如何求彩色图像的直方图。
一般对于彩色图像我们会将从RGB的彩色空间转换到HSV(H:色相0-180 S:饱和度0-256 V:亮度空间),然后统计H,S的2D直方图来表示彩色图像的直方图。通常来说2D直方图中越亮的区域表示像素值集中于该区域
img=cv2.imread(r'D:\PersonSpace\digital_image_process\data_result\histogram\home.jpg')
img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
temp=cv2.cvtColor(img,cv2.COLOR_RGB2HSV)
# print(np.max(temp[:,:,2]))
hist=cv2.calcHist([temp],[0,1],None,[180,256],[0,180,0,256])
print(hist.shape)
print(np.max(hist))
print(np.min(hist))
plt.subplot(221)
plt.imshow(img)
plt.subplot(222)
plt.imshow(hist,interpolation='nearest',cmap='jet') #可以看到部分区域存在亮点,这两个位置的亮点分别代表黄色建筑位和蓝色天空
print(np.sum(hist<=1))
# plt.hist(hist.flatten(),bins=276,range=(0,276))
# plt.show()
(180, 256)
2776.0
0.0
40212
#为了展示更加明显的图,我们需要使用colormap来处理一下原图,根据H,S的位置决定色彩,根据hist的值决定亮度
#HSV的一个colormap,默认设置亮度为255
hsv_map = np.zeros((180, 256, 3), np.uint8)
h, s = np.indices(hsv_map.shape[:2])
hsv_map[:,:,0] = h
hsv_map[:,:,1] = s
hsv_map[:,:,2] = 255
hsv_map = cv2.cvtColor(hsv_map, cv2.COLOR_HSV2RGB)
#应用到我们的得到hist上
hist_temp=np.zeros((180, 256, 3), np.uint8)
h, s = np.indices(hist_temp.shape[:2])
hist_temp[:,:,0] = h
hist_temp[:,:,1] = s
hist_temp[:,:,2] = np.clip(hist*255,0,255)
# hist_temp[:,:,2]=temp[:,:,2]
hist_temp = cv2.cvtColor(hist_temp, cv2.COLOR_HSV2RGB)
# print()
plt.subplot(211)
plt.imshow(hsv_map)
plt.subplot(212)
plt.imshow(hist_temp)
<matplotlib.image.AxesImage at 0x1cae02fa5b0>
局部直方图均衡化
根据全局的直方图来做均衡化,在整个图像亮度分布较为均匀时效果还可以,但是如果图像中存在小部分区域的亮度要明显过亮或者过暗的区域时,这些区域的对比度反而可能因为均衡化导致对比度下降。
因此,AHE(自适应直方图均衡化)被提出,其核心是通过将图像划分成一个个局部区域,然后计算局部区域的直方图,并对这个局部区域进行均衡化,这样可以避免全局亮度分布不均匀带来影响
img=cv2.imread('D:\PersonSpace\digital_image_process\data_result\histogram\local_histgram_equal.png',0)
hist=cv2.calcHist([img],[0],None,[256],[0,256])
img_hist=cv2.equalizeHist(img)
hist_equal=cv2.calcHist([img_hist],[0],None,[256],[0,256])
plt.subplot(221)
plt.imshow(img,cmap='gray')
plt.subplot(222)
plt.plot(hist)
plt.xlim([0,256])
plt.subplot(223)
plt.imshow(img_hist,cmap='gray')
plt.subplot(224)
plt.plot(hist_equal)
plt.xlim([0,256])
plt.show()
#如果采用全局的直方图标准化会导致之前对比度较好的区域,对比度出现衰退
但是自适应的直方图均衡化统计的是局部区域的亮度分布,会倾向于放大均匀区域的对比度,这可能导致一些原本不明显的噪声被放大,因此后面提出了CLAHE(限制对比度自适应直方图均衡化),其会设定一个对比度上界,如果某个区域的对比度超过了设定值,其会调整构建该区域直方图时使用bin值,重新划分统计直方图时统计区间,这样可以避免直方图中出现明显尖锐的高峰。
目前直方图主要有两种计算方式:一种是基于滑动窗口的计算方式,一个通过插值的计算方式
Opencv中CLAHE
CLAHE:对比抑制自适应直方图均衡方法,其从两个方面来实现对局部区域进行直方图标准化。
1.首先其使用tileGridSize参数指定局部区域大小,其会将原图划分成一个个局部块,然后在局部块中统计直方图信息,然后依据统计出局部直方图信息对图像进行局部直方图标准化。
2.由于考虑如果局部区域中存在着一些噪声,此时如果局部区域的对比度被放大,就会导致噪声在观感上变的更加明显,因此其使用clipLimit参数设定一个局部区域的最大对比度,如果该区域的对比度超过要求,会调整在统计该区域直方图使用的bin大小,是的
clahe=cv2.createCLAHE(tileGridSize=(8,8),clipLimit=3)
cl1=clahe.apply(img)
local_hist=cv2.calcHist([cl1],[0],None,[256],[0,256])
plt.subplot(211)
plt.imshow(cl1,'gray')
plt.subplot(212)
plt.plot(hist)
plt.plot(local_hist,color='g')
plt.xlim([0,256])
plt.show()
直方图反射
直方图反射算法可以通过比较待查找物体和目标图像上的直方图分布来查找物体所在区域并生成每个像素属于该物体的概率。
- 其首先计算某个物体的直方图M,然后计算目标图像的直方图。
- 物体往往几个特定颜色组成,因此其会在目标图像的直方图上找对应颜色,然后计算这些颜色输入目标物体的概率,公式为M/I,如果某个颜色在M中多,在I中少,其属于物体概率大,如果在I多,M中少,则其相应的概率会降低。
- 然后根据每个像素点的颜色值来获取到该像素点属于目标物体概率,最终得到一个概率图,图中的值是每个像素点属于目标物体的概率。
- 再使用一个卷积核来平滑概率图
- 最后使用二值化来将概率图转成值为0或1的掩码图。
一般来说想要使用直方图反射的方法来实现对物体的精确定位,最好不要使用灰度的直方图,而是使用彩色的直方图(H,S),这样能够更准确地比对目标对象。
opencv实现中用于平滑的核大小和最后阈值的大小也会影响最终掩码的,核越大,阈值越大要求越高
### 手动实现直方图映射
target_object=cv2.imread(r'D:\PersonSpace\digital_image_process\data_result\histogram\grass.png')
target_object=cv2.cvtColor(target_object,cv2.COLOR_BGR2RGB)
hsv=cv2.cvtColor(target_object,cv2.COLOR_RGB2HSV)
target=cv2.imread(r'D:\PersonSpace\digital_image_process\data_result\histogram\messi5.jpg')
target=cv2.cvtColor(target,cv2.COLOR_BGR2RGB)
hsvt=cv2.cvtColor(target,cv2.COLOR_RGB2HSV)
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
#获取每个像素点属于物体的概率
h,s,t=cv2.split(hsvt) #获取每个像素点的h,s
B=R[h.ravel(),s.ravel()] #根据每个像素点的h,s来确定该像素点的概率
B=B.reshape(target.shape[:2])
#约束概率
B=np.clip(B,0,1)
#使用卷积平滑概率图
disc=cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(8,8)) #产生特定的用于形态学的卷积核,就是生成卷积核作用
B_filtered=cv2.filter2D(B,-1,disc) #对B进行滤波操作,-1应该是对应ddepth说明滤波后图像像素是用8bit数据格式描述还是其他,-1表示和源图像一致
#二值化,ret是阈值化后的图像,ret是阈值
B_filtered=np.uint8(B_filtered)
cv2.normalize(B_filtered,B_filtered,0,255,cv2.NORM_MINMAX) #将值标准化到0-255之间,便于后续做二值化处理 (value-value_min)/(value_max-value_min)*255
thresh,ret=cv2.threshold(B_filtered,10,1,0) #对图像进行二值化,50表示作为二值化的阈值,255(maxval)表示二值化后最大值,type使用阈值类型
print(np.max(ret))
#获取mask图像
get_object=np.uint8(ret[:,:,None]*target)
# get_object=cv2.cvtColor(get_object,cv2.COLOR_BGR2RGB)
plt.subplot(321)
plt.imshow(target)
plt.subplot(322)
plt.imshow(ret,cmap='gray')
plt.subplot(323)
plt.imshow(B)
plt.subplot(324)
plt.imshow(B_filtered)
plt.subplot(325)
plt.imshow(get_object)
plt.show()
C:\Users\zwplus\AppData\Local\Temp\ipykernel_22896\1789344351.py:14: RuntimeWarning: divide by zero encountered in divide
R=M/I
C:\Users\zwplus\AppData\Local\Temp\ipykernel_22896\1789344351.py:14: RuntimeWarning: invalid value encountered in divide
R=M/I
1
### 使用opencv内置函数
# cv.calcBackProject( images, channels, hist, ranges, scale[, dst] )
# images:list 需要进行直方图反射的图像
# channels:使用那些通道来统计直方图
# hist:反射使用的待查找物体的直方图
# ranges:每个通道的取值范围,和计算hist的一致
# scale: That is, similarly to calcHist , at each location (x, y) the function collects the values from the selected channels in the input images
# and finds the corresponding histogram bin. But instead of incrementing it, the function reads the bin value, scales it by scale , and stores in backProject(x,y) .
# But instead of incrementing it, the function reads the bin value, scales it by scale , and stores in backProject(x,y) .
# 没有找到scale精确描述,猜测可能该方法首先从输入hist中找到相应的bins,然后scale*bins来作为计算目标图像直方图的bins?调整该值发现,该值越大,结果越差,值越小结果更精确
target_object=cv2.imread(r'D:\PersonSpace\digital_image_process\data_result\histogram\cloth.png')
target_object=cv2.cvtColor(target_object,cv2.COLOR_BGR2RGB)
target=cv2.imread(r'D:\PersonSpace\digital_image_process\data_result\histogram\messi5.jpg')
target=cv2.cvtColor(target,cv2.COLOR_BGR2RGB)
#计算H,S的直方图
hsv=cv2.cvtColor(target_object,cv2.COLOR_RGB2HSV)
hist=cv2.calcHist([hsv],[0,1],None,[180,256],[0,180,0,256])
hsvt=cv2.cvtColor(target,cv2.COLOR_RGB2HSV)
# target_hist=cv2.calcHist([hsvt],[0,1],None,[180,256],[0,180,0,256])
#计算每个像素点的概率图
dst=cv2.calcBackProject([hsvt],[0,1],hist,[0,180,0,256],1) #0,1和0,180,0,256是用用来计算hsvt的hist的
#平滑,核大小会影响最终的结果,核越大对掩码约束越高
disc=cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(9,9))
cv2.filter2D(dst,-1,disc,dst)
#感觉做不做标准化对效果影响一般
cv2.normalize(dst,dst,0,255,cv2.NORM_MINMAX)
#阈值化,阈值大小会影响最终的结果,核越大对掩码约束越高
thresh,ret=cv2.threshold(dst,60,1,0)
cloth=ret[:,:,None]*target
plt.subplot(311)
plt.imshow(target)
plt.subplot(312)
plt.imshow(ret,cmap='gray')
plt.subplot(313)
plt.imshow(cloth)
plt.show()