如何用OpenCV100%稳定获取MP4视频的最后一帧?
如何用OpenCV100%稳定获取MP4视频的最后一帧?
我尝试用OpenCV获取MP4视频的最后一帧,使用CAP_PROP_POS_FRAMES方法时部分视频有效、部分无效;改用毫秒定位CAP_PROP_POS_MSEC也存在同样问题。所有视频都是未损坏的MP4格式,已知MP4使用CAP_PROP_POS_FRAMES有兼容性问题,官方推荐CAP_PROP_POS_MSEC,请问有没有100%稳定获取最后一帧的方法?
我的尝试代码
import cv2 from PIL import Image import os import numpy as np last_video_frame_cache = {} def get_last_frame(video_file): # Open the video file cap = cv2.VideoCapture(video_file) # get last ms fps = cap.get(cv2.CAP_PROP_FPS) total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) total_duration_ms = int(total_frames/fps*1000) last_frame_ms = total_duration_ms - 1 # go to the last millisecond print(f"fps {fps} total_frames {total_frames} total_duration_ms {total_duration_ms} last_frame_ms {last_frame_ms}") cap.set(cv2.CAP_PROP_POS_MSEC, last_frame_ms) ret, last_frame = cap.read() # # # go back a few frames since for mp4s you can't go to the exact last frame # total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # cap.set(cv2.CAP_PROP_POS_FRAMES, total_frames-20) # optional # # read current frame # current_frame_index = int(cap.get(cv2.CAP_PROP_POS_FRAMES)) # print(f"current_frame_index {current_frame_index} total_Frames {total_frames}") # #works # Iterate until at last frame. went back 3 frames so this shouldn't take too long # while cap.isOpened(): # # read frame # ret, frame = cap.read() # # for debugging # current_frame_index = int(cap.get(cv2.CAP_PROP_POS_FRAMES)) # print(f"current_frame_index {current_frame_index}") # if not ret: # break # # needs to be here or else wont work # last_frame = frame # Convert the frame to an image image = Image.fromarray(last_frame) # put into cache last_video_frame_cache[video_file] = image video_file_seperated, video_file_ext_seperated = os.path.splitext(video_file) last_frame_path = video_file_seperated + "_last_frame" + ".jpg" # write last frame cv2.imwrite(last_frame_path, last_frame) # for mse last_frame_array = np.array(image) last_frame_grey_global = cv2.cvtColor(last_frame_array, cv2.COLOR_BGR2GRAY) # Release the video file cap.release() print("getting manually") return image
稳定解决方案
MP4的索引和GOP(图像组)结构会导致直接通过CAP_PROP_POS_FRAMES或CAP_PROP_POS_MSEC定位到最后一帧失败——因为视频的最后几帧可能不是关键帧,OpenCV无法直接解码非关键帧(依赖前面的关键帧)。最可靠的方法是从视频末尾向前跳一段距离后,逐帧读取直到结束,记录最后成功读取的帧:
优化后的代码
import cv2 from PIL import Image import os import numpy as np last_video_frame_cache = {} def get_last_frame_stable(video_file): cap = cv2.VideoCapture(video_file) if not cap.isOpened(): raise ValueError(f"无法打开视频文件: {video_file}") total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 向前跳转到总帧数-50的位置,避免从头读取,同时避开最后可能的非关键帧问题 # 如果视频帧数少于50,直接跳转到第0帧 jump_to_frame = max(0, total_frames - 50) cap.set(cv2.CAP_PROP_POS_FRAMES, jump_to_frame) last_frame = None while cap.isOpened(): ret, frame = cap.read() if not ret: break last_frame = frame if last_frame is None: # 极端情况:跳转后仍无法读取,尝试从头读取 cap.set(cv2.CAP_PROP_POS_FRAMES, 0) while cap.isOpened(): ret, frame = cap.read() if not ret: break last_frame = frame if last_frame is None: raise RuntimeError(f"无法读取视频的任何帧: {video_file}") # 后续处理逻辑保持不变 image = Image.fromarray(last_frame) last_video_frame_cache[video_file] = image video_file_seperated, _ = os.path.splitext(video_file) last_frame_path = f"{video_file_seperated}_last_frame.jpg" cv2.imwrite(last_frame_path, last_frame) last_frame_grey_global = cv2.cvtColor(np.array(image), cv2.COLOR_BGR2GRAY) cap.release() return image
方案说明
- 跳转策略:先跳转到总帧数-50的位置,既避免了从头读取的低效,又确保落在某个关键帧之后,能顺利解码后续所有帧。
- 双重保障:如果跳转后无法读取到帧(比如视频实际帧数和
CAP_PROP_FRAME_COUNT返回值不符),则退化为从头逐帧读取,确保不会失败。 - 兼容性:该方法不依赖视频的索引准确性,完全通过逐帧读取的方式获取最后一帧,几乎能兼容所有MP4视频。
内容的提问来源于stack exchange,提问作者Eric Velez
相关产品推荐
相关产品推荐

