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

目录

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【SpringBoot】Spring data JPA整合ShardingSphere-JDBC静态读写分离实现

大佬栽树&#xff0c;我乘凉 许大仙老师&#xff1a;【yuque.com/fairy-era/yg511q/ud9uli67b6gxgdh7】 开整 数据库准备 一主两从 数据库脚本 CREATE DATABASE IF NOT EXISTS dbtest CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE dbtest; -- 创建表 CREATE TA…

【Java探索之旅】我与Java的初相识(完):注释,标识符,关键字

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; Java入门到精通 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一. Java的注释方式二. 标识符三. 关键字四. 全篇总结 &#x1f4d1;前言 在Java编程…

《面试专题-----经典高频面试题收集三》解锁 Java 面试的关键:深度解析并发编程基础篇高频经典面试题(第三篇)

目录 并发编程面试题1.什么是进程、线程、协程&#xff0c;他们之间的关系是怎样的2.协程对于多线程有什么优缺点吗 并发编程面试题 1.什么是进程、线程、协程&#xff0c;他们之间的关系是怎样的 进程: 本质上是⼀个独⽴执⾏的程序&#xff0c;进程是操作系统进⾏资源分配和…

C# float/double 减 float/double 等 (X.xxxxxxxxxxxxxE-07)(黑盒测试)

问题 因为没有深究原理&#xff0c;所有只进行了“黑盒测试” 黑盒测试结论&#xff1a; 问题操作结论float/double运算进过一系列的运算后大概率 &#xff01; 0.0 &#xff0c; 而是等于0.00000000000xxxx等于X.xxxxxxxx一串数字的时候不影响下一步继续使用当需要显示fl…

Linux基本数据库mysql了解

关系型数据库与非关系型数据库的区别 什么是关系型数据库 关系型数据库是依据关系模型来创建的数据库。 所谓关系模型就是“一对一、一对多、多对多”等关系模型&#xff0c;关系模型就是指二维表格模型,因而一个关系型数据库就是由二维表及其之间的联系组成的一个数据组织。 关…

06_树的入门

二叉树入门 树的基本定义树的相关术语二叉树的基本定义二叉查找树的创建二叉树的结点类二叉查找树API设计二叉查找树实现二叉查找树其他便捷方法查找二叉树中最小的键查找二叉树中最大的键 二叉树的基础遍历前序遍历中序遍历后序遍历 二叉树的层序遍历二叉树的最大深度问题折纸…

顺序结构复习

复习一些易错知识点还有习题 目录 可能不熟悉的知识点 逻辑表达式的求解 if,else的配队 条件运算符 运算符优先级的问题 switch的使用 goto和if构成的循环 例题讲解 1 2 3 4 ​编辑 5 ​编辑 6赋值 ​编辑 7 可能不熟悉的知识点 逻辑表达式的求解 如果…

