Skip to content

01_combine_hdr

复制本地路径 | 在线编辑

原始文件为 .py 代码,本文是转换后的 Markdown 文件。

'''
注意点
1. 保存 HDR 图像,请务必保存为 np.float32!!! 不然 OpenHDR 打开会有问题,有大块黑色或者单色先检查一下这个情况
2. OpenHDR 打不开或者 opencv 返回 None 可能是因为不同 HDR 格式,看看文件是不是32-bit_rle_rgbe,这是另一种格式
'''
import os
os.chdir(os.path.dirname(os.path.abspath(__file__)))

import numpy as np

'''
1.1 use dcraw to convert nef to tiff
'''
picnum = 16
if not os.path.exists('../data/door_stack/exposure0.tiff'):
    for i in range(picnum):
        os.system(f'mv ../data/door_stack/exposure{i+1}.jpg ../data/door_stack/exposure{i}.jpg')
        os.system(f'mv ../data/door_stack/exposure{i+1}.nef ../data/door_stack/exposure{i}.nef')

    for i in range(picnum):
        # -w: white balance (dont use -D which prevent scaling)
        # -q [0-3]: Demosaicing(Interploation), high number -> high quality. 
        #           However, dcraw's algorithm is too old!! AHD is its best algorithm...
        # -o [0-6]: 0->raw, 1->sRGB, ...
        os.system(f'dcraw -v -T -4 -w -q 3 -o 1 ../data/door_stack/exposure{i}.nef > /dev/null')
print('1.1 Done')

'''
1.4 different wfunc
'''
exptime = np.zeros(picnum)
for i in range(picnum):
    exptime[i] = 2**i / 2048

import math
import cv2

# 处理完全欠曝光和过曝光的情况
# 找出最小值都小于0.05的列,最大值都大于0.95的列
def find_extreme(I, minv=0.05, maxv=0.95):
    max_value = np.max(I, axis=0)
    min_value = np.min(I, axis=0)

    return max_value <= minv, min_value >= maxv

def w_uniform(I, minv=0.05, maxv=0.95):
    return np.where((minv<I)&(I<maxv), 1, 0)

def w_tent(I, minv=0.05, maxv=0.95):
    return np.where((minv<I)&(I<maxv), np.minimum(I, 1-I), 0)

def w_gaussian(I, minv=0.05, maxv=0.95):
    z = np.exp(-4*(I-0.5)*(I-0.5)/0.25)
    return np.where((minv<I) & (I<maxv), z, 0)

def w_photon(I, minv=0.05, maxv=0.95):
    return np.where((minv<I)&(I<maxv), exptime[:, np.newaxis], 0)

wfuncs = {
    'uniform': w_uniform, 
    'tent': w_tent, 
    'gaussian': w_gaussian, 
    'photon': w_photon
}

'''
1.2 Relinear jpg
'''
# 最小二乘法求解非线性响应,具体求解在 relinear.py 中
# 结果放在 data/response 中
if not os.path.exists('../data/response'):
    os.system('python 01_relinear_jpg.py')

'''
1.3 Generate HDR
'''
# 公式 5 和 6 的计算,分子、分母、欠曝光索引、过曝光索引
def fill_extreme(result, toomin, toomax):
    result[toomin] = result[(~toomin)&(~toomax)].min()
    result[toomax] = result[(~toomin)&(~toomax)].max()
    return result

for imgtype in ['jpg', 'tiff']:

    # 先读取一张图片,获取长宽信息
    temp_pic = cv2.imread(f'../data/door_stack/exposure0.{imgtype}', -1)
    max_value = np.iinfo(temp_pic.dtype).max
    rows, cols = temp_pic.shape[0:2]

    for wfuncname, wfunc in wfuncs.items():
        print(imgtype, wfuncname)
        hdrimg1 = np.zeros((rows, cols, 3), dtype=np.float32)
        hdrimg2 = np.zeros((rows, cols, 3), dtype=np.float32)

        # 响应曲线, tiff 为线性, jpg 就是计算而来, 即公式1.5和1.6中 Lin=response[LDR]
        response = None
        if imgtype == 'jpg':
            response = np.load(f'../data/response/{wfuncname}.npy')
        elif imgtype == 'tiff':
            response = np.array([i/max_value for i in range(max_value+1)])

        # 占用内存太大了,全部处理,空间不够,分块进行操作
        onerow, onecol = rows//4, cols//4
        for ridx in range(0, 4):
            for cidx in range(0, 4):
                print(ridx, cidx)
                rbegin, cbegin = ridx*onerow, cidx*onecol

                pics = np.zeros((picnum, onerow, onecol, 3), dtype=int)
                for i in range(picnum):
                    onepic = cv2.imread(f'../data/door_stack/exposure{i}.{imgtype}', -1)
                    pics[i] = onepic[rbegin:rbegin+onerow, cbegin:cbegin+onecol]

                # 每次处理一个颜色
                for channel in range(0, 3):
                    nowpics = pics[..., channel].reshape(picnum, -1)

                    # 找出欠曝和过曝的
                    toomin, toomax = find_extreme(nowpics/max_value)

                    # 求解 w
                    w = wfunc(nowpics / max_value)

                    # 由于欠曝和过曝,w 有的列都为 1,将这些点先填一个假的值
                    w[0, toomax] = w[0, toomin] = 1

                    # 分母
                    wsum = np.sum(w, axis=0)

                    # 线性合并, X 是分子
                    X = np.sum(w * response[nowpics] / exptime[:, np.newaxis], axis=0) 
                    result = fill_extreme(X/wsum, toomin, toomax).reshape(onerow, onecol)
                    hdrimg1[rbegin:rbegin+onerow, cbegin:cbegin+onecol, channel] = result

                    # 对数合并, X 是分子
                    response[response==0] = 1
                    log_delta = np.log2(response[nowpics]) - np.log2(exptime[:, np.newaxis])
                    X = np.sum(w * log_delta, axis=0)
                    result = fill_extreme(np.power(2, X/wsum), toomin, toomax).reshape(onerow, onecol)
                    hdrimg2[rbegin:rbegin+onerow, cbegin:cbegin+onecol, channel] = result

        # hdrimg1 = hdrimg1 / hdrimg1.max()
        # hdrimg2 = hdrimg2 / hdrimg2.max()

        os.makedirs(f'../result/stage-1/', exist_ok=True)
        cv2.imwrite(f'../result/stage-1/{imgtype}-{wfuncname}-line.hdr', hdrimg1)
        cv2.imwrite(f'../result/stage-1/{imgtype}-{wfuncname}-log.hdr', hdrimg2)

Comments