autoGamma.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. """
  2. author: chaohui.yang
  3. date: 2022-09-07
  4. """
  5. import math
  6. from abc import abstractmethod, ABCMeta
  7. from datetime import datetime
  8. import numpy as np
  9. from matplotlib.ticker import MultipleLocator
  10. from scipy import interpolate
  11. import xlrd
  12. import json
  13. import jsbeautifier
  14. import matplotlib.pyplot as plt
  15. np.set_printoptions(precision=5)
  16. np.set_printoptions(suppress=True)
  17. def readDataFromExcel(filePath: str):
  18. """
  19. 从EXCEL中读取目标x,y以及gamma值,以及测量的XYZ值
  20. :param filePath: ecxel路径
  21. :return: 转换关系矩阵RGB_2_XYZ, 包含11组测量值的矩阵XYZ_11, 包含11个测量值索引的数组XYZ11_index,target_x, target_y, target_gamma
  22. """
  23. workbook = xlrd.open_workbook(filePath)
  24. sheet = workbook.sheets()[0]
  25. # 使用表格中定义的target_x,target_y,以及target_gamma值
  26. target_x = sheet.cell_value(8, 1)
  27. target_y = sheet.cell_value(9, 1)
  28. target_gamma = sheet.cell_value(10, 1)
  29. print(f"从表格读取到:\ntarget_x = {target_x}\ntarget_y = {target_y}\ntarget_gamma = {target_gamma}\n")
  30. XYZ11_index = sheet.row_values(1, 1)[3:14]
  31. rows_X = sheet.row_values(2, 1)[0:14] # 获取第X行内容
  32. rows_Y = sheet.row_values(3, 1)[0:14] # 获取第Y行内容
  33. rows_Z = sheet.row_values(4, 1)[0:14] # 获取第Z行内容
  34. XYZ_all = [[rows_X[i], rows_Y[i], rows_Z[i]] for i in range(len(rows_X))]
  35. XYZ_11 = np.array(XYZ_all[3:14]).T
  36. RGB_2_XYZ = np.array(XYZ_all[0:3]).T # np.array([XYZ[0], XYZ[1], XYZ[2]])
  37. return RGB_2_XYZ, XYZ_11, XYZ11_index, target_x, target_y, target_gamma
  38. def cal_target_XYZ11(XYZ_2_RGB, target_x, target_y, target_gamma):
  39. """
  40. 根据“转换关系”矩阵XYZ_2_RGB,计算出11个目标XYZ值
  41. :param XYZ_2_RGB: 表示“转换关系”的矩阵,也就是测量而得的R/G/B对应的XYZ值所合成的矩阵(3X3)
  42. :param target_x:
  43. :param target_y:
  44. :param target_gamma:
  45. :return: targetXYZ11:包含11个目标XYZ值的矩阵(3X11)
  46. """
  47. print("以下求最大可行目标亮度值....")
  48. targetY_list = []
  49. for i in range(0, 3):
  50. targetY = 1 / (XYZ_2_RGB[i][0] * target_x / target_y + XYZ_2_RGB[i][1] + XYZ_2_RGB[i][2] * (
  51. 1 - target_x - target_y) / target_y)
  52. targetY_list.append(targetY)
  53. print(i + 1, "式子的解:targetY = ", targetY)
  54. targetY_max = round(min(targetY_list), 5)
  55. print("最大可行目标亮度值: targetY_max = ", targetY_max)
  56. targetXYZ11 = np.zeros((3, 11))
  57. # print("targetXYZ11: \n", targetXYZ11)
  58. # 算出11个target_XYZ
  59. for i in range(0, 11):
  60. targetXYZ11[1][i] = targetY_max * ((1 - 0.1 * i) ** target_gamma)
  61. targetXYZ11[0][i] = (targetXYZ11[1][i] * target_x) / target_y
  62. targetXYZ11[2][i] = targetXYZ11[1][i] * (1 - target_x - target_y) / target_y
  63. # print("11个目标target_XYZ值: \n", targetXYZ11)
  64. return targetXYZ11
  65. # 插值方法
  66. def interpolate_to_256(ori_matrix, ori_matrix_index):
  67. """
  68. 插值算法。根据输入数组和对应元素序列,插值使得最终为256个元素
  69. :param ori_matrix: 输入矩阵(n x m),通常每行分别对应R/G/B值或X/Y/Z值
  70. :param ori_matrix_index: 对应每列矩阵元素的索引值序列(1 x m)
  71. :return: output_matrix: 输出插值后的矩阵(nx256)
  72. """
  73. # print(f"待扩充矩阵序列间隔为ori_index :\n{ori_matrix_index}")
  74. # print("待扩充矩阵为: \n", ori_matrix)
  75. new_index = np.arange(0, 256, 1)
  76. # 插值方式kind = linear|slinear|quadratic|cubic
  77. f = interpolate.interp1d(ori_matrix_index, ori_matrix, kind="linear")
  78. output_matrix = f(new_index)
  79. # print("扩充后的矩阵为 :\n", output_matrix)
  80. return output_matrix
  81. def cal_RGB_by_XYZ(transMatrix, matrix_XYZ):
  82. """
  83. 根据逆矩阵和XYZ计算RGB
  84. :param transMatrix: RGB TO XYZ的转换矩阵逆矩阵(3X3)
  85. :param matrix_XYZ: XYZ矩阵(3X3)
  86. :return: RGB矩阵(3X3)
  87. """
  88. matrix_RGB = np.dot(transMatrix, matrix_XYZ)
  89. return matrix_RGB
  90. def calActualRGB11(targetRGB11, targetXYZ11, XYZ256):
  91. """
  92. 根据11个目标RGB值、目标XYZ值以及测量再插值而得的256个XYZ,算出实际需要的11个RGB值
  93. :param targetRGB11: 包含11个目标RGB值的3x11矩阵
  94. :param targetXYZ11: 包含11个目标XYZ值的3x11矩阵
  95. :param XYZ256: 包含256个由测量的XYZ值的3x256矩阵
  96. :return: actualRGB11:包含11个实际需要的RGB值的3x11矩阵
  97. """
  98. print(f"以下开始计算实际需要的11组RGB值...\n")
  99. actualRGB11 = np.zeros([3, 11])
  100. targetRGB11 = targetRGB11.T[::-1].T
  101. print("翻转的targetRGB11 is: ", targetRGB11)
  102. targetXYZ11 = targetXYZ11.T[::-1].T
  103. print("翻转的targetXYZ11 is: ", targetXYZ11)
  104. for n in range(0, 3):
  105. for m in range(1, 11):
  106. distances = []
  107. for index in range(0, 256):
  108. distance = abs(XYZ256[n, index] - targetXYZ11[n, m])
  109. distances.append(distance)
  110. # print(f"targetXYZ11[{n}][{m}]对应的XYZ256的距离为:\n{distances}")
  111. min_index = distances.index(min(distances))
  112. print(f"目标点targetXYZ11[{n}][{m}] , 对应XYZ256中最短距离的元素值index为: {min_index}")
  113. # 当min_index刚好为首、尾index时无需参与gamma计算,直接取对应的target值
  114. if min_index == 0 or min_index == 255:
  115. actualRGB11[n, m] = targetRGB11[n, m]
  116. print(f"该点不计算panel gamma值\n")
  117. else:
  118. panelGamma = round(math.log((XYZ256[n, min_index]) / XYZ256[n, 255]) / math.log((min_index) / 255), 5)
  119. print(f"对应targetXYZ11[{n}][{m}]最短距离的XYZ256[{n}][{min_index}]的panel gamma值为:{panelGamma}\n")
  120. # 当panelGamma算出为0时,无法数学计算直接取对应的target值
  121. if panelGamma == 0:
  122. actualRGB11[n, m] = targetRGB11[n, m]
  123. else:
  124. actualRGB11[n, m] = (targetRGB11[n, m]) ** (1 / panelGamma)
  125. actualRGB11[n] = actualRGB11[n][::-1]
  126. # print("实际需要的11组RGB值为 : \n", actualRGB11)
  127. return actualRGB11
  128. def showRGBcurve(RGB255):
  129. """
  130. 根据计RGB255点绘出R/G/B三条曲线
  131. :param RGB255: 最终计算而得的RGB255数组(3X256),每行分别对应R/G/B的256个值
  132. """
  133. x = np.arange(0, 256)
  134. plt.title("RGB255")
  135. # x轴的刻度间隔
  136. x_major_locator = MultipleLocator(15)
  137. # y轴的刻度间隔
  138. y_major_locator = MultipleLocator(10)
  139. # ax为两条坐标轴的实例
  140. ax = plt.gca()
  141. ax.xaxis.set_major_locator(x_major_locator)
  142. ax.yaxis.set_major_locator(y_major_locator)
  143. plt.plot(x, RGB255[0], label="R", color='red')
  144. plt.plot(x, RGB255[1], label="G", color='green')
  145. plt.plot(x, RGB255[2], label="B", color='blue')
  146. plt.legend()
  147. plt.show()
  148. class RGBcalculator(metaclass=ABCMeta):
  149. @abstractmethod
  150. def calRGB255(self, filePath: str):
  151. pass
  152. class Firetv_RGBcalculator(RGBcalculator):
  153. target_x = 0
  154. target_y = 0
  155. target_gamma = 2.2
  156. RGB_2_XYZ = np.zeros((3, 3))
  157. XYZ_11 = np.zeros((3, 11))
  158. XYZ11_index = [255, 229, 204, 178, 153, 127, 102, 76, 51, 26, 0]
  159. def calRGB255(self, FilePath: str):
  160. # 从excel中读取“转换矩阵”,“11组测量的XYZ”,以及“11组XYZ对应的坐标索引”
  161. self.RGB_2_XYZ, self.XYZ_11, selfXYZ11_index, self.target_x, \
  162. self.target_y, self.target_gamma = readDataFromExcel(FilePath)
  163. print("转换矩阵RGB_to_XYZ为:\n", self.RGB_2_XYZ)
  164. XYZ_2_RGB = np.linalg.pinv(self.RGB_2_XYZ)
  165. print("转换逆矩阵XYZ_2_RGB为:\n", XYZ_2_RGB)
  166. targetXYZ11 = cal_target_XYZ11(XYZ_2_RGB, self.target_x, self.target_y, self.target_gamma)
  167. print("11个目标target_XYZ值: \n", targetXYZ11)
  168. # 算出11组目标RGB
  169. targetRGB11 = cal_RGB_by_XYZ(XYZ_2_RGB, targetXYZ11)
  170. print("11个目标targetRGB11值: \n", targetRGB11)
  171. # 对测量出来的XYZ11做插值运算
  172. XYZ256 = interpolate_to_256(self.XYZ_11, self.XYZ11_index)
  173. # XYZ256 = interpolate3d_to_256(XYZ_11, XYZ11_index)
  174. print(f"测量的11个XYZ11为:\n {self.XYZ_11}")
  175. print(f"对XYZ11插值扩展后为XYZ256:\n {XYZ256}")
  176. ActualRGB11 = calActualRGB11(targetRGB11, targetXYZ11, XYZ256)
  177. print("实际需要的11个RGB值: \n", ActualRGB11)
  178. # 对实际需要的RGB11做插值运算得到最终的RGB256
  179. ActualRGB256 = interpolate_to_256(ActualRGB11, self.XYZ11_index)
  180. ActualRGB256 = np.multiply(ActualRGB256, 255) # 去归一化,以RGB-255显示
  181. ActualRGB256 = np.around(ActualRGB256, 4) # 小数点保留4位
  182. print(f"最终需要的256组RGB值: \n{ActualRGB256}")
  183. return ActualRGB256
  184. def calRGB255_for_Debug(self, new_XYZ_of_RGB, new_XYZ_11, new_XYZ11_index, new_target_x,
  185. new_target_y, new_target_gamma):
  186. """
  187. :param new_XYZ_of_RGB: 3X3矩阵
  188. :param new_XYZ_11: 3X11矩阵
  189. :param new_XYZ11_index: 1X11数组
  190. :param new_target_x:
  191. :param new_target_y:
  192. :param new_target_gamma:
  193. :return:
  194. """
  195. self.target_x = new_target_x
  196. self.target_y = new_target_y
  197. self.target_gamma = new_target_gamma
  198. # “转换矩阵”,“11组测量的XYZ”,以及“11组XYZ对应的`Z坐标索引”
  199. self.RGB_2_XYZ = new_XYZ_of_RGB
  200. self.XYZ_11 = new_XYZ_11
  201. self.XYZ11_index = new_XYZ11_index
  202. print(f"获取到:\ntarget_x = {self.target_x}\ntarget_y = {self.target_y}\ntarget_gamma = {self.target_gamma}\n")
  203. print("转换矩阵RGB_to_XYZ为:\n", self.RGB_2_XYZ)
  204. XYZ_2_RGB = np.linalg.pinv(self.RGB_2_XYZ)
  205. print("转换逆矩阵XYZ_2_RGB为:\n", XYZ_2_RGB)
  206. targetXYZ11 = cal_target_XYZ11(XYZ_2_RGB, self.target_x, self.target_y, self.target_gamma)
  207. print("11个目标target_XYZ值: \n", targetXYZ11)
  208. # 算出11组目标RGB
  209. targetRGB11 = cal_RGB_by_XYZ(XYZ_2_RGB, targetXYZ11)
  210. print("11个目标targetRGB11值: \n", targetRGB11)
  211. # 对测量出来的XYZ11做插值运算
  212. XYZ256 = interpolate_to_256(self.XYZ_11, self.XYZ11_index)
  213. # XYZ256 = interpolate3d_to_256(XYZ_11, XYZ11_index)
  214. print(f"测量的11个XYZ11为:\n {self.XYZ_11}")
  215. print(f"对XYZ11插值扩展后为XYZ256:\n {XYZ256}")
  216. ActualRGB11 = calActualRGB11(targetRGB11, targetXYZ11, XYZ256)
  217. print("实际需要的11个RGB值: \n", ActualRGB11)
  218. ActualXYZ11 = np.dot(XYZ_2_RGB, ActualRGB11)
  219. print(f"实际需要的11组XYZ值: \n{ActualXYZ11}")
  220. # 对实际需要的RGB11做插值运算得到最终的RGB256
  221. ActualRGB256 = interpolate_to_256(ActualRGB11, self.XYZ11_index)
  222. # showRGBcurve(np.multiply(ActualRGB256, 255))
  223. ActualRGB256 = np.multiply(ActualRGB256, 1023) # 去归一化,以RGB-255显示
  224. ActualRGB256 = np.around(ActualRGB256, 0).astype(int) # 小数点保留4位
  225. ActualXYZ256 = np.dot(XYZ_2_RGB, ActualRGB256)
  226. print(f"最终需要的256组XYZ值: \n{ActualXYZ256}")
  227. print(f"最终需要的256组RGB值: \n{ActualRGB256}")
  228. return ActualRGB256
  229. def dumpRGB2ini(RGB255):
  230. """
  231. 功能:将最终计算的RGB255值输出至文本,供机芯调用.该格式的ini暂时用不到
  232. :param RGB255:最终计算的RGB255数组(3X256),每行分别对应R/G/B的256个值
  233. """
  234. wb_mode = {}
  235. list = ["COLD", "STANDARD", "STANDARD2", "WARM", "WARM2", "SDR2HDR COLD", "SDR2HDR STANDARD", "SDR2HDR WARM"]
  236. for str in list:
  237. wb_mode[str] = [{"R_VALUE": []}, {"G_VALUE": []}, {"B_VALUE": []}]
  238. wb_mode[str][0]["R_VALUE"] = RGB255[0, :].tolist()
  239. wb_mode[str][1]["G_VALUE"] = RGB255[1, :].tolist()
  240. wb_mode[str][2]["B_VALUE"] = RGB255[2, :].tolist()
  241. # print("wb_mode: \n", wb_mode)
  242. options = jsbeautifier.default_options()
  243. options.indent_size = 2
  244. json_dict = jsbeautifier.beautify(json.dumps(wb_mode), options)
  245. print(json_dict)
  246. filename = 'gamma.ini'
  247. with open(filename, 'w', encoding='utf-8') as f:
  248. f.write(json_dict)
  249. print(f"gamma文件已保存至{filename}")
  250. def dumpRGB2ini_commom(RGB255):
  251. """
  252. 功能:将最终计算的RGB255值按一定格式输出至文本,供机芯调用.该格式比较通用
  253. :param RGB255: 最终计算的RGB255数组(3X256),每行分别对应R/G/B的256个值
  254. """
  255. RGB_list = []
  256. RGB_reg = "{0x000,0x010,0x020,0x030,0x040,0x050,0x060,0x070,0x080,0x090,0x0A0,0x0B0,0x0C0,0x0D0,0x0E0,0x0F0,\
  257. 0x100,0x110,0x120,0x130,0x140,0x150,0x160,0x170,0x180,0x190,0x1A0,0x1B0,0x1C0,0x1D0,0x1E0,0x1F0,\
  258. 0x200,0x210,0x220,0x230,0x240,0x250,0x260,0x270,0x280,0x290,0x2A0,0x2B0,0x2C0,0x2D0,0x2E0,0x2F0,\
  259. 0x300,0x310,0x320,0x330,0x340,0x350,0x360,0x370,0x380,0x390,0x3A0,0x3B0,0x3C0,0x3D0,0x3E0,0x3F0,\
  260. 0x400,0x410,0x420,0x430,0x440,0x450,0x460,0x470,0x480,0x490,0x4A0,0x4B0,0x4C0,0x4D0,0x4E0,0x4F0,\
  261. 0x500,0x510,0x520,0x530,0x540,0x550,0x560,0x570,0x580,0x590,0x5A0,0x5B0,0x5C0,0x5D0,0x5E0,0x5F0,\
  262. 0x600,0x610,0x620,0x630,0x640,0x650,0x660,0x670,0x680,0x690,0x6A0,0x6B0,0x6C0,0x6D0,0x6E0,0x6F0,\
  263. 0x700,0x710,0x720,0x730,0x740,0x750,0x760,0x770,0x780,0x790,0x7A0,0x7B0,0x7C0,0x7D0,0x7E0,0x7F0,\
  264. 0x800,0x810,0x820,0x830,0x840,0x850,0x860,0x870,0x880,0x890,0x8A0,0x8B0,0x8C0,0x8D0,0x8E0,0x8F0,\
  265. 0x900,0x910,0x920,0x930,0x940,0x950,0x960,0x970,0x980,0x990,0x9A0,0x9B0,0x9C0,0x9D0,0x9E0,0x9F0,\
  266. 0xA00,0xA10,0xA20,0xA30,0xA40,0xA50,0xA60,0xA70,0xA80,0xA90,0xAA0,0xAB0,0xAC0,0xAD0,0xAE0,0xAF0,\
  267. 0xB00,0xB10,0xB20,0xB30,0xB40,0xB50,0xB60,0xB70,0xB80,0xB90,0xBA0,0xBB0,0xBC0,0xBD0,0xBE0,0xBF0,\
  268. 0xC00,0xC10,0xC20,0xC30,0xC40,0xC50,0xC60,0xC70,0xC80,0xC90,0xCA0,0xCB0,0xCC0,0xCD0,0xCE0,0xCF0,\
  269. 0xD00,0xD10,0xD20,0xD30,0xD40,0xD50,0xD60,0xD70,0xD80,0xD90,0xDA0,0xDB0,0xDC0,0xDD0,0xDE0,0xDF0,\
  270. 0xE00,0xE10,0xE20,0xE30,0xE40,0xE50,0xE60,0xE70,0xE80,0xE90,0xEA0,0xEB0,0xEC0,0xED0,0xEE0,0xEF0,\
  271. 0xF00,0xF10,0xF20,0xF30,0xF40,0xF50,0xF60,0xF70,0xF80,0xF90,0xFA0,0xFB0,0xFC0,0xFD0,0xFE0,0xFF0}"
  272. # print("RGB255:\n", RGB255)
  273. list = ["R //Gamma - R", "G //Gamma - G", "B //Gamma - B"]
  274. for i in range(0, 3):
  275. RGB_list.append(list[i])
  276. for n in range(0, 256):
  277. RGB_list.append(RGB255[i][n])
  278. RGB_list.append("\n")
  279. # 按照已知gamma.txt样式生成内容
  280. RGB_list.append("/*")
  281. RGB_list.append("//Settint R")
  282. RGB_list.append(RGB_reg)
  283. RGB_list.append("\n")
  284. RGB_list.append("//Settint G")
  285. RGB_list.append(RGB_reg)
  286. RGB_list.append("\n")
  287. RGB_list.append("//Settint R")
  288. RGB_list.append(RGB_reg)
  289. RGB_list.append("\n")
  290. RGB_list.append("/*")
  291. # print("RGB_list: \n", RGB_list)
  292. time = datetime.strftime(datetime.now(), '%Y%m%d_%H%M%S') #
  293. filename = 'gamma.ini'
  294. with open(filename, 'w') as fp:
  295. [fp.write(str(item) + '\n') for item in RGB_list]
  296. fp.close()
  297. print(f"gamma文件已保存至{filename}")
  298. class Factory(metaclass=ABCMeta):
  299. @abstractmethod
  300. def create(self):
  301. pass
  302. class FiretvFactory(Factory):
  303. def __init__(self):
  304. print("FiretvFactory...")
  305. def create(self):
  306. return Firetv_RGBcalculator()