深度学习点云去噪实战:动态图卷积与EdgeConv网络原理详解
2026/9/10 12:18:17 网站建设 项目流程

简介:基于深度学习的三维点云去噪Python源码项目,面向计算机视觉、人工智能相关专业学生与开发者,适用于毕业设计、课程设计、课程大作业及项目初期算法验证。代码已完成运行测试,可直接执行,任务流程覆盖三维点云数据的读取、预处理、添加模拟噪声、网络构建、模型训练、结果评估与可视化。资源包为压缩包格式,共有四十个文件,包括三十六个脚本源码、三个配置文件与一份说明文档,大小仅四十六千字节,项目结构紧凑。脚本源码涉及点云采样、数据增强、噪声去除、损失函数、模型训练、参数配置等关键环节;配置文件用于设定网络超参与训练参数,说明文档提供结构介绍与上手指引。项目内置噪声添加工具,可生成带噪点云,并提供去噪处理、整体流程等独立模块,用户既可直接运行完整流程,也能按需修改进行二次开发;目前已有五百四十一人学习下载,适合用于三维点云深度学习方法研究、课程报告或毕业设计参考。

1. 点云去噪为什么需要深度学习,而不是换一个滤波器

拿到一帧激光雷达点云或者结构光扫描数据,最烦的不是点数多,而是无论你调多少轮统计滤波参数,总有一层"雾"在物体边缘,还有几颗离群点像胡椒盐一样黏在表面上。传统的点云去噪做法是统计滤波、半径滤波、双边滤波,它们全都基于一个假设:噪声是稀疏的、离群的、或者频率高于表面信号。但真实扫描里的噪声往往不是这样,它贴着表面走、密度跟真实点接近、分布还随物体材质变化,传统滤波在"保特征"和"去噪"之间只能二选一。基于深度学习的三维点云去噪解决的是另一件事:与其手动设计一个几何滤波器,不如让网络从大量"干净点云+加噪点云"的配对里学会一个映射,把有噪声的点云直接回归到干净表面。这个标题压缩了一条完整的落地链路:点云预处理、深度网络设计、损失函数构造、Python源码实现、训练调参和评估。适合准备做点云预处理自动化、或者正在被传统滤波参数折磨的工程师。

2. 点云去噪网络选型:从置换不变性到动态图卷积

2.1 点云无序性决定了不能直接用卷积神经网络

点云是一组没有固定顺序的坐标点。同一帧点云,把点顺序打乱,几何意义不变,但如果你把它们当成图像像素塞进卷积核,输出的特征会因为顺序不同而完全不同。所以点云网络的第一条原则是置换不变性,即无论输入顺序怎么变,学习的特征必须一致。这个特性催生了PointNet的经典做法:对每个点做独立的特征提取,再用最大池化把所有点的特征聚合成一个全局特征。这个方案对分类、分割够用,但用在去噪上有一个明显短板:最大池化只保留"最显著"的特征,局部几何细节损失严重。去噪恰恰需要精细的局部几何信息,否则输出点云会变得过度平滑,小结构全被抹平。

常见的替代方案是多层级特征提取后把不同尺度的特征拼接起来,比如PointNet++。但对去噪任务,PointNet++的感受野受限于分组半径,在点密度不均匀的场景里效果不稳定。我通常在去噪任务里更倾向用动态图卷积(Dynamic Graph CNN,DGCNN),它通过K近邻建图,在图上做卷积,不依赖点的绝对坐标,而是依赖点与点之间的相对位置,对物体的位移、旋转天然鲁棒,而且能够捕捉到比PointNet更细的局部结构。

2.2 EdgeConv:去噪网络的骨干结构

DGCNN的核心是EdgeConv,即边缘卷积。它每次动态地根据特征距离重新计算每个点的邻域,而不是只在输入坐标上计算一次。去噪网络里,点与点之间的特征距离比欧氏距离更能反映"它们是否属于同一块表面":贴近表面的噪声点与真实点之间的欧氏距离可能很小,但在特征空间里差异显著。EdgeConv的动态建图让网络在训练过程中逐步学会区分"表面结构"和"附着噪声",这比固定邻域的分组方式更灵活。

一个标准的EdgeConv实现如下,这里用PyTorch写,K是近邻数量,通过折积升维后再聚合:

