目录

一、概述

二、头文件 exposure_mode_helper.h

三、实现文件 exposure_mode_helper.cpp 逐段解析

四、算法完整流程文字版

五、代码实现:


一、概述

  ExposureModeHelper 属于 libcamera IPA 模块,是给 AEGC(自动曝光自动增益控制)使用的曝光拆分工具类。 核心职责:给定目标总曝光量,将其拆分为:硬件曝光时间、硬件模拟增益、量化补偿增益、ISP 数字增益四部分输出。

    普通 AE 策略通常:先把曝光时间拉到最大,再增加增益。 该类支持多阶段 (stage) 策略:每个阶段配置一对(阶段最大曝光时间,阶段总增益上限)。 业务价值:可以主动限制曝光时长(避免运动场景拖影、保障帧率),提前启用增益;当传入空 stages,自动回退传统「优先曝光时间,后增益」策略。

重要定义:

  1. stage 中的 gain 是逻辑总增益上限(模拟增益 + 数字增益合计),不是直接的模拟增益;会被硬件模拟增益上限maxGain_钳位,超出部分落到数字增益。
  2. quantizationGain:硬件寄存器量化带来的补偿系数,不是硬件可配置参数,是软件算法补偿因子,和数字增益相互独立,要同时应用两者才能精准复现目标曝光。
  3. lineLength(入参,类型 Duration)= lineDuration_成员:sensor 一行的时间(行周期),曝光时间必须是该值整数倍,不是像素计数。

二、头文件 exposure_mode_helper.h

#pragma once
#include <tuple>
#include <utility>
#include <vector>
#include <libcamera/base/span.h>
#include <libcamera/base/utils.h>
#include "camera_sensor_helper.h"
namespace libcamera {
namespace ipa {

class ExposureModeHelper
{
public:
	/// \param stages 阶段数组,每一项(阶段最大曝光时间,阶段总增益上限)
	ExposureModeHelper(const Span<std::pair<utils::Duration, double>> stages);
	~ExposureModeHelper() = default;

	/// 配置sensor行周期与sensor辅助对象,用于硬件量化
	/// \param lineLength sensor行周期(时间维度)
	void configure(utils::Duration lineLength, const CameraSensorHelper *sensorHelper);

	/// 设置硬件运行时约束;**每次硬件限制变化必须调用;splitExposure调用前必须执行**
	void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime,
		       double minGain, double maxGain);

	/// 核心接口:输入目标总曝光量,返回四元组
	/// 返回:(实际下发sensor曝光时间,模拟增益,量化补偿增益,数字增益)
	std::tuple<utils::Duration, double, double, double>
	splitExposure(utils::Duration exposure) const;

	// 获取已经配置的硬件限制
	utils::Duration minExposureTime() const { return minExposureTime_; }
	utils::Duration maxExposureTime() const { return maxExposureTime_; }
	double minGain() const { return minGain_; }
	double maxGain() const { return maxGain_; }

private:
	/// 私有工具:曝光时间钳位 + 对齐行周期量化
	utils::Duration clampExposureTime(utils::Duration exposureTime,
					  double *quantizationGain = nullptr) const;
	/// 私有工具:增益钳位 + sensor增益档位量化
	double clampGain(double gain, double *quantizationGain = nullptr) const;

	std::vector<utils::Duration> exposureTimes_; ///< 各阶段的最大曝光时间
	std::vector<double> gains_;                   ///< 各阶段逻辑总增益上限

	utils::Duration lineDuration_;        ///< sensor行周期(来自configure)
	utils::Duration minExposureTime_;     ///< 硬件最小曝光
	utils::Duration maxExposureTime_;     ///< 硬件最大曝光
	double minGain_;                      ///< 硬件最小模拟增益
	double maxGain_;                      ///< 硬件最大模拟增益
	const CameraSensorHelper *sensorHelper_; ///< sensor辅助对象,外部保证生命周期有效
};
} /* namespace ipa */
} /* namespace libcamera */

三、实现文件 exposure_mode_helper.cpp 逐段解析

构造函数

