基本概念
上一篇文章讲了直方图均衡相关内容,从实际应用中我们发现,只能对直方图做均衡操作并不能完全满足我们的需要,有时我们更希望调整概率分布(直方图)为指定形状,这就是直方图匹配。
比如下图图一猫咪的直方图较为集中在中间,我们希望它的分布贴近第二幅文字图的分布(集中在暗区域)
im1
数学基础
从上一篇文章的介绍中我们发现,对于任何图像(\(r\)的概率密度分布),我们都可以通过其累积分布函数将其函数变换到随机变量\(s\),而\(s\)是均匀分布的。那我们反过来想,能否将均匀分布的随机变量逆变换到某个特定的分布呢?
公式推导
设\(r\)为随机变量值域为\([0,255]\),其概率密度函数为\(p_r(r)\)。一函数(变换为):
\[s = T(r) = (L-1) \int_{0}^{r} p_r(m) \mathrm{d}m\]
由上一篇文章我们易知\(s\)服从均匀分布,我们寻找一个\(z\)随机变量符合另一特殊分布\(p_z(z)\),易知存在一函数(变换):
\[s = G(z) = (L-1) \int_{0}^{z} p_z(m) \mathrm{d}m\]
由于\(G(z)\)为累积分布函数,单调,故存在反函数\(G^{-1}(z)\),则有新变换 \(N = G^{-1} \cdot T\),使得\(r\)随机变量经\(N\)变换后符合\(z\)的分布特征。
\[z = G^{-1}(s) = G^{-1}(T(r)) = N(r)\]
由此,我们找到了某值域内求从某一分布随机变量\(r\)变换到指定分布随机变量\(z\)的方法。
个人理解
直方图均衡,均匀分布,实际上在中间做了桥接。
实验效果
均衡化
输出两幅图原图以及均衡化后的图片,直方图。
im1
直观对比两图的累积分布函数与直方图
\(T(r)\)表示\(r\)的累积分布函数,本样例中为小猫图像的累积分布函数,图示为淡蓝色曲线。 \(G(z)\)表示为\(z\)的累积分布函数,本样例中为文字图片的累积分布函数,图示为橙黄色曲线。
im1
求反函数
反函数的计算,并没有按照课本上所说,搜索找最近值,对于一张\(m \times n\)图像,对于每个搜索需要\(O(log(L))\),总复杂度是相乘的关系\(O(MNlog(L))\),而通过一个二重循环实现打表计算则可以优化到\(O(MN)+O(L)\),对于实际图片\(MN\)远大于\(L\),所以这种优化是值得的
123456cnt = 0for i in range(256): while cnt<=raw_table[i]: new_table[cnt] = i cnt+=1return new_table
计算反函数表方便后面对图像进行变换。
123reverse_imtest1_table = GetReverseTable(imtest1_tabel)match_imtest0_table = reverse_imtest1_table[imtest0_tabel]match_imtest0 = match_imtest0_table[imtest0]
输出结果如下
im2
直方图对比表示了 image0,image1,匹配后的 image0 三图,由图可以看出匹配后的 image0 直方图分布已经很贴近 image1 了。
图片效果
(小猫被玩的有点可怜)
im2
核心代码
图像直方图展示
12345678910111213141516# 展示直方图def DrawHist(input_img,pic_handle,histogram_handle): kwargs = dict(bins = 50, histtype='bar', edgecolor = "white",alpha=0.5, density = True) pic_handle.set_title("The image") pic_handle.imshow(input_img) histogram_handle.set_title("The histogram") histogram_handle.hist(input_img.flatten(),**kwargs)imtest0 = imageio.imread('imageio:chelsea.png')imtest1 = GetRGB('./3_3Photo/3.jpg')plt.figure(figsize=(20,10))DrawHist(imtest0,plt.subplot(221),plt.subplot(222))DrawHist(imtest1,plt.subplot(223),plt.subplot(224))plt.show()
直方图匹配
12345678910# 获取目标图像累积分布函数的反函数lut表(离散)def GetReverseTable(raw_table): new_table = np.zeros(256,dtype = np.int64) cnt = 0 for i in range(256): while cnt<=raw_table[i]: new_table[cnt] = i cnt+=1 return new_table
加入反函数输出
12345678910111213141516171819202122# 加入反函数,再绘图plt.figure(figsize=(15,5))plotline = plt.subplot(121)plotline.set_title("The function")plotline.plot(np.arange(256),imtest0_tabel,label = "$T(r)$")plotline.plot(np.arange(256),np.arange(256),color = "black",label = "$r$")plotline.plot(np.arange(256),imtest1_tabel,label = "$G(z)$")plotline.plot(np.arange(256),reverse_imtest1_table, label = "$G^{-1}(z)$")plotline.plot(np.arange(256),match_imtest0_table, label= "$G^{-1}(T(r)), N(r)$")plotline.legend()# 设置直方图参数 分割块 叠加模式 边界颜色 透明度 正则化(映射到0~1)kwargs = dict(bins = 50, histtype='bar', edgecolor = "white",alpha=0.5, density = True)histogram1 = plt.subplot(122)histogram1.set_title("The histogram")histogram1.hist(imtest0.flatten(),color = "black",label = "imtest0 original",**kwargs )histogram1.hist(imtest1.flatten(),label = "imtest1 original",**kwargs)histogram1.hist(match_imtest0.flatten(),label = "imtest0 after match",**kwargs)histogram1.legend()plt.show()
完整代码
123456import numpy as npimport imageioimport cv2import matplotlib.pyplot as pltim = imageio.imread('photo2.jpg')
123456# 获取RGB图像def GetRGB(path): im_BGR = cv2.imread(path,cv2.COLOR_GRAY2RGB) im = cv2.cvtColor(im_BGR,cv2.COLOR_BGR2RGB) return im
12345678910111213141516# 展示直方图def DrawHist(input_img,pic_handle,histogram_handle): kwargs = dict(bins = 50, histtype='bar', edgecolor = "white",alpha=0.5, density = True) pic_handle.set_title("The image") pic_handle.imshow(input_img) histogram_handle.set_title("The histogram") histogram_handle.hist(input_img.flatten(),**kwargs)imtest0 = imageio.imread('imageio:chelsea.png')imtest1 = GetRGB('./3_3Photo/3.jpg')plt.figure(figsize=(20,10))DrawHist(imtest0,plt.subplot(221),plt.subplot(222))DrawHist(imtest1,plt.subplot(223),plt.subplot(224))plt.show()
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172# 尝试直方图均衡化,def CaculateHistogram(input_image): # 参数1. 单通道8位灰度图像 # 输出1. 总像素值 # 输出2. 灰度值分布计数 # 输出3. 灰度值分布前缀和(用来做后续直方图平局奴化) # 区分通道 if len(np.shape(input_image)) == 3: height,width,level = np.shape(input_image) summ = height*width*level else : height,width = np.shape(input_image) summ = height*width# 第一种 通过访问指定位置像素 耗时executed in 7m 9s# 巨慢无比# for i in range(height):# for j in range(width):# caculate_num[input_image[i][j]] += 1# 第二种 直接拆分行,像素获取像素值 耗时executed in 14.4s# for line in input_image:# for px in line:# caculate_num[px] +=1# 第三种 调用np.histogram()方法 耗时executed in 644ms caculate_num,index_x = GetHistogramArray(input_image) caculate_num = np.append(caculate_num,1)# print(np.shape(caculate_num)) # 第四种 吕少推荐的方法(还没看懂)# caculate_num = cumsum(input_image,256)# print(np.shape(caculate_num)) sum_num = np.copy(caculate_num) for i in range(1,256):# print(sum_num[i-1],caculate_num[i]) sum_num[i] = sum_num[i-1] + sum_num[i] return summ,caculate_num,sum_numdef GetHistogramArray(image): return np.histogram(image,np.arange(0,256));def cumsum(img, bins): histogram = np.zeros(bins) for pixel in np.arange(0, bins, 1): histogram[pixel] += len(img[img==pixel]) return histogramdef HistogramEqualizationLUT(input_image): # 参数1, 单通道8位灰度图像 # 输出1. 单通道8位灰度图像(直方图均衡化后) size,data,data_sum = CaculateHistogram(input_image) fxy = lambda x: (255*data_sum[x])//size table = np.array([fxy(i) for i in range(256)])# print(table)# 不可以这样# image_new = table[input_image] lut = lambda x: table[x] return lut(input_image),tableimtest0_he,imtest0_tabel = HistogramEqualizationLUT(imtest0)imtest1_he,imtest1_tabel = HistogramEqualizationLUT(imtest1)plt.figure(figsize=(20,20))DrawHist(imtest0,plt.subplot(421),plt.subplot(422))DrawHist(imtest0_he,plt.subplot(423),plt.subplot(424))DrawHist(imtest1,plt.subplot(425),plt.subplot(426))DrawHist(imtest1_he,plt.subplot(427),plt.subplot(428))
123456789101112131415plt.figure(figsize=(15,5))plotline = plt.subplot(121)plotline.set_title("The function")plotline.plot(np.arange(256),np.arange(256),color = 'black',label = "$r$")plotline.plot(np.arange(256),imtest0_tabel,label = "$T(r)$")plotline.plot(np.arange(256),imtest1_tabel,label = "$G(z)$")plotline.legend()kwargs = dict(bins = 50, histtype='bar', edgecolor = "white",alpha=0.6, density = True)histogram = plt.subplot(122)histogram.set_title("The histogram")histogram.hist(imtest0.flatten(),**kwargs, label = "imtest0 $p_r(r)$" )histogram.hist(imtest1.flatten(), **kwargs, label = "imtest1 $p_z(z)$")histogram.legend()plt.show()
12345678910111213141516# 获取目标图像累积分布函数的反函数lut表(离散)def GetReverseTable(raw_table): new_table = np.zeros(256,dtype = np.int64) cnt = 0 for i in range(256): while cnt<=raw_table[i]: new_table[cnt] = i cnt+=1 return new_tablereverse_imtest1_table = GetReverseTable(imtest1_tabel)match_imtest0_table = reverse_imtest1_table[imtest0_tabel]match_imtest0 = match_imtest0_table[imtest0]
12345678910111213141516171819202122# 加入反函数,再绘图plt.figure(figsize=(15,5))plotline = plt.subplot(121)plotline.set_title("The function")plotline.plot(np.arange(256),imtest0_tabel,label = "$T(r)$")plotline.plot(np.arange(256),np.arange(256),color = "black",label = "$r$")plotline.plot(np.arange(256),imtest1_tabel,label = "$G(z)$")plotline.plot(np.arange(256),reverse_imtest1_table, label = "$G^{-1}(z)$")plotline.plot(np.arange(256),match_imtest0_table, label= "$G^{-1}(T(r)), N(r)$")plotline.legend()# 设置直方图参数 分割块 叠加模式 边界颜色 透明度 正则化(映射到0~1)kwargs = dict(bins = 50, histtype='bar', edgecolor = "white",alpha=0.5, density = True)histogram1 = plt.subplot(122)histogram1.set_title("The histogram")histogram1.hist(imtest0.flatten(),color = "black",label = "imtest0 original",**kwargs )histogram1.hist(imtest1.flatten(),label = "imtest1 original",**kwargs)histogram1.hist(match_imtest0.flatten(),label = "imtest0 after match",**kwargs)histogram1.legend()plt.show()
123456# 输出三种图像plt.figure(figsize=(20,15))DrawHist(imtest0,plt.subplot(321),plt.subplot(322))DrawHist(imtest0_he,plt.subplot(323),plt.subplot(324))DrawHist(match_imtest0,plt.subplot(325),plt.subplot(326))plt.show()
