feat 添加异步存图

dev_lsy
liusiyang 5 days ago
parent 2a987af65f
commit 5d56785769

@ -19,12 +19,225 @@
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <string>
#include <sys/time.h>
#include <sys/stat.h>
#include <cerrno>
#include <cstddef>
#include <memory>
#include <mutex>
#include <vector>
#include <thread>
#include <condition_variable>
#include <deque>
#include <vector>
#include <opencv2/opencv.hpp>
using namespace std;
// ============================================================================
// 异步存图器header-only 单例)
// 场景:检测热点循环里直接 cv::imwrite编码 + 写盘会把主流程拖慢
// CTcsCheck::AdaptiveBinary 逐块存残点调试图)。
// 做法:调用方把图 clone 后入队即返回,独立后台线程串行编码落盘,
// 调用方只付一次内存拷贝的代价,不再等磁盘 IO。
// 线程安全;队列有长度上限,写盘慢于入队时 Push 会阻塞(背压),避免内存暴涨。
// 实现放在头文件、不依赖 CheckUtil.cppTcsCheck 这类未链接 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
{
public:
@ -63,6 +276,29 @@ public:
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 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 警告
static inline std::string str_Format(const std::string &format)

@ -451,7 +451,7 @@ Point2f GetCoorPoint(Point2f point, Mat img_mat)
return new_point;
}
int GetEdgeRoi(Mat img, Rect &new_roi, cv::RotatedRect &rotated_roi, float scale_x, float scale_y){
int GetEdgeRoi(Mat img, Rect &new_roi, cv::RotatedRect &rotated_roi, float scale_x, float scale_y, bool bSaveImg, const std::string &strCameraName){
if (img.empty())
{
return 1;
@ -469,6 +469,13 @@ int GetEdgeRoi(Mat img, Rect &new_roi, cv::RotatedRect &rotated_roi, float scale
// 做一步闭运算
Mat r_img_bin_close;
morphologyEx(r_img_bin, r_img_bin_close, MORPH_CLOSE, Mat::ones(15, 15, CV_8U));
if (bSaveImg)
{
cv::imwrite(strCameraName + "_r_img_bin.png", r_img_bin);
cv::imwrite(strCameraName + "_r_img_bin_close.png", r_img_bin_close);
}
std::vector<std::vector<cv::Point>> contours;
cv::findContours(r_img_bin_close, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
if (contours.empty())
@ -611,7 +618,14 @@ int ImgCheckAnalysisy::Adapt_Config(Mat img, Rect cur_roi, cv::RotatedRect& rot_
return 1;
}
// cv::RotatedRect rot_cur_roi;
int get_edge_roi = GetEdgeRoi(img, cur_roi, rot_cur_roi, 20, 20);
// 只有 -s (bsaveProcessImg) 时才保存粗定位中间图;文件名以相机名开头
bool bSaveProcessImg = (DetImgInfo_shareP != nullptr && DetImgInfo_shareP->bsaveProcessImg);
std::string strCameraName = m_AnalysisyConfig.commonCheckConfig.baseConfig.strCamearName;
if (strCameraName.empty() && DetImgInfo_shareP != nullptr)
{
strCameraName = DetImgInfo_shareP->strCameraName;
}
int get_edge_roi = GetEdgeRoi(img, cur_roi, rot_cur_roi, 20, 20, bSaveProcessImg, strCameraName);
if(get_edge_roi != 0){
return 2;
}

@ -11,6 +11,8 @@ include(${PROJECT_SOURCE_DIR}/cmake/print_archs.cmake)
include_directories(
/usr/local/include
${CMAKE_CURRENT_SOURCE_DIR}/include
# CheckUtil.hpp (异步存图等通用工具)
${PROJECT_SOURCE_DIR}/AlgorithmModule/include
${OpenCV_INCLUDE_DIRS}
)
link_directories(

@ -1,25 +1,5 @@
#include "TcsCheck.h"
namespace {
// 静默递归创建多级目录(成功或已存在返回 true
bool MakeDirs(const std::string& path, mode_t mode = 0755)
{
if (path.empty()) {
return false;
}
if (mkdir(path.c_str(), mode) == 0 || errno == EEXIST) {
return true;
}
const std::size_t pos = path.find_last_of('/');
if (pos == std::string::npos || pos == 0) {
return false;
}
if (!MakeDirs(path.substr(0, pos), mode)) {
return false;
}
return (mkdir(path.c_str(), mode) == 0 || errno == EEXIST);
}
} // namespace
#include "CheckUtil.hpp" // 异步存图CheckUtil::SaveImgAsync / WaitSaveImgDone
class CLock
{
@ -204,12 +184,12 @@ cv::Mat CTcsCheck::AdaptiveBinary(cv::Mat matBlur)
if (m_cpCfg.bDebugsaveImg)
{
std::string roi_ext = "[" + std::to_string(x) + "," + std::to_string(y) + "]";
std::string strSaveDir = "/home/aidlux/BOE/CELL_AOI/Tradition_Detect/" + m_cpCfg.productId + "/";
MakeDirs(strSaveDir);
std::string strSaveDir = "/home/aidlux/BOE/CELL_AOI/AI_Detect/" + m_cpCfg.productId + "/";
std::string strSavePath_in = strSaveDir + m_cpCfg.productChannel +"_"+ roi_ext + "_"+ "in" + ".png";
std::string strSavePath_out = strSaveDir + m_cpCfg.productChannel +"_"+ roi_ext + "_"+ "out" + ".png";
cv::imwrite(strSavePath_in, block);
cv::imwrite(strSavePath_out, blockBin);
// 父目录由异步存图器按需创建
CheckUtil::SaveImgAsync(strSavePath_in, block);
CheckUtil::SaveImgAsync(strSavePath_out, blockBin);
}
}
}
@ -528,6 +508,9 @@ void CTcsCheck::ProcessImages(bool bDrawResult)
Process(bDrawResult);
}
// 异步存图收尾:确保队列里的调试图全部落盘后再返回
CheckUtil::WaitSaveImgDone();
}

Loading…
Cancel
Save