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()

浙公网安备 33010602011771号