告别HDR格式混乱:用Python代码实战HLG与PQ曲线互转(附完整代码)
告别HDR格式混乱用Python代码实战HLG与PQ曲线互转附完整代码在视频处理领域HDR高动态范围技术已经成为提升视觉体验的关键要素。然而HLGHybrid Log-Gamma和PQPerceptual Quantizer这两种主流HDR标准之间的转换问题却让不少开发者头疼。本文将带你深入理解这两种曲线的核心差异并通过可落地的Python代码实现它们之间的相互转换。1. HDR技术基础与核心概念HDR技术的核心在于更宽广的亮度范围和更丰富的色彩表现。要理解HLG与PQ的转换首先需要掌握几个关键术语OETF光电转换函数将场景中的线性光信号转换为非线性电信号EOTF电光转换函数将非线性电信号转换为显示器输出的线性光信号OOTF光光转换函数连接场景光与显示光的映射关系# 常用常量定义 a_2020 0.2627 # BT.2020标准中的RGB转YUV系数 b_2020 0.6780 c_2020 0.0593HLG与PQ的根本区别在于它们的应用场景和设计理念特性HLGPQ设计目标广播电视数字影院/高端显示设备亮度处理相对亮度自适应显示能力绝对亮度固定10000nit标准元数据需求不需要需要典型应用广播电视、流媒体电影制作、专业显示2. 色彩空间转换基础在HDR处理中YUV与RGB色彩空间之间的转换是基础操作。以下是BT.2020标准下的转换实现import numpy as np import cv2 def RGB2YUV_bt2020(R, G, B): BT.2020 RGB转YUV Y a_2020 * R b_2020 * G c_2020 * B Cb (B - Y) / 1.8814 Cr (R - Y) / 1.4747 # 量化到10bit范围 Y (219 * Y 16) * 4 Cb (224 * Cb 128) * 4 Cr (224 * Cr 128) * 4 return np.uint16(Y), np.uint16(Cb), np.uint16(Cr) def YUV2RGB_bt2020(Y, U, V): BT.2020 YUV转RGB Y (Y/4 - 16)/219 Cb (U/4 - 128)/224 Cr (V/4 - 128)/224 R Y 1.4747 * Cr G Y - (0.2627 * 1.4747 / 0.6780) * Cr - (0.0593 * 1.8814 / 0.6780) * Cb B Y 1.8814 * Cb return np.clip(R, 0, 1), np.clip(G, 0, 1), np.clip(B, 0, 1)注意实际工程中需要考虑色度下采样如4:2:0的处理上述代码假设输入已经是正确采样格式。3. PQ曲线核心算法实现PQ曲线由SMPTE ST 2084标准定义其核心是EOTF和逆EOTF的实现# PQ曲线相关常量 m1 2610 / (4096 * 4) m2 2523 * 128 / 4096 c1 3424 / 4096 c2 2413 * 32 / 4096 c3 2392 * 32 / 4096 def pq_EOTF(E_p): PQ电光转换函数从电信号到光信号 E_p np.float32(E_p) E_i np.power(E_p, 1/m2) e_i E_i - c1 E_0 np.maximum(e_i, 0) deno c2 - c3 * E_i Y_i np.power(E_0 / deno, 1/m1) return Y_i def pq_inverse_EOTF(Y): PQ逆电光转换函数从光信号到电信号 Y np.float32(Y) Y_m1 np.power(Y, m1) num c1 c2 * Y_m1 den 1 c3 * Y_m1 E_p np.power(num / den, m2) return E_p4. HLG曲线核心算法实现HLG标准由BBC和NHK联合开发其核心是OETF和逆OETF# HLG相关常量 hlg_a 0.17883277 hlg_b 0.28466892 hlg_c 0.55991073 hlg_tsd 1/12 # 过渡点 def hlg_OETF(E): HLG光电转换函数 E np.float32(E) mask E hlg_tsd E_p np.where(mask, np.sqrt(3 * E), hlg_a * np.log(12 * E - hlg_b) hlg_c) return E_p def hlg_inverse_OETF(E_p): HLG逆光电转换函数 E_p np.float32(E_p) mask E_p 0.5 E np.where(mask, np.power(E_p, 2) / 3, (np.exp((E_p - hlg_c) / hlg_a) hlg_b) / 12) return E5. HLG与PQ互转实战5.1 PQ转HLG完整流程def pq_to_hlg(R_pq, G_pq, B_pq, display_peak1000): 将PQ信号转换为HLG信号 参数 R_pq, G_pq, B_pq: PQ编码的RGB值0-1范围 display_peak: 显示设备峰值亮度单位nit 返回 R_hlg, G_hlg, B_hlg: HLG编码的RGB值 # 步骤1PQ EOTF获取显示线性光 Rd pq_EOTF(R_pq) Gd pq_EOTF(G_pq) Bd pq_EOTF(B_pq) # 步骤2亮度归一化假设母带为1000nit scale display_peak / 1000 Rd np.minimum(Rd * scale, 1.0) Gd np.minimum(Gd * scale, 1.0) Bd np.minimum(Bd * scale, 1.0) # 步骤3计算显示亮度并应用逆OOTF YD 0.2627 * Rd 0.6780 * Gd 0.0593 * Bd gamma 1.2 * np.sqrt(1000 / display_peak) YD_gamma np.power(YD, (1 - gamma) / gamma) Rs Rd * YD_gamma Gs Gd * YD_gamma Bs Bd * YD_gamma # 步骤4应用HLG OETF R_hlg hlg_OETF(Rs) G_hlg hlg_OETF(Gs) B_hlg hlg_OETF(Bs) return R_hlg, G_hlg, B_hlg5.2 HLG转PQ完整流程def hlg_to_pq(R_hlg, G_hlg, B_hlg, display_peak1000): 将HLG信号转换为PQ信号 参数 R_hlg, G_hlg, B_hlg: HLG编码的RGB值0-1范围 display_peak: 目标PQ显示设备峰值亮度单位nit 返回 R_pq, G_pq, B_pq: PQ编码的RGB值 # 步骤1HLG逆OETF获取场景线性光 Rs hlg_inverse_OETF(R_hlg) Gs hlg_inverse_OETF(G_hlg) Bs hlg_inverse_OETF(B_hlg) # 步骤2计算场景亮度并应用OOTF Ys 0.2627 * Rs 0.6780 * Gs 0.0593 * Bs gamma 1.2 * np.sqrt(1000 / display_peak) Ys_gamma np.power(Ys, gamma - 1) Rd Rs * Ys_gamma Gd Gs * Ys_gamma Bd Bs * Ys_gamma # 步骤3亮度反归一化 scale 1000 / display_peak Rd np.minimum(Rd * scale, 1.0) Gd np.minimum(Gd * scale, 1.0) Bd np.minimum(Bd * scale, 1.0) # 步骤4应用PQ逆EOTF R_pq pq_inverse_EOTF(Rd) G_pq pq_inverse_EOTF(Gd) B_pq pq_inverse_EOTF(Bd) return R_pq, G_pq, B_pq6. 工程实践中的常见问题与解决方案在实际项目中实现HDR格式转换时开发者常会遇到以下几个典型问题数值溢出处理PQ曲线的亮度范围理论上可达10000nit但实际显示设备可能只有1000nit解决方案在转换过程中加入clip操作确保数值在合理范围内def safe_pq_eotf(E_p): 带溢出保护的PQ EOTF实现 E_p np.clip(E_p, 0, 1) # 确保输入在0-1范围内 return pq_EOTF(E_p)色彩空间混淆确保所有计算在正确的色彩空间通常是BT.2020中进行避免错误地将BT.709色彩空间用于HDR内容性能优化技巧使用查表法LUT加速曲线计算利用GPU加速矩阵运算def create_pq_lut(): 创建PQ曲线的查找表加速计算 lut np.zeros(1024, dtypenp.float32) for i in range(1024): E_p i / 1023.0 lut[i] pq_EOTF(E_p) return lut pq_lut create_pq_lut() def fast_pq_eotf(E_p): 使用LUT加速的PQ EOTF idx np.uint16(E_p * 1023) return pq_lut[idx]元数据处理PQ内容通常伴随静态元数据如MaxFALL、MaxCLL转换时需要适当处理或生成这些元数据7. 完整工作流示例以下是一个完整的YUV格式HDR视频帧的转换流程def convert_yuv_pq_to_hlg(yuv_frame, width, height): 将YUV420格式的PQ视频帧转换为HLG # 分离YUV分量 Y yuv_frame[0:height, 0:width] U yuv_frame[height:heightheight//2, 0:width//2] V yuv_frame[heightheight//2:, 0:width//2] # YUV转RGB R, G, B YUV2RGB_bt2020(Y, U, V) # PQ转HLG R_hlg, G_hlg, B_hlg pq_to_hlg(R, G, B) # RGB转YUV Y_hlg, U_hlg, V_hlg RGB2YUV_bt2020(R_hlg, G_hlg, B_hlg) # 重新打包YUV帧 hlg_frame np.zeros_like(yuv_frame) hlg_frame[0:height, 0:width] Y_hlg hlg_frame[height:heightheight//2, 0:width//2] U_hlg hlg_frame[heightheight//2:, 0:width//2] V_hlg return hlg_frame提示实际视频处理中建议使用FFmpeg等专业工具处理YUV格式的读取和写入上述代码仅展示核心转换逻辑。在实现完整视频转换工具时还需要考虑以下工程细节帧缓存管理多线程处理内存优化色彩空间转换的精度控制