整合去雾网页工具

This commit is contained in:
admin
2026-06-10 17:42:11 +08:00
commit 6db15ebc3f
101 changed files with 10167 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
We appreciate the [PyTorch implementation](https://github.com/TheFairBear/PyTorch-Image-Dehazing) of AOD-Net. Based on this code, we add simple [PONO and MS](https://github.com/Boyiliee/PONO) into AOD-Net, which improves the performance efficiently.
Previous AOD-Net Results:
![](../AOD-Net_result.png)
For TestSet A, the PSNR increases from 19.69 to 20.38 dB, the SSIM increases from 0.8478 to 0.8587. For TestSetB, the PSNR increases from 21.54 to 21.67 dB, the SSIM increases from 0.9272 to 0.9285.
If you find this repo useful, please cite:
```
@inproceedings{ICCV17a,
title={AOD-Net: All-in-One Dehazing Network},
author={Li, Boyi and Peng, Xiulian and Wang, Zhangyang and Xu, Ji-Zheng and Feng, Dan},
booktitle={Proceedings of the IEEE International Conference on Computer Vision},
year={2017}
}
@inproceedings{li2019positional,
title={Positional Normalization},
author={Li, Boyi and Wu, Felix and Weinberger, Kilian Q and Belongie, Serge},
booktitle={Advances in Neural Information Processing Systems},
pages={1620--1632},
year={2019}
}
```

View File

@@ -0,0 +1,99 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
class AODnet(nn.Module):
def __init__(self):
super(AODnet, self).__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=1, stride=1, padding=0)
self.conv2 = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=3, stride=1, padding=1)
self.conv3 = nn.Conv2d(in_channels=6, out_channels=3, kernel_size=5, stride=1, padding=2)
self.conv4 = nn.Conv2d(in_channels=6, out_channels=3, kernel_size=7, stride=1, padding=3)
self.conv5 = nn.Conv2d(in_channels=12, out_channels=3, kernel_size=3, stride=1, padding=1)
self.b = 1
def forward(self, x):
x1 = F.relu(self.conv1(x))
x2 = F.relu(self.conv2(x1))
cat1 = torch.cat((x1, x2), 1)
x3 = F.relu(self.conv3(cat1))
cat2 = torch.cat((x2, x3), 1)
x4 = F.relu(self.conv4(cat2))
cat3 = torch.cat((x1, x2, x3, x4), 1)
k = F.relu(self.conv5(cat3))
if k.size() != x.size():
raise Exception("k, haze image are different size!")
output = k * x - k + self.b
return F.relu(output)
class AOD_pono_net(nn.Module):
def __init__(self):
super(AOD_pono_net, self).__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=1, stride=1, padding=0)
self.conv2 = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=3, stride=1, padding=1)
self.conv3 = nn.Conv2d(in_channels=6, out_channels=3, kernel_size=5, stride=1, padding=2)
self.conv4 = nn.Conv2d(in_channels=6, out_channels=3, kernel_size=7, stride=1, padding=3)
self.conv5 = nn.Conv2d(in_channels=12, out_channels=3, kernel_size=3, stride=1, padding=1)
self.b = 1
self.pono = PONO(affine=False)
self.ms = MS()
def forward(self, x):
x1 = F.relu(self.conv1(x))
x2 = F.relu(self.conv2(x1))
cat1 = torch.cat((x1, x2), 1)
x1, mean1, std1 = self.pono(x1)
x2, mean2, std2 = self.pono(x2)
x3 = F.relu(self.conv3(cat1))
cat2 = torch.cat((x2, x3), 1)
x3 = self.ms(x3, mean1, std1)
x4 = F.relu(self.conv4(cat2))
x4 = self.ms(x4, mean2, std2)
cat3 = torch.cat((x1, x2, x3, x4), 1)
k = F.relu(self.conv5(cat3))
if k.size() != x.size():
raise Exception("k, haze image are different size!")
output = k * x - k + self.b
return F.relu(output)
class PONO(nn.Module):
def __init__(self, input_size=None, return_stats=False, affine=True, eps=1e-5):
super(PONO, self).__init__()
self.return_stats = return_stats
self.input_size = input_size
self.eps = eps
self.affine = affine
if affine:
self.beta = nn.Parameter(torch.zeros(1, 1, *input_size))
self.gamma = nn.Parameter(torch.ones(1, 1, *input_size))
else:
self.beta, self.gamma = None, None
def forward(self, x):
mean = x.mean(dim=1, keepdim=True)
std = (x.var(dim=1, keepdim=True) + self.eps).sqrt()
x = (x - mean) / std
if self.affine:
x = x * self.gamma + self.beta
return x, mean, std
class MS(nn.Module):
def __init__(self, beta=None, gamma=None):
super(MS, self).__init__()
self.gamma, self.beta = gamma, beta
def forward(self, x, beta=None, gamma=None):
beta = self.beta if beta is None else beta
gamma = self.gamma if gamma is None else gamma
if gamma is not None:
x.mul_(gamma)
if beta is not None:
x.add_(beta)
return x

