Python 大白从零开始 OpenCV 学习课-6. 灰度变换与直方图处理

Posted Python小白进阶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python 大白从零开始 OpenCV 学习课-6. 灰度变换与直方图处理相关的知识,希望对你有一定的参考价值。

从零开始 OpenCV 学习课-6. 灰度变换与直方图处理

本系列面向 Python 小白,从零开始实战解说 OpenCV 项目实战。

空间域的图像处理方法直接对图像的像素点进行处理,空间域图像处理技术主要是灰度变换和空间滤波。

本节介绍图像的灰度变化与直方图处理,提供完整例程和运行结果:二值处理,反色变换,线性变换,分段线性变换,对比度拉伸,灰度级分层,非线性变换,直方图处理,直方图均衡化,直方图匹配,局部直方图处理,统计量增强,反向投影。



1. 图像增强技术

图像增强(Image Enhancement)的方法主要有空间域变换、频率域变换和伪彩色处理。

  • 空间域变换:空间域是指图像平面,空间域的图像处理方法直接对图像的像素点进行处理。空间域图像处理技术主要是灰度变换和空间滤波。
  • 频率域变换: 通过傅立叶变换,在频率域进行处理,然后再转换回空间域。
  • 伪彩色处理:把灰度图像映射到彩色空间,常用于遥感图像、医学影像处理。



2. 图像的灰度化处理和二值化处理

按照颜色对图像进行分类,可以分为二值图像、灰度图像和彩色图像。

  • 二值图像:只有黑色和白色两种颜色的图像。每个像素点可以用 0/1 表示,0 表示黑色,1 表示白色。
  • 灰度图像:只有灰度的图像。每个像素点用 8bit 数字 [0,255] 表示灰度,如:0 表示纯黑,255 表示纯白。
  • 彩色图像:彩色图像通常采用红色(R)、绿色(G)和蓝色(B)三个色彩通道的组合表示。

OpenCV 中彩色图像使用 BGR 格式。彩色图像进行灰度化处理,可以在读取图像文件时直接读取为灰度图像,也可以通过颜色空间转换函数 cv2.cvtColor 将彩色图像转换为灰度图像。

灰度化处理相关函数和例程介绍,详见 [OpenCV 学习课-2.图像读取与显示]。

# 1.1 图像的读取
    imgFile = "../images/imgLena.tif"  # 读取文件的路径
    img1 = cv2.imread(imgFile, flags=1)  # flags=1 读取彩色图像(BGR)
    img2 = cv2.imread(imgFile, flags=0)  # flags=0 读取为灰度图像

# 1.10 图像显示(plt.imshow)
    imgRGB = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)  # 图片格式转换:BGR(OpenCV) -> RGB(PyQt5)
    imGray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)  # 图片格式转换:BGR(OpenCV) -> Gray    

进一步地,通过函数 cv2.threshold 可以对图像进行二值化处理。

函数说明:

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

函数 threshold() 可以将灰度图像转换为二值图像,图像完全由像素 0 和 255 构成,呈现出只有黑白两色的视觉效果。

灰度阈值化通过选取的灰度阈值 thresh,将每个像素的灰度值与阈值进行比较,将灰度大于阈值的像素点置为最大灰度,小于阈值的像素点置为最小灰度,得到二值图像,可以突出图像轮廓,把目标从背景中分割出来。

参数说明:

  • 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:返回阈值变换的输出图像

注意:

    1. 函数 cv2.threshold 进行固定阈值的二值化处理;函数 cv2.adaptiveThreshold 为自适应阈值的二值化处理函数,可以通过比较像素点与周围像素点的关系动态调整阈值。
    1. 确切地说,只有 type 为 cv2.THRESH_BINARY 或 cv2.THRESH_BINARY_INV 时输出为二值图像,其它变换类型时进行阈值处理但并不是二值处理。

例程:1.47 图像的二值变换(固定阈值)

    # 1.47 固定阈值二值变换
    img = cv2.imread("../images/imgLena.tif")  # 读取彩色图像(BGR)
    imgGray = cv2.imread("../images/imgLena.tif", flags=0)  # flags=0 读取为灰度图像
    # imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 颜色转换:BGR(OpenCV) -> Gray

    # cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst
    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()



3. 图像的灰度变换

灰度变换是图像增强的重要方法,可以使图像动态范围扩大、图像对比度增强,图像更清晰,特征更明显,从而改善图像的显示效果。

灰度变换就是按一定规则(灰度映射函数)修改图像每一个像素的灰度值,从而改变图像灰度的动态范围。按照灰度映射函数的性质,灰度变换可以分为线性变换、分段线性和非线性变换,非线性变换中对数变换、指数变换和幂律变换(n次幂、n次根)最为常用。常见的灰度变换函数的形状如下图所示。


3.1 反色变换(图像反转)

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

图像的反转处理可以增强暗色区域中的白色或灰色细节。

注意图像反转(Invert)与图像翻转(Flip)的区别:图像翻转是沿对称轴的几何变换,像素值不变;图像反转是像素颜色的逆转,像素位置不变。

例程:1.48 图像的反色变换

    # 1.48 图像的反色变换
    img = 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]

    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()


3.2 线性灰度变换

线性灰度变换将原始图像灰度值的动态范围按线性关系扩展到指定范围或整个动态范围。

线性灰度变化对图像的每一个像素作线性拉伸,可以凸显图像的细节,提高图像的对比度。

线性灰度变换可以由以下公式描述 :
KaTeX parse error: No such environment: align at position 8: \\begin̲a̲l̲i̲g̲n̲̲ Dt &= \\fracd-…
式中,D 为原始图像的灰度值,Dt 为线性灰度变换后的图像灰度值。

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

直方图正规化是根据图像的最小灰度级和最大灰度级,将其拉伸到灰度级全域 [0,255] 的线性变换。


例程:1.49 图像的线性灰度变换

    # 1.49 图像的线性灰度变换
    img = cv2.imread("../images/imgLena.tif")  # 读取彩色图像(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()


3.3 分段线性灰度变换

分段线性变换函数可以增强图像各部分的反差,增强感兴趣的灰度区间、抑制不感兴趣的灰度级。

分段线性函数的优点是可以根据需要拉伸特征物的灰度细节,一些重要的变换只能用分段函数来描述和实现,缺点则是参数较多不容易确定。

分段线性函数通用公式如下:
D t = c a D , 0 ≤ D < a d − c b − a [ D − a ] + c , a ≤ D ≤ b f − d e − b [ D − a ] + d , b < D ≤ e Dt = \\begincases \\dfracca D &, 0 \\leq D < a\\\\ \\dfracd-cb-a[D-a]+c &, a \\leq D \\leq b\\\\ \\dfracf-de-b[D-a]+d &, b < D \\leq e\\\\ \\endcases Dt=acDbadc[Da]+cebfd[Da]+d,0D<a,aDb,b<De以上是关于Python 大白从零开始 OpenCV 学习课-6. 灰度变换与直方图处理的主要内容,如果未能解决你的问题,请参考以下文章

Python 大白从零开始 OpenCV 学习课-1.安装与环境配置

Python 大白从零开始 OpenCV 学习课-5. 图像的几何变换

Python 大白从零开始 OpenCV 学习课-3.图像的创建与修改

Python 大白从零开始 OpenCV 学习课-4.图像的叠加与混合

Python 大白从零开始 OpenCV 学习课-6. 灰度变换与直方图处理

Python 大白从零开始 OpenCV 项目实战 图像读取与显示