3.4 图像灰度变换之分段线性变换

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

在前面章,我们介绍了什么是图像的灰度变换,以及灰度变换中的图像反转,对数变换和幂律变换。图像变换的函数式可以表示如下: g ( x , y ) = F ( f ( x , y ) ) f ( x , y ) 是位于图像点 ( x , y ) 处的像素的值 g(x,y)=F(f(x,y))\quad f(x,y)是位于图像点(x,y)处的像素的值 g(x,y)=F(f(x,y))f(x,y)是位于图像点(x,y)处的像素的值这一章我们来讨论图像的分段线性变换。


1. 原理

分段线性变换,顾名思义就是针对不同灰度级采用不同的变换函数,变换函数如下:
g ( x , y ) = { F 1 ( f ( x , y ) ) f ( x , y ) < x 1 F 2 ( f ( x , y ) ) x 1 ≤ f ( x , y ) < x 2 F 3 ( f ( x , y ) ) f ( x , y ) ≥ x 2 g(x,y) = \begin{cases} F_1(f(x,y)) & f(x,y)<x1 \\ F_2(f(x,y)) & x1 \leq f(x,y) < x2 \\ F_3(f(x,y)) & f(x,y) \geq x2 \\ \end{cases} g(x,y)= F1(f(x,y))F2(f(x,y))F3(f(x,y))f(x,y)<x1x1f(x,y)<x2f(x,y)x2 其中 f ( x , y ) 是位于图像点 ( x , y ) 处的像素的值 , g ( x , y ) 是变换后的像素值, F 1 , F 2 , F 3 是不同定义域的变换函数 其中\quad f(x,y)\quad 是位于图像点(x,y)处的像素的值, \quad g(x,y)是变换后的像素值,F_1,F_2,F_3是不同定义域的变换函数 其中f(x,y)是位于图像点(x,y)处的像素的值,g(x,y)是变换后的像素值,F1,F2,F3是不同定义域的变换函数

1.1 代码实现

import numpy as np
import matplotlib.pyplot as plt


def linear(p1, p2, x):
    """
    计算通过两个点定义的直线函数在给定定义域内的值域。
    参数:
    p1: 第一个点的坐标 (x1, y1)
    p2: 第二个点的坐标 (x2, y2)
    x: 定义域的范围
    """

    # 计算斜率和截距
    k = (p2[1] - p1[1]) / (p2[0] - p1[0])
    b = p1[1] - k * p1[0]

    # 计算定义域内的最小值和最大值
    y = k * x[:] + b

    return np.uint8(y)


if __name__ == '__main__':
    # 先取两个点,用来确定分段函数的中间线段
    r1, s1 = 60, 20
    r2, s2 = 180, 230

    # 定义 x 的范围
    x1 = np.arange(0, r1)  # 第一段函数的定义域
    x2 = np.arange(r1, r2)  # 第二段函数的定义域
    x3 = np.arange(r2, 255)  # 第三段函数的定义域

    y1 = linear((0, 0), (r1, s1), x1)
    y2 = linear((r1, s1), (r2, s2), x2)
    y3 = linear((r2, s2), (255, 255), x3)

    # 绘制图像
    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.rcParams['font.size'] = 15
    plt.figure(figsize=(10, 10))
    plt.xlabel('输入值')
    plt.ylabel('输出值')
    plt.title('分段线性变换')
    plt.xlim(0, 255)  # 设置 x 轴的范围
    plt.ylim(0, 255)  # 设置 y 轴的范围
    plt.grid(True)

    plt.plot(x1, y1, label='第一段', color='blue')
    plt.plot(x2, y2, label='第二段', color='green')
    plt.plot(x3, y3, label='第三段', color='red')

    plt.text(r1, s1, '({0}, {1})'.format(r1, s1), verticalalignment='bottom', horizontalalignment='right')
    plt.text(r2, s2, '({0}, {1})'.format(r2, s2), verticalalignment='bottom', horizontalalignment='right')

    plt.legend()
    plt.savefig("image/tmp.png")
    plt.show()

1.2 效果图

在这里插入图片描述
上图中的变换函数如下:
g ( x , y ) = { 1 3 f ( x , y ) 0 < f ( x , y ) < 60 7 4 f ( x , y ) − 85 60 < f ( x , y ) < 180 29 19 f ( x , y ) − 2550 19 180 < f ( x , y ) < 255 g(x,y) = \begin{cases} \frac{1}{3}f(x,y) & 0 < f(x,y)<60 \\ \frac{7}{4}f(x,y)-85 & 60< f(x,y) < 180 \\ \frac{29}{19}f(x,y)-\frac{2550}{19} & 180 < f(x,y)<255 \\ \end{cases} g(x,y)= 31f(x,y)47f(x,y)851929f(x,y)1925500<f(x,y)<6060<f(x,y)<180180<f(x,y)<255 其中 f ( x , y ) 是位于图像点 ( x , y ) 处的像素的值 , g ( x , y ) 是变换后的像素值 其中\quad f(x,y)\quad 是位于图像点(x,y)处的像素的值, \quad g(x,y)是变换后的像素值 其中f(x,y)是位于图像点(x,y)处的像素的值,g(x,y)是变换后的像素值

2. 示例

2.1 示例一:对比度拉伸

图像对比度小的特点是,像素值分布比较集中,比如下面的例子中像素值的最小值是93,最大值是138,也就是整个像素都集中在(93 ~ 138),这种情况像素的对比度非常小,因此我们才会考虑拉伸图像的对比度,即将像素值的范围拉大到(0 ~ 255),其分段变换函数为:
g ( x , y ) = { 0 f ( x , y ) = 93 F ( f ( x , y ) ) 93 ≤ f ( x , y ) < 138 255 f ( x , y ) = 138 g(x,y) = \begin{cases} 0 & f(x,y)=93 \\ F(f(x,y)) & 93 \leq f(x,y) < 138 \\ 255 & f(x,y) = 138 \\ \end{cases} g(x,y)= 0F(f(x,y))255f(x,y)=9393f(x,y)<138f(x,y)=138

2.1.1 示例代码

import numpy as np
import matplotlib.pyplot as plt
import cv2 as cv


def contrastStretch(p1, p2, img):
    dst = np.zeros_like(img)

    # 分段函数中,中间那段的斜率和截距
    k = (p2[1] - p1[1]) / (p2[0] - p1[0])
    b = p1[1] - k * p1[0]

    for i in range(img.shape[0]):
        for j in range(img.shape[1]):
            if img[i, j] <= p1[0]:
                dst[i, j] = 0
            elif img[i, j] >= p2[0]:
                dst[i, j] = 255
            else:
                dst[i, j] = k * img[i, j] + b

    return np.uint8(dst)


if __name__ == '__main__':

    # 读取灰度图像
    imgGray = cv.imread('Image/Fig0305.tif', 0)

    min, max = np.min(imgGray), np.max(imgGray)

    img1 = contrastStretch((min, 0), (max, 255), imgGray)
    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.figure(figsize=(7, 4))
    titleList = ["Original Image", "对比度拉伸"]
    imageList = [imgGray, img1]
    for i in range(2):
        plt.subplot(1, 2, i + 1), plt.title(titleList[i]), plt.axis('off')
        plt.imshow(imageList[i], vmin=0, vmax=255, cmap='gray')
    plt.tight_layout()
    plt.savefig("Image/tmp.png")
    plt.show()

2.1.2 示例效果

在这里插入图片描述
从上面的效果图可以看出,原图的对比度非常低,因为原图的像素值范围是(93 ~ 138),右边是拉伸对比度后的效果图( (93 ~ 138) -> (0, 255) ),可以看出拉伸后的效果非常好。

2.2 示例二:灰度级分层

图像的对比度小,我们并不需要拉伸整个对比度,只需要对关注的区域(或者说某个区间的像素)进行变换处理(比如增强,也就是变亮处理),其分段变换函数为:
g ( x , y ) = { F 1 ( f ( x , y ) ) f ( x , y ) < 155 255 155 ≤ f ( x , y ) < 245 F 2 ( f ( x , y ) ) f ( x , y ) > = 245 g(x,y) = \begin{cases} F_1(f(x,y)) & f(x,y)<155 \\ 255 & 155 \leq f(x,y) < 245 \\ F_2(f(x,y)) & f(x,y) >= 245 \\ \end{cases} g(x,y)= F1(f(x,y))255F2(f(x,y))f(x,y)<155155f(x,y)<245f(x,y)>=245 ( 155 , 245 ) 是我们需要增强的区域,也就是像素值属于这一区间的区域 (155,245)是我们需要增强的区域,也就是像素值属于这一区间的区域 (155,245)是我们需要增强的区域,也就是像素值属于这一区间的区域

2.2.1 示例代码

import numpy as np
import matplotlib.pyplot as plt
import cv2 as cv


def contrastStretch1(v1, v2, img):
    dst = img.copy()
    # 像素值在区间 (v1, v2) 范围之内的,直接变换为 255
    # 像素值不在该区间的,不做处理
    dst[(img[:, :] >= v1) & (img[:, :] <= v2)] = 255

    return np.uint8(dst)


def contrastStretch2(v1, v2, img):
    dst = np.zeros_like(img)
    # 像素值在区间 (v1, v2) 范围之内的,直接变换为 255
    # 像素值不在该区间的,不做处理
    dst[(img[:, :] >= v1) & (img[:, :] <= v2)] = 255

    return np.uint8(dst)


if __name__ == '__main__':

    # 读取灰度图像
    imgGray = cv.imread('Image/Fig0306.tif', 0)

    min, max = 155, 245

    img1 = contrastStretch1(min, max, imgGray)
    img2 = contrastStretch2(min, max, imgGray)

    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.figure(figsize=(9, 4))
    titleList = ["Original Image", "灰度级分层", "二值化"]
    imageList = [imgGray, img1, img2]
    for i in range(3):
        plt.subplot(1, 3, i + 1), plt.title(titleList[i]), plt.axis('off')
        plt.imshow(imageList[i], vmin=0, vmax=255, cmap='gray')
    plt.tight_layout()
    plt.savefig("Image/tmp.png")
    plt.show()

2.2.2 示例效果

在这里插入图片描述

2.3 示例三:比特平面分层

像素值可以转换成8位二进制,如果图像的所有像素值都转为8位,那么空间上我们就可以得到8个比特位图像,这个就叫比特平面分层。

2.3.1 示例代码

import numpy as np
import matplotlib.pyplot as plt
import cv2 as cv


def bitPlane(img, bit):
    dst = np.zeros(img.shape, np.uint8)

    for i in range(img.shape[0]):
        for j in range(img.shape[1]):
            bitStr = np.binary_repr(img[i, j], width=8)  # 将像素值转为 8 位二进制
            bitNum = int(bitStr[bit - 1])  # 获取第 bit 位的标识
            if bitNum == 1:
                dst[i, j] = 255

    return dst


if __name__ == '__main__':

    # 读取灰度图像
    imgGray = cv.imread('Image/Fig0307.tif', 0)

    img1 = bitPlane(imgGray, 1)
    img2 = bitPlane(imgGray, 2)
    img3 = bitPlane(imgGray, 3)
    img4 = bitPlane(imgGray, 4)
    img5 = bitPlane(imgGray, 5)
    img6 = bitPlane(imgGray, 6)
    img7 = bitPlane(imgGray, 7)
    img8 = bitPlane(imgGray, 8)

    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.figure(figsize=(10, 5))
    titleList = ["Original Image", "第一位", "第二位", "第三位", "第四位", "第五位", "第六位", "第七位", "第八位"]
    imageList = [imgGray, img1, img2, img3, img4, img5, img6, img7, img8]
    for i in range(9):
        plt.subplot(3, 3, i + 1), plt.title(titleList[i]), plt.axis('off')
        plt.imshow(imageList[i], vmin=0, vmax=255, cmap='gray')
    plt.tight_layout()
    plt.savefig("Image/tmp.png")
    plt.show()

2.3.2 示例效果

在这里插入图片描述
通过上图比较可以发现,高比特位平面有大量具有视觉意义的数据,大家自己思考一下为什么比特位分层为什么是分段线性变换。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值