Ben 51e364997a feat: add continuous exposure control and native-depth metering
Add continuous global/ROI feedback with decision tracing, valid-image masks, and exact Gray16 source means alongside normalized metrics. Preserve legacy control behavior and expose the 1.3.0 API.

Validation: Release CTest 12/12 (70 cases), installed consumer 1/1, and diff checks passed. Software-only verification; no real device operation.
2026-09-22 20:45:56 +08:00

52 lines
3.1 KiB
C++

#include <target_exposure_control/exposure_control.hpp>
#include <cmath>
#include <iostream>
int main() {
using namespace target_exposure;
if(std::string(kVersion)!="1.3.0") return 9;
const cv::Mat offlineImage(8,8,CV_8UC1,cv::Scalar(123));
Roi offlineRoi; offlineRoi.rectangles.emplace_back(0,0,8,8);
const auto offline=measureImage(offlineImage,offlineRoi,true);
if(!offline.target.valid() || offline.target.arithmetic_gray!=123.0 || offline.global.arithmetic_gray!=123.0) return 4;
const auto globalOnly=measureImage(offlineImage,{},false);
if(globalOnly.target.status!=MeterStatus::NoTarget || !globalOnly.global.valid()) return 5;
MeteringConfig preciseConfig; preciseConfig.min_target_pixels=1; preciseConfig.min_retained_pixels=1;
const cv::Mat source16=(cv::Mat_<unsigned short>(1,4)<<0,1000,1001,65535);
const cv::Mat validMask=(cv::Mat_<unsigned char>(1,4)<<0,1,255,0);
Roi preciseRoi; preciseRoi.rectangles={{0,0,3,1},{1,0,3,1}};
const auto precise=measureImage(source16,preciseRoi,true,preciseConfig,validMask);
if(!precise.target.valid() || precise.global.pixels!=2 || precise.target.pixels!=2 ||
precise.global.source_arithmetic_gray!=1000.5 || precise.target.source_arithmetic_gray!=1000.5 ||
precise.target.source_bit_depth!=16 || precise.target.source_max_gray!=65535.0 ||
std::abs(*precise.target.arithmetic_gray-1000.5*255/65535)>1e-12) return 8;
Config c;
c.min_exposure_us = 100; c.max_exposure_us = 20'000; c.empirical_exposure_us = 1'000;
c.detect_confirm_frames = 1;
ExposureControl module;
for (TimestampUs t : {TimestampUs{1'000'000}, TimestampUs{2'000'000}}) {
const auto pre = module.preCapture(t, c);
if (!pre.accepted) return 1;
FrameInput f;
f.image = cv::Mat(8, 8, CV_8UC1, cv::Scalar(40));
f.roi.rectangles.emplace_back(0, 0, 8, 8);
f.has_usable_target = true; f.timestamp_us = t; f.actual_exposure_us = 1'000;
const auto result = module.processFrame(f);
if (!result.accepted || result.state != State::TRACK) return 2;
if (t == 2'000'000 && (!result.adjustment_suggested || *result.suggested_exposure_us != 1'250)) return 3;
}
ContinuousConfig continuousConfig;
continuousConfig.min_exposure_us = 100; continuousConfig.max_exposure_us = 20000;
ContinuousExposureControl continuous;
ContinuousFrameInput input;
input.metering = measureImage(cv::Mat(8, 8, CV_8UC1, cv::Scalar(20)), {}, false);
input.source = FeedbackSource::YoloRegions;
input.actual_exposure_us = 1000;
const auto decision = continuous.update(input, continuousConfig);
if (!decision.accepted || decision.effective_source != FeedbackSource::Global ||
decision.fallback_reason != FeedbackFallbackReason::RegionUnavailable ||
!decision.adjustment_suggested || *decision.suggested_exposure_us != 1250) return 6;
if (std::string(toString(decision.state)) != "Adjusting") return 7;
std::cout << "Installed package consumer passed; stateless Gray8/Gray16 with valid mask, original and continuous control; exposure unit us.\n";
}