B10 - 图像处理 - 灰度变换与直方图
图像的几何变换是改变像素值位置,而灰度变换是改变像素值,直接的线性变换还是通过直方图来操作,都是这个过程。对比度变换则是提升暗场和亮场的区分度,也就是变换像素值时,暗亮区域有不同的权重
什么是图像的灰度变换?
- 灰度变换是图像增强的重要方法,可以使图像动态范围扩大、图像对比度增强,图像更清晰,特征更明显,从而改善图像的显示效果
- 灰度变换就是按一定规则(灰度映射函数)修改图像每一个像素的灰度值,从而改变图像灰度的动态范围。按照灰度映射函数的性质,灰度变换可以分为线性变换、分段线性和非线性变换,非线性变换中对数变换、指数变换和幂律变换(n 次幂、n 次根)最为常用
图像的反色变换?
- 图像的反色变换,即图像反转,将黑色像素点变白色,白色像素点变黑色。广义的反色变换也可以应用于彩色图像,即对所有像素点取补
- 图像的反转处理可以增强暗色区域中的白色或灰色细节
1
2
3
4
5
6
7
8
9img = cv2.imread("../images/imgLena.tif") # 读取彩色图像(BGR)
imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 颜色转换:BGR(OpenCV) -> Gray
h, w = img.shape[:2] # 图片的高度和宽度
#imgInv = np.zeros_like(img) # 创建与 img 相同形状的黑色图像
imgInv = np.empty((w, h), np.uint8) # 创建空白数组
for i in range(h):
for j in range(w):
imgInv[i][j] = 255 - imgGray[i][j]
show_images([img,imgGray,imgInv])
图像灰度的线性变换?
- 线性灰度变换将原始图像灰度值的动态范围按线性关系扩展到指定范围或整个动态范围,可以凸显图像的细节,提高图像的对比
- 其中,D 为原始图像的灰度值,Dt 为线性灰度变换后的图像灰度值,可知
- 直方图正规化是根据图像的最小灰度级和最大灰度级,将其拉伸到灰度级全域 [0,255] 的线性变换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24img = cv2.imread("../images/imgLena.tif") # 读取彩色图像(BGR)
imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#颜色转换BGR(OpenCV) -> Gray
h, w = img.shape[:2] # 图片的高度和宽度
img1 = np.empty((h, w), np.uint8) # 创建空白数组
img2 = np.empty((h, w), np.uint8) # 创建空白数组
img3 = np.empty((h, w), np.uint8) # 创建空白数组
img4 = np.empty((h, w), np.uint8) # 创建空白数组
img5 = np.empty((h, w), np.uint8) # 创建空白数组
img6 = np.empty((h, w), np.uint8) # 创建空白数组
#Dt[i,j] = alfa*D[i,j] + beta
alfa1, beta1 = 1, 50 # alfa=1,beta>0: 灰度值上移
alfa2, beta2 = 1, -50 # alfa=1,beta<0: 灰度值下移
alfa3, beta3 = 1.5, 0 # alfa>1,beta=0: 对比度增强
alfa4, beta4 = 0.75, 0 # 0<alfa<1,beta=0: 对比度减小
alfa5, beta5 = -0.5, 0 # alfa<0,beta=0: 暗区域变亮,亮区域变暗
alfa6, beta6 = -1, 255 # alfa=-1,beta=255: 灰度值反转
for i in range(h):
for j in range(w):
img1[i][j] = min(255, max((imgGray[i][j]+beta1), 0)) # alfa=1,beta>0: 颜色发白
img2[i][j] = min(255, max((imgGray[i][j]+beta2), 0)) # alfa=1,beta<0: 颜色发黑
img3[i][j] = min(255, max(alfa3*imgGray[i][j], 0)) # alfa>1,beta=0: 对比度增强
img4[i][j] = min(255, max(alfa4*imgGray[i][j], 0)) # 0<alfa<1,beta=0: 对比度减小
img5[i][j] = alfa5*imgGray[i][j]+beta5 # alfa<0,beta=255: 暗区域变亮,亮区域变暗
img6[i][j] = min(255, max(alfa6*imgGray[i][j]+beta6, 0)) # alfa=-1,beta=255: 灰度值反转
图像灰度的分段灰度变换?
- 分段线性变换函数可以增强图像各部分的反差,增强感兴趣的灰度区间、抑制不感兴趣的灰度级
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22imgGray = cv2.imread("../images/Fig0310b.tif", flags=0) # flags=0 读取为灰度图像
height, width = imgGray.shape[:2] # 图片的高度和宽度
rMin = 100
rMax = 200
r1, s1 = rMin, 0 # (x1,y1)
r2, s2 = rMax, 255 # (x2,y2)
imgStretch = np.empty((width, height), np.uint8) # 创建空白数组
k1 = s1 / r1 # imgGray[h,w] < r1:
k2 = (s2-s1) / (r2-r1) # r1 <= imgGray[h,w] <= r2
k3 = (255-s2) / (255-r2) # imgGray[h,w] > r2
for h in range(height):
for w in range(width):
if imgGray[h,w] < r1:
imgStretch[h,w] = k1 * imgGray[h,w]
elif r1 <= imgGray[h,w] <= r2:
imgStretch[h,w] = k2 * (imgGray[h,w] - r1) + s1
elif imgGray[h,w] > r2:
imgStretch[h,w] = k3 * (imgGray[h,w] - r2) + s2
x = [0, 96, 182, 255]
y = [0, 30, 220, 255]
plt.plot(x, y)
show_images([imgGray,imgStretch])
图像的灰度级分层?
- 灰度级分层可以突出图像中特定的灰度级区间,可以对灰度级进行分层处理
1
2
3
4
5
6
7
8
9imgGray = cv2.imread("../images/Fig0312a.tif", flags=0) # flags=0 读取为灰度图像
width, height = imgGray.shape[:2] # 图片的高度和宽度
a, b = 155, 245 # 突出 [a, b] 区间的灰度
imgLayer1 = imgGray.copy()
imgLayer1[(imgLayer1[:,:]<a) | (imgLayer1[:,:]>b)] = 0 # 其它区域:黑色
imgLayer1[(imgLayer1[:,:]>=a) & (imgLayer1[:,:]<=b)] = 255 # 灰度级窗口:白色
imgLayer2 = imgGray.copy()
imgLayer2[(imgLayer2[:,:]>=a) & (imgLayer2[:,:]<=b)] = 255 # 灰度级窗口:白色,其它区域不变
show_images([imgGray,imgLayer1,imgLayer2])
图像的比特平面分层?
- 像素值也可以表示为二进制形式,对 8bits 二进制数的每一位进行切片,可以得到 8 个比特平面,称为比特平面分层。通常,高阶比特平面包含了大量有视觉意义的数据,而低阶比特平面包含了更精细的灰度细节。因此,比特平面分层可以用于图像压缩和图像重建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16img = cv2.imread("../images/Fig0726a.tif", flags=0) # flags=0 读取为灰度图像
height, width = img.shape[:2] # 图片的高度和宽度
plt.figure(figsize=(10, 8))
for l in range(9, 0, -1):
plt.subplot(3, 3, (9-l)+1, xticks=[], yticks=[])
if l==9:
plt.imshow(img, cmap='gray'), plt.title('Original')
else:
imgBit = np.empty((height, width), dtype=np.uint8) # 创建空数组
for w in range(width):
for h in range(height):
x = np.binary_repr(img[h,w], width=8) # 以字符串形式返回输入数字的二进制表示形式
x = x[::-1]
a = x[l-1]
imgBit[h,w] = int(a) # 第 i 位二进制的值
show_images([imgBit])
OpenCV 直方图?
- 统计固定像素范围 (如:0-255) 的像素数量,并以横座标表示像素值,纵座标表示数量构建柱状图
- 灰度直方图反映了图像中的灰度分布规律,直观地表现了图像中各灰度级的占比,很好地体现出图像的亮度和对比度信息:灰度图分布居中说明亮度正常,偏左说明亮度较暗,偏右表明亮度较高;狭窄陡峭表明对比度降低,宽泛平缓表明对比度较高
- OpenCV 提供了函数 cv2. calcHist 可以计算直方图,Numpy 中的函数 np. bincount 也可以实现同样的功能
1
2
3
4
5img = cv2.imread("../images/imgLena.tif", flags=0)
histCV = cv2.calcHist([img], [0], None, [256], [0, 256])
histNP, bins = np.histogram(img.flatten(), 256)
plt.bar(range(256), histCV[:,0])
plt.bar(bins[:-1], histNP)
OpenCV 直方图均衡?
- 直方图均衡化是将原始图像通过函数变换,调控图像的灰度分布,得到直方图分布合理的新图像,以此来调节图像亮度、增强动态范围偏小的图像的对比度
- 由于人眼视觉特性,直方图均匀分布的图像视觉效果较好。直方图均衡化的基本思想是对图像中占比大的灰度级进行展宽,而对占比小的灰度级进行压缩,使图像的直方图分布较为均匀,扩大灰度值差别的动态范围,从而增强图像整体的对比度
- 直方图均衡化就是对图像进行非线性拉伸,重新分配图像像素值,本质上是根据直方图对图像进行线性或非线性灰度变换。OpenCV 使用 cv: : equalizeHist 实现直方图均衡化
1
2
3
4
5
6
7img = cv2.imread("../images/Fig0310b.tif", flags=0)
imgEqu = cv2.equalizeHist(img) # 直方图均衡化变换
show_images([img,imgEqu])
histImg, bins = np.histogram(img.flatten(), 256)
histEqu, bins = np.histogram(imgEqu.flatten(), 256)
plt.bar(bins[:-1], histImg)
plt.bar(bins[:-1], histEqu)
OpenCV 的直方图匹配?
- 直方图匹配又称为直方图规定化,是指将图像的直方图调整为规定的形状。例如,将一幅图像或某一区域的直方图匹配到另一幅影像上,使两幅影像的色调保持一致。就需要在直方图均衡的基础上,再进行一次反变换,将均匀形状的直方图调整为规定的形状
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28img = cv2.imread("../images/imgGaia.tif", flags=0)
imgRef = cv2.imread("../images/Fig0307a.tif", flags=0) # 匹配模板
#imgOut = calcHistMatch(img, imgRef) # 子程序:直方图匹配
#计算累计直方图
histImg, bins = np.histogram(img.flatten(), 256) # 原始图像直方图
histRef, bins = np.histogram(imgRef.flatten(), 256) # 匹配模板直方图
cdfImg = histImg.cumsum() # 计算原始图像累积分布函数 CDF
cdfRef = histRef.cumsum() # 计算匹配模板累积分布函数 CDF
#计算直方图匹配转换函数
transM = np.zeros(256)
for i in range(256):
index = 0
vMin = np.fabs(cdfImg[i] - cdfRef[0])
for j in range(256):
diff = np.fabs(cdfImg[i] - cdfRef[j])
if (diff < vMin):
index = int(j)
vMin = diff
transM[i] = index
#直方图匹配
#imgOut = np.zeros_like(img)
imgOut = transM[img].astype(np.uint8)
show_image([img,imgRef,imgOut])
histImg, bins = np.histogram(img.flatten(), 256)
plt.bar(bins[:-1], histImg)
histRef, bins = np.histogram(imgRef.flatten(), 256)
plt.bar(bins[:-1], histRef)
histOut, bins = np.histogram(imgOut.flatten(), 256)
OpenCV 直方图彩色直方图匹配?
- 直方图匹配又称为直方图规定化,是指将图像的直方图调整为规定的形状。例如,将一幅图像或某一区域的直方图匹配到另一幅影像上,使两幅影像的色调保持一致
- OpenCV 对于彩色图像的直方图匹配,需要对各个颜色通道分别进行运算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22img = cv2.imread("../images/imgGaia.tif", flags=1) # flags=1 读取为彩色图像
imgRef = cv2.imread("../images/imgLena.tif", flags=1) # 匹配模板图像 (matching template)
_, _, channel = img.shape
imgOut = np.zeros_like(img)
for i in range(channel):
print(i)
histImg, _ = np.histogram(img[:,:,i], 256) # 计算原始图像直方图
histRef, _ = np.histogram(imgRef[:,:,i], 256) # 计算匹配模板直方图
cdfImg = np.cumsum(histImg) # 计算原始图像累积分布函数 CDF
cdfRef = np.cumsum(histRef) # 计算匹配模板累积分布函数 CDF
for j in range(256):
tmp = abs(cdfImg[j] - cdfRef)
tmp = tmp.tolist()
index = tmp.index(min(tmp)) # find the smallest number in tmp, get the index of this number
imgOut[:,:,i][img[:,:,i]==j] = index
show_images([img,imgRef,imgOut])
histImg, bins = np.histogram(img.flatten(), 256)
plt.bar(bins[:-1], histImg)
histRef, bins = np.histogram(imgRef.flatten(), 256)
plt.bar(bins[:-1], histRef)
histOut, bins = np.histogram(imgOut.flatten(), 256)
plt.bar(bins[:-1], histOut)
OpenCV 对比度受限直方图均衡?
- 直方图均衡考虑了全局的对比度,在许多情况下不能很好适应,如下图直方图均衡后背景对比度有所改善。但是比较两幅图像中雕像的脸。由于亮度过高,我们丢失了大部分信息
- 自适应直方图均衡,首先图像被划分为小块。然后,这些块中像往常一样均衡直方图。如此将直方图限制在一个小区域。但是如果存在噪音,它将被放大,为避免这种情况,将应用对比度限制,即如果任何直方图条柱高于指定的对比度限制 (如 40),则在应用直方图均衡之前,这些像素将被裁剪并均匀分布到其他条柱。均衡后,要删除图块边框中的伪影,将应用双线性插值
- cv2. createCLAHE 是一种限制对比度自适应直方图均衡化方法,采用了限制直方图分布的方法和加速的插值方法
1
2
3
4
5img = cv2.imread("../images/FigClahe.jpg", flags=0)
imgEqu = cv2.equalizeHist(img) # 全局直方图均衡化
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(4,4))
imgLocalEqu = clahe.apply(img) # 自适应的局部直方图均衡化
show_images([img,imgEqu,imgLocalEqu])
OpenCV 的直方图统计量图像增强?
- 直方图统计量图像增强,是基于直方图的统计量信息(如均值和方差)对图像的灰度和对比度进行调整。直方图统计量不仅用于图像的全局增强,在图像局部增强中更加有效
- 局部均值和方差是根据像素邻域特征进行灰度调整的基础。像素邻域的局部均值是平均灰度的测度,局部方差是对比度的测度。使用局部均值和方差可以开发出简单而强大的图像局部增强算法
1
2
3
4
5
6img = cv2.imread("../images/Fig0326a.tif", flags=0) # flags=0 读取为灰度图像
imgROI = img[12:120, 12:120]
maxImg, maxROI = img.max(), imgROI.max()
const = maxImg / maxROI
imgHSE = enhanceHistStat(img, const) # 子程序:直方图统计量增强 (自定义方法)
show_images([img,imgEqu,imgHSE])
OpenCV 直方图的反向投影?
- 直方图在一定程度上可以反应图像的特征,我们截取一个有固定特征的样例,比如草地,然后计算该块草地的直方图,然后用这个直方图去和整幅图像的直方图做对比,根据一定的判断条件,就能得出相似的即为草地
- 直方图反向投影处理的过程,首先建立模板区域的直方图,再将直方图投影到输入图像,计算输入图像中每个像素点的像素值与直方图匹配概率,得到概率图像并以一定阈值进行二值化处理。OpenCV 使用 cv: : calcBackProject 执行直方图的反向投影
1
2
3
4
5
6
7
8
9
10
11
12roi = cv2.imread("../images/BallFrag.png", flags=1) # 查找的图像区域
target = cv2.imread("../images/imgBall.png", flags=1) # 被查找的目标图像
hsvRoi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
hsvTar = cv2.cvtColor(target, cv2.COLOR_BGR2HSV)
histRoi = cv2.calcHist([hsvRoi], [0, 1], None, [180, 256], [0, 180, 0, 256]) # 计算目标直方图
cv2.normalize(histRoi, histRoi, 0, 255, cv2.NORM_MINMAX) # 归一化 ->[0,255]
dst = cv2.calcBackProject([hsvTar], [0, 1], histRoi, [0, 180, 0, 256], 1) # 反向投影
disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) # 定义椭圆结构形状
imgConv = cv2.filter2D(dst, -1, disc) # 图像卷积
ret, thresh = cv2.threshold(imgConv, 100, 255, 0) # 图像二值化处理,得到掩模模板
imgTrack = cv2.bitwise_and(target, target, mask=thresh) # 以 thresh 为掩模按位与,显示查找区域
show_images([target,thresh,imgTrack])
什么是对比度?
- 即图像特征之间的颜色或灰度差异程度,具有较高对比度的图像通常比具有较低对比度的图像显示更多的颜色或灰度变化。换句话说,对比度增强完成以下操作 :1)使图像中的明亮区域更亮;2)使图像中的暗区变暗
- 图像对比度增强是现实世界机器视觉系统和深度学习即服务中最重要的预处理步骤之一
对比度增强的方法?
- 全局增强:将相同的输出强度值分配给具有相同输入值的所有像素,而不管它们在图像中的位置。如线性变换、对比度拉伸、直方图均衡、形态学
- 局部增强:根据每个像素的空间邻域的特征调整强度。已经证明,本地算法通常提供更好的结果。如自适应直方图均衡,对比度受限的直方图均衡
什么是图像灰度值的 “伽马变换”?
- 伽马变换 (非线性):为了避免输出图像中的亮度过高,使用了非线性变换,γ 的值在对比度增强中起着至关重要的作用,当 γ<1 时,暗像素将被转换为亮像素,反之亦
- 伽马变换通过非线性变换对人类视觉特性进行补偿,最大化地利用有效的灰度级带宽。很多拍摄、显示、打印设备的亮度曲线都符合幂律曲线,因此伽马变换广泛应用于各种设备显示效果的调校,称为伽马校正
什么是图像灰度值的 “对数变换”?
- 对数曲线在像素值较低的区域斜率大,在像素值较高的区域斜率小。对数变换将输入中范围较窄的低灰度值映射为范围较宽的灰度级,输入中的高灰度值则被映射为范围较窄的灰度级。对数变换后,较暗区域的对比度提升,可以增强图像的暗部细
- 对数变换实现了扩展低灰度值而压缩高灰度值的效果,广泛应用于频谱图像的显示中
什么是图像灰度值的 “倒高斯变换(非线性)”?
- 倒高斯(非线性):倒高斯校正,其中 σ 控制高斯核的形
什么是图像灰度值的 “对比度拉伸”?
- 从它包含的强度值范围被拉伸到跨越所需的值范围 [0, val
pixel = ((pixel – min) / (max – min))*val$$ 。
1 | #Create an empty array to store the final output |
- 其中 R 是结果图像,I 是输入图像,T 和 B 分别是顶帽和黑帽变换,上图分别是原图、(5 x 5)、(15 x 15)、(30 x 30) kernel size 情况下的结果
参考:
- 【OpenCV 例程 200 篇】37. 图像的灰度化处理和二值化处理(cv2.threshold)_1. 对获取图像进行合适大小变换后,双边滤波、灰度化、二值化并得到轮廓 (可以结合 hs_youcans_的博客 - CSDN 博客
- 【OpenCV 例程 200 篇】38. 图像的反色变换(图像反转)_opencv 黑白色反转_youcans_的博客 - CSDN 博客
- 【OpenCV 例程 200 篇】39. 图像灰度的线性变换_灰度线性变换例题_youcans_的博客 - CSDN 博客
- What is Image contrast enhancement ? - saiwa
- Types of Contrast Enhancement Algorithms and Implementation in Python
- A Quick Overview of Contrast Enhancement and Its Variants for Medical Image Processing | by Sunil Yadav | Medium
- Contrast Enhancement of Grayscale Images Using Morphological Operators | by Shivanee Jaiswal | Towards Data Science
- 【OpenCV 例程 300 篇】40. 图像分段线性灰度变换_youcans_的博客 - CSDN 博客
- 【OpenCV 例程 200 篇】41. 图像的灰度变换(灰度级分层)_opencv 灰度变换_youcans_的博客 - CSDN 博客
- 【OpenCV 例程 200 篇】42. 图像的灰度变换(比特平面分层)_bit plane slicing_youcans_的博客 - CSDN 博客
- 【OpenCV 例程 200 篇】43. 图像的灰度变换(对数变换)_opencv 对数变换_youcans_的博客 - CSDN 博客
- 【OpenCV 例程 200 篇】44. 图像的灰度变换(伽马变换)_opencv 伽马变换_youcans_的博客 - CSDN 博客
- 【OpenCV 例程 200 篇】45. 图像的灰度直方图(cv2.calcHist)_计算灰度直方图_youcans_的博客 - CSDN 博客
- 【OpenCV 例程 300 篇】46. 直方图处理之直方图均衡化(cv2.equalizeHist)_youcans_的博客 - CSDN 博客
- 【OpenCV 例程 300 篇】47. 直方图处理之直方图匹配_youcans_的博客 - CSDN 博客
- 【OpenCV 例程 300 篇】48. 直方图处理之彩色直方图匹配_youcans_的博客 - CSDN 博客
- 【OpenCV 例程 300 篇】49. 直方图处理之局部直方图处理(cv2.createCLAHE)_youcans_的博客 - CSDN 博客
- 【OpenCV 例程 300 篇】50. 直方图处理之直方图统计量图像增强_youcans_的博客 - CSDN 博客
- 【OpenCV 例程 200 篇】51. 直方图处理之直方图反向追踪(cv2.calcBackProject)_youcans_的博客 - CSDN 博客