Skip to content

Commit 204540d

Browse files
authored
Merge pull request #14 from HYX20011209/noise_calib
[ADD] new feature for noise model calibration [ELD]
2 parents 27abcf5 + 294af1b commit 204540d

File tree

5 files changed

+532
-0
lines changed

5 files changed

+532
-0
lines changed

docs/calib.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Noise calibration
2+
3+
## Prerequisites
4+
5+
### 1. Rawpy
6+
7+
The calibration code the raspy package, which can be installed based on this instructions(https://pypi.org/project/rawpy/)
8+
9+
Note: If rawpy cannot be installed on Mac(m1/m2), you can refer to this issue(https://github.com/letmaik/rawpy/issues/171)
10+
11+
### 2. data
12+
13+
It is recommended that you organize the data to be calibrated into separate folders for each camera. Within each camera folder, you should further divide the data into several subfolders based on different ISO values.
14+
15+
## calibration process
16+
17+
For the calibration process, you can execute all steps at once by directly following the code given in the main function, or you can perform each step separately according to your needs. These steps include selecting the positions of color blocks, calibrating the color blocks to obtain K, calibrating dark images to obtain other parameters, and fitting log(K) and log(variance).

tools/calib/calib_color.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import numpy as np
2+
import rawpy
3+
from utils import *
4+
import matplotlib.pyplot as plt
5+
import json
6+
7+
# Fit K and Var(No) using the formula Var(D) = K(KI) + Var(No)
8+
def calib_color_per_iso(cam_dir, iso):
9+
cur_iso_path = os.path.join(os.path.join(cam_dir,'color'), str(iso))
10+
raw_imgs = os.listdir(cur_iso_path)
11+
if '.DS_Store' in raw_imgs:
12+
raw_imgs.remove('.DS_Store')
13+
14+
# Retrieve previously stored position information
15+
camera = cam_dir.split('/')[-1]
16+
block_position_path = f'./pos/{camera}_block_pos.npy'
17+
block_positoins = np.load(block_position_path)
18+
KI = np.zeros((4,len(block_positoins)))
19+
var_D = np.zeros((4,len(block_positoins)))
20+
for raw_img in raw_imgs:
21+
cur_raw_path = os.path.join(cur_iso_path, raw_img)
22+
raw = rawpy.imread(cur_raw_path)
23+
black_level = raw.black_level_per_channel
24+
white_point = raw.camera_white_level_per_channel
25+
26+
raw_vis = raw.raw_image_visible.copy()
27+
raw_pattern = raw.raw_pattern
28+
raw = raw.raw_image_visible.astype(np.float32)
29+
# rggb_img = bayer2_Rggb(raw)
30+
rggb_img = pack_raw_bayer(raw_vis, raw_pattern)
31+
rggb_img = rggb_img.transpose(1,2,0).astype(np.int64)
32+
33+
rggb_img -= black_level
34+
# rggb_img = rggb_img.astype(np.float32)
35+
# rggb_img /= (np.array(white_point) - np.array(black_level))
36+
37+
for i, pos in enumerate(block_positoins):
38+
minx,miny,w,h = pos
39+
maxx,maxy = minx+w, miny+h
40+
minx //= 2
41+
miny //= 2
42+
maxx //= 2
43+
maxy //= 2
44+
KI[:,i] += np.mean(rggb_img[miny:maxy,minx:maxx,:],axis=(0,1))
45+
# plt.imshow(rggb_img[miny:maxy,minx:maxx,:].mean(-1))
46+
# print(rggb_img[miny:maxy,minx:maxx,:].max())
47+
# print(rggb_img[miny:maxy,minx:maxx,:].min())
48+
# plt.show()
49+
var_D[:,i] += np.var(rggb_img[miny:maxy,minx:maxx,:],axis=(0,1))
50+
51+
KI /= len(raw_imgs)
52+
53+
var_D /= len(raw_imgs)
54+
K, var_No = np.zeros((4)), np.zeros((4))
55+
for i in range(4):
56+
K[i], var_No[i] = linear_regression(KI[i], var_D[i])
57+
print(iso)
58+
# plt.scatter(KI[1],var_D[1])
59+
# plt.show()
60+
for i in range(4):
61+
plt.scatter(KI[i],var_D[i])
62+
fig_dir = './figs/'
63+
if not os.path.exists(fig_dir):
64+
os.makedirs(fig_dir)
65+
fig_name = fig_dir + camera + '/' + f'{iso}_K{i}.png'
66+
plt.savefig(fig_name)
67+
plt.close()
68+
return K,var_No
69+
70+
71+
def calib_color_per_iso_whole(cam_dir, iso):
72+
cur_iso_path = os.path.join(os.path.join(cam_dir,'color'), str(iso))
73+
raw_imgs = os.listdir(cur_iso_path)
74+
if '.DS_Store' in raw_imgs:
75+
raw_imgs.remove('.DS_Store')
76+
77+
example_img_path = os.path.join(cur_iso_path,raw_imgs[0])
78+
rgb_img_example = raw2rgb(example_img_path)
79+
rect = select_whole_positions(rgb_img_example)
80+
# rect = (1514, 1018, 2543, 1897)
81+
minx,miny,w,h = rect
82+
maxx,maxy = minx+w, miny+h
83+
minx //= 2
84+
miny //= 2
85+
maxx //= 2
86+
maxy //= 2
87+
print(rect)
88+
total_imgs = np.zeros((len(raw_imgs),maxy-miny,maxx-minx,4))
89+
for i,raw_img in enumerate(raw_imgs):
90+
cur_raw_path = os.path.join(cur_iso_path, raw_img)
91+
raw = rawpy.imread(cur_raw_path)
92+
black_level = raw.black_level_per_channel
93+
white_point = raw.camera_white_level_per_channel
94+
95+
raw_vis = raw.raw_image_visible.copy()
96+
raw_pattern = raw.raw_pattern
97+
raw = raw.raw_image_visible.astype(np.float32)
98+
# rggb_img = bayer2_Rggb(raw)
99+
rggb_img = pack_raw_bayer(raw_vis, raw_pattern)
100+
rggb_img = rggb_img.transpose(1,2,0).astype(np.int64)
101+
102+
rggb_img -= black_level
103+
rggb_img = rggb_img.astype(np.float32)
104+
rggb_img /= (np.array(white_point) - np.array(black_level))
105+
106+
total_imgs[i] = rggb_img[miny:maxy,minx:maxx,:]
107+
108+
mean_img = np.mean(total_imgs,axis=0)
109+
var_img = np.var(total_imgs,axis=0)
110+
111+
# K, var_No = np.zeros((4)), np.zeros((4))
112+
# for i in range(4):
113+
# K[i], var_No[i] = linear_regression(mean_img, var_D[i])
114+
plt.scatter(mean_img.reshape(-1,4)[:,1],var_img.reshape(-1,4)[:,1])
115+
plt.show()
116+
# return K,var_No
117+
118+
def calib_color_per_camera(cam_dir):
119+
color_dir = os.path.join(cam_dir, 'color')
120+
iso_list = sorted(os.listdir(color_dir))
121+
if '.DS_Store' in iso_list:
122+
iso_list.remove('.DS_Store')
123+
color_calib_params = dict()
124+
color_calib_params['K_list'], color_calib_params['var_No_list'] = dict(), dict()
125+
for iso in iso_list:
126+
K, var_No = calib_color_per_iso(cam_dir,iso=int(iso))
127+
color_calib_params['K_list'][iso] = K.tolist()
128+
color_calib_params['var_No_list'][iso] = var_No.tolist()
129+
130+
cam_name = cam_dir.split('/')[-1]
131+
color_calib_params_dir = os.path.join('./result/calib/color_calib_params',cam_name)
132+
if not os.path.exists(color_calib_params_dir):
133+
os.makedirs(color_calib_params_dir)
134+
with open(os.path.join(color_calib_params_dir,'color_calib_params.json'),'w') as json_file:
135+
json.dump(color_calib_params,json_file)

tools/calib/calib_dark.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import numpy as np
2+
import rawpy
3+
from utils import *
4+
import matplotlib.pyplot as plt
5+
import json
6+
import scipy
7+
8+
def calib_dark_per_iso(cam_dir,iso):
9+
cur_iso_path = os.path.join(os.path.join(cam_dir,'dark'), str(iso))
10+
raw_imgs = os.listdir(cur_iso_path)
11+
if '.DS_Store' in raw_imgs:
12+
raw_imgs.remove('.DS_Store')
13+
14+
r = 400
15+
# Read
16+
sigma_read = np.zeros((4, len(raw_imgs)), dtype=np.float32)
17+
mean_read = np.zeros((4, len(raw_imgs)), dtype=np.float32)
18+
r2_read = np.zeros((4, len(raw_imgs)), dtype=np.float32)
19+
# row
20+
sigma_row = np.zeros(len(raw_imgs), dtype=np.float32)
21+
mean_row = np.zeros(len(raw_imgs), dtype=np.float32)
22+
r2_row = np.zeros(len(raw_imgs), dtype=np.float32)
23+
# TL
24+
sigma_TL = np.zeros((4, len(raw_imgs)), dtype=np.float32)
25+
mean_TL = np.zeros((4, len(raw_imgs)), dtype=np.float32)
26+
r2_TL = np.zeros((4, len(raw_imgs)), dtype=np.float32)
27+
lamda = np.zeros((4, len(raw_imgs)), dtype=np.float32)
28+
# Gauss
29+
sigma_gauss = np.zeros((4, len(raw_imgs)), dtype=np.float32)
30+
mean_gauss = np.zeros((4, len(raw_imgs)), dtype=np.float32)
31+
r2_gauss = np.zeros((4, len(raw_imgs)), dtype=np.float32)
32+
33+
for i, raw_img in enumerate(raw_imgs):
34+
# print(i)
35+
cur_raw_path = os.path.join(cur_iso_path, raw_img)
36+
raw = rawpy.imread(cur_raw_path)
37+
black_level = raw.black_level_per_channel
38+
39+
# print(f'bl:{black_level}')
40+
41+
raw_vis = raw.raw_image_visible.copy()
42+
raw_pattern = raw.raw_pattern
43+
raw = raw.raw_image_visible.astype(np.float32)
44+
# rggb_img = bayer2_Rggb(raw)
45+
46+
# Calculate R before converting to 4 channels
47+
raw -= np.mean(black_level)
48+
row_all = np.mean(raw[raw.shape[0]//2-r*2:raw.shape[0]//2+r*2,raw.shape[1]//2-r*2:raw.shape[1]//2+r*2], axis=1)
49+
_, (sig_row,u_row,r_row) = scipy.stats.probplot(row_all,rvalue=True)
50+
sigma_row[i] = sig_row
51+
mean_row[i] = u_row
52+
r2_row[i] = r_row**2
53+
54+
# Convert the image into RGGB four channels
55+
rggb_img = pack_raw_bayer(raw_vis, raw_pattern)
56+
rggb_img = rggb_img.transpose(1,2,0).astype(np.int64)
57+
# Subtract the black level
58+
rggb_img -= black_level
59+
60+
# Crop out a square with a side length of 800
61+
H, W = rggb_img.shape[:2]
62+
rggb_img = rggb_img[H//2-r:H//2+r, W//2-r:W//2+r, :]
63+
64+
65+
# Iterate over the 4 channels
66+
for c in range(4):
67+
cur_channel_img = rggb_img[:, :, c]
68+
69+
# Calculate the variance of TL (or the variance of Gaussian) + the variance of row, here recorded as the variance of read
70+
_, (sig_read,u_read,r_read) = scipy.stats.probplot(cur_channel_img.reshape(-1),rvalue=True)
71+
sigma_read[c][i] = sig_read
72+
mean_read[c][i] = u_read
73+
r2_read[c][i] = r_read**2
74+
75+
# Calculate TL
76+
row_all_cur_channel = np.mean(cur_channel_img,axis=1)
77+
cur_channel_img = cur_channel_img.astype(np.float32)
78+
cur_channel_img -= row_all_cur_channel.reshape(-1,1)
79+
X = cur_channel_img.reshape(-1)
80+
lam = scipy.stats.ppcc_max(X)
81+
lamda[c][i] = lam
82+
_, (sig_TL,u_TL,r_TL) = scipy.stats.probplot(X,dist=scipy.stats.tukeylambda(lam), rvalue=True)
83+
sigma_TL[c][i] = sig_TL
84+
mean_TL[c][i] = u_TL
85+
r2_TL[c][i] = r_TL**2
86+
87+
# Calculate gauss
88+
_,(sig_gauss,u_gauss,r_gauss) = scipy.stats.probplot(X,rvalue=True)
89+
sigma_gauss[c][i] = sig_gauss
90+
mean_gauss[c][i] = u_gauss
91+
r2_gauss[c][i] = r_gauss**2
92+
param = {
93+
'black_level':black_level,
94+
'lam':lamda.tolist(),
95+
'sigmaRead':sigma_read.tolist(), 'meanRead':mean_read.tolist(), 'r2Gs':r2_read.tolist(),
96+
'sigmaR':sigma_row.tolist(), 'meanR':mean_row.tolist(), 'r2R':r2_row.tolist(),
97+
'sigmaTL':sigma_TL.tolist(), 'meanTL':mean_TL.tolist(), 'r2TL':r2_TL.tolist(),
98+
'sigmaGs':sigma_gauss.tolist(), 'meanGs':mean_gauss.tolist(), 'r2Gs':r2_gauss.tolist(),
99+
}
100+
101+
param_channel_mean = {
102+
'black_level':black_level,
103+
'lam':np.mean(lamda,axis=0).tolist(),
104+
'sigmaRead':np.mean(sigma_read,axis=0).tolist(), 'meanRead':np.mean(mean_read,axis=0).tolist(), 'r2Gs':np.mean(r2_read,axis=0).tolist(),
105+
'sigmaR':sigma_row.tolist(), 'meanR':mean_row.tolist(), 'r2R':r2_row.tolist(),
106+
'sigmaTL':np.mean(sigma_TL,axis=0).tolist(), 'meanTL':np.mean(mean_TL,axis=0).tolist(), 'r2TL':np.mean(r2_TL,axis=0).tolist(),
107+
'sigmaGs':np.mean(sigma_gauss,axis=0).tolist(), 'meanGs':np.mean(mean_gauss,axis=0).tolist(), 'r2Gs':np.mean(r2_gauss,axis=0).tolist(),
108+
}
109+
110+
return param, param_channel_mean
111+
112+
113+
# get noise params from dark imgs per camera
114+
def calib_dark_per_camera(cam_dir):
115+
dark_dir = os.path.join(cam_dir, 'dark')
116+
iso_list = sorted(os.listdir(dark_dir))
117+
if '.DS_Store' in iso_list:
118+
iso_list.remove('.DS_Store')
119+
# if '400' in iso_list:
120+
# iso_list.remove('400')
121+
dark_calib_params = dict()
122+
dark_calib_params_channel_mean = dict()
123+
for iso in iso_list:
124+
print(iso)
125+
param, param_channel_mean = calib_dark_per_iso(cam_dir,iso=int(iso))
126+
dark_calib_params[iso] = param
127+
dark_calib_params_channel_mean[iso] = param_channel_mean
128+
129+
130+
cam_name = cam_dir.split('/')[-1]
131+
if not os.path.exists('./result/calib/dark_calib_params'):
132+
os.mkdir('./result/calib/dark_calib_params')
133+
dark_calib_params_dir = os.path.join('./result/calib/dark_calib_params',cam_name)
134+
if not os.path.exists(dark_calib_params_dir):
135+
os.mkdir(dark_calib_params_dir)
136+
137+
with open(os.path.join(dark_calib_params_dir,'dark_calib_params.json'),'w') as json_file:
138+
json.dump(dark_calib_params,json_file)
139+
with open(os.path.join(dark_calib_params_dir,'dark_calib_params_channel_mean.json'),'w') as json_file:
140+
json.dump(dark_calib_params_channel_mean,json_file)

0 commit comments

Comments
 (0)