141 lines
5.4 KiB
C++
141 lines
5.4 KiB
C++
#pragma once
|
|
|
|
#include "fgc/ICameraSource.h"
|
|
#include "fgc/Logger.h"
|
|
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <string>
|
|
|
|
namespace fgc {
|
|
|
|
// Synthetic camera for development without hardware. On trigger() it generates
|
|
// a small RGB gradient frame (varying with time so successive captures differ)
|
|
// and delivers it to the frame callback.
|
|
//
|
|
// It also implements the deliberate-acquisition API with a crude sensor model
|
|
// (brightness proportional to exposure x gain), so the quality gate's whole
|
|
// shoot -> judge -> correct loop can be exercised end to end with --mock-camera.
|
|
class MockCameraSource : public ICameraSource {
|
|
public:
|
|
explicit MockCameraSource(int count = 1, uint32_t width = 640, uint32_t height = 480)
|
|
: count_(count), width_(width), height_(height) {}
|
|
|
|
void open() override { LOG_INFO << "[mock] camera opened (" << count_ << ")"; }
|
|
void close() override {}
|
|
void start() override { started_ = true; LOG_INFO << "[mock] camera acquisition started"; }
|
|
void stop() override { started_ = false; }
|
|
|
|
bool trigger() override {
|
|
if (!callback_) return false;
|
|
LOG_TRACE_CAT(LogCat::Camera) << "TX trigger cam0 (mock)";
|
|
Frame f = synth();
|
|
LOG_TRACE_CAT(LogCat::Camera)
|
|
<< "RX frame cam0 " << width_ << 'x' << height_ << ' ' << f.data.size() << "B (mock)";
|
|
++frames_;
|
|
callback_(f);
|
|
return true;
|
|
}
|
|
|
|
void setFrameCallback(FrameCallback cb) override { callback_ = std::move(cb); }
|
|
int cameraCount() const override { return count_; }
|
|
|
|
// ---- Deliberate acquisition, for the quality gate ----
|
|
|
|
bool acquireFrame(Frame& out, int /*timeout_ms*/) override {
|
|
out = synth();
|
|
++frames_;
|
|
LOG_TRACE_CAT(LogCat::Camera)
|
|
<< "RX frame cam0 " << width_ << 'x' << height_ << " exp=" << exposure_us_
|
|
<< "us gain=" << gain_db_ << "dB (mock)";
|
|
return true;
|
|
}
|
|
|
|
bool setExposure(double us) override {
|
|
exposure_us_ = us;
|
|
return true;
|
|
}
|
|
bool setGain(double db) override {
|
|
gain_db_ = db;
|
|
return true;
|
|
}
|
|
bool setAutoExposureGain(bool on) override {
|
|
auto_on_ = on;
|
|
return true;
|
|
}
|
|
double currentGain() override { return gain_db_; }
|
|
|
|
// Synthetic device info so the expanded camera view renders (and is testable)
|
|
// without hardware. Non-hardware fields (temperature, exposure...) stay at 0.
|
|
std::vector<CameraDeviceInfo> deviceInfo() override {
|
|
std::vector<CameraDeviceInfo> out;
|
|
for (int i = 0; i < count_; ++i) {
|
|
CameraDeviceInfo d;
|
|
d.id = "MOCK" + std::to_string(i);
|
|
d.model = "MockCamera";
|
|
d.serial = "SIM-0000";
|
|
d.streaming = started_;
|
|
d.width = width_;
|
|
d.height = height_;
|
|
d.payload_bytes = static_cast<long long>(width_) * height_ * 3;
|
|
d.actual_fps = 1.0;
|
|
d.frames_delivered = (i == 0) ? frames_ : 0; // only cam0 produces frames
|
|
out.push_back(std::move(d));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
private:
|
|
// A gradient frame whose overall brightness follows exposure x gain, so the
|
|
// quality gate sees its corrections take effect. The pattern varies with time
|
|
// so successive captures differ.
|
|
Frame synth() const {
|
|
Frame f;
|
|
f.width = width_;
|
|
f.height = height_;
|
|
f.channels = 3;
|
|
f.cam_id = 0;
|
|
f.timestamp_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::system_clock::now().time_since_epoch())
|
|
.count();
|
|
f.data.resize(static_cast<size_t>(width_) * height_ * 3);
|
|
|
|
// Exposure/gain -> brightness. Calibrated so the default 5000 us at 0 dB
|
|
// lands near the policy's default target mean, making a mock run converge
|
|
// the way a real one should.
|
|
const double lin = exposure_us_ * std::pow(10.0, gain_db_ / 20.0);
|
|
const double level = auto_on_ ? 110.0 : lin * 0.022;
|
|
|
|
const uint8_t phase = static_cast<uint8_t>(f.timestamp_ms);
|
|
for (uint32_t y = 0; y < height_; ++y) {
|
|
for (uint32_t x = 0; x < width_; ++x) {
|
|
size_t i = (static_cast<size_t>(y) * width_ + x) * 3;
|
|
// Zero-mean texture around `level` so there is real detail for the
|
|
// sharpness metric without shifting the mean. The cast matters:
|
|
// (x+y+phase)%3 is unsigned, so subtracting 1 from the zero case
|
|
// would wrap to UINT_MAX and clip a third of the frame to white.
|
|
const double tex = 20.0 * (static_cast<int>((x + y + phase) % 3) - 1);
|
|
auto clamp8 = [](double v) {
|
|
return static_cast<uint8_t>(v < 0 ? 0 : (v > 255 ? 255 : v));
|
|
};
|
|
f.data[i + 0] = clamp8(level + tex);
|
|
f.data[i + 1] = clamp8(level + tex * 0.8);
|
|
f.data[i + 2] = clamp8(level + tex * 0.6);
|
|
}
|
|
}
|
|
return f;
|
|
}
|
|
|
|
int count_;
|
|
uint32_t width_;
|
|
uint32_t height_;
|
|
bool started_ = false;
|
|
long long frames_ = 0;
|
|
double exposure_us_ = 5000.0;
|
|
double gain_db_ = 0.0;
|
|
bool auto_on_ = true;
|
|
FrameCallback callback_;
|
|
};
|
|
|
|
} // namespace fgc
|