Last active
November 21, 2020 17:00
-
-
Save MrLixm/96e24bdbf643dbf24cd098be446829cc to your computer and use it in GitHub Desktop.
Implementation of Lottes(2016) Tonemapping.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import logging | |
import colour | |
import numpy as np | |
from oiio import OpenImageIO as oiio | |
# --------------------------------------------------------- | |
def lerp(x, y, a): | |
""" Performs a linear interpolation between x and y using a to weight between them | |
Args: | |
x : Specify the start of the range in which to interpolate. | |
y : Specify the end of the range in which to interpolate. | |
a : Specify the value to use to interpolate between x and y. | |
""" | |
return x * (1 - a) + y * a | |
def tonemap_lottes(rgb, contrast=1.65, shoulder=0.977, hdrMax=16.0, midIn=0.18, midOut=0.267): | |
""" | |
Args: | |
midOut: | |
midIn: | |
hdrMax: | |
shoulder: | |
contrast: | |
rgb(ndarray): | |
Returns: | |
ndarray | |
""" | |
rgb: np.ndarray | |
logging.debug(f"Tonemapping init with contrast={contrast}, shoulder={shoulder}, hdrMax={hdrMax}, midIn={midIn}, midOut={midOut}") | |
""" Constants """ | |
cs = contrast * shoulder | |
# b,c anchor curves | |
b0 = -pow(midIn, contrast) | |
b1 = pow(hdrMax, cs) * pow(midIn, contrast) | |
b2 = pow(hdrMax, contrast) * pow(midIn, cs) * midOut | |
b3 = pow(hdrMax, cs) * midOut | |
b4 = pow(midIn, cs) * midOut | |
b = -((b0 + (midOut * (b1 - b2)) / (b3 - b4)) / b4) | |
logging.debug(f"b = {b}") | |
c0 = pow(hdrMax, cs) * pow(midIn, contrast) | |
c1 = pow(hdrMax, contrast) * pow(midIn, cs) * midOut | |
c2 = pow(hdrMax, cs) * midOut | |
c3 = pow(midIn, cs) * midOut | |
c = (c0 - c1) / (c2 - c3) | |
logging.debug(f"c = {c}") | |
""" | |
peak_max should be float | |
rgb_ratio should be float3 (here 3d narray) | |
output_scale should be float | |
""" | |
peak_max = np.amax(rgb) | |
logging.debug(f"peak_max = {peak_max}") | |
rgb_ratio = np.divide(rgb, peak_max) | |
""" Tonemapping """ | |
# contrast adjustement | |
output_scale = pow(peak_max, contrast) | |
# highlight compression | |
output_scale = output_scale / (pow(output_scale, shoulder) * b + c) | |
logging.debug(f"output_scale = {output_scale}") | |
# ------------------------------------------------------------------------------------------------------------------ | |
""" We could end here with output_scale * rgb_ratio but let's add crosstalk""" | |
crosstalk = 64.0 # (64.0, 32.0, 128.0) controls amount of channel crosstalk | |
saturation = contrast # (0.0, 0.0, 0.0) + contrast full tonal range saturation control | |
cross_saturation = 4 # (4.0, 1.0, 16.0) | |
c_white = 1.0 | |
# c_white = np.array([[[1.0, 1.0, 1.0]], [[1.0, 1.0, 1.0]]]) | |
rgb_ratio = np.power(np.abs(rgb_ratio), saturation / cross_saturation) | |
rgb_ratio = lerp(rgb_ratio, c_white, pow(output_scale, crosstalk)) # TODO if output_scale or max3(output_scale) | |
rgb_ratio = np.power(np.abs(rgb_ratio), cross_saturation) | |
# ------------------------------------------------------------------------------------------------------------------ | |
output = output_scale * rgb_ratio | |
logging.debug("Tonemapping applied") | |
return output | |
if __name__ == '__main__': | |
logging.basicConfig(level=logging.DEBUG, format='- %(asctime)s [%(levelname)s] %(message)s', datefmt='%H:%M:%S') | |
v_contrast = 5 # also a , value: 1.4 | |
v_shoulder = 1 # also d | |
v_hdrMax = 16 | |
v_midIn = 0.18 | |
v_midOut = 0.18 | |
image_path = r"L:\temp\gm\render_test_sRGB_v01.exr" | |
outfile_path = rf"L:\temp\gm\rndr_tonemap_v02_{v_contrast}_{v_shoulder}_{v_hdrMax}_{v_midIn}_{v_midOut}.jpg" | |
img_in = oiio.ImageBuf(image_path, 0, 0) | |
img_in_spec = img_in.nativespec() | |
img_in_roi = img_in_spec.roi | |
img_in = oiio.ImageBufAlgo.channels(img_in, (0, 1, 2)) # crop the image to RGB only | |
RGB = img_in.get_pixels() | |
tm_rgb = tonemap_lottes(RGB, v_contrast, v_shoulder, v_hdrMax, v_midIn, v_midOut) | |
tm_rgb = colour.cctf_encoding(tm_rgb, function='sRGB') | |
out_buf = img_in.copy() | |
out_buf.set_pixels(img_in_roi, tm_rgb) | |
out_buf.specmod().attribute("compression", "jpg:100") | |
if out_buf.has_error: | |
raise RuntimeError("Final buffer has error. {}".format(out_buf.geterror())) | |
out_buf.write(outfile_path) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment