# 目标测光曝光控制模块 独立 C++17 静态库,依赖 OpenCV `core`、`imgproc`。可独立计算原始图像的全局/ROI 亮度;也可通过控制实例接收采集时间及实际曝光,返回下一帧曝光建议。支持源码子目录引用和安装后的 CMake 包引用。 模块负责测光、时间窗口历史、3/5 点预测、比例/PI/死区控制及 CAPTURE/TRACK/LOST 状态管理。识别、采图、相机 SDK、机器人、三维处理、自动增益和业务节拍均由调用方负责。 ## 接入方式 源码引用: ```cmake add_subdirectory(path/to/TargetExposureControl) target_link_libraries(your_app PRIVATE TargetExposureControl::Control) ``` 安装包引用: ```cmake find_package(TargetExposureControl 1 CONFIG REQUIRED) target_link_libraries(your_app PRIVATE TargetExposureControl::Control) ``` 配置 CMake 时设置 `OpenCV_DIR`,安装方式还需将安装前缀加入 `CMAKE_PREFIX_PATH`。安装包要求使用构建时相同版本的 OpenCV;使用其他版本时重新构建本库。编译器、架构、运行库和 Debug/Release 也应与调用工程一致。OpenCV 的运行时 DLL 由使用方部署。本库没有相机 SDK 或 Qt 依赖。 每路相机/图像流使用一个 `target_exposure::ExposureControl` 实例。不同实例状态隔离,同一实例由调用方串行调用。所有曝光单位为 **微秒 us**,所有时间戳也使用同一时钟域下的非负微秒;两者语义不同,不可相互替代。 ## 独立离线测光(1.1.0) 只计算保存图像时,直接调用 `measureImage`,不创建控制实例、不设置曝光或时间,也不调用 `preCapture`。以下变量由调用方提供: ```cpp target_exposure::Roi roi; roi.rectangles = final_metering_rects; // 可换成旋转框或实际分割掩膜 const auto measurement = target_exposure::measureImage(original_image, roi, roi_is_usable); // measurement.global.arithmetic_gray:全局普通平均灰度。 // measurement.target.arithmetic_gray:ROI 普通平均灰度。 // measurement.target.gray:ROI 截尾均值,保留既有控制算法的统计口径。 ``` 第四个参数为可选的 `MeteringConfig`,默认整图测光、目标区域两端各截尾 5%、至少 16 个原始像素和 8 个保留像素。只计算全局时传空 `Roi` 和 `false`。`roi_is_usable` 只表示存在可测光的区域;手绘 ROI 有效不代表工件已到位或已识别。 每个区域使用一次像素直方图,同时输出普通均值、截尾均值、原始/保留像素数和截尾前的近黑/饱和比例。目标样本不足时两个均值均为空,不能用零替代;全局不受目标区域无效影响。配置非法时两个区域均返回 `MeterStatus::InvalidConfiguration`;图像、ROI 错误由对应状态报告。无有效区域时不检查闲置 ROI 内容,全局照常计算。 这是无状态函数,同一输入可重复计算,结果不受曝光控制实例的历史影响;调用期间原图和 mask 均不被修改或保留。允许在输入数据不被并发修改的前提下并发测光。采集时间、处理耗时、模型来源及 CSV/JSON 存档由应用程序关联,不由测光函数生成。 `processFrame()` 使用同一测光实现,控制仍使用原 `gray` 字段。新增字段追加在原有数据字段之后,保留原源码的聚合初始化顺序;新增数据字段不保证旧二进制 ABI,应用和静态库应一起重新编译。 ## 曝光控制调用顺序 1. 准备本工况的 `Config`。必须提供最小、最大和经验曝光;静止采图上限由业务节拍确定。 2. **拍照前**调用 `preCapture(now_us, config)`。检查 `accepted`;若 `initialization_required`,由调用方先成功设置 `suggested_exposure_us`,再采集。 3. 调用方采集原始图像并识别,读取这一帧真正使用的曝光值,构造 `FrameInput`。 4. 调用 `processFrame(input)`,检查输入接收状态、测光有效性及原因。仅当 `adjustment_suggested=true` 时,由调用方将建议应用于下一帧。 5. 每一帧都重新执行拍照前检查。建议未成功应用时,不把建议值当实际曝光。初始化设置失败时由业务重试设置或显式复位,不继续假装初始化成功。 可编译运行的完整示例见 [examples/minimal.cpp](examples/minimal.cpp),配置见 [examples/static_station_config.hpp](examples/static_station_config.hpp)。示例由外部循环模拟图像和曝光应用,模块本身不采图、不等待、不循环试曝光。 ```cpp target_exposure::ExposureControl exposure; target_exposure::Config config; config.min_exposure_us = 100; config.max_exposure_us = 20000; // 仅作示例,替换为本工况上限 config.empirical_exposure_us = 1000; // 替换为本工况经验值 // 拍照前: auto prepare = exposure.preCapture(now_us, config); // accepted=false 时处理错误;initialization_required=true 时先由业务设置曝光。 // 业务完成采图、识别后: target_exposure::FrameInput input; input.image = original_image; // CV_8UC1 或 CV_8UC3(BGR) input.roi.rectangles = final_metering_rects; // 也可叠加旋转矩形或 masks input.has_usable_target = detector_has_target; input.timestamp_us = captured_at_us; input.actual_exposure_us = actual_frame_exposure_us; auto result = exposure.processFrame(input); // result.adjustment_suggested 为 true 时,由业务设置 result.suggested_exposure_us。 ``` 上面的变量来自调用工程,片段用于说明接口顺序;独立运行使用完整示例。 ## 输入和测光契约 - 支持 Gray8 和 BGR8。BGR 使用 OpenCV 固定 `COLOR_BGR2GRAY` 转换;不做归一化、gamma 调整或自动增强。不接受画框图、增强图和未经解释的 Bayer/12/16 bit 数据。 - `Roi` 中矩形、旋转矩形及 mask 可以混用,按像素取并集,重叠只统计一次。mask 必须为与图像同尺寸的 `CV_8UC1`,非零即选中。 - 普通矩形使用 OpenCV 的左闭右开范围,先裁剪到图像;旋转矩形按像素中心是否位于旋转后的半开矩形内判断,不使用其包围框直接测光。 - 输入 ROI 被视为最终结果,不隐式内缩。`inset(rect, fraction_per_side)` 是独立、显式辅助函数;比例指每一侧内缩量,整数矩形向内取整。 - `arithmetic_gray` 为截尾前的普通平均;全局 `gray` 与其相同,`global_center_fraction<1` 时统计固定比例的居中矩形。目标 `gray` 在并集内上下各删除 `floor(N × trim_fraction_each_side)` 个样本后求平均。 - 无目标、ROI 空/格式错误、像素不足均有明确状态,`gray`、`arithmetic_gray` 为 `std::nullopt`。真实黑图的灰度 0 可以是有效测量。 - 非有限、非正或超出当前配置范围的实际曝光属于输入契约错误,返回 `InvalidActualExposure`,保留错误实参用于诊断,不伪造实际曝光或产生调整建议。调用方应修正配置/曝光状态后重走拍照前流程。 - 同一实例内帧时间必须严格递增。重复、乱序及缺少拍照前检查的帧不更新控制状态。相机时间重置时显式 `reset(ResetReason::ClockChanged)`。 - 图像只在调用期间读取,不改原图、不保存图像副本;调用期间调用方须保证数据有效且不被其他线程修改。 ## 关键配置 所有控制参数都在公共头文件的强类型结构体中配置,无额外配置文件解析依赖。 | 配置 | 默认/示例 | 含义 | |---|---|---| | `min/max/empirical_exposure_us` | 默认未设置,必须由调用方填写 | 曝光范围及初始化曝光 | | `profile_id`、`revision` | default、0 | 工况身份;任何参数或版本变化也会在拍照前初始化 | | `history_window_us`、`idle_timeout_us` | 各 300000000 us | 历史窗口与停机阈值分别判断 | | `history_capacity` | 512 | 帧诊断和有效目标各自保留最多此数量;不得小于预测点数 | | `detect_confirm_frames/lost_confirm_frames/freeze_frames` | 3/3/2 | 三态连续计数和冻结拍数 | | `metering.trim_fraction_each_side` | 0.05 | 每侧截尾比例 | | `metering.min_target_pixels/min_retained_pixels` | 16/8 | 截尾前、后最少像素 | | `metering.global_center_fraction` | 1.0 | 全图;可配置为 0~1 之间的中央区域比例 | | `controller.target_gray` | 118 | 8 bit 标度下的目标灰度示例 | | `controller.deadband_ratio/hysteresis_ratio/large_error_ratio` | 0.10/0.02/0.40 | 相对误差阈值,0.10 表示 10% | | `controller.proportional_gain/kp/ki` | 0.70/0.40/0.10 | 附件比例分支及离散 PI 系数示例 | | `controller.integral_abs_limit/max_step_ratio` | 1.0/0.25 | 积分幅度上限、大小误差统一步长上限 | | `controller.same_direction_frames` | 2 | 小误差连续同方向确认;大误差按已确认规则立即动作 | | `prediction.points` | 3,可选 5 | 只使用最近指定数量的有效目标记录 | | `prediction.enabled/photometric_model_confirmed` | true/false | 必须确认光度模型前提后才采用预测;未确认仍正常反馈控制 | | `prediction.black_level` | 0.0 | 已知黑电平偏置,不从场景中猜测 | | `prediction.nominal_interval_us/max_horizon_us` | 0/60000000 us | 未传下一拍时刻时的备用间隔;0 表示缺失,无时距则降级 | | `prediction.max_relative_fit_rmse` | 0.10 | 归一化响应拟合的相对残差上限 | | `prediction.max_near_black_fraction/max_saturated_fraction` | 0.10/0.05 | 排除失去线性响应的记录;截尾前统计 | | `prediction.min_predicted_gray/max_predicted_gray` | 1/254 | 预测适用范围 | | `recovery.global_reference_gray` | 未设置 | 独立全局参考,不自动等于目标灰度 | | `recovery.deadband_ratio/gain/max_step_ratio` | 0.30/0.30/0.10 | 单次恢复判据与保守比例修正 | 所有示例值只用于演示配置和逻辑,不能代表所有工件。调用方选择黑件/白件或不同工况配置。参数错误由 `validate(config)` 或 `preCapture().errors` 报告。 详细预测公式、三态和恢复预算语义见 [控制与状态说明](docs/控制与状态说明.md)。 ## 构建与验证 Windows x64、VS 2022/MSVC 下,原控制版本使用 OpenCV 4.7 验证;当前独立测光版本使用与 MatchVerify 相同的 OpenCV 4.10 重新构建验证。源码构建不硬编码本机路径;以下命令使用本机实际依赖位置。 ```powershell pwsh -NoProfile -File tools/verify.ps1 ` -BuildRoot E:/05_Temp/TargetExposureControl/capture-metering ` -OpenCVDir E:/06_EnvConfig/opencv/build/x64/vc16/lib ` -OpenCVRuntimeDir E:/06_EnvConfig/opencv/build/x64/vc16/bin ``` 脚本依次配置、构建 Release、运行全部分组 CTest 和最小示例、安装库,再构建并运行只引用安装包的独立消费工程。完整日志位于构建根目录的 `verification.log`。所有测试使用模拟时间和内存图像,不连接相机。 OpenCV 4.7 Windows 包的顶层配置不识别某些新 MSVC 版本。脚本发现已存在的 `x64/vc16/lib/OpenCVConfig.cmake` 时直接使用该包配置;没有修改 OpenCV。手动配置时可将 `OpenCV_DIR` 设到该目录。其他 OpenCV 布局可传具体包目录和 `-OpenCVRuntimeDir`。 源码作为子目录接入时默认不构建示例与测试;可通过 `TEC_BUILD_TESTS`、`TEC_BUILD_EXAMPLES` 控制。安装包内还包含中文说明与完整示例。 实际命令、结果及剩余验证边界见 [实施记录](实施记录.md)。构建、模拟控制测试和安装接入验证不能证明真实相机参数生效、实际成像改善或现场验收。