两百行代码实现简易点云标注工具

news2024/12/24 9:57:22

夏天来了非常热,LZ周末不想出去玩,于是乎继之前的图片标注工具利用两个晚上写了一个简单的点云标注工具。该工具基于Qt5.14.2-msvc2017(其实LZ的VS版本是2019,似乎兼容)平台C++语言开发,用到的第三方库为PCL1.12.1+VTK9.1.0。之前了解到VTK9之前需要编译集成Qt的QVTKWidget插件,而之后改为将QWidget提升为QVTKOpenGLNativeWidget(同样需要自己编译VTK,编译和配置方法可参考:VTK笔记-Qt5.12.11编译VTK9.0.3-QVTKOpenGLNativeWidget、QT5+VTK9.1最新配置方法)。
实现ui界面:其实就是拖拽各种控件啦~ LZ特别喜欢弄这个,感觉挺有成就感的。包括信号和槽也可以用designer可视化编辑实现,就懒得写代码了。在这里插入图片描述

受于篇幅限制,只贴出部分核心实现代码,一共也就两百多行:
mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H


#include "label_settings.h"
#include "scale_settings.h"
#include "help_settings.h"

#include <QMainWindow>
#include <QFileDialog>
#include <QMessageBox>

#include <iostream>

#include <pcl/io/pcd_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/features/moment_of_inertia_estimation.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <vtkOutputWindow.h>


QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE


class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);

    ~MainWindow();

private slots:
    void on_action_open_cloud_triggered();

    void on_action_close_cloud_triggered();

    void on_action_save_label_triggered();

    void on_action_delete_label_triggered();

    void update_viewer();

    void on_action_add_box_triggered();

    void on_action_x_bigger_triggered();

    void on_action_x_smaller_triggered();

    void on_action_y_bigger_triggered();

    void on_action_y_smaller_triggered();

    void on_action_z_bigger_triggered();

    void on_action_z_smaller_triggered();

    void on_action_l_bigger_triggered();

    void on_action_l_smaller_triggered();

    void on_action_w_bigger_triggered();

    void on_action_w_smaller_triggered();

    void on_action_h_bigger_triggered();

    void on_action_h_smaller_triggered();

    void on_action_set_scale_triggered();

    void on_action_show_help_triggered();

private:
    Ui::MainWindow *ui;

    Label_Settings *label_settings;

    Scale_Settings *scale_settings;

    Help_Settings *help_settings;

    QString cloud_path, label_path;

    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud;

    pcl::visualization::PCLVisualizer::Ptr viewer;

    struct BoundingBox
    {
        Eigen::Vector3f position;
        Eigen::Quaternionf quat;
        float l;
        float w;
        float h;
        int label;
    } box;

    std::vector<BoundingBox> boxes;

    static int box_id;
};


#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "helpers.h"


int MainWindow::box_id = 0;


MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    label_settings = new Label_Settings;
    scale_settings = new Scale_Settings;
    help_settings = new Help_Settings;

    cloud.reset(new pcl::PointCloud<pcl::PointXYZ>);

    auto renderer = vtkSmartPointer<vtkRenderer>::New();
    auto renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
    viewer.reset(new pcl::visualization::PCLVisualizer(renderer, renderWindow, "viewer", false));

    vtkOutputWindow::SetGlobalWarningDisplay(0);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_action_open_cloud_triggered()
{
    cloud_path = QFileDialog::getOpenFileName(this, QString("打开.pcd文件"), "", "*.pcd");
    if(cloud_path.isEmpty())
    {
        warningbox(QString("请选择有效的点云路径!"));
        return;
    }

    pcl::io::loadPCDFile(cloud_path.toStdString(), *cloud);
    boxes.clear();

    viewer->removeAllPointClouds();
    viewer->removeAllShapes();
    viewer->addPointCloud(cloud, "cloud");
    viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "cloud");
    viewer->setupInteractor(ui->qvtkWidget->interactor(), ui->qvtkWidget->renderWindow());
    ui->qvtkWidget->setRenderWindow(viewer->getRenderWindow());
    ui->qvtkWidget->renderWindow()->Render();
    ui->statusBar->showMessage(cloud_path);
}

void MainWindow::on_action_close_cloud_triggered()
{
    cloud->clear();
    boxes.clear();
    viewer->removeAllPointClouds();
    viewer->removeAllShapes();
    ui->qvtkWidget->renderWindow()->Render();
    ui->statusBar->clearMessage();
}

void MainWindow::on_action_save_label_triggered()
{
    if(cloud_path.isEmpty())  return;

    QString cloud_path_temp = cloud_path;
    label_path = cloud_path_temp.replace(".pcd", ".txt");

    std::fstream txt(label_path.toStdString(), 'w');
    for(auto box : boxes)
    {
        txt << box.label << " "
            << box.position.x() << " " << box.position.y() << " " << box.position.z() << " "
            << box.l << " " << box.w << " " << box.h << std::endl;
    }
    txt.close();
}

