381 lines
14 KiB
C++
381 lines
14 KiB
C++
#ifndef DRONESCREW_SERVER_PRESENTER_H
|
||
#define DRONESCREW_SERVER_PRESENTER_H
|
||
|
||
#include <QObject>
|
||
#include <QJsonObject>
|
||
#include <QString>
|
||
#include <QTimer>
|
||
#include <QMutex>
|
||
#include <QDateTime>
|
||
#include <atomic>
|
||
#include <condition_variable>
|
||
#include <deque>
|
||
#include <thread>
|
||
#include <memory>
|
||
#include <mutex>
|
||
#include <vector>
|
||
|
||
#include "IMvsDevice.h"
|
||
#include "IVrFFMediaPusher.h"
|
||
#include "Algo/IDroneScrewAlgo.h"
|
||
|
||
/**
|
||
* @brief 双板架构「板一」 Presenter(双目版本)
|
||
*
|
||
* 流程:
|
||
* 双目海康MVS相机采集 → 算法检测(左右目输入)→ 画检测框 → 推送至 RTSP 服务 → ZMQ 异步发布结果
|
||
*
|
||
* 架构特点:
|
||
* - 使用 IMvsDevice 双目相机(左右目)
|
||
* - 左右目图像同步等待机制(参考 BinocularMarkPresenter)
|
||
* - 算法检测需要左右目双目输入
|
||
* - RTMP/RTSP 推流推送 Rtsp.streamCamera 指定的单路图像
|
||
* - ZMQ 原始图像订阅发送左右目两张图像(或仅左目单张图像,由 detectMode 控制)
|
||
*
|
||
* detectMode 说明:
|
||
* - "binocular"(默认):ZMQ 原始图像 PUB 发送左右目两张图像(BIWI 协议头)
|
||
* - "mono":ZMQ 原始图像 PUB 仅发送左目图像(BIWI 协议头,右目置空)
|
||
* 注意:mono 模式下算法仍使用双目输入,仅传输层面减少右目数据量
|
||
*/
|
||
class DroneScrewServerPresenter : public QObject
|
||
{
|
||
Q_OBJECT
|
||
|
||
public:
|
||
explicit DroneScrewServerPresenter(QObject* parent = nullptr);
|
||
~DroneScrewServerPresenter() override;
|
||
|
||
/**
|
||
* @brief 加载配置文件
|
||
*/
|
||
bool loadConfiguration(const QString& configFilePath);
|
||
|
||
/**
|
||
* @brief 设置 saveConfiguration 的目标路径
|
||
*
|
||
* 运行时调用此方法可以修改保存位置,主要用于将用户配置持久化到
|
||
* 独立文件中(如 runtime_config.xml),避免被部署包覆盖。
|
||
*/
|
||
void setConfigFilePath(const QString& path) { m_configFilePath = path; }
|
||
|
||
/**
|
||
* @brief 保存当前配置到 m_configFilePath
|
||
*
|
||
* 保存 Camera(曝光/增益)和 Trigger 等运行时参数。
|
||
*/
|
||
bool saveConfiguration();
|
||
|
||
/**
|
||
* @brief 初始化相机 / 推流 / 算法
|
||
*/
|
||
int initAll();
|
||
|
||
/**
|
||
* @brief 反初始化
|
||
*/
|
||
void deinitAll();
|
||
|
||
/**
|
||
* @brief 当前检测中
|
||
*/
|
||
bool isDetecting() const { return m_bIsDetecting.load(); }
|
||
bool isLiveStreaming() const { return m_bLiveStreaming.load(); }
|
||
|
||
/**
|
||
* @brief 同步执行单次检测,并返回真实执行结果
|
||
*
|
||
* 供 ZMQ REP 的 single 命令使用。失败时返回 success=false,
|
||
* errorCode/message 描述失败原因。
|
||
*/
|
||
DroneScrewResult runSingleDetection();
|
||
|
||
/**
|
||
* @brief 获取 ZMQ REP / PUB 端口(供主程序启动 ZMQ 协议层)
|
||
*/
|
||
int getZmqControlPort() const { return m_zmqControlPort; }
|
||
int getZmqResultPort() const { return m_zmqResultPort; }
|
||
int getZmqRawImagePort() const { return m_zmqRawImagePort; }
|
||
QString getRtspUrl() const { return m_rtspAdvertiseUrl; }
|
||
QJsonObject getRuntimeInfo() const;
|
||
QJsonObject getCalibrationInfo() const;
|
||
bool getLastFinalDetectionResult(DroneScrewResult& result) const;
|
||
int startDetectionWork();
|
||
int stopDetectionWork();
|
||
int startLiveStream();
|
||
int stopLiveStream();
|
||
int swapCameraRoles();
|
||
|
||
/**
|
||
* @brief 启用 / 停用 ZMQ 原始图像 PUB
|
||
*
|
||
* ZMQ 协议层启动 raw PUB 端口后会调用 Enable,
|
||
* 之后采集回调里会 emit rawImageReady。
|
||
*/
|
||
void setRawPubEnabled(bool enabled) { m_bRawPubEnabled = enabled; }
|
||
|
||
/**
|
||
* @brief 设置检测模式下的图像传输模式
|
||
* @param mode "binocular"(默认,发送左右目)或 "mono"(仅发送左目)
|
||
*
|
||
* 仅影响 ZMQ 原始图像 PUB 通道的传输内容;
|
||
* 算法内部始终使用双目输入。
|
||
*/
|
||
void setDetectMode(const std::string& mode);
|
||
std::string detectMode() const;
|
||
|
||
public slots:
|
||
/**
|
||
* @brief 设置曝光(同时设置左右相机)
|
||
*/
|
||
void handleSetExposure(double exposureTime);
|
||
|
||
/**
|
||
* @brief 设置增益(同时设置左右相机)
|
||
*/
|
||
void handleSetGain(double gain);
|
||
|
||
/**
|
||
* @brief 设置左相机曝光
|
||
*/
|
||
void handleSetLeftExposure(double exposureTime);
|
||
|
||
/**
|
||
* @brief 设置右相机曝光
|
||
*/
|
||
void handleSetRightExposure(double exposureTime);
|
||
|
||
/**
|
||
* @brief 设置左相机增益
|
||
*/
|
||
void handleSetLeftGain(double gain);
|
||
|
||
/**
|
||
* @brief 设置右相机增益
|
||
*/
|
||
void handleSetRightGain(double gain);
|
||
|
||
/**
|
||
* @brief 更新算法参数
|
||
*/
|
||
void handleUpdateAlgoParams(const DroneScrewAlgoParams& params);
|
||
|
||
signals:
|
||
/**
|
||
* @brief 单帧检测结果信号(提供给 ZMQ 协议层异步发布)
|
||
*/
|
||
void detectionResult(const DroneScrewResult& result);
|
||
|
||
/**
|
||
* @brief 原始双目相机帧信号
|
||
*
|
||
* 仅当 m_bRawPubEnabled 为 true 时才会 emit,供 ZMQ 协议层广播给客户端。
|
||
* 注意:MvsImageData 内部的 pData 由 presenter 管理,槽函数收到后请立即处理
|
||
* 或拷贝;本对象只保证在同步 emit 期间内有效。
|
||
*/
|
||
void rawImageReady(const MvsImageData& leftImg, const MvsImageData& rightImg);
|
||
|
||
/**
|
||
* @brief 状态/事件信号
|
||
*/
|
||
void statusChanged(const QString& msg);
|
||
|
||
private slots:
|
||
/**
|
||
* @brief 相机重连定时器
|
||
*/
|
||
void onCameraReconnectTimer();
|
||
|
||
private:
|
||
int tryConnectCamera();
|
||
void closeCamera();
|
||
int configureCamera(IMvsDevice* camera,
|
||
const char* role,
|
||
bool ioTrigger = false,
|
||
double frameRate = 2.0,
|
||
int64_t binningHorizontal = 1,
|
||
int64_t binningVertical = 1,
|
||
int64_t decimationHorizontal = 1,
|
||
int64_t decimationVertical = 1);
|
||
IMvsDevice* cameraForRole(const char* role) const;
|
||
const char* liveStreamCameraRole() const;
|
||
IMvsDevice* liveStreamCamera() const;
|
||
IMvsDevice* nonLiveStreamCamera() const;
|
||
bool isLiveStreamCameraRole(const char* role) const;
|
||
bool areCamerasGrabbing();
|
||
int prepareDetectionAcquisition(double frameRate,
|
||
int64_t binningHorizontal,
|
||
int64_t binningVertical,
|
||
int64_t decimationHorizontal,
|
||
int64_t decimationVertical,
|
||
const char* tag);
|
||
int prepareLiveStreamAcquisition();
|
||
void resetFrameReadyFlags();
|
||
int initRtspPusher(unsigned int width, unsigned int height, unsigned int fps);
|
||
int startRtspPusher();
|
||
void releaseRtspPusher();
|
||
int pushRtspFrame(const MvsImageData& img);
|
||
void resetRtspPushState();
|
||
void recordRtspPushResult(int ret, unsigned long long frameId);
|
||
bool tryRgaScaleMonoToY(const MvsImageData& img,
|
||
unsigned char* dstY,
|
||
unsigned int outWidth,
|
||
unsigned int outHeight);
|
||
void leftCameraCallback(const MvsImageData& img);
|
||
void rightCameraCallback(const MvsImageData& img);
|
||
|
||
void detectThreadFunc();
|
||
double exposureForRole(const char* role) const;
|
||
int applyExposureForRole(const char* role);
|
||
int applyCurrentExposureForRole(const char* role);
|
||
double gainForRole(const char* role) const;
|
||
int pulseGpioTrigger(bool logOk = true);
|
||
void startGpioTriggerLoop();
|
||
void stopGpioTriggerLoop();
|
||
void triggerThreadFunc();
|
||
bool waitAndCopyLatestBinocular(MvsImageData& leftImg,
|
||
MvsImageData& rightImg,
|
||
unsigned int timeoutMs);
|
||
void startImageSaveThread(const QString& modeName);
|
||
void stopImageSaveThread();
|
||
void imageSaveThreadFunc(int workerIndex);
|
||
bool enqueueImageSave(const MvsImageData& leftImg,
|
||
const MvsImageData& rightImg,
|
||
const DroneScrewResult& result,
|
||
unsigned long long index);
|
||
QString imageSaveModeName() const;
|
||
void resetPrecisionResultSamples();
|
||
void clearLastFinalDetectionResult();
|
||
void recordPrecisionResultSample(const DroneScrewResult& result,
|
||
unsigned long long imageIndex);
|
||
bool selectBestPrecisionResult(DroneScrewResult& result,
|
||
unsigned long long& imageIndex,
|
||
double& rankScore,
|
||
int& sampleCount) const;
|
||
void setLastFinalDetectionResult(const DroneScrewResult& result);
|
||
|
||
private:
|
||
struct ImageSaveBuffer
|
||
{
|
||
unsigned int width{0};
|
||
unsigned int height{0};
|
||
int pixelFormat{0};
|
||
std::vector<unsigned char> data;
|
||
bool valid{false};
|
||
};
|
||
|
||
struct ImageSaveJob
|
||
{
|
||
QString dirPath;
|
||
unsigned long long index{0};
|
||
ImageSaveBuffer left;
|
||
ImageSaveBuffer right;
|
||
DroneScrewResult result;
|
||
bool hasResult{false};
|
||
};
|
||
|
||
struct PrecisionResultSample
|
||
{
|
||
DroneScrewResult result;
|
||
unsigned long long imageIndex{0};
|
||
};
|
||
|
||
// 双目相机设备
|
||
IMvsDevice* m_pLeftCamera{nullptr};
|
||
IMvsDevice* m_pRightCamera{nullptr};
|
||
|
||
// 推流
|
||
IVrFFMediaPusher* m_pPusher{nullptr};
|
||
|
||
// 算法
|
||
std::unique_ptr<IDroneScrewAlgo> m_pAlgo;
|
||
DroneScrewAlgoParams m_algoParams;
|
||
|
||
// 状态
|
||
std::atomic<bool> m_bCameraConnected{false};
|
||
std::atomic<bool> m_bIsDetecting{false};
|
||
QTimer* m_pReconnectTimer{nullptr};
|
||
|
||
// 配置
|
||
std::string m_strLeftCameraSerial;
|
||
std::string m_strRightCameraSerial;
|
||
unsigned int m_nLeftCameraIndex{0};
|
||
unsigned int m_nRightCameraIndex{1};
|
||
double m_leftExposureTime{10000.0};
|
||
double m_rightExposureTime{10000.0};
|
||
double m_leftGain{1.0};
|
||
double m_rightGain{1.0};
|
||
QString m_configFilePath;
|
||
int m_zmqControlPort{5555};
|
||
int m_zmqResultPort{5556};
|
||
int m_zmqRawImagePort{5557};
|
||
int m_rtspPort{8554};
|
||
QString m_rtspPath{"/live/dronescrew"};
|
||
QString m_rtspAdvertiseUrl;
|
||
int m_pushBitrateKbps{4096};
|
||
int m_pushFps{30};
|
||
int m_liveStreamFps{30};
|
||
unsigned int m_liveStreamWidth{1024};
|
||
unsigned int m_liveStreamHeight{750};
|
||
std::string m_liveStreamCameraRole{"left"}; // fixed to left for realtime stream
|
||
bool m_useIoTrigger{true};
|
||
int m_triggerGpio{113};
|
||
int m_triggerSource{0}; // MVS Line0
|
||
int m_triggerActivation{0}; // MVS RisingEdge
|
||
std::thread m_triggerThread;
|
||
std::atomic<bool> m_bTriggerThreadExit{false};
|
||
unsigned int m_rtspWidth{0};
|
||
unsigned int m_rtspHeight{0};
|
||
std::atomic<bool> m_bRtspStarted{false};
|
||
std::atomic<bool> m_bLiveStreaming{false};
|
||
std::atomic<int> m_detectPipelineMode{0}; // 0=precision, 1=distance
|
||
std::atomic<int> m_activeTriggerFps{2};
|
||
std::atomic<int64_t> m_rtspFrameCounter{0}; // 相对帧计数(推流启动时重置)
|
||
std::mutex m_rtspPusherMutex;
|
||
std::mutex m_rtspPushMutex;
|
||
std::atomic<int64_t> m_rtspPushedFrameCounter{0};
|
||
std::atomic<int> m_rtspLastPushRet{0};
|
||
std::atomic<unsigned long long> m_rtspLastPushFrameId{0};
|
||
std::vector<unsigned char> m_rtspFrameBuffer;
|
||
std::vector<unsigned int> m_rtspScaleX;
|
||
std::vector<unsigned int> m_rtspScaleY;
|
||
unsigned int m_rtspScaleSrcWidth{0};
|
||
unsigned int m_rtspScaleSrcHeight{0};
|
||
unsigned int m_rtspScaleOutWidth{0};
|
||
unsigned int m_rtspScaleOutHeight{0};
|
||
bool m_rtspUvInitialized{false};
|
||
bool m_rtspRgaScaleDisabled{false};
|
||
bool m_rtspRgaScaleLogged{false};
|
||
|
||
// 原始图像 PUB 开关
|
||
std::atomic<bool> m_bRawPubEnabled{false};
|
||
|
||
// 检测模式下的图像传输模式
|
||
mutable std::mutex m_detectModeMutex;
|
||
std::string m_detectMode{"binocular"}; // "binocular" | "mono"
|
||
|
||
// 检测线程
|
||
std::thread m_detectThread;
|
||
std::atomic<bool> m_bThreadExit{false};
|
||
QMutex m_frameMutex;
|
||
MvsImageData m_leftImageData;
|
||
MvsImageData m_rightImageData;
|
||
bool m_bLeftImageReady{false};
|
||
bool m_bRightImageReady{false};
|
||
|
||
// 异步图像保存
|
||
std::vector<std::thread> m_imageSaveThreads;
|
||
std::atomic<bool> m_imageSaveThreadExit{false};
|
||
std::mutex m_imageSaveMutex;
|
||
std::condition_variable m_imageSaveCv;
|
||
std::deque<ImageSaveJob> m_imageSaveQueue;
|
||
QString m_imageSaveSessionDir;
|
||
unsigned long long m_imageSaveDropped{0};
|
||
|
||
mutable std::mutex m_detectionResultMutex;
|
||
std::vector<PrecisionResultSample> m_precisionResultSamples;
|
||
DroneScrewResult m_lastFinalResult;
|
||
bool m_hasLastFinalResult{false};
|
||
};
|
||
|
||
#endif // DRONESCREW_SERVER_PRESENTER_H
|