实验目的
在数字图像处理中,图像去雾是一个重要的研究领域,主要目的包括:
-
改善图像质量:去雾技术能够提高图像的可见度和对比度,从而使图像更清晰,便于进一步的图像分析和处理。
-
应用广泛:图像去雾技术广泛应用于监控、交通、航空摄影、卫星图像处理等领域,对于提高图像信息的可用性和准确性非常关键。
-
促进后续处理:清晰的图像是许多图像处理任务(如目标检测、场景识别、图像分类等)的前提条件,去雾可以有效地优化这些任务的处理效果。
-
增强算法性能:研究不同的图像去雾算法可以帮助开发更高效、更准确的图像预处理工具,提升整体的图像处理性能。
通过这些目的的实现,图像去雾技术不仅提升了图像的质量,也为各种实际应用提供了支持,增强了图像处理技术的实用性和广泛性。
实验原理
Retinex算法的核心原理是假设图像可以被分解为反射分量(物体本身的颜色)和光照分量(由环境光照决定)。算法的目标是从观察到的图像中独立地估计这两个分量,从而恢复出物体的真实颜色,无论环境光照如何变化。
关键代码
import numpy as np
import cv2
import math
class DCP:
def __init__(self, img):
self.img = img
self.smog = None
self.new_img = np.zeros(img.shape)
self.w = img.shape[0]
self.h = img.shape[1]
self.dark_channel = None
self.A = 0
def runAlgorithm(self, kernel_size, w=0.85, t0=0.1):
"""
:param kernel_size: 以像素点x为中心的局部区域大小
:param w: 预留雾的程度, 0<w<=1
:param t0: 透射率下限
"""
# 取出BGR三通道的最小值,返回二维矩阵
min_BGR = np.min(self.img, axis=2)
# 计算暗通道
dark_channel = np.zeros(min_BGR.shape)
mid = kernel_size//2
tmp_img = np.zeros((self.w+kernel_size-1, self.h+kernel_size-1), dtype=np.float)
tmp_img[:, :] = 255
tmp_img[mid:self.w+mid, mid:self.h+mid] = min_BGR
for i in range(mid, self.w+mid-1):
for j in range(mid, self.h+mid-1):
dark_channel[i-mid, j-mid] = min(np.min(tmp_img[i-mid:i+mid, j-mid:j++mid]), 255)
self.dark_channel = dark_channel
# 获取大气光值A
pixels = {}
# 根据像素点位置保存像素点
for i in range(self.w):
for j in range(self.h):
pixels[(i, j)] = dark_channel[i, j]
pixels_list = list(pixels.items())
sorted(pixels_list, key=lambda x: x[1], reverse=True)
# 挑选前0.1%最亮像素
# 如果前0.1%像素点个数小于1,最选择第一个
if int(len(pixels_list) * 0.001) == 0:
loc = pixels_list[0]
self.A = np.min(self.img[loc[0], loc[1], :])
else:
brightest_pixels = pixels_list[: int(len(pixels_list) * 0.001)]
sum_A = 0
for pixel in brightest_pixels:
loc = pixel[0]
sum_A += np.min(self.img[loc[0], loc[1], :])
# 取前0.1%像素中最亮像素点的平均值
self.A = sum_A/len(brightest_pixels)
# 计算透射率t(x)的预估值
self.dark_channel = np.float64(self.dark_channel)
tx = 1 - w * self.dark_channel / self.A
new_tx = np.clip(tx, t0, 1)
# 滤波
ksize = 20*kernel_size
eps = 1e-5
mean_i = cv2.blur(self.dark_channel, (ksize, ksize))
mean_p = cv2.blur(new_tx, (ksize, ksize))
corr_i = cv2.blur(np.multiply(new_tx, new_tx), (ksize, ksize))
corr_ip = cv2.blur(np.multiply(new_tx, self.dark_channel), (ksize, ksize))
var_i = corr_i - np.multiply(mean_i, mean_i)
cov_ip = corr_ip - np.multiply(mean_i, mean_p)
a = cov_ip / (var_i + eps)
b = mean_p - np.multiply(a, mean_i)
mean_a = cv2.blur(a, (ksize, ksize))
mean_b = cv2.blur(b, (ksize, ksize))
new_tx = np.multiply(mean_a, self.dark_channel) + mean_b
# 计算无雾图
tmp_img = np.float64(self.img)
for i in range(3):
self.new_img[:, :, i] = (tmp_img[:, :, i] - self.A) / new_tx + self.A
self.new_img = np.clip(self.new_img, 0, 255)
def getDarkChannel(self):
return self.dark_channel
def getRes(self):
return np.uint8(self.new_img)
class Retinex:
def __init__(self, img, sigma_list=[15, 80, 250]):
self.img = np.float64(img)
self.sigma_list = sigma_list
self.RImg = None
def runAlgorithm(self):
img_msr = self.MSR()
img_msr = self.clipImage(img_msr, 0.05, 0.95)
for i in range(self.img.shape[2]):
img_msr[:, :, i] = (img_msr[:, :, i] - np.min(img_msr[:, :, i])) / (
np.max(img_msr[:, :, i]) - np.min(img_msr[:, :, i])) * 255
img_msr = np.clip(img_msr, 0, 255)
self.RImg = np.uint8(img_msr)
# 使用像素出现频率确定上下剪切点
def clipImage(self, img, low_clip, high_clip):
low_val = high_val = 0
total = img.shape[0] * img.shape[1]
for i in range(img.shape[2]):
unique, counts = np.unique(img[:, :, i], return_counts=True)
current = 0
for u, c in zip(unique, counts):
if float(current) / total < low_clip:
low_val = u
if float(current) / total < high_clip:
high_val = u
current += c
img[:, :, i] = np.clip(img[:, :, i], low_val, high_val)
return img
def SSR(self, sigma):
LImg = cv2.GaussianBlur(self.img, (0, 0), sigma)
ans = np.log(self.img + 1) - np.log(LImg + 1)
return ans
def MSR(self):
tmp = np.zeros_like(self.img)
for sigma in self.sigma_list:
tmp += self.SSR(sigma)
tmp /= 3
return tmp
def getRimg(self):
return self.RImg
实现GUI
GUI打开图片的路径不能有中文!
from tkinter import *
from tkinter.filedialog import askopenfilename
from PIL import ImageTk, Image
import cv2
from model import *
from tkinter.ttk import Combobox
def changeStyle(img):
# 根据设定窗口尺寸修改展示图片
img = cv2.resize(img, (400, 300), interpolation=cv2.INTER_LINEAR)
# 转化成能显示的格式
dst = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
dst = Image.fromarray(dst)
show_img = ImageTk.PhotoImage(image=dst)
return show_img
def openImage():
global image
imgPath = askopenfilename()
if len(imgPath) == 0:
Label(window, text="路径为空", fg='red').grid(row=4, column=1, sticky='w')
else:
imgPath = imgPath.replace("\\", "//")
try:
image = cv2.imread(imgPath, cv2.IMREAD_COLOR)
Label(window, text="路径正确", fg='green').grid(row=4, column=1, sticky='w')
show_img = changeStyle(image)
origin_img_label = Label(window, image=show_img)
origin_img_label.image = show_img
origin_img_label.grid(row=1, column=0)
except:
Label(window, text="路径错误", fg='red').grid(row=4, column=1, sticky='w')
def removeSmog():
algName = varLabel.get()
if algName == "请选择去雾算法":
Label(window, text="请选择去雾算法", fg="red").grid(row=4, column=1)
Label(window, image=empty_img).grid(row=1, column=1)
else:
Label(window, text="", width=20).grid(row=4, column=1)
if algName == values[0]:
# 使用模型
DCP_model = DCP(image)
DCP_model.runAlgorithm(kernel_size=15)
res_img = DCP_model.getRes()
res_img = changeStyle(res_img)
res_img_label = Label(window, image=res_img)
res_img_label.image = res_img
res_img_label.grid(row=1, column=1)
elif algName == values[1]:
# 使用模型
Retinex_model = Retinex(image)
Retinex_model.runAlgorithm()
res_img = Retinex_model.getRimg()
res_img = changeStyle(res_img)
res_img_label = Label(window, image=res_img)
res_img_label.image = res_img
res_img_label.grid(row=1, column=1)
if __name__ == "__main__":
window = Tk() # 创建一个窗口
window.title("图像去雾")
window.geometry("820x600") # 设置窗口大小
# 创建一张空白图
empty_img = Image.new('RGB', (400, 300), (255, 255, 255))
empty_img = ImageTk.PhotoImage(image=empty_img)
Label(window, text="图像去雾", font=30).grid(row=0, columnspan=2, sticky="n")
Label(window, image=empty_img).grid(row=1, column=0, pady=20)
Label(window, text="原图", font=15).grid(row=2, column=0)
Label(window, image=empty_img).grid(row=1, column=1)
Label(window, text="去雾后图像", font=15).grid(row=2, column=1)
varLabel = StringVar()
varLabel.set("请选择去雾算法")
values = ["暗通道先验算法", "Retinex算法"]
Combobox(window, height=2, width=20, state="readonly", cursor="arrow", textvariable=varLabel, values=values).grid(row=3, column=0)
Button(window, text="打开图片", width=10, height=2, command=openImage).grid(row=3, column=1, pady=50, sticky="w")
Button(window, text="图片去雾", width=10, height=2, command=removeSmog).grid(row=3, column=1, pady=50)
window.mainloop() # 维持窗口打开状态
参考代码