Files
eUi/src/core/service/imgSaveThreadManager.h
T
QingYuan f9540b528c 增加missionSettings参数分类
增加paramEditDialog左侧窗口,根据分类显示不同参数
2025-11-14 10:29:13 +08:00

384 lines
14 KiB
C++

#include <qthread.h>
#include <opencv.hpp>
#include <qqueue.h>
#include <qdatetime.h>
#include <algorithm>
#include <string>
#include <vector>
#include <cctype>
#include <detData.h>
#include <paramSyncManager.h>
#include <global.h>
enum imgSaveThreadType {
oriImg,
resImg,
segImg,
clsImg
};
class ImageSavePathFormatter {
public:
QString rootDir = "";
QString genPath(std::shared_ptr<detData> data, imgSaveThreadType type, QString customPath = "", std::vector<std::pair <QString, QString>>customParams = std::vector<std::pair <QString, QString>>()){
QString pathTemplate = !customPath.isEmpty() ? customPath : defaultPath;
QString result = pathTemplate;
// 替换日期时间占位符
QDateTime now = QDateTime::currentDateTime();
result.replace("{yyyy-MM-dd}", now.toString("yyyy-MM-dd"));
result.replace("{HH}", QString("%1").arg(now.time().hour(), 2, 10, QChar('0')));
result.replace("{mm}", QString("%1").arg(now.time().minute(), 2, 10, QChar('0')));
result.replace("{ss}", QString("%1").arg(now.time().second(), 2, 10, QChar('0')));
// 替换图片类型占位符
static const QMap<imgSaveThreadType, QString> typeMap{
{ oriImg, "原图" },
{ resImg, "结果图" },
{ segImg, "结果图" },
{ clsImg, "结果图" }
};
result.replace("{imageType}", typeMap.value(type, "Unknown"));
// 替换检测结果占位符
QString processResult;
if (!data->aiDetOK && !data->halconOK) processResult = "NG";
else if (!data->aiDetOK) processResult = "aiNG";
else if (!data->halconOK) processResult = "halconNG";
else processResult = "OK";
result.replace("{processResult}", processResult);
// 替换SN/设备信息占位符
QString snCode = (data->SNcode.size() > 0 && data->SNcode.size() < 50)
? QString::fromStdString(repairWindowsFileName(data->SNcode))
: QString::fromStdString(repairWindowsFileName(data->cameraKeyName));
result.replace("{SN}", snCode);
result.replace("{cameraName}", QString::fromStdString(data->cameraKeyName));
// 替换根目录占位符
result.replace("{imgSaveRootDir}", rootDir.isEmpty() ? "." : rootDir);
// 替换根目录占位符
result.replace("{saveObjectName}", data->saveObjectName.isEmpty() ? "" : data->saveObjectName);
// 确保路径以/结尾
if (!result.endsWith("/") && !result.endsWith("\\")) {
result += "/";
}
for (auto& pair : customParams) {
result.replace(pair.first, pair.second);
}
return result;
}
std::string genFileName(std::shared_ptr<detData> data, imgSaveThreadType type, int index = 0, QString customFileName = "", std::vector<std::pair <QString, QString>> customParams = std::vector<std::pair <QString, QString>>(), const std::string& format = "bmp") {
QString fileTemplate = !customFileName.isEmpty() ? customFileName : defaultFileName;
QString result = fileTemplate;
// 替换SN/设备信息占位符
QString snCode = (data->SNcode.size() > 0 && data->SNcode.size() < 50)
? QString::fromStdString(repairWindowsFileName(data->SNcode))
: QString::fromStdString(repairWindowsFileName(data->cameraKeyName));
result.replace("{SN}", snCode);
result.replace("{cameraName}", QString::fromStdString(data->cameraKeyName));
// 替换索引占位符
result.replace("{index}", QString::number(index));
// 替换时间戳占位符
QDateTime now = QDateTime::currentDateTime();
result.replace("{yyyy-MM-dd}", now.toString("yyyy-MM-dd"));
result.replace("{HH-mm-ss}", now.toString("HH-mm-ss"));
result.replace("{ms}", QString("%1").arg(now.time().msec(), 3, 10, QChar('0')));
// 替换图片类型占位符
static const QMap<imgSaveThreadType, QString> typeMap{
{ oriImg, "ORI" },
{ resImg, "RES" },
{ segImg, "SEG" },
{ clsImg, "CLS" }
};
result.replace("{imageType}", typeMap.value(type, "UNKNOWN"));
for (auto& pair : customParams) {
result.replace(pair.first, pair.second);
}
return result.toLocal8Bit().toStdString() + "." + format;
}
std::vector<std::string> genFileNames(std::shared_ptr<detData> data, imgSaveThreadType type, std::vector<cv::Mat>& imgs) {
std::vector<std::string> result;
switch (type) {
case oriImg:
for (int i = 0; i < imgs.size(); i++) {
if (i < data->oriFormat.size())
result.push_back(genFileName(data, type, i, data->customFileName, data->customImageSaveTemplateParams, data->oriFormat[i]));
else
result.push_back(genFileName(data, type, i, data->customFileName, data->customImageSaveTemplateParams));
}
break;
case resImg:
for (int i = 0; i < imgs.size(); i++) {
result.push_back(genFileName(data, type, i, data->customFileName, data->customImageSaveTemplateParams, "jpg"));
}
break;
default:
for (int i = 0; i < imgs.size(); i++) {
result.push_back(genFileName(data, type, i, data->customFileName, data->customImageSaveTemplateParams));
}
}
return result;
}
std::vector<std::string> genClsFileNames(std::shared_ptr<detData> data, imgSaveThreadType type, std::vector<std::pair<std::string, cv::Mat>>& imgs, QString customFileName = "") {
std::vector<std::string> result;
for (int i = 0; i < imgs.size(); i++) {
result.push_back(genFileName(data, type, i, defaultFileName, data->customImageSaveTemplateParams));
}
return result;
}
// 清理Windows文件名/文件夹名中不支持的字符
std::string repairWindowsFileName(const std::string& input) {
std::string result = input;
// Windows文件名/文件夹名不能包含以下字符: \ / : * ? " < > |
// 以及控制字符(ASCII 0-31)
std::string invalidChars = "\\/:*?\"<>|";
// 移除所有不合法字符
for (char invalidChar : invalidChars) {
result.erase(std::remove(result.begin(), result.end(), invalidChar), result.end());
}
// 移除控制字符(ASCII 0-31)
result.erase(std::remove_if(result.begin(), result.end(),
[](char c) { return c >= 0 && c <= 31; }), result.end());
// 移除首尾空格和点号
while (!result.empty() && (result.front() == ' ' || result.front() == '.')) {
result.erase(result.begin());
}
while (!result.empty() && (result.back() == ' ' || result.back() == '.')) {
result.pop_back();
}
// 如果结果为空,提供默认名称
if (result.empty()) {
result = "unnamed";
}
return result;
}
private:
QString defaultPath = "{imgSaveRootDir}/{yyyy-MM-dd}/{saveObjectName}{imageType}/{HH}H/{processResult}/{SN}/";
QString defaultFileName = "{SN}_{cameraName}_{imageType}_{index}_{HH-mm-ss}-{ms}";
};
struct saveJob {
saveJob(std::string jobNameIn) :jobName(jobNameIn) {}
std::string jobName;
QQueue<std::pair<std::string, cv::Mat>> imgQueue;
};
class imgSaveThread : public QThread {
public:
enum type {
normal,
priority
};
QMutex mutex;
std::vector<int> compressionParams{ cv::IMWRITE_PNG_COMPRESSION ,9 };
QQueue< std::shared_ptr<saveJob>>priorityQueue;
QQueue< std::shared_ptr<saveJob>>defaultQueue;
int imgRemainCount = 0;
void run() {
while (true) {
QQueue<std::pair<std::string, cv::Mat>> localQueue;
std::shared_ptr<saveJob> job = nullptr;
// 优先处理 priorityQueue
{
QMutexLocker locker(&mutex);
if (!priorityQueue.isEmpty()) {
job = priorityQueue.dequeue();
}
else if (!defaultQueue.isEmpty()) {
job = defaultQueue.dequeue();
}
}
// 没有任务就跳出
if (job == nullptr) {
break;
}
qDebug() << "saveJob start" << QString::fromStdString(job->jobName) << "remain" << imgRemainCount;
imgRemainCount -= job->imgQueue.size();
localQueue.swap(job->imgQueue);
// 批量保存
while (!localQueue.isEmpty()) {
auto data = std::move(localQueue.dequeue());
#if saveImgEnable
cv::imwrite(data.first, data.second);
#endif
}
qDebug() << "saveJob end " << QString::fromStdString(job->jobName);
}
}
void addJob(std::shared_ptr<saveJob> job, type type_ = normal) {
QMutexLocker locker(&mutex);
switch (type_) {
case normal:
defaultQueue.append(job);
break;
case priority:
priorityQueue.append(job);
break;
}
imgRemainCount += job->imgQueue.size();
qDebug() << "savejob enqueue" << QString::fromStdString(job->jobName) << "queue size" << priorityQueue.size() << "|" << defaultQueue.size() << " imgRemain" << imgRemainCount;
this->start();
}
int queueSize() {
QMutexLocker locker(&mutex);
int size = defaultQueue.size();
return size;
}
};
class imgSaveThreadManager {
public:
imgSaveThreadManager(std::string pathIn, paramSyncManager* paramSyncManagerIn) {
_ImageSavePathFormatter.rootDir = QString::fromStdString(pathIn);
_paramSyncManager = paramSyncManagerIn;
saveOri = _paramSyncManager->get(_paramSyncManager->getGroup(0), "存储原图", true).toBool();
saveRes = _paramSyncManager->get(_paramSyncManager->getGroup(0), "存储结果图", true).toBool();
saveSeg = _paramSyncManager->get(_paramSyncManager->getGroup(0), "存储Seg图", true).toBool();
saveCls = _paramSyncManager->get(_paramSyncManager->getGroup(0), "存储Cls图", true).toBool();
}
~imgSaveThreadManager() {};
imgSaveThread saveThread;
void saveImage(std::shared_ptr <detData> data) {
if (getParamTimer.elapsed() > 5000) {
saveOri = _paramSyncManager->get(_paramSyncManager->getGroup(0), "存储原图", true).toBool();
saveRes = _paramSyncManager->get(_paramSyncManager->getGroup(0), "存储结果图", true).toBool();
saveSeg = _paramSyncManager->get(_paramSyncManager->getGroup(0), "存储Seg图", true).toBool();
saveCls = _paramSyncManager->get(_paramSyncManager->getGroup(0), "存储Cls图", true).toBool();
getParamTimer.restart();
}
std::shared_ptr<saveJob> oriJob(new saveJob("[ORI_JOB]" + data->SNcode));
std::shared_ptr<saveJob> resJob(new saveJob("[RES_JOB]" + data->SNcode));
QString oriSavePath = _ImageSavePathFormatter.genPath(data, oriImg, data->customPath, data->customImageSaveTemplateParams);//获取文件夹名
if (saveOri == true || saveSeg == true || saveCls == true) {
createDirectoryIfNotExists(oriSavePath);//不存在此文件夹则创建
if (data->oriImgs.size() != 0 && saveOri == true) {
std::vector<std::string>fileNames = _ImageSavePathFormatter.genFileNames(data, oriImg, data->oriImgs);//按规则为文件创建命名
packageData(oriJob->imgQueue, oriSavePath.toLocal8Bit().toStdString(), fileNames, data->oriImgs);//打包数据
}
//SEG原图
if (data->segOriginImgs.size() != 0 && saveSeg == true) {
std::vector<std::string>fileNames = _ImageSavePathFormatter.genFileNames(data, segImg, data->segOriginImgs);//按规则为文件创建命名
packageData(resJob->imgQueue, oriSavePath.toLocal8Bit().toStdString(), fileNames, data->segOriginImgs);//打包数据
}
//CLS原图
if (data->clsOriginImgs.size() != 0 && saveCls == true) {
std::vector<std::string>fileNames = _ImageSavePathFormatter.genClsFileNames(data, clsImg, data->clsOriginImgs);//按规则为文件创建命名
packageData(resJob->imgQueue, oriSavePath.toLocal8Bit().toStdString(), fileNames, data->clsOriginImgs);//打包数据
}
}
QString resSavePath = _ImageSavePathFormatter.genPath(data, resImg, data->customPath, data->customImageSaveTemplateParams);//获取文件夹名
if (saveRes == true){
createDirectoryIfNotExists(resSavePath);//不存在此文件夹则创建
//结果图保存
if (data->resultImgs.size() != 0) {
std::vector<std::string>fileNames = _ImageSavePathFormatter.genFileNames(data, resImg, data->resultImgs);//按规则为文件创建命名
packageData(resJob->imgQueue, resSavePath.toLocal8Bit().toStdString(), fileNames, data->resultImgs);//打包数据
}
}
if (oriJob->imgQueue.size() != 0)
saveThread.addJob(oriJob);
if (resJob->imgQueue.size() != 0)
saveThread.addJob(resJob, imgSaveThread::priority);
//额外储存NG文件夹
//std::string additionalSaveNgImgPath = genDirName(data, additionalSaveNgImg);
//std::vector<std::string>additionalSaveNgImgNames = genFileNames(data, additionalSaveNgImg);
//if (additionalSaveNgImgNames.size() > 0)
// createDirectoryIfNotExists(additionalSaveNgImgPath);//不存在此文件夹则创建
//int additionalSaveNgImgNamesIndex = 0;
//for (int index = 0; index < data->segOriginImgs.size()&& additionalSaveNgImgNames.size() > additionalSaveNgImgNamesIndex; index++)
//{
// saveThread.addData(additionalSaveNgImgPath + additionalSaveNgImgNames[additionalSaveNgImgNamesIndex], data->segOriginImgs[index]);
// additionalSaveNgImgNamesIndex++;
//}
//for (int index = 0; index < data->oriImgs.size() && additionalSaveNgImgNames.size() > additionalSaveNgImgNamesIndex; index++)
//{
// saveThread.addData(additionalSaveNgImgPath + additionalSaveNgImgNames[additionalSaveNgImgNamesIndex], data->oriImgs[index]);
// additionalSaveNgImgNamesIndex++;
//}
//for (int index = 0; index < data->resultImgs.size() && additionalSaveNgImgNames.size() > additionalSaveNgImgNamesIndex; index++)
//{
// saveThread.addData(additionalSaveNgImgPath + additionalSaveNgImgNames[additionalSaveNgImgNamesIndex], data->resultImgs[index]);
// additionalSaveNgImgNamesIndex++;
//}
}
private:
bool saveOri = true, saveRes = true, saveSeg = true, saveCls = true;
QElapsedTimer getParamTimer;//防止加锁次数过多导致卡顿
paramSyncManager* _paramSyncManager;
ImageSavePathFormatter _ImageSavePathFormatter;
void packageData(QQueue<std::pair<std::string, cv::Mat>>& jobQueue, std::string imgSavaPath, std::vector<std::string> fileNames, std::vector<cv::Mat>& mats) {
if (fileNames.size() != mats.size())
qWarning() << "检测到存图文件名数量与图片数量不符";
for (int index = 0; index < fileNames.size() && index < mats.size(); index++) {
try {
if (mats[index].data == nullptr) {
qDebug() << "在存图线程中发现空指针";
continue;
}
}
catch (...) {
qDebug() << "在存图线程中发现无效图像";
continue;
}
cv::Mat img;
mats[index].copyTo(img);
jobQueue.enqueue(std::make_pair(imgSavaPath + fileNames[index], img));
}
}
void packageData(QQueue<std::pair<std::string, cv::Mat>>& jobQueue, std::string imgSavaPath, std::vector<std::string> fileNames, std::vector<std::pair<std::string, cv::Mat>>& mats) {
if (fileNames.size() != mats.size())
qWarning() << "检测到存图文件名数量与图片数量不符";
for (int index = 0; index < fileNames.size() && index < mats.size(); index++) {
try {
if (mats[index].second.data == nullptr) {
qDebug() << "在存图线程中发现空指针";
continue;
}
}
catch (...) {
qDebug() << "在存图线程中发现无效图像";
continue;
}
cv::Mat img;
mats[index].second.copyTo(img);
jobQueue.enqueue(std::make_pair(imgSavaPath + fileNames[index], img));
}
}
};
#pragma once