import torch import torch.nn as nn import torch.nn.functional as F def knn(x, k): """计算每个点的k近邻索引 x形状为 (batch_size, channels, num_points) 返回索引形状 (batch_size, num_points, k) """ batch_size, _, num_points = x.shape inner = -2 * torch.matmul(x.transpose(2, 1), x) xx = torch.sum(x ** 2, dim=1, keepdim=True) pairwise_dist = -xx - inner - xx.transpose(2, 1) idx = pairwise_dist.topk(k=k, dim=-1)[1] return idx def get_edge_features(x, k=20): """以坐标或特征为输入,返回中心点与邻居拼接后的边特征 """ idx = knn(x, k) batch_size, num_dims, num_points = x.shape device = x.device idx_base = torch.arange(0, batch_size, device=device).view(-1, 1, 1) * num_points idx = idx + idx_base idx = idx.view(-1) _, num_dims, _ = x.shape x = x.transpose(2, 1).contiguous() feature = x.view(batch_size * num_points, -1)[idx, :] feature = feature.view(batch_size, num_points, k, num_dims) x = x.view(batch_size, num_points, 1, num_dims).repeat(1, 1, k, 1) edge_feature = torch.cat((feature - x, x), dim=3).permute(0, 3, 1, 2) return edge_feature class EdgeConvLayer(nn.Module): def __init__(self, k, in_channels, out_channels): super().__init__() self.k = k self.mlp = nn.Sequential( nn.Conv2d(in_channels * 2, out_channels, 1), nn.BatchNorm2d(out_channels), nn.ReLU(inplace=True) ) def forward(self, x): edge_feat = get_edge_features(x, self.k) feat = self.mlp(edge_feat) feat = feat.max(dim=-1, keepdim=False)[0] return feat

knn函数计算的是特征空间里的最近邻,get_edge_features把中心点坐标和邻居的相对坐标拼接在一起,组成一个维度为2 * in_channels的边特征。EdgeConvLayer里用的是最大池化对邻居聚合,这个操作保留了邻域里最强的响应。这里的k直接决定感受野:k太小,网络只能看到局部几个点,无法判断这是平面还是角点;k太大,会把不连续表面的点也纳入邻域,导致边缘被误判为噪声。经验上,室内扫描数据k=20,室外大尺度场景可以调到16,因为室外点云密度通常更稀疏。

2.3 去噪网络的整体架构与输入输出设计

我把去噪网络拆成编码器-解码器结构:编码器逐层做EdgeConv,把点数从N降采样到N/2再到N/4,提取多尺度特征;解码器通过三最近邻插值逐步恢复分辨率,并与编码器对应层的特征拼接。这种结构能在降采样过程中排除部分离群噪声,同时保留足够的上采样细节。整个过程没有全连接层,所以网络可以接受任意数量的输入点,不需要像分类网络那样固定点数。

输入方面,最常见的是直接喂原始坐标(x, y, z),输出与输入同形状的坐标偏移量(dx, dy, dz),用输入坐标加偏移量得到去噪后的坐标。换句话说,网络不是从零开始生成点云,而是学习"每个点往哪个方向移动多少"。这样做的好处是:干净区域网络可以把偏移量预测为0,网络只需学习一个残差,收敛压力比直接回归坐标小得多。

class DenoiseNet(nn.Module): def __init__(self, k=20, feature_dims=[64, 128, 256]): super().__init__() self.k = k self.conv1 = EdgeConvLayer(k, 3, feature_dims[0]) self.conv2 = EdgeConvLayer(k, feature_dims[0], feature_dims[1]) self.conv3 = EdgeConvLayer(k, feature_dims[1], feature_dims[2]) self.up1 = nn.Conv1d(feature_dims[2] + feature_dims[1], feature_dims[1], 1) self.up2 = nn.Conv1d(feature_dims[1] + feature_dims[0], feature_dims[0], 1) self.up3 = nn.Conv1d(feature_dims[0] + 3, 64, 1) self.out_conv = nn.Conv1d(64, 3, 1) def forward(self, x): # x: (batch, 3, num_points) x1 = self.conv1(x) x1_cat = torch.cat((x, x1), dim=1) x2 = self.conv2(x1_cat) x2_cat = torch.cat((x1_cat, x2), dim=1) x3 = self.conv3(x2_cat) x3_cat = torch.cat((x2_cat, x3), dim=1) feat = F.relu(self.up1(x3_cat)) feat = F.relu(self.up2(torch.cat((feat, x2_cat), dim=1))) feat = F.relu(self.up3(torch.cat((feat, x1_cat), dim=1))) offset = self.out_conv(feat) return x + offset

