├── doc ├── 改1.png ├── 改2.png ├── 原去雾1.png ├── 原去雾2.png ├── 技术路线.png └── 算法流程.png ├── images ├── bench_original.jpg ├── cones_original.jpg ├── dubai_original.jpg ├── road_original.jpg ├── train_original.jpg ├── forest_original.jpg ├── herzeliya_original.jpg ├── neighbour_original.jpg ├── pumpkins_original.jpg └── tiananmen_original.jpg ├── results ├── bench_result.jpg ├── cones_result.jpg ├── dubai_result.jpg ├── forest_result.jpg ├── road_result.jpg ├── train_result.jpg ├── pumpkins_result.jpg ├── herzeliya_result.jpg ├── neighbour_result.jpg └── tiananmen_result.jpg ├── code ├── main.py ├── demo.py └── haze_remove.py └── README.md /doc/改1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/doc/改1.png -------------------------------------------------------------------------------- /doc/改2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/doc/改2.png -------------------------------------------------------------------------------- /doc/原去雾1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/doc/原去雾1.png -------------------------------------------------------------------------------- /doc/原去雾2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/doc/原去雾2.png -------------------------------------------------------------------------------- /doc/技术路线.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/doc/技术路线.png -------------------------------------------------------------------------------- /doc/算法流程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/doc/算法流程.png -------------------------------------------------------------------------------- /images/bench_original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/images/bench_original.jpg -------------------------------------------------------------------------------- /images/cones_original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/images/cones_original.jpg -------------------------------------------------------------------------------- /images/dubai_original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/images/dubai_original.jpg -------------------------------------------------------------------------------- /images/road_original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/images/road_original.jpg -------------------------------------------------------------------------------- /images/train_original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/images/train_original.jpg -------------------------------------------------------------------------------- /results/bench_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/results/bench_result.jpg -------------------------------------------------------------------------------- /results/cones_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/results/cones_result.jpg -------------------------------------------------------------------------------- /results/dubai_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/results/dubai_result.jpg -------------------------------------------------------------------------------- /results/forest_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/results/forest_result.jpg -------------------------------------------------------------------------------- /results/road_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/results/road_result.jpg -------------------------------------------------------------------------------- /results/train_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/results/train_result.jpg -------------------------------------------------------------------------------- /images/forest_original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/images/forest_original.jpg -------------------------------------------------------------------------------- /results/pumpkins_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/results/pumpkins_result.jpg -------------------------------------------------------------------------------- /images/herzeliya_original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/images/herzeliya_original.jpg -------------------------------------------------------------------------------- /images/neighbour_original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/images/neighbour_original.jpg -------------------------------------------------------------------------------- /images/pumpkins_original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/images/pumpkins_original.jpg -------------------------------------------------------------------------------- /images/tiananmen_original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/images/tiananmen_original.jpg -------------------------------------------------------------------------------- /results/herzeliya_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/results/herzeliya_result.jpg -------------------------------------------------------------------------------- /results/neighbour_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/results/neighbour_result.jpg -------------------------------------------------------------------------------- /results/tiananmen_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuwz/haze-remove/HEAD/results/tiananmen_result.jpg -------------------------------------------------------------------------------- /code/main.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import numpy as np 3 | import os 4 | from haze_remove import * 5 | 6 | if __name__ == '__main__': 7 | files = os.listdir('images') 8 | BETA = 0.7 9 | K = 8 10 | RATE = 0.001 11 | SIZE = 15 12 | for name in files: 13 | filename = 'images/' + name 14 | result = Main(filename,BETA,K,RATE,SIZE) 15 | new_name = './results/' + name.split('_')[0] + '_result.jpg' 16 | print(new_name) 17 | # 保存文件时需进行反归一化操作 18 | cv.imwrite(new_name, result*255) 19 | print('已在文件夹内生成全部去雾图像') -------------------------------------------------------------------------------- /code/demo.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import numpy as np 3 | import os 4 | from haze_remove import * 5 | 6 | if __name__ == '__main__': 7 | # 以天安门图像为例 8 | filename = '.\\images\\tiananmen_original.jpg' 9 | src = cv.imread(filename) 10 | # 定义部分参数 11 | BETA = 0.7 12 | K = 8 13 | RATE = 0.001 14 | SIZE = 15 15 | # 对图像进行归一化操作 16 | img = src.astype('float64')/255 17 | # 暗通道图像生成与结合 18 | min_dark = Min_Dark_Channel(img, SIZE) # 最小值滤波 19 | mid_dark = Mid_Dark_Channel(img, SIZE) # 中值滤波 20 | k_dark = Kmeans_Dark_Channel(img, SIZE, K) # 聚类处理 21 | add_dark = Add(mid_dark, k_dark, BETA) # 加权相加 22 | dark = Gauss_Add(add_dark, min_dark) # 高斯加权 23 | A = Light_Channel(img, dark, RATE) 24 | te = Transmission_Estimate(img, A, SIZE) 25 | tx = Transmission_Refine(src, te) 26 | tx[tx < 0] = 0 27 | tx[tx > 1] = 1 28 | result = Recover(img, tx, A, 0.1) 29 | # 显示过程中及结果图像 30 | cv.imshow('original', src) 31 | cv.imshow('k-means dark', k_dark) 32 | cv.imshow('dark', dark) 33 | cv.imshow('tx', tx) 34 | cv.imshow('result', result) 35 | cv.waitKey(0) 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 问题背景 2 | 本文为专业课程《图像处理与计算机视觉》的课程实践。主要复现了*Kaiming He*经典的暗通道先验去雾算法[1],并参考文献[2]思路对原算法进行改进,针对image文件夹中的若干张图像进行去雾处理,去雾结果保存至文件夹results中。本方法的技术路线如下图所示。 3 | 4 |
5 | 6 |
7 | 8 | ## 结果展示 9 | 10 | 以天安门图像为例,其算法流程及过程图像如下图所示。 11 | 12 | 13 |
14 | 15 |
16 | 17 | 对比部分去雾前后图像,如下所示,效果较好。 18 | 19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 |
28 | 29 |
30 | 31 | 32 | 33 |
34 | 35 |
36 | 37 | 38 | 另外,对比原去雾算法与本方法,图像中近景与远景交界处残留的雾气得到了改善,如下图所示。但是,本方法对比原图,也存在着去雾结果整体偏暗,影响视觉观感的缺点。 39 | 40 | 41 |
42 | 43 |
44 | 45 | 46 |
47 | 48 |
49 | 50 | 51 | ## 参考文献 52 | 53 | 1.

