一种色温调整方法

发布时间:2026/7/30 23:12:14
一种色温调整方法 目录一、先搞懂什么是色温物理基础1. 黑体辐射定律2. 图像处理中的核心问题二、算法核心原理色温校正 通道比例校正1. 核心公式三、最难的一步色温K→ RGB 颜色怎么转1. 标准转换流程CIE 国际照明委员会标准2. 代码中的高精度实现3. 关键约束四、代码流程拆解从参数到像素阶段 1参数读取用户设置阶段 2色温转 RGBconvert_k_to_rgb阶段 3计算校正系数preprocess 函数阶段 4逐像素校正process 函数阶段 5GPU 加速cl_process 函数五、直观例子场景白炽灯照片校正为日光反向操作日光转黄昏暖调六、这个算法的专业优势七、核心原理总结八、代码实现一下是基于黑体辐射物理原理 人眼视觉标准CIE的专业色彩校正算法核心是把图像从一个真实光源色温精准映射到另一个目标色温还原 / 改变物体的真实色彩。分物理原理 → 数学转换 → 代码实现 → 像素处理四层用最通俗的方式讲透。一、先搞懂什么是色温物理基础1. 黑体辐射定律色温的本质是普朗克黑体辐射定律 把一个不反光的 “黑体” 加热温度越高发出的光颜色越冷蓝温度越低光颜色越暖红 / 黄。色温值光的颜色常见场景1000K深橙红烛光、火柴3000K暖黄色白炽灯5500K中性白正午日光相机默认白平衡9000K冷蓝色阴天、阴影2. 图像处理中的核心问题相机拍照片时会记录光源的色温白炽灯下拍的照片整体偏黄光源色温低阴天拍的照片整体偏蓝光源色温高色温校正的目的 消除光源色偏让白色物体显示为白色或主动调整色温营造氛围。二、算法核心原理色温校正 通道比例校正这是整个代码的灵魂一句话总结色温只改变 RGB 三个通道的亮度比例不改变图像结构、不做非线性扭曲。1. 核心公式我们有两个关键色温原始色温 T₁照片拍摄时的真实光源色温目标色温 T₂你想要的色温算法步骤把 T₁ 转换成对应的 RGB 值 →R₁, G₁, B₁把 T₂ 转换成对应的 RGB 值 →R₂, G₂, B₂计算校正系数每个通道独立 (系数,系数,系数图像每个像素 原像素 × 对应通道系数 \(新R 原R × 系数R\) \(新G 原G × 系数G\) \(新B 原B × 系数B\)✅关键结论色温校正就是对 RGB 三通道做独立的线性乘法。三、最难的一步色温K→ RGB 颜色怎么转这是convert_k_to_rgb函数的核心也是专业与业余滤镜的区别。1. 标准转换流程CIE 国际照明委员会标准色温不能直接转 RGB必须走标准色彩空间转换链2. 代码中的高精度实现代码没有直接计算复杂的普朗克积分而是用工业界标准的近似公式针对 1000K~12000K 色温用5 次有理函数拟合普朗克轨迹直接输出精准的 CIE xy 坐标误差极小专业图像级精度3. 关键约束色温范围锁定 1000K ~ 12000K超出这个范围黑体辐射的色彩无实际意义也不符合人眼视觉。使用线性 RGB 空间必须去掉伽马校正用线性光计算这是物理正确的前提代码全程处理线性像素。四、代码流程拆解从参数到像素对应color-temperature.c的执行流程完全贴合上面的原理阶段 1参数读取用户设置获取两个核心参数original-temperature原始色温拍摄光源intended-temperature目标色温想要的效果阶段 2色温转 RGBconvert_k_to_rgb输入色温 K → 计算 CIE xy 坐标xy 坐标 → 转换为线性 RGB符合 BT.709/sRGB 标准归一化处理保证 G1 作为基准亮度基准输出结果示例3000K → RGB ≈ (1.0, 0.68, 0.33) 暖黄红多蓝少5500K → RGB ≈ (1.0, 1.0, 1.0) 中性白9000K → RGB ≈ (0.70, 0.82, 1.0) 冷蓝蓝多红少阶段 3计算校正系数preprocess 函数// 核心代码逻辑 rgb_in convert_k_to_rgb(original_temp); // 原色温RGB rgb_out convert_k_to_rgb(intended_temp); // 目标色温RGB // 计算每个通道的乘法系数 correction_r rgb_out.r / rgb_in.r; correction_g rgb_out.g / rgb_in.g; correction_b rgb_out.b / rgb_in.b;这一步只算一次不处理像素效率极高。阶段 4逐像素校正process 函数对图像每个像素做线性乘法最简单、最高效的物理校正// 输入像素线性RGB float r input_pixel[0]; float g input_pixel[1]; float b input_pixel[2]; // 色温校正乘以系数 r * correction_r; g * correction_g; b * correction_b; // 输出结果 output_pixel[0] r; output_pixel[1] g; output_pixel[2] b;✅Alpha 通道透明度完全不变。阶段 5GPU 加速cl_process 函数用 OpenCL 把上面的乘法运算放到 GPU 并行执行处理 8K 大图时速度提升几十倍原理和 CPU 版本完全一样只是执行硬件不同。五、直观例子场景白炽灯照片校正为日光原始色温3000K白炽灯暖黄目标色温5500K日光中性白转换 RGB3000K RGB(1.0, 0.68, 0.33)5500K RGB(1.0, 1.0, 1.0)计算系数R 系数 1.0 / 1.0 1.0G 系数 1.0 / 0.68 ≈ 1.47B 系数 1.0 / 0.33 ≈ 3.0像素处理 红色不变绿色提亮 47%蓝色提亮 200% → 黄偏色直接消除照片恢复白色。反向操作日光转黄昏暖调系数会变成R1G1B1红色增强蓝绿色减弱 → 照片变成暖黄色黄昏效果。六、这个算法的专业优势物理正确基于黑体辐射 CIE 标准不是凭感觉调色是摄影、影视工业标准算法。无损线性纯乘法运算无截断、无非线性失真画质无损失。高性能逐点运算支持 CPU/GPU 加速适合专业软件处理大图。可逆知道原始和目标色温可以完美还原回原始图像。七、核心原理总结本质色温校正 RGB 三通道独立线性缩放基于黑体辐射物理原理关键通过标准公式把色温K精准转换成 RGB再计算通道比例系数流程色温→RGB→算系数→逐像素相乘价值专业级色彩校正消除色偏 / 创意调色是 GIMP、摄影后期的核心功能。这就是color-temperature.c从物理理论到代码实现的全部原理。八、代码实现/* This file is an image processing operation for GEGL * * GEGL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * GEGL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with GEGL; if not, see https://www.gnu.org/licenses/. * * Copyright 2008 Jan Heller jan.heller (at) matfyz.cz */ #include config.h #include glib/gi18n-lib.h #define LOWEST_TEMPERATURE 1000 #define HIGHEST_TEMPERATURE 12000 #ifdef GEGL_PROPERTIES property_double (original_temperature, _(Original temperature), 6500) description(_(Estimated temperature of the light source in Kelvin the image was taken with.)) value_range (LOWEST_TEMPERATURE, HIGHEST_TEMPERATURE) ui_meta (unit, kelvin) property_double (intended_temperature, _(Intended temperature), 6500) description(_(Corrected estimation of the temperature of the light source in Kelvin.)) value_range (LOWEST_TEMPERATURE, HIGHEST_TEMPERATURE) ui_meta (unit, kelvin) #else #define GEGL_OP_POINT_FILTER #define GEGL_OP_NAME color_temperature #define GEGL_OP_C_SOURCE color-temperature.c #include gegl-op.h static const gfloat rgb_r55[3][12]; static void convert_k_to_rgb (gfloat temperature, gfloat *rgb) { gint channel; if (temperature LOWEST_TEMPERATURE) temperature LOWEST_TEMPERATURE; if (temperature HIGHEST_TEMPERATURE) temperature HIGHEST_TEMPERATURE; /* Evaluation of an approximation of the Planckian locus in linear RGB space * by rational functions of degree 5 using Horners scheme * f(x) (p1*x^5 p2*x^4 p3*x^3 p4*x^2 p5*x p6) / * (x^5 q1*x^4 q2*x^3 q3*x^2 q4*x q5) */ for (channel 0; channel 3; channel) { gfloat nomin, denom; gint deg; nomin rgb_r55[channel][0]; for (deg 1; deg 6; deg) nomin nomin * temperature rgb_r55[channel][deg]; denom rgb_r55[channel][6]; for (deg 1; deg 6; deg) denom denom * temperature rgb_r55[channel][6 deg]; rgb[channel] nomin / denom; } } static void finalize (GObject *object) { GeglProperties *o GEGL_PROPERTIES (object); g_clear_pointer (o-user_data, g_free); G_OBJECT_CLASS (gegl_op_parent_class)-finalize (object); } static void notify (GObject *object, GParamSpec *pspec) { if (strcmp (pspec-name, original-temperature) 0 || strcmp (pspec-name, intended-temperature) 0) { GeglProperties *o GEGL_PROPERTIES (object); /* one of the properties has changed, * invalidate the preprocessed coefficients */ g_clear_pointer (o-user_data, g_free); } if (G_OBJECT_CLASS (gegl_op_parent_class)-notify) G_OBJECT_CLASS (gegl_op_parent_class)-notify (object, pspec); } static gfloat * preprocess (GeglProperties *o) { gfloat *coeffs g_new (gfloat, 3); gfloat original_temperature_rgb[3]; gfloat intended_temperature_rgb[3]; convert_k_to_rgb (o-original_temperature, original_temperature_rgb); convert_k_to_rgb (o-intended_temperature, intended_temperature_rgb); coeffs[0] original_temperature_rgb[0] / intended_temperature_rgb[0]; coeffs[1] original_temperature_rgb[1] / intended_temperature_rgb[1]; coeffs[2] original_temperature_rgb[2] / intended_temperature_rgb[2]; return coeffs; } /* GeglOperationPointFilter gives us a linear buffer to operate on * in our requested pixel format */ static gboolean process (GeglOperation *op, void *in_buf, void *out_buf, glong n_pixels, const GeglRectangle *roi, gint level) { GeglProperties *o GEGL_PROPERTIES (op); gfloat *in_pixel in_buf; gfloat *out_pixel out_buf; const gfloat *coeffs o-user_data; in_pixel in_buf; out_pixel out_buf; if (! coeffs) { coeffs o-user_data preprocess (o); } while (n_pixels--) { out_pixel[0] in_pixel[0] * coeffs[0]; out_pixel[1] in_pixel[1] * coeffs[1]; out_pixel[2] in_pixel[2] * coeffs[2]; out_pixel[3] in_pixel[3]; in_pixel 4; out_pixel 4; } return TRUE; } #include opencl/gegl-cl.h #include opencl/color-temperature.cl.h static GeglClRunData *cl_data NULL; /* OpenCL processing function */ static gboolean cl_process (GeglOperation *op, cl_mem in_tex, cl_mem out_tex, size_t global_worksize, const GeglRectangle *roi, int level) { /* Retrieve a pointer to GeglProperties structure which contains all the * chanted properties */ GeglProperties *o GEGL_PROPERTIES (op); const gfloat *coeffs o-user_data; cl_int cl_err 0; if (! coeffs) { coeffs o-user_data preprocess (o); } if (!cl_data) { const char *kernel_name[] {gegl_color_temperature, NULL}; cl_data gegl_cl_compile_and_build (color_temperature_cl_source, kernel_name); } if (!cl_data) return 1; cl_err gegl_clSetKernelArg(cl_data-kernel[0], 0, sizeof(cl_mem), (void*)in_tex); CL_CHECK; cl_err gegl_clSetKernelArg(cl_data-kernel[0], 1, sizeof(cl_mem), (void*)out_tex); CL_CHECK; cl_err gegl_clSetKernelArg(cl_data-kernel[0], 2, sizeof(cl_float), (void*)coeffs[0]); CL_CHECK; cl_err gegl_clSetKernelArg(cl_data-kernel[0], 3, sizeof(cl_float), (void*)coeffs[1]); CL_CHECK; cl_err gegl_clSetKernelArg(cl_data-kernel[0], 4, sizeof(cl_float), (void*)coeffs[2]); CL_CHECK; cl_err gegl_clEnqueueNDRangeKernel(gegl_cl_get_command_queue (), cl_data-kernel[0], 1, NULL, global_worksize, NULL, 0, NULL, NULL); CL_CHECK; return FALSE; error: return TRUE; } static void gegl_op_class_init (GeglOpClass *klass) { GObjectClass *object_class; GeglOperationClass *operation_class; GeglOperationPointFilterClass *point_filter_class; gchar *composition ?xml version1.0 encodingUTF-8? gegl node operationgegl:crop width200 height200/ node operationgegl:over node operationgegl:color-temperature params param nameintended-temperature12000/param /params /node node operationgegl:load pathstandard-input.png/ /node node operationgegl:checkerboard params param namecolor1rgb(0.25,0.25,0.25)/param param namecolor2rgb(0.75,0.75,0.75)/param /params /node /gegl; object_class G_OBJECT_CLASS (klass); operation_class GEGL_OPERATION_CLASS (klass); point_filter_class GEGL_OPERATION_POINT_FILTER_CLASS (klass); object_class-finalize finalize; object_class-notify notify; point_filter_class-process process; point_filter_class-cl_process cl_process; operation_class-opencl_support TRUE; gegl_operation_class_set_keys (operation_class, name, gegl:color-temperature, title, _(Color Temperature), categories, color, reference-hash, 0a5ec345755968efc091b084587de7cb, description, _(Change the color temperature of the image, from an assumed original color temperature to an intended one.), reference-composition, composition, NULL); } /* Coefficients of rational functions of degree 5 fitted per color channel to * the linear RGB coordinates of the range 1000K-12000K of the Planckian locus * with the 20K step. Original CIE-xy data from * * http://www.aim-dtp.net/aim/technology/cie_xyz/k2xy.txt * * converted to the linear RGB space assuming the ITU-R BT.709-5/sRGB primaries */ static const gfloat rgb_r55[3][12] { { 6.9389923563552169e-01, 2.7719388100974670e03, 2.0999316761104289e07, -4.8889434162208414e09, -1.1899785506796783e07, -4.7418427686099203e04, 1.0000000000000000e00, 3.5434394338546258e03, -5.6159353379127791e05, 2.7369467137870544e08, 1.6295814912940913e08, 4.3975072422421846e05 }, { 9.5417426141210926e-01, 2.2041043287098860e03, -3.0142332673634286e06, -3.5111986367681120e03, -5.7030969525354260e00, 6.1810926909962016e-01, 1.0000000000000000e00, 1.3728609973644000e03, 1.3099184987576159e06, -2.1757404458816318e03, -2.3892456292510311e00, 8.1079012401293249e-01 }, { -7.1151622540856201e10, 3.3728185802339764e16, -7.9396187338868539e19, 2.9699115135330123e22, -9.7520399221734228e22, -2.9250107732225114e20, 1.0000000000000000e00, 1.3888666482167408e16, 2.3899765140914549e19, 1.4583606312383295e23, 1.9766018324502894e22, 2.9395068478016189e18 } }; #endif