├── README.md
├── section_1
├── README.md
├── problem_1
│ ├── cameraman.png
│ ├── cameraman.tif
│ ├── cameraman_result.png
│ ├── einstein.png
│ ├── einstein.tif
│ ├── einstein_result.png
│ ├── main.py
│ └── scanLine4e.py
└── problem_2
│ ├── lena512color.png
│ ├── lena512color.tiff
│ ├── lena512color_NTSC.png
│ ├── lena512color_average.png
│ ├── main.py
│ ├── mandril_color.png
│ ├── mandril_color.tif
│ ├── mandril_color_NTSC.png
│ ├── mandril_color_average.png
│ └── rgblgray.py
├── section_2
├── README.md
├── cameraman.png
├── einstein.png
├── gaussKernel.py
├── img
│ ├── cameraman_1.png
│ ├── cameraman_2.png
│ ├── cameraman_3.png
│ ├── cameraman_5.png
│ ├── cameraman_sub.png
│ ├── einstein_1.png
│ ├── einstein_2.png
│ ├── einstein_3.png
│ ├── einstein_5.png
│ ├── einstein_sub.png
│ ├── lena_1.png
│ ├── lena_2.png
│ ├── lena_3.png
│ ├── lena_5.png
│ ├── lena_rep_1.png
│ ├── lena_rep_2.png
│ ├── lena_rep_3.png
│ ├── lena_rep_5.png
│ ├── lena_sub.png
│ ├── mandril_1.png
│ ├── mandril_2.png
│ ├── mandril_3.png
│ ├── mandril_5.png
│ ├── mandril_rep_1.png
│ ├── mandril_rep_2.png
│ ├── mandril_rep_3.png
│ ├── mandril_rep_5.png
│ └── mandril_sub.png
├── lena.png
├── main.py
├── mandril.png
└── twodConv.py
├── section_3
├── README.md
├── dft2D.py
├── idft2D.py
├── problem3.png
├── problem3.py
├── problem4.png
├── problem4.py
└── rose512.tif
├── section_4
├── README.md
├── luna.png
├── pb1.py
├── pb1_result.png
├── pb2.py
├── pb2_noise.png
├── pb2_result.png
├── pb3.py
└── pb3_result.png
├── section_5
├── README.md
├── basic_function.py
├── binaryzation.py
├── img
│ ├── bin.png
│ ├── bin_algo.png
│ ├── dist.png
│ ├── example.png
│ ├── morph.png
│ ├── morph_thin.png
│ ├── tailor.png
│ ├── tailor_exp.png
│ └── thin_exp.png
├── main.py
├── origin.png
├── sk_distTrans.py
├── sk_morph.py
├── sk_thin.py
└── tailor.py
├── section_6
├── README.md
├── img_denoised.png
├── img_noise.png
├── lena.png
├── main.py
└── multi-decomposition.png
└── section_7
├── README.md
├── analysis.png
├── blurry_kernel.png
├── lena.png
├── lena_edgetaper.png
├── main.py
├── result.png
├── result_et.png
└── test.png
/README.md:
--------------------------------------------------------------------------------
1 | # 数字图像处理 Image Processing and Analysis
2 | ## 0. 简介
3 | * 数字图像处理实现实例与实验
4 | * 环境: Ubuntu16.04 + Python 3.6.0 + Opencv 3.1.0
5 |
6 | ## 1. Section Information
7 | * Section 1 -- 灰度图像处理
8 | * 黑白图像灰度扫描
9 | * 彩色图像转换为黑白图像
10 | * Section 2 -- 二维卷积与滤波
11 | * 图像二维卷积函数
12 | * 归一化二维高斯滤波核函数
13 | * 灰度图像的高斯滤波
14 | * Section 3 -- 二维快速傅里叶变换
15 | * 图像二维快速傅里叶变换
16 | * 图像二维快速傅里叶逆变换
17 | * 测试图像二维快速傅里叶变换与逆变换
18 | * 计算图像的中心化二维快速傅里叶变换与谱图像
19 | * Section 4 -- 图像增强
20 | * 直方图均衡化
21 | * 保边缘平滑法的实现
22 | * 拉普拉斯增强
23 | * Section 5 -- 图像形态学处理(以指纹图像处理为例)
24 | * 灰度图像二值化
25 | * 形态学基础算法: 腐蚀, 膨胀, 开运算
26 | * 骨架提取算法: 基于腐蚀和开运算的骨架提取, 基于细化的骨架提取, 基于距离变换的骨架提取
27 | * 裁剪算法
28 | * Section 6 -- 小波域维纳滤波
29 | * 单层小波分解维纳滤波去噪实验
30 | * 多层小波分解的维纳滤波去噪效果对比试验
31 | * Section 7 -- 图像复原与重建
32 | * 图像的模糊核估计实验
33 | * 基于维纳滤波的图像去模糊实验
34 |
--------------------------------------------------------------------------------
/section_1/README.md:
--------------------------------------------------------------------------------
1 | # Section 1: 灰度图像处理
2 |
3 | > 实验环境
4 | > * Python 3.6.0
5 | > * Opencv 3.1.0
6 |
7 | ## 问题1 黑白图像灰度扫描
8 |
9 | ### 问题描述 & Code
10 | 实现一个函数s=scanLine4e(f, I, loc),其中f是一个灰度图像,I是一个整数,loc是一个字符串。当loc为’row’时,I代表行数;当loc为’column’时,I代表列数。输出s是对应的相关行或列的像素矢量。
11 |
12 | ```Python
13 | def scanLine4e(f, I, loc):
14 | """
15 | Parameters:
16 | f: Grayscale image
17 | I: An int num
18 | loc: 'row' or 'column'
19 | Return:
20 | A list of pixel value of the specific row/column
21 | """
22 | if loc == 'row':
23 | return f[I, :]
24 | elif loc == 'column':
25 | return f[:, I]
26 | else:
27 | raise ValueError("The third parameter should be row/column")
28 | ```
29 |
30 | 调用上述函数,提取cameraman.tif和einstein.tif的中心行和中心列的像素矢量并将扫描结果绘制成图。
31 | > 注:对于行/列总像素数为偶数的情况,取中间两行/列中较后一个
32 |
33 | ```Python
34 | import cv2
35 | import matplotlib.pyplot as plt
36 |
37 | from scanLine4e import scanLine4e
38 |
39 | def main(filename):
40 | """
41 | Main function
42 | """
43 | # Read image
44 | print ("Processing {} ...".format(filename))
45 | img = cv2.imread(filename, cv2.IMREAD_GRAYSCALE)
46 |
47 | # Call function :scanLine4e
48 | mid_row = scanLine4e(img, img.shape[0]//2, 'row')
49 | mid_col = scanLine4e(img, img.shape[1]//2, 'column')
50 |
51 | # Plot
52 | fig = plt.figure()
53 | row = fig.add_subplot(2,1,1)
54 | column = fig.add_subplot(2,1,2)
55 |
56 | row.set_title("mid_row")
57 | column.set_title("mid_column")
58 |
59 | row.plot(mid_row)
60 | column.plot(mid_col)
61 |
62 | plt.tight_layout()
63 | plt.savefig("{}_result.png".format(filename[:-4]))
64 |
65 | if __name__ == "__main__":
66 | main("cameraman.tif")
67 | main("einstein.tif")
68 | ```
69 |
70 | ### 实验 & 结果
71 | 运行[main.py](./problem_1/main.py)以复现结果
72 |
78 |
79 | ## 问题2 彩色图像转换为黑白图像
80 |
81 | ### 问题描述 & Code
82 | 图像处理中的一个常见问题是将彩色RGB图像转换成单色灰度图像。第一种常用的方法是取三个元素R、G、B的均值。第二种常用的方式,又称NTSC标准,考虑了人类的彩色感知体验,对于R、G、B三通道分别采用了不同的加权系数,分别是R通道0.2989,G通道0.5870,B通道0.1140.实现一个函数g=rgblgray(f,method),其功能是将一幅24位RGB图像f转换成灰度图像g。当method的值为'average'时,采用第一种转换方式;当method值为'NTSC'时,采用第二种方法。(该函数将NTSC作为缺省方式)
83 |
84 | ```Python
85 | import numpy as np
86 |
87 | def rgblgray(f, method='NTSC'):
88 | """
89 | Parameters:
90 | f: image(RGB)
91 | method: 'average' or 'NTSC'
92 | Return:
93 | image in grayscale
94 | """
95 | if method == 'average':
96 | img_gray = f[:,:,0]/3 + f[:,:,1]/3 + f[:,:,2]/3
97 | elif method == 'NTSC':
98 | img_gray = f[:,:,0]*0.2989+f[:,:,1]*0.5870+f[:,:,2]*0.1140
99 | else:
100 | raise ValueError("The 3rd parameter should be average/NTSC")
101 |
102 | img_gray = img_gray.astype(np.uint8)
103 | return img_gray
104 | ```
105 |
106 | 调用上述函数,将图像mandril_color.tif和lena512color.tiff用上诉两种方式转换为单色灰度图像。
107 |
108 | ```Python
109 | import cv2
110 |
111 | from rgblgray import rgblgray
112 |
113 | def main(filename):
114 | """
115 | Main function
116 | """
117 | # Read image
118 | print ("Processing {} ...".format(filename))
119 | img = cv2.imread(filename)
120 |
121 | # BGR 2 RGB
122 | img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # BGR 2 RGB
123 |
124 | # Call function: rgblgray
125 | img_gray_a = rgblgray(img, 'average')
126 | img_gray_NTSC = rgblgray(img, 'NTSC')
127 |
128 | # Save image
129 | cv2.imwrite("{}_average.png".format(filename[:-4]), img_gray_a)
130 | cv2.imwrite("{}_NTSC.png".format(filename[:-4]), img_gray_NTSC)
131 |
132 | if __name__ == "__main__":
133 |
134 | main("lena512color.tiff")
135 | main("mandril_color.tif")
136 | ```
137 | ### 实验结果 & 讨论
138 | 运行[main.py](./problem_2/main.py)以复现结果
139 |
140 |
141 |

142 |

143 |

144 |

145 |

146 |