参数说明:feature_dims控制网络宽度,数值越大表达能力越强,但显存占用会快速上涨。up1up3是1x1卷积,用于把拼接特征压缩到目标维度。最后的输出是一个与输入形状相同的坐标偏移,把它加到原始坐标上就得到去噪结果。训练时,如果输入点云包含离群点,网络会被迫将离群点移回表面,通常会先把它归一到近邻表面的位置,再在表面内部做微调。

2.4 损失函数组合:倒角距离加上法向量一致性

去噪任务与生成任务不同,我们希望输出点云与真实点云形状一致,又不希望网络为了追求距离误差而把所有点都"平均"到质心。因此损失函数必须同时约束位置和局部几何。最基础的损失是倒角距离(Chamfer Distance,CD),它计算预测点云中每个点到真实点云的最近距离,再反向计算一次,把两个方向的均值加起来。这个指标不需要点与点之间的对应关系,是点云去噪里事实上的标准损失。

只优化CD会导致表面抖动:点在真实表面附近来回震荡,虽然距离误差不大,但法向量乱了。我会再加一项法向量一致性损失:在预测点云和真实点云上分别用PCA估计每个点的法向量,对齐后计算法向量夹角误差。加上这项约束后,预测点会更稳定地贴合真实表面,而不是停留在"距离恰好"的位置。

def chamfer_distance(pred, target): """pred和target形状均为 (batch, num_points, 3) 返回双向倒角距离均值 """ pred = pred.permute(0, 2, 1) target = target.permute(0, 2, 1) pred = pred.unsqueeze(2) target = target.unsqueeze(1) dist = torch.sqrt(((pred - target) ** 2).sum(-1) + 1e-6) dist_pred_to_target = dist.min(dim=2)[0].mean(dim=1) dist_target_to_pred = dist.min(dim=1)[0].mean(dim=1) return (dist_pred_to_target + dist_target_to_pred).mean()

实际训练时,我会把总损失设为CD + 0.1 * normal_loss。这个0.1不是拍脑袋定的,我在合成数据集上调过几轮,权重过大会让网络优先对齐法向量,表面会显得过于规整,细节被吃掉,权重过小则退化到纯CD。如果你的数据噪声主要是高斯噪声,0.1到0.3之间都可用;如果是大幅度的形变噪声,法向量项可以去掉。

3. 用自己的数据训练点云去噪网络:从合成噪声到批处理流程

3.1 没有配对数据时,先用合成噪声做预训练

做去噪训练,最理想的情况是有一套"干净点云"和对应的"带噪点云"配对数据。真实场景里这种配对数据往往不存在:同一物体扫两次,本身就有配准误差,没法当成干净的Ground Truth。常见的做法是在干净模型上人工添加可控噪声,做一个预训练模型,然后用真实数据做无监督微调。合成噪声虽然和真实噪声有分布差异,但能让网络先学会"表面重建"这个基本能力。

合成噪声生成时,最简单的策略是对每个点施加一个沿法线方向的随机位移。我在实践里用的是混合噪声:75%的噪声点沿法线方向偏移,幅度取高斯分布标准差为噪声级别的数值,剩下25%作为离群点,随机落在整个点云包围盒内。这样训练出来的网络对贴近表面的噪声和离群点都有处理能力。给每个样本保存原始的噪声参数,方便后期按噪声强度做条件去噪,即通知网络当前输入噪声有多大,让它更有针对性地去噪。

import numpy as np def add_synthetic_noise(points, noise_level=0.05, outlier_ratio=0.1, seed=0): """给点云添加混合噪声 points: (N, 3) 干净点云 noise_level: 沿法线方向的高斯噪声标准差 outlier_ratio: 离群点比例 """ rng = np.random.default_rng(seed) n_points = points.shape[0] # 用PCA粗估法线 centroid = points.mean(axis=0) _, _, vh = np.linalg.svd(points - centroid, full_matrices=False) normal = vh[-1] noise = rng.normal(0, noise_level, size=(n_points, 1)) * normal noisy = points + noise # 随机选一部分点变成离群点 n_outliers = int(n_points * outlier_ratio) outlier_idx = rng.choice(n_points, n_outliers, replace=False) bounds = points.max(axis=0) - points.min(axis=0) noisy[outlier_idx] = points[outlier_idx] + rng.uniform(-0.5, 0.5, size=(n_outliers, 3)) * bounds return noisy