ExposureModeHelper::ExposureModeHelper(const Span<std::pair<utils::Duration, double>> stages)
	: lineDuration_(1us), minExposureTime_(0us), maxExposureTime_(0us),
	  minGain_(0), maxGain_(0), sensorHelper_(nullptr)
{
	for (const auto &[s, g] : stages) {
		exposureTimes_.push_back(s);
		gains_.push_back(g);
	}
}

将传入的 stage 数组拆分为两个并行 vector 保存。

注意:此时不做硬件钳位;钳位、量化发生在splitExposure运行时。

configure()

void ExposureModeHelper::configure(utils::Duration lineDuration,
				   const CameraSensorHelper *sensorHelper)
{
	lineDuration_ = lineDuration;
	sensorHelper_ = sensorHelper;
}

设置 sensor 行周期、sensorHelper 裸指针。

  • 若不调用:默认曝光单位为微秒,增益不做硬件量化。
  • ⚠️只保存指针,不管理对象生命周期,调用方必须保证 sensorHelper 在使用期间有效。

setLimits()

void ExposureModeHelper::setLimits(utils::Duration minExposureTime,
				   utils::Duration maxExposureTime,
				   double minGain, double maxGain)
{
	minExposureTime_ = minExposureTime;
	maxExposureTime_ = maxExposureTime;
	minGain_ = minGain;
	maxGain_ = maxGain;
}

设置硬件运行边界。

  • 固定曝光:minExposureTime == maxExposureTime
  • 固定模拟增益:minGain == maxGain
  • 未调用该函数时maxExposureTime_/maxGain_为 0,调用splitExposure会触发ASSERT崩溃。

clampExposureTime 曝光时间钳位与量化

utils::Duration ExposureModeHelper::clampExposureTime(utils::Duration exposureTime,
						      double *quantizationGain) const
{
	utils::Duration clamped;
	utils::Duration exp;
	// 第一步:钳位到硬件最大最小曝光
	clamped = std::clamp(exposureTime, minExposureTime_, maxExposureTime_);
	// 第二步:对齐行周期;时长除以行周期取long(向零截断,正数等价向下取整)
	exp = static_cast<long>(clamped / lineDuration_) * lineDuration_;
	// 量化补偿系数 = 理想值 / 硬件实际可设置值;exp ≤ clamped → quantGain ≥ 1
	if (quantizationGain)
		*quantizationGain = clamped / exp;
	return exp;
}

示例:理想曝光 105us,行周期 10us → exp=100us,quantizationGain = 105/100 = 1.05。

clampGain 模拟增益钳位与量化

double ExposureModeHelper::clampGain(double gain, double *quantizationGain) const
{
	// 钳位到硬件模拟增益上下限
	double clamped = std::clamp(gain, minGain_, maxGain_);
	if (sensorHelper_)
		// sensorHelper完成离散增益档位量化,输出量化补偿
		return sensorHelper_->quantizeGain(clamped, quantizationGain);
	if (quantizationGain)
		*quantizationGain = 1.0; // 无量化损失,补偿系数为1
	return clamped;
}

stage 传入的总增益上限,会经过本函数被硬件模拟增益上限截断,超出部分无法由模拟增益实现,最后落到 digitalGain。

核心函数 splitExposure

输入:exposure:目标总曝光量 返回 tuple:(exposureTime, analogue_gain, quantization_gain, digital_gain)

严格正确总曝光等式

exposure_{target} = exposureTime_{hw} \times gain_{ana_hw} \times quantGain \times gain_{digital}

  • exposureTime_hw:下发 sensor 的曝光时间,对齐行周期
  • gain_ana_hw:下发 sensor 的模拟增益(量化后档位)
  • quantGain:曝光 + 模拟增益带来的总量化补偿
  • gain_digital:ISP 配置的数字增益
