【OpenCV】Chapter4.灰度变换与直方图

2022-09-22 16:20:14 浏览数 (1)

最近想对OpenCV进行系统学习,看到网上这份教程写得不错,于是跟着来学习实践一下。 【youcans@qq.com, youcans 的 OpenCV 例程, https://youcans.blog.csdn.net/article/details/125112487】 程序仓库:https://github.com/zstar1003/OpenCV-Learning

图像二值化

二值图像指的是只有黑色和白色两种颜色的图像。每个像素点可以用 0/1 表示,0 表示黑色,1 表示白色。 OpenCV提供了cv2.threshold,可以对图像进行二值化处理。

cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst

参数说明:

  • scr:变换操作的输入图像,nparray 二维数组,必须是单通道灰度图像!
  • thresh:阈值,取值范围 0~255
  • maxval:填充色,取值范围 0~255,一般取 255
  • type:变换类型
    • cv2.THRESH_BINARY:大于阈值时置 255,否则置 0
    • cv2.THRESH_BINARY_INV:大于阈值时置 0,否则置 255
    • cv2.THRESH_TRUNC:大于阈值时置为阈值 thresh,否则不变(保持原色)
    • cv2.THRESH_TOZERO:大于阈值时不变(保持原色),否则置 0
    • cv2.THRESH_TOZERO_INV:大于阈值时置 0,否则不变(保持原色)
    • cv2.THRESH_OTSU:使用 OTSU 算法选择阈值
  • 返回值 retval:返回二值化的阈值
  • 返回值 dst:返回阈值变换的输出图像

示例程序:

代码语言:javascript复制
"""
图像二值化
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


imgGray = cv2.imread("../img/img.jpg", flags=0)  # flags=0 读取为灰度图像

ret1, img1 = cv2.threshold(imgGray, 63, 255, cv2.THRESH_BINARY)  # 转换为二值图像, thresh=63
ret2, img2 = cv2.threshold(imgGray, 127, 255, cv2.THRESH_BINARY)  # 转换为二值图像, thresh=127
ret3, img3 = cv2.threshold(imgGray, 191, 255, cv2.THRESH_BINARY)  # 转换为二值图像, thresh=191
ret4, img4 = cv2.threshold(imgGray, 127, 255, cv2.THRESH_BINARY_INV)  # 逆二值图像,BINARY_INV
ret5, img5 = cv2.threshold(imgGray, 127, 255, cv2.THRESH_TRUNC)  # TRUNC 阈值处理,THRESH_TRUNC
ret6, img6 = cv2.threshold(imgGray, 127, 255, cv2.THRESH_TOZERO)  # TOZERO 阈值处理,THRESH_TOZERO

plt.figure(figsize=(9, 6))
titleList = ["1. BINARY(thresh=63)", "2. BINARY(thresh=127)", "3. BINARY(thresh=191)", "4. THRESH_BINARY_INV",
             "5. THRESH_TRUNC", "6. THRESH_TOZERO"]
imageList = [img1, img2, img3, img4, img5, img6]
for i in range(6):
    plt.subplot(2, 3, i   1), plt.title(titleList[i]), plt.axis('off')
    plt.imshow(imageList[i], 'gray')  # 灰度图像 ndim=2
plt.show()

反色变换

图像的反色变换,即图像反转,将黑色像素点变白色,白色像素点变黑色。广义的反色变换也可以应用于彩色图像,即对所有像素点取补。

反色变换实现起来比较简单,遍历每个像素点,取反即可。

代码语言:javascript复制
"""
反色变换
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


img = cv2.imread("../img/img.jpg")  # 读取彩色图像(BGR)
imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 颜色转换:BGR(OpenCV) -> Gray
h, w = img.shape[:2]  # 图片的高度和宽度

imgInv = np.empty((w, h), np.uint8)  # 创建空白数组
for i in range(h):
    for j in range(w):
        imgInv[i][j] = 255 - imgGray[i][j]

plt.figure(figsize=(10, 6))
plt.subplot(131), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title("imgBGR"), plt.axis('off')
plt.subplot(132), plt.imshow(imgGray, cmap='gray'), plt.title("imgGray"), plt.axis('off')
plt.subplot(133), plt.imshow(imgInv, cmap='gray'), plt.title("imgInv"), plt.axis('off')
plt.show()

灰度线性变换

灰度线性变换主要是指对图像的每一个像素作线性拉伸,可以凸显图像的细节,提高图像的对比度。 变换公式:

