├── 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 |
73 | 74 | 75 | 76 | 77 |
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 | ![模糊核估计](./analysis.png) 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 | ![重建模糊核对比](./blurry_kernel.png) 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 | ![去模糊](./result.png) 207 | 208 | 可见上述算法具有一定的去模糊效果, 但同时图像存在明显的振铃现象. 振铃现象可通过Edgetaper算法改善. 由于Python OpenCV中没有相关的函数, 209 | 因此, 在MATLAB中调用Edgetaper函数预处理模糊图像, 以其为基础, 进行维纳滤波去模糊. 其结果如下图所示. 210 | 211 | ![Edgetaper去振铃](./result_et.png) 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 --------------------------------------------------------------------------------