Skip to content

Commit 8e4bc85

Browse files
committed
fix deprecations, onnx conversion, inference example
1 parent f721581 commit 8e4bc85

File tree

10 files changed

+142
-122
lines changed

10 files changed

+142
-122
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/__pycache__
2+
*/__pycache__
3+
/.idea
4+
*.pt
5+
*.pth
6+
*.onnx
7+
/results
8+
result.png

README.md

Lines changed: 28 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,44 @@
1-
# ShadowFormer (AAAI'23)
2-
This is the official implementation of the AAAI 2023 paper [ShadowFormer: Global Context Helps Image Shadow Removal](https://arxiv.org/pdf/2302.01650.pdf).
1+
# Fork of [GuoLanqing/ShadowFormer](https://github.com/GuoLanqing/ShadowFormer)
32

4-
[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/shadowformer-global-context-helps-image/shadow-removal-on-istd)](https://paperswithcode.com/sota/shadow-removal-on-istd?p=shadowformer-global-context-helps-image)
5-
[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/shadowformer-global-context-helps-image/shadow-removal-on-adjusted-istd)](https://paperswithcode.com/sota/shadow-removal-on-adjusted-istd?p=shadowformer-global-context-helps-image)
6-
[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/shadowformer-global-context-helps-image/shadow-removal-on-srd)](https://paperswithcode.com/sota/shadow-removal-on-srd?p=shadowformer-global-context-helps-image)
3+
Differences between original repository and fork:
74

8-
#### News
9-
* **Feb 24, 2023**: Release the pretrained models for ISTD and ISTD+.
10-
* **Feb 18, 2023**: Release the training and testing codes.
11-
* **Feb 17, 2023**: Add the testing results and the description of our work.
5+
* Compatibility with PyTorch >=2.5. (🔥)
6+
* Original pretrained models and converted ONNX models from GitHub [releases page](https://github.com/clibdev/ShadowFormer/releases). (🔥)
7+
* Model conversion to ONNX format using the [export.py](export.py) file. (🔥)
8+
* Sample script [inference.py](inference.py) for inference of single image.
9+
* The following deprecations has been fixed:
10+
* UserWarning: torch.meshgrid: in an upcoming release, it will be required to pass the indexing argument.
11+
* FutureWarning: Importing from timm.models.layers is deprecated, please import via timm.layers.
12+
* FutureWarning: You are using 'torch.load' with 'weights_only=False'.
1213

13-
## Introduction
14-
To tackle image shadow removal problem, we propose a novel transformer-based method, dubbed ShadowFormer, for exploiting non-shadow
15-
regions to help shadow region restoration. A multi-scale channel attention framework is employed to hierarchically
16-
capture the global information. Based on that, we propose a Shadow-Interaction Module (SIM) with Shadow-Interaction Attention (SIA) in the bottleneck stage to effectively model the context correlation between shadow and non-shadow regions.
17-
For more details, please refer to our [original paper](https://arxiv.org/pdf/2302.01650.pdf)
14+
# Installation
1815

19-
<p align=center><img width="80%" src="doc/pipeline.jpg"/></p>
20-
21-
<p align=center><img width="80%" src="doc/details.jpg"/></p>
22-
23-
## Requirement
24-
* Python 3.7
25-
* Pytorch 1.7
26-
* CUDA 11.1
27-
```bash
16+
```shell
2817
pip install -r requirements.txt
2918
```
3019

31-
## Datasets
32-
* ISTD [[link]](https://github.com/DeepInsight-PCALab/ST-CGAN)
33-
* ISTD+ [[link]](https://github.com/cvlab-stonybrook/SID)
34-
* SRD [[Training]](https://drive.google.com/file/d/1W8vBRJYDG9imMgr9I2XaA13tlFIEHOjS/view)[[Testing]](https://drive.google.com/file/d/1GTi4BmQ0SJ7diDMmf-b7x2VismmXtfTo/view)
20+
# Pretrained models
3521

36-
## Pretrained models
37-
[ISTD](https://drive.google.com/file/d/1bHbkHxY5D5905BMw2jzvkzgXsFPKzSq4/view?usp=share_link) | [ISTD+](https://drive.google.com/file/d/10pBsJenoWGriZ9kjWOcE4l4Kzg-F1TFd/view?usp=share_link) | [SRD]()
22+
* Download links:
3823

39-
Please download the corresponding pretrained model and modify the `weights` in `test.py`.
24+
| Name | Model Size (MB) | Link | SHA-256 |
25+
|----------------------|-----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
26+
| ShadowFormer (ISTD) | 130.9<br>83.0 | [PyTorch](https://github.com/clibdev/ShadowFormer/releases/latest/download/shadowformer-istd.pt)<br>[ONNX](https://github.com/clibdev/ShadowFormer/releases/latest/download/shadowformer-istd.onnx) | 4700ae374b965253734dbcac0b63c9cac9af5895ff19655710042a988751fc98<br>96b90f5f1d11b67e3c7835cae3ccacaaa78ac4fadbf03a04fd36769e21f619a6 |
27+
| ShadowFormer (ISTD+) | 130.9<br>83.0 | [PyTorch](https://github.com/clibdev/ShadowFormer/releases/latest/download/shadowformer-istd-plus.pt)<br>[ONNX](https://github.com/clibdev/ShadowFormer/releases/latest/download/shadowformer-istd-plus.onnx) | 2748060149908df37cc65f0695ef61d64cd25847aba0c35af36823f9b780f5b2<br>077128017e7400c0e7c22210d6afb83748bfb068a6e02037156ea4ab8a8592a9 |
4028

41-
## Test
42-
You can directly test the performance of the pre-trained model as follows
43-
1. Modify the paths to dataset and pre-trained model. You need to modify the following path in the `test.py`
44-
```python
45-
input_dir # shadow image input path -- Line 27
46-
weights # pretrained model path -- Line 31
47-
```
48-
2. Test the model
49-
```python
50-
python test.py --save_images
51-
```
52-
You can check the output in `./results`.
29+
# Inference
5330

54-
## Train
55-
1. Download datasets and set the following structure
56-
```
57-
|-- ISTD_Dataset
58-
|-- train
59-
|-- train_A # shadow image
60-
|-- train_B # shadow mask
61-
|-- train_C # shadow-free GT
62-
|-- test
63-
|-- test_A # shadow image
64-
|-- test_B # shadow mask
65-
|-- test_C # shadow-free GT
66-
```
67-
2. You need to modify the following terms in `option.py`
68-
```python
69-
train_dir # training set path
70-
val_dir # testing set path
71-
gpu: 0 # Our model can be trained using a single RTX A5000 GPU. You can also train the model using multiple GPUs by adding more GPU ids in it.
72-
```
73-
3. Train the network
74-
If you want to train the network on 256X256 images:
75-
```python
76-
python train.py --warmup --win_size 8 --train_ps 256
77-
```
78-
or you want to train on original resolution, e.g., 480X640 for ISTD:
79-
```python
80-
python train.py --warmup --win_size 10 --train_ps 320
31+
```shell
32+
python inference.py --weights shadowformer-istd.pt --input_path img/noisy_image.png --mask_path img/mask.png
33+
python inference.py --weights shadowformer-istd-plus.pt --input_path img/noisy_image.png --mask_path img/mask.png
8134
```
8235

83-
## Evaluation
84-
The results reported in the paper are calculated by the `matlab` script used in [previous method](https://github.com/zhuyr97/AAAI2022_Unfolding_Network_Shadow_Removal/tree/master/codes). Details refer to `evaluation/measure_shadow.m`.
85-
We also provide the `python` code for calculating the metrics in `test.py`, using `python test.py --cal_metrics` to print.
36+
# Export to ONNX format
8637

87-
## Results
88-
#### Evaluation on ISTD
89-
The evaluation results on ISTD are as follows
90-
| Method | PSNR | SSIM | RMSE |
91-
| :-- | :--: | :--: | :--: |
92-
| ST-CGAN | 27.44 | 0.929 | 6.65 |
93-
| DSC | 29.00 | 0.944 | 5.59 |
94-
| DHAN | 29.11 | 0.954 | 5.66 |
95-
| Fu et al. | 27.19 | 0.945 | 5.88 |
96-
| Zhu et al. | 29.85 | 0.960 | 4.27 |
97-
| **ShadowFormer (Ours)** | **32.21** | **0.968** | **4.09** |
98-
99-
#### Visual Results
100-
<p align=center><img width="80%" src="doc/res.jpg"/></p>
101-
102-
#### Testing results
103-
The testing results on dataset ISTD, ISTD+, SRD are: [results](https://drive.google.com/file/d/1zcv7KBCIKgk-CGQJCWnM2YAKcSAj8Sc4/view?usp=share_link)
104-
105-
## References
106-
Our implementation is based on [Uformer](https://github.com/ZhendongWang6/Uformer) and [Restormer](https://github.com/swz30/Restormer). We would like to thank them.
107-
108-
Citation
109-
-----
110-
Preprint available [here](https://arxiv.org/pdf/2302.01650.pdf).
111-
112-
In case of use, please cite our publication:
113-
114-
L. Guo, S. Huang, D. Liu, H. Cheng and B. Wen, "ShadowFormer: Global Context Helps Image Shadow Removal," AAAI 2023.
115-
116-
Bibtex:
38+
```shell
39+
pip install onnx
11740
```
118-
@article{guo2023shadowformer,
119-
title={ShadowFormer: Global Context Helps Image Shadow Removal},
120-
author={Guo, Lanqing and Huang, Siyu and Liu, Ding and Cheng, Hao and Wen, Bihan},
121-
journal={arXiv preprint arXiv:2302.01650},
122-
year={2023}
123-
}
41+
```shell
42+
python export.py --weights shadowformer-istd.pt
43+
python export.py --weights shadowformer-istd-plus.pt
12444
```
125-
126-
## Contact
127-
If you have any questions, please contact [email protected]

export.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from model import ShadowFormer
2+
import torch
3+
import utils
4+
import argparse
5+
import os
6+
7+
parser = argparse.ArgumentParser()
8+
parser.add_argument('--weights', default='./shadowformer-istd.pt')
9+
parser.add_argument('--device', default='cpu', type=str, help='cuda or cpu')
10+
parser.add_argument('--dynamic', action='store_true', default=False, help='enable dynamic axis in onnx model')
11+
opt = parser.parse_args()
12+
13+
device = torch.device(opt.device)
14+
15+
win_size = 10
16+
img_multiple_of = 8 * win_size
17+
18+
model_restoration = ShadowFormer(img_size=320, embed_dim=32, win_size=win_size, token_projection='linear', token_mlp='leff')
19+
20+
utils.load_checkpoint(model_restoration, opt.weights)
21+
22+
model_restoration.to(device)
23+
model_restoration.eval()
24+
25+
model_path = os.path.splitext(opt.weights)[0] + '.onnx'
26+
27+
dummy_input_1 = torch.randn(1, 3, 480, 640).to(opt.device)
28+
dummy_input_2 = torch.randn(1, 1, 480, 640).to(opt.device)
29+
30+
dynamic_axes = {'input': {2: '?', 3: '?'}, 'mask': {2: '?', 3: '?'}, 'output': {2: '?', 3: '?'}} if opt.dynamic else None
31+
32+
torch.onnx.export(
33+
model_restoration,
34+
(dummy_input_1, dummy_input_2),
35+
model_path,
36+
verbose=False,
37+
input_names=['input', 'mask'],
38+
output_names=['output'],
39+
dynamic_axes=dynamic_axes,
40+
opset_version=17
41+
)

img/mask.png

2.06 KB
Loading

img/noisy_image.png

611 KB
Loading

inference.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from model import ShadowFormer
2+
import torch
3+
import utils
4+
import numpy as np
5+
import torch.nn.functional as F
6+
import argparse
7+
8+
parser = argparse.ArgumentParser()
9+
parser.add_argument('--input_path', default='./img/noisy_image.png')
10+
parser.add_argument('--mask_path', default='./img/mask.png')
11+
parser.add_argument('--output_path', default='./result.png')
12+
parser.add_argument('--weights', default='./shadowformer-istd.pt')
13+
parser.add_argument('--device', default='cuda', help='cuda or cpu')
14+
opt = parser.parse_args()
15+
16+
device = torch.device(opt.device)
17+
18+
win_size = 10
19+
img_multiple_of = 8 * win_size
20+
21+
model_restoration = ShadowFormer(img_size=320, embed_dim=32, win_size=win_size, token_projection='linear', token_mlp='leff')
22+
23+
utils.load_checkpoint(model_restoration, opt.weights)
24+
25+
model_restoration.to(device)
26+
model_restoration.eval()
27+
28+
with torch.no_grad():
29+
rgb_noisy = torch.from_numpy(np.float32(utils.load_img(opt.input_path)))
30+
rgb_noisy = rgb_noisy.permute(2,0,1)
31+
rgb_noisy = rgb_noisy.unsqueeze(0).to(device)
32+
33+
mask = utils.load_mask(opt.mask_path)
34+
mask = torch.from_numpy(np.float32(mask))
35+
mask = torch.unsqueeze(mask, dim=0)
36+
mask = mask.unsqueeze(0).to(device)
37+
38+
# Pad the input if not_multiple_of win_size * 8
39+
height, width = rgb_noisy.shape[2], rgb_noisy.shape[3]
40+
H, W = ((height + img_multiple_of) // img_multiple_of) * img_multiple_of, (
41+
(width + img_multiple_of) // img_multiple_of) * img_multiple_of
42+
padh = H - height if height % img_multiple_of != 0 else 0
43+
padw = W - width if width % img_multiple_of != 0 else 0
44+
rgb_noisy = F.pad(rgb_noisy, (0, padw, 0, padh), 'reflect')
45+
mask = F.pad(mask, (0, padw, 0, padh), 'reflect')
46+
47+
rgb_restored = model_restoration(rgb_noisy, mask)
48+
rgb_restored = torch.clamp(rgb_restored, 0, 1).cpu().numpy().squeeze().transpose((1, 2, 0))
49+
50+
# Unpad the output
51+
rgb_restored = rgb_restored[:height, :width, :]
52+
53+
utils.save_img(rgb_restored*255.0, opt.output_path)

model.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import torch
22
import torch.nn as nn
33
import torch.utils.checkpoint as checkpoint
4-
from timm.models.layers import DropPath, to_2tuple, trunc_normal_
4+
from timm.layers import DropPath, to_2tuple, trunc_normal_
55
import torch.nn.functional as F
66
from einops import rearrange, repeat
77
from einops.layers.torch import Rearrange
@@ -362,7 +362,7 @@ def __init__(self, dim, win_size, num_heads, token_projection='linear', qkv_bias
362362
# get pair-wise relative position index for each token inside the window
363363
coords_h = torch.arange(self.win_size[0]) # [0,...,Wh-1]
364364
coords_w = torch.arange(self.win_size[1]) # [0,...,Ww-1]
365-
coords = torch.stack(torch.meshgrid([coords_h, coords_w])) # 2, Wh, Ww
365+
coords = torch.stack(torch.meshgrid([coords_h, coords_w], indexing='ij')) # 2, Wh, Ww
366366
coords_flatten = torch.flatten(coords, 1) # 2, Wh*Ww
367367
relative_coords = coords_flatten[:, :, None] - coords_flatten[:, None, :] # 2, Wh*Ww, Wh*Ww
368368
relative_coords = relative_coords.permute(1, 2, 0).contiguous() # Wh*Ww, Wh*Ww, 2

requirements.txt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
torch==1.7.1
2-
torchvision==0.8.2
1+
torch>=2.5.0
2+
torchvision>=0.20.0
33
matplotlib
44
scikit-image
5-
opencv-python
5+
opencv-python>=4.10.0
66
yacs
77
joblib
88
natsort
@@ -12,4 +12,7 @@ einops
1212
linformer
1313
timm
1414
ptflops
15-
dataclasses
15+
dataclasses
16+
17+
### Optional - ONNX format
18+
# onnx>=1.17.0

test.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,15 @@
1717
import cv2
1818
from model import UNet
1919

20-
from skimage import img_as_float32, img_as_ubyte
2120
from skimage.metrics import peak_signal_noise_ratio as psnr_loss
2221
from skimage.metrics import structural_similarity as ssim_loss
23-
from sklearn.metrics import mean_squared_error as mse_loss
2422

2523
parser = argparse.ArgumentParser(description='RGB denoising evaluation on the validation set of SIDD')
26-
parser.add_argument('--input_dir', default='../ISTD_Dataset/test/',
24+
parser.add_argument('--input_dir', default='./ISTD_Dataset/test',
2725
type=str, help='Directory of validation images')
2826
parser.add_argument('--result_dir', default='./results/',
2927
type=str, help='Directory for results')
30-
parser.add_argument('--weights', default='./log/ShadowFormer_istd/models/model_best.pth',
28+
parser.add_argument('--weights', default='shadowformer-istd.pt',
3129
type=str, help='Path to weights')
3230
parser.add_argument('--gpus', default='0', type=str, help='CUDA_VISIBLE_DEVICES')
3331
parser.add_argument('--arch', default='ShadowFormer', type=str, help='arch')

utils/model_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def save_checkpoint(model_dir, state, session):
2121
torch.save(state, model_out_path)
2222

2323
def load_checkpoint(model, weights):
24-
checkpoint = torch.load(weights)
24+
checkpoint = torch.load(weights, weights_only=True)
2525
try:
2626
model.load_state_dict(checkpoint["state_dict"])
2727
except:

0 commit comments

Comments
 (0)