qt项目-《图像标注软件》源码阅读笔记-Shape类绘图及其子类

news2025/1/19 7:55:08

目录

1. Shape 概览 

2. Shape 基类

2.1 字段

2.2 方法

2.3 嵌套类型

3. Shape2D 2d形状纯虚基类

3.1 字段

3.2 方法

4. Shape3D 3d形状纯虚基类

5. Shape2D子类

5.1 Rectangle 矩形类


1. Shape 概览 

功能:Shape类及其子类负责形状的绘制及形状的存储。Shape有7个子类。

  1. Brush代表画刷形状,用于分割标注;
  2. Rectangle代表矩形形状;
  3. Polygons代表多边形形状;
  4. Circle代表圆形形状;
  5. Curve代表平滑曲线形状;
  6. Rectangle3D代表3d长方体形状;
  7. Brush3D代表3d画刷形状,用于3d分割标注。

2. Shape 基类

2.1 字段

  1. color:标注颜色;
  2. isFill:是否被选中,选中时为true,并填充标注形状的内部,便于用户交互;
  3. isHide:是否隐藏标注;
  4. isHover:是否悬浮,若鼠标悬浮在标注内部,则变为true,填充内部颜色,便于用户交互;
  5. label:标注对应的标签文字;
  6. type:标注类型,枚举类型,各种形状(Brush,Rectangle,Polygons,...);

2.2 方法

  1. Shape(Type t):构造函数,初始化标注类型,即标注的形状;
  2. virtual ~Shape()=0;纯虚函数,使其成为抽象类。

2.3 嵌套类型

enum Type{Brush,Rectangle,Polygons,Circle,Curve,Rectangle3D,Brush3D};

// 子类可以通过继承 My::Shape 类并在构造函数中初始化 type 成员来使用这个枚举类型。

嵌套类型有很多种,包括内部类、嵌套结构、嵌套枚举等。嵌套类型提供了一种将相关的类型组织在一起并隐藏其实现细节的方式。这里使用的是枚举类型的嵌套。

作用和优点

  • 清晰的类型定义, 通过将这些类型放入枚举中,代码提供了一种清晰的、可读性强的方式来表示标注形状的类型。这样的设计使得在代码中使用这些类型更为直观,提高了代码的可维护性。
  • 扩展性,如果需要添加新的标注形状,只需在 enum Type 中添加新的成员。这样的设计使得系统更具有扩展性,而无需修改大量现有代码。
#ifndef SHAPE_H
#define SHAPE_H
#include<QString>
#include<QColor>

#include"Namespace.h"  // 头文件内声明了Shape, Brush,Rectangle,Polygons,...等类名

/// \brief 所有标注形状的基类
///
/// 负责形状的绘制及形状的存储
class My::Shape{
public:

    /// \brief 标注形状的类型
    ///
    /// Brush代表画刷形状,用于分割标注,
    /// Rectangle代表矩形形状,Polygons代表多边形形状,Circle代表圆形形状,Curve代表平滑曲线形状
    /// Rectangle3D代表3d长方体形状,Brush3D代表3d画刷形状,用于3d分割标注
    enum Type{Brush,Rectangle,Polygons,Circle,Curve,Rectangle3D,Brush3D};

    /// \brief 标注类型
    const Type type;

    /// \brief 标注对应的标签文字
    QString label;

    /// \brief 默认标注颜色
    QColor color=QColor(100,255,0,100);

    /// \brief 是否填充内部
    ///
    /// 当被选中时,isFill会变为true,填充标注形状的内部,便于用户交互
    bool isFill=false;

    /// \brief 是否隐藏标注
    bool isHide=false;

    /// \brief 是否悬浮
    ///
    /// 当前鼠标是否悬浮在标注内部,若悬浮在内部,则变为true,填充内部颜色,便于用户交互
    bool isHover=false;

    Shape(Type t);

    virtual ~Shape()=0;
};
#endif // SHAPE_H

