基于DamoFD-0.5G的短视频人脸追踪方案你有没有遇到过这种情况用手机拍了一段短视频画面里的人物稍微动一下或者镜头一晃人脸就跑到框外面去了或者追踪框跳来跳去看着特别别扭。尤其是在做短视频剪辑、Vlog制作或者直播的时候这种不稳定的追踪效果直接拉低了整个视频的观感。其实这背后是一个挺实际的技术问题怎么在动态的视频里稳稳地“跟住”人脸。今天我就来分享一个我们团队在实际项目中验证过的方案核心是使用一个叫DamoFD-0.5G的轻量级人脸检测模型再配合一些简单的后处理技巧来实现流畅、稳定的人脸追踪。这个方案最大的好处是轻量、高效、效果好特别适合在普通电脑甚至一些边缘设备上跑起来。1. 为什么选择DamoFD-0.5G在聊具体怎么做之前得先说说为什么是它。市面上人脸检测的模型不少从老牌的MTCNN到后来的RetinaFace各有各的特点。但放到短视频处理这个场景里我们主要关心三件事准不准、快不快、省不省资源。DamoFD-0.5G 是达摩院在ICLR 2023上发布的一个模型名字里的“0.5G”指的是它的计算量大概在0.5 GFlops左右非常轻量。别看它小在公开的人脸检测数据集WiderFace上它的表现比同量级的其他模型要好尤其是在复杂场景、小人脸检测上优势更明显。对于我们做视频追踪来说这意味着速度快可以处理更高帧率的视频实时性更好。精度够用能比较准确地框出人脸包括侧脸、遮挡等情况。部署简单模型小依赖少用ModelScope平台几行代码就能调用大大降低了使用门槛。你可以把它理解为一个专门为“看清人脸”而优化过的小巧工具基础打好了后面的追踪才能稳。2. 整体方案思路检测 追踪 平滑单纯对每一帧画面都用模型检测一次人脸理论上是最准的但这样有两个问题一是速度可能跟不上二是帧与帧之间的人脸框可能“抖动”看起来不连贯。我们的思路是“粗细结合”关键帧精准检测不是每一帧都检测而是每隔几帧比如每秒抽2-5帧用DamoFD做一次“重检测”。这一步保证我们每隔一小段时间就能获得一个非常准确的人脸位置基准。帧间快速关联在非关键帧之间我们用一个非常轻量的方法比如基于位置和外观特征的简单匹配来预测人脸的位置这样效率极高。轨迹平滑处理把所有帧的人脸框位置连起来会形成一条“轨迹”。这条原始轨迹可能因为误检、遮挡而有毛刺我们再用一个平滑滤波器比如卡尔曼滤波或简单的移动平均把这条轨迹修得圆滑一些。这样既保证了关键位置上的准确性又通过插值和平滑保证了追踪的流畅和稳定整体计算开销也控制得很好。下面我们一步步来看怎么实现。3. 第一步用DamoFD提取关键帧人脸首先我们需要把DamoFD用起来。得益于ModelScope平台这个过程非常简单。import cv2 from modelscope.pipelines import pipeline from modelscope.utils.constant import Tasks # 初始化人脸检测管道指定使用DamoFD-0.5G模型 face_detection pipeline(taskTasks.face_detection, modeldamo/cv_ddsar_face-detection_iclr23-damofd) def detect_faces_in_keyframe(video_path, keyframe_interval10): 从视频中按间隔抽取关键帧并进行人脸检测。 参数: video_path: 视频文件路径 keyframe_interval: 关键帧间隔帧数 返回: keyframe_results: 列表每个元素是(帧序号, 检测结果) cap cv2.VideoCapture(video_path) frame_count 0 keyframe_results [] while True: ret, frame cap.read() if not ret: break # 每隔 keyframe_interval 帧处理一次 if frame_count % keyframe_interval 0: # 将BGR格式的帧转换为RGBModelScope通常期望RGB frame_rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # 进行人脸检测 result face_detection(frame_rgb) keyframe_results.append((frame_count, result)) print(f关键帧 {frame_count}: 检测到 {len(result[boxes])} 张人脸) frame_count 1 cap.release() return keyframe_results # 使用示例 video_file your_short_video.mp4 keyframe_detections detect_faces_in_keyframe(video_file, keyframe_interval15)运行这段代码你就能得到一系列关键帧里的人脸检测结果。每个结果里包含了人脸框的坐标boxes、置信度scores和五点关键点keypoints。这是所有后续工作的基础数据。4. 第二步构建人脸轨迹与帧间关联有了关键帧的精确位置我们需要把中间帧的人脸位置“补”上。这里用一个简单的基于IoU交并比和特征距离的匹配方法。假设我们处理的是单人脸追踪多人脸情况类似但需要做ID匹配稍微复杂点。import numpy as np from scipy.spatial.distance import cdist def associate_faces_across_frames(keyframe_results, video_path, max_disappeared5): 关联关键帧之间的人脸构建初步轨迹。 这里简化为单人脸追踪取每帧中置信度最高的人脸。 cap cv2.VideoCapture(video_path) total_frames int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 初始化轨迹列表长度等于总帧数初始为None trajectory [None] * total_frames # 首先把关键帧的检测结果放进去 for frame_idx, result in keyframe_results: if result[boxes]: # 取置信度最高的人脸框 best_idx np.argmax(result[scores]) bbox result[boxes][best_idx] # [x1, y1, x2, y2] trajectory[frame_idx] bbox # 简单的线性插值填充关键帧之间的空缺 prev_idx None for i in range(total_frames): if trajectory[i] is not None: if prev_idx is not None and prev_idx i - 1: # 在两个有关键帧的位置之间进行插值 start_box np.array(trajectory[prev_idx]) end_box np.array(trajectory[i]) steps i - prev_idx for j in range(1, steps): interp_ratio j / steps interp_box start_box (end_box - start_box) * interp_ratio trajectory[prev_idx j] interp_box.tolist() prev_idx i # 处理视频开头和结尾没有检测到人脸的情况可选用最近的有效框填充 # ... cap.release() return trajectory这段代码做了件事先用关键帧的人脸框作为“锚点”然后在锚点之间用线性插值把中间帧的人脸位置估算出来。这虽然简单但对于人物匀速运动、镜头平缓移动的场景效果已经不错了。5. 第三步轨迹平滑与抖动处理上一步得到的原始轨迹可能还不够平滑尤其是当检测偶尔有误差或者人物运动突然变化时。我们需要一个“平滑滤波器”来去除高频抖动保留主要的运动趋势。这里用一个非常实用且简单的滤波器——一维卡尔曼滤波器我们分别对框的中心点x, y和宽高w, h进行滤波。class SimpleKalmanFilter: 一个简化版的卡尔曼滤波器用于平滑单个坐标值。 def __init__(self, process_variance1e-3, measurement_variance1e-1): # 状态 [值, 速度] self.state np.array([0., 0.]) self.covariance np.eye(2) # 过程噪声系统不确定性 self.Q np.eye(2) * process_variance # 测量噪声观测不确定性 self.R measurement_variance # 状态转移矩阵 [1, dt; 0, 1]这里假设dt1每帧 self.F np.array([[1., 1.], [0., 1.]]) # 观测矩阵 [1, 0] self.H np.array([[1., 0.]]) def predict(self): self.state self.F self.state self.covariance self.F self.covariance self.F.T self.Q return self.state[0] def update(self, measurement): # 计算卡尔曼增益 S self.H self.covariance self.H.T self.R K self.covariance self.H.T / S # 更新状态 y measurement - self.H self.state self.state self.state K * y # 更新协方差 I np.eye(2) self.covariance (I - K self.H) self.covariance return self.state[0] def smooth_trajectory(raw_trajectory): 使用卡尔曼滤波器平滑人脸框轨迹。 对中心点(x, y)和宽高(w, h)分别滤波。 if not raw_trajectory: return [] # 将原始框列表转换为numpy数组方便处理 # 每个框格式为 [x1, y1, x2, y2] boxes np.array([b if b is not None else [0,0,0,0] for b in raw_trajectory]) # 计算中心点和宽高 centers_x (boxes[:, 0] boxes[:, 2]) / 2 centers_y (boxes[:, 1] boxes[:, 3]) / 2 widths boxes[:, 2] - boxes[:, 0] heights boxes[:, 3] - boxes[:, 1] # 为四个值分别创建滤波器 kf_x SimpleKalmanFilter() kf_y SimpleKalmanFilter() kf_w SimpleKalmanFilter(process_variance1e-4, measurement_variance1e-2) # 宽高变化通常更慢 kf_h SimpleKalmanFilter(process_variance1e-4, measurement_variance1e-2) smoothed_boxes [] for i in range(len(boxes)): # 预测步骤 pred_x kf_x.predict() pred_y kf_y.predict() pred_w kf_w.predict() pred_h kf_h.predict() # 如果有观测值非零框则更新 if raw_trajectory[i] is not None: smoothed_x kf_x.update(centers_x[i]) smoothed_y kf_y.update(centers_y[i]) smoothed_w kf_w.update(widths[i]) smoothed_h kf_h.update(heights[i]) else: # 如果该帧没有检测到使用预测值并标记为None或进行特殊处理 smoothed_x, smoothed_y, smoothed_w, smoothed_h pred_x, pred_y, pred_w, pred_h # 将平滑后的中心点和宽高转换回框坐标 x1 smoothed_x - smoothed_w / 2 y1 smoothed_y - smoothed_h / 2 x2 smoothed_x smoothed_w / 2 y2 smoothed_y smoothed_h / 2 smoothed_boxes.append([x1, y1, x2, y2]) return smoothed_boxes经过这个平滑处理你会发现原来那些小幅度的、突然的跳动不见了人脸框的运动变得非常顺滑即使人物有正常的转头、小幅移动框也能平稳地跟随。6. 第四步效果展示与实用技巧理论说再多不如看看实际效果。下面这段代码可以将我们追踪并平滑后的人脸框画回到原视频上生成一段新的视频。def apply_tracking_to_video(video_path, smoothed_trajectory, output_pathoutput_tracked.mp4): 将平滑后的追踪框绘制到视频每一帧并保存为新视频。 cap cv2.VideoCapture(video_path) fps int(cap.get(cv2.CAP_PROP_FPS)) width int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 定义视频编码器和输出 fourcc cv2.VideoWriter_fourcc(*mp4v) out cv2.VideoWriter(output_path, fourcc, fps, (width, height)) frame_idx 0 while True: ret, frame cap.read() if not ret: break # 获取当前帧对应的平滑后的人脸框 if frame_idx len(smoothed_trajectory) and smoothed_trajectory[frame_idx] is not None: bbox smoothed_trajectory[frame_idx] x1, y1, x2, y2 [int(coord) for coord in bbox] # 在帧上绘制矩形框 cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) # 可以加上文字标签 cv2.putText(frame, fTracked Face, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 2) out.write(frame) frame_idx 1 cap.release() out.release() print(f追踪结果已保存至: {output_path}) # 串联整个流程 raw_traj associate_faces_across_frames(keyframe_detections, video_file) smooth_traj smooth_trajectory(raw_traj) apply_tracking_to_video(video_file, smooth_traj)几个提升效果的小技巧调整关键帧间隔如果视频里人物动作剧烈就把keyframe_interval调小一点比如10帧让“重检测”更频繁基准更准。如果动作平缓可以调大比如30帧加快处理速度。处理遮挡当人脸被短暂遮挡比如手划过面前时简单的线性插值可能会失效。一个改进方法是引入一个“记忆”机制在丢失跟踪后用卡尔曼滤波器持续预测几帧并在这期间尝试用DamoFD重新检测检测到了再关联上。多人脸追踪上面的例子是单人脸。对于多人脸核心是为每个检测到的人脸分配一个唯一的ID并在帧间通过特征匹配例如用人脸区域的小图计算相似度或运动一致性来维持ID。DamoFD本身不提供ID需要自己实现一个简单的Re-ID模块或者结合其他轻量跟踪器如KCF。利用关键点DamoFD输出的五点关键点双眼、鼻尖、嘴角很有用。你可以用这些关键点来计算人脸的姿态是否正对镜头甚至可以用来做更精细的稳定比如让框随着头部转动而自适应调整大小和角度。7. 总结整体试下来这套以DamoFD-0.5G为核心的短视频人脸追踪方案在大多数日常场景下的表现是令人满意的。它最大的优势在于平衡用一个小巧但足够精准的模型打好检测基础再用一些不复杂的算法做追踪和平滑最终实现的效果却挺“稳当”。部署起来也没什么压力ModelScope的生态让模型调用变得非常简单剩下的逻辑用Python就能搞定。如果你是在做短视频工具开发、视频内容分析或者只是想给自己拍的视频加个智能跟踪框这个方案都是一个不错的起点。当然它也不是万能的。面对极度快速的运动、严重的遮挡或者特别密集的人群可能还需要更复杂的算法。但对于80%的普通短视频场景它已经能帮你解决画面抖动、追踪不稳这些头疼问题了。你可以先拿自己的一段视频试试看看效果再根据实际需求调整里面的参数比如检测间隔、平滑强度这些。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。