379 lines
13 KiB
C++
379 lines
13 KiB
C++
#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
|
||
|