void MainWindow::on_action_delete_label_triggered()
{
    boxes.pop_back();
    viewer->removeShape(std::to_string(box_id));
    ui->qvtkWidget->renderWindow()->Render();
}

void MainWindow::update_viewer()
{
    viewer->removeShape(std::to_string(box_id));
    viewer->addCube(box.position, box.quat, box.l, box.w, box.h, std::to_string(box_id));
    viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 1, 0, 0, std::to_string(box_id));
    viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_OPACITY, 0.1, std::to_string(box_id));
    viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_LINE_WIDTH, 1, std::to_string(box_id));
    ui->qvtkWidget->renderWindow()->Render();
}

void MainWindow::on_action_add_box_triggered()
{
    if(cloud->size() == 0)  return;

    label_settings->exec();

    pcl::PointXYZ min_point_AABB, max_point_AABB;
    pcl::MomentOfInertiaEstimation<pcl::PointXYZ> feature_extractor;
    feature_extractor.setInputCloud(cloud);
    feature_extractor.compute();
    feature_extractor.getAABB(min_point_AABB, max_point_AABB);

    box.position.x() = (min_point_AABB.x + max_point_AABB.x) / 2;
    box.position.y() = (min_point_AABB.y + max_point_AABB.y) / 2;
    box.position.z() = (min_point_AABB.z + max_point_AABB.z) / 2;
    box.quat = Eigen::Quaternionf(1, 0, 0, 0);
    box.l = max_point_AABB.x - min_point_AABB.x;
    box.w = max_point_AABB.y - min_point_AABB.y;
    box.h = max_point_AABB.z - min_point_AABB.z;
    box.label = Label_Settings::label;

    boxes.push_back(box);
    box_id++;

    update_viewer();
    viewer->addCoordinateSystem(std::max(box.l, std::max(box.w, box.h)) / 10);
}

void MainWindow::on_action_x_bigger_triggered()
{
    if(cloud->size() == 0)  return;

    box.position.x() += Scale_Settings::xyz_scale * box.l;
    boxes.pop_back();
    boxes.push_back(box);
    update_viewer();
}

void MainWindow::on_action_x_smaller_triggered()
{
    if(cloud->size() == 0)  return;

    box.position.x() -= Scale_Settings::xyz_scale * box.l;
    boxes.pop_back();
    boxes.push_back(box);
    update_viewer();
}

void MainWindow::on_action_y_bigger_triggered()
{
    if(cloud->size() == 0)  return;

    box.position.y() += Scale_Settings::xyz_scale * box.w;
    boxes.pop_back();
    boxes.push_back(box);
    update_viewer();
}

void MainWindow::on_action_y_smaller_triggered()
{
    if(cloud->size() == 0)  return;

    box.position.y() -= Scale_Settings::xyz_scale * box.w;
    boxes.pop_back();
    boxes.push_back(box);
    update_viewer();
}

void MainWindow::on_action_z_bigger_triggered()
{
    if(cloud->size() == 0)  return;

    box.position.z() += Scale_Settings::xyz_scale * box.h;
    boxes.pop_back();
    boxes.push_back(box);
    update_viewer();
}

void MainWindow::on_action_z_smaller_triggered()
{
    if(cloud->size() == 0)  return;

    box.position.z() -= Scale_Settings::xyz_scale * box.h;
    boxes.pop_back();
    boxes.push_back(box);
    update_viewer();
}

void MainWindow::on_action_l_bigger_triggered()
{
    if(cloud->size() == 0)  return;

    box.l += Scale_Settings::lwh_scale * box.l;
    boxes.pop_back();
    boxes.push_back(box);
    update_viewer();
}

void MainWindow::on_action_l_smaller_triggered()
{
    if(cloud->size() == 0)  return;

    box.l -= Scale_Settings::lwh_scale * box.l;
    boxes.pop_back();
    boxes.push_back(box);
    update_viewer();
}

void MainWindow::on_action_w_bigger_triggered()
{
    if(cloud->size() == 0)  return;

    box.w += Scale_Settings::lwh_scale * box.w;
    boxes.pop_back();
    boxes.push_back(box);
    update_viewer();
}

void MainWindow::on_action_w_smaller_triggered()
{
    if(cloud->size() == 0)  return;

    box.w -= Scale_Settings::lwh_scale * box.w;
    boxes.pop_back();
    boxes.push_back(box);
    update_viewer();
}

void MainWindow::on_action_h_bigger_triggered()
{
    if(cloud->size() == 0)  return;

    box.h += Scale_Settings::lwh_scale * box.h;
    boxes.pop_back();
    boxes.push_back(box);
    update_viewer();
}

