Files
eUi/src/core/service/imgSaveThreadManager.h
T
2025-10-29 17:27:58 +08:00

319 lines
12 KiB
C++

#include <qthread.h>
#include <opencv.hpp>
#include <qqueue.h>
#include <qdatetime.h>
#include <backStruct.h>
#include <detData.h>
enum imgSaveThreadType {
oriImg,
resImg,
segImg,
clsImg
};
class ImageSavePathFormatter {
public:
QString rootDir = "";
std::string 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(data->SNcode)
: QString::fromStdString(data->cameraKeyName);
result.replace("{SN}", snCode);
result.replace("{cameraName}", QString::fromStdString(data->cameraKeyName));
// 替换根目录占位符
result.replace("{imgSaveRootDir}", rootDir.isEmpty() ? "." : rootDir);
// 确保路径以/结尾
if (!result.endsWith("/") && !result.endsWith("\\")) {
result += "/";
}
for (auto& pair : customParams) {
result.replace(pair.first, pair.second);
}
return result.toLocal8Bit().toStdString();
}
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(data->SNcode)
: QString::fromStdString(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("{timestamp}", QString::number(now.toMSecsSinceEpoch()));
// 替换图片类型占位符
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, customFileName));
}
return result;
}
private:
QString defaultPath = "{imgSaveRootDir}/{yyyy-MM-dd}/{HH}H/{imageType}/{processResult}/{SN}/";
QString defaultFileName = "{SN}_{cameraName}_{index}";
QString defaultClsFileName = "{SN}_{cameraName}_{param1}_{index}";
};
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) {
_ImageSavePathFormatter.rootDir = QString::fromStdString(pathIn);
}
~imgSaveThreadManager() {};
imgSaveThread saveThread;
void saveImage(std::shared_ptr <detData> data) {
std::string oriSavePath = _ImageSavePathFormatter.genPath(data, oriImg, data->customPath, data->customImageSaveTemplateParams);//获取文件夹名
createDirectoryIfNotExists(oriSavePath);//不存在此文件夹则创建
if (data->oriImgs.size() != 0) {
std::vector<std::string>fileNames = _ImageSavePathFormatter.genFileNames(data, oriImg, data->oriImgs);//按规则为文件创建命名
std::shared_ptr<saveJob> job (new saveJob("[ORI_JOB]" + data->SNcode));
packageData(job->imgQueue, oriSavePath, fileNames, data->oriImgs);//打包数据
saveThread.addJob(job);
}
std::string resSavePath = _ImageSavePathFormatter.genPath(data, resImg, data->customPath, data->customImageSaveTemplateParams);//获取文件夹名
createDirectoryIfNotExists(resSavePath);//不存在此文件夹则创建
std::shared_ptr<saveJob> job(new saveJob("[RES_JOB]"+ data->SNcode));
//结果图保存
if (data->resultImgs.size() != 0) {
std::vector<std::string>fileNames = _ImageSavePathFormatter.genFileNames(data, resImg, data->resultImgs);//按规则为文件创建命名
packageData(job->imgQueue, resSavePath,fileNames, data->resultImgs);//打包数据
}
//SEG原图
if (data->segOriginImgs.size() != 0) {
std::vector<std::string>fileNames = _ImageSavePathFormatter.genFileNames(data, segImg, data->segOriginImgs);//按规则为文件创建命名
packageData(job->imgQueue, oriSavePath, fileNames, data->segOriginImgs);//打包数据
}
//CLS原图
if (data->clsOriginImgs.size() != 0) {
std::vector<std::string>fileNames = _ImageSavePathFormatter.genClsFileNames(data, clsImg, data->clsOriginImgs);//按规则为文件创建命名
packageData(job->imgQueue, oriSavePath, fileNames, data->clsOriginImgs);//打包数据
}
saveThread.addJob(job, 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:
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