代码里的noise_level是相对模型尺寸的比值。如果你的模型归一化到单位球内,0.05表示5%的边长偏移,视觉上已经比较明显;如果模型很大还没有归一化,务必先做归一化,否则噪声幅度对不同的样本含义完全不同。outlier_ratio控制离群点数量,太高会让网络倾向把所有点当成噪声,特征细节会损失。

3.2 训练脚本的数据加载与坐标归一化

我训练时用的数据格式很简单:每个样本一个.npy文件,里面是一个(N, 3)的矩阵,N不固定。训练时每个batch的点数必须相同,所以要在加载时做随机采样,把不同文件统一到同一个点数。常见做法是把点数固定为1024,点数多的下采样,点数少的重复采样补全。这个采样过程必须放在离群点添加之后,否则采样结果会偏向干净区域。

class PointCloudDataset(torch.utils.data.Dataset): def __init__(self, file_list, num_points=1024, noise_level=0.05): self.file_list = file_list self.num_points = num_points self.noise_level = noise_level def __len__(self): return len(self.file_list) def __getitem__(self, idx): clean = np.load(self.file_list[idx]).astype(np.float32) # 归一化到单位球内 centroid = clean.mean(axis=0) clean = clean - centroid scale = np.max(np.linalg.norm(clean, axis=1)) clean = clean / scale # 重采样到固定点数 if clean.shape[0] >= self.num_points: idx_choice = np.random.choice(clean.shape[0], self.num_points, replace=False) else: idx_choice = np.random.choice(clean.shape[0], self.num_points, replace=True) clean = clean[idx_choice] noisy = add_synthetic_noise(clean, self.noise_level) return torch.from_numpy(noisy.T).float(), torch.from_numpy(clean.T).float()

noisy.T就是把形状从(N, 3)转成(3, N),这是为了匹配网络里Conv1d的通道排列习惯。归一化这一步很多人会漏掉:如果直接拿毫米级尺度的点云训练,网络权重的数量级需要适应数据的绝对坐标,学习率也会变得很难调。统一归一到单位球之后,noise_level变成了一个相对尺度的比值,超参数在不同数据集之间可以迁移。

训练循环与常规PyTorch流程一致。值得一提的是优化器的选择:我常用AdamW,初始学习率设为1e-3,配合余弦退火调度器,在前20个epoch让学习率缓缓下降到1e-5。去噪网络不是特别深,用SGD的话收敛太慢,反而容易在倒角距离这种非平滑损失上震荡。

model = DenoiseNet(k=20) optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-5) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=200) criterion = chamfer_distance for epoch in range(200): model.train() total_loss = 0 for noisy, clean in train_loader: noisy, clean = noisy.cuda(), clean.cuda() pred = model(noisy) loss = criterion(pred, clean) optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 10.0) optimizer.step() total_loss += loss.item() scheduler.step() print(f"epoch {epoch}, loss {total_loss / len(train_loader):.6f}")

clip_grad_norm_在这里不能省。EdgeConv在建图时用到topk操作,梯度的数值范围比普通卷积大,有时一个显眼的离群点会产生很大的梯度,把整个网络的权重重置一遍。我见过不少训练不稳定的情况,加上梯度裁剪之后,Loss曲线从剧烈抖动变回平滑下降。

3.3 无监督微调:没有干净点云时的自救方案

如果实际数据没有配对版本,就不要用上面的监督损失。常用的替代方案是去噪自编码器思路:对真实点云再叠加随机噪声,然后让网络输出与输入尽可能相似,但要求输出点云更"干净"。这个目标表述起来有点循环,实际操作中我会用两个正则项:一是输出点云与其自身的K近邻距离总和尽可能小,约束点云落在低维流形上;二是输入与输出的位移向量模长尽可能小,防止网络把点云整体搬走或过度扭曲。最终损失写成L = L_smooth + lambda * L_displacement的形式,L_smooth可以用输出点的邻域距离均值计算。