使用 #include "Namespace.h" 的形式是为了引入命名空间 My 中的其他类。这样做的主要优点包括:

  • 代码组织: 使用命名空间可以将相关的类、函数、变量等组织在一起,提高了代码的可读性和可维护性。通过引入命名空间 My,你可以将所有与项目有关的类都放在同一个命名空间下,使代码更有条理。
  • 避免命名冲突: 命名空间提供了一种防止命名冲突的机制。如果在项目中有多个独立开发的部分,使用命名空间可以避免不同部分定义相同名称的类或函数时的冲突问题。

3. Shape2D 2d形状纯虚基类

3.1 字段

points: 存储标注形状的像素点位

3.2 方法

  • draw: 绘制标注形状,虚函数;
  • isInShape: 判断鼠标是否在标注形状内部,虚函数;
  • offset:偏移标注形状,虚函数;
#ifndef SHAPE2D_H
#define SHAPE2D_H
#include<QVector>
#include<QPoint>
#include<QLabel>
#include<opencv2/opencv.hpp>
#include"Shape.h"

/// \brief 2d标注形状的基类
class My::Shape2D:public My::Shape{
public:

    /// \brief 存储标注形状的像素点位
    QVector<QPointF> points;
    Shape2D(My::Shape::Type t):Shape(t){}

    /// \brief 绘制标注形状虚函数
    virtual void draw(QWidget* w);

    /// \brief 判断鼠标是否在标注形状内部虚函数
    virtual bool isInShape(QPointF p,QWidget* w);

    /// \brief 偏移标注形状虚函数
    virtual void offset(float xOffset,float yOffset);
    virtual ~Shape2D()=0;
};

#endif // SHAPE_H


4. Shape3D 3d形状纯虚基类

#ifndef SHAPE3D_H
#define SHAPE3D_H
#include<QVector>
#include<opencv2/opencv.hpp>
#include"Shape.h"

/// \brief 3d标注形状的基类
class My::Shape3D:public My::Shape{
public:

    /// \brief 存储像素点位
    QVector<cv::Point3f> points;
public:
    Shape3D(Type t);
    virtual ~Shape3D()=0;
};

#endif // SHAPE3D_H

5. Shape2D子类

每个子类都有3个相同的方法(继承自Shape2D基类):

  • draw: 绘制标注形状,虚函数;
  • isInShape: 判断鼠标是否在标注形状内部,虚函数;
  • offset:偏移标注形状,虚函数;

5.1 Rectangle 矩形类

字段:

  • width: 存储矩形的宽,相对Widget宽度的百分比;
  • height:存储矩形的高,也是相对值;

继承的points存放的x, y也是相对Widget百分比值。

方法:

Rectangle: 默认构造函数,在头文件做好了实现,用于构造自己和父类。

// Rectangle()默认构造函数,调用基类构造函数,传递Type,
// 用于初始化 Rectangle 类的基类 Shape2D 的成员。
Rectangle():Shape2D(Shape2D::Rectangle){} 
#ifndef RECTANGLE_H
#define RECTANGLE_H
#include"Shape2D.h"

/// \brief 代表矩形形状,继承Shape2D类
class My::Rectangle:public My::Shape2D{
public:
    
    /// \brief 存储矩形的宽
    float width;

    /// \brief 存储矩形的高
    float height;

    Rectangle():Shape2D(Shape2D::Rectangle){} // Rectangle()默认构造函数,调用基类构造函数,传递Type,用于初始化 Rectangle 类的基类 Shape2D 的成员。

    /// \brief 绘制形状函数
    virtual void draw(QWidget* w);

    /// \brief 判断是否在形状内函数
    virtual bool isInShape(QPointF p,QWidget* w);

    /// \brief 偏移形状函数
    virtual void offset(float xOffset,float yOffset);
};

#endif // RECTANGLE_H
#include<QPainter>
#include"Rectangle.h"
#include<QDebug>
#include<math.h>


