如何在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
相关产品推荐
相关产品推荐

