一、点操作
1.1 修改像素值
import cv2
import numpy as np
img=cv2.imread("test_1.jpg")
img[100,100]=[255,255,255]
print (img[100,100])
Out[1]:[255,255,255]
类似于矩阵中数据替换,但是这种方式很慢,当然能矩阵运算就不用替换方法。
所以可以用.itemset ()函数来修改
import cv2
import numpy as np
img=cv2.imread("test_1.jpg")
print(img[100,100])#输出原先的[100,100]位置的BGR参数
print(img.item(100,100,2))#输出R的参数
img.itemset((100,100,2),100)#改变R的参数 58->100
print(img.item(100,100,2))#打印[100,100]位置的R参数
Out[1]:
[45 51 58]
58
100
我们可以利用plt显示出来这个两个图像*(不过在这里显示彩色图像是不建议的,会失真)*
fig, (ax1, ax2) = plt.subplots(1, 2)
# 显示第一张图片
ax1.imshow(ori_img)
ax1.set_title('Original Image ')
ax1.axis('off') # 隐藏坐标轴
# 显示第二张图片
ax2.imshow(img)
ax2.set_title(' Point Operate Image')
ax2.axis('off') # 隐藏坐标轴
# 调整布局
plt.tight_layout()
plt.show()
点操作的影响对图像整体来说是很小的,相当于你,在整个矩阵上只改变了一个元素。
这个图像的是高为884宽为1170三通道的RGB彩色图像,仅仅改变一个像素点的影响相当于
I
F
=
1
1
,
034
,
280
IF =\frac{1}{1,034,280 }
IF=1,034,2801
影响几乎可以忽略不记。
1.2 图像算术操作
1.2.1图像加法
图像的加法操作就是矩阵相加,元素对元素,像素对像素。这里有两种利用cv库中函数的方法
cv2.add(img1,img2)(无权)
-
i m g 1 img_1 img1:被加数
-
i m g 2 img_2 img2:加数,可以是简单的标量
cv2.addWeighted( i m g 1 img_1 img1, α \alpha α, i m g 2 img_2 img2, β \beta β, γ \gamma γ)(加权)
-
i m g 2 img_2 img2:第一幅图片
-
α \alpha α:第一幅权重
-
i m g 2 img_2 img2:第二幅图片
-
β \beta β:第二幅权重
-
γ \gamma γ:附加常数
i m g = i m g 1 ∗ α + i m g 2 ∗ β + γ img=img_1*\alpha+img_2*\beta+\gamma img=img1∗α+img2∗β+γ
**注意:**Opencv 和 Numpy中的加法不一样,在超过像素上限255的情况下,就会有溢出问题。
Opencv采取饱和操作,当值超过255后直接取255
Numpy会取余,如:(250+10)%255=5,最后取得的值是5
所以图像操作中更倾向于使用cv库的函数
**例子:**这里就用上课说的提取图像的边缘来增强图像细节
1.对图像进行边缘检测(此处重点不在边缘检测,不详细阐述如何操作)
#BGR2GRAY
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#利用拉普拉斯算子卷积
grad=cv2.Laplacian(gray,-1,ksize=3)
2.将边缘检测后的图像与灰度图像相加
import cv2
import numpy as np
img=cv2.imread("test_1.jpg")
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#图像梯度
grad=cv2.Laplacian(gray,-1,ksize=3)
#图像显示
sth_img=cv2.add(gray,grad)
cv2.imshow('Strengthen Image',sth_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
**注意:**这里一定不要和img
相加,原始图像是三通道的,gray
和grad
都是单通道的。只有同一大小的图像才能相加。
1.2.2 图像减法
这个和图像加法类似,和矩阵的算法相同的。
1.2.3 图像乘法
和矩阵的数乘是相同的,只是所有的值均扩大或缩小 n n n倍。
二、图像直方图和图像直方图均衡化
2.1绘制图像直方图
图像直方图是显示图像中各灰度值(或颜色值)频率分布的图表。横轴表示灰度级(通常从0到255),纵轴表示对应灰度级的像素数量。直方图能够帮助分析图像的对比度、亮度分布和动态范围。
具体操作:
2.1.1 加载图像
我们这里使用图像处理库(OpenCV)来读取图像文件。
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
img=cv.imread('test_1.jpg')#这个img的格式是np.array
height,width,channel=img.shape #这个图像的是高为884宽为1170三通道的RGB彩色图像
cv.imshow('test', img)
In [1]img.shape
Out[1]: (884, 1170, 3)
test_1
2.1.2 转换为灰度图
这里需要分析灰度直方图,所以要将彩色图像转换为灰度图。
这里考虑将彩色图像的三个分量以不同的权值进行加权平均。
公式为:
G
r
a
y
=
0.299
R
+
0.587
G
+
0.114
B
Gray = 0.299R + 0.587G + 0.114B
Gray=0.299R+0.587G+0.114B
这个公式考虑了人眼对不同颜色的敏感度,其中绿色对亮度的贡献最大,红色次之,蓝色最少。
原先想降低库函数引用数目,不过后面还是引用了,有点画蛇添足:)
#BGR2GRA
gray_img=(0.299 * img[:, :, 0] + 0.587 * img[:, :, 1] + 0.114 * img[:, :, 2])
gray_img = np.clip(gray_img, 0, 255)#pixel的值限制在0-255的范围内
gray_img = gray_img.astype(np.uint8)#pixel转换为integer
#当然这里可以显示 gray_img
cv.imshow('test_gray', gray_img)
test_gray
2.1.3 计算直方图
这里使用numpy库中的np.histogram
函数计算灰度的直方图。
his,bin_edges= np.histogram(gray_img, bins=np.arange(256), range=(0, 256))
bins
参数指定了要创建的区间数量,即这里将gray_img中像素点的值按照(0-255)分别取出
2.1.4 绘制直方图
使用绘图库Matplotlib绘制直方图,横轴为灰度级,纵轴为像素数量
plt.figure(figsize=(10, 6))
plt.bar(bin_edges[:-1], his, width=1, edgecolor='blue', color='gray')
plt.title('Grayscale Histogram')#也不知道为什么title还有label显示不了中文了
plt.xlabel('Pixel Intensity')
plt.ylabel('Pixel Count')
plt.xlim(0, 255)
plt.grid(True, linestyle='--', alpha=0.7)
plt.show()
Historgam
## 2.2 图像直方图均衡化 图像直方图均衡化是一种用于改善图像对比度的技术。其核心原理是调整图像灰度值的分布,使得图像的亮度更加均匀,尤其适合处理光照不均匀或者对比度较低的图像。该方法通过将图像像素的灰度值重新分配,使得直方图的输出尽量平坦,图像中的细节更为清晰。
具体操作:
2.2.1 直方图规定化
上面我们已经具体得出灰度直方图,只需将直方图中具体的数取出就行
def his_pixel_sum(his, step):
bin_size = 256 // step
pixel_counts=[]
for i in range(step):
start = i * bin_size
end = (i + 1) * bin_size
pixel_sum = his[start:end].sum()
print(f'{start}-{end - 1} pixels: {pixel_sum}')
pixel_counts.append(pixel_sum)
return pixel_counts
pixel_counts=his_pixel_sum(his, 16)
Out[1]:
0-15 pixels: 29
16-31 pixels: 24986
32-47 pixels: 113675
48-63 pixels: 148112
64-79 pixels: 119634
80-95 pixels: 87436
96-111 pixels: 60576
112-127 pixels: 42908
128-143 pixels: 39399
144-159 pixels: 49553
160-175 pixels: 47962
176-191 pixels: 41019
192-207 pixels: 32533
208-223 pixels: 25440
224-239 pixels: 24156
240-255 pixels: 176862
这里我们自定义一个函数his_pixel_sum()
,它的输入his
是np.histogram
的返回值,一个(255,)的数组,step
是步幅,将256个数按照step
步走完。至于for循环,将分好的区间内像素点的总数输出出来,并且放入空列表pixel_counts
中,最终返回pixel_counts
列表。
这个函数可以实现 ( 0 − 255 ) ⟶ ( 0 − 15 ) (0-255) \stackrel{}{\longrightarrow} (0-15) (0−255)⟶(0−15)的映射,这可以使直方图规定化。
用表格的方式示例:(数据为真实数据)
灰度值 | 像素数 |
---|---|
0 | 0 |
1 | 0 |
2 | 0 |
… | … |
253 | 10280 |
254 | 137529 |
255 | 5809 |
2.2.2 计算累积分布函数 (CDF)。
**累积分布函数 (CDF,Cumulative Distribution Function) **是通过将灰度值的直方图进行累加获得的。其表达的是像素强度小于等于某个值的所有像素总数。
具体操作:
- 从第一个灰度值开始,将它的像素数记为累加值。
- 对于下一个灰度值,将当前的像素数加上上一个灰度值的累加结果,得到新的累加值。
- 依次对所有灰度值进行累加,直到遍历完所有灰度级别。
公式:
设定原始图像的直方图为 hist[i]
,累积直方图 cdf[i]
的计算公式为:
c d f [ i ] = h i s t [ 0 ] + h i s t [ 1 ] + ⋯ + h i s t [ i ] cdf[i]=hist[0]+hist[1]+⋯+hist[i] cdf[i]=hist[0]+hist[1]+⋯+hist[i]
cdf = his.cumsum()
#利用数组的.cumsum计算累加和
#cdf同样是(255,)的数组
cdf_normalized = cdf * his.max() / cdf.max()
使用绘图库Matplotlib绘制直方图,横轴为灰度级,纵轴为像素数量
plt.plot(bin_edges[:-1], cdf_normalized, color='black') # 累积直方图(CDF)
plt.title('Cumulative Distribution Function (CDF)')
plt.show()
当然我们也可以将两幅图合并观察
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.plot(bin_edges[:-1], hist, color='blue') # 原始直方图
plt.title('Original Histogram')
plt.subplot(1, 2, 2)
plt.plot(bin_edges[:-1], cdf_normalized, color='black') # 累积直方图(CDF)
plt.title('Cumulative Distribution Function (CDF)')
plt.show()
2.2.3 正则化 CDF
为了确保像素值映射到新的灰度值范围(0-255),需要将累积的结果归一化。
归一化的公式为:
c d f norm [ i ] = c d f [ i ] − c d f m i n N − c d f m i n × ( L − 1 ) cdf_{\text{norm}}[i] = \frac{cdf[i]-cdf_{min}}{N-cdf_{min} } \times (L - 1) cdfnorm[i]=N−cdfmincdf[i]−cdfmin×(L−1)
-
$cdf[i] $是第
i
灰度级别的累积分布函数值。 -
c d f m i n cdf_min cdfmin 是 CDF 中的最小非零值,用于归一化。
-
N
是图像的总像素数。 -
L
是灰度值的范围(通常是 256,对于 8 位灰度图)。这里我们可以做将像素值归一到[0-255]、[0-128]、[0-15]、[0-8]等灰度值范围内
假设这里我们归一到[0-128]范围内
# 忽略为零的值,计算最小的非零 CDF 值 cdf_m = np.ma.masked_equal(cdf, 0) # 将CDF中的0掩盖 cdf_m = (cdf_m - cdf_m.min()) * 127 / (cdf_m.max() - cdf_m.min()) # 归一化到[0, 128] cdf = np.ma.filled(cdf_m, 0).astype('uint8') # 填充掩盖的部分为0,并将类型转换为uint8
2.2.4 映射新像素值
将原始图像的像素通过归一化后的 CDF 映射到新的灰度值,这样可以实现图像的对比度增强。
# 将原图像的灰度值映射到均衡化后的值
equalized_img = cdf[img]
原始图像中的值将会换成对应的cdf中的值,也就是说在执行 cdf[img]
时,NumPy 将使用 img
中的灰度值作为索引来查找 cdf
数组中的值。
这将生成一个新的数组equalized_img
,其中的每个元素是原始图像中对应灰度值的均衡化后的值。
这个新的数组equalized_img
就是我们直方图均衡化后的图像。
equalized_img = cdf[img]
cv.imshow('equalized_img', equalized_img)
cv.waitKey(0)
cv.destroyAllWindows()
2.2.5 绘制均衡化后的直方图
绘制均衡化后的直方图和上述2.1.4绘制直方图的操作一致,这里就直接操作不再赘述了。
# 均衡化后的直方图计算
equalized_hist,bin_edges = np.histogram(equalized_img, bins=128, range=(0, 128))
#绘制
plt.plot(np.arange(0,16), equalized_hist, color='blue')
plt.title('Equalized Histogram')
plt.xlim([0, 255])
plt.xlabel('Pixel Intensity')
plt.ylabel('Pixel Count')
plt.grid(True, linestyle='--', alpha=0.7)
plt.show()
2.2.6 结果
最终我们将原始灰度图像,直方图均衡化后的图像以及原始直方图和均衡化后直方图放在一起对比。
这里我们也将灰度值区间**[0-16]和[0-255]**给出
可以发现经过均衡化后,灰度值的分布会更均匀。
三、变换
3.1 缩放变换
cv库中的**cv2.resize()**函数可以帮助我们变换图像的大小
resize( $src,dst, dsize, f_x=0, f_y=0, $ i n t e r p o l a t i o n interpolation interpolation=INTER_LINEAR )
-
s r c src src:原图
-
d s t dst dst:输出的图像
-
d s i z e dsize dsize:输出图像大小
-
f x f_x fx:x轴缩放因子
-
f y f_y fy:y轴缩放因子
-
i n t e r p o l a t i o n interpolation interpolation:缩放方式
cv2.INTER_NEAREST 最近邻插值
cv2.INTER_LINEAR 双线性插值(默认) 【放大时用】
cv2.INTER_AREA 使用像素区域关系进行重采样。【缩小时用】
cv2.INTER_CUBIC 4x4像素邻域的双3次插值
cv2.INTER_LANCZOS4 8x8像素邻域的Lanczos插值
import cv2
import numpy as np
img=cv2.imread("test_1.jpg")
smaller=cv2.resize(img,None,fx=(1/4),fy=(1/4),interpolation=cv2.INTER_AREA)
cv2.imshow('Smaller Image',smaller)
cv2.imshow('Original Image',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Original Image Smaller Image
3.2 平移变换
平移变换没有内置函数,需要自行构建变换矩阵
矩阵类型为np.float32型
可以用Numpy建立矩阵A
$$
A=\left[
\begin{matrix}
1 & 0 & dx \
0 & 1 & dy \
\end{matrix}
\right]
$$
(dx,dy)是图像对应图像中心的偏移量
例如
$$
A=\left[
\begin{matrix}
1 & 0 & 100 \
0 & 1 & 50 \
\end{matrix}
\right]
$$
代码
M=np.float32([[1,0,100],[0,1,50]])
就是向右平移100个像素点,向下平移50个像素点
利用Opencv中的内置函数**cv2.warpAffine()**对图像进行变换
d s t dst dst=cv2.warpAffine( s r c , M , d s i z e src,M,dsize src,M,dsize)
d s t dst dst:输出图像
s r c src src:输入图像
M M M:变换矩阵
d s i z e dsize dsize:输出图像大小
import cv2
import numpy as np
img=cv2.imread("test_1.jpg")
M=np.float32([[1,0,100],[0,1,50]])
x=img.shape[0]
y=img.shape[1]
out=cv2.warpAffine(img,M,(y,x))
cv2.imshow("out",out)
cv2.waitKey(0)
cv2.destroyAllWindows()
3.3 旋转变换
旋转变换是更改坐标系
逆时针旋转θ度,点(x,y)和原坐标轴夹角α度
{
x
′
=
r
∗
c
o
s
(
α
−
θ
)
y
′
=
r
∗
s
i
n
(
α
−
θ
)
\left\{ \begin{aligned} x'=r*cos(\alpha-\theta)\\ y'=r*sin(\alpha-\theta)\\ \end{aligned} \right.
{x′=r∗cos(α−θ)y′=r∗sin(α−θ)
其中
r
=
x
2
+
y
2
s
i
n
α
=
y
x
2
+
y
2
c
o
s
α
=
x
x
2
+
y
2
r=\sqrt{x^2+y^2}\\ sin\alpha=\frac {y}{\sqrt{x^2+y^2}}\\ cos\alpha=\frac{x}{\sqrt{x^2+y^2}}\\
r=x2+y2sinα=x2+y2ycosα=x2+y2x
上式拆开
$$
$$
{ x ′ = x ∗ c o s θ + y ∗ s i n θ y ′ = − x ∗ s i n θ + y ∗ c o s θ \left\{ \begin{aligned} x'=x*cos\theta+y*sin\theta\\ y'=-x*sin\theta+y*cos\theta\\ \end{aligned} \right. {x′=x∗cosθ+y∗sinθy′=−x∗sinθ+y∗cosθ
即
[
x
′
,
y
′
]
=
[
x
,
y
]
[
c
o
s
θ
−
s
i
n
θ
s
i
n
θ
c
o
s
θ
]
[x',y']=[x,y]\left[\begin{matrix}cos\theta&-sin\theta\\sin\theta&cos\theta\end{matrix}\right]
[x′,y′]=[x,y][cosθsinθ−sinθcosθ]
绕任意点旋转时对应矩阵(先将初始点移动到原点,再进行旋转)
D
=
[
α
β
(
1
−
α
)
∗
c
e
n
t
e
r
.
x
−
β
∗
c
e
n
t
e
r
.
y
β
α
β
∗
c
e
n
t
e
r
.
x
−
(
1
−
α
)
∗
c
e
n
t
e
r
.
y
]
α
=
s
c
a
l
e
∗
c
o
s
θ
β
=
s
c
a
l
e
∗
s
i
n
θ
D=\left[\begin{matrix}\alpha&\beta&(1-\alpha)*center.x-\beta*center.y\\\beta&\alpha&\beta*center.x-(1-\alpha)*center.y\end{matrix}\right]\\ \alpha=scale*cos\theta\\ \beta=scale*sin\theta\\
D=[αββα(1−α)∗center.x−β∗center.yβ∗center.x−(1−α)∗center.y]α=scale∗cosθβ=scale∗sinθ
旋转矩阵M可以由Opencv的内置函数得到
M M M=cv2.getRotationMatrix2D( c e n t e r , a n g l e , s c a l e center,angle,scale center,angle,scale)
c e n t e r center center:旋转中心,元组形式
a n g l e angle angle:旋转后的角度,角度制
s c a l e scale scale:旋转后的缩放因子
M M M:得到任意点的旋转矩阵
import cv2
import numpy as np
img=cv2.imread('test1.jpg')
rows=img.shape[0]
cols=img.shape[1]
M=cv2.getRotationMatrix2D((int((cols)/2.0),int((rows)/2.0)),45,0.5)
out=cv2.warpAffine(img,M,(cols,rows))
cv2.namedWindow('img',cv2.WINDOW_NORMAL)
cv2.resizeWindow('img',int(cols/3),int(rows/3))
cv2.imshow('img',out)
cv2.waitKey(0)
cv2.destroyAllWindows()
3.4 仿射变换
仿射变换的灵魂在于给出之前的三点和变换后三点的坐标,使得其可以对应变换
仿射变换的变换矩阵形式和旋转的相似都是2X3的矩阵
M
=
[
A
,
B
]
=
[
a
00
a
01
b
0
a
10
a
11
b
1
]
M=[A ,B]=\left[\begin{matrix}a_{00}&a_{01}&b_{0}\\ a_{10}&a_{11}&b_{1}\end{matrix}\right]
M=[A,B]=[a00a10a01a11b0b1]
其中2X2矩阵A是线性变换矩阵 2X1矩阵B是平移项
A
=
[
a
00
a
01
a
10
a
11
]
,
B
=
[
b
0
b
1
]
A=\left[\begin{matrix}a_{00}&a_{01}\\ a_{10}&a_{11}\end{matrix}\right] ,B=\left[\begin{matrix}b_{0}\\ b_{1}\end{matrix}\right]
A=[a00a10a01a11],B=[b0b1]
进过线性变换
f
(
x
)
=
A
x
+
B
f(x)=Ax+B
f(x)=Ax+B
T = A [ x y ] + B = M [ x y 1 ] T=A\left[\begin{matrix}x\\y\end{matrix}\right]+B=M\left[\begin{matrix}x\\y\\1\end{matrix}\right] T=A[xy]+B=M xy1
M M M=cv2.getAffineTransform( s r c , d s t src,dst src,dst)
M M M:仿射变换的变换矩阵
s r c src src: 输入图像中3个点的坐标
d s t dst dst:在输出图像的对应坐标
import cv2
import numpy as np
img=cv2.imread('test_1.jpg')
rows=img.shape[0]
cols=img.shape[1]
pts1 = np.float32([[50,50],[200,50],[50,200]])
pts2 = np.float32([[10,100],[200,50],[100,250]])
M=cv2.getAffineTransform(pts1,pts2)
out=cv2.warpAffine(img,M,(cols,rows))
cv2.namedWindow('img',cv2.WINDOW_NORMAL)
cv2.resizeWindow('img',int(cols/3),int(rows/3))
cv2.imshow('img',out)
cv2.waitKey(0)
cv2.destroyAllWindows()
3.5 透视变换
本质就是将图片投射到另一个平面上
[
x
′
,
y
′
,
z
′
]
=
[
u
,
v
,
w
]
[
a
00
a
01
a
02
a
10
a
11
a
12
a
20
a
21
a
22
]
[x',y',z']=[u,v,w]\left[\begin{matrix}a_{00}&a_{01}&a_{02}\\ a_{10}&a_{11}&a_{12}\\a_{20}&a_{21}&a_{22}\end{matrix}\right]
[x′,y′,z′]=[u,v,w]
a00a10a20a01a11a21a02a12a22
u v w 是原坐标,w是z轴上的大小,一般取1
(x,y)是变换后的坐标
x
=
x
′
z
′
,
y
=
y
′
z
′
x=\frac{x'}{z'},y=\frac{y'}{z'}
x=z′x′,y=z′y′
变换矩阵M分为三部分
M
=
[
a
00
a
01
a
02
a
10
a
11
a
12
a
20
a
21
a
22
]
=
[
T
1
T
2
T
3
a
22
]
M=\left[\begin{matrix}a_{00}&a_{01}&a_{02}\\ a_{10}&a_{11}&a_{12}\\a_{20}&a_{21}&a_{22}\end{matrix}\right]=\left[\begin{matrix}T_1&T_2\\ T_3&a_{22}\end{matrix}\right]
M=
a00a10a20a01a11a21a02a12a22
=[T1T3T2a22]
T1是线性变换部分,T2是平移部分,T3是透视变换部分,a22=1
用 **cv2.getPerspectiveTransform()**函数获取变换矩阵,再用 **cv2.warpPerspective()**进行矩阵乘法运算
M M M= cv2.getPerspectiveTransform( s r c , d s t src, dst src,dst)
M M M:透视变换的变换矩阵
s r c src src: 输入图像中4个点的坐标
d s t dst dst:在输出图像的对应坐标
d s t dst dst = cv2.warpPerspective(src, M, dsize)
M M M:透视变换的变换矩阵
d s i z e dsize dsize:输出图像大小,元组
s r c src src: 输入图像
d s t dst dst:输出图像
import cv2
import numpy as np
img=cv2.imread('1.jpg')
rows=img.shape[0]
cols=img.shape[1]
pts1 = np.float32([[56,65],[368,52],[28,387],[389,390]])
pts2 = np.float32([[0,0],[300,0],[0,300],[300,300]])
M=cv2.getPerspectiveTransform(pts1,pts2)
out=cv2.warpPerspective(img,M,(cols,rows))
cv2.namedWindow('img',cv2.WINDOW_NORMAL)
cv2.resizeWindow('img',int(cols/3),int(rows/3))
cv2.imshow('img',out)
cv2.waitKey(0)
cv2.destroyAllWindows()
四、低通滤波器
4.1 傅里叶变换(Fourier Transform)
傅里叶变换是一个将空间域(图像的像素值)转换为频率域的方法。在图像处理中,傅里叶变换能够将图像的每个像素表示为一组频率分量。高频分量表示图像中的快速变化(如边缘、细节),低频分量则表示图像中的平滑变化(如背景或大面积的颜色变化)。
4.2 低通滤波器(Low-pass Filter)
低通滤波器是一种允许低频分量通过,阻挡或削弱高频分量的滤波器。在图像处理中,低通滤波器主要用于去除图像中的噪声、平滑图像或模糊边缘。
4.3 通过傅里叶变换实现低通滤波
-
将图像转换到频率域:首先,对图像应用二维傅里叶变换(FFT),将图像从空间域转换到频率域。
-
应用低通滤波器:在频率域中,通过设置一个阈值,保留低频分量,衰减或去除高频分量。通常这可以通过掩码操作实现。
-
逆傅里叶变换:对滤波后的频率域图像应用逆傅里叶变换,将其转换回空间域,从而获得经过低通滤波处理的图像。
具体实现:
4.3.1 读取图像
读取图像,并将图像转化为灰度图
image = cv2.imread('test_1.jpg', cv2.IMREAD_GRAYSCALE)
4.3.2 定义低通滤波器
对图像进行二维的傅里叶变换,我们需要先定义一个低通滤波器,这个滤波器在应用FFT2的时候是必要的。
# 定义低通滤波器
def low_pass_filter(image, cutoff):
rows, cols = image.shape
crow, ccol = rows // 2, cols // 2 # 找到图像的中心
mask = np.zeros((rows, cols))
mask[crow-cutoff:crow+cutoff, ccol-cutoff:ccol+cutoff] = 1 # 中心区域保留,其他位置为0
return mask
cutoff
是低通滤波器的截止频率,也就是在傅里叶变换后的频域图像上离中心点多少范围内的值保留,其他的高频信号需要丢弃。
4.3.3 定义FFT2和IFFT2过程
定义好低通滤波器后,我们可以开始定义FFT2和IFFT2过程
# 进行傅里叶变换,应用低通滤波器,然后逆傅里叶变换
def apply_low_pass_filter(image, cutoff):
# 1. 傅里叶变换到频域
f_transform = fft2(image)
f_transform_shifted = fftshift(f_transform) # 将低频分量移到中心
# 2. 创建并应用低通滤波器
mask = low_pass_filter(image, cutoff)
filtered_transform = f_transform_shifted * mask
# 3. 逆傅里叶变换回到空间域
inverse_transform_shifted = np.fft.ifftshift(filtered_transform)
filtered_image = ifft2(inverse_transform_shifted)
# 返回滤波后图像和滤波器
return np.abs(filtered_image), mask
#1.傅里叶变换到频域
fft2
的结果会在频率域中,将低频分量放在图像的四个角,而高频分量在图像的中心。这里应用fftshift
函数,这个函数的作用是将这些频率成分重新排列,使得低频分量被移到频谱的中心,而高频分量被移到边缘。因为我们通常更关心低频的结构信息,这样我们就可以用低通滤波器将中心的低频信息保留,高频信息滤除。
#3.逆傅里叶变换回到空间域
np.fft.ifftshift
在将聚集在中心位置的低频信号分散回四周,再通过傅里叶逆变换换回原始图像
np.abs()
在逆变换之后,理论上应该得到纯实数图像,但由于数值误差和计算机的有限精度,结果中可能会有极小的虚部。如果直接显示复数图像,会导致无法正确显示,所以我们只取复数的模。
4.3.4 显示傅里叶变换后的频域图像
def FFT2_SPE(image):
f_transform = fft2(image)
f_transform_shifted = fftshift(f_transform)
f_transform_shifted_abs=np.abs(f_transform_shifted)
log_spectrum=np.log(1+f_transform_shifted_abs)
return log_spectrum
这FFT2_SPE
用于显示图像的傅里叶变换的频谱,并对频谱进行对数尺度缩放,以便更清楚地展示高频和低频分量的细节。
- 首先对图像进行二维傅里叶变换,将图像从空间域转换为频率域。输出结果是复数矩阵,包含图像的频率成分(实部和虚部)。
- 而后使用
fftshift
将傅里叶变换的输出重新排列,把低频分量(即图像中的大区域和慢变化)移动到频谱图的中心,把高频分量(即图像的边缘、细节和快速变化)移到四周。 - 随后利用
np.abs
取复数矩阵的模(即幅值),忽略相位部分,只关注频率成分的强度。傅里叶变换结果中,模表示每个频率成分的强度。 - 最后使用
np.log(1 + ...)
对模值进行对数变换,将频谱中的强度动态范围缩小。傅里叶变换的结果往往动态范围非常大,低频部分强度很高,高频部分的细节强度很低,直接显示会导致高频细节几乎看不见。加 1 是为了避免对零取对数(因为log(0)
是无定义的),可以使频谱中的所有值都在合理的范围内。
由于傅里叶变换的频谱强度范围非常大,直接显示会导致高频部分的细节无法看清。通过取对数,较小的值被放大,更容易观察到频谱中的细节信息,尤其是高频部分。
4.3.5 可视化原始图片、频谱图、滤波后图片
# 设置低通滤波器的截止频率
cutoff_frequency = 100 # 100/60/30 100的效果最好
# 绘制结果
fig, axs = plt.subplots(1, 3, figsize=(15, 5))
# 显示原始图像
axs[0].imshow(image, cmap='gray')
axs[0].set_title('Original Image')
# 显示傅里叶变换后的频域图像
axs[1].imshow(log_spectrum, cmap='gray')
axs[1].set_title('Fourier Transform (Magnitude Spectrum)')
# 显示经过低通滤波后的图像
axs[2].imshow(filtered_image, cmap='gray')
axs[2].set_title(f'Filtered Image (Cutoff={cutoff_frequency})')
plt.show()
4.4 结论
-
低通滤波器可以使边缘平滑,使其过渡更加自然,避免过度锐化导致的伪影或噪声。
-
低通滤波器可以简化数据,减少存储需求:
数据降采样:在需要减少数据量或降低计算复杂度的场景中,低通滤波器可以用于去除不必要的高频信息,然后进行降采样处理。这有助于减少存储需求和加快处理速度。
压缩图像:在某些图像压缩算法中,低通滤波器可用于去除人眼难以察觉的细节,从而达到压缩图像数据的效果。
-
低通滤波器可以减少噪声:
去除高频噪声:在图像或信号中,高频分量通常与噪声或不希望的快速变化相关。低通滤波器可以去除这些高频分量,消除图像或信号中的高频噪声,从而使图像更平滑、更干净。
五、高通滤波器
高通滤波器(High-Pass Filter)在图像处理中用于保留高频分量(如边缘、细节),并衰减低频分量(如图像的平滑区域或大范围变化)。与低通滤波器相反,高通滤波器专注于保留图像中的快速变化信息。
至于高通滤波器的实现,与低通滤波器相比,过程都是一样的,只有滤波器的定义不同。
5.1 定义高通滤波器
# 定义高通滤波器
def high_pass_filter(image, cutoff):
rows, cols = image.shape
crow, ccol = rows // 2, cols // 2 # 找到图像的中心
mask = np.ones((rows, cols)) # 初始化为全1的矩阵
mask[crow-cutoff:crow+cutoff, ccol-cutoff:ccol+cutoff] = 0 # 中心区域设为0(即去除低频分量)
return mask
其他的操作和低通滤波器一样。
5.2 可视化原始图片、频谱图、滤波后图片
# 设置高通滤波器的截止频率
cutoff_frequency = 100 # 100/60/30 30的边缘检测效果最好
# 绘制结果
fig, axs = plt.subplots(1, 3, figsize=(15, 5))
# 显示原始图像
axs[0].imshow(image, cmap='gray')
axs[0].set_title('Original Image')
# 显示傅里叶变换后的频域图像
axs[1].imshow(log_spectrum, cmap='gray')
axs[1].set_title('Fourier Transform (Magnitude Spectrum)')
# 显示经过高通滤波后的图像
axs[2].imshow(filtered_image, cmap='gray')
axs[2].set_title(f'Filtered Image (Cutoff={cutoff_frequency})')
plt.show()
5.3 结论
如图所示,可以观察到高频滤波器只能将边缘信息显示出来。
六、带通滤波器
带通滤波器(Band-Pass Filter) 是一种用于允许特定频率范围内的信号或图像通过,同时阻止该范围之外的频率分量的滤波器。它既可以抑制低频,也可以抑制高频,从而仅允许中间频率(即频带)通过。带通滤波器广泛应用于图像处理、信号处理、通信等领域。
6.1 带通滤波器的作用
带通滤波器的主要作用是保留特定频段的信号或图像细节,抑制其他频率成分。它适用于希望只分析或保留某一频率范围的应用场景。
6.2 实现带通滤波器的步骤
-
傅里叶变换:将图像从空间域转换到频率域。
-
设计带通滤波器掩码:在频率域中设计一个掩码,只允许某个频段内的频率通过。
-
应用带通滤波器:将掩码与频率域图像相乘,保留特定频率范围内的成分。
-
逆傅里叶变换:将经过滤波后的频率域图像通过逆傅里叶变换回到空间域
6.3 具体实现
带通滤波器可以通过结合高通滤波器和低通滤波器实现。即先通过一个低通滤波器,再通过一个高通滤波器,保留两者之间的频率范围。
6.3.1 读取图像
image = cv2.imread('test_1.jpg', cv2.IMREAD_GRAYSCALE)
6.3.2 定义带滤波器
# 定义带通滤波器
def band_pass_filter(image, low_cutoff, high_cutoff):
rows, cols = image.shape
crow, ccol = rows // 2, cols // 2 # 找到图像的中心
mask = np.zeros((rows, cols)) # 初始化掩码为全0
# 在掩码的中心区域设置为1,表示通过的频率范围
mask[crow-high_cutoff:crow+high_cutoff, ccol-high_cutoff:ccol+high_cutoff] = 1
mask[crow-low_cutoff:crow+low_cutoff, ccol-low_cutoff:ccol+low_cutoff] = 0
return mask
6.3.3 定义FFT2和IFFT2过程
# 进行傅里叶变换,应用低通滤波器,然后逆傅里叶变换
def apply_low_pass_filter(image, cutoff):
# 1. 傅里叶变换到频域
f_transform = fft2(image)
f_transform_shifted = fftshift(f_transform) # 将低频分量移到中心
# 2. 创建并应用低通滤波器
mask = low_pass_filter(image, cutoff)
filtered_transform = f_transform_shifted * mask
# 3. 逆傅里叶变换回到空间域
inverse_transform_shifted = np.fft.ifftshift(filtered_transform)
filtered_image = ifft2(inverse_transform_shifted)
# 返回滤波后图像和滤波器
return np.abs(filtered_image), mask
6.3.4 显示傅里叶变换后的频域图像
def FFT2_SPE(image):
f_transform = fft2(image)
f_transform_shifted = fftshift(f_transform)
f_transform_shifted_abs=np.abs(f_transform_shifted)
log_spectrum=np.log(1+f_transform_shifted_abs)
return log_spectrum
6.3.5 可视化原始图片、频谱图、滤波后图片
# 设置带通滤波器的截止频率
low_cutoff_frequency = 10 # 设置低频截止10/100/1000
high_cutoff_frequency =30# 设置高频截止30/300/3000
# 绘制结果
fig, axs = plt.subplots(1, 3, figsize=(15, 5))
# 显示原始图像
axs[0].imshow(image, cmap='gray')
axs[0].set_title('Original Image')
# 显示傅里叶变换后的频域图像
axs[1].imshow(log_spectrum, cmap='gray')
axs[1].set_title('Fourier Transform (Magnitude Spectrum)')
# 显示经过低通滤波后的图像
axs[2].imshow(filtered_image, cmap='gray')
axs[2].set_title(f'Filtered Image (Cutoff={cutoff_frequency})')
plt.show()
6.4 结论
带通滤波器可以选择性频率保留和去除低频和高频噪声
- 选择性频率保留:可以在感兴趣的频率范围内保留信号的有用信息。
- 去除低频和高频噪声:可以有效地去除来自低频和高频的噪声或干扰,保持中间频率的清晰度。