提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
在前面章,我们介绍了什么是图像的灰度变换,以及灰度变换中的图像反转,对数变换和幂律变换。图像变换的函数式可以表示如下: 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)<x1x1≤f(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)=9393≤f(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)<155155≤f(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 示例效果
通过上图比较可以发现,高比特位平面有大量具有视觉意义的数据,大家自己思考一下为什么比特位分层为什么是分段线性变换。