一、灰度直方图及绘制
- 灰度直方图统计
灰度直方图用来描述每个像素在图像矩阵中出现的次数或概率。其横坐标一般为0-255个像素值,纵坐标为该像素值对应的像素点个数。如下图所示的图像矩阵(单通道灰度图,三通道时可以分别绘制),可以统计每个像素值出现的次数,也可以统计概率,统计像素值出现次数的灰度直方图如下所示。
- 灰度直方图绘制
a) 可以利用opencv的calcHist()统计像素值出现次数,通过matploblib的plot()绘制
b)可以直接利用matploblib的hist()方法
cv2.calcHist(img,channels,mask,histSize,ranges,accumulate)
参数:
img:输入图像,为列表,如[img]
channels: 计算的通道,为列表,如[0]表示单通道,[0,1]统计两个通道
mask: 掩模,和输入图像大小一样的矩阵,为1的地方会进行统计(与图像逻辑与后再统计);无掩模时为None
histSize: 每一个channel对应的bins个数,为列表,如[256]表示256个像素值
ranges: bins的边界,为列表,如[0,256]表示像素值范围在0-256之间
accumulate: Accumulation flag. If it is set, the histogram is not cleared in the beginning when it is allocated. This feature enables you to compute a single histogram from several sets of arrays, or to update the histogram in time.
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np
img = cv.imread(r"e:\2.jpg", 0)
hist = cv.calcHist([img], [0], None, [256], [0, 256])
plt.subplot(1, 3, 1), plt.plot(hist, color="r"), plt.axis([0, 256, 0, np.max(hist)])
plt.xlabel("gray level")
plt.ylabel("number of pixels")
plt.subplot(1, 3, 2), plt.hist(img.ravel(), bins=256, range=[0, 256]), plt.xlim([0, 256])
plt.xlabel("gray level")
plt.ylabel("number of pixels")
plt.subplot(1, 3, 3)
plt.plot(hist, color="r"), plt.axis([0, 256, 0, np.max(hist)])
plt.hist(img.ravel(), bins=256, range=[0, 256]), plt.xlim([0, 256])
plt.xlabel("gray level")
plt.ylabel("number of pixels")
plt.show()
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np
img = cv.imread(r"e:\1.jpg",0)
histogram,bins = np.histogram(img,bins=256,range=[0,256])
print(histogram)
plt.plot(histogram,color="g")
plt.axis([0,256,0,np.max(histogram)])
plt.xlabel("gray level")
plt.ylabel("number of pixels")
plt.show()
np.histogram()
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np
img = cv.imread(r"e:\1.jpg",0)
rows,cols = img.shape
hist = img.reshape(rows*cols)
histogram,bins,patch = plt.hist(hist,256,facecolor="green",histtype="bar") #histogram即为统计出的灰度值分布
plt.xlabel("gray level")
plt.ylabel("number of pixels")
plt.axis([0,255,0,np.max(histogram)])
plt.show()
plt.hist()
二、对比度增强
对比度增强,即将图片的灰度范围拉宽,如图片灰度分布范围在[50,150]之间,将其范围拉升到[0,256]之间。
- 线性变换:convertScaleAbs(src,dst,alpha,beta)
参数:
src: 图像对象矩阵
dst:输出图像矩阵
alpha:y=ax+b中的a值
beta:y=ax+b中的b值
(对于计算后大于255的像素值会截断为255)
通过函数y=ax+b对灰度值进行处理,例如对于过暗的图片,其灰度分布在[0,100], 选择a=2,b=10能将灰度范围拉伸到[10, 210]。
import cv2 as cv
img = cv.imread(r"e:\3.jpg")
print(img)
img_bright = cv.convertScaleAbs(img,alpha=1.5,beta=0)
print(img_bright)
cv.imshow("img",img)
cv.imshow("img_bright",img_bright)
cv.waitKey(0)
cv.destroyAllWindows()
采用numpy实现,其中可以针对不同区域像素点,采用不同系数a,b来动态改变像素值
import cv2 as cv
import numpy as np
img = cv.imread(r"e:\3.jpg")
a=1
b=100
y = np.float(a)*img+b
y[y>255]=255
y = np.round(y)
img_bright= y.astype(np.uint8)
cv.imshow("img",img)
cv.imshow("img_bright",img_bright)
cv.waitKey(0)
cv.destroyAllWindows()
-
直方图正规化
cv2.normalize(src,dst,alpha,beta,normType,dtype,mask)
参数:
src: 图像对象矩阵
dst:输出图像矩阵(和src的shape一样)
alpha:正规化的值,如果是范围值,为范围的下限 (alpha – norm value to normalize to or the lower range boundary in case of the range normalization.)
beta:如果是范围值,为范围的上限;正规化中不用到 ( upper range boundary in case of the range normalization; it is not used for the norm normalization.)
norm_type:normalize的类型
cv2.NORM_L1:将像素矩阵的1-范数做为最大值(矩阵中值的绝对值的和)
cv2.NORM_L2:将像素矩阵的2-范数做为最大值(矩阵中值的平方和的开方)
cv2.NORM_MINMAX:将像素矩阵的∞-范数做为最大值 (矩阵中值的绝对值的最大值)
dtype: 输出图像矩阵的数据类型,默认为-1,即和src一样
mask:掩模矩阵,只对感兴趣的地方归一化 -
伽马变换
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np
img = cv.imread(r"e:\3.jpg")
img_norm = img/255.0 #注意255.0得采用浮点数
img_gamma = np.power(img_norm,0.4)*255.0
img_gamma = img_gamma.astype(np.uint8)
cv.imshow("img",img)
cv.imshow("img_gamma",img_gamma)
cv.waitKey(0)
cv.destroyAllWindows()
- 全局直方图均衡化
直方图均衡化的目的是将原图片每个像素值的像素点个数进行重新分配到[0,255]的256个像素值上,使得每个像素值对应的像素点个数近似相等,即重新分配后,0-255的每个像素值对应的像素点个数近似为(rows*cols/256)
cv2.equalizeHist(src,dst)
参数:
src: 图像对象矩阵,必须为单通道的uint8类型的矩阵数据
dst:输出图像矩阵(和src的shape一样)
import cv2 as cv
img = cv.imread(r"e:\3.jpg",0)
img_equalize = cv.equalizeHist(img)
cv.imshow("img",img)
cv.imshow("img_equalize",img_equalize)
cv.waitKey(0)
cv.destroyAllWindows()
使用numpy实现equalizeHist
# coding:utf-8
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np
import math
# 统计灰度分布
def calc_hist(img):
rows, cols = img.shape[:2]
hist = np.zeros(256, np.uint64) # 注意此处的数据格式不要用np.uint8,会溢出但不报错
for r in range(rows):
for c in range(cols):
hist[img[r, c]] += 1
return hist
def equalize_hist(img):
rows, cols = img.shape[:2]
hist = calc_hist(img)
# 计算灰度累积分布
hist_sum = np.zeros([256], np.uint32) # 注意数据类型为np.uint32,防止溢出
for i in range(256):
if i == 0:
hist_sum[i] = hist[i]
else:
hist_sum[i] = hist[i] + hist_sum[i - 1]
# 输出图像的灰度分布
output_hist = np.zeros(256, np.uint8)
cofficient = 256.0 / (rows * cols)
for i in range(256):
q = cofficient * float(hist_sum[i]) - 1
if q >= 0:
output_hist[i] = math.floor(q)
else:
output_hist[i] = 0
# 输出图像的像素值
output_img = np.zeros([rows, cols], np.uint8)
for r in range(rows):
for c in range(cols):
output_img[r, c] = output_hist[img[r, c]]
return output_img
if __name__ == "__main__":
img = cv.imread(r"e:\1.jpg", 0)
img_equalize = equalize_hist(img)
cv.imshow("img", img)
cv.imshow("img_equalize", img_equalize)
cv.waitKey(0)
cv.destroyAllWindows()
原图
均衡化后
上述通过numpy实现equalizeHist的算法思路和原理如下:
- 限制对比度自适应直方图均衡化
相比全局直方图均衡化,自适应直方图均衡化将图像划分为不重叠的小块,在每一小块进行直方图均衡化,但若小块内有噪声,影响很大,需要通过限制对比度来进行抑制,即限制对比度自适应直方图均衡化。
clahe=cv2.createCLAHE(clipLimit,tileGridSize)
参数:
clipLimit:限制对比度的阈值,默认为40,直方图中像素值出现次数大于该阈值,多余的次数会被重新分配
tileGridSize:图像会被划分的size, 如tileGridSize=(8,8),默认为(8,8)
import cv2 as cv
img = cv.imread(r"e:\1.jpg",0)
clahe = cv.createCLAHE(3,(8,8))
dst = clahe.apply(img)
cv.imshow("img",img)
cv.imshow("dst",dst)
cv.waitKey(0)
cv.destroyAllWindows()
原图
自适应均衡化后