式中,D 为原始图像的灰度值,Dt 为线性灰度变换后的图像灰度值。

  • 当 α = 1 , β = 0时,保持原始图像不变
  • 当 α = 1 , β > 0时,图像的灰度值上移,灰度图像颜色发白(彩色图像颜色发亮)
  • 当 α = 1 , β < 0时,图像的灰度值下移,灰度图像颜色发黑(彩色图像颜色发暗)
  • 当 α > 1 时,图像的对比度增强
  • 当 0 < α < 1 时,图像的对比度减小
  • 当 α < 0 , β = 255时,图像暗区域变亮,亮区域变暗,图像求补
  • 当 α = − 1 , β = 255时,图像的灰度值反转

示例程序:

代码语言:javascript复制
"""
灰度线性变换
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np

img = cv2.imread("../img/img.jpg")  # 读取彩色图像(BGR)
imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 颜色转换:BGR(OpenCV) -> Gray
h, w = img.shape[:2]  # 图片的高度和宽度
img1 = np.empty((w, h), np.uint8)  # 创建空白数组
img2 = np.empty((w, h), np.uint8)  # 创建空白数组
img3 = np.empty((w, h), np.uint8)  # 创建空白数组
img4 = np.empty((w, h), np.uint8)  # 创建空白数组
img5 = np.empty((w, h), np.uint8)  # 创建空白数组
img6 = np.empty((w, h), 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: 灰度值反转

plt.figure(figsize=(10, 6))
titleList = ["1. imgGray", "2. beta=50", "3. beta=-50", "4. alfa=1.5", "5. alfa=0.75", "6. alfa=-0.5"]
imageList = [imgGray, img1, img2, img3, img4, img5]
for i in range(6):
    plt.subplot(2, 3, i   1), plt.title(titleList[i]), plt.axis('off')
    plt.imshow(imageList[i], vmin=0, vmax=255, cmap='gray')
plt.show()

伽马变换

伽马变换也称幂律变换,可以提升暗部细节,对发白(曝光过度)或过暗(曝光不足)的图片进行矫正。 公式:

0< gamma <1

时,拉伸图像中灰度级较低的区域,压缩灰度级较高的部分,增加图像的对比度

gamma >1

时,拉伸图像中灰度级较高的区域,压缩灰度级较低的部分,降低图像的对比度。

示例程序:

代码语言:javascript复制
"""
伽马变换
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


img = cv2.imread("../img/img.jpg", flags=0)  # flags=0 读取为灰度图像
gammaList = [0.125, 0.25, 0.5, 1.0, 2.0, 4.0]  # gamma 值
normImg = lambda x: 255. * (x - x.min()) / (x.max() - x.min()   1e-6)  # 归一化为 [0,255]

plt.figure(figsize=(9, 6))
for k in range(len(gammaList)):
    imgGamma = np.power(img, gammaList[k])
    imgGamma = np.uint8(normImg(imgGamma))

    plt.subplot(2, 3, k   1), plt.axis('off')
    plt.imshow(imgGamma, cmap='gray', vmin=0, vmax=255)
    plt.title(f"$gamma={gammaList[k]}$")
plt.show()

灰度直方图

灰度直方图是反映图像像素分布的统计图,横坐标代表像素值的取值区间,纵坐标代表每一像素值在图像中的像素总数或者所占的百分比。

灰度图分布居中说明亮度正常,偏左说明亮度较暗,偏右表明亮度较高;狭窄陡峭表明对比度较低,宽泛平缓表明对比度较高。

有两种方式可以灰度直方图,第一种方式是使用OpenCV提供的cv2.calcHist函数,另一种方式是使用Numpy的np.histogram函数。

示例程序:

代码语言:javascript复制
"""
灰度直方图
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np

img = cv2.imread("../img/lena.jpg", flags=0)  # flags=0 读取为灰度图像

histCV = cv2.calcHist([img], [0], None, [256], [0, 256])  # OpenCV 函数 cv2.calcHist
histNP, bins = np.histogram(img.flatten(), 256)

plt.figure(figsize=(10, 3))
plt.subplot(131), plt.imshow(img, cmap='gray', vmin=0, vmax=255), plt.title("Original"), plt.axis('off')
plt.subplot(132, xticks=[], yticks=[]), plt.axis([0, 255, 0, np.max(histCV)])
plt.bar(range(256), histCV[:, 0]), plt.title("Gray Hist(cv2.calcHist)")
plt.subplot(133, xticks=[], yticks=[]), plt.axis([0, 255, 0, np.max(histCV)])
plt.bar(bins[:-1], histNP), plt.title("Gray Hist(np.histogram)")
plt.show()

直方图均衡化

直方图均衡化的基本思想是对图像中占比大的灰度级进行展宽,而对占比小的灰度级进行压缩,使图像的直方图分布较为均匀,扩大灰度值差别的动态范围,从而增强图像整体的对比度。

OpenCV 提供了函数cv2.equalizeHist可以实现直方图均衡化。

示例代码:

代码语言:javascript复制
"""
直方图均衡
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


