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

树莓派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 False
    
    客户端反序列化修改(需导入numpy):
    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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 17:34:54