147 |
148 |
149 | 从实验结果图中可以看出,由于NTSC标准考虑了人类的彩色感知体验,以该方式加权平均RGB三通道所得的灰度图片更好地保留了彩色图片中的细节纹理特征,相较直接平均的第一种方式,其效果更优。
--------------------------------------------------------------------------------
/section_1/problem_1/cameraman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_1/problem_1/cameraman.png
--------------------------------------------------------------------------------
/section_1/problem_1/cameraman.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_1/problem_1/cameraman.tif
--------------------------------------------------------------------------------
/section_1/problem_1/cameraman_result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_1/problem_1/cameraman_result.png
--------------------------------------------------------------------------------
/section_1/problem_1/einstein.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_1/problem_1/einstein.png
--------------------------------------------------------------------------------
/section_1/problem_1/einstein.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_1/problem_1/einstein.tif
--------------------------------------------------------------------------------
/section_1/problem_1/einstein_result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_1/problem_1/einstein_result.png
--------------------------------------------------------------------------------
/section_1/problem_1/main.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import matplotlib.pyplot as plt
7 |
8 | from scanLine4e import scanLine4e
9 |
10 | def main(filename):
11 | """
12 | Main function
13 | """
14 | # Read image
15 | print ("Processing {} ...".format(filename))
16 | img = cv2.imread(filename, cv2.IMREAD_GRAYSCALE)
17 |
18 | # Call function :scanLine4e
19 | mid_row = scanLine4e(img, img.shape[0]//2, 'row')
20 | mid_col = scanLine4e(img, img.shape[1]//2, 'column')
21 |
22 | # Plot
23 | fig = plt.figure()
24 | row = fig.add_subplot(2,1,1)
25 | column = fig.add_subplot(2,1,2)
26 |
27 | row.set_title("mid_row")
28 | column.set_title("mid_column")
29 |
30 | row.plot(mid_row)
31 | column.plot(mid_col)
32 |
33 | plt.tight_layout()
34 | plt.savefig("{}_result.png".format(filename[:-4]))
35 |
36 |
37 |
38 | if __name__ == "__main__":
39 |
40 | main("cameraman.tif")
41 | main("einstein.tif")
42 |
43 |
--------------------------------------------------------------------------------
/section_1/problem_1/scanLine4e.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | def scanLine4e(f, I, loc):
6 | """
7 | Parameters:
8 | f: Grayscale image
9 | I: An int num
10 | loc: 'row' or 'column'
11 | Return:
12 | A list of pixel value of the specific row/column
13 | """
14 | if loc == 'row':
15 | return f[I, :]
16 | elif loc == 'column':
17 | return f[:, I]
18 | else:
19 | raise ValueError("The third parameter should be row or column")
--------------------------------------------------------------------------------
/section_1/problem_2/lena512color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_1/problem_2/lena512color.png
--------------------------------------------------------------------------------
/section_1/problem_2/lena512color.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_1/problem_2/lena512color.tiff
--------------------------------------------------------------------------------
/section_1/problem_2/lena512color_NTSC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_1/problem_2/lena512color_NTSC.png
--------------------------------------------------------------------------------
/section_1/problem_2/lena512color_average.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_1/problem_2/lena512color_average.png
--------------------------------------------------------------------------------
/section_1/problem_2/main.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 |
7 | from rgblgray import rgblgray
8 |
9 | def main(filename):
10 | """
11 | Main function
12 | """
13 | # Read image
14 | print ("Processing {} ...".format(filename))
15 | img = cv2.imread(filename)
16 |
17 | # BGR 2 RGB
18 | img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # BGR 2 RGB
19 |
20 | # Call function: rgblgray
21 | img_gray_a = rgblgray(img, 'average')
22 | img_gray_NTSC = rgblgray(img, 'NTSC')
23 |
24 | # Save image
25 | cv2.imwrite("{}_average.png".format(filename[:-4]), img_gray_a)
26 | cv2.imwrite("{}_NTSC.png".format(filename[:-4]), img_gray_NTSC)
27 |
28 | if __name__ == "__main__":
29 |
30 | main("lena512color.tiff")
31 | main("mandril_color.tif")
32 |
33 |
--------------------------------------------------------------------------------
/section_1/problem_2/mandril_color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_1/problem_2/mandril_color.png
--------------------------------------------------------------------------------
/section_1/problem_2/mandril_color.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_1/problem_2/mandril_color.tif
--------------------------------------------------------------------------------
/section_1/problem_2/mandril_color_NTSC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_1/problem_2/mandril_color_NTSC.png
--------------------------------------------------------------------------------
/section_1/problem_2/mandril_color_average.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_1/problem_2/mandril_color_average.png
--------------------------------------------------------------------------------
/section_1/problem_2/rgblgray.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import numpy as np
6 |
7 | def rgblgray(f, method='NTSC'):
8 | """
9 | Parameters:
10 | f: image(RGB)
11 | method: 'average' or 'NTSC'
12 | Return:
13 | image in grayscale
14 | """
15 | if method == 'average':
16 | img_gray = f[:,:,0]/3 + f[:,:,1]/3 + f[:,:,2]/3
17 | elif method == 'NTSC':
18 | img_gray = f[:,:,0]*0.2989 + f[:,:,1]*0.5870 + f[:,:,2]*0.1140
19 | else:
20 | raise ValueError("The third parameter should be average or NTSC")
21 |
22 | img_gray = img_gray.astype(np.uint8)
23 | return img_gray
--------------------------------------------------------------------------------
/section_2/README.md:
--------------------------------------------------------------------------------
1 | # Section 2: 二维卷积与滤波
2 |
3 | > 实验环境
4 | > * Python 3.6.0
5 | > * Opencv 3.1.0
6 |
7 | ## 问题1 图像二维卷积函数
8 |
9 | ### 问题描述
10 | 实现一个函数 g = twodConv(f, w), 其中 f 是一个灰度源图像,w 是一个矩形卷积核。要求输出图像 g 与源图像 f 大小(也就是像素的行数和列数)一致。请意,为满足这一要求,对于源图像 f 需要进行边界像素填补(padding)。这里请实现两种方案。第一种方案是像素复制,对应的选项定义为’replicate’,填补的像素拷贝与其最近的图像边界像素灰度。第二种方案是补零,对应的选项定义为’zero’, 填补的像素灰度为 0. 将第二种方案设置为缺省选择。
11 |
12 | ### Code实现
13 | 实现时按照以下步骤:
14 | * 统计输入图片信息和核信息
15 | * Padding:
16 |
17 | 首先新建一Padding后相应大小的零矩阵,将图片元素填至对应位置,实现“补0”的Padding方式;若Padding方式为“replicate”则一次将首行末行和首列末列元素复制并填充至相应位置。
18 | * 卷积:
19 | * 卷积核旋转180°
20 | * 遍历元素,对应相乘并求和,最终得到卷积结果
21 | ```Python
22 | import cv2
23 | import numpy as np
24 |
25 | def twodConv(f, k, p='zero'):
26 | """
27 | 2D Conv
28 | Parameters:
29 | f: the input image (Gray Scale)
30 | k: conv kernel
31 | p: padding mode, e.g. 'replicate' or 'zero'
32 | Return:
33 | the conv result with the same shape as the input
34 | """
35 | # Shape of image & kernel
36 | h_f, w_f = f.shape
37 | h_k, w_k = k.shape
38 |
39 | r_h = (h_k-1)//2
40 | r_w = (w_k-1)//2
41 |
42 | # Padding
43 | f_pad = np.zeros([h_f+2*r_h, w_f+2*r_w])
44 | f_pad[r_h:h_f+r_h, r_w:w_f+r_w] = f
45 |
46 | if p == 'replicate':
47 | for i in range(r_h):
48 | f_pad[i, :] = f_pad[r_h, :]
49 | f_pad[-1-i, :] = f_pad[h_f-r_h+1, :]
50 | for i in range(r_w):
51 | f_pad[:, i] = f_pad[:, r_w]
52 | f_pad[:, -1-i] = f_pad[:, w_f-r_w+1]
53 | elif p == 'zero': pass
54 | else:
55 | raise ValueError("The third one should be replicate or zero")
56 |
57 | # Conv
58 | f_res = np.zeros_like(f)
59 | k = np.rot90(k, 2)
60 |
61 | for i in range(r_h, h_f+r_h):
62 | for j in range(r_w, w_f+r_w):
63 | roi = f_pad[i-r_h:i+r_h+1, j-r_w:j+r_w+1]
64 | f_res[i-r_h][j-r_w] = np.sum(roi*k)
65 |
66 | return f_res.astype(np.uint8)
67 | ```
68 |
69 | ## 问题2 归一化二维高斯滤波核函数
70 | ### 问题描述
71 | 实现一个高斯滤波核函数 w = gaussKernel(sig,m),其中 sig 对应于高斯函数定义中的σ,w的大小为 m×m。请注意,这里如果 m 没有提供,需要进行计算确定。如果 m 已提供但过小,应给出警告信息提示。w 要求归一化,即全部元素加起来和为 1.
72 |
73 | ### Code实现
74 | 实现时,首先根据输入的sig值计算高斯核的最小尺寸M。若输入了m,且满足不小于最小尺寸的条件,则继续,若不满足,则Raise一个参数错误;若未输入m,则使用M作为高斯核尺寸。然后,计算并依次生成高斯核每个位置的数值。最后,进行归一化操作。
75 |
76 | ```Python
77 | import cv2
78 | import numpy as np
79 | import math
80 |
81 | def gaussKernel(sig, m=None):
82 | """
83 | Generate a Gauss kernel.
84 | Parameters:
85 | sig: sigma
86 | m: the shape of the Gauss kernel is m*m
87 | Return:
88 | a Gauss kernel
89 | """
90 | # Cal & Judge m
91 | M = math.ceil(sig*3)*2 + 1
92 | if m:
93 | if m < M:
94 | raise ValueError("m is smaller than it should be.")
95 | else: pass
96 | else:
97 | m = M
98 |
99 | # Generate kernel
100 | k = np.zeros((m,m))
101 | center = m//2
102 | s = sig**2
103 |
104 | for i in range(m):
105 | for j in range(m):
106 | x, y = i-center, j-center
107 | k[i][j] = (1/(2*math.pi*s))*math.exp(-(x**2+y**2)/(2*s))
108 |
109 | k = k/np.sum(k)
110 |
111 | return k
112 | ```
113 |
114 | ## 问题3 灰度图像的高斯滤波
115 | ### 问题描述
116 | 调用上面实现的函数,对于作业 1 中产生的灰度图像(cameraman, einstein, 以及 lena512color和 mandril_color 对应的 NTSC 转换后的灰度图像)进行高斯滤波,采用σ=1,2,3,5。任选一种像素填补方案。
117 |
118 | 对于σ=1 下的结果,与直接调用相关函数的结果进行比较(可以简单计算差值图像)。然后,任选两幅图像,比较其他参数条件不变的情况下像素复制和补零下滤波结果在边界上的差别。
119 | ### Code实现
120 | 依次对4幅图片展开实验,分别在sig为1,2,3,5的情况下进行高斯滤波(Padding方式采用默认的’zero‘),并当sig=1时与Opencv自带的高斯滤波的结果进行减法操作,以对比效果。
121 |
122 | 另外,对lena.png和mandril.png进行Padding方式为replicate的高斯滤波。
123 | ```Python
124 | import cv2
125 | import numpy as np
126 |
127 | from twodConv import twodConv
128 | from gaussKernel import gaussKernel
129 |
130 | def main(filename):
131 | """
132 | Main function for problem 3
133 | """
134 | # Read image
135 | print ("Processing {} ...".format(filename))
136 | img = cv2.imread(filename, cv2.IMREAD_GRAYSCALE)
137 |
138 | # Generate kernel & Conv
139 | sig_list = [1,2,3,5]
140 |
141 | for sig in sig_list:
142 | # Generate Gauss kernel
143 | k = gaussKernel(sig)
144 |
145 | #Conv2D
146 | res = twodConv(img, k)
147 | cv2.imwrite("{}_{}.png".format(filename[:-4],sig), res)
148 |
149 | # Compare with opencv
150 | if sig == 1:
151 | res_cv2 = cv2.GaussianBlur(img, (7,7), 1, borderType=0)
152 | sub = res_cv2 - res
153 | cv2.imwrite("{}_sub.png".format(filename[:-4]), sub)
154 |
155 | # Padding mode: replicate VS zero
156 | rep_list = ['lena.png', 'mandril.png']
157 | if filename in rep_list:
158 | res_rep = twodConv(img, k, 'replicate')
159 | cv2.imwrite("{}_rep_{}.png"\
160 | .format(filename[:-4],sig), res_rep)
161 |
162 | if __name__ == "__main__":
163 | img_list = ['cameraman.png', 'einstein.png', 'lena.png', \
164 | 'mandril.png']
165 | for i in img_list:
166 | main(i)
167 | ```
168 | ### 实验结果与分析
169 | * 不同sigma下的滤波效果对比
170 |
171 |
172 |

173 |

174 |

175 |

176 |

177 |
178 |
179 |

180 |

181 |

182 |

183 |

184 |
185 |
186 |

187 |

188 |

189 |

190 |

191 |
192 |
193 |

194 |

195 |

196 |

197 |

198 |
199 |
200 | 上图中左侧第一列为原始图片,其右侧均为高斯滤波后的结果,由左至右Sigma的数值依次增大,分别为1、 2 、3 、5.可以清晰地看出,随着Sigma的增大,图像模糊程度递增,显示出的尺度逐渐变大。
201 |
202 | * 与直接调用函数的效果对比(Sigma=1)
203 |
204 |
205 |

206 |

207 |

208 |

209 |
210 |
211 | 通过与直接调用函数的结果求差值图像,可以发现,二者差异相当小,差值图像几乎全黑。为进一步细致对比,输出差值矩阵,观察发现差值最大为1.
212 |
213 | * 不同Padding方式在边界上的差别对比
214 |
215 |
216 |

217 |

218 |

219 |

220 |
221 |
222 |

223 |

224 |

225 |

226 |
227 |
228 |
229 |

230 |

231 |

232 |

233 |
234 |
235 |

236 |

237 |

238 |

239 |
240 |
241 | 本部分使用lena和mandril两个图在不同padding方法下的效果进行对比分析。第1 、3行为补零方式,第2、 4行为像素复制方式;由左至右高斯滤波的Sigma值递增,为1 、2 、3和5.可以看到,相比像素复制方式,补零方式的结果图的图像边界上存在黑边,且随着Sigma的增大,黑边变宽。理论上分析,这是因为采用补零方式进行Padding时,边界周围元素均填充0,在卷积时会直接影响边界元素的数值,拉低边界元素的整体数值,从而体现为黑边。同时,随着卷积核尺寸的增加,受填充像素影响的边界元素数量增加,进而体现为黑边变宽。
--------------------------------------------------------------------------------
/section_2/cameraman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/cameraman.png
--------------------------------------------------------------------------------
/section_2/einstein.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/einstein.png
--------------------------------------------------------------------------------
/section_2/gaussKernel.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 | import math
8 |
9 | def gaussKernel(sig, m=None):
10 | """
11 | Generate a Gauss kernel.
12 | Parameters:
13 | sig: sigma
14 | m: the shape of the Gauss kernel is m*m
15 | Return:
16 | a Gauss kernel
17 | """
18 | # Cal & Judge m
19 | M = math.ceil(sig*3)*2 + 1
20 | if m:
21 | if m < M:
22 | raise ValueError("m is smaller than it should be.")
23 | else: pass
24 | else:
25 | m = M
26 |
27 | # Generate kernel
28 | k = np.zeros((m,m))
29 | center = m//2
30 | s = sig**2
31 |
32 | for i in range(m):
33 | for j in range(m):
34 | x, y = i-center, j-center
35 | k[i][j] = (1/(2*math.pi*s)) * math.exp(-(x**2+y**2)/(2*s))
36 |
37 | k = k/np.sum(k)
38 |
39 | return k
40 |
41 | if __name__ == "__main__":
42 | sig = 1
43 | m = None
44 | k = gaussKernel(sig, m)
45 | print (k)
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/section_2/img/cameraman_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/cameraman_1.png
--------------------------------------------------------------------------------
/section_2/img/cameraman_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/cameraman_2.png
--------------------------------------------------------------------------------
/section_2/img/cameraman_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/cameraman_3.png
--------------------------------------------------------------------------------
/section_2/img/cameraman_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/cameraman_5.png
--------------------------------------------------------------------------------
/section_2/img/cameraman_sub.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/cameraman_sub.png
--------------------------------------------------------------------------------
/section_2/img/einstein_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/einstein_1.png
--------------------------------------------------------------------------------
/section_2/img/einstein_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/einstein_2.png
--------------------------------------------------------------------------------
/section_2/img/einstein_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/einstein_3.png
--------------------------------------------------------------------------------
/section_2/img/einstein_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/einstein_5.png
--------------------------------------------------------------------------------
/section_2/img/einstein_sub.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/einstein_sub.png
--------------------------------------------------------------------------------
/section_2/img/lena_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/lena_1.png
--------------------------------------------------------------------------------
/section_2/img/lena_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/lena_2.png
--------------------------------------------------------------------------------
/section_2/img/lena_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/lena_3.png
--------------------------------------------------------------------------------
/section_2/img/lena_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/lena_5.png
--------------------------------------------------------------------------------
/section_2/img/lena_rep_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/lena_rep_1.png
--------------------------------------------------------------------------------
/section_2/img/lena_rep_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/lena_rep_2.png
--------------------------------------------------------------------------------
/section_2/img/lena_rep_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/lena_rep_3.png
--------------------------------------------------------------------------------
/section_2/img/lena_rep_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/lena_rep_5.png
--------------------------------------------------------------------------------
/section_2/img/lena_sub.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/lena_sub.png
--------------------------------------------------------------------------------
/section_2/img/mandril_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/mandril_1.png
--------------------------------------------------------------------------------
/section_2/img/mandril_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/mandril_2.png
--------------------------------------------------------------------------------
/section_2/img/mandril_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/mandril_3.png
--------------------------------------------------------------------------------
/section_2/img/mandril_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/mandril_5.png
--------------------------------------------------------------------------------
/section_2/img/mandril_rep_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/mandril_rep_1.png
--------------------------------------------------------------------------------
/section_2/img/mandril_rep_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/mandril_rep_2.png
--------------------------------------------------------------------------------
/section_2/img/mandril_rep_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/mandril_rep_3.png
--------------------------------------------------------------------------------
/section_2/img/mandril_rep_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/mandril_rep_5.png
--------------------------------------------------------------------------------
/section_2/img/mandril_sub.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/img/mandril_sub.png
--------------------------------------------------------------------------------
/section_2/lena.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/lena.png
--------------------------------------------------------------------------------
/section_2/main.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 |
8 | from twodConv import twodConv
9 | from gaussKernel import gaussKernel
10 |
11 | def main(filename):
12 | """
13 | Main function for problem 3
14 | """
15 | # Read image
16 | print ("Processing {} ...".format(filename))
17 | img = cv2.imread(filename, cv2.IMREAD_GRAYSCALE)
18 |
19 | # Generate kernel & Conv
20 | sig_list = [1,2,3,5]
21 |
22 | for sig in sig_list:
23 | # Generate Gauss kernel
24 | k = gaussKernel(sig)
25 |
26 | #Conv2D
27 | res = twodConv(img, k)
28 | cv2.imwrite("{}_{}.png".format(filename[:-4],sig), res)
29 |
30 | # Compare with opencv
31 | if sig == 1:
32 | res_cv2 = cv2.GaussianBlur(img, (7,7), 1, borderType=0)
33 | sub = res_cv2 - res
34 | cv2.imwrite("{}_sub.png".format(filename[:-4]), sub)
35 |
36 | # Padding mode: replicate VS zero
37 | rep_list = ['lena.png', 'mandril.png']
38 | if filename in rep_list:
39 | res_rep = twodConv(img, k, 'replicate')
40 | cv2.imwrite("{}_rep_{}.png".format(filename[:-4],sig), res_rep)
41 |
42 |
43 |
44 | if __name__ == "__main__":
45 | img_list = ['cameraman.png', 'einstein.png', 'lena.png', 'mandril.png']
46 | for i in img_list:
47 | main(i)
48 |
49 |
50 |
--------------------------------------------------------------------------------
/section_2/mandril.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_2/mandril.png
--------------------------------------------------------------------------------
/section_2/twodConv.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 |
8 | def twodConv(f, k, p='zero'):
9 | """
10 | 2D Conv
11 | Parameters:
12 | f: the input image (Gray Scale)
13 | k: conv kernel
14 | p: padding mode, e.g. 'replicate' or 'zero'
15 | Return:
16 | the conv result with the same shape as the input
17 | """
18 | # Shape of image & kernel
19 | h_f, w_f = f.shape
20 | h_k, w_k = k.shape
21 |
22 | r_h = (h_k-1)//2
23 | r_w = (w_k-1)//2
24 |
25 | # Padding
26 | f_pad = np.zeros([h_f+2*r_h, w_f+2*r_w])
27 | f_pad[r_h:h_f+r_h, r_w:w_f+r_w] = f
28 |
29 | if p == 'replicate':
30 | for i in range(r_h):
31 | f_pad[i, :] = f_pad[r_h, :]
32 | f_pad[-1-i, :] = f_pad[h_f-r_h+1, :]
33 | for i in range(r_w):
34 | f_pad[:, i] = f_pad[:, r_w]
35 | f_pad[:, -1-i] = f_pad[:, w_f-r_w+1]
36 | elif p == 'zero': pass
37 | else:
38 | raise ValueError("The third parameter should be replicate or zero")
39 |
40 | # Conv
41 | f_res = np.zeros_like(f)
42 | k = np.rot90(k, 2)
43 |
44 | for i in range(r_h, h_f+r_h):
45 | for j in range(r_w, w_f+r_w):
46 | roi = f_pad[i-r_h:i+r_h+1, j-r_w:j+r_w+1]
47 | f_res[i-r_h][j-r_w] = np.sum(roi*k)
48 |
49 | return f_res.astype(np.uint8)
50 |
51 |
52 | if __name__ == "__main__":
53 | img = np.array([[1,2,3,4],
54 | [5,6,7,8],
55 | [9,8,7,6]])
56 |
57 | k = np.array([[1,2,3],
58 | [-1,0,1],
59 | [2,1,2]])
60 |
61 | ours = twodConv(img, k, 'replicate')
62 | # ours = twodConv(img, k)
63 |
64 | from scipy import signal
65 | sci_res = signal.convolve2d(img, k, mode='same', boundary='symm')
66 | # sci_res = signal.convolve2d(img, k, mode='same', boundary='fill')
67 |
68 | print (ours)
69 | print (sci_res)
--------------------------------------------------------------------------------
/section_3/README.md:
--------------------------------------------------------------------------------
1 | # Section 3: 二维快速傅里叶变换
2 |
3 | > 实验环境
4 | > * Python 3.6.0
5 | > * Opencv 3.1.0
6 |
7 | ## 问题1 图像二维快速傅里叶变换
8 |
9 | ### 问题描述
10 | 实现一个函数 F=dft2D(f), 其中 f 是一个灰度源图像,F 是其对应的二维快速傅里叶变换(FFT)图像. 具体实现要求按照课上的介绍通过两轮一维傅里叶变换实现。也就是首先计算源图像每一行的一维傅里叶变换,然后对于得到的结果计算其每一列的一维傅里叶变换。
11 |
12 | ### Code实现
13 | 实现思路如下:
14 | * 复制图片对象,防止后续傅里叶操作影响原图,并将新对象转为复数格式
15 | * 通过两轮不同维度的一维傅里叶变换实现二维快速傅里叶变换
16 |
17 | ```Python
18 | def dft2D(f):
19 | """
20 | 通过计算一维傅里叶变换实现图像二维快速傅里叶变换
21 | Parameters:
22 | f: image (Gray scale)
23 | Return:
24 | the result of FFT
25 | """
26 | F = f.copy()
27 | F = F.astype(np.complex128)
28 |
29 | # FFT for each row
30 | for i in range(F.shape[0]):
31 | F[i] = np.fft.fft(F[i])
32 |
33 | # FFT for each column
34 | for i in range(F.shape[1]):
35 | F[:, i] = np.fft.fft(F[:, i])
36 |
37 | return F
38 | ```
39 |
40 | ## 问题2 图像二维快速傅里叶逆变换
41 |
42 | ### 问题描述
43 | 实现一个函数 f=idft2D(F), 其中 F 是一个灰度图像的傅里叶变换,f 是其对应的二维快速傅里叶逆变换 (IFFT)图像,也就是灰度源图像. 具体实现要求按照课上的介绍通过类似正向变换的方式实现。
44 |
45 | ### Code实现
46 | 实现思路如下:
47 | * 将傅里叶结果求共轭
48 | * 对上步结果做二维快速傅里叶变换
49 | * 对上一部结果除以MN,做共轭运算
50 |
51 | ```Python
52 | def idft2D(F):
53 | """
54 | 实现二维傅里叶逆变换
55 | Parameters:
56 | F : 灰度图像的傅里叶变换结果
57 | Return:
58 | 傅里叶逆变换结果
59 | """
60 | # Conjugate
61 | f = F.conjugate()
62 |
63 | # 2D FFT
64 | for i in range(f.shape[0]):
65 | f[i] = np.fft.fft(f[i])
66 | for i in range(f.shape[1]):
67 | f[:, i] = np.fft.fft(f[:, i])
68 |
69 | # Divide by MN & Conjugate
70 | f = f/f.size
71 | f = np.abs(f.conjugate())
72 |
73 | return f
74 | ```
75 |
76 | ## 问题3 测试图像二维快速傅里叶变换与逆变换
77 |
78 | ### 问题描述
79 | 对于给定的输入图像 rose512.tif, 首先将其灰度范围通过归一化调整到[0,1]. 将此归一化的图像记为 f. 首先调用问题 1 下实现的函数 dft2D 计算其傅里叶变换,记为 F。然后调用问题 2 下的函数 idft2D 计算 F 的傅里叶逆变换,记为 g. 计算并显示误差图像 d = f-g.
80 |
81 | ### Code实现
82 | 实现思路如下:
83 | * 读入图片并归一化
84 | * 调用dft2D做二维快速傅里叶变换
85 | * 调用idft2D做二维快速傅里叶逆变换
86 | * 计算差值图像
87 | * 绘制实验结果
88 |
89 | 运行方式:```python problem3.py```
90 |
91 | ```Python
92 | import cv2
93 | import numpy as np
94 | import matplotlib.pyplot as plt
95 |
96 | from dft2D import dft2D
97 | from idft2D import idft2D
98 |
99 | def main(filepath):
100 | """
101 | Main function for problem 3.
102 | """
103 | # Read the image
104 | img = cv2.imread(filepath, 0)
105 | img = img/255.
106 |
107 | # 2D FFT
108 | img_fft = dft2D(img)
109 |
110 | # 2D IFFT
111 | img_ifft = idft2D(img_fft)
112 |
113 | # Cal difference
114 | img_diff = img - img_ifft
115 |
116 | # Plot the result
117 | plt.subplot(221).set_title("Origin")
118 | plt.imshow(img, cmap='gray')
119 |
120 | plt.subplot(222).set_title("FFT")
121 | plt.imshow(np.log(np.abs(img_fft)+1), cmap='gray')
122 |
123 | plt.subplot(223).set_title("IFFT")
124 | plt.imshow(img_ifft, cmap='gray')
125 |
126 | plt.subplot(224).set_title("Difference")
127 | plt.imshow(np.round(img_diff), cmap='gray')
128 |
129 | plt.tight_layout()
130 | plt.savefig("problem3.png")
131 |
132 | if __name__ == "__main__":
133 | main("rose512.tif")
134 | ```
135 |
136 | ### 实验结果与分析
137 | 从图1的实验结果图可以看出,原图与逆变换结果的差值图像为全黑,表明先后经过傅里叶变换和傅里叶逆变换两个过程不会对原图像产生影响。
138 |
139 |
140 |

141 | 图1 问题三实验结果图
142 |
143 |
144 | ## 问题 4 计算图像的中心化二维快速傅里叶变换与谱图像
145 |
146 | ### 问题描述
147 | 我们的目标是复现下图中的结果。首先合成矩形物体图像,建议图像尺寸为 512×512,矩形位于图像中心,建议尺寸为 60 像素长,10 像素宽,灰度假设已归一化设为 1. 对于输入图像 f 计算其中心化二维傅里叶变换 F。然后计算对应的谱图像 S=log(1+abs(F)). 显示该谱图像。
148 |
149 | ### Code实现
150 | 实现思路如下:
151 | * 根据要求合成底色为黑色的白色矩形物体图像,并归一化
152 | * 为与中心化的二维傅里叶变换结果对比,对原图做二维傅里叶变换
153 | * 计算原图的中心化二维傅里叶变换
154 | * 计算中心化二维傅里叶变换的谱图像
155 | * 绘制实验结果
156 |
157 | 运行方式:```python problem4.py```
158 |
159 | ```Python
160 | import cv2
161 | import numpy as np
162 | import matplotlib.pyplot as plt
163 |
164 | from dft2D import dft2D
165 | from idft2D import idft2D
166 |
167 | def main():
168 | """
169 | Main Function for problem 4.
170 | """
171 | # Generate the image
172 | img = np.zeros([512,512])
173 | img[226:285, 251:260] = 255
174 | img = img/255.
175 |
176 | # FFT
177 | img_fft = dft2D(img)
178 |
179 | # Centralization FFT
180 | img_fft_c = img.copy()
181 | for i in range(img.shape[0]):
182 | for j in range(img.shape[1]):
183 | img_fft_c[i, j] = img_fft_c[i, j] * ((-1)**(i+j))
184 |
185 | img_fft_c = dft2D(img_fft_c)
186 |
187 | # Logarithmic transformation
188 | img_fft_clog = np.log(1 + np.abs(img_fft_c))
189 |
190 | # Plot
191 | plt.subplot(221).set_title("Origin")
192 | plt.imshow(img, cmap='gray')
193 |
194 | plt.subplot(222).set_title("FFT")
195 | plt.imshow(np.abs(img_fft), cmap='gray')
196 |
197 | plt.subplot(223).set_title("Centralization")
198 | plt.imshow(np.abs(img_fft_c), cmap='gray')
199 |
200 | plt.subplot(224).set_title("Log")
201 | plt.imshow(img_fft_clog, cmap='gray')
202 |
203 | plt.tight_layout()
204 | plt.savefig("problem4.png")
205 |
206 | if __name__ == "__main__":
207 | main()
208 | ```
209 |
210 | ### 实验结果与分析
211 | 实验结果如图2所示。
212 |
213 |
214 |

215 | 图2 问题四实验结果图——矩形物体图像的傅里叶变换
216 |
--------------------------------------------------------------------------------
/section_3/dft2D.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 |
8 | def dft2D(f):
9 | """
10 | 通过计算一维傅里叶变换实现图像二维快速傅里叶变换
11 | Parameters:
12 | f: image (Gray scale)
13 | Return:
14 | the result of FFT
15 | """
16 |
17 | F = f.copy()
18 | F = F.astype(np.complex128)
19 |
20 | # FFT for each row
21 | for i in range(F.shape[0]):
22 | F[i] = np.fft.fft(F[i])
23 |
24 | # FFT for each column
25 | for i in range(F.shape[1]):
26 | F[:, i] = np.fft.fft(F[:, i])
27 |
28 | return F
29 |
30 | if __name__ == "__main__":
31 | img = np.zeros([512,512])
32 | img[226:285, 251:260] = 255
33 |
34 | img = img/255.
35 |
36 | img_fft = dft2D(img)
37 |
38 | import matplotlib.pyplot as plt
39 | plt.imshow(np.abs(img_fft), cmap='gray')
40 | plt.show()
41 |
42 |
--------------------------------------------------------------------------------
/section_3/idft2D.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 |
8 | def idft2D(F):
9 | """
10 | 实现二维傅里叶逆变换
11 | Parameters:
12 | F : 灰度图像的傅里叶变换结果
13 | Return:
14 | 傅里叶逆变换结果
15 | """
16 | # Conjugate
17 | f = F.conjugate()
18 |
19 | # 2D FFT
20 | for i in range(f.shape[0]):
21 | f[i] = np.fft.fft(f[i])
22 | for i in range(f.shape[1]):
23 | f[:, i] = np.fft.fft(f[:, i])
24 |
25 | # Divide by MN & Conjugate
26 | f = f/f.size
27 | f = np.abs(f.conjugate())
28 |
29 | return f
30 |
31 |
32 | if __name__ == "__main__":
33 | # img = cv2.imread("house.tif", cv2.IMREAD_GRAYSCALE)
34 | img = np.zeros([512,512])
35 | img[226:285, 251:260] = 255
36 |
37 | img = img/255.
38 |
39 | from dft2D import dft2D
40 | img_fft = dft2D(img)
41 |
42 | img_ifft = idft2D(img_fft)
43 |
44 | import matplotlib.pyplot as plt
45 | plt.subplot(141)
46 | plt.imshow(img, cmap='gray')
47 | plt.subplot(142)
48 | plt.imshow(np.round(np.abs(img_fft)), cmap='gray')
49 | plt.subplot(143)
50 | plt.imshow(np.abs(img_ifft), cmap='gray')
51 | plt.subplot(144)
52 | plt.imshow(np.round(img - img_ifft), cmap='gray')
53 | plt.show()
--------------------------------------------------------------------------------
/section_3/problem3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_3/problem3.png
--------------------------------------------------------------------------------
/section_3/problem3.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 | import matplotlib.pyplot as plt
8 |
9 | from dft2D import dft2D
10 | from idft2D import idft2D
11 |
12 | def main(filepath):
13 | """
14 | Main function for problem 3.
15 | """
16 | # Read the image
17 | img = cv2.imread(filepath, 0)
18 | img = img/255.
19 |
20 | # 2D FFT
21 | img_fft = dft2D(img)
22 |
23 | # 2D IFFT
24 | img_ifft = idft2D(img_fft)
25 |
26 | # Cal difference
27 | img_diff = img - img_ifft
28 |
29 | # Plot the result
30 | plt.subplot(221).set_title("Origin")
31 | plt.imshow(img, cmap='gray')
32 |
33 | plt.subplot(222).set_title("FFT")
34 | plt.imshow(np.log(np.abs(img_fft)+1), cmap='gray')
35 |
36 | plt.subplot(223).set_title("IFFT")
37 | plt.imshow(img_ifft, cmap='gray')
38 |
39 | plt.subplot(224).set_title("Difference")
40 | plt.imshow(np.round(img_diff), cmap='gray')
41 |
42 | plt.tight_layout()
43 | plt.savefig("problem3.png")
44 |
45 | if __name__ == "__main__":
46 | main("rose512.tif")
47 |
--------------------------------------------------------------------------------
/section_3/problem4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_3/problem4.png
--------------------------------------------------------------------------------
/section_3/problem4.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 | import matplotlib.pyplot as plt
8 |
9 | from dft2D import dft2D
10 | from idft2D import idft2D
11 |
12 | def main():
13 | """
14 | Main Function for problem 4.
15 | """
16 | # Generate the image
17 | img = np.zeros([512,512])
18 | img[226:285, 251:260] = 255
19 | img = img/255.
20 |
21 | # FFT
22 | img_fft = dft2D(img)
23 |
24 | # Centralization FFT
25 | img_fft_c = img.copy()
26 | for i in range(img.shape[0]):
27 | for j in range(img.shape[1]):
28 | img_fft_c[i, j] = img_fft_c[i, j] * ((-1)**(i+j))
29 |
30 | img_fft_c = dft2D(img_fft_c)
31 |
32 | # Logarithmic transformation
33 | img_fft_clog = np.log(1 + np.abs(img_fft_c))
34 |
35 | # Plot
36 | plt.subplot(221).set_title("Origin")
37 | plt.imshow(img, cmap='gray')
38 |
39 | plt.subplot(222).set_title("FFT")
40 | plt.imshow(np.abs(img_fft), cmap='gray')
41 |
42 | plt.subplot(223).set_title("Centralization")
43 | plt.imshow(np.abs(img_fft_c), cmap='gray')
44 |
45 | plt.subplot(224).set_title("Log")
46 | plt.imshow(img_fft_clog, cmap='gray')
47 |
48 | plt.tight_layout()
49 | plt.savefig("problem4.png")
50 |
51 | if __name__ == "__main__":
52 | main()
--------------------------------------------------------------------------------
/section_3/rose512.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_3/rose512.tif
--------------------------------------------------------------------------------
/section_4/README.md:
--------------------------------------------------------------------------------
1 | # Section 4: 图像增强
2 |
3 | > 实验环境
4 | > * Python 3.6.0
5 | > * Opencv 3.1.0
6 |
7 | ## 问题1 直方图均衡化
8 |
9 | ### 问题描述
10 | 编写一个图像直方图均衡化程序: g=histequal4e(I),其中I是8比特图像.
11 |
12 | ### Code实现
13 | 实现思路如下:
14 | * 获取原图像的灰度直方图
15 | * 根据各灰度所占比重重构均衡化的灰度直方图
16 | * 根据上一步结果,生成直方图均衡化的图片
17 |
18 | 运行方式: `python pb1.py`
19 | ```Python
20 | import cv2
21 | import numpy as np
22 |
23 | def histequal4e(img):
24 | """
25 | 直方图均衡化
26 | Parameters:
27 | img : 原图片(GRAY)
28 | Return:
29 | 直方图均衡化后的图片
30 | """
31 | # 获取原图像灰度直方图
32 | hist = np.bincount(img.flatten(), minlength=256)
33 | # 根据比重构建均衡化后的直方图
34 | hist_new = np.cumsum(hist)/np.sum(hist) * 255
35 | # 生成直方图均衡化的图片
36 | img_result = np.zeros_like(img)
37 | for i in range(img.shape[0]):
38 | for j in range(img.shape[1]):
39 | img_result[i,j] = hist_new[img[i,j]]
40 | return img_result
41 |
42 | if __name__ == "__main__":
43 | img = cv2.imread("luna.png", 0)
44 | img_result = histequal4e(img)
45 | cv2.imwrite("pb1_result.png", img_result)
46 | ```
47 |
48 | ### 实验结果与分析
49 | 实验结果如下图所示, 左图为原图, 右图为直方图均衡化后的图像. 可以明显看出, 经过直方图的均衡化, 图片的灰度分布更均匀, 直接表现为图片的对比度更高, 显得更为清晰.
50 |
51 |

52 |

53 |
54 |
55 | ## 问题2 保边缘平滑法的实现
56 |
57 | ### 问题描述
58 | 编写一个程序完成如下功能:读入清晰图像,加上椒盐噪声,采用有选择保边缘平滑法对图像进行平滑。
59 |
60 | ### Code实现
61 | 实现思路如下:
62 | * 实现函数sp_noise(img),向图像添加椒盐噪声.
63 | * 实现函数smooth(img), 用选择保边缘平滑法对噪声图片进行平滑操作, 其具体思路为:对每个像素进行操作, 分别利用九种不同模板, 计算各模板内像素的平均值和方差, 将最小方差模板对应的像素平均值赋值给该像素. 值得注意的是, 对于图像边缘处的像素可能并不是每个模板均存在.
64 | * 依次对图片应用上述两个函数, 实现对图片施加椒盐噪声, 再利用选择保边缘平滑法对噪声图片进行平滑.
65 |
66 | 运行方式: `python pb2.py`
67 | ```Python
68 | import cv2
69 | import numpy as np
70 |
71 | def sp_noise(img):
72 | """
73 | 给图像添加椒盐噪声
74 | Parameters:
75 | img: 原图
76 | Return:
77 | 添加椒盐噪声后的图片
78 | """
79 | import random
80 |
81 |
82 |
83 | img_noise = np.zeros_like(img)
84 | for i in range(img.shape[0]):
85 | for j in range(img.shape[1]):
86 | rdn = random.random()
87 | # 椒噪声
88 | if rdn < 0.05:
89 | img_noise[i][j] = 0
90 | # 盐噪声
91 | elif rdn > 0.95:
92 | img_noise[i][j] = 255
93 | # 不添加噪声
94 | else:
95 | img_noise[i][j] = img[i][j]
96 |
97 | return img_noise
98 |
99 | def smooth(img):
100 | """
101 | 用选择保边缘平滑法
102 | Parameters:
103 | img: 待平滑图像(GRAY)
104 | Return:
105 | 平滑后图像
106 | """
107 | img_smooth = np.zeros_like(img)
108 | h, w = img.shape[0:2]
109 | for i in range(h):
110 | for j in range(w):
111 | std_mean = []
112 | # 3邻域
113 | if i>0 and j>0 and i1 and j>0 and j0 and i0 and j>1 and i0 and i1 and j>1:
141 | mask = [img[i-2,j-2], img[i-2,j-1], img[i-1,j-2],
142 | img[i-1,j-1], img[i-1,j], img[i,j-1], img[i,j]]
143 | std_mean.append((np.std(mask),np.mean(mask)))
144 | # 右上六边形
145 | if i>1 and j1:
156 | mask = [img[i+2,j-2], img[i+2,j-1], img[i+1,j-2],
157 | img[i+1,j-1], img[i+1,j], img[i,j-1], img[i,j]]
158 | std_mean.append((np.std(mask),np.mean(mask)))
159 | # 选取方差做小的模板的均值作为改点像素值
160 | img_smooth[i,j] = sorted(std_mean,
161 | key=lambda std_mean:std_mean[0])[0][1]
162 | return img_smooth
163 |
164 | if __name__ == "__main__":
165 | img = cv2.imread("luna.png", 0)
166 | # 添加椒盐噪声
167 | img_noise = sp_noise(img)
168 | cv2.imwrite("pb2_noise.png", img_noise)
169 | # 平滑:选择保边缘平滑法
170 | from time import time
171 | start = time()
172 | img_smooth = smooth(img_noise)
173 | end = time()
174 | print ("time cost:", end-start)
175 | cv2.imwrite("pb2_result.png", img_smooth)
176 | ```
177 |
178 | ### 实验结果与分析
179 | 实验结果如下图所示, 左图为施加椒盐噪声后的图像, 右图为经选择保边缘平滑法处理后的图像. 观察图片可以发现, 选择保边缘平滑法能一定程度上有效消除噪声, 且保持图片区域边界的细节不被破坏. 但由于需要对每个像素做九个模板操作,并进行数值比较, 因此算法耗时较长.
180 |
181 |

182 |

183 |
184 |
185 | # 问题 3 拉普拉斯增强
186 | ## 问题描述
187 | 编写一个程序完成拉普拉斯增强.
188 |
189 | ## Code实现
190 | 实现思路如下:
191 | * 生成拉普拉斯增强算子核
192 | * 利用上一步生成的增强算子对原图片进行滤波, 实现对原图片的拉普拉斯增强
193 |
194 | 运行方式: `python pb3.py`
195 | ```Python
196 | import cv2
197 | import numpy as np
198 |
199 | def Lap_eh(img):
200 | """
201 | 拉普拉斯增强
202 | Parameters:
203 | img: 待增强图像
204 | Return:
205 | 增强后的图像
206 | """
207 | lap_kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]])
208 | img_eh = cv2.filter2D(img, -1, lap_kernel)
209 | return img_eh
210 |
211 | if __name__ == "__main__":
212 | img = cv2.imread("luna.png", 0)
213 | img_eh = Lap_eh(img)
214 | cv2.imwrite("pb3_result.png", img_eh)
215 | ```
216 |
217 | ### 实验结果与分析
218 | 实验结果如下图所示, 左图为原始图像, 右图为经拉普拉斯增强后的图像. 对比增强前后图片可以发现, 经拉普拉斯增强后, 图片中的边缘更加清晰, 图片细节的纹理更加明显, 整体的视觉效果更强.
219 |
220 |

221 |

222 |
--------------------------------------------------------------------------------
/section_4/luna.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_4/luna.png
--------------------------------------------------------------------------------
/section_4/pb1.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 |
8 | def histequal4e(img):
9 | """
10 | 直方图均衡化
11 | Parameters:
12 | img : 原图片(GRAY)
13 | Return:
14 | 直方图均衡化后的图片
15 | """
16 | # 获取原图像灰度直方图
17 | hist = np.bincount(img.flatten(), minlength=256)
18 |
19 | # 根据比重构建均衡化后的直方图
20 | hist_new = np.cumsum(hist)/np.sum(hist) * 255
21 |
22 | # 生成直方图均衡化的图片
23 | img_result = np.zeros_like(img)
24 | for i in range(img.shape[0]):
25 | for j in range(img.shape[1]):
26 | img_result[i,j] = hist_new[img[i,j]]
27 |
28 | return img_result
29 |
30 | if __name__ == "__main__":
31 | img = cv2.imread("luna.png", 0)
32 | img_result = histequal4e(img)
33 | cv2.imwrite("pb1_result.png", img_result)
34 |
35 |
36 |
--------------------------------------------------------------------------------
/section_4/pb1_result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_4/pb1_result.png
--------------------------------------------------------------------------------
/section_4/pb2.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 |
8 | def sp_noise(img):
9 | """
10 | 给图像添加椒盐噪声
11 | Parameters:
12 | img: 原图
13 | Return:
14 | 添加椒盐噪声后的图片
15 | """
16 | import random
17 |
18 | img_noise = np.zeros_like(img)
19 | for i in range(img.shape[0]):
20 | for j in range(img.shape[1]):
21 | rdn = random.random()
22 | # 椒噪声
23 | if rdn < 0.05:
24 | img_noise[i][j] = 0
25 | # 盐噪声
26 | elif rdn > 0.95:
27 | img_noise[i][j] = 255
28 | # 不添加噪声
29 | else:
30 | img_noise[i][j] = img[i][j]
31 |
32 | return img_noise
33 |
34 | def smooth(img):
35 | """
36 | 用选择保边缘平滑法
37 | Parameters:
38 | img: 待平滑图像(GRAY)
39 | Return:
40 | 平滑后图像
41 | """
42 | img_smooth = np.zeros_like(img)
43 | h, w = img.shape[0:2]
44 | for i in range(h):
45 | for j in range(w):
46 | std_mean = []
47 |
48 | # 3邻域
49 | if i>0 and j>0 and i1 and j>0 and j0 and i0 and j>1 and i0 and i1 and j>1:
79 | mask = [img[i-2,j-2], img[i-2,j-1], img[i-1,j-2], img[i-1,j-1], img[i-1,j], img[i,j-1], img[i,j]]
80 | std_mean.append((np.std(mask),np.mean(mask)))
81 |
82 | # 右上六边形
83 | if i>1 and j1:
94 | mask = [img[i+2,j-2], img[i+2,j-1], img[i+1,j-2], img[i+1,j-1], img[i+1,j], img[i,j-1], img[i,j]]
95 | std_mean.append((np.std(mask),np.mean(mask)))
96 |
97 | # 选取方差做小的模板的均值作为改点像素值
98 | img_smooth[i,j] = sorted(std_mean, key=lambda std_mean:std_mean[0])[0][1]
99 | return img_smooth
100 |
101 |
102 |
103 | if __name__ == "__main__":
104 |
105 | img = cv2.imread("luna.png", 0)
106 | # 添加椒盐噪声
107 | img_noise = sp_noise(img)
108 | cv2.imwrite("pb2_noise.png", img_noise)
109 | # 平滑:选择保边缘平滑法
110 | from time import time
111 | start = time()
112 | img_smooth = smooth(img_noise)
113 | end = time()
114 | print ("time cost:", end-start)
115 | cv2.imwrite("pb2_result.png", img_smooth)
--------------------------------------------------------------------------------
/section_4/pb2_noise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_4/pb2_noise.png
--------------------------------------------------------------------------------
/section_4/pb2_result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_4/pb2_result.png
--------------------------------------------------------------------------------
/section_4/pb3.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 |
8 | def Lap_eh(img):
9 | """
10 | 拉普拉斯增强
11 | Parameters:
12 | img: 待增强图像
13 | Return:
14 | 增强后的图像
15 | """
16 | lap_kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]])
17 | img_eh = cv2.filter2D(img, -1, lap_kernel)
18 |
19 | return img_eh
20 |
21 | if __name__ == "__main__":
22 | img = cv2.imread("luna.png", 0)
23 | img_eh = Lap_eh(img)
24 | cv2.imwrite("pb3_result.png", img_eh)
--------------------------------------------------------------------------------
/section_4/pb3_result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_4/pb3_result.png
--------------------------------------------------------------------------------
/section_5/README.md:
--------------------------------------------------------------------------------
1 | # Section 5: 图像形态学处理--以指纹图像处理为例
2 |
3 | > 实验环境
4 | > * Python 3.6.0
5 | > * Opencv 3.1.0
6 |
7 | ## 1. 问题描述
8 | 编一个程序实现如下功能:
9 | * 读入一幅指纹图像(自己找)
10 | * 对图像进行二值化(方法自定,可以是阈值法)
11 | * 采用形态学骨架提取和距离变换骨架提取两种算法分别提取图像骨架
12 | * 采用裁剪算法,并分析其效果
13 |
14 | ## 2. Code实现 & 实验
15 | ### 2.1 整体实现思路与分析
16 | 首先, 为实现上述要求功能, 需实现以下子功能:
17 | * 灰度图像二值化
18 | * 形态学基础算法: 腐蚀, 膨胀, 开运算
19 | * 骨架提取算法: 为了更好地理解理论知识, 这里采用三种方法提取骨架(基于腐蚀和开运算的骨架提取, 基于细化的骨架提取, 基于距离变换的骨架提取)
20 | * 裁剪算法
21 |
22 | 下面,依次展开. 实验结果通过运行`python main.py`可获得.
23 |
24 | ### 2.2 灰度图像二值化: 迭代法阈值确定(binaryzation.py)
25 |
26 |

