如何为Python手动图片查看器添加时间轴以分析VPN调试帧
帧查看工具优化实现方案
背景
某VPN应用无本地日志,仅提供诊断窗口且保存功能无效,需通过录屏转帧分析。已使用以下PowerShell+ffmpeg命令将25fps、1920x1080的WMV视频转为无损BMP帧,文件名格式为\d{5}.bmp:
gci C:\Users\Xeni\Documents -filter *.wmv | %{ $folder = 'C:\Users\Xeni\Desktop\' + $($_.name -replace '.wmv') md $folder; D:\ffmpeg\ffmpeg.exe -hwaccel cuda -i $_.fullname -vf fps=25 "$folder\%05d.bmp" }
现有实现
基于OpenCV编写了基础的帧切换工具,支持左右箭头切换、ESC退出,但缺少全屏显示与时间轴定位功能,代码如下:
import cv2 import re import sys from pathlib import Path def slideshow(folder): images = [str(file) for file in Path(folder).iterdir() if re.match('\d{5}.bmp', file.name)] total = len(images) index = 0 key = -1 while key != 27: img = cv2.imread(images[index]) cv2.imshow('', img) key = cv2.waitKeyEx(0) if key == 2555904: index += 1 elif key == 2424832: index -= 1 index %= total del img cv2.destroyAllWindows() if __name__ == '__main__': slideshow(sys.argv[1])
需求
- 添加时间轴,对应帧的时间为文件名数值/25秒
- 按需加载图片(内存仅16GiB,无法预加载数十GB帧),仅加载当前显示帧,切换后及时释放内存
- 支持通过时间轴手动定位帧
- 支持全屏显示
解决方案
结合tkinter实现带时间轴的GUI界面,同时满足所有需求:
import cv2 import re import sys import tkinter as tk from pathlib import Path from PIL import Image, ImageTk class FrameViewer: def __init__(self, folder): self.folder = Path(folder) # 按帧编号排序,确保播放顺序正确 self.frame_files = sorted( [f for f in self.folder.iterdir() if re.match(r'\d{5}\.bmp', f.name)], key=lambda x: int(x.stem) ) self.total_frames = len(self.frame_files) if self.total_frames == 0: raise ValueError("文件夹中无符合格式的BMP帧文件") self.current_index = 0 self.is_fullscreen = False # 初始化tkinter窗口 self.root = tk.Tk() self.root.title("帧查看器") self.root.bind("<Escape>", self.exit_app) self.root.bind("<Left>", lambda e: self.change_frame(-1)) self.root.bind("<Right>", lambda e: self.change_frame(1)) self.root.bind("<F11>", self.toggle_fullscreen) # 图像显示画布 self.canvas = tk.Canvas(self.root) self.canvas.pack(fill=tk.BOTH, expand=True) # 时间轴滑块 self.timeline = tk.Scale( self.root, from_=0, to=self.total_frames - 1, orient=tk.HORIZONTAL, length=800, command=self.jump_to_frame ) self.timeline.pack(fill=tk.X, padx=10, pady=5) # 时间信息标签 self.time_label = tk.Label(self.root, text="") self.time_label.pack(pady=2) # 加载初始帧 self.load_and_display_frame() def load_and_display_frame(self): # 主动释放前一帧资源 if hasattr(self, 'tk_img'): self.tk_img = None # 加载当前帧 frame_path = self.frame_files[self.current_index] img = cv2.imread(str(frame_path)) img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) pil_img = Image.fromarray(img_rgb) # 适配显示尺寸 if self.is_fullscreen: screen_w, screen_h = self.root.winfo_screenwidth(), self.root.winfo_screenheight() pil_img = pil_img.resize((screen_w, screen_h), Image.Resampling.LANCZOS) else: self.root.geometry(f"{pil_img.width}x{pil_img.height + 80}") # 转tkinter可用格式并显示 self.tk_img = ImageTk.PhotoImage(pil_img) self.canvas.delete("all") self.canvas.create_image(0, 0, anchor=tk.NW, image=self.tk_img) # 更新时间轴与时间标签 self.timeline.set(self.current_index) frame_num = int(frame_path.stem) total_sec = frame_num / 25 mins = int(total_sec // 60) secs = total_sec % 60 self.time_label.config(text=f"帧编号: {frame_num:05d} | 时间: {mins:02d}:{secs:06.3f}") # 释放临时图像资源 del img, img_rgb, pil_img def change_frame(self, delta): self.current_index += delta self.current_index = max(0, min(self.current_index, self.total_frames - 1)) self.load_and_display_frame() def jump_to_frame(self, value): self.current_index = int(value) self.load_and_display_frame() def toggle_fullscreen(self, event): self.is_fullscreen = not self.is_fullscreen self.root.attributes("-fullscreen", self.is_fullscreen) self.load_and_display_frame() def exit_app(self, event): self.root.destroy() def run(self): self.root.mainloop() if __name__ == '__main__': if len(sys.argv) != 2: print("用法: python frame_viewer.py <帧文件夹路径>") sys.exit(1) try: viewer = FrameViewer(sys.argv[1]) viewer.run() except Exception as e: print(f"错误: {e}") sys.exit(1)
功能说明
- 按需加载:每次仅加载当前显示的帧,切换后主动释放临时资源,避免内存占用过高
- 时间轴定位:拖动底部滑块可直接跳转到对应帧,滑块值与帧索引一一对应
- 时间显示:实时展示当前帧的编号和对应时间(按帧编号/25秒计算,格式为MM:SS.sss)
- 全屏切换:按F11切换全屏模式,自动适配屏幕分辨率
- 快捷键支持:
- 左/右箭头:切换上一帧/下一帧
- ESC:退出程序
- F11:切换全屏
依赖安装
需要安装额外依赖包:
pip install opencv-python pillow
内容的提问来源于stack exchange,提问作者Ξένη Γήινος
相关产品推荐
相关产品推荐

