2025.2.7

今天继续页面优化,在昨天的基础上加上了关闭摄像头,暂停和继续摄像头的功能,页面如下:

 开启摄像头后页面如下:

 可点击关闭摄像头将摄像头关闭,或者点击暂停暂时停止拍摄。

代码如下:

from PySide6 import QtWidgets, QtCore, QtGui
import cv2, os, time, random
from threading import Thread, Lock
from ultralytics import YOLO

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

    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.analysis_btn.setStyleSheet(btn_style)
        self.status_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.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.analysis_btn.clicked.connect(self.switch_to_analysis)
        self.status_btn.clicked.connect(self.switch_to_status)
        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.analysis_btn.setStyleSheet("background-color: #87CEFA; color: black;")
        self.status_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.status_btn.setStyleSheet("background-color: #87CEFA; color: black;")
        self.analysis_btn.setStyleSheet("background-color: #D3D3D3; color: gray;")

    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("暂停/继续")
        # self.cam_btn.clicked.connect(self.toggle_pause())
        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)
        right_layout.addWidget(self.start_cam_btn)

        # 新增导入按钮
        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.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.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]:
            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.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)

        # 成熟度展示
        maturity_group = QtWidgets.QGroupBox("今日成熟度分布")
        grid = QtWidgets.QGridLayout(maturity_group)

        maturities = ["60%", "55%", "65%", "57%"]
        for i, mat in enumerate(maturities):
            label = QtWidgets.QLabel(f"成熟度:{mat}")
            grid.addWidget(label, i // 2, i % 2)

        layout.addWidget(maturity_group)
        self.stacked_pages.addWidget(page)

    def setup_video(self):
        self.model = YOLO('yolov8n.pt')
        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(30)
            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)"
        )
        if path:
            self.cap = cv2.VideoCapture(path)
            if self.cap.isOpened():
                self.video_timer.start(30)
                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.video_paused = True
            if self.video_timer.isActive():
                self.video_timer.stop()
                self.cam_btn.setText("继续")

            image = cv2.imread(path)
            frame = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

            # 调整图片显示大小
            h, w, _ = frame.shape
            aspect_ratio = w / h
            label_width = self.video_label.width()
            label_height = self.video_label.height()
            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)

            frame = cv2.resize(frame, (new_w, new_h))
            self.current_frame = frame.copy()
            self.scale_x = 1.0  # 图片已缩放,比例设为1
            self.scale_y = 1.0

            results = self.model(frame)[0]
            self.current_detections = results

            # 更新检测信息
            if len(results) > 0:
                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")

            # 更新下拉框
            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.cvtColor(annotated, cv2.COLOR_BGR2RGB)
            q_img = QtGui.QImage(annotated.data, annotated.shape[1], annotated.shape[0],
                                 annotated.shape[2] * annotated.shape[1], QtGui.QImage.Format_RGB888)
            self.video_label.setPixmap(QtGui.QPixmap.fromImage(q_img))

    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:
            if self.frame_queue:
                with self.lock:
                    frame = self.frame_queue.pop(0)

                # 计算缩放比例
                original_h, original_w = 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(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)))
                self.x_label.setText("N/A")
                self.y_label.setText("N/A")

                # 绘制检测结果
                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 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():
            save_dir = os.path.join("result", time.strftime("%Y%m%d_%H%M%S"))
            os.makedirs(save_dir, exist_ok=True)

            # 保存图片
            image_path = os.path.join(save_dir, "capture.jpg")
            self.video_label.pixmap().save(image_path)

            # 保存信息
            info_path = os.path.join(save_dir, "info.txt")
            with open(info_path, "w") as f:
                f.write(f"置信度: {self.confidence_label.text()}\n")
                f.write(f"目标数量: {self.target_count_label.text()}\n")
                f.write(f"X坐标: {self.x_label.text()}\n")
                f.write(f"Y坐标: {self.y_label.text()}\n")

            QtWidgets.QMessageBox.information(self, "保存成功", f"数据已保存至: {save_dir}")


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

 

posted @ 2025-02-06 11:05  贾贾鱼  阅读(14)  评论(0)    收藏  举报