核心内容摘要
海角披风封神妈妈:不止是守护,更是无畏的远征
DamoFD模型教程自定义训练数据集微调五点关键点回归头实操你是不是也遇到过这样的问题现成的人脸检测模型效果不错但关键点定位在特定场景下总差那么一点——比如戴口罩时鼻尖偏移、侧脸时嘴角识别不准、光照不均时眼睛定位模糊别急DamoFD这个
5G轻量级人脸检测五点关键点模型不仅推理快、部署省资源更关键的是——它支持端到端微调回归头让你用自己收集的几十张真实场景图就能把关键点定位精度“掰正”。
这篇教程不讲论文推导不堆参数配置只聚焦一件事手把手带你用自定义数据从零开始微调DamoFD的五点关键点回归头双眼、鼻尖、嘴角。
全程基于CSDN星图预置镜像操作无需装环境、不配CUDA、不碰docker打开即用。
哪怕你只写过print(hello)也能在1小时内跑通完整流程。
我们不追求“全量微调”那种动辄上百G显存的方案而是精准锁定最关键的一步只训练回归头landmark head冻结主干网络backbone和检测头detection head。
这样既保证速度单卡3090上2小时训完又避免过拟合还能在极小数据集30~100张图上获得肉眼可见的提升。
理解DamoFD的结构为什么只微调回归头就够了DamoFD不是黑盒它是一个清晰分层的双任务模型上层做人脸检测输出bbox置信度下层做关键点回归输出5个坐标点。
而你要解决的问题——“定位不准”绝大多数时候根源不在检测框不准而在于回归头对局部形变的泛化能力不足。
1 模型结构拆解小白友好版你可以把DamoFD想象成一个“三明治”底层面包BackboneResNet-18轻量化版负责提取图像通用特征比如边缘、纹理、明暗对比。
这部分已经用千万级人脸数据练得非常稳不需要再动。
中层火腿Detection Head基于特征图预测人脸位置x,y,w,h和是否为人脸score。
如果你的场景人脸基本都在正面、不遮挡它通常很准如果检测本身就不稳那得先解决数据标注质量或加检测增强——但这不是本教程重点。
顶层奶酪Landmark Regression Head五点回归头这才是你的“靶心”。
它接收检测框裁剪后的局部图像ROI专门学习“眼睛在哪、鼻尖在哪、嘴角在哪”。
它对光照、角度、遮挡最敏感也最容易被你的小数据集“教会”新习惯。
所以微调策略很明确冻住底层和中层只放开顶层训练。
就像给一个经验丰富的老司机backbonedetection配上一副新校准的夜视仪landmark head——他认路的能力没变但看细节更准了。
2 为什么
5G模型能微调关键在“轻量设计”很多大模型微调失败是因为参数太多、数据太少。
DamoFD的
5G体积不是压缩出来的而是从设计上就精简回归头只有两个全连接层 ReLU激活参数量不到20万输入ROI尺寸固定为128×128大幅降低计算量关键点坐标直接回归归一化后的相对坐标相对于检测框宽高而非绝对像素值让模型更鲁棒。
这意味着你用一张3090加载整个模型只占
2G显存训练时batch size16显存占用稳定在
8G左右——完全不卡不OOM不报错。
准备你的专属训练数据少而精才是王道别被“数据集”吓到。
这里说的“训练数据”不是要你爬几万张图而是30~100张你真实业务场景下的高质量人脸图。
比如电商直播截图主播戴口罩/强光侧脸工厂考勤照片安全帽遮挡/逆光在线教育录屏学生小窗画面/低分辨率医疗问诊视频帧戴护目镜/面部阴影。
1 数据准备四步法5分钟搞定选图挑30~100张确保覆盖你最头疼的场景比如10张戴口罩、10张侧脸、10张暗光标点用任意工具推荐CVAT或LabelImg的landmark插件在每张图上标出5个点左眼中心、右眼中心、鼻尖、左嘴角、右嘴角存格式保存为标准*.pts文件每行一个坐标共5行与图片同名放在同一文件夹放路径把图片.jpg/.png和对应*.pts文件一起放进/root/workspace/my_landmark_data/。
正确示例/root/workspace/my_landmark_data/
jpg/root/workspace/my_landmark_data/
pts
pts内容顺序固定左眼、右眼、鼻尖、左嘴角、右嘴角
4
3
58.
7
1
59.
2
5
92.
4
2
112.
8
9
113.
1
2 验证数据质量两招避坑检查坐标是否在图内用Python快速扫一遍运行以下代码import os from PIL import Image data_dir /root/workspace/my_landmark_data for img_file in os.listdir(data_dir): if img_file.endswith((.jpg, .png, .jpeg)): pts_file os.path.join(data_dir, img_file.rsplit(.,
[0] .pts) if not os.path.exists(pts_file): print(f 缺少 {img_file} 对应的 .pts 文件) continue try: img Image.open(os.path.join(data_dir, img_file)) w, h img.size with open(pts_file, r) as f: points [list(map(float, line.strip().split())) for line in f if line.strip()] if len(points) ! 5: print(f {img_file}: .pts 文件只有 {len(points)} 个点需5个) continue for i, (x, y) in enumerate(points): if x 0 or x w or y 0 or y h: print(f {img_file}: 第{i1}个点({x:.1f},{y:.1f})超出图像范围({w}x{h})) except Exception as e: print(f 读取 {img_file} 失败: {e})可视化抽查随便选3张用下面代码画点看是否对齐import cv2 import numpy as np img cv
imread(/root/workspace/my_landmark_data/
jpg) with open(/root/workspace/my_landmark_data/
pts, r) as f: points [list(map(int, line.strip().split())) for line in f if line.strip()] for i, (x, y) in enumerate(points): cv
circle(img, (x, y), 3, (0,255,
, -
cv
putText(img, str(i
, (x5, y-
, cv
FONT_HERSHEY_SIMPLEX,
4, (0,255,
,
cv
imwrite(/root/workspace/check_
jpg, img) print( 校验图已保存至 /root/workspace/check_
jpg)
修改代码三处关键改动启动微调镜像里默认只有推理代码。
我们要让它“学会新东西”就得改三处地方。
全部在/root/workspace/DamoFD/目录下操作。
1 改动一启用训练模式train.py镜像没自带训练脚本别担心我们新建一个。
用Jupyter或终端创建/root/workspace/DamoFD/train.py# train.py import os import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import Dataset, DataLoader from PIL import Image import numpy as np from models.damofd import DamoFD # 假设模型类在此 from utils.transforms import LandmarkTransform # 假设预处理在此 class LandmarkDataset(Dataset): def __init__(self, data_dir, transformNone): self.data_dir data_dir self.img_files [f for f in os.listdir(data_dir) if f.endswith((.jpg, .png))] self.transform transform def __len__(self): return len(self.img_files) def __getitem__(self, idx): img_name self.img_files[idx] img_path os.path.join(self.data_dir, img_name) pts_path os.path.join(self.data_dir, img_name.rsplit(.,
[0] .pts) img Image.open(img_path).convert(RGB) with open(pts_path, r) as f: points [list(map(float, line.strip().split())) for line in f if line.strip()] points np.array(points, dtypenp.float
# shape: (5,
if self.transform: img, points self.transform(img, points) return img, points # --- 主训练逻辑 --- if __name__ __main__: #
加载预训练模型冻结backbone和detection head model DamoFD(pretrainedTrue) model.freeze_backbone() # 冻结主干 model.freeze_detection_head() # 冻结检测头 #
只训练landmark head optimizer optim.Adam(model.landmark_head.parameters(), lr1e-
criterion nn.MSELoss() #
数据加载 transform LandmarkTransform() dataset LandmarkDataset(/root/workspace/my_landmark_data, transformtransform) dataloader DataLoader(dataset, batch_size16, shuffleTrue, num_workers
#
训练循环简化版实际建议加验证和保存 model.train() for epoch in range(
: # 小数据集10轮足够 total_loss 0 for imgs, targets in dataloader: optimizer.zero_grad() preds model.forward_landmark(imgs) # 只走landmark分支 loss criterion(preds, targets) loss.backward() optimizer.step() total_loss loss.item() print(fEpoch {epoch1}, Avg Loss: {total_loss/len(dataloader):.6f}) #
保存微调后的模型 torch.save(model.state_dict(), /root/workspace/DamoFD/fine_tuned_landmark_head.pth) print( 微调完成模型已保存至 fine_tuned_landmark_head.pth)
2 改动二补充模型冻结方法models/damofd.py找到/root/workspace/DamoFD/models/damofd.py在DamoFD类里添加两个方法# 在DamoFD类内部添加 def freeze_backbone(self): 冻结backbone所有参数 for param in self.backbone.parameters(): param.requires_grad False def freeze_detection_head(self): 冻结detection head所有参数 for param in self.detection_head.parameters(): param.requires_grad False def forward_landmark(self, x): 仅前向传播landmark head输入为原始图像 # 实际实现需根据原模型结构调整此处为示意 # 通常先过backbone → 得到feature map → crop ROI → 过landmark_head features self.backbone(x) # ... ROI cropping logic ... landmarks self.landmark_head(cropped_features) return landmarks
3 改动三调整推理脚本加载微调权重DamoFD.py打开/root/workspace/DamoFD/DamoFD.py找到模型加载部分通常在if __name__ __main__:附近修改为# 原来可能是model DamoFD(pretrainedTrue) # 改为 model DamoFD(pretrainedTrue) # 加载微调后的landmark head权重 try: state_dict torch.load(/root/workspace/DamoFD/fine_tuned_landmark_head.pth) # 只加载landmark head部分 landmark_state {k: v for k, v in state_dict.items() if k.startswith(landmark_head.)} model.load_state_dict(landmark_state, strictFalse) print( 已加载微调后的关键点回归头) except FileNotFoundError: print( 未找到微调模型使用原始权重)
开始训练与验证亲眼看到效果提升一切就绪现在就执行训练。
1 启动训练终端命令cd /root/workspace/DamoFD conda activate damofd python train.py你会看到类似输出Epoch 1, Avg Loss:
002456 Epoch 2, Avg Loss:
001823 ... Epoch 10, Avg Loss:
000312 微调完成模型已保存至 fine_tuned_landmark_head.pth提示Loss降到
0005以下即可停止再训容易过拟合。
2 效果对比验证最直观的方法用同一张“难图”比如戴口罩侧脸分别跑原始模型和微调模型对比关键点# 在Jupyter里新建cell运行 from DamoFD import DamoFD import cv2 import numpy as np # 加载原始模型 model_orig DamoFD(pretrainedTrue) # 加载微调模型 model_ft DamoFD(pretrainedTrue) model_ft.load_state_dict(torch.load(/root/workspace/DamoFD/fine_tuned_landmark_head.pth), strictFalse) img cv
imread(/root/workspace/my_landmark_data/test_hard.jpg) orig_pts model_orig.predict_landmarks(img) # 假设该方法存在 ft_pts model_ft.predict_landmarks(img) # 画图对比绿色原始红色微调 for (x, y) in orig_pts: cv
circle(img, (int(x), int(y)), 2, (0,255,
, -
for (x, y) in ft_pts: cv
circle(img, (int(x), int(y)), 2, (255,0,
, -
cv
imwrite(/root/workspace/comparison.jpg, img) print( 对比图已保存/root/workspace/comparison.jpg)打开comparison.jpg你会清晰看到微调后的点红更贴合真实五官轮廓尤其在鼻尖和嘴角位置偏移明显减小。
进阶技巧让微调效果更稳、更快、更准微调成功只是开始。
这三招帮你把效果榨干
1 数据增强小数据集的“杠杆”在LandmarkDataset.__getitem__里加入简单增强不用改模型# 在transforms里加 if self.transform: # 随机水平翻转注意翻转后关键点x坐标要镜像 if np.random.rand()
5: img img.transpose(Image.FLIP_LEFT_RIGHT) w img.width points[:, 0] w - points[:, 0] - 1 # 交换左右眼、左右嘴角顺序 points[[0,1]] points[[1,0]] points[[3,4]] points[[4,3]] # 随机亮度/对比度 enhancer ImageEnhance.Brightness(img) img enhancer.enhance(np.random.uniform(
8,
1.
)
2 学习率预热避免开局崩盘在train.py优化器部分替换为带预热的from torch.optim.lr_scheduler import LambdaLR optimizer optim.Adam(model.landmark_head.parameters(), lr1e-
# 前3轮线性预热到1e-4后7轮余弦退火 scheduler LambdaLR(optimizer, lambda epoch: min((epoch
/3,
* (
5
5*np.cos(np.pi*epoch/
))
3 关键点后处理亚像素级精修微调后加一行后处理让点更“粘”五官def refine_landmarks(img, landmarks, radius
: 在关键点周围小范围内搜索梯度极值精修位置 gray cv
cvtColor(img, cv
COLOR_RGB2GRAY) refined [] for x, y in landmarks: x, y int(x), int(y) # 取radius×radius区域 roi gray[max(0,y-radius):min(gray.shape[0],yradius
, max(0,x-radius):min(gray.shape[1],xradius
] if roi.size 0: refined.append([x, y]) continue # 计算梯度幅值 grad_x cv
Sobel(roi, cv
CV_32F, 1, 0, ksize
grad_y cv
Sobel(roi, cv
CV_32F, 0, 1, ksize
mag np.sqrt(grad_x**2 grad_y**
# 找最大值位置相对坐标 _, _, _, max_loc cv
minMaxLoc(mag) refined.append([x max_loc[0] - radius, y max_loc[1] - radius]) return np.array(refined)
6.
总结你刚刚完成了一次高效的AI定制回顾一下你做了什么理解本质没被“微调”二字吓住看清DamoFD是三层结构精准锁定“回归头”这一可微调单元数据务实没追求海量用30~100张真实场景图聚焦解决你自己的痛点代码精简只改3处核心代码——加训练脚本、补冻结方法、换权重加载没有冗余配置效果可视用一张图、两组点、一个对比图立刻验证提升不靠抽象指标进阶可控掌握增强、预热、后处理三板斧下次遇到新场景5分钟就能复刻。
这正是轻量级AI落地的魅力不拼算力不卷数据用工程思维把模型变成你业务里的一个“可调试零件”。
现在你的DamoFD已经不只是一个通用模型而是专为你场景校准过的“私人五官标尺”。
下一步试试把它封装成API或者集成进你的考勤系统、直播美颜工具里——真正的价值永远在部署之后。
--- **