树莓派3B通过TCP Socket向Ubuntu笔记本传输摄像头图像故障排查
解决树莓派TCP传输摄像头图像到Ubuntu后结果异常的问题
我之前也碰到过完全一样的情况!用树莓派摄像头通过TCP传图到PC,结果保存的图片要么花屏、要么只有一半,甚至根本打不开——大概率是TCP流式传输的粘包/分包问题,或者图像数据的编码解码没对齐。结合你的场景,我整理了一套能正常工作的代码,顺便给你拆解下关键问题:
常见问题排查点
- TCP粘包问题:TCP是流式协议,连续发送的字节会被合并成一个流,接收端不知道每帧图像的边界,导致拼接错误。
- 图像编码不一致:直接传输PiRGBArray的原始数据体积大,且容易因为格式(比如RGB/BGR顺序)导致显示异常,最好先编码成JPG再传输。
- 摄像头参数不匹配:树莓派摄像头初始化的分辨率、格式要和接收端处理逻辑统一。
完整代码实现
树莓派端(client.py)
补全你提供的代码片段,加入了图像编码、先传数据长度的逻辑:
from picamera.array import PiRGBArray from picamera import PiCamera import time import cv2 import socket import struct # 服务器(Ubuntu笔记本)的IP和端口 TCP_IP = '192.168.0.109' TCP_PORT = 5000 # 初始化摄像头 camera = PiCamera() camera.resolution = (640, 480) # 统一分辨率,避免接收端处理异常 camera.framerate = 32 rawCapture = PiRGBArray(camera, size=(640, 480)) # 等待摄像头预热 time.sleep(0.1) # 创建TCP客户端socket client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect((TCP_IP, TCP_PORT)) try: for frame in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True): # 获取当前帧的图像数组 image = frame.array # 将图像编码成JPG,减少传输体积,同时统一格式 encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 90] result, img_encode = cv2.imencode('.jpg', image, encode_param) # 先发送图像数据的长度(用struct打包成4字节的整数) data_len = struct.pack('!I', len(img_encode)) client_socket.sendall(data_len) # 再发送图像编码后的字节数据 client_socket.sendall(img_encode) # 清空缓存,准备下一帧 rawCapture.truncate(0) # 可选:按q退出 key = cv2.waitKey(1) & 0xFF if key == ord("q"): break finally: client_socket.close() camera.close() cv2.destroyAllWindows()
Ubuntu笔记本端(server.py)
接收端需要先读取数据长度,再接收对应字节数的图像数据,避免粘包:
import socket import struct import cv2 import numpy as np TCP_IP = '0.0.0.0' # 监听所有网卡 TCP_PORT = 5000 # 创建TCP服务器socket server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind((TCP_IP, TCP_PORT)) server_socket.listen(1) print(f"等待树莓派连接... 监听端口 {TCP_PORT}") conn, addr = server_socket.accept() print(f"已连接:{addr}") try: while True: # 先读取4字节的图像长度 len_data = conn.recv(4) if not len_data: break img_len = struct.unpack('!I', len_data)[0] # 接收完整的图像数据 img_data = b'' while len(img_data) < img_len: chunk = conn.recv(min(img_len - len(img_data), 4096)) if not chunk: break img_data += chunk # 解码图像并保存/显示 if len(img_data) == img_len: img_np = np.frombuffer(img_data, dtype=np.uint8) image = cv2.imdecode(img_np, cv2.IMREAD_COLOR) # 显示图像(可选) cv2.imshow('Received Image', image) # 保存图像到本地 cv2.imwrite('received_image.jpg', image) # 按q退出 if cv2.waitKey(1) & 0xFF == ord('q'): break finally: conn.close() server_socket.close() cv2.destroyAllWindows()
关键注意事项
- 先传长度再传数据:用
struct把图像长度打包成固定4字节的整数,接收端先读这4字节,就知道接下来要收多少图像数据,完美解决粘包问题。 - 统一图像编码:用
cv2.imencode把图像转成JPG字节流,比直接传原始RGB数组小很多,而且跨平台兼容性更好。 - WiFi稳定性:如果WiFi信号不好,可能会出现丢包,你可以在代码里加简单的重传逻辑,或者用UDP(但UDP会丢包,适合对实时性要求高的场景)。
- 防火墙设置:确保Ubuntu笔记本的5000端口没有被防火墙挡住,可以临时关闭防火墙测试:
sudo ufw disable(测试完记得打开)。
你可以先运行Ubuntu端的server.py,再运行树莓派的client.py,应该就能得到正常的图像了!
内容的提问来源于stack exchange,提问作者Kyaw Soe Han
相关产品推荐
相关产品推荐

