下面是一个基于PyQt6的智能视频分割器的完整实现方案。这个应用将允许用户加载视频文件,预览视频内容,并智能地分割视频片段。
![图片[1]_基于PyQt6的智能视频分割器实现_知途无界](https://zhituwujie.com/wp-content/uploads/2025/05/d2b5ca33bd20250505093850.png)
功能概述
- 视频文件加载和预览
- 智能场景检测分割
- 手动分割点设置
- 分割片段导出功能
完整代码实现
import sys
import cv2
import numpy as np
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QLabel, QSlider, QFileDialog, QListWidget)
from PyQt6.QtCore import Qt, QTimer, QSize
from PyQt6.QtGui import QImage, QPixmap
class VideoSplitterApp(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("智能视频分割器")
self.setGeometry(100, 100, 1200, 800)
# 初始化变量
self.video_path = None
self.cap = None
self.total_frames = 0
self.fps = 0
self.current_frame = 0
self.scene_changes = []
self.split_points = []
# 创建UI
self.init_ui()
def init_ui(self):
# 主窗口部件
main_widget = QWidget()
self.setCentralWidget(main_widget)
# 主布局
main_layout = QHBoxLayout()
main_widget.setLayout(main_layout)
# 左侧面板 - 视频预览和控制
left_panel = QWidget()
left_layout = QVBoxLayout()
left_panel.setLayout(left_layout)
# 视频显示区域
self.video_label = QLabel("视频预览区域")
self.video_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.video_label.setMinimumSize(800, 450)
self.video_label.setStyleSheet("background-color: black; color: white;")
left_layout.addWidget(self.video_label)
# 控制面板
control_panel = QWidget()
control_layout = QHBoxLayout()
control_panel.setLayout(control_layout)
self.play_button = QPushButton("播放")
self.play_button.clicked.connect(self.toggle_play)
control_layout.addWidget(self.play_button)
self.stop_button = QPushButton("停止")
self.stop_button.clicked.connect(self.stop_video)
control_layout.addWidget(self.stop_button)
# 进度条
self.progress_slider = QSlider(Qt.Orientation.Horizontal)
self.progress_slider.setRange(0, 100)
self.progress_slider.valueChanged.connect(self.slider_moved)
control_layout.addWidget(self.progress_slider)
left_layout.addWidget(control_panel)
# 分割点控制
split_control_panel = QWidget()
split_control_layout = QHBoxLayout()
split_control_panel.setLayout(split_control_layout)
self.add_split_button = QPushButton("添加分割点")
self.add_split_button.clicked.connect(self.add_split_point)
split_control_layout.addWidget(self.add_split_button)
self.detect_scenes_button = QPushButton("智能场景检测")
self.detect_scenes_button.clicked.connect(self.detect_scene_changes)
split_control_layout.addWidget(self.detect_scenes_button)
self.export_button = QPushButton("导出分割片段")
self.export_button.clicked.connect(self.export_segments)
split_control_layout.addWidget(self.export_button)
left_layout.addWidget(split_control_panel)
# 右侧面板 - 分割点列表和状态
right_panel = QWidget()
right_layout = QVBoxLayout()
right_panel.setLayout(right_layout)
# 文件信息
self.file_info_label = QLabel("未加载视频文件")
right_layout.addWidget(self.file_info_label)
# 分割点列表
self.split_list = QListWidget()
self.split_list.itemClicked.connect(self.split_point_selected)
right_layout.addWidget(QLabel("分割点列表:"))
right_layout.addWidget(self.split_list)
# 状态信息
self.status_label = QLabel("就绪")
right_layout.addWidget(self.status_label)
# 添加面板到主布局
main_layout.addWidget(left_panel, 3)
main_layout.addWidget(right_panel, 1)
# 定时器用于视频播放
self.timer = QTimer()
self.timer.timeout.connect(self.update_frame)
# 菜单栏
self.create_menu_bar()
def create_menu_bar(self):
menubar = self.menuBar()
# 文件菜单
file_menu = menubar.addMenu("文件")
open_action = file_menu.addAction("打开视频")
open_action.triggered.connect(self.open_video)
exit_action = file_menu.addAction("退出")
exit_action.triggered.connect(self.close)
# 工具菜单
tool_menu = menubar.addMenu("工具")
detect_action = tool_menu.addAction("智能场景检测")
detect_action.triggered.connect(self.detect_scene_changes)
export_action = tool_menu.addAction("导出所有片段")
export_action.triggered.connect(self.export_segments)
def open_video(self):
file_path, _ = QFileDialog.getOpenFileName(
self, "打开视频文件", "",
"视频文件 (*.mp4 *.avi *.mov *.mkv);;所有文件 (*)"
)
if file_path:
self.video_path = file_path
self.cap = cv2.VideoCapture(file_path)
if not self.cap.isOpened():
self.status_label.setText("无法打开视频文件")
return
self.total_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
self.fps = self.cap.get(cv2.CAP_PROP_FPS)
self.current_frame = 0
self.scene_changes = []
self.split_points = []
self.split_list.clear()
self.file_info_label.setText(
f"文件: {file_path.split('/')[-1]}\n"
f"总帧数: {self.total_frames}\n"
f"FPS: {self.fps:.2f}\n"
f"时长: {self.total_frames/self.fps:.2f}秒"
)
self.status_label.setText("视频加载成功")
self.update_frame()
def update_frame(self):
if self.cap and self.cap.isOpened():
ret, frame = self.cap.read()
if ret:
self.current_frame = int(self.cap.get(cv2.CAP_PROP_POS_FRAMES))
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# 显示当前帧
h, w, ch = frame.shape
bytes_per_line = ch * w
q_img = QImage(frame.data, w, h, bytes_per_line, QImage.Format.Format_RGB888)
pixmap = QPixmap.fromImage(q_img)
self.video_label.setPixmap(pixmap.scaled(
self.video_label.width(),
self.video_label.height(),
Qt.AspectRatioMode.KeepAspectRatio
))
# 更新进度条
progress = int((self.current_frame / self.total_frames) * 100)
self.progress_slider.setValue(progress)
# 检查是否到达分割点
if self.current_frame in self.split_points:
self.status_label.setText(f"到达分割点: 帧 {self.current_frame}")
else:
self.stop_video()
def toggle_play(self):
if not self.cap:
return
if self.timer.isActive():
self.timer.stop()
self.play_button.setText("播放")
else:
self.timer.start(1000 / self.fps)
self.play_button.setText("暂停")
def stop_video(self):
self.timer.stop()
if self.cap:
self.cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
self.current_frame = 0
self.progress_slider.setValue(0)
self.play_button.setText("播放")
self.update_frame()
def slider_moved(self, value):
if self.cap:
frame_pos = int((value / 100) * self.total_frames)
self.cap.set(cv2.CAP_PROP_POS_FRAMES, frame_pos)
self.current_frame = frame_pos
self.update_frame()
def add_split_point(self):
if self.cap and self.cap.isOpened():
frame_pos = int(self.cap.get(cv2.CAP_PROP_POS_FRAMES))
if frame_pos not in self.split_points:
self.split_points.append(frame_pos)
self.split_points.sort()
self.update_split_list()
self.status_label.setText(f"添加分割点: 帧 {frame_pos}")
def update_split_list(self):
self.split_list.clear()
for point in sorted(self.split_points):
time_sec = point / self.fps
self.split_list.addItem(f"帧 {point} - {time_sec:.2f}秒")
def split_point_selected(self, item):
text = item.text()
frame_num = int(text.split()[1])
if self.cap:
self.cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num)
self.update_frame()
def detect_scene_changes(self):
if not self.cap:
return
self.status_label.setText("正在检测场景变化...")
QApplication.processEvents() # 更新UI
# 重置场景变化检测
self.scene_changes = []
# 保存当前帧位置
current_pos = self.current_frame
self.cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
# 场景检测参数
threshold = 30.0 # 场景变化阈值
min_scene_len = int(self.fps * 1) # 最小场景长度(1秒)
prev_frame = None
scene_start = 0
while True:
ret, frame = self.cap.read()
if not ret:
break
current_frame_num = int(self.cap.get(cv2.CAP_PROP_POS_FRAMES))
# 转换为灰度并缩小尺寸以提高性能
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.resize(gray, (0, 0), fx=0.25, fy=0.25)
if prev_frame is not None:
# 计算帧间差异
diff = cv2.absdiff(gray, prev_frame)
diff_mean = np.mean(diff)
# 如果差异超过阈值且满足最小场景长度,则认为是场景变化
if diff_mean > threshold and (current_frame_num - scene_start) >= min_scene_len:
self.scene_changes.append(current_frame_num)
scene_start = current_frame_num
prev_frame = gray
# 恢复原始位置
self.cap.set(cv2.CAP_PROP_POS_FRAMES, current_pos)
# 更新分割点列表
self.split_points = list(set(self.split_points + self.scene_changes))
self.split_points.sort()
self.update_split_list()
self.status_label.setText(f"检测到 {len(self.scene_changes)} 个场景变化")
def export_segments(self):
if not self.cap or not self.split_points:
self.status_label.setText("没有可导出的分割点")
return
# 获取保存目录
save_dir = QFileDialog.getExistingDirectory(self, "选择保存目录")
if not save_dir:
return
self.status_label.setText("正在导出视频片段...")
QApplication.processEvents() # 更新UI
# 确保分割点包含视频开始和结束
all_points = [0] + self.split_points + [self.total_frames]
all_points = sorted(list(set(all_points))) # 去重并排序
# 导出每个片段
for i in range(len(all_points) - 1):
start_frame = all_points[i]
end_frame = all_points[i+1]
# 跳过太短的片段(小于0.5秒)
if (end_frame - start_frame) < (self.fps * 0.5):
continue
# 设置输出视频
output_path = f"{save_dir}/segment_{i+1}_frames_{start_frame}-{end_frame}.mp4"
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(
output_path,
fourcc,
self.fps,
(int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)),
int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
)
# 定位到起始帧
self.cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
# 写入视频帧
while self.cap.get(cv2.CAP_PROP_POS_FRAMES) < end_frame:
ret, frame = self.cap.read()
if ret:
out.write(frame)
else:
break
out.release()
# 恢复原始位置
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.current_frame)
self.status_label.setText(f"成功导出 {len(all_points)-1} 个视频片段")
def closeEvent(self, event):
if self.cap:
self.cap.release()
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = VideoSplitterApp()
window.show()
sys.exit(app.exec())
功能说明
- 视频加载与预览:
- 支持多种视频格式(mp4, avi, mov, mkv)
- 显示视频基本信息(帧数、FPS、时长)
- 提供播放/暂停、停止和进度条控制
- 智能场景检测:
- 基于帧间差异分析自动检测场景变化
- 可配置阈值和最小场景长度
- 检测结果自动添加到分割点列表
- 手动分割:
- 可在任意位置添加手动分割点
- 分割点列表显示帧号和时间位置
- 点击列表项可跳转到对应帧
- 片段导出:
- 可选择保存目录
- 自动跳过过短片段
- 保留原始视频参数(分辨率、FPS等)
扩展建议
- 性能优化:
- 添加多线程处理避免UI卡顿
- 实现视频帧缓存提高播放流畅度
- 高级功能:
- 添加音频波形显示辅助分割
- 实现基于内容的智能分割(人脸检测、运动检测等)
- 支持批量处理多个视频文件
- 用户体验:
- 添加快捷键支持
- 实现撤销/重做功能
- 添加更多视频编辑功能(裁剪、合并等)
这个实现提供了视频分割的基本功能框架,您可以根据需要进一步扩展和完善。
© 版权声明
文中内容均来源于公开资料,受限于信息的时效性和复杂性,可能存在误差或遗漏。我们已尽力确保内容的准确性,但对于因信息变更或错误导致的任何后果,本站不承担任何责任。如需引用本文内容,请注明出处并尊重原作者的版权。
THE END

























暂无评论内容