如何在Tkinter GUI中直接显示树莓派4间的Pi Camera视频流
解决方案:在Tkinter窗口内整合树莓派摄像头流
下面提供两种可行的方案,替代原有的raspivid+netcat+mplayer组合,直接在Tkinter界面内渲染摄像头画面:
方案1:OpenCV + TCP 流媒体 + Tkinter
机器人端(发送端)代码
用OpenCV读取Pi Camera的帧,编码为JPEG后通过TCP套接字发送到控制端:
import cv2 import socket import struct # 配置参数 HOST = "控制端树莓派的IP" # 示例:"192.168.1.100" PORT = 8000 # 初始化摄像头 cap = cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) # 创建TCP套接字 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect((HOST, PORT)) try: while True: ret, frame = cap.read() if not ret: break # 编码为JPEG _, buffer = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 80]) # 发送帧长度 + 帧数据 client_socket.sendall(struct.pack('>L', len(buffer)) + buffer) finally: cap.release() client_socket.close()
控制端(接收端)Tkinter整合代码
接收TCP传输的帧,解码后转换成Tkinter的PhotoImage,在Label组件中显示:
import tkinter as tk import cv2 import socket import struct import numpy as np from PIL import Image, ImageTk class CameraStreamApp: def __init__(self, root): self.root = root self.root.title("机器人摄像头流") # 创建显示用的Label self.label = tk.Label(root) self.label.pack() # 初始化TCP套接字 self.HOST = "0.0.0.0" # 监听所有网卡 self.PORT = 8000 self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server_socket.bind((self.HOST, self.PORT)) self.server_socket.listen(1) self.client_socket, _ = self.server_socket.accept() # 开始帧接收和更新 self.update_frame() def update_frame(self): # 接收帧长度 length_data = self.recvall(struct.calcsize('>L')) if not length_data: self.root.quit() return length = struct.unpack('>L', length_data)[0] # 接收帧数据 frame_data = self.recvall(length) # 解码帧 frame = cv2.imdecode(np.frombuffer(frame_data, dtype=np.uint8), cv2.IMREAD_COLOR) # 转换颜色空间(OpenCV是BGR,Tkinter需要RGB) frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # 转换成PhotoImage img = Image.fromarray(frame_rgb) imgtk = ImageTk.PhotoImage(image=img) self.label.imgtk = imgtk # 保留引用防止被垃圾回收 self.label.config(image=imgtk) # 循环调用更新 self.root.after(10, self.update_frame) def recvall(self, count): # 确保接收完整的数据 buf = b'' while count: new_buf = self.client_socket.recv(count) if not new_buf: return None buf += new_buf count -= len(new_buf) return buf if __name__ == "__main__": root = tk.Tk() app = CameraStreamApp(root) root.mainloop()
方案2:Picamera2 + Tkinter(树莓派原生摄像头库)
如果使用Pi Camera v2/v3,推荐用官方的Picamera2库,延迟更低:
机器人端(发送端)代码
from picamera2 import Picamera2 import socket import struct import cv2 HOST = "控制端IP" PORT = 8000 picam2 = Picamera2() picam2.configure(picam2.create_preview_configuration(main={"size": (640, 480)})) picam2.start() client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect((HOST, PORT)) try: while True: frame = picam2.capture_array() # 转换为BGR格式(Picamera2默认是RGB) frame_bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) _, buffer = cv2.imencode('.jpg', frame_bgr, [cv2.IMWRITE_JPEG_QUALITY, 80]) client_socket.sendall(struct.pack('>L', len(buffer)) + buffer) finally: picam2.stop() client_socket.close()
控制端代码
和方案1的控制端代码完全一致,无需修改。
注意事项
- 两台树莓派需在同一以太网段,确保防火墙开放对应端口(8000)
- 安装依赖:
机器人端/控制端都需要安装:pip install opencv-python pillow
用Picamera2的话,机器人端还需安装:sudo apt install python3-picamera2 - 可以调整帧的分辨率和JPEG质量来平衡流畅度和画质
- 如果需要开机自启动发送端脚本,可将其加入
rc.local或创建systemd服务,替代原来的bash脚本
内容的提问来源于stack exchange,提问作者Pablo Córdoba Rodríguez
相关产品推荐
相关产品推荐