std::tuple<utils::Duration, double, double, double>
ExposureModeHelper::splitExposure(utils::Duration exposure) const
{
	ASSERT(maxExposureTime_);
	ASSERT(maxGain_);
	utils::Duration exposureTime;
	double gain;
	double quantGain;
	double quantGain2;

	bool gainFixed = minGain_ == maxGain_;
	bool exposureTimeFixed = minExposureTime_ == maxExposureTime_;

	// 分支1:硬件层面曝光时间和增益全部锁死,不可调节
	if (exposureTimeFixed && gainFixed) {
		exposureTime = clampExposureTime(minExposureTime_, &quantGain);
		gain = clampGain(minGain_, &quantGain2);
		quantGain *= quantGain2;
		return { exposureTime, gain, quantGain,
			 exposure / (exposureTime * gain * quantGain) };
	}

	double stageGain = clampGain(1.0);
	double lastStageGain = stageGain; // 上一阶段经过钳位后的总增益上限,初始为1.0

	// 分支2:遍历所有stage做分阶段曝光拆分
	for (unsigned int stage = 0; stage < gains_.size(); stage++) {
		utils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage],
								      &quantGain);
		stageGain = clampGain(gains_[stage]);

		// a:维持上一阶段增益上限lastStageGain不提升;曝光最多拉到本阶段stageExposureTime
		// 条件成立:仅靠【不提升增益 + 曝光最高到本阶段上限】就可以满足目标曝光
		if (stageExposureTime * lastStageGain >= exposure) {
			exposureTime = clampExposureTime(exposure / lastStageGain, &quantGain);
			gain = clampGain(exposure / exposureTime, &quantGain2);
			quantGain *= quantGain2;
			return { exposureTime, gain, quantGain,
				 exposure / (exposureTime * gain * quantGain) };
		}

		// b:a条件不成立;允许增益提升到本阶段stageGain,曝光钉死本阶段最大曝光
		// 条件成立:本阶段最大曝光 + 本阶段最大增益,可满足目标曝光
		if (stageExposureTime * stageGain >= exposure) {
			exposureTime = stageExposureTime;
			gain = clampGain(exposure / exposureTime, &quantGain2);
			quantGain *= quantGain2;
			return { exposureTime, gain, quantGain,
				 exposure / (exposureTime * gain * quantGain) };
		}

		// c:a、b都不成立:本stage即使曝光、增益全部拉满,依旧达不到目标曝光
		// 更新上阶段增益,进入下一轮stage
		lastStageGain = stageGain;
	}

	// 分支3:全部stage遍历完毕,进入兜底逻辑
	// stageGain为最后一个stage经过钳位后的增益上限;stages为空时,for不执行,stageGain=1.0
	exposureTime = clampExposureTime(exposure / stageGain, &quantGain);
	gain = clampGain(exposure / exposureTime, &quantGain2);
	quantGain *= quantGain2;
	return { exposureTime, gain, quantGain,
		 exposure / (exposureTime * gain * quantGain) };
}

四、算法完整流程文字版

  1. 前置断言:maxExposureTime_、maxGain_必须非 0;
  2. 如果硬件曝光、模拟增益同时被锁死,直接计算硬件可设置值,缺口全部由数字增益补齐返回;
  3. 初始化lastStageGain = clampGain(1.0);遍历每一个 stage:
    1. 将当前 stage 配置的最大曝光、最大总增益做硬件钳位、量化,得到stageExposureTime、stageGain;
    2. 判断stageExposureTime * lastStageGain >= exposure:
      • ✅成立:不提升增益,维持上阶段增益上限;反求需要的曝光时间,硬件钳位量化,微调增益,直接返回结果。

      关键点:最终曝光不需要等于 stageExposureTime,反求得到更小的曝光时间。

    3. 上一步不成立,判断stageExposureTime * stageGain >= exposure:
      • ✅成立:曝光固定为本阶段最大曝光stageExposureTime;允许增益提升到本阶段上限,反求增益,返回结果。
    4. 两个条件均不成立:本 stage 能力不足以满足目标曝光,更新lastStageGain = stageGain,进入下一 stage;
  4. 所有 stage 执行完毕,执行兜底:
    • 使用最后 stage 的增益上限stageGain做基准,计算曝光时间与增益;
    • 特殊情况:stages 为空,for 循环不执行,stageGain=1.0,此时行为等价传统 AE:优先最大化曝光时间,之后增加模拟增益,剩余缺口交给数字增益。
  5. 返回四元组。

