"""色准(ΔE2000 / 标准色)相关纯算法。 Step 1 重构:从 pqAutomationApp.PQAutomationApp 中原样搬迁以下方法, 去掉 self 参数,改为模块级纯函数: - calculate_delta_e_2000 - calculate_color_accuracy - get_accuracy_color_standards """ import math import numpy as np def calculate_delta_e_2000( measured_x, measured_y, measured_lv, standard_x, standard_y ): """ 计算 ΔE 2000 色差(修正版) Args: measured_x, measured_y: 测量的 xy 坐标 measured_lv: 测量的亮度(cd/m²) standard_x, standard_y: 标准的 xy 坐标 Returns: float: ΔE 2000 色差值 """ # ========== 1. xy → XYZ(使用实际亮度)========== def xy_to_XYZ(x, y, Y): if y == 0: return 0, 0, 0 X = x * Y / y Z = (1 - x - y) * Y / y return X, Y, Z # 修复:使用实际测量的亮度 X1, Y1, Z1 = xy_to_XYZ(measured_x, measured_y, measured_lv) # 修复:标准值使用相同的参考亮度(只比较色度差异) X2, Y2, Z2 = xy_to_XYZ(standard_x, standard_y, measured_lv) # ========== 2. XYZ → Lab(D65 白点)========== def XYZ_to_Lab(X, Y, Z): # D65 白点 Xn, Yn, Zn = 95.047, 100.000, 108.883 # 归一化 xr = X / Xn yr = Y / Yn zr = Z / Zn # f(t) 函数 def f(t): delta = 6.0 / 29.0 if t > delta ** 3: return t ** (1.0 / 3.0) else: return t / (3 * delta ** 2) + 4.0 / 29.0 fx = f(xr) fy = f(yr) fz = f(zr) L = 116 * fy - 16 a = 500 * (fx - fy) b = 200 * (fy - fz) return L, a, b L1, a1, b1 = XYZ_to_Lab(X1, Y1, Z1) L2, a2, b2 = XYZ_to_Lab(X2, Y2, Z2) # ========== 3. ΔE 2000 公式 ========== L_bar = (L1 + L2) / 2.0 C1 = math.sqrt(a1 ** 2 + b1 ** 2) C2 = math.sqrt(a2 ** 2 + b2 ** 2) C_bar = (C1 + C2) / 2.0 G = 0.5 * (1 - math.sqrt(C_bar ** 7 / (C_bar ** 7 + 25 ** 7))) a1_prime = a1 * (1 + G) a2_prime = a2 * (1 + G) C1_prime = math.sqrt(a1_prime ** 2 + b1 ** 2) C2_prime = math.sqrt(a2_prime ** 2 + b2 ** 2) C_bar_prime = (C1_prime + C2_prime) / 2.0 def calc_hue(a_prime, b): if a_prime == 0 and b == 0: return 0 h = math.atan2(b, a_prime) * 180 / math.pi if h < 0: h += 360 return h h1_prime = calc_hue(a1_prime, b1) h2_prime = calc_hue(a2_prime, b2) if C1_prime == 0 or C2_prime == 0: delta_h_prime = 0 else: delta_h = h2_prime - h1_prime if abs(delta_h) <= 180: delta_h_prime = delta_h elif delta_h > 180: delta_h_prime = delta_h - 360 else: delta_h_prime = delta_h + 360 if C1_prime == 0 or C2_prime == 0: H_bar_prime = h1_prime + h2_prime else: if abs(h1_prime - h2_prime) <= 180: H_bar_prime = (h1_prime + h2_prime) / 2.0 elif h1_prime + h2_prime < 360: H_bar_prime = (h1_prime + h2_prime + 360) / 2.0 else: H_bar_prime = (h1_prime + h2_prime - 360) / 2.0 delta_L_prime = L2 - L1 delta_C_prime = C2_prime - C1_prime delta_H_prime = ( 2 * math.sqrt(C1_prime * C2_prime) * math.sin(math.radians(delta_h_prime / 2.0)) ) S_L = 1 + (0.015 * (L_bar - 50) ** 2) / math.sqrt(20 + (L_bar - 50) ** 2) S_C = 1 + 0.045 * C_bar_prime T = ( 1 - 0.17 * math.cos(math.radians(H_bar_prime - 30)) + 0.24 * math.cos(math.radians(2 * H_bar_prime)) + 0.32 * math.cos(math.radians(3 * H_bar_prime + 6)) - 0.20 * math.cos(math.radians(4 * H_bar_prime - 63)) ) S_H = 1 + 0.015 * C_bar_prime * T delta_theta = 30 * math.exp(-(((H_bar_prime - 275) / 25) ** 2)) R_C = 2 * math.sqrt(C_bar_prime ** 7 / (C_bar_prime ** 7 + 25 ** 7)) R_T = -R_C * math.sin(math.radians(2 * delta_theta)) kL = 1.0 kC = 1.0 kH = 1.0 delta_E = math.sqrt( (delta_L_prime / (kL * S_L)) ** 2 + (delta_C_prime / (kC * S_C)) ** 2 + (delta_H_prime / (kH * S_H)) ** 2 + R_T * (delta_C_prime / (kC * S_C)) * (delta_H_prime / (kH * S_H)) ) return delta_E def calculate_color_accuracy(measured, standard): """计算色差(简化版,欧氏距离 × 1000)""" delta_E = {} for color in measured.keys(): dx = measured[color][0] - standard[color][0] dy = measured[color][1] - standard[color][1] delta_E[color] = np.sqrt(dx * dx + dy * dy) * 1000 return delta_E # 29 色 SDR 标准色板(保持与原实现一致) _SDR_COLOR_PATTERNS = [ ("White", 255, 255, 255), ("Gray 80", 230, 230, 230), ("Gray 65", 209, 209, 209), ("Gray 50", 186, 186, 186), ("Gray 35", 158, 158, 158), ("Dark Skin", 115, 82, 66), ("Light Skin", 194, 150, 130), ("Blue Sky", 94, 122, 156), ("Foliage", 89, 107, 66), ("Blue Flower", 130, 128, 176), ("Bluish Green", 99, 189, 168), ("Orange", 217, 120, 41), ("Purplish Blue", 74, 92, 163), ("Moderate Red", 194, 84, 97), ("Purple", 92, 61, 107), ("Yellow Green", 158, 186, 64), ("Orange Yellow", 230, 161, 46), ("Blue (Legacy)", 51, 61, 150), ("Green (Legacy)", 71, 148, 71), ("Red (Legacy)", 176, 48, 59), ("Yellow (Legacy)", 237, 199, 33), ("Magenta (Legacy)", 186, 84, 145), ("Cyan (Legacy)", 0, 133, 163), ("100% Red", 255, 0, 0), ("100% Green", 0, 255, 0), ("100% Blue", 0, 0, 255), ("100% Cyan", 0, 255, 255), ("100% Magenta", 255, 0, 255), ("100% Yellow", 255, 255, 0), ] def _rgb_to_xy_srgb(r, g, b): """sRGB (8bit) → CIE 1931 xy""" r, g, b = r / 255.0, g / 255.0, b / 255.0 def gamma_decode(c): if c <= 0.04045: return c / 12.92 else: return ((c + 0.055) / 1.055) ** 2.4 r_linear = gamma_decode(r) g_linear = gamma_decode(g) b_linear = gamma_decode(b) # sRGB → XYZ(D65 白点,IEC 61966-2-1) X = r_linear * 0.4124564 + g_linear * 0.3575761 + b_linear * 0.1804375 Y = r_linear * 0.2126729 + g_linear * 0.7151522 + b_linear * 0.0721750 Z = r_linear * 0.0193339 + g_linear * 0.1191920 + b_linear * 0.9503041 total = X + Y + Z if total == 0: return 0.3127, 0.3290 # D65 白点 return X / total, Y / total def get_accuracy_color_standards(test_type): """ 获取色准测试的标准 xy 色度坐标(动态计算) Args: test_type: 测试类型 ("sdr_movie" 或 "hdr_movie") Returns: dict: {color_name: (x, y), ...} """ # 注意:原实现对 sdr/hdr 使用同一张色板,这里保持原行为。 del test_type # 参数保留以兼容调用方签名 standards = {} for name, r, g, b in _SDR_COLOR_PATTERNS: standards[name] = _rgb_to_xy_srgb(r, g, b) return standards