Files
eUi/include/opencvPaintAlgo.hpp
T
RCiRoot c48cf25896 V20260112-2.3.7
在更多关键的地方添加了错误捕获。
CsvWriter修改为DataRecorder。
打包图片不再开启新线程处理。
部分错误修复。
2026-01-21 09:41:31 +08:00

379 lines
13 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
//画箭头 长度为负值时为反方向
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);
}
}
}
#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