I'm trying to write a program that takes in a matrix full of 12-bit RAW sensor values (that range from [512-4096]) (512 is the Bayer sensor black level--> i.e. what pure black is defined as) and adjusts the EV of each pixel, excatly like the Adobe Camera Raw (ACR) "exposure" slider. I'm trying to figure out how it is done basically. I've looked up dozens of blogs that explain how EV is calculated and it seems to be:
This link seems to give the formula of: PixelAdjusted = Pixel * 2^EV
This just seems very wrong because an adjustment of 5 EV blows the picture wayyy out of proportion.. and I can't find any other resources online. Wikipedia has an excellent entry on Exposure Value but it doesn't seem to have what I'm looking for either... any help on this?
Thanks!
Here is an example of what I mean:
RAW file in ACR with EV 0:

With EV 5:

I currently have this formula:
black_level = 512
bit_depth = 2**12
normalized = max((raw_pixel - black_level),0) / (bit_depth) ## normalize to [0,1]
exposed = normalized * (2**EV) ## expose by desired EV value
## scale back to normal level:
raw_pixel_new = exposed * bit_depth
But this fails on any EV value that is not 5, so the formula is not correct. I also know this formula is wrong because it doesn't work if EV = 0. I have found dozens of sites that explain that the formula is just new_pixel = pixel * 2^exposure but this doesn't seem to work with Raw photos... am I missing something?
Any thoughts?
import rawpy
import numpy as np
from PIL import Image
bit_depth = 12
black_level = 512
exposure = 4
path = "/001_ev0.DNG"
raw = rawpy.imread(path)
im = raw.raw_image_visible
im = np.maximum(im - black_level, 0)
im *= 2**exposure
# im = im + black_level # for some reason commenting this out makes it slightly better
im = np.minimum(im,2**12 - 1)
raw.raw_image[:,:] = im
im = raw.postprocess(use_camera_wb=True,no_auto_bright=True)
img = Image.fromarray(im, 'RGB')
img.show() #This should look like the file: 001_ev4.tif
Images:
https://drive.google.com/open?id=1T0ru_Vid8ctM3fDdbx1hvxNojOXOzXxg
I've spent 14 hours on this for some reason... I have no idea what I'm doing wrong as I can't get it to consistently work (with multiple EVs) there's always either a green or a magenta hue. I think the fact that this is a RAW photo fucks up the green channel...
Assuming you start off with a raw image, you are in a linear space. In this case, changing exposure is a multiplicative operation.
Increasing Exposure Value (EV) by 1 corresponds to doubling the exposure. Exposure is a linear measure of the amount of light that reaches each pixel. Doubling the exposure doubles the amount of light. Because in photography one usually thinks in terms of fractions of current exposure, it makes sense to talk about "increasing EV by 1", rather than "multiplying exposure by 2".
Thus, indeed, to increase the Exposure Value by n, multiply the pixel values by 2n.
If the input image is a JPEG or TIFF file, it likely is in sRGB color space. This is a non-linear color space meant to increase the apparent range of the 8-bit image file. It is necessary to convert sRGB to linear RGB first, before modifying exposure. This can be accomplished approximately by raising each pixel value to a power of 2.2, Wikipedia has the exact formulation.
The problems in the OP are caused by an inexact black level. raw.black_level_per_channel returns 528 for the given image (it's the same value for each of the channels, though I guess this is not necessarily the case for other camera models), not 512. Furthermore, the code writes raw.raw_image_visible back into raw.raw_image, which is not correct.
The following code produces correct results:
import rawpy
import numpy as np
from PIL import Image
bit_depth = 12
exposure = 5
path = "/001_ev0.DNG"
raw = rawpy.imread(path)
black_level = raw.black_level_per_channel[0] # assume they're all the same
im = raw.raw_image
im = np.maximum(im, black_level) - black_level # changed order of computation
im *= 2**exposure
im = im + black_level
im = np.minimum(im, 2**12 - 1)
raw.raw_image[:,:] = im
im = raw.postprocess(use_camera_wb=True, no_auto_bright=True)
img = Image.fromarray(im, 'RGB')
img.show()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With