import cv2 import numpy as np import torch def extract_video_features(video_path, model, processor, device): """提取视频关键帧特征(智能抽帧版) Args: video_path: 视频文件路径 model: CLIP模型 processor: CLIP处理器 device: 计算设备 Returns: features: 形状为 (n_frames, feature_dim) 的特征数组 timestamps: 对应帧的时间戳(秒) """ # 打开视频文件 cap = cv2.VideoCapture(video_path) if not cap.isOpened(): raise ValueError(f"无法打开视频文件: {video_path}") # 获取视频属性 fps = cap.get(cv2.CAP_PROP_FPS) total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 处理无效帧率 if fps <= 0: fps = 30 print(f"警告: 视频帧率无效,使用默认值 {fps}") # 智能抽帧参数设置 base_interval = max(1, int(fps * 2)) # 基础间隔(2秒) min_interval = 1 # 最小间隔(1帧) max_interval = max(1, int(fps * 5)) # 最大间隔(5秒) motion_threshold = 15 # 运动阈值(像素平均变化) window_size = 3 # 运动量平均窗口大小 # 初始化变量 prev_gray = None motion_history = [] features = [] timestamps = [] current_index = 0 while current_index < total_frames: # 设置当前帧位置 cap.set(cv2.CAP_PROP_POS_FRAMES, current_index) ret, frame = cap.read() if not ret: break try: # 转换为灰度图计算运动量 current_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 计算运动量(与前一帧的差异) if prev_gray is not None: frame_diff = cv2.absdiff(current_gray, prev_gray) # 三个都是 (v_row, v_col)维 motion_level = np.mean(frame_diff) motion_history.append(motion_level) # 保持窗口大小 if len(motion_history) > window_size: motion_history.pop(0) # 计算平均运动量 avg_motion = np.mean(motion_history) # 动态调整抽帧间隔 if avg_motion > motion_threshold: next_interval = max(min_interval, base_interval - int(avg_motion/3)) else: next_interval = min(max_interval, base_interval + int(base_interval * 0.5)) else: next_interval = base_interval # 第一帧使用基础间隔 # 转换为RGB并提取特征 frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) inputs = processor(images=frame_rgb, return_tensors="pt").to(device) with torch.no_grad(): frame_features = model.get_image_features(**inputs) # 保存结果 features.append(frame_features.cpu().numpy().squeeze()) timestamps.append(current_index / fps) # 更新状态 prev_gray = current_gray current_index += next_interval except Exception as e: print(f"处理帧 {current_index} 时出错: {str(e)}") current_index += base_interval # 出错时使用基础间隔 continue cap.release() # 转换为numpy数组 if len(features) > 0: features = np.vstack(features) timestamps = np.array(timestamps) else: features = np.empty((0, 512)) # 空数组 timestamps = np.array([]) return features, timestamps, total_frames, fps