#pragma once #include #include #include #include #include #include #include #include namespace cmvr::common { /** * @brief 图像像素坐标。 * * 坐标系约定: * - `u`:图像列坐标,向右为正,单位像素。 * - `v`:图像行坐标,向下为正,单位像素。 */ struct Pixel { int u{0}; // 图像像素列坐标,单位像素。 int v{0}; // 图像像素行坐标,单位像素。 }; /** * @brief BGR 颜色定义。 */ struct Color { uint8_t b{0}; // 蓝色通道,范围 [0, 255]。 uint8_t g{255}; // 绿色通道,范围 [0, 255]。 uint8_t r{0}; // 红色通道,范围 [0, 255]。 /** * @brief 转为 OpenCV 的 `cv::Scalar`。 */ cv::Scalar toCvScalar() const { return cv::Scalar(static_cast(b), static_cast(g), static_cast(r)); } /** * @brief 预定义红色。 */ static Color red() { return Color{0, 0, 255}; } /** * @brief 预定义绿色。 */ static Color green() { return Color{0, 255, 0}; } /** * @brief 预定义蓝色。 */ static Color blue() { return Color{255, 0, 0}; } /** * @brief 预定义黄色。 */ static Color yellow() { return Color{0, 255, 255}; } /** * @brief 预定义白色。 */ static Color white() { return Color{255, 255, 255}; } /** * @brief 预定义黑色。 */ static Color black() { return Color{0, 0, 0}; } }; /** * @brief 图像上的文本叠加配置。 */ struct TextOverlay { std::string text; // 需要显示的字符串内容。 Pixel position_px{}; // 文本左下角在图像像素坐标系中的位置。 Color color{Color::green()}; // 文本颜色。 double font_scale{0.7}; // OpenCV 字体缩放系数。 int thickness{2}; // 文本线宽,单位像素。 int font_face{cv::FONT_HERSHEY_SIMPLEX}; // OpenCV 字体类型。 bool draw_background{false}; // 是否绘制文本背景框。 Color background_color{Color::black()}; // 文本背景框颜色。 int background_padding_px{2}; // 文本背景框四周留白,单位像素。 }; /** * @brief 图像上的点叠加配置。 */ struct PointOverlay { Pixel position_px{}; // 点中心在图像像素坐标系中的位置。 Color color{Color::red()}; // 点颜色。 int radius_px{5}; // 圆点半径,单位像素。 int thickness{2}; // 线宽,`-1` 表示填充。 std::string label; // 点旁边附带显示的字符串;为空时不显示。 Pixel label_offset_px{8, -8}; // 标签相对点中心的像素偏移。 double label_font_scale{0.6}; // 点标签字体缩放系数。 int label_thickness{2}; // 点标签线宽,单位像素。 }; /** * @brief 图像上的圆圈叠加配置。 */ struct CircleOverlay { Pixel center_px{}; // 圆心在图像像素坐标系中的位置。 int radius_px{10}; // 圆半径,单位像素。 Color color{Color::yellow()}; // 圆圈颜色。 int thickness{2}; // 圆圈线宽,单位像素,`-1` 表示填充。 }; /** * @brief 图像上的十字叠加配置。 */ struct CrossOverlay { Pixel center_px{}; // 十字中心在图像像素坐标系中的位置。 int arm_length_px{8}; // 单侧十字臂长度,单位像素。 Color color{Color::blue()}; // 十字颜色。 int thickness{2}; // 十字线宽,单位像素。 }; /** * @brief 通用图像叠加显示接口。 * * 该接口不依赖任何感知或跟踪类,只抽象三类输入: * - 待显示的图像数据; * - 文本叠加项; * - 点叠加项。 */ class ImageDisplay { public: virtual ~ImageDisplay() = default; /** * @brief 设置窗口名称。 * @param window_name 显示窗口名称。 */ virtual void setWindowName(const std::string& window_name) = 0; /** * @brief 设置待显示的图像。 * @param image 输入图像,支持灰度图、BGR 图或 BGRA 图。 */ virtual void setImage(const cv::Mat& image) = 0; /** * @brief 清空所有叠加项,但保留当前图像。 */ virtual void clearOverlays() = 0; /** * @brief 添加文本叠加项。 * @param overlay 文本叠加配置。 */ virtual void showText(const TextOverlay& overlay) = 0; /** * @brief 按位置、字符串、颜色和大小直接添加文本。 * @param text 需要显示的字符串内容。 * @param position_px 文本左下角在图像像素坐标系中的位置。 * @param color 文本颜色。 * @param font_scale OpenCV 字体缩放系数。 * @param thickness 文本线宽,单位像素。 */ virtual void showText(const std::string& text, const Pixel& position_px, const Color& color, double font_scale = 0.7, int thickness = 2) = 0; /** * @brief 添加点叠加项。 * @param overlay 点叠加配置。 */ virtual void showPoint(const PointOverlay& overlay) = 0; /** * @brief 按位置、颜色和大小直接添加点。 * @param position_px 点中心在图像像素坐标系中的位置。 * @param color 点颜色。 * @param radius_px 圆点半径,单位像素。 * @param thickness 线宽,`-1` 表示填充。 * @param label 点旁边附带显示的字符串;为空时不显示。 */ virtual void showPoint(const Pixel& position_px, const Color& color, int radius_px = 5, int thickness = 2, const std::string& label = {}) = 0; /** * @brief 显示圆圈叠加项。 * @param overlay 圆圈叠加配置。 */ virtual void showCircle(const CircleOverlay& overlay) = 0; /** * @brief 按位置、半径、颜色和线宽直接显示圆圈。 * @param center_px 圆心在图像像素坐标系中的位置。 * @param radius_px 圆半径,单位像素。 * @param color 圆圈颜色。 * @param thickness 圆圈线宽,单位像素,`-1` 表示填充。 */ virtual void showCircle(const Pixel& center_px, int radius_px, const Color& color, int thickness = 2) = 0; /** * @brief 显示十字叠加项。 * @param overlay 十字叠加配置。 */ virtual void showCross(const CrossOverlay& overlay) = 0; /** * @brief 按位置、尺寸、颜色和线宽直接显示十字。 * @param center_px 十字中心在图像像素坐标系中的位置。 * @param arm_length_px 单侧十字臂长度,单位像素。 * @param color 十字颜色。 * @param thickness 十字线宽,单位像素。 */ virtual void showCross(const Pixel& center_px, int arm_length_px, const Color& color, int thickness = 2) = 0; /** * @brief 判断当前是否已有待显示图像。 */ virtual bool hasImage() const = 0; /** * @brief 将当前图像和叠加项渲染到输出图像。 * @param rendered_image 输出的渲染结果图像,BGR 三通道。 * @return 渲染成功返回 `true`。 */ virtual bool render(cv::Mat& rendered_image) const = 0; /** * @brief 显示当前渲染结果。 * @param wait_key_ms `cv::waitKey` 等待时间,单位毫秒。 * @return 返回 `cv::waitKey` 的按键值;若没有可显示图像则返回 `-1`。 */ virtual int show(int wait_key_ms = 1) = 0; /** * @brief 关闭显示窗口。 */ virtual void close() = 0; }; /** * @brief 基于 OpenCV HighGUI 的图像叠加显示实现。 */ class OpenCvImageCvDisplay final : public ImageDisplay { public: /** * @brief 构造显示器。 * @param window_name 显示窗口名称。 */ explicit OpenCvImageCvDisplay(std::string window_name = "image_overlay") : window_name_(std::move(window_name)) {} /** * @brief 设置窗口名称。 * @param window_name 显示窗口名称。 */ void setWindowName(const std::string& window_name) override { window_name_ = window_name; } /** * @brief 设置待显示图像。 * @param image 输入图像,支持灰度图、BGR 图或 BGRA 图。 */ void setImage(const cv::Mat& image) override { image_ = image.clone(); } /** * @brief 清空所有叠加项,但保留当前图像。 */ void clearOverlays() override { texts_.clear(); points_.clear(); circles_.clear(); crosses_.clear(); } /** * @brief 添加文本叠加项。 * @param overlay 文本叠加配置。 */ void showText(const TextOverlay& overlay) override { texts_.push_back(overlay); } /** * @brief 按位置、字符串、颜色和大小直接添加文本。 * @param text 需要显示的字符串内容。 * @param position_px 文本左下角在图像像素坐标系中的位置。 * @param color 文本颜色。 * @param font_scale OpenCV 字体缩放系数。 * @param thickness 文本线宽,单位像素。 */ void showText(const std::string& text, const Pixel& position_px, const Color& color, double font_scale = 0.7, int thickness = 2) override { TextOverlay overlay; overlay.text = text; overlay.position_px = position_px; overlay.color = color; overlay.font_scale = font_scale; overlay.thickness = thickness; showText(overlay); } /** * @brief 添加点叠加项。 * @param overlay 点叠加配置。 */ void showPoint(const PointOverlay& overlay) override { points_.push_back(overlay); } /** * @brief 按位置、颜色和大小直接添加点。 * @param position_px 点中心在图像像素坐标系中的位置。 * @param color 点颜色。 * @param radius_px 圆点半径,单位像素。 * @param thickness 线宽,`-1` 表示填充。 * @param label 点旁边附带显示的字符串;为空时不显示。 */ void showPoint(const Pixel& position_px, const Color& color, int radius_px = 5, int thickness = 2, const std::string& label = {}) override { PointOverlay overlay; overlay.position_px = position_px; overlay.color = color; overlay.radius_px = radius_px; overlay.thickness = thickness; overlay.label = label; showPoint(overlay); } /** * @brief 显示圆圈叠加项。 * @param overlay 圆圈叠加配置。 */ void showCircle(const CircleOverlay& overlay) override { circles_.push_back(overlay); } /** * @brief 按位置、半径、颜色和线宽直接显示圆圈。 * @param center_px 圆心在图像像素坐标系中的位置。 * @param radius_px 圆半径,单位像素。 * @param color 圆圈颜色。 * @param thickness 圆圈线宽,单位像素,`-1` 表示填充。 */ void showCircle(const Pixel& center_px, int radius_px, const Color& color, int thickness = 2) override { CircleOverlay overlay; overlay.center_px = center_px; overlay.radius_px = radius_px; overlay.color = color; overlay.thickness = thickness; showCircle(overlay); } /** * @brief 显示十字叠加项。 * @param overlay 十字叠加配置。 */ void showCross(const CrossOverlay& overlay) override { crosses_.push_back(overlay); } /** * @brief 按位置、尺寸、颜色和线宽直接显示十字。 * @param center_px 十字中心在图像像素坐标系中的位置。 * @param arm_length_px 单侧十字臂长度,单位像素。 * @param color 十字颜色。 * @param thickness 十字线宽,单位像素。 */ void showCross(const Pixel& center_px, int arm_length_px, const Color& color, int thickness = 2) override { CrossOverlay overlay; overlay.center_px = center_px; overlay.arm_length_px = arm_length_px; overlay.color = color; overlay.thickness = thickness; showCross(overlay); } /** * @brief 判断当前是否已有待显示图像。 */ bool hasImage() const override { return !image_.empty(); } /** * @brief 将当前图像和叠加项渲染到输出图像。 * @param rendered_image 输出的渲染结果图像,BGR 三通道。 * @return 渲染成功返回 `true`。 */ bool render(cv::Mat& rendered_image) const override { if (!toBgrImage(image_, rendered_image)) { return false; } for (const CircleOverlay& overlay : circles_) { drawCircle(rendered_image, overlay); } for (const CrossOverlay& overlay : crosses_) { drawCross(rendered_image, overlay); } for (const PointOverlay& overlay : points_) { drawPoint(rendered_image, overlay); } for (const TextOverlay& overlay : texts_) { drawText(rendered_image, overlay); } return true; } /** * @brief 显示当前渲染结果。 * @param wait_key_ms `cv::waitKey` 等待时间,单位毫秒。 * @return 返回 `cv::waitKey` 的按键值;若没有可显示图像则返回 `-1`。 */ int show(int wait_key_ms = 1) override { cv::Mat rendered_image; if (!render(rendered_image)) { return -1; } if (!window_created_) { cv::namedWindow(window_name_, cv::WINDOW_NORMAL); window_created_ = true; } cv::imshow(window_name_, rendered_image); return cv::waitKey(wait_key_ms); } /** * @brief 关闭显示窗口。 */ void close() override { if (!window_created_) { return; } cv::destroyWindow(window_name_); window_created_ = false; } private: /** * @brief 将输入图像转换为可叠加绘制的 BGR 三通道图像。 * @param input 输入图像。 * @param output 输出 BGR 图像。 * @return 转换成功返回 `true`。 */ static bool toBgrImage(const cv::Mat& input, cv::Mat& output) { if (input.empty()) { return false; } if (input.channels() == 1) { cv::cvtColor(input, output, cv::COLOR_GRAY2BGR); return true; } if (input.channels() == 3) { output = input.clone(); return true; } if (input.channels() == 4) { cv::cvtColor(input, output, cv::COLOR_BGRA2BGR); return true; } return false; } /** * @brief 在图像上绘制文本叠加项。 * @param image 目标图像,BGR 三通道。 * @param overlay 文本叠加配置。 */ static void drawText(cv::Mat& image, const TextOverlay& overlay) { const cv::Point origin(overlay.position_px.u, overlay.position_px.v); const int thickness = std::max(1, overlay.thickness); const double font_scale = std::max(0.0, overlay.font_scale); if (overlay.draw_background) { int baseline = 0; const cv::Size text_size = cv::getTextSize(overlay.text, overlay.font_face, font_scale, thickness, &baseline); const int pad = std::max(0, overlay.background_padding_px); const cv::Point top_left(origin.x - pad, origin.y - text_size.height - pad); const cv::Point bottom_right(origin.x + text_size.width + pad, origin.y + baseline + pad); cv::rectangle(image, top_left, bottom_right, overlay.background_color.toCvScalar(), cv::FILLED); } cv::putText(image, overlay.text, origin, overlay.font_face, font_scale, overlay.color.toCvScalar(), thickness, cv::LINE_AA); } /** * @brief 在图像上绘制点叠加项。 * @param image 目标图像,BGR 三通道。 * @param overlay 点叠加配置。 */ static void drawPoint(cv::Mat& image, const PointOverlay& overlay) { const cv::Point center(overlay.position_px.u, overlay.position_px.v); const int radius = std::max(1, overlay.radius_px); cv::circle(image, center, radius, overlay.color.toCvScalar(), overlay.thickness, cv::LINE_AA); if (!overlay.label.empty()) { TextOverlay label_overlay; label_overlay.text = overlay.label; label_overlay.position_px = Pixel{center.x + overlay.label_offset_px.u, center.y + overlay.label_offset_px.v}; label_overlay.color = overlay.color; label_overlay.font_scale = overlay.label_font_scale; label_overlay.thickness = overlay.label_thickness; drawText(image, label_overlay); } } /** * @brief 在图像上绘制圆圈叠加项。 * @param image 目标图像,BGR 三通道。 * @param overlay 圆圈叠加配置。 */ static void drawCircle(cv::Mat& image, const CircleOverlay& overlay) { const cv::Point center(overlay.center_px.u, overlay.center_px.v); const int radius = std::max(1, overlay.radius_px); const int thickness = (overlay.thickness == 0) ? 1 : overlay.thickness; cv::circle(image, center, radius, overlay.color.toCvScalar(), thickness, cv::LINE_AA); } /** * @brief 在图像上绘制十字叠加项。 * @param image 目标图像,BGR 三通道。 * @param overlay 十字叠加配置。 */ static void drawCross(cv::Mat& image, const CrossOverlay& overlay) { const cv::Point center(overlay.center_px.u, overlay.center_px.v); const int arm_length = std::max(1, overlay.arm_length_px); const int thickness = std::max(1, overlay.thickness); const cv::Scalar color = overlay.color.toCvScalar(); cv::line(image, cv::Point(center.x - arm_length, center.y), cv::Point(center.x + arm_length, center.y), color, thickness, cv::LINE_AA); cv::line(image, cv::Point(center.x, center.y - arm_length), cv::Point(center.x, center.y + arm_length), color, thickness, cv::LINE_AA); } std::string window_name_; // 显示窗口名称。 cv::Mat image_; // 当前待显示图像。 std::vector texts_; // 当前待绘制的文本叠加列表。 std::vector points_; // 当前待绘制的点叠加列表。 std::vector circles_; // 当前待绘制的圆圈叠加列表。 std::vector crosses_; // 当前待绘制的十字叠加列表。 bool window_created_{false}; // 显示窗口是否已经创建。 }; } // namespace cmvr::common