Search code examples
pythonopencvhdr

Tone mapping a HDR image using OpenCV 4.0


I want to create a script which takes a .HDR file and tonemaps it into a .JPG. I have looked at a few OpenCV tutorials and it seems it should be able to do this.

I have written this script:

import cv2
import numpy as np

filename = "image/gg.hdr"
im = cv2.imread(filename)

cv2.imshow('', im.astype(np.uint8))
cv2.waitKey(0)

tonemapDurand = cv2.createTonemapDurand(2.2)
ldrDurand = tonemapDurand.process(im.copy())

new_filename = filename + ".jpg"
im2_8bit = np.clip(ldrDurand * 255, 0, 255).astype('uint8')
cv2.imwrite(new_filename, ldrDurand)

cv2.imshow('', ldrDurand.astype(np.uint8))

Which according to the tutorials should work. I am getting a black image in the end though. I have verified that the result it saves is .JPG, as well as that the input image (a 1.6 megapixel HDR envrionment map) is a valid .HDR.

OpenCV should be able to load .HDRs according to the documentation.

I have tried reproducing the tutorial linked and that worked correctly, so the issue is in the .HDR image, anybody know what to do?

Thanks

EDIT: I used this HDR image. Providing a link rather than a direct download due to copyright etc.


Solution

  • You were almost there, except for two small mistakes.

    The first mistake is using cv2.imread to load the HDR image without specifying any flags. Unless you call it with IMREAD_ANYDEPTH, the data will be downscaled to 8-bit and you lose all that high dynamic range.

    When you do specify IMREAD_ANYDEPTH, the image will be loaded as 32bit floating point format. This would normally have intensities in range [0.0, 1.0], but due to being HDR, the values exceed 1.0 (in this particular case they go up to about 22). This means that you won't be able to visualize it (in a useful way) by simply casting the data to np.uint8. You could perhaps normalize it first into the nominal range, or use the scale and clip method... whatever you find appropriate. Since the early visualization is not relevant to the outcome, I'll skip it.

    The second issue is trivial. You correctly scale and clip the tone-mapped image back to np.uint8, but then you never use it.


    Script

    import cv2
    import numpy as np
    
    filename = "GoldenGate_2k.hdr"
    im = cv2.imread(filename, cv2.IMREAD_ANYDEPTH)
    
    tonemapDurand = cv2.createTonemapDurand(2.2)
    ldrDurand = tonemapDurand.process(im)
    
    im2_8bit = np.clip(ldrDurand * 255, 0, 255).astype('uint8')
    
    new_filename = filename + ".jpg"
    cv2.imwrite(new_filename, im2_8bit)
    

    Output

    Sample Output