├── .gitignore
├── .idea
└── vcs.xml
├── LICENSE
├── Pyramid_ref.pdf
├── README.md
├── exposure_fusion.pdf
├── image.py
├── image_set
├── jpeg
│ ├── flower.jpeg
│ ├── t_0_1
│ ├── t_1_1
│ ├── t_2_1
│ ├── t_3_1
│ ├── t_4_1
│ ├── t_5_1
│ ├── t_6_1
│ ├── t_7_1
│ ├── t_8_1
│ └── t_9_1
├── mask
│ ├── mask_mean.jpg
│ ├── mask_over.jpg
│ └── mask_under.jpg
└── raw
│ ├── t_0_0
│ ├── t_1_0
│ ├── t_2_0
│ ├── t_3_0
│ ├── t_4_0
│ ├── t_5_0
│ ├── t_6_0
│ ├── t_7_0
│ ├── t_8_0
│ └── t_9_0
├── laplacianfusion.py
├── list_images.txt
├── list_jpeg.txt
├── list_raw.txt
├── main.py
├── naivefusion.py
├── res
├── lap_mask_6.jpg
└── mask_naive.jpg
├── result_jpeg_exposure.png
├── result_jpeg_exposure_mask.jpg
├── result_jpeg_naive.png
├── result_jpeg_naive_mask.png
└── utils.py
/.gitignore:
--------------------------------------------------------------------------------
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 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Django stuff:
53 | *.log
54 | local_settings.py
55 |
56 | # Flask stuff:
57 | instance/
58 | .webassets-cache
59 |
60 | # Scrapy stuff:
61 | .scrapy
62 |
63 | # Sphinx documentation
64 | docs/_build/
65 |
66 | # PyBuilder
67 | target/
68 |
69 | # IPython Notebook
70 | .ipynb_checkpoints
71 |
72 | # pyenv
73 | .python-version
74 |
75 | # celery beat schedule file
76 | celerybeat-schedule
77 |
78 | # dotenv
79 | .env
80 |
81 | # virtualenv
82 | venv/
83 | ENV/
84 |
85 | # Spyder project settings
86 | .spyderproject
87 | .spyderworkspace
88 |
89 | # Rope project settings
90 | .ropeproject
91 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Rachid Riad
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Pyramid_ref.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/Pyramid_ref.pdf
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Exposure Fusion
2 | Exposure Fusion Technique
3 | This code implements the Exposure Fusion, a Low-Dynamic-Range technique. It blends multi-exposure sequence of photo into a high-quality image, and is guided by measurement as Contrast, Saturation and Well-exposedness.
4 |
5 | You can find the Research paper where the algorithm comes from [here](https://github.com/Rachine/ExposureFusion/blob/master/exposure_fusion.pdf) [1]
6 |
7 | This code used python 2.7 and the packages Numpy and SciPy.
8 |
9 | Here some multi-exposure sequence of photo used:
10 |
11 |
12 |
13 | Here the result with the Naive implementation:
14 |
15 |
16 |
17 | Here the result with the Exposure Fusion algorithm with the Laplacian Pyramid:
18 |
19 |
20 |
21 | ### Usage
22 |
23 | To test the code with your own images, put them in a new folder under the folder _image\_set_ and edit the text file _list\_images.txt_ by putting the names of your images.
24 | Then run _main.py_ with the desired arguments. You can see all the possible arguments by running: `python main.py -h`
25 |
26 | ### Authors
27 | - Chaïmaa Kadaoui
28 | - [Rachid Riad](https://rachine.github.io/)
29 |
30 |
31 | ### References
32 | [1]: Exposure Fusion: A Simple and Practical Alternative to High Dynamic Range Photography
33 | Mertens, T.; Kautz, J.; Van Reeth, F.
34 |
35 | [2]: P. Burt and T. Adelson. The Laplacian Pyramid as a Com-
36 | pact Image Code. IEEE Transactions on Communication,
37 | COM-31:532–540, 1983.
38 |
--------------------------------------------------------------------------------
/exposure_fusion.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/exposure_fusion.pdf
--------------------------------------------------------------------------------
/image.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import os.path
4 | import numpy as np
5 | import matplotlib.pyplot as plt
6 | from scipy import ndimage, misc
7 | import pdb
8 |
9 |
10 | def weightedAverage(pixel):
11 | return 0.299 * pixel[0] + 0.587 * pixel[1] + 0.114 * pixel[2]
12 |
13 |
14 | def exponential_euclidean(canal, sigma):
15 | return np.exp(-(canal - 0.5)**2 / (2 * sigma**2))
16 |
17 |
18 | def show(color_array):
19 | """ Function to show image"""
20 | plt.imshow(color_array)
21 | plt.show()
22 | plt.axis('off')
23 |
24 |
25 | def show_gray(gray_array):
26 | """ Function to show grayscale image"""
27 | fig = plt.figure()
28 | plt.imshow(gray_array, cmap=plt.cm.Greys_r)
29 | plt.show()
30 | plt.axis('off')
31 |
32 |
33 | class Image(object):
34 | """Class for Image"""
35 |
36 | def __init__(self, fmt, path, crop=False, n=0):
37 | self.path = os.path.join("image_set", fmt, str(path))
38 | self.fmt = fmt
39 | self.array = misc.imread(self.path)
40 | self.array = self.array.astype(np.float32) / 255
41 | if crop:
42 | self.crop_image(n)
43 | self.shape = self.array.shape
44 |
45 | def crop_image(self, n):
46 | resolution = 2**n
47 | (height, width, _) = self.array.shape
48 | (max_height, max_width) = (resolution * (height // resolution),
49 | resolution * (width // resolution))
50 | (begin_height, begin_width) = ((height - max_height) / 2,
51 | (width - max_width) / 2)
52 | self.array = self.array[begin_height:max_height + begin_height,
53 | begin_width:max_width + begin_width]
54 |
55 | @property
56 | def grayScale(self):
57 | """Grayscale image"""
58 | rgb = self.array
59 | self._grayScale = np.dot(rgb[..., :3], [0.299, 0.587, 0.114])
60 | return self._grayScale
61 |
62 | def saturation(self):
63 | """Function that returns the Saturation map"""
64 | red_canal = self.array[:, :, 0]
65 | green_canal = self.array[:, :, 1]
66 | blue_canal = self.array[:, :, 2]
67 | mean = (red_canal + green_canal + blue_canal) / 3.0
68 | saturation = np.sqrt(((red_canal - mean)**2 + (green_canal - mean)**2 +
69 | (blue_canal - mean)**2) / 3)
70 | return saturation
71 |
72 | def contrast(self):
73 | """Function that returns the Constrast numpy array"""
74 | grey = self.grayScale
75 | contrast = np.zeros((self.shape[0], self.shape[1]))
76 | grey_extended = np.zeros((self.shape[0] + 2, self.shape[1] + 2))
77 | grey_extended[1:self.shape[0] + 1, 1:self.shape[1] + 1] = grey
78 | # kernel = np.array([[ -1, -1, -1 ],
79 | # [ -1, 8, -1 ],
80 | # [ -1, -1, -1 ]])
81 | kernel = np.array([[0, 1, 0], [1, -4, 1], [0, 1, 0]])
82 | for row in range(self.shape[0]):
83 | for col in range(self.shape[1]):
84 | contrast[row][col] = np.abs(
85 | (kernel *
86 | grey_extended[row:(row + 3), col:(col + 3)]).sum())
87 | contrast = (contrast - np.min(contrast))
88 | contrast = contrast / np.max(contrast)
89 | return contrast
90 |
91 | def sobel(self):
92 | """Function that returns the Constrast numpy array"""
93 | grey = self.grayScale
94 | sobel_h = np.zeros((self.shape[0], self.shape[1]))
95 | sobel_v = np.zeros((self.shape[0], self.shape[1]))
96 | grey_extended = np.zeros((self.shape[0] + 2, self.shape[1] + 2))
97 | grey_extended[1:self.shape[0] + 1, 1:self.shape[1] + 1] = grey
98 | kernel1 = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]])
99 | kernel2 = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, -1]])
100 | for row in range(self.shape[0]):
101 | for col in range(self.shape[1]):
102 | sobel_h[row][col] = np.abs(
103 | (kernel1 *
104 | grey_extended[row:(row + 3), col:(col + 3)]).sum())
105 | sobel_v[row][col] = np.abs(
106 | (kernel2 *
107 | grey_extended[row:(row + 3), col:(col + 3)]).sum())
108 | return sobel_h, sobel_v
109 |
110 | def exposedness(self):
111 | """Function that returns the Well-Exposedness map"""
112 | red_canal = self.array[:, :, 0]
113 | green_canal = self.array[:, :, 1]
114 | blue_canal = self.array[:, :, 2]
115 | sigma = 0.2
116 | red_exp = exponential_euclidean(red_canal, sigma)
117 | green_exp = exponential_euclidean(green_canal, sigma)
118 | blue_exp = exponential_euclidean(blue_canal, sigma)
119 | return red_exp * green_exp * blue_exp
120 |
121 |
122 | if __name__ == "__main__":
123 | im = Image("jpeg", "grandcanal_mean.jpg")
124 | sat = im.contrast()
125 | show_gray(sat)
126 |
--------------------------------------------------------------------------------
/image_set/jpeg/flower.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/jpeg/flower.jpeg
--------------------------------------------------------------------------------
/image_set/jpeg/t_0_1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/jpeg/t_0_1
--------------------------------------------------------------------------------
/image_set/jpeg/t_1_1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/jpeg/t_1_1
--------------------------------------------------------------------------------
/image_set/jpeg/t_2_1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/jpeg/t_2_1
--------------------------------------------------------------------------------
/image_set/jpeg/t_3_1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/jpeg/t_3_1
--------------------------------------------------------------------------------
/image_set/jpeg/t_4_1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/jpeg/t_4_1
--------------------------------------------------------------------------------
/image_set/jpeg/t_5_1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/jpeg/t_5_1
--------------------------------------------------------------------------------
/image_set/jpeg/t_6_1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/jpeg/t_6_1
--------------------------------------------------------------------------------
/image_set/jpeg/t_7_1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/jpeg/t_7_1
--------------------------------------------------------------------------------
/image_set/jpeg/t_8_1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/jpeg/t_8_1
--------------------------------------------------------------------------------
/image_set/jpeg/t_9_1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/jpeg/t_9_1
--------------------------------------------------------------------------------
/image_set/mask/mask_mean.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/mask/mask_mean.jpg
--------------------------------------------------------------------------------
/image_set/mask/mask_over.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/mask/mask_over.jpg
--------------------------------------------------------------------------------
/image_set/mask/mask_under.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/mask/mask_under.jpg
--------------------------------------------------------------------------------
/image_set/raw/t_0_0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/raw/t_0_0
--------------------------------------------------------------------------------
/image_set/raw/t_1_0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/raw/t_1_0
--------------------------------------------------------------------------------
/image_set/raw/t_2_0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/raw/t_2_0
--------------------------------------------------------------------------------
/image_set/raw/t_3_0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/raw/t_3_0
--------------------------------------------------------------------------------
/image_set/raw/t_4_0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/raw/t_4_0
--------------------------------------------------------------------------------
/image_set/raw/t_5_0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/raw/t_5_0
--------------------------------------------------------------------------------
/image_set/raw/t_6_0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/raw/t_6_0
--------------------------------------------------------------------------------
/image_set/raw/t_7_0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/raw/t_7_0
--------------------------------------------------------------------------------
/image_set/raw/t_8_0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/raw/t_8_0
--------------------------------------------------------------------------------
/image_set/raw/t_9_0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/image_set/raw/t_9_0
--------------------------------------------------------------------------------
/laplacianfusion.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on Sat Dec 10 16:26:15 2016
4 |
5 | @author: Rachid & Chaima
6 | """
7 |
8 | import numpy as np
9 | import matplotlib.pyplot as plt
10 | from scipy import ndimage, misc
11 | import image
12 | import utils
13 | import pdb
14 |
15 | #
16 | #def div0( a, b ):
17 | # """ ignore / 0, div0( [-1, 0, 1], 0 ) -> [0, 0, 0] """
18 | # with np.errstate(divide='ignore', invalid='ignore'):
19 | # c = np.true_divide( a, b )
20 | # c[ ~ np.isfinite( c )] = 0
21 | # return c
22 |
23 |
24 | class LaplacianMap(object):
25 | """Class for weights attribution with Laplacian Fusion"""
26 |
27 | def __init__(self, fmt, names, n=3):
28 | """names is a liste of names, fmt is the format of the images"""
29 | self.images = []
30 | for name in names:
31 | self.images.append(image.Image(fmt, name, crop=True, n=n))
32 | self.shape = self.images[0].shape
33 | self.num_images = len(self.images)
34 | self.height_pyr = n
35 |
36 | def get_weights_map(self, w_c, w_s, w_e):
37 | """Return the normalized Weight map"""
38 | self.weights = []
39 | sums = np.zeros((self.shape[0], self.shape[1]))
40 | for image_name in self.images:
41 | contrast = image_name.contrast()
42 | saturation = image_name.saturation()
43 | exposedness = image_name.exposedness()
44 | weight = (contrast**w_c) * (saturation**w_s) * (exposedness**
45 | w_e) + 1e-12
46 | self.weights.append(weight)
47 | sums = sums + weight
48 | for index in range(self.num_images):
49 | self.weights[index] = self.weights[index] / sums
50 | return self.weights
51 |
52 | def get_gaussian_pyramid(self, image, n):
53 | """Return the Gaussian Pyramid of an image"""
54 | gaussian_pyramid_floors = [image]
55 | for floor in range(1, n):
56 | gaussian_pyramid_floors.append(
57 | utils.Reduce(gaussian_pyramid_floors[-1], 1))
58 | return gaussian_pyramid_floors
59 |
60 | def get_gaussian_pyramid_weights(self):
61 | """Return the Gaussian Pyramid of the Weight map of all images"""
62 | self.weights_pyramid = []
63 | for index in range(self.num_images):
64 | self.weights_pyramid.append(
65 | self.get_gaussian_pyramid(self.weights[index],
66 | self.height_pyr))
67 | return self.weights_pyramid
68 |
69 | def get_laplacian_pyramid(self, image, n):
70 | """Return the Laplacian Pyramid of an image"""
71 | gaussian_pyramid_floors = self.get_gaussian_pyramid(image, n)
72 | laplacian_pyramid_floors = [gaussian_pyramid_floors[-1]]
73 | for floor in range(n - 2, -1, -1):
74 | new_floor = gaussian_pyramid_floors[floor] - utils.Expand(
75 | gaussian_pyramid_floors[floor + 1], 1)
76 | laplacian_pyramid_floors = [new_floor] + laplacian_pyramid_floors
77 | return laplacian_pyramid_floors
78 |
79 | def get_laplacian_pyramid_images(self):
80 | """Return all the Laplacian pyramid for all images"""
81 | self.laplacian_pyramid = []
82 | for index in range(self.num_images):
83 | self.laplacian_pyramid.append(
84 | self.get_laplacian_pyramid(self.images[index].array,
85 | self.height_pyr))
86 | return self.laplacian_pyramid
87 |
88 | def result_exposure(self, w_c=1, w_s=1, w_e=1):
89 | "Return the Exposure Fusion image with Laplacian/Gaussian Fusion method"
90 | print "weights"
91 | self.get_weights_map(w_c, w_s, w_e)
92 | print "gaussian pyramid"
93 | self.get_gaussian_pyramid_weights()
94 | print "laplacian pyramid"
95 | self.get_laplacian_pyramid_images()
96 | result_pyramid = []
97 | for floor in range(self.height_pyr):
98 | print 'floor ', floor
99 | result_floor = np.zeros(self.laplacian_pyramid[0][floor].shape)
100 | for index in range(self.num_images):
101 | print 'image ', index
102 | for canal in range(3):
103 | result_floor[:, :,
104 | canal] += self.laplacian_pyramid[index][floor][:, :,
105 | canal] * self.weights_pyramid[index][floor]
106 | result_pyramid.append(result_floor)
107 | # Get the image from the Laplacian pyramid
108 | self.result_image = result_pyramid[-1]
109 | for floor in range(self.height_pyr - 2, -1, -1):
110 | print 'floor ', floor
111 | self.result_image = result_pyramid[floor] + utils.Expand(
112 | self.result_image, 1)
113 | self.result_image[self.result_image < 0] = 0
114 | self.result_image[self.result_image > 1] = 1
115 | return self.result_image
116 |
117 |
118 | if __name__ == "__main__":
119 | names = [line.rstrip('\n') for line in open('list_images.txt')]
120 | lap = LaplacianMap('arno', names, n=6)
121 | res = lap.result_exposure(1, 1, 1)
122 | image.show(res)
123 | misc.imsave("res/arno_3.jpg", res)
124 |
--------------------------------------------------------------------------------
/list_images.txt:
--------------------------------------------------------------------------------
1 | mask_under.jpg
2 | mask_over.jpg
3 | mask_mean.jpg
--------------------------------------------------------------------------------
/list_jpeg.txt:
--------------------------------------------------------------------------------
1 | t_0_1
2 | t_1_1
3 | t_2_1
4 | t_3_1
5 | t_4_1
6 | t_5_1
7 | t_6_1
8 | t_7_1
9 | t_8_1
10 | t_9_1
11 |
--------------------------------------------------------------------------------
/list_raw.txt:
--------------------------------------------------------------------------------
1 | t_0_0
2 | t_1_0
3 | t_2_0
4 | t_3_0
5 | t_4_0
6 | t_5_0
7 | t_6_0
8 | t_7_0
9 | t_8_0
10 | t_9_0
11 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | import argparse
4 | import image
5 | import naivefusion
6 | import laplacianfusion
7 |
8 | # Loading the arguments
9 |
10 | parser = argparse.ArgumentParser(
11 | formatter_class=argparse.ArgumentDefaultsHelpFormatter)
12 | parser.add_argument(
13 | '-l',
14 | '--list',
15 | dest='names',
16 | type=str,
17 | default='list_images.txt',
18 | help='The text file which contains the names of the images')
19 | parser.add_argument(
20 | '-f',
21 | '--folder',
22 | dest='folder',
23 | type=str,
24 | required=True,
25 | help='The folder containing the images')
26 | parser.add_argument(
27 | '-hp',
28 | '--heightpyr',
29 | dest='height_pyr',
30 | type=int,
31 | default=6,
32 | help='The height of the Laplacian pyramid')
33 | parser.add_argument(
34 | '-wc',
35 | dest='w_c',
36 | type=float,
37 | default=1.0,
38 | help='Exponent of the contrast')
39 | parser.add_argument(
40 | '-ws',
41 | dest='w_s',
42 | type=float,
43 | default=1.0,
44 | help='Exponent of the saturation')
45 | parser.add_argument(
46 | '-we',
47 | dest='w_e',
48 | type=float,
49 | default=1.0,
50 | help='Exponent of the exposedness')
51 | args = parser.parse_args()
52 | params = vars(args) # convert to ordinary dict
53 |
54 | names = [line.rstrip('\n') for line in open(params['names'])]
55 | folder = params['folder']
56 | height_pyr = params['height_pyr']
57 | w_c = params['w_c']
58 | w_s = params['w_s']
59 | w_e = params['w_e']
60 |
61 | # Naive Fusion
62 |
63 | W = naivefusion.WeightsMap(folder, names)
64 | res_naive = W.result_exposure(w_c, w_s, w_e)
65 | image.show(res_naive)
66 |
67 | # Laplacian Fusion
68 |
69 | lap = laplacianfusion.LaplacianMap(folder, names, n=height_pyr)
70 | res_lap = lap.result_exposure(w_c, w_s, w_e)
71 | image.show(res_lap)
72 |
--------------------------------------------------------------------------------
/naivefusion.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import numpy as np
4 | import matplotlib.pyplot as plt
5 | from scipy import ndimage, misc
6 | import image
7 |
8 | #def div0( a, b ):
9 | # """ ignore / 0, div0( [-1, 0, 1], 0 ) -> [0, 0, 0] """
10 | # with np.errstate(divide='ignore', invalid='ignore'):
11 | # c = np.true_divide( a, b )
12 | # c[ ~ np.isfinite( c )] = 0
13 | # return c
14 |
15 |
16 | class WeightsMap(object):
17 | """Class for weights attribution for all images"""
18 |
19 | def __init__(self, fmt, names):
20 | """names is a liste of names, fmt is the format of the images"""
21 | self.images = []
22 | for name in names:
23 | self.images.append(image.Image(fmt, name))
24 | self.shape = self.images[0].shape
25 | self.num_images = len(self.images)
26 |
27 | def get_weights_map(self, w_c, w_s, w_e):
28 | """Return the normalized Weight map"""
29 | self.weights = []
30 | sums = np.zeros((self.shape[0], self.shape[1]))
31 | for image_name in self.images:
32 | contrast = image_name.contrast()
33 | saturation = image_name.saturation()
34 | exposedness = image_name.exposedness()
35 | weight = (contrast**w_c)*(saturation**w_s)*(exposedness**w_e) + 1e-12
36 | # weight = ndimage.gaussian_filter(weight, sigma=(3, 3), order=0)
37 | self.weights.append(weight)
38 | sums = sums + weight
39 | for index in range(self.num_images):
40 | self.weights[index] = self.weights[index]/sums
41 | return self.weights
42 |
43 | def result_exposure(self, w_c=1, w_s=1, w_e=1):
44 | "Return the Exposure Fusion image with Naive method"
45 | self.get_weights_map(w_c, w_s, w_e)
46 | self.result_image = np.zeros(self.shape)
47 | for canal in range(3):
48 | for index in range(self.num_images):
49 | self.result_image[:, :, canal] += self.weights[index] * self.images[index].array[:, :, canal]
50 | self.result_image[self.result_image < 0] = 0
51 | self.result_image[self.result_image > 1] = 1
52 | return self.result_image
53 |
54 | if __name__ == "__main__":
55 | names = [line.rstrip('\n') for line in open('list_jpeg_test.txt')]
56 | W = WeightsMap("mask", names)
57 | im = W.result_exposure(1, 1, 1)
58 | image.show(im)
59 | misc.imsave("res/mask_naive.jpg", im)
60 |
--------------------------------------------------------------------------------
/res/lap_mask_6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/res/lap_mask_6.jpg
--------------------------------------------------------------------------------
/res/mask_naive.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/res/mask_naive.jpg
--------------------------------------------------------------------------------
/result_jpeg_exposure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/result_jpeg_exposure.png
--------------------------------------------------------------------------------
/result_jpeg_exposure_mask.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/result_jpeg_exposure_mask.jpg
--------------------------------------------------------------------------------
/result_jpeg_naive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/result_jpeg_naive.png
--------------------------------------------------------------------------------
/result_jpeg_naive_mask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rachine/ExposureFusion/cc830224331270cfe545c68d27c8abac013762af/result_jpeg_naive_mask.png
--------------------------------------------------------------------------------
/utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on Sat Dec 10 16:32:39 2016
4 |
5 | @author: Rachid & Chaima
6 | """
7 | import pdb
8 |
9 | import numpy as np
10 | import scipy.signal as sig
11 |
12 |
13 | def kernel_1D(n, a=0.6):
14 | """Kernel function in 1 dimension"""
15 | kernel = [.0625, .25, .375, .25, .0625]
16 | # if n == 0:
17 | # return a
18 | # elif n == -1 or n == 1:
19 | # return 1./4
20 | # elif n == -2 or n == 2:
21 | # return 1./4 - float(a)/2
22 | # else:
23 | # return 0
24 | return kernel[n]
25 |
26 |
27 | def kernel_old(m, n, a=0.6):
28 | """Returns the value of the kernel at position w and n"""
29 | return kernel_1D(m, a)*kernel_1D(n, a)
30 |
31 |
32 | def get_kernel(a=0.6):
33 | kernel = np.zeros((5,5))
34 | for i in range(5):
35 | for j in range(5):
36 | kernel[i, j] = kernel_1D(i, a)*kernel_1D(j, a)
37 | return kernel
38 |
39 |
40 | def Reduce_old(image, n, a=0.6):
41 | """Reduce function for Pyramids"""
42 | try:
43 | if n == 0:
44 | return image
45 | else:
46 | image = Reduce(image, n-1, a)
47 | [R, C] = [image.shape[0], image.shape[1]]
48 | image_extended = np.zeros((R+4, C+4))
49 | image_extended[2:R+2, 2:C+2] = image
50 | try:
51 | image_reduced = np.zeros((R/2, C/2))
52 | except Exception as e:
53 | print "Dimension Error"
54 | print e
55 |
56 | for i in range(R/2):
57 | for j in range(C/2):
58 | for m in range(-2, 3):
59 | for n in range(-2, 3):
60 | image_reduced[i, j] += kernel_old(m, n) * image_extended[2 * i + m + 2, 2 * j + n + 2]
61 | return image_reduced
62 | except Exception as e:
63 | print "Dimension Error"
64 | print e
65 |
66 |
67 | def weighted_sum(image, i, j, a):
68 | weighted_sum = 0
69 | for m in range(-2, 3):
70 | for n in range(-2, 3):
71 | pixel_i = float(i - m) / 2
72 | pixel_j = float(j - n) / 2
73 | if pixel_i.is_integer() and pixel_j.is_integer():
74 | weighted_sum += kernel_old(m, n, a) * image[pixel_i, pixel_j]
75 | return 4 * weighted_sum
76 |
77 |
78 | def Expand_old(image, n, a=0.6):
79 | """Expand function for Pyramids"""
80 | try:
81 | if n == 0:
82 | return image
83 | else:
84 | image = Expand(image, n-1, a)
85 | [R, C] = image.shape
86 | image_extended = np.zeros((R+4, C+4))
87 | image_extended[2:R+2, 2:C+2] = image
88 | new_floor = np.zeros((2 * R, 2 * C))
89 | for i in range(2 * R):
90 | for j in range(2 * C):
91 | new_floor[i, j] = weighted_sum(image_extended, i+2, j+2, a)
92 | new_floor = (new_floor - np.min(new_floor))
93 | new_floor = new_floor / np.max(new_floor)
94 | return new_floor
95 | except Exception as e:
96 | print "Dimension error"
97 | print e
98 |
99 |
100 | def Reduce1(image, a=0.6):
101 | kernel = get_kernel(a)
102 | shape = image.shape
103 | if len(shape) == 3:
104 | image_reduced = np.zeros((shape[0]/2, shape[1]/2, 3))
105 | for canal in range(3):
106 | canal_reduced = sig.convolve2d(image[:, :, canal], kernel, 'same')
107 | image_reduced[:, :, canal] = canal_reduced[::2, ::2]
108 | else:
109 | image_reduced = sig.convolve2d(image, kernel, 'same')[::2, ::2]
110 | return image_reduced
111 |
112 |
113 | def Reduce(image, n, a=0.6):
114 | """Reduce function for Pyramids"""
115 | try:
116 | if n == 0:
117 | return image
118 | else:
119 | image = Reduce(image, n-1, a)
120 | return Reduce1(image, a)
121 | except Exception as e:
122 | print "Dimension Error"
123 | print e
124 |
125 |
126 | def Expand1(image, a=0.6):
127 | kernel = get_kernel(a)
128 | shape = image.shape
129 | if len(shape) == 3:
130 | image_to_expand = np.zeros((2*shape[0], 2*shape[1], 3))
131 | image_expanded = np.zeros(image_to_expand.shape)
132 | for canal in range(3):
133 | image_to_expand[::2, ::2, canal] = image[:, :, canal]
134 | image_expanded[:, :, canal] = sig.convolve2d(image_to_expand[:, :, canal], 4*kernel, 'same')
135 | else:
136 | image_to_expand = np.zeros((2 * shape[0], 2 * shape[1]))
137 | image_to_expand[::2, ::2] = image
138 | image_expanded = sig.convolve2d(image_to_expand[:, :], 4*kernel, 'same')
139 | return image_expanded
140 |
141 |
142 | def Expand(image, n, a=0.6):
143 | """Expand function for Pyramids"""
144 | try:
145 | if n == 0:
146 | return image
147 | else:
148 | image = Expand(image, n-1, a)
149 | return Expand1(image, a)
150 | except Exception as e:
151 | print "Dimension error"
152 | print e
153 |
--------------------------------------------------------------------------------