View File

@@ -0,0 +1,138 @@
import os
import torch
import torch.backends.cudnn
import torch.nn
import torch.nn.parallel
import torch.optim
import torch.utils.data
import torchvision
from torchvision import transforms
from torchvision.utils import make_grid
from tensorboardX import SummaryWriter
from utils import logger, weight_init
from config import get_config
from model import AODnet, AOD_pono_net
from data import HazeDataset
@logger
def load_data(cfg):
data_transform = transforms.Compose([
transforms.Resize([480, 640]),
transforms.ToTensor()
])
train_haze_dataset = HazeDataset(cfg.ori_data_path, cfg.haze_data_path, data_transform)
train_loader = torch.utils.data.DataLoader(train_haze_dataset, batch_size=cfg.batch_size, shuffle=True,
num_workers=cfg.num_workers, drop_last=True, pin_memory=True)
val_haze_dataset = HazeDataset(cfg.val_ori_data_path, cfg.val_haze_data_path, data_transform)
val_loader = torch.utils.data.DataLoader(val_haze_dataset, batch_size=cfg.val_batch_size, shuffle=False,
num_workers=cfg.num_workers, drop_last=True, pin_memory=True)
return train_loader, len(train_loader), val_loader, len(val_loader)
@logger
def save_model(epoch, path, net, optimizer, net_name):
if not os.path.exists(os.path.join(path, net_name)):
os.mkdir(os.path.join(path, net_name))
torch.save({'epoch': epoch, 'state_dict': net.state_dict(), 'optimizer': optimizer.state_dict()},
f=os.path.join(path, net_name, '{}_{}.pkl'.format('AOD', epoch)))
@logger
def load_network(device):
net = AOD_pono_net().to(device)
net.apply(weight_init)
return net
@logger
def load_optimizer(net, cfg):
optimizer = torch.optim.Adam(net.parameters(), lr=cfg.lr, weight_decay=cfg.weight_decay)
return optimizer
@logger
def loss_func(device):
criterion = torch.nn.MSELoss().to(device)
return criterion
@logger
def load_summaries(cfg):
summary = SummaryWriter(log_dir=os.path.join(cfg.log_dir, cfg.net_name), comment='')
return summary
def main(cfg):
# -------------------------------------------------------------------
# basic config
print(cfg)
if cfg.gpu > -1:
os.environ['CUDA_VISIBLE_DEVICES'] = str(cfg.gpu)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# -------------------------------------------------------------------
# load summaries
summary = load_summaries(cfg)
# -------------------------------------------------------------------
# load data
train_loader, train_number, val_loader, val_number = load_data(cfg)
# -------------------------------------------------------------------
# load loss
criterion = loss_func(device)
# -------------------------------------------------------------------
# load network
network = load_network(device)
# -------------------------------------------------------------------
# load optimizer
optimizer = load_optimizer(network, cfg)
# -------------------------------------------------------------------
# start train
print('Start train')
network.train()
for epoch in range(cfg.epochs):
for step, (ori_image, haze_image) in enumerate(train_loader):
count = epoch * train_number + (step + 1)
ori_image, haze_image = ori_image.to(device), haze_image.to(device)
dehaze_image = network(haze_image)
loss = criterion(dehaze_image, ori_image)
optimizer.zero_grad()
loss.backward()
torch.nn.utils.clip_grad_norm_(network.parameters(), cfg.grad_clip_norm)
optimizer.step()
summary.add_scalar('loss', loss.item(), count)
if step % cfg.print_gap == 0:
summary.add_image('DeHaze_Images', make_grid(dehaze_image[:4].data, normalize=True, scale_each=True),
count)
summary.add_image('Haze_Images', make_grid(haze_image[:4].data, normalize=True, scale_each=True), count)
summary.add_image('Origin_Images', make_grid(ori_image[:4].data, normalize=True, scale_each=True),
count)
print('Epoch: {}/{} | Step: {}/{} | lr: {:.6f} | Loss: {:.6f}'
.format(epoch + 1, cfg.epochs, step + 1, train_number,
optimizer.param_groups[0]['lr'], loss.item()))
# -------------------------------------------------------------------
# start validation
print('Epoch: {}/{} | Validation Model Saving Images'.format(epoch + 1, cfg.epochs))
network.eval()
for step, (ori_image, haze_image) in enumerate(val_loader):
if step > 10: # only save image 10 times
break
ori_image, haze_image = ori_image.to(device), haze_image.to(device)
dehaze_image = network(haze_image)
torchvision.utils.save_image(
torchvision.utils.make_grid(torch.cat((haze_image, dehaze_image, ori_image), 0),
nrow=ori_image.shape[0]),
os.path.join(cfg.sample_output_folder, '{}_{}.jpg'.format(epoch + 1, step)))
network.train()
# -------------------------------------------------------------------
# save per epochs model
save_model(epoch, cfg.model_dir, network, optimizer, cfg.net_name)
# -------------------------------------------------------------------
# train finish
summary.close()
if __name__ == '__main__':
config_args, unparsed_args = get_config()
main(config_args)

