diff --git a/.gitignore b/.gitignore index 152f87f..52ad280 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ node_modules/ dist/ defogging.egg-info/ build/ +.cache/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..3c5d4f2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: python +python: + - "3.4" + - "3.5" + - "3.6" +# command to install dependencies +install: + - pip install -r requirements.txt + - pip install . +# command to run tests +script: pytest diff --git a/HISTORY.rst b/HISTORY.rst deleted file mode 100644 index 919fd99..0000000 --- a/HISTORY.rst +++ /dev/null @@ -1,10 +0,0 @@ -======= -History -======= - -* 0.1.6: Fix errors when download from PyPI. - -* 0.1.1: Update README. - -* 0.1.0: First release on PyPI. - diff --git a/README.rst b/README.rst index 80779f2..2658257 100644 --- a/README.rst +++ b/README.rst @@ -1,36 +1,76 @@ -==== +========= defogging -==== +========= .. image:: https://img.shields.io/pypi/v/defogging.svg :target: https://pypi.python.org/pypi/defogging/ -Implement Defogging with Python +.. image:: https://img.shields.io/travis/scloudyy/Defogging.svg + :target: https://travis-ci.org/scloudyy/Defogging + +Implement a robust and efficient defogging method with Python Features -------- -* Defogging single foggy image +* Defogging a single foggy image install ------- -Use pip to install - -.. code-block:: bash:: +Use ``pip`` to install: :: $ pip install defogging -usage +Or you can install from source: :: + + $ git clone git@github.com:scloudyy/Defogging.git + $ cd Defogging + $ python setup.py build + $ pip install . + +Usage ----- -Defogging a single foggy image +Use ``defogging`` through command line: :: + + $ defogging your_img.bmp + +the result will be saved as ``your_img_defogging.bmp`` + +And you can also use ``defogging`` in your own code: + +.. code-block:: python + + from defogging import Defog + + in_name = "foggy.bmp" + out_name = "defogged.bmp" + + df = Defog() + df.read_img(in_name) + df.defog() + df.save_img(out_name) + +you can directly input a foggy object in the form of ``numpy.array`` : + +.. code-block:: python + + df.read_array(your_array, range) + +where ``range`` indicates the value range of your array.The range has two options: +the first is ``1``, which means the value range of your array is [0,1], +and the second is ``255``, which means the value range is [0,255] + +If you want to process the defogged object further, you can also get defogged array: -.. code-block:: bash:: +.. code-block:: python - $ defogging your_img.bmp + dst = df.get_array(range) -the result will be saved as your_img_defogging.bmp +also, ``range`` indicates the value range of returned array, either ``1`` or ``255``. +If you choose ``1``, the range will lie in [0,1], and the type of it is ``float``. +Or if you choose ``255``, the range will be [0,255], and the type will be ``uint8``. diff --git a/defogging/__init__.py b/defogging/__init__.py index e69de29..532eed3 100644 --- a/defogging/__init__.py +++ b/defogging/__init__.py @@ -0,0 +1,75 @@ +from PIL import Image +import numpy as np +import sys + +from .defogging import defogging + + +class Defog(): + def __init__(self): + self.__foggy_name = None + self.__foggy_img = None + self.__foggy_src = None + self.__defogged = None + + def read_img(self, name): + """ + read a foggy image from hard disk + + :param name: the name of the foggy image + """ + self.__foggy_name = name + self.__foggy_img = Image.open(name) + self.__foggy_src = np.array(self.__foggy_img).astype(float) / 255 + + def read_array(self, array, range): + """ + read a foggy object from numpy.array + + :param array: a foggy object in numpy.array + :param range: the range of the input array, only in two value: 1 and 255 + 1 means array's range in [0,1], 255 means array's range in [0,255] + """ + if range == 1: + self.__foggy_src = array.astype(float) + self.__foggy_img = Image.fromarray(np.uint8(array * 255)) + elif range == 255: + self.__foggy_src = array.astype(float) / 255 + self.__foggy_img = Image.fromarray(np.uint8(array)) + + def defog(self): + self.__defogged = defogging(self.__foggy_src, self.__foggy_img) + + def get_array(self, range): + """ + return the defogged array + + :param range: the range of the defogged array, only in two value: 1 and 255 + 1 means array's range in [0,1], 255 means array's range in [0,255] + """ + if not isinstance(self.__defogged, np.ndarray): + return 0 + if range == 1: + return self.__defogged + elif range == 255: + return np.uint8(self.__defogged * 255) + + def save_img(self, name): + """ + save defogged image to the hard disk + + :param name: name of image + """ + defogged_img = Image.fromarray(np.uint8(self.__defogged * 255)) + defogged_img.save(name) + +def main(): + args = sys.argv + name = args[1] + df = Defog() + df.read_img(name) + df.defog() + df.save_img(name + "_defogged.bmp") + +if __name__ == '__main__': + main() diff --git a/defogging/core/__init__.py b/defogging/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/defogging/airlight.py b/defogging/core/airlight.py similarity index 97% rename from defogging/airlight.py rename to defogging/core/airlight.py index eed68aa..20cf2c5 100644 --- a/defogging/airlight.py +++ b/defogging/core/airlight.py @@ -3,7 +3,7 @@ from numpy import * -def cal_airlight(src, L, ratio): +def airlight(src, L, ratio): """ airlight: used to calculate values of airlight diff --git a/defogging/guidedfilter.py b/defogging/core/guidedfilter.py similarity index 97% rename from defogging/guidedfilter.py rename to defogging/core/guidedfilter.py index a450f0b..eda6935 100644 --- a/defogging/guidedfilter.py +++ b/defogging/core/guidedfilter.py @@ -4,7 +4,7 @@ from numpy.matlib import repmat -def cal_guidedfilter(src, I, r, eps): +def guidedfilter(src, I, r, eps): """ guidedfilter: O(1) time implementation of guided filter. diff --git a/defogging/core/patchshift.py b/defogging/core/patchshift.py new file mode 100644 index 0000000..2ecedff --- /dev/null +++ b/defogging/core/patchshift.py @@ -0,0 +1,26 @@ +from numpy import * + +from defogging.utils.minormaxfilter import min_or_max +from defogging.utils.padding import padding + + +def patchshift(trans, r, base): + """ + + :param trans: original transmission + :param r: radius + :param base: the shift base + :return: dst + """ + + min_base = min_or_max(base, r, "min") + max_base = min_or_max(base, r, "max") + dif = max_base - min_base + (hei, wid) = trans.shape[0:2] + + pad_dif = padding(dif, r) + for i in range(hei): + for j in range(wid): + b = pad_dif[i:i+2*r+1, j:j+2*r+1] + a = where(b == amax(b)) + return diff --git a/defogging/recover.py b/defogging/core/recover.py similarity index 95% rename from defogging/recover.py rename to defogging/core/recover.py index 1b09324..6a500d6 100644 --- a/defogging/recover.py +++ b/defogging/core/recover.py @@ -1,7 +1,7 @@ from numpy import * -def cal_recover(src, A, trans): +def recover(src, A, trans): """ J = (I(x) - A) / t(x) +A diff --git a/defogging/transmission.py b/defogging/core/transmission.py similarity index 63% rename from defogging/transmission.py rename to defogging/core/transmission.py index 637ed68..b421c65 100644 --- a/defogging/transmission.py +++ b/defogging/core/transmission.py @@ -1,7 +1,11 @@ from numpy import * -from .minfilter import cal_minfilter -def cal_transmission(src, A, r, w): +from defogging.utils.minormaxfilter import min_or_max +from .patchshift import patchshift +from .guidedfilter import guidedfilter + + +def transmission(src, A, r, w, L): """ :param src: original input image(three channels) @@ -15,8 +19,10 @@ def cal_transmission(src, A, r, w): for i in range(hei): for j in range(wid): tmp[i, j] = min(src[i, j, :] / A[0, 0, :]) - min_tmp = cal_minfilter(tmp, r) + min_tmp = min_or_max(tmp, r, "min") dst = ones((hei, wid)) - min_tmp[:, :] dst = vectorize(lambda x: x if x > 0.1 else 0.1)(dst) - return dst + + dst_refined = guidedfilter(dst, L, 30, 1e-6) + return dst_refined diff --git a/defogging/defogging.py b/defogging/defogging.py index 4fd91b5..00a1473 100644 --- a/defogging/defogging.py +++ b/defogging/defogging.py @@ -1,33 +1,22 @@ #!/use/bin/env python3 # -*- coding: utf-8 -*- + from numpy import * -from PIL import Image -import sys +import scipy.ndimage -from .airlight import cal_airlight -from .transmission import cal_transmission -from .guidedfilter import cal_guidedfilter -from .recover import cal_recover +from .core.recover import recover +from .core.airlight import airlight +from .core.transmission import transmission +from .utils.reshape import reshape -def defogging(img, name): - src = array(img).astype(float) / 255 +def defogging(src, img): L = array(img.convert("L")).astype(float) / 255 - (hei, wid) = src.shape[0:2] - A = cal_airlight(src, L, 0.2) - trans = cal_transmission(src, A, round(0.02 * min(hei, wid)), 0.95) - trans_refined = cal_guidedfilter(trans, L, 30, 1e-6) - dst = cal_recover(src, A, trans_refined) - - dst_img = Image.fromarray(uint8(dst * 255)) - outname = name + "_defogging.bmp" - dst_img.save(outname) - return dst_img - -def main(): - args = sys.argv - img = Image.open(args[1]) - defogging(img, args[1]) - -if __name__ == '__main__': - main() \ No newline at end of file + src_d = scipy.ndimage.zoom(src, (0.5,0.5,1)) + L_d = scipy.ndimage.zoom(L, 0.5) + (hei, wid) = src_d.shape[0:2] + A = airlight(src_d, L_d, 0.2) + trans_d = transmission(src_d, A, round(0.02 * min(hei, wid)), 0.95, L_d) + trans = reshape(scipy.ndimage.zoom(trans_d, 2), src.shape[0:2]) + dst = recover(src, A, trans) + return dst diff --git a/defogging/minfilter.py b/defogging/minfilter.py deleted file mode 100644 index 7f54ceb..0000000 --- a/defogging/minfilter.py +++ /dev/null @@ -1,18 +0,0 @@ -from numpy import * -from .utils import cal_padding - - -def cal_minfilter(src, r): - """ - - :param src: input(one channel only) - :param r: window radius - :return: dst - """ - (hei, wid) = src.shape[0:2] - dst = zeros((hei, wid)) - src_pad = cal_padding(src, r) - for i in range(hei): - for j in range(wid): - dst[i, j] = amin(src_pad[i:i+2*r+1, j:j+2*r+1]) - return dst diff --git a/defogging/utils/__init__.py b/defogging/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/defogging/utils/minormaxfilter.py b/defogging/utils/minormaxfilter.py new file mode 100644 index 0000000..0873fc9 --- /dev/null +++ b/defogging/utils/minormaxfilter.py @@ -0,0 +1,24 @@ +from numpy import * + +from .padding import padding + + +def min_or_max(src, r, op): + """ + + :param src: input(one channel only) + :param r: window radius + :param op: "min" or "max" + :return: dst + """ + if op == "min": + operation = amin + else: + operation = amax + (hei, wid) = src.shape[0:2] + dst = zeros((hei, wid)) + src_pad = padding(src, r) + for i in range(hei): + for j in range(wid): + dst[i, j] = operation(src_pad[i:i+2*r+1, j:j+2*r+1]) + return dst diff --git a/defogging/utils.py b/defogging/utils/padding.py similarity index 95% rename from defogging/utils.py rename to defogging/utils/padding.py index 9274c36..b9f8960 100644 --- a/defogging/utils.py +++ b/defogging/utils/padding.py @@ -1,7 +1,7 @@ import numpy as np -def cal_padding(src, r): +def padding(src, r): """ padding: used to padding the edges of the src with width of r diff --git a/defogging/utils/reshape.py b/defogging/utils/reshape.py new file mode 100644 index 0000000..5d4a421 --- /dev/null +++ b/defogging/utils/reshape.py @@ -0,0 +1,21 @@ +import scipy.ndimage +import numpy as np +from numpy.matlib import repmat + +def reshape(src, shape): + (hei_dst, wid_dst) = shape + (hei, wid) = src.shape[0:2] + dst = src + if hei > hei_dst: + dst = dst[0:hei_dst, :] + elif hei < hei_dst: + tmp = repmat(dst[hei - 1:hei, :], hei_dst - hei, 1) + dst = np.insert(dst, hei, values=tmp, axis=0) + + if wid > wid_dst: + dst = dst[:, 0:wid_dst] + elif wid < wid_dst: + tmp = repmat(dst[:, wid - 1:wid].T, wid_dst - wid, 1) + dst = np.insert(dst, wid, values=tmp, axis=1) + + return dst diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0d33a9e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +numpy +pillow diff --git a/setup.py b/setup.py index 4b96c83..301411b 100644 --- a/setup.py +++ b/setup.py @@ -2,10 +2,12 @@ from setuptools import setup, find_packages import sys, os """ -python setup.py register sdist upload +python setup.py register sdist bdist_egg upload +python setup.py register sdist upload -r "https://test.pypi.org/legacy/" +pip install -i https://testpypi.python.org/simple/ defogging """ -VERSION = '0.1.6' +VERSION = '0.1.7' with open('README.rst') as readme_file: readme = readme_file.read() @@ -21,7 +23,7 @@ author_email='onecloud.shen@gmail.com', url='https://github.com/scloudyy/Defogging', license='GPL', - packages=['defogging'], + packages=['defogging', 'defogging/core', 'defogging/utils'], include_package_data=True, zip_safe=True, install_requires=[ @@ -30,7 +32,7 @@ ], entry_points={ 'console_scripts':[ - 'defogging = defogging.defogging:main' + 'defogging = defogging:main' ] }, ) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_defogging.py b/tests/test_defogging.py new file mode 100644 index 0000000..70b9754 --- /dev/null +++ b/tests/test_defogging.py @@ -0,0 +1,14 @@ +import pytest +import sys +from defogging import Defog + +def main(): + args = sys.argv + df = Defog() + df.read_img(args[1]) + df.defog() + out_name = args[1] + "_defogged.bmp" + df.save_img(out_name) + +if __name__ == '__main__': + main() diff --git a/tests/test_padding.py b/tests/test_padding.py new file mode 100644 index 0000000..1f5620b --- /dev/null +++ b/tests/test_padding.py @@ -0,0 +1,10 @@ +import pytest +from defogging.utils.padding import padding +from numpy import * + +def test_padding(): + a = array([[1,1,1], [1,1,1], [1,1,1]]) + b = padding(a, 2) + (hei_a, wid_a) = a.shape[0:2] + (hei_b, wid_b) = b.shape[0:2] + assert hei_b == hei_a + 4 and wid_b == wid_a + 4 diff --git a/tests/test_reshape.py b/tests/test_reshape.py new file mode 100644 index 0000000..a595805 --- /dev/null +++ b/tests/test_reshape.py @@ -0,0 +1,12 @@ +import pytest +import numpy as np +from defogging.utils.reshape import reshape + +def test_reshape(): + a = np.array([[1,2,3,4],[4,5,6,6],[7,8,9,9]]) + a_re = reshape(a, (6,6)) + (hei, wid) = a_re.shape[0:2] + assert hei == 6 and wid == 6 + a_re = reshape(a, (2, 2)) + (hei, wid) = a_re.shape[0:2] + assert hei == 2 and wid == 2