|
|
|
@ -19,12 +19,225 @@
|
|
|
|
#include <iostream>
|
|
|
|
#include <iostream>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include <string>
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
|
|
|
#include <cerrno>
|
|
|
|
|
|
|
|
#include <cstddef>
|
|
|
|
|
|
|
|
#include <memory>
|
|
|
|
#include <mutex>
|
|
|
|
#include <mutex>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include <thread>
|
|
|
|
#include <thread>
|
|
|
|
|
|
|
|
#include <condition_variable>
|
|
|
|
|
|
|
|
#include <deque>
|
|
|
|
|
|
|
|
#include <vector>
|
|
|
|
#include <opencv2/opencv.hpp>
|
|
|
|
#include <opencv2/opencv.hpp>
|
|
|
|
using namespace std;
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
// 异步存图器(header-only 单例)
|
|
|
|
|
|
|
|
// 场景:检测热点循环里直接 cv::imwrite,编码 + 写盘会把主流程拖慢
|
|
|
|
|
|
|
|
// (例:CTcsCheck::AdaptiveBinary 逐块存残点调试图)。
|
|
|
|
|
|
|
|
// 做法:调用方把图 clone 后入队即返回,独立后台线程串行编码落盘,
|
|
|
|
|
|
|
|
// 调用方只付一次内存拷贝的代价,不再等磁盘 IO。
|
|
|
|
|
|
|
|
// 线程安全;队列有长度上限,写盘慢于入队时 Push 会阻塞(背压),避免内存暴涨。
|
|
|
|
|
|
|
|
// 实现放在头文件、不依赖 CheckUtil.cpp:TcsCheck 这类未链接 AlgorithmModule
|
|
|
|
|
|
|
|
// 的模块也能直接使用(否则会形成反向链接依赖)。
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
class CAsyncImgSaver
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
|
|
static CAsyncImgSaver &Instance()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
static CAsyncImgSaver s_inst; // C++11 起局部静态初始化线程安全
|
|
|
|
|
|
|
|
return s_inst;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 入队一张图(img 会被 clone,返回后调用方可立即释放/复用原图)
|
|
|
|
|
|
|
|
void Push(const std::string &strPath, const cv::Mat &img, const std::vector<int> &vParams)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (strPath.empty() || img.empty())
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ImgTask task;
|
|
|
|
|
|
|
|
task.strPath = strPath;
|
|
|
|
|
|
|
|
task.vParams = vParams;
|
|
|
|
|
|
|
|
task.mat = img.clone(); // 深拷贝,隔离调用方数据的生命周期
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::unique_lock<std::mutex> lk(m_mtx);
|
|
|
|
|
|
|
|
if (m_bStop)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return; // 已关闭:直接丢弃,避免后台线程退出后无限等待
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_nQueueLimit > 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// 队列满则阻塞等待(背压),顺带让 cv::Mat 内存占用可控
|
|
|
|
|
|
|
|
m_cvHasRoom.wait(lk, [this] { return m_bStop || m_deqTask.size() < m_nQueueLimit; });
|
|
|
|
|
|
|
|
if (m_bStop)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
m_deqTask.push_back(std::move(task));
|
|
|
|
|
|
|
|
++m_nPending;
|
|
|
|
|
|
|
|
lk.unlock();
|
|
|
|
|
|
|
|
m_cvHasTask.notify_one();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 等待队列中(含正在写盘)的图全部落盘
|
|
|
|
|
|
|
|
void WaitIdle()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
std::unique_lock<std::mutex> lk(m_mtx);
|
|
|
|
|
|
|
|
m_cvIdle.wait(lk, [this] { return m_nPending == 0; });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 队列长度上限(张);调大后唤醒被背压阻塞的入队方
|
|
|
|
|
|
|
|
void SetQueueLimit(std::size_t nLimit)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lk(m_mtx);
|
|
|
|
|
|
|
|
m_nQueueLimit = nLimit;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
m_cvHasRoom.notify_all();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭:排空剩余队列后退出后台线程(可重复调用)
|
|
|
|
|
|
|
|
void Stop()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lk(m_mtx);
|
|
|
|
|
|
|
|
m_bStop = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
m_cvHasTask.notify_all();
|
|
|
|
|
|
|
|
m_cvHasRoom.notify_all();
|
|
|
|
|
|
|
|
if (m_thread.joinable())
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
m_thread.join();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
|
|
struct ImgTask
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
std::string strPath;
|
|
|
|
|
|
|
|
cv::Mat mat;
|
|
|
|
|
|
|
|
std::vector<int> vParams;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CAsyncImgSaver()
|
|
|
|
|
|
|
|
: m_bStop(false)
|
|
|
|
|
|
|
|
, m_nQueueLimit(4096) // 默认最多积压 4096 张待写图
|
|
|
|
|
|
|
|
, m_nPending(0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
m_thread = std::thread(&CAsyncImgSaver::Worker, this);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
~CAsyncImgSaver()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Stop(); // 进程退出兜底:把队列里剩余的图写完再退
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CAsyncImgSaver(const CAsyncImgSaver &) = delete;
|
|
|
|
|
|
|
|
CAsyncImgSaver &operator=(const CAsyncImgSaver &) = delete;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 后台线程主循环:串行取图 -> 建目录 -> 编码落盘
|
|
|
|
|
|
|
|
void Worker()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
std::string strLastDir; // 缓存上次建好的目录,避免每张图都做一次 mkdir
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
ImgTask task;
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
std::unique_lock<std::mutex> lk(m_mtx);
|
|
|
|
|
|
|
|
m_cvHasTask.wait(lk, [this] { return m_bStop || !m_deqTask.empty(); });
|
|
|
|
|
|
|
|
if (m_deqTask.empty())
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return; // 收到停止信号且队列已排空
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
task = std::move(m_deqTask.front());
|
|
|
|
|
|
|
|
m_deqTask.pop_front();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
m_cvHasRoom.notify_one(); // 队列腾出空位,唤醒可能被阻塞的入队方
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!task.mat.empty())
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
EnsureParentDir(task.strPath, strLastDir);
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
cv::imwrite(task.strPath, task.mat, task.vParams);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (const cv::Exception &e)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
std::cerr << "[SaveImgAsync] imwrite failed: " << task.strPath << " : " << e.what() << std::endl;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lk(m_mtx);
|
|
|
|
|
|
|
|
if (m_nPending > 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
--m_nPending;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_nPending == 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
m_cvIdle.notify_all();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 递归创建多级目录(已存在视为成功)
|
|
|
|
|
|
|
|
static bool MakeDirs(const std::string &strPath)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (strPath.empty())
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mkdir(strPath.c_str(), 0755) == 0 || errno == EEXIST)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::size_t pos = strPath.find_last_of('/');
|
|
|
|
|
|
|
|
if (pos == std::string::npos || pos == 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!MakeDirs(strPath.substr(0, pos)))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return (mkdir(strPath.c_str(), 0755) == 0 || errno == EEXIST);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 创建文件所在目录;strCacheDir 命中则省掉一次 mkdir
|
|
|
|
|
|
|
|
static void EnsureParentDir(const std::string &strPath, std::string &strCacheDir)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
const std::size_t pos = strPath.find_last_of('/');
|
|
|
|
|
|
|
|
if (pos == std::string::npos || pos == 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return; // 没有目录部分(相对文件名)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string strDir = strPath.substr(0, pos);
|
|
|
|
|
|
|
|
if (strDir == strCacheDir)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
MakeDirs(strDir);
|
|
|
|
|
|
|
|
strCacheDir = strDir;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::thread m_thread;
|
|
|
|
|
|
|
|
std::mutex m_mtx;
|
|
|
|
|
|
|
|
std::condition_variable m_cvHasTask; // 有新任务
|
|
|
|
|
|
|
|
std::condition_variable m_cvHasRoom; // 队列有空位
|
|
|
|
|
|
|
|
std::condition_variable m_cvIdle; // 全部落盘
|
|
|
|
|
|
|
|
std::deque<ImgTask> m_deqTask;
|
|
|
|
|
|
|
|
bool m_bStop;
|
|
|
|
|
|
|
|
std::size_t m_nQueueLimit;
|
|
|
|
|
|
|
|
std::size_t m_nPending; // 已入队但尚未写完的数量(含正在写)
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class CheckUtil
|
|
|
|
class CheckUtil
|
|
|
|
{
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
public:
|
|
|
|
@ -63,6 +276,29 @@ public:
|
|
|
|
static void PrintRect(cv::Rect roi, std::string str = "");
|
|
|
|
static void PrintRect(cv::Rect roi, std::string str = "");
|
|
|
|
static int cutSmallImg(cv::Mat img, std::vector<cv::Rect> &samllRoiList, cv::Rect config_roi, int config_SmallImg_Width, int config_SmallImg_Height, int config_MinOverlap_Width, int config_MinOverlap_Height);
|
|
|
|
static int cutSmallImg(cv::Mat img, std::vector<cv::Rect> &samllRoiList, cv::Rect config_roi, int config_SmallImg_Width, int config_SmallImg_Height, int config_MinOverlap_Width, int config_MinOverlap_Height);
|
|
|
|
static cv::Point2f transformPoint(const cv::Point2f &point, const cv::Mat &transform_matrix);
|
|
|
|
static cv::Point2f transformPoint(const cv::Point2f &point, const cv::Mat &transform_matrix);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ===================== 异步存图(实现见文件顶部 CAsyncImgSaver) =====================
|
|
|
|
|
|
|
|
// 入队即返回,父目录不存在会在后台自动创建;img 会被深拷贝,调用方可立即复用原图
|
|
|
|
|
|
|
|
static void SaveImgAsync(const std::string &strPath, const cv::Mat &img,
|
|
|
|
|
|
|
|
const std::vector<int> &vParams = std::vector<int>())
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
CAsyncImgSaver::Instance().Push(strPath, img, vParams);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 等待队列里所有异步存图落盘(退出/校验结果前调用)
|
|
|
|
|
|
|
|
static void WaitSaveImgDone()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
CAsyncImgSaver::Instance().WaitIdle();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 队列上限(张),默认 4096;超限时 SaveImgAsync 会阻塞(背压),0 表示不限制
|
|
|
|
|
|
|
|
static void SetSaveImgQueueLimit(std::size_t nLimit)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
CAsyncImgSaver::Instance().SetQueueLimit(nLimit);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 主动关闭后台存图线程(排空剩余队列后退出);不调用也会在进程退出时自动收尾
|
|
|
|
|
|
|
|
static void StopSaveImg()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
CAsyncImgSaver::Instance().Stop();
|
|
|
|
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
// 无参数重载:直接返回原字符串,避免 -Wformat-security 警告
|
|
|
|
// 无参数重载:直接返回原字符串,避免 -Wformat-security 警告
|
|
|
|
static inline std::string str_Format(const std::string &format)
|
|
|
|
static inline std::string str_Format(const std::string &format)
|
|
|
|
|