|
|
import cv2
|
|
|
import numpy as np
|
|
|
import os
|
|
|
import sys
|
|
|
|
|
|
from ArithControl import Arith_EOController as Arith
|
|
|
|
|
|
import os
|
|
|
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'
|
|
|
|
|
|
from H30T_Reader import H30T_Reader
|
|
|
|
|
|
class VideoPlayer:
|
|
|
def __init__(self, video_path):
|
|
|
self.video_path = video_path
|
|
|
self.cap = cv2.VideoCapture(video_path)
|
|
|
self.h30t_reader = H30T_Reader()
|
|
|
self.h30t_reader.open(video_path)
|
|
|
if not self.cap.isOpened():
|
|
|
print(f"Error: Could not open video file {video_path}")
|
|
|
sys.exit(1)
|
|
|
|
|
|
# 获取视频属性
|
|
|
self.total_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
|
self.fps = self.cap.get(cv2.CAP_PROP_FPS)
|
|
|
self.frame_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
|
|
self.frame_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
|
|
|
|
|
# 播放状态
|
|
|
self.current_frame = 0
|
|
|
self.is_playing = True
|
|
|
self.is_seeking = False
|
|
|
self.should_exit = False
|
|
|
|
|
|
# 鼠标绘制状态
|
|
|
self.is_drawing = False
|
|
|
self.start_point = None
|
|
|
self.end_point = None
|
|
|
self.tracking_boxes = [] # 存储所有绘制的矩形框
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 窗口设置
|
|
|
self.window_name = "Video Player - Press SPACE to pause/play, LEFT/RIGHT for frame control"
|
|
|
cv2.namedWindow(self.window_name, cv2.WINDOW_NORMAL)
|
|
|
cv2.resizeWindow(self.window_name, 800, 600)
|
|
|
|
|
|
# 创建highgui进度条
|
|
|
cv2.createTrackbar('Progress', self.window_name, 0, self.total_frames - 1, self.on_trackbar)
|
|
|
|
|
|
# 设置键盘回调
|
|
|
self.setup_keyboard_controls()
|
|
|
|
|
|
# 设置鼠标回调
|
|
|
cv2.setMouseCallback(self.window_name, self.mouse_callback)
|
|
|
|
|
|
|
|
|
self.ArithModule = Arith.EOController()
|
|
|
|
|
|
self.arithout = []
|
|
|
|
|
|
def on_trackbar(self, pos):
|
|
|
"""highgui进度条回调函数"""
|
|
|
if not self.is_seeking: # 避免循环更新
|
|
|
self.is_seeking = True
|
|
|
self.current_frame = pos
|
|
|
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.current_frame)
|
|
|
self.is_seeking = False
|
|
|
|
|
|
def mouse_callback(self, event, x, y, flags, param):
|
|
|
"""鼠标回调函数 - 处理点击和拖动事件"""
|
|
|
# 检查鼠标是否在视频区域内(排除信息栏)
|
|
|
if y >= self.frame_height: # 鼠标在信息栏区域,忽略
|
|
|
return
|
|
|
|
|
|
# 边界检查
|
|
|
x = max(0, min(x, self.frame_width - 1))
|
|
|
y = max(0, min(y, self.frame_height - 1))
|
|
|
|
|
|
if event == cv2.EVENT_LBUTTONDOWN:
|
|
|
# 鼠标按下
|
|
|
self.is_drawing = True
|
|
|
self.start_point = (x, y)
|
|
|
self.end_point = (x, y)
|
|
|
print(f"鼠标按下: ({x}, {y})")
|
|
|
|
|
|
elif event == cv2.EVENT_MOUSEMOVE:
|
|
|
if self.is_drawing:
|
|
|
# 计算拖动距离
|
|
|
if self.start_point:
|
|
|
dx = x - self.start_point[0]
|
|
|
dy = y - self.start_point[1]
|
|
|
self.drag_distance = (dx * dx + dy * dy) ** 0.5
|
|
|
|
|
|
# 更新矩形终点
|
|
|
self.end_point = (x, y)
|
|
|
|
|
|
|
|
|
elif event == cv2.EVENT_LBUTTONUP:
|
|
|
if self.is_drawing:
|
|
|
self.end_point = (x, y)
|
|
|
# 根据框尺寸判断点还是框
|
|
|
w = self.end_point[0] - self.start_point[0]
|
|
|
h = self.end_point[1] - self.start_point[1]
|
|
|
if w > 5 and h > 5:
|
|
|
self.on_mouse_box([self.start_point[0], self.start_point[1], self.end_point[0] - self.start_point[0], self.end_point[1] - self.start_point[1]])
|
|
|
else:
|
|
|
self.on_mouse_click(self.start_point)
|
|
|
# 清除状态
|
|
|
self.is_drawing = False
|
|
|
self.start_point = None
|
|
|
self.end_point = None
|
|
|
|
|
|
|
|
|
|
|
|
def on_mouse_click(self, point):
|
|
|
"""鼠标点击事件处理"""
|
|
|
print(f"鼠标点击: ({point})")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def on_mouse_box(self, box):
|
|
|
"""鼠标框选事件处理"""
|
|
|
print(f"框选: ({box})")
|
|
|
# 调用自定义处理器
|
|
|
|
|
|
|
|
|
|
|
|
def setup_keyboard_controls(self):
|
|
|
"""设置键盘控制映射"""
|
|
|
# 默认键盘映射
|
|
|
self.key_handlers = {
|
|
|
27: self.handle_escape, # ESC
|
|
|
32: self.handle_space, # SPACE
|
|
|
81: self.handle_left_arrow, # LEFT ARROW
|
|
|
83: self.handle_right_arrow, # RIGHT ARROW
|
|
|
}
|
|
|
|
|
|
# 支持自定义键盘映射
|
|
|
self.custom_key_handlers = {}
|
|
|
|
|
|
# 支持自定义鼠标事件处理器
|
|
|
self.mouse_click_handler = None
|
|
|
self.mouse_drag_start_handler = None
|
|
|
self.mouse_drag_end_handler = None
|
|
|
|
|
|
def add_key_handler(self, key_code, handler_func):
|
|
|
"""添加自定义键盘处理器"""
|
|
|
self.custom_key_handlers[key_code] = handler_func
|
|
|
|
|
|
def remove_key_handler(self, key_code):
|
|
|
"""移除键盘处理器"""
|
|
|
if key_code in self.key_handlers:
|
|
|
del self.key_handlers[key_code]
|
|
|
if key_code in self.custom_key_handlers:
|
|
|
del self.custom_key_handlers[key_code]
|
|
|
|
|
|
def set_mouse_click_handler(self, handler_func):
|
|
|
"""设置鼠标点击事件处理器"""
|
|
|
self.mouse_click_handler = handler_func
|
|
|
|
|
|
def set_mouse_drag_start_handler(self, handler_func):
|
|
|
"""设置鼠标拖动开始事件处理器"""
|
|
|
self.mouse_drag_start_handler = handler_func
|
|
|
|
|
|
def set_mouse_drag_end_handler(self, handler_func):
|
|
|
"""设置鼠标拖动结束事件处理器"""
|
|
|
self.mouse_drag_end_handler = handler_func
|
|
|
|
|
|
def handle_keyboard_input(self, key):
|
|
|
"""处理键盘输入的回调函数"""
|
|
|
# 先检查自定义处理器
|
|
|
if key in self.custom_key_handlers:
|
|
|
self.custom_key_handlers[key]()
|
|
|
# 再检查默认处理器
|
|
|
elif key in self.key_handlers:
|
|
|
self.key_handlers[key]()
|
|
|
|
|
|
def handle_escape(self):
|
|
|
"""处理ESC键 - 退出程序"""
|
|
|
self.should_exit = True
|
|
|
print("Exiting...")
|
|
|
|
|
|
def handle_space(self):
|
|
|
"""处理空格键 - 播放/暂停切换"""
|
|
|
self.is_playing = not self.is_playing
|
|
|
status = "PLAYING" if self.is_playing else "PAUSED"
|
|
|
print(f"Play/Pause toggled: {status}")
|
|
|
|
|
|
def handle_left_arrow(self):
|
|
|
"""处理左方向键 - 上一帧"""
|
|
|
self.current_frame = max(self.current_frame - 1, 0)
|
|
|
print(f"Previous frame: {self.current_frame}")
|
|
|
|
|
|
def handle_right_arrow(self):
|
|
|
"""处理右方向键 - 下一帧"""
|
|
|
self.current_frame = min(self.current_frame + 1, self.total_frames - 1)
|
|
|
print(f"Next frame: {self.current_frame}")
|
|
|
|
|
|
|
|
|
|
|
|
def draw_info_overlay(self, frame):
|
|
|
"""在视频帧上绘制信息覆盖层"""
|
|
|
# 创建信息显示区域
|
|
|
info_height = 60
|
|
|
info_bar = np.zeros((info_height, self.frame_width, 3), dtype=np.uint8)
|
|
|
info_bar[:] = (0, 0, 0) # 黑色背景,半透明效果
|
|
|
|
|
|
# 计算时间信息
|
|
|
current_time = self.current_frame / self.fps if self.fps > 0 else 0
|
|
|
total_time = self.total_frames / self.fps if self.fps > 0 else 0
|
|
|
|
|
|
# 显示信息文本
|
|
|
time_text = f"Time: {current_time:.1f}s / {total_time:.1f}s"
|
|
|
frame_text = f"Frame: {self.current_frame} / {self.total_frames}"
|
|
|
status_text = "PLAYING" if self.is_playing else "PAUSED"
|
|
|
tracking_text = f"Tracking Boxes: {len(self.tracking_boxes)}"
|
|
|
|
|
|
# 添加文本到信息栏
|
|
|
cv2.putText(info_bar, time_text, (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
|
|
|
cv2.putText(info_bar, frame_text, (10, 50), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
|
|
|
cv2.putText(info_bar, status_text, (self.frame_width - 150, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.7,
|
|
|
(0, 255, 0) if self.is_playing else (0, 0, 255), 2)
|
|
|
cv2.putText(info_bar, tracking_text, (self.frame_width - 200, 50), cv2.FONT_HERSHEY_SIMPLEX, 0.7,
|
|
|
(255, 255, 0), 2) # 黄色显示跟踪框数量
|
|
|
|
|
|
# 将信息栏添加到帧的底部
|
|
|
combined_frame = np.vstack([frame, info_bar])
|
|
|
return combined_frame
|
|
|
|
|
|
def draw_tracking_boxes(self, frame):
|
|
|
"""在帧上绘制跟踪框(类似selectROI的显示效果)"""
|
|
|
# 绘制当前帧的跟踪框
|
|
|
for roi in self.tracking_boxes:
|
|
|
if roi['frame'] == self.current_frame:
|
|
|
x1, y1, x2, y2 = roi['bbox_corners']
|
|
|
# 绘制矩形框
|
|
|
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
|
|
|
# 绘制ID标签
|
|
|
cv2.putText(frame, f"ROI {roi['id']}", (x1, y1-10),
|
|
|
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
|
|
|
# 绘制中心点
|
|
|
center = roi['center']
|
|
|
cv2.circle(frame, center, 3, (0, 255, 0), -1)
|
|
|
# 绘制尺寸信息
|
|
|
width, height = roi['bbox'][2], roi['bbox'][3]
|
|
|
cv2.putText(frame, f"{width}x{height}", (x1, y2+20),
|
|
|
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
|
|
|
|
|
|
# 绘制正在绘制的矩形(实时预览)
|
|
|
if self.is_drawing and self.start_point and self.end_point:
|
|
|
x1, y1 = min(self.start_point[0], self.end_point[0]), min(self.start_point[1], self.end_point[1])
|
|
|
x2, y2 = max(self.start_point[0], self.end_point[0]), max(self.start_point[1], self.end_point[1])
|
|
|
|
|
|
# 绘制半透明矩形
|
|
|
overlay = frame.copy()
|
|
|
cv2.rectangle(overlay, (x1, y1), (x2, y2), (255, 0, 0), -1)
|
|
|
cv2.addWeighted(overlay, 0.3, frame, 0.7, 0, frame)
|
|
|
|
|
|
# 绘制边框
|
|
|
cv2.rectangle(frame, (x1, y1), (x2, y2), (255, 0, 0), 2)
|
|
|
|
|
|
# 显示尺寸信息
|
|
|
width, height = x2 - x1, y2 - y1
|
|
|
cv2.putText(frame, f"ROI: {width} x {height}", (10, 30),
|
|
|
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 0), 2)
|
|
|
|
|
|
return frame
|
|
|
|
|
|
def play(self):
|
|
|
while not self.should_exit:
|
|
|
if self.is_playing:
|
|
|
ret, frame = self.cap.read()
|
|
|
subtitle = self.h30t_reader.read()
|
|
|
if not ret:
|
|
|
# 视频结束,重新开始
|
|
|
self.current_frame = 0
|
|
|
self.cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
|
|
|
continue
|
|
|
|
|
|
self.current_frame = int(self.cap.get(cv2.CAP_PROP_POS_FRAMES))
|
|
|
|
|
|
# 获取当前帧
|
|
|
# self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.current_frame)
|
|
|
ret, frame = self.cap.read()
|
|
|
|
|
|
if not ret:
|
|
|
break
|
|
|
|
|
|
# 运行算法
|
|
|
self.arithout = self.ArithModule.run(frame)
|
|
|
|
|
|
# 绘制算法结果
|
|
|
frame = self.draw_arith_result(frame)
|
|
|
|
|
|
# 绘制信息覆盖层
|
|
|
display_frame = self.draw_info_overlay(frame)
|
|
|
|
|
|
# 显示帧
|
|
|
cv2.imshow(self.window_name, display_frame)
|
|
|
|
|
|
# 更新进度条位置(避免循环更新)
|
|
|
if not self.is_seeking:
|
|
|
cv2.setTrackbarPos('Progress', self.window_name, self.current_frame)
|
|
|
|
|
|
# 处理键盘输入
|
|
|
key = cv2.waitKey(int(1000/self.fps) if self.is_playing else 0) & 0xFF
|
|
|
self.handle_keyboard_input(key)
|
|
|
|
|
|
self.cleanup()
|
|
|
|
|
|
def draw_arith_result(self,frame):
|
|
|
for box in self.arithout:
|
|
|
x1, y1, x2, y2, cls, track_id = box
|
|
|
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
|
|
|
cv2.putText(frame, f"ID: {track_id}", (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
|
|
|
return frame
|
|
|
|
|
|
|
|
|
def cleanup(self):
|
|
|
self.cap.release()
|
|
|
cv2.destroyAllWindows()
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
|
video_path = 'DJI_20250418150210_0006_S.MP4'
|
|
|
|
|
|
# 检查文件是否存在
|
|
|
if not os.path.exists(video_path):
|
|
|
print(f"Error: Video file {video_path} does not exist")
|
|
|
sys.exit(1)
|
|
|
|
|
|
# 创建并运行视频播放器
|
|
|
player = VideoPlayer(video_path)
|
|
|
player.play()
|
|
|
|
|
|
#
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
main()
|