这种方式比监督训练要慢很多,但能让网络适应真实扫描中的噪声形态。我的经验是先在合成的重度噪声上预训练一个模型,再在真实数据上用无监督方式微调50个epoch左右,效果通常优于直接用无监督训练从头训。

4. 评估去噪效果:哪些指标能真实反映质量,参数又该怎么固定

4.1 评估指标选型:倒角距离、F-score和点到网格距离

去噪模型的评估不能只看Loss曲线,因为倒角距离作为损失函数,数值低不代表视觉效果就好。一个过于保守的网络会把所有点都往表面方向"吸",倒角距离确实变小了,但高频特征也没了。严谨的做法是同时看三个维度:倒角距离衡量整体几何误差,F-score衡量表面覆盖率,点到网格距离衡量与原始CAD模型的偏差。

F-score的定义相对直观:设定一个距离阈值d,如果预测点到真实点云的最近距离小于d,就记为?击中?,计算预测点云中击中的比例作为精度,真实点云中被预测覆盖的比例作为召回率,取两者的调和平均。阈值d需要根据应用场景设定,通常取平均点间距的1.5倍。这个指标比CD更贴近实际使用感知。

def f_score(pred, gt, threshold=0.01): """pred和gt都是 (num_points, 3) 的numpy数组 """ from scipy.spatial import cKDTree tree_gt = cKDTree(gt) tree_pred = cKDTree(pred) dist_pred_to_gt, _ = tree_gt.query(pred, k=1) dist_gt_to_pred, _ = tree_pred.query(gt, k=1) precision = (dist_pred_to_gt < threshold).mean() recall = (dist_gt_to_pred < threshold).mean() f = 2 * precision * recall / (precision + recall + 1e-8) return f

参数说明:threshold=0.01是在单位球归一化之后的经验值,如果你的场景模型尺寸不变,这个数值可以沿用;如果场景尺度变化很大,建议按平均点间距的比例来设置。F-score对离群点非常敏感,一个距离很远的离群点会降低precision,但它不会像CD那样用一个大距离值造成灾难性影响,更适合做模型之间的横向对比。

4.2 去噪效果对点时密度的敏感性:固定测试条件是前提

点云去噪评估里最容易被忽略的一个变数,是测试时的点云密度。同一个网络的参数不变,把输入点数从1024换成8192,去噪效果会有明显差异。原因并不玄学:EdgeConv里的K个近邻点,在点数多的时候只覆盖很小的局部区域,网络对法向量的估计更准,在点数少的时候邻域覆盖范围大,容易跨越不连续表面。你在报告指标或发模型时,必须固定评估时的点数。

我习惯在测试阶段把点云统一采样到2048点,这个数值对大部分室内物体足够,又不会让KNN的计算成本太高。如果你把训练时的点数设为2048,测试也保持一致,网络对密度的适应压力最小。点密度差异过大的数据,可以在输入前做体素下采样,让点间距分布变得均匀,然后再送入网络。

4.3 训练参数速查:一组可复用的默认配置

下面这张表是我在多个合成数据集上验证过的初始配置,适合单位球归一化后的物体点云,覆盖了室内扫描和CAD模型采样,可以作为第一版训练参数的起点。

参数名推荐值说明
输入点数1024点数太少细节丢失,太多显存压力大
EdgeConv的K值20点数变多时可以适当提高到24
特征维度[64, 128, 256]任务简单可缩减一半
噪声级别0.05沿法线方向的高斯噪声标准差
离群点比例0.1对应稀疏离群噪声的占比
初始学习率1e-3AdamW下的稳定起点
学习率调度余弦退火200个epoch降到1e-5
批大小8训练点数1024时显存约6GB
梯度裁剪阈值10.0防止topk导致梯度爆炸
训练轮数200观察Loss是否在50轮内大幅下降

说几个我在调参时遇到的陷阱。噪声级别加到0.1以上时,网络会把部分真实表面也当成噪声,输出去噪完的点云明显"缩水",因为网络学会的是把偏离平均面的点拉回来。批大小设到32以上时,BN层的统计量会更稳定,但显存占用会显著上升,训练不一定更快收敛。K值不要为了追去噪效果盲目调大,K=32在边缘处会把两个面片混在一起,出现"桥接"伪影。

4.4 复现装置:训练过程中必看的三个信号