img = cv2.imread("../img/lena.jpg", flags=0)  # flags=0 读取为灰度图像

imgEqu = cv2.equalizeHist(img)  # 使用 cv2.qualizeHist 完成直方图均衡化变换

fig = plt.figure(figsize=(7, 7))
plt.subplot(221), plt.title("Original image (youcans)"), plt.axis('off')
plt.imshow(img, cmap='gray', vmin=0, vmax=255)  # 原始图像
plt.subplot(222), plt.title("Hist-equalized image"), plt.axis('off')
plt.imshow(imgEqu, cmap='gray', vmin=0, vmax=255)  # 转换图像
histImg, bins = np.histogram(img.flatten(), 256)  # 计算原始图像直方图
plt.subplot(223, yticks=[]), plt.bar(bins[:-1], histImg)  # 原始图像直方图
plt.title("Histogram of original image"), plt.axis([0, 255, 0, np.max(histImg)])
histEqu, bins = np.histogram(imgEqu.flatten(), 256)  # 计算原始图像直方图
plt.subplot(224, yticks=[]), plt.bar(bins[:-1], histEqu)  # 转换图像直方图
plt.title("Histogram of equalized image"), plt.axis([0, 255, 0, np.max(histImg)])
plt.show()

直方图规定化

直方图规定化,是指将图像的直方图调整为规定的形状。 例如,将一幅图像或某一区域的直方图匹配到另一幅影像上,使两幅影像的色调保持一致。

示例程序:

代码语言:javascript复制
"""
直方图规定化
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


img = cv2.imread("../img/lena.jpg", flags=0)  # flags=0 读取为灰度图像
imgRef = cv2.imread("../img/mb.jpg", flags=0)  # 匹配模板图像, matching template

# 计算累计直方图
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 = transM[img].astype(np.uint8)

fig = plt.figure(figsize=(10, 7))
plt.subplot(231), plt.title("Original image"), plt.axis('off')
plt.imshow(img, cmap='gray')  # 原始图像
plt.subplot(232), plt.title("Matching template"), plt.axis('off')
plt.imshow(imgRef, cmap='gray')  # 匹配模板
plt.subplot(233), plt.title("Matching output"), plt.axis('off')
plt.imshow(imgOut, cmap='gray')  # 匹配结果
histImg, bins = np.histogram(img.flatten(), 256)  # 计算原始图像直方图
plt.subplot(234, yticks=[]), plt.bar(bins[:-1], histImg)
histRef, bins = np.histogram(imgRef.flatten(), 256)  # 计算匹配模板直方图
plt.subplot(235, yticks=[]), plt.bar(bins[:-1], histRef)
histOut, bins = np.histogram(imgOut.flatten(), 256)  # 计算匹配结果直方图
plt.subplot(236, yticks=[]), plt.bar(bins[:-1], histOut)
plt.show()

局部直方图均衡化

局部直方图处理的过程是:

(1)设定某一大小的模板(矩形邻域),在图像中沿逐个像素移动;

(2)对每个像素位置,计算模板区域的直方图,对该局部区域进行直方图均衡或直方图匹配变换,变换结果只用于模板区域中心像素点的灰度值修正;

(3)模板(邻域)在图像中逐行逐列移动,遍历所有像素点,完成对整幅图像的局部直方图处理。

OpenCV 提供了类cv2.createCLAHE用于创建自适应均衡化的对象和方法,可以实现局部直方图处理。

cv2.createCLAHE([, clipLimit[, tileGridSize]]) → retval

参数说明:

  • clipLimit:颜色对比度的阈值,可选项,默认值 8
  • titleGridSize:局部直方图均衡化的模板(邻域)大小,可选项,默认值 (8,8)

示例程序:

代码语言:javascript复制
"""
局部直方图均衡化
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


img = cv2.imread("../img/lena.jpg", flags=0)  # flags=0 读取为灰度图像

imgEqu = cv2.equalizeHist(img)  # 全局直方图均衡化
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(4, 4))  # 创建 CLAHE 对象
imgLocalEqu = clahe.apply(img)  # 自适应的局部直方图均衡化

plt.figure(figsize=(9, 6))
plt.subplot(131), plt.title('Original'), plt.axis('off')
plt.imshow(img, cmap='gray', vmin=0, vmax=255)
plt.subplot(132), plt.title(f'Global Equalize Hist'), plt.axis('off')
plt.imshow(imgEqu, cmap='gray', vmin=0, vmax=255)
plt.subplot(133), plt.title(f'Local Equalize Hist'), plt.axis('off')
plt.imshow(imgLocalEqu, cmap='gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()

0 人点赞