#pragma once #include #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(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) { screenDC_ = GetDC(nullptr); 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)) override { if (image.channels() != 3) throw std::runtime_error("TextRendererZH only supports 3-channel BGR images."); if (image.cols != width_ || image.rows != height_) { resize(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) { if (width == width_ && height == height_) return; releaseResources(); width_ = width; height_ = height; createResources(); } 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; }; #pragma endregion