1 廢話不多說,直接上代碼
opencv方式
import time
import subprocess
import cv2, os
from math import ceildef extract_frames_opencv(video_path, output_folder, frame_rate=1):"""使用 OpenCV 從視頻中抽取每秒指定幀數的幀,并保存到指定文件夾。如果視頻長度不是整數秒,則會在最后一幀時補充空白圖像。參數:video_path (str): 輸入視頻文件的路徑。output_folder (str): 輸出幀圖像文件的文件夾路徑。frame_rate (int): 每秒抽取的幀數,默認為 1。返回:None"""start_time = time.time()# 創建輸出文件夾os.makedirs(output_folder, exist_ok=True)# 打開視頻文件cap = cv2.VideoCapture(video_path)# 獲取視頻長度和幀率fps = cap.get(cv2.CAP_PROP_FPS)total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))duration = total_frames / fps# 計算需要抽取的總幀數target_frames = int(duration * frame_rate)# 逐幀抽取圖像frame_idx = 0for i in range(target_frames):cap.set(cv2.CAP_PROP_POS_FRAMES, int(i * fps / frame_rate))ret, frame = cap.read()if ret:cv2.imwrite(os.path.join(output_folder, f"frame_{frame_idx:06d}.jpg"), frame)frame_idx += 1else:break# 如果最后一幀不是完整的一幀,則補充空白圖像if frame_idx < target_frames:for i in range(frame_idx, target_frames):blank_image = 255 * np.ones((int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)), int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), 3), dtype=np.uint8)cv2.imwrite(os.path.join(output_folder, f"frame_{i:06d}.jpg"), blank_image)# 釋放視頻捕獲對象cap.release()print(f"成功從視頻中抽取了 {target_frames} 幀, 一共耗時{time.time() - start_time}s")
ffmpeg方式
def extract_frames_ffmpeg(video_path, output_folder, frame_rate=1):"""使用 FFmpeg 從視頻中抽取每秒指定幀數的幀,并保存到指定文件夾。如果視頻長度不是整數秒,則會拋出異常。參數:video_path (str): 輸入視頻文件的路徑。output_folder (str): 輸出幀圖像文件的文件夾路徑。frame_rate (int): 每秒抽取的幀數,默認為 1。返回:None"""start_time = time.time()# 創建輸出文件夾os.makedirs(output_folder, exist_ok=True)# 獲取視頻長度command = ["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of","default=nokey=1:noprint_wrappers=1", video_path]result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)if result.returncode != 0:raise ValueError("Failed to get video duration.")duration = float(result.stdout.decode().strip())# 四舍五入視頻長度到最接近的整數秒duration = round(duration)# 構建 FFmpeg 命令command = ["ffmpeg","-i", video_path,"-vf", f"fps={frame_rate}","-frames:v", "%d" % (ceil(duration * frame_rate)),os.path.join(output_folder, "frame_%06d.jpg")]# 執行 FFmpeg 命令subprocess.run(command, check=True)print(f"成功從視頻中抽取了 {ceil(duration * frame_rate)} 幀, 一共耗時{time.time() - start_time}s")
2 測試實驗對比
測試一個56s的mp4文件
extract_frames_opencv(video_path, output_folder, 1) # 成功從視頻中抽取了 55 幀, 一共耗時10.131151914596558s extract_frames_ffmpeg(video_path, output_folder, 1) # 成功從視頻中抽取了 56 幀, 一共耗時8.075150966644287s
?1幀/s時,ffmpeg稍快2s
extract_frames_opencv(video_path, output_folder1, 5) # 成功從視頻中抽取了 278 幀, 一共耗時54.822526931762695s extract_frames_ffmpeg(video_path, output_folder2, 5) # 成功從視頻中抽取了 280 幀, 一共耗時8.546468019485474s
5幀/s時,ffmpeg方式只增加0.5s,opencv增加了5倍時長。
在大文件抽幀、或者抽幀頻率較高時,ffmpeg效率更高。
3 ffmpeg抽幀圖片更小?
對比了下抽幀圖片,分辨率一致,但是ffmpeg抽幀圖片好像小很多,為什么呢?
搜了下官方解釋:
FFmpeg 和 OpenCV 在抽取視頻幀并保存圖像時,可能會產生不同的結果,包括文件大小和圖像質量等。出現這種差異的原因主要有以下幾點:
-
編碼器選擇:
- FFmpeg 默認使用 JPEG 編碼器,這種編碼方式通常可以生成較小的文件大小,但可能會略有些圖像質量損失。
- OpenCV 默認使用無損的 PNG 編碼器,生成的文件通常會更大,但圖像質量更好。
-
色彩空間轉換:
- FFmpeg 在抽取幀時,可能會將視頻的色彩空間轉換為更適合 JPEG 編碼的格式,例如 YUV 420。這種轉換可以進一步減小文件大小。
- OpenCV 則可能會保留視頻原有的色彩空間,如 RGB,在保存為 PNG 時不進行額外的轉換。
-
圖像縮放和重采樣:
- FFmpeg 在抽取幀時,可能會根據輸出分辨率對圖像進行縮放和重采樣,從而進一步優化文件大小。
- OpenCV 則可能會直接保存原始分辨率的圖像,不進行任何縮放處理。
綜上所述,FFmpeg 在抽取視頻幀并保存為圖像時,通常會采取一些優化措施,如使用 JPEG 編碼、色彩空間轉換和圖像縮放等,從而生成相對較小的文件大小。而 OpenCV 則更傾向于保留原始的視覺質量,因此生成的圖像文件會相對更大。