View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
python pono_train.py --epochs 10 \
--net_name aod-xavier \
--lr 1e-4 \
--use_gpu true \
--gpu 3 \
--ori_data_path data/images/ \
--haze_data_path data/data/ \
--val_ori_data_path data/images/ \
--val_haze_data_path data/val/ \
--num_workers 2 \
--batch_size 8 \
--val_batch_size 16 \
--print_gap 500 \
--model_dir ponomodels \
--log_dir ponologs \
--sample_output_folder ponosamples

Binary file not shown.

View File

@@ -0,0 +1,61 @@
#!/bin/bash
# 原图像位置
Dir_src_pics="./data/img"
Dir_result="./data/result"
Dir_ori_src_pics="/root/Dehaze/SRC_files/src"
mkdir -p $Dir_src_pics $Dir_result
# 进行绝对路径转化
Dir_src_pics=$(readlink -f "$Dir_src_pics")
Dir_result=$(readlink -f "$Dir_result")
if [ ! -d "$Dir_src_pics" ] && [ ! -d "$Dir_result" ]; then
echo "image、label都不存在程序退出"
echo -e "\033[31mori_image_directory\033[0m: $Dir_src_pics"
echo "$Dir_src_pics"
echo -e "\033[31mori_label_directory\033[0m: $Dir_result"
echo "$Dir_result"
exit 1
fi
PS3='All in one choice : '
applications=("Delete_src_pics" "Delete_generate_pics" "Copy_src_pics_to_test" "Run_test_program" "quit")
select fav in "${applications[@]}"; do
case $fav in
# 删除原始文件选项
"Delete_src_pics")
# 删除src文件
echo "Delete all src files in $Dir_src_pics"
rm $Dir_src_pics/*
;;
# 删除生成文件选项
"Delete_generate_pics")
# 删除result文件
echo "Delete all src files in $Dir_result"
rm $Dir_result/*
;;
# 复制待处理文件选项
"Copy_src_pics_to_test")
# 删除src文件
echo "Copy all src files in $Dir_ori_src_pics"
ln -s $Dir_ori_src_pics/* $Dir_src_pics
;;
# 运行程序
"Run_test_program")
source ~/miniconda/bin/activate Dehaze_DCP
python ./test/test.py $Dir_src_pics $Dir_result
;;
# 退出选项
"quit")
echo "User requested exit"
exit
;;
# 其他选项
*) echo "invalid option $REPLY";;
esac
done

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View File

@@ -0,0 +1,42 @@
# AOD-Net
ICCV 2017
AOD-Net is a light-weight but effective end-to-end dehazing neural network.
It is very easy and fast for you to train or test.
For test:
We have offered test.py, relevant prototxt and data.
You can just use 'python test.py' with GPU/CPU, you can obtain your result in data/result.
Good luck for your research.
# Improved AOD-Net with Positional Normalization (PONO)
We appreciate the [PyTorch implementation](https://github.com/TheFairBear/PyTorch-Image-Dehazing) of AOD-Net. Based on this code, we add simple [PONO](https://github.com/Boyiliee/PONO) into AOD-Net, which improves the performance efficiently.
Previous AOD-Net Results:
![](./AOD-Net_result.png)
For TestSet A, the PSNR increases from 19.69 to 20.38 dB, the SSIM increases from 0.8478 to 0.8587. For TestSetB, the PSNR increases from 21.54 to 21.67 dB, the SSIM increases from 0.9272 to 0.9285.
Please find in [AOD-Net with PONO](https://github.com/Boyiliee/AOD-Net/tree/master/AOD-Net%20with%20PONO) for details.
Bibtex:
```
@inproceedings{ICCV17a,
title={AOD-Net: All-in-One Dehazing Network},
author={Li, Boyi and Peng, Xiulian and Wang, Zhangyang and Xu, Ji-Zheng and Feng, Dan},
booktitle={Proceedings of the IEEE International Conference on Computer Vision},
year={2017}
}
@article{pono,
title={Positional Normalization},
author={Li, Boyi and Wu, Felix and Weinberger, Kilian Q. and Belongie, Serge},
journal={Advances in Neural Information Processing Systems},
year={2019}
}
```

View File

@@ -0,0 +1,79 @@
from __future__ import annotations
import argparse
from pathlib import Path
import caffe
import cv2
BASE_DIR = Path(__file__).resolve().parents[1]
SUPPORTED_EXTENSIONS = {".png", ".jpg", ".jpeg", ".bmp", ".tif", ".tiff"}
def edit_fcn_proto(template_file: Path, output_file: Path, height: int, width: int) -> None:
template = template_file.read_text()
output_file.write_text(template.format(height=height, width=width))
def iter_images(input_dir: Path) -> list[Path]:
return [
path
for path in sorted(input_dir.iterdir(), key=lambda p: p.name.lower())
if path.is_file() and path.suffix.lower() in SUPPORTED_EXTENSIONS
]
def run(input_dir: Path, output_dir: Path, max_dim: int) -> None:
caffe.set_mode_cpu()
output_dir.mkdir(parents=True, exist_ok=True)
template_file = BASE_DIR / "test" / "test_template.prototxt"
deploy_file = BASE_DIR / "test" / "DeployT.prototxt"
model_file = BASE_DIR / "AOD_Net.caffemodel"
images = iter_images(input_dir)
print(f"image numbers: {len(images)}")
for index, img_path in enumerate(images, start=1):
npstore = caffe.io.load_image(str(img_path))
orig_h, orig_w = npstore.shape[0], npstore.shape[1]
if orig_h > max_dim or orig_w > max_dim:
scale = max_dim / float(max(orig_h, orig_w))
new_h = int(orig_h * scale)
new_w = int(orig_w * scale)
npstore = cv2.resize(npstore, (new_w, new_h), interpolation=cv2.INTER_CUBIC)
print(f"Resized {img_path.name} from {orig_w}x{orig_h} to {new_w}x{new_h}")
height, width = npstore.shape[0], npstore.shape[1]
edit_fcn_proto(template_file, deploy_file, height, width)
net = caffe.Net(str(deploy_file), str(model_file), caffe.TEST)
data = npstore.transpose((2, 0, 1))
net.blobs["data"].data[...] = [data]
net.forward()
result = net.blobs["sum"].data[0].transpose((1, 2, 0))
result = result[:, :, ::-1]
if height != orig_h or width != orig_w:
result = cv2.resize(result, (orig_w, orig_h), interpolation=cv2.INTER_CUBIC)
save_path = output_dir / f"{img_path.stem}_AOD-Net.png"
cv2.imwrite(str(save_path), result * 255.0, [cv2.IMWRITE_JPEG_QUALITY, 100])
print(f"[{index}/{len(images)}] saved: {save_path}")
def main() -> None:
parser = argparse.ArgumentParser(description="Run AOD-Net on a folder of images.")
parser.add_argument("input_dir", nargs="?", default=str(BASE_DIR / "data" / "img"))
parser.add_argument("output_dir", nargs="?", default=str(BASE_DIR / "data" / "result"))
parser.add_argument("--max-dim", type=int, default=1920)
args = parser.parse_args()
run(Path(args.input_dir), Path(args.output_dir), args.max_dim)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,189 @@
name:"DirectDehazing"
input: "data"
input_dim: 1
input_dim: 3
input_dim:{height}
input_dim:{width}
input: "label"
input_dim: 1
input_dim: 3
input_dim:{height}
input_dim:{width}
layer {{
name: "conv1"
type: "Convolution"
bottom: "data"
top: "conv1"
convolution_param {{
num_output: 3
kernel_size: 1
}}
}}
layer {{
name: "relu1"
type: "ReLU"
bottom: "conv1"
top: "conv1"
}}
layer {{
name: "conv2"
type: "Convolution"
bottom: "conv1"
top: "conv2"
convolution_param {{
num_output: 3
kernel_size: 3
pad:1
}}
}}
layer {{
name: "relu2"
type: "ReLU"
bottom: "conv2"
top: "conv2"
}}
layer {{
name: "Concat1"
type: "Concat"
bottom: "conv1"
bottom: "conv2"
top: "Concat1"
concat_param {{
axis: 1
}}
}}
layer {{
name: "conv3"
type: "Convolution"
bottom: "Concat1"
top: "conv3"
convolution_param {{
num_output: 3
kernel_size: 5
pad:2
}}
}}
layer {{
name: "relu3"
type: "ReLU"
bottom: "conv3"
top: "conv3"
}}
layer {{
name: "Concat2"
type: "Concat"
bottom: "conv2"
bottom: "conv3"
top: "Concat2"
concat_param {{
axis: 1
}}
}}
layer {{
name: "conv4"
type: "Convolution"
bottom: "Concat2"
top: "conv4"
convolution_param {{
num_output: 3
kernel_size: 7
pad:3
}}
}}
layer {{
name: "relu4"
type: "ReLU"
bottom: "conv4"
top: "conv4"
}}
layer {{
name: "Concat3"
type: "Concat"
bottom: "conv1"
bottom: "conv2"
bottom: "conv3"
bottom: "conv4"
top: "Concat3"
concat_param {{
axis: 1
}}
}}
layer {{
name: "conv5"
type: "Convolution"
bottom: "Concat3"
top: "conv5"
convolution_param {{
num_output: 3
kernel_size: 3
pad:1
}}
}}
layer {{
name: "relu5"
type: "ReLU"
bottom: "conv5"
top: "K"
}}
layer {{
name: "prod"
type: "Eltwise"
bottom: "data"
bottom: "K"
top: "prod"
eltwise_param {{
operation: PROD
}}
}}
layer {{
name:"eltwise_layer"
type:"Eltwise"
bottom:"prod"
bottom:"K"
top:"eltwise_layer"
eltwise_param{{
operation:SUM
coeff:1
coeff:-1
}}
}}
layer {{
name: "sum"
bottom: "eltwise_layer"
top: "sum"
type: "Power"
power_param {{
power: 1
scale: 1
shift: 1
}}
}}
layer {{
name: "clip"
type: "ReLU"
bottom: "sum"
top: "sum"
}}
layer {{
name: "loss"
type: "EuclideanLoss"
bottom: "sum"
bottom: "label"
top: "loss"
}}