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

news/2024/12/28 16:35:54/

夏天来了非常热,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_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic: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.ppmy.cn/news/667806.html

相关文章

不能错过!提升MacBook工作效率的5个小贴士

全文共1764字,预计学习时长6分钟 来源:Pexels 电脑和手机一样,更新换代速度飞快。随着人民生活水平的不断提高,渐渐从最初的奢侈品“转型”为如今的消耗品。 但是,你知道吗?对MacBook进行维护通常是可以延长其寿命的。 大约七年前,笔者买了第一台MacBook,现在还在用它从…

MacBook还能这么玩?赶快学会了去和朋友炫技

全文共1265字&#xff0c;预计学习时长6分钟 图源&#xff1a;unsplash MacBook有哪些隐藏技能你知道吗&#xff1f;让它变身复读机、天气预报主持人或是心灵鸡汤大师&#xff0c;几个很简单的步骤就能实现。 这八个有趣的终端命令大多数人都不知道&#xff0c;赶快学会去和朋…

MacBook电脑添加环境变量

1、MacBook系统的环境变量&#xff0c;加载顺序为&#xff1a; a. /etc/profile b. /etc/paths c. ~/.bash_profile d. ~/.bash_login e. ~/.profile f. ~/.bashrc其中 a 和 b 是系统级别的&#xff0c;系统启动就会加载&#xff0c;其余是用户接别的。 c, d, e 按照从前往后的…

air英语怎么读_如何用英语发音“Macbook Air”

2016-08-29 回答 你好&#xff0c;不能,只能打开word然后保存,所有mac下软件都是这样。 首先单击“开始”菜单中的“运行”命令&#xff0c;打开“运行”对话框&#xff0c;在“打开”栏里输入“regedit”&#xff0c;然后按“确定”。然后在打开的“注册表编辑器”窗口中&…

macbook php环境视频,Macbook PHP环境搭建

sudo vi /etc/apache2/httpd.conf 取消它的注释(#)LoadModule php7_module libexec/apache2/libphp7.so LoadModule userdir_module libexec/apache2/mod_userdir.so 文件配置修改如下列 "/Users/xwz/PhpstormProjects"文件路径&#xff1a;非桌面文件 #new paths #A…

MacBook安装yaml

0.缘起&#xff1a; 遇到报错 ImportError: No module named yaml 尝试安装 sudo easy_install pyyaml 一直卡住。。。 ————————–分割线——————————- 解决方案&#xff1a; 1.下载yaml http://pyyaml.org/wiki/PyYAML 已不能访问&#xff08;2018-0…

如何解决MacBook休眠时耗电过大的问题

MacBook买来有一段时间了&#xff0c;但待机耗电这个问题一直没解决。有的时候待机一天就掉2%-3%的电&#xff0c;但有的时候待机一天能掉20%。网上尝试了很多办法都不管用。昨天心血来潮又重新试了几次&#xff0c;现在终于待机耗电正常了。 本文介绍的方法需要用到终端&…

MacBookPro外接显示器开启HiDPI

参考知乎地址 1.什么是HiDPI 通俗的解释&#xff1a;通过牺牲一定的分辨率实现更细腻的显示效果 2.为什么要开启HiDPI 外接显示器开启HiDPI后显示效果更加细腻清晰&#xff0c;未开启前字体模糊发虚 3.如何开启HiDPI 接下俩请看搬运工的表演 3.1 关闭SIP 重启电脑&…