/// \brief 绘制矩形
void My::Rectangle::draw(QWidget *w){
    if(isHide)return;  // 隐藏标注了,则不会下面的绘制。
    QPainter painter(w);  // 它用于在 QWidget 上进行绘制。
    QPen pen;
    pen.setColor(color);  // 设置线颜色
    pen.setWidth(4);  // 矩形线宽,固定是4个像素。
    painter.setPen(pen);
    if(isFill || isHover)painter.setBrush(color);  // 是否被选中,或者悬浮,则设置刷子颜色。
    else painter.setBrush(Qt::NoBrush);  // 否在不使用刷子
    if(points.length()==0)return;  // 为空,则说明没有点击,则不能绘制矩形,直接返回。

    // 分别绘制不同方向的矩形。
    // 这种操作的目的通常是为了将相对于控件大小的百分比坐标转化为实际的像素坐标。x,y,width,height都是相对值。
    if(width>=0&&height>=0){  // 矩形的宽度和高度都正常,不是负数. draw(x,y,w,h)
        painter.drawRect(int(points[0].x()*w->width()), int(points[0].y()*w->height()),int(width*w->width()),int(height*w->height()));
    }
    if(width<0&&height>=0){   // 宽度为负数,points[0]点在右上 draw(x,y,w,h)
        painter.drawRect(int(points[0].x()*w->width()+width* w->width()), int(points[0].y()*w->height()),int(abs(width*w->width())),int(height*w->height()));
    }
    if(width<0&&height<0){    // 均为负数,points[0]点在右下
        painter.drawRect(int(points[0].x()*w->width()+width* w->width()),int(points[0].y()*w->height()+height*w->height()),int(abs(width*w->width())),int(abs(height*w->height())));
    }
    if(width>=0&&height<0){   // 高度为负数. points[0]点在左下
        painter.drawRect(int(points[0].x()*w->width()),int(points[0].y()*w->height()+height*w->height()),int(width*w->width()),int(abs(height*w->height())));
    }

}


/// \brief 判断是否在矩形内部
bool My::Rectangle::isInShape(QPointF p,QWidget* w){

    if(points.length()==0)return false;  // 没有点击过,不存在矩形,则返回。
    
    // 矩形的四个顶点: 左上开始,顺时针走。
    std::vector<cv::Point2f> vec;
    vec.push_back(cv::Point2f(float(points[0].x())*w->width(),float(points[0].y())*w->height()));
    vec.push_back(cv::Point2f((float(points[0].x())+width)*w->width(),float(points[0].y())*w->height()));
    vec.push_back(cv::Point2f((float(points[0].x())+width)*w->width(),(float(points[0].y())+height)*w->height()));
    vec.push_back(cv::Point2f(float(points[0].x())*w->width(),(float(points[0].y())+height)*w->height()));

    cv::Point2f cvp(float(p.x())*w->width(),float(p.y())*w->height());

    double res=cv::pointPolygonTest(vec,cvp,false);
    if(res<0)return false;
    else return true;
}


/// \brief 偏移标注
void My::Rectangle::offset(float xOffset,float yOffset){
    for(int i=0;i<points.length();i++){
        points[i].rx()+=double(xOffset);  // 它返回的是一个引用,允许我们修改这个点的 x 坐标。
        points[i].ry()+=double(yOffset);
    }
}

(1)在这段代码中,x = int(points[0].x()*w->width()),这里涉及到坐标的映射和缩放。

假设 points[0].x() 是矩形左上角点在相对于矩形区域的 x 方向上的百分比坐标(范围在 0.0 到 1.0 之间),w->width()QWidget 的宽度。通过乘以 w->width(),将相对坐标转换为绝对坐标,即在 QWidget 上的具体像素坐标。

这种操作的目的通常是为了将相对于控件大小的百分比坐标转化为实际的像素坐标。这是因为在绘制图形时,常常需要考虑到用户可能调整窗口大小,而相对于窗口大小的百分比坐标能够适应不同的窗口尺寸。

