You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在PyQt客户端窗口中显示服务端传输的视频流

问题说明

现有基于Socket的C/S架构实时视频传输代码可正常运行,但视频默认通过OpenCV生成独立弹窗展示,需要将客户端接收的视频流嵌入PyQt窗口内显示。
说明:本地单机PyQt程序中使用QPixmap可正常加载实时视频流,但在网络传输的C/S架构下暂未实现该嵌入效果,原有可运行代码如下:

原有服务端代码(server.py)

import socket, cv2, pickle,struct,imutils

# Socket Create
server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
host_name  = socket.gethostname()
host_ip = socket.gethostbyname(host_name)
print('HOST IP:',host_ip)
port = 9999
socket_address = (host_ip,port)

# Socket Bind
server_socket.bind(socket_address)

# Socket Listen
server_socket.listen(5)


# Socket Accept
while True:
    client_socket,addr = server_socket.accept()
    print('GOT CONNECTION FROM:',addr)
    if client_socket:
        vid = cv2.VideoCapture(0)
        
        while(vid.isOpened()):
            img,frame = vid.read()
            frame = imutils.resize(frame,width=320)
            a = pickle.dumps(frame)
            message = struct.pack("Q",len(a))+a
            client_socket.sendall(message)
            
            cv2.imshow('TRANSMITTING VIDEO',frame)
            key = cv2.waitKey(1) & 0xFF
            if key ==ord('q'):
                client_socket.close()

原有客户端代码(client.py)

import socket,cv2, pickle,struct

# create socket
client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
host_name  = socket.gethostname()
host_ip = socket.gethostbyname(host_name)
port = 9999
client_socket.connect((host_ip,port)) # a tuple
data = b""
payload_size = struct.calcsize("Q")
while True:
    while len(data) < payload_size:
        packet = client_socket.recv(4*1024) # 4K
        if not packet: break
        data+=packet
    packed_msg_size = data[:payload_size]
    data = data[payload_size:]
    msg_size = struct.unpack("Q",packed_msg_size)[0]
    
    while len(data) < msg_size:
        data += client_socket.recv(4*1024)
    frame_data = data[:msg_size]
    data  = data[msg_size:]
    frame = pickle.loads(frame_data)
    cv2.imshow("RECEIVING VIDEO",frame)
    key = cv2.waitKey(1) & 0xFF
    if key  == ord('q'):
        break
client_socket.close()

实现方法

核心改动点:

  • 服务端移除不必要的OpenCV弹窗逻辑,仅保留视频采集、帧序列化、网络发送能力
  • 客户端不能在UI主线程跑阻塞式的socket收流循环,否则会直接卡死PyQt界面,需要单独开工作线程处理网络接收,通过Qt信号槽机制把解码后的帧传给主线程渲染
  • 主线程收到帧后,先把OpenCV默认的BGR格式转成RGB格式,再转换为QPixmap加载到QLabel控件上,即可实现视频嵌入PyQt窗口显示

修改后的服务端代码

import socket, cv2, pickle, struct, imutils

# 套接字初始化
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host_name = socket.gethostname()
host_ip = socket.gethostbyname(host_name)
print('HOST IP:', host_ip)
port = 9999
socket_address = (host_ip, port)

server_socket.bind(socket_address)
server_socket.listen(5)

while True:
    client_socket, addr = server_socket.accept()
    print('GOT CONNECTION FROM:', addr)
    if client_socket:
        vid = cv2.VideoCapture(0)
        try:
            while vid.isOpened():
                ret, frame = vid.read()
                if not ret:
                    break
                frame = imutils.resize(frame, width=640) # 可自行调整分辨率
                a = pickle.dumps(frame)
                message = struct.pack("Q", len(a)) + a
                client_socket.sendall(message)
        except Exception as e:
            print("Connection error:", e)
        finally:
            vid.release()
            client_socket.close()

注意:如果客户端和服务端不在同一台机器,客户端连接时要把host_ip改成服务端的实际局域网/公网IP,不要用gethostname()自动获取的本地IP。

修改后的客户端代码(带PyQt嵌入显示)

import sys
import socket
import pickle
import struct
import cv2
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtGui import QImage, QPixmap


class VideoReceiveThread(QThread):
    # 自定义信号,收到帧时传递给UI线程
    frame_received = pyqtSignal(object)

    def __init__(self, host_ip, port=9999):
        super().__init__()
        self.host_ip = host_ip
        self.port = port
        self.running = True

    def run(self):
        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        client_socket.connect((self.host_ip, self.port))
        data = b""
        payload_size = struct.calcsize("Q")
        try:
            while self.running:
                # 接收帧头(帧长度)
                while len(data) < payload_size:
                    packet = client_socket.recv(4*1024)
                    if not packet:
                        break
                    data += packet
                if not self.running:
                    break
                packed_msg_size = data[:payload_size]
                data = data[payload_size:]
                msg_size = struct.unpack("Q", packed_msg_size)[0]

                # 接收完整帧数据
                while len(data) < msg_size:
                    data += client_socket.recv(4*1024)
                frame_data = data[:msg_size]
                data = data[msg_size:]
                frame = pickle.loads(frame_data)
                # 发送帧到UI线程
                self.frame_received.emit(frame)
        except Exception as e:
            print("Receive error:", e)
        finally:
            client_socket.close()

    def stop(self):
        self.running = False
        self.wait()


class VideoWindow(QMainWindow):
    def __init__(self, host_ip):
        super().__init__()
        self.setWindowTitle("实时视频流")
        self.setGeometry(100, 100, 640, 480)
        # 用QLabel作为视频显示载体
        self.video_label = QLabel(self)
        self.video_label.setAlignment(Qt.AlignCenter)
        self.setCentralWidget(self.video_label)
        # 启动视频接收线程
        self.recv_thread = VideoReceiveThread(host_ip)
        self.recv_thread.frame_received.connect(self.update_frame)
        self.recv_thread.start()

    def update_frame(self, frame):
        # OpenCV默认是BGR格式,转成RGB才能正常显示
        rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        h, w, ch = rgb_frame.shape
        bytes_per_line = ch * w
        # 转QImage
        q_img = QImage(rgb_frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
        # 转QPixmap并自适应label大小
        pixmap = QPixmap.fromImage(q_img).scaled(self.video_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
        self.video_label.setPixmap(pixmap)

    def closeEvent(self, event):
        # 窗口关闭时停止线程释放资源
        self.recv_thread.stop()
        event.accept()


if __name__ == "__main__":
    # 这里改成服务端的实际IP,同一台机器测试可以用127.0.0.1
    SERVER_IP = "127.0.0.1"
    app = QApplication(sys.argv)
    window = VideoWindow(SERVER_IP)
    window.show()
    sys.exit(app.exec_())

依赖安装

运行前先安装需要的库:

pip install opencv-python imutils PyQt5

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 04:36:23