27 |
28 | 本次作业以上图为实验所用的指纹图像. 可以看到, 此图像较为模糊, 某些区域较难区分前景和背景, 很难人为地确定二值化阈值. 因此, 基于迭代算法确定阈值, 然后进行二值化. 迭代算法的框图如下图所示.
29 |
30 |

31 |
32 |
33 | ```Python
34 | def loop(img):
35 | """
36 | 根据阈值分割的迭代法计算阈值T
37 | Parameters:
38 | img: origin image (GRAY)
39 | Return:
40 | Threshold T
41 | """
42 | g_min = int(np.min(img))
43 | g_max = int(np.max(img))
44 | T = 0
45 | T_new = 0.5*(g_min + g_max)
46 |
47 | eps = 1e-5
48 |
49 | while np.abs(T_new - T) >= eps:
50 | T = T_new
51 | l = []
52 | g = []
53 | for i in range(img.shape[0]):
54 | for j in range(img.shape[1]):
55 | if img[i,j] < T: l.append(img[i,j])
56 | else: g.append(img[i,j])
57 | T_new = 0.5*(np.mean(l) + np.mean(g))
58 |
59 | return T_new
60 |
61 | def binaryzation(img):
62 | """
63 | 灰度图像二值化
64 | Parameter:
65 | img: 灰度图像
66 | Return:
67 | 二值化图像
68 | """
69 | # 迭代法求阈值
70 | k = loop(img)
71 | # 二值化
72 | img_bin = np.where(img>k, 0, 255)
73 |
74 | return k, img_bin.astype(np.uint8)
75 | ```
76 | 通过迭代法得到二值化阈值174.78, 二值化的结果如下图所示. 左图为原图, 右图为二值化结果.可以看到, 二值化效果较优.
77 |
78 |

