原理
关于相机响应曲线的计算,可以阅读Paul E. Debevec的文章Recovering High Dynamic Range Radiance Maps from Photographs,主要用到的原理如下:
matlab和python3的interp1
matlab部分详解参考:MATLAB插值函数interp1
PS: 有python库可以通过包装器使用MATLAB函数:mlabwrap,目前还不支持python3。
'''
MATLAB中的插值函数interp1,其调用格式为: yi= interp1(x,y,xi,'method')
其中x,y为插值点,yi为在被插值点xi处的插值结果;x,y为向量,
'method'表示采用的插值方法
注意:在上述用法中,所有的插值方法都要求x是单调的,并且xi不能够超过x的范围。
要实现外插需要:
yi = interp1(x,y,xi,'method', 'extrap');
extrapolation='extrap'用于指定外插策略,来计算落在 x 域范围外的点。
'''
x = 0:2*pi;
y = sin(x);
xx = 0:0.5:2*pi;
yy = interp1(x,y,xx,'nearest');
'''
scipy的插值函数interp1,其调用格式为:
f = interp1d(x, y, kind='method')
yi = f(xi)
注意:上述调用要求xi不超出x的范围,要实现外插,需要用下面的方式调用:
f = interp1d(x, y, kind='method', fill_value="extrapolate")
yi = f(xi)
此处一元插值的思想即为先计算出拟合函数f,再由f计算得到该区间的其他y值
'''
from scipy.interpolate import interp1d
x = np.linspace(0, 10, 100)
y = np.sin(x)
xx = np.linspace(0, 10, 40)
f = interp1d(x, y, kind='nearest')
#插值结果
yy = f(xx)
python3实现
import numpy as np
import matplotlib.pyplot as plt
import cv2, os
from scipy.interpolate import interp1d
##======================================##
def LDR2HDR(img, response, expo):
'''
img: input LDR image
expo: source exposure
response: CRF function
'''
Radiance = cv2.LUT(img, response)
HDR = Radiance/expo
return HDR
def HDR2LDR(radiance, response, expo):
'''
expo: target exposure
response: CRF function
'''
h,w,c = radiance.shape
MaxR = np.max(response)
# Img = radiance*expo
Img = np.clip(radiance*expo,0,MaxR)
x = np.linspace(0, 255, 256)
out = np.zeros((h*w,c))
for i in range(3):
# 某个通道的最大值不一定为radiance的最大值,因此需要外插
Icrf = interp1d(response[:,:,i].flatten(), x, kind='nearest', fill_value="extrapolate")
out[:,i] = Icrf(Img[:, :, i].flatten())
return out.reshape((h,w,c))
##======================================##
## load data
mainPath = 'Path_to_middlebury'
nameList = ['Exp0/view0.png', 'Exp1/view0.png', 'Exp2/view0.png']
expo = np.array([1,4,16], dtype=np.float32)
images = []
for i in range(3):
images.append(cv2.imread(mainPath+nameList[i]))
## compute CRF
calibrate = cv2.createCalibrateDebevec()
response = calibrate.process(images, expo)
response_norm = response/np.max(response)
## LDR to radiance
Eresult = []
for i in range(3):
Radiance = LDR2HDR(images[i], response_norm, expo[i])
Eresult.append(Radiance)
## radiance to LDR -> ICRF
img_02 = HDR2LDR(Eresult[0], response, expo[2]).astype(np.uint8)
plt.imshow(images[2][:,:,::-1])
plt.show()
plt.imshow(img_02[:,:,::-1])
plt.show()
对于8bit图像,利用CRF曲线做转换使用cv2.LUT即可;对于更高位深的图像如16bit图像,可换用np.interp替代cv2.LUT,具体用法见numpy.interp — NumPy v1.19 Manual。注意np.interp用于外插时在xp范围外的数xi对应的yi用邻近的xp边缘值y_s填充。
def LDR2HDR_16bit(img, Response, expo):
'''
expo: source exposure
response: CRF function
'''
Radiance = np.zeros(img.shape)
for i in range(3):
Radiance[:,:,i] = np.interp(img[:,:,i], list(np.linspace(0, 2**16-1, 2**16)), list(Response[:,i]))
HDR = np.array(Radiance)/expo
return HDR
def HDR2LDR_16bit(radiance, response, expo):
'''
expo: target exposure
response: CRF function
'''
h,w,c = radiance.shape
MaxR = np.max(response)
# Img = radiance*expo
Img = np.clip(radiance*expo,0,MaxR)
print('maxCRF:%f /maxAD:%f /maxRadiance:%f'%(MaxR, np.max(Img),np.max(radiance)))
x = np.linspace(0, 2**16-1, 2**16)
out = np.zeros((h*w,c))
# response[:,i]-某个通道的最大值不一定为radiance的最大值,因此需要外插
for i in range(3):
print('max channel%d:'%(i),np.max(response[:,i]))
Icrf = interp1d(response[:,i].flatten(), x, kind='nearest', fill_value="extrapolate")
out[:,i] = Icrf(Img[:, :, i].flatten())
return out.reshape((h,w,c))
参考:
Scipy Tutorial-插值interp1d
scipy.interpolate.interp1d
关于MatLab的interp1与Python的interp1d对应
opencv-High Dynamic Range Imaging