简介:这份资源面向深度学习开发者,聚焦使用PyTorch框架中的Keypoint R-CNN训练自建数据集的关键点检测模型,适合正在学习姿态估计、人脸关键点等任务的初中级研究者参考。压缩包共116个文件,约8.55MB,其中包含8个Python脚本、2个Jupyter Notebook训练与转换示例、31个JSON标注、34个TXT说明及39张JPG样本图像,配套目录结构清晰,便于按数据处理、模型配置、训练评估等环节对照学习。目前已吸引164人学习下载。资源覆盖从标签格式整理、模型头部调整到训练调参与部署导出的完整流程,可帮助读者快速搭建自己的关键点检测实验,避免踩坑常见的数据预处理与训练配置问题。 从项目周期来说,关键点检测一直是计算机视觉里比较“挑数据”的方向。跟普通目标检测只给一个框不同,关键点要的是一组具有语义意义的坐标,这对标注质量、标签格式、模型对细节特征的敏感度都有更高要求。我最近刚好用PyTorch自带的Keypoint R-CNN在自建数据集上完整走了一遍训练流程,覆盖了数据标注、COCO格式转换、DataLoader适配、模型训练到推理验证,踩了不少坑,也沉淀了一套可复用的操作路径。这篇就围绕这个项目,把我在实践里的完整步骤、参数选择逻辑和问题排查经验整理出来,给同样在做自建关键点检测的朋友提供一个可直接参考的方案。
1. 项目定位与技术选型
1.1 为什么选Keypoint R-CNN而不是其他方案
做关键点检测,业界方案其实不少——从两阶段的Top-Down系列(比如HRNet+检测器),到一阶段的Bottom-Up系列(比如OpenPose),再到基于Transformer的检测头,各有适用场景。我这次选PyTorch官方实现的Keypoint R-CNN,核心考量是效率:
第一,这个模型集成在torchvision.models.detection模块里,完全兼容PyTorch生态,不需要额外安装第三方检测库。如果你已经装了PyTorch,直接torchvision里就能用,环境成本几乎为零。
第二,Keypoint R-CNN本质上是在Faster R-CNN的检测分支上多挂了一个关键点头,属于“检测为主、关键点为辅”的架构。这意味着它对目标框的回归和关键点预测是联合训练的,最终预测时能一次性拿到检测框、类别、关键点三样东西,非常契合那种“先定位目标在哪,再定位目标细节”的业务场景。
第三,社区资料相对丰富。虽然用Keypoint R-CNN做自建数据集的教程比YOLO系少很多,但毕竟它源自Mask R-CNN架构,遇到问题比较容易在Mask R-CNN、Faster R-CNN的相关讨论里找到参考。
做个简单对比:
| 方案 | 标注要求 | 训练成本 | 部署难度 | 适合场景 |
|---|---|---|---|---|
| Keypoint R-CNN | 中等(框+点) | 中等 | 中等 | 小数据集、精细定位、需要框和点同时输出 |
| HRNet | 高(密集点) | 高 | 高 | 人体姿态大数据集 |
| OpenPose | 高(多人数点) | 高 | 高 | 多人实时姿态估计 |
| 自定义轻量回归网络 | 低 | 低 | 低 | 单一目标、关键点数量少 |
我这次的项目场景是“一个目标只有2个关键点”的定位任务,数据量也不算大(几百张图),用Keypoint R-CNN算是比较务实的选型。
1.2 Keypoint R-CNN的结构简析与踩坑预判
Keypoint R-CNN的网络结构并不难理解:Backbone(默认ResNet50+FPN)提取多尺度特征,RPN生成候选框,RoIAlign从特征图上抠出每个候选框对应区域的特征,然后分两个头并行输出——一个头做分类和框回归,另一个头做关键点热图预测。
值得提前说的一点是,torchvision里的Keypoint R-CNN是直接在maskrcnn_resnet50_fpn的基础上改出来的,它复用了Mask R-CNN的RoI头部,只是把mask分支的语义分割任务换成了关键点热图回归。所以如果你翻源码会发现,关键点分支其实就是在预测一个K x 56 x 56的热图,K是关键点类别数,真正标注的坐标点是用来生成这个热图的监督信号。
踩坑预判上,主要有三个点需要提前留意:
第一,数据格式必须是COCO风格,而且关键点部分有严格的字段要求,少了num_keypoints或visibility数组,训练直接报错。
第二,训练时如果只给模型传了检测框标签而忘了传关键点标签,loss会出问题——因为关键点分支没有监督信号,模型就会往全零热图的方向收敛。
第三,推理阶段输出的是热图,要拿到具体坐标必须做Argmax或者Soft-Argmax解码,这一步没有内置方法,需要自己写。
后面我会逐个问题展开讲。
2. 自建数据集构建——整个项目最花时间的环节
2.1 标注工具选型与流程
自建数据集第一步是标注。我的实际经验是,不要一上来就写脚本整理标签,先把标注工具定下来。现在主流的人体关键点标注工具比较多,但如果是非人体目标、关键点数量又少,很多姿态标注工具反而用不上。
我这次用的是LabelStudio,够灵活,能同时画检测框和关键点,导出的COCO格式基本能用。操作流程不复杂:
- 在LabelStudio中创建项目,选择Object Detection with Keypoints模板。
- 上传所有图片,定义关键点名称列表(比如
point_a, point_b)。 - 逐张标注,先画目标框,再在框内打点。
- 导出为COCO格式JSON。
这里给个建议:哪怕是两个点,也建议先画框再打点。因为Keypoint R-CNN训练时的正样本来自RPN生成的候选框与GT框的IoU匹配,如果框不准,候选框的上下文特征就乱,关键点自然学不好。
另一个建议是:把标注任务拆成多人协作时,提前定义好关键点的语义和顺序,比如“0号点始终是左端点、1号点始终是右端点”。别看这是个细节,顺序一乱,模型训练一万轮也是在学混乱对应关系。
2.2 COCO格式与自建JSON的坑
LabelStudio导出的COCO格式大体能用,但离torchvision的目标还有距离。torchvision的torchvision.datasets.CocoDetection能读标准COCO,但如果你准备自己写Dataset,就需要手动解析annotations里的keypoints字段。
COCO关键点标注格式长这样:
{ "keypoints": [x1, y1, v1, x2, y2, v2], "num_keypoints": 2, "bbox": [x, y, width, height], "category_id": 1, "image_id": 1, "id": 1 }其中keypoints数组的长度是关键点数量 * 3,按x, y, visibility的顺序排。visibility取值为0、1、2,含义分别是:0表示该点未标注,1表示该点被遮挡但仍标注了位置,2表示该点可见且已标注。
这里有个很容易踩的坑:torchvision的Keypoint R-CNN在训练时,虽然只用到visibility大于0的点的坐标来计算loss,但要求数组维度必须正确。如果某个目标只有1个可见点、另1个点标为0,模型会在该实例的num_keypoints为1时依然正常训练,但如果关键点全为0,这个实例实际上无法给关键点分支提供任何监督信号,可能影响整体loss的数值表现。
写自定义Dataset时,我建议把关键点数据处理封装成统一的Tensor,不要每个epoch重复解析JSON:
import torch from torch.utils.data import Dataset from PIL import Image import json import os class KeypointDataset(Dataset): def __init__(self, img_dir, ann_file, transforms=None): self.img_dir = img_dir self.transforms = transforms with open(ann_file, "r") as f: self.coco = json.load(f) self.images = {img["id"]: img for img in self.coco["images"]} self.annotations = {img_id: [] for img_id in self.images} for ann in self.coco["annotations"]: self.annotations[ann["image_id"]].append(ann) def __len__(self): return len(self.images) def __getitem__(self, idx): img_id = list(self.images.keys())[idx] img_info = self.images[img_id] img = Image.open(os.path.join(self.img_dir, img_info["file_name"])).convert("RGB") boxes = [] keypoints = [] labels = [] anns = self.annotations[img_id] for ann in anns: x, y, w, h = ann["bbox"] boxes.append([x, y, x + w, y + h]) kp = ann["keypoints"] # [x1, y1, v1, x2, y2, v2, ...] keypoints.append(kp) labels.append(ann["category_id"]) target = {} target["boxes"] = torch.as_tensor(boxes, dtype=torch.float32) target["labels"] = torch.as_tensor(labels, dtype=torch.int64) target["keypoints"] = torch.as_tensor(keypoints, dtype=torch.float32).view(-1, 3, 2) # 注意:torchvision 中 keypoints 的形状是 [N, K, 3],其中最后一维是 x, y, visibility if self.transforms is not None: img, target = self.transforms(img, target) return img, target关于target["keypoints"]的shape,torchvision源码里接收的是[N, K, 3]。K是关键点数量,我这里是2。如果你直接把COCO的列表转进去,需要先reshape对。这里稍微绕,第一次写很容易弄成[N, 3, K],训练时直接报shape mismatch。
2.3 数据增强与预处理策略
数据增强在关键点任务上比纯检测更敏感。检测框可以做随机翻转、缩放、亮度变化,但关键点必须跟着框做同步变换,而且flip的时候点序也得跟着换(比如左端点翻到右边去了,标签里点0和点1的位置就要交换)。
torchvision的references/detection里提供了RandomHorizontalFlip,它会自动处理关键点翻转,但前提是你传入的keypoints字段必须格式正确。如果自己写增强函数,务必记得坐标变换要和框一致。
我实际用的增强组合是:
- 随机水平翻转(概率0.5)
- 随机亮度、对比度、饱和度调整
- 随机缩放(0.8到1.2倍)
- 固定尺寸Resize到
800x800以内,保持长宽比
Resize这里需要特别注意:虽然检测框可以直接按比例缩放,但如果图片做了Padding(为了统一尺寸),关键点坐标必须同步加Padding偏移量。最好的方式是不做Padding,而是采用torchvision中标准的两阶段resize策略:将图片短边缩放到800,长边不超过1333,超过则按比例缩到1333。这样做的好处是不会破坏关键点坐标与图像内容的对齐关系。
3. 模型初始化和训练配置
3.1 加载预训练模型与修改输出头
torchvision提供的关键点模型主要有两种加载方式。一种是官方预训练的keypointrcnn_resnet50_fpn(在COCO关键点数据集上训练的),但它是针对17个人体关键点的,输出头维度和自建数据集不匹配。另一种是拿maskrcnn_resnet50_fpn的检测权重做初始化,再自己换关键点头。
看下面这段代码:
import torchvision from torchvision.models.detection import keypointrcnn_resnet50_fpn from torchvision.models.detection.faster_rcnn import FastRCNNPredictor from torchvision.models.detection.keypoint_rcnn import KeypointRCNNPredictor def get_model(num_keypoints, num_classes): # 加载预训练模型,COCO 预训练权重 model = keypointrcnn_resnet50_fpn(weights="COCO_V1") # 替换分类头,适配自建数据集的类别数 in_features = model.roi_heads.box_predictor.cls_score.in_features model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes) # 替换关键点预测头,适配自建数据集的关键点数 in_channels = model.roi_heads.keypoint_predictor.kps_score_lowres.in_channels model.roi_heads.keypoint_predictor = KeypointRCNNPredictor(in_channels, num_keypoints) return modelnum_classes是背景+目标类别数,不是单纯的目标类别数。如果你的数据集只有1类目标(比如只检测一种物体),num_classes = 2。这个坑我很早踩过,传1直接训练报错或者loss出现nan。
num_keypoints是关键点数量,我这次是2。
有一点需要提醒:如果直接加载COCO预训练权重,分类头和关键点头都要替换,否则会出现输出维度不一致的运行时错误。COCO的权重在替换头之前加载,意味着backbone和RPN部分已经具备了很好的特征提取能力,新的头只需要从零开始学,这比完全从随机权重开始训要快得多。
3.2 训练参数配置与损失函数分析
在参数设置上,我的配置是这样的:
model = get_model(num_keypoints=2, num_classes=2) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) params = [p for p in model.parameters() if p.requires_grad] optimizer = torch.optim.SGD(params, lr=0.005, momentum=0.9, weight_decay=0.0005) lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.1) num_epochs = 30这里把初始学习率设为0.005,是沿用torchvision官方检测任务的常用值。如果数据集比较小,我建议降到0.001~0.002,否则loss前期会比较震荡。
Keypoint R-CNN的损失由4部分组成:
loss_objectness:RPN判断候选框是否为前景的损失loss_rpn_box_reg:RPN框回归损失loss_classifier:分类损失loss_box_reg:检测框回归损失loss_keypoint:关键点热图的损失
关键点分支默认用的是MSELoss对热图做回归。torchvision实现里把GT关键点坐标转成了高斯热图(标准差约2像素),模型输出再上采样到56x56,跟GT热图做MSE。这就是为什么最终推理要拿到坐标必须做Argmax解码——模型输出的不是坐标,是热图响应。
如果训练日志里loss_keypoint下降非常慢,一个很常见的原因是:数据集里存在大量visibility=0的关键点,这些点在torchvision内部计算热图时会被排除掉,导致关键点分支真实监督信号很少。所以如果标注时某些点是不可见的,宁可把visibility设为1(遮挡但可推测),也不要设为0,否则keypoint loss会非常“虚”。
3.3 训练循环与官方参考代码的适配
训练循环需要自己写,但可以直接参考torchvision/references/detection下的train.py。核心逻辑是:
for epoch in range(num_epochs): model.train() for images, targets in data_loader: images = [img.to(device) for img in images] targets = [{k: v.to(device) for k, v in t.items()} for t in targets] loss_dict = model(images, targets) losses = sum(loss for loss in loss_dict.values()) optimizer.zero_grad() losses.backward() optimizer.step() lr_scheduler.step()重点要说的是模型的前向有两种模式:model(images)返回预测结果;model(images, targets)返回loss字典。训练时传targets,推理时不传。这与Faster R-CNN系列是一致的,第一次接触torchvision检测模块的朋友容易搞混。
另一个容易出错的地方是DataLoader的collate_fn。由于每张图的目标数量不同,targets里的box数量不一样,默认的collate无法直接堆叠成tensor,必须自定义:
from torch.utils.data import DataLoader def collate_fn(batch): return tuple(zip(*batch)) data_loader = DataLoader( dataset, batch_size=2, shuffle=True, collate_fn=collate_fn, num_workers=4 )数据加载环节还有个细节:图片通道顺序必须转成RGB且归一化到0-1范围。很多人直接用PIL打开(本来就是RGB),但读取的像素值是0-255,需要在Dataset里除以255。torchvision官方检测参考代码里用torchvision.transforms.ToTensor()自动做了归一化和维度变换,千万不要在预处理里重复归一化导致像素值范围错误。
4. 推理、解码与可视化验证
4.1 模型推理输出结构
训练完成后,推理时模型返回的是一个列表,列表长度等于batch内图片张数。每张图的输出是一个字典,包含:
boxes:形状[N, 4],N是检出目标数,坐标为[x1, y1, x2, y2]scores:形状[N],每个框的置信度labels:形状[N],类别IDkeypoints:形状[N, K, 3],K是关键点数量,最后一维是x, y, score(这里的score不是visibility,是热图解码得到的响应强度)
需要特别注意的是keypoints里的score和visibility完全是两回事。score来自热图最大值,反映了模型对该点位置的确信程度,可以用来过滤低质量的关键点预测。
推理代码:
model.eval() with torch.no_grad(): prediction = model([img_tensor.to(device)])[0] boxes = prediction["boxes"].cpu().numpy() scores = prediction["scores"].cpu().numpy() keypoints = prediction["keypoints"].cpu().numpy() # 只保留置信度高于阈值的检测结果 threshold = 0.5 for i, score in enumerate(scores): if score < threshold: continue x1, y1, x2, y2 = boxes[i] kps = keypoints[i] for (kx, ky, ks) in kps: print(f"关键点: ({kx:.2f}, {ky:.2f}), 置信度: {ks:.3f}")4.2 热图解码与坐标还原
torchvision的推理输出里已经对关键点头做了argmax解码,所以keypoints中直接就是像素坐标,不需要再写解码函数。但我还是建议理解一下底层逻辑,因为如果在自定义场景里要修改关键点分支,或者想输出关键点热图做可视化,这个知识是绕不开的。
关键点分支输出的原始head map形状是[K, 56, 56],经过roi_heads.keypoint_predictor内部的上采样层放大到[K, 112, 112],然后与ROI区域对齐后通过heatmap_to_keypoints函数取每个通道上最大值对应的位置,再映射回原图坐标。
我写过一个简易解码函数,逻辑差不多:
def decode_heatmap(heatmap, box): # heatmap: [K, 56, 56] K, H, W = heatmap.shape keypoints = [] for k in range(K): h = heatmap[k] idx = torch.argmax(h) y, x = idx // W, idx % W # 映射回ROI坐标 x = x / W * (box[2] - box[0]) + box[0] y = y / H * (box[3] - box[1]) + box[1] keypoints.append([x, y, h[y, x]]) return keypoints4.3 可视化验证与质量控制
关键点模型训练得怎么样,不能只看loss曲线,要做逐图可视化。我在项目里写了一个快速可视化脚本:
import matplotlib.pyplot as plt import numpy as np def visualize_result(image, boxes, keypoints, save_path="result.png"): img_np = image.permute(1, 2, 0).numpy() fig, ax = plt.subplots(1, 1, figsize=(10, 10)) ax.imshow(img_np) for i, box in enumerate(boxes): x1, y1, x2, y2 = box rect = plt.Rectangle((x1, y1), x2 - x1, y2 - y1, fill=False, edgecolor="red", linewidth=2) ax.add_patch(rect) kps = keypoints[i] for (kx, ky, ks) in kps: ax.plot(kx, ky, "o", color="lime", markersize=6) plt.axis("off") plt.savefig(save_path, bbox_inches="tight", dpi=150)可视化时建议把检测框和关键点一起画出来,因为关键点的位置是否准确,很大程度上依赖于框是否框准了。如果框漂了,关键点自然而然就在错误的位置上“自信地”输出一个错误坐标。
质量控制方面,我关注三个维度:
- 检出率:测试集上能检出多少个目标框。
- 关键点坐标误差:与标注真值的平均欧氏距离。
- 关键点稳定性:对同一张图做轻微平移、旋转,关键点输出的波动幅度。
第三个维度很值得关注——如果关键点位置对输入噪声非常敏感,说明模型过拟合了训练集,泛化能力不足,光看loss曲线是发现不了这个问题的。
5. 训练过程中的常见问题与排查实录
5.1 Loss为NaN的排查路径
遇到过几次训练过程中loss变成NaN。按照以下顺序排查基本都能解决:
- 检查输入数据中是否有NaN或Inf:标注坐标超大、box宽高为负、图片文件损坏等都会导致。我遇到过一次因标注文件里有一张图片的路径指向了一个损坏的图片文件,PIL打开后返回的是空图像,模型前向时产生了NaN。
- 检查学习率是否过高:如果初始学习率从0.005开始,数据集又很小,loss在前几个iteration就可能冲成NaN,降到0.001或0.0005即可解决。
- 检查是否存在空标注的图片:如果某张图片没有标注框,
targets["boxes"]是空tensor,传入模型后RPN无法生成有效正样本,可能导致loss异常。这类图片应直接从训练集中剔除。 - 检查关键点坐标是否超出图像边界:COCO标注要求关键点在图像内部,如果标注工具导出时越界,需要裁剪或过滤掉。
5.2 模型训练后关键点始终“长”在图片固定位置
这个现象比较隐蔽,但很典型——训练完可视化时,发现不管目标出现在哪里,预测的关键点总落在图片中的某几个固定像素上。
这几乎可以肯定是target["keypoints"]的坐标与图像坐标不对齐导致的。常见原因有两种:
第一种是图片做了Resize但关键点没做同步缩放,模型输入是缩放后的图,但监督信号是原始尺寸的坐标,两个空间不匹配。
第二种是keypoints数组的reshape写错了,把坐标顺序搞乱了,模型学到的就是一个平均位置。
排查方法很简单:在训练循环里打一个断点,打印某张图的target["keypoints"]和图像尺寸,手动检查这个坐标是否真的落在目标物体上。这一步能省下大量后面debug的时间。
5.3 关键点抖动大和精度不足的处理策略
关键点预测位置时准时不稳,最常见的原因是训练数据太少。目标检测框几百张图勉强能训,但关键点坐标这种逐像素精度的任务,对数据量的需求要高得多。
我建议几种可行的优化方向:
第一,增加数据增强强度,尤其是随机旋转(角度限制在±15度以内)和随机裁剪,让模型对不同姿态下的目标特征更鲁棒。
第二,fine-tune时冻结backbone前几层,只训练高层的特征和检测/关键点头。小数据集下如果backbone全量微调,很容易过拟合到训练集特有的背景纹理上。
第三,如果业务允许,把ImageNet或COCO关键点预训练权重作为backbone初始化,而不是从头训。即便是不同类别的关键点,低层特征(边缘、纹理)仍然是通用的,能显著提高收敛速度和稳定性。
# 冻结backbone前几层的示例 for name, param in model.backbone.body.named_parameters(): if name.startswith("layer1") or name.startswith("layer2"): param.requires_grad = False5.4 训练速度慢的实用优化建议
训练速度问题是另一个高频痛点。Keypoint R-CNN本身是两步检测器,计算量比单阶段模型大,如果GPU资源有限,很容易训到崩溃。几个实际可行的提速方案:
- 调整Batch Size和梯度累积:显存不够时,用gradient accumulation模拟更大的batch。比如显存只支持batch=1,但想等效batch=4,可以每4个batch累加一次梯度再更新。
- 混合精度训练:在PyTorch 1.6+上直接用
torch.cuda.amp可以无痛套用:
scaler = torch.cuda.amp.GradScaler() for images, targets in data_loader: with torch.cuda.amp.autocast(): loss_dict = model(images, targets) losses = sum(loss for loss in loss_dict.values()) optimizer.zero_grad() scaler.scale(losses).backward() scaler.step(optimizer) scaler.update()- 数据加载瓶颈:
num_workers设置过小会导致GPU等待数据。一般设置为CPU核心数的一半左右。如果发现训练时GPU利用率上不去,优先检查数据加载是不是卡在PIL.Image.open和JSON解析上了。
6. 项目扩展与后续优化思路
模型跑通只是开始。从实际工程角度看,还有几个方向可以根据业务需求继续推进。
6.1 使用ONNX导出加速推理
Keypoint R-CNN的PyTorch推理在CPU上速度一般,如果部署环境是CPU,建议先转ONNX再跑:
model.eval() dummy_input = torch.randn(1, 3, 800, 800).to(device) torch.onnx.export( model, dummy_input, "keypoint_rcnn.onnx", opset_version=11, input_names=["images"], output_names=["boxes", "scores", "labels", "keypoints"] )导出后还需要额外处理坐标解码逻辑,ONNX导出的keypoints输出就是热图解码前的结果还是最终坐标,取决于torchvision的版本。建议导出后先用onnxruntime跑一遍,对比PyTorch推理输出,确认一致再部署。
6.2 评估指标:不只是mAP
关键点检测的评估,除了检测部分的mAP,关键点部分通常用OKS(Object Keypoint Similarity)来评估。OKS的公式基于关键点位置与GT的欧氏距离,并用目标尺度归一化,比纯像素误差更贴近真实场景。
def compute_oks(gt_kpts, pred_kpts, bbox_area, sigma=0.05): # 简化版OKS计算,假设所有关键点使用同一sigma distance = np.sqrt(np.sum((gt_kpts - pred_kpts) ** 2, axis=1)) oks = np.exp(-(distance ** 2) / (2 * bbox_area * sigma ** 2)) return np.mean(oks)用OKS做评估,能更准确判断模型在不同大小目标上的表现。
6.3 轻量化部署的替代方案
如果后续对推理速度有硬指标要求(比如实时性),Keypoint R-CNN本身的双阶段结构会成为瓶颈。此时可以考虑用训练好的模型做知识蒸馏,指导一个轻量级的单阶段关键点回归网络(比如基于MobileNet的CenterNet变体),在保留精度的前提下大幅提速。
但从实操角度讲,我个人的建议是:先用Keypoint R-CNN把数据、流程和baseline跑通,再根据实际业务瓶颈决定是否需要走轻量化路线。直接一上来就做轻量自定义网络,往往会在数据保障不全的情况下引入太多变量,排查问题时会非常被动。
最后
这次用PyTorch的Keypoint R-CNN做自建数据集关键点检测,整体流程走下来,最大的体会是:这个模型的训练本身不复杂,复杂的是数据。从标注格式对齐到坐标变换,从关键点头的维度替换到热图解码,每一步看似小,但都直接影响最终效果。如果你也是刚接触关键点检测,建议严格按照“数据检查 → 小规模过拟合实验 → 全量训练 → 可视化验证”的顺序推进,别一上来就追求精度指标,先把流程跑通,再逐步调优。我在实践中最常回头看的一句话是:关键点检测的项目,数据质量决定效果上限,模型训练只是把上限兑现的过程。
本文还有配套的精品资源,点击获取