IIRLab
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- IIR-Lab/Dockerfile +17 -0
- IIR-Lab/ISP_pipeline/.gitignore +129 -0
- IIR-Lab/ISP_pipeline/Dockerfile +12 -0
- IIR-Lab/ISP_pipeline/LICENSE +21 -0
- IIR-Lab/ISP_pipeline/__pycache__/debayer.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/__pycache__/debayer.cpython-39.pyc +0 -0
- IIR-Lab/ISP_pipeline/__pycache__/imaging.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/__pycache__/imaging.cpython-39.pyc +0 -0
- IIR-Lab/ISP_pipeline/__pycache__/process_pngs_isp.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/__pycache__/process_pngs_isp.cpython-39.pyc +0 -0
- IIR-Lab/ISP_pipeline/__pycache__/utility.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/__pycache__/utility.cpython-39.pyc +0 -0
- IIR-Lab/ISP_pipeline/cfa_pattern_change.py +74 -0
- IIR-Lab/ISP_pipeline/debayer.py +1295 -0
- IIR-Lab/ISP_pipeline/docker_guidelines.md +29 -0
- IIR-Lab/ISP_pipeline/imaging.py +1293 -0
- IIR-Lab/ISP_pipeline/lsc_table_r_gr_gb_b_2.npy +3 -0
- IIR-Lab/ISP_pipeline/process_pngs_isp.py +276 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__init__.py +3 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/__init__.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/__init__.cpython-39.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/arch_util.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/color.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/color.cpython-39.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/csrnet_network.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/csrnet_network.cpython-39.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/exif_data_formats.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/exif_data_formats.cpython-39.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/exif_utils.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/exif_utils.cpython-39.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/fs.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/fs.cpython-39.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/io.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/io.cpython-39.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/lut_network.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/misc.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/misc.cpython-39.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/optim.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/optim.cpython-39.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/pipeline.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/pipeline.cpython-39.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/pipeline_bm3d.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/pipeline_bm3d.cpython-39.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/pipeline_utils.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/pipeline_utils.cpython-39.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/refine_network.cpython-312.pyc +0 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/arch_util.py +626 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/color.py +306 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/csrnet_network.py +76 -0
- IIR-Lab/ISP_pipeline/raw_prc_pipeline/exif_data_formats.py +22 -0
IIR-Lab/Dockerfile
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM pytorch/pytorch:2.0.1-cuda11.7-cudnn8-runtime
|
| 2 |
+
|
| 3 |
+
ENV TZ=Asia
|
| 4 |
+
ARG DEBIAN_FRONTEND=noninteractive
|
| 5 |
+
|
| 6 |
+
RUN apt-get update && apt-get install -y \
|
| 7 |
+
libpng-dev libjpeg-dev \
|
| 8 |
+
libopencv-dev ffmpeg \
|
| 9 |
+
libgl1-mesa-glx
|
| 10 |
+
|
| 11 |
+
COPY requirements.txt .
|
| 12 |
+
RUN python -m pip install --upgrade pip
|
| 13 |
+
RUN pip install --no-cache -r requirements.txt
|
| 14 |
+
|
| 15 |
+
COPY . /nightimage
|
| 16 |
+
RUN chmod +x /nightimage/run.sh
|
| 17 |
+
WORKDIR /nightimage
|
IIR-Lab/ISP_pipeline/.gitignore
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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/
|
IIR-Lab/ISP_pipeline/Dockerfile
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.9
|
| 2 |
+
|
| 3 |
+
RUN apt-get update && apt-get install -y \
|
| 4 |
+
libpng-dev libjpeg-dev \
|
| 5 |
+
libopencv-dev ffmpeg \
|
| 6 |
+
libgl1-mesa-glx
|
| 7 |
+
|
| 8 |
+
COPY requirements.txt .
|
| 9 |
+
RUN python -m pip install --no-cache -r requirements.txt
|
| 10 |
+
|
| 11 |
+
COPY . /nightimaging
|
| 12 |
+
WORKDIR /nightimaging
|
IIR-Lab/ISP_pipeline/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2023 Color Reproduction and Synthesis
|
| 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.
|
IIR-Lab/ISP_pipeline/__pycache__/debayer.cpython-312.pyc
ADDED
|
Binary file (54.5 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/__pycache__/debayer.cpython-39.pyc
ADDED
|
Binary file (21.2 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/__pycache__/imaging.cpython-312.pyc
ADDED
|
Binary file (60.6 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/__pycache__/imaging.cpython-39.pyc
ADDED
|
Binary file (32.1 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/__pycache__/process_pngs_isp.cpython-312.pyc
ADDED
|
Binary file (12.9 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/__pycache__/process_pngs_isp.cpython-39.pyc
ADDED
|
Binary file (6.02 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/__pycache__/utility.cpython-312.pyc
ADDED
|
Binary file (51.6 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/__pycache__/utility.cpython-39.pyc
ADDED
|
Binary file (26.6 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/cfa_pattern_change.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import os
|
| 3 |
+
import json
|
| 4 |
+
import cv2
|
| 5 |
+
|
| 6 |
+
def change_cfa_pattern(img):
|
| 7 |
+
raw_colors = np.asarray([3,1,2,0]).reshape((2, 2))
|
| 8 |
+
changed_raw_colors = np.asarray([0,1,2,3]).reshape((2, 2))
|
| 9 |
+
demosaiced_image = np.zeros((img.shape[0]//2, img.shape[1]//2, 4))
|
| 10 |
+
for i in range(2):
|
| 11 |
+
for j in range(2):
|
| 12 |
+
ch = raw_colors[i, j]
|
| 13 |
+
demosaiced_image[:, :, ch] = img[i::2, j::2]
|
| 14 |
+
for i in range(2):
|
| 15 |
+
for j in range(2):
|
| 16 |
+
ch1 = changed_raw_colors[i, j]
|
| 17 |
+
img[i::2, j::2] = demosaiced_image[:, :, ch1]
|
| 18 |
+
|
| 19 |
+
return img
|
| 20 |
+
|
| 21 |
+
def rggb_raw(raw):
|
| 22 |
+
# pack RGGB Bayer raw to 4 channels
|
| 23 |
+
H, W = raw.shape
|
| 24 |
+
raw = raw[None, ...]
|
| 25 |
+
raw_pack = np.concatenate((raw[:, 0:H:2, 0:W:2],
|
| 26 |
+
raw[:, 0:H:2, 1:W:2],
|
| 27 |
+
raw[:, 1:H:2, 0:W:2],
|
| 28 |
+
raw[:, 1:H:2, 1:W:2]), axis=0)
|
| 29 |
+
# tmp = rggb[...,0]
|
| 30 |
+
# rggb[...,0] = rggb[...,-1]
|
| 31 |
+
# rggb[...,-1] = tmp
|
| 32 |
+
return raw_pack
|
| 33 |
+
|
| 34 |
+
def raw_rggb(raws):
|
| 35 |
+
# depack 4 channels raw to RGGB Bayer
|
| 36 |
+
C, H, W = raws.shape
|
| 37 |
+
output = np.zeros((H * 2, W * 2)).astype(np.uint16)
|
| 38 |
+
|
| 39 |
+
output[0:2 * H:2, 0:2 * W:2] = raws[0:1, :, :]
|
| 40 |
+
output[0:2 * H:2, 1:2 * W:2] = raws[1:2, :, :]
|
| 41 |
+
output[1:2 * H:2, 0:2 * W:2] = raws[2:3, :, :]
|
| 42 |
+
output[1:2 * H:2, 1:2 * W:2] = raws[3:4, :, :]
|
| 43 |
+
|
| 44 |
+
return output
|
| 45 |
+
|
| 46 |
+
if __name__ == "__main__":
|
| 47 |
+
json_path = "/data1/02_data/Train_Data/"
|
| 48 |
+
file_name = os.listdir(json_path)
|
| 49 |
+
json_list = []
|
| 50 |
+
for file_name_all in file_name:
|
| 51 |
+
if file_name_all.endswith(".json"):
|
| 52 |
+
json_list.append(json_path+file_name_all)
|
| 53 |
+
a = []
|
| 54 |
+
for i in range(len(json_list)):
|
| 55 |
+
with open(json_list[i],'r',encoding='UTF-8') as f:
|
| 56 |
+
result = json.load(f)
|
| 57 |
+
# a,b = result["noise_profile"]
|
| 58 |
+
# black = result["white_level"]
|
| 59 |
+
cfa_pattern = result["cfa_pattern"]
|
| 60 |
+
if cfa_pattern[0] == 2:
|
| 61 |
+
a.append(json_list[i])
|
| 62 |
+
for j in range(len(a)):
|
| 63 |
+
pic_name,_ = os.path.splitext(a[j])
|
| 64 |
+
img = cv2.imread(pic_name+str(".png"), cv2.IMREAD_UNCHANGED)
|
| 65 |
+
# img1 = cv2.imread(pic_name+".png", cv2.IMREAD_UNCHANGED)
|
| 66 |
+
# test = img - img1
|
| 67 |
+
# print(test)
|
| 68 |
+
changed_img = change_cfa_pattern(img=img)
|
| 69 |
+
# cv2.imwrite(pic_name+"test1.png",changed_img)
|
| 70 |
+
np.save(pic_name+"origin.npy",img)
|
| 71 |
+
np.save(pic_name+"changed.npy",changed_img)
|
| 72 |
+
# np.save("./json_all.npy",result)
|
| 73 |
+
|
| 74 |
+
|
IIR-Lab/ISP_pipeline/debayer.py
ADDED
|
@@ -0,0 +1,1295 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import math
|
| 3 |
+
import time
|
| 4 |
+
import utility
|
| 5 |
+
from scipy import signal
|
| 6 |
+
|
| 7 |
+
# =============================================================
|
| 8 |
+
# function: dbayer_mhc
|
| 9 |
+
# demosaicing using Malvar-He-Cutler algorithm
|
| 10 |
+
# http://www.ipol.im/pub/art/2011/g_mhcd/
|
| 11 |
+
# =============================================================
|
| 12 |
+
def debayer_mhc(raw, bayer_pattern="rggb", clip_range=[0, 65535], timeshow=False):
|
| 13 |
+
|
| 14 |
+
# convert to float32 in case it was not
|
| 15 |
+
raw = np.float32(raw)
|
| 16 |
+
|
| 17 |
+
# dimensions
|
| 18 |
+
width, height = utility.helpers(raw).get_width_height()
|
| 19 |
+
|
| 20 |
+
# number of pixels to pad
|
| 21 |
+
no_of_pixel_pad = 2
|
| 22 |
+
raw = np.pad(raw, \
|
| 23 |
+
(no_of_pixel_pad, no_of_pixel_pad),\
|
| 24 |
+
'reflect') # reflect would not repeat the border value
|
| 25 |
+
|
| 26 |
+
# allocate space for the R, G, B planes
|
| 27 |
+
R = np.empty( (height + no_of_pixel_pad * 2, width + no_of_pixel_pad * 2), dtype = np.float32 )
|
| 28 |
+
G = np.empty( (height + no_of_pixel_pad * 2, width + no_of_pixel_pad * 2), dtype = np.float32 )
|
| 29 |
+
B = np.empty( (height + no_of_pixel_pad * 2, width + no_of_pixel_pad * 2), dtype = np.float32 )
|
| 30 |
+
|
| 31 |
+
# create a RGB output
|
| 32 |
+
demosaic_out = np.empty( (height, width, 3), dtype = np.float32 )
|
| 33 |
+
|
| 34 |
+
# fill up the directly available values according to the Bayer pattern
|
| 35 |
+
if (bayer_pattern == "rggb"):
|
| 36 |
+
|
| 37 |
+
G[::2, 1::2] = raw[::2, 1::2]
|
| 38 |
+
G[1::2, ::2] = raw[1::2, ::2]
|
| 39 |
+
R[::2, ::2] = raw[::2, ::2]
|
| 40 |
+
B[1::2, 1::2] = raw[1::2, 1::2]
|
| 41 |
+
|
| 42 |
+
# Green channel
|
| 43 |
+
for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
|
| 44 |
+
|
| 45 |
+
# to display progress
|
| 46 |
+
t0 = time.process_time()
|
| 47 |
+
|
| 48 |
+
for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
|
| 49 |
+
|
| 50 |
+
# G at Red location
|
| 51 |
+
if (((i % 2) == 0) and ((j % 2) == 0)):
|
| 52 |
+
G[i, j] = 0.125 * np.sum([-1. * R[i-2, j], \
|
| 53 |
+
2. * G[i-1, j], \
|
| 54 |
+
-1. * R[i, j-2], 2. * G[i, j-1], 4. * R[i,j], 2. * G[i, j+1], -1. * R[i, j+2],\
|
| 55 |
+
2. * G[i+1, j], \
|
| 56 |
+
-1. * R[i+2, j]])
|
| 57 |
+
# G at Blue location
|
| 58 |
+
elif (((i % 2) != 0) and ((j % 2) != 0)):
|
| 59 |
+
G[i, j] = 0.125 * np.sum([-1. * B[i-2, j], \
|
| 60 |
+
2. * G[i-1, j], \
|
| 61 |
+
-1. * B[i, j-2], 2. * G[i, j-1], 4. * B[i,j], 2. * G[i, j+1], -1. * B[i, j+2], \
|
| 62 |
+
2. * G[i+1, j],\
|
| 63 |
+
-1. * B[i+2, j]])
|
| 64 |
+
if (timeshow):
|
| 65 |
+
elapsed_time = time.process_time() - t0
|
| 66 |
+
print("Green: row index: " + str(i-1) + " of " + str(height) + \
|
| 67 |
+
" | elapsed time: " + "{:.3f}".format(elapsed_time) + " seconds")
|
| 68 |
+
|
| 69 |
+
# Red and Blue channel
|
| 70 |
+
for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
|
| 71 |
+
|
| 72 |
+
# to display progress
|
| 73 |
+
t0 = time.process_time()
|
| 74 |
+
|
| 75 |
+
for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
|
| 76 |
+
|
| 77 |
+
# Green locations in Red rows
|
| 78 |
+
if (((i % 2) == 0) and ((j % 2) != 0)):
|
| 79 |
+
# R at Green locations in Red rows
|
| 80 |
+
R[i, j] = 0.125 * np.sum([.5 * G[i-2, j],\
|
| 81 |
+
-1. * G[i-1, j-1], -1. * G[i-1, j+1], \
|
| 82 |
+
-1. * G[i, j-2], 4. * R[i, j-1], 5. * G[i,j], 4. * R[i, j+1], -1. * G[i, j+2], \
|
| 83 |
+
-1. * G[i+1, j-1], -1. * G[i+1, j+1], \
|
| 84 |
+
.5 * G[i+2, j]])
|
| 85 |
+
|
| 86 |
+
# B at Green locations in Red rows
|
| 87 |
+
B[i, j] = 0.125 * np.sum([-1. * G[i-2, j], \
|
| 88 |
+
-1. * G[i-1, j-1], 4. * B[i-1, j], -1. * G[i-1, j+1], \
|
| 89 |
+
.5 * G[i, j-2], 5. * G[i,j], .5 * G[i, j+2], \
|
| 90 |
+
-1. * G[i+1, j-1], 4. * B[i+1,j], -1. * G[i+1, j+1], \
|
| 91 |
+
-1. * G[i+2, j]])
|
| 92 |
+
|
| 93 |
+
# Green locations in Blue rows
|
| 94 |
+
elif (((i % 2) != 0) and ((j % 2) == 0)):
|
| 95 |
+
|
| 96 |
+
# R at Green locations in Blue rows
|
| 97 |
+
R[i, j] = 0.125 * np.sum([-1. * G[i-2, j], \
|
| 98 |
+
-1. * G[i-1, j-1], 4. * R[i-1, j], -1. * G[i-1, j+1], \
|
| 99 |
+
.5 * G[i, j-2], 5. * G[i,j], .5 * G[i, j+2], \
|
| 100 |
+
-1. * G[i+1, j-1], 4. * R[i+1, j], -1. * G[i+1, j+1], \
|
| 101 |
+
-1. * G[i+2, j]])
|
| 102 |
+
|
| 103 |
+
# B at Green locations in Blue rows
|
| 104 |
+
B[i, j] = 0.125 * np.sum([.5 * G[i-2, j], \
|
| 105 |
+
-1. * G [i-1, j-1], -1. * G[i-1, j+1], \
|
| 106 |
+
-1. * G[i, j-2], 4. * B[i, j-1], 5. * G[i,j], 4. * B[i, j+1], -1. * G[i, j+2], \
|
| 107 |
+
-1. * G[i+1, j-1], -1. * G[i+1, j+1], \
|
| 108 |
+
.5 * G[i+2, j]])
|
| 109 |
+
|
| 110 |
+
# R at Blue locations
|
| 111 |
+
elif (((i % 2) != 0) and ((j % 2) != 0)):
|
| 112 |
+
R[i, j] = 0.125 * np.sum([-1.5 * B[i-2, j], \
|
| 113 |
+
2. * R[i-1, j-1], 2. * R[i-1, j+1], \
|
| 114 |
+
-1.5 * B[i, j-2], 6. * B[i,j], -1.5 * B[i, j+2], \
|
| 115 |
+
2. * R[i+1, j-1], 2. * R[i+1, j+1], \
|
| 116 |
+
-1.5 * B[i+2, j]])
|
| 117 |
+
|
| 118 |
+
# B at Red locations
|
| 119 |
+
elif (((i % 2) == 0) and ((j % 2) == 0)):
|
| 120 |
+
B[i, j] = 0.125 * np.sum([-1.5 * R[i-2, j], \
|
| 121 |
+
2. * B[i-1, j-1], 2. * B[i-1, j+1], \
|
| 122 |
+
-1.5 * R[i, j-2], 6. * R[i,j], -1.5 * R[i, j+2], \
|
| 123 |
+
2. * B[i+1, j-1], 2. * B[i+1, j+1], \
|
| 124 |
+
-1.5 * R[i+2, j]])
|
| 125 |
+
|
| 126 |
+
if (timeshow):
|
| 127 |
+
elapsed_time = time.process_time() - t0
|
| 128 |
+
print("Red/Blue: row index: " + str(i-1) + " of " + str(height) + \
|
| 129 |
+
" | elapsed time: " + "{:.3f}".format(elapsed_time) + " seconds")
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
elif (bayer_pattern == "gbrg"):
|
| 133 |
+
|
| 134 |
+
G[::2, ::2] = raw[::2, ::2]
|
| 135 |
+
G[1::2, 1::2] = raw[1::2, 1::2]
|
| 136 |
+
R[1::2, ::2] = raw[1::2, ::2]
|
| 137 |
+
B[::2, 1::2] = raw[::2, 1::2]
|
| 138 |
+
|
| 139 |
+
# Green channel
|
| 140 |
+
for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
|
| 141 |
+
|
| 142 |
+
# to display progress
|
| 143 |
+
t0 = time.process_time()
|
| 144 |
+
|
| 145 |
+
for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
|
| 146 |
+
|
| 147 |
+
# G at Red location
|
| 148 |
+
if (((i % 2) != 0) and ((j % 2) == 0)):
|
| 149 |
+
G[i, j] = 0.125 * np.sum([-1. * R[i-2, j], \
|
| 150 |
+
2. * G[i-1, j], \
|
| 151 |
+
-1. * R[i, j-2], 2. * G[i, j-1], 4. * R[i,j], 2. * G[i, j+1], -1. * R[i, j+2],\
|
| 152 |
+
2. * G[i+1, j], \
|
| 153 |
+
-1. * R[i+2, j]])
|
| 154 |
+
# G at Blue location
|
| 155 |
+
elif (((i % 2) == 0) and ((j % 2) != 0)):
|
| 156 |
+
G[i, j] = 0.125 * np.sum([-1. * B[i-2, j], \
|
| 157 |
+
2. * G[i-1, j], \
|
| 158 |
+
-1. * B[i, j-2], 2. * G[i, j-1], 4. * B[i,j], 2. * G[i, j+1], -1. * B[i, j+2], \
|
| 159 |
+
2. * G[i+1, j],\
|
| 160 |
+
-1. * B[i+2, j]])
|
| 161 |
+
if (timeshow):
|
| 162 |
+
elapsed_time = time.process_time() - t0
|
| 163 |
+
print("Green: row index: " + str(i-1) + " of " + str(height) + \
|
| 164 |
+
" | elapsed time: " + "{:.3f}".format(elapsed_time) + " seconds")
|
| 165 |
+
|
| 166 |
+
# Red and Blue channel
|
| 167 |
+
for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
|
| 168 |
+
|
| 169 |
+
# to display progress
|
| 170 |
+
t0 = time.process_time()
|
| 171 |
+
|
| 172 |
+
for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
|
| 173 |
+
|
| 174 |
+
# Green locations in Red rows
|
| 175 |
+
if (((i % 2) != 0) and ((j % 2) != 0)):
|
| 176 |
+
# R at Green locations in Red rows
|
| 177 |
+
R[i, j] = 0.125 * np.sum([.5 * G[i-2, j],\
|
| 178 |
+
-1. * G[i-1, j-1], -1. * G[i-1, j+1], \
|
| 179 |
+
-1. * G[i, j-2], 4. * R[i, j-1], 5. * G[i,j], 4. * R[i, j+1], -1. * G[i, j+2], \
|
| 180 |
+
-1. * G[i+1, j-1], -1. * G[i+1, j+1], \
|
| 181 |
+
.5 * G[i+2, j]])
|
| 182 |
+
|
| 183 |
+
# B at Green locations in Red rows
|
| 184 |
+
B[i, j] = 0.125 * np.sum([-1. * G[i-2, j], \
|
| 185 |
+
-1. * G[i-1, j-1], 4. * B[i-1, j], -1. * G[i-1, j+1], \
|
| 186 |
+
.5 * G[i, j-2], 5. * G[i,j], .5 * G[i, j+2], \
|
| 187 |
+
-1. * G[i+1, j-1], 4. * B[i+1,j], -1. * G[i+1, j+1], \
|
| 188 |
+
-1. * G[i+2, j]])
|
| 189 |
+
|
| 190 |
+
# Green locations in Blue rows
|
| 191 |
+
elif (((i % 2) == 0) and ((j % 2) == 0)):
|
| 192 |
+
|
| 193 |
+
# R at Green locations in Blue rows
|
| 194 |
+
R[i, j] = 0.125 * np.sum([-1. * G[i-2, j], \
|
| 195 |
+
-1. * G[i-1, j-1], 4. * R[i-1, j], -1. * G[i-1, j+1], \
|
| 196 |
+
.5 * G[i, j-2], 5. * G[i,j], .5 * G[i, j+2], \
|
| 197 |
+
-1. * G[i+1, j-1], 4. * R[i+1, j], -1. * G[i+1, j+1], \
|
| 198 |
+
-1. * G[i+2, j]])
|
| 199 |
+
|
| 200 |
+
# B at Green locations in Blue rows
|
| 201 |
+
B[i, j] = 0.125 * np.sum([.5 * G[i-2, j], \
|
| 202 |
+
-1. * G [i-1, j-1], -1. * G[i-1, j+1], \
|
| 203 |
+
-1. * G[i, j-2], 4. * B[i, j-1], 5. * G[i,j], 4. * B[i, j+1], -1. * G[i, j+2], \
|
| 204 |
+
-1. * G[i+1, j-1], -1. * G[i+1, j+1], \
|
| 205 |
+
.5 * G[i+2, j]])
|
| 206 |
+
|
| 207 |
+
# R at Blue locations
|
| 208 |
+
elif (((i % 2) == 0) and ((j % 2) != 0)):
|
| 209 |
+
R[i, j] = 0.125 * np.sum([-1.5 * B[i-2, j], \
|
| 210 |
+
2. * R[i-1, j-1], 2. * R[i-1, j+1], \
|
| 211 |
+
-1.5 * B[i, j-2], 6. * B[i,j], -1.5 * B[i, j+2], \
|
| 212 |
+
2. * R[i+1, j-1], 2. * R[i+1, j+1], \
|
| 213 |
+
-1.5 * B[i+2, j]])
|
| 214 |
+
|
| 215 |
+
# B at Red locations
|
| 216 |
+
elif (((i % 2) != 0) and ((j % 2) == 0)):
|
| 217 |
+
B[i, j] = 0.125 * np.sum([-1.5 * R[i-2, j], \
|
| 218 |
+
2. * B[i-1, j-1], 2. * B[i-1, j+1], \
|
| 219 |
+
-1.5 * R[i, j-2], 6. * R[i,j], -1.5 * R[i, j+2], \
|
| 220 |
+
2. * B[i+1, j-1], 2. * B[i+1, j+1], \
|
| 221 |
+
-1.5 * R[i+2, j]])
|
| 222 |
+
|
| 223 |
+
if (timeshow):
|
| 224 |
+
elapsed_time = time.process_time() - t0
|
| 225 |
+
print("Red/Blue: row index: " + str(i-1) + " of " + str(height) + \
|
| 226 |
+
" | elapsed time: " + "{:.3f}".format(elapsed_time) + " seconds")
|
| 227 |
+
|
| 228 |
+
elif (bayer_pattern == "grbg"):
|
| 229 |
+
|
| 230 |
+
G[::2, ::2] = raw[::2, ::2]
|
| 231 |
+
G[1::2, 1::2] = raw[1::2, 1::2]
|
| 232 |
+
R[::2, 1::2] = raw[::2, 1::2]
|
| 233 |
+
B[1::2, ::2] = raw[1::2, ::2]
|
| 234 |
+
|
| 235 |
+
# Green channel
|
| 236 |
+
for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
|
| 237 |
+
|
| 238 |
+
# to display progress
|
| 239 |
+
t0 = time.process_time()
|
| 240 |
+
|
| 241 |
+
for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
|
| 242 |
+
|
| 243 |
+
# G at Red location
|
| 244 |
+
if (((i % 2) == 0) and ((j % 2) != 0)):
|
| 245 |
+
G[i, j] = 0.125 * np.sum([-1. * R[i-2, j], \
|
| 246 |
+
2. * G[i-1, j], \
|
| 247 |
+
-1. * R[i, j-2], 2. * G[i, j-1], 4. * R[i,j], 2. * G[i, j+1], -1. * R[i, j+2],\
|
| 248 |
+
2. * G[i+1, j], \
|
| 249 |
+
-1. * R[i+2, j]])
|
| 250 |
+
# G at Blue location
|
| 251 |
+
elif (((i % 2) != 0) and ((j % 2) == 0)):
|
| 252 |
+
G[i, j] = 0.125 * np.sum([-1. * B[i-2, j], \
|
| 253 |
+
2. * G[i-1, j], \
|
| 254 |
+
-1. * B[i, j-2], 2. * G[i, j-1], 4. * B[i,j], 2. * G[i, j+1], -1. * B[i, j+2], \
|
| 255 |
+
2. * G[i+1, j],\
|
| 256 |
+
-1. * B[i+2, j]])
|
| 257 |
+
if (timeshow):
|
| 258 |
+
elapsed_time = time.process_time() - t0
|
| 259 |
+
print("Green: row index: " + str(i-1) + " of " + str(height) + \
|
| 260 |
+
" | elapsed time: " + "{:.3f}".format(elapsed_time) + " seconds")
|
| 261 |
+
|
| 262 |
+
# Red and Blue channel
|
| 263 |
+
for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
|
| 264 |
+
|
| 265 |
+
# to display progress
|
| 266 |
+
t0 = time.process_time()
|
| 267 |
+
|
| 268 |
+
for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
|
| 269 |
+
|
| 270 |
+
# Green locations in Red rows
|
| 271 |
+
if (((i % 2) == 0) and ((j % 2) == 0)):
|
| 272 |
+
# R at Green locations in Red rows
|
| 273 |
+
R[i, j] = 0.125 * np.sum([.5 * G[i-2, j],\
|
| 274 |
+
-1. * G[i-1, j-1], -1. * G[i-1, j+1], \
|
| 275 |
+
-1. * G[i, j-2], 4. * R[i, j-1], 5. * G[i,j], 4. * R[i, j+1], -1. * G[i, j+2], \
|
| 276 |
+
-1. * G[i+1, j-1], -1. * G[i+1, j+1], \
|
| 277 |
+
.5 * G[i+2, j]])
|
| 278 |
+
|
| 279 |
+
# B at Green locations in Red rows
|
| 280 |
+
B[i, j] = 0.125 * np.sum([-1. * G[i-2, j], \
|
| 281 |
+
-1. * G[i-1, j-1], 4. * B[i-1, j], -1. * G[i-1, j+1], \
|
| 282 |
+
.5 * G[i, j-2], 5. * G[i,j], .5 * G[i, j+2], \
|
| 283 |
+
-1. * G[i+1, j-1], 4. * B[i+1,j], -1. * G[i+1, j+1], \
|
| 284 |
+
-1. * G[i+2, j]])
|
| 285 |
+
|
| 286 |
+
# Green locations in Blue rows
|
| 287 |
+
elif (((i % 2) != 0) and ((j % 2) != 0)):
|
| 288 |
+
|
| 289 |
+
# R at Green locations in Blue rows
|
| 290 |
+
R[i, j] = 0.125 * np.sum([-1. * G[i-2, j], \
|
| 291 |
+
-1. * G[i-1, j-1], 4. * R[i-1, j], -1. * G[i-1, j+1], \
|
| 292 |
+
.5 * G[i, j-2], 5. * G[i,j], .5 * G[i, j+2], \
|
| 293 |
+
-1. * G[i+1, j-1], 4. * R[i+1, j], -1. * G[i+1, j+1], \
|
| 294 |
+
-1. * G[i+2, j]])
|
| 295 |
+
|
| 296 |
+
# B at Green locations in Blue rows
|
| 297 |
+
B[i, j] = 0.125 * np.sum([.5 * G[i-2, j], \
|
| 298 |
+
-1. * G [i-1, j-1], -1. * G[i-1, j+1], \
|
| 299 |
+
-1. * G[i, j-2], 4. * B[i, j-1], 5. * G[i,j], 4. * B[i, j+1], -1. * G[i, j+2], \
|
| 300 |
+
-1. * G[i+1, j-1], -1. * G[i+1, j+1], \
|
| 301 |
+
.5 * G[i+2, j]])
|
| 302 |
+
|
| 303 |
+
# R at Blue locations
|
| 304 |
+
elif (((i % 2) != 0) and ((j % 2) == 0)):
|
| 305 |
+
R[i, j] = 0.125 * np.sum([-1.5 * B[i-2, j], \
|
| 306 |
+
2. * R[i-1, j-1], 2. * R[i-1, j+1], \
|
| 307 |
+
-1.5 * B[i, j-2], 6. * B[i,j], -1.5 * B[i, j+2], \
|
| 308 |
+
2. * R[i+1, j-1], 2. * R[i+1, j+1], \
|
| 309 |
+
-1.5 * B[i+2, j]])
|
| 310 |
+
|
| 311 |
+
# B at Red locations
|
| 312 |
+
elif (((i % 2) == 0) and ((j % 2) != 0)):
|
| 313 |
+
B[i, j] = 0.125 * np.sum([-1.5 * R[i-2, j], \
|
| 314 |
+
2. * B[i-1, j-1], 2. * B[i-1, j+1], \
|
| 315 |
+
-1.5 * R[i, j-2], 6. * R[i,j], -1.5 * R[i, j+2], \
|
| 316 |
+
2. * B[i+1, j-1], 2. * B[i+1, j+1], \
|
| 317 |
+
-1.5 * R[i+2, j]])
|
| 318 |
+
|
| 319 |
+
if (timeshow):
|
| 320 |
+
elapsed_time = time.process_time() - t0
|
| 321 |
+
print("Red/Blue: row index: " + str(i-1) + " of " + str(height) + \
|
| 322 |
+
" | elapsed time: " + "{:.3f}".format(elapsed_time) + " seconds")
|
| 323 |
+
|
| 324 |
+
elif (bayer_pattern == "bggr"):
|
| 325 |
+
|
| 326 |
+
G[::2, 1::2] = raw[::2, 1::2]
|
| 327 |
+
G[1::2, ::2] = raw[1::2, ::2]
|
| 328 |
+
R[1::2, 1::2] = raw[1::2, 1::2]
|
| 329 |
+
B[::2, ::2] = raw[::2, ::2]
|
| 330 |
+
|
| 331 |
+
# Green channel
|
| 332 |
+
for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
|
| 333 |
+
|
| 334 |
+
# to display progress
|
| 335 |
+
t0 = time.process_time()
|
| 336 |
+
|
| 337 |
+
for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
|
| 338 |
+
|
| 339 |
+
# G at Red location
|
| 340 |
+
if (((i % 2) != 0) and ((j % 2) != 0)):
|
| 341 |
+
G[i, j] = 0.125 * np.sum([-1. * R[i-2, j], \
|
| 342 |
+
2. * G[i-1, j], \
|
| 343 |
+
-1. * R[i, j-2], 2. * G[i, j-1], 4. * R[i,j], 2. * G[i, j+1], -1. * R[i, j+2],\
|
| 344 |
+
2. * G[i+1, j], \
|
| 345 |
+
-1. * R[i+2, j]])
|
| 346 |
+
# G at Blue location
|
| 347 |
+
elif (((i % 2) == 0) and ((j % 2) == 0)):
|
| 348 |
+
G[i, j] = 0.125 * np.sum([-1. * B[i-2, j], \
|
| 349 |
+
2. * G[i-1, j], \
|
| 350 |
+
-1. * B[i, j-2], 2. * G[i, j-1], 4. * B[i,j], 2. * G[i, j+1], -1. * B[i, j+2], \
|
| 351 |
+
2. * G[i+1, j],\
|
| 352 |
+
-1. * B[i+2, j]])
|
| 353 |
+
if (timeshow):
|
| 354 |
+
elapsed_time = time.process_time() - t0
|
| 355 |
+
print("Green: row index: " + str(i-1) + " of " + str(height) + \
|
| 356 |
+
" | elapsed time: " + "{:.3f}".format(elapsed_time) + " seconds")
|
| 357 |
+
|
| 358 |
+
# Red and Blue channel
|
| 359 |
+
for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
|
| 360 |
+
|
| 361 |
+
# to display progress
|
| 362 |
+
t0 = time.process_time()
|
| 363 |
+
|
| 364 |
+
for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
|
| 365 |
+
|
| 366 |
+
# Green locations in Red rows
|
| 367 |
+
if (((i % 2) != 0) and ((j % 2) == 0)):
|
| 368 |
+
# R at Green locations in Red rows
|
| 369 |
+
R[i, j] = 0.125 * np.sum([.5 * G[i-2, j],\
|
| 370 |
+
-1. * G[i-1, j-1], -1. * G[i-1, j+1], \
|
| 371 |
+
-1. * G[i, j-2], 4. * R[i, j-1], 5. * G[i,j], 4. * R[i, j+1], -1. * G[i, j+2], \
|
| 372 |
+
-1. * G[i+1, j-1], -1. * G[i+1, j+1], \
|
| 373 |
+
.5 * G[i+2, j]])
|
| 374 |
+
|
| 375 |
+
# B at Green locations in Red rows
|
| 376 |
+
B[i, j] = 0.125 * np.sum([-1. * G[i-2, j], \
|
| 377 |
+
-1. * G[i-1, j-1], 4. * B[i-1, j], -1. * G[i-1, j+1], \
|
| 378 |
+
.5 * G[i, j-2], 5. * G[i,j], .5 * G[i, j+2], \
|
| 379 |
+
-1. * G[i+1, j-1], 4. * B[i+1,j], -1. * G[i+1, j+1], \
|
| 380 |
+
-1. * G[i+2, j]])
|
| 381 |
+
|
| 382 |
+
# Green locations in Blue rows
|
| 383 |
+
elif (((i % 2) == 0) and ((j % 2) != 0)):
|
| 384 |
+
|
| 385 |
+
# R at Green locations in Blue rows
|
| 386 |
+
R[i, j] = 0.125 * np.sum([-1. * G[i-2, j], \
|
| 387 |
+
-1. * G[i-1, j-1], 4. * R[i-1, j], -1. * G[i-1, j+1], \
|
| 388 |
+
.5 * G[i, j-2], 5. * G[i,j], .5 * G[i, j+2], \
|
| 389 |
+
-1. * G[i+1, j-1], 4. * R[i+1, j], -1. * G[i+1, j+1], \
|
| 390 |
+
-1. * G[i+2, j]])
|
| 391 |
+
|
| 392 |
+
# B at Green locations in Blue rows
|
| 393 |
+
B[i, j] = 0.125 * np.sum([.5 * G[i-2, j], \
|
| 394 |
+
-1. * G [i-1, j-1], -1. * G[i-1, j+1], \
|
| 395 |
+
-1. * G[i, j-2], 4. * B[i, j-1], 5. * G[i,j], 4. * B[i, j+1], -1. * G[i, j+2], \
|
| 396 |
+
-1. * G[i+1, j-1], -1. * G[i+1, j+1], \
|
| 397 |
+
.5 * G[i+2, j]])
|
| 398 |
+
|
| 399 |
+
# R at Blue locations
|
| 400 |
+
elif (((i % 2) == 0) and ((j % 2) == 0)):
|
| 401 |
+
R[i, j] = 0.125 * np.sum([-1.5 * B[i-2, j], \
|
| 402 |
+
2. * R[i-1, j-1], 2. * R[i-1, j+1], \
|
| 403 |
+
-1.5 * B[i, j-2], 6. * B[i,j], -1.5 * B[i, j+2], \
|
| 404 |
+
2. * R[i+1, j-1], 2. * R[i+1, j+1], \
|
| 405 |
+
-1.5 * B[i+2, j]])
|
| 406 |
+
|
| 407 |
+
# B at Red locations
|
| 408 |
+
elif (((i % 2) != 0) and ((j % 2) != 0)):
|
| 409 |
+
B[i, j] = 0.125 * np.sum([-1.5 * R[i-2, j], \
|
| 410 |
+
2. * B[i-1, j-1], 2. * B[i-1, j+1], \
|
| 411 |
+
-1.5 * R[i, j-2], 6. * R[i,j], -1.5 * R[i, j+2], \
|
| 412 |
+
2. * B[i+1, j-1], 2. * B[i+1, j+1], \
|
| 413 |
+
-1.5 * R[i+2, j]])
|
| 414 |
+
|
| 415 |
+
if (timeshow):
|
| 416 |
+
elapsed_time = time.process_time() - t0
|
| 417 |
+
print("Red/Blue: row index: " + str(i-1) + " of " + str(height) + \
|
| 418 |
+
" | elapsed time: " + "{:.3f}".format(elapsed_time) + " seconds")
|
| 419 |
+
|
| 420 |
+
else:
|
| 421 |
+
print("Invalid bayer pattern. Valid pattern can be rggb, gbrg, grbg, bggr")
|
| 422 |
+
return demosaic_out # This will be all zeros
|
| 423 |
+
|
| 424 |
+
# Fill up the RGB output with interpolated values
|
| 425 |
+
demosaic_out[0:height, 0:width, 0] = R[no_of_pixel_pad : height + no_of_pixel_pad, \
|
| 426 |
+
no_of_pixel_pad : width + no_of_pixel_pad]
|
| 427 |
+
demosaic_out[0:height, 0:width, 1] = G[no_of_pixel_pad : height + no_of_pixel_pad, \
|
| 428 |
+
no_of_pixel_pad : width + no_of_pixel_pad]
|
| 429 |
+
demosaic_out[0:height, 0:width, 2] = B[no_of_pixel_pad : height + no_of_pixel_pad, \
|
| 430 |
+
no_of_pixel_pad : width + no_of_pixel_pad]
|
| 431 |
+
|
| 432 |
+
demosaic_out = np.clip(demosaic_out, clip_range[0], clip_range[1])
|
| 433 |
+
return demosaic_out
|
| 434 |
+
|
| 435 |
+
|
| 436 |
+
def fill_channel_directional_weight(data, bayer_pattern):
|
| 437 |
+
|
| 438 |
+
#== Calculate the directional weights (weight_N, weight_E, weight_S, weight_W.
|
| 439 |
+
# where N, E, S, W stand for north, east, south, and west.)
|
| 440 |
+
data = np.asarray(data)
|
| 441 |
+
v = np.asarray(signal.convolve2d(data, [[1],[0],[-1]], mode="same", boundary="symm"))
|
| 442 |
+
h = np.asarray(signal.convolve2d(data, [[1, 0, -1]], mode="same", boundary="symm"))
|
| 443 |
+
|
| 444 |
+
weight_N = np.zeros(np.shape(data), dtype=np.float32)
|
| 445 |
+
weight_E = np.zeros(np.shape(data), dtype=np.float32)
|
| 446 |
+
weight_S = np.zeros(np.shape(data), dtype=np.float32)
|
| 447 |
+
weight_W = np.zeros(np.shape(data), dtype=np.float32)
|
| 448 |
+
|
| 449 |
+
value_N = np.zeros(np.shape(data), dtype=np.float32)
|
| 450 |
+
value_E = np.zeros(np.shape(data), dtype=np.float32)
|
| 451 |
+
value_S = np.zeros(np.shape(data), dtype=np.float32)
|
| 452 |
+
value_W = np.zeros(np.shape(data), dtype=np.float32)
|
| 453 |
+
|
| 454 |
+
if ((bayer_pattern == "rggb") or (bayer_pattern == "bggr")):
|
| 455 |
+
|
| 456 |
+
|
| 457 |
+
# note that in the following the locations in the comments are given
|
| 458 |
+
# assuming the bayer_pattern rggb
|
| 459 |
+
|
| 460 |
+
#== CALCULATE WEIGHTS IN B LOCATIONS
|
| 461 |
+
weight_N[1::2, 1::2] = np.abs(v[1::2, 1::2]) + np.abs(v[::2, 1::2])
|
| 462 |
+
|
| 463 |
+
# repeating the column before the last to the right so that sampling
|
| 464 |
+
# does not cause any dimension mismatch
|
| 465 |
+
temp_h_b = np.hstack((h, np.atleast_2d(h[:, -2]).T))
|
| 466 |
+
weight_E[1::2, 1::2] = np.abs(h[1::2, 1::2]) + np.abs(temp_h_b[1::2, 2::2])
|
| 467 |
+
|
| 468 |
+
# repeating the row before the last row to the bottom so that sampling
|
| 469 |
+
# does not cause any dimension mismatch
|
| 470 |
+
temp_v_b = np.vstack((v, v[-1]))
|
| 471 |
+
weight_S[1::2, 1::2] = np.abs(v[1::2, 1::2]) + np.abs(temp_v_b[2::2, 1::2])
|
| 472 |
+
weight_W[1::2, 1::2] = np.abs(h[1::2, 1::2]) + np.abs(h[1::2, ::2])
|
| 473 |
+
|
| 474 |
+
#== CALCULATE WEIGHTS IN R LOCATIONS
|
| 475 |
+
# repeating the second row at the top of matrix so that sampling does
|
| 476 |
+
# not cause any dimension mismatch, also remove the bottom row
|
| 477 |
+
temp_v_r = np.delete(np.vstack((v[1], v)), -1, 0)
|
| 478 |
+
weight_N[::2, ::2] = np.abs(v[::2, ::2]) + np.abs(temp_v_r[::2, ::2])
|
| 479 |
+
|
| 480 |
+
weight_E[::2, ::2] = np.abs(h[::2, ::2]) + np.abs(h[::2, 1::2])
|
| 481 |
+
|
| 482 |
+
weight_S[::2, ::2] = np.abs(v[::2, ::2]) + np.abs(v[1::2, ::2])
|
| 483 |
+
|
| 484 |
+
# repeating the second column at the left of matrix so that sampling
|
| 485 |
+
# does not cause any dimension mismatch, also remove the rightmost
|
| 486 |
+
# column
|
| 487 |
+
temp_h_r = np.delete(np.hstack((np.atleast_2d(h[:, 1]).T, h)), -1, 1)
|
| 488 |
+
weight_W[::2, ::2] = np.abs(h[::2, ::2]) + np.abs(temp_h_r[::2, ::2])
|
| 489 |
+
|
| 490 |
+
weight_N = np.divide(1., 1. + weight_N)
|
| 491 |
+
weight_E = np.divide(1., 1. + weight_E)
|
| 492 |
+
weight_S = np.divide(1., 1. + weight_S)
|
| 493 |
+
weight_W = np.divide(1., 1. + weight_W)
|
| 494 |
+
|
| 495 |
+
#== CALCULATE DIRECTIONAL ESTIMATES IN B LOCATIONS
|
| 496 |
+
value_N[1::2, 1::2] = data[::2, 1::2] + v[::2, 1::2] / 2.
|
| 497 |
+
|
| 498 |
+
# repeating the column before the last to the right so that sampling
|
| 499 |
+
# does not cause any dimension mismatch
|
| 500 |
+
temp = np.hstack((data, np.atleast_2d(data[:, -2]).T))
|
| 501 |
+
value_E[1::2, 1::2] = temp[1::2, 2::2] - temp_h_b[1::2, 2::2] / 2.
|
| 502 |
+
|
| 503 |
+
# repeating the row before the last row to the bottom so that sampling
|
| 504 |
+
# does not cause any dimension mismatch
|
| 505 |
+
temp = np.vstack((data, data[-1]))
|
| 506 |
+
value_S[1::2, 1::2] = temp[2::2, 1::2] - temp_v_b[2::2, 1::2] / 2.
|
| 507 |
+
|
| 508 |
+
value_W[1::2, 1::2] = data[1::2, ::2] + h[1::2, ::2] / 2.
|
| 509 |
+
|
| 510 |
+
#== CALCULATE DIRECTIONAL ESTIMATES IN R LOCATIONS
|
| 511 |
+
# repeating the second row at the top of matrix so that sampling does
|
| 512 |
+
# not cause any dimension mismatch, also remove the bottom row
|
| 513 |
+
temp = np.delete(np.vstack((data[1], data)), -1, 0)
|
| 514 |
+
value_N[::2, ::2] = temp[::2, ::2] + temp_v_r[::2, ::2] / 2.
|
| 515 |
+
|
| 516 |
+
value_E[::2, ::2] = data[::2, 1::2] - h[::2, 1::2] / 2.
|
| 517 |
+
|
| 518 |
+
value_S[::2, ::2] = data[1::2, ::2] - v[1::2, ::2] / 2.
|
| 519 |
+
|
| 520 |
+
# repeating the second column at the left of matrix so that sampling
|
| 521 |
+
# does not cause any dimension mismatch, also remove the rightmost
|
| 522 |
+
# column
|
| 523 |
+
temp = np.delete(np.hstack((np.atleast_2d(data[:, 1]).T, data)), -1, 1)
|
| 524 |
+
value_W[::2, ::2] = temp[::2, ::2] + temp_h_r[::2, ::2] / 2.
|
| 525 |
+
|
| 526 |
+
output = np.zeros(np.shape(data), dtype=np.float32)
|
| 527 |
+
output = np.divide((np.multiply(value_N, weight_N) + \
|
| 528 |
+
np.multiply(value_E, weight_E) + \
|
| 529 |
+
np.multiply(value_S, weight_S) + \
|
| 530 |
+
np.multiply(value_W, weight_W)),\
|
| 531 |
+
(weight_N + weight_E + weight_S + weight_W))
|
| 532 |
+
|
| 533 |
+
output[::2, 1::2] = data[::2, 1::2]
|
| 534 |
+
output[1::2, ::2] = data[1::2, ::2]
|
| 535 |
+
|
| 536 |
+
return output
|
| 537 |
+
|
| 538 |
+
elif ((bayer_pattern == "gbrg") or (bayer_pattern == "grbg")):
|
| 539 |
+
|
| 540 |
+
# note that in the following the locations in the comments are given
|
| 541 |
+
# assuming the bayer_pattern gbrg
|
| 542 |
+
|
| 543 |
+
#== CALCULATE WEIGHTS IN B LOCATIONS
|
| 544 |
+
# repeating the second row at the top of matrix so that sampling does
|
| 545 |
+
# not cause any dimension mismatch, also remove the bottom row
|
| 546 |
+
temp_v_b = np.delete(np.vstack((v[1], v)), -1, 0)
|
| 547 |
+
weight_N[::2, 1::2] = np.abs(v[::2, 1::2]) + np.abs(temp_v_b[::2, 1::2])
|
| 548 |
+
|
| 549 |
+
# repeating the column before the last to the right so that sampling
|
| 550 |
+
# does not cause any dimension mismatch
|
| 551 |
+
temp_h_b = np.hstack((h, np.atleast_2d(h[:, -2]).T))
|
| 552 |
+
weight_E[::2, 1::2] = np.abs(h[::2, 1::2]) + np.abs(temp_h_b[::2, 2::2])
|
| 553 |
+
|
| 554 |
+
# repeating the row before the last row to the bottom so that sampling
|
| 555 |
+
# does not cause any dimension mismatch
|
| 556 |
+
weight_S[::2, 1::2] = np.abs(v[::2, 1::2]) + np.abs(v[1::2, 1::2])
|
| 557 |
+
weight_W[::2, 1::2] = np.abs(h[::2, 1::2]) + np.abs(h[::2, ::2])
|
| 558 |
+
|
| 559 |
+
#== CALCULATE WEIGHTS IN R LOCATIONS
|
| 560 |
+
weight_N[1::2, ::2] = np.abs(v[1::2, ::2]) + np.abs(v[::2, ::2])
|
| 561 |
+
weight_E[1::2, ::2] = np.abs(h[1::2, ::2]) + np.abs(h[1::2, 1::2])
|
| 562 |
+
|
| 563 |
+
# repeating the row before the last row to the bottom so that sampling
|
| 564 |
+
# does not cause any dimension mismatch
|
| 565 |
+
temp_v_r = np.vstack((v, v[-1]))
|
| 566 |
+
weight_S[1::2, ::2] = np.abs(v[1::2, ::2]) + np.abs(temp_v_r[2::2, ::2])
|
| 567 |
+
|
| 568 |
+
# repeating the second column at the left of matrix so that sampling
|
| 569 |
+
# does not cause any dimension mismatch, also remove the rightmost
|
| 570 |
+
# column
|
| 571 |
+
temp_h_r = np.delete(np.hstack((np.atleast_2d(h[:, 1]).T, h)), -1, 1)
|
| 572 |
+
weight_W[1::2, ::2] = np.abs(h[1::2, ::2]) + np.abs(temp_h_r[1::2, ::2])
|
| 573 |
+
|
| 574 |
+
weight_N = np.divide(1., 1. + weight_N)
|
| 575 |
+
weight_E = np.divide(1., 1. + weight_E)
|
| 576 |
+
weight_S = np.divide(1., 1. + weight_S)
|
| 577 |
+
weight_W = np.divide(1., 1. + weight_W)
|
| 578 |
+
|
| 579 |
+
#== CALCULATE DIRECTIONAL ESTIMATES IN B LOCATIONS
|
| 580 |
+
# repeating the second row at the top of matrix so that sampling does
|
| 581 |
+
# not cause any dimension mismatch, also remove the bottom row
|
| 582 |
+
temp = np.delete(np.vstack((data[1], data)), -1, 0)
|
| 583 |
+
value_N[::2, 1::2] = temp[::2, 1::2] + temp_v_b[::2, 1::2] / 2.
|
| 584 |
+
|
| 585 |
+
# repeating the column before the last to the right so that sampling
|
| 586 |
+
# does not cause any dimension mismatch
|
| 587 |
+
temp = np.hstack((data, np.atleast_2d(data[:, -2]).T))
|
| 588 |
+
value_E[::2, 1::2] = temp[::2, 2::2] - temp_h_b[::2, 2::2] / 2.
|
| 589 |
+
|
| 590 |
+
# repeating the row before the last row to the bottom so that sampling
|
| 591 |
+
# does not cause any dimension mismatch
|
| 592 |
+
value_S[::2, 1::2] = data[1::2, 1::2] - v[1::2, 1::2] / 2.
|
| 593 |
+
|
| 594 |
+
value_W[::2, 1::2] = data[::2, ::2] + h[::2, ::2] / 2.
|
| 595 |
+
|
| 596 |
+
#== CALCULATE DIRECTIONAL ESTIMATES IN R LOCATIONS
|
| 597 |
+
# repeating the second row at the top of matrix so that sampling does
|
| 598 |
+
# not cause any dimension mismatch, also remove the bottom row
|
| 599 |
+
value_N[1::2, ::2] = data[::2, ::2] + v[::2, ::2] / 2.
|
| 600 |
+
value_E[1::2, ::2] = data[1::2, 1::2] - h[1::2, 1::2] / 2.
|
| 601 |
+
|
| 602 |
+
# repeating the row before the last row to the bottom so that sampling
|
| 603 |
+
# does not cause any dimension mismatch
|
| 604 |
+
temp = np.vstack((data, data[-1]))
|
| 605 |
+
value_S[1::2, ::2] = temp[2::2, ::2] - temp_v_r[2::2, ::2] / 2.
|
| 606 |
+
|
| 607 |
+
# repeating the second column at the left of matrix so that sampling
|
| 608 |
+
# does not cause any dimension mismatch, also remove the rightmost
|
| 609 |
+
# column
|
| 610 |
+
temp = np.delete(np.hstack((np.atleast_2d(data[:, 1]).T, data)), -1, 1)
|
| 611 |
+
value_W[1::2, ::2] = temp[1::2, ::2] + temp_h_r[1::2, ::2] / 2.
|
| 612 |
+
|
| 613 |
+
output = np.zeros(np.shape(data), dtype=np.float32)
|
| 614 |
+
output = np.divide((np.multiply(value_N, weight_N) + \
|
| 615 |
+
np.multiply(value_E, weight_E) + \
|
| 616 |
+
np.multiply(value_S, weight_S) + \
|
| 617 |
+
np.multiply(value_W, weight_W)),\
|
| 618 |
+
(weight_N + weight_E + weight_S + weight_W))
|
| 619 |
+
|
| 620 |
+
output[::2, ::2] = data[::2, ::2]
|
| 621 |
+
output[1::2, 1::2] = data[1::2, 1::2]
|
| 622 |
+
|
| 623 |
+
return output
|
| 624 |
+
|
| 625 |
+
|
| 626 |
+
def fill_br_locations(data, G, bayer_pattern):
|
| 627 |
+
|
| 628 |
+
# Fill up the B/R values interpolated at R/B locations
|
| 629 |
+
B = np.zeros(np.shape(data), dtype=np.float32)
|
| 630 |
+
R = np.zeros(np.shape(data), dtype=np.float32)
|
| 631 |
+
|
| 632 |
+
data = np.asarray(data)
|
| 633 |
+
G = np.asarray(G)
|
| 634 |
+
d1 = np.asarray(signal.convolve2d(data, [[-1, 0, 0],[0, 0, 0], [0, 0, 1]], mode="same", boundary="symm"))
|
| 635 |
+
d2 = np.asarray(signal.convolve2d(data, [[0, 0, 1], [0, 0, 0], [-1, 0, 0]], mode="same", boundary="symm"))
|
| 636 |
+
|
| 637 |
+
df_NE = np.asarray(signal.convolve2d(G, [[0, 0, 0], [0, 1, 0], [-1, 0, 0]], mode="same", boundary="symm"))
|
| 638 |
+
df_SE = np.asarray(signal.convolve2d(G, [[-1, 0, 0], [0, 1, 0], [0, 0, 0]], mode="same", boundary="symm"))
|
| 639 |
+
df_SW = np.asarray(signal.convolve2d(G, [[0, 0, -1], [0, 1, 0], [0, 0, 0]], mode="same", boundary="symm"))
|
| 640 |
+
df_NW = np.asarray(signal.convolve2d(G, [[0, 0, 0], [0, 1, 0], [0, 0, -1]], mode="same", boundary="symm"))
|
| 641 |
+
|
| 642 |
+
weight_NE = np.zeros(np.shape(data), dtype=np.float32)
|
| 643 |
+
weight_SE = np.zeros(np.shape(data), dtype=np.float32)
|
| 644 |
+
weight_SW = np.zeros(np.shape(data), dtype=np.float32)
|
| 645 |
+
weight_NW = np.zeros(np.shape(data), dtype=np.float32)
|
| 646 |
+
|
| 647 |
+
value_NE = np.zeros(np.shape(data), dtype=np.float32)
|
| 648 |
+
value_SE = np.zeros(np.shape(data), dtype=np.float32)
|
| 649 |
+
value_SW = np.zeros(np.shape(data), dtype=np.float32)
|
| 650 |
+
value_NW = np.zeros(np.shape(data), dtype=np.float32)
|
| 651 |
+
|
| 652 |
+
if ((bayer_pattern == "rggb") or (bayer_pattern == "bggr")):
|
| 653 |
+
|
| 654 |
+
#== weights for B in R locations
|
| 655 |
+
weight_NE[::2, ::2] = np.abs(d2[::2, ::2]) + np.abs(df_NE[::2, ::2])
|
| 656 |
+
weight_SE[::2, ::2] = np.abs(d1[::2, ::2]) + np.abs(df_SE[::2, ::2])
|
| 657 |
+
weight_SW[::2, ::2] = np.abs(d2[::2, ::2]) + np.abs(df_SW[::2, ::2])
|
| 658 |
+
weight_NW[::2, ::2] = np.abs(d1[::2, ::2]) + np.abs(df_NW[::2, ::2])
|
| 659 |
+
|
| 660 |
+
#== weights for R in B locations
|
| 661 |
+
weight_NE[1::2, 1::2] = np.abs(d2[1::2, 1::2]) + np.abs(df_NE[1::2, 1::2])
|
| 662 |
+
weight_SE[1::2, 1::2] = np.abs(d1[1::2, 1::2]) + np.abs(df_SE[1::2, 1::2])
|
| 663 |
+
weight_SW[1::2, 1::2] = np.abs(d2[1::2, 1::2]) + np.abs(df_SW[1::2, 1::2])
|
| 664 |
+
weight_NW[1::2, 1::2] = np.abs(d1[1::2, 1::2]) + np.abs(df_NW[1::2, 1::2])
|
| 665 |
+
|
| 666 |
+
weight_NE = np.divide(1., 1. + weight_NE)
|
| 667 |
+
weight_SE = np.divide(1., 1. + weight_SE)
|
| 668 |
+
weight_SW = np.divide(1., 1. + weight_SW)
|
| 669 |
+
weight_NW = np.divide(1., 1. + weight_NW)
|
| 670 |
+
|
| 671 |
+
#== directional estimates of B in R locations
|
| 672 |
+
# repeating the second row at the top of matrix so that sampling does
|
| 673 |
+
# not cause any dimension mismatch, also remove the bottom row
|
| 674 |
+
temp = np.delete(np.vstack((data[1], data)), -1, 0)
|
| 675 |
+
value_NE[::2, ::2] = temp[::2, 1::2] + df_NE[::2, ::2] / 2.
|
| 676 |
+
value_SE[::2, ::2] = data[1::2, 1::2] + df_SE[::2, ::2] / 2.
|
| 677 |
+
# repeating the second column at the left of matrix so that sampling
|
| 678 |
+
# does not cause any dimension mismatch, also remove the rightmost
|
| 679 |
+
# column
|
| 680 |
+
temp = np.delete(np.hstack((np.atleast_2d(data[:, 1]).T, data)), -1, 1)
|
| 681 |
+
value_SW[::2, ::2] = temp[1::2, ::2] + df_SW[::2, ::2] / 2.
|
| 682 |
+
|
| 683 |
+
# repeating the second row at the top of matrix so that sampling does
|
| 684 |
+
# not cause any dimension mismatch, also remove the bottom row
|
| 685 |
+
temp = np.delete(np.vstack((data[1], data)), -1, 0)
|
| 686 |
+
# repeating the second column at the left of matrix so that sampling
|
| 687 |
+
# does not cause any dimension mismatch, also remove the rightmost
|
| 688 |
+
# column
|
| 689 |
+
temp = np.delete(np.hstack((np.atleast_2d(temp[:, 1]).T, temp)), -1, 1)
|
| 690 |
+
value_NW[::2, ::2] = temp[::2, ::2] + df_NW[::2, ::2]
|
| 691 |
+
|
| 692 |
+
#== directional estimates of R in B locations
|
| 693 |
+
# repeating the column before the last to the right so that sampling
|
| 694 |
+
# does not cause any dimension mismatch
|
| 695 |
+
temp = np.hstack((data, np.atleast_2d(data[:, -2]).T))
|
| 696 |
+
value_NE[1::2, 1::2] = temp[::2, 2::2] + df_NE[1::2, 1::2] / 2.
|
| 697 |
+
# repeating the column before the last to the right so that sampling
|
| 698 |
+
# does not cause any dimension mismatch
|
| 699 |
+
temp = np.hstack((data, np.atleast_2d(data[:, -2]).T))
|
| 700 |
+
# repeating the row before the last row to the bottom so that sampling
|
| 701 |
+
# does not cause any dimension mismatch
|
| 702 |
+
temp = np.vstack((temp, temp[-1]))
|
| 703 |
+
value_SE[1::2, 1::2] = temp[2::2, 2::2] + df_SE[1::2, 1::2] / 2.
|
| 704 |
+
# repeating the row before the last row to the bottom so that sampling
|
| 705 |
+
# does not cause any dimension mismatch
|
| 706 |
+
temp = np.vstack((data, data[-1]))
|
| 707 |
+
value_SW[1::2, 1::2] = temp[2::2, ::2] + df_SW[1::2, 1::2] / 2.
|
| 708 |
+
value_NW[1::2, 1::2] = data[::2, ::2] + df_NW[1::2, 1::2] / 2.
|
| 709 |
+
|
| 710 |
+
RB = np.divide(np.multiply(weight_NE, value_NE) + \
|
| 711 |
+
np.multiply(weight_SE, value_SE) + \
|
| 712 |
+
np.multiply(weight_SW, value_SW) + \
|
| 713 |
+
np.multiply(weight_NW, value_NW),\
|
| 714 |
+
(weight_NE + weight_SE + weight_SW + weight_NW))
|
| 715 |
+
|
| 716 |
+
if (bayer_pattern == "rggb"):
|
| 717 |
+
|
| 718 |
+
R[1::2, 1::2] = RB[1::2, 1::2]
|
| 719 |
+
R[::2, ::2] = data[::2, ::2]
|
| 720 |
+
B[::2, ::2] = RB[::2, ::2]
|
| 721 |
+
B[1::2, 1::2] = data[1::2, 1::2]
|
| 722 |
+
|
| 723 |
+
elif (bayer_pattern == "bggr"):
|
| 724 |
+
R[::2, ::2] = RB[::2, ::2]
|
| 725 |
+
R[1::2, 1::2] = data[1::2, 1::2]
|
| 726 |
+
B[1::2, 1::2] = RB[1::2, 1::2]
|
| 727 |
+
B[::2, ::2] = data[::2, ::2]
|
| 728 |
+
|
| 729 |
+
|
| 730 |
+
R[1::2, ::2] = G[1::2, ::2]
|
| 731 |
+
R[::2, 1::2] = G[::2, 1::2]
|
| 732 |
+
R = fill_channel_directional_weight(R, "gbrg")
|
| 733 |
+
|
| 734 |
+
B[1::2, ::2] = G[1::2, ::2]
|
| 735 |
+
B[::2, 1::2] = G[::2, 1::2]
|
| 736 |
+
B = fill_channel_directional_weight(B, "gbrg")
|
| 737 |
+
|
| 738 |
+
|
| 739 |
+
elif ((bayer_pattern == "grbg") or (bayer_pattern == "gbrg")):
|
| 740 |
+
#== weights for B in R locations
|
| 741 |
+
weight_NE[::2, 1::2] = np.abs(d2[::2, 1::2]) + np.abs(df_NE[::2, 1::2])
|
| 742 |
+
weight_SE[::2, 1::2] = np.abs(d1[::2, 1::2]) + np.abs(df_SE[::2, 1::2])
|
| 743 |
+
weight_SW[::2, 1::2] = np.abs(d2[::2, 1::2]) + np.abs(df_SW[::2, 1::2])
|
| 744 |
+
weight_NW[::2, 1::2] = np.abs(d1[::2, 1::2]) + np.abs(df_NW[::2, 1::2])
|
| 745 |
+
|
| 746 |
+
#== weights for R in B locations
|
| 747 |
+
weight_NE[1::2, ::2] = np.abs(d2[1::2, ::2]) + np.abs(df_NE[1::2, ::2])
|
| 748 |
+
weight_SE[1::2, ::2] = np.abs(d1[1::2, ::2]) + np.abs(df_SE[1::2, ::2])
|
| 749 |
+
weight_SW[1::2, ::2] = np.abs(d2[1::2, ::2]) + np.abs(df_SW[1::2, ::2])
|
| 750 |
+
weight_NW[1::2, ::2] = np.abs(d1[1::2, ::2]) + np.abs(df_NW[1::2, ::2])
|
| 751 |
+
|
| 752 |
+
weight_NE = np.divide(1., 1. + weight_NE)
|
| 753 |
+
weight_SE = np.divide(1., 1. + weight_SE)
|
| 754 |
+
weight_SW = np.divide(1., 1. + weight_SW)
|
| 755 |
+
weight_NW = np.divide(1., 1. + weight_NW)
|
| 756 |
+
|
| 757 |
+
#== directional estimates of B in R locations
|
| 758 |
+
# repeating the second row at the top of matrix so that sampling does
|
| 759 |
+
# not cause any dimension mismatch, also remove the bottom row
|
| 760 |
+
temp = np.delete(np.vstack((data[1], data)), -1, 0)
|
| 761 |
+
# repeating the column before the last to the right so that sampling
|
| 762 |
+
# does not cause any dimension mismatch
|
| 763 |
+
temp = np.hstack((temp, np.atleast_2d(temp[:, -2]).T))
|
| 764 |
+
value_NE[::2, 1::2] = temp[::2, 2::2] + df_NE[::2, 1::2] / 2.
|
| 765 |
+
# repeating the column before the last to the right so that sampling
|
| 766 |
+
# does not cause any dimension mismatch
|
| 767 |
+
temp = np.hstack((data, np.atleast_2d(data[:, -2]).T))
|
| 768 |
+
value_SE[::2, 1::2] = temp[1::2, 2::2] + df_SE[::2, 1::2] / 2.
|
| 769 |
+
value_SW[::2, 1::2] = data[1::2, ::2] + df_SW[::2, 1::2] / 2.
|
| 770 |
+
|
| 771 |
+
# repeating the second row at the top of matrix so that sampling does
|
| 772 |
+
# not cause any dimension mismatch, also remove the bottom row
|
| 773 |
+
temp = np.delete(np.vstack((data[1], data)), -1, 0)
|
| 774 |
+
value_NW[::2, 1::2] = temp[::2, ::2] + df_NW[::2, 1::2]
|
| 775 |
+
|
| 776 |
+
#== directional estimates of R in B locations
|
| 777 |
+
value_NE[1::2, ::2] = data[::2, 1::2] + df_NE[1::2, ::2] / 2.
|
| 778 |
+
# repeating the column before the last to the right so that sampling
|
| 779 |
+
# does not cause any dimension mismatch
|
| 780 |
+
temp = np.hstack((data, np.atleast_2d(data[:, -2]).T))
|
| 781 |
+
# repeating the row before the last row to the bottom so that sampling
|
| 782 |
+
# does not cause any dimension mismatch
|
| 783 |
+
temp = np.vstack((temp, temp[-1]))
|
| 784 |
+
value_SE[1::2, ::2] = temp[2::2, 1::2] + df_SE[1::2, ::2] / 2.
|
| 785 |
+
# repeating the row before the last row to the bottom so that sampling
|
| 786 |
+
# does not cause any dimension mismatch
|
| 787 |
+
temp = np.vstack((data, data[-1]))
|
| 788 |
+
# repeating the second column at the left of matrix so that sampling
|
| 789 |
+
# does not cause any dimension mismatch, also remove the rightmost
|
| 790 |
+
# column
|
| 791 |
+
temp = np.delete(np.hstack((np.atleast_2d(temp[:, 1]).T, temp)), -1, 1)
|
| 792 |
+
value_SW[1::2, ::2] = temp[2::2, ::2] + df_SW[1::2, ::2] / 2.
|
| 793 |
+
# repeating the second column at the left of matrix so that sampling
|
| 794 |
+
# does not cause any dimension mismatch, also remove the rightmost
|
| 795 |
+
# column
|
| 796 |
+
temp = np.delete(np.hstack((np.atleast_2d(data[:, 1]).T, data)), -1, 1)
|
| 797 |
+
value_NW[1::2, ::2] = temp[::2, ::2] + df_NW[1::2, ::2] / 2.
|
| 798 |
+
|
| 799 |
+
RB = np.divide(np.multiply(weight_NE, value_NE) + \
|
| 800 |
+
np.multiply(weight_SE, value_SE) + \
|
| 801 |
+
np.multiply(weight_SW, value_SW) + \
|
| 802 |
+
np.multiply(weight_NW, value_NW),\
|
| 803 |
+
(weight_NE + weight_SE + weight_SW + weight_NW))
|
| 804 |
+
|
| 805 |
+
if (bayer_pattern == "grbg"):
|
| 806 |
+
|
| 807 |
+
R[1::2, ::2] = RB[1::2, ::2]
|
| 808 |
+
R[::2, 1::2] = data[::2, 1::2]
|
| 809 |
+
B[::2, 1::2] = RB[::2, 1::2]
|
| 810 |
+
B[1::2, ::2] = data[1::2, ::2]
|
| 811 |
+
|
| 812 |
+
elif (bayer_pattern == "gbrg"):
|
| 813 |
+
R[::2, 1::2] = RB[::2, 1::2]
|
| 814 |
+
R[1::2, ::2] = data[1::2, ::2]
|
| 815 |
+
B[1::2, ::2] = RB[1::2, ::2]
|
| 816 |
+
B[::2, 1::2] = data[::2, 1::2]
|
| 817 |
+
|
| 818 |
+
|
| 819 |
+
R[::2, ::2] = G[::2, ::2]
|
| 820 |
+
R[1::2, 1::2] = G[1::2, 1::2]
|
| 821 |
+
R = fill_channel_directional_weight(R, "rggb")
|
| 822 |
+
|
| 823 |
+
B[1::2, 1::2] = G[1::2, 1::2]
|
| 824 |
+
B[::2, ::2] = G[::2, ::2]
|
| 825 |
+
B = fill_channel_directional_weight(B, "rggb")
|
| 826 |
+
|
| 827 |
+
|
| 828 |
+
return B, R
|
| 829 |
+
|
| 830 |
+
# # =============================================================
|
| 831 |
+
# # function: dbayer_mhc_fast
|
| 832 |
+
# # demosaicing using Malvar-He-Cutler algorithm
|
| 833 |
+
# # http://www.ipol.im/pub/art/2011/g_mhcd/
|
| 834 |
+
# # =============================================================
|
| 835 |
+
# def debayer_mhc_fast(raw, bayer_pattern="rggb", clip_range=[0, 65535], timeshow=False):
|
| 836 |
+
#
|
| 837 |
+
# # convert to float32 in case it was not
|
| 838 |
+
# raw = np.float32(raw)
|
| 839 |
+
#
|
| 840 |
+
# # dimensions
|
| 841 |
+
# width, height = utility.helpers(raw).get_width_height()
|
| 842 |
+
#
|
| 843 |
+
# # allocate space for the R, G, B planes
|
| 844 |
+
# R = np.empty((height, width), dtype = np.float32)
|
| 845 |
+
# G = np.empty((height, width), dtype = np.float32)
|
| 846 |
+
# B = np.empty((height, width), dtype = np.float32)
|
| 847 |
+
#
|
| 848 |
+
# # create a RGB output
|
| 849 |
+
# demosaic_out = np.empty( (height, width, 3), dtype = np.float32 )
|
| 850 |
+
#
|
| 851 |
+
# # define the convolution kernels
|
| 852 |
+
# kernel_g_at_rb = [[0., 0., -1., 0., 0.],\
|
| 853 |
+
# [0., 0., 2., 0., 0.],\
|
| 854 |
+
# [-1., 2., 4., 2., -1.],\
|
| 855 |
+
# [0., 0., 2., 0., 0.],\
|
| 856 |
+
# [0., 0., -1., 0., 0.]] * .125
|
| 857 |
+
#
|
| 858 |
+
# kernel_r_at_gr = [[0., 0., .5, 0., 0.],\
|
| 859 |
+
# [0., -1., 0., -1., 0.],\
|
| 860 |
+
# [-1., 4., 5., 4., -1.],\
|
| 861 |
+
# [0., -1., 0., -1., 0.],\
|
| 862 |
+
# [0., 0., .5, 0., 0.]] * .125
|
| 863 |
+
#
|
| 864 |
+
# kernel_b_at_gr = [[0., 0., -1., 0., 0.],\
|
| 865 |
+
# [0., -1., 4., -1., 0.],\
|
| 866 |
+
# [.5., 0., 5., 0., .5],\
|
| 867 |
+
# [0., -1., 4., -1., 0],\
|
| 868 |
+
# [0., 0., -1., 0., 0.]] * .125
|
| 869 |
+
#
|
| 870 |
+
# kernel_r_at_gb = [[0., 0., -1., 0., 0.],\
|
| 871 |
+
# [0., -1., 4., -1., 0.],\
|
| 872 |
+
# [.5, 0., 5., 0., .5],\
|
| 873 |
+
# [0., -1., 4., -1., 0.],\
|
| 874 |
+
# [0., 0., -1., 0., 0.]] * .125
|
| 875 |
+
#
|
| 876 |
+
# kernel_b_at_gb = [[0., 0., .5, 0., 0.],\
|
| 877 |
+
# [0., -1., 0., -1., 0.],\
|
| 878 |
+
# [-1., 4., 5., 4., -1.],\
|
| 879 |
+
# [0., -1., 0., -1., 0.],\
|
| 880 |
+
# [0., 0., .5, 0., 0.]] * .125
|
| 881 |
+
#
|
| 882 |
+
# kernel_r_at_b = [[0., 0., -1.5, 0., 0.],\
|
| 883 |
+
# [0., 2., 0., 2., 0.],\
|
| 884 |
+
# [-1.5, 0., 6., 0., -1.5],\
|
| 885 |
+
# [0., 2., 0., 2., 0.],\
|
| 886 |
+
# [0., 0., -1.5, 0., 0.]] * .125
|
| 887 |
+
#
|
| 888 |
+
# kernel_b_at_r = [[0., 0., -1.5, 0., 0.],\
|
| 889 |
+
# [0., 2., 0., 2., 0.],\
|
| 890 |
+
# [-1.5, 0., 6., 0., -1.5],\
|
| 891 |
+
# [0., 2., 0., 2., 0.],\
|
| 892 |
+
# [0., 0., -1.5, 0., 0.]] * .125
|
| 893 |
+
#
|
| 894 |
+
#
|
| 895 |
+
#
|
| 896 |
+
# # fill up the directly available values according to the Bayer pattern
|
| 897 |
+
# if (bayer_pattern == "rggb"):
|
| 898 |
+
#
|
| 899 |
+
# G[::2, 1::2] = raw[::2, 1::2]
|
| 900 |
+
# G[1::2, ::2] = raw[1::2, ::2]
|
| 901 |
+
# R[::2, ::2] = raw[::2, ::2]
|
| 902 |
+
# B[1::2, 1::2] = raw[1::2, 1::2]
|
| 903 |
+
#
|
| 904 |
+
# # Green channel
|
| 905 |
+
# for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
|
| 906 |
+
#
|
| 907 |
+
# # to display progress
|
| 908 |
+
# t0 = time.process_time()
|
| 909 |
+
#
|
| 910 |
+
# for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
|
| 911 |
+
#
|
| 912 |
+
# # G at Red location
|
| 913 |
+
# if (((i % 2) == 0) and ((j % 2) == 0)):
|
| 914 |
+
# G[i, j] = 0.125 * np.sum([-1. * R[i-2, j], \
|
| 915 |
+
# 2. * G[i-1, j], \
|
| 916 |
+
# -1. * R[i, j-2], 2. * G[i, j-1], 4. * R[i,j], 2. * G[i, j+1], -1. * R[i, j+2],\
|
| 917 |
+
# 2. * G[i+1, j], \
|
| 918 |
+
# -1. * R[i+2, j]])
|
| 919 |
+
# # G at Blue location
|
| 920 |
+
# elif (((i % 2) != 0) and ((j % 2) != 0)):
|
| 921 |
+
# G[i, j] = 0.125 * np.sum([-1. * B[i-2, j], \
|
| 922 |
+
# 2. * G[i-1, j], \
|
| 923 |
+
# -1. * B[i, j-2], 2. * G[i, j-1], 4. * B[i,j], 2. * G[i, j+1], -1. * B[i, j+2], \
|
| 924 |
+
# 2. * G[i+1, j],\
|
| 925 |
+
# -1. * B[i+2, j]])
|
| 926 |
+
# if (timeshow):
|
| 927 |
+
# elapsed_time = time.process_time() - t0
|
| 928 |
+
# print("Green: row index: " + str(i-1) + " of " + str(height) + \
|
| 929 |
+
# " | elapsed time: " + "{:.3f}".format(elapsed_time) + " seconds")
|
| 930 |
+
#
|
| 931 |
+
# # Red and Blue channel
|
| 932 |
+
# for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
|
| 933 |
+
#
|
| 934 |
+
# # to display progress
|
| 935 |
+
# t0 = time.process_time()
|
| 936 |
+
#
|
| 937 |
+
# for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
|
| 938 |
+
#
|
| 939 |
+
# # Green locations in Red rows
|
| 940 |
+
# if (((i % 2) == 0) and ((j % 2) != 0)):
|
| 941 |
+
# # R at Green locations in Red rows
|
| 942 |
+
# R[i, j] = 0.125 * np.sum([.5 * G[i-2, j],\
|
| 943 |
+
# -1. * G[i-1, j-1], -1. * G[i-1, j+1], \
|
| 944 |
+
# -1. * G[i, j-2], 4. * R[i, j-1], 5. * G[i,j], 4. * R[i, j+1], -1. * G[i, j+2], \
|
| 945 |
+
# -1. * G[i+1, j-1], -1. * G[i+1, j+1], \
|
| 946 |
+
# .5 * G[i+2, j]])
|
| 947 |
+
#
|
| 948 |
+
# # B at Green locations in Red rows
|
| 949 |
+
# B[i, j] = 0.125 * np.sum([-1. * G[i-2, j], \
|
| 950 |
+
# -1. * G[i-1, j-1], 4. * B[i-1, j], -1. * G[i-1, j+1], \
|
| 951 |
+
# .5 * G[i, j-2], 5. * G[i,j], .5 * G[i, j+2], \
|
| 952 |
+
# -1. * G[i+1, j-1], 4. * B[i+1,j], -1. * G[i+1, j+1], \
|
| 953 |
+
# -1. * G[i+2, j]])
|
| 954 |
+
#
|
| 955 |
+
# # Green locations in Blue rows
|
| 956 |
+
# elif (((i % 2) != 0) and ((j % 2) == 0)):
|
| 957 |
+
#
|
| 958 |
+
# # R at Green locations in Blue rows
|
| 959 |
+
# R[i, j] = 0.125 * np.sum([-1. * G[i-2, j], \
|
| 960 |
+
# -1. * G[i-1, j-1], 4. * R[i-1, j], -1. * G[i-1, j+1], \
|
| 961 |
+
# .5 * G[i, j-2], 5. * G[i,j], .5 * G[i, j+2], \
|
| 962 |
+
# -1. * G[i+1, j-1], 4. * R[i+1, j], -1. * G[i+1, j+1], \
|
| 963 |
+
# -1. * G[i+2, j]])
|
| 964 |
+
#
|
| 965 |
+
# # B at Green locations in Blue rows
|
| 966 |
+
# B[i, j] = 0.125 * np.sum([.5 * G[i-2, j], \
|
| 967 |
+
# -1. * G [i-1, j-1], -1. * G[i-1, j+1], \
|
| 968 |
+
# -1. * G[i, j-2], 4. * B[i, j-1], 5. * G[i,j], 4. * B[i, j+1], -1. * G[i, j+2], \
|
| 969 |
+
# -1. * G[i+1, j-1], -1. * G[i+1, j+1], \
|
| 970 |
+
# .5 * G[i+2, j]])
|
| 971 |
+
#
|
| 972 |
+
# # R at Blue locations
|
| 973 |
+
# elif (((i % 2) != 0) and ((j % 2) != 0)):
|
| 974 |
+
# R[i, j] = 0.125 * np.sum([-1.5 * B[i-2, j], \
|
| 975 |
+
# 2. * R[i-1, j-1], 2. * R[i-1, j+1], \
|
| 976 |
+
# -1.5 * B[i, j-2], 6. * B[i,j], -1.5 * B[i, j+2], \
|
| 977 |
+
# 2. * R[i+1, j-1], 2. * R[i+1, j+1], \
|
| 978 |
+
# -1.5 * B[i+2, j]])
|
| 979 |
+
#
|
| 980 |
+
# # B at Red locations
|
| 981 |
+
# elif (((i % 2) == 0) and ((j % 2) == 0)):
|
| 982 |
+
# B[i, j] = 0.125 * np.sum([-1.5 * R[i-2, j], \
|
| 983 |
+
# 2. * B[i-1, j-1], 2. * B[i-1, j+1], \
|
| 984 |
+
# -1.5 * R[i, j-2], 6. * R[i,j], -1.5 * R[i, j+2], \
|
| 985 |
+
# 2. * B[i+1, j-1], 2. * B[i+1, j+1], \
|
| 986 |
+
# -1.5 * R[i+2, j]])
|
| 987 |
+
#
|
| 988 |
+
# if (timeshow):
|
| 989 |
+
# elapsed_time = time.process_time() - t0
|
| 990 |
+
# print("Red/Blue: row index: " + str(i-1) + " of " + str(height) + \
|
| 991 |
+
# " | elapsed time: " + "{:.3f}".format(elapsed_time) + " seconds")
|
| 992 |
+
#
|
| 993 |
+
#
|
| 994 |
+
# elif (bayer_pattern == "gbrg"):
|
| 995 |
+
#
|
| 996 |
+
# G[::2, ::2] = raw[::2, ::2]
|
| 997 |
+
# G[1::2, 1::2] = raw[1::2, 1::2]
|
| 998 |
+
# R[1::2, ::2] = raw[1::2, ::2]
|
| 999 |
+
# B[::2, 1::2] = raw[::2, 1::2]
|
| 1000 |
+
#
|
| 1001 |
+
# # Green channel
|
| 1002 |
+
# for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
|
| 1003 |
+
#
|
| 1004 |
+
# # to display progress
|
| 1005 |
+
# t0 = time.process_time()
|
| 1006 |
+
#
|
| 1007 |
+
# for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
|
| 1008 |
+
#
|
| 1009 |
+
# # G at Red location
|
| 1010 |
+
# if (((i % 2) != 0) and ((j % 2) == 0)):
|
| 1011 |
+
# G[i, j] = 0.125 * np.sum([-1. * R[i-2, j], \
|
| 1012 |
+
# 2. * G[i-1, j], \
|
| 1013 |
+
# -1. * R[i, j-2], 2. * G[i, j-1], 4. * R[i,j], 2. * G[i, j+1], -1. * R[i, j+2],\
|
| 1014 |
+
# 2. * G[i+1, j], \
|
| 1015 |
+
# -1. * R[i+2, j]])
|
| 1016 |
+
# # G at Blue location
|
| 1017 |
+
# elif (((i % 2) == 0) and ((j % 2) != 0)):
|
| 1018 |
+
# G[i, j] = 0.125 * np.sum([-1. * B[i-2, j], \
|
| 1019 |
+
# 2. * G[i-1, j], \
|
| 1020 |
+
# -1. * B[i, j-2], 2. * G[i, j-1], 4. * B[i,j], 2. * G[i, j+1], -1. * B[i, j+2], \
|
| 1021 |
+
# 2. * G[i+1, j],\
|
| 1022 |
+
# -1. * B[i+2, j]])
|
| 1023 |
+
# if (timeshow):
|
| 1024 |
+
# elapsed_time = time.process_time() - t0
|
| 1025 |
+
# print("Green: row index: " + str(i-1) + " of " + str(height) + \
|
| 1026 |
+
# " | elapsed time: " + "{:.3f}".format(elapsed_time) + " seconds")
|
| 1027 |
+
#
|
| 1028 |
+
# # Red and Blue channel
|
| 1029 |
+
# for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
|
| 1030 |
+
#
|
| 1031 |
+
# # to display progress
|
| 1032 |
+
# t0 = time.process_time()
|
| 1033 |
+
#
|
| 1034 |
+
# for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
|
| 1035 |
+
#
|
| 1036 |
+
# # Green locations in Red rows
|
| 1037 |
+
# if (((i % 2) != 0) and ((j % 2) != 0)):
|
| 1038 |
+
# # R at Green locations in Red rows
|
| 1039 |
+
# R[i, j] = 0.125 * np.sum([.5 * G[i-2, j],\
|
| 1040 |
+
# -1. * G[i-1, j-1], -1. * G[i-1, j+1], \
|
| 1041 |
+
# -1. * G[i, j-2], 4. * R[i, j-1], 5. * G[i,j], 4. * R[i, j+1], -1. * G[i, j+2], \
|
| 1042 |
+
# -1. * G[i+1, j-1], -1. * G[i+1, j+1], \
|
| 1043 |
+
# .5 * G[i+2, j]])
|
| 1044 |
+
#
|
| 1045 |
+
# # B at Green locations in Red rows
|
| 1046 |
+
# B[i, j] = 0.125 * np.sum([-1. * G[i-2, j], \
|
| 1047 |
+
# -1. * G[i-1, j-1], 4. * B[i-1, j], -1. * G[i-1, j+1], \
|
| 1048 |
+
# .5 * G[i, j-2], 5. * G[i,j], .5 * G[i, j+2], \
|
| 1049 |
+
# -1. * G[i+1, j-1], 4. * B[i+1,j], -1. * G[i+1, j+1], \
|
| 1050 |
+
# -1. * G[i+2, j]])
|
| 1051 |
+
#
|
| 1052 |
+
# # Green locations in Blue rows
|
| 1053 |
+
# elif (((i % 2) == 0) and ((j % 2) == 0)):
|
| 1054 |
+
#
|
| 1055 |
+
# # R at Green locations in Blue rows
|
| 1056 |
+
# R[i, j] = 0.125 * np.sum([-1. * G[i-2, j], \
|
| 1057 |
+
# -1. * G[i-1, j-1], 4. * R[i-1, j], -1. * G[i-1, j+1], \
|
| 1058 |
+
# .5 * G[i, j-2], 5. * G[i,j], .5 * G[i, j+2], \
|
| 1059 |
+
# -1. * G[i+1, j-1], 4. * R[i+1, j], -1. * G[i+1, j+1], \
|
| 1060 |
+
# -1. * G[i+2, j]])
|
| 1061 |
+
#
|
| 1062 |
+
# # B at Green locations in Blue rows
|
| 1063 |
+
# B[i, j] = 0.125 * np.sum([.5 * G[i-2, j], \
|
| 1064 |
+
# -1. * G [i-1, j-1], -1. * G[i-1, j+1], \
|
| 1065 |
+
# -1. * G[i, j-2], 4. * B[i, j-1], 5. * G[i,j], 4. * B[i, j+1], -1. * G[i, j+2], \
|
| 1066 |
+
# -1. * G[i+1, j-1], -1. * G[i+1, j+1], \
|
| 1067 |
+
# .5 * G[i+2, j]])
|
| 1068 |
+
#
|
| 1069 |
+
# # R at Blue locations
|
| 1070 |
+
# elif (((i % 2) == 0) and ((j % 2) != 0)):
|
| 1071 |
+
# R[i, j] = 0.125 * np.sum([-1.5 * B[i-2, j], \
|
| 1072 |
+
# 2. * R[i-1, j-1], 2. * R[i-1, j+1], \
|
| 1073 |
+
# -1.5 * B[i, j-2], 6. * B[i,j], -1.5 * B[i, j+2], \
|
| 1074 |
+
# 2. * R[i+1, j-1], 2. * R[i+1, j+1], \
|
| 1075 |
+
# -1.5 * B[i+2, j]])
|
| 1076 |
+
#
|
| 1077 |
+
# # B at Red locations
|
| 1078 |
+
# elif (((i % 2) != 0) and ((j % 2) == 0)):
|
| 1079 |
+
# B[i, j] = 0.125 * np.sum([-1.5 * R[i-2, j], \
|
| 1080 |
+
# 2. * B[i-1, j-1], 2. * B[i-1, j+1], \
|
| 1081 |
+
# -1.5 * R[i, j-2], 6. * R[i,j], -1.5 * R[i, j+2], \
|
| 1082 |
+
# 2. * B[i+1, j-1], 2. * B[i+1, j+1], \
|
| 1083 |
+
# -1.5 * R[i+2, j]])
|
| 1084 |
+
#
|
| 1085 |
+
# if (timeshow):
|
| 1086 |
+
# elapsed_time = time.process_time() - t0
|
| 1087 |
+
# print("Red/Blue: row index: " + str(i-1) + " of " + str(height) + \
|
| 1088 |
+
# " | elapsed time: " + "{:.3f}".format(elapsed_time) + " seconds")
|
| 1089 |
+
#
|
| 1090 |
+
# elif (bayer_pattern == "grbg"):
|
| 1091 |
+
#
|
| 1092 |
+
# G[::2, ::2] = raw[::2, ::2]
|
| 1093 |
+
# G[1::2, 1::2] = raw[1::2, 1::2]
|
| 1094 |
+
# R[::2, 1::2] = raw[::2, 1::2]
|
| 1095 |
+
# B[1::2, ::2] = raw[1::2, ::2]
|
| 1096 |
+
#
|
| 1097 |
+
# # Green channel
|
| 1098 |
+
# for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
|
| 1099 |
+
#
|
| 1100 |
+
# # to display progress
|
| 1101 |
+
# t0 = time.process_time()
|
| 1102 |
+
#
|
| 1103 |
+
# for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
|
| 1104 |
+
#
|
| 1105 |
+
# # G at Red location
|
| 1106 |
+
# if (((i % 2) == 0) and ((j % 2) != 0)):
|
| 1107 |
+
# G[i, j] = 0.125 * np.sum([-1. * R[i-2, j], \
|
| 1108 |
+
# 2. * G[i-1, j], \
|
| 1109 |
+
# -1. * R[i, j-2], 2. * G[i, j-1], 4. * R[i,j], 2. * G[i, j+1], -1. * R[i, j+2],\
|
| 1110 |
+
# 2. * G[i+1, j], \
|
| 1111 |
+
# -1. * R[i+2, j]])
|
| 1112 |
+
# # G at Blue location
|
| 1113 |
+
# elif (((i % 2) != 0) and ((j % 2) == 0)):
|
| 1114 |
+
# G[i, j] = 0.125 * np.sum([-1. * B[i-2, j], \
|
| 1115 |
+
# 2. * G[i-1, j], \
|
| 1116 |
+
# -1. * B[i, j-2], 2. * G[i, j-1], 4. * B[i,j], 2. * G[i, j+1], -1. * B[i, j+2], \
|
| 1117 |
+
# 2. * G[i+1, j],\
|
| 1118 |
+
# -1. * B[i+2, j]])
|
| 1119 |
+
# if (timeshow):
|
| 1120 |
+
# elapsed_time = time.process_time() - t0
|
| 1121 |
+
# print("Green: row index: " + str(i-1) + " of " + str(height) + \
|
| 1122 |
+
# " | elapsed time: " + "{:.3f}".format(elapsed_time) + " seconds")
|
| 1123 |
+
#
|
| 1124 |
+
# # Red and Blue channel
|
| 1125 |
+
# for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
|
| 1126 |
+
#
|
| 1127 |
+
# # to display progress
|
| 1128 |
+
# t0 = time.process_time()
|
| 1129 |
+
#
|
| 1130 |
+
# for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
|
| 1131 |
+
#
|
| 1132 |
+
# # Green locations in Red rows
|
| 1133 |
+
# if (((i % 2) == 0) and ((j % 2) == 0)):
|
| 1134 |
+
# # R at Green locations in Red rows
|
| 1135 |
+
# R[i, j] = 0.125 * np.sum([.5 * G[i-2, j],\
|
| 1136 |
+
# -1. * G[i-1, j-1], -1. * G[i-1, j+1], \
|
| 1137 |
+
# -1. * G[i, j-2], 4. * R[i, j-1], 5. * G[i,j], 4. * R[i, j+1], -1. * G[i, j+2], \
|
| 1138 |
+
# -1. * G[i+1, j-1], -1. * G[i+1, j+1], \
|
| 1139 |
+
# .5 * G[i+2, j]])
|
| 1140 |
+
#
|
| 1141 |
+
# # B at Green locations in Red rows
|
| 1142 |
+
# B[i, j] = 0.125 * np.sum([-1. * G[i-2, j], \
|
| 1143 |
+
# -1. * G[i-1, j-1], 4. * B[i-1, j], -1. * G[i-1, j+1], \
|
| 1144 |
+
# .5 * G[i, j-2], 5. * G[i,j], .5 * G[i, j+2], \
|
| 1145 |
+
# -1. * G[i+1, j-1], 4. * B[i+1,j], -1. * G[i+1, j+1], \
|
| 1146 |
+
# -1. * G[i+2, j]])
|
| 1147 |
+
#
|
| 1148 |
+
# # Green locations in Blue rows
|
| 1149 |
+
# elif (((i % 2) != 0) and ((j % 2) != 0)):
|
| 1150 |
+
#
|
| 1151 |
+
# # R at Green locations in Blue rows
|
| 1152 |
+
# R[i, j] = 0.125 * np.sum([-1. * G[i-2, j], \
|
| 1153 |
+
# -1. * G[i-1, j-1], 4. * R[i-1, j], -1. * G[i-1, j+1], \
|
| 1154 |
+
# .5 * G[i, j-2], 5. * G[i,j], .5 * G[i, j+2], \
|
| 1155 |
+
# -1. * G[i+1, j-1], 4. * R[i+1, j], -1. * G[i+1, j+1], \
|
| 1156 |
+
# -1. * G[i+2, j]])
|
| 1157 |
+
#
|
| 1158 |
+
# # B at Green locations in Blue rows
|
| 1159 |
+
# B[i, j] = 0.125 * np.sum([.5 * G[i-2, j], \
|
| 1160 |
+
# -1. * G [i-1, j-1], -1. * G[i-1, j+1], \
|
| 1161 |
+
# -1. * G[i, j-2], 4. * B[i, j-1], 5. * G[i,j], 4. * B[i, j+1], -1. * G[i, j+2], \
|
| 1162 |
+
# -1. * G[i+1, j-1], -1. * G[i+1, j+1], \
|
| 1163 |
+
# .5 * G[i+2, j]])
|
| 1164 |
+
#
|
| 1165 |
+
# # R at Blue locations
|
| 1166 |
+
# elif (((i % 2) != 0) and ((j % 2) == 0)):
|
| 1167 |
+
# R[i, j] = 0.125 * np.sum([-1.5 * B[i-2, j], \
|
| 1168 |
+
# 2. * R[i-1, j-1], 2. * R[i-1, j+1], \
|
| 1169 |
+
# -1.5 * B[i, j-2], 6. * B[i,j], -1.5 * B[i, j+2], \
|
| 1170 |
+
# 2. * R[i+1, j-1], 2. * R[i+1, j+1], \
|
| 1171 |
+
# -1.5 * B[i+2, j]])
|
| 1172 |
+
#
|
| 1173 |
+
# # B at Red locations
|
| 1174 |
+
# elif (((i % 2) == 0) and ((j % 2) != 0)):
|
| 1175 |
+
# B[i, j] = 0.125 * np.sum([-1.5 * R[i-2, j], \
|
| 1176 |
+
# 2. * B[i-1, j-1], 2. * B[i-1, j+1], \
|
| 1177 |
+
# -1.5 * R[i, j-2], 6. * R[i,j], -1.5 * R[i, j+2], \
|
| 1178 |
+
# 2. * B[i+1, j-1], 2. * B[i+1, j+1], \
|
| 1179 |
+
# -1.5 * R[i+2, j]])
|
| 1180 |
+
#
|
| 1181 |
+
# if (timeshow):
|
| 1182 |
+
# elapsed_time = time.process_time() - t0
|
| 1183 |
+
# print("Red/Blue: row index: " + str(i-1) + " of " + str(height) + \
|
| 1184 |
+
# " | elapsed time: " + "{:.3f}".format(elapsed_time) + " seconds")
|
| 1185 |
+
#
|
| 1186 |
+
# elif (bayer_pattern == "bggr"):
|
| 1187 |
+
#
|
| 1188 |
+
# G[::2, 1::2] = raw[::2, 1::2]
|
| 1189 |
+
# G[1::2, ::2] = raw[1::2, ::2]
|
| 1190 |
+
# R[1::2, 1::2] = raw[1::2, 1::2]
|
| 1191 |
+
# B[::2, ::2] = raw[::2, ::2]
|
| 1192 |
+
#
|
| 1193 |
+
# # Green channel
|
| 1194 |
+
# for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
|
| 1195 |
+
#
|
| 1196 |
+
# # to display progress
|
| 1197 |
+
# t0 = time.process_time()
|
| 1198 |
+
#
|
| 1199 |
+
# for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
|
| 1200 |
+
#
|
| 1201 |
+
# # G at Red location
|
| 1202 |
+
# if (((i % 2) != 0) and ((j % 2) != 0)):
|
| 1203 |
+
# G[i, j] = 0.125 * np.sum([-1. * R[i-2, j], \
|
| 1204 |
+
# 2. * G[i-1, j], \
|
| 1205 |
+
# -1. * R[i, j-2], 2. * G[i, j-1], 4. * R[i,j], 2. * G[i, j+1], -1. * R[i, j+2],\
|
| 1206 |
+
# 2. * G[i+1, j], \
|
| 1207 |
+
# -1. * R[i+2, j]])
|
| 1208 |
+
# # G at Blue location
|
| 1209 |
+
# elif (((i % 2) == 0) and ((j % 2) == 0)):
|
| 1210 |
+
# G[i, j] = 0.125 * np.sum([-1. * B[i-2, j], \
|
| 1211 |
+
# 2. * G[i-1, j], \
|
| 1212 |
+
# -1. * B[i, j-2], 2. * G[i, j-1], 4. * B[i,j], 2. * G[i, j+1], -1. * B[i, j+2], \
|
| 1213 |
+
# 2. * G[i+1, j],\
|
| 1214 |
+
# -1. * B[i+2, j]])
|
| 1215 |
+
# if (timeshow):
|
| 1216 |
+
# elapsed_time = time.process_time() - t0
|
| 1217 |
+
# print("Green: row index: " + str(i-1) + " of " + str(height) + \
|
| 1218 |
+
# " | elapsed time: " + "{:.3f}".format(elapsed_time) + " seconds")
|
| 1219 |
+
#
|
| 1220 |
+
# # Red and Blue channel
|
| 1221 |
+
# for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
|
| 1222 |
+
#
|
| 1223 |
+
# # to display progress
|
| 1224 |
+
# t0 = time.process_time()
|
| 1225 |
+
#
|
| 1226 |
+
# for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
|
| 1227 |
+
#
|
| 1228 |
+
# # Green locations in Red rows
|
| 1229 |
+
# if (((i % 2) != 0) and ((j % 2) == 0)):
|
| 1230 |
+
# # R at Green locations in Red rows
|
| 1231 |
+
# R[i, j] = 0.125 * np.sum([.5 * G[i-2, j],\
|
| 1232 |
+
# -1. * G[i-1, j-1], -1. * G[i-1, j+1], \
|
| 1233 |
+
# -1. * G[i, j-2], 4. * R[i, j-1], 5. * G[i,j], 4. * R[i, j+1], -1. * G[i, j+2], \
|
| 1234 |
+
# -1. * G[i+1, j-1], -1. * G[i+1, j+1], \
|
| 1235 |
+
# .5 * G[i+2, j]])
|
| 1236 |
+
#
|
| 1237 |
+
# # B at Green locations in Red rows
|
| 1238 |
+
# B[i, j] = 0.125 * np.sum([-1. * G[i-2, j], \
|
| 1239 |
+
# -1. * G[i-1, j-1], 4. * B[i-1, j], -1. * G[i-1, j+1], \
|
| 1240 |
+
# .5 * G[i, j-2], 5. * G[i,j], .5 * G[i, j+2], \
|
| 1241 |
+
# -1. * G[i+1, j-1], 4. * B[i+1,j], -1. * G[i+1, j+1], \
|
| 1242 |
+
# -1. * G[i+2, j]])
|
| 1243 |
+
#
|
| 1244 |
+
# # Green locations in Blue rows
|
| 1245 |
+
# elif (((i % 2) == 0) and ((j % 2) != 0)):
|
| 1246 |
+
#
|
| 1247 |
+
# # R at Green locations in Blue rows
|
| 1248 |
+
# R[i, j] = 0.125 * np.sum([-1. * G[i-2, j], \
|
| 1249 |
+
# -1. * G[i-1, j-1], 4. * R[i-1, j], -1. * G[i-1, j+1], \
|
| 1250 |
+
# .5 * G[i, j-2], 5. * G[i,j], .5 * G[i, j+2], \
|
| 1251 |
+
# -1. * G[i+1, j-1], 4. * R[i+1, j], -1. * G[i+1, j+1], \
|
| 1252 |
+
# -1. * G[i+2, j]])
|
| 1253 |
+
#
|
| 1254 |
+
# # B at Green locations in Blue rows
|
| 1255 |
+
# B[i, j] = 0.125 * np.sum([.5 * G[i-2, j], \
|
| 1256 |
+
# -1. * G [i-1, j-1], -1. * G[i-1, j+1], \
|
| 1257 |
+
# -1. * G[i, j-2], 4. * B[i, j-1], 5. * G[i,j], 4. * B[i, j+1], -1. * G[i, j+2], \
|
| 1258 |
+
# -1. * G[i+1, j-1], -1. * G[i+1, j+1], \
|
| 1259 |
+
# .5 * G[i+2, j]])
|
| 1260 |
+
#
|
| 1261 |
+
# # R at Blue locations
|
| 1262 |
+
# elif (((i % 2) == 0) and ((j % 2) == 0)):
|
| 1263 |
+
# R[i, j] = 0.125 * np.sum([-1.5 * B[i-2, j], \
|
| 1264 |
+
# 2. * R[i-1, j-1], 2. * R[i-1, j+1], \
|
| 1265 |
+
# -1.5 * B[i, j-2], 6. * B[i,j], -1.5 * B[i, j+2], \
|
| 1266 |
+
# 2. * R[i+1, j-1], 2. * R[i+1, j+1], \
|
| 1267 |
+
# -1.5 * B[i+2, j]])
|
| 1268 |
+
#
|
| 1269 |
+
# # B at Red locations
|
| 1270 |
+
# elif (((i % 2) != 0) and ((j % 2) != 0)):
|
| 1271 |
+
# B[i, j] = 0.125 * np.sum([-1.5 * R[i-2, j], \
|
| 1272 |
+
# 2. * B[i-1, j-1], 2. * B[i-1, j+1], \
|
| 1273 |
+
# -1.5 * R[i, j-2], 6. * R[i,j], -1.5 * R[i, j+2], \
|
| 1274 |
+
# 2. * B[i+1, j-1], 2. * B[i+1, j+1], \
|
| 1275 |
+
# -1.5 * R[i+2, j]])
|
| 1276 |
+
#
|
| 1277 |
+
# if (timeshow):
|
| 1278 |
+
# elapsed_time = time.process_time() - t0
|
| 1279 |
+
# print("Red/Blue: row index: " + str(i-1) + " of " + str(height) + \
|
| 1280 |
+
# " | elapsed time: " + "{:.3f}".format(elapsed_time) + " seconds")
|
| 1281 |
+
#
|
| 1282 |
+
# else:
|
| 1283 |
+
# print("Invalid bayer pattern. Valid pattern can be rggb, gbrg, grbg, bggr")
|
| 1284 |
+
# return demosaic_out # This will be all zeros
|
| 1285 |
+
#
|
| 1286 |
+
# # Fill up the RGB output with interpolated values
|
| 1287 |
+
# demosaic_out[0:height, 0:width, 0] = R[no_of_pixel_pad : height + no_of_pixel_pad, \
|
| 1288 |
+
# no_of_pixel_pad : width + no_of_pixel_pad]
|
| 1289 |
+
# demosaic_out[0:height, 0:width, 1] = G[no_of_pixel_pad : height + no_of_pixel_pad, \
|
| 1290 |
+
# no_of_pixel_pad : width + no_of_pixel_pad]
|
| 1291 |
+
# demosaic_out[0:height, 0:width, 2] = B[no_of_pixel_pad : height + no_of_pixel_pad, \
|
| 1292 |
+
# no_of_pixel_pad : width + no_of_pixel_pad]
|
| 1293 |
+
#
|
| 1294 |
+
# demosaic_out = np.clip(demosaic_out, clip_range[0], clip_range[1])
|
| 1295 |
+
# return demosaic_out
|
IIR-Lab/ISP_pipeline/docker_guidelines.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Final submission docker guidelines
|
| 2 |
+
|
| 3 |
+
All final submissions should be submitted as a docker image. The docker image should be built from the dockerfile in the root of the repository. The docker image should be built with the following command:
|
| 4 |
+
|
| 5 |
+
```bash
|
| 6 |
+
docker build -t <your_team_name> .
|
| 7 |
+
```
|
| 8 |
+
|
| 9 |
+
The docker image should be run with the following command:
|
| 10 |
+
|
| 11 |
+
```bash
|
| 12 |
+
docker run -it --rm -v $(pwd)/data:/data <your_team_name> ./run.sh
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
As output, the docker image should produce images in `JPEG` format in the `/data` directory. All produced files should be named as the input files, but with the `.jpg` extension. The filenames should be the same as the RAW input filenames in `/data`. Make sure that your code does not create any other folders in the `/data` directory. Docker should contain all the necessary dependencies to run the code. It also should include the `run.sh` script as the entrypoint. Take into account that inside the docker image, the `/data` directory will be mounted to the `$(pwd)/data` directory of the host machine. This means that the docker image should be able to read the input files from the `/data` directory and write the output files to the `/data` directory.
|
| 16 |
+
|
| 17 |
+
## Example
|
| 18 |
+
|
| 19 |
+
We providing an example of a docker image that can be used as a reference. It can be found in our [github repository](https://github.com/createcolor/nightimaging23)
|
| 20 |
+
|
| 21 |
+
Your dockerfile may look like this:
|
| 22 |
+
|
| 23 |
+
```dockerfile
|
| 24 |
+
FROM tensorflow/tensorflow:2.3.0
|
| 25 |
+
WORKDIR /opt/app
|
| 26 |
+
COPY . .
|
| 27 |
+
RUN pip install -r /app/requirements.txt
|
| 28 |
+
CMD ["./run.sh"]
|
| 29 |
+
```
|
IIR-Lab/ISP_pipeline/imaging.py
ADDED
|
@@ -0,0 +1,1293 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Note:
|
| 2 |
+
# The functions try to operate in float32 data precision
|
| 3 |
+
|
| 4 |
+
# =============================================================
|
| 5 |
+
# Import the libraries
|
| 6 |
+
# =============================================================
|
| 7 |
+
import numpy as np # array operations
|
| 8 |
+
import math # basing math operations
|
| 9 |
+
from matplotlib import pylab as plt
|
| 10 |
+
import time # measure runtime
|
| 11 |
+
import utility
|
| 12 |
+
import debayer
|
| 13 |
+
import sys # float precision
|
| 14 |
+
from scipy import signal # convolutions
|
| 15 |
+
from scipy import interpolate # for interpolation
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
# =============================================================
|
| 20 |
+
# class: ImageInfo
|
| 21 |
+
# Helps set up necessary information/metadata of the image
|
| 22 |
+
# =============================================================
|
| 23 |
+
class ImageInfo:
|
| 24 |
+
def __init__(self, name = "unknown", data = -1, is_show = False):
|
| 25 |
+
self.name = name
|
| 26 |
+
self.data = data
|
| 27 |
+
self.size = np.shape(self.data)
|
| 28 |
+
self.is_show = is_show
|
| 29 |
+
self.color_space = "unknown"
|
| 30 |
+
self.bayer_pattern = "unknown"
|
| 31 |
+
self.channel_gain = (1.0, 1.0, 1.0, 1.0)
|
| 32 |
+
self.bit_depth = 0
|
| 33 |
+
self.black_level = (0, 0, 0, 0)
|
| 34 |
+
self.white_level = (1, 1, 1, 1)
|
| 35 |
+
self.color_matrix = [[1., .0, .0],\
|
| 36 |
+
[.0, 1., .0],\
|
| 37 |
+
[.0, .0, 1.]] # xyz2cam
|
| 38 |
+
self.min_value = np.min(self.data)
|
| 39 |
+
self.max_value = np.max(self.data)
|
| 40 |
+
self.data_type = self.data.dtype
|
| 41 |
+
|
| 42 |
+
# Display image only isShow = True
|
| 43 |
+
if (self.is_show):
|
| 44 |
+
plt.imshow(self.data)
|
| 45 |
+
plt.show()
|
| 46 |
+
|
| 47 |
+
def set_data(self, data):
|
| 48 |
+
# This function updates data and corresponding fields
|
| 49 |
+
self.data = data
|
| 50 |
+
self.size = np.shape(self.data)
|
| 51 |
+
self.data_type = self.data.dtype
|
| 52 |
+
self.min_value = np.min(self.data)
|
| 53 |
+
self.max_value = np.max(self.data)
|
| 54 |
+
|
| 55 |
+
def get_size(self):
|
| 56 |
+
return self.size
|
| 57 |
+
|
| 58 |
+
def get_width(self):
|
| 59 |
+
return self.size[1]
|
| 60 |
+
|
| 61 |
+
def get_height(self):
|
| 62 |
+
return self.size[0]
|
| 63 |
+
|
| 64 |
+
def get_depth(self):
|
| 65 |
+
if np.ndim(self.data) > 2:
|
| 66 |
+
return self.size[2]
|
| 67 |
+
else:
|
| 68 |
+
return 0
|
| 69 |
+
|
| 70 |
+
def set_color_space(self, color_space):
|
| 71 |
+
self.color_space = color_space
|
| 72 |
+
|
| 73 |
+
def get_color_space(self):
|
| 74 |
+
return self.color_space
|
| 75 |
+
|
| 76 |
+
def set_channel_gain(self, channel_gain):
|
| 77 |
+
self.channel_gain = channel_gain
|
| 78 |
+
|
| 79 |
+
def get_channel_gain(self):
|
| 80 |
+
return self.channel_gain
|
| 81 |
+
|
| 82 |
+
def set_color_matrix(self, color_matrix):
|
| 83 |
+
self.color_matrix = color_matrix
|
| 84 |
+
|
| 85 |
+
def get_color_matrix(self):
|
| 86 |
+
return self.color_matrix
|
| 87 |
+
|
| 88 |
+
def set_bayer_pattern(self, bayer_pattern):
|
| 89 |
+
self.bayer_pattern = bayer_pattern
|
| 90 |
+
|
| 91 |
+
def get_bayer_pattern(self):
|
| 92 |
+
return self.bayer_pattern
|
| 93 |
+
|
| 94 |
+
def set_bit_depth(self, bit_depth):
|
| 95 |
+
self.bit_depth = bit_depth
|
| 96 |
+
|
| 97 |
+
def get_bit_depth(self):
|
| 98 |
+
return self.bit_depth
|
| 99 |
+
|
| 100 |
+
def set_black_level(self, black_level):
|
| 101 |
+
self.black_level = black_level
|
| 102 |
+
|
| 103 |
+
def get_black_level(self):
|
| 104 |
+
return self.black_level
|
| 105 |
+
|
| 106 |
+
def set_white_level(self, white_level):
|
| 107 |
+
self.white_level = white_level
|
| 108 |
+
|
| 109 |
+
def get_white_level(self):
|
| 110 |
+
return self.white_level
|
| 111 |
+
|
| 112 |
+
def get_min_value(self):
|
| 113 |
+
return self.min_value
|
| 114 |
+
|
| 115 |
+
def get_max_value(self):
|
| 116 |
+
return self.max_value
|
| 117 |
+
|
| 118 |
+
def get_data_type(self):
|
| 119 |
+
return self.data_type
|
| 120 |
+
|
| 121 |
+
def __str__(self):
|
| 122 |
+
return "Image " + self.name + " info:" + \
|
| 123 |
+
"\n\tname:\t" + self.name + \
|
| 124 |
+
"\n\tsize:\t" + str(self.size) + \
|
| 125 |
+
"\n\tcolor space:\t" + self.color_space + \
|
| 126 |
+
"\n\tbayer pattern:\t" + self.bayer_pattern + \
|
| 127 |
+
"\n\tchannel gains:\t" + str(self.channel_gain) + \
|
| 128 |
+
"\n\tbit depth:\t" + str(self.bit_depth) + \
|
| 129 |
+
"\n\tdata type:\t" + str(self.data_type) + \
|
| 130 |
+
"\n\tblack level:\t" + str(self.black_level) + \
|
| 131 |
+
"\n\tminimum value:\t" + str(self.min_value) + \
|
| 132 |
+
"\n\tmaximum value:\t" + str(self.max_value)
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
# =============================================================
|
| 136 |
+
# function: black_level_correction
|
| 137 |
+
# subtracts the black level channel wise
|
| 138 |
+
# =============================================================
|
| 139 |
+
def black_level_correction(raw, black_level, white_level, clip_range):
|
| 140 |
+
|
| 141 |
+
print("----------------------------------------------------")
|
| 142 |
+
print("Running black level correction...")
|
| 143 |
+
|
| 144 |
+
# make float32 in case if it was not
|
| 145 |
+
black_level = np.float32(black_level)
|
| 146 |
+
white_level = np.float32(white_level)
|
| 147 |
+
raw = np.float32(raw)
|
| 148 |
+
|
| 149 |
+
# create new data so that original raw data do not change
|
| 150 |
+
data = np.zeros(raw.shape)
|
| 151 |
+
|
| 152 |
+
# bring data in range 0 to 1
|
| 153 |
+
data[::2, ::2] = (raw[::2, ::2] - black_level[0]) / (white_level[0] - black_level[0])
|
| 154 |
+
data[::2, 1::2] = (raw[::2, 1::2] - black_level[1]) / (white_level[1] - black_level[1])
|
| 155 |
+
data[1::2, ::2] = (raw[1::2, ::2] - black_level[2]) / (white_level[2] - black_level[2])
|
| 156 |
+
data[1::2, 1::2] = (raw[1::2, 1::2]- black_level[3]) / (white_level[3] - black_level[3])
|
| 157 |
+
|
| 158 |
+
# bring within the bit depth range
|
| 159 |
+
data = data * clip_range[1]
|
| 160 |
+
|
| 161 |
+
# clip within the range
|
| 162 |
+
data = np.clip(data, clip_range[0], clip_range[1]) # upper level not necessary
|
| 163 |
+
data = np.float32(data)
|
| 164 |
+
|
| 165 |
+
return data
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
# =============================================================
|
| 169 |
+
# function: channel_gain_white_balance
|
| 170 |
+
# multiply with the white balance channel gains
|
| 171 |
+
# =============================================================
|
| 172 |
+
def channel_gain_white_balance(data, channel_gain):
|
| 173 |
+
|
| 174 |
+
print("----------------------------------------------------")
|
| 175 |
+
print("Running channel gain white balance...")
|
| 176 |
+
|
| 177 |
+
# convert into float32 in case they were not
|
| 178 |
+
data = np.float32(data)
|
| 179 |
+
channel_gain = np.float32(channel_gain)
|
| 180 |
+
|
| 181 |
+
# multiply with the channel gains
|
| 182 |
+
data[::2, ::2] = data[::2, ::2] * channel_gain[0]
|
| 183 |
+
data[::2, 1::2] = data[::2, 1::2] * channel_gain[1]
|
| 184 |
+
data[1::2, ::2] = data[1::2, ::2] * channel_gain[2]
|
| 185 |
+
data[1::2, 1::2] = data[1::2, 1::2] * channel_gain[3]
|
| 186 |
+
|
| 187 |
+
# clipping within range
|
| 188 |
+
data = np.clip(data, 0., None) # upper level not necessary
|
| 189 |
+
|
| 190 |
+
return data
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
# =============================================================
|
| 194 |
+
# function: bad_pixel_correction
|
| 195 |
+
# correct for the bad (dead, stuck, or hot) pixels
|
| 196 |
+
# =============================================================
|
| 197 |
+
def bad_pixel_correction(data, neighborhood_size):
|
| 198 |
+
|
| 199 |
+
print("----------------------------------------------------")
|
| 200 |
+
print("Running bad pixel correction...")
|
| 201 |
+
|
| 202 |
+
if ((neighborhood_size % 2) == 0):
|
| 203 |
+
print("neighborhood_size shoud be odd number, recommended value 3")
|
| 204 |
+
return data
|
| 205 |
+
|
| 206 |
+
# convert to float32 in case they were not
|
| 207 |
+
# Being consistent in data format to be float32
|
| 208 |
+
data = np.float32(data)
|
| 209 |
+
|
| 210 |
+
# Separate out the quarter resolution images
|
| 211 |
+
D = {} # Empty dictionary
|
| 212 |
+
D[0] = data[::2, ::2]
|
| 213 |
+
D[1] = data[::2, 1::2]
|
| 214 |
+
D[2] = data[1::2, ::2]
|
| 215 |
+
D[3] = data[1::2, 1::2]
|
| 216 |
+
|
| 217 |
+
# number of pixels to be padded at the borders
|
| 218 |
+
no_of_pixel_pad = math.floor(neighborhood_size / 2.)
|
| 219 |
+
|
| 220 |
+
for idx in range(0, len(D)): # perform same operation for each quarter
|
| 221 |
+
|
| 222 |
+
# display progress
|
| 223 |
+
print("bad pixel correction: Quarter " + str(idx+1) + " of 4")
|
| 224 |
+
|
| 225 |
+
img = D[idx]
|
| 226 |
+
width, height = utility.helpers(img).get_width_height()
|
| 227 |
+
|
| 228 |
+
# pad pixels at the borders
|
| 229 |
+
img = np.pad(img, \
|
| 230 |
+
(no_of_pixel_pad, no_of_pixel_pad),\
|
| 231 |
+
'reflect') # reflect would not repeat the border value
|
| 232 |
+
|
| 233 |
+
for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
|
| 234 |
+
for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
|
| 235 |
+
|
| 236 |
+
# save the middle pixel value
|
| 237 |
+
mid_pixel_val = img[i, j]
|
| 238 |
+
|
| 239 |
+
# extract the neighborhood
|
| 240 |
+
neighborhood = img[i - no_of_pixel_pad : i + no_of_pixel_pad+1,\
|
| 241 |
+
j - no_of_pixel_pad : j + no_of_pixel_pad+1]
|
| 242 |
+
|
| 243 |
+
# set the center pixels value same as the left pixel
|
| 244 |
+
# Does not matter replace with right or left pixel
|
| 245 |
+
# is used to replace the center pixels value
|
| 246 |
+
neighborhood[no_of_pixel_pad, no_of_pixel_pad] = neighborhood[no_of_pixel_pad, no_of_pixel_pad-1]
|
| 247 |
+
|
| 248 |
+
min_neighborhood = np.min(neighborhood)
|
| 249 |
+
max_neighborhood = np.max(neighborhood)
|
| 250 |
+
|
| 251 |
+
if (mid_pixel_val < min_neighborhood):
|
| 252 |
+
img[i,j] = min_neighborhood
|
| 253 |
+
elif (mid_pixel_val > max_neighborhood):
|
| 254 |
+
img[i,j] = max_neighborhood
|
| 255 |
+
else:
|
| 256 |
+
img[i,j] = mid_pixel_val
|
| 257 |
+
|
| 258 |
+
# Put the corrected image to the dictionary
|
| 259 |
+
D[idx] = img[no_of_pixel_pad : height + no_of_pixel_pad,\
|
| 260 |
+
no_of_pixel_pad : width + no_of_pixel_pad]
|
| 261 |
+
|
| 262 |
+
# Regrouping the data
|
| 263 |
+
data[::2, ::2] = D[0]
|
| 264 |
+
data[::2, 1::2] = D[1]
|
| 265 |
+
data[1::2, ::2] = D[2]
|
| 266 |
+
data[1::2, 1::2] = D[3]
|
| 267 |
+
|
| 268 |
+
return data
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
# =============================================================
|
| 272 |
+
# class: demosaic
|
| 273 |
+
# =============================================================
|
| 274 |
+
class demosaic:
|
| 275 |
+
def __init__(self, data, bayer_pattern="rggb", clip_range=[0, 65535], name="demosaic"):
|
| 276 |
+
self.data = np.float32(data)
|
| 277 |
+
self.bayer_pattern = bayer_pattern
|
| 278 |
+
self.clip_range = clip_range
|
| 279 |
+
self.name = name
|
| 280 |
+
|
| 281 |
+
def mhc(self, timeshow=False):
|
| 282 |
+
|
| 283 |
+
print("----------------------------------------------------")
|
| 284 |
+
print("Running demosaicing using Malvar-He-Cutler algorithm...")
|
| 285 |
+
|
| 286 |
+
return debayer.debayer_mhc(self.data, self.bayer_pattern, self.clip_range, timeshow)
|
| 287 |
+
|
| 288 |
+
def post_process_local_color_ratio(self, beta):
|
| 289 |
+
# Objective is to reduce high chroma jump
|
| 290 |
+
# Beta is controlling parameter, higher gives more effect,
|
| 291 |
+
# however, too high does not make any more change
|
| 292 |
+
|
| 293 |
+
print("----------------------------------------------------")
|
| 294 |
+
print("Demosaicing post process using local color ratio...")
|
| 295 |
+
|
| 296 |
+
data = self.data
|
| 297 |
+
|
| 298 |
+
# add beta with the data to prevent divide by zero
|
| 299 |
+
data_beta = self.data + beta
|
| 300 |
+
|
| 301 |
+
# convolution kernels
|
| 302 |
+
# zeta1 averages the up, down, left, and right four values of a 3x3 window
|
| 303 |
+
zeta1 = np.multiply([[0., 1., 0.], [1., 0., 1.], [0., 1., 0.]], .25)
|
| 304 |
+
# zeta2 averages the four corner values of a 3x3 window
|
| 305 |
+
zeta2 = np.multiply([[1., 0., 1.], [0., 0., 0.], [1., 0., 1.]], .25)
|
| 306 |
+
|
| 307 |
+
# average of color ratio
|
| 308 |
+
g_over_b = signal.convolve2d(np.divide(data_beta[:, :, 1], data_beta[:, :, 2]), zeta1, mode="same", boundary="symm")
|
| 309 |
+
g_over_r = signal.convolve2d(np.divide(data_beta[:, :, 1], data_beta[:, :, 0]), zeta1, mode="same", boundary="symm")
|
| 310 |
+
b_over_g_zeta2 = signal.convolve2d(np.divide(data_beta[:, :, 2], data_beta[:, :, 1]), zeta2, mode="same", boundary="symm")
|
| 311 |
+
r_over_g_zeta2 = signal.convolve2d(np.divide(data_beta[:, :, 0], data_beta[:, :, 1]), zeta2, mode="same", boundary="symm")
|
| 312 |
+
b_over_g_zeta1 = signal.convolve2d(np.divide(data_beta[:, :, 2], data_beta[:, :, 1]), zeta1, mode="same", boundary="symm")
|
| 313 |
+
r_over_g_zeta1 = signal.convolve2d(np.divide(data_beta[:, :, 0], data_beta[:, :, 1]), zeta1, mode="same", boundary="symm")
|
| 314 |
+
|
| 315 |
+
# G at B locations and G at R locations
|
| 316 |
+
if self.bayer_pattern == "rggb":
|
| 317 |
+
# G at B locations
|
| 318 |
+
data[1::2, 1::2, 1] = -beta + np.multiply(data_beta[1::2, 1::2, 2], g_over_b[1::2, 1::2])
|
| 319 |
+
# G at R locations
|
| 320 |
+
data[::2, ::2, 1] = -beta + np.multiply(data_beta[::2, ::2, 0], g_over_r[::2, ::2])
|
| 321 |
+
# B at R locations
|
| 322 |
+
data[::2, ::2, 2] = -beta + np.multiply(data_beta[::2, ::2, 1], b_over_g_zeta2[::2, ::2])
|
| 323 |
+
# R at B locations
|
| 324 |
+
data[1::2, 1::2, 0] = -beta + np.multiply(data_beta[1::2, 1::2, 1], r_over_g_zeta2[1::2, 1::2])
|
| 325 |
+
# B at G locations
|
| 326 |
+
data[::2, 1::2, 2] = -beta + np.multiply(data_beta[::2, 1::2, 1], b_over_g_zeta1[::2, 1::2])
|
| 327 |
+
data[1::2, ::2, 2] = -beta + np.multiply(data_beta[1::2, ::2, 1], b_over_g_zeta1[1::2, ::2])
|
| 328 |
+
# R at G locations
|
| 329 |
+
data[::2, 1::2, 0] = -beta + np.multiply(data_beta[::2, 1::2, 1], r_over_g_zeta1[::2, 1::2])
|
| 330 |
+
data[1::2, ::2, 0] = -beta + np.multiply(data_beta[1::2, ::2, 1], r_over_g_zeta1[1::2, ::2])
|
| 331 |
+
|
| 332 |
+
elif self.bayer_pattern == "grbg":
|
| 333 |
+
# G at B locations
|
| 334 |
+
data[1::2, ::2, 1] = -beta + np.multiply(data_beta[1::2, ::2, 2], g_over_b[1::2, 1::2])
|
| 335 |
+
# G at R locations
|
| 336 |
+
data[::2, 1::2, 1] = -beta + np.multiply(data_beta[::2, 1::2, 0], g_over_r[::2, 1::2])
|
| 337 |
+
# B at R locations
|
| 338 |
+
data[::2, 1::2, 2] = -beta + np.multiply(data_beta[::2, 1::2, 1], b_over_g_zeta2[::2, 1::2])
|
| 339 |
+
# R at B locations
|
| 340 |
+
data[1::2, ::2, 0] = -beta + np.multiply(data_beta[1::2, ::2, 1], r_over_g_zeta2[1::2, ::2])
|
| 341 |
+
# B at G locations
|
| 342 |
+
data[::2, ::2, 2] = -beta + np.multiply(data_beta[::2, ::2, 1], b_over_g_zeta1[::2, ::2])
|
| 343 |
+
data[1::2, 1::2, 2] = -beta + np.multiply(data_beta[1::2, 1::2, 1], b_over_g_zeta1[1::2, 1::2])
|
| 344 |
+
# R at G locations
|
| 345 |
+
data[::2, ::2, 0] = -beta + np.multiply(data_beta[::2, ::2, 1], r_over_g_zeta1[::2, ::2])
|
| 346 |
+
data[1::2, 1::2, 0] = -beta + np.multiply(data_beta[1::2, 1::2, 1], r_over_g_zeta1[1::2, 1::2])
|
| 347 |
+
|
| 348 |
+
elif self.bayer_pattern == "gbrg":
|
| 349 |
+
# G at B locations
|
| 350 |
+
data[::2, 1::2, 1] = -beta + np.multiply(data_beta[::2, 1::2, 2], g_over_b[::2, 1::2])
|
| 351 |
+
# G at R locations
|
| 352 |
+
data[1::2, ::2, 1] = -beta + np.multiply(data_beta[1::2, ::2, 0], g_over_r[1::2, ::2])
|
| 353 |
+
# B at R locations
|
| 354 |
+
data[1::2, ::2, 2] = -beta + np.multiply(data_beta[1::2, ::2, 1], b_over_g_zeta2[1::2, ::2])
|
| 355 |
+
# R at B locations
|
| 356 |
+
data[::2, 1::2, 0] = -beta + np.multiply(data_beta[::2, 1::2, 1], r_over_g_zeta2[::2, 1::2])
|
| 357 |
+
# B at G locations
|
| 358 |
+
data[::2, ::2, 2] = -beta + np.multiply(data_beta[::2, ::2, 1], b_over_g_zeta1[::2, ::2])
|
| 359 |
+
data[1::2, 1::2, 2] = -beta + np.multiply(data_beta[1::2, 1::2, 1], b_over_g_zeta1[1::2, 1::2])
|
| 360 |
+
# R at G locations
|
| 361 |
+
data[::2, ::2, 0] = -beta + np.multiply(data_beta[::2, ::2, 1], r_over_g_zeta1[::2, ::2])
|
| 362 |
+
data[1::2, 1::2, 0] = -beta + np.multiply(data_beta[1::2, 1::2, 1], r_over_g_zeta1[1::2, 1::2])
|
| 363 |
+
|
| 364 |
+
elif self.bayer_pattern == "bggr":
|
| 365 |
+
# G at B locations
|
| 366 |
+
data[::2, ::2, 1] = -beta + np.multiply(data_beta[::2, ::2, 2], g_over_b[::2, ::2])
|
| 367 |
+
# G at R locations
|
| 368 |
+
data[1::2, 1::2, 1] = -beta + np.multiply(data_beta[1::2, 1::2, 0], g_over_r[1::2, 1::2])
|
| 369 |
+
# B at R locations
|
| 370 |
+
data[1::2, 1::2, 2] = -beta + np.multiply(data_beta[1::2, 1::2, 1], b_over_g_zeta2[1::2, 1::2])
|
| 371 |
+
# R at B locations
|
| 372 |
+
data[::2, ::2, 0] = -beta + np.multiply(data_beta[::2, ::2, 1], r_over_g_zeta2[::2, ::2])
|
| 373 |
+
# B at G locations
|
| 374 |
+
data[::2, 1::2, 2] = -beta + np.multiply(data_beta[::2, 1::2, 1], b_over_g_zeta1[::2, 1::2])
|
| 375 |
+
data[1::2, ::2, 2] = -beta + np.multiply(data_beta[1::2, ::2, 1], b_over_g_zeta1[1::2, ::2])
|
| 376 |
+
# R at G locations
|
| 377 |
+
data[::2, 1::2, 0] = -beta + np.multiply(data_beta[::2, 1::2, 1], r_over_g_zeta1[::2, 1::2])
|
| 378 |
+
data[1::2, ::2, 0] = -beta + np.multiply(data_beta[1::2, ::2, 1], r_over_g_zeta1[1::2, ::2])
|
| 379 |
+
|
| 380 |
+
|
| 381 |
+
return np.clip(data, self.clip_range[0], self.clip_range[1])
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
def directionally_weighted_gradient_based_interpolation(self):
|
| 385 |
+
# Reference:
|
| 386 |
+
# http://www.arl.army.mil/arlreports/2010/ARL-TR-5061.pdf
|
| 387 |
+
|
| 388 |
+
print("----------------------------------------------------")
|
| 389 |
+
print("Running demosaicing using directionally weighted gradient based interpolation...")
|
| 390 |
+
|
| 391 |
+
# Fill up the green channel
|
| 392 |
+
G = debayer.fill_channel_directional_weight(self.data, self.bayer_pattern)
|
| 393 |
+
|
| 394 |
+
B, R = debayer.fill_br_locations(self.data, G, self.bayer_pattern)
|
| 395 |
+
|
| 396 |
+
width, height = utility.helpers(self.data).get_width_height()
|
| 397 |
+
output = np.empty((height, width, 3), dtype=np.float32)
|
| 398 |
+
output[:, :, 0] = R
|
| 399 |
+
output[:, :, 1] = G
|
| 400 |
+
output[:, :, 2] = B
|
| 401 |
+
|
| 402 |
+
return np.clip(output, self.clip_range[0], self.clip_range[1])
|
| 403 |
+
|
| 404 |
+
|
| 405 |
+
def post_process_median_filter(self, edge_detect_kernel_size=3, edge_threshold=0, median_filter_kernel_size=3, clip_range=[0, 65535]):
|
| 406 |
+
# Objective is to reduce the zipper effect around the edges
|
| 407 |
+
# Inputs:
|
| 408 |
+
# edge_detect_kernel_size: the neighborhood size used to detect edges
|
| 409 |
+
# edge_threshold: the threshold value above which (compared against)
|
| 410 |
+
# the gradient_magnitude to declare if it is an edge
|
| 411 |
+
# median_filter_kernel_size: the neighborhood size used to perform
|
| 412 |
+
# median filter operation
|
| 413 |
+
# clip_range: used for scaling in edge_detection
|
| 414 |
+
#
|
| 415 |
+
# Output:
|
| 416 |
+
# output: median filtered output around the edges
|
| 417 |
+
# edge_location: a debug image to see where the edges were detected
|
| 418 |
+
# based on the threshold
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
# detect edge locations
|
| 422 |
+
edge_location = utility.edge_detection(self.data).sobel(edge_detect_kernel_size, "is_edge", edge_threshold, clip_range)
|
| 423 |
+
|
| 424 |
+
# allocate space for output
|
| 425 |
+
output = np.empty(np.shape(self.data), dtype=np.float32)
|
| 426 |
+
|
| 427 |
+
if (np.ndim(self.data) > 2):
|
| 428 |
+
|
| 429 |
+
for i in range(0, np.shape(self.data)[2]):
|
| 430 |
+
output[:, :, i] = utility.helpers(self.data[:, :, i]).edge_wise_median(median_filter_kernel_size, edge_location[:, :, i])
|
| 431 |
+
|
| 432 |
+
elif (np.ndim(self.data) == 2):
|
| 433 |
+
output = utility.helpers(self.data).edge_wise_median(median_filter_kernel_size, edge_location)
|
| 434 |
+
|
| 435 |
+
return output, edge_location
|
| 436 |
+
|
| 437 |
+
def __str__(self):
|
| 438 |
+
return self.name
|
| 439 |
+
|
| 440 |
+
|
| 441 |
+
# =============================================================
|
| 442 |
+
# class: lens_shading_correction
|
| 443 |
+
# Correct the lens shading / vignetting
|
| 444 |
+
# =============================================================
|
| 445 |
+
class lens_shading_correction:
|
| 446 |
+
def __init__(self, data, name="lens_shading_correction"):
|
| 447 |
+
# convert to float32 in case it was not
|
| 448 |
+
self.data = np.float32(data)
|
| 449 |
+
self.name = name
|
| 450 |
+
|
| 451 |
+
def flat_field_compensation(self, dark_current_image, flat_field_image):
|
| 452 |
+
# dark_current_image:
|
| 453 |
+
# is captured from the camera with cap on
|
| 454 |
+
# and fully dark condition, several images captured and
|
| 455 |
+
# temporally averaged
|
| 456 |
+
# flat_field_image:
|
| 457 |
+
# is found by capturing an image of a flat field test chart
|
| 458 |
+
# with certain lighting condition
|
| 459 |
+
# Note: flat_field_compensation is memory intensive procedure because
|
| 460 |
+
# both the dark_current_image and flat_field_image need to be
|
| 461 |
+
# saved in memory beforehand
|
| 462 |
+
print("----------------------------------------------------")
|
| 463 |
+
print("Running lens shading correction with flat field compensation...")
|
| 464 |
+
|
| 465 |
+
# convert to float32 in case it was not
|
| 466 |
+
dark_current_image = np.float32(dark_current_image)
|
| 467 |
+
flat_field_image = np.float32(flat_field_image)
|
| 468 |
+
temp = flat_field_image - dark_current_image
|
| 469 |
+
return np.average(temp) * np.divide((self.data - dark_current_image), temp)
|
| 470 |
+
|
| 471 |
+
def approximate_mathematical_compensation(self, params, clip_min=0, clip_max=65535):
|
| 472 |
+
# parms:
|
| 473 |
+
# parameters of a parabolic model y = a*(x-b)^2 + c
|
| 474 |
+
# For example, params = [0.01759, -28.37, -13.36]
|
| 475 |
+
# Note: approximate_mathematical_compensation require less memory
|
| 476 |
+
print("----------------------------------------------------")
|
| 477 |
+
print("Running lens shading correction with approximate mathematical compensation...")
|
| 478 |
+
width, height = utility.helpers(self.data).get_width_height()
|
| 479 |
+
|
| 480 |
+
center_pixel_pos = [height/2, width/2]
|
| 481 |
+
max_distance = utility.distance_euclid(center_pixel_pos, [height, width])
|
| 482 |
+
|
| 483 |
+
# allocate memory for output
|
| 484 |
+
temp = np.empty((height, width), dtype=np.float32)
|
| 485 |
+
|
| 486 |
+
for i in range(0, height):
|
| 487 |
+
for j in range(0, width):
|
| 488 |
+
distance = utility.distance_euclid(center_pixel_pos, [i, j]) / max_distance
|
| 489 |
+
# parabolic model
|
| 490 |
+
gain = params[0] * (distance - params[1])**2 + params[2]
|
| 491 |
+
temp[i, j] = self.data[i, j] * gain
|
| 492 |
+
|
| 493 |
+
temp = np.clip(temp, clip_min, clip_max)
|
| 494 |
+
return temp
|
| 495 |
+
|
| 496 |
+
def __str__(self):
|
| 497 |
+
return "lens shading correction. There are two methods: " + \
|
| 498 |
+
"\n (1) flat_field_compensation: requires dark_current_image and flat_field_image" + \
|
| 499 |
+
"\n (2) approximate_mathematical_compensation:"
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
# =============================================================
|
| 503 |
+
# class: lens_shading_correction
|
| 504 |
+
# Correct the lens shading / vignetting
|
| 505 |
+
# =============================================================
|
| 506 |
+
class bayer_denoising:
|
| 507 |
+
def __init__(self, data, name="bayer_denoising"):
|
| 508 |
+
# convert to float32 in case it was not
|
| 509 |
+
self.data = np.float32(data)
|
| 510 |
+
self.name = name
|
| 511 |
+
|
| 512 |
+
def utilize_hvs_behavior(self, bayer_pattern, initial_noise_level, hvs_min, hvs_max, threshold_red_blue, clip_range):
|
| 513 |
+
# Objective: bayer denoising
|
| 514 |
+
# Inputs:
|
| 515 |
+
# bayer_pattern: rggb, gbrg, grbg, bggr
|
| 516 |
+
# initial_noise_level:
|
| 517 |
+
# Output:
|
| 518 |
+
# denoised bayer raw output
|
| 519 |
+
# Source: Based on paper titled "Noise Reduction for CFA Image Sensors
|
| 520 |
+
# Exploiting HVS Behaviour," by Angelo Bosco, Sebastiano Battiato,
|
| 521 |
+
# Arcangelo Bruna and Rosetta Rizzo
|
| 522 |
+
# Sensors 2009, 9, 1692-1713; doi:10.3390/s90301692
|
| 523 |
+
|
| 524 |
+
print("----------------------------------------------------")
|
| 525 |
+
print("Running bayer denoising utilizing hvs behavior...")
|
| 526 |
+
|
| 527 |
+
# copy the self.data to raw and we will only work on raw
|
| 528 |
+
# to make sure no change happen to self.data
|
| 529 |
+
raw = self.data
|
| 530 |
+
raw = np.clip(raw, clip_range[0], clip_range[1])
|
| 531 |
+
width, height = utility.helpers(raw).get_width_height()
|
| 532 |
+
|
| 533 |
+
# First make the bayer_pattern rggb
|
| 534 |
+
# The algorithm is written only for rggb pattern, thus convert all other
|
| 535 |
+
# pattern to rggb. Furthermore, this shuffling does not affect the
|
| 536 |
+
# algorithm output
|
| 537 |
+
if (bayer_pattern != "rggb"):
|
| 538 |
+
raw = utility.helpers(self.data).shuffle_bayer_pattern(bayer_pattern, "rggb")
|
| 539 |
+
|
| 540 |
+
# fixed neighborhood_size
|
| 541 |
+
neighborhood_size = 5 # we are keeping this fixed
|
| 542 |
+
# bigger size such as 9 can be declared
|
| 543 |
+
# however, the code need to be changed then
|
| 544 |
+
|
| 545 |
+
# pad two pixels at the border
|
| 546 |
+
no_of_pixel_pad = math.floor(neighborhood_size / 2) # number of pixels to pad
|
| 547 |
+
|
| 548 |
+
raw = np.pad(raw, \
|
| 549 |
+
(no_of_pixel_pad, no_of_pixel_pad),\
|
| 550 |
+
'reflect') # reflect would not repeat the border value
|
| 551 |
+
|
| 552 |
+
# allocating space for denoised output
|
| 553 |
+
denoised_out = np.empty((height, width), dtype=np.float32)
|
| 554 |
+
|
| 555 |
+
texture_degree_debug = np.empty((height, width), dtype=np.float32)
|
| 556 |
+
for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
|
| 557 |
+
for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
|
| 558 |
+
|
| 559 |
+
# center pixel
|
| 560 |
+
center_pixel = raw[i, j]
|
| 561 |
+
|
| 562 |
+
# signal analyzer block
|
| 563 |
+
half_max = clip_range[1] / 2
|
| 564 |
+
if (center_pixel <= half_max):
|
| 565 |
+
hvs_weight = -(((hvs_max - hvs_min) * center_pixel) / half_max) + hvs_max
|
| 566 |
+
else:
|
| 567 |
+
hvs_weight = (((center_pixel - clip_range[1]) * (hvs_max - hvs_min))/(clip_range[1] - half_max)) + hvs_max
|
| 568 |
+
|
| 569 |
+
# noise level estimator previous value
|
| 570 |
+
if (j < no_of_pixel_pad+2):
|
| 571 |
+
noise_level_previous_red = initial_noise_level
|
| 572 |
+
noise_level_previous_blue = initial_noise_level
|
| 573 |
+
noise_level_previous_green = initial_noise_level
|
| 574 |
+
else:
|
| 575 |
+
noise_level_previous_green = noise_level_current_green
|
| 576 |
+
if ((i % 2) == 0): # red
|
| 577 |
+
noise_level_previous_red = noise_level_current_red
|
| 578 |
+
elif ((i % 2) != 0): # blue
|
| 579 |
+
noise_level_previous_blue = noise_level_current_blue
|
| 580 |
+
|
| 581 |
+
# Processings depending on Green or Red/Blue
|
| 582 |
+
# Red
|
| 583 |
+
if (((i % 2) == 0) and ((j % 2) == 0)):
|
| 584 |
+
# get neighborhood
|
| 585 |
+
neighborhood = [raw[i-2, j-2], raw[i-2, j], raw[i-2, j+2],\
|
| 586 |
+
raw[i, j-2], raw[i, j+2],\
|
| 587 |
+
raw[i+2, j-2], raw[i+2, j], raw[i+2, j+2]]
|
| 588 |
+
|
| 589 |
+
# absolute difference from the center pixel
|
| 590 |
+
d = np.abs(neighborhood - center_pixel)
|
| 591 |
+
|
| 592 |
+
# maximum and minimum difference
|
| 593 |
+
d_max = np.max(d)
|
| 594 |
+
d_min = np.min(d)
|
| 595 |
+
|
| 596 |
+
# calculate texture_threshold
|
| 597 |
+
texture_threshold = hvs_weight + noise_level_previous_red
|
| 598 |
+
|
| 599 |
+
# texture degree analyzer
|
| 600 |
+
if (d_max <= threshold_red_blue):
|
| 601 |
+
texture_degree = 1.
|
| 602 |
+
elif ((d_max > threshold_red_blue) and (d_max <= texture_threshold)):
|
| 603 |
+
texture_degree = -((d_max - threshold_red_blue) / (texture_threshold - threshold_red_blue)) + 1.
|
| 604 |
+
elif (d_max > texture_threshold):
|
| 605 |
+
texture_degree = 0.
|
| 606 |
+
|
| 607 |
+
# noise level estimator update
|
| 608 |
+
noise_level_current_red = texture_degree * d_max + (1 - texture_degree) * noise_level_previous_red
|
| 609 |
+
|
| 610 |
+
# Blue
|
| 611 |
+
elif (((i % 2) != 0) and ((j % 2) != 0)):
|
| 612 |
+
|
| 613 |
+
# get neighborhood
|
| 614 |
+
neighborhood = [raw[i-2, j-2], raw[i-2, j], raw[i-2, j+2],\
|
| 615 |
+
raw[i, j-2], raw[i, j+2],\
|
| 616 |
+
raw[i+2, j-2], raw[i+2, j], raw[i+2, j+2]]
|
| 617 |
+
|
| 618 |
+
# absolute difference from the center pixel
|
| 619 |
+
d = np.abs(neighborhood - center_pixel)
|
| 620 |
+
|
| 621 |
+
# maximum and minimum difference
|
| 622 |
+
d_max = np.max(d)
|
| 623 |
+
d_min = np.min(d)
|
| 624 |
+
|
| 625 |
+
# calculate texture_threshold
|
| 626 |
+
texture_threshold = hvs_weight + noise_level_previous_blue
|
| 627 |
+
|
| 628 |
+
# texture degree analyzer
|
| 629 |
+
if (d_max <= threshold_red_blue):
|
| 630 |
+
texture_degree = 1.
|
| 631 |
+
elif ((d_max > threshold_red_blue) and (d_max <= texture_threshold)):
|
| 632 |
+
texture_degree = -((d_max - threshold_red_blue) / (texture_threshold - threshold_red_blue)) + 1.
|
| 633 |
+
elif (d_max > texture_threshold):
|
| 634 |
+
texture_degree = 0.
|
| 635 |
+
|
| 636 |
+
# noise level estimator update
|
| 637 |
+
noise_level_current_blue = texture_degree * d_max + (1 - texture_degree) * noise_level_previous_blue
|
| 638 |
+
|
| 639 |
+
# Green
|
| 640 |
+
elif ((((i % 2) == 0) and ((j % 2) != 0)) or (((i % 2) != 0) and ((j % 2) == 0))):
|
| 641 |
+
|
| 642 |
+
neighborhood = [raw[i-2, j-2], raw[i-2, j], raw[i-2, j+2],\
|
| 643 |
+
raw[i-1, j-1], raw[i-1, j+1],\
|
| 644 |
+
raw[i, j-2], raw[i, j+2],\
|
| 645 |
+
raw[i+1, j-1], raw[i+1, j+1],\
|
| 646 |
+
raw[i+2, j-2], raw[i+2, j], raw[i+2, j+2]]
|
| 647 |
+
|
| 648 |
+
# difference from the center pixel
|
| 649 |
+
d = np.abs(neighborhood - center_pixel)
|
| 650 |
+
|
| 651 |
+
# maximum and minimum difference
|
| 652 |
+
d_max = np.max(d)
|
| 653 |
+
d_min = np.min(d)
|
| 654 |
+
|
| 655 |
+
# calculate texture_threshold
|
| 656 |
+
texture_threshold = hvs_weight + noise_level_previous_green
|
| 657 |
+
|
| 658 |
+
# texture degree analyzer
|
| 659 |
+
if (d_max == 0):
|
| 660 |
+
texture_degree = 1
|
| 661 |
+
elif ((d_max > 0) and (d_max <= texture_threshold)):
|
| 662 |
+
texture_degree = -(d_max / texture_threshold) + 1.
|
| 663 |
+
elif (d_max > texture_threshold):
|
| 664 |
+
texture_degree = 0
|
| 665 |
+
|
| 666 |
+
# noise level estimator update
|
| 667 |
+
noise_level_current_green = texture_degree * d_max + (1 - texture_degree) * noise_level_previous_green
|
| 668 |
+
|
| 669 |
+
# similarity threshold calculation
|
| 670 |
+
if (texture_degree == 1):
|
| 671 |
+
threshold_low = threshold_high = d_max
|
| 672 |
+
elif (texture_degree == 0):
|
| 673 |
+
threshold_low = d_min
|
| 674 |
+
threshold_high = (d_max + d_min) / 2
|
| 675 |
+
elif ((texture_degree > 0) and (texture_degree < 1)):
|
| 676 |
+
threshold_high = (d_max + ((d_max + d_min) / 2)) / 2
|
| 677 |
+
threshold_low = (d_min + threshold_high) / 2
|
| 678 |
+
|
| 679 |
+
# weight computation
|
| 680 |
+
weight = np.empty(np.size(d), dtype=np.float32)
|
| 681 |
+
pf = 0.
|
| 682 |
+
for w_i in range(0, np.size(d)):
|
| 683 |
+
if (d[w_i] <= threshold_low):
|
| 684 |
+
weight[w_i] = 1.
|
| 685 |
+
elif (d[w_i] > threshold_high):
|
| 686 |
+
weight[w_i] = 0.
|
| 687 |
+
elif ((d[w_i] > threshold_low) and (d[w_i] < threshold_high)):
|
| 688 |
+
weight[w_i] = 1. + ((d[w_i] - threshold_low) / (threshold_low - threshold_high))
|
| 689 |
+
|
| 690 |
+
pf += weight[w_i] * neighborhood[w_i] + (1. - weight[w_i]) * center_pixel
|
| 691 |
+
|
| 692 |
+
denoised_out[i - no_of_pixel_pad, j-no_of_pixel_pad] = pf / np.size(d)
|
| 693 |
+
# texture_degree_debug is a debug output
|
| 694 |
+
texture_degree_debug[i - no_of_pixel_pad, j-no_of_pixel_pad] = texture_degree
|
| 695 |
+
|
| 696 |
+
if (bayer_pattern != "rggb"):
|
| 697 |
+
denoised_out = utility.shuffle_bayer_pattern(denoised_out, "rggb", bayer_pattern)
|
| 698 |
+
|
| 699 |
+
return np.clip(denoised_out, clip_range[0], clip_range[1]), texture_degree_debug
|
| 700 |
+
|
| 701 |
+
def __str__(self):
|
| 702 |
+
return self.name
|
| 703 |
+
|
| 704 |
+
|
| 705 |
+
# =============================================================
|
| 706 |
+
# class: color_correction
|
| 707 |
+
# Correct the color in linaer domain
|
| 708 |
+
# =============================================================
|
| 709 |
+
class color_correction:
|
| 710 |
+
def __init__(self, data, color_matrix, color_space="srgb", illuminant="d65", name="color correction", clip_range=[0, 65535]):
|
| 711 |
+
# Inputs:
|
| 712 |
+
# data: linear rgb image before nonlinearity/gamma
|
| 713 |
+
# xyz2cam: 3x3 matrix found from the camera metedata, specifically
|
| 714 |
+
# color matrix 2 from the metadata
|
| 715 |
+
# color_space: output color space
|
| 716 |
+
# illuminance: the illuminant of the lighting condition
|
| 717 |
+
# name: name of the class
|
| 718 |
+
self.data = np.float32(data)
|
| 719 |
+
self.xyz2cam = np.float32(color_matrix)
|
| 720 |
+
self.color_space = color_space
|
| 721 |
+
self.illuminant = illuminant
|
| 722 |
+
self.name = name
|
| 723 |
+
self.clip_range = clip_range
|
| 724 |
+
|
| 725 |
+
def get_rgb2xyz(self):
|
| 726 |
+
# Objective: get the rgb2xyz matrix dependin on the output color space
|
| 727 |
+
# and the illuminant
|
| 728 |
+
# Source: http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
|
| 729 |
+
if (self.color_space == "srgb"):
|
| 730 |
+
if (self.illuminant == "d65"):
|
| 731 |
+
return [[.4124564, .3575761, .1804375],\
|
| 732 |
+
[.2126729, .7151522, .0721750],\
|
| 733 |
+
[.0193339, .1191920, .9503041]]
|
| 734 |
+
elif (self.illuminant == "d50"):
|
| 735 |
+
return [[.4360747, .3850649, .1430804],\
|
| 736 |
+
[.2225045, .7168786, .0606169],\
|
| 737 |
+
[.0139322, .0971045, .7141733]]
|
| 738 |
+
else:
|
| 739 |
+
print("for now, color_space must be d65 or d50")
|
| 740 |
+
return
|
| 741 |
+
|
| 742 |
+
elif (self.color_space == "adobe-rgb-1998"):
|
| 743 |
+
if (self.illuminant == "d65"):
|
| 744 |
+
return [[.5767309, .1855540, .1881852],\
|
| 745 |
+
[.2973769, .6273491, .0752741],\
|
| 746 |
+
[.0270343, .0706872, .9911085]]
|
| 747 |
+
elif (self.illuminant == "d50"):
|
| 748 |
+
return [[.6097559, .2052401, .1492240],\
|
| 749 |
+
[.3111242, .6256560, .0632197],\
|
| 750 |
+
[.0194811, .0608902, .7448387]]
|
| 751 |
+
else:
|
| 752 |
+
print("for now, illuminant must be d65 or d50")
|
| 753 |
+
return
|
| 754 |
+
else:
|
| 755 |
+
print("for now, color_space must be srgb or adobe-rgb-1998")
|
| 756 |
+
return
|
| 757 |
+
|
| 758 |
+
def calculate_cam2rgb(self):
|
| 759 |
+
# Objective: Calculates the color correction matrix
|
| 760 |
+
|
| 761 |
+
# matric multiplication
|
| 762 |
+
rgb2cam = np.dot(self.xyz2cam, self.get_rgb2xyz())
|
| 763 |
+
|
| 764 |
+
# make sum of each row to be 1.0, necessary to preserve white balance
|
| 765 |
+
# basically divice each value by its row wise sum
|
| 766 |
+
rgb2cam = np.divide(rgb2cam, np.reshape(np.sum(rgb2cam, 1), [3, 1]))
|
| 767 |
+
|
| 768 |
+
# - inverse the matrix to get cam2rgb.
|
| 769 |
+
# - cam2rgb should also have the characteristic that sum of each row
|
| 770 |
+
# equal to 1.0 to preserve white balance
|
| 771 |
+
# - check if rgb2cam is invertible by checking the condition of
|
| 772 |
+
# rgb2cam. If rgb2cam is singular it will give a warning and
|
| 773 |
+
# return an identiry matrix
|
| 774 |
+
if (np.linalg.cond(rgb2cam) < (1 / sys.float_info.epsilon)):
|
| 775 |
+
return np.linalg.inv(rgb2cam) # this is cam2rgb / color correction matrix
|
| 776 |
+
else:
|
| 777 |
+
print("Warning! matrix not invertible.")
|
| 778 |
+
return np.identity(3, dtype=np.float32)
|
| 779 |
+
|
| 780 |
+
def apply_cmatrix(self):
|
| 781 |
+
# Objective: Apply the color correction matrix (cam2rgb)
|
| 782 |
+
|
| 783 |
+
print("----------------------------------------------------")
|
| 784 |
+
print("running color correction...")
|
| 785 |
+
|
| 786 |
+
# check if data is 3 dimensional
|
| 787 |
+
if (np.ndim(self.data) != 3):
|
| 788 |
+
print("data need to be three dimensional")
|
| 789 |
+
return
|
| 790 |
+
|
| 791 |
+
# get the color correction matrix
|
| 792 |
+
cam2rgb = self.calculate_cam2rgb()
|
| 793 |
+
|
| 794 |
+
# get width and height
|
| 795 |
+
width, height = utility.helpers(self.data).get_width_height()
|
| 796 |
+
|
| 797 |
+
# apply the matrix
|
| 798 |
+
R = self.data[:, :, 0]
|
| 799 |
+
G = self.data[:, :, 1]
|
| 800 |
+
B = self.data[:, :, 2]
|
| 801 |
+
|
| 802 |
+
color_corrected = np.empty((height, width, 3), dtype=np.float32)
|
| 803 |
+
color_corrected[:, :, 0] = R * cam2rgb[0, 0] + G * cam2rgb[0, 1] + B * cam2rgb[0, 2]
|
| 804 |
+
color_corrected[:, :, 1] = R * cam2rgb[1, 0] + G * cam2rgb[1, 1] + B * cam2rgb[1, 2]
|
| 805 |
+
color_corrected[:, :, 2] = R * cam2rgb[2, 0] + G * cam2rgb[2, 1] + B * cam2rgb[2, 2]
|
| 806 |
+
|
| 807 |
+
return np.clip(color_corrected, self.clip_range[0], self.clip_range[1])
|
| 808 |
+
|
| 809 |
+
def __str__(self):
|
| 810 |
+
return self.name
|
| 811 |
+
|
| 812 |
+
|
| 813 |
+
# =============================================================
|
| 814 |
+
# class: nonlinearity
|
| 815 |
+
# apply gamma or degamma
|
| 816 |
+
# =============================================================
|
| 817 |
+
class nonlinearity:
|
| 818 |
+
def __init__(self, data, name="nonlinearity"):
|
| 819 |
+
self.data = np.float32(data)
|
| 820 |
+
self.name = name
|
| 821 |
+
|
| 822 |
+
def luma_adjustment(self, multiplier, clip_range=[0, 65535]):
|
| 823 |
+
# The multiplier is applied only on luma channel
|
| 824 |
+
# by a multipler in log10 scale:
|
| 825 |
+
# multipler 10 means multiplied by 1.
|
| 826 |
+
# multipler 100 means multiplied by 2. as such
|
| 827 |
+
|
| 828 |
+
print("----------------------------------------------------")
|
| 829 |
+
print("Running brightening...")
|
| 830 |
+
|
| 831 |
+
return np.clip(np.log10(multiplier) * self.data, clip_range[0], clip_range[1])
|
| 832 |
+
|
| 833 |
+
def by_value(self, value, clip_range):
|
| 834 |
+
|
| 835 |
+
print("----------------------------------------------------")
|
| 836 |
+
print("Running nonlinearity by value...")
|
| 837 |
+
|
| 838 |
+
# clip within the range
|
| 839 |
+
data = np.clip(self.data, clip_range[0], clip_range[1])
|
| 840 |
+
# make 0 to 1
|
| 841 |
+
data = data / clip_range[1]
|
| 842 |
+
# apply nonlinearity
|
| 843 |
+
return np.clip(clip_range[1] * (data**value), clip_range[0], clip_range[1])
|
| 844 |
+
|
| 845 |
+
def by_table(self, table, nonlinearity_type="gamma", clip_range=[0, 65535]):
|
| 846 |
+
|
| 847 |
+
print("----------------------------------------------------")
|
| 848 |
+
print("Running nonlinearity by table...")
|
| 849 |
+
|
| 850 |
+
gamma_table = np.loadtxt(table)
|
| 851 |
+
gamma_table = clip_range[1] * gamma_table / np.max(gamma_table)
|
| 852 |
+
linear_table = np.linspace(clip_range[0], clip_range[1], np.size(gamma_table))
|
| 853 |
+
|
| 854 |
+
# linear interpolation, query is the self.data
|
| 855 |
+
if (nonlinearity_type == "gamma"):
|
| 856 |
+
# mapping is from linear_table to gamma_table
|
| 857 |
+
return np.clip(np.interp(self.data, linear_table, gamma_table), clip_range[0], clip_range[1])
|
| 858 |
+
elif (nonlinearity_type == "degamma"):
|
| 859 |
+
# mapping is from gamma_table to linear_table
|
| 860 |
+
return np.clip(np.interp(self.data, gamma_table, linear_table), clip_range[0], clip_range[1])
|
| 861 |
+
|
| 862 |
+
def by_equation(self, a, b, clip_range):
|
| 863 |
+
|
| 864 |
+
print("----------------------------------------------------")
|
| 865 |
+
print("Running nonlinearity by equation...")
|
| 866 |
+
|
| 867 |
+
# clip within the range
|
| 868 |
+
data = np.clip(self.data, clip_range[0], clip_range[1])
|
| 869 |
+
# make 0 to 1
|
| 870 |
+
data = data / clip_range[1]
|
| 871 |
+
|
| 872 |
+
# apply nonlinearity
|
| 873 |
+
return np.clip(clip_range[1] * (a * np.exp(b * data) + data + a * data - a * np.exp(b) * data - a), clip_range[0], clip_range[1])
|
| 874 |
+
|
| 875 |
+
def __str__(self):
|
| 876 |
+
return self.name
|
| 877 |
+
|
| 878 |
+
|
| 879 |
+
# =============================================================
|
| 880 |
+
# class: tone_mapping
|
| 881 |
+
# improve the overall tone of the image
|
| 882 |
+
# =============================================================
|
| 883 |
+
class tone_mapping:
|
| 884 |
+
def __init__(self, data, name="tone mapping"):
|
| 885 |
+
self.data = np.float32(data)
|
| 886 |
+
self.name = name
|
| 887 |
+
|
| 888 |
+
def nonlinear_masking(self, strength_multiplier=1.0, gaussian_kernel_size=[5, 5], gaussian_sigma=1.0, clip_range=[0, 65535]):
|
| 889 |
+
# Objective: improves the overall tone of the image
|
| 890 |
+
# Inputs:
|
| 891 |
+
# strength_multiplier: >0. The higher the more aggressing tone mapping
|
| 892 |
+
# gaussian_kernel_size: kernel size for calculating the mask image
|
| 893 |
+
# gaussian_sigma: spread of the gaussian kernel for calculating the
|
| 894 |
+
# mask image
|
| 895 |
+
#
|
| 896 |
+
# Source:
|
| 897 |
+
# N. Moroney, “Local color correction using non-linear masking”,
|
| 898 |
+
# Proc. IS&T/SID 8th Color Imaging Conference, pp. 108-111, (2000)
|
| 899 |
+
#
|
| 900 |
+
# Note, Slight changes is carried by mushfiqul alam, specifically
|
| 901 |
+
# introducing the strength_multiplier
|
| 902 |
+
|
| 903 |
+
print("----------------------------------------------------")
|
| 904 |
+
print("Running tone mapping by non linear masking...")
|
| 905 |
+
|
| 906 |
+
# convert to gray image
|
| 907 |
+
if (np.ndim(self.data) == 3):
|
| 908 |
+
gray_image = utility.color_conversion(self.data).rgb2gray()
|
| 909 |
+
else:
|
| 910 |
+
gray_image = self.data
|
| 911 |
+
|
| 912 |
+
# gaussian blur the gray image
|
| 913 |
+
gaussian_kernel = utility.create_filter().gaussian(gaussian_kernel_size, gaussian_sigma)
|
| 914 |
+
|
| 915 |
+
# the mask image: (1) blur
|
| 916 |
+
# (2) bring within range 0 to 1
|
| 917 |
+
# (3) multiply with strength_multiplier
|
| 918 |
+
mask = signal.convolve2d(gray_image, gaussian_kernel, mode="same", boundary="symm")
|
| 919 |
+
mask = strength_multiplier * mask / clip_range[1]
|
| 920 |
+
|
| 921 |
+
# calculate the alpha image
|
| 922 |
+
temp = np.power(0.5, mask)
|
| 923 |
+
if (np.ndim(self.data) == 3):
|
| 924 |
+
width, height = utility.helpers(self.data).get_width_height()
|
| 925 |
+
alpha = np.empty((height, width, 3), dtype=np.float32)
|
| 926 |
+
alpha[:, :, 0] = temp
|
| 927 |
+
alpha[:, :, 1] = temp
|
| 928 |
+
alpha[:, :, 2] = temp
|
| 929 |
+
else:
|
| 930 |
+
alpha = temp
|
| 931 |
+
|
| 932 |
+
# output
|
| 933 |
+
return np.clip(clip_range[1] * np.power(self.data/clip_range[1], alpha), clip_range[0], clip_range[1])
|
| 934 |
+
|
| 935 |
+
def dynamic_range_compression(self, drc_type="normal", drc_bound=[-40., 260.], clip_range=[0, 65535]):
|
| 936 |
+
|
| 937 |
+
ycc = utility.color_conversion(self.data).rgb2ycc("bt601")
|
| 938 |
+
y = ycc[:, :, 0]
|
| 939 |
+
cb = ycc[:, :, 1]
|
| 940 |
+
cr = ycc[:, :, 2]
|
| 941 |
+
|
| 942 |
+
if (drc_type == "normal"):
|
| 943 |
+
edge = y
|
| 944 |
+
elif (drc_type == "joint"):
|
| 945 |
+
edge = utility.edge_detection(y).sobel(3, "gradient_magnitude")
|
| 946 |
+
|
| 947 |
+
y_bilateral_filtered = utility.special_function(y).bilateral_filter(edge)
|
| 948 |
+
detail = np.divide(ycc[:, :, 0], y_bilateral_filtered)
|
| 949 |
+
|
| 950 |
+
C = drc_bound[0] * clip_range[1] / 255.
|
| 951 |
+
temp = drc_bound[1] * clip_range[1] / 255.
|
| 952 |
+
F = (temp * (C + clip_range[1])) / (clip_range[1] * (temp - C))
|
| 953 |
+
y_bilateral_filtered_contrast_reduced = F * (y_bilateral_filtered - (clip_range[1] / 2.)) + (clip_range[1] / 2.)
|
| 954 |
+
|
| 955 |
+
y_out = np.multiply(y_bilateral_filtered_contrast_reduced, detail)
|
| 956 |
+
|
| 957 |
+
ycc_out = ycc
|
| 958 |
+
ycc_out[:, :, 0] = y_out
|
| 959 |
+
rgb_out = utility.color_conversion(ycc_out).ycc2rgb("bt601")
|
| 960 |
+
|
| 961 |
+
return np.clip(rgb_out, clip_range[0], clip_range[1])
|
| 962 |
+
|
| 963 |
+
|
| 964 |
+
# =============================================================
|
| 965 |
+
# class: sharpening
|
| 966 |
+
# sharpens the image
|
| 967 |
+
# =============================================================
|
| 968 |
+
class sharpening:
|
| 969 |
+
def __init__(self, data, name="sharpening"):
|
| 970 |
+
self.data = np.float32(data)
|
| 971 |
+
self.name = name
|
| 972 |
+
|
| 973 |
+
def unsharp_masking(self, gaussian_kernel_size=[5, 5], gaussian_sigma=2.0,\
|
| 974 |
+
slope=1.5, tau_threshold=0.05, gamma_speed=4., clip_range=[0, 65535]):
|
| 975 |
+
# Objective: sharpen image
|
| 976 |
+
# Input:
|
| 977 |
+
# gaussian_kernel_size: dimension of the gaussian blur filter kernel
|
| 978 |
+
#
|
| 979 |
+
# gaussian_sigma: spread of the gaussian blur filter kernel
|
| 980 |
+
# bigger sigma more sharpening
|
| 981 |
+
#
|
| 982 |
+
# slope: controls the boost.
|
| 983 |
+
# the amount of sharpening, higher slope
|
| 984 |
+
# means more aggresssive sharpening
|
| 985 |
+
#
|
| 986 |
+
# tau_threshold: controls the amount of coring.
|
| 987 |
+
# threshold value till which the image is
|
| 988 |
+
# not sharpened. The lower the value of
|
| 989 |
+
# tau_threshold the more frequencies
|
| 990 |
+
# goes through the sharpening process
|
| 991 |
+
#
|
| 992 |
+
# gamma_speed: controls the speed of convergence to the slope
|
| 993 |
+
# smaller value gives a little bit more
|
| 994 |
+
# sharpened image, this may be a fine tuner
|
| 995 |
+
|
| 996 |
+
print("----------------------------------------------------")
|
| 997 |
+
print("Running sharpening by unsharp masking...")
|
| 998 |
+
|
| 999 |
+
# create gaussian kernel
|
| 1000 |
+
gaussian_kernel = utility.create_filter().gaussian(gaussian_kernel_size, gaussian_sigma)
|
| 1001 |
+
|
| 1002 |
+
# convolove the image with the gaussian kernel
|
| 1003 |
+
# first input is the image
|
| 1004 |
+
# second input is the kernel
|
| 1005 |
+
# output shape will be the same as the first input
|
| 1006 |
+
# boundary will be padded by using symmetrical method while convolving
|
| 1007 |
+
if np.ndim(self.data > 2):
|
| 1008 |
+
image_blur = np.empty(np.shape(self.data), dtype=np.float32)
|
| 1009 |
+
for i in range(0, np.shape(self.data)[2]):
|
| 1010 |
+
image_blur[:, :, i] = signal.convolve2d(self.data[:, :, i], gaussian_kernel, mode="same", boundary="symm")
|
| 1011 |
+
else:
|
| 1012 |
+
image_blur = signal.convolove2d(self.data, gaussian_kernel, mode="same", boundary="symm")
|
| 1013 |
+
|
| 1014 |
+
# the high frequency component image
|
| 1015 |
+
image_high_pass = self.data - image_blur
|
| 1016 |
+
|
| 1017 |
+
# soft coring (see in utility)
|
| 1018 |
+
# basically pass the high pass image via a slightly nonlinear function
|
| 1019 |
+
tau_threshold = tau_threshold * clip_range[1]
|
| 1020 |
+
|
| 1021 |
+
# add the soft cored high pass image to the original and clip
|
| 1022 |
+
# within range and return
|
| 1023 |
+
return np.clip(self.data + utility.special_function(\
|
| 1024 |
+
image_high_pass).soft_coring(\
|
| 1025 |
+
slope, tau_threshold, gamma_speed), clip_range[0], clip_range[1])
|
| 1026 |
+
|
| 1027 |
+
def __str__(self):
|
| 1028 |
+
return self.name
|
| 1029 |
+
|
| 1030 |
+
|
| 1031 |
+
# =============================================================
|
| 1032 |
+
# class: noise_reduction
|
| 1033 |
+
# reduce noise of the nonlinear image (after gamma)
|
| 1034 |
+
# =============================================================
|
| 1035 |
+
class noise_reduction:
|
| 1036 |
+
def __init__(self, data, clip_range=[0, 65535], name="noise reduction"):
|
| 1037 |
+
self.data = np.float32(data)
|
| 1038 |
+
self.clip_range = clip_range
|
| 1039 |
+
self.name = name
|
| 1040 |
+
|
| 1041 |
+
def sigma_filter(self, neighborhood_size=7, sigma=[6, 6, 6]):
|
| 1042 |
+
|
| 1043 |
+
print("----------------------------------------------------")
|
| 1044 |
+
print("Running noise reduction by sigma filter...")
|
| 1045 |
+
|
| 1046 |
+
if np.ndim(self.data > 2): # if rgb image
|
| 1047 |
+
output = np.empty(np.shape(self.data), dtype=np.float32)
|
| 1048 |
+
for i in range(0, np.shape(self.data)[2]):
|
| 1049 |
+
output[:, :, i] = utility.helpers(self.data[:, :, i]).sigma_filter_helper(neighborhood_size, sigma[i])
|
| 1050 |
+
return np.clip(output, self.clip_range[0], self.clip_range[1])
|
| 1051 |
+
else: # gray image
|
| 1052 |
+
return np.clip(utility.helpers(self.data).sigma_filter_helper(neighborhood_size, sigma), self.clip_range[0], self.clip_range[1])
|
| 1053 |
+
|
| 1054 |
+
def __str__(self):
|
| 1055 |
+
return self.name
|
| 1056 |
+
|
| 1057 |
+
|
| 1058 |
+
# =============================================================
|
| 1059 |
+
# class: distortion_correction
|
| 1060 |
+
# correct the distortion
|
| 1061 |
+
# =============================================================
|
| 1062 |
+
class distortion_correction:
|
| 1063 |
+
def __init__(self, data, name="distortion correction"):
|
| 1064 |
+
self.data = np.float32(data)
|
| 1065 |
+
self.name = name
|
| 1066 |
+
|
| 1067 |
+
|
| 1068 |
+
def empirical_correction(self, correction_type="pincushion-1", strength=0.1, zoom_type="crop", clip_range=[0, 65535]):
|
| 1069 |
+
#------------------------------------------------------
|
| 1070 |
+
# Objective:
|
| 1071 |
+
# correct geometric distortion with the assumption that the distortion
|
| 1072 |
+
# is symmetric and the center is at the center of of the image
|
| 1073 |
+
# Input:
|
| 1074 |
+
# correction_type: which type of correction needed to be carried
|
| 1075 |
+
# out, choose one the four:
|
| 1076 |
+
# pincushion-1, pincushion-2, barrel-1, barrel-2
|
| 1077 |
+
# 1 and 2 are difference between the power
|
| 1078 |
+
# over the radius
|
| 1079 |
+
#
|
| 1080 |
+
# strength: should be equal or greater than 0.
|
| 1081 |
+
# 0 means no correction will be done.
|
| 1082 |
+
# if negative value were applied correction_type
|
| 1083 |
+
# will be reversed. Thus,>=0 value expected.
|
| 1084 |
+
#
|
| 1085 |
+
# zoom_type: either "fit" or "crop"
|
| 1086 |
+
# fit will return image with full content
|
| 1087 |
+
# in the whole area
|
| 1088 |
+
# crop will return image will 0 values outsise
|
| 1089 |
+
# the border
|
| 1090 |
+
#
|
| 1091 |
+
# clip_range: to clip the final image within the range
|
| 1092 |
+
#------------------------------------------------------
|
| 1093 |
+
|
| 1094 |
+
if (strength < 0):
|
| 1095 |
+
print("Warning! strength should be equal of greater than 0.")
|
| 1096 |
+
return self.data
|
| 1097 |
+
|
| 1098 |
+
print("----------------------------------------------------")
|
| 1099 |
+
print("Running distortion correction by empirical method...")
|
| 1100 |
+
|
| 1101 |
+
# get half_width and half_height, assume this is the center
|
| 1102 |
+
width, height = utility.helpers(self.data).get_width_height()
|
| 1103 |
+
half_width = width / 2
|
| 1104 |
+
half_height = height / 2
|
| 1105 |
+
|
| 1106 |
+
# create a meshgrid of points
|
| 1107 |
+
xi, yi = np.meshgrid(np.linspace(-half_width, half_width, width),\
|
| 1108 |
+
np.linspace(-half_height, half_height, height))
|
| 1109 |
+
|
| 1110 |
+
# cartesian to polar coordinate
|
| 1111 |
+
r = np.sqrt(xi**2 + yi**2)
|
| 1112 |
+
theta = np.arctan2(yi, xi)
|
| 1113 |
+
|
| 1114 |
+
# maximum radius
|
| 1115 |
+
R = math.sqrt(width**2 + height**2)
|
| 1116 |
+
|
| 1117 |
+
# make r within range 0~1
|
| 1118 |
+
r = r / R
|
| 1119 |
+
|
| 1120 |
+
# apply the radius to the desired transformation
|
| 1121 |
+
s = utility.special_function(r).distortion_function(correction_type, strength)
|
| 1122 |
+
|
| 1123 |
+
# select a scaling_parameter based on zoon_type and k value
|
| 1124 |
+
if ((correction_type=="barrel-1") or (correction_type=="barrel-2")):
|
| 1125 |
+
if (zoom_type == "fit"):
|
| 1126 |
+
scaling_parameter = r[0, 0] / s[0, 0]
|
| 1127 |
+
elif (zoom_type == "crop"):
|
| 1128 |
+
scaling_parameter = 1. / (1. + strength * (np.min([half_width, half_height])/R)**2)
|
| 1129 |
+
elif ((correction_type=="pincushion-1") or (correction_type=="pincushion-2")):
|
| 1130 |
+
if (zoom_type == "fit"):
|
| 1131 |
+
scaling_parameter = 1. / (1. + strength * (np.min([half_width, half_height])/R)**2)
|
| 1132 |
+
elif (zoom_type == "crop"):
|
| 1133 |
+
scaling_parameter = r[0, 0] / s[0, 0]
|
| 1134 |
+
|
| 1135 |
+
# multiply by scaling_parameter and un-normalize
|
| 1136 |
+
s = s * scaling_parameter * R
|
| 1137 |
+
|
| 1138 |
+
# convert back to cartesian coordinate and add back the center coordinate
|
| 1139 |
+
xt = np.multiply(s, np.cos(theta))
|
| 1140 |
+
yt = np.multiply(s, np.sin(theta))
|
| 1141 |
+
|
| 1142 |
+
# interpolation
|
| 1143 |
+
if np.ndim(self.data == 3):
|
| 1144 |
+
|
| 1145 |
+
output = np.empty(np.shape(self.data), dtype=np.float32)
|
| 1146 |
+
|
| 1147 |
+
output[:, :, 0] = utility.helpers(self.data[:, :, 0]).bilinear_interpolation(xt + half_width, yt + half_height)
|
| 1148 |
+
output[:, :, 1] = utility.helpers(self.data[:, :, 1]).bilinear_interpolation(xt + half_width, yt + half_height)
|
| 1149 |
+
output[:, :, 2] = utility.helpers(self.data[:, :, 2]).bilinear_interpolation(xt + half_width, yt + half_height)
|
| 1150 |
+
|
| 1151 |
+
elif np.ndim(self.data == 2):
|
| 1152 |
+
|
| 1153 |
+
output = utility.helpers(self.data).bilinear_interpolation(xt + half_width, yt + half_height)
|
| 1154 |
+
|
| 1155 |
+
return np.clip(output, clip_range[0], clip_range[1])
|
| 1156 |
+
|
| 1157 |
+
|
| 1158 |
+
def __str__(self):
|
| 1159 |
+
return self.name
|
| 1160 |
+
|
| 1161 |
+
|
| 1162 |
+
# =============================================================
|
| 1163 |
+
# class: memory_color_enhancement
|
| 1164 |
+
# enhance memory colors such as sky, grass, skin color
|
| 1165 |
+
# =============================================================
|
| 1166 |
+
class memory_color_enhancement:
|
| 1167 |
+
def __init__(self, data, name="memory color enhancement"):
|
| 1168 |
+
self.data = np.float32(data)
|
| 1169 |
+
self.name = name
|
| 1170 |
+
|
| 1171 |
+
def by_hue_squeeze(self, target_hue, hue_preference, hue_sigma, is_both_side, multiplier, chroma_preference, chroma_sigma, color_space="srgb", illuminant="d65", clip_range=[0, 65535], cie_version="1931"):
|
| 1172 |
+
|
| 1173 |
+
# RGB to xyz
|
| 1174 |
+
data = utility.color_conversion(self.data).rgb2xyz(color_space, clip_range)
|
| 1175 |
+
# xyz to lab
|
| 1176 |
+
data = utility.color_conversion(data).xyz2lab(cie_version, illuminant)
|
| 1177 |
+
# lab to lch
|
| 1178 |
+
data = utility.color_conversion(data).lab2lch()
|
| 1179 |
+
|
| 1180 |
+
# hue squeezing
|
| 1181 |
+
# we are traversing through different color preferences
|
| 1182 |
+
width, height = utility.helpers(self.data).get_width_height()
|
| 1183 |
+
hue_correction = np.zeros((height, width), dtype=np.float32)
|
| 1184 |
+
for i in range(0, np.size(target_hue)):
|
| 1185 |
+
|
| 1186 |
+
delta_hue = data[:, :, 2] - hue_preference[i]
|
| 1187 |
+
|
| 1188 |
+
if is_both_side[i]:
|
| 1189 |
+
weight_temp = np.exp( -np.power(data[:, :, 2] - target_hue[i], 2) / (2 * hue_sigma[i]**2)) + \
|
| 1190 |
+
np.exp( -np.power(data[:, :, 2] + target_hue[i], 2) / (2 * hue_sigma[i]**2))
|
| 1191 |
+
else:
|
| 1192 |
+
weight_temp = np.exp( -np.power(data[:, :, 2] - target_hue[i], 2) / (2 * hue_sigma[i]**2))
|
| 1193 |
+
|
| 1194 |
+
weight_hue = multiplier[i] * weight_temp / np.max(weight_temp)
|
| 1195 |
+
|
| 1196 |
+
weight_chroma = np.exp( -np.power(data[:, :, 1] - chroma_preference[i], 2) / (2 * chroma_sigma[i]**2))
|
| 1197 |
+
|
| 1198 |
+
hue_correction = hue_correction + np.multiply(np.multiply(delta_hue, weight_hue), weight_chroma)
|
| 1199 |
+
|
| 1200 |
+
# correct the hue
|
| 1201 |
+
data[:, :, 2] = data[:, :, 2] - hue_correction
|
| 1202 |
+
|
| 1203 |
+
# lch to lab
|
| 1204 |
+
data = utility.color_conversion(data).lch2lab()
|
| 1205 |
+
# lab to xyz
|
| 1206 |
+
data = utility.color_conversion(data).lab2xyz(cie_version, illuminant)
|
| 1207 |
+
# xyz to rgb
|
| 1208 |
+
data = utility.color_conversion(data).xyz2rgb(color_space, clip_range)
|
| 1209 |
+
|
| 1210 |
+
return data
|
| 1211 |
+
|
| 1212 |
+
|
| 1213 |
+
def __str__(self):
|
| 1214 |
+
return self.name
|
| 1215 |
+
|
| 1216 |
+
|
| 1217 |
+
# =============================================================
|
| 1218 |
+
# class: chromatic_aberration_correction
|
| 1219 |
+
# removes artifacts similar to result from chromatic
|
| 1220 |
+
# aberration
|
| 1221 |
+
# =============================================================
|
| 1222 |
+
class chromatic_aberration_correction:
|
| 1223 |
+
def __init__(self, data, name="chromatic aberration correction"):
|
| 1224 |
+
self.data = np.float32(data)
|
| 1225 |
+
self.name = name
|
| 1226 |
+
|
| 1227 |
+
def purple_fringe_removal(self, nsr_threshold, cr_threshold, clip_range=[0, 65535]):
|
| 1228 |
+
# --------------------------------------------------------------
|
| 1229 |
+
# nsr_threshold: near saturated region threshold (in percentage)
|
| 1230 |
+
# cr_threshold: candidate region threshold
|
| 1231 |
+
# --------------------------------------------------------------
|
| 1232 |
+
|
| 1233 |
+
width, height = utility.helpers(self.data).get_width_height()
|
| 1234 |
+
|
| 1235 |
+
r = self.data[:, :, 0]
|
| 1236 |
+
g = self.data[:, :, 1]
|
| 1237 |
+
b = self.data[:, :, 2]
|
| 1238 |
+
|
| 1239 |
+
## Detection of purple fringe
|
| 1240 |
+
# near saturated region detection
|
| 1241 |
+
nsr_threshold = clip_range[1] * nsr_threshold / 100
|
| 1242 |
+
temp = (r + g + b) / 3
|
| 1243 |
+
temp = np.asarray(temp)
|
| 1244 |
+
mask = temp > nsr_threshold
|
| 1245 |
+
nsr = np.zeros((height, width)).astype(int)
|
| 1246 |
+
nsr[mask] = 1
|
| 1247 |
+
|
| 1248 |
+
# candidate region detection
|
| 1249 |
+
temp = r - b
|
| 1250 |
+
temp1 = b - g
|
| 1251 |
+
temp = np.asarray(temp)
|
| 1252 |
+
temp1 = np.asarray(temp1)
|
| 1253 |
+
mask = (temp < cr_threshold) & (temp1 > cr_threshold)
|
| 1254 |
+
cr = np.zeros((height, width)).astype(int)
|
| 1255 |
+
cr[mask] = 1
|
| 1256 |
+
|
| 1257 |
+
# quantization
|
| 1258 |
+
qr = utility.helpers(r).nonuniform_quantization()
|
| 1259 |
+
qg = utility.helpers(g).nonuniform_quantization()
|
| 1260 |
+
qb = utility.helpers(b).nonuniform_quantization()
|
| 1261 |
+
|
| 1262 |
+
g_qr = utility.edge_detection(qr).sobel(5, "gradient_magnitude")
|
| 1263 |
+
g_qg = utility.edge_detection(qg).sobel(5, "gradient_magnitude")
|
| 1264 |
+
g_qb = utility.edge_detection(qb).sobel(5, "gradient_magnitude")
|
| 1265 |
+
|
| 1266 |
+
g_qr = np.asarray(g_qr)
|
| 1267 |
+
g_qg = np.asarray(g_qg)
|
| 1268 |
+
g_qb = np.asarray(g_qb)
|
| 1269 |
+
|
| 1270 |
+
# bgm: binary gradient magnitude
|
| 1271 |
+
bgm = np.zeros((height, width), dtype=np.float32)
|
| 1272 |
+
mask = (g_qr != 0) | (g_qg != 0) | (g_qb != 0)
|
| 1273 |
+
bgm[mask] = 1
|
| 1274 |
+
|
| 1275 |
+
fringe_map = np.multiply(np.multiply(nsr, cr), bgm)
|
| 1276 |
+
fring_map = np.asarray(fringe_map)
|
| 1277 |
+
mask = (fringe_map == 1)
|
| 1278 |
+
|
| 1279 |
+
r1 = r
|
| 1280 |
+
g1 = g
|
| 1281 |
+
b1 = b
|
| 1282 |
+
r1[mask] = g1[mask] = b1[mask] = (r[mask] + g[mask] + b[mask]) / 3.
|
| 1283 |
+
|
| 1284 |
+
output = np.empty(np.shape(self.data), dtype=np.float32)
|
| 1285 |
+
output[:, :, 0] = r1
|
| 1286 |
+
output[:, :, 1] = g1
|
| 1287 |
+
output[:, :, 2] = b1
|
| 1288 |
+
|
| 1289 |
+
return np.float32(output)
|
| 1290 |
+
|
| 1291 |
+
|
| 1292 |
+
def __str__(self):
|
| 1293 |
+
return self.name
|
IIR-Lab/ISP_pipeline/lsc_table_r_gr_gb_b_2.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0932e4b3ed5feb111880988eadae26a3cc77a5bd448150087b0983d8a62bb2b7
|
| 3 |
+
size 402653312
|
IIR-Lab/ISP_pipeline/process_pngs_isp.py
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
sys.path.append('ISP_pipeline')
|
| 3 |
+
from raw_prc_pipeline.pipeline import RawProcessingPipelineDemo
|
| 4 |
+
import cv2
|
| 5 |
+
import numpy as np
|
| 6 |
+
import json
|
| 7 |
+
import PIL.Image as Image
|
| 8 |
+
import os,sys
|
| 9 |
+
from raw_prc_pipeline import io
|
| 10 |
+
from copy import deepcopy
|
| 11 |
+
import torch
|
| 12 |
+
|
| 13 |
+
def resize_using_pil(img, width=1024, height=768):
|
| 14 |
+
img_pil = Image.fromarray(img)
|
| 15 |
+
out_size = (width, height)
|
| 16 |
+
if img_pil.size == out_size:
|
| 17 |
+
return img
|
| 18 |
+
out_img = img_pil.resize(out_size, Image.LANCZOS)
|
| 19 |
+
# out_img = img_pil
|
| 20 |
+
out_img = np.array(out_img)
|
| 21 |
+
return out_img
|
| 22 |
+
|
| 23 |
+
def fix_orientation(image, orientation):
|
| 24 |
+
|
| 25 |
+
if type(orientation) is list:
|
| 26 |
+
orientation = orientation[0]
|
| 27 |
+
|
| 28 |
+
if orientation == 'Horizontal(normal)':
|
| 29 |
+
pass
|
| 30 |
+
elif orientation == "Mirror horizontal":
|
| 31 |
+
image = cv2.flip(image, 0)
|
| 32 |
+
elif orientation == "Rotate 180":
|
| 33 |
+
image = cv2.rotate(image, cv2.ROTATE_180)
|
| 34 |
+
elif orientation == "Mirror vertical":
|
| 35 |
+
image = cv2.flip(image, 1)
|
| 36 |
+
elif orientation == "Mirror horizontal and rotate 270 CW":
|
| 37 |
+
image = cv2.flip(image, 0)
|
| 38 |
+
image = cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE)
|
| 39 |
+
elif orientation == "Rotate 90 CW":
|
| 40 |
+
image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
|
| 41 |
+
elif orientation == "Mirror horizontal and rotate 90 CW":
|
| 42 |
+
image = cv2.flip(image, 0)
|
| 43 |
+
image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
|
| 44 |
+
elif orientation == "Rotate 270 CW":
|
| 45 |
+
image = cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE)
|
| 46 |
+
|
| 47 |
+
return image
|
| 48 |
+
|
| 49 |
+
def isp_night_imaging(data, meta_data, iso,
|
| 50 |
+
do_demosaic = True, # H/2 W/2
|
| 51 |
+
|
| 52 |
+
do_channel_gain_white_balance = True,
|
| 53 |
+
do_xyz_transform = True,
|
| 54 |
+
do_srgb_transform = True,
|
| 55 |
+
|
| 56 |
+
do_gamma_correct = True, # con
|
| 57 |
+
|
| 58 |
+
do_refinement = True, # 32 bit
|
| 59 |
+
do_to_uint8 = True,
|
| 60 |
+
|
| 61 |
+
do_resize_using_pil = True, # H/8, W/8
|
| 62 |
+
do_fix_orientation = True
|
| 63 |
+
):
|
| 64 |
+
|
| 65 |
+
pipeline_params = {
|
| 66 |
+
'tone_mapping': 'Flash', # options: Flash, Storm, Base, Linear, Drago, Mantiuk, Reinhard
|
| 67 |
+
'illumination_estimation': 'gw', # ie algorithm, options: "gw", "wp", "sog", "iwp"
|
| 68 |
+
'denoise_flg': True,
|
| 69 |
+
'out_landscape_width': 1024,
|
| 70 |
+
'out_landscape_height': 768,
|
| 71 |
+
"color_matrix": [ 1.06835938, -0.29882812, -0.14257812,
|
| 72 |
+
-0.43164062, 1.35546875, 0.05078125,
|
| 73 |
+
-0.1015625, 0.24414062, 0.5859375]
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
pipeline_demo = RawProcessingPipelineDemo(**pipeline_params)
|
| 77 |
+
|
| 78 |
+
# ===================================
|
| 79 |
+
# Demosacing
|
| 80 |
+
# ===================================
|
| 81 |
+
if do_demosaic:
|
| 82 |
+
data = torch.stack((data[0,:,:], (data[1,:,:]+data[2,:,:])/2, data[3,:,:]), dim=0)
|
| 83 |
+
data = data.permute(1, 2, 0).contiguous()
|
| 84 |
+
# torch.cuda.empty_cache()
|
| 85 |
+
else:
|
| 86 |
+
pass
|
| 87 |
+
|
| 88 |
+
# ===================================
|
| 89 |
+
# Channel gain for white balance
|
| 90 |
+
# ===================================
|
| 91 |
+
if do_channel_gain_white_balance:
|
| 92 |
+
data = pipeline_demo.white_balance(data, img_meta=meta_data)
|
| 93 |
+
|
| 94 |
+
else:
|
| 95 |
+
pass
|
| 96 |
+
|
| 97 |
+
# ===================================
|
| 98 |
+
# xyz_transform
|
| 99 |
+
# ===================================
|
| 100 |
+
if do_xyz_transform:
|
| 101 |
+
data = pipeline_demo.xyz_transform(data,img_meta=meta_data) # CCM
|
| 102 |
+
else:
|
| 103 |
+
pass
|
| 104 |
+
|
| 105 |
+
# ===================================
|
| 106 |
+
# srgb_transform
|
| 107 |
+
# ===================================
|
| 108 |
+
if do_srgb_transform:
|
| 109 |
+
data = pipeline_demo.srgb_transform(data, img_meta=meta_data) # fix ccm
|
| 110 |
+
else:
|
| 111 |
+
pass
|
| 112 |
+
|
| 113 |
+
# ===================================
|
| 114 |
+
# gamma_correct
|
| 115 |
+
# ===================================
|
| 116 |
+
if do_gamma_correct:
|
| 117 |
+
data = pipeline_demo.gamma_correct(data, img_meta=meta_data)
|
| 118 |
+
else:
|
| 119 |
+
pass
|
| 120 |
+
|
| 121 |
+
# ===================================
|
| 122 |
+
# refinement
|
| 123 |
+
# ===================================
|
| 124 |
+
if do_refinement:
|
| 125 |
+
if iso < 1000:
|
| 126 |
+
pth1 = "Rendering_models/low_iso.pth"
|
| 127 |
+
data = pipeline_demo.do_refinement(data, "csrnet", pth1)
|
| 128 |
+
else:
|
| 129 |
+
pth1 = "Rendering_models/high_iso.pth"
|
| 130 |
+
data = pipeline_demo.do_refinement(data, "csrnet", pth1)
|
| 131 |
+
torch.cuda.empty_cache()
|
| 132 |
+
|
| 133 |
+
else:
|
| 134 |
+
pass
|
| 135 |
+
|
| 136 |
+
# ===================================
|
| 137 |
+
# to_uint8
|
| 138 |
+
# ===================================
|
| 139 |
+
if do_to_uint8:
|
| 140 |
+
data = pipeline_demo.to_uint8(data, img_meta=meta_data)
|
| 141 |
+
torch.cuda.empty_cache()
|
| 142 |
+
else:
|
| 143 |
+
pass
|
| 144 |
+
|
| 145 |
+
# ===================================
|
| 146 |
+
# resize_using_pil
|
| 147 |
+
# ===================================
|
| 148 |
+
if do_resize_using_pil:
|
| 149 |
+
data = resize_using_pil(data, pipeline_demo.params["out_landscape_width"], pipeline_demo.params["out_landscape_height"])
|
| 150 |
+
|
| 151 |
+
else:
|
| 152 |
+
pass
|
| 153 |
+
|
| 154 |
+
# ===================================
|
| 155 |
+
# fix_orientation
|
| 156 |
+
# ===================================
|
| 157 |
+
if do_fix_orientation:
|
| 158 |
+
data = fix_orientation(data, meta_data["orientation"])
|
| 159 |
+
else:
|
| 160 |
+
pass
|
| 161 |
+
|
| 162 |
+
return data
|
| 163 |
+
|
| 164 |
+
def readjson(json_path,):
|
| 165 |
+
with open(json_path,'r',encoding='UTF-8') as f:
|
| 166 |
+
result = json.load(f)
|
| 167 |
+
|
| 168 |
+
return result
|
| 169 |
+
|
| 170 |
+
def get_smooth_kernel_size(factor):
|
| 171 |
+
if factor == 1:
|
| 172 |
+
return (5, 5)
|
| 173 |
+
elif factor == 0.5:
|
| 174 |
+
return (3, 3)
|
| 175 |
+
elif factor == 0.375:
|
| 176 |
+
return (3, 3)
|
| 177 |
+
elif factor in [0.2, 0.25]:
|
| 178 |
+
return (5, 5)
|
| 179 |
+
elif factor == 0.125:
|
| 180 |
+
return (7, 7)
|
| 181 |
+
else:
|
| 182 |
+
raise Exception('Unknown factor')
|
| 183 |
+
|
| 184 |
+
def read_rawpng(path, metadata):
|
| 185 |
+
|
| 186 |
+
raw = cv2.imread(str(path), cv2.IMREAD_UNCHANGED)
|
| 187 |
+
|
| 188 |
+
if raw.shape[0] == 4:
|
| 189 |
+
return raw * 959
|
| 190 |
+
raw = (raw.astype(np.float32) - 256.) / (4095.- 256.)
|
| 191 |
+
|
| 192 |
+
raw = bayer2raw(raw, metadata)
|
| 193 |
+
raw = np.clip(raw, 0., 1.)
|
| 194 |
+
return raw
|
| 195 |
+
|
| 196 |
+
def bayer2raw(raw, metadata):
|
| 197 |
+
# pack RGGB Bayer raw to 4 channels
|
| 198 |
+
H, W = raw.shape
|
| 199 |
+
raw = raw[None, ...]
|
| 200 |
+
if metadata['cfa_pattern'][0] == 0:
|
| 201 |
+
# RGGB
|
| 202 |
+
raw_pack = np.concatenate((raw[:, 0:H:2, 0:W:2],
|
| 203 |
+
raw[:, 0:H:2, 1:W:2],
|
| 204 |
+
raw[:, 1:H:2, 0:W:2],
|
| 205 |
+
raw[:, 1:H:2, 1:W:2]), axis=0)
|
| 206 |
+
else :
|
| 207 |
+
# BGGR
|
| 208 |
+
raw_pack = np.concatenate((raw[:, 1:H:2, 1:W:2],
|
| 209 |
+
raw[:, 0:H:2, 1:W:2],
|
| 210 |
+
raw[:, 1:H:2, 0:W:2],
|
| 211 |
+
raw[:, 0:H:2, 0:W:2]), axis=0)
|
| 212 |
+
return raw_pack
|
| 213 |
+
|
| 214 |
+
def raw_rggb_float32(raws):
|
| 215 |
+
# depack 4 channels raw to RGGB Bayer
|
| 216 |
+
C, H, W = raws.shape
|
| 217 |
+
output = np.zeros((H * 2, W * 2)).astype(np.float32)
|
| 218 |
+
|
| 219 |
+
output[0:2 * H:2, 0:2 * W:2] = raws[0:1, :, :]
|
| 220 |
+
output[0:2 * H:2, 1:2 * W:2] = raws[1:2, :, :]
|
| 221 |
+
output[1:2 * H:2, 0:2 * W:2] = raws[2:3, :, :]
|
| 222 |
+
output[1:2 * H:2, 1:2 * W:2] = raws[3:4, :, :]
|
| 223 |
+
|
| 224 |
+
return output
|
| 225 |
+
|
| 226 |
+
def json_read(pth):
|
| 227 |
+
with open(pth) as j:
|
| 228 |
+
data = json.load(j)
|
| 229 |
+
return data
|
| 230 |
+
|
| 231 |
+
def linear_insert_1color(img_dt, resize, fx=128, fy=128):
|
| 232 |
+
pos_0_0, pos_0_1, pos_1_1, pos_1_0, m, n = insert_linear_pos(img_dt=img_dt, resize=resize, x_scale=fx, y_scale=fy)
|
| 233 |
+
a = (pos_1_0 - pos_0_0)
|
| 234 |
+
b = (pos_0_1 - pos_0_0)
|
| 235 |
+
c = pos_1_1 + pos_0_0 - pos_1_0 - pos_0_1
|
| 236 |
+
return np.round(a * n + b * m + c * n * m + pos_0_0).astype(int)
|
| 237 |
+
|
| 238 |
+
def insert_linear_pos(img_dt, resize, x_scale=128, y_scale=128):
|
| 239 |
+
m_, n_ = img_dt.shape
|
| 240 |
+
# 获取新的图像的大小
|
| 241 |
+
if resize is None:
|
| 242 |
+
n_new, m_new = np.round(x_scale * n_).astype(int), np.round(y_scale * m_).astype(int)
|
| 243 |
+
else:
|
| 244 |
+
n_new, m_new = resize
|
| 245 |
+
|
| 246 |
+
n_scale, m_scale = n_ / n_new, m_ / m_new # src_with/dst_with, Src_height/dst_heaight
|
| 247 |
+
# 一、获取位置对应的四个点
|
| 248 |
+
# 1-1- 初始化位置
|
| 249 |
+
m_indxs = np.repeat(np.arange(m_new), n_new).reshape(m_new, n_new)
|
| 250 |
+
n_indxs = np.array(list(range(n_new))*m_new).reshape(m_new, n_new)
|
| 251 |
+
# 1-2- 初始化位置
|
| 252 |
+
m_indxs_c = (m_indxs + 0.5 ) * m_scale - 0.5
|
| 253 |
+
n_indxs_c = (n_indxs + 0.5 ) * n_scale - 0.5
|
| 254 |
+
### 将小于零的数处理成0
|
| 255 |
+
m_indxs_c[np.where(m_indxs_c < 0)] = 0.0
|
| 256 |
+
n_indxs_c[np.where(n_indxs_c < 0)] = 0.0
|
| 257 |
+
|
| 258 |
+
# 1-3 获取正方形顶点坐标
|
| 259 |
+
m_indxs_c_down = m_indxs_c.astype(int)
|
| 260 |
+
n_indxs_c_down = n_indxs_c.astype(int)
|
| 261 |
+
m_indxs_c_up = m_indxs_c_down + 1
|
| 262 |
+
n_indxs_c_up = n_indxs_c_down + 1
|
| 263 |
+
### 溢出部分修正
|
| 264 |
+
m_max = m_ - 1
|
| 265 |
+
n_max = n_ - 1
|
| 266 |
+
m_indxs_c_up[np.where(m_indxs_c_up > m_max)] = m_max
|
| 267 |
+
n_indxs_c_up[np.where(n_indxs_c_up > n_max)] = n_max
|
| 268 |
+
|
| 269 |
+
# 1-4 获取正方形四个顶点的位置
|
| 270 |
+
pos_0_0 = img_dt[m_indxs_c_down, n_indxs_c_down].astype(int)
|
| 271 |
+
pos_0_1 = img_dt[m_indxs_c_up, n_indxs_c_down].astype(int)
|
| 272 |
+
pos_1_1 = img_dt[m_indxs_c_up, n_indxs_c_up].astype(int)
|
| 273 |
+
pos_1_0 = img_dt[m_indxs_c_down, n_indxs_c_up].astype(int)
|
| 274 |
+
# 1-5 获取浮点位置
|
| 275 |
+
m, n = np.modf(m_indxs_c)[0], np.modf(n_indxs_c)[0]
|
| 276 |
+
return pos_0_0, pos_0_1, pos_1_1, pos_1_0, m, n
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
expected_img_ext = '.jpg'
|
| 2 |
+
expected_landscape_img_height = 866
|
| 3 |
+
expected_landscape_img_width = 1300
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (320 Bytes). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/__init__.cpython-39.pyc
ADDED
|
Binary file (269 Bytes). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/arch_util.cpython-312.pyc
ADDED
|
Binary file (26.8 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/color.cpython-312.pyc
ADDED
|
Binary file (17.6 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/color.cpython-39.pyc
ADDED
|
Binary file (9.81 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/csrnet_network.cpython-312.pyc
ADDED
|
Binary file (4.95 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/csrnet_network.cpython-39.pyc
ADDED
|
Binary file (2.52 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/exif_data_formats.cpython-312.pyc
ADDED
|
Binary file (1.4 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/exif_data_formats.cpython-39.pyc
ADDED
|
Binary file (1.03 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/exif_utils.cpython-312.pyc
ADDED
|
Binary file (10.1 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/exif_utils.cpython-39.pyc
ADDED
|
Binary file (5.4 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/fs.cpython-312.pyc
ADDED
|
Binary file (2.86 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/fs.cpython-39.pyc
ADDED
|
Binary file (1.63 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/io.cpython-312.pyc
ADDED
|
Binary file (3.09 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/io.cpython-39.pyc
ADDED
|
Binary file (2.02 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/lut_network.cpython-312.pyc
ADDED
|
Binary file (17.9 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/misc.cpython-312.pyc
ADDED
|
Binary file (13.2 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/misc.cpython-39.pyc
ADDED
|
Binary file (7.07 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/optim.cpython-312.pyc
ADDED
|
Binary file (2.29 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/optim.cpython-39.pyc
ADDED
|
Binary file (1.49 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/pipeline.cpython-312.pyc
ADDED
|
Binary file (15 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/pipeline.cpython-39.pyc
ADDED
|
Binary file (11.6 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/pipeline_bm3d.cpython-312.pyc
ADDED
|
Binary file (12.2 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/pipeline_bm3d.cpython-39.pyc
ADDED
|
Binary file (9.51 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/pipeline_utils.cpython-312.pyc
ADDED
|
Binary file (34 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/pipeline_utils.cpython-39.pyc
ADDED
|
Binary file (20 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/__pycache__/refine_network.cpython-312.pyc
ADDED
|
Binary file (22.5 kB). View file
|
|
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/arch_util.py
ADDED
|
@@ -0,0 +1,626 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.nn.init as init
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def initialize_weights(net_l, scale=1):
|
| 8 |
+
if not isinstance(net_l, list):
|
| 9 |
+
net_l = [net_l]
|
| 10 |
+
for net in net_l:
|
| 11 |
+
for m in net.modules():
|
| 12 |
+
if isinstance(m, nn.Conv2d):
|
| 13 |
+
init.kaiming_normal_(m.weight, a=0, mode='fan_in')
|
| 14 |
+
m.weight.data *= scale # for residual block
|
| 15 |
+
if m.bias is not None:
|
| 16 |
+
m.bias.data.zero_()
|
| 17 |
+
elif isinstance(m, nn.Linear):
|
| 18 |
+
init.kaiming_normal_(m.weight, a=0, mode='fan_in')
|
| 19 |
+
m.weight.data *= scale
|
| 20 |
+
if m.bias is not None:
|
| 21 |
+
m.bias.data.zero_()
|
| 22 |
+
elif isinstance(m, nn.BatchNorm2d):
|
| 23 |
+
init.constant_(m.weight, 1)
|
| 24 |
+
init.constant_(m.bias.data, 0.0)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def make_layer(block, n_layers):
|
| 28 |
+
layers = []
|
| 29 |
+
for _ in range(n_layers):
|
| 30 |
+
layers.append(block())
|
| 31 |
+
return nn.Sequential(*layers)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class ResidualBlock_noBN(nn.Module):
|
| 35 |
+
'''Residual block w/o BN
|
| 36 |
+
---Conv-ReLU-Conv-+-
|
| 37 |
+
|________________|
|
| 38 |
+
'''
|
| 39 |
+
|
| 40 |
+
def __init__(self, nf=64):
|
| 41 |
+
super(ResidualBlock_noBN, self).__init__()
|
| 42 |
+
self.conv1 = nn.Conv2d(nf, nf, 3, 1, 1, bias=True)
|
| 43 |
+
self.conv2 = nn.Conv2d(nf, nf, 3, 1, 1, bias=True)
|
| 44 |
+
|
| 45 |
+
# initialization
|
| 46 |
+
initialize_weights([self.conv1, self.conv2], 0.1)
|
| 47 |
+
|
| 48 |
+
def forward(self, x):
|
| 49 |
+
identity = x
|
| 50 |
+
out = F.relu(self.conv1(x), inplace=True)
|
| 51 |
+
out = self.conv2(out)
|
| 52 |
+
return identity + out
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def flow_warp(x, flow, interp_mode='bilinear', padding_mode='zeros'):
|
| 56 |
+
"""Warp an image or feature map with optical flow
|
| 57 |
+
Args:
|
| 58 |
+
x (Tensor): size (N, C, H, W)
|
| 59 |
+
flow (Tensor): size (N, H, W, 2), normal value
|
| 60 |
+
interp_mode (str): 'nearest' or 'bilinear'
|
| 61 |
+
padding_mode (str): 'zeros' or 'border' or 'reflection'
|
| 62 |
+
|
| 63 |
+
Returns:
|
| 64 |
+
Tensor: warped image or feature map
|
| 65 |
+
"""
|
| 66 |
+
assert x.size()[-2:] == flow.size()[1:3]
|
| 67 |
+
B, C, H, W = x.size()
|
| 68 |
+
# mesh grid
|
| 69 |
+
grid_y, grid_x = torch.meshgrid(torch.arange(0, H), torch.arange(0, W))
|
| 70 |
+
grid = torch.stack((grid_x, grid_y), 2).float() # W(x), H(y), 2
|
| 71 |
+
grid.requires_grad = False
|
| 72 |
+
grid = grid.type_as(x)
|
| 73 |
+
vgrid = grid + flow
|
| 74 |
+
# scale grid to [-1,1]
|
| 75 |
+
vgrid_x = 2.0 * vgrid[:, :, :, 0] / max(W - 1, 1) - 1.0
|
| 76 |
+
vgrid_y = 2.0 * vgrid[:, :, :, 1] / max(H - 1, 1) - 1.0
|
| 77 |
+
vgrid_scaled = torch.stack((vgrid_x, vgrid_y), dim=3)
|
| 78 |
+
output = F.grid_sample(x, vgrid_scaled, mode=interp_mode, padding_mode=padding_mode)
|
| 79 |
+
return output
|
| 80 |
+
|
| 81 |
+
"""
|
| 82 |
+
Copyright (c) 2022 Samsung Electronics Co., Ltd.
|
| 83 |
+
|
| 84 |
+
Author(s):
|
| 85 |
+
Luxi Zhao (lucy.zhao@samsung.com; lucyzhao.zlx@gmail.com)
|
| 86 |
+
Abdelrahman Abdelhamed (abdoukamel@gmail.com)
|
| 87 |
+
|
| 88 |
+
Licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) License, (the "License");
|
| 89 |
+
you may not use this file except in compliance with the License.
|
| 90 |
+
You may obtain a copy of the License at https://creativecommons.org/licenses/by-nc-sa/4.0
|
| 91 |
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
|
| 92 |
+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 93 |
+
See the License for the specific language governing permissions and limitations under the License.
|
| 94 |
+
For conditions of distribution and use, see the accompanying LICENSE.md file.
|
| 95 |
+
|
| 96 |
+
"""
|
| 97 |
+
|
| 98 |
+
import numpy as np
|
| 99 |
+
import torch
|
| 100 |
+
import torch.nn as nn
|
| 101 |
+
import torch.nn.functional as F
|
| 102 |
+
def utils_get_image_stats(image_shape, grid_size):
|
| 103 |
+
"""
|
| 104 |
+
Information about the cropped image.
|
| 105 |
+
:return: grid size, tile size, sizes of the 4 margins, meshgrids.
|
| 106 |
+
"""
|
| 107 |
+
|
| 108 |
+
grid_rows = grid_size[0]
|
| 109 |
+
grid_cols = grid_size[1]
|
| 110 |
+
|
| 111 |
+
residual_height = image_shape[0] % grid_rows
|
| 112 |
+
residual_width = image_shape[1] % grid_cols
|
| 113 |
+
|
| 114 |
+
tile_height = image_shape[0] // grid_rows
|
| 115 |
+
tile_width = image_shape[1] // grid_cols
|
| 116 |
+
|
| 117 |
+
margin_top = tile_height // 2
|
| 118 |
+
margin_left = tile_width // 2
|
| 119 |
+
|
| 120 |
+
margin_bot = tile_height + residual_height - margin_top
|
| 121 |
+
margin_right = tile_width + residual_width - margin_left
|
| 122 |
+
|
| 123 |
+
return tile_height, tile_width, margin_top, margin_left, margin_bot, margin_right
|
| 124 |
+
|
| 125 |
+
def apply_ltm_lut(imgs, luts):
|
| 126 |
+
|
| 127 |
+
imgs = (imgs - .5) * 2.
|
| 128 |
+
|
| 129 |
+
grids = imgs.unsqueeze(0).unsqueeze(0)
|
| 130 |
+
luts = luts.unsqueeze(0)
|
| 131 |
+
|
| 132 |
+
outs = F.grid_sample(luts, grids,
|
| 133 |
+
mode='bilinear', padding_mode='border', align_corners=True)
|
| 134 |
+
|
| 135 |
+
return outs.squeeze(0).squeeze(1).permute(1,2,0)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def apply_ltm(image, tone_curve, num_curves):
|
| 139 |
+
"""
|
| 140 |
+
Apply tone curve to an image (patch).
|
| 141 |
+
:param image: (h, w, 3) if num_curves == 3, else (h, w)
|
| 142 |
+
:param tone_curve: (num_curves, control_points)
|
| 143 |
+
:param num_curves: 3 for 1 curve per channel, 1 for 1 curve for all channels.
|
| 144 |
+
:return: tone-mapped image.
|
| 145 |
+
"""
|
| 146 |
+
|
| 147 |
+
if image.shape[-1] == 3:
|
| 148 |
+
if type(image) == np.ndarray:
|
| 149 |
+
r = tone_curve[0][image[..., 0]]
|
| 150 |
+
g = tone_curve[1][image[..., 1]]
|
| 151 |
+
b = tone_curve[2][image[..., 2]]
|
| 152 |
+
new_image = np.stack((r, g, b), axis=-1)
|
| 153 |
+
else:
|
| 154 |
+
r = tone_curve[0][image[..., 0].reshape(-1).long()].reshape(image[..., 0].shape)
|
| 155 |
+
g = tone_curve[1][image[..., 1].reshape(-1).long()].reshape(image[..., 1].shape)
|
| 156 |
+
b = tone_curve[2][image[..., 2].reshape(-1).long()].reshape(image[..., 2].shape)
|
| 157 |
+
new_image = torch.stack((r, g, b), axis=-1)
|
| 158 |
+
# new_image = np.stack((r, g, b), axis=-1)
|
| 159 |
+
else:
|
| 160 |
+
new_image = tone_curve[0][image[..., 0].reshape(-1).long()].reshape(image[..., 0].shape).unsqueeze(dim=2)
|
| 161 |
+
#tone_curve[0][image[..., 0].reshape(-1).long()].reshape(image[..., 0].shape)
|
| 162 |
+
|
| 163 |
+
return new_image
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def apply_gtm(image, tone_curve, num_curves):
|
| 167 |
+
"""
|
| 168 |
+
Apply a single tone curve to an image.
|
| 169 |
+
:param image: (h, w, 3) if num_curves == 3, else (h, w)
|
| 170 |
+
:param tone_curve: (1, num_curves, control_points)
|
| 171 |
+
:param num_curves: 3 for 1 curve per channel, 1 for 1 curve for all channels.
|
| 172 |
+
:return: tone-mapped image.
|
| 173 |
+
"""
|
| 174 |
+
tone_curve = tone_curve[0]
|
| 175 |
+
out = apply_ltm(image, tone_curve, num_curves)
|
| 176 |
+
return out
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def apply_ltm_center(image, tone_curves, stats, num_curves):
|
| 180 |
+
"""
|
| 181 |
+
Apply tone curves to the center region of an image.
|
| 182 |
+
:param image: the original image.
|
| 183 |
+
:param tone_curves: a list of all tone curves in row scan order.
|
| 184 |
+
:return: interpolated center region of an image.
|
| 185 |
+
"""
|
| 186 |
+
grid_rows, grid_cols, tile_height, tile_width, margin_top, margin_left, margin_bot, margin_right, meshgrids = stats
|
| 187 |
+
xs_tl, ys_tl, xs_br, ys_br = meshgrids['center']
|
| 188 |
+
|
| 189 |
+
if torch.cuda.is_available():
|
| 190 |
+
device = torch.device("cuda")
|
| 191 |
+
else:
|
| 192 |
+
device = torch.device("cpu")
|
| 193 |
+
xs_tl = xs_tl.to(device)
|
| 194 |
+
ys_tl = ys_tl.to(device)
|
| 195 |
+
xs_br = xs_br.to(device)
|
| 196 |
+
ys_br = ys_br.to(device)
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
# Get neighbourhoods
|
| 200 |
+
neighbourhoods = []
|
| 201 |
+
for y in range(margin_top, image.shape[0]-margin_bot, tile_height):
|
| 202 |
+
for x in range(margin_left, image.shape[1]-margin_right, tile_width):
|
| 203 |
+
neighbourhoods.append(image[y:y + tile_height, x:x + tile_width, :])
|
| 204 |
+
|
| 205 |
+
assert len(neighbourhoods) == (grid_rows-1) * (grid_cols-1)
|
| 206 |
+
|
| 207 |
+
# Get indices for all 4-tile neighbourhoods
|
| 208 |
+
tile_ids = []
|
| 209 |
+
for i in range(grid_rows - 1):
|
| 210 |
+
for j in range(grid_cols - 1):
|
| 211 |
+
start = i * grid_cols + j
|
| 212 |
+
tile_ids.append([start, start + 1, start + grid_cols, start + grid_cols + 1])
|
| 213 |
+
|
| 214 |
+
# Apply LTM and interpolate
|
| 215 |
+
new_ns = []
|
| 216 |
+
for i, n in enumerate(neighbourhoods):
|
| 217 |
+
n_tile_ids = tile_ids[i] # ids of the 4 tone curves (tiles) of the neighbourhood
|
| 218 |
+
# n_4versions = [apply_ltm(n, tone_curves[j], num_curves) for j in n_tile_ids] # tl, tr, bl, br
|
| 219 |
+
n_4versions = [apply_ltm_lut(n, tone_curves[j])for j in n_tile_ids]
|
| 220 |
+
out = ys_br * xs_br * n_4versions[0] + ys_br * xs_tl * n_4versions[1] + ys_tl * xs_br * n_4versions[2] + ys_tl * xs_tl * n_4versions[3]
|
| 221 |
+
out /= (tile_height-1) * (tile_width-1)
|
| 222 |
+
|
| 223 |
+
new_ns.append(out)
|
| 224 |
+
|
| 225 |
+
# Stack the interpolated neighbourhoods together
|
| 226 |
+
rows = []
|
| 227 |
+
for i in range(grid_rows - 1):
|
| 228 |
+
cols = [new_ns[i * (grid_cols - 1) + j] for j in range(grid_cols - 1)]
|
| 229 |
+
row = torch.cat(cols, dim=1)
|
| 230 |
+
rows.append(row)
|
| 231 |
+
out = torch.cat(rows, dim=0)
|
| 232 |
+
return out
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
def apply_ltm_border(image, tone_curves, stats, num_curves=3):
|
| 236 |
+
"""
|
| 237 |
+
Apply tone curves to the border, not including corner areas.
|
| 238 |
+
:param image: the original image.
|
| 239 |
+
:param tone_curves: a list of all tone curves in row scan order.
|
| 240 |
+
:return: interpolated border regions of the image. In order of top, bottom, left, right.
|
| 241 |
+
"""
|
| 242 |
+
grid_rows, grid_cols, tile_height, tile_width, margin_top, margin_left, margin_bot, margin_right, meshgrids = stats
|
| 243 |
+
(top_xs_l, top_xs_r), (bot_xs_l, bot_xs_r), (left_ys_t, left_ys_b), (right_ys_t, right_ys_b) = meshgrids['border']
|
| 244 |
+
|
| 245 |
+
if torch.cuda.is_available():
|
| 246 |
+
device = torch.device("cuda")
|
| 247 |
+
else:
|
| 248 |
+
device = torch.device("cpu")
|
| 249 |
+
|
| 250 |
+
top_xs_l = top_xs_l.to(device)
|
| 251 |
+
top_xs_r = top_xs_r.to(device)
|
| 252 |
+
bot_xs_l = bot_xs_l.to(device)
|
| 253 |
+
bot_xs_r = bot_xs_r.to(device)
|
| 254 |
+
|
| 255 |
+
left_ys_t = left_ys_t.to(device)
|
| 256 |
+
left_ys_b = left_ys_b.to(device)
|
| 257 |
+
right_ys_t = right_ys_t.to(device)
|
| 258 |
+
right_ys_b = right_ys_b.to(device)
|
| 259 |
+
# top, bottom, left, right neighbourhoods to be interpolated
|
| 260 |
+
ntop = []
|
| 261 |
+
nbot = []
|
| 262 |
+
nleft = []
|
| 263 |
+
nright = []
|
| 264 |
+
|
| 265 |
+
for x in range(margin_left, image.shape[1] - margin_right, tile_width):
|
| 266 |
+
ntop.append(image[:margin_top, x:x + tile_width, :])
|
| 267 |
+
nbot.append(image[-margin_bot:, x:x + tile_width, :])
|
| 268 |
+
|
| 269 |
+
for y in range(margin_top, image.shape[0] - margin_bot, tile_height):
|
| 270 |
+
nleft.append(image[y:y + tile_height, :margin_left, :])
|
| 271 |
+
nright.append(image[y:y + tile_height, -margin_right:, :])
|
| 272 |
+
|
| 273 |
+
def apply_ltm_two_tiles(tc1, tc2, meshgrid1, meshgrid2, nbhd, interp_length, num_curves):
|
| 274 |
+
"""
|
| 275 |
+
Apply tone curve to, and interpolate a two-tile neighbourhood, either horizontal or vertical
|
| 276 |
+
:param tc1: left / top tone curves
|
| 277 |
+
:param tc2: right / bottom tone curves
|
| 278 |
+
:param meshgrid1: left / top meshgrids (leftmost / topmost positions are 0)
|
| 279 |
+
:param meshgrid2: right / bottom meshgrids (rightmost / bottommost positions are 0)
|
| 280 |
+
:param nbhd: neighbourhood to interpolate
|
| 281 |
+
:param interp_length: normalizing factor of the meshgrid.
|
| 282 |
+
Example: if xs = np.meshgrid(np.arange(10)), then interp_length = 9
|
| 283 |
+
:return: interpolated neighbourhood
|
| 284 |
+
"""
|
| 285 |
+
|
| 286 |
+
# new_nbhd1 = apply_ltm(nbhd, tc1, num_curves)
|
| 287 |
+
# new_nbhd2 = apply_ltm(nbhd, tc2, num_curves)
|
| 288 |
+
|
| 289 |
+
new_nbhd1 = apply_ltm_lut(nbhd, tc1)
|
| 290 |
+
new_nbhd2 = apply_ltm_lut(nbhd, tc2)
|
| 291 |
+
|
| 292 |
+
out = meshgrid1 * new_nbhd2 + meshgrid2 * new_nbhd1
|
| 293 |
+
out /= interp_length
|
| 294 |
+
return out
|
| 295 |
+
|
| 296 |
+
new_ntop = [apply_ltm_two_tiles(tone_curves[i], # left tone curve
|
| 297 |
+
tone_curves[i + 1], # right tone curve
|
| 298 |
+
top_xs_l, top_xs_r,
|
| 299 |
+
n, tile_width - 1, num_curves) for i, n in enumerate(ntop)]
|
| 300 |
+
|
| 301 |
+
new_nbot = [apply_ltm_two_tiles(tone_curves[(grid_rows - 1) * grid_cols + i], # left tone curve
|
| 302 |
+
tone_curves[(grid_rows - 1) * grid_cols + i + 1], # right tone curve
|
| 303 |
+
bot_xs_l, bot_xs_r,
|
| 304 |
+
n, tile_width - 1, num_curves) for i, n in enumerate(nbot)]
|
| 305 |
+
|
| 306 |
+
new_nleft = [apply_ltm_two_tiles(tone_curves[i * grid_cols], # top tone curve
|
| 307 |
+
tone_curves[(i + 1) * grid_cols], # bottom tone curve
|
| 308 |
+
left_ys_t, left_ys_b,
|
| 309 |
+
n, tile_height - 1, num_curves) for i, n in enumerate(nleft)]
|
| 310 |
+
|
| 311 |
+
new_nright = [apply_ltm_two_tiles(tone_curves[(i + 1) * grid_cols - 1], # top tone curve
|
| 312 |
+
tone_curves[(i + 2) * grid_cols - 1], # bottom tone curve
|
| 313 |
+
right_ys_t, right_ys_b,
|
| 314 |
+
n, tile_height - 1, num_curves) for i, n in enumerate(nright)]
|
| 315 |
+
|
| 316 |
+
new_ntop = torch.cat(new_ntop, dim=1)
|
| 317 |
+
new_nbot = torch.cat(new_nbot, dim=1)
|
| 318 |
+
new_nleft = torch.cat(new_nleft, dim=0)
|
| 319 |
+
new_nright = torch.cat(new_nright, dim=0)
|
| 320 |
+
return new_ntop, new_nbot, new_nleft, new_nright
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
def apply_ltm_corner(image, tone_curves, stats, num_curves=3):
|
| 324 |
+
"""
|
| 325 |
+
tone_curves: a list of all tone curves in row scan order.
|
| 326 |
+
return: interpolated corner tiles in the order of top left, top right, bot left, bot right
|
| 327 |
+
"""
|
| 328 |
+
grid_rows, grid_cols, tile_height, tile_width, margin_top, margin_left, margin_bot, margin_right, _ = stats
|
| 329 |
+
|
| 330 |
+
corner_ids = [0, grid_cols - 1, -grid_rows, -1]
|
| 331 |
+
tl_tile = image[:margin_top, :margin_left]
|
| 332 |
+
tr_tile = image[:margin_top, -margin_right:]
|
| 333 |
+
bl_tile = image[-margin_bot:, :margin_left]
|
| 334 |
+
br_tile = image[-margin_bot:, -margin_right:]
|
| 335 |
+
|
| 336 |
+
corner_tiles = [tl_tile, tr_tile, bl_tile, br_tile]
|
| 337 |
+
corner_tcs = [tone_curves[i] for i in corner_ids] # tcs: (grid_size, num_curves, control_points)
|
| 338 |
+
#new_tiles = [apply_ltm(corner_tiles[i], corner_tcs[i], num_curves) for i in range(len(corner_tcs))]
|
| 339 |
+
new_tiles = [apply_ltm_lut(corner_tiles[i],corner_tcs[i]) for i in range(len(corner_tcs))]
|
| 340 |
+
|
| 341 |
+
return new_tiles[0], new_tiles[1], new_tiles[2], new_tiles[3]
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
# def get_meshgrids(height, width):
|
| 345 |
+
# """
|
| 346 |
+
# Get two meshgrids of size (height, width). One with top left corner being (0, 0),
|
| 347 |
+
# the other with bottom right corner being (0, 0).
|
| 348 |
+
# :return: top left xs, ys, bottom right xs, ys
|
| 349 |
+
# """
|
| 350 |
+
# xs, ys = np.meshgrid(np.arange(width), np.arange(height))
|
| 351 |
+
# newys, newxs = torch.meshgrid(torch.arange(height, dtype=torch.int32), torch.arange(width, dtype=torch.int32))
|
| 352 |
+
# # mesh grid for top left corner
|
| 353 |
+
# xs_tl = np.tile(np.abs(xs)[..., np.newaxis], 3) # [0, 1, 2, ..., tile_width-1]
|
| 354 |
+
# ys_tl = np.tile(np.abs(ys)[..., np.newaxis], 3)
|
| 355 |
+
# new_xs_tl = newxs[..., None].abs().repeat(1, 1, 3)
|
| 356 |
+
# new_ys_tl = newys[..., None].abs().repeat(1, 1, 3)
|
| 357 |
+
# # mesh grid for bottom right corner
|
| 358 |
+
# xs_br = np.tile(np.abs(xs - width + 1)[..., np.newaxis], 3) # [-(tile_width-1), ..., -2, -1, 0]
|
| 359 |
+
# ys_br = np.tile(np.abs(ys - height + 1)[..., np.newaxis], 3)
|
| 360 |
+
|
| 361 |
+
# new_xs_br = (newxs - width + 1).abs()[..., None].repeat(1, 1, 3)
|
| 362 |
+
# new_ys_br = (newys - width + 1).abs()[..., None].repeat(1, 1, 3)
|
| 363 |
+
# # return xs_tl, ys_tl, xs_br, ys_br
|
| 364 |
+
# return new_xs_tl, new_ys_tl, new_xs_br, new_ys_br
|
| 365 |
+
def get_meshgrids(height, width):
|
| 366 |
+
"""
|
| 367 |
+
Get two meshgrids of size (height, width). One with top left corner being (0, 0),
|
| 368 |
+
the other with bottom right corner being (0, 0).
|
| 369 |
+
:return: top left xs, ys, bottom right xs, ys
|
| 370 |
+
"""
|
| 371 |
+
xs, ys = np.meshgrid(np.arange(width), np.arange(height))
|
| 372 |
+
# mesh grid for top left corner
|
| 373 |
+
xs_tl = np.tile(np.abs(xs)[..., np.newaxis], 3) # [0, 1, 2, ..., tile_width-1]
|
| 374 |
+
ys_tl = np.tile(np.abs(ys)[..., np.newaxis], 3)
|
| 375 |
+
# mesh grid for bottom right corner
|
| 376 |
+
xs_br = np.tile(np.abs(xs - width + 1)[..., np.newaxis], 3) # [-(tile_width-1), ..., -2, -1, 0]
|
| 377 |
+
ys_br = np.tile(np.abs(ys - height + 1)[..., np.newaxis], 3)
|
| 378 |
+
|
| 379 |
+
return torch.tensor(xs_tl), torch.tensor(ys_tl), torch.tensor(xs_br), torch.tensor(ys_br)
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
def get_meshgrid_center(tile_height, tile_width):
|
| 384 |
+
return get_meshgrids(tile_height, tile_width)
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
def get_meshgrid_border(tile_height, tile_width, margin_top, margin_left, margin_bot, margin_right):
|
| 388 |
+
"""
|
| 389 |
+
:return: meshgrids for the 4 border regions, in the order of top, bottom, left, right
|
| 390 |
+
"""
|
| 391 |
+
# top
|
| 392 |
+
top_xs_l, _, top_xs_r, _ = get_meshgrids(margin_top, tile_width)
|
| 393 |
+
|
| 394 |
+
# bottom
|
| 395 |
+
bot_xs_l, _, bot_xs_r, _ = get_meshgrids(margin_bot, tile_width)
|
| 396 |
+
|
| 397 |
+
# left
|
| 398 |
+
_, left_ys_t, _, left_ys_b = get_meshgrids(tile_height, margin_left)
|
| 399 |
+
|
| 400 |
+
# right
|
| 401 |
+
_, right_ys_t, _, right_ys_b = get_meshgrids(tile_height, margin_right)
|
| 402 |
+
|
| 403 |
+
return (top_xs_l, top_xs_r), (bot_xs_l, bot_xs_r), (left_ys_t, left_ys_b), (right_ys_t, right_ys_b)
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
def get_image_stats(image, grid_size):
|
| 407 |
+
"""
|
| 408 |
+
Information about the cropped image.
|
| 409 |
+
:param image: the original image
|
| 410 |
+
:return: grid size, tile size, sizes of the 4 margins, meshgrids.
|
| 411 |
+
"""
|
| 412 |
+
|
| 413 |
+
grid_rows = grid_size[0]
|
| 414 |
+
grid_cols = grid_size[1]
|
| 415 |
+
|
| 416 |
+
tile_height, tile_width, margin_top, margin_left, margin_bot, margin_right = utils_get_image_stats(image.shape,
|
| 417 |
+
grid_size)
|
| 418 |
+
|
| 419 |
+
meshgrid_center = get_meshgrid_center(tile_height, tile_width)
|
| 420 |
+
meshgrid_border = get_meshgrid_border(tile_height, tile_width, margin_top, margin_left, margin_bot, margin_right)
|
| 421 |
+
|
| 422 |
+
meshgrids = {
|
| 423 |
+
'center': meshgrid_center,
|
| 424 |
+
'border': meshgrid_border
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
return grid_rows, grid_cols, tile_height, tile_width, margin_top, margin_left, margin_bot, margin_right, meshgrids
|
| 428 |
+
|
| 429 |
+
|
| 430 |
+
#自己构造image 1 * 512 * 512 *3,tone_curve 1 * 64 * 3 * 256 维度得tf tensor 然后只在这里debug
|
| 431 |
+
def do_interpolation_lut(image, tone_curves, grid_size, num_curves=3):
|
| 432 |
+
"""
|
| 433 |
+
Perform tone mapping and interpolation on an image.
|
| 434 |
+
Center region: bilinear interpolation.
|
| 435 |
+
Border region: linear interpolation.
|
| 436 |
+
Corner region: no interpolation.
|
| 437 |
+
:param num_curves: 3 -> 1 curve for each R,G,B channel, 1 -> 1 curve for all channels
|
| 438 |
+
:param image: input int8
|
| 439 |
+
:param tone_curves: (grid_size, num_curves, control_points)
|
| 440 |
+
:param grid_size: (ncols, nrows)
|
| 441 |
+
:return: image: float32, between [0-1]
|
| 442 |
+
"""
|
| 443 |
+
if grid_size[0] == 1 and grid_size[1] == 1:
|
| 444 |
+
return apply_gtm(image, tone_curves, num_curves).astype(np.float64)
|
| 445 |
+
|
| 446 |
+
# get image statistics
|
| 447 |
+
stats = get_image_stats(image, grid_size)
|
| 448 |
+
|
| 449 |
+
|
| 450 |
+
|
| 451 |
+
# Center area:
|
| 452 |
+
center = apply_ltm_center(image, tone_curves, stats, num_curves)
|
| 453 |
+
|
| 454 |
+
# Border area:
|
| 455 |
+
b_top, b_bot, b_left, b_right = apply_ltm_border(image, tone_curves, stats, num_curves)
|
| 456 |
+
|
| 457 |
+
# Corner area:
|
| 458 |
+
tlc, trc, blc, brc = apply_ltm_corner(image, tone_curves, stats, num_curves)
|
| 459 |
+
|
| 460 |
+
# stack the corners, borders, and center together
|
| 461 |
+
row_t = torch.cat([tlc, b_top, trc], dim=1)
|
| 462 |
+
row_c = torch.cat([b_left, center, b_right], dim=1)
|
| 463 |
+
row_b = torch.cat([blc, b_bot, brc], dim=1)
|
| 464 |
+
out = torch.cat([row_t, row_c, row_b], dim=0)
|
| 465 |
+
|
| 466 |
+
assert out.shape == image.shape
|
| 467 |
+
|
| 468 |
+
return out
|
| 469 |
+
|
| 470 |
+
|
| 471 |
+
|
| 472 |
+
class Model(nn.Module):
|
| 473 |
+
def __init__(self):
|
| 474 |
+
super(Model, self).__init__()
|
| 475 |
+
# self.conv1 = nn.Conv2d(3, 4, kernel_size=3, padding=1)
|
| 476 |
+
# self.pool1 = nn.MaxPool2d(2)
|
| 477 |
+
|
| 478 |
+
# self.conv2 = nn.Conv2d(4, 8, kernel_size=3, padding=1)
|
| 479 |
+
# self.pool2 = nn.MaxPool2d(2)
|
| 480 |
+
|
| 481 |
+
# self.conv3 = nn.Conv2d(8, 16, kernel_size=3, padding=1)
|
| 482 |
+
# self.pool3 = nn.MaxPool2d(2)
|
| 483 |
+
|
| 484 |
+
# self.conv4 = nn.Conv2d(16, 32, kernel_size=3, padding=1)
|
| 485 |
+
# self.pool4 = nn.MaxPool2d(2)
|
| 486 |
+
|
| 487 |
+
# self.conv5 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
|
| 488 |
+
# self.pool5 = nn.MaxPool2d(2)
|
| 489 |
+
|
| 490 |
+
# self.conv6 = nn.Conv2d(64, 768, kernel_size=3, padding=1)
|
| 491 |
+
# self.pool6 = nn.MaxPool2d(2)
|
| 492 |
+
|
| 493 |
+
self.layer_1 = nn.Sequential(
|
| 494 |
+
nn.Conv2d(3, 4, kernel_size=3, padding=1),
|
| 495 |
+
nn.BatchNorm2d(4),
|
| 496 |
+
nn.ReLU(),
|
| 497 |
+
nn.MaxPool2d(2)
|
| 498 |
+
)
|
| 499 |
+
self.layer_2 = nn.Sequential(
|
| 500 |
+
nn.Conv2d(4, 8, kernel_size=3, padding=1),
|
| 501 |
+
nn.BatchNorm2d(8),
|
| 502 |
+
nn.ReLU(),
|
| 503 |
+
nn.MaxPool2d(2)
|
| 504 |
+
)
|
| 505 |
+
self.layer_3 = nn.Sequential(
|
| 506 |
+
nn.Conv2d(8, 16, kernel_size=3, padding=1),
|
| 507 |
+
nn.BatchNorm2d(16),
|
| 508 |
+
nn.ReLU(),
|
| 509 |
+
nn.MaxPool2d(2)
|
| 510 |
+
)
|
| 511 |
+
self.layer_4 = nn.Sequential(
|
| 512 |
+
nn.Conv2d(16, 32, kernel_size=3, padding=1),
|
| 513 |
+
nn.BatchNorm2d(32),
|
| 514 |
+
nn.ReLU(),
|
| 515 |
+
nn.MaxPool2d(2)
|
| 516 |
+
)
|
| 517 |
+
self.layer_5 = nn.Sequential(
|
| 518 |
+
nn.Conv2d(32, 64, kernel_size=3, padding=1),
|
| 519 |
+
nn.BatchNorm2d(64),
|
| 520 |
+
nn.ReLU(),
|
| 521 |
+
nn.MaxPool2d(2)
|
| 522 |
+
)
|
| 523 |
+
self.layer_6 = nn.Sequential(
|
| 524 |
+
nn.Conv2d(64, 768, kernel_size=3, padding=1),
|
| 525 |
+
nn.BatchNorm2d(768),
|
| 526 |
+
nn.Sigmoid(),
|
| 527 |
+
nn.MaxPool2d(2)
|
| 528 |
+
)
|
| 529 |
+
|
| 530 |
+
|
| 531 |
+
def forward(self, x):
|
| 532 |
+
|
| 533 |
+
'''
|
| 534 |
+
|
| 535 |
+
original = x
|
| 536 |
+
x = self.conv1(x)
|
| 537 |
+
x = self.pool1(x)
|
| 538 |
+
|
| 539 |
+
x = self.conv2(x)
|
| 540 |
+
x = self.pool2(x)
|
| 541 |
+
|
| 542 |
+
x = self.conv3(x)
|
| 543 |
+
x = self.pool3(x)
|
| 544 |
+
|
| 545 |
+
x = self.conv4(x)
|
| 546 |
+
x = self.pool4(x)
|
| 547 |
+
|
| 548 |
+
x = self.conv5(x)
|
| 549 |
+
x = self.pool5(x)
|
| 550 |
+
|
| 551 |
+
x = self.conv6(x)
|
| 552 |
+
x = self.pool6(x)
|
| 553 |
+
oldres = x
|
| 554 |
+
x = original
|
| 555 |
+
'''
|
| 556 |
+
|
| 557 |
+
x = self.layer_1(x)
|
| 558 |
+
|
| 559 |
+
x = self.layer_2(x)
|
| 560 |
+
|
| 561 |
+
x = self.layer_3(x)
|
| 562 |
+
|
| 563 |
+
x = self.layer_4(x)
|
| 564 |
+
|
| 565 |
+
x = self.layer_5(x)
|
| 566 |
+
|
| 567 |
+
x = self.layer_6(x)
|
| 568 |
+
|
| 569 |
+
x = x.reshape(x.shape[0], x.shape[2] * x.shape[3], 3, int(x.shape[1] / 3))
|
| 570 |
+
return x
|
| 571 |
+
|
| 572 |
+
|
| 573 |
+
def _lut_transform(imgs, luts):
|
| 574 |
+
# img (b, 3, h, w), lut (b, c, m, m, m)
|
| 575 |
+
if imgs.shape[1]==1:
|
| 576 |
+
|
| 577 |
+
#for gray image pro-processs
|
| 578 |
+
luts = luts.expand(1,1,64,64,64)
|
| 579 |
+
# normalize pixel values
|
| 580 |
+
imgs = (imgs - .5) * 2.
|
| 581 |
+
grids = (imgs.unsqueeze(4)).repeat(1,1,1,1,3)
|
| 582 |
+
else:
|
| 583 |
+
# normalize pixel values
|
| 584 |
+
imgs = (imgs - .5) * 2.
|
| 585 |
+
# reshape img to grid of shape (b, 1, h, w, 3)
|
| 586 |
+
# imgs = imgs.permute(2,0,1).unsqueeze(dim=0)
|
| 587 |
+
# grids = imgs.permute(0, 2, 3, 1).unsqueeze(1)
|
| 588 |
+
grids = imgs.unsqueeze(0).unsqueeze(0)
|
| 589 |
+
luts = luts.unsqueeze(0)
|
| 590 |
+
# after gridsampling, output is of shape (b, c, 1, h, w)
|
| 591 |
+
outs = F.grid_sample(luts, grids,
|
| 592 |
+
mode='bilinear', padding_mode='border', align_corners=True)
|
| 593 |
+
return outs.squeeze(2)
|
| 594 |
+
|
| 595 |
+
|
| 596 |
+
if __name__ == '__main__':
|
| 597 |
+
|
| 598 |
+
import torch
|
| 599 |
+
import cv2
|
| 600 |
+
|
| 601 |
+
grid_size = [8,8]
|
| 602 |
+
|
| 603 |
+
np.random.seed(42)
|
| 604 |
+
rand_img = np.random.random((512, 512, 3))
|
| 605 |
+
luts_np = np.random.random((64, 3, 9))
|
| 606 |
+
|
| 607 |
+
img_torch = torch.tensor(rand_img, dtype=torch.float32).cuda()
|
| 608 |
+
luts_torch = torch.tensor(luts_np, dtype=torch.float32).cuda()
|
| 609 |
+
|
| 610 |
+
|
| 611 |
+
iluts = []
|
| 612 |
+
for i in range(luts_torch.shape[0]):
|
| 613 |
+
iluts.append(torch.stack(
|
| 614 |
+
torch.meshgrid(*(luts_torch[i].unbind(0)[::-1])),
|
| 615 |
+
dim=0).flip(0))
|
| 616 |
+
iluts = torch.stack(iluts, dim=0)
|
| 617 |
+
|
| 618 |
+
|
| 619 |
+
result = do_interpolation_lut(img_torch, iluts, grid_size)
|
| 620 |
+
print(result)
|
| 621 |
+
|
| 622 |
+
|
| 623 |
+
|
| 624 |
+
|
| 625 |
+
|
| 626 |
+
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/color.py
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def rgb2gray(data):
|
| 5 |
+
return 0.299 * data[:, :, 0] + \
|
| 6 |
+
0.587 * data[:, :, 1] + \
|
| 7 |
+
0.114 * data[:, :, 2]
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def rgb2ycc(data, rule="bt601"):
|
| 11 |
+
# map to select kr and kb
|
| 12 |
+
kr_kb_dict = {"bt601": [0.299, 0.114],
|
| 13 |
+
"bt709": [0.2126, 0.0722],
|
| 14 |
+
"bt2020": [0.2627, 0.0593]}
|
| 15 |
+
|
| 16 |
+
kr = kr_kb_dict[rule][0]
|
| 17 |
+
kb = kr_kb_dict[rule][1]
|
| 18 |
+
kg = 1 - (kr + kb)
|
| 19 |
+
|
| 20 |
+
output = np.empty(np.shape(data), dtype=np.float32)
|
| 21 |
+
output[:, :, 0] = kr * data[:, :, 0] + \
|
| 22 |
+
kg * data[:, :, 1] + \
|
| 23 |
+
kb * data[:, :, 2]
|
| 24 |
+
output[:, :, 1] = 0.5 * ((data[:, :, 2] - output[:, :, 0]) / (1 - kb))
|
| 25 |
+
output[:, :, 2] = 0.5 * ((data[:, :, 0] - output[:, :, 0]) / (1 - kr))
|
| 26 |
+
|
| 27 |
+
return output
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def ycc2rgb(data, rule="bt601"):
|
| 31 |
+
# map to select kr and kb
|
| 32 |
+
kr_kb_dict = {"bt601": [0.299, 0.114],
|
| 33 |
+
"bt709": [0.2126, 0.0722],
|
| 34 |
+
"bt2020": [0.2627, 0.0593]}
|
| 35 |
+
|
| 36 |
+
kr = kr_kb_dict[rule][0]
|
| 37 |
+
kb = kr_kb_dict[rule][1]
|
| 38 |
+
kg = 1 - (kr + kb)
|
| 39 |
+
|
| 40 |
+
output = np.empty(np.shape(data), dtype=np.float32)
|
| 41 |
+
output[:, :, 0] = 2. * data[:, :, 2] * (1 - kr) + data[:, :, 0]
|
| 42 |
+
output[:, :, 2] = 2. * data[:, :, 1] * (1 - kb) + data[:, :, 0]
|
| 43 |
+
output[:, :, 1] = (data[:, :, 0] - kr * output[:, :, 0] - kb * output[:, :, 2]) / kg
|
| 44 |
+
|
| 45 |
+
return output
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def degamma_srgb(data, clip_range=[0, 65535]):
|
| 49 |
+
# bring data in range 0 to 1
|
| 50 |
+
data = np.clip(data, clip_range[0], clip_range[1])
|
| 51 |
+
data = np.divide(data, clip_range[1])
|
| 52 |
+
|
| 53 |
+
data = np.asarray(data)
|
| 54 |
+
mask = data > 0.04045
|
| 55 |
+
|
| 56 |
+
# basically, if data[x, y, c] > 0.04045, data[x, y, c] = ( (data[x, y, c] + 0.055) / 1.055 ) ^ 2.4
|
| 57 |
+
# else, data[x, y, c] = data[x, y, c] / 12.92
|
| 58 |
+
data[mask] += 0.055
|
| 59 |
+
data[mask] /= 1.055
|
| 60 |
+
data[mask] **= 2.4
|
| 61 |
+
|
| 62 |
+
data[np.invert(mask)] /= 12.92
|
| 63 |
+
|
| 64 |
+
# rescale
|
| 65 |
+
return np.clip(data * clip_range[1], clip_range[0], clip_range[1])
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def degamma_adobe_rgb_1998(data, clip_range=[0, 65535]):
|
| 69 |
+
# bring data in range 0 to 1
|
| 70 |
+
data = np.clip(data, clip_range[0], clip_range[1])
|
| 71 |
+
data = np.divide(data, clip_range[1])
|
| 72 |
+
|
| 73 |
+
data = np.power(data, 2.2) # originally raised to 2.19921875
|
| 74 |
+
|
| 75 |
+
# rescale
|
| 76 |
+
return np.clip(data * clip_range[1], clip_range[0], clip_range[1])
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def rgb2xyz(data, color_space="srgb", clip_range=[0, 255]):
|
| 80 |
+
# input rgb in range clip_range
|
| 81 |
+
# output xyz is in range 0 to 1
|
| 82 |
+
if color_space == "srgb":
|
| 83 |
+
# degamma / linearization
|
| 84 |
+
data = degamma_srgb(data, clip_range)
|
| 85 |
+
data = np.float32(data)
|
| 86 |
+
data = np.divide(data, clip_range[1])
|
| 87 |
+
|
| 88 |
+
# matrix multiplication`
|
| 89 |
+
output = np.empty(np.shape(data), dtype=np.float32)
|
| 90 |
+
output[:, :, 0] = data[:, :, 0] * 0.4124 + data[:, :, 1] * 0.3576 + data[:, :, 2] * 0.1805
|
| 91 |
+
output[:, :, 1] = data[:, :, 0] * 0.2126 + data[:, :, 1] * 0.7152 + data[:, :, 2] * 0.0722
|
| 92 |
+
output[:, :, 2] = data[:, :, 0] * 0.0193 + data[:, :, 1] * 0.1192 + data[:, :, 2] * 0.9505
|
| 93 |
+
elif color_space == "adobe-rgb-1998":
|
| 94 |
+
# degamma / linearization
|
| 95 |
+
data = degamma_adobe_rgb_1998(data, clip_range)
|
| 96 |
+
data = np.float32(data)
|
| 97 |
+
data = np.divide(data, clip_range[1])
|
| 98 |
+
|
| 99 |
+
# matrix multiplication
|
| 100 |
+
output = np.empty(np.shape(data), dtype=np.float32)
|
| 101 |
+
output[:, :, 0] = data[:, :, 0] * 0.5767309 + data[:, :, 1] * 0.1855540 + data[:, :, 2] * 0.1881852
|
| 102 |
+
output[:, :, 1] = data[:, :, 0] * 0.2973769 + data[:, :, 1] * 0.6273491 + data[:, :, 2] * 0.0752741
|
| 103 |
+
output[:, :, 2] = data[:, :, 0] * 0.0270343 + data[:, :, 1] * 0.0706872 + data[:, :, 2] * 0.9911085
|
| 104 |
+
elif color_space == "linear":
|
| 105 |
+
# matrix multiplication`
|
| 106 |
+
output = np.empty(np.shape(data), dtype=np.float32)
|
| 107 |
+
data = np.float32(data)
|
| 108 |
+
data = np.divide(data, clip_range[1])
|
| 109 |
+
output[:, :, 0] = data[:, :, 0] * 0.4124 + data[:, :, 1] * 0.3576 + data[:, :, 2] * 0.1805
|
| 110 |
+
output[:, :, 1] = data[:, :, 0] * 0.2126 + data[:, :, 1] * 0.7152 + data[:, :, 2] * 0.0722
|
| 111 |
+
output[:, :, 2] = data[:, :, 0] * 0.0193 + data[:, :, 1] * 0.1192 + data[:, :, 2] * 0.9505
|
| 112 |
+
else:
|
| 113 |
+
print("Warning! color_space must be srgb or adobe-rgb-1998.")
|
| 114 |
+
return
|
| 115 |
+
|
| 116 |
+
return output
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def gamma_srgb(data, clip_range=[0, 65535]):
|
| 120 |
+
# bring data in range 0 to 1
|
| 121 |
+
data = np.clip(data, clip_range[0], clip_range[1])
|
| 122 |
+
data = np.divide(data, clip_range[1])
|
| 123 |
+
|
| 124 |
+
data = np.asarray(data)
|
| 125 |
+
mask = data > 0.0031308
|
| 126 |
+
|
| 127 |
+
# basically, if data[x, y, c] > 0.0031308, data[x, y, c] = 1.055 * ( var_R(i, j) ^ ( 1 / 2.4 ) ) - 0.055
|
| 128 |
+
# else, data[x, y, c] = data[x, y, c] * 12.92
|
| 129 |
+
data[mask] **= 0.4167
|
| 130 |
+
data[mask] *= 1.055
|
| 131 |
+
data[mask] -= 0.055
|
| 132 |
+
|
| 133 |
+
data[np.invert(mask)] *= 12.92
|
| 134 |
+
|
| 135 |
+
# rescale
|
| 136 |
+
return np.clip(data * clip_range[1], clip_range[0], clip_range[1])
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def gamma_adobe_rgb_1998(data, clip_range=[0, 65535]):
|
| 140 |
+
# bring data in range 0 to 1
|
| 141 |
+
data = np.clip(data, clip_range[0], clip_range[1])
|
| 142 |
+
data = np.divide(data, clip_range[1])
|
| 143 |
+
|
| 144 |
+
data = np.power(data, 0.4545)
|
| 145 |
+
|
| 146 |
+
# rescale
|
| 147 |
+
return np.clip(data * clip_range[1], clip_range[0], clip_range[1])
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def xyz2rgb(data, color_space="srgb", clip_range=[0, 255]):
|
| 151 |
+
# input xyz is in range 0 to 1
|
| 152 |
+
# output rgb in clip_range
|
| 153 |
+
|
| 154 |
+
# allocate space for output
|
| 155 |
+
output = np.empty(np.shape(data), dtype=np.float32)
|
| 156 |
+
|
| 157 |
+
if color_space == "srgb":
|
| 158 |
+
# matrix multiplication
|
| 159 |
+
output[:, :, 0] = data[:, :, 0] * 3.2406 + data[:, :, 1] * -1.5372 + data[:, :, 2] * -0.4986
|
| 160 |
+
output[:, :, 1] = data[:, :, 0] * -0.9689 + data[:, :, 1] * 1.8758 + data[:, :, 2] * 0.0415
|
| 161 |
+
output[:, :, 2] = data[:, :, 0] * 0.0557 + data[:, :, 1] * -0.2040 + data[:, :, 2] * 1.0570
|
| 162 |
+
|
| 163 |
+
# gamma to retain nonlinearity
|
| 164 |
+
output = gamma_srgb(output * clip_range[1], clip_range)
|
| 165 |
+
elif color_space == "adobe-rgb-1998":
|
| 166 |
+
# matrix multiplication
|
| 167 |
+
output[:, :, 0] = data[:, :, 0] * 2.0413690 + data[:, :, 1] * -0.5649464 + data[:, :, 2] * -0.3446944
|
| 168 |
+
output[:, :, 1] = data[:, :, 0] * -0.9692660 + data[:, :, 1] * 1.8760108 + data[:, :, 2] * 0.0415560
|
| 169 |
+
output[:, :, 2] = data[:, :, 0] * 0.0134474 + data[:, :, 1] * -0.1183897 + data[:, :, 2] * 1.0154096
|
| 170 |
+
|
| 171 |
+
# gamma to retain nonlinearity
|
| 172 |
+
output = gamma_adobe_rgb_1998(output * clip_range[1], clip_range)
|
| 173 |
+
elif color_space == "linear":
|
| 174 |
+
|
| 175 |
+
# matrix multiplication
|
| 176 |
+
output[:, :, 0] = data[:, :, 0] * 3.2406 + data[:, :, 1] * -1.5372 + data[:, :, 2] * -0.4986
|
| 177 |
+
output[:, :, 1] = data[:, :, 0] * -0.9689 + data[:, :, 1] * 1.8758 + data[:, :, 2] * 0.0415
|
| 178 |
+
output[:, :, 2] = data[:, :, 0] * 0.0557 + data[:, :, 1] * -0.2040 + data[:, :, 2] * 1.0570
|
| 179 |
+
|
| 180 |
+
# gamma to retain nonlinearity
|
| 181 |
+
output = output * clip_range[1]
|
| 182 |
+
else:
|
| 183 |
+
print("Warning! color_space must be srgb or adobe-rgb-1998.")
|
| 184 |
+
return
|
| 185 |
+
|
| 186 |
+
return output
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def get_xyz_reference(cie_version="1931", illuminant="d65"):
|
| 190 |
+
if cie_version == "1931":
|
| 191 |
+
xyz_reference_dictionary = {"A": [109.850, 100.0, 35.585],
|
| 192 |
+
"B": [99.0927, 100.0, 85.313],
|
| 193 |
+
"C": [98.074, 100.0, 118.232],
|
| 194 |
+
"d50": [96.422, 100.0, 82.521],
|
| 195 |
+
"d55": [95.682, 100.0, 92.149],
|
| 196 |
+
"d65": [95.047, 100.0, 108.883],
|
| 197 |
+
"d75": [94.972, 100.0, 122.638],
|
| 198 |
+
"E": [100.0, 100.0, 100.0],
|
| 199 |
+
"F1": [92.834, 100.0, 103.665],
|
| 200 |
+
"F2": [99.187, 100.0, 67.395],
|
| 201 |
+
"F3": [103.754, 100.0, 49.861],
|
| 202 |
+
"F4": [109.147, 100.0, 38.813],
|
| 203 |
+
"F5": [90.872, 100.0, 98.723],
|
| 204 |
+
"F6": [97.309, 100.0, 60.191],
|
| 205 |
+
"F7": [95.044, 100.0, 108.755],
|
| 206 |
+
"F8": [96.413, 100.0, 82.333],
|
| 207 |
+
"F9": [100.365, 100.0, 67.868],
|
| 208 |
+
"F10": [96.174, 100.0, 81.712],
|
| 209 |
+
"F11": [100.966, 100.0, 64.370],
|
| 210 |
+
"F12": [108.046, 100.0, 39.228]}
|
| 211 |
+
elif cie_version == "1964":
|
| 212 |
+
xyz_reference_dictionary = {"A": [111.144, 100.0, 35.200],
|
| 213 |
+
"B": [99.178, 100.0, 84.3493],
|
| 214 |
+
"C": [97.285, 100.0, 116.145],
|
| 215 |
+
"D50": [96.720, 100.0, 81.427],
|
| 216 |
+
"D55": [95.799, 100.0, 90.926],
|
| 217 |
+
"D65": [94.811, 100.0, 107.304],
|
| 218 |
+
"D75": [94.416, 100.0, 120.641],
|
| 219 |
+
"E": [100.0, 100.0, 100.0],
|
| 220 |
+
"F1": [94.791, 100.0, 103.191],
|
| 221 |
+
"F2": [103.280, 100.0, 69.026],
|
| 222 |
+
"F3": [108.968, 100.0, 51.965],
|
| 223 |
+
"F4": [114.961, 100.0, 40.963],
|
| 224 |
+
"F5": [93.369, 100.0, 98.636],
|
| 225 |
+
"F6": [102.148, 100.0, 62.074],
|
| 226 |
+
"F7": [95.792, 100.0, 107.687],
|
| 227 |
+
"F8": [97.115, 100.0, 81.135],
|
| 228 |
+
"F9": [102.116, 100.0, 67.826],
|
| 229 |
+
"F10": [99.001, 100.0, 83.134],
|
| 230 |
+
"F11": [103.866, 100.0, 65.627],
|
| 231 |
+
"F12": [111.428, 100.0, 40.353]}
|
| 232 |
+
else:
|
| 233 |
+
print("Warning! cie_version must be 1931 or 1964.")
|
| 234 |
+
return
|
| 235 |
+
return np.divide(xyz_reference_dictionary[illuminant], 100.0)
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def xyz2lab(data, cie_version="1931", illuminant="d65"):
|
| 239 |
+
xyz_reference = get_xyz_reference(cie_version, illuminant)
|
| 240 |
+
|
| 241 |
+
data = data
|
| 242 |
+
data[:, :, 0] = data[:, :, 0] / xyz_reference[0]
|
| 243 |
+
data[:, :, 1] = data[:, :, 1] / xyz_reference[1]
|
| 244 |
+
data[:, :, 2] = data[:, :, 2] / xyz_reference[2]
|
| 245 |
+
|
| 246 |
+
data = np.asarray(data)
|
| 247 |
+
|
| 248 |
+
# if data[x, y, c] > 0.008856, data[x, y, c] = data[x, y, c] ^ (1/3)
|
| 249 |
+
# else, data[x, y, c] = 7.787 * data[x, y, c] + 16/116
|
| 250 |
+
mask = data > 0.008856
|
| 251 |
+
data[mask] **= 1. / 3.
|
| 252 |
+
data[np.invert(mask)] *= 7.787
|
| 253 |
+
data[np.invert(mask)] += 16. / 116.
|
| 254 |
+
|
| 255 |
+
data = np.float32(data)
|
| 256 |
+
output = np.empty(np.shape(data), dtype=np.float32)
|
| 257 |
+
output[:, :, 0] = 116. * data[:, :, 1] - 16.
|
| 258 |
+
output[:, :, 1] = 500. * (data[:, :, 0] - data[:, :, 1])
|
| 259 |
+
output[:, :, 2] = 200. * (data[:, :, 1] - data[:, :, 2])
|
| 260 |
+
|
| 261 |
+
return output
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
def lab2xyz(data, cie_version="1931", illuminant="d65"):
|
| 265 |
+
output = np.empty(np.shape(data), dtype=np.float32)
|
| 266 |
+
|
| 267 |
+
output[:, :, 1] = (data[:, :, 0] + 16.) / 116.
|
| 268 |
+
output[:, :, 0] = (data[:, :, 1] / 500.) + output[:, :, 1]
|
| 269 |
+
output[:, :, 2] = output[:, :, 1] - (data[:, :, 2] / 200.)
|
| 270 |
+
|
| 271 |
+
# if output[x, y, c] > 0.008856, output[x, y, c] ^ 3
|
| 272 |
+
# else, output[x, y, c] = ( output[x, y, c] - 16/116 ) / 7.787
|
| 273 |
+
output = np.asarray(output)
|
| 274 |
+
mask = output > 0.008856
|
| 275 |
+
output[mask] **= 3.
|
| 276 |
+
output[np.invert(mask)] -= 16 / 116
|
| 277 |
+
output[np.invert(mask)] /= 7.787
|
| 278 |
+
|
| 279 |
+
xyz_reference = get_xyz_reference(cie_version, illuminant)
|
| 280 |
+
|
| 281 |
+
output = np.float32(output)
|
| 282 |
+
output[:, :, 0] = output[:, :, 0] * xyz_reference[0]
|
| 283 |
+
output[:, :, 1] = output[:, :, 1] * xyz_reference[1]
|
| 284 |
+
output[:, :, 2] = output[:, :, 2] * xyz_reference[2]
|
| 285 |
+
|
| 286 |
+
return output
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
def lab2lch(data):
|
| 290 |
+
output = np.empty(np.shape(data), dtype=np.float32)
|
| 291 |
+
|
| 292 |
+
output[:, :, 0] = data[:, :, 0] # L transfers directly
|
| 293 |
+
output[:, :, 1] = np.power(np.power(data[:, :, 1], 2) + np.power(data[:, :, 2], 2), 0.5)
|
| 294 |
+
output[:, :, 2] = np.arctan2(data[:, :, 2], data[:, :, 1]) * 180 / np.pi
|
| 295 |
+
|
| 296 |
+
return output
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
def lch2lab(data):
|
| 300 |
+
output = np.empty(np.shape(data), dtype=np.float32)
|
| 301 |
+
|
| 302 |
+
output[:, :, 0] = data[:, :, 0] # L transfers directly
|
| 303 |
+
output[:, :, 1] = np.multiply(np.cos(data[:, :, 2] * np.pi / 180), data[:, :, 1])
|
| 304 |
+
output[:, :, 2] = np.multiply(np.sin(data[:, :, 2] * np.pi / 180), data[:, :, 1])
|
| 305 |
+
|
| 306 |
+
return output
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/csrnet_network.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import functools
|
| 2 |
+
import math
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn as nn
|
| 5 |
+
import torch.nn.functional as F
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class Condition(nn.Module):
|
| 9 |
+
def __init__(self, in_nc=3, nf=32):
|
| 10 |
+
super(Condition, self).__init__()
|
| 11 |
+
stride = 2
|
| 12 |
+
pad = 0
|
| 13 |
+
self.pad = nn.ZeroPad2d(1)
|
| 14 |
+
self.conv1 = nn.Conv2d(in_nc, nf, 7, stride, pad, bias=True)
|
| 15 |
+
self.conv2 = nn.Conv2d(nf, nf, 3, stride, pad, bias=True)
|
| 16 |
+
self.conv3 = nn.Conv2d(nf, nf, 3, stride, pad, bias=True)
|
| 17 |
+
self.act = nn.ReLU(inplace=True)
|
| 18 |
+
|
| 19 |
+
def forward(self, x):
|
| 20 |
+
conv1_out = self.act(self.conv1(self.pad(x)))
|
| 21 |
+
conv2_out = self.act(self.conv2(self.pad(conv1_out)))
|
| 22 |
+
conv3_out = self.act(self.conv3(self.pad(conv2_out)))
|
| 23 |
+
out = torch.mean(conv3_out, dim=[2, 3], keepdim=False)
|
| 24 |
+
|
| 25 |
+
return out
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# 3layers with control
|
| 29 |
+
class CSRNet(nn.Module):
|
| 30 |
+
def __init__(self, in_nc=3, out_nc=3, base_nf=48, cond_nf=24):
|
| 31 |
+
super(CSRNet, self).__init__()
|
| 32 |
+
|
| 33 |
+
self.base_nf = base_nf
|
| 34 |
+
self.out_nc = out_nc
|
| 35 |
+
|
| 36 |
+
self.cond_net = Condition(in_nc=in_nc, nf=cond_nf)
|
| 37 |
+
|
| 38 |
+
self.cond_scale1 = nn.Linear(cond_nf, base_nf, bias=True)
|
| 39 |
+
self.cond_scale2 = nn.Linear(cond_nf, base_nf, bias=True)
|
| 40 |
+
self.cond_scale3 = nn.Linear(cond_nf, 3, bias=True)
|
| 41 |
+
|
| 42 |
+
self.cond_shift1 = nn.Linear(cond_nf, base_nf, bias=True)
|
| 43 |
+
self.cond_shift2 = nn.Linear(cond_nf, base_nf, bias=True)
|
| 44 |
+
self.cond_shift3 = nn.Linear(cond_nf, 3, bias=True)
|
| 45 |
+
|
| 46 |
+
self.conv1 = nn.Conv2d(in_nc, base_nf, 1, 1, bias=True)
|
| 47 |
+
self.conv2 = nn.Conv2d(base_nf, base_nf, 1, 1, bias=True)
|
| 48 |
+
self.conv3 = nn.Conv2d(base_nf, out_nc, 1, 1, bias=True)
|
| 49 |
+
|
| 50 |
+
self.act = nn.ReLU(inplace=True)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def forward(self, x):
|
| 54 |
+
cond = self.cond_net(x)
|
| 55 |
+
|
| 56 |
+
scale1 = self.cond_scale1(cond)
|
| 57 |
+
shift1 = self.cond_shift1(cond)
|
| 58 |
+
|
| 59 |
+
scale2 = self.cond_scale2(cond)
|
| 60 |
+
shift2 = self.cond_shift2(cond)
|
| 61 |
+
|
| 62 |
+
scale3 = self.cond_scale3(cond)
|
| 63 |
+
shift3 = self.cond_shift3(cond)
|
| 64 |
+
|
| 65 |
+
out = self.conv1(x)
|
| 66 |
+
out = out * scale1.view(-1, self.base_nf, 1, 1) + shift1.view(-1, self.base_nf, 1, 1) + out
|
| 67 |
+
out = self.act(out)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
out = self.conv2(out)
|
| 71 |
+
out = out * scale2.view(-1, self.base_nf, 1, 1) + shift2.view(-1, self.base_nf, 1, 1) + out
|
| 72 |
+
out = self.act(out)
|
| 73 |
+
|
| 74 |
+
out = self.conv3(out)
|
| 75 |
+
out = out * scale3.view(-1, self.out_nc, 1, 1) + shift3.view(-1, self.out_nc, 1, 1) + out
|
| 76 |
+
return out
|
IIR-Lab/ISP_pipeline/raw_prc_pipeline/exif_data_formats.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class ExifFormat:
|
| 2 |
+
def __init__(self, id, name, size, short_name):
|
| 3 |
+
self.id = id
|
| 4 |
+
self.name = name
|
| 5 |
+
self.size = size
|
| 6 |
+
self.short_name = short_name # used with struct.unpack()
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
exif_formats = {
|
| 10 |
+
1: ExifFormat(1, 'unsigned byte', 1, 'B'),
|
| 11 |
+
2: ExifFormat(2, 'ascii string', 1, 's'),
|
| 12 |
+
3: ExifFormat(3, 'unsigned short', 2, 'H'),
|
| 13 |
+
4: ExifFormat(4, 'unsigned long', 4, 'L'),
|
| 14 |
+
5: ExifFormat(5, 'unsigned rational', 8, ''),
|
| 15 |
+
6: ExifFormat(6, 'signed byte', 1, 'b'),
|
| 16 |
+
7: ExifFormat(7, 'undefined', 1, 'B'), # consider `undefined` as `unsigned byte`
|
| 17 |
+
8: ExifFormat(8, 'signed short', 2, 'h'),
|
| 18 |
+
9: ExifFormat(9, 'signed long', 4, 'l'),
|
| 19 |
+
10: ExifFormat(10, 'signed rational', 8, ''),
|
| 20 |
+
11: ExifFormat(11, 'single float', 4, 'f'),
|
| 21 |
+
12: ExifFormat(12, 'double float', 8, 'd'),
|
| 22 |
+
}
|