Sanskar0632 commited on
Commit
cde2910
·
1 Parent(s): 2d08e8f

Upload 6 files

Browse files
Files changed (6) hide show
  1. .gitignore +137 -0
  2. README.md +67 -10
  3. demo.py +63 -0
  4. exposure_enhancement.py +190 -0
  5. requirements.txt +4 -0
  6. 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
- title: Imageenhancement2
3
- emoji: 🌖
4
- colorFrom: green
5
- colorTo: purple
6
- sdk: static
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ ![](demo/2.bmp) | ![](demo/enhanced/2_DUAL_g0.6_l0.15.bmp)
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