五、代码实现:

exposure_mode_helper.h实现:

/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
 *
 * Helper class that performs computations relating to exposure
 */

#pragma once

#include <tuple>
#include <utility>
#include <vector>

#include <libcamera/base/span.h>
#include <libcamera/base/utils.h>

#include "camera_sensor_helper.h"

namespace libcamera {

namespace ipa {

class ExposureModeHelper
{
public:
	ExposureModeHelper(const Span<std::pair<utils::Duration, double>> stages);
	~ExposureModeHelper() = default;

	void configure(utils::Duration lineLength, const CameraSensorHelper *sensorHelper);
	void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime,
		       double minGain, double maxGain);

	std::tuple<utils::Duration, double, double, double>
	splitExposure(utils::Duration exposure) const;

	utils::Duration minExposureTime() const { return minExposureTime_; }
	utils::Duration maxExposureTime() const { return maxExposureTime_; }
	double minGain() const { return minGain_; }
	double maxGain() const { return maxGain_; }

private:
	utils::Duration clampExposureTime(utils::Duration exposureTime,
					  double *quantizationGain = nullptr) const;
	double clampGain(double gain, double *quantizationGain = nullptr) const;

	std::vector<utils::Duration> exposureTimes_;
	std::vector<double> gains_;

	utils::Duration lineDuration_;
	utils::Duration minExposureTime_;
	utils::Duration maxExposureTime_;
	double minGain_;
	double maxGain_;
	const CameraSensorHelper *sensorHelper_;
};

} /* namespace ipa */

} /* namespace libcamera */

exposure_mode_helper.c实现:

/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
 *
 * Helper class that performs computations relating to exposure
 */
#include "exposure_mode_helper.h"

#include <algorithm>

#include <libcamera/base/log.h>

/**
 * \file exposure_mode_helper.h
 * \brief Helper class that performs computations relating to exposure
 *
 * AEGC algorithms have a need to split exposure between exposure time, analogue
 * and digital gain. Multiple implementations do so based on paired stages of
 * exposure time and gain limits; provide a helper to avoid duplicating the code.
 */