79 |

80 |
81 |
82 | ### 2.3 形态学基础算法(basic_function.py)
83 | 主要实现了本作业所需的三个基础算法: 腐蚀, 膨胀 和 开运算.
84 | > 为了提高运算效率, 在实现腐蚀和膨胀时, 均基于位移运算.
85 | * 基于位移运算的腐蚀运算
86 | ```Python
87 | def erode(img):
88 | """
89 | 使用3*3矩形结构子 腐蚀操作
90 | Parameter:
91 | img: 待腐蚀图像
92 | Return:
93 | img_result: 腐蚀结果图像
94 | """
95 | # 初始化图像平移矩阵
96 | m_1 = np.array([[1,0,-1],[0,1,-1]], dtype=np.float32)
97 | m_2 = np.array([[1,0,0],[0,1,-1]], dtype=np.float32)
98 | m_3 = np.array([[1,0,1],[0,1,-1]], dtype=np.float32)
99 | m_4 = np.array([[1,0,-1],[0,1,0]], dtype=np.float32)
100 | m_5 = np.array([[1,0,1],[0,1,0]], dtype=np.float32)
101 | m_6 = np.array([[1,0,-1],[0,1,1]], dtype=np.float32)
102 | m_7 = np.array([[1,0,0],[0,1,1]], dtype=np.float32)
103 | m_8 = np.array([[1,0,1],[0,1,1]], dtype=np.float32)
104 | M = [m_1, m_2, m_3, m_4, m_5, m_6, m_7, m_8]
105 |
106 | # 9个平移后的图像取交集得到腐蚀结果
107 | img_result = img.copy()
108 | for i in M:
109 | img_shift = cv2.warpAffine(img,i,(img.shape[1],img.shape[0]))
110 | img_result = cv2.bitwise_and(img_result, img_shift)
111 |
112 | return img_result
113 | ```
114 | * 基于位移运算的膨胀运算
115 | ```Python
116 |
117 | def dilate(img):
118 | """
119 | 使用3*3矩形结构子 膨胀操作
120 | Parameter:
121 | img: 待膨胀图像
122 | Return:
123 | img_result: 膨胀结果图像
124 | """
125 | # 初始化图像平移矩阵
126 | m_1 = np.array([[1,0,-1],[0,1,-1]], dtype=np.float32)
127 | m_2 = np.array([[1,0,0],[0,1,-1]], dtype=np.float32)
128 | m_3 = np.array([[1,0,1],[0,1,-1]], dtype=np.float32)
129 | m_4 = np.array([[1,0,-1],[0,1,0]], dtype=np.float32)
130 | m_5 = np.array([[1,0,1],[0,1,0]], dtype=np.float32)
131 | m_6 = np.array([[1,0,-1],[0,1,1]], dtype=np.float32)
132 | m_7 = np.array([[1,0,0],[0,1,1]], dtype=np.float32)
133 | m_8 = np.array([[1,0,1],[0,1,1]], dtype=np.float32)
134 | M = [m_1, m_2, m_3, m_4, m_5, m_6, m_7, m_8]
135 |
136 | # 9个平移后的图像取并集得到腐蚀结果
137 | img_result = img.copy()
138 | for i in M:
139 | img_shift = cv2.warpAffine(img,i,(img.shape[1],img.shape[0]))
140 | img_result = cv2.bitwise_or(img_result, img_shift)
141 |
142 | return img_result
143 | ```
144 | * 开运算
145 | ```Python
146 | def open_morph(img):
147 | """
148 | 开运算
149 | Parameter:
150 | img: 待进行开运算的图像
151 | Return:
152 | img_result: 开运算结果图像
153 | """
154 | # 先腐蚀, 再膨胀
155 | img_result = erode(img)
156 | img_result = dilate(img_result)
157 |
158 | return img_result
159 | ```
160 |
161 | ### 2.4 基于腐蚀和开运算的骨架提取(sk_morph.py)
162 | 首先实现根据骨架定义的提取方式, 也即基于腐蚀和开运算的骨架提取.
163 | ```Python
164 | def sk_morph(img):
165 | """
166 | 形态学骨架提取
167 | Parameter:
168 | img: 待提取骨架图像(默认为前景为白色的二值图像)
169 | Return:
170 |
171 | img_result: 骨架图像(前景为白色的二值图像)
172 | """
173 | # 骨架图像初始化
174 | img_result = np.zeros_like(img)
175 |
176 | # 循环提取骨架, 当腐蚀后图像无前景时停止
177 | while(np.sum(img)):
178 | # 开运算
179 | img_open = open_morph(img)
180 | # 求差
181 | img_s = img - img_open
182 | # 求并生成骨架
183 | img_result = cv2.bitwise_or(img_result, img_s.copy())
184 | # 腐蚀
185 | img = erode(img)
186 | return img_result
187 | ```
188 | 对二值化图像利用上述算法提取骨架, 结果如下图所示. 左图为二值化结果, 右图为提取结果.
189 |
190 |

