Files
eUi/include/opencvPaintAlgo.hpp
T
RCiRoot 3c5e6487f4 V20251231-2.3.3
增加了rpcFIns的boos改版,在发送数据量较多时表现良好。(测试)
增加了存图时会将路径写入到单独的数据库中。(测试)
 增加了chainWatcher用于监控线程是否异常退出或未启动。(测试)
在赢合预设中进站ng之类的会发送4到结果地址。
TextRendererZH类修改为可暂存需要绘画/打印的内容,并在需要的时候统一打印。
参数修改界面新增能隐藏预览中部分分类的功能。
优化程序结构。
2026-01-01 00:49:11 +08:00

352 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#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
#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 TextRendererBase {
public:
TextRendererZH(int width, int height, int fontSize = 20, const std::wstring& fontName = L"微软雅黑")
: width_(width), height_(height), fontSize_(fontSize), fontName_(fontName)
{
QMutexLocker locker(&mutex_);
screenDC_ = GetDC(nullptr);
if (!screenDC_) {
throw std::runtime_error("Failed to get screen DC");
}
createResources();
}
~TextRendererZH() {
QMutexLocker locker(&mutex_);
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)) override {
QMutexLocker locker(&mutex_);
if (image.channels() != 3)
throw std::runtime_error("TextRendererZH only supports 3-channel BGR images.");
if (image.cols != width_ || image.rows != height_) {
resize_unsafe(image.cols, image.rows);
}
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);
}
private:
void resize_unsafe(int width, int height) {
if (width == width_ && height == height_) return;
releaseResources();
width_ = width;
height_ = height;
createResources();
}
public:
struct pointPaintJob {
cv::Point center;
cv::Scalar color;
};
void appendPaintJob(cv::Point center, cv::Scalar color) {
QMutexLocker locker(&mutex_);
pointPaintJobs.push_back(pointPaintJob{ center,color });
}
struct contourPaintJob {
std::vector<cv::Point> contour;
cv::Scalar color;
int thikness;
};
void appendPaintJob(std::vector<cv::Point> contour, cv::Scalar color, int thikness) {
QMutexLocker locker(&mutex_);
contourPaintJobs.push_back(contourPaintJob{ contour,color,thikness });
}
struct linePaintJob {
cv::Point p1;
cv::Point p2;
cv::Scalar color;
int thikness;
};
void appendPaintJob(cv::Point p1, cv::Point p2, cv::Scalar color, int thikness) {
QMutexLocker locker(&mutex_);
linePaintJobs.push_back(linePaintJob{ p1,p2,color,thikness });
}
struct circleAndCorssPaintJob {
cv::Point center;
double radius;
cv::Scalar color;
int thikness;
int type;
};
//type0 圆 type1 十字
void appendPaintJob(cv::Point center, double radius,cv::Scalar color, int thikness,int type) {
QMutexLocker locker(&mutex_);
circleAndCorssPaintJobs.push_back(circleAndCorssPaintJob{ center,radius,color,thikness,type });
}
struct rectPaintJob {
cv::Rect rect;
cv::Scalar color;
int thikness;
};
void appendPaintJob(cv::Rect rect, cv::Scalar color,int thikness) {
QMutexLocker locker(&mutex_);
rectPaintJobs.push_back(rectPaintJob{ rect,color,thikness });
}
struct textPaintJob {
std::string text;
cv::Point p;
cv::Scalar color;
};
void appendPaintJob(std::string text, cv::Point p, cv::Scalar color) {
QMutexLocker locker(&mutex_);
textPaintJobs.push_back(textPaintJob{ text,p,color });
}
void excusePaintJobs(cv::Mat& image,double resizeRate = 1.0) {
QMutexLocker locker(&mutex_);
if (textPaintJobs.empty() && pointPaintJobs.empty() && circleAndCorssPaintJobs.empty() && rectPaintJobs.empty() && contourPaintJobs.empty() && linePaintJobs.empty()) return;
if (image.channels() != 3)
throw std::runtime_error("TextRendererZH only supports 3-channel BGR images.");
if (image.cols != width_ || image.rows != height_) {
resize_unsafe(image.cols, image.rows);
}
// 绘制点
for (const auto& job : pointPaintJobs) {
cv::circle(image, job.center, 1, job.color, -1);
}
// 绘制圆或十字
for (const auto& job : circleAndCorssPaintJobs) {
if(job.type == 0)
cv::circle(image, job.center, job.radius, job.color, job.thikness);
else
cv::drawMarker(image, job.center, job.color, cv::MARKER_CROSS, job.radius, job.thikness);
}
// 绘制点集
for (const auto& job : contourPaintJobs) {
cv::drawContours(image, { job.contour }, 0, job.color, job.thikness);
}
// 绘制线段
for (const auto& job : linePaintJobs) {
cv::line(image, job.p1, job.p2, job.color, job.thikness);
}
// 绘制矩形
for (const auto& job : rectPaintJobs) {
cv::rectangle(image, job.rect, job.color, job.thikness);
}
// 绘制文本(需要GDI处理)
if (!textPaintJobs.empty()) {
// 只进行一次颜色转换和数据拷贝
cv::Mat img;
cv::cvtColor(image, img, cv::COLOR_BGR2BGRA);
memcpy(pBits_, img.data, img.total() * img.elemSize());
SetBkMode(memDC_, TRANSPARENT);
// 批量绘制所有文本
for (const auto& job : textPaintJobs) {
SetTextColor(memDC_, RGB(job.color[2], job.color[1], job.color[0]));
std::wstring wtext = utf8ToWstring(job.text);
TextOutW(memDC_, job.p.x, job.p.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);
}
// 清空已执行的任务
contourPaintJobs.clear();
linePaintJobs.clear();
pointPaintJobs.clear();
circleAndCorssPaintJobs.clear();
rectPaintJobs.clear();
textPaintJobs.clear();
}
private:
void createResources() {
memDC_ = CreateCompatibleDC(screenDC_);
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);
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());
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;
// 任务队列
std::vector<textPaintJob> textPaintJobs;
std::vector<pointPaintJob> pointPaintJobs;
std::vector<circleAndCorssPaintJob> circleAndCorssPaintJobs;
std::vector<linePaintJob> linePaintJobs;
std::vector<contourPaintJob> contourPaintJobs;
std::vector<rectPaintJob> rectPaintJobs;
// 线程保护
mutable QMutex mutex_;
};
#pragma endregion