import cv2
import os
# ----------- 区域选择部分(修复版)------------
roi_coordinates = {'x1':0, 'y1':0, 'x2':0, 'y2':0}
drawing = False
def select_region(event, x, y, flags, param):
global roi_coordinates, drawing
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
roi_coordinates = {'x1': x, 'y1': y, 'x2': x, 'y2': y}
elif event == cv2.EVENT_MOUSEMOVE:
if drawing:
roi_coordinates['x2'] = x
roi_coordinates['y2'] = y
elif event == cv2.EVENT_LBUTTONUP:
drawing = False
roi_coordinates['x2'] = x
roi_coordinates['y2'] = y
def preview_and_select(video_path, frame_num=0):
global roi_coordinates, drawing
roi_coordinates = {'x1':0, 'y1':0, 'x2':0, 'y2':0} # 重置状态
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise ValueError("视频打开失败,请检查路径或文件格式")
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num)
ret, frame = cap.read()
if not ret:
cap.release()
raise ValueError("无法读取指定帧,可能超出视频范围")
cv2.namedWindow('Select ROI (Q=确认 R=重置 A/D=跳帧)')
cv2.setMouseCallback('Select ROI (Q=确认 R=重置 A/D=跳帧)', select_region)
clone = frame.copy()
while True:
current_frame = clone.copy()
if roi_coordinates['x1'] != roi_coordinates['x2'] or roi_coordinates['y1'] != roi_coordinates['y2']:
x1 = min(roi_coordinates['x1'], roi_coordinates['x2'])
y1 = min(roi_coordinates['y1'], roi_coordinates['y2'])
x2 = max(roi_coordinates['x1'], roi_coordinates['x2'])
y2 = max(roi_coordinates['y1'], roi_coordinates['y2'])
cv2.rectangle(current_frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(current_frame, f"X: {x1}-{x2}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,255,0), 2)
cv2.putText(current_frame, f"Y: {y1}-{y2}", (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,255,0), 2)
cv2.imshow('Select ROI (Q=确认 R=重置 A/D=跳帧)', current_frame)
key = cv2.waitKey(1) & 0xFF
if key == ord('r'):
clone = frame.copy()
roi_coordinates = {'x1':0, 'y1':0, 'x2':0, 'y2':0}
elif key == ord('q'):
break
elif key == ord('d'):
frame_num += 10
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num)
ret, frame = cap.read()
if ret:
clone = frame.copy()
roi_coordinates = {'x1':0, 'y1':0, 'x2':0, 'y2':0}
elif key == ord('a'):
frame_num = max(0, frame_num-10)
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num)
ret, frame = cap.read()
if ret:
clone = frame.copy()
roi_coordinates = {'x1':0, 'y1':0, 'x2':0, 'y2':0}
cv2.destroyAllWindows()
cap.release()
# 计算有效区域
x = min(roi_coordinates['x1'], roi_coordinates['x2'])
y = min(roi_coordinates['y1'], roi_coordinates['y2'])
w = abs(roi_coordinates['x1'] - roi_coordinates['x2'])
h = abs(roi_coordinates['y1'] - roi_coordinates['y2'])
return (x, y, w, h) if w > 10 and h > 10 else None # 最小尺寸校验
# ----------- 视频拆帧部分(增强版)------------
def crop_and_save_frames(video_path, output_dir, region, interval=1):
"""
增强功能:
- 自动路径创建
- 进度显示
- 区域有效性验证
- 错误处理
"""
if not os.path.exists(video_path):
raise FileNotFoundError(f"视频文件不存在:{video_path}")
os.makedirs(output_dir, exist_ok=True)
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise IOError("无法打开视频文件,可能是不支持的格式")
# 验证区域有效性
total_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
total_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
x, y, w, h = region
if (x + w > total_width) or (y + h > total_height):
cap.release()
raise ValueError(f"选择区域超出视频尺寸(视频分辨率:{total_width}x{total_height})")
frame_count = 0
saved_count = 0
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
try:
while True:
ret, frame = cap.read()
if not ret:
break
if frame_count % interval == 0:
roi = frame[y:y+h, x:x+w]
if roi.size == 0: # 空图像检查
continue
output_path = os.path.join(output_dir, f"frame_{saved_count:05d}.jpg")
cv2.imwrite(output_path, roi, [cv2.IMWRITE_JPEG_QUALITY, 95])
saved_count += 1
frame_count += 1
if frame_count % 10 == 0:
print(f"\r处理进度: {frame_count}/{total_frames} ({frame_count/total_frames:.1%})", end='')
print(f"\n完成!共保存 {saved_count} 张图片到:{os.path.abspath(output_dir)}")
finally:
cap.release()
# ----------- 完整使用示例 ------------
if __name__ == "__main__":
# 配置参数
video_path = r"E:\photo\video\1.mp4" # 替换为实际视频路径
output_folder = r"E:\photo\video\1" # 输出目录
preview_frame = 50 # 预览起始帧号
frame_interval = 30 # 抽帧间隔(每30帧抽1帧)
# 步骤1:选择区域
selected_region = preview_and_select(video_path, preview_frame)
if not selected_region:
print("未选择有效区域,程序终止")
exit()
print(f"已选择区域:{selected_region}")
# 步骤2:执行拆帧
try:
crop_and_save_frames(
video_path=video_path,
output_dir=output_folder,
region=selected_region,
interval=frame_interval
)
except Exception as e:
print(f"处理出错:{str(e)}")
使用流程:
-
修改
video_path
为实际视频路径 -
运行后会弹出预览窗口:
-
鼠标拖拽选择区域
-
按Q确认选择
-
按R重置选择
-
按A/D前后跳转10帧
-
-
选择完成后自动执行拆帧操作