He K, Sun J, Tang X. Single image haze removal using dark channel prior[J]. IEEE transactions on pattern analysis and machine intelligence, 2010, 33(12): 2341-2353.

54 | 2.

何柯辰,刘奇,邓小波,陈曦,全美霖,陈奕涵.基于双重暗通道结合与高斯加权的去雾改进算法[J].半导体光电,2021,42(05):754-759. 55 |

-------------------------------------------------------------------------------- /code/haze_remove.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import numpy as np 3 | from matplotlib import pyplot as plt 4 | 5 | 6 | def Min_Dark_Channel(img, size): 7 | # 取最小值图像并进行开运算处理 8 | # 对输入图像每个像素点取RGB最小值 9 | b, g, r = cv.split(img) 10 | dc = cv.min(cv.min(r, g), b) 11 | # 对最小值图像进行最小值滤波即腐蚀处理 12 | erode_kernel = cv.getStructuringElement(cv.MORPH_RECT, (size, size)) 13 | dark = cv.erode(dc, erode_kernel) 14 | # 腐蚀处理后进行膨胀处理 15 | dilate_kernel = cv.getStructuringElement(cv.MORPH_RECT, (2+size, 2+size)) 16 | dark = cv.dilate(dark, dilate_kernel) 17 | return dark 18 | 19 | 20 | # 对最小值图像进行中值滤波以获取对应暗通道图像 21 | def Mid_Dark_Channel(img, size): 22 | # 获取RGB三通道最小值 23 | b, g, r = cv.split(img) 24 | dc = cv.min(cv.min(r, g), b) 25 | # 中值滤波要求图像格式为UINT8,故进行反归一化处理 26 | dc *= 255 27 | dc = dc.astype('uint8') 28 | # 中值滤波 29 | dark = cv.medianBlur(dc, size) 30 | # 归一化处理,以保持全局图像格式一致 31 | dark = dark.astype('float64')/255 32 | return dark 33 | 34 | 35 | # K means处理图像并进行最小值滤波 36 | def Kmeans_Dark_Channel(img, size, k=8): 37 | Z = img.reshape((-1, 3)) 38 | # 函数要求图像格式为float32 39 | Z = Z.astype('float32') 40 | # 定义终止标准 聚类数并应用k均值 41 | criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 10, 1.0) 42 | K = k 43 | ret, label, center = cv.kmeans( 44 | Z, K, None, criteria, 10, cv.KMEANS_RANDOM_CENTERS) 45 | center = np.uint8(center*255) 46 | res = center[label.flatten()] 47 | res2 = res.reshape((img.shape)) 48 | res2 = res2.astype('float64')/255 49 | # 获取最小值图像并进行最小值滤波 50 | b, g, r = cv.split(res2) 51 | dc = cv.min(cv.min(r, g), b) 52 | kernel = cv.getStructuringElement(cv.MORPH_RECT, (size, size)) 53 | kdark = cv.erode(dc, kernel) 54 | return kdark 55 | 56 | 57 | def Add(dark, kdark, beta): 58 | return cv.add(dark * beta, kdark * (1 - beta)) 59 | 60 | 61 | # 高斯加权处理 62 | def Gauss_Add(Fdark, mindark): 63 | a1 = 4 64 | a2 = 2 65 | z = 0.6 66 | 67 | gx = a2 * np.exp(-1*(1.5-Fdark)**2/z) 68 | Tx = gx + a1 69 | Ig = (a1*Fdark+gx*mindark)/Tx 70 | Ig = Ig.astype('float64') 71 | return Ig 72 | 73 | 74 | def Light_Channel(sec_img, dark, rate): 75 | # 获取图像尺寸 76 | [h, w] = sec_img.shape[:2] 77 | size_of_img = h*w 78 | numpx = int(max(size_of_img * rate, 1)) 79 | dark_vec = dark.reshape(1, -1) 80 | # 获取亮度阈值 81 | threshold = dark_vec[0, dark_vec.argsort()[0][-1 * numpx]] 82 | # 寻找符合条件的像素点的位置 83 | atmo = {} 84 | for x in range(h): 85 | for y in range(w): 86 | if dark[x, y] >= threshold: 87 | atmo.update({(x, y): np.mean(sec_img[x, y, :])}) 88 | pos = sorted(atmo.items(), key=lambda item: item[1], reverse=True)[0][0] 89 | A = np.array([sec_img[pos[0], pos[1], :]]) 90 | return A 91 | 92 | 93 | def Transmission_Estimate(img, A, size): 94 | # 估计透射率取值 95 | omega = 0.95 96 | img_empty = np.empty(img.shape, img.dtype) 97 | 98 | for ind in range(0, 3): 99 | img_empty[:, :, ind] = img[:, :, ind]/A[0, ind] 100 | 101 | transmission = 1 - omega*Min_Dark_Channel(img_empty, size) 102 | return transmission 103 | 104 | 105 | def Transmission_Refine(img, te): 106 | # 使用导向滤波优化透射率 107 | gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) 108 | gray = np.float64(gray)/255 109 | r = 60 110 | eps = 0.0001 111 | t = Guided_Filter(gray, te, r, eps) 112 | 113 | return t 114 | 115 | 116 | def Guided_Filter(img, p, r, eps=1e-4): 117 | # 导向滤波 118 | mean_I = cv.boxFilter(img, cv.CV_64F, (r, r)) 119 | mean_p = cv.boxFilter(p, cv.CV_64F, (r, r)) 120 | mean_Ip = cv.boxFilter(img*p, cv.CV_64F, (r, r)) 121 | cov_Ip = mean_Ip - mean_I*mean_p 122 | 123 | mean_II = cv.boxFilter(img*img, cv.CV_64F, (r, r)) 124 | var_I = mean_II - mean_I*mean_I 125 | 126 | a = cov_Ip/(var_I + eps) 127 | b = mean_p - a*mean_I 128 | 129 | mean_a = cv.boxFilter(a, cv.CV_64F, (r, r)) 130 | mean_b = cv.boxFilter(b, cv.CV_64F, (r, r)) 131 | 132 | q = mean_a*img + mean_b 133 | return q 134 | 135 | 136 | def Recover(im, t, A, tx): 137 | res = np.empty(im.shape, im.dtype) 138 | t = cv.max(t, tx) 139 | 140 | for ind in range(0, 3): 141 | res[:, :, ind] = (im[:, :, ind]-A[0, ind])/t + A[0, ind] 142 | 143 | return res 144 | 145 | 146 | def Main(filename, BETA, K, RATE, SIZE): 147 | src = cv.imread(filename) 148 | img = src.astype('float64')/255 149 | min_dark = Min_Dark_Channel(img, SIZE) 150 | mid_dark = Mid_Dark_Channel(img, SIZE) 151 | k_dark = Kmeans_Dark_Channel(img, SIZE, K) 152 | add_dark = Add(mid_dark, k_dark, BETA) 153 | dark = Gauss_Add(add_dark, min_dark) 154 | A = Light_Channel(img, dark, RATE) 155 | te = Transmission_Estimate(img, A, SIZE) 156 | tx = Transmission_Refine(src, te) 157 | tx[tx < 0] = 0 158 | tx[tx > 1] = 1 159 | result = Recover(img, tx, A, 0.1) 160 | 161 | return result 162 | --------------------------------------------------------------------------------