训练到第10个epoch左右,观察输入噪声点云和输出去噪点云的可视化对比,这是一个比Loss数值更可靠的信号。一个好的去噪网络在这个阶段应该把大尺度噪声去掉,但表面还会有一些"橘子皮"一样的细微凹凸。如果输出点云已经开始变得过分光滑,通常是法向量损失的权重太高,把细节磨平了。

第二个信号是验证集上的F-score曲线。如果F-score上升但CD下降变慢,说明网络在往"保守去噪"方向走,应该降低法向量损失的权重。第三个信号是看看输出点云是否出现"真空泡",也就是点云内部出现空洞。这通常是K值过小,网络感受野跟不上点云密度,局部表面无法被完整重建。

python evaluate.py --checkpoint checkpoints/best.pth --input data/test_noisy.ply --output results/denoised.ply --num_points 2048

这个命令行适合放到批处理脚本里,--num_points参数表示测试阶段统一采样的点数,--checkpoint指向训练时保存的最佳权重。如果测试结果里离群点没有去除干净,优先检查噪声比例是否和训练时一致;如果表面过于光滑,检查法向量损失的权重是否合适,而不是去动网络结构。

5. 从源码包到落地:点云去噪的工程化经验

5.1 真实扫描数据的清洗顺序

我从源码包拿到的模型,第一件事不是直接跑真实大场景点云,而是先做一次"重力对齐"和"统计滤波粗清洗"。原因很简单:实际扫描数据里除了高斯噪声和离群点,往往还有由于视场遮挡产生的飞点、边缘拖影、以及深度相机特有的光学噪声。这些脏数据如果直接喂进网络,网络输出的去噪结果可能很"合理",但丢失的几何细节也会被一起掩盖。我的处理顺序是:先去除明显的离群点,沿法线方向轻度平滑一次,再送进深度网络做精细去噪。这样分步处理的原因是把规则的不规则噪声分离开,让深度网络专注于学习"表面重建",而不是同时处理两类完全不同的问题。

顺序反过来会出问题。如果你先让深度网络处理原始点云,再去统计滤波,统计滤波会把网络已经修正过的表面再次拉走。深度网络输出的点云密度通常比输入略低,一些微弱的结构会在第二次滤波中丢失。

5.2 推理加速:批处理与模型导出

点云去噪模型在单个物体的推断上,GPU耗时通常在几十毫秒量级。若应用在实时感知流程中,瓶颈一般不是网络前向计算,而是数据预处理里的KNN建图。KNN需要对每个点计算与其他点的距离,推理时要及时把KNN放在GPU上跑,避免与PyTorch默认的CPU调度冲突。如果你部署到移动端或者嵌入式设备,可以先把EdgeConv的K值固定下来,用ONNX导出模型,再在目标平台上开启TensorRT加速。

dummy_input = torch.randn(1, 3, 2048).cuda() torch.onnx.export( model.cuda().eval(), dummy_input, "pointcloud_denoise.onnx", input_names=["input_pointcloud"], output_names=["denoised_pointcloud"], dynamic_axes={"input_pointcloud": {2: "num_points"}, "denoised_pointcloud": {2: "num_points"}} )

dynamic_axes里把num_points声明为动态维度,这样导出模型可以接受任意点数输入。如果不加这一行,ONNX模型输入的第二个维度被固定为2048,换点数就得重新导出,非常不灵活。导出之后,运行polygraphy或者onnxruntime检查一遍,重点看EdgeConv里的topk算子是否被完整支持。

5.3 验证去噪效果的"像素级"技巧:法向量贴图对比

最后一个我在项目里习惯用的验证技巧:把去噪前后的点云渲染成法向量贴图对比,而不是只看点云本身。点云视角下,去噪效果好坏容易被光照反射覆盖,但法向量贴图会把局部弯曲暴露无遗。平面区域的噪点会让法向量出现"颗粒感",真实边缘的法向量变化是锐利且规律的。把两组法向量贴图叠加后,就能直接看出网络是把边缘保留住了,还是把边缘磨成了渐变。

这个技巧还能用于判断是否过拟合:如果一个模型在训练集上F-score很好,在验证集的法向量贴图上出现"网格状"规律性伪影,通常说明模型记住了训练数据的采样模式,而不是真正理解了几何表面形态。此时应增加数据增强,对输入点云随机旋转、加噪声,打乱模型对"固定图样"的记忆。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询