191 |

192 |
193 |
194 |
195 | ### 2.5 基于细化的骨架提取(sk_thin.py)
196 | 细化也可以达到骨架提取的目的, 选用细化常用的结构元序列进行细化操作.
197 |
198 | > **值得注意的是**,实现时为提升效率, 细化时采用卷积的方式实现, 而未使用循环语句. 由于细化时需对每个结构元进行击中击不中运算, 而击中击不中运算中包含判断. 为在根据卷积结果判断击中击不中, 对结果元中的每个位置进行差异化赋值. 赋值方法如下:
199 | > * 对于结构元中的击中位置, 以$2^n$赋值, 其中n为击中位置的序号(从0开始). 对于细化常用的结构元, 有四个击中区域, 则$n=0,1,2,3$, 也即分别赋值1,2,4,8.
200 | > * 对于结构元中的击不中位置, 均赋值$2^(n+1)$.对于细化常用的结构元, 有四个击不中区域, 则$n+1=4$, 也即均赋值16.
201 | > * 对于结构元中的不必考虑位置, 赋值为0.
202 | >
203 | > 此时, 若击中, 则卷积结果为15, 否则为击不中. 因此, 卷积后只需找到结果中等于15的位置即为击中位置,其余位置为击不中.
204 |
205 |

206 |
207 |
208 | ```Python
209 | def thinning(img, K):
210 | """
211 | 细化运算实体
212 | Parameters:
213 | img: 待细化图像
214 | K: 结构子序列
215 | Return:
216 | 细化后结果图像
217 | """
218 | # 归一
219 | img_result = img/255
220 | # 初始化用于保存上一次结果的矩阵
221 | img_old = 1 - img_result
222 |
223 | # 循环细化.直至图像保持不变
224 | while np.sum(img_result-img_old):
225 | img_old = img_result
226 | for i in K:
227 | # 基于卷积结果的击中击不中
228 | img_temp = np.where(cv2.filter2D(img_result.copy(),-1,i
229 | ,borderType=0)==15, 1, 0)
230 | img_result = img_result - img_temp
231 |
232 | img_result *= 255
233 | return img_result.astype(np.uint8)
234 |
235 | def sk_thin(img):
236 | """
237 | 细化提取骨架
238 | Parameter:
239 | img: 待提取图像
240 | Return:
241 | 提取骨架结果图像
242 | """
243 | # 生成8个结构子序列
244 | k_1 = np.array([[16,16,16],[0,1,0],[2,4,8]], dtype=np.uint8)
245 | k_2 = np.array([[0,16,16],[1,2,16],[4,8,0]], dtype=np.uint8)
246 | k_3 = np.array([[1,0,16],[2,4,16],[8,0,16]], dtype=np.uint8)
247 | k_4 = np.array([[1,2,0],[4,8,16],[0,16,16]], dtype=np.uint8)
248 | k_5 = np.array([[1,2,4],[0,8,0],[16,16,16]], dtype=np.uint8)
249 | k_6 = np.array([[0,1,2],[16,4,8],[16,16,0]], dtype=np.uint8)
250 | k_7 = np.array([[16,0,1],[16,2,4],[16,0,8]], dtype=np.uint8)
251 | k_8 = np.array([[16,16,0],[16,1,2],[0,4,8]], dtype=np.uint8)
252 |
253 | K = [k_1, k_2, k_3, k_4, k_5, k_6, k_7, k_8]
254 |
255 | # 细化操作
256 | img_result = thinning(img, K)
257 |
258 | return img_result
259 | ```
260 | 对二值化图像利用上述算法提取骨架, 结果如下图所示. 左图为二值化结果, 右图为提取结果.
261 |
262 |

263 |

264 |
265 |
266 | ### 2.6 基于距离变换的骨架提取(sk_distTrans.py)
267 | 基于距离变换的骨架提取首先通过形态学运算获得前景边界, 然后对边界图像做距离变换(彭老师提供了距离变换的Matlab代码, 由于本作业使用python-openCV, 因此距离变换直接调用函数, 距离为欧氏距离), 接着求距离变化中的局部极大值, 落入原二值图像中的局部极大值即为图像的骨架.
268 |
269 | > **值得注意的是**,实现时为提升效率, 求取极大值时使用卷积方法实现, 构建8个卷积模板, 其作用为用中心元素像素值依次减去周围8个元素的像素值, 若某一位置得到的8个卷积结果均大于等于0, 则证明它是8邻域内的极大值.
270 |
271 | ```Python
272 | def find_max(img):
273 | """
274 | 获得8邻域内极大值像素组成的图像
275 | Parameter:
276 | img: 待操作图像(距离变换结果)
277 | Return:
278 | img_result: 由8邻域内极大值像素组成的二值化图像
279 | """
280 | # 生成8个减法模板
281 | kmax_1 = np.array([[-1,0,0],[0,1,0],[0,0,0]],dtype=np.float32)
282 | kmax_2 = np.array([[0,-1,0],[0,1,0],[0,0,0]],dtype=np.float32)
283 | kmax_3 = np.array([[0,0,-1],[0,1,0],[0,0,0]],dtype=np.float32)
284 | kmax_4 = np.array([[0,0,0],[-1,1,0],[0,0,0]],dtype=np.float32)
285 | kmax_5 = np.array([[0,0,0],[0,1,-1],[0,0,0]],dtype=np.float32)
286 | kmax_6 = np.array([[0,0,0],[0,1,0],[-1,0,0]],dtype=np.float32)
287 | kmax_7 = np.array([[0,0,0],[0,1,0],[0,-1,0]],dtype=np.float32)
288 | kmax_8 = np.array([[0,0,0],[0,1,0],[0,0,-1]],dtype=np.float32)
289 | kernel = [kmax_1, kmax_2, kmax_3, kmax_4, kmax_5, kmax_6,
290 | kmax_7, kmax_8]
291 |
292 | # 依次进行减法模板操作, 取结果交集为极大值像素图像
293 | img_result = cv2.bitwise_not(np.zeros_like(img, dtype=np.uint8))
294 | for i in kernel:
295 | # 减法模板滤波
296 | img_m = cv2.filter2D(img, -1, i)
297 | # 差值非负处取为255: 操作点像素值>=被减处像素
298 | img_m = np.where(img_m>=0.0, 255, 0)
299 | img_m = img_m.astype(np.uint8)
300 | # 大于等于8邻域内所有像素的点为区域极大值点
301 | img_result = cv2.bitwise_and(img_result, img_m)
302 |
303 | return img_result
304 |
305 | def sk_distTrans(img):
306 | """
307 | 基于距离变换的骨架提取
308 | Parameter:
309 | img: 待提取骨架图像(默认为前景为白色的二值图像)
310 | Return:
311 | img_result: 骨架图像(前景为白色的二值图像)
312 | """
313 | # 通过形态学操作获得前景边界
314 | img_bd = img - erode(img)
315 | # cv2.imwrite("bd.png", img_bd)
316 | # 对边界图像做距离变换
317 | img_distTrans = cv2.distanceTransform(
318 | cv2.bitwise_not(img_bd.copy()),
319 | cv2.DIST_L2, cv2.DIST_MASK_3)
320 | # 求距离变换图中的局部极大值
321 | img_max = find_max(img_distTrans)
322 | # 落入原二值图像中的局部极大值即为图像的骨架
323 | img_result = cv2.bitwise_and(img_max, img)
324 |
325 | return img_result
326 | ```
327 | 对二值化图像利用上述算法提取骨架, 结果如下图所示. 左图为二值化结果, 右图为提取结果. 和之前提取的骨架结果对比, 可以发现原二值图像中的某些细小的前景经过基于距离变换的骨架提取后消失(如图像左下方区域). 通过对算法实现过程进行分析, 我认为造成这种情况的原因如下: 细小点经形态学边界提取的结果通常为其本身, 而对边界图像求取距离变换后, 实心图像内部的距离均为0, 而外部距离大于0, 在并不符合区域内极大值点的要求, 因而造成细小点的消失.
328 |
329 |

330 |

331 |
332 |
333 | ### 2.7 裁剪(tailor.py)
334 | 裁剪的目的是清除细化和骨架算法产生的一些不必要的附加成分. 为理解裁剪算法, 更清晰地感受裁剪的效果, 本部分以基于细化的骨架提取结果为例, 对其进行裁剪操作, 并对比分析其效果.
335 |
336 | 观察待裁剪图像, 可以发现图像主干周围存在寄生成分, 这里通过观察与实验, 假设3个及以下像素长度的分支均为寄生部分, 进行删除操作.实现步骤如下:
337 | * 生成由8个常用于端点检测的结构元组成的结构元序列
338 | * 利用上述序列连续对待裁剪图像细化3次
339 | * 利用序列找到上一步结果图像的端点
340 | * 对端点进行3次膨胀处理, 每次膨胀后用待裁剪图像做消减因子, 捡回"误伤"元素
341 | * 取上一步结果和连续3次细化结果的并集图像为裁剪结果
342 |
343 | > 这里涉及到击中击不中算法时仍使用前文所用的卷积方法
344 |
345 | ```Python
346 | def thinning(img, K):
347 | """
348 | 细化
349 | Parameters:
350 | img: 待细化图像
351 | K: 结构子序列
352 | Return:
353 | 细化后结果图像
354 | """
355 | # 归一
356 | img_result = img/255
357 | # 利用结构子序列重复3次细化
358 | for i in range(3):
359 | for i in K:
360 | img_temp = np.where(cv2.filter2D(img_result.copy(),-1,i,
361 | borderType=0)==3, 1, 0)
362 | img_result = img_result - img_temp
363 |
364 | img_result *= 255
365 | return img_result.astype(np.uint8)
366 |
367 | def find_end(img, K):
368 | """
369 | 找到端节点
370 | Parameters:
371 | img: 输入图像
372 | K: 结构子序列
373 | Return:
374 | 只有端节点为前景的图像
375 | """
376 | # 像素归一化
377 | img_ones = img/255
378 | img_result = np.zeros_like(img, dtype=np.uint8)
379 |
380 | # 利用结构子序列寻找端点
381 | for i in K:
382 | img_temp = np.where(cv2.filter2D(img_ones.copy(),-1,i,
383 | borderType=0)==3, 1, 0)
384 | img_result = img_result + img_temp
385 |
386 | img_result *= 255
387 | return img_result.astype(np.uint8)
388 |
389 | def tailor(img):
390 | """
391 | 裁剪
392 | Parameters:
393 | img: 待裁剪图像
394 | Return:
395 | 裁剪结果图像
396 | """
397 | # 生成8个结构子
398 | k_1 = np.array([[0,4,4],[1,2,4],[0,4,4]], dtype=np.uint8)
399 | k_2 = np.array([[0,1,0],[4,2,4],[4,4,4]], dtype=np.uint8)
400 | k_3 = np.array([[4,4,0],[4,1,2],[4,4,0]], dtype=np.uint8)
401 | k_4 = np.array([[4,4,4],[4,1,4],[0,2,0]], dtype=np.uint8)
402 | k_5 = np.array([[1,4,4],[4,2,4],[4,4,4]], dtype=np.uint8)
403 | k_6 = np.array([[4,4,1],[4,2,4],[4,4,4]], dtype=np.uint8)
404 | k_7 = np.array([[4,4,4],[4,1,4],[4,4,2]], dtype=np.uint8)
405 | k_8 = np.array([[4,4,4],[4,1,4],[2,4,4]], dtype=np.uint8)
406 |
407 | K = [k_1, k_2, k_3, k_4, k_5, k_6, k_7, k_8]
408 |
409 | # 细化(去除3个像素组成的分支)
410 | img_thin = thinning(img, K)
411 | # 找端点
412 | img_end = find_end(img_thin, K)
413 | # 膨胀运算,捡回误伤元素
414 | img_dilate = img_end
415 | for _ in range(3):
416 | img_dilate = dilate(img_dilate)
417 | img_dilate = cv2.bitwise_and(img_dilate, img)
418 | # 获得裁剪结果
419 | img_result = cv2.bitwise_or(img_dilate, img_thin)
420 |
421 | return img_result
422 | ```
423 | 裁剪结果如下图所示, 左图为待裁剪图像, 右图为裁剪结果. 可以看到, 图像的寄生成分得到有效消除, 但同时也些许像素长度小于等于3像素的非寄生成分不可避免地被误删.
424 |
425 |

426 |

