Python OpenCV获取MP4视频最后一帧时ret为False、frame为空问题
问题根源
cv2.CAP_PROP_FRAME_COUNT返回值并非对所有视频都100%准确,可变帧率(VFR)视频、带B帧的H.264/H.265封装视频、索引不完整的MP4文件都会出现该值和实际可读取总帧数存在偏差的情况,你遇到的27.75fps非整数帧率视频大概率属于可变帧率范畴,直接减1定位很容易落到实际不存在的帧位置,所以read()返回失败。- 部分编码器封装的视频本身不支持精准的单帧步进定位,直接设置
CAP_PROP_POS_FRAMES到末尾附近也容易出现定位失效的问题。
修复方案
方案1:带帧位置回退的兼容方案(平衡速度和兼容性,适合绝大多数场景)
不要直接定位到总帧数-1,而是先定位到比总帧数靠前几帧的位置,再逐帧读取直到读取失败,取最后一次成功读取的帧即可,代码示例:
import cv2 def createsample(self, videopath) -> str: """Gets the last frame of the video as a sample.""" cap = cv2.VideoCapture(videopath) if not cap.isOpened(): raise IOError(f"Cannot open video file {videopath}") sample = COGSPATH + r"\data\sample.jpg" # 先获取总帧数,最多往前回退10帧定位(可以根据实际情况调整阈值) total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) start_pos = max(total_frames - 10, 0) cap.set(cv2.CAP_PROP_POS_FRAMES, start_pos) last_frame = None while True: ret, frame = cap.read() if not ret: break last_frame = frame # 校验是否成功读到帧 if last_frame is None: raise RuntimeError("Cannot read any frame from the video") cv2.imwrite(sample, last_frame) cap.release() cv2.destroyAllWindows() return sample
方案2:超大视频优化方案(读取速度更快)
可以结合时长属性做定位,先定位到视频总时长前1秒的位置,再逐帧读取,避免遍历过多帧:
import cv2 def createsample(self, videopath) -> str: """Gets the last frame of the video as a sample.""" cap = cv2.VideoCapture(videopath) if not cap.isOpened(): raise IOError(f"Cannot open video file {videopath}") sample = COGSPATH + r"\data\sample.jpg" # 总时长单位为秒,转毫秒 total_duration = cap.get(cv2.CAP_PROP_DURATION) * 1000 # 定位到最后1秒的位置 cap.set(cv2.CAP_PROP_POS_MSEC, max(total_duration - 1000, 0)) last_frame = None while True: ret, frame = cap.read() if not ret: break last_frame = frame if last_frame is None: raise RuntimeError("Cannot read any frame from the video") cv2.imwrite(sample, last_frame) cap.release() cv2.destroyAllWindows() return sample
额外说明
如果以上方案还是失效,先检查对应视频的编码格式,部分非常规编码的MP4需要安装额外的解码库,Windows环境可以安装K-Lite Codec Pack,Linux环境可以安装ffmpeg相关解码包解决。
内容的提问来源于stack exchange,提问作者DeepQuantum
相关产品推荐
相关产品推荐