namespace libcamera {

using namespace std::literals::chrono_literals;

LOG_DEFINE_CATEGORY(ExposureModeHelper)

namespace ipa {

/**
 * \class ExposureModeHelper
 * \brief Class for splitting exposure into exposure time and total gain
 *
 * The ExposureModeHelper class provides a standard interface through which an
 * AEGC algorithm can divide exposure between exposure time and gain. It is
 * configured with a set of exposure time and gain pairs and works by initially
 * fixing gain at 1.0 and increasing exposure time up to the exposure time value
 * from the first pair in the set in an attempt to meet the required exposure
 * value.
 *
 * If the required exposure is not achievable by the first exposure time value
 * alone it ramps gain up to the value from the first pair in the set. If the
 * required exposure is still not met it then allows exposure time to ramp up to
 * the exposure time value from the second pair in the set, and continues in this
 * vein until either the required exposure time is met, or else the hardware's
 * exposure time or gain limits are reached.
 *
 * This method allows users to strike a balance between a well-exposed image and
 * an acceptable frame-rate, as opposed to simply maximising exposure time
 * followed by gain. The same helpers can be used to perform the latter
 * operation if needed by passing an empty set of pairs to the initialisation
 * function.
 *
 * The gain values may exceed a camera sensor's analogue gain limits if either
 * it or the IPA is also capable of digital gain. The configure() function must
 * be called with the hardware's limits to inform the helper of those
 * constraints. Any gain that is needed will be applied as analogue gain first
 * until the hardware's limit is reached, following which digital gain will be
 * used.
 */

/**
 * \brief Construct an ExposureModeHelper instance
 * \param[in] stages The vector of paired exposure time and gain limits
 *
 * The input stages are exposure time and _total_ gain pairs; the gain
 * encompasses both analogue and digital gain.
 *
 * The vector of stages may be empty. In that case, the helper will simply use
 * the runtime limits set through setLimits() instead.
 */
ExposureModeHelper::ExposureModeHelper(const Span<std::pair<utils::Duration, double>> stages)
	: lineDuration_(1us), minExposureTime_(0us), maxExposureTime_(0us),
	  minGain_(0), maxGain_(0), sensorHelper_(nullptr)
{
	for (const auto &[s, g] : stages) {
		exposureTimes_.push_back(s);
		gains_.push_back(g);
	}
}

/**
 * \brief Configure sensor details
 * \param[in] lineDuration The current line length of the sensor
 * \param[in] sensorHelper The sensor helper
 *
 * This function sets the line length and sensor helper. These are used in
 * splitExposure() to take the quantization of the exposure and gain into
 * account.
 *
 * When this has not been called, it is assumed that exposure is in micro second
 * granularity and gain has no quantization at all.
 *
 * ExposureModeHelper keeps a pointer to the CameraSensorHelper, so the caller
 * has to ensure that sensorHelper is valid until the next call to configure().
 */
void ExposureModeHelper::configure(utils::Duration lineDuration,
				   const CameraSensorHelper *sensorHelper)
{
	lineDuration_ = lineDuration;
	sensorHelper_ = sensorHelper;
}

/**
 * \brief Set the exposure time and gain limits
 * \param[in] minExposureTime The minimum exposure time supported
 * \param[in] maxExposureTime The maximum exposure time supported
 * \param[in] minGain The minimum analogue gain supported
 * \param[in] maxGain The maximum analogue gain supported
 *
 * This function configures the exposure time and analogue gain limits that need
 * to be adhered to as the helper divides up exposure. Note that this function
 * *must* be called whenever those limits change and before splitExposure() is
 * used.
 *
 * If the algorithm using the helpers needs to indicate that either exposure time
 * or analogue gain or both should be fixed it can do so by setting both the
 * minima and maxima to the same value.
 */
void ExposureModeHelper::setLimits(utils::Duration minExposureTime,
				   utils::Duration maxExposureTime,
				   double minGain, double maxGain)
{
	minExposureTime_ = minExposureTime;
	maxExposureTime_ = maxExposureTime;
	minGain_ = minGain;
	maxGain_ = maxGain;
}

utils::Duration ExposureModeHelper::clampExposureTime(utils::Duration exposureTime,
						      double *quantizationGain) const
{
	utils::Duration clamped;
	utils::Duration exp;

	clamped = std::clamp(exposureTime, minExposureTime_, maxExposureTime_);
	exp = static_cast<long>(clamped / lineDuration_) * lineDuration_;
	if (quantizationGain)
		*quantizationGain = clamped / exp;

	return exp;
}

double ExposureModeHelper::clampGain(double gain, double *quantizationGain) const
{
	double clamped = std::clamp(gain, minGain_, maxGain_);
	if (sensorHelper_)
		return sensorHelper_->quantizeGain(clamped, quantizationGain);

	if (quantizationGain)
		*quantizationGain = 1.0;

	return clamped;
}

/**
 * \brief Split exposure into exposure time and gain
 * \param[in] exposure Exposure value
 *
 * This function divides a given exposure into exposure time, analogue and
 * digital gain by iterating through stages of exposure time and gain limits.
 * At each stage the current stage's exposure time limit is multiplied by the
 * previous stage's gain limit (or 1.0 initially) to see if the combination of
 * the two can meet the required exposure. If they cannot then the current
 * stage's exposure time limit is multiplied by the same stage's gain limit to
 * see if that combination can meet the required exposure time. If they cannot
 * then the function moves to consider the next stage.
 *
 * When a combination of exposure time and gain _stage_ limits are found that
 * are sufficient to meet the required exposure, the function attempts to reduce
 * exposure time as much as possible whilst fixing gain and still meeting the
 * exposure. If a _runtime_ limit prevents exposure time from being lowered
 * enough to meet the exposure with gain fixed at the stage limit, gain is also
 * lowered to compensate.
 *
 * Once the exposure time and gain values are ascertained, gain is assigned as
 * analogue gain as much as possible, with digital gain only in use if the
 * maximum analogue gain runtime limit is unable to accommodate the exposure
 * value.
 *
 * If no combination of exposure time and gain limits is found that meets the
 * required exposure, the helper falls-back to simply maximising the exposure
 * time first, followed by analogue gain, followed by digital gain.
 *
 * During the calculations the gain missed due to quantization is recorded and
 * returned as quantization gain. The quantization gain is not included in the
 * digital gain. So to exactly apply the given exposure, both quantization gain
 * and digital gain must be applied.
 *
 * \return Tuple of exposure time, analogue gain, quantization gain and digital
 * gain
 */
std::tuple<utils::Duration, double, double, double>
ExposureModeHelper::splitExposure(utils::Duration exposure) const
{
	ASSERT(maxExposureTime_);
	ASSERT(maxGain_);

	utils::Duration exposureTime;
	double gain;
	double quantGain;
	double quantGain2;
	bool gainFixed = minGain_ == maxGain_;
	bool exposureTimeFixed = minExposureTime_ == maxExposureTime_;

	/*
	 * There's no point entering the loop if we cannot change either gain
	 * nor exposure time anyway.
	 */
	if (exposureTimeFixed && gainFixed) {
		exposureTime = clampExposureTime(minExposureTime_, &quantGain);
		gain = clampGain(minGain_, &quantGain2);
		quantGain *= quantGain2;

		return { exposureTime, gain, quantGain,
			 exposure / (exposureTime * gain * quantGain) };
	}

	double stageGain = clampGain(1.0);
	double lastStageGain = stageGain;

	for (unsigned int stage = 0; stage < gains_.size(); stage++) {
		utils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage],
								      &quantGain);
		stageGain = clampGain(gains_[stage]);