427 |
428 |
429 | ### 2.8 主程序(main.py)
430 | 将上面的子功能进行组合形成主程序.上文中的实验结果可通过运行主程序得到.`python main.py`
431 |
432 | ```Python
433 | import cv2
434 | import numpy as np
435 |
436 | from binaryzation import binaryzation
437 | from sk_morph import sk_morph
438 | from sk_thin import sk_thin
439 | from sk_distTrans import sk_distTrans
440 | from tailor import tailor
441 |
442 | def main(img):
443 | """
444 | 主程序
445 | """
446 | # 二值化: 基于迭代法获取二值化阈值
447 | print ("Processing: 二值化...")
448 | k, img_bin = binaryzation(img)
449 |
450 | print ("阈值:", k)
451 | cv2.imwrite("bin.png", img_bin)
452 |
453 | # 基于腐蚀和开运算的骨架提取
454 | print ("Processing: 基于腐蚀和开运算的骨架提取...")
455 | img_sk_morph = sk_morph(img_bin)
456 | cv2.imwrite("morph.png", img_sk_morph)
457 |
458 | # 基于单纯细化的骨架提取
459 | print ("Processing: 基于单纯细化的骨架提取...")
460 | img_sk_thin = sk_thin(img_bin)
461 | cv2.imwrite("morph_thin.png", img_sk_thin)
462 |
463 | # 基于距离变换的骨架提取
464 | print ("Processing: 基于距离变换的骨架提取...")
465 | img_sk_dist = sk_distTrans(img_bin)
466 | cv2.imwrite("dist.png", img_sk_dist)
467 |
468 | # 裁剪:以细化所得骨架为例
469 | print ("Processing: 裁剪:以细化所得骨架为例...")
470 | img_result = tailor(img_sk_thin)
471 | cv2.imwrite("tailor.png", img_result)
472 |
473 | if __name__ == "__main__":
474 | img = cv2.imread("origin.png", 0)
475 | main(img)
476 | ```
--------------------------------------------------------------------------------
/section_5/basic_function.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 |
8 | def erode(img):
9 | """
10 | 使用3*3矩形结构子 腐蚀操作
11 | Parameter:
12 | img: 待腐蚀图像
13 | Return:
14 | img_result: 腐蚀结果图像
15 | """
16 | # 初始化图像平移矩阵
17 | m_1 = np.array([[1,0,-1],[0,1,-1]], dtype=np.float32)
18 | m_2 = np.array([[1,0,0],[0,1,-1]], dtype=np.float32)
19 | m_3 = np.array([[1,0,1],[0,1,-1]], dtype=np.float32)
20 | m_4 = np.array([[1,0,-1],[0,1,0]], dtype=np.float32)
21 | m_5 = np.array([[1,0,1],[0,1,0]], dtype=np.float32)
22 | m_6 = np.array([[1,0,-1],[0,1,1]], dtype=np.float32)
23 | m_7 = np.array([[1,0,0],[0,1,1]], dtype=np.float32)
24 | m_8 = np.array([[1,0,1],[0,1,1]], dtype=np.float32)
25 | M = [m_1, m_2, m_3, m_4, m_5, m_6, m_7, m_8]
26 |
27 | # 9个平移后的图像取交集得到腐蚀结果
28 | img_result = img.copy()
29 | for i in M:
30 | img_shift = cv2.warpAffine(img, i, (img.shape[1],img.shape[0]))
31 | img_result = cv2.bitwise_and(img_result, img_shift)
32 |
33 | return img_result
34 |
35 | def dilate(img):
36 | """
37 | 使用3*3矩形结构子 膨胀操作
38 | Parameter:
39 | img: 待膨胀图像
40 | Return:
41 | img_result: 膨胀结果图像
42 | """
43 | # 初始化图像平移矩阵
44 | m_1 = np.array([[1,0,-1],[0,1,-1]], dtype=np.float32)
45 | m_2 = np.array([[1,0,0],[0,1,-1]], dtype=np.float32)
46 | m_3 = np.array([[1,0,1],[0,1,-1]], dtype=np.float32)
47 | m_4 = np.array([[1,0,-1],[0,1,0]], dtype=np.float32)
48 | m_5 = np.array([[1,0,1],[0,1,0]], dtype=np.float32)
49 | m_6 = np.array([[1,0,-1],[0,1,1]], dtype=np.float32)
50 | m_7 = np.array([[1,0,0],[0,1,1]], dtype=np.float32)
51 | m_8 = np.array([[1,0,1],[0,1,1]], dtype=np.float32)
52 | M = [m_1, m_2, m_3, m_4, m_5, m_6, m_7, m_8]
53 |
54 | # 9个平移后的图像取并集得到腐蚀结果
55 | img_result = img.copy()
56 | for i in M:
57 | img_shift = cv2.warpAffine(img, i, (img.shape[1],img.shape[0]))
58 | img_result = cv2.bitwise_or(img_result, img_shift)
59 |
60 | return img_result
61 |
62 | def open_morph(img):
63 | """
64 | 开运算
65 | Parameter:
66 | img: 待进行开运算的图像
67 | Return:
68 | img_result: 开运算结果图像
69 | """
70 | # 先腐蚀, 再膨胀
71 | img_result = erode(img)
72 | img_result = dilate(img_result)
73 |
74 | return img_result
75 |
76 | def show_img(name, img):
77 | """
78 | Show the image
79 |
80 | Parameters:
81 | name: name of window
82 | img: image
83 | """
84 | cv2.namedWindow(name, 0)
85 | cv2.imshow(name, img)
86 |
87 | # if __name__ == "__main__":
88 | # img = np.array([[0,0,0,0,0],[0,255,255,255,0],[255,255,255,255,255],[0,255,255,255,0],[0,0,0,0,0]], dtype=np.uint8)
89 | # show_img("origin", img)
90 | # img_erode = erode(img)
91 | # show_img("erode", img_erode)
92 | # img_dilate = dilate(img)
93 | # show_img("dilate", img_dilate)
94 | # img_open = open_morph(img)
95 | # show_img("open", img_open)
96 |
97 | # cv2.waitKey()
98 | # cv2.destroyAllWindows()
99 |
--------------------------------------------------------------------------------
/section_5/binaryzation.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 |
8 | def loop(img):
9 | """
10 | 根据阈值分割的迭代法计算阈值T
11 | Parameters:
12 | img: origin image (GRAY)
13 | Return:
14 | Threshold T
15 | """
16 | g_min = int(np.min(img))
17 | g_max = int(np.max(img))
18 | T = 0
19 | T_new = 0.5*(g_min + g_max)
20 |
21 | eps = 1e-5
22 |
23 | while np.abs(T_new - T) >= eps:
24 | T = T_new
25 | l = []
26 | g = []
27 | for i in range(img.shape[0]):
28 | for j in range(img.shape[1]):
29 | if img[i,j] < T: l.append(img[i,j])
30 | else: g.append(img[i,j])
31 | T_new = 0.5*(np.mean(l) + np.mean(g))
32 |
33 | return T_new
34 |
35 | def binaryzation(img):
36 | """
37 | 灰度图像二值化
38 | Parameter:
39 | img: 灰度图像
40 | Return:
41 | 二值化图像
42 | """
43 | # 迭代法求阈值
44 | k = loop(img)
45 | # 二值化
46 | img_bin = np.where(img>k, 0, 255)
47 |
48 | return k, img_bin.astype(np.uint8)
49 |
50 | if __name__ == "__main__":
51 | img = cv2.imread("origin.png", 0)
52 | k = loop(img)
53 | print (k)
54 | _,img_bin = cv2.threshold(img.copy(), k, 255, cv2.THRESH_BINARY_INV)
55 | cv2.imwrite("bin.png", img_bin)
56 |
57 |
58 |
--------------------------------------------------------------------------------
/section_5/img/bin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_5/img/bin.png
--------------------------------------------------------------------------------
/section_5/img/bin_algo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_5/img/bin_algo.png
--------------------------------------------------------------------------------
/section_5/img/dist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_5/img/dist.png
--------------------------------------------------------------------------------
/section_5/img/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_5/img/example.png
--------------------------------------------------------------------------------
/section_5/img/morph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_5/img/morph.png
--------------------------------------------------------------------------------
/section_5/img/morph_thin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_5/img/morph_thin.png
--------------------------------------------------------------------------------
/section_5/img/tailor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_5/img/tailor.png
--------------------------------------------------------------------------------
/section_5/img/tailor_exp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_5/img/tailor_exp.png
--------------------------------------------------------------------------------
/section_5/img/thin_exp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_5/img/thin_exp.png
--------------------------------------------------------------------------------
/section_5/main.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 |
8 | from binaryzation import binaryzation
9 | from sk_morph import sk_morph
10 | from sk_thin import sk_thin
11 | from sk_distTrans import sk_distTrans
12 | from tailor import tailor
13 |
14 | def main(img):
15 | """
16 | 主程序
17 | """
18 | # 二值化: 基于迭代法获取二值化阈值
19 | print ("Processing: 二值化...")
20 | k, img_bin = binaryzation(img)
21 | print ("阈值:", k)
22 | cv2.imwrite("bin.png", img_bin)
23 |
24 | # 基于腐蚀和开运算的骨架提取
25 | print ("Processing: 基于腐蚀和开运算的骨架提取...")
26 | img_sk_morph = sk_morph(img_bin)
27 | cv2.imwrite("morph.png", img_sk_morph)
28 |
29 | # 基于单纯细化的骨架提取
30 | print ("Processing: 基于单纯细化的骨架提取...")
31 | img_sk_thin = sk_thin(img_bin)
32 | cv2.imwrite("morph_thin.png", img_sk_thin)
33 |
34 | # 基于距离变换的骨架提取
35 | print ("Processing: 基于距离变换的骨架提取...")
36 | img_sk_dist = sk_distTrans(img_bin)
37 | cv2.imwrite("dist.png", img_sk_dist)
38 |
39 | # 裁剪:以细化所得骨架为例
40 | print ("Processing: 裁剪:以细化所得骨架为例...")
41 | img_result = tailor(img_sk_thin)
42 | cv2.imwrite("tailor.png", img_result)
43 |
44 | if __name__ == "__main__":
45 | img = cv2.imread("origin.png", 0)
46 | main(img)
--------------------------------------------------------------------------------
/section_5/origin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_5/origin.png
--------------------------------------------------------------------------------
/section_5/sk_distTrans.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 |
8 | from basic_function import erode
9 |
10 | def find_max(img):
11 | """
12 | 获得8邻域内极大值像素组成的图像
13 | Parameter:
14 | img: 待操作图像(距离变换结果)
15 | Return:
16 | img_result: 由8邻域内极大值像素组成的二值化图像
17 | """
18 | # 生成8个减法模板
19 | kmax_1 = np.array([[-1,0,0],[0,1,0],[0,0,0]],dtype=np.float32)
20 | kmax_2 = np.array([[0,-1,0],[0,1,0],[0,0,0]],dtype=np.float32)
21 | kmax_3 = np.array([[0,0,-1],[0,1,0],[0,0,0]],dtype=np.float32)
22 | kmax_4 = np.array([[0,0,0],[-1,1,0],[0,0,0]],dtype=np.float32)
23 | kmax_5 = np.array([[0,0,0],[0,1,-1],[0,0,0]],dtype=np.float32)
24 | kmax_6 = np.array([[0,0,0],[0,1,0],[-1,0,0]],dtype=np.float32)
25 | kmax_7 = np.array([[0,0,0],[0,1,0],[0,-1,0]],dtype=np.float32)
26 | kmax_8 = np.array([[0,0,0],[0,1,0],[0,0,-1]],dtype=np.float32)
27 | kernel = [kmax_1, kmax_2, kmax_3, kmax_4, kmax_5, kmax_6, kmax_7, kmax_8]
28 |
29 | # 依次进行减法模板操作, 取结果交集为极大值像素图像
30 | img_result = cv2.bitwise_not(np.zeros_like(img, dtype=np.uint8))
31 | for i in kernel:
32 | # 减法模板滤波
33 | img_m = cv2.filter2D(img, -1, i)
34 | # 差值非负处取为255: 操作点像素值>=被减处像素
35 | img_m = np.where(img_m>=0.0, 255, 0)
36 | img_m = img_m.astype(np.uint8)
37 | # 大于等于8邻域内所有像素的点为区域极大值点
38 | img_result = cv2.bitwise_and(img_result, img_m)
39 |
40 | return img_result
41 |
42 | def sk_distTrans(img):
43 | """
44 | 基于距离变换的骨架提取
45 | Parameter:
46 | img: 待提取骨架图像(默认为前景为白色的二值图像)
47 | Return:
48 | img_result: 骨架图像(前景为白色的二值图像)
49 | """
50 | # 通过形态学操作获得前景边界
51 | img_bd = img - erode(img)
52 | # cv2.imwrite("bd.png", img_bd)
53 | # 对边界图像做距离变换
54 | img_distTrans = cv2.distanceTransform(cv2.bitwise_not(img_bd.copy()), cv2.DIST_L2, cv2.DIST_MASK_3)
55 | # 求距离变换图中的局部极大值
56 | img_max = find_max(img_distTrans)
57 | # 落入原二值图像中的局部极大值即为图像的骨架
58 | img_result = cv2.bitwise_and(img_max, img)
59 |
60 | return img_result
61 |
62 |
63 | if __name__ == "__main__":
64 | img = cv2.imread("bin.png", 0)
65 | img_result = sk_distTrans(img)
66 | cv2.imwrite("dist.png", img_result)
67 |
68 |
--------------------------------------------------------------------------------
/section_5/sk_morph.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 |
8 | from basic_function import erode, open_morph
9 |
10 | def sk_morph(img):
11 | """
12 | 形态学骨架提取
13 | Parameter:
14 | img: 待提取骨架图像(默认为前景为白色的二值图像)
15 | Return:
16 | img_result: 骨架图像(前景为白色的二值图像)
17 | """
18 | # 骨架图像初始化
19 | img_result = np.zeros_like(img)
20 |
21 | # 循环提取骨架, 当腐蚀后图像无前景时停止
22 | while(np.sum(img)):
23 | # 开运算
24 | img_open = open_morph(img)
25 | # 求差
26 | img_s = img - img_open
27 | # 求并生成骨架
28 | img_result = cv2.bitwise_or(img_result, img_s.copy())
29 | # 腐蚀
30 | img = erode(img)
31 | return img_result
32 |
33 |
34 | if __name__ == "__main__":
35 | img = cv2.imread("bin.png", 0)
36 | img_result = sk_morph(img)
37 | cv2.imwrite("morph.png", img_result)
38 |
39 |
--------------------------------------------------------------------------------
/section_5/sk_thin.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 |
8 | from basic_function import dilate
9 |
10 | def thinning(img, K):
11 | """
12 | 细化运算实体
13 | Parameters:
14 | img: 待细化图像
15 | K: 结构子序列
16 | Return:
17 | 细化后结果图像
18 | """
19 | # 归一
20 | img_result = img/255
21 | # 初始化用于保存上一次结果的矩阵
22 | img_old = 1 - img_result
23 |
24 | # 循环细化.直至图像保持不变
25 | while np.sum(img_result-img_old):
26 | img_old = img_result
27 | for i in K:
28 | # 基于卷积结果的击中击不中
29 | img_temp = np.where(cv2.filter2D(img_result.copy(),-1,i,borderType=0)==15, 1, 0)
30 | img_result = img_result - img_temp
31 |
32 | img_result *= 255
33 | return img_result.astype(np.uint8)
34 |
35 | def sk_thin(img):
36 | """
37 | 细化提取骨架
38 | Parameter:
39 | img: 待提取图像
40 | Return:
41 | 提取骨架结果图像
42 | """
43 | # 生成8个结构子序列
44 | k_1 = np.array([[16,16,16],[0,1,0],[2,4,8]], dtype=np.uint8)
45 | k_2 = np.array([[0,16,16],[1,2,16],[4,8,0]], dtype=np.uint8)
46 | k_3 = np.array([[1,0,16],[2,4,16],[8,0,16]], dtype=np.uint8)
47 | k_4 = np.array([[1,2,0],[4,8,16],[0,16,16]], dtype=np.uint8)
48 | k_5 = np.array([[1,2,4],[0,8,0],[16,16,16]], dtype=np.uint8)
49 | k_6 = np.array([[0,1,2],[16,4,8],[16,16,0]], dtype=np.uint8)
50 | k_7 = np.array([[16,0,1],[16,2,4],[16,0,8]], dtype=np.uint8)
51 | k_8 = np.array([[16,16,0],[16,1,2],[0,4,8]], dtype=np.uint8)
52 |
53 | K = [k_1, k_2, k_3, k_4, k_5, k_6, k_7, k_8]
54 |
55 | # 细化操作
56 | img_result = thinning(img, K)
57 |
58 | return img_result
59 |
60 |
61 | if __name__ == "__main__":
62 | # 测试用图
63 | # img = 255 - np.zeros((5, 11))
64 | # img[1:5,9] = img[1:5,10] = img[4,3:5] = 0
65 |
66 | img = cv2.imread("bin.png", 0)
67 | img_result = sk_thin(img)
68 | cv2.imwrite("morph_thin.png", img_result)
--------------------------------------------------------------------------------
/section_5/tailor.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 |
8 | from basic_function import dilate
9 |
10 | def thinning(img, K):
11 | """
12 | 细化
13 | Parameters:
14 | img: 待细化图像
15 | K: 结构子序列
16 | Return:
17 | 细化后结果图像
18 | """
19 | # 归一
20 | img_result = img/255
21 | # 利用结构子序列重复3次细化
22 | for i in range(3):
23 | for i in K:
24 | img_temp = np.where(cv2.filter2D(img_result.copy(),-1,i,borderType=0)==3, 1, 0)
25 | img_result = img_result - img_temp
26 |
27 | img_result *= 255
28 | return img_result.astype(np.uint8)
29 |
30 | def find_end(img, K):
31 | """
32 | 找到端节点
33 | Parameters:
34 | img: 输入图像
35 | K: 结构子序列
36 | Return:
37 | 只有端节点为前景的图像
38 | """
39 | # 像素归一化
40 | img_ones = img/255
41 | img_result = np.zeros_like(img, dtype=np.uint8)
42 |
43 | # 利用结构子序列寻找端点
44 | for i in K:
45 | img_temp = np.where(cv2.filter2D(img_ones.copy(),-1,i,borderType=0)==3, 1, 0)
46 | img_result = img_result + img_temp
47 |
48 | img_result *= 255
49 | return img_result.astype(np.uint8)
50 |
51 | def tailor(img):
52 | """
53 | 裁剪
54 | Parameters:
55 | img: 待裁剪图像
56 | Return:
57 | 裁剪结果图像
58 | """
59 | # 生成8个结构子
60 | k_1 = np.array([[0,4,4],[1,2,4],[0,4,4]], dtype=np.uint8)
61 | k_2 = np.array([[0,1,0],[4,2,4],[4,4,4]], dtype=np.uint8)
62 | k_3 = np.array([[4,4,0],[4,1,2],[4,4,0]], dtype=np.uint8)
63 | k_4 = np.array([[4,4,4],[4,1,4],[0,2,0]], dtype=np.uint8)
64 | k_5 = np.array([[1,4,4],[4,2,4],[4,4,4]], dtype=np.uint8)
65 | k_6 = np.array([[4,4,1],[4,2,4],[4,4,4]], dtype=np.uint8)
66 | k_7 = np.array([[4,4,4],[4,1,4],[4,4,2]], dtype=np.uint8)
67 | k_8 = np.array([[4,4,4],[4,1,4],[2,4,4]], dtype=np.uint8)
68 |
69 | K = [k_1, k_2, k_3, k_4, k_5, k_6, k_7, k_8]
70 |
71 | # 细化(去除3个像素组成的分支)
72 | img_thin = thinning(img, K)
73 | # 找端点
74 | img_end = find_end(img_thin, K)
75 | # 膨胀运算,捡回误伤元素
76 | img_dilate = img_end
77 | for _ in range(3):
78 | img_dilate = dilate(img_dilate)
79 | img_dilate = cv2.bitwise_and(img_dilate, img)
80 | # 获得裁剪结果
81 | img_result = cv2.bitwise_or(img_dilate, img_thin)
82 |
83 | return img_result
84 |
85 |
86 | if __name__ == "__main__":
87 | img = cv2.imread("morph_thin.png", 0)
88 | img_result = tailor(img)
89 | cv2.imwrite("tailor.png", img_result)
--------------------------------------------------------------------------------
/section_6/README.md:
--------------------------------------------------------------------------------
1 | # Section 6: 小波域维纳滤波
2 |
3 | > 实验环境
4 | > * Python 3.6.0
5 | > * Opencv 3.1.0
6 | > * PyWavelets 1.1.1
7 |
8 | ## 1. 问题描述
9 | 以lena图像为例,编程实现小波域维纳滤波.
10 |
11 | ## 2. Code实现 & 实验
12 | ### 2.1 实验思路 & Code实现思路
13 | 为探究图像小波域的维纳滤波效果, 本次实验主要分为两个部分:
14 | * 单层小波分解维纳滤波去噪实验: 探究维纳滤波去噪的有效性
15 | * 多层小波分解的维纳滤波去噪效果对比试验: 探究多层分解滤波对去噪效果的影响
16 |
17 | 为达到上述实验目的, 需实现以下功能:
18 | * 向图像添加高斯噪声
19 | * 实现单层小波分解维纳滤波去噪:
20 | * 小波分解(直接使用pywt.dwt2)
21 | * 通过斜方向边缘图像HH估计噪声方差$\sigma_n = \frac{median(abs(HH))}{0.6745}$
22 | * 对每个分解图像求全局方差$\sigma^2 = \frac{1}{M}\sum_i Y(i)^2 - \sigma_n^2$
23 | * 点运算滤波$X_i = \frac{\sigma^2}{\sigma^2 + \sigma_n^2}Y_i$
24 | * 小波重构(直接使用pywt.idwt2)
25 | * 实现多层小波分解的维纳滤波去噪: 通过递归实现.
26 |
27 | 整体实验代码如下, 具体函数实现将在下面相应部分展示.
28 | ```Python
29 | # 读入图像
30 | img = cv2.imread("lena.png", 0)
31 |
32 | # 添加高斯噪声
33 | img_noise = gasuss_noise(img, var=0.4, alpha=0.25)
34 |
35 | plt.figure("Add Gaussian Noise")
36 | plt.subplot(1,2,1)
37 | plt.title("Origin Image")
38 | plt.imshow(img, "gray")
39 | plt.subplot(1,2,2)
40 | plt.title("Image with Noise")
41 |
42 | plt.imshow(img_noise, "gray")
43 | plt.savefig("img_noise.png")
44 |
45 | # 单层维纳滤波去噪实验
46 | img_de = denoise(img_noise)
47 |
48 | # 多层维纳滤波去噪效果对比试验
49 | N = 5
50 | plt.figure("Multi-decomposition")
51 | plt.subplot(2,3,1)
52 | plt.title("Image with Noise")
53 | plt.imshow(img_noise, "gray")
54 | for i in range(N):
55 | img_denoised = de(img_noise, i+1)
56 | plt.subplot(2,3,i+2)
57 | plt.title("{} Decomposition".format(i+1))
58 | plt.imshow(img_denoised, "gray")
59 | plt.tight_layout()
60 | plt.savefig("multi-decomposition.png")
61 | ```
62 |
63 | 下面,依次展开. 实验结果通过运行`python main.py`可获得.
64 |
65 | ### 2.2 向图像添加高斯噪声
66 | ```Python
67 | def gasuss_noise(img, mean=0, var=0.001, alpha=0.1):
68 | """
69 | 添加高斯噪声
70 | Parameters:
71 | mean : 噪声均值
72 | var : 噪声方差
73 | """
74 | noise = np.random.normal(mean, var ** 0.5, img.shape)*255
75 | img_noise = img + alpha*noise
76 | img_noise = np.clip(img_noise, 0, 255.0)
77 |
78 | return img_noise
79 | ```
80 | 向图像添加高斯噪声的结果如下图所示.
81 |
82 |

