| import os |
|
|
| os.environ["OPENCV_IO_ENABLE_OPENEXR"] = "1" |
| import argparse |
|
|
| import cv2 |
| import matplotlib.pyplot as plt |
| import numpy as np |
| import torch |
| from matplotlib import colors |
| from tqdm import tqdm |
|
|
| |
| |
|
|
|
|
| class tonemap: |
| def __init__(self): |
| pass |
|
|
| def process(self, img): |
| return img |
|
|
| def inv_process(self, img): |
| return img |
|
|
|
|
| |
| class log_tonemap(tonemap): |
| |
| |
| |
| |
| def __init__(self, base, scale=1, offset=1): |
| self.base = base |
| self.scale = scale |
| self.offset = offset |
|
|
| def process(self, img): |
| tonemapped = (np.log(img + self.offset) / np.log(self.base)) * self.scale |
| return tonemapped |
|
|
| def inv_process(self, img): |
| inverse_tonemapped = np.power(self.base, (img) / self.scale) - self.offset |
| return inverse_tonemapped |
|
|
|
|
| class log_tonemap_clip(tonemap): |
| |
| |
| |
| |
| def __init__(self, base, scale=1, offset=1): |
| self.base = base |
| self.scale = scale |
| self.offset = offset |
|
|
| def process(self, img): |
| tonemapped = np.clip((np.log(img * self.scale + self.offset) / np.log(self.base)), 0, 2) - 1 |
| return tonemapped |
|
|
| def inv_process(self, img): |
| inverse_tonemapped = (np.power(self.base, (img + 1)) - self.offset) / self.scale |
| return inverse_tonemapped |
|
|
|
|
| |
| class gamma_tonemap(tonemap): |
| def __init__( |
| self, |
| gamma, |
| ): |
| self.gamma = gamma |
|
|
| def process(self, img): |
| tonemapped = np.power(img, 1 / self.gamma) |
| return tonemapped |
|
|
| def inv_process(self, img): |
| inverse_tonemapped = np.power(img, self.gamma) |
| return inverse_tonemapped |
|
|
|
|
| class linear_clip(tonemap): |
| def __init__(self, scale, mean): |
| self.scale = scale |
| self.mean = mean |
|
|
| def process(self, img): |
| tonemapped = np.clip((img - self.mean) / self.scale, -1, 1) |
| return tonemapped |
|
|
| def inv_process(self, img): |
| inverse_tonemapped = img * self.scale + self.mean |
| return inverse_tonemapped |
|
|
|
|
| def make_tonemap_HDR(opt): |
| if opt.mode == "luminance": |
| res_tonemap = log_tonemap_clip(10, 1.0, 1.0) |
| else: |
| res_tonemap = linear_clip(5000.0, 5000.0) |
| return res_tonemap |
|
|
|
|
| class LDRfromHDR: |
| def __init__( |
| self, tonemap="none", orig_scale=False, clip=True, quantization=0, color_jitter=0, noise=0 |
| ): |
| self.tonemap_str, val = tonemap |
| if tonemap[0] == "gamma": |
| self.tonemap = gamma_tonemap(val) |
| elif tonemap[0] == "log10": |
| self.tonemap = log_tonemap(val) |
| else: |
| print("Warning: No tonemap specified, using linear") |
|
|
| self.clip = clip |
| self.orig_scale = orig_scale |
| self.bits = quantization |
| self.jitter = color_jitter |
| self.noise = noise |
|
|
| self.wbModel = None |
|
|
| def process(self, HDR): |
| LDR, normalized_scale = self.rescale(HDR) |
| LDR = self.apply_clip(LDR) |
| LDR = self.apply_scale(LDR, normalized_scale) |
| LDR = self.apply_tonemap(LDR) |
| LDR = self.colorJitter(LDR) |
| LDR = self.gaussianNoise(LDR) |
| LDR = self.quantize(LDR) |
| LDR = self.apply_white_balance(LDR) |
| return LDR, normalized_scale |
|
|
| def rescale(self, img, percentile=90, max_mapping=0.8): |
| r_percentile = np.percentile(img, percentile) |
| alpha = max_mapping / (r_percentile + 1e-10) |
|
|
| img_reexposed = img * alpha |
|
|
| normalized_scale = normalizeScale(1 / alpha) |
|
|
| return img_reexposed, normalized_scale |
|
|
| def rescaleAlpha(self, img, percentile=90, max_mapping=0.8): |
| r_percentile = np.percentile(img, percentile) |
| alpha = max_mapping / (r_percentile + 1e-10) |
|
|
| return alpha |
|
|
| def apply_clip(self, img): |
| if self.clip: |
| img = np.clip(img, 0, 1) |
| return img |
|
|
| def apply_scale(self, img, scale): |
| if self.orig_scale: |
| scale = unNormalizeScale(scale) |
| img = img * scale |
| return img |
|
|
| def apply_tonemap(self, img): |
| if self.tonemap_str == "none": |
| return img |
| gammaed = self.tonemap.process(img) |
| return gammaed |
|
|
| def quantize(self, img): |
| if self.bits == 0: |
| return img |
| max_val = np.power(2, self.bits) |
| img = img * max_val |
| img = np.floor(img) |
| img = img / max_val |
| return img |
|
|
| def colorJitter(self, img): |
| if self.jitter == 0: |
| return img |
| hsv = colors.rgb_to_hsv(img) |
| hue_offset = np.random.normal(0, self.jitter, 1) |
| hsv[:, :, 0] = (hsv[:, :, 0] + hue_offset) % 1.0 |
| rgb = colors.hsv_to_rgb(hsv) |
| return rgb |
|
|
| def gaussianNoise(self, img): |
| if self.noise == 0: |
| return img |
| noise_amount = np.random.uniform(0, self.noise, 1) |
| noise_img = np.random.normal(0, noise_amount, img.shape) |
| img = img + noise_img |
| img = np.clip(img, 0, 1).astype(np.float32) |
| return img |
|
|
| def apply_white_balance(self, img): |
| if self.wbModel is None: |
| return img |
| img = self.wbModel.correctImage(img) |
| return img.copy() |
|
|
|
|
| def make_LDRfromHDR(opt): |
| LDR_from_HDR = LDRfromHDR( |
| opt.tonemap_LDR, opt.orig_scale, opt.clip, opt.quantization, opt.color_jitter, opt.noise |
| ) |
| return LDR_from_HDR |
|
|
|
|
| def torchnormalizeEV(EV, mean=5.12, scale=6, clip=True): |
| |
| EV -= mean |
| EV = EV / scale |
|
|
| if clip: |
| EV = torch.clip(EV, min=-1, max=1) |
|
|
| return EV |
|
|
|
|
| def torchnormalizeEV0(EV, mean=5.12, scale=6, clip=True): |
| |
| EV -= mean |
| EV = EV / scale |
|
|
| if clip: |
| EV = torch.clip(EV, min=-1, max=1) |
|
|
| EV += 0.5 |
| EV = EV / 2 |
|
|
| return EV |
|
|
|
|
| def normalizeScale(x, scale=4): |
| x = np.log10(x + 1) |
|
|
| x = x / (scale / 2) |
| x = x - 1 |
|
|
| return x |
|
|
|
|
| def unNormalizeScale(x, scale=4): |
| x = x + 1 |
| x = x * (scale / 2) |
|
|
| x = np.power(10, x) - 1 |
|
|
| return x |
|
|
|
|
| def normalizeIlluminance(x, scale=5): |
| x = np.log10(x + 1) |
|
|
| x = x / (scale / 2) |
| x = x - 1 |
|
|
| return x |
|
|
|
|
| def unNormalizeIlluminance(x, scale=5): |
| x = x + 1 |
| x = x * (scale / 2) |
|
|
| x = np.power(10, x) - 1 |
|
|
| return x |
|
|
|
|
| def main(args): |
| processor = LDRfromHDR( |
| |
| tonemap=("gamma", args.gamma), |
| orig_scale=False, |
| clip=True, |
| quantization=0, |
| color_jitter=0, |
| noise=0, |
| ) |
|
|
| img_list = list(os.listdir(args.hdr_dir)) |
| img_list = [f for f in img_list if f.endswith(args.extension)] |
| img_list = [f for f in img_list if not f.startswith("._")] |
|
|
| if not os.path.exists(args.out_dir): |
| os.makedirs(args.out_dir) |
|
|
| for fname in tqdm(img_list): |
| fname_out = ".".join(fname.split(".")[:-1]) |
| out = os.path.join(args.out_dir, f"{fname_out}.jpg") |
| if os.path.exists(out) and not args.overwrite: |
| continue |
|
|
| fpath = os.path.join(args.hdr_dir, fname) |
| img = cv2.imread(fpath, cv2.IMREAD_ANYCOLOR | cv2.IMREAD_ANYDEPTH) |
| img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) |
|
|
| ldr, scale = processor.process(img) |
|
|
| ldr = (ldr * 255).astype(np.uint8) |
| ldr = cv2.cvtColor(ldr, cv2.COLOR_RGB2BGR) |
| cv2.imwrite(out, ldr) |
|
|
|
|
| if __name__ == "__main__": |
| parser = argparse.ArgumentParser() |
| parser.add_argument("--hdr_dir", type=str, default="hdr") |
| parser.add_argument("--out_dir", type=str, default="ldr") |
| parser.add_argument("--extension", type=str, default=".exr") |
| parser.add_argument("--overwrite", action="store_true") |
| parser.add_argument("--gamma", type=float, default=2) |
| args = parser.parse_args() |
|
|
| main(args) |
|
|