直方图是多种空间域处理技术的基础,可以用于图像增强。
考虑连续灰度值,并用变量r表示待处理图像的灰度。设r的取值区间为[0,L-1],且r = 0表示黑色,r = L - 1表示白色。令 p r ( r ) p_r(r) pr(r)和 p s ( s ) p_s(s) ps(s)分别表示随机变量r和s的概率密度函数,概率密度函数归一化后可以画出图像的直方图
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
image = cv.imread('images/test_1.jpg', 0)
hist = cv.calcHist([image],[0],None,[256],[0,255])
plt.plot(hist)
plt.show()
可以看到较暗的图片灰度直方图分布的非常不均衡,那么我们构造一个出变换
s = T ( r ) = ( L − 1 ) ∫ 0 r p r ( w ) d w , 0 ≤ r ≤ L − 1 s=T(r)=(L-1)\int_{0}^{r}p_r(w)dw,0\leq r \leq L-1 s=T(r)=(L−1)∫0rpr(w)dw,0≤r≤L−1
使得对应的输出图像概率密度函数变成均匀的
对于离散值,我们处理其概率(直方图值)与求和来替代处理概率密度函数与积分。
一副数字图像中灰度级 r k r_k rk出现的概率近似为:
p r ( r k ) = n k M N , k = 0 , 1 , 2 , … , L − 1 p_r(r_k)=\frac{n_k}{MN}, k=0,1,2,\dots,L-1 pr(rk)=MNnk,k=0,1,2,…,L−1
其中,MN是图像中像素的综述, n k n_k nk是灰度为 r k r_k rk像素的个数。
则离散形式的变化公式为:
s k = T ( r k ) = ( L − 1 ) ∑ j = 0 k p r ( r j ) = ( L − 1 ) M N ∑ j = 0 k n j , k = 0 , 1 , 2 , … , L − 1 s_k=T(r_k)=(L-1)\sum_{j=0}^kp_r(r_j)=\frac{(L-1)}{MN}\sum_{j=0}^kn_j, k=0,1,2,\dots,L-1 sk=T(rk)=(L−1)j=0∑kpr(rj)=MN(L−1)j=0∑knj,k=0,1,2,…,L−1
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
image = cv.imread('images/test_1.jpg', 0)
image2 = cv.equalizeHist(image)
cv.imshow("image_2",image2)
hist = cv.calcHist([image2],[0],None,[256],[0,255])
plt.plot(hist)
plt.show()
cv.waitKey(0)
可以看到直方图的分布更加均匀了,图像的对比度和相关细节显示的也更加明显了。
直方图均衡能自动地确定变换函数,当需要自动增强时,这是一种好方法,因为由于这种技术得到的结果可以预知,并且这种方法实现也很简单,但是对于某些应用,采用均匀直方图的基本增强并不是最好的方法,有时我们希望处理后的图像具有规定的直方图形状可能更有用。这种采用处理后有特殊直方图的方法称为直方图匹配或直方图规定划。
例:直方图规定划
设一副图片的灰度概率密度函数(PDF)为 p r ( r ) = 2 r / ( L − 1 ) 2 , 0 ≤ r ≤ ( L − 1 ) p_r(r)=2r/(L-1)^2, 0\leq r\leq (L-1) pr(r)=2r/(L−1)2,0≤r≤(L−1)
我们想要寻找一个变换函数,使得产生的图像的灰度PDF是 p z ( z ) = 3 z 2 / ( L − 1 ) 3 , 0 ≤ r ≤ ( L − 1 ) p_z(z)=3z^2/(L-1)^3,0\leq r\leq (L-1) pz(z)=3z2/(L−1)3,0≤r≤(L−1)
求出原图像的直方图均衡变换:
s = T ( r ) = ( L − 1 ) ∫ 0 r p r ( w ) d w = 2 ( L − 1 ) ∫ o r w d w = r 2 ( L − 1 ) , 0 ≤ r ≤ L − 1 s=T(r)=(L-1)\int_{0}^{r}p_r(w)dw=\frac{2}{(L-1)}\int_{o}^rwdw=\frac{r^2}{(L-1)},0\leq r \leq L-1 s=T(r)=(L−1)∫0rpr(w)dw=(L−1)2∫orwdw=(L−1)r2,0≤r≤L−1
求出规定图像的直方图均衡变换:
G ( z ) = ( L − 1 ) ∫ 0 z p z ( w ) d w = 3 ( L − 1 ) 2 ∫ o z w 2 d w = z 3 ( L − 1 ) 2 , 0 ≤ r ≤ L − 1 G(z)=(L-1)\int_{0}^{z}p_z(w)dw=\frac{3}{(L-1)^2}\int_{o}^zw^2dw=\frac{z^3}{(L-1)^2},0\leq r \leq L-1 G(z)=(L−1)∫0zpz(w)dw=(L−1)23∫ozw2dw=(L−1)2z3,0≤r≤L−1
最后使其相等 G ( z ) = s G(z)=s G(z)=s
利用反变换得到 z = G − 1 [ T ( r ) ] = G − 1 ( s ) z=G^-1[T(r)]=G^-1(s) z=G−1[T(r)]=G−1(s)
在离散形式下
s k = T ( r k ) = ( L − 1 ) ∑ j = 0 k p r ( r j ) = ( L − 1 ) M N ∑ j = 0 k n j , k = 0 , 1 , 2 , … , L − 1 s_k=T(r_k)=(L-1)\sum_{j=0}^kp_r(r_j)=\frac{(L-1)}{MN}\sum_{j=0}^kn_j,k=0,1,2,\dots,L-1 sk=T(rk)=(L−1)j=0∑kpr(rj)=MN(L−1)j=0∑knj,k=0,1,2,…,L−1
G ( z q ) = ( L − 1 ) ∑ i = 0 q p z ( z i ) G(z_q)=(L-1)\sum_{i=0}^qp_z(z_i) G(zq)=(L−1)i=0∑qpz(zi)
对于一个q值,有 G ( z q ) = s k G(z_q)=s_k G(zq)=sk
再利用反变换,有 Z q = G − 1 ( s k ) Z_q=G^-1(s_k) Zq=G−1(sk)
那么我们总结直方图规定划过程如下:
具体python实现:
将一张较为黑暗的原始图像
与一张拍摄光线较好的匹配图像
进行直方图匹配,目的是为了把原始图像的直方图匹配成匹配图像的直方图分布,使得原始图像能够呈现出匹配图像的光线效果。
import numpy as np
from matplotlib import pyplot as plt
from PIL import Image
import matplotlib
matplotlib.rcParams['font.sans-serif']=['SimHei'] # 用黑体显示中文
#传入的直方图要求是个字典,每个灰度对应着概率
def drawHist(hist,name):
keys = hist.keys()
values = hist.values()
x_size = len(hist)-1#x轴长度,也就是灰度级别
axis_params = []
axis_params.append(0)
axis_params.append(x_size)
#plt.figure()
if name != None:
plt.title(name)
plt.bar(tuple(keys),tuple(values))#绘制直方图
#plt.show()
#将灰度数组映射为直方图字典,nums表示灰度的数量级
def arrayToHist(grayArray,nums):
if(len(grayArray.shape) != 2):
print("length error")
return None
w,h = grayArray.shape
hist = {}
for k in range(nums):
hist[k] = 0
for i in range(w):
for j in range(h):
if(hist.get(grayArray[i][j]) is None):
hist[grayArray[i][j]] = 0
hist[grayArray[i][j]] += 1
#normalize
n = w*h
for key in hist.keys():
hist[key] = float(hist[key])/n
return hist
#计算累计直方图计算出新的均衡化的图片,nums为灰度数,256
def equalization(grayArray,h_s,nums):
#计算累计直方图
tmp = 0.0
h_acc = h_s.copy()
for i in range(256):
tmp += h_s[i]
h_acc[i] = tmp
if(len(grayArray.shape) != 2):
print("length error")
return None
w,h = grayArray.shape
des = np.zeros((w,h),dtype = np.uint8)
for i in range(w):
for j in range(h):
des[i][j] = int((nums - 1)* h_acc[grayArray[i][j] ] +0.5)
return des
def histMatch(grayArray,h_d):
#计算累计直方图
tmp = 0.0
h_acc = h_d.copy()
for i in range(256):
tmp += h_d[i]
h_acc[i] = tmp
h1 = arrayToHist(grayArray,256)
tmp = 0.0
h1_acc = h1.copy()
for i in range(256):
tmp += h1[i]
h1_acc[i] = tmp
#计算映射
M = np.zeros(256)
for i in range(256):
idx = 0
minv = 1
for j in h_acc:
if (np.fabs(h_acc[j] - h1_acc[i]) < minv):
minv = np.fabs(h_acc[j] - h1_acc[i])
idx = int(j)
M[i] = idx
des = M[grayArray]
return des
imdir = "images/test_1.jpg"#原始图片的路径
imdir_match = "images/persian_cat.jpg"
#直方图匹配
#打开文件并灰度化
im_s = Image.open(imdir).convert("L")
im_s = np.array(im_s)
print(np.shape(im_s))
#打开文件并灰度化
im_match = Image.open(imdir_match).convert("L")
im_match = np.array(im_match)
print(np.shape(im_match))
#开始绘图
plt.figure()
#原始图和直方图
plt.subplot(2,3,1)
plt.title("原始图片")
plt.imshow(im_s,cmap='gray')
plt.subplot(2,3,4)
hist_s = arrayToHist(im_s,256)
drawHist(hist_s,"原始直方图")
#match图和其直方图
plt.subplot(2,3,2)
plt.title("match图片")
plt.imshow(im_match,cmap='gray')
plt.subplot(2,3,5)
hist_m = arrayToHist(im_match,256)
drawHist(hist_m,"match直方图")
#match后的图片及其直方图
im_d = histMatch(im_s,hist_m)#将目标图的直方图用于给原图做均衡,也就实现了match
plt.subplot(2,3,3)
plt.title("match后的图片")
plt.imshow(im_d,cmap='gray')
plt.subplot(2,3,6)
hist_d = arrayToHist(im_d,256)
drawHist(hist_d,"match后的直方图")
plt.show()