Files
eUi/include/opencvPaintAlgo.hpp
T
RCiRoot 97462c8aab On main: V20260416-2.5.1
增加halconGuard类,用于防止halcon算法的输入输出和程序不一致而引起的报错,并且会检查算法文件是否存在,如不存在则会弹窗提示。
增加handleAiGpuThreadGuard类,用于防止因重复调用同一个模型而引起的报错,并且支持不停机更新模型,支持初始化时检测模型文件是否存在。
增加内存占用显示控件。
用户管理增加root权限,默认密码为dseuiroot,模型热更新时需要此权限。
支持缓存界面上的检测数据。
相机模块新增在回调后的指定时间没有取图时则清空容器的功能。
增加等待结果汇总功能,在启用时同一个SN的图片会等待总结果后再进行存图。
存图线程修改为多线程编码,单线程储存,提高IO效率。并且增加根据内存占用的百分比更新存图逻辑的功能,默认占用超过70%不储存OK原图,超过80%不储存原图,超过90%时不储存所有图片。
2026-04-26 12:25:13 +08:00

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