void MainWindow::on_action_h_smaller_triggered()
{
    if(cloud->size() == 0)  return;

    box.h -= Scale_Settings::lwh_scale * box.h;
    boxes.pop_back();
    boxes.push_back(box);
    update_viewer();
}

void MainWindow::on_action_set_scale_triggered()
{
    scale_settings->exec();
}

void MainWindow::on_action_show_help_triggered()
{
    help_settings->exec();
}

代码很容易看懂。需要解释的地方:boxes是用于储存所有BoundingBox的容器,box_id是用于定义每个box的唯一id。类的构造函数中的

vtkOutputWindow::SetGlobalWarningDisplay(0);

用来消除VTK的warning窗口。
另外,旧版本Qt+VTK刷新窗口方式为

ui->qvtkWidget->update();

新版本对应语句为

ui->qvtkWidget->renderWindow()->Render();

本工具实现了打开点云、关闭点云,新建点云3d boundingbox(初始化为点云的AABB包围盒)并调整包围盒的位置、大小,以及保存标注、删除标注的功能。不过代码量很小,就别指望有什么高级功能了hh~ LZ感觉用是能用,就是调整包围盒太位置和大小的时候麻烦了,可能是没有实现鼠标拖动相应的功能吧。其他功能待感兴趣的小伙伴发掘和完善~
最后贴上一张界面效果图和保存的标注(格式:每行为box的cls x y z w h l)
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

ModaHub魔搭社区:“百模大战”下,字节跳动选择做一个“大模型商场”

“火山方舟”面向企业提供模型精调、评测、推理等全方位的平台服务&#xff08;MaaS&#xff0c;即Model-as-a-Service&#xff09;&#xff0c;目前集成了百川智能、出门问问、复旦大学MOSS、IDEA研究院、澜舟科技、MiniMax、智谱AI等多家AI科技公司及科研团队的大模型。 这种…

通过elementui的el-table实现table嵌套展示

el-table属性&#xff1a; :expand-row-keys: 可以控制行的展开和关闭&#xff0c;也可以控制只能有一行进行展开 expand-change&#xff1a;是表格行的切换事件&#xff0c;具体方法如下 el-table-column属性&#xff1a; type"expand" &#xff1a;表示如果有子t…

从网上复制shell脚本 到 linux下运行 碰到的各种问题汇总

从网上复制shell脚本 到 linux下运行 碰到的各种问题汇总 快捷键CtrlU查看网页源码 一、报错现象&#xff1a;: No such file or directory 解决方法&#xff1a;在linux系统下执行&#xff1a;dos2unix filename 问题原因&#xff1a;本质是文件中二进制符号^M乱码问题 参考…

JAVA学习(七)

1. JAVA 多线程并发 1.1 JAVA 并发 并发知识库 1.2 JAVA 线程实现/创建 创建方式 1.2.1 承 继承 Thread 类 Thread 类本质上是实现了 Runnable 接口的一个实例&#xff0c;代表一个线程的实例。启动线程的唯一方 法就是通过 Thread 类的 start()实例方法。start()方法是一个…

c++实现httpd服务器

文章目录 网络的初始化端口的分配协议的使用WSAStartup函数参数1&#xff1a;WORD wVersionRequested参数2&#xff1a;LPWSADATA lpWSAData返回值 int代码实现 创建套接字int af表示套接字的类型(网络套接字 文件套接字)int type数据流 数据报int protocol 通信协议 TCP &…

网络空间安全数学基础考试要点

网络空间安全数学基础 阶的计算不要求那个公式&#xff0c;但是Order几次方要求 考试会考原根 Legendre必考 多项式计算必考 扩域多项式计算 同态不考 域元素表示 本元多项式不考 1.整除 3 ≡ \equiv ≡ 4 mod 7不对吧3 ≡ \equiv ≡ 3 mod 74 ≡ \equiv ≡ 4 &#xff08;m…

DITA技巧:给文字加颜色

- 1 - 场景 在文档中&#xff0c;我们有时候会在文字中使用颜色。 比如&#xff1a; 在文档中&#xff0c;使用在文字上加颜色来代表一定意义。使用MS Word编写文档的时候&#xff0c;直接在文字上加颜色就可以了。转换成DITA以后&#xff0c;大家会发现在XML编辑器的工具栏…

css实现大屏效果的背景div

