2025.3.4

今天依旧继续完善此程序,完成所有功能的后端代码逻辑,并且进行测试查看是否有bug存在,最后代码如下:

from PySide6 import QtWidgets, QtCore, QtGui
import cv2, os, time, random
from threading import Thread, Lock
from ultralytics import YOLO
import torch
import json
import shutil
from docx import Document
from docx.shared import Inches
from datetime import datetime

os.environ['YOLO_VERBOSE'] = 'False'


class MainWindow(QtWidgets.QMainWindow):
    update_signal = QtCore.Signal(QtGui.QImage)

    def __init__(self):
        super().__init__()
        self.setup_ui()
        self.setup_video()

        self.analysis_results = []
        self.current_detections = []
        self.current_frame = None
        self.lock = Lock()
        self.video_paused = False
        self.is_video_file = False
        self.scale_x = 1.0
        self.scale_y = 1.0
        self.results_dir = "results"
        os.makedirs(self.results_dir, exist_ok=True)

    def setup_ui(self):
        self.setWindowTitle("智能分析系统")
        self.resize(1200, 800)
        self.setStyleSheet("background-color: #F0F0F0;")

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QtWidgets.QVBoxLayout(central_widget)
        main_layout.setContentsMargins(10, 10, 10, 10)
        main_layout.setSpacing(10)

        # 顶部切换按钮
        btn_style = """
        QPushButton {
            background-color: #87CEFA;
            color: black;
            border: none;
            padding: 8px 16px;
            border-radius: 4px;
            min-width: 120px;
            min-height: 35px;
            font-size: 14px;
        }
        QPushButton:disabled {
            background-color: #D3D3D3;
            color: gray;
        }
        """

        self.analysis_btn = QtWidgets.QPushButton("数据分析")
        self.status_btn = QtWidgets.QPushButton("今日情况")
        self.history_btn = QtWidgets.QPushButton("历史记录")
        self.analysis_btn.setStyleSheet(btn_style)
        self.status_btn.setStyleSheet(btn_style.replace("#87CEFA", "#D3D3D3").replace("black", "gray"))
        self.history_btn.setStyleSheet(btn_style.replace("#87CEFA", "#D3D3D3").replace("black", "gray"))

        top_layout = QtWidgets.QHBoxLayout()
        top_layout.addWidget(self.analysis_btn)
        top_layout.addWidget(self.status_btn)
        top_layout.addWidget(self.history_btn)
        top_layout.setSpacing(10)
        main_layout.addLayout(top_layout)

        # 页面堆栈
        self.stacked_pages = QtWidgets.QStackedWidget()
        main_layout.addWidget(self.stacked_pages)

        self.setup_analysis_page()
        self.setup_status_page()
        self.setup_history_page()

        self.analysis_btn.clicked.connect(self.switch_to_analysis)
        self.status_btn.clicked.connect(self.switch_to_status)
        self.history_btn.clicked.connect(self.switch_to_history)
        self.switch_to_analysis()

    def switch_to_analysis(self):
        self.stacked_pages.setCurrentIndex(0)
        self.analysis_btn.setDisabled(True)
        self.status_btn.setDisabled(False)
        self.history_btn.setDisabled(False)
        self.analysis_btn.setStyleSheet("background-color: #87CEFA; color: black;")
        self.status_btn.setStyleSheet("background-color: #D3D3D3; color: gray;")
        self.history_btn.setStyleSheet("background-color: #D3D3D3; color: gray;")

    def switch_to_status(self):
        self.stacked_pages.setCurrentIndex(1)
        self.status_btn.setDisabled(True)
        self.analysis_btn.setDisabled(False)
        self.history_btn.setDisabled(False)
        self.status_btn.setStyleSheet("background-color: #87CEFA; color: black;")
        self.analysis_btn.setStyleSheet("background-color: #D3D3D3; color: gray;")
        self.history_btn.setStyleSheet("background-color: #D3D3D3; color: gray;")

    def switch_to_history(self):
        self.stacked_pages.setCurrentIndex(2)
        self.history_btn.setDisabled(True)
        self.analysis_btn.setDisabled(False)
        self.status_btn.setDisabled(False)
        self.history_btn.setStyleSheet("background-color: #87CEFA; color: black;")
        self.analysis_btn.setStyleSheet("background-color: #D3D3D3; color: gray;")
        self.status_btn.setStyleSheet("background-color: #D3D3D3; color: gray;")
        self.load_history_records()

    def setup_analysis_page(self):
        page = QtWidgets.QWidget()
        layout = QtWidgets.QHBoxLayout(page)
        layout.setContentsMargins(5, 5, 5, 5)
        layout.setSpacing(15)

        left_panel = QtWidgets.QWidget()
        left_layout = QtWidgets.QVBoxLayout(left_panel)
        left_layout.setSpacing(10)

        self.video_label = QtWidgets.QLabel()
        self.video_label.setMinimumSize(640, 480)
        self.video_label.setStyleSheet("""
            border: 2px solid #666;
            background-color: #000000;
            border-radius: 5px;
        """)
        self.video_label.setAlignment(QtCore.Qt.AlignCenter)
        self.cam_btn = QtWidgets.QPushButton("暂停/继续")
        left_layout.addWidget(self.video_label)
        left_layout.addWidget(self.cam_btn)

        right_panel = QtWidgets.QWidget()
        right_layout = QtWidgets.QVBoxLayout(right_panel)
        right_layout.setSpacing(15)

        btn_style = """
        QPushButton {
            background-color: #4CAF50;
            color: white;
            border: none;
            padding: 8px 16px;
            border-radius: 4px;
            min-width: 150px;
            min-height: 35px;
            font-size: 14px;
        }
        QPushButton:hover { background-color: #45a049; }
        QPushButton:pressed { background-color: #3d8b40; }
        """

        self.start_cam_btn = QtWidgets.QPushButton("开启摄像头")
        self.start_cam_btn.setStyleSheet(btn_style)
        self.import_image_btn = QtWidgets.QPushButton("导入图片")
        self.import_image_btn.setStyleSheet(btn_style)
        self.import_btn = QtWidgets.QPushButton("导入视频")
        self.import_btn.setStyleSheet(btn_style)
        right_layout.addWidget(self.start_cam_btn)
        right_layout.addWidget(self.import_image_btn)
        right_layout.addWidget(self.import_btn)

        info_group = QtWidgets.QGroupBox("检测信息")
        info_group.setStyleSheet("""
            QGroupBox {
                font-size: 14px;
                border: 1px solid #666;
                border-radius: 5px;
                margin-top: 10px;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                left: 10px;
                padding: 0 3px;
            }
        """)
        info_layout = QtWidgets.QFormLayout(info_group)
        info_layout.setVerticalSpacing(10)
        info_layout.setHorizontalSpacing(15)

        self.confidence_label = QtWidgets.QLabel("0%")
        self.target_count_label = QtWidgets.QLabel("0")
        self.x_label = QtWidgets.QLabel("0")
        self.y_label = QtWidgets.QLabel("0")
        self.temperature_label = QtWidgets.QLabel("0℃")
        self.humidity_label = QtWidgets.QLabel("0%")
        self.target_select = QtWidgets.QComboBox()

        label_style = "font-size: 14px; min-width: 80px;"
        for label in [self.confidence_label, self.target_count_label,
                      self.x_label, self.y_label,
                      self.temperature_label, self.humidity_label]:
            label.setStyleSheet(label_style)

        info_layout.addRow("置信度:", self.confidence_label)
        info_layout.addRow("目标数量:", self.target_count_label)
        info_layout.addRow("X坐标:", self.x_label)
        info_layout.addRow("Y坐标:", self.y_label)
        info_layout.addRow("温度:", self.temperature_label)
        info_layout.addRow("湿度:", self.humidity_label)
        info_layout.addRow("目标选择:", self.target_select)

        self.save_btn = QtWidgets.QPushButton("保存结果")
        self.save_btn.setStyleSheet(btn_style)
        right_layout.addWidget(info_group)
        right_layout.addWidget(self.save_btn)
        right_layout.addStretch()

        layout.addWidget(left_panel)
        layout.addWidget(right_panel)
        self.stacked_pages.addWidget(page)

    def setup_status_page(self):
        page = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout(page)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)

        grid_layout = QtWidgets.QGridLayout()
        grid_layout.setContentsMargins(0, 0, 0, 0)
        grid_layout.setSpacing(0)

        self.status_labels = []
        self.import_buttons = []

        # 定义每张图片的对齐方式
        alignments = [
            QtCore.Qt.AlignRight | QtCore.Qt.AlignBottom,  # 图片1:右下
            QtCore.Qt.AlignHCenter | QtCore.Qt.AlignBottom,  # 图片2:下中
            QtCore.Qt.AlignLeft | QtCore.Qt.AlignBottom,  # 图片3:左下
            QtCore.Qt.AlignRight | QtCore.Qt.AlignTop,  # 图片4:右上
            QtCore.Qt.AlignHCenter | QtCore.Qt.AlignTop,  # 图片5:上中
            QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop,  # 图片6:左上
        ]

        # 第一行:导入按钮 1-3
        for i in range(3):
            btn = QtWidgets.QPushButton(f"导入照片 {i+1}")
            btn.setFixedSize(80, 30)
            btn.setStyleSheet("font-size: 12px;")
            btn.clicked.connect(lambda _, idx=i: self.import_status_image(idx))
            self.import_buttons.append(btn)
            grid_layout.addWidget(btn, 0, i)

        # 第二行:图片框 1-3
        for i in range(3):
            label = QtWidgets.QLabel()
            label.setFixedSize(390, 300)
            label.setStyleSheet("""
                border: 1px solid #D3D3D3;
                background-color: white;
            """)
            label.setAlignment(alignments[i])  # 设置对齐方式
            self.status_labels.append(label)
            grid_layout.addWidget(label, 1, i)

        # 第三行:图片框4-6
        for i in range(3, 6):
            label = QtWidgets.QLabel()
            label.setFixedSize(390, 300)
            label.setStyleSheet("""
                border: 1px solid #D3D3D3;
                background-color: white;
            """)
            label.setAlignment(alignments[i])  # 设置对齐方式
            self.status_labels.append(label)
            grid_layout.addWidget(label, 2, i-3)

        # 第四行:导入按钮4-6
        for i in range(3):
            btn = QtWidgets.QPushButton(f"导入照片 {i+4}")
            btn.setFixedSize(80, 30)
            btn.setStyleSheet("font-size: 12px;")
            btn.clicked.connect(lambda _, idx=i+3: self.import_status_image(idx))
            self.import_buttons.append(btn)
            grid_layout.addWidget(btn, 3, i)

        layout.addLayout(grid_layout)
        self.stacked_pages.addWidget(page)

    def import_status_image(self, index):
        # 定义每张图片的对齐方式
        alignments = [
            QtCore.Qt.AlignRight | QtCore.Qt.AlignBottom,  # 图片1:右下
            QtCore.Qt.AlignHCenter | QtCore.Qt.AlignBottom,  # 图片2:下中
            QtCore.Qt.AlignLeft | QtCore.Qt.AlignBottom,  # 图片3:左下
            QtCore.Qt.AlignRight | QtCore.Qt.AlignTop,  # 图片4:右上
            QtCore.Qt.AlignHCenter | QtCore.Qt.AlignTop,  # 图片5:上中
            QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop,  # 图片6:左上
        ]
        path, _ = QtWidgets.QFileDialog.getOpenFileName(
            self, "选择图片文件", "", "图片文件 (*.jpg *.png *.bmp)"
        )
        if path:
            pixmap = QtGui.QPixmap(path)
            scaled = pixmap.scaled(390, 300, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
            self.status_labels[index].setPixmap(scaled)
            self.status_labels[index].setAlignment(alignments[index])  # 设置对齐方式

    def import_status_image(self, index):
        path, _ = QtWidgets.QFileDialog.getOpenFileName(
            self, "选择图片文件", "", "图片文件 (*.jpg *.png *.bmp)"
        )
        if path:
            pixmap = QtGui.QPixmap(path)
            scaled = pixmap.scaled(390, 300, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
            self.status_labels[index].setPixmap(scaled)
            self.status_labels[index].setAlignment(QtCore.Qt.AlignCenter)

    def setup_history_page(self):
        page = QtWidgets.QWidget()
        main_layout = QtWidgets.QVBoxLayout(page)

        container = QtWidgets.QWidget()
        container_layout = QtWidgets.QHBoxLayout(container)
        container_layout.setContentsMargins(0, 0, 0, 0)

        self.history_table = QtWidgets.QTableWidget()
        self.history_table.setColumnCount(8)
        self.history_table.setHorizontalHeaderLabels(
            ["保存时间", "原图", "目标数量", "平均置信度", "温度", "湿度", "检测结果", "操作"])

        # 表格样式调整
        self.history_table.setStyleSheet("""
            QTableView {
                gridline-color: #666;
                border: 1px solid #666;
            }
            QHeaderView::section {
                background-color: #87CEFA;
                padding: 4px;
                border: 1px solid #666;
            }
        """)

        self.history_table.setFixedSize(805, 700)  # 设置为 800px 宽度,600px 高度

        # 列宽设置
        widths = [180, 80, 80, 100, 80, 80, 100, 80]
        for idx, w in enumerate(widths):
            self.history_table.setColumnWidth(idx, w)

        container_layout.addStretch()
        container_layout.addWidget(self.history_table)
        container_layout.addStretch()

        main_layout.addWidget(container)
        self.stacked_pages.addWidget(page)

    def setup_video(self):
        self.model = YOLO('runs/detect/train2/weights/best.pt').to(torch.device("cuda:0"))
        self.frame_queue = []
        self.cap = None
        self.video_timer = QtCore.QTimer()
        self.video_timer.timeout.connect(self.update_frame)
        self.update_signal.connect(self.update_image)

        self.cam_btn.setStyleSheet("""
            QPushButton {
                background-color: #4CAF50;
                color: white;
                min-width: 150px;
                min-height: 35px;
            }
        """)
        self.start_cam_btn.clicked.connect(self.start_camera)
        self.cam_btn.clicked.connect(self.toggle_pause)
        self.save_btn.clicked.connect(self.save_results)
        self.import_btn.clicked.connect(self.import_video)
        self.import_image_btn.clicked.connect(self.import_image)
        self.target_select.currentTextChanged.connect(self.update_selection)

        Thread(target=self.process_frames, daemon=True).start()

    def start_camera(self):
        """开启或关闭摄像头"""
        if self.start_cam_btn.text() == "开启摄像头":
            if self.cap is None or not self.cap.isOpened():
                self.cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
                if self.cap.isOpened():
                    self.video_timer.start(30)
                    self.cam_btn.setText("暂停")
                    self.start_cam_btn.setText("关闭摄像头")
                    self.video_paused = False
                    # self.start_cam_btn.setDisabled(True)
        elif self.start_cam_btn.text() == "关闭摄像头":
            self.close_camera()

    def close_camera(self):
        """关闭摄像头"""
        if self.cap:
            self.cap.release()
            self.cap = None
            self.video_timer.stop()
            self.start_cam_btn.setText("开启摄像头")
            self.cam_btn.setText("开启摄像头")
            self.start_cam_btn.setDisabled(False)

    def toggle_pause(self):
        """暂停或继续"""
        if self.cam_btn.text() == "暂停":
            self.video_timer.stop()
            self.cam_btn.setText("继续")
            self.video_paused = True
        elif self.cam_btn.text() == "继续":
            self.video_timer.start(31)
            self.cam_btn.setText("暂停")
            self.video_paused = False

    def import_video(self):
        if self.cap:
            self.close_camera()
        path, _ = QtWidgets.QFileDialog.getOpenFileName(
            self, "选择视频文件", "", "视频文件 (*.mp4 *.avi) (.mp4 *.avi)"
        )
        if path:
            self.cap = cv2.VideoCapture(path)
            if self.cap.isOpened():
                self.video_timer.start(31)
                self.cam_btn.setText("暂停")
                self.start_cam_btn.setDisabled(True)

    def update_image(self, q_img):
        """更新视频标签显示的图像"""
        self.video_label.setPixmap(QtGui.QPixmap.fromImage(q_img))

    def import_image(self):
        if self.cap:  # 如果摄像头正在运行则关闭
            self.close_camera()
        path, _ = QtWidgets.QFileDialog.getOpenFileName(
            self, "选择图片文件", "", "图片文件 (*.jpg *.png *.bmp)"
        )
        if path:
            # !!! 重置检测结果和暂停状态 !!!
            self.current_detections = []  # 若没有这一步,在程序运行初始时导入图片可能会导致程序崩溃

            self.video_paused = True

            # 停止可能存在的视频定时器
            if self.video_timer.isActive():
                self.video_timer.stop()
                self.cam_btn.setText("继续")

            image = cv2.imread(path)
            if image is None:
                QtWidgets.QMessageBox.warning(self, "错误", "无法读取图片文件")
                return
            original_frame = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)  # 保存原始帧

            # 调整图片显示大小
            label_width = self.video_label.width()
            label_height = self.video_label.height()
            original_h, original_w = original_frame.shape[:2]
            aspect_ratio = original_w / original_h

            # 计算缩放后的尺寸
            if aspect_ratio > 1:
                new_w = label_width
                new_h = int(new_w / aspect_ratio)
            else:
                new_h = label_height
                new_w = int(new_h * aspect_ratio)

            self.scale_x = new_w / original_w
            self.scale_y = new_h / original_h

            scaled_frame = cv2.resize(original_frame, (new_w, new_h))
            self.current_frame = scaled_frame.copy()

            results = self.model.predict(path)[0]  # 直接从本地路径读取图片,否则会因为cv2读取的颜色空间转换问题导致推理结果出现问题。
            self.current_detections = results

            self.update_detection_info(results, new_w, new_h)

            # 更新下拉框
            self.target_select.clear()
            self.target_select.addItem("全部")
            for i in range(1, len(results) + 1):
                self.target_select.addItem(str(i))

            # 显示检测结果
            annotated = results.plot()
            annotated = cv2.resize(annotated, (new_w, new_h))
            annotated = cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB)  # 确保是 RGB 格式

            q_img = QtGui.QImage(
                annotated.data.tobytes(),  # 转换为 bytes
                new_w,
                new_h,
                new_w * 3,  # bytes_per_line,RGB 每行 3 个像素
                QtGui.QImage.Format_RGB888  # 指定像素格式为 RGB888
            )
            self.video_label.setPixmap(QtGui.QPixmap.fromImage(q_img))

            # !!! 强制更新界面 !!!
            QtWidgets.QApplication.processEvents()

    def update_detection_info(self, results, width, height):
        confidences = results.boxes.conf.cpu().numpy()
        avg_conf = confidences.mean() if len(confidences) > 0 else 0
        self.confidence_label.setText(f"{avg_conf:.1%}")
        self.target_count_label.setText(str(len(results)))
        self.x_label.setText("N/A")
        self.y_label.setText("N/A")

        temperature = random.randint(1, 100)
        humidity = random.randint(1, 100)
        self.temperature_label.setText(f"{temperature}℃")
        self.humidity_label.setText(f"{humidity}%")

    def update_frame(self):
        if self.cap and self.cap.isOpened():
            ret, frame = self.cap.read()
            if ret:
                frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                with self.lock:
                    self.frame_queue.append(frame)
                    self.current_frame = frame.copy()

    def process_frames(self):
        while True:
            time.sleep(0.01)  # !!! 添加休眠避免空转消耗GPU !!!

            if self.frame_queue:
                with self.lock:
                    original_frame = self.frame_queue.pop(0)

                original_h, original_w = original_frame.shape[:2]
                label_width = self.video_label.width()
                label_height = self.video_label.height()
                aspect_ratio = original_w / original_h

                if aspect_ratio > 1:
                    new_w = label_width
                    new_h = int(new_w / aspect_ratio)
                else:
                    new_h = label_height
                    new_w = int(new_h * aspect_ratio)

                self.scale_x = new_w / original_w
                self.scale_y = new_h / original_h

                frame_rgb = cv2.cvtColor(original_frame, cv2.COLOR_BGR2RGB)
                results = self.model(frame_rgb)[0]
                self.current_detections = results

                scaled_frame = cv2.resize(frame_rgb, (new_w, new_h))
                self.current_frame = scaled_frame.copy()

                confidences = results.boxes.conf.cpu().numpy()
                avg_conf = confidences.mean() if len(confidences) > 0 else 0
                self.confidence_label.setText(f"{avg_conf:.1%}")
                self.target_count_label.setText(str(len(results)))

                annotated = results.plot()
                annotated = cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB)
                annotated = cv2.resize(annotated, (new_w, new_h))

                q_img = QtGui.QImage(annotated.data, new_w, new_h,
                                     annotated.shape[2] * new_w, QtGui.QImage.Format_RGB888)
                self.update_signal.emit(q_img)

    def update_selection(self):
        if not self.current_detections:
            return

        if self.video_paused and self.current_frame is not None and self.current_detections:
            selected = self.target_select.currentText()
            frame = self.current_frame.copy()

            total_conf = []
            for i, detection in enumerate(self.current_detections):
                box = detection.boxes.xyxy[0].cpu().numpy().astype(int)
                scaled_box = [
                    int(box[0] * self.scale_x),
                    int(box[1] * self.scale_y),
                    int(box[2] * self.scale_x),
                    int(box[3] * self.scale_y)
                ]

                conf = detection.boxes.conf.item()
                cls_id = detection.boxes.cls.item()
                class_name = self.model.names[int(cls_id)]
                label = f"{class_name} {conf:.2f}"

                if selected == "全部":
                    color = (0, 0, 255)
                    thickness = 2
                    cv2.rectangle(frame, (scaled_box[0], scaled_box[1]),
                                  (scaled_box[2], scaled_box[3]), color, thickness)
                    cv2.putText(frame, label, (scaled_box[0], scaled_box[1] - 10),
                                cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, thickness)
                    total_conf.append(conf)
                elif selected == str(i + 1):
                    color = (0, 255, 0)
                    thickness = 2
                    cv2.rectangle(frame, (scaled_box[0], scaled_box[1]),
                                  (scaled_box[2], scaled_box[3]), color, thickness)
                    cv2.putText(frame, label, (scaled_box[0], scaled_box[1] - 10),
                                cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, thickness)
                    total_conf.append(conf)

                    x_center = (scaled_box[0] + scaled_box[2]) // 2
                    y_center = (scaled_box[1] + scaled_box[3]) // 2
                    self.x_label.setText(str(x_center))
                    self.y_label.setText(str(y_center))

            if selected == "全部":
                avg_conf = sum(total_conf) / len(total_conf) if total_conf else 0
                self.confidence_label.setText(f"{avg_conf:.1%}")
                self.x_label.setText("N/A")
                self.y_label.setText("N/A")
            elif total_conf:
                self.confidence_label.setText(f"{total_conf[0]:.1%}")

            h, w, ch = frame.shape
            q_img = QtGui.QImage(frame.data, w, h, ch * w, QtGui.QImage.Format_RGB888)
            self.video_label.setPixmap(QtGui.QPixmap.fromImage(q_img))

    def save_results(self):
        if self.video_label.pixmap() is None:
            QtWidgets.QMessageBox.warning(self, "警告", "没有检测结果可以保存")
            return

        timestamp = time.strftime("%Y%m%d_%H%M%S")
        save_dir = os.path.join(self.results_dir, timestamp)
        os.makedirs(save_dir, exist_ok=True)

        original_image_path = os.path.join(save_dir, "original.jpg")
        processed_image_path = os.path.join(save_dir, "processed.jpg")
        detection_result_path = os.path.join(save_dir, "detection.jpg")

        self.video_label.pixmap().save(processed_image_path)

        if self.current_frame is not None:
            original_image = cv2.cvtColor(self.current_frame, cv2.COLOR_RGB2BGR)
            cv2.imwrite(original_image_path, original_image)

        if self.current_detections:
            annotated = self.current_detections.plot()
            cv2.imwrite(detection_result_path, annotated)

        doc = Document()
        doc.add_heading('检测报告', 0)
        time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        doc.add_paragraph(f"检测时间: {time_str}")

        doc.add_heading('原始图像', level=1)
        doc.add_picture(original_image_path, width=Inches(4))

        doc.add_heading('检测结果', level=1)
        doc.add_picture(processed_image_path, width=Inches(4))

        info = [
            ("平均置信度", self.confidence_label.text()),
            ("目标数量", self.target_count_label.text()),
            ("温度", self.temperature_label.text()),
            ("湿度", self.humidity_label.text())
        ]
        table = doc.add_table(rows=1, cols=2)
        hdr_cells = table.rows[0].cells
        hdr_cells[0].text = '项目'
        hdr_cells[1].text = ''

        for item in info:
            row_cells = table.add_row().cells
            row_cells[0].text = item[0]
            row_cells[1].text = item[1]

        report_path = os.path.join(save_dir, "report.docx")
        doc.save(report_path)

        metadata = {
            "target_count": int(self.target_count_label.text()),
            "average_confidence": float(self.confidence_label.text().strip("%")) / 100,
            "temperature": int(self.temperature_label.text().strip("")),
            "humidity": int(self.humidity_label.text().strip("%")),
            "timestamp": timestamp
        }
        metadata_path = os.path.join(save_dir, "metadata.json")
        with open(metadata_path, "w") as f:
            json.dump(metadata, f)

        QtWidgets.QMessageBox.information(self, "保存成功", "结果已保存至: " + save_dir)
        self.load_history_records()

    def load_history_records(self):
        self.history_table.setRowCount(0)
        for subdir in os.listdir(self.results_dir):
            subdir_path = os.path.join(self.results_dir, subdir)
            metadata_path = os.path.join(subdir_path, "metadata.json")
            if os.path.exists(metadata_path):
                with open(metadata_path, "r") as f:
                    metadata = json.load(f)

                row_num = self.history_table.rowCount()
                self.history_table.insertRow(row_num)

                ts = metadata["timestamp"]
                formatted_time = f"{ts[:4]}/{ts[4:6]}/{ts[6:8]} {ts[9:11].zfill(2)}:{ts[11:13].zfill(2)}:{ts[13:15].zfill(2)}"

                self.history_table.setItem(row_num, 0, QtWidgets.QTableWidgetItem(formatted_time))
                self.history_table.setItem(row_num, 2, QtWidgets.QTableWidgetItem(str(metadata["target_count"])))
                self.history_table.setItem(row_num, 3, QtWidgets.QTableWidgetItem(f"{metadata['average_confidence']:.1%}"))
                self.history_table.setItem(row_num, 4, QtWidgets.QTableWidgetItem(f"{metadata['temperature']}℃"))
                self.history_table.setItem(row_num, 5, QtWidgets.QTableWidgetItem(f"{metadata['humidity']}%"))

                original_btn = QtWidgets.QPushButton("查看")
                original_btn.clicked.connect(lambda _, p=os.path.join(subdir_path, "original.jpg"): self.show_image(p))
                self.history_table.setCellWidget(row_num, 1, original_btn)

                result_btn = QtWidgets.QPushButton("查看")
                result_btn.clicked.connect(lambda _, p=os.path.join(subdir_path, "processed.jpg"): self.show_image(p))
                self.history_table.setCellWidget(row_num, 6, result_btn)

                delete_btn = QtWidgets.QPushButton("删除")
                delete_btn.setStyleSheet("background-color: #ff4444; color: white;font-size: 13px;")
                delete_btn.clicked.connect(lambda _, p=subdir_path: self.delete_record(p))
                self.history_table.setCellWidget(row_num, 7, delete_btn)

    def delete_record(self, record_path):
        try:
            shutil.rmtree(record_path)
            self.load_history_records()
            QtWidgets.QMessageBox.information(self, "成功", "记录已删除")
        except Exception as e:
            QtWidgets.QMessageBox.warning(self, "错误", f"删除失败: {str(e)}")

    def show_image(self, path):
        dialog = QtWidgets.QDialog(self)
        dialog.setWindowTitle("查看图片")
        layout = QtWidgets.QVBoxLayout(dialog)
        label = QtWidgets.QLabel()
        pixmap = QtGui.QPixmap(path)
        label.setPixmap(pixmap)
        layout.addWidget(label)
        dialog.exec()


if __name__ == "__main__":
    app = QtWidgets.QApplication()
    window = MainWindow()
    window.show()
    app.exec()

 

posted @ 2025-03-04 23:27  贾贾鱼  阅读(13)  评论(0)    收藏  举报