
简介面向3D视觉与关键点检测研究者的KeypointNet资源包解决了大规模3D关键点数据稀缺与标注成本高的问题。该数据集基于ShapeNet模型众包注释构建覆盖16个对象类别、83231个关键点和8329个3D模型并已发布无监督关键点检测器相关实现。压缩包共92个文件约1.54MB核心内容包括Python训练/测试与可视化脚本、C/CUDA源码、YAML环境配置、JSON注释文件以及PCD/PLY/OBJ等点云与网格样例便于直接复现论文实验或二次开发。包内还提供清洗后的类别数据划分训练/验证/测试与基准测试脚本可帮助读者快速上手关键点标注、模型训练和指标评估。已有550人学习适合具备一定点云处理基础、希望深入关键点检测的硕博研究生与算法工程师。1. 点云关键点为什么难做从2D关键点到3D关键点的跨越我在做点云配准和三维检索的时候被关键点这个问题卡了很久。2D图像里的关键点检测已经非常成熟了SIFT、ORB、SuperPoint随便挑一个都够用可一旦把任务换成无序、稀疏、密度不均匀的3D点云情况就完全不一样了。点云没有规则的网格结构没有固定的拓扑同一个物体在不同视角下采样的点还可能千差万别这让检测关键点这件事变得非常棘手。KeypointNet这个名字最初引起我的注意就是因为它试图解决这个问题在大规模三维点云中学习出有语义一致性的关键点。很多人会下意识觉得3D关键点不就是把2D方法搬过来吗其实根本没这么简单。图像上的关键点通常对应着梯度变化剧烈的像素块比如角点、边缘这些可以用局部窗口内的梯度统计来刻画。但点云上的关键点是什么如果你只用几何曲率、法向变化这类低层特征去筛得到的往往是椅背上的装饰性突起、桌角上的一块毛刺这类几何上显著的点。这些点在一个实例上可能很突出换一个同类物体、换一个姿态就完全对不上了。也就是说它们虽然叫关键点但对下游任务毫无帮助——因为下游任务要的是语义上有意义、能在实例之间稳定对应的点比如椅子的四个脚端点、杯子的杯口中心、飞机的机翼尖端。1.1 几何显著点不等于语义可匹配点这里要区分两个概念几何显著点和语义可匹配点。几何显著点是数值层面上的个性它只看局部形状波动不管这个波动代表什么。语义可匹配点是类别层面上的共性它要求同类物体上的同一部位被一致地标出来就算这个部位在某个实例上一点都不几何显著。举个例子一个光滑的球体表面上几何最显著的点可能根本不存在但从语义上看球体的中心、球面上的南北极都是可以稳定定义的关键点。传统方法掉进去的坑就在这里。它们大多靠曲率极值、邻域法向差异、热核特征HKS或者三维尺度空间中的局部极值来选点然后配一个描述子比如FPFH、3DSC做匹配。这类方法在同一个物体、相近视角下表现还行一旦跨越实例、跨类别那个由曲率定义的特征峰就跟着几何形态乱跑完全不受控。我当年用ISS内部形状签名在几个椅子模型上提取关键点同一个语义位置在不同椅子上得到的点序号能差出十万八千里匹配准确率低到怀疑人生。这才是关键点检测的真正难点不是找突出的点而是找能被同类所有实例共享的点。1.2 传统3D关键点方法的典型套路和它们的局限传统方法大致分三类。第一类是局部几何描述子法用邻域结构的统计量找局部极值典型代表有ISS、Harris3D、3D-SIFT第二类是基于谱分析的方法把拉普拉斯算子的特征函数当作形状指纹典型代表是HKS和SI-HKS第三类是基于优化匹配的方法通过求解两个形状之间的对应关系来反推关键点典型代表是各种保距映射的变体。这三类方法各有各的问题。第一类对噪声和采样密度极敏感点云只要少一层采样特征峰就可能漂移第二类虽然对拓扑变化更鲁棒但计算量很大而且谱特征在语义上仍然是几何驱动的并不理解这是一个椅背还是一张桌面第三类方法的精度高度依赖初始化和全局优化跑起来慢而且很容易陷入局部最优。更致命的是这三类方法都没有利用类别内的大量实例这个信息。我们明明有几千把椅子摆在那里它们的共同结构就是天然的监督信号但传统方法硬是只把每个实例孤立地看待不让信息跨实例流动。KeypointNet之所以在那个时间点让我眼前一亮正是因为它换了一个框架不是手工定义关键点而是让网络从类别数据中自己学到什么是该被稳定的点。这个思路的转变比网络本身的价值更大。2. KeypointNet的架构设计检测、描述、匹配三合一KeypointNet的核心思想可以概括成一句话把关键点检测、描述子生成、跨实例匹配三件事放进同一个网络里联合优化。这个三合一的设计不是锦上添花而是必须的。如果只做检测网络很容易学到某个实例上的特有位置因为没有一个约束告诉它关键点必须在同类别其他实例中存在如果只做描述子又不知道应该重点描述哪些点。把检测和描述连起来学检测任务为描述子提供候选位置描述子匹配任务反过来又对检测位置施加一致性约束两者互相纠偏。2.1 网络整体流程从工程角度看KeypointNet的推理流程非常清晰。输入是一组无序点云每个点有三维坐标可选加法向网络先用一个基于PointNet思路的多尺度特征提取器把逐点特征抽出来然后在特征之上分出两个头一个头输出每个点的关键点评分另一个头输出每个点的描述子向量。最后做非极大值抑制NMS取评分局部极大的一批点作为关键点再用对应的描述子去和另一帧点云做匹配。如果你跑过PointNet你会发现这个骨干相当眼熟。多尺度分组Multi-scale Grouping或者多分辨率分组Multi-resolution Grouping负责捕捉不同尺度下的局部几何信息。为什么多尺度在这里很重要因为关键点的语义属性往往是由多尺度上下文共同决定的。判断一个点是不是飞机机翼尖端只看它周围3厘米的局部形状根本不够需要把半个机翼的走向拉进来才能判断。单尺度特征在这种任务上很容易顾此失彼小尺度区分不了细节和噪声大尺度又丢失了位置精度。2.2 关键点评分分支和描述子分支关键点评分分支是一个典型的逐点二分类结构输出一个sigmoid激活的值表示这个点是关键点的概率。训练时用带权重的分类损失让正样本真实关键点的得分逼近1负样本逼近0。推理阶段更讲究一点直接取所有得分高于阈值的点会得到一堆聚成一团的冗余候选点所以通常会做一个简单的NMS——按得分从高到低排序每选中一个点就把它邻域radius内的点全部屏蔽掉。这个radius怎么定按照对象尺度的一定比例来。我在实验里取过点云包围盒直径的2%到5%效果比较稳定。描述子分支输出的向量维度一般在32到128之间。这个描述子要满足的约束是同一语义关键点在不同实例上的描述子要尽可能接近不同语义点的描述子要尽可能远离。也就是说描述子空间内部应该自动形成语义聚类。训练时用基于度量学习的损失函数最常见的做法是把一切匹配对拉近把不匹配对推开并且留一个margin防止所有点都挤到一起。这个margin设多大需要观察描述子分布的实际情况设得太小描述子没有区分度设得太大训练过程会非常不稳定。2.3 为什么检测、描述、匹配联合训练能work我自己后来复现了不少相关方法回头再看KeypointNet能work的关键在于它绕开了一个鸡生蛋蛋生鸡的问题如果先检测再匹配检测不准匹配就无从谈起如果先匹配再检测匹配对的候选太多也太慢。联合训练让两个任务共享同一个特征空间检测分支帮助描述子分支把注意力集中在语义点附近的局部几何上描述子分支通过匹配一致性把检测分支拉向跨实例稳定的方向。这种耦合带来的收敛效果比两阶段方法好得多。另外网络的特征提取器没有自己去区分这是哪一类物体而是让同一个骨干去适配不同类别的关键点模式。训练时把类别信息作为条件或干脆按类别分开训练模型推理时模型就能根据输入点云的形状结构自动定位该类别的语义关键点。这一点很关键因为椅子的关键点和飞机机翼的关键点在几何模式上完全没有可比性硬要放在同一个模型里学反而会互相干扰。KeypointNet提供了按类别建模和跨类别统一建模两种选择我在实测中更倾向于按类别建模精度高出一截代价只是多存几个模型文件。3. 训练数据与损失如何在无标注场景下学到一致关键点听到有语义一致性的关键点很多人的第一反应是那得标注多少数据逐点标注几十万、上百万个点云数据里的语义关键点成本完全不可承受。KeypointNet在设计上最巧妙的部分就是用很少的人工标注加上自动传播构造出大规模的训练数据。原始论文里只在极少数物体上手工标注了少量关键点大概是每个类别几十个种子点然后通过隐式函数变形把这些种子点传播到整个ShapeNet数据集的对应物体上从而获得海量的关键点配对。这套数据生成策略在我看来才是整篇论文最值得学习的地方。3.1 金标准生成从种子标注到类别级关键点我先把传播链路简化说明一下。假设现在是椅子这一类你手动在一把参考椅子上标了30个关键点比如四条腿的端点、坐垫的四角、靠背的顶部边缘等等。要得到第二把椅子上的对应关键点不能靠普通的三维坐标变换——因为两把椅子的形状、比例、弯曲程度都不同。这里用的是隐式函数变形用一个神经网络典型如Deep Implicit Function把参考椅子的形状隐式表示出来同时给定第二把椅子的隐式函数然后在两个隐式场之间建立对应关系把参考椅子上的关键点坐标映射到第二把椅子的表面。这个方法听着玄其实本质上是把点对应问题转化为隐式场之间的变形问题。隐式函数用符号距离场SDF表示物体表面两个SDF之间可以通过最近点迭代或者可微变形对齐起来。关键点一旦在参考物体上被标出就能顺着这个对应关系批量迁移到所有同类别物体上。我实现的时候最大的感触是种子点的质量决定了整个数据集的标注质量。种子点必须落在真正的语义边界或者对称中心上如果标歪了传播到所有物体上都会被放大最后训练出来的关键点位置全部偏移。3.2 损失函数中的一致性约束拿到这些自动生成的关键点配对之后训练就变成一个有监督学习问题。关键点评分分支用二元交叉熵损失重点关注关键点附近的局部区域避免类别不平衡把负样本压得太死。描述子分支用对比损失或者三元组损失让匹配关键点的描述子距离尽量小不匹配的尽量大。还有一个在实验中容易被忽略的细节对称性。很多物体存在对称结构比如椅子的左右腿是对称的飞机的左右机翼是对称的。如果不做任何处理网络可能在匹配时把左边的机翼尖端匹配到右边的机翼尖端从几何角度看确实合理但从语义角度看就错了。处理办法是在损失函数里加入对称性感知的惩罚项或者在数据增强时显式地把对称点对也标记成软匹配关系。我第一次跑实验的时候没注意这个问题匹配准确率曲线在0.8上死活上不去后来排查发现是训练数据里的对称歧义把描述子空间搞乱了。加了这个约束之后效果立竿见影。3.3 训练细节与超参经验训练KeypointNet的几个关键超参我根据个人经验做一个整理输入点数一般采样2048到4096个点太少会丢失局部细节太多会拖慢训练速度关键点数量每个类别定义30到50个关键点比较合理关键点太稀疏会丢失很多语义部位描述子维度64维是一个性能和存储折中的选择128维更鲁棒但匹配开销更大NMS半径取点云包围盒直径的2%到5%具体要按物体尺度扫一遍确认学习率初始1e-3配合余弦退火训练后期降到1e-5损失权重检测损失和描述子损失的权重比在1:1到1:2之间调整描述子权重略高有利于匹配环节训练大约需要一到两个GPU天这取决于数据集的规模。我建议先用一个小类比如瓶子跑通全流程再扩展到大规模类别。这样能最快暴露代码和参数的问题而不是等跑了一整天后才发现数据处理有bug。4. 在ShapeNet上的实测效果与下游应用我自己在ShapeNet的多个类别上跑过KeypointNet的实验这个数据集的好处是干净、类别覆盖广、每类有几百到几千个实例非常适合做跨实例的语义关键点评估。评测指标一般有两个方向一是关键点定位准确率看预测的关键点与真实关键点的距离是否小于某个阈值二是匹配准确率看用描述子做最近邻匹配时匹配到的点是否真的是对应的语义点。4.1 关键点检测与匹配指标从印象中的结果看KeypointNet在大部分类别上的关键点定位准确率都明显高于传统方法。ISS和3D-SIFT这类方法在小阈值下几乎失效因为它们的点虽然几何上正确但和语义位置的相关性很弱。KeypointNet则能把错误率压低一个数量级以上。在匹配任务上它的优势更明显描述子是在关键点检测这个任务的引导下学出来的天然集中在语义点上因此匹配时目标范围内的候选点更少误匹配率也低很多。需要提醒的是评测方式不同结果差异会很大。如果在有噪声、遮挡的局部点云上测所有方法都会显著下降但KeypointNet的下降幅度比传统方法平缓。原因还是那句它学的是语义规律不是像素级别的巧合。我在部分扫描数据上做过测试在物体只露出60%的情况下仍能稳定找到椅子的坐垫角点和飞机机翼端点这类部位这在传统方法里几乎不可能。4.2 真实扫描数据的效果真实世界扫描数据和ShapeNet合成数据之间有一条不小的鸿沟。合成数据是完整、无噪声、均匀采样的真实扫描数据有遮挡、有离群点、有密度不均还有传感器噪声。KeypointNet在真实扫描数据上表现尚可但前提是训练时做了很好的域适应比如在训练数据里加入随机裁剪、噪声扰动和密度变化模拟。我当时把原始点云切出一块再叠加高斯噪声和随机离群点当作训练数据增强策略模型在真实数据上的鲁棒性提高了不少。还有一个值得注意的点真实扫描数据往往是不完整的比如桌子上放着的杯子你只能扫到上半部分。这种情况下KeypointNet仍然倾向于给出一个接近完整杯子的关键点分布因为语义先验已经写进了网络权重里。但如果你在下游任务中需要严格落在可见表面的关键点就要加一个后处理把预测关键点投影到距离最近的可见点上或者直接过滤掉那些落在网格内部的点。4.3 下游应用配准、检索、位姿估计关键点检测本身不是目的它通常服务于更上层的任务。我用关键点做过点云配准做法很简单分别提取两帧点云的关键点和描述子用最近邻匹配得到粗糙对应点集再用RANSAC剔除误匹配最后用ICP精配。相比直接用ICP这个流程的速度快了一个量级因为关键点数量通常只是原始点数的百分之几最近邻搜索的规模大幅缩小。实例检索是另一个典型的应用场景。给定一个查询物体先提取它的关键点集合然后和数据库里每个物体的关键点集合计算匹配得分得分最高的就是检索结果。这里关键点语义一致性的价值体现得最充分同类物体的关键点位置重合度高描述子近似检索准确率远高于基于全局特征的方法。位姿估计方面关键点的价值在于它可以提供语义上的对应关系从而直接估计物体在相机坐标系下的六自由度姿态。用关键点配准做位姿估计最大的优势是不需要精确的物体模型也不需要深度图做稠密匹配。我实际做机器抓取实验时先把物体点云上的关键点映射到CAD模型上的对应点然后求解一个刚性变换就能得到一个足够好的初始位姿再做一次ICP精修就能达到抓取精度。5. 复现KeypointNet的避坑记录说实话KeypointNet的代码结构不算复杂真正让人头疼的是那些写在纸面上看不出来的细节。我把自己踩过的坑整理一下希望能帮你省点时间。5.1 环境与依赖KeypointNet这个工作有官方的TensorFlow实现但我个人建议用PyTorch重新实现一遍因为后续做扩展和调整更方便。必要的依赖主要有这几块PyTorch 1.8以上CUDA 10.2以上点云库Open3D或者PCL用于数据读取、可视化、去噪实现多尺度分组时可以直接用PointNet的开源实现避免自己手写球查询ball query导致效率低下数据预处理用numpy就够了注意点云格式统一为float32环境配置的坑主要有两个。第一某些机上CUDA版本和PyTorch版本不兼容跑网络的时候报奇怪的显存错误建议优先用一个成熟的docker镜像不要自己从头配。第二球查询操作非常吃内存BatchSize设大了显存直接爆炸我一开始把BatchSize设成32结果在12G显存的卡上直接OutOfMemory。后来我把BatchSize降到8同时把输入点数降到2048才稳定跑完整个训练。5.2 训练和推理的完整流程明确一下训练阶段的数据流从ShapeNet加载某个类别的点云模型统一采样到2048个点把参考物体的关键点通过隐式函数变形传播到当前实例得到每帧的关键点标签对点云做归一化把坐标缩放到以重心为中心、半径为1的球体内数据增强随机旋转、随机裁剪、随机增加噪声和离群点送入网络得到关键点评分和描述子计算检测损失和描述子损失反向传播更新参数按照余弦退火调整学习率推理阶段的流程加载训练好的模型权重输入点云前向传播得到每个点的关键点评分和描述子按得分阈值筛掉低置信度的点再做NMS去除冗余输出关键点坐标和描述子用于下游匹配一个值得特别提醒的细节训练和推理时的输入点数要保持一致不要训练用2048、推理时用10000。如果必须输入不同点数最稳妥的方法是先把点云裁剪或采样到固定点数再输入网络否则多尺度分组的邻域大小和密度统计会完全对不上关键点检测精度会明显下降。5.3 踩坑清单我实际遇到的问题描述子损失不收敛把margin从默认的1.0降到0.5同时提高检测损失的权重让两个任务均衡关键点聚集在一个区域NMS半径设置太小增大到点云直径的5%左右对称物体匹配错位在数据增强里加入对称翻转同时在损失函数里处理对称对应关系真实数据比合成数据效果差很多增加真实扫描数据微调训练时多加入随机裁剪和噪声增强多类别统一模型不如单类别模型按类别分开训练每个类别的关键点定义差异过大硬共享参数会互相干扰KNN匹配时描述子维度太高导致内存占用过大如果点云规模非常大建议先用PCA把描述子降到16到32维最后分享一个小技巧。训练完之后把关键点可视化出来是最快的检查方式。打开Open3D窗口把预测关键点用高亮颜色画在点云上然后旋转视角观察它们是否落在语义合理的位置。这一步几乎能发现所有潜在问题比分什么指标都快。我每次改完训练策略都会先跑一两个验证样本看一眼关键点分布再继续调参效率比单看数字高很多。本文还有配套的精品资源点击获取