实现大屏效果的背景div, 效果如下: html <div class"box">1111111</div>css .box {width: 200px;height: 80px;background: linear-gradient(270deg, #00cda2, #00cda2) 0 0 no-repeat,linear-gradient(180deg, #00cda2, #00cda2) 0 0 no-repeat,line…

JUC高并发编程-初篇(后续发布高阶篇)

JUC高并发编程 1.JUC概述 1.1 什么是JUC JUC就是java.util.concurrent工具包的简称。这是一个处理线程的工具包&#xff0c;JDK1.5开始出现的。 1.2 线程和进程概念 进程&#xff1a;指在系统中正在运行的一个应用程序&#xff1b;程序一旦运行就是进程&#xff1b;进程—…

数据结构--队列的基本概念

数据结构–队列的基本概念 队列的定义 队列其实是一种受限制的线性表 队列(Queue)&#xff1a;是 只允许在一端进行插入或删除操作 \color{red}只允许在一端进行插入或删除操作 只允许在一端进行插入或删除操作的线性表 重要术语: 队头、队尾、空队列 队列的特点: 先进先出 \…

表格检测识别技术面临的挑战和发展趋势

第四章 表格检测识别技术面临的挑战和发展趋势 现在表格区域检测的准确率已经很高了。但检测和识别是相辅相成的&#xff0c;单独的检测不够完善。如何利用检测和结构识别的结果互相提高效果&#xff0c;是未来的研究方向和重点。 由于表格应用场景较为广泛&#xff0c;表格形…

【MySQL学习笔记】(三)操作表(结构)

表 1 创建表2 查看表结构3 修改表4 删除表 注&#xff1a;本篇文章操作的是表的结构&#xff0c;并不是表的内容。 属于笔记&#xff08;一&#xff09;中的SQL分类中的DDL 1 创建表 语法&#xff1a; CREATE TABLE table_name ( field1 datatype, field2 datatype, field3 d…

线程同步器:CountDownLatch、CyclicBarrier、Semaphore

CountDownLatch 日常开发中经常遇到一个线程需要等待一些线程都结束后才能继续向下运行的场景&#xff0c;在CountDownLatch出现之前通常使用join方法来实现&#xff0c;但join方法不够灵活&#xff0c;所以开发了CountDownLatch。场景&#xff1a;一个等其他多个线程&#xf…

消息中间件进阶学习

文章目录 1、RabbitMQ1.1、如何保证消息不丢失&#xff1f;小总结面试快速答法 1.2、消息的重复消费问题面试快速答法 1.3、死信交换机小总结面试快速答法 1.4、消息堆积怎么解决小总结面试快速答法 1.5、集群小总结面试快速答法 2、Kafka2.1、Kafka是如何保证消息不丢失小总结…

Linux中Docker详细安装说明

1.准备环境 说明&#xff1a;准备Linux系统centos7版本(以上) 2.切换管理模式 说明&#xff1a;输入一下命令&#xff0c;然后回车&#xff0c;输入密码。 su – 3.更新yum 说明&#xff1a;为了保证doker能够给顺利安装&#xff0c;那么更新一下&#xff1b;如果没有也可以…

单相智能电量多用户远程预付费控系统优化的设计及应用

摘要&#xff1a;由于现有系统仅对电表数据进行读取操作&#xff0c;存在成本较高和耗时较长的问题&#xff0c;为此对单相智能多用户远程预付费控系统优化设计进行研究。选择电能表子系统作为优化对象&#xff0c;选取78KO527A微控制器作为电能表子系统的控制核心&#xff0c;…

文献阅读:中国物理海洋学研究70 年-发展历程、学术成就概览

摘要 本文概略评述新中国成立70 年来物理海洋学各分支研究领域的发展历程和若干学术成就。中国物理海洋学研究起步于海浪、潮汐、近海环流与水团&#xff0c;以及以风暴潮为主的海洋气象灾害的研究。随着国力的增强&#xff0c;研究领域不断拓展&#xff0c;涌现了大量具有广泛…

Linux踢掉远程登录用户

Linux踢掉远程登录用户 安装psmisc yum install -y psmisc查看远程登录用户 who得到以下结果 [rootcentos7 ~]# w10:58:13 up 0 min, 2 users, load average: 0.12, 0.03, 0.01 USER TTY FROM LOGIN IDLE JCPU PCPU WHAT lhz pts/0 19…

mysql——数据库设计

前言 之前我们已经了解了 mysql 的基本增删改查mysql 从入门到放弃——基本约束以及语法 现在我们系统的进行一遍数据库的设计 直接进入主题 来个例子&#xff1a;下面我们将围绕这个例子来进行数据库的设计 我们就来简单的模拟 大学教务处的选课 系统 中的 选课功能 注意…

十大排序算法(Java实现)

文章目录 零、总览 / 前言一、冒泡排序1.算法描述2.代码&复杂度 二、选择排序1.算法描述2.代码&复杂度 三、插入排序1.算法描述2.代码&复杂度分析 四、希尔排序1.算法步骤2.代码&复杂度分析 五、归并排序1.算法描述2.代码&复杂度分析 六、快速排序1.算法描…