点云-标注-分类-航线规划软件 (一)点云自动分类 - 实践

news/2025/10/2 11:38:14/文章来源:https://www.cnblogs.com/ljbguanli/p/19123383

点云-标注-分类-航线规划软件 (一)点云自动分类 - 实践

在点云-标注-分类-航线规划软件,除了支持人工手动标注以外,系统还支持点云自动分类功能:

在这里插入图片描述

具体的,使用的点云分类算法为RandLa-Net,该网络模型介绍如下:

三维点云语义分割作为计算机视觉与遥感感知领域的核心任务之一,旨在为无序、非结构化的点云数据中每一个点赋予语义类别标签。在电力巡检、智能电网、基础设施建模等工程场景中,该技术可实现对输电线路关键部件(如杆塔、导线、绝缘子)及周边环境要素(如植被、房屋、地面)的自动化识别与空间定位,具有重要的工程应用价值。

然而,传统基于深度学习的点云分割方法(如 PointNet++ 、KPConv 、PointCNN [)在处理大规模点云时,普遍存在计算复杂度高、内存占用大、推理效率低等问题,难以满足工业级实时处理需求。为此,Hu 等人于 CVPR 2020 提出 RandLA-Net: Efficient Semantic Segmentation of Large-Scale Point Clouds,通过引入随机采样策略局部特征注意力聚合机制,在保持较高分割精度的同时,显著提升模型推理效率,成为当前工业级点云处理的代表性轻量化架构。

网络架构与核心原理

RandLA-Net 采用编码器-解码器(Encoder-Decoder)架构,其整体网络结构如下:

整体网络结构

网络包含 4 层编码器与 4 层解码器,每层执行:

解码阶段采用**三线性插值(Trilinear Interpolation)**上采样,并通过跳跃连接(Skip Connection)融合低层几何细节与高层语义信息,最终输出逐点语义概率分布。

输入:N × (3 + C),C 为额外通道数(如强度、RGB)
输出:N × K,K 为语义类别数(如杆塔、导线、植被、房屋等)

在这里插入图片描述
RandLaNet的核心创新体现在以下三方面:

随机采样下采样策略(Random Sampling)

传统方法广泛采用最远点采样(Farthest Point Sampling, FPS)以保证采样点的空间均匀性,但其时间复杂度为 O(n²),在大规模点云中成为性能瓶颈。RandLA-Net 创新性地采用**均匀随机采样(Uniform Random Sampling)**作为下采样策略,将采样复杂度降至 O(1),极大提升处理效率。

理论补偿机制:虽牺牲理论最优的空间覆盖性,但通过后续局部特征增强模块(LocSE + Attentive Pooling)弥补信息损失,实验证明在多个基准数据集(Semantic3D, SemanticKITTI, S3DIS)上精度损失可忽略(<0.5% mIoU),而速度提升达 200 倍以上。


局部空间编码模块(Local Spatial Encoding, LocSE)

为缓解随机采样导致的局部几何结构信息丢失,RandLA-Net 引入 LocSE 模块,对每个中心点 p_i 及其 k 个近邻点 {p_j},计算其相对空间位移向量:

Δp_ij = [x_j - x_i, y_j - y_i, z_j - z_i, ||p_j - p_i||₂]

该四维向量经多层感知机(MLP)映射后,与原始点特征拼接,形成增强后的局部空间感知特征。该机制显式建模点间相对位置关系,提升网络对细长结构(如导线)、垂直结构(如杆塔)等电力设施的空间敏感性。


注意力池化聚合(Attentive Pooling)

在特征聚合阶段,传统方法多采用最大池化(Max Pooling)或平均池化(Average Pooling),忽略邻域点贡献的差异性。RandLA-Net 设计注意力权重机制,对每个邻域点 p_j 学习其相对于中心点 p_i 的重要性权重 α_ij:

α_ij = softmax(MLP([f_i, f_j, Δp_ij]))
f_i' = Σ_j (α_ij ⋅ f_j)

其中 f_i, f_j 为点特征,Δp_ij 为 LocSE 输出。该机制实现自适应邻域特征加权聚合,有效抑制噪声点干扰,增强对关键结构点(如金具连接处、绝缘子串端点)的特征表达能力。


具体实现

那么,这个网络具体是如何使用的呢,在单机版分类软件中,为了使软件能够稳定运行,我们采用了一种流式处理的方式来进行点云分类,简单来讲,就是通过流式读取点云(每次读取100000个点)然后进行累积,当点云的大小达到一个G时,我们再将点云输入到模型中进行推理,这样做的好处是能够让一些性能稍弱的电脑也能够正常使用:

def process_large_las_streaming(file_path, output_dir, target_size_gb=1, chunk_size=1000000,db_utils=None):
temp_files = []
try:
Path(output_dir).mkdir(parents=True, exist_ok=True)
temp_output_dir = Path(output_dir) / "temp"
temp_output_dir.mkdir(parents=True, exist_ok=True)
avg_point_size = 32  # 这里可以考虑使用更精确的方法来估计单个点的大小
target_point_count = int(target_size_gb * 1024**3 / avg_point_size)
processed_points = 0
total_points = 0
with laspy.open(file_path) as reader:
header = copy_header(reader.header)
if header.point_count > 0:
total_points = header.point_count
else:
print("Warning: Header point count is 0. Estimating total points from chunks.")
combined_obj = LasObj()
for points in reader.chunk_iterator(chunk_size):
# 将当前块的数据添加到 combined_obj 中
combined_obj.x = np.concatenate((combined_obj.x, points.x))
combined_obj.y = np.concatenate((combined_obj.y, points.y))
combined_obj.z = np.concatenate((combined_obj.z, points.z))
combined_obj.classification = np.concatenate((combined_obj.classification, points.classification))
if hasattr(points, 'red'):
combined_obj.red = np.concatenate((combined_obj.red, points.red))
combined_obj.green = np.concatenate((combined_obj.green, points.green))
combined_obj.blue = np.concatenate((combined_obj.blue, points.blue))
processed_points += len(points)
if len(combined_obj.x) >= target_point_count or (total_points > 0 and processed_points >= total_points):
# 更新头部信息中的点数
temp_header = copy_header(header)
temp_header.point_count = len(combined_obj.x)
# 推理分类
labels = inference_api.pd_inference(combined_obj)
belong_file="1Points/"+os.path.basename(file_path)
cut_points = db_utils.select("cuts", where_clause="belong_file = ?", params=(belong_file,),
order_by="CAST(name AS INTEGER) ASC")
points_data = [(float(row[2]), float(row[3]), float(row[4])) for row in cut_points]  # xyz分别对应第2,3,4个字段
towers = np.array(points_data)
protected_labels = {4,9,}  # 示例需要保护的标签(如卷子、变压器)
# 优化分类
points_mine = np.column_stack((combined_obj.x, combined_obj.y, combined_obj.z))
labels=process_point_cloud_with_boxes(points_mine,labels,towers)#处理导线
labels =optimize_classification(points_mine, labels, towers, grid_size=1.0, tower_radius=1.0, protected_labels=protected_labels)#处理杆塔
#labels=refine_labels_by_knn(points_mine,labels)
combined_obj.classification = labels
# 保存到临时文件
temp_file = temp_output_dir / f"{Path(file_path).stem}_{processed_points}.las"
save_to_las(combined_obj, str(temp_file), temp_header)
temp_files.append(temp_file)
print(f"Progress: {processed_points}/{total_points if total_points > 0 else 'unknown'} points processed.")
combined_obj = LasObj()
# 处理剩余的点云数据
if len(combined_obj.x) > 0:
temp_header = copy_header(header)
temp_header.point_count = len(combined_obj.x)
labels = inference_api.pd_inference(combined_obj)
combined_obj.classification = labels
temp_file = temp_output_dir / f"{Path(file_path).stem}_{processed_points}.las"
save_to_las(combined_obj, str(temp_file), temp_header)
temp_files.append(temp_file)
print(f"Progress: {processed_points}/{total_points if total_points > 0 else 'unknown'} points processed.")
# 在合并时覆盖原始文件
merge_temp_files_with_laspy(temp_files, file_path)
except Exception as e:
print(f"Error processing file: {e}")
raise
finally:
for temp_file in temp_files:
if temp_file.exists():
temp_file.unlink()

在上述方法中,可大致分为 预处理(即划分1GB点云)、模型推理以及后处理三个过程,其中模型推理的代码如下:

labels = inference_api.pd_inference(combined_obj)

该方法传入的是一个lasObj,具体实现如下:

def pd_inference(lasObj):
print('tf 开始推理')
time1 = time.time()
chosen_snap_0 = resource_path('resources/pd_model/model0/snap-177.meta')
chosen_snap_1 = resource_path('resources/pd_model/model1/snap-45.meta')
chosen_snap_2 = resource_path('resources/pd_model/model2/snap-94.meta')
pred = tester_service.runner(lasObj, chosen_snap_0,chosen_snap_1, chosen_snap_2)
time2 = time.time()
print(f'tf 推理结束,本次推理共用时 {time2 - time1}s')
return pred

runner方法定义如下,我们发现RandLaNet一次性预测所有类别时,其效果不理想,因此采用分段式预测的方式,即首先预测植被和其他物体,随后将其他物体的点云提取出来,放入第二个模型,第二个模型负责预测电力物、建筑物、公路以及地面,随后再将电力物点云提取出来,送入第三个模型,负责细分电力物,如导线、杆塔等,最终,将分类后的点云按照对应的ID(索引)赋值对应的类别即可。

def runner(las_file, chosen_snap_0,chosen_snap_1, chosen_snap_2):
tf.logging.set_verbosity(tf.logging.ERROR)
# 定义类别映射字典
res_dict = {
'tower': 0,         # 杆塔
'wire': 1,          # 导线
'cross_wire': 2,    # 交叉导线
'other_wire': 3,    # 其他导线
'swich': 4,         # 开关
'building': 5,      # 建筑物
'plant': 6,         # 植被
'ground': 7,        # 地面
'lantern': 8,       # 路灯
'transformer': 9,   # 变压器
'road': 10,         # 公路
'otherthing': 11    # 其他物体
}
# 模型0的类别映射:将点云分为“其他”和“植被”
label_to_names_0 = {
0: 'other',        # 其他
1: 'plant'         # 植被
}
# 模型1的类别映射:将“其他”类别进一步分类为“电力物”、“建筑物”、“公路”等
label_to_names_1 = {
0: 'power',        # 电力物
1: 'building',     # 建筑物
2: 'ground',         # 地面
3: 'road' ,       # 公路
}
# 模型2的类别映射:将“电力物”类别细分为具体的电力相关类别
label_to_names_2 = {
0: 'tower',        # 杆塔
1: 'wire',         # 导线
2: 'other_wire',   # 其他导线
3: 'swich',        # 开关
4: 'lantern',      # 路灯
5: 'transformer',  # 变压器
6: 'otherthing'    # 其他物体
}
# 清空会话
tf.keras.backend.clear_session()
# 读取点云数据并归一化
pointcloud_arr = np.vstack((las_file.x, las_file.y, las_file.z)).transpose()
points_arr = pointcloud_arr
x_min = pointcloud_arr[:, 0].min()
y_min = pointcloud_arr[:, 1].min()
z_min = pointcloud_arr[:, 2].min()
points_arr[:, 0] -= x_min
points_arr[:, 1] -= y_min
points_arr[:, 2] -= z_min
# 加载配置文件
config = read_yaml_config(resource_path("configs/configs.yaml"))
parameter0 = config.get('parameter0')  # 模型0的参数
parameter1 = config.get('parameter1')  # 模型1的参数
parameter2 = config.get('parameter2')  # 模型2的参数
# =============================== 模型0推理 ===============================
# 模型0任务:将点云分为“其他”和“植被”
data_0, preds_0 = model_test(
"peidian_0", cfg0, points_arr, label_to_names_0, len(label_to_names_0), chosen_snap_0,
sub_grid_size=parameter0
)
# 提取出“其他”类别的点云送入模型1
preds_ind_other = np.where(preds_0 == 0)[0]  # “其他”类别的索引
other_data = data_0[preds_ind_other]
# =============================== 模型1推理 ===============================
# 模型1任务:将“其他”类别进一步分类为“电力物”、“建筑物”、“公路”等
tf.keras.backend.clear_session()
data_1, preds_1 = model_test(
"peidian_1", cfg1, other_data, label_to_names_1, len(label_to_names_1), chosen_snap_1,
sub_grid_size=parameter1
)
# 提取出“电力物”类别的点云送入模型2
preds_ind_power = np.where(preds_1 == 0)[0]  # “电力物”类别的索引
power_data = data_1[preds_ind_power]
# =============================== 模型2推理 ===============================
# 模型2任务:将“电力物”类别细分为具体的电力相关类别
tf.keras.backend.clear_session()
data_2, preds_2 = model_test(
"peidian_2", cfg2, power_data, label_to_names_2, len(label_to_names_2), chosen_snap_2,
sub_grid_size=parameter2
)
# =============================== 合并结果 ===============================
# 初始化最终结果为模型0的预测结果
pred_result = copy.deepcopy(preds_0)
# 将模型0的“其他”类别替换为模型1和模型2的预测结果
for label_1 in label_to_names_1:
idx_in_preds_1 = np.where(preds_1 == label_1)[0]  # 模型1中属于该类别的索引
if label_to_names_1[label_1] == 'power':  # 如果是“电力物”类别,继续替换为模型2的结果
for label_2 in label_to_names_2:
idx_in_preds_2 = np.where(preds_2 == label_2)[0]  # 模型2中属于该类别的索引
pred_result[preds_ind_other[preds_ind_power[idx_in_preds_2]]] = res_dict[label_to_names_2[label_2]]
else:
# 将模型1的非“电力物”类别直接映射到最终结果
pred_result[preds_ind_other[idx_in_preds_1]] = res_dict[label_to_names_1[label_1]]
# 将模型0的“植被”类别映射到最终结果
idx_plant = np.where(preds_0 == 1)[0]
pred_result[idx_plant] = res_dict['plant']
del data_1
del data_2
gc.collect()
return pred_result

至于具体的网络推理代码,我这里就不再赘述了,大家可以下载 RandLaNet 的源码自行了解,至此,便介绍完了点云的自动分类流程,随后,会进行后处理过程,该过程则是通过机器学习的方式,对分类结果进行进一步的优化。

码字不易,给个赞呗!

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

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

相关文章

JVM的内存分配策略有哪些?

JVM的内存分配策略主要围绕对象在堆内存中的分配规则展开,核心目标是高效利用内存并减少垃圾回收开销。主要分配策略如下: 1. 优先在Eden区分配 大多数对象在新生代的Eden区中创建。当Eden区没有足够空间时,JVM会触…

网站的功能有哪些公司文化建设的意义

双写机制 问题的出现 在发生数据库宕机时&#xff0c;可能Innodb正在写入某个页到表中&#xff0c;但是这个页只写了一部分&#xff0c;这种情况被称为部分写失效&#xff0c;虽然innodb会先写重做日志,在修改页&#xff0c;但是重做日志中记录的是对页的物理操作&#xff0c;但…

网站网站做代理怎么发展下线太原seo优化公司

论文链接&#xff1a;http://aihuang.org/p/papers/AAAI2018Denoising.pdf来源&#xff1a;AAAI 2018MotivationDistant Supervision 是一种常用的生成关系分类训练样本的方法&#xff0c;它通过将知识库与非结构化文本对齐来自动构建大量训练样本&#xff0c;减少模型对人工标…

网站给部分文字做遮挡代码义乌外贸网站建设

2020年android 仿微信朋友圈 评论1.如果有人问我:那些艰难的岁月你是怎么熬过来的?我想我只有一句话回答:我有一种强大的精神力量支撑着我,这种力量名字叫“想死又不敢”二十、我喜欢转身转得漂亮&#xff0c;放手放得潇洒你在玩以这样的挂念&#xff0c;会悲伤的逃脱11、我嫉…

如何建设英文网站免费建站网站

HYJY系列电压继电器 HYJY-30-01集成电路电压继电器 HYJY-30-01A HYJY-30-01B HYJY-30-02集成电路电压继电器 HYJY-30-02A HYJY-30-02B HYJY-30-03-3集成电路电压继电器 HYJY-30-03-2 HYJY-30-03-1 HYJY-30-02电压继电器&#xff08;以下简称继电器&#xff09;用于发…

在Linux系统上一键配置DoH,解决DNS解析被污染

前言 最近我的 swag 服务突然证书 renew 失败 诊断了一下发现原来是无法解析 acme-v02.api.letsencrypt.org 域名 换了几个 DNS 都不行,应该是 DNS 被污染或者劫持了 这时我才意识到不上 DoH/DoT 怕是没办法了🤣 本…

免费网站源码下载器网站开发报价文件

介绍 ai查询 在Java中&#xff0c;动态数组通常通过ArrayList类来实现&#xff0c;它是Java集合框架&#xff08;Java Collections Framework&#xff09;的一部分。ArrayList是一个可调整大小的数组实现&#xff0c;提供了比标准数组更多的灵活性和功能。 以下是使用ArrayLis…

大连模板网站制作哪家专业天津专业网站制作流程优势

目录 一.Python 线程队列 Queue 分类二.Python 线程优先队列 PriorityQueue 简介三.Python 线程优先队列 PriorityQueue 函数介绍四.Python 线程优先队列 PriorityQueue 使用五.猜你喜欢 零基础 Python 学习路线推荐 : Python 学习目录 >> Python 基础入门 在 线程队列…

服务器搭建网站方案500字淮北人论坛招聘信息

预算2014年院线预算 影城&#xff08;多选&#xff09;北京CBD影城 查询 科目蚌埠万达广场店北京CBD影城  营业收入11 票房收入11 卖品收入00 逾期收入00 广告收入00 映前广告00 LCD广告00 阵地收入00 IMAX广告收入00 其他广告收入00 其…

网页设计制作网站模板图片自己做的网站放在服务器哪里

文章目录 一、绪论1.1、数据结构的基本概念1.2、数据结构三要素1.2.1、逻辑结构1.2.2、数据的运算1.2.3、物理结构&#xff08;存储结构&#xff09;1.2.4、数据类型和抽象数据类型 二、算法的基本概念2.1、算法的特性2.2、“好”算法的特质2.2.1、算法时间复杂度2.2.2、算法空…

网站商城微信支付wordpress大前端2.0

V90伺服驱动器其它相关介绍,请参考V90控制专栏,常用地址如下: V90 Epos模式下点动控制 https://rxxw-control.blog.csdn.net/article/details/134263795https://rxxw-control.blog.csdn.net/article/details/134263795绝对定位往复运动可以参考下面文章链接: https://rx…

《电路基础》第五章学习笔记

《电路基础》第五章学习笔记本章主要介绍运算放大器。 一个特性与电压控制电压源类似的电子元件。可以对信号进行相加、放大、积分和微分。可以看作增益非常高的电压放大器。运算放大器定义: 运算放大器是一个用于执行…

完整教程:C 语言各种指针详解

完整教程:C 语言各种指针详解2025-10-02 11:23 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !importan…

51单片机-实现DAC(PWM)数模转换PWM控制呼吸灯、直流电机实验教程 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Elasticsearch集群监控信息(亲测) - 教程

Elasticsearch集群监控信息(亲测) - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "M…

基于Java springboot农村政务服务管理便捷的系统(源码+文档+运行视频+讲解视频)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

三木做网站初中校园网站建设制度

Java面向对象编程篇3——接口与抽象类 1、接口&#xff08;interface&#xff09; 接口中可以含有变量和方法。但是要注意&#xff0c;接口中的变量会被隐式地指定为public static final变量&#xff08;并且只能是public static final变量&#xff0c;用private修饰会报编译…

Matplotlib子图布局与响应式设计实战:GridSpec与CSS框架深度结合 - 教程

Matplotlib子图布局与响应式设计实战:GridSpec与CSS框架深度结合 - 教程2025-10-02 11:11 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: a…

专做机械类毕业设计的网站网站如何备案流程图

由于Vmware虚拟网卡和linux兼容问题导致驱动无法正常安装&#xff0c;默认的网卡类型不兼容. 解决方法 找到我们的Vmware虚拟机文件夹&#xff0c;将VMware 虚拟机配置 (.vmx)&#xff0c;追加一条设置我们网卡类型 ethernet0.virtualDev "e1000" 原因 VMware 都在虚…

怎样建一个好的网站万能素材

一、触摸事件&#xff1a; 1.touchstart&#xff1a;手指刚接触屏幕时触发。 2.touchmove:手指在屏幕上移动时触发。 3.touchend:手指移开屏幕时触发。 eg: var span document.getElementsByTagName(“span”)[0]; var div document.getElementsByTagName(“div”)[0]; //手指…