资讯动态

树莓派相机自动白平衡详解(四)

发布时间:2026/8/22 10:59:43 来源:尧图企业网站定制
树莓派相机自动白平衡详解一树莓派相机自动白平衡详解二树莓派相机自动白平衡详解三以上说明的树莓派自动白平衡的一些基本思路。但是之前没有找到源码因此有些理解还是存在一些误差。现在根据源码修正并详细说明。目录一、整体架构与调用流二、核心函数逐条解析三、上层后处理四、代码实现五、原版 rpi‑controller vs libipa 移植版关键差异五、数值示例一、整体架构与调用流算法运行于独立异步子线程主线程只负责调度、拷贝结果、帧间 IIR 平滑、硬件输出。IPA主线程 process() ↓ 唤醒异步线程 asyncFunc() ↓ doAwb()AwbBayes子类实现 1. prepareStats() 统计预处理 └ generateStats() · 过滤无效AWB统计网格region · biasProportion每个region叠加biasCT虚拟白点分量无中性色块时兜底防发散 · sensitivityR / sensitivityB 做传感器通道校正 2. 判断有效zone数量如果小于minRegions直接跳过本次计算复用上一帧结果 3. awbBayes() 算法主体 ① zone内部归一化R R/(G1)B B/(G1)得到R/G、B/G色度 ② interpolatePrior()依据当前lux照度对多组yaml配置的prior做线性插值生成当前帧prior(PWLt→−logP(I)) ③ prior整体乘以系数 prior * valid_zone_count / total_zone_count源码注释标注属于经验调参非严格贝叶斯推导 ④ coarseSearch()【阶段1 粗搜索】全部候选严格落在CT‑Curve普朗克轨迹上输出t_coarse ⑤ fineSearch()【阶段2精细搜索】允许白点离开CT‑Curve适配LED、荧光等非黑体光源 ⑥ 换算白平衡增益乘以传感器校正系数存入异步缓存asyncResults_ ↓ fetchAsyncResults() 拷贝子线程结果 · 帧间一阶IIR平滑不属于Bayes算法本体 · gain硬件寄存器范围clamp限幅 · 输出增益给ISPmetadata上报色温二、核心函数逐条解析1. generateStats / prepareStats过滤 regionminPixels单 region 有效像素占比、minGG 通道最小值不满足直接丢弃该 region。bias 兜底不是新增 zone在现有 region 的 R/B 累加虚拟白点分量防止整张图无真实中性色块时算法完全失控。乘以sensitivityR/sensitivityB把传感器原始色度映射到 canonical 标准色度空间。2. computeDelta2Sum 似然代价函数输入候选白平衡增益 gainR、gainB中性点偏移whitepointR/whitepointB通常 0deltaLimit截断保护delta2 min(delta2, deltaLimit)单个网格残差超过阈值直接钳位抑制大片红墙、绿草等高饱和彩色物体干扰总打分。 返回所有 zone 截断后的残差平方累加和delta2Sum。总打分finalLogLikelihood delta2Sum − priorLogLikelihood最小化该值等价最大化后验概率。3. interpolatePrioryaml 配置多组AwbPrior{lux, prior‑PWL}。lux 小于最小配置 lux取最小 lux 对应的 priorlux 大于最大配置 lux取最大 lux 对应的 prior。lux 落在两组 lux 中间对两条 prior 折线做线性插值合成当前帧 prior。awbBayes 内执行经验缩放prior * valid_zone / total_zone。4. coarseSearch 阶段 1CT‑Curve 全局粗搜索在 mode 限定色温区间[ctLo, ctHi]遍历色温t自适应步长t t/10 * coarseStep色温越高步长越大。每个t查表 CT‑Curve 得到曲线上白点计算增益gainR1/rgainB1/b。调用computeDelta2Sum()得到似然代价查表 prior 得到priorLogLikelihood计算finalLogLikelihood。保存所有(t, finalLogLikelihood)选出代价最小采样点取该点左右相邻点调用interpolateQuadatric()三点二次抛物线插值输出粗搜索最优色温t_coarse。所有候选点严格位于 CT‑Curve 上不能横向偏移。5. fineSearch 阶段 2 精细搜索【重点严格对齐原始 patch】原始源码注释Mostly the estimated CT is already good enough, but the fine search allows us to wander transverely off the CT curve.翻译绝大多数场景粗搜索输出的色温已经足够好fineSearch主要设计目标让白点可以离开普朗克 CT‑Curve适配非黑体光源色温发生变化属于循环带来的附带副作用不是设计目标。执行步骤使用输入的粗搜索结果tIn t_coarse取tIn ± nsteps*step两点计算差分得到切线构造transverse向量归一化为单位向量。⚠️该向量仅在tIn位置几何上是 CT‑Curve 法线向量一旦计算完成全程固定不变施加到其他tTest仅做简单矢量加法代码不校验其他位置的几何法线关系。如果向量模平方过小length2() 1e‑6直接 return不做横向偏移输出保持 CT‑Curve 上结果。nsteps变量先用于计算 transverse 向量之后执行nsteps numDeltas扩大外层循环的扫描范围源码注释意图横向方向采样点越多顺带把 tTest 扫描范围放大。外层 i 循环生成一组测试色温tTest tIn i*step每个tTest查表 CT‑Curve得到该色温对应的曲线上基准点查表 prior 得到priorLogLikelihood。内层 j 循环dist 取值区间硬边界[-transverseNeg, transversePos]dist 是固定 transverse 单位向量的缩放系数。⚠️源码完全不计算候选白点到 CT‑Curve 的真实垂直几何距离没有 K・d² 二次距离惩罚项仅限制 dist 缩放系数的取值范围。每个 dist生成候选白点计算 gainR/gainB调用computeDelta2Sum()得到该点总代价。j 循环结束对 j 方向采样点做interpolateQuadatric()二次插值得到该tTest的最优distOpt必须重算插值点完整代价不能复用采样数组内的代价。和全局最优解对比更新bestT、bestR、bestB。i 循环全部结束把全局最优解写回输出参数t、r、b。输出bestT有可能和输入t_coarse不一致这是 i 循环带来的附带副作用不是该函数的设计目标。6. awbBayes 主入口zone 做 R、B 除以 G 归一化interpolatePrior 得到插值后的 prior做经验缩放调用coarseSearch()得到t_coarse调用fineSearch()得到优化后的bestT bestR bestB换算增益6. 结果存入异步线程缓存。三、上层后处理主线程拷贝异步计算结果IIR 一阶帧间平滑抑制帧与帧之间增益跳变gain 做硬件寄存器允许范围的 clamp输出给 ISP 硬件上报 metadata 色温。四、代码实现#include stdio.h #include math.h // 基础工具、结构体 typedef struct { double x; double y; } Point2D; /** * 极简PWL分段折线模拟 * x数组输入(色温K)y数组输出n控制点数量 */ typedef struct { double *x; double *y; int n; } Pwl; /** PWL求值返回插值结果 */ double pwlEval(const Pwl *p, double t) { if (t p-x[0]) return p-y[0]; if (t p-x[p-n-1]) return p-y[p-n-1]; for(int i 0; i p-n-1; i) { double x0 p-x[i]; double x1 p-x[i1]; if(t x0 t x1) { double alpha (t - x0) / (x1 - x0); return p-y[i] * (1.0 - alpha) p-y[i1] * alpha; } } return 0.0; } /** PWL值域钳位 */ double pwlClamp(const Pwl *p, double t) { if(t p-x[0]) return p-x[0]; if(t p-x[p-n-1]) return p-x[p-n-1]; return t; } /** PWL整体乘以一个标量原版prior * scale */ void pwlScale(Pwl *p, double scale) { for(int i 0; i p-n; i) p-y[i] * scale; } /** 三点二次抛物线插值求极小点对应interpolateQuadatric */ double interpolateQuadatric(Point2D a, Point2D b, Point2D c) { const double eps 1e-3; Point2D ca {c.x - a.x, c.y - a.y}; Point2D ba {b.x - a.x, b.y - a.y}; double denom 2.0 * (ba.y * ca.x - ca.y * ba.x); if(fabs(denom) 1e-12) return b.x; double xmin a.x - (ba.x*ba.x*(c.y-a.y) - ca.x*ca.x*(b.y-a.y)) / denom; if(a.y c.y - eps) return a.x; else if(c.y a.y - eps) return c.x; return xmin; } /** 二维向量运算 */ Point2D pointAdd(Point2D a, Point2D b) { Point2D r {a.x b.x, a.y b.y}; return r; } Point2D pointMul(Point2D a, double s) { Point2D r {a.x * s, a.y * s}; return r; } double pointLen2(Point2D p) { return p.x*p.x p.y*p.y; } double pointLen(Point2D p) { return sqrt(pointLen2(p)); } // AWB数据结构 /** 每个统计zoneR/G B/G */ typedef struct { double R; double B; } AwbZone; /** bayes配置对应yaml */ typedef struct { double coarseStep; double transversePos; double transverseNeg; double deltaLimit; double whitepointR; double whitepointB; } AwbBayesConfig; /** * computeDelta2Sum似然代价 * gainR/gainB: 候选增益(GainG1) * zones: 有效统计zone数组 * zoneCnt: 有效zone数量 * cfg: bayes配置 */ double computeDelta2Sum(double gainR, double gainB, const AwbZone *zones, int zoneCnt, double wpR, double wpB, const AwbBayesConfig *cfg) { double sum 0.0; for(int i 0; i zoneCnt; i) { double dr gainR * zones[i].R - 1.0 - wpR; double db gainB * zones[i].B - 1.0 - wpB; double d2 dr*dr db*db; if(d2 cfg-deltaLimit) d2 cfg-deltaLimit; sum d2; } return sum; } /** * coarseSearch 阶段1粗搜索全部候选落在CT‑Curve * ctR: t → R/G; ctB: t → B/G * prior: t → priorLogLikelihood(-logP(I)) * tLo tHi: 色温搜索上下限 * 返回粗搜索最优色温 tCoarse */ double coarseSearch(const Pwl *ctR, const Pwl *ctB, const Pwl *prior, double tLo, double tHi, const AwbZone *zones, int zoneCnt, const AwbBayesConfig *cfg) { Point2D points[256]; int pointCnt 0; int bestIdx 0; double t tLo; while(1) { double r pwlEval(ctR, t); double b pwlEval(ctB, t); double gainR 1.0 / r; double gainB 1.0 / b; double delta2Sum computeDelta2Sum(gainR, gainB, zones, zoneCnt, cfg-whitepointR, cfg-whitepointB, cfg); double tClamp pwlClamp(prior, t); double priorVal pwlEval(prior, tClamp); double finalCost delta2Sum - priorVal; points[pointCnt].x t; points[pointCnt].y finalCost; if(points[pointCnt].y points[bestIdx].y) bestIdx pointCnt; pointCnt; if(fabs(t - tHi) 1e-6) break; /* 自适应步长原版 t t/10 * coarseStep */ double step t / 10.0 * cfg-coarseStep; t step; if(t tHi) t tHi; } double tBest points[bestIdx].x; if(pointCnt 2 bestIdx 1 bestIdx pointCnt-1) { Point2D a points[bestIdx-1]; Point2D b points[bestIdx]; Point2D c points[bestIdx1]; tBest interpolateQuadatric(a,b,c); } return tBest; } /** * fineSearch ————【原版rpi‑controller核心】 * 输入tIn coarseSearch输出tCoarse输入输出tOut rOut bOut * 注意原版源码行为 * 1、transverse向量仅使用tIn附近差分计算**一次全程固定复用** * 2、i循环生成tTest是附带副作用作者注释fineSearch主要目的允许白点离开CT‑Curve不是微调色温 * 3、dist是向量缩放系数**不计算点到曲线垂直几何距离无K*d²惩罚仅硬限制dist区间** * 4、若transverse向量退化直接返回不做横向偏移。 */ void fineSearch(double tIn, double *tOut, double *rOut, double *bOut, const Pwl *ctR, const Pwl *ctB, const Pwl *prior, const AwbZone *zones, int zoneCnt, const AwbBayesConfig *cfg) { int spanR_dummy -1; int spanB_dummy -1; (void)spanR_dummy; (void)spanB_dummy; double step tIn / 10.0 * cfg-coarseStep * 0.1; int nsteps 5; /* 利用输入tIn计算transverse向量仅此一次之后不再重算 */ double tPlus tIn nsteps * step; double tMinus tIn - nsteps * step; double rP pwlEval(ctR, tPlus); double bP pwlEval(ctB, tPlus); double rM pwlEval(ctR, tMinus); double bM pwlEval(ctB, tMinus); double rDiff rP - rM; double bDiff bP - bM; Point2D transverse {bDiff, -rDiff}; if(pointLen2(transverse) 1e-6) { /* 向量退化直接返回不做横向求精 */ *tOut tIn; *rOut pwlEval(ctR, tIn); *bOut pwlEval(ctB, tIn); return; } /* 归一化为单位向量 */ double lenT pointLen(transverse); transverse.x / lenT; transverse.y / lenT; double bestCost 1e20; double bestT tIn; double bestR pwlEval(ctR, tIn); double bestB pwlEval(ctB, tIn); double transverseRange cfg-transverseNeg cfg-transversePos; const int maxNumDeltas 12; int numDeltas (int)(transverseRange * 100.0 0.5) 1; if(numDeltas 3) numDeltas 3; if(numDeltas maxNumDeltas) numDeltas maxNumDeltas; /* 源码nsteps numDeltas横向采样越多tTest扫描范围自动扩大附带副作用 */ nsteps numDeltas; /* 外层i循环生成tTest属于附带实现不是设计主要目标 */ for(int i -nsteps; i nsteps; i) { double tTest tIn i * step; double tTestClamp pwlClamp(prior, tTest); double priorVal pwlEval(prior, tTestClamp); double rCurve pwlEval(ctR, tTest); double bCurve pwlEval(ctB, tTest); Point2D samp[12]; int bestJ 0; /* j内层循环沿【固定transverse向量】做dist缩放采样 */ for(int j 0; j numDeltas; j) { double dist -cfg-transverseNeg (transverseRange * j) / (numDeltas - 1); Point2D curvePt {rCurve, bCurve}; Point2D rbTest pointAdd(curvePt, pointMul(transverse, dist)); double rTest rbTest.x; double bTest rbTest.y; double gainR 1.0 / rTest; double gainB 1.0 / bTest; double delta2Sum computeDelta2Sum(gainR, gainB, zones, zoneCnt, cfg-whitepointR, cfg-whitepointB, cfg); double cost delta2Sum - priorVal; samp[j].x dist; samp[j].y cost; if(samp[j].y samp[bestJ].y) bestJ j; } /* 边界保护保证插值左右点存在 */ bestJ (bestJ 1) ? 1 : bestJ; bestJ (bestJ numDeltas-2) ? (numDeltas-2) : bestJ; /* 抛物线插值求这条j采样线上最优dist */ double distOpt interpolateQuadatric(samp[bestJ-1], samp[bestJ], samp[bestJ1]); /* 钳位dist不能超出配置的hard‑limit */ if(distOpt -cfg-transverseNeg) distOpt -cfg-transverseNeg; if(distOpt cfg-transversePos) distOpt cfg-transversePos; Point2D curvePt {rCurve, bCurve}; Point2D rbOpt pointAdd(curvePt, pointMul(transverse, distOpt)); double rOpt rbOpt.x; double bOpt rbOpt.y; /* 【重要】插值点必须完整重算代价不直接复用samp数组y值 */ double gainROpt 1.0 / rOpt; double gainBOpt 1.0 / bOpt; double deltaSumOpt computeDelta2Sum(gainROpt, gainBOpt, zones, zoneCnt, cfg-whitepointR, cfg-whitepointB, cfg); double costOpt deltaSumOpt - priorVal; /* 更新全局最优 */ if(costOpt bestCost) { bestCost costOpt; bestT tTest; bestR rOpt; bestB bOpt; } } *tOut bestT; *rOut bestR; *bOut bestB; } /** * awbBayes 主计算入口剥离lux插值、bias统计预处理只保留核心搜索 * ctR ctB: CT‑Curve; priorScaled:已经lux插值经验缩放后的prior PWL * tLo tHi: 色温搜索区间 * zones zoneCnt:预处理完成的zone数组 * cfg: bayes配置 * out_gainR out_gainB out_t:输出结果 */ void awbBayesCore(const Pwl *ctR, const Pwl *ctB, const Pwl *priorScaled, double tLo, double tHi, const AwbZone *zones, int zoneCnt, const AwbBayesConfig *cfg, double *out_gainR, double *out_gainB, double *out_t) { /* 阶段1粗搜索 */ double tCoarse coarseSearch(ctR, ctB, priorScaled, tLo, tHi, zones, zoneCnt, cfg); /* 阶段2 fineSearch */ double tFine, rFine, bFine; fineSearch(tCoarse, tFine, rFine, bFine, ctR, ctB, priorScaled, zones, zoneCnt, cfg); *out_gainR 1.0 / rFine; *out_gainB 1.0 / bFine; *out_t tFine; } // 示例main调用演示 int main(void) { /* -------- 模拟CT‑Curve普朗克轨迹极简示例控制点 -------- */ double ctR_x[] {2700.0, 3627.0, 5716.0, 8575.0}; double ctR_y[] {0.90, 0.75, 0.58, 0.42}; double ctB_x[] {2700.0, 3627.0, 5716.0, 8575.0}; double ctB_y[] {0.40, 0.54, 0.70, 0.80}; Pwl ctR {ctR_x, ctR_y, 4}; Pwl ctB {ctB_x, ctB_y, 4}; /* 模拟prior先验PWL示例 */ double priorX[] {2700, 3627, 5716, 8575}; double priorY[] {15.0, 5.0, 5.0, 20.0}; Pwl prior {priorX, priorY, 4}; /* 原版经验缩放prior * (有效zone / total zone)这里示例0.7 */ pwlScale(prior, 0.7); /* 模拟2个有效统计zone */ AwbZone zones[] { {0.59, 0.69}, {0.57, 0.71} }; int zoneCnt sizeof(zones)/sizeof(zones[0]); AwbBayesConfig cfg { .coarseStep 0.2, .transversePos 0.01, .transverseNeg 0.01, .deltaLimit 0.2, .whitepointR 0.0, .whitepointB 0.0 }; double gainR, gainB, tOut; awbBayesCore(ctR, ctB, prior, 2500.0, 10000.0, zones, zoneCnt, cfg, gainR, gainB, tOut); printf( awbBayesCore 输出结果 \n); printf(estimated CCT(K): %.2f K\n, tOut); printf(gainR%.4f gainB%.4f (gainG1.0)\n, gainR, gainB); return 0; }五、原版 rpi‑controller vs libipa 移植版关键差异对比项rpi‑controller patch v5‑1 原版libipa 移植版 AwbBayesfineSearch 设计目标主要目标允许白点跳出 CT‑Curve色温变化是 i 循环附带副作用删除 i 循环t 完全固定等于 coarseSearch 输出不再扫描色温transverse 向量仅t_coarse位置计算一次全程固定复用其他 tTest 只做矢量相加只使用粗搜点的局部法线没有多组 tTestdist/ddist向量缩放系数不计算点‑曲线真实垂直距离无 K・d² 惩罚d真实几何垂直距离代价函数增加 \(K\cdot d^2\)二次惩罚prior 经验缩放prior * valid_zone / total_zone源码注释存疑经验调参删除该 prior 缩放逻辑deltaLimit /bias 机制全部保留全部保留提示网络上很多资料描述的「固定 t、单条法线、\(K\cdot d^2\)距离惩罚」是libipa 移植版本不能套用到这份原始 rpi‑controller 源码。五、数值示例场景条件室内lux400luxyaml 关键配置coarseStep0.2transversePos0.01transverseNeg0.01deltaLimit0.2biasProportion0.05biasCT4500Kauto 模式色温搜索区间2500‑10000KISP 输出 24 个 AWB 网格 region过滤后有效 zone16大于minRegions。prepareStats过滤无效 region每个 region 叠加 biasCT 虚拟白点做sensitivityR/sensitivityB校正。interpolatePriorlux400在两组 lux‑prior 之间插值执行 prior 经验缩放。coarseSearch2500‑10000K 自适应步长遍历 CT‑Curve每个 t 计算似然 先验代价三点插值输出粗搜索最优。fineSearch以t_coarse3720K计算固定 transverse 单位向量i 循环生成一组tTest附带扫描非设计目标每个tTestj 循环沿固定 transverse 向量做 dist 采样生成可跳出 CT‑Curve 的候选白点j 方向插值求最优 dist重算代价在全部候选中选出全局最小代价因 i 循环副作用输出bestT3660K白点色度bestR0.734bestB0.556白点已经离开 CT‑Curve本函数真正设计目标。增益换算gainG1.0。主线程IIR 帧间平滑、硬件限幅输出 ISP 并上报 metadata 色温。⚠️注意bestT3660K偏离 3720K 属于附带副作用原始作者设计意图fineSearch 只用来实现白点偏离普朗克曲线不是用来微调色温。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价