taotoolkit 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- taotoolkit/__init__.py +81 -0
- taotoolkit/asrVrf.py +322 -0
- taotoolkit/tao_camDvt.py +1186 -0
- taotoolkit/tao_cmdTool.py +626 -0
- taotoolkit/tao_common.py +435 -0
- taotoolkit/tao_device.py +1011 -0
- taotoolkit/tao_flash.py +823 -0
- taotoolkit/tao_tuning.py +1344 -0
- taotoolkit/tao_vrfTool.py +690 -0
- taotoolkit-1.0.0.dist-info/METADATA +50 -0
- taotoolkit-1.0.0.dist-info/RECORD +13 -0
- taotoolkit-1.0.0.dist-info/WHEEL +5 -0
- taotoolkit-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- encoding: utf-8 -*-
|
|
3
|
+
"""VRF文件处理工具"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import sys
|
|
8
|
+
import cv2
|
|
9
|
+
import argparse
|
|
10
|
+
import math
|
|
11
|
+
import numpy as np
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from .asrVrf import Vrf
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from .tao_common import (
|
|
17
|
+
debugCtrl,
|
|
18
|
+
eExit,
|
|
19
|
+
loge,
|
|
20
|
+
logi,
|
|
21
|
+
logd,
|
|
22
|
+
normalize_args,
|
|
23
|
+
register_command,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_file_index(filename):
|
|
28
|
+
pattern = re.compile(r"req\[(\d+)\]")
|
|
29
|
+
res = pattern.findall(filename)
|
|
30
|
+
if len(res) != 1:
|
|
31
|
+
raise Exception("Cannot get file index!")
|
|
32
|
+
file_index = int(res[0])
|
|
33
|
+
return file_index
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_meta_file_by_index(path, file_index):
|
|
37
|
+
file_list = os.listdir(path)
|
|
38
|
+
meta_fname = ""
|
|
39
|
+
for f in file_list:
|
|
40
|
+
if f.endswith("_%d.txt" % (file_index)):
|
|
41
|
+
meta_fname = f
|
|
42
|
+
break
|
|
43
|
+
meta_fname = path + "\\" + meta_fname
|
|
44
|
+
return meta_fname
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_meta_file(path):
|
|
48
|
+
file_list = os.listdir(path)
|
|
49
|
+
meta_fname = ""
|
|
50
|
+
for f in file_list:
|
|
51
|
+
if f.endswith(".xml"):
|
|
52
|
+
meta_fname = f
|
|
53
|
+
break
|
|
54
|
+
meta_fname = path + "\\" + meta_fname
|
|
55
|
+
return meta_fname
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def grep_info(tag, meta_txt):
|
|
59
|
+
pattern_str = "<%s>(.*?)</%s>" % (tag, tag)
|
|
60
|
+
pattern = re.compile(pattern_str)
|
|
61
|
+
return float(pattern.findall(meta_txt)[0])
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def unpack_mipi_raw10(byte_buf):
|
|
65
|
+
data = np.frombuffer(byte_buf, dtype=np.uint8)
|
|
66
|
+
# 5 bytes contain 4 10-bit pixels (5x8 == 4x10)
|
|
67
|
+
b1, b2, b3, b4, b5 = np.reshape(data, (data.shape[0] // 5, 5)).astype(np.uint16).T
|
|
68
|
+
p1 = (b1 << 2) + ((b5) & 0x3)
|
|
69
|
+
p2 = (b2 << 2) + ((b5 >> 2) & 0x3)
|
|
70
|
+
p3 = (b3 << 2) + ((b5 >> 4) & 0x3)
|
|
71
|
+
p4 = (b4 << 2) + ((b5 >> 6) & 0x3)
|
|
72
|
+
unpacked = np.reshape(np.concatenate((p1[:, None], p2[:, None], p3[:, None], p4[:, None]), axis=1), 4 * p1.shape[0])
|
|
73
|
+
return unpacked
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def unpack_mipi_raw12(byte_buf):
|
|
77
|
+
data = np.frombuffer(byte_buf, dtype=np.uint8)
|
|
78
|
+
# 3 bytes contain 2 12-bit pixels (3x8 == 2x12)
|
|
79
|
+
b1, b2, b3 = np.reshape(data, (data.shape[0] // 3, 3)).astype(np.uint16).T
|
|
80
|
+
p1 = (b1 << 4) + ((b3) & 0xF)
|
|
81
|
+
p2 = (b2 << 4) + ((b3 >> 4) & 0xF)
|
|
82
|
+
unpacked = np.reshape(np.concatenate((p1[:, None], p2[:, None]), axis=1), 2 * p1.shape[0])
|
|
83
|
+
return unpacked
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def unpack_mipi_raw14(byte_buf):
|
|
87
|
+
data = np.frombuffer(byte_buf, dtype=np.uint8)
|
|
88
|
+
# 7 bytes contain 4 14-bit pixels (7x8 == 4x14)
|
|
89
|
+
b1, b2, b3, b4, b5, b6, b7 = np.reshape(data, (data.shape[0] // 7, 7)).astype(np.uint16).T
|
|
90
|
+
p1 = (b1 << 6) + (b5 & 0x3F)
|
|
91
|
+
p2 = (b2 << 6) + (b5 >> 6) + (b6 & 0xF)
|
|
92
|
+
p3 = (b3 << 6) + (b6 >> 4) + (b7 & 0x3)
|
|
93
|
+
p4 = (b4 << 6) + (b7 >> 2)
|
|
94
|
+
unpacked = np.reshape(np.concatenate((p1[:, None], p2[:, None], p3[:, None], p4[:, None]), axis=1), 4 * p1.shape[0])
|
|
95
|
+
return unpacked
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def convertMipi2RawWithStrde(mipiFile, bitDeepth, width, height, stride):
|
|
99
|
+
mipiData = np.fromfile(mipiFile, dtype="uint8")
|
|
100
|
+
|
|
101
|
+
# print(mipiFile," file size:", mipiData.size)
|
|
102
|
+
mipiData.resize(height, stride)
|
|
103
|
+
# print(mipiData.shape)
|
|
104
|
+
|
|
105
|
+
if bitDeepth == 8 or bitDeepth == 16:
|
|
106
|
+
print("raw8 and raw16 no need to unpack")
|
|
107
|
+
return None
|
|
108
|
+
elif bitDeepth == 10:
|
|
109
|
+
byte2pixel = 5 / 4 # 5 bytes contain 4 10-bit pixels (5x8 == 4x10)
|
|
110
|
+
dataWithoutStride = np.ascontiguousarray(mipiData[::, : int(width * byte2pixel) :])
|
|
111
|
+
bayerData = unpack_mipi_raw10(dataWithoutStride)
|
|
112
|
+
elif bitDeepth == 12:
|
|
113
|
+
byte2pixel = 3 / 2 # 3 bytes contain 2 12-bit pixels (3x8 == 2x12)
|
|
114
|
+
dataWithoutStride = np.ascontiguousarray(mipiData[::, : int(width * byte2pixel) :])
|
|
115
|
+
bayerData = unpack_mipi_raw12(dataWithoutStride)
|
|
116
|
+
elif bitDeepth == 14:
|
|
117
|
+
byte2pixel = 7 / 4 # 7 bytes contain 4 14-bit pixels (7x8 == 4x14)
|
|
118
|
+
dataWithoutStride = np.ascontiguousarray(mipiData[::, : int(width * byte2pixel) :])
|
|
119
|
+
bayerData = unpack_mipi_raw14(dataWithoutStride)
|
|
120
|
+
else:
|
|
121
|
+
print("unsupport bayer bitDeepth:", bitDeepth)
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
# bayerData.tofile("tmp1920x1080_raw12.raw")
|
|
125
|
+
return bayerData
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def convertMipi2Raw(mipiFile, bitDeepth, imgWidth, imgHeight, bayer_order=cv2.COLOR_BayerBG2BGR):
|
|
129
|
+
mipiData = np.fromfile(mipiFile, dtype="uint8")
|
|
130
|
+
print("mipiraw file size:", mipiData.size)
|
|
131
|
+
bayerData = None
|
|
132
|
+
|
|
133
|
+
if bitDeepth == 8 or bitDeepth == 16:
|
|
134
|
+
print("raw8 and raw16 no need to unpack")
|
|
135
|
+
return None
|
|
136
|
+
elif bitDeepth == 10:
|
|
137
|
+
# raw10
|
|
138
|
+
bayerData = unpack_mipi_raw10(mipiData[: imgWidth * imgHeight * 5 // 4])
|
|
139
|
+
img = bayerData >> 2
|
|
140
|
+
elif bitDeepth == 12:
|
|
141
|
+
# raw12
|
|
142
|
+
bayerData = unpack_mipi_raw12(mipiData[: imgWidth * imgHeight * 3 // 2])
|
|
143
|
+
img = bayerData >> 4
|
|
144
|
+
elif bitDeepth == 14:
|
|
145
|
+
# raw14
|
|
146
|
+
bayerData = unpack_mipi_raw14(mipiData[: imgWidth * imgHeight * 7 // 4])
|
|
147
|
+
img = bayerData >> 6
|
|
148
|
+
else:
|
|
149
|
+
print("unsupport bayer bitDeepth:", bitDeepth)
|
|
150
|
+
|
|
151
|
+
# bayerData.tofile(mipiFile[:-4]+'_unpack.raw')
|
|
152
|
+
|
|
153
|
+
# img = img.astype(np.uint8).reshape(imgHeight, imgWidth, 1)
|
|
154
|
+
# # print(bayer_order)
|
|
155
|
+
# rgbimg = cv2.cvtColor(img, bayer_order)
|
|
156
|
+
# cv2.imwrite(mipiFile[:-4]+'_unpack.jpg', rgbimg)
|
|
157
|
+
|
|
158
|
+
return bayerData
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def judge_exist_xml(path):
|
|
162
|
+
files = os.listdir(path)
|
|
163
|
+
for k in range(len(files)):
|
|
164
|
+
files[k] = os.path.splitext(files[k])[1]
|
|
165
|
+
|
|
166
|
+
Str = ".xml"
|
|
167
|
+
|
|
168
|
+
if Str in files:
|
|
169
|
+
return True
|
|
170
|
+
else:
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
"""
|
|
175
|
+
处理单个RAW文件,将其转换为VRF格式
|
|
176
|
+
|
|
177
|
+
根据输入参数处理RAW图像数据,包括MIPI数据转换、元数据提取和VRF文件生成。
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
raw_name: RAW文件路径
|
|
181
|
+
width: 图像宽度
|
|
182
|
+
height: 图像高度
|
|
183
|
+
BLC10b: 黑电平校正值(10bit)
|
|
184
|
+
BayerOrder: Bayer格式顺序
|
|
185
|
+
BitDepth: 像素位深度
|
|
186
|
+
stride: MIPI数据步长,默认为0
|
|
187
|
+
isMipiRaw: 是否为MIPI RAW格式,默认为False
|
|
188
|
+
dgain: 数字增益值,默认为1
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
无返回值,处理结果保存为.vrf文件
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def raw2vrf(
|
|
196
|
+
raw_name, width, height, BLC10b, BayerOrder, BitDepth, stride=0, isMipiRaw=False, dgain=1.0, WBGain=[1.0, 1.0, 1.0]
|
|
197
|
+
):
|
|
198
|
+
|
|
199
|
+
path, filename = os.path.split(raw_name)
|
|
200
|
+
|
|
201
|
+
exist_xml = False
|
|
202
|
+
|
|
203
|
+
vrfObj = Vrf()
|
|
204
|
+
vrfObj.Width = np.uint16(width)
|
|
205
|
+
vrfObj.Height = np.uint16(height)
|
|
206
|
+
|
|
207
|
+
vrfObj.BLC10b = np.uint8(BLC10b)
|
|
208
|
+
vrfObj.BayerOrder = np.uint8(BayerOrder)
|
|
209
|
+
vrfObj.BitDepth = np.uint8(BitDepth)
|
|
210
|
+
|
|
211
|
+
if exist_xml:
|
|
212
|
+
# file_index = get_file_index(filename)
|
|
213
|
+
# meta_fname = get_meta_file_by_index(path, file_index)
|
|
214
|
+
meta_fname = get_meta_file(path)
|
|
215
|
+
f = open(meta_fname, "rt")
|
|
216
|
+
meta_txt = f.read()
|
|
217
|
+
f.close()
|
|
218
|
+
|
|
219
|
+
dgain = grep_info("digitalGain", meta_txt)
|
|
220
|
+
vrfObj.AWBGainB = np.uint16(128 * grep_info("gain_b", meta_txt))
|
|
221
|
+
vrfObj.AWBGainG = np.uint16(128 * grep_info("gain_g", meta_txt))
|
|
222
|
+
vrfObj.AWBGainR = np.uint16(128 * grep_info("gain_r", meta_txt))
|
|
223
|
+
|
|
224
|
+
vrfObj.AGain = np.uint16(16 * grep_info("shortGain", meta_txt))
|
|
225
|
+
vrfObj.TGain = np.uint16(vrfObj.AGain * dgain)
|
|
226
|
+
vrfObj.DrcGainDark = np.uint8(16 * grep_info("DrcGainDark", meta_txt))
|
|
227
|
+
vrfObj.DrcGain = np.uint8(16 * grep_info("DrcGain", meta_txt))
|
|
228
|
+
vrfObj.ExpoTime_us = np.uint32((grep_info("Short", meta_txt) / 1000))
|
|
229
|
+
|
|
230
|
+
else:
|
|
231
|
+
vrfObj.AWBGainB = np.uint16(128 * WBGain[2])
|
|
232
|
+
vrfObj.AWBGainG = np.uint16(128 * WBGain[1])
|
|
233
|
+
vrfObj.AWBGainR = np.uint16(128 * WBGain[0])
|
|
234
|
+
|
|
235
|
+
vrfObj.AGain = np.uint16(16)
|
|
236
|
+
vrfObj.TGain = np.uint16(vrfObj.AGain * dgain)
|
|
237
|
+
vrfObj.DrcGainDark = np.uint8(16)
|
|
238
|
+
vrfObj.DrcGain = np.uint8(16)
|
|
239
|
+
|
|
240
|
+
vrfObj.ExpoTime_us = np.uint32(65536)
|
|
241
|
+
|
|
242
|
+
vrfObj.PrintVrf()
|
|
243
|
+
if isMipiRaw:
|
|
244
|
+
if stride != 0:
|
|
245
|
+
bayerData = convertMipi2RawWithStrde(raw_name, vrfObj.BitDepth, width, height, stride)
|
|
246
|
+
else:
|
|
247
|
+
bayerData = convertMipi2Raw(raw_name, vrfObj.BitDepth, width, height)
|
|
248
|
+
else:
|
|
249
|
+
if BitDepth == 8:
|
|
250
|
+
bayerData = np.fromfile(raw_name, dtype="uint8")
|
|
251
|
+
else:
|
|
252
|
+
bayerData = np.fromfile(raw_name, dtype="uint16")
|
|
253
|
+
|
|
254
|
+
if bayerData != None:
|
|
255
|
+
vrfObj.unpackImage = bayerData.astype(np.uint16)
|
|
256
|
+
else:
|
|
257
|
+
eExit("get bayer data failed")
|
|
258
|
+
|
|
259
|
+
vrfName = os.path.join(path, os.path.splitext(filename)[0] + ".vrf")
|
|
260
|
+
vrfObj.SaveVrf(vrfName)
|
|
261
|
+
print(f"save {vrfName} successful")
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def scaner_file(url):
|
|
265
|
+
file = os.listdir(url)
|
|
266
|
+
for f in file:
|
|
267
|
+
real_url = os.path.join(url, f)
|
|
268
|
+
if os.path.isfile(real_url):
|
|
269
|
+
print(os.path.abspath(real_url))
|
|
270
|
+
elif os.path.isdir(real_url):
|
|
271
|
+
scaner_file(real_url)
|
|
272
|
+
else:
|
|
273
|
+
print("other")
|
|
274
|
+
pass
|
|
275
|
+
print(real_url)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def raw2vrfBatch(
|
|
279
|
+
path,
|
|
280
|
+
width,
|
|
281
|
+
height,
|
|
282
|
+
BLC10b,
|
|
283
|
+
BayerOrder,
|
|
284
|
+
BitDepth,
|
|
285
|
+
stride,
|
|
286
|
+
isMipiRaw=False,
|
|
287
|
+
dgain=1.0,
|
|
288
|
+
WBGain=[1.0, 1.0, 1.0],
|
|
289
|
+
suffix="raw",
|
|
290
|
+
):
|
|
291
|
+
file_list = os.listdir(path)
|
|
292
|
+
for f in file_list:
|
|
293
|
+
f_lower = f.lower()
|
|
294
|
+
if (
|
|
295
|
+
f_lower.endswith(".rawmipi")
|
|
296
|
+
or f_lower.endswith(".rawmipi10")
|
|
297
|
+
or f_lower.endswith(".raw")
|
|
298
|
+
or f_lower.endswith(".mipiraw")
|
|
299
|
+
or f_lower.endswith(f".{suffix}")
|
|
300
|
+
):
|
|
301
|
+
raw_name = os.path.join(path, f)
|
|
302
|
+
raw2vrf(raw_name, width, height, BLC10b, BayerOrder, BitDepth, stride, isMipiRaw, dgain, WBGain)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def srgb_to_linear(c_srgb_8bit):
|
|
306
|
+
"""
|
|
307
|
+
sRGB (0-255, gamma编码) -> 线性光强度 (0-1)
|
|
308
|
+
标准 sRGB EOTF (IEC 61966-2-1)
|
|
309
|
+
"""
|
|
310
|
+
c = np.asarray(c_srgb_8bit, dtype=np.float64) / 255.0
|
|
311
|
+
linear = np.where(c <= 0.04045, c / 12.92, ((c + 0.055) / 1.055) ** 2.4)
|
|
312
|
+
return linear
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def map_colorchecker_to_bitdepth(raw_color_values_8bit, dataMin, dataMax, out_dtype=np.uint16):
|
|
316
|
+
"""
|
|
317
|
+
把 sRGB 8bit 色卡值动态映射到任意 bitDepth 对应的 RAW 数据范围
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
raw_color_values_8bit: (24, 3) 的 sRGB 8bit 色值列表
|
|
321
|
+
dataMin: 目标 bit depth 下的最小值 (通常是黑电平)
|
|
322
|
+
dataMax: 目标 bit depth 下的最大值 (通常是 2^bitDepth - 1)
|
|
323
|
+
out_dtype: 输出数据类型
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
(24, 3) 的 ndarray,值域映射到 [dataMin, dataMax]
|
|
327
|
+
"""
|
|
328
|
+
linear_values = srgb_to_linear(raw_color_values_8bit) # [0,1]
|
|
329
|
+
mapped = linear_values * (dataMax - dataMin) + dataMin
|
|
330
|
+
return mapped.astype(out_dtype)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def get_bayer_pattern(bayer_order):
|
|
334
|
+
"""返回 2x2 pattern,索引方式 pattern[row_phase][col_phase] -> 'R'/'G'/'B'"""
|
|
335
|
+
patterns = {
|
|
336
|
+
3: [["R", "G"], ["G", "B"]], # RGGB
|
|
337
|
+
0: [["B", "G"], ["G", "R"]], # BGGR
|
|
338
|
+
1: [["G", "B"], ["R", "G"]], # GBRG
|
|
339
|
+
2: [["G", "R"], ["B", "G"]], # GRBG
|
|
340
|
+
}
|
|
341
|
+
return patterns.get(bayer_order, patterns[3]) # 默认 RGGB
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def fill_bayer_region(arr, y_start, y_end, x_start, x_end, r, g, b, pattern):
|
|
345
|
+
"""
|
|
346
|
+
按 Bayer CFA 相位,把 arr 的 [y_start:y_end, x_start:x_end] 区域
|
|
347
|
+
填充为给定的 r/g/b 值。相位由区域在全图中的绝对起始坐标决定。
|
|
348
|
+
"""
|
|
349
|
+
h = y_end - y_start
|
|
350
|
+
w = x_end - x_start
|
|
351
|
+
if h <= 0 or w <= 0:
|
|
352
|
+
return
|
|
353
|
+
region = np.zeros((h, w), dtype=arr.dtype)
|
|
354
|
+
color_map = {"R": r, "G": g, "B": b}
|
|
355
|
+
for dy in range(2):
|
|
356
|
+
for dx in range(2):
|
|
357
|
+
row_phase = (y_start + dy) % 2
|
|
358
|
+
col_phase = (x_start + dx) % 2
|
|
359
|
+
region[dy::2, dx::2] = color_map[pattern[row_phase][col_phase]]
|
|
360
|
+
arr[y_start:y_end, x_start:x_end] = region
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def generateVrf(outPath, width, height, bitDepth=10):
|
|
364
|
+
"""
|
|
365
|
+
生成带中心标准 24 色卡的随机噪声 VRF 文件
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
outPath: 输出路径
|
|
369
|
+
width: 图像宽度
|
|
370
|
+
height: 图像高度
|
|
371
|
+
bitDepth: 位深 (默认 10)
|
|
372
|
+
"""
|
|
373
|
+
if not os.path.exists(outPath):
|
|
374
|
+
os.makedirs(outPath)
|
|
375
|
+
|
|
376
|
+
vrfObj = Vrf()
|
|
377
|
+
vrfObj.BitDepth = np.uint8(bitDepth)
|
|
378
|
+
vrfObj.Width = np.uint16(width)
|
|
379
|
+
vrfObj.Height = np.uint16(height)
|
|
380
|
+
vrfObj.BLC10b = np.uint8(0)
|
|
381
|
+
|
|
382
|
+
dataMax = int(math.pow(2, bitDepth)) - 1
|
|
383
|
+
dataMin = int(vrfObj.BLC10b * (vrfObj.BitDepth - 9))
|
|
384
|
+
|
|
385
|
+
logi(f"bitDepth:{bitDepth} wxh: {width,height} dataRange: {dataMin,dataMax}")
|
|
386
|
+
|
|
387
|
+
# 生成完整随机噪声
|
|
388
|
+
random_noise = np.random.randint(dataMin, dataMax, (height, width), dtype=np.uint16)
|
|
389
|
+
|
|
390
|
+
# ========== 标准 24 色卡 (sRGB, 8-bit) ==========
|
|
391
|
+
# 24 个色块,按行排列 (4 行 x 6 列)
|
|
392
|
+
color_checker_rows = 4
|
|
393
|
+
color_checker_cols = 6
|
|
394
|
+
# ========== 标准 24 色卡 (sRGB, 8-bit, gamma编码) ==========
|
|
395
|
+
raw_color_values_8bit = [
|
|
396
|
+
[115, 82, 68], # 暗肤色
|
|
397
|
+
[194, 150, 130], # 亮肤色
|
|
398
|
+
[98, 122, 157], # 蓝天
|
|
399
|
+
[87, 108, 67], # 叶片绿
|
|
400
|
+
[133, 128, 177], # 蓝花
|
|
401
|
+
[103, 189, 170], # 蓝绿
|
|
402
|
+
[214, 126, 44], # 橙色
|
|
403
|
+
[80, 91, 166], # 紫蓝花
|
|
404
|
+
[193, 90, 99], # moderate red
|
|
405
|
+
[94, 60, 108], # 紫色
|
|
406
|
+
[157, 188, 64], # 黄绿
|
|
407
|
+
[224, 163, 46], # 橙黄
|
|
408
|
+
[56, 61, 150], # 蓝
|
|
409
|
+
[70, 148, 73], # 绿
|
|
410
|
+
[175, 54, 60], # 红
|
|
411
|
+
[231, 199, 31], # 黄
|
|
412
|
+
[187, 86, 149], # 品红
|
|
413
|
+
[8, 133, 161], # 青
|
|
414
|
+
[243, 243, 242], # 白
|
|
415
|
+
[200, 200, 200], # 浅灰 1
|
|
416
|
+
[160, 160, 160], # 浅灰 2
|
|
417
|
+
[122, 122, 121], # 浅灰 3
|
|
418
|
+
[85, 85, 85], # 浅灰 4
|
|
419
|
+
[52, 52, 52], # 黑
|
|
420
|
+
]
|
|
421
|
+
|
|
422
|
+
# 映射到 sensor RAW 的实际数据范围 [dataMin, dataMax]
|
|
423
|
+
raw_color_values = map_colorchecker_to_bitdepth(raw_color_values_8bit, dataMin, dataMax)
|
|
424
|
+
|
|
425
|
+
# ========== 计算中心区域 ==========
|
|
426
|
+
left = (width // 3) // 2 * 2
|
|
427
|
+
top = (height // 3) // 2 * 2
|
|
428
|
+
right = width - left
|
|
429
|
+
bottom = height - top
|
|
430
|
+
|
|
431
|
+
cell_w = ((right - left) // color_checker_cols) // 2 * 2
|
|
432
|
+
cell_h = ((bottom - top) // color_checker_rows) // 2 * 2
|
|
433
|
+
|
|
434
|
+
logd(f"色卡区域: left={left}, right={right}, top={top}, bottom={bottom}")
|
|
435
|
+
logd(f"每个色块大小: {cell_w} x {cell_h}")
|
|
436
|
+
|
|
437
|
+
border_px = 2
|
|
438
|
+
black_level = dataMin # 用 sensor 黑电平作为"纯黑"边框值
|
|
439
|
+
# ========== 绘制 24 色卡 ==========
|
|
440
|
+
for row in range(color_checker_rows):
|
|
441
|
+
for col in range(color_checker_cols):
|
|
442
|
+
y_start = top + row * cell_h
|
|
443
|
+
y_end = top + (row + 1) * cell_h
|
|
444
|
+
x_start = left + col * cell_w
|
|
445
|
+
x_end = left + (col + 1) * cell_w
|
|
446
|
+
|
|
447
|
+
idx = row * color_checker_cols + col
|
|
448
|
+
r, g, b = raw_color_values[idx]
|
|
449
|
+
|
|
450
|
+
pattern = get_bayer_pattern(vrfObj.BayerOrder)
|
|
451
|
+
|
|
452
|
+
# 1) 先把整个格子填成黑色边框底色
|
|
453
|
+
fill_bayer_region(
|
|
454
|
+
random_noise, y_start, y_end, x_start, x_end, black_level, black_level, black_level, pattern
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
# 2) 再在内缩 border_px 后的区域填实际色块颜色
|
|
458
|
+
inner_y_start = y_start + border_px
|
|
459
|
+
inner_y_end = y_end - border_px
|
|
460
|
+
inner_x_start = x_start + border_px
|
|
461
|
+
inner_x_end = x_end - border_px
|
|
462
|
+
|
|
463
|
+
if inner_y_end > inner_y_start and inner_x_end > inner_x_start:
|
|
464
|
+
fill_bayer_region(
|
|
465
|
+
random_noise, inner_y_start, inner_y_end, inner_x_start, inner_x_end, r, g, b, pattern
|
|
466
|
+
)
|
|
467
|
+
else:
|
|
468
|
+
logi(f"警告: 色块 idx={idx} 尺寸太小,border_px={border_px} 后无法容纳色块内容,已跳过内部填充")
|
|
469
|
+
|
|
470
|
+
vrfObj.unpackImage = random_noise
|
|
471
|
+
vrfName = os.path.join(outPath, f"random_noise{bitDepth}.vrf")
|
|
472
|
+
vrfObj.SaveVrf(vrfName)
|
|
473
|
+
logi(f"VRF 文件已生成: {vrfName}")
|
|
474
|
+
|
|
475
|
+
# return vrfObj
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def generateRandomVrf(outPath, width, height, bitDepth=10):
|
|
479
|
+
if not os.path.exists(outPath):
|
|
480
|
+
os.makedirs(outPath)
|
|
481
|
+
|
|
482
|
+
vrfObj = Vrf()
|
|
483
|
+
vrfObj.BitDepth = np.uint8(bitDepth)
|
|
484
|
+
vrfObj.Width = np.uint16(width)
|
|
485
|
+
vrfObj.Height = np.uint16(height)
|
|
486
|
+
|
|
487
|
+
dataMax = int(math.pow(2, bitDepth)) - 1
|
|
488
|
+
dataMin = int(vrfObj.BLC10b * (vrfObj.BitDepth - 9))
|
|
489
|
+
# 生成10位随机噪声数据(0-1023)
|
|
490
|
+
logi(f"bitDepth:{bitDepth} wxh: {width,height} dataRange: {dataMin,dataMax}")
|
|
491
|
+
random_noise = np.random.randint(dataMin, dataMax, (height, width), dtype=np.uint16) # 10位数据范围是0-1023
|
|
492
|
+
|
|
493
|
+
vrfObj.unpackImage = random_noise
|
|
494
|
+
|
|
495
|
+
vrfName = os.path.join(outPath, f"random_noise{bitDepth}.vrf")
|
|
496
|
+
vrfObj.SaveVrf(vrfName)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def makeNoiseToVrf(vrfFile: str, noiseLevel: float):
|
|
500
|
+
path = Path(vrfFile)
|
|
501
|
+
if not path.is_file():
|
|
502
|
+
raise FileNotFoundError(f"{vrfFile} 不是一个有效的文件")
|
|
503
|
+
|
|
504
|
+
outPath, _, nameWithoutSuffix, _ = path.parent, path.name, path.stem, path.suffix
|
|
505
|
+
|
|
506
|
+
vrfObj = Vrf()
|
|
507
|
+
vrfObj.Read(vrfFile)
|
|
508
|
+
bitDepth = vrfObj.BitDepth
|
|
509
|
+
|
|
510
|
+
dataMax = int(math.pow(2, bitDepth)) - 1
|
|
511
|
+
dataMin = vrfObj.BLC10b * (vrfObj.BitDepth - 9)
|
|
512
|
+
|
|
513
|
+
# 生成随机噪声, 噪声范围可以根据需要调整
|
|
514
|
+
noiseMax = int(noiseLevel * dataMax)
|
|
515
|
+
noiseMin = -noiseMax
|
|
516
|
+
print(f"bitDepth:{bitDepth} noiseRange: {noiseMin,noiseMax}")
|
|
517
|
+
random_noise = np.random.randint(noiseMin, noiseMax, (vrfObj.Height, vrfObj.Width), dtype=np.int16)
|
|
518
|
+
|
|
519
|
+
# 将噪声添加到原始数据中
|
|
520
|
+
noisy_data = vrfObj.unpackImage + random_noise
|
|
521
|
+
|
|
522
|
+
# 确保数据在bitDepth位范围内
|
|
523
|
+
print(f"dataMin: {dataMin}")
|
|
524
|
+
noisy_data = np.clip(noisy_data, dataMin, dataMax).astype(np.uint16)
|
|
525
|
+
|
|
526
|
+
vrfObj.unpackImage = noisy_data
|
|
527
|
+
|
|
528
|
+
vrfName = os.path.join(outPath, nameWithoutSuffix + f"_random_noise_{noiseLevel}.vrf")
|
|
529
|
+
vrfObj.SaveVrf(vrfName)
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def CLI_raw2vrf(argsList):
|
|
533
|
+
parser = argparse.ArgumentParser(prog="raw2vrf", description="mipiraw/raw文件转换成vrf文件")
|
|
534
|
+
parser.add_argument(
|
|
535
|
+
"-p",
|
|
536
|
+
"--path",
|
|
537
|
+
type=str,
|
|
538
|
+
required=True,
|
|
539
|
+
default="",
|
|
540
|
+
help="RAW文件路径或文件名,如果是路径则会处理该路径下的所有raw/mipiraw文件",
|
|
541
|
+
)
|
|
542
|
+
parser.add_argument(
|
|
543
|
+
"-f", "--format", type=str, required=True, choices=["raw", "mipiraw"], help="raw格式,可选值: raw, mipiraw"
|
|
544
|
+
)
|
|
545
|
+
parser.add_argument("--width", type=int, required=True, help="图像宽度")
|
|
546
|
+
parser.add_argument("--height", type=int, required=True, help="图像高度")
|
|
547
|
+
parser.add_argument("-d", "--depth", type=int, required=True, choices=[8, 10, 12, 14, 16], help="像素位深度")
|
|
548
|
+
parser.add_argument("-g", "--gain", type=float, default=1, help="数字增益值,float类型")
|
|
549
|
+
parser.add_argument("-s", "--stride", type=int, default=0, help="raw stride,默认为0表示无stride")
|
|
550
|
+
parser.add_argument("-o", "--ob10b", type=int, default=0, help="黑电平校正值(10bit)")
|
|
551
|
+
parser.add_argument(
|
|
552
|
+
"-b",
|
|
553
|
+
"--bayerorder",
|
|
554
|
+
type=int,
|
|
555
|
+
default=0,
|
|
556
|
+
choices=[0, 1, 2, 3],
|
|
557
|
+
help="Bayer格式顺序,0:BGGR, 1:GBRG, 2:GRBG, 3:RGGB",
|
|
558
|
+
)
|
|
559
|
+
parser.add_argument("--wbgain", type=float, default=[1.0, 1.0, 1.0], help="wb gain,float类型, 顺序为R G B", nargs=3)
|
|
560
|
+
parser.add_argument(
|
|
561
|
+
"--suffix",
|
|
562
|
+
type=str,
|
|
563
|
+
required=False,
|
|
564
|
+
default="raw",
|
|
565
|
+
help="文件后缀,默认为raw/rawmipi/mipiraw,如果是路径则会处理该路径下的所有指定后缀的文件",
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
args = parser.parse_args(argsList)
|
|
569
|
+
|
|
570
|
+
path = args.path
|
|
571
|
+
isMipiRaw = True if args.format == "mipiraw" else False
|
|
572
|
+
width = int(args.width)
|
|
573
|
+
height = int(args.height)
|
|
574
|
+
dgain = float(args.gain)
|
|
575
|
+
stride = int(args.stride)
|
|
576
|
+
BLC10b = np.uint16(int(args.ob10b))
|
|
577
|
+
BayerOrder = np.uint16(int(args.bayerorder))
|
|
578
|
+
BitDepth = np.uint16(int(args.depth))
|
|
579
|
+
WBGain = [float(gain) for gain in args.wbgain]
|
|
580
|
+
suffix = args.suffix
|
|
581
|
+
|
|
582
|
+
if os.path.isdir(path):
|
|
583
|
+
raw2vrfBatch(path, width, height, BLC10b, BayerOrder, BitDepth, stride, isMipiRaw, dgain, WBGain, suffix)
|
|
584
|
+
else:
|
|
585
|
+
raw2vrf(path, width, height, BLC10b, BayerOrder, BitDepth, stride, isMipiRaw, dgain, WBGain)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def rotVrf(input, output, mode):
|
|
589
|
+
vrfObj = Vrf()
|
|
590
|
+
vrfObj.Read(input)
|
|
591
|
+
vrfObj.rot(mode)
|
|
592
|
+
vrfObj.SaveVrf(output)
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def rotVrfBatch(path, mode, tag):
|
|
596
|
+
file_list = os.listdir(path)
|
|
597
|
+
for f in file_list:
|
|
598
|
+
f_lower = f.lower()
|
|
599
|
+
if f_lower.endswith(".vrf") and f.find(tag) == -1:
|
|
600
|
+
input = os.path.join(path, f)
|
|
601
|
+
output = os.path.join(path, os.path.splitext(f)[0] + f"{tag}.vrf")
|
|
602
|
+
rotVrf(input, output, mode)
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def CLI_rotVrf(argsList):
|
|
606
|
+
parser = argparse.ArgumentParser(prog="rotVrf", description="vrf旋转")
|
|
607
|
+
parser.add_argument(
|
|
608
|
+
"-p", "--path", type=str, required=True, default="", help="RAW文件路径或文件名,路径则处理所有raw/mipiraw文件"
|
|
609
|
+
)
|
|
610
|
+
parser.add_argument("-o", "--orientation", type=str, required=True, choices=["mirror", "flip", "mirror_flip"])
|
|
611
|
+
parser.add_argument("-s", "--suffix", type=str, required=False, default="_rot", help="输出文件添加后缀")
|
|
612
|
+
args = parser.parse_args(argsList)
|
|
613
|
+
|
|
614
|
+
if os.path.isdir(args.path):
|
|
615
|
+
rotVrfBatch(args.path, args.orientation, args.suffix)
|
|
616
|
+
else:
|
|
617
|
+
rotVrf(args.path, os.path.splitext(args.path)[0] + f"{args.suffix}.vrf", args.orientation)
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def CLI_generateVrf(argsList):
|
|
621
|
+
parser = argparse.ArgumentParser(prog="generateVrf", description="生成VRF")
|
|
622
|
+
parser.add_argument("-p", "--path", type=str, required=True, default="", help="输出VRF路径")
|
|
623
|
+
parser.add_argument("--width", type=int, required=True, help="vrf宽度")
|
|
624
|
+
parser.add_argument("--height", type=int, required=True, help="vrf高度")
|
|
625
|
+
parser.add_argument("-d", "--depth", type=int, required=True, choices=[8, 10, 12, 14, 16], help="像素位深度")
|
|
626
|
+
args = parser.parse_args(argsList)
|
|
627
|
+
|
|
628
|
+
generateVrf(args.path, args.width, args.height, args.depth)
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def float_range(min_val: float, max_val: float):
|
|
632
|
+
def _check(value):
|
|
633
|
+
try:
|
|
634
|
+
f = float(value)
|
|
635
|
+
except ValueError:
|
|
636
|
+
raise argparse.ArgumentTypeError(f"无效的浮点数: {value}")
|
|
637
|
+
if f < min_val or f > max_val:
|
|
638
|
+
raise argparse.ArgumentTypeError(f"值必须在 {min_val} 到 {max_val} 之间")
|
|
639
|
+
return f
|
|
640
|
+
|
|
641
|
+
return _check
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def CLI_addNoise2Vrf(argsList):
|
|
645
|
+
parser = argparse.ArgumentParser(prog="addNoise2Vrf", description="在VRF中添加随机噪声")
|
|
646
|
+
parser.add_argument(
|
|
647
|
+
"-p", "--path", type=str, required=True, default="", help="RAW文件路径或文件名,路径则处理所有raw/mipiraw文件"
|
|
648
|
+
)
|
|
649
|
+
parser.add_argument("-l", "--level", type=float_range(0.0, 1.0), required=True, help="噪声等级,范围0-1.0")
|
|
650
|
+
args = parser.parse_args(argsList)
|
|
651
|
+
|
|
652
|
+
makeNoiseToVrf(args.path, args.level)
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
_COMMAND_REGISTRY = {}
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def printHelp():
|
|
659
|
+
print("[vrfProcess]")
|
|
660
|
+
print("raw2vrf -h : raw/mipiraw to vrf")
|
|
661
|
+
print("rotVrf -h : 旋转 vrf")
|
|
662
|
+
print("gvrf -h : 生成随机噪声 vrf")
|
|
663
|
+
print("avrf -h : 增加随机噪声到 vrf")
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
CLI_raw2vrf = register_command("raw2vrf", registry=_COMMAND_REGISTRY)(CLI_raw2vrf)
|
|
667
|
+
CLI_rotVrf = register_command("rotvrf", registry=_COMMAND_REGISTRY)(CLI_rotVrf)
|
|
668
|
+
CLI_generateVrf = register_command("gvrf", registry=_COMMAND_REGISTRY)(CLI_generateVrf)
|
|
669
|
+
CLI_addNoise2Vrf = register_command("avrf", registry=_COMMAND_REGISTRY)(CLI_addNoise2Vrf)
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def vrfToolMain(args):
|
|
673
|
+
debugCtrl(1) if "debug" in args else debugCtrl(0)
|
|
674
|
+
args.remove("debug") if "debug" in args else args
|
|
675
|
+
args = normalize_args(args)
|
|
676
|
+
if len(args) < 1 or args[0] in {"help", "-h", "--help"}:
|
|
677
|
+
printHelp()
|
|
678
|
+
return
|
|
679
|
+
cmd = args[0]
|
|
680
|
+
handler = _COMMAND_REGISTRY.get(cmd)
|
|
681
|
+
if handler is None:
|
|
682
|
+
loge(f"unsupport command {cmd}")
|
|
683
|
+
return
|
|
684
|
+
handler(args[1:])
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
if __name__ == "__main__":
|
|
688
|
+
debugCtrl(1) if "debug" in sys.argv else debugCtrl(0)
|
|
689
|
+
sys.argv.remove("debug") if "debug" in sys.argv else sys.argv
|
|
690
|
+
vrfToolMain(sys.argv)
|