83 |
84 |
85 | ### 2.3 单层小波分解维纳滤波去噪实验
86 | 单层小波分解维纳滤波去噪的函数实现如下.
87 | ```Python
88 | def denoise(img):
89 | """
90 | 维纳滤波去噪
91 | Parameter:
92 | img: 含噪声图像
93 | Return:
94 | img_denoised: 维纳滤波去噪结果
95 | """
96 | # 小波分解
97 | A, (H, V, D) = pywt.dwt2(img, 'bior4.4')
98 |
99 | # 排版: 以便后续可视化小波分解结果
100 | AH = np.concatenate([A, H], axis=1)
101 | VD = np.concatenate([V, D], axis=1)
102 | fig = np.concatenate([AH, VD], axis=0)
103 |
104 | # 维纳滤波
105 | sigma_n = np.median(np.abs(D))/0.6745
106 |
107 | AHVD = []
108 | for i in [H, V, D]:
109 | sigma_sq = np.mean(i**2) - sigma_n**2
110 | i = (sigma_sq/(sigma_sq + sigma_n**2)) * i
111 | AHVD.append(i)
112 | [H, V, D] = AHVD
113 |
114 | # 小波重构
115 | img_denoised = pywt.idwt2((A,(H,V,D)), 'bior4.4')
116 |
117 |
118 |
119 | # 比较滤波前后差异
120 | img_diff = img -img_denoised
121 |
122 | # 可视化结果
123 | plt.figure("Wavelet Denoising")
124 | plt.subplot(2,2,1)
125 | plt.title("Image with Noise")
126 | plt.imshow(img, "gray")
127 | plt.subplot(2,2,2)
128 | plt.title("Wavelet Decomposition")
129 | plt.imshow(fig, "gray")
130 | plt.subplot(2,2,3)
131 | plt.title("Wavelet Denoised")
132 | plt.imshow(img_denoised, "gray")
133 | plt.subplot(2,2,4)
134 | plt.title("Difference")
135 | plt.imshow(img_diff, "gray")
136 | plt.tight_layout()
137 | plt.savefig("img_denoised.png")
138 |
139 | return img_denoised
140 | ```
141 |
142 | 单层小波分解的维纳滤波去噪结果如下图所示.
143 |
144 |

145 |
146 | 可以看到, 通过小波分解, 基于斜方向边缘图像计算的噪声方差能较好地估计真实的噪声方差, 经维纳滤波后, 大部分噪声被成功滤去, 去噪效果较好.
147 |
148 | ### 2.4 多层小波分解的维纳滤波去噪效果对比试验
149 | 递归实现的多层小波分解维纳滤波去噪函数如下.
150 | ```Python
151 | def de(img, n, sigma_n=None):
152 | """
153 | 维纳滤波去噪: 递归多层分解去噪
154 | Parameters:
155 | img: 待分解去噪图像
156 | n: 待分解层数
157 | sigma_n: 噪声方差(由第一层分解的HH计算得到)
158 | Return:
159 | img_denoised: 维纳滤波去噪结果
160 | """
161 | # 递归终止条件: 待分解层数为0
162 | if not(n): return img
163 |
164 | # 递归多层维纳滤波
165 | # 小波分解
166 | A, (H, V, D) = pywt.dwt2(img, 'bior4.4')
167 | # 对于第一层分解, 计算噪声方差
168 | if not(sigma_n): sigma_n = np.median(np.abs(D))/0.6745
169 | # 递归
170 | A = de(A, n-1, sigma_n)
171 | # 递归异常处理: 处理pywt对奇数行列图像分解重构后行列数增加1的特殊情况
172 | if A.shape[0] > H.shape[0]: A = A[:-1, :-1]
173 | # 维纳滤波
174 | AHVD = []
175 | for i in [H, V, D]:
176 | sigma_sq = np.mean(i**2) - sigma_n**2
177 | i = (sigma_sq/(sigma_sq + sigma_n**2)) * i
178 | AHVD.append(i)
179 | [H, V, D] = AHVD
180 | # 小波重构
181 | img_denoised = pywt.idwt2((A,(H,V,D)), 'bior4.4')
182 |
183 | return img_denoised
184 | ```
185 | 实验时, 依次对噪声图片进行了1层至5层的小波分解滤波去噪, 其各自的去噪效果如下图所示.
186 |
187 |

