增加halconGuard类,用于防止halcon算法的输入输出和程序不一致而引起的报错,并且会检查算法文件是否存在,如不存在则会弹窗提示。 增加handleAiGpuThreadGuard类,用于防止因重复调用同一个模型而引起的报错,并且支持不停机更新模型,支持初始化时检测模型文件是否存在。 增加内存占用显示控件。 用户管理增加root权限,默认密码为dseuiroot,模型热更新时需要此权限。 支持缓存界面上的检测数据。 相机模块新增在回调后的指定时间没有取图时则清空容器的功能。 增加等待结果汇总功能,在启用时同一个SN的图片会等待总结果后再进行存图。 存图线程修改为多线程编码,单线程储存,提高IO效率。并且增加根据内存占用的百分比更新存图逻辑的功能,默认占用超过70%不储存OK原图,超过80%不储存原图,超过90%时不储存所有图片。
527 lines
18 KiB
C++
527 lines
18 KiB
C++
#pragma once
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <unordered_map>
|
|
#include <opencv.hpp>
|
|
#include <qmutex.h>
|
|
#pragma region 画叉
|
|
static void Cross(cv::Mat& image, const cv::Point& center, int length, int thickness, const cv::Scalar& color = cv::Scalar(255, 255, 255)) {
|
|
// 检查输入图像是否有效
|
|
if (image.empty()) {
|
|
throw std::invalid_argument("Input image is empty");
|
|
}
|
|
|
|
// 计算水平和垂直线段的起点和终点
|
|
cv::Point horizontalStart(center.x - length / 2, center.y);
|
|
cv::Point horizontalEnd(center.x + length / 2, center.y);
|
|
cv::Point verticalStart(center.x, center.y - length / 2);
|
|
cv::Point verticalEnd(center.x, center.y + length / 2);
|
|
|
|
// 绘制水平和垂直线段
|
|
cv::line(image, horizontalStart, horizontalEnd, color, thickness);
|
|
cv::line(image, verticalStart, verticalEnd, color, thickness);
|
|
}
|
|
#pragma endregion
|
|
//画箭头 长度为负值时为反方向
|
|
static void drawArrow(cv::Mat& Paint, const cv::Point& start, const int& length, const bool& horizonal, const cv::Scalar& color, const int& thinkess, const int& arrowSize) {
|
|
|
|
if (horizonal == true) {//线段是否水平
|
|
cv::Point end = cv::Point(start.x + length, start.y);
|
|
cv::line(Paint, start, end, color, thinkess);
|
|
if (length > 0) {//判断方向 往右
|
|
cv::line(Paint, end, cv::Point(end.x - arrowSize, end.y - arrowSize), color, thinkess);
|
|
cv::line(Paint, end, cv::Point(end.x - arrowSize, end.y + arrowSize), color, thinkess);
|
|
}
|
|
else {
|
|
cv::line(Paint, end, cv::Point(end.x + arrowSize, end.y - arrowSize), color, thinkess);
|
|
cv::line(Paint, end, cv::Point(end.x + arrowSize, end.y + arrowSize), color, thinkess);
|
|
}
|
|
}
|
|
else {//垂直线段
|
|
cv::Point end = cv::Point(start.x, start.y + length);
|
|
cv::line(Paint, start, end, color, thinkess);
|
|
if (length > 0) {//判断方向 往下
|
|
cv::line(Paint, end, cv::Point(end.x - arrowSize, end.y - arrowSize), color, thinkess);
|
|
cv::line(Paint, end, cv::Point(end.x + arrowSize, end.y - arrowSize), color, thinkess);
|
|
}
|
|
else {
|
|
cv::line(Paint, end, cv::Point(end.x - arrowSize, end.y + arrowSize), color, thinkess);
|
|
cv::line(Paint, end, cv::Point(end.x + arrowSize, end.y + arrowSize), color, thinkess);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drawRotatedRect(cv::Mat& img, cv::RotatedRect rotateRect, cv::Scalar color, int thickness, int linType)
|
|
{
|
|
std::vector<cv::Point2f> boxPts(4);
|
|
rotateRect.points(boxPts.data());
|
|
for (int mm = 0; mm < 4; mm++) {
|
|
cv::line(img, boxPts[mm], boxPts[(mm + 1) % 4], color, thickness, linType);
|
|
}
|
|
}
|
|
|
|
#pragma region 写中文
|
|
static std::wstring utf8ToWstring(const std::string& utf8Str) {
|
|
int len = MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, nullptr, 0);
|
|
if (len == 0) return L"";
|
|
std::wstring wstr(len, L'\0');
|
|
MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, &wstr[0], len);
|
|
wstr.pop_back(); // remove null terminator
|
|
return wstr;
|
|
}
|
|
// 在图像上绘制中文(仅 Windows,使用 GDI
|
|
static void putTextZH(cv::Mat& image, const std::string& text, cv::Point org, int fontSize = 20, cv::Scalar color = cv::Scalar(255, 0, 0)) {
|
|
if (image.channels() != 3)
|
|
throw std::runtime_error("Only supports 3-channel images");
|
|
|
|
// 转为 BGRA
|
|
cv::Mat img;
|
|
cv::cvtColor(image, img, cv::COLOR_BGR2BGRA);
|
|
|
|
HDC hdc = GetDC(nullptr);
|
|
HDC memDC = CreateCompatibleDC(hdc);
|
|
|
|
BITMAPINFO bmi = {};
|
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bmi.bmiHeader.biWidth = img.cols;
|
|
bmi.bmiHeader.biHeight = -img.rows;
|
|
bmi.bmiHeader.biPlanes = 1;
|
|
bmi.bmiHeader.biBitCount = 32;
|
|
bmi.bmiHeader.biCompression = BI_RGB;
|
|
|
|
void* pBits = nullptr;
|
|
HBITMAP hBitmap = CreateDIBSection(memDC, &bmi, DIB_RGB_COLORS, &pBits, nullptr, 0);
|
|
SelectObject(memDC, hBitmap);
|
|
memcpy(pBits, img.data, img.total() * img.elemSize());
|
|
|
|
HFONT hFont = CreateFontW(fontSize, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
|
|
GB2312_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
|
|
ANTIALIASED_QUALITY, DEFAULT_PITCH | FF_SWISS, L"微软雅黑");
|
|
SelectObject(memDC, hFont);
|
|
|
|
SetBkMode(memDC, TRANSPARENT);
|
|
SetTextColor(memDC, RGB(color[2], color[1], color[0]));
|
|
|
|
TextOutW(memDC, org.x, org.y, utf8ToWstring(text).c_str(), static_cast<int>(utf8ToWstring(text).length()));
|
|
|
|
GetDIBits(memDC, hBitmap, 0, img.rows, img.data, &bmi, DIB_RGB_COLORS);
|
|
|
|
cv::cvtColor(img, image, cv::COLOR_BGRA2BGR);
|
|
|
|
DeleteObject(hFont);
|
|
DeleteObject(hBitmap);
|
|
DeleteDC(memDC);
|
|
ReleaseDC(nullptr, hdc);
|
|
}
|
|
class TextRendererBase {
|
|
public:
|
|
virtual ~TextRendererBase() = default;
|
|
virtual void putText(cv::Mat& image, const std::string& text, cv::Point org, cv::Scalar color = cv::Scalar(255, 0, 0)) = 0;
|
|
};
|
|
//写中文
|
|
class TextRendererZH{
|
|
public:
|
|
TextRendererZH(const TextRendererZH&) = delete;
|
|
TextRendererZH& operator=(const TextRendererZH&) = delete;
|
|
|
|
TextRendererZH(TextRendererZH&&) noexcept = default;
|
|
TextRendererZH& operator=(TextRendererZH&&) noexcept = default;
|
|
TextRendererZH(int width, int height, int fontSize = 20, const std::wstring& fontName = L"微软雅黑")
|
|
: width_(width), height_(height), fontSize_(fontSize), fontName_(fontName)
|
|
{
|
|
if (width_ <= 0) width_ = 1;
|
|
if (height_ <= 0) height_ = 1;
|
|
if (fontSize_ <= 0) fontSize_ = 20;
|
|
|
|
screenDC_ = GetDC(nullptr);
|
|
if (!screenDC_) {
|
|
logMessage("TextRendererZH: 获取屏幕 DC 失败,文本绘制已禁用。");
|
|
return;
|
|
}
|
|
createResources();
|
|
}
|
|
|
|
~TextRendererZH() {
|
|
releaseResources();
|
|
if (screenDC_) ReleaseDC(nullptr, screenDC_);
|
|
}
|
|
|
|
void putText(cv::Mat& image, const std::string& text, cv::Point org, cv::Scalar color = cv::Scalar(255, 0, 0)) {
|
|
QMutexLocker locker(&mutex_);
|
|
|
|
if (text.empty()) return;
|
|
|
|
if (!screenDC_ || !memDC_ || !hBitmap_ || !pBits_ || !hFont_) {
|
|
logMessage("TextRendererZH: 资源未就绪,跳过文本绘制。");
|
|
return;
|
|
}
|
|
|
|
if (image.channels() != 3) {
|
|
logMessage("TextRendererZH: 仅支持 3 通道 BGR 图像,跳过文本绘制。");
|
|
return;
|
|
}
|
|
|
|
if (image.cols != width_ || image.rows != height_) {
|
|
resize_unsafe(image.cols, image.rows);
|
|
if (!memDC_ || !hBitmap_ || !pBits_ || !hFont_) {
|
|
logMessage("TextRendererZH: 尺寸调整失败,跳过文本绘制。");
|
|
return;
|
|
}
|
|
}
|
|
|
|
cv::Mat img;
|
|
cv::cvtColor(image, img, cv::COLOR_BGR2BGRA);
|
|
|
|
memcpy(pBits_, img.data, img.total() * img.elemSize());
|
|
|
|
SetBkMode(memDC_, TRANSPARENT);
|
|
SetTextColor(memDC_, RGB(color[2], color[1], color[0]));
|
|
|
|
std::wstring wtext = utf8ToWstring(text);
|
|
TextOutW(memDC_, org.x, org.y, wtext.c_str(), (int)wtext.length());
|
|
|
|
GetDIBits(memDC_, hBitmap_, 0, height_, img.data, &bmi_, DIB_RGB_COLORS);
|
|
|
|
cv::cvtColor(img, image, cv::COLOR_BGRA2BGR);
|
|
}
|
|
|
|
void resize(int width, int height) {
|
|
QMutexLocker locker(&mutex_);
|
|
resize_unsafe(width, height);
|
|
}
|
|
|
|
void setFontSize(int fontSize) {
|
|
if (fontSize <= 0) {
|
|
logMessage("TextRendererZH: 字体大小必须大于 0,保持当前字体大小。");
|
|
return;
|
|
}
|
|
|
|
QMutexLocker locker(&mutex_);
|
|
if (fontSize_ == fontSize) return;
|
|
|
|
fontSize_ = fontSize;
|
|
if (!screenDC_) return;
|
|
|
|
releaseResources();
|
|
createResources();
|
|
}
|
|
|
|
private:
|
|
void resize_unsafe(int width, int height) {
|
|
if (width <= 0 || height <= 0) {
|
|
logMessage("TextRendererZH: 目标图像尺寸非法,保持当前渲染尺寸。");
|
|
return;
|
|
}
|
|
if (width == width_ && height == height_) return;
|
|
|
|
releaseResources();
|
|
|
|
width_ = width;
|
|
height_ = height;
|
|
createResources();
|
|
}
|
|
|
|
private:
|
|
static void logMessage(const char* message) {
|
|
std::cout << message << std::endl;
|
|
}
|
|
|
|
void createResources() {
|
|
if (!screenDC_) {
|
|
logMessage("TextRendererZH: 屏幕 DC 不可用,无法创建资源。");
|
|
return;
|
|
}
|
|
|
|
memDC_ = CreateCompatibleDC(screenDC_);
|
|
if (!memDC_) {
|
|
logMessage("TextRendererZH: 创建内存 DC 失败。");
|
|
return;
|
|
}
|
|
|
|
memset(&bmi_, 0, sizeof(bmi_));
|
|
bmi_.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bmi_.bmiHeader.biWidth = width_;
|
|
bmi_.bmiHeader.biHeight = -height_;
|
|
bmi_.bmiHeader.biPlanes = 1;
|
|
bmi_.bmiHeader.biBitCount = 32;
|
|
bmi_.bmiHeader.biCompression = BI_RGB;
|
|
|
|
hBitmap_ = CreateDIBSection(memDC_, &bmi_, DIB_RGB_COLORS, &pBits_, nullptr, 0);
|
|
if (!hBitmap_ || !pBits_) {
|
|
logMessage("TextRendererZH: 创建位图资源失败。");
|
|
releaseResources();
|
|
return;
|
|
}
|
|
SelectObject(memDC_, hBitmap_);
|
|
|
|
hFont_ = CreateFontW(
|
|
fontSize_, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
|
|
GB2312_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
|
|
ANTIALIASED_QUALITY, DEFAULT_PITCH | FF_SWISS, fontName_.c_str());
|
|
if (!hFont_) {
|
|
logMessage("TextRendererZH: 创建字体资源失败。");
|
|
releaseResources();
|
|
return;
|
|
}
|
|
SelectObject(memDC_, hFont_);
|
|
}
|
|
|
|
void releaseResources() {
|
|
if (hFont_) {
|
|
DeleteObject(hFont_);
|
|
hFont_ = nullptr;
|
|
}
|
|
if (hBitmap_) {
|
|
DeleteObject(hBitmap_);
|
|
hBitmap_ = nullptr;
|
|
pBits_ = nullptr;
|
|
}
|
|
if (memDC_) {
|
|
DeleteDC(memDC_);
|
|
memDC_ = nullptr;
|
|
}
|
|
}
|
|
|
|
// UTF8 转宽字符串
|
|
static std::wstring utf8ToWstring(const std::string& str) {
|
|
if (str.empty()) return {};
|
|
int size_needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0);
|
|
std::wstring wstrTo(size_needed, 0);
|
|
MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &wstrTo[0], size_needed);
|
|
return wstrTo;
|
|
}
|
|
|
|
private:
|
|
int width_;
|
|
int height_;
|
|
int fontSize_;
|
|
std::wstring fontName_;
|
|
|
|
HDC screenDC_ = nullptr;
|
|
HDC memDC_ = nullptr;
|
|
HBITMAP hBitmap_ = nullptr;
|
|
void* pBits_ = nullptr;
|
|
BITMAPINFO bmi_{};
|
|
|
|
HFONT hFont_ = nullptr;
|
|
|
|
QMutex mutex_;
|
|
};
|
|
|
|
//缓存绘画数据在调用executePaintJobs统一绘画
|
|
class cvPainter {
|
|
public:
|
|
cvPainter(const int& fontSize = 50) {
|
|
defaultFontSize = fontSize;
|
|
}
|
|
cvPainter(const cvPainter&) = delete;
|
|
cvPainter& operator=(const cvPainter&) = delete;
|
|
// QMutex不可移动,禁用移动操作
|
|
cvPainter(cvPainter&&) = delete;
|
|
cvPainter& operator=(cvPainter&&) = delete;
|
|
~cvPainter() = default;
|
|
|
|
private:
|
|
int defaultFontSize = 50;
|
|
static void logMessage(const char* message) {
|
|
std::cout << message << std::endl;
|
|
}
|
|
|
|
public:
|
|
|
|
// 清空所有待执行任务
|
|
void clearPaintJobs() {
|
|
QMutexLocker locker(&mutex_);
|
|
contourPaintJobs.clear();
|
|
linePaintJobs.clear();
|
|
pointPaintJobs.clear();
|
|
circleAndCrossPaintJobs.clear();
|
|
rectPaintJobs.clear();
|
|
textPaintJobs.clear();
|
|
}
|
|
|
|
struct pointPaintJob {
|
|
cv::Point center;
|
|
cv::Scalar color;
|
|
};
|
|
void appendPaintJob(cv::Point center, cv::Scalar color) {
|
|
QMutexLocker locker(&mutex_);
|
|
pointPaintJobs.emplace_back(pointPaintJob{ center, color });
|
|
}
|
|
|
|
struct contourPaintJob {
|
|
std::vector<std::vector<cv::Point>> contour;
|
|
int i;
|
|
cv::Scalar color;
|
|
int thickness;
|
|
int lineType = cv::LINE_8;
|
|
cv::Mat hierarchy; // 用cv::Mat存储,避免cv::InputArray产生悬空引用
|
|
int maxLevel = INT_MAX;
|
|
cv::Point offset;
|
|
};
|
|
void appendPaintJob(std::vector<std::vector<cv::Point>> contour,
|
|
int i,
|
|
cv::Scalar color, int thickness,
|
|
int lineType = cv::LINE_8,
|
|
cv::InputArray hierarchy = cv::noArray(),
|
|
int maxLevel = INT_MAX, cv::Point offset = cv::Point()) {
|
|
QMutexLocker locker(&mutex_);
|
|
contourPaintJobs.emplace_back(contourPaintJob{
|
|
std::move(contour), i, color, thickness, lineType,
|
|
hierarchy.getMat(), maxLevel, offset });
|
|
}
|
|
|
|
struct linePaintJob {
|
|
cv::Point p1;
|
|
cv::Point p2;
|
|
cv::Scalar color;
|
|
int thickness;
|
|
};
|
|
void appendPaintJob(cv::Point p1, cv::Point p2, cv::Scalar color, int thickness) {
|
|
if (thickness <= 0) {
|
|
logMessage("cvPainter: 线段粗细必须大于 0,已忽略该绘制任务。");
|
|
return;
|
|
}
|
|
QMutexLocker locker(&mutex_);
|
|
linePaintJobs.emplace_back(linePaintJob{ p1, p2, color, thickness });
|
|
}
|
|
|
|
struct circleAndCrossPaintJob {
|
|
cv::Point center;
|
|
int radius; // int,与cv::circle / cv::drawMarker接口一致
|
|
cv::Scalar color;
|
|
int thickness;
|
|
int type; // 0=圆, 1=十字
|
|
};
|
|
// type=0:圆 type=1:十字
|
|
void appendPaintJob(cv::Point center, int radius, cv::Scalar color, int thickness, int type) {
|
|
if (radius <= 0) {
|
|
logMessage("cvPainter: 半径必须大于 0,已忽略该绘制任务。");
|
|
return;
|
|
}
|
|
if (thickness <= 0) {
|
|
logMessage("cvPainter: 圆形或十字的线宽必须大于 0,已忽略该绘制任务。");
|
|
return;
|
|
}
|
|
if (type != 0 && type != 1) {
|
|
logMessage("cvPainter: type 只能为 0(圆)或 1(十字),已忽略该绘制任务。");
|
|
return;
|
|
}
|
|
QMutexLocker locker(&mutex_);
|
|
circleAndCrossPaintJobs.emplace_back(circleAndCrossPaintJob{ center, radius, color, thickness, type });
|
|
}
|
|
|
|
struct rectPaintJob {
|
|
cv::Rect rect;
|
|
cv::Scalar color;
|
|
int thickness;
|
|
};
|
|
void appendPaintJob(cv::Rect rect, cv::Scalar color, int thickness) {
|
|
if (rect.empty()) {
|
|
logMessage("cvPainter: 矩形不能为空,已忽略该绘制任务。");
|
|
return;
|
|
}
|
|
if (thickness == 0) {
|
|
logMessage("cvPainter: 矩形线宽不能为 0,已忽略该绘制任务。");
|
|
return;
|
|
}
|
|
QMutexLocker locker(&mutex_);
|
|
rectPaintJobs.emplace_back(rectPaintJob{ rect, color, thickness });
|
|
}
|
|
|
|
struct textPaintJob {
|
|
std::string text;
|
|
cv::Point p;
|
|
cv::Scalar color;
|
|
int fontSize;
|
|
};
|
|
void appendPaintJob(const std::string& text, cv::Point p, cv::Scalar color, int fontSize = -1) {
|
|
if (text.empty()) return;
|
|
if (fontSize == -1)
|
|
fontSize = defaultFontSize;
|
|
QMutexLocker locker(&mutex_);
|
|
textPaintJobs.emplace_back(textPaintJob{ text, p, color, fontSize });
|
|
}
|
|
|
|
void executePaintJobs(cv::Mat& image) {
|
|
QMutexLocker locker(&mutex_);
|
|
|
|
if (textPaintJobs.empty() && pointPaintJobs.empty() && circleAndCrossPaintJobs.empty() &&
|
|
rectPaintJobs.empty() && contourPaintJobs.empty() && linePaintJobs.empty()) return;
|
|
|
|
if (image.empty()) {
|
|
logMessage("cvPainter: 输入图像为空,已保留当前绘制任务。");
|
|
return;
|
|
}
|
|
if (image.channels() != 3) {
|
|
logMessage("cvPainter: 仅支持 3 通道 BGR 图像,已保留当前绘制任务。");
|
|
return;
|
|
}
|
|
|
|
// 绘制点
|
|
for (const auto& job : pointPaintJobs) {
|
|
cv::circle(image, job.center, 1, job.color, -1);
|
|
}
|
|
|
|
// 绘制圆或十字
|
|
for (const auto& job : circleAndCrossPaintJobs) {
|
|
if (job.type == 0)
|
|
cv::circle(image, job.center, job.radius, job.color, job.thickness);
|
|
else
|
|
cv::drawMarker(image, job.center, job.color, cv::MARKER_CROSS, job.radius, job.thickness);
|
|
}
|
|
|
|
// 绘制轮廓
|
|
for (const auto& job : contourPaintJobs) {
|
|
cv::drawContours(image, job.contour, job.i, job.color, job.thickness,
|
|
job.lineType, job.hierarchy, job.maxLevel, job.offset);
|
|
}
|
|
|
|
// 绘制线段
|
|
for (const auto& job : linePaintJobs) {
|
|
cv::line(image, job.p1, job.p2, job.color, job.thickness);
|
|
}
|
|
|
|
// 绘制矩形
|
|
for (const auto& job : rectPaintJobs) {
|
|
cv::rectangle(image, job.rect, job.color, job.thickness);
|
|
}
|
|
|
|
// 使用 TextRendererZH 绘制文本,支持中文
|
|
for (const auto& job : textPaintJobs) {
|
|
auto it = textRenderers_.find(job.fontSize);
|
|
if (it == textRenderers_.end()) {
|
|
auto renderer = std::make_unique<TextRendererZH>(image.cols, image.rows, job.fontSize);
|
|
auto inserted = textRenderers_.emplace(job.fontSize, std::move(renderer));
|
|
it = inserted.first;
|
|
}
|
|
|
|
it->second->putText(image, job.text, job.p, job.color);
|
|
}
|
|
|
|
// 清空已执行的任务
|
|
contourPaintJobs.clear();
|
|
linePaintJobs.clear();
|
|
pointPaintJobs.clear();
|
|
circleAndCrossPaintJobs.clear();
|
|
rectPaintJobs.clear();
|
|
textPaintJobs.clear();
|
|
}
|
|
|
|
private:
|
|
std::vector<textPaintJob> textPaintJobs;
|
|
std::vector<pointPaintJob> pointPaintJobs;
|
|
std::vector<circleAndCrossPaintJob> circleAndCrossPaintJobs;
|
|
std::vector<linePaintJob> linePaintJobs;
|
|
std::vector<contourPaintJob> contourPaintJobs;
|
|
std::vector<rectPaintJob> rectPaintJobs;
|
|
|
|
// 按字体大小缓存文本渲染器
|
|
std::unordered_map<int, std::unique_ptr<TextRendererZH>> textRenderers_;
|
|
|
|
mutable QMutex mutex_;
|
|
};
|
|
#
|
|
#pragma endregion
|
|
|