Adjusts the exposure of RAW images based on EV values
I’m trying to write a program that takes a matrix full of 12-bit RAW sensor values (ranging from [512-4096]) (512 is the Bayer sensor black level –> i.e. what is pure black defined as) and adjusts the EV of each pixel, just like the Adobe Camera Raw (ACR) “exposure” slider. I’m trying to figure out how it’s basically done. I’ve consulted dozens of blogs explaining how EV calculations are calculated, and it seems:
This link seems to give the following formula: PixelAdjusted = Pixel * 2^EV
This seems very wrong, as the 5 EV adjustment would make the picture disproportionate. And I can’t find any other resources online. Wikipedia has a good entry on Exposure Value, but it doesn’t seem to have what I’m looking for either… Does it help in any way?
Here’s an example of what I mean:
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 for any EV value other than 5, so the formula is incorrect. I also know that this formula is wrong because it doesn’t work when
EV = 0. I’ve found that many websites explain the formula just
new_pixel = pixel * 2^exposure but that doesn’t seem to work for Raw photos… Am I missing something?
Here is some python code and some files I used to test:
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
For some reason I’ve spent 14 hours on this… I don’t know what I’m doing wrong because I can’t get it to work consistently (with multiple EVs) always green or magenta tones. I think the fact that it’s a RAW photo screws up the green channel ….
Let’s say you start with the original image and you are in linear space. In this case, changing the exposure is a multiplication operation.
increase of 1 in the exposure value (EV) corresponds to a doubling of exposure. Exposure is a linear measure of the amount of light reaching each pixel. Doubling the exposure doubles the amount of light. Because in photography people usually think in terms of the score of the current exposure, it makes sense to talk about “increasing the EV by 1” instead of “multiplying the exposure by 2”.
So, in effect, to increase the exposure value by n, multiply the pixel value by 2n.
If the input image is a JPEG or TIFF file, it may be in the sRGB color space. This is a non-linear color space designed to increase the apparent range of an 8-bit image file. Before modifying the exposure, you need to convert sRGB to linear RGB. This can be approximated by increasing each pixel value to a power of 2.2, Wikipedia has the exact formulation .
Problems in OP are caused by inaccurate black levels.
raw.black_level_per_channel returns 528 for a given image (the value is the same for each channel, but I guess this is not necessarily the case for other camera models), not 512. Also, the code says that raw.raw_image_visible changes back to
raw.raw_image, which is incorrect.
The following code produces the correct result:
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 # 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()