188 |
189 | 可以看到, 随着分解层数的增多, 去噪效果有所提升, 但同时, 图像也无法避免地变得模糊. 另外, 也可以发现, 虽然层数越多去噪效果越好, 但越往后去噪效果的差异越小.
--------------------------------------------------------------------------------
/section_6/img_denoised.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_6/img_denoised.png
--------------------------------------------------------------------------------
/section_6/img_noise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_6/img_noise.png
--------------------------------------------------------------------------------
/section_6/lena.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_6/lena.png
--------------------------------------------------------------------------------
/section_6/main.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 | import pywt
8 |
9 | import matplotlib.pyplot as plt
10 |
11 | def gasuss_noise(img, mean=0, var=0.001, alpha=0.1):
12 | """
13 | 添加高斯噪声
14 | Parameters:
15 | mean : 噪声均值
16 | var : 噪声方差
17 | """
18 | noise = np.random.normal(mean, var ** 0.5, img.shape)*255
19 | img_noise = img + alpha*noise
20 | img_noise = np.clip(img_noise, 0, 255.0)
21 |
22 | return img_noise
23 |
24 | def denoise(img):
25 | """
26 | 维纳滤波去噪
27 | Parameter:
28 | img: 含噪声图像
29 | Return:
30 | img_denoised: 维纳滤波去噪结果
31 | """
32 | # 小波分解
33 | A, (H, V, D) = pywt.dwt2(img, 'bior4.4')
34 |
35 | # 排版: 以便后续可视化小波分解结果
36 | AH = np.concatenate([A, H], axis=1)
37 | VD = np.concatenate([V, D], axis=1)
38 | fig = np.concatenate([AH, VD], axis=0)
39 |
40 | # 维纳滤波
41 | sigma_n = np.median(np.abs(D))/0.6745
42 |
43 | AHVD = []
44 | for i in [A, H, V, D]:
45 | sigma_sq = np.mean(i**2) - sigma_n**2
46 | i = (sigma_sq/(sigma_sq + sigma_n**2)) * i
47 | AHVD.append(i)
48 | [A, H, V, D] = AHVD
49 |
50 | # 小波重构
51 | img_denoised = pywt.idwt2((A,(H,V,D)), 'bior4.4')
52 |
53 | # 比较滤波前后差异
54 | img_diff = img -img_denoised
55 |
56 | # 可视化结果
57 | plt.figure("Wavelet Denoising")
58 | plt.subplot(2,2,1)
59 | plt.title("Image with Noise")
60 | plt.imshow(img, "gray")
61 | plt.subplot(2,2,2)
62 | plt.title("Wavelet Decomposition")
63 | plt.imshow(fig, "gray")
64 | plt.subplot(2,2,3)
65 | plt.title("Wavelet Denoised")
66 | plt.imshow(img_denoised, "gray")
67 | plt.subplot(2,2,4)
68 | plt.title("Difference")
69 | plt.imshow(img_diff, "gray")
70 | plt.tight_layout()
71 | plt.savefig("img_denoised.png")
72 |
73 | return img_denoised
74 |
75 | def de(img, n, sigma_n=None):
76 | """
77 | 维纳滤波去噪: 递归多层分解去噪
78 | Parameters:
79 | img: 待分解去噪图像
80 | n: 待分解层数
81 | sigma_n: 噪声方差(由第一层分解的HH计算得到)
82 | Return:
83 | img_denoised: 维纳滤波去噪结果
84 | """
85 | # 递归终止条件: 待分解层数为0
86 | if not(n): return img
87 |
88 | # 递归多层维纳滤波
89 | # 小波分解
90 | A, (H, V, D) = pywt.dwt2(img, 'bior4.4')
91 | # 对于第一层分解, 计算噪声方差
92 | if not(sigma_n): sigma_n = np.median(np.abs(D))/0.6745
93 | # 递归
94 | A = de(A, n-1, sigma_n)
95 | # 递归异常处理: 处理pywt对奇数行列图像分解重构后行列数增加1的特殊情况
96 | if A.shape[0] > H.shape[0]: A = A[:-1, :-1]
97 | # 维纳滤波
98 | AHVD = []
99 | for i in [H, V, D]:
100 | sigma_sq = np.mean(i**2) - sigma_n**2
101 | i = (sigma_sq/(sigma_sq + sigma_n**2)) * i
102 | AHVD.append(i)
103 | [H, V, D] = AHVD
104 | # 小波重构
105 | img_denoised = pywt.idwt2((A,(H,V,D)), 'bior4.4')
106 |
107 | return img_denoised
108 |
109 | if __name__ == "__main__":
110 | # 读入图像
111 | img = cv2.imread("lena.png", 0)
112 | # 添加高斯噪声
113 | img_noise = gasuss_noise(img, var=0.4, alpha=0.25)
114 |
115 | plt.figure("Add Gaussian Noise")
116 | plt.subplot(1,2,1)
117 | plt.title("Origin Image")
118 | plt.imshow(img, "gray")
119 | plt.subplot(1,2,2)
120 | plt.title("Image with Noise")
121 | plt.imshow(img_noise, "gray")
122 | plt.savefig("img_noise.png")
123 |
124 | # 单层维纳滤波去噪实验
125 | img_de = denoise(img_noise)
126 |
127 | # 多层维纳滤波去噪效果对比试验
128 | N = 5
129 | plt.figure("Multi-decomposition")
130 | plt.subplot(2,3,1)
131 | plt.title("Image with Noise")
132 | plt.imshow(img_noise, "gray")
133 | for i in range(N):
134 | img_denoised = de(img_noise, i+1)
135 |
136 | plt.subplot(2,3,i+2)
137 | plt.title("{} Decomposition".format(i+1))
138 | plt.imshow(img_denoised, "gray")
139 | plt.tight_layout()
140 | plt.savefig("multi-decomposition.png")
141 |
142 |
--------------------------------------------------------------------------------
/section_6/multi-decomposition.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_6/multi-decomposition.png
--------------------------------------------------------------------------------
/section_7/README.md:
--------------------------------------------------------------------------------
1 | # Section 7: 图像复原与重建
2 |
3 | > 实验环境
4 | > * Python 3.6.0
5 | > * Opencv 3.1.0
6 |
7 | ## 问题描述
8 | 综合利用图像处理课程中学习到的相关知识,去除测试图片test.png中的模糊,获得一张清晰的图像.
9 |
10 | ## Code实现 & 实验
11 | ### 1. 实验思路 & Code实现思路
12 | 为实现对测试图片的去模糊, 需实现以下两个子功能:
13 | * 图像的模糊核估计: 根据可参数化的模糊核估计方法, 对图像进行$DFT{log|DFT(u,v)|}$操作, 可视化结果, 估计模糊核, 根据估计重建模糊核.
14 | * 图像去模糊: 利用重建的模糊核, 基于维纳滤波实现图像的去模糊.
15 |
16 | 整体实验代码如下, 具体函数实现将在下面相应部分展示.
17 | ```Python
18 | # 读入图像
19 | img = cv2.imread("./lena.png", 0)
20 | img_blur = cv2.imread("./test.png", 0)
21 | # 可参数化模糊核估计 & 可视化
22 | blurry_analysis(img, img_blur)
23 | # 模糊核重建 & 对比
24 | blurry_k = kernel_est(img_blur.shape, -30, 42)
25 |
26 | blurry_k_gt = get_blurry_k_gt(img, img_blur)
27 |
28 | plt.subplot(1,2,1)
29 | plt.title("blurry Kernel GroundTruth")
30 | plt.imshow(blurry_k_gt, cmap='gray')
31 | plt.subplot(1,2,2)
32 | plt.title("blurry Kernel Est-result")
33 | plt.imshow(blurry_k, cmap='gray')
34 | plt.tight_layout()
35 | plt.savefig("blurry_kernel.png")
36 |
37 |
38 |
39 | # 维纳滤波去模糊
40 | img_result = wiener(img_blur, blurry_k)
41 |
42 |
43 | plt.subplot(1,3,1)
44 | plt.title("Origin Image")
45 | plt.imshow(img, cmap='gray')
46 | plt.subplot(1,3,2)
47 | plt.title("Blurry Image")
48 | plt.imshow(img_blur, cmap='gray')
49 | plt.subplot(1,3,3)
50 | plt.title("Deblur Result")
51 | plt.imshow(img_result, cmap='gray')
52 | plt.tight_layout()
53 | plt.savefig("result.png")
54 |
55 | # Edgetaper 去振铃效果对比
56 | img_et = cv2.imread("./lena_edgetaper.png", 0)
57 | img_et_result = wiener(img_et, blurry_k)
58 |
59 | plt.subplot(2,2,1)
60 | plt.title("Origin Image")
61 | plt.imshow(img, cmap='gray')
62 | plt.subplot(2,2,2)
63 | plt.title("Blurry Image")
64 | plt.imshow(img_blur, cmap='gray')
65 | plt.subplot(2,2,3)
66 | plt.title("Deblur Result")
67 | plt.imshow(img_result, cmap='gray')
68 | plt.subplot(2,2,4)
69 | plt.title("Edgetaper-Deblur")
70 | plt.imshow(img_et_result, cmap='gray')
71 | plt.tight_layout()
72 | plt.savefig("result_et.png")
73 | ```
74 |
75 | ### 2. 图像的模糊核估计实验
76 | 首先, 对图像的模糊核进行估计与可视化. 实现代码如下.
77 | ```Python
78 | def blurry_analysis(img_origin, img_blur):
79 | """
80 | 可参数化的模糊核估计 & 可视化对比
81 | Parameter:
82 | img_origin: Origin image
83 | img_blur: Blurry image
84 | """
85 | # 模糊核估计
86 | img_b_fft = np.fft.fft2(np.log(np.fft.fft2(img_blur)))
87 | img_b_fft = np.fft.fftshift(img_b_fft)
88 | img_b_fft = 20*np.log(np.abs(img_b_fft))
89 | # 对原图做相同操作, 用以对比
90 | img_o_fft = np.fft.fft2(np.log(np.fft.fft2(img_origin)))
91 | img_o_fft = np.fft.fftshift(img_o_fft)
92 | img_o_fft = 20*np.log(np.abs(img_o_fft))
93 | # 取中间部分图像, 便于可视化观察
94 | h, w = img_o_fft.shape[:2]
95 | img_o_center = img_o_fft[int(3*h/10):int(7*h/10)
96 | , int(3*w/10):int(7*w/10)]
97 |
98 |
99 | img_b_center = img_b_fft[int(3*h/10):int(7*h/10)
100 | , int(3*w/10):int(7*w/10)]
101 | # 可视化
102 | plt.subplot(2,3,1)
103 | plt.title("Origin Image")
104 | plt.imshow(img_origin, cmap='gray')
105 | plt.subplot(2,3,2)
106 | plt.title("Analysis Result")
107 | plt.imshow(img_o_fft, cmap='gray')
108 | plt.subplot(2,3,3)
109 | plt.title("Center of Result")
110 | plt.imshow(img_o_center, cmap='gray')
111 | plt.subplot(2,3,4)
112 | plt.title("Blurry Image")
113 | plt.imshow(img_blur, cmap='gray')
114 | plt.subplot(2,3,5)
115 | plt.title("Analysis Result")
116 | plt.imshow(img_b_fft, cmap='gray')
117 | plt.subplot(2,3,6)
118 | plt.title("Center of Result")
119 | plt.imshow(img_b_center, cmap='gray')
120 | plt.tight_layout()
121 | plt.savefig("analysis.png")
122 | ```
123 | 可视化结果如下图所示.
124 |
125 | 
126 |
127 | 从可视化结果可以发现, 测试图片的模糊属于运动模糊, 并可以大致估计出模糊核的角度为与竖直夹角-30°, 长度约为40, 进而可以重建运动模糊核.
128 |
129 | ```Python
130 |
131 |
132 | def kernel_est(img_shape, theta, length):
133 | """
134 | 运动模糊核重建
135 | Parameter:
136 | img_shape: 图像尺寸[h,w]
137 | theta: 运动模糊核角度
138 | length: 运动模糊核长度
139 | Return:
140 | kernel: 重建的运动模糊核
141 | """
142 | # 参数初始化
143 | h, w = img_shape[:2]
144 | pos_c_h = round(h/2)
145 | pos_c_w = round(w/2)
146 | theta = np.pi * (-theta/180)
147 | # 重建运动模糊核
148 | kernel = np.zeros((h, w))
149 | for i in range(length):
150 | l = length/2 - i
151 | delta_w = l * np.cos(theta)
152 | delta_h = l * np.sin(theta)
153 | kernel[int(pos_c_w+delta_w), int(pos_c_h+delta_h)] = 1
154 | kernel = kernel/np.sum(kernel)
155 |
156 | return kernel
157 | ```
158 | 实验为检验估计并重建的运动模糊核的正确性, 利用清晰原图生成模糊核的真实值, 可视化进行对比.
159 |
160 | ```Python
161 | def get_blurry_k_gt(img_origin, img_blur):
162 | """
163 | 获取真实的模糊核
164 | Parameter:
165 |
166 | """
167 | img_o_fft = np.fft.fft2(img_origin)
168 | img_b_fft = np.fft.fft2(img_blur)
169 | blurry_fft = np.fft.ifft2(img_b_fft / img_o_fft)
170 | blurry_fft = np.abs(np.fft.fftshift(blurry_fft))
171 | blurry_fft = blurry_fft/ np.sum(blurry_fft)
172 |
173 | return blurry_fft
174 | ```
175 | 运动模糊核真实值的可视化结果 与 重建的模糊核 的对比结果如下图所示.
176 |
177 | 
178 |
179 | 可以看出, 运动模糊核的重建效果较好.
180 |
181 | ### 3. 图像去模糊实验
182 | 利用之前估计并重建的模糊核, 基于维纳滤波实现去模糊.
183 |
184 | ```Python
185 | def wiener(img_blur, kernel, K=0.005):
186 | """
187 | 维纳滤波
188 | Parameter:
189 | img_blur: 模糊图像
190 | kernel: 模糊核
191 | K: 分母参数
192 | Return:
193 | img_result: 维纳滤波去模糊结果
194 | """
195 | img_b_fft = np.fft.fft2(img_blur)
196 | kernel_fft = np.fft.fft2(kernel)
197 | kernel_fft = np.conj(kernel_fft) / (np.abs(kernel_fft)**2 + K)
198 | img_result = np.fft.ifft2(img_b_fft * kernel_fft)
199 | img_result = np.abs(np.fft.fftshift(img_result))
200 | img_result = img_result.astype(np.uint8)
201 |
202 | return img_result
203 | ```
204 | 测试图像去模糊的结果如下, 为更好的对比去模糊效果, 下图中左图为原始清晰图像, 中间为测试模糊图像, 右图为去模糊结果图像.
205 |
206 | 
207 |
208 | 可见上述算法具有一定的去模糊效果, 但同时图像存在明显的振铃现象. 振铃现象可通过Edgetaper算法改善. 由于Python OpenCV中没有相关的函数,
209 | 因此, 在MATLAB中调用Edgetaper函数预处理模糊图像, 以其为基础, 进行维纳滤波去模糊. 其结果如下图所示.
210 |
211 | 
212 |
213 | 对比第二行两张图, 可以明显看出, Edgetaper算法可以有效去振铃, 经其预处理后的图像再利用维纳滤波去模糊可以得到较好的结果, 如图中右下方图所示.
214 | 另外, 对比右侧一列的去模糊前后图, 可以发现实验中实现的代码可以有效的改善运动模糊, 效果较为明显. 对比左上原图和右下去模糊结果图, 可以发现,
215 | 虽然算法可以有效改善模糊状况, 但去模糊后的结果和原始清晰图像间仍存在一定的差距.
216 |
--------------------------------------------------------------------------------
/section_7/analysis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_7/analysis.png
--------------------------------------------------------------------------------
/section_7/blurry_kernel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_7/blurry_kernel.png
--------------------------------------------------------------------------------
/section_7/lena.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_7/lena.png
--------------------------------------------------------------------------------
/section_7/lena_edgetaper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_7/lena_edgetaper.png
--------------------------------------------------------------------------------
/section_7/main.py:
--------------------------------------------------------------------------------
1 | """
2 | @leofansq
3 | https://github.com/leofansq
4 | """
5 | import cv2
6 | import numpy as np
7 | import math
8 |
9 | import matplotlib.pyplot as plt
10 |
11 | def blurry_analysis(img_origin, img_blur):
12 | """
13 | 可参数化的模糊核估计 & 可视化对比
14 | Parameter:
15 | img_origin: Origin image
16 | img_blur: Blurry image
17 | """
18 | # 模糊核估计
19 | img_b_fft = np.fft.fft2(np.log(np.fft.fft2(img_blur)))
20 | img_b_fft = np.fft.fftshift(img_b_fft)
21 | img_b_fft = 20*np.log(np.abs(img_b_fft))
22 | # 对原图做相同操作, 用以对比
23 | img_o_fft = np.fft.fft2(np.log(np.fft.fft2(img_origin)))
24 | img_o_fft = np.fft.fftshift(img_o_fft)
25 | img_o_fft = 20*np.log(np.abs(img_o_fft))
26 | # 取中间部分图像, 便于可视化观察
27 | h, w = img_o_fft.shape[:2]
28 | img_o_center = img_o_fft[int(3*h/10):int(7*h/10), int(3*w/10):int(7*w/10)]
29 | img_b_center = img_b_fft[int(3*h/10):int(7*h/10), int(3*w/10):int(7*w/10)]
30 | # 可视化
31 | plt.subplot(2,3,1)
32 | plt.title("Origin Image")
33 | plt.imshow(img_origin, cmap='gray')
34 | plt.subplot(2,3,2)
35 | plt.title("Analysis Result")
36 | plt.imshow(img_o_fft, cmap='gray')
37 | plt.subplot(2,3,3)
38 | plt.title("Center of Result")
39 | plt.imshow(img_o_center, cmap='gray')
40 | plt.subplot(2,3,4)
41 | plt.title("Blurry Image")
42 | plt.imshow(img_blur, cmap='gray')
43 | plt.subplot(2,3,5)
44 | plt.title("Analysis Result")
45 | plt.imshow(img_b_fft, cmap='gray')
46 | plt.subplot(2,3,6)
47 | plt.title("Center of Result")
48 | plt.imshow(img_b_center, cmap='gray')
49 | plt.tight_layout()
50 | plt.savefig("analysis.png")
51 |
52 | def kernel_est(img_shape, theta, length):
53 | """
54 | 运动模糊核重建
55 | Parameter:
56 | img_shape: 图像尺寸[h,w]
57 | theta: 运动模糊核角度
58 | length: 运动模糊核长度
59 | Return:
60 | kernel: 重建的运动模糊核
61 | """
62 | # 参数初始化
63 | h, w = img_shape[:2]
64 | pos_c_h = round(h/2)
65 | pos_c_w = round(w/2)
66 | theta = np.pi * (-theta/180)
67 | # 重建运动模糊核
68 | kernel = np.zeros((h, w))
69 | for i in range(length):
70 | l = length/2 - i
71 | delta_w = l * np.cos(theta)
72 | delta_h = l * np.sin(theta)
73 | kernel[int(pos_c_w+delta_w), int(pos_c_h+delta_h)] = 1
74 | kernel = kernel/np.sum(kernel)
75 |
76 | return kernel
77 |
78 | def get_blurry_k_gt(img_origin, img_blur):
79 | """
80 | 获取真实的模糊核
81 | Parameter:
82 | img_origin: 原始清晰图像
83 | img_blur: 模糊图像
84 | Return:
85 | blurry_fft: 真实的模糊核
86 | """
87 | img_o_fft = np.fft.fft2(img_origin)
88 | img_b_fft = np.fft.fft2(img_blur)
89 | blurry_fft = np.fft.ifft2(img_b_fft / img_o_fft)
90 | blurry_fft = np.abs(np.fft.fftshift(blurry_fft))
91 | blurry_fft = blurry_fft/ np.sum(blurry_fft)
92 |
93 | return blurry_fft
94 |
95 | def wiener(img_blur, kernel, K=0.005):
96 | """
97 | 维纳滤波
98 | Parameter:
99 | img_blur: 模糊图像
100 | kernel: 模糊核
101 | K: 分母参数
102 | Return:
103 | img_result: 维纳滤波去模糊结果
104 | """
105 | img_b_fft = np.fft.fft2(img_blur)
106 | kernel_fft = np.fft.fft2(kernel)
107 | kernel_fft = np.conj(kernel_fft) / (np.abs(kernel_fft)**2 + K)
108 | img_result = np.fft.ifft2(img_b_fft * kernel_fft)
109 | img_result = np.abs(np.fft.fftshift(img_result))
110 | img_result = img_result.astype(np.uint8)
111 |
112 | return img_result
113 |
114 | if __name__ == "__main__":
115 | # 读入图像
116 | img = cv2.imread("./lena.png", 0)
117 | img_blur = cv2.imread("./test.png", 0)
118 |
119 | # 可参数化模糊核估计 & 可视化
120 | blurry_analysis(img, img_blur)
121 |
122 | # 模糊核重建 & 对比
123 | blurry_k = kernel_est(img_blur.shape, -30, 42)
124 | blurry_k_gt = get_blurry_k_gt(img, img_blur)
125 |
126 | plt.subplot(1,2,1)
127 | plt.title("blurry Kernel GroundTruth")
128 | plt.imshow(blurry_k_gt, cmap='gray')
129 | plt.subplot(1,2,2)
130 | plt.title("blurry Kernel Est-result")
131 | plt.imshow(blurry_k, cmap='gray')
132 | plt.tight_layout()
133 | plt.savefig("blurry_kernel.png")
134 |
135 | # 维纳滤波去模糊
136 | img_result = wiener(img_blur, blurry_k)
137 |
138 | plt.subplot(1,3,1)
139 | plt.title("Origin Image")
140 | plt.imshow(img, cmap='gray')
141 | plt.subplot(1,3,2)
142 | plt.title("Blurry Image")
143 | plt.imshow(img_blur, cmap='gray')
144 | plt.subplot(1,3,3)
145 | plt.title("Deblur Result")
146 | plt.imshow(img_result, cmap='gray')
147 | plt.tight_layout()
148 | plt.savefig("result.png")
149 |
150 | # Edgetaper 去振铃效果对比
151 | img_et = cv2.imread("./lena_edgetaper.png", 0)
152 | img_et_result = wiener(img_et, blurry_k)
153 |
154 | plt.subplot(2,2,1)
155 | plt.title("Origin Image")
156 | plt.imshow(img, cmap='gray')
157 | plt.subplot(2,2,2)
158 | plt.title("Blurry Image")
159 | plt.imshow(img_blur, cmap='gray')
160 | plt.subplot(2,2,3)
161 | plt.title("Deblur Result")
162 | plt.imshow(img_result, cmap='gray')
163 | plt.subplot(2,2,4)
164 | plt.title("Edgetaper-Deblur")
165 | plt.imshow(img_et_result, cmap='gray')
166 | plt.tight_layout()
167 | plt.savefig("result_et.png")
168 |
--------------------------------------------------------------------------------
/section_7/result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_7/result.png
--------------------------------------------------------------------------------
/section_7/result_et.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_7/result_et.png
--------------------------------------------------------------------------------
/section_7/test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leofansq/AI01002H-ImageProcessing/ae8e5b8df1ea3fd6398b0fd596c649266dd10db5/section_7/test.png
--------------------------------------------------------------------------------