redis基本用法学习(C#调用CSRedisCore操作redis)

除了NRedisStack包&#xff0c;csredis也是常用的redis操作模块&#xff08;从EasyCaching提供的常用redis操作包来看&#xff0c;CSRedis、freeredis、StackExchange.Redis应该都属于常用redis操作模块&#xff09;&#xff0c;本文学习使用C#调用CSRedis包操作redis的基本方式…

解决虚拟机卡顿、卡死、待机后不动的情况(真实有效

本人环境&#xff1a; VM workstation 17.5 ubuntu 22.04 虚拟机配置&#xff1a;4核 4g issue&#xff1a; 出现开机卡死不动运行一段时间&#xff0c;可能半小时不到&#xff0c;就页面卡死不动经常需要关机重启才解决&#xff0c;可能没有解决 1.配置虚拟化引擎 这一步我称…

在线客服系统:解决常见问题的实用工具与解决方案

市场得不断发展促使着消费者服务意识的觉醒&#xff0c;越来越多的消费者在购买产品的时候不仅看产品的功能、外观、性能&#xff0c;还关注品牌的服务质量。在线客服系统的出现帮助企业解决了客户服务难的问题。接下来&#xff0c;我们具体聊一聊在线客服系统能解决哪些问题&a…

Python办公自动化Day2-openpyxl

目录 文章声明⭐⭐⭐让我们开始今天的学习吧&#xff01;常规操作添加数据遍历所有单元格数据合并/取消合并单元格添加/删除行与列移动指定范围单元格 文章声明⭐⭐⭐ 该文章为我&#xff08;有编程语言基础&#xff0c;非编程小白&#xff09;的 Python办公自动化自学笔记知识…

2023年第6届传智杯省赛第二场复赛 解题报告 | 珂学家

前言 因为OJ的承办方是牛客&#xff0c;除了初赛用的原题有点争议外&#xff0c;复赛用的是原创的新题(点赞)。 说真的&#xff0c;这个难度&#xff0c;超过我的想象&#xff0c;打得非常的吃力。 我其实总共打了两场初赛&#xff0c;一场复赛&#xff0c;外加VP一场复赛&a…

如何用Excel制作一张能在网上浏览的动态数据报表

前言 如今各类BI产品大行其道&#xff0c;“数据可视化”成为一个热门词汇。相比价格高昂的各种BI软件&#xff0c;用Excel来制作动态报表就更加经济便捷。今天小编就将为大家介绍一下如何使用葡萄城公司的纯前端表格控件——SpreadJS来实现一个Excel动态报表&#xff1a; 实…

C语言中关于指针的理解

#include <stdio.h> int main() {int a11;int *p&a; //因为a是整型的&#xff0c;所以我们定义指针p的时候要和a的类型一样char b;char *pa&b; //同理&#xff0c;b是字符型&#xff0c;所以这里的pa也要用字符型return 0; }因为*p指向的是地址&…

高级RGA(二):父文档检索器

在我之前写的<<使用langchain与你自己的数据对话>>系列博客中&#xff0c;我们介绍了利用大型语言模型LLM来检索文档时的过程和步骤&#xff0c;如下图所示&#xff1a; 我们在检索文档之前&#xff0c;通常需要对文档进行切割&#xff0c;然后将其存入向量数据库如…

Seata源码——TCC模式总结

什么是TCC TCC 是分布式事务中的二阶段提交协议&#xff0c;它的全称为 Try-Confirm-Cancel&#xff0c;即资源预留&#xff08;Try&#xff09;、确认操作&#xff08;Confirm&#xff09;、取消操作&#xff08;Cancel&#xff09; TCC的步骤 1.Try&#xff1a;对业务资源…

米勒电容与米勒效应

米勒电容与米勒效应 米勒效应米勒效应的形成原理及分析米勒效应的危害和改进 米勒效应 Ciss CGE CGC 输入电容 Coss CGC CEC 输出电容 Crss CGC 米勒电容 下面我们以MOS中的米勒效应来展开说明&#xff1a; 米勒效应在MOS驱动中臭名昭著&#xff0c;它是由MOS管的米勒电容引发…

揭秘NCO:数字领域的音乐之旅

好的&#xff0c;让我们更详细地解析NCO的数学奥秘&#xff0c;深入探讨数字音乐的乐谱。在我们深入数学公式之前&#xff0c;让我们回顾一下&#xff0c;NCO就像是一位神奇的音符设计师&#xff0c;创造数字音乐的灵感源泉。 NCO&#xff1a;数字音符的魔法创造者 NCO&#x…

JavaEE:CAS详解

一.什么是CAS CAS: 全称 Compare and swap &#xff0c;字面意思 :” 比较并交换 “ &#xff0c;一个 CAS 涉及到以下操作&#xff1a; 我们假设内存中的原数据V&#xff0c;旧的预期值A&#xff0c;需要修改的新值B。 我们来进行操作&#xff1a; 1. 比较 V 和 A 是否相等。…

C语言中关于操作符的理解

本篇文章只会列出大家在生活中经常使用的操作符 算术操作符 在算数操作符中常用的有&#xff0c;&#xff0c;-&#xff0c;*&#xff0c;/&#xff0c;% &#xff0c;我们重点讲一讲 / (除) 和 % (模) " / "运算 #include <stdio.h>int main() {int a5/2;fl…