树莓派4B USB摄像头TCP/IP视频传输代码故障修复请求
问题:树莓派TCP视频传输客户端无法正常接收帧
树莓派4B连接A4 Tech品牌USB摄像头,编写server.py捕获视频并通过TCP/IP发送,client.py负责接收视频,但客户端无法正常工作。
服务端代码(server.py)
import cv2 import socket import struct import pickle import logging logging.basicConfig(level=logging.DEBUG) def send_frame(client_socket, frame): # Serialize the frame using pickle data = pickle.dumps(frame) # Pack the size of the serialized frame message_size = struct.pack("Q", len(data)) try: # Send the size of the serialized frame to the client client_socket.sendall(message_size) # Send the serialized frame data to the client client_socket.sendall(data) logging.debug(f"Sent frame size: {len(data)} bytes.") return True except Exception as e: logging.error(f"Error sending data: {e}") return False server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) host = 'IP' # Use the Raspberry Pi's IP address port = 8000 server_socket.bind((host, port)) server_socket.listen(5) camera = cv2.VideoCapture(0, cv2.CAP_V4L2) # Use V4L2 backend explicitly if not camera.isOpened(): logging.error("Failed to open camera") exit() camera.set(cv2.CAP_PROP_FRAME_WIDTH, 320) camera.set(cv2.CAP_PROP_FRAME_HEIGHT, 240) logging.info('Waiting for client connection...') try: while True: client_socket, addr = server_socket.accept() logging.info(f'Connected to {addr}') if client_socket: try: while camera.isOpened(): ret, frame = camera.read() if not ret: logging.error("Failed to read frame from camera") break if not send_frame(client_socket, frame): break except Exception as e: logging.error(f"Error during frame send loop: {e}") finally: client_socket.close() except KeyboardInterrupt: logging.info("Server stopping") finally: camera.release() server_socket.close()
客户端代码(client.py)
import cv2 import socket import struct import pickle import logging logging.basicConfig(level=logging.DEBUG) def receive_frame(client_socket): data = b"" payload_size = struct.calcsize("Q") # Receive the size of the incoming frame while len(data) < payload_size: packet = client_socket.recv(4096) if not packet: logging.error("No payload received. Exiting.") return None data += packet if len(data) < payload_size: logging.error(f"Incomplete payload size received. Expected size: {payload_size}, received size: {len(data)}. Exiting.") return None packed_msg_size = data[:payload_size] data = data[payload_size:] msg_size = struct.unpack("Q", packed_msg_size)[0] logging.debug(f"Expected message size: {msg_size} bytes.") # Receive the actual frame data while len(data) < msg_size: packet = client_socket.recv(4096) if not packet: logging.error(f"Incomplete data received. Expected size: {msg_size}, received size: {len(data)}. Exiting.") return None data += packet if len(data) != msg_size: logging.error(f"Data received does not match expected size. Expected: {msg_size}, Got: {len(data)}") return None try: frame_data = pickle.loads(data) return frame_data except pickle.UnpicklingError as e: logging.error(f"Error deserializing data: {e}") return None client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) host = 'Same IP' # Use the Raspberry Pi's IP address port = 8000 client_socket.connect((host, port)) try: while True: frame = receive_frame(client_socket) if frame is None: logging.error("No data packet received") break cv2.imshow('Video', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break except Exception as e: logging.error(f"Error processing data: {e}") finally: client_socket.close() cv2.destroyAllWindows()
错误信息
Server.py debug "DEBUG:root:Sent frame size: 230564 bytes." and error in client.py "DEBUG:root:Expected message size: 230564 bytes.
ERROR:root:Data received does not match expected size. Expected: 230564, Got: 231572
ERROR:root:No data packet received"
修复方案
问题核心是客户端接收逻辑未保留剩余字节:TCP是流式传输,单次recv可能同时收到当前帧的剩余数据和下一帧的头部信息,原代码直接丢弃剩余数据,导致下一次解析帧大小时出现错误。
修改客户端接收逻辑
修改receive_frame函数,返回处理完当前帧后剩余的未解析数据,供下一次循环使用:
def receive_frame(client_socket, remaining_data=b""): data = remaining_data payload_size = struct.calcsize("Q") # 接收帧大小信息 while len(data) < payload_size: packet = client_socket.recv(4096) if not packet: logging.error("No payload received. Exiting.") return None, b"" data += packet packed_msg_size = data[:payload_size] data = data[payload_size:] msg_size = struct.unpack("Q", packed_msg_size)[0] logging.debug(f"Expected message size: {msg_size} bytes.") # 接收帧数据 while len(data) < msg_size: packet = client_socket.recv(4096) if not packet: logging.error(f"Incomplete data received. Expected size: {msg_size}, received size: {len(data)}. Exiting.") return None, b"" data += packet # 分离当前帧数据和剩余数据 frame_data = data[:msg_size] remaining_data = data[msg_size:] try: frame = pickle.loads(frame_data) return frame, remaining_data except pickle.UnpicklingError as e: logging.error(f"Error deserializing data: {e}") return None, remaining_data
同时修改客户端主循环,传递剩余数据:
try: remaining_data = b"" while True: frame, remaining_data = receive_frame(client_socket, remaining_data) if frame is None: logging.error("No data packet received") break cv2.imshow('Video', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break except Exception as e: logging.error(f"Error processing data: {e}") finally: client_socket.close() cv2.destroyAllWindows()
额外优化建议
- 替换pickle提升效率与安全性:pickle序列化效率低且存在安全风险,可改用
cv2.imencode将帧转为JPEG字节流传输:
服务端send_frame修改:
客户端反序列化修改(需导入def send_frame(client_socket, frame): _, buffer = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 70]) data = buffer.tobytes() message_size = struct.pack("Q", len(data)) try: client_socket.sendall(message_size) client_socket.sendall(data) logging.debug(f"Sent frame size: {len(data)} bytes.") return True except Exception as e: logging.error(f"Error sending data: {e}") return Falsenumpy):import numpy as np # ... frame = cv2.imdecode(np.frombuffer(frame_data, dtype=np.uint8), cv2.IMREAD_COLOR) - 增加超时处理:客户端连接时设置超时,避免无限等待:
client_socket.settimeout(10) client_socket.connect((host, port))
内容的提问来源于stack exchange,提问作者Nickvels
相关产品推荐
相关产品推荐

