You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何将face.py的双显示内容嵌入PyQt界面的双QLabel/QFrame面板

解决方案:将OpenCV摄像头画面与人脸处理结果集成到PyQt界面

嘿,作为PyQt新手,把CV程序的显示内容迁移到Qt界面确实需要绕几个小弯,不过别担心,我给你整理了一套清晰的步骤,结合代码示例来帮你搞定~

核心思路梳理

你的face.py原本用OpenCV的imshow弹出窗口显示内容,现在要改成把图像数据传给PyQt的QLabel显示。关键要解决两个问题:

  • OpenCV图像(BGR格式)转成PyQt支持的QImage
  • 实时摄像头的循环不能阻塞PyQt主线程(否则界面会卡死),得用子线程来处理摄像头读取

步骤1:改造face.py,拆分功能为可调用的函数

先把原来的livevideo()拆成几个独立的函数,方便在Qt里调用,不要直接在里面做imshow

# face.py
import cv2
import numpy as np

# 全局变量(也可以封装成类,更规范)
cap = None
avg_face = None

def init_camera():
    """初始化摄像头"""
    global cap
    cap = cv2.VideoCapture(0)
    if not cap.isOpened():
        raise Exception("无法打开摄像头")

def get_frame():
    """获取摄像头一帧画面,返回OpenCV格式的图像"""
    global cap
    ret, frame = cap.read()
    if ret:
        return frame
    return None

def capture_and_update_avg_face(frame):
    """捕获人脸并更新平均人脸,返回处理后的图像(带人脸标记+平均人脸)"""
    global avg_face
    # 这里放你原来的人脸检测逻辑,比如用haar级联或者MTCNN
    face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, 1.1, 4)
    
    # 绘制检测到的人脸
    for (x, y, w, h) in faces:
        cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 2)
        # 提取人脸区域
        face_roi = frame[y:y+h, x:x+w]
        face_roi = cv2.resize(face_roi, (100, 100))
        
        # 更新平均人脸
        if avg_face is None:
            avg_face = face_roi.astype(np.float32)
        else:
            cv2.accumulateWeighted(face_roi, avg_face, 0.1)
    
    # 生成要显示的画面:左边是捕获的帧,右边是平均人脸(如果有的话)
    display_frame = frame.copy()
    if avg_face is not None:
        # 把平均人脸转成uint8格式
        avg_face_uint8 = cv2.convertScaleAbs(avg_face)
        # 把平均人脸放到画面右侧
        h, w = display_frame.shape[:2]
        if w + 100 <= 640:  # 假设摄像头分辨率是640x480
            display_frame[:, w:w+100] = cv2.resize(avg_face_uint8, (100, h))
    return display_frame

def release_camera():
    """释放摄像头资源"""
    global cap
    if cap is not None and cap.isOpened():
        cap.release()

步骤2:编写interface.py,搭建Qt界面+线程处理

这里用QMainWindow作为主窗口,两个QLabel分别显示实时画面和人脸处理结果,同时用QThread来跑摄像头循环,避免界面卡顿:

# interface.py
import sys
import cv2
import numpy as np
from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QHBoxLayout, QVBoxLayout, QWidget)
from PyQt5.QtCore import QThread, pyqtSignal, Qt, QTimer
from PyQt5.QtGui import QImage, QPixmap

# 导入face.py的函数
from face import init_camera, get_frame, capture_and_update_avg_face, release_camera

class CameraThread(QThread):
    # 定义信号:传递OpenCV格式的帧
    frame_signal = pyqtSignal(np.ndarray)

    def run(self):
        """线程运行:循环读取摄像头帧"""
        init_camera()
        while self.isRunning():
            frame = get_frame()
            if frame is not None:
                self.frame_signal.emit(frame)
            # 控制帧率,避免占用过高资源
            self.msleep(30)
    
    def stop(self):
        """停止线程并释放摄像头"""
        self.quit()
        self.wait()
        release_camera()

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("人脸平均系统")
        self.setGeometry(100, 100, 1200, 500)

        # 创建两个QLabel用于显示画面
        self.camera_label = QLabel()
        self.camera_label.setAlignment(Qt.AlignCenter)
        self.camera_label.setStyleSheet("border: 2px solid black;")

        self.face_avg_label = QLabel()
        self.face_avg_label.setAlignment(Qt.AlignCenter)
        self.face_avg_label.setStyleSheet("border: 2px solid black;")

        # 布局:横向排列两个标签
        top_layout = QHBoxLayout()
        top_layout.addWidget(self.camera_label)
        top_layout.addWidget(self.face_avg_label)
        top_layout.setStretch(0, 1)
        top_layout.setStretch(1, 1)

        central_widget = QWidget()
        central_widget.setLayout(top_layout)
        self.setCentralWidget(central_widget)

        # 初始化摄像头线程
        self.camera_thread = CameraThread()
        self.camera_thread.frame_signal.connect(self.update_camera_label)
        self.camera_thread.start()

        # 存储当前帧,用于按'm'键时处理
        self.current_frame = None

    def update_camera_label(self, frame):
        """更新实时摄像头标签的显示"""
        self.current_frame = frame.copy()
        # 把OpenCV图像转成QImage
        qimage = self.cv2_to_qimage(frame)
        # 适配标签大小
        pixmap = QPixmap.fromImage(qimage).scaled(self.camera_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
        self.camera_label.setPixmap(pixmap)

    def update_face_avg_label(self, processed_frame):
        """更新人脸处理结果标签的显示"""
        qimage = self.cv2_to_qimage(processed_frame)
        pixmap = QPixmap.fromImage(qimage).scaled(self.face_avg_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
        self.face_avg_label.setPixmap(pixmap)

    def cv2_to_qimage(self, cv_img):
        """OpenCV BGR图像转QImage RGB格式"""
        rgb_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
        h, w, ch = rgb_img.shape
        bytes_per_line = ch * w
        return QImage(rgb_img.data, w, h, bytes_per_line, QImage.Format_RGB888)

    def keyPressEvent(self, event):
        """重写按键事件,处理'm'键"""
        if event.key() == Qt.Key_M:
            if self.current_frame is not None:
                # 调用face.py的函数处理人脸
                processed_frame = capture_and_update_avg_face(self.current_frame)
                # 更新第二个标签
                self.update_face_avg_label(processed_frame)
        # 按ESC退出程序
        elif event.key() == Qt.Key_Escape:
            self.camera_thread.stop()
            self.close()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

关键细节说明

  1. 线程处理:摄像头的读取循环放在CameraThread里,通过frame_signal把帧传给主线程更新界面,这样主线程不会被阻塞,界面始终流畅。
  2. 图像格式转换cv2_to_qimage函数把OpenCV的BGR格式转成PyQt需要的RGB格式,这是必须的,否则画面颜色会错乱。
  3. 按键事件:重写keyPressEvent来捕获'm'键,调用人脸处理函数后更新第二个标签。
  4. 资源释放:关闭窗口时记得停止线程并释放摄像头,避免资源泄漏。

内容的提问来源于stack exchange,提问作者Syd Xyn

火山引擎 最新活动