Spaces:
Configuration error
Configuration error
Commit
·
cde2910
1
Parent(s):
2d08e8f
Upload 6 files
Browse files- .gitignore +137 -0
- README.md +67 -10
- demo.py +63 -0
- exposure_enhancement.py +190 -0
- requirements.txt +4 -0
- utils.py +26 -0
.gitignore
ADDED
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Byte-compiled / optimized / DLL files
|
2 |
+
__pycache__/
|
3 |
+
*.py[cod]
|
4 |
+
*$py.class
|
5 |
+
|
6 |
+
# C extensions
|
7 |
+
*.so
|
8 |
+
|
9 |
+
# Distribution / packaging
|
10 |
+
.Python
|
11 |
+
build/
|
12 |
+
develop-eggs/
|
13 |
+
dist/
|
14 |
+
downloads/
|
15 |
+
eggs/
|
16 |
+
.eggs/
|
17 |
+
lib/
|
18 |
+
lib64/
|
19 |
+
parts/
|
20 |
+
sdist/
|
21 |
+
var/
|
22 |
+
wheels/
|
23 |
+
pip-wheel-metadata/
|
24 |
+
share/python-wheels/
|
25 |
+
*.egg-info/
|
26 |
+
.installed.cfg
|
27 |
+
*.egg
|
28 |
+
MANIFEST
|
29 |
+
|
30 |
+
# PyInstaller
|
31 |
+
# Usually these files are written by a python script from a template
|
32 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
33 |
+
*.manifest
|
34 |
+
*.spec
|
35 |
+
|
36 |
+
# Installer logs
|
37 |
+
pip-log.txt
|
38 |
+
pip-delete-this-directory.txt
|
39 |
+
|
40 |
+
# Unit test / coverage reports
|
41 |
+
htmlcov/
|
42 |
+
.tox/
|
43 |
+
.nox/
|
44 |
+
.coverage
|
45 |
+
.coverage.*
|
46 |
+
.cache
|
47 |
+
nosetests.xml
|
48 |
+
coverage.xml
|
49 |
+
*.cover
|
50 |
+
*.py,cover
|
51 |
+
.hypothesis/
|
52 |
+
.pytest_cache/
|
53 |
+
|
54 |
+
# Translations
|
55 |
+
*.mo
|
56 |
+
*.pot
|
57 |
+
|
58 |
+
# Django stuff:
|
59 |
+
*.log
|
60 |
+
local_settings.py
|
61 |
+
db.sqlite3
|
62 |
+
db.sqlite3-journal
|
63 |
+
|
64 |
+
# Flask stuff:
|
65 |
+
instance/
|
66 |
+
.webassets-cache
|
67 |
+
|
68 |
+
# Scrapy stuff:
|
69 |
+
.scrapy
|
70 |
+
|
71 |
+
# Sphinx documentation
|
72 |
+
docs/_build/
|
73 |
+
|
74 |
+
# PyBuilder
|
75 |
+
target/
|
76 |
+
|
77 |
+
# Jupyter Notebook
|
78 |
+
.ipynb_checkpoints
|
79 |
+
|
80 |
+
# IPython
|
81 |
+
profile_default/
|
82 |
+
ipython_config.py
|
83 |
+
|
84 |
+
# pyenv
|
85 |
+
.python-version
|
86 |
+
|
87 |
+
# pipenv
|
88 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
89 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
90 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
91 |
+
# install all needed dependencies.
|
92 |
+
#Pipfile.lock
|
93 |
+
|
94 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
95 |
+
__pypackages__/
|
96 |
+
|
97 |
+
# Celery stuff
|
98 |
+
celerybeat-schedule
|
99 |
+
celerybeat.pid
|
100 |
+
|
101 |
+
# SageMath parsed files
|
102 |
+
*.sage.py
|
103 |
+
|
104 |
+
# Environments
|
105 |
+
.env
|
106 |
+
.venv
|
107 |
+
env/
|
108 |
+
venv/
|
109 |
+
ENV/
|
110 |
+
env.bak/
|
111 |
+
venv.bak/
|
112 |
+
|
113 |
+
# Spyder project settings
|
114 |
+
.spyderproject
|
115 |
+
.spyproject
|
116 |
+
|
117 |
+
# Rope project settings
|
118 |
+
.ropeproject
|
119 |
+
|
120 |
+
# mkdocs documentation
|
121 |
+
/site
|
122 |
+
|
123 |
+
# mypy
|
124 |
+
.mypy_cache/
|
125 |
+
.dmypy.json
|
126 |
+
dmypy.json
|
127 |
+
|
128 |
+
# Pyre type checker
|
129 |
+
.pyre/
|
130 |
+
|
131 |
+
|
132 |
+
# demo folder
|
133 |
+
/demo/enhanced/*
|
134 |
+
exposure_corrector (copy).py
|
135 |
+
|
136 |
+
# notebooks
|
137 |
+
*.ipynb
|
README.md
CHANGED
@@ -1,10 +1,67 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Low-light-Image-Enhancement
|
2 |
+
Python implementation of two low-light image enhancement techniques via illumination map estimation, based on the following papers:
|
3 |
+
* Dual Illumination Estimation for Robust Exposure Correction [[link](https://arxiv.org/pdf/1910.13688.pdf)]
|
4 |
+
* LIME: Low-light Image Enhancement via Illumination Map Estimation [[link](https://ieeexplore.ieee.org/document/7782813)]
|
5 |
+
|
6 |
+
Both methods are based on retinex modelling, and aim at estimating the illumination map by preserving the prominent structure of the image, while removing the redundant texture details. To do this, the same optimization formulation is used by both papers (see references). The novelty introduced by the first paper (called DUAL below) compared to the second (called LIME below) is the estimation of this map for the original image, and for its inverted version, which allows to correct both the under-exposed and over-exposed parts of the image.
|
7 |
+
|
8 |
+
The code implemented in this repository allows the use of both methods, which can be easily selected from the script parameters.
|
9 |
+
|
10 |
+
## Installation
|
11 |
+
This implementation runs on python >= 3.7, use pip to install dependencies:
|
12 |
+
```
|
13 |
+
pip3 install -r requirements.txt
|
14 |
+
```
|
15 |
+
|
16 |
+
## Usage
|
17 |
+
Use the `demo.py` script to enhance your images.
|
18 |
+
```
|
19 |
+
usage: demo.py [-h] [-f FOLDER] [-g GAMMA] [-l LAMBDA_] [-ul] [-s SIGMA]
|
20 |
+
[-bc BC] [-bs BS] [-be BE] [-eps EPS]
|
21 |
+
|
22 |
+
Python implementation of two low-light image enhancement techniques via illumination map estimation.
|
23 |
+
|
24 |
+
optional arguments:
|
25 |
+
-h, --help show this help message and exit
|
26 |
+
-f FOLDER, --folder FOLDER
|
27 |
+
folder path to test images.
|
28 |
+
-g GAMMA, --gamma GAMMA
|
29 |
+
the gamma correction parameter.
|
30 |
+
-l LAMBDA_, --lambda_ LAMBDA_
|
31 |
+
the weight for balancing the two terms in the illumination refinement optimization objective.
|
32 |
+
-ul, --lime Use the LIME method. By default, the DUAL method is used.
|
33 |
+
-s SIGMA, --sigma SIGMA
|
34 |
+
Spatial standard deviation for spatial affinity based Gaussian weights.
|
35 |
+
-bc BC parameter for controlling the influence of Mertens's contrast measure.
|
36 |
+
-bs BS parameter for controlling the influence of Mertens's saturation measure.
|
37 |
+
-be BE parameter for controlling the influence of Mertens's well exposedness measure.
|
38 |
+
-eps EPS constant to avoid computation instability.
|
39 |
+
```
|
40 |
+
|
41 |
+
### Example
|
42 |
+
```
|
43 |
+
python3 demo.py -f ./demo/ -l 0.15 -g 0.6
|
44 |
+
```
|
45 |
+
|
46 |
+
### Result
|
47 |
+
Low Light Image | Enhanced Image
|
48 |
+
:-------------------------:|:-------------------------:
|
49 |
+
 | 
|
50 |
+
|
51 |
+
### TODO
|
52 |
+
- [ ] Add a fourier based solver to speed up the inference
|
53 |
+
|
54 |
+
|
55 |
+
### :mortar_board: Citation
|
56 |
+
If you find this work useful in your research, please consider citing:
|
57 |
+
```
|
58 |
+
@misc{lowlightpython,
|
59 |
+
author = {Souhaib Attaiki},
|
60 |
+
title = {Low light Image Enhancement},
|
61 |
+
year = {2020},
|
62 |
+
publisher = {GitHub},
|
63 |
+
journal = {GitHub repository},
|
64 |
+
howpublished = {\url{https://github.com/pvnieo/Low-light-Image-Enhancement}},
|
65 |
+
}
|
66 |
+
|
67 |
+
```
|
demo.py
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# std
|
2 |
+
import argparse
|
3 |
+
from argparse import RawTextHelpFormatter
|
4 |
+
import glob
|
5 |
+
from os import makedirs
|
6 |
+
from os.path import join, exists, basename, splitext
|
7 |
+
# 3p
|
8 |
+
import cv2
|
9 |
+
from tqdm import tqdm
|
10 |
+
# project
|
11 |
+
from exposure_enhancement import enhance_image_exposure
|
12 |
+
|
13 |
+
|
14 |
+
def main(args):
|
15 |
+
# load images
|
16 |
+
imdir = args.folder
|
17 |
+
ext = ['png', 'jpg', 'bmp'] # Add image formats here
|
18 |
+
files = []
|
19 |
+
[files.extend(glob.glob(imdir + '*.' + e)) for e in ext]
|
20 |
+
images = [cv2.imread(file) for file in files]
|
21 |
+
|
22 |
+
# create save directory
|
23 |
+
directory = join(imdir, "enhanced")
|
24 |
+
if not exists(directory):
|
25 |
+
makedirs(directory)
|
26 |
+
|
27 |
+
# enhance images
|
28 |
+
for i, image in tqdm(enumerate(images), desc="Enhancing images"):
|
29 |
+
enhanced_image = enhance_image_exposure(image, args.gamma, args.lambda_, not args.lime,
|
30 |
+
sigma=args.sigma, bc=args.bc, bs=args.bs, be=args.be, eps=args.eps)
|
31 |
+
filename = basename(files[i])
|
32 |
+
name, ext = splitext(filename)
|
33 |
+
method = "LIME" if args.lime else "DUAL"
|
34 |
+
corrected_name = f"{name}_{method}_g{args.gamma}_l{args.lambda_}{ext}"
|
35 |
+
cv2.imwrite(join(directory, corrected_name), enhanced_image)
|
36 |
+
|
37 |
+
|
38 |
+
if __name__ == "__main__":
|
39 |
+
parser = argparse.ArgumentParser(
|
40 |
+
description="Python implementation of two low-light image enhancement techniques via illumination map estimation.",
|
41 |
+
formatter_class=RawTextHelpFormatter
|
42 |
+
)
|
43 |
+
parser.add_argument("-f", '--folder', default='./demo/', type=str,
|
44 |
+
help="folder path to test images.")
|
45 |
+
parser.add_argument("-g", '--gamma', default=0.6, type=float,
|
46 |
+
help="the gamma correction parameter.")
|
47 |
+
parser.add_argument("-l", '--lambda_', default=0.15, type=float,
|
48 |
+
help="the weight for balancing the two terms in the illumination refinement optimization objective.")
|
49 |
+
parser.add_argument("-ul", "--lime", action='store_true',
|
50 |
+
help="Use the LIME method. By default, the DUAL method is used.")
|
51 |
+
parser.add_argument("-s", '--sigma', default=3, type=int,
|
52 |
+
help="Spatial standard deviation for spatial affinity based Gaussian weights.")
|
53 |
+
parser.add_argument("-bc", default=1, type=float,
|
54 |
+
help="parameter for controlling the influence of Mertens's contrast measure.")
|
55 |
+
parser.add_argument("-bs", default=1, type=float,
|
56 |
+
help="parameter for controlling the influence of Mertens's saturation measure.")
|
57 |
+
parser.add_argument("-be", default=1, type=float,
|
58 |
+
help="parameter for controlling the influence of Mertens's well exposedness measure.")
|
59 |
+
parser.add_argument("-eps", default=1e-3, type=float,
|
60 |
+
help="constant to avoid computation instability.")
|
61 |
+
|
62 |
+
args = parser.parse_args()
|
63 |
+
main(args)
|
exposure_enhancement.py
ADDED
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# 3p
|
2 |
+
import numpy as np
|
3 |
+
import cv2
|
4 |
+
from scipy.spatial import distance
|
5 |
+
from scipy.ndimage.filters import convolve
|
6 |
+
from scipy.sparse import diags, csr_matrix
|
7 |
+
from scipy.sparse.linalg import spsolve
|
8 |
+
# project
|
9 |
+
from utils import get_sparse_neighbor
|
10 |
+
|
11 |
+
|
12 |
+
def create_spacial_affinity_kernel(spatial_sigma: float, size: int = 15):
|
13 |
+
"""Create a kernel (`size` * `size` matrix) that will be used to compute the he spatial affinity based Gaussian weights.
|
14 |
+
|
15 |
+
Arguments:
|
16 |
+
spatial_sigma {float} -- Spatial standard deviation.
|
17 |
+
|
18 |
+
Keyword Arguments:
|
19 |
+
size {int} -- size of the kernel. (default: {15})
|
20 |
+
|
21 |
+
Returns:
|
22 |
+
np.ndarray - `size` * `size` kernel
|
23 |
+
"""
|
24 |
+
kernel = np.zeros((size, size))
|
25 |
+
for i in range(size):
|
26 |
+
for j in range(size):
|
27 |
+
kernel[i, j] = np.exp(-0.5 * (distance.euclidean((i, j), (size // 2, size // 2)) ** 2) / (spatial_sigma ** 2))
|
28 |
+
|
29 |
+
return kernel
|
30 |
+
|
31 |
+
|
32 |
+
def compute_smoothness_weights(L: np.ndarray, x: int, kernel: np.ndarray, eps: float = 1e-3):
|
33 |
+
"""Compute the smoothness weights used in refining the illumination map optimization problem.
|
34 |
+
|
35 |
+
Arguments:
|
36 |
+
L {np.ndarray} -- the initial illumination map to be refined.
|
37 |
+
x {int} -- the direction of the weights. Can either be x=1 for horizontal or x=0 for vertical.
|
38 |
+
kernel {np.ndarray} -- spatial affinity matrix
|
39 |
+
|
40 |
+
Keyword Arguments:
|
41 |
+
eps {float} -- small constant to avoid computation instability. (default: {1e-3})
|
42 |
+
|
43 |
+
Returns:
|
44 |
+
np.ndarray - smoothness weights according to direction x. same dimension as `L`.
|
45 |
+
"""
|
46 |
+
Lp = cv2.Sobel(L, cv2.CV_64F, int(x == 1), int(x == 0), ksize=1)
|
47 |
+
T = convolve(np.ones_like(L), kernel, mode='constant')
|
48 |
+
T = T / (np.abs(convolve(Lp, kernel, mode='constant')) + eps)
|
49 |
+
return T / (np.abs(Lp) + eps)
|
50 |
+
|
51 |
+
|
52 |
+
def fuse_multi_exposure_images(im: np.ndarray, under_ex: np.ndarray, over_ex: np.ndarray,
|
53 |
+
bc: float = 1, bs: float = 1, be: float = 1):
|
54 |
+
"""perform the exposure fusion method used in the DUAL paper.
|
55 |
+
|
56 |
+
Arguments:
|
57 |
+
im {np.ndarray} -- input image to be enhanced.
|
58 |
+
under_ex {np.ndarray} -- under-exposure corrected image. same dimension as `im`.
|
59 |
+
over_ex {np.ndarray} -- over-exposure corrected image. same dimension as `im`.
|
60 |
+
|
61 |
+
Keyword Arguments:
|
62 |
+
bc {float} -- parameter for controlling the influence of Mertens's contrast measure. (default: {1})
|
63 |
+
bs {float} -- parameter for controlling the influence of Mertens's saturation measure. (default: {1})
|
64 |
+
be {float} -- parameter for controlling the influence of Mertens's well exposedness measure. (default: {1})
|
65 |
+
|
66 |
+
Returns:
|
67 |
+
np.ndarray -- the fused image. same dimension as `im`.
|
68 |
+
"""
|
69 |
+
merge_mertens = cv2.createMergeMertens(bc, bs, be)
|
70 |
+
images = [np.clip(x * 255, 0, 255).astype("uint8") for x in [im, under_ex, over_ex]]
|
71 |
+
fused_images = merge_mertens.process(images)
|
72 |
+
return fused_images
|
73 |
+
|
74 |
+
|
75 |
+
def refine_illumination_map_linear(L: np.ndarray, gamma: float, lambda_: float, kernel: np.ndarray, eps: float = 1e-3):
|
76 |
+
"""Refine the illumination map based on the optimization problem described in the two papers.
|
77 |
+
This function use the sped-up solver presented in the LIME paper.
|
78 |
+
|
79 |
+
Arguments:
|
80 |
+
L {np.ndarray} -- the illumination map to be refined.
|
81 |
+
gamma {float} -- gamma correction factor.
|
82 |
+
lambda_ {float} -- coefficient to balance the terms in the optimization problem.
|
83 |
+
kernel {np.ndarray} -- spatial affinity matrix.
|
84 |
+
|
85 |
+
Keyword Arguments:
|
86 |
+
eps {float} -- small constant to avoid computation instability (default: {1e-3}).
|
87 |
+
|
88 |
+
Returns:
|
89 |
+
np.ndarray -- refined illumination map. same shape as `L`.
|
90 |
+
"""
|
91 |
+
# compute smoothness weights
|
92 |
+
wx = compute_smoothness_weights(L, x=1, kernel=kernel, eps=eps)
|
93 |
+
wy = compute_smoothness_weights(L, x=0, kernel=kernel, eps=eps)
|
94 |
+
|
95 |
+
n, m = L.shape
|
96 |
+
L_1d = L.copy().flatten()
|
97 |
+
|
98 |
+
# compute the five-point spatially inhomogeneous Laplacian matrix
|
99 |
+
row, column, data = [], [], []
|
100 |
+
for p in range(n * m):
|
101 |
+
diag = 0
|
102 |
+
for q, (k, l, x) in get_sparse_neighbor(p, n, m).items():
|
103 |
+
weight = wx[k, l] if x else wy[k, l]
|
104 |
+
row.append(p)
|
105 |
+
column.append(q)
|
106 |
+
data.append(-weight)
|
107 |
+
diag += weight
|
108 |
+
row.append(p)
|
109 |
+
column.append(p)
|
110 |
+
data.append(diag)
|
111 |
+
F = csr_matrix((data, (row, column)), shape=(n * m, n * m))
|
112 |
+
|
113 |
+
# solve the linear system
|
114 |
+
Id = diags([np.ones(n * m)], [0])
|
115 |
+
A = Id + lambda_ * F
|
116 |
+
L_refined = spsolve(csr_matrix(A), L_1d, permc_spec=None, use_umfpack=True).reshape((n, m))
|
117 |
+
|
118 |
+
# gamma correction
|
119 |
+
L_refined = np.clip(L_refined, eps, 1) ** gamma
|
120 |
+
|
121 |
+
return L_refined
|
122 |
+
|
123 |
+
|
124 |
+
def correct_underexposure(im: np.ndarray, gamma: float, lambda_: float, kernel: np.ndarray, eps: float = 1e-3):
|
125 |
+
"""correct underexposudness using the retinex based algorithm presented in DUAL and LIME paper.
|
126 |
+
|
127 |
+
Arguments:
|
128 |
+
im {np.ndarray} -- input image to be corrected.
|
129 |
+
gamma {float} -- gamma correction factor.
|
130 |
+
lambda_ {float} -- coefficient to balance the terms in the optimization problem.
|
131 |
+
kernel {np.ndarray} -- spatial affinity matrix.
|
132 |
+
|
133 |
+
Keyword Arguments:
|
134 |
+
eps {float} -- small constant to avoid computation instability (default: {1e-3})
|
135 |
+
|
136 |
+
Returns:
|
137 |
+
np.ndarray -- image underexposudness corrected. same shape as `im`.
|
138 |
+
"""
|
139 |
+
|
140 |
+
# first estimation of the illumination map
|
141 |
+
L = np.max(im, axis=-1)
|
142 |
+
# illumination refinement
|
143 |
+
L_refined = refine_illumination_map_linear(L, gamma, lambda_, kernel, eps)
|
144 |
+
|
145 |
+
# correct image underexposure
|
146 |
+
L_refined_3d = np.repeat(L_refined[..., None], 3, axis=-1)
|
147 |
+
im_corrected = im / L_refined_3d
|
148 |
+
return im_corrected
|
149 |
+
|
150 |
+
# TODO: resize image if too large, optimization take too much time
|
151 |
+
|
152 |
+
|
153 |
+
def enhance_image_exposure(im: np.ndarray, gamma: float, lambda_: float, dual: bool = True, sigma: int = 3,
|
154 |
+
bc: float = 1, bs: float = 1, be: float = 1, eps: float = 1e-3):
|
155 |
+
"""Enhance input image, using either DUAL method, or LIME method. For more info, please see original papers.
|
156 |
+
|
157 |
+
Arguments:
|
158 |
+
im {np.ndarray} -- input image to be corrected.
|
159 |
+
gamma {float} -- gamma correction factor.
|
160 |
+
lambda_ {float} -- coefficient to balance the terms in the optimization problem (in DUAL and LIME).
|
161 |
+
|
162 |
+
Keyword Arguments:
|
163 |
+
dual {bool} -- boolean variable to indicate enhancement method to be used (either DUAL or LIME) (default: {True})
|
164 |
+
sigma {int} -- Spatial standard deviation for spatial affinity based Gaussian weights. (default: {3})
|
165 |
+
bc {float} -- parameter for controlling the influence of Mertens's contrast measure. (default: {1})
|
166 |
+
bs {float} -- parameter for controlling the influence of Mertens's saturation measure. (default: {1})
|
167 |
+
be {float} -- parameter for controlling the influence of Mertens's well exposedness measure. (default: {1})
|
168 |
+
eps {float} -- small constant to avoid computation instability (default: {1e-3})
|
169 |
+
|
170 |
+
Returns:
|
171 |
+
np.ndarray -- image exposure enhanced. same shape as `im`.
|
172 |
+
"""
|
173 |
+
# create spacial affinity kernel
|
174 |
+
kernel = create_spacial_affinity_kernel(sigma)
|
175 |
+
|
176 |
+
# correct underexposudness
|
177 |
+
im_normalized = im.astype(float) / 255.
|
178 |
+
under_corrected = correct_underexposure(im_normalized, gamma, lambda_, kernel, eps)
|
179 |
+
|
180 |
+
if dual:
|
181 |
+
# correct overexposure and merge if DUAL method is selected
|
182 |
+
inv_im_normalized = 1 - im_normalized
|
183 |
+
over_corrected = 1 - correct_underexposure(inv_im_normalized, gamma, lambda_, kernel, eps)
|
184 |
+
# fuse images
|
185 |
+
im_corrected = fuse_multi_exposure_images(im_normalized, under_corrected, over_corrected, bc, bs, be)
|
186 |
+
else:
|
187 |
+
im_corrected = under_corrected
|
188 |
+
|
189 |
+
# convert to 8 bits and returns
|
190 |
+
return np.clip(im_corrected * 255, 0, 255).astype("uint8")
|
requirements.txt
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
numpy
|
2 |
+
scipy
|
3 |
+
opencv-python
|
4 |
+
tqdm
|
utils.py
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
|
3 |
+
def get_sparse_neighbor(p: int, n: int, m: int):
|
4 |
+
"""Returns a dictionnary, where the keys are index of 4-neighbor of `p` in the sparse matrix,
|
5 |
+
and values are tuples (i, j, x), where `i`, `j` are index of neighbor in the normal matrix,
|
6 |
+
and x is the direction of neighbor.
|
7 |
+
|
8 |
+
Arguments:
|
9 |
+
p {int} -- index in the sparse matrix.
|
10 |
+
n {int} -- number of rows in the original matrix (non sparse).
|
11 |
+
m {int} -- number of columns in the original matrix.
|
12 |
+
|
13 |
+
Returns:
|
14 |
+
dict -- dictionnary containing indices of 4-neighbors of `p`.
|
15 |
+
"""
|
16 |
+
i, j = p // m, p % m
|
17 |
+
d = {}
|
18 |
+
if i - 1 >= 0:
|
19 |
+
d[(i - 1) * m + j] = (i - 1, j, 0)
|
20 |
+
if i + 1 < n:
|
21 |
+
d[(i + 1) * m + j] = (i + 1, j, 0)
|
22 |
+
if j - 1 >= 0:
|
23 |
+
d[i * m + j - 1] = (i, j - 1, 1)
|
24 |
+
if j + 1 < m:
|
25 |
+
d[i * m + j + 1] = (i, j + 1, 1)
|
26 |
+
return d
|