Files
pqAutomationApp/algorithm/pq_algorithm.py
2026-04-21 15:31:48 +08:00

560 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import math
import numpy as np
from shapely.geometry import Polygon
# ==================
# 色域覆盖率计算公式
# ==================
def calculate_gamut_coverage(points1, points2):
"""
计算色域覆盖率(使用 shapely 库)
Args:
points1: 测量三角形 [[x1, y1], [x2, y2], [x3, y3]]
points2: 参考三角形 [[x1, y1], [x2, y2], [x3, y3]]
Returns:
area: 交集面积
coverage: 覆盖率(%)≤ 100%
"""
try:
# 创建多边形对象
poly1 = Polygon(points1)
poly2 = Polygon(points2)
# 计算交集
intersection = poly1.intersection(poly2)
# 计算面积
intersection_area = intersection.area
reference_area = poly2.area
# 计算覆盖率
coverage = (
(intersection_area / reference_area) * 100 if reference_area > 0 else 0.0
)
return intersection_area, coverage
except Exception as e:
print(f"[Error] 计算色域覆盖率失败: {e}")
return 0.0, 0.0
def calculate_gamut_coverage_DCIP3(points):
"""
计算 CIE 1931 xy 色度空间下的 DCI-P3 覆盖率
Args:
points: [[x1, y1], [x2, y2], [x3, y3]] 测量的 xy 坐标
Returns:
area: 交集面积
coverage: 覆盖率(%
"""
# DCI-P3 标准色域坐标CIE 1931 xy
dcip3_xy = [
[0.680, 0.320], # Red
[0.265, 0.690], # Green
[0.150, 0.060], # Blue
]
area, coverage = calculate_gamut_coverage(points, dcip3_xy)
return area, coverage
def calculate_gamut_coverage_BT709(results):
"""
计算相对于 BT.709 的色域覆盖率
Args:
results: 测量结果列表
[[x1, y1, lv1, X1, Y1, Z1], ...]
或 [[x1, y1], [x2, y2], [x3, y3]]
Returns:
tuple: (intersection_area, coverage_percentage)
"""
# BT.709 标准色域三角形CIE 1931 xy
bt709_triangle = [
[0.6400, 0.3300], # Red
[0.3000, 0.6000], # Green
[0.1500, 0.0600], # Blue
]
# 提取测量的 RGB 三个点的 xy 坐标
if len(results[0]) > 2:
measured_points = [[r[0], r[1]] for r in results[:3]]
else:
measured_points = results[:3]
area, coverage = calculate_gamut_coverage(measured_points, bt709_triangle)
return area, coverage
def calculate_gamut_coverage_BT601(results):
"""
计算相对于 BT.601 的色域覆盖率
Args:
results: 测量结果列表
Returns:
tuple: (intersection_area, coverage_percentage)
"""
# BT.601 标准色域三角形NTSC 版本)
bt601_triangle = [
[0.6300, 0.3400], # Red
[0.3100, 0.5950], # Green
[0.1550, 0.0700], # Blue
]
# 提取测量点
if len(results[0]) > 2:
measured_points = [[r[0], r[1]] for r in results[:3]]
else:
measured_points = results[:3]
area, coverage = calculate_gamut_coverage(measured_points, bt601_triangle)
return area, coverage
def calculate_gamut_coverage_BT2020(results):
"""
计算相对于 BT.2020 的色域覆盖率
Args:
results: 测量结果列表
Returns:
tuple: (intersection_area, coverage_percentage)
"""
# BT.2020 标准色域三角形
bt2020_triangle = [
[0.7080, 0.2920], # Red
[0.1700, 0.7970], # Green
[0.1310, 0.0460], # Blue
]
# 提取测量点
if len(results[0]) > 2:
measured_points = [[r[0], r[1]] for r in results[:3]]
else:
measured_points = results[:3]
area, coverage = calculate_gamut_coverage(measured_points, bt2020_triangle)
return area, coverage
def xy_to_uv_1976(x, y):
"""
CIE 1931 xy → CIE 1976 u'v' 转换(现代标准)
Args:
x: CIE 1931 x 坐标
y: CIE 1931 y 坐标
Returns:
(u', v'): CIE 1976 色度坐标
"""
denom = -2 * x + 12 * y + 3
if abs(denom) < 1e-10:
return (0, 0)
u_prime = (4 * x) / denom
v_prime = (9 * y) / denom
return (u_prime, v_prime)
def calculate_uv_gamut_coverage(uv_coords, reference="DCI-P3"):
"""
计算 CIE 1976 u'v' 色度空间下的色域覆盖率
Args:
uv_coords: [[u1, v1], [u2, v2], [u3, v3]] 测量的 u'v' 坐标
reference: 参考标准 ("DCI-P3", "BT.709", "BT.2020", "BT.601")
Returns:
float: 覆盖率百分比
"""
try:
# ========== 根据参考标准选择 xy 坐标 ==========
if reference == "BT.2020":
ref_xy = [
(0.7080, 0.2920), # Red
(0.1700, 0.7970), # Green
(0.1310, 0.0460), # Blue
]
elif reference == "BT.709":
ref_xy = [
(0.6400, 0.3300), # Red
(0.3000, 0.6000), # Green
(0.1500, 0.0600), # Blue
]
elif reference == "DCI-P3":
ref_xy = [
(0.680, 0.320), # Red
(0.265, 0.690), # Green
(0.150, 0.060), # Blue
]
elif reference == "BT.601":
ref_xy = [
(0.6300, 0.3400), # Red
(0.3100, 0.5950), # Green
(0.1550, 0.0700), # Blue
]
else:
# 默认使用 DCI-P3
ref_xy = [
(0.680, 0.320),
(0.265, 0.690),
(0.150, 0.060),
]
# 转换参考标准为 u'v'
ref_uv = [list(xy_to_uv_1976(x, y)) for x, y in ref_xy]
# 确保测量坐标格式正确
measured_uv = [[float(u), float(v)] for u, v in uv_coords]
# 计算覆盖率
area, coverage = calculate_gamut_coverage(measured_uv, ref_uv)
return coverage
except Exception as e:
print(f"[Error] 计算 u'v' 色域覆盖率失败: {str(e)}")
import traceback
traceback.print_exc()
return 0.0
def calculate_gamut_coverage_DCIP3_uv(uv_points):
"""
计算 CIE 1976 u'v' 色度空间下的 DCI-P3 覆盖率
Args:
uv_points: [[u1, v1], [u2, v2], [u3, v3]] 测量的 u'v' 坐标
Returns:
area: 交集面积
coverage: 覆盖率(%
"""
dcip3_xy = [
(0.680, 0.320), # Red
(0.265, 0.690), # Green
(0.150, 0.060), # Blue
]
dcip3_uv = [list(xy_to_uv_1976(x, y)) for x, y in dcip3_xy]
area, coverage = calculate_gamut_coverage(uv_points, dcip3_uv)
return area, coverage
def calculate_gamut_coverage_BT709_uv(uv_points):
"""
计算 CIE 1976 u'v' 色度空间下的 BT.709 覆盖率
Args:
uv_points: [[u1, v1], [u2, v2], [u3, v3]] 测量的 u'v' 坐标
Returns:
area: 交集面积
coverage: 覆盖率(%
"""
bt709_xy = [
(0.6400, 0.3300), # Red
(0.3000, 0.6000), # Green
(0.1500, 0.0600), # Blue
]
bt709_uv = [list(xy_to_uv_1976(x, y)) for x, y in bt709_xy]
area, coverage = calculate_gamut_coverage(uv_points, bt709_uv)
return area, coverage
def calculate_gamut_coverage_BT2020_uv(uv_points):
"""
计算 CIE 1976 u'v' 色度空间下的 BT.2020 覆盖率
Args:
uv_points: [[u1, v1], [u2, v2], [u3, v3]] 测量的 u'v' 坐标
Returns:
area: 交集面积
coverage: 覆盖率(%
"""
bt2020_xy = [
(0.7080, 0.2920), # Red
(0.1700, 0.7970), # Green
(0.1310, 0.0460), # Blue
]
bt2020_uv = [list(xy_to_uv_1976(x, y)) for x, y in bt2020_xy]
area, coverage = calculate_gamut_coverage(uv_points, bt2020_uv)
return area, coverage
def calculate_gamut_coverage_BT601_uv(uv_points):
"""
计算 CIE 1976 u'v' 色度空间下的 BT.601 覆盖率
Args:
uv_points: [[u1, v1], [u2, v2], [u3, v3]] 测量的 u'v' 坐标
Returns:
area: 交集面积
coverage: 覆盖率(%
"""
bt601_xy = [
(0.6300, 0.3400), # Red
(0.3100, 0.5950), # Green
(0.1550, 0.0700), # Blue
]
bt601_uv = [list(xy_to_uv_1976(x, y)) for x, y in bt601_xy]
area, coverage = calculate_gamut_coverage(uv_points, bt601_uv)
return area, coverage
# ===============
# Gamma 计算公式
# ===============
def calculate_gamma(results, max_index_fix, pattern_params=None):
"""
计算 Gamma 值
Args:
results: [[x, y, lv, X, Y, Z], ...] 测量结果
max_index_fix: 最大灰阶索引
pattern_params: [[R, G, B], ...] 8bit pattern参数用于计算input_level
如果提供,则使用 pattern_value/255 作为底数
如果不提供,则使用传统的 灰阶百分比 作为底数
Returns:
results_with_gamma: [[x, y, lv, gamma], ...]
"""
if not results:
raise ValueError("测量结果为空")
# 提取亮度值
luminance_values = [result[2] for result in results]
if len(luminance_values) < 2:
raise ValueError(f"数据点数量不足: {len(luminance_values)}")
# 使用实际的最大亮度值
max_luminance = max(luminance_values)
min_luminance = min(luminance_values)
if max_luminance <= 0:
raise ValueError(f"最大亮度无效: {max_luminance}")
# 判断数据排列顺序
is_descending = luminance_values[0] > luminance_values[-1]
results_with_gamma = []
valid_gamma_sum = 0.0
valid_gamma_count = 0
for i, result in enumerate(results):
x, y, lv = result[0], result[1], result[2]
# 归一化亮度
L_bar = lv / max_luminance
# 计算输入灰阶值 input_level
if pattern_params is not None and i < len(pattern_params):
# 使用 8bit pattern 数据 / 255 作为底数22293 Gamma 数据对齐)
# 取 R 通道值(灰阶图案 R=G=B
pattern_value = pattern_params[i][0]
input_level = pattern_value / 255.0
else:
# 传统计算方式:根据数据顺序计算输入灰阶值
if is_descending:
# 从亮到暗: i=0 (100%) → i=max (0%)
input_level = 1.0 - (i / max_index_fix) if max_index_fix > 0 else 0
else:
# 从暗到亮: i=0 (0%) → i=max (100%)
input_level = (i / max_index_fix) if max_index_fix > 0 else 0
# 计算 Gamma
gamma = 0.0
# 跳过极端值(接近 0% 和 100%
if input_level <= 0.01 or input_level >= 0.99 or L_bar <= 0.001:
gamma = 0.0
else:
try:
log_L = math.log(L_bar)
log_I = math.log(input_level)
gamma = log_L / log_I
# 合理性检查
if 0.5 <= gamma <= 5.0:
valid_gamma_sum += gamma
valid_gamma_count += 1
else:
gamma = 0.0
except (ValueError, ZeroDivisionError):
gamma = 0.0
results_with_gamma.append(
[round(x, 7), round(y, 7), round(lv, 7), round(gamma, 4)]
)
return results_with_gamma
def calculate_L_bar(results):
"""
计算归一化亮度列表(用于绘制 Gamma 曲线)
Args:
results: 测量结果列表 [[x, y, lv, X, Y, Z], ...]
Returns:
L_bar: 归一化亮度列表 [0.0, 0.1, 0.2, ..., 1.0]
"""
if not results:
return []
# 提取亮度值
luminance_values = [result[2] for result in results]
# 使用实际的最大亮度值
max_luminance = max(luminance_values)
if max_luminance <= 0:
return [0.0] * len(luminance_values)
# 归一化
L_bar = [lv / max_luminance for lv in luminance_values]
return L_bar
# ============
# 色度坐标提取
# ============
def calculate_cct_from_results(results):
"""
从测量结果提取色度坐标xy 坐标)
注意:方法名保持不变(历史原因),实际功能是提取色度坐标
Args:
results: [[x, y, lv, ...], ...] 测量结果列表
Returns:
dict: {
'x': [x1, x2, x3, ...],
'y': [y1, y2, y3, ...],
'lv': [lv1, lv2, lv3, ...]
}
"""
x_values = []
y_values = []
lv_values = []
for result in results:
try:
if isinstance(result, (list, tuple)) and len(result) >= 2:
x = float(result[0])
y = float(result[1])
x_values.append(x)
y_values.append(y)
# 如果有亮度值也保存(可选)
if len(result) >= 3:
lv = float(result[2])
lv_values.append(lv)
else:
# 数据格式错误时的默认值D65 白点)
x_values.append(0.3127)
y_values.append(0.3290)
lv_values.append(0.0)
except Exception as e:
print(f"提取色度坐标失败: {str(e)}")
x_values.append(0.3127)
y_values.append(0.3290)
lv_values.append(0.0)
return {"x": x_values, "y": y_values, "lv": lv_values if lv_values else None}
# ========
# 测试代码
# ========
if __name__ == "__main__":
print("=" * 60)
print("PQ 算法库测试(使用 shapely 库)")
print("=" * 60)
# 测试 1: DCI-P3 覆盖率
print("\n1. 测试 DCI-P3 覆盖率计算")
test_points = [[0.680, 0.320], [0.265, 0.690], [0.150, 0.060]]
area, coverage = calculate_gamut_coverage_DCIP3(test_points)
print(f" DCI-P3 覆盖率: {coverage:.2f}% (期望: 100.00%)")
# 测试 2: BT.709 覆盖率
print("\n2. 测试 BT.709 覆盖率计算(测量色域超出标准)")
test_results_bt709 = [
[0.6637, 0.3236, 41.90],
[0.3087, 0.6250, 160.29],
[0.1435, 0.0447, 12.69],
]
area, coverage = calculate_gamut_coverage_BT709(test_results_bt709)
print(f" BT.709 覆盖率: {coverage:.2f}% (期望: ~98%)")
# 测试 3: BT.2020 覆盖率
print("\n3. 测试 BT.2020 覆盖率计算(测量色域小于标准)")
test_results_bt2020 = [
[0.6662, 0.3216, 39.75],
[0.3096, 0.6282, 155.24],
[0.1440, 0.0452, 11.96],
]
area, coverage = calculate_gamut_coverage_BT2020(test_results_bt2020)
print(f" BT.2020 覆盖率: {coverage:.2f}% (期望: ~60%)")
# 测试 4: 色度坐标提取
print("\n4. 测试色度坐标提取")
test_results = [
[0.3127, 0.3290, 190.5],
[0.3125, 0.3288, 150.2],
[0.3129, 0.3292, 100.8],
]
chromaticity_data = calculate_cct_from_results(test_results)
print(f" x 坐标: {chromaticity_data['x']}")
print(f" y 坐标: {chromaticity_data['y']}")
print(f" 亮度值: {chromaticity_data['lv']}")
# 测试 5: Gamma 计算
print("\n5. 测试 Gamma 计算")
test_gamma_results = [
[0.31, 0.33, 450.0, 0, 0, 0],
[0.31, 0.33, 350.0, 0, 0, 0],
[0.31, 0.33, 250.0, 0, 0, 0],
[0.31, 0.33, 150.0, 0, 0, 0],
[0.31, 0.33, 50.0, 0, 0, 0],
[0.31, 0.33, 0.1, 0, 0, 0],
]
gamma_results = calculate_gamma(test_gamma_results, 5)
for i, result in enumerate(gamma_results):
print(f"{i+1}: Gamma={result[3]:.3f}")
print("\n" + "=" * 60)
print("测试完成!")
print("=" * 60)