(2)if(width<0&&height>=0){   // 宽度为负数,points[0]点在右上

左上x = int(points[0].x()*w->width() + width* w->width()). 

int(points[0].x()*w->width()是矩形的右上x,width* w->width()是负数宽度,相加就得到左上x.

这里有个问题就是points是在哪里赋值的?

待续。。。

其他子类类似,就不细究了。


参考:GitHub - jameslahm/labelme: A image annotation software for 2D or 3D images

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1331081.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构入门精讲 | 第十三篇】考研408、公司面试树专项练习(二)

在上一篇中我们进行了树的判断题、选择题、填空题专项练习&#xff0c;在这一篇中我们将进行编程题的相关练习。 目录 编程题R7-1 目录树R7-1 是否同一棵二叉搜索树R7-2 二叉搜索树的结构R7-3 平衡二叉树的根R7-1 完全二叉搜索树R7-1 修理牧场R7-2 嘴强王者R7-3 房屋分拆R7-4 动…

Qt/QML编程学习之心得:在QML中调用fileDialog(十六)

Qt中有一些内置的对话框dialog,比如 在QWidget工程中使用比较容易,比如 #include <QFileDialog>fileName = QFileDialog::getOpenFileName(this, tr("Open Image"), "/home/jana", tr("Image Files (*.png *.jpg *.bmp)")); 那么在QM…

【LearnOpenGL基础入门——5】着色器

目录 一.简介 二.GLSL 三.数据类型 四.输入与输出 五.Uniform 六.更多属性 一.简介 着色器(Shader)是运行在GPU上的小程序。这些小程序为图形渲染管线的某个特定部分而运行。从基本意义上来说&#xff0c;着色器只是一种把输入转化为输出的程序。着色器也是一种非常独立…

最新技术整理3款开源免费直播推流工具,实现实时视频推流、视频拉流,目标端可以是服务器、云平台、移动设备等(附源码)

最新技术整理3款开源免费直播推流工具&#xff0c;实现实时视频推流、视频拉流&#xff0c;目标端可以是服务器、云平台、移动设备等&#xff08;附源码&#xff09;。 什么是推流&#xff1f; 视频推流是指将实时的视频数据从一个源端发送到一个或多个目标端的过程。推流的源…

基于javaWeb的高校后勤报修系统的设计与实现论文

基于JavaWeb的高校后勤报修系统的设计与实现 摘 要 当下&#xff0c;正处于信息化的时代&#xff0c;许多行业顺应时代的变化&#xff0c;结合使用计算机技术向数字化、信息化建设迈进。以前学校对于后勤报修信息的管理和控制&#xff0c;采用人工登记的方式保存相关数据&…

python脚本 ssh工具 ssh上传文档 选择文档并上传到ssh服务器

此文分享一个python脚本,用于快速的定位、选择文档,并将其上传到指定的ssh服务器。 效果演示 🔥完整演示效果 👇第一步,显然,我们需要选择功能 👇第二步,我们需要定位并选择需要上传的文档 👇第三步,确认我们需要上传文档的ssh服务器 👇第四步,定位、选择…

mybatis的一级缓存使用以及禁用

目录 验证代码如下 mappper 代码 xml 中代码 实际执行代码 执行结果 DefaultSqlSession CachingExecutor BaseExecutor PerpetualCache 总结 禁用一级缓存 mapper 对应的 xml 的 select 查询设置 flushCache 属性为 true MappedStatement 的内部类 Builder 向外部变…

parseInt(0.0000005)大于等于5

文章目录 一、前言二、parseInt()的神秘行为三、解决parseInt()的奥秘四、结论五、最后 一、前言 parseInt() 是 JavaScript 的内置函数&#xff0c;用于解析数字字符串中的整数。例如&#xff0c;从数字字符串中解析整数100&#xff1a; const number parseInt(100); numbe…

Netty-3-封帧

实际上&#xff0c;数据的封帧与解帧本身虽然实现起来十分简单&#xff0c;但它们在本质上仍然是数据的一种编解码。 那么它们相比之前介绍的数据编解码有什么区别呢&#xff1f;单从编码目标看&#xff0c;之前介绍的数据编解码是为了对用户的数据对象进行传输。 封帧与解帧则…

【Linux】编辑、查看和搜索文件

大多数 Linux 发行版不包含真正的 vi;而是自带一款高级替代版本&#xff0c;叫做 vim(它是“vi improved”的简写)由 Bram Moolenaar 开发的&#xff0c;vim 相对于传统的 Unix vi 来说&#xff0c;取得了实质性进步。 启动和退出 vim 使用vim可以启动&#xff0c;如命令行输…

MyBatis 通过 SqlSession 实现动态Entity批量插入

需要几个关键点: 1、entity对应的service需要继承BaseService 2、entity对应的serviceImpl需要实现baseMapper方法&#xff0c;需要把当前的mapper返回去 3、entity对应的Mapper需要BaseMapper

快速安装方式安装开源OpenSIPS和CP控制界面

OpenSIPS是目前世界上主流的两个SIP软交换引擎(其中另外一个是kamailio)或者SIP信令服务器&#xff08;个人认为是比较正确的称谓&#xff09;。关于Opensips的基础和一些参数配置和安装方式笔者在很久以前的历史文档中有非常多的介绍。最近&#xff0c;很多用户使用OpenSIPS软…

PHP案例代码:PHP如何提供下载功能?

对Web开发人员来说,“下载”功能是一个非常常见的需求。在网站中提供文件下载,通常用于提供用户手册、软件升级、音乐、视频等各种资源文件。本教程将向您介绍如何实现一个PHP下载功能,同时告诉浏览器文件名称、文件大小、文件类型,并统计下载次数。 首先,我们需要了解一些…

蓝牙物联网在智能家居中的应用前景

物联网智能家居系统是应用物联网技术&#xff0c;在传统家居环境下将各种零散无序的电器整合成统一整体&#xff0c;实现家电的全程自动控制&#xff0c;满足用户高效管理需求的一种新型家居模式。 其主要的子系统有家居感知系统、家庭网络系统、智能家居控制管理系统等&#x…

Hadoop入门学习笔记——七、Hive语法

视频课程地址&#xff1a;https://www.bilibili.com/video/BV1WY4y197g7 课程资料链接&#xff1a;https://pan.baidu.com/s/15KpnWeKpvExpKmOC8xjmtQ?pwd5ay8 Hadoop入门学习笔记&#xff08;汇总&#xff09; 目录 七、Hive语法7.1. 数据库相关操作7.1.1. 创建数据库7.1.2…

Python之zip函数相关介绍

python3的zip函数 zip函数接受任意多个可迭代对象作为参数&#xff0c;将对象中对应的元素打包成一个tuple&#xff0c;然后返回一个可迭代的zip对象。 这个可迭代对象可以使用循环的方式列出其元素&#xff0c;若多个可迭代对象的长度不一致,则所返回的列表与长度最短的可迭…

WPF中使用DataGrid封装组合控件TreeView+DataGrid

&#xff08;关注博主后&#xff0c;在“粉丝专栏”&#xff0c;可免费阅读此文&#xff09; wpf的功能非常强大&#xff0c;很多控件都是原生的&#xff0c;但是要使用TreeViewDataGrid的组合&#xff0c;就需要我们自己去封装实现。 我们需要的效果如图所示&#x…

汽车品牌官网建设的效果如何

汽车、电动摩托车、自行车等是人们常用的出行工具&#xff0c;头部品牌不少&#xff0c;中小品牌也同样很多&#xff0c;在当今激烈竞争的市场&#xff0c;各商家都希望不断拓客寻求销量增长。由于市场产品同质化严重且无突出特色&#xff0c;加之选择性广&#xff0c;因此在实…

华为鸿蒙应用--封装数据持久化工具:首选项Preferences(鸿蒙工具)-ArkTs

一、使用方法&#xff1a; 0、初始化实例&#xff1a;一般在EntryAbility.ts的onWindowStageCreate中初始化&#xff1b;&#xff08;可忽略&#xff09; 1、将数据写入Preferences实例 function() {let arrayNum: number[] [1, 2, 3];let arrayStr: string[] ["5&quo…

vue3项目 - 使用 pnpm 包管理器来创建项目

创建项目 npm install -g pnpm pnpm create vue 输入项目名称、包名称、选择要安装的依赖&#xff0c;最后 pnpm install pnpm format #规范格式 pnpm dev #启动项目