		/*
		 * We perform the clamping on both exposure time and gain in
		 * case the helper has had limits set that prevent those values
		 * being lowered beyond a certain minimum...this can happen at
		 * runtime for various reasons and so would not be known when
		 * the stage limits are initialised.
		 */

		/* Clamp the gain to lastStageGain and regulate exposureTime. */
		if (stageExposureTime * lastStageGain >= exposure) {
			exposureTime = clampExposureTime(exposure / lastStageGain, &quantGain);
			gain = clampGain(exposure / exposureTime, &quantGain2);
			quantGain *= quantGain2;

			return { exposureTime, gain, quantGain,
				 exposure / (exposureTime * gain * quantGain) };
		}

		/* Clamp the exposureTime to stageExposureTime and regulate gain. */
		if (stageExposureTime * stageGain >= exposure) {
			exposureTime = stageExposureTime;
			gain = clampGain(exposure / exposureTime, &quantGain2);
			quantGain *= quantGain2;

			return { exposureTime, gain, quantGain,
				 exposure / (exposureTime * gain * quantGain) };
		}

		lastStageGain = stageGain;
	}

	/*
	 * From here on all we can do is max out the exposure time, followed by
	 * the analogue gain. If we still haven't achieved the target we send
	 * the rest of the exposure time to digital gain. If we were given no
	 * stages to use then the default stageGain of 1.0 is used so that
	 * exposure time is maxed before gain is touched at all.
	 */
	exposureTime = clampExposureTime(exposure / stageGain, &quantGain);
	gain = clampGain(exposure / exposureTime, &quantGain2);
	quantGain *= quantGain2;

	return { exposureTime, gain, quantGain,
		 exposure / (exposureTime * gain * quantGain) };
}

/**
 * \fn ExposureModeHelper::minExposureTime()
 * \brief Retrieve the configured minimum exposure time limit set through
 * setLimits()
 * \return The minExposureTime_ value
 */

/**
 * \fn ExposureModeHelper::maxExposureTime()
 * \brief Retrieve the configured maximum exposure time set through setLimits()
 * \return The maxExposureTime_ value
 */

/**
 * \fn ExposureModeHelper::minGain()
 * \brief Retrieve the configured minimum gain set through setLimits()
 * \return The minGain_ value
 */

/**
 * \fn ExposureModeHelper::maxGain()
 * \brief Retrieve the configured maximum gain set through setLimits()
 * \return The maxGain_ value
 */

} /* namespace ipa */

} /* namespace libcamera */

更多推荐