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

树莓派Socket传输图像时收发数据长度不一致问题解决

问题描述

我有一台树莓派和USB摄像头,计划实现以下流程:树莓派采集USB摄像头的图像并发送至电脑,电脑对图像进行处理后将结果数据回传至树莓派,程序需持续循环运行。目前遇到的问题是,树莓派发送数据后,电脑接收的数据长度与发送的不一致,请问该如何解决?


Server.py(树莓派端代码)

import socket
import cv2
import io
from PIL import Image
import numpy as np
import struct

def encode_image(frame):
    stream = io.BytesIO()
    frame = cv2.resize(frame, (640, 480))
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    frame = Image.fromarray(frame)
    frame.save(stream, format="JPEG")
    stream.seek(0)
    return stream

def decode_image(image_bytes):
    stream = io.BytesIO(image_bytes)
    return cv2.cvtColor(np.array(Image.open(stream)), cv2.COLOR_RGB2BGR)

server_socket = socket.socket()
server_socket.bind(('0.0.0.0', 8000))
server_socket.listen(0)
c, addr = server_socket.accept()

cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    x = encode_image(frame).read()
    print(len(x))
    print(x)   
    c.send(struct.pack('<L', len(x)))
    c.send(x)
    data = c.recv(1024)
    if data == "q":
        break
    print(data.decode('utf-8'))

c.close()
server_socket.close()

Client.py(电脑端代码)

import socket
import cv2
import io
from PIL import Image
import numpy as np
import struct

cl_socket = socket.socket()
cl_socket.connect(('192.168.1.108', 8000))


def encode_image(frame):
    stream = io.BytesIO()
    frame = cv2.resize(frame, (640, 480))
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    frame = Image.fromarray(frame)
    frame.save(stream, format="JPEG")
    return stream.getvalue()

def decode_image(image_bytes):
    stream = io.BytesIO(image_bytes)
    return cv2.cvtColor(np.array(Image.open(stream)), cv2.COLOR_RGB2BGR)

while True:
    image_len = struct.unpack('<L', cl_socket.recv(struct.calcsize('<L')))[0]
    print(image_len)
    if not image_len:
        break
    image_bytes = io.BytesIO()
    image_bytes.write(cl_socket.recv(image_len))
    image_bytes.seek(0)
    print(image_bytes.getvalue())
    image_bytes.seek(0)
    x = decode_image(image_bytes.read())
    cv2.imshow('frame', x)
    cv2.waitKey(1)
    
cl_socket.close()

解决方案

问题根源

  1. socket接收不完整:cl_socket.recv(image_len)无法保证一次性接收指定长度的数据,网络传输中数据会被拆分为多个数据包,直接调用一次recv()只能拿到部分数据,导致接收字节数与发送的不一致。
  2. 缺少回传逻辑:电脑端处理完图像后没有回传数据,树莓派端的c.recv(1024)会一直阻塞,破坏循环流程。
  3. 发送可靠性不足:树莓派端用send()发送数据,无法确保所有数据都被成功送出,可能出现部分数据丢失。

修复步骤

1. 实现完整的图像接收逻辑

客户端需要循环调用recv(),直到接收到全部image_len字节的数据:

# 替换客户端接收图像数据的代码
image_bytes = io.BytesIO()
remaining = image_len
while remaining > 0:
    # 每次最多读取4096字节,避免缓冲区溢出
    chunk = cl_socket.recv(min(remaining, 4096))
    if not chunk:
        print("连接中断,未接收完整图像")
        break
    image_bytes.write(chunk)
    remaining -= len(chunk)
if remaining != 0:
    break

2. 添加数据回传逻辑

电脑端处理完图像后,向树莓派回传处理结果(示例为字符串,可替换为实际数据):

# 客户端显示图像后添加回传代码
cl_socket.send("图像处理完成".encode('utf-8'))

3. 优化树莓派端发送逻辑

使用sendall()替代send(),确保所有数据都被发送;同时合并长度信息与图像数据,减少系统调用:

# 替换树莓派端的发送代码
x = encode_image(frame).read()
# 打包长度+图像数据,一次性发送
data_package = struct.pack('<L', len(x)) + x
c.sendall(data_package)

4. 补充异常处理

添加摄像头读取、网络连接的异常捕获,避免程序直接崩溃:

  • 树莓派端检查摄像头是否正常打开,读取帧失败时跳过当前循环;
  • 两端用try-except处理ConnectionResetError等网络异常。

修复后的完整代码

修复后的Server.py

import socket
import cv2
import io
from PIL import Image
import numpy as np
import struct

def encode_image(frame):
    stream = io.BytesIO()
    frame = cv2.resize(frame, (640, 480))
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    frame = Image.fromarray(frame)
    frame.save(stream, format="JPEG")
    stream.seek(0)
    return stream

def decode_image(image_bytes):
    stream = io.BytesIO(image_bytes)
    return cv2.cvtColor(np.array(Image.open(stream)), cv2.COLOR_RGB2BGR)

server_socket = socket.socket()
server_socket.bind(('0.0.0.0', 8000))
server_socket.listen(0)
print("等待客户端连接...")
try:
    c, addr = server_socket.accept()
    print(f"客户端已连接:{addr}")
    
    cap = cv2.VideoCapture(0)
    if not cap.isOpened():
        print("无法打开摄像头")
        exit()
    
    while True:
        ret, frame = cap.read()
        if not ret:
            print("读取摄像头失败")
            continue
        
        x = encode_image(frame).read()
        print(f"发送图像长度:{len(x)}")
        # 合并长度与图像数据,用sendall确保发送完整
        data_package = struct.pack('<L', len(x)) + x
        c.sendall(data_package)
        
        try:
            # 接收客户端回传数据
            data = c.recv(1024)
            if not data:
                print("客户端断开连接")
                break
            recv_str = data.decode('utf-8')
            if recv_str == "q":
                print("收到退出指令")
                break
            print(f"收到客户端回传:{recv_str}")
        except ConnectionResetError:
            print("客户端异常断开")
            break
        
finally:
    cap.release()
    c.close()
    server_socket.close()
    print("连接已关闭")

修复后的Client.py

import socket
import cv2
import io
from PIL import Image
import numpy as np
import struct

def encode_image(frame):
    stream = io.BytesIO()
    frame = cv2.resize(frame, (640, 480))
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    frame = Image.fromarray(frame)
    frame.save(stream, format="JPEG")
    return stream.getvalue()

def decode_image(image_bytes):
    stream = io.BytesIO(image_bytes)
    return cv2.cvtColor(np.array(Image.open(stream)), cv2.COLOR_RGB2BGR)

cl_socket = socket.socket()
try:
    cl_socket.connect(('192.168.1.108', 8000))
    print("已连接到树莓派")
    
    while True:
        # 接收图像长度
        len_data = cl_socket.recv(struct.calcsize('<L'))
        if not len_data:
            print("树莓派断开连接")
            break
        image_len = struct.unpack('<L', len_data)[0]
        print(f"接收图像长度:{image_len}")
        
        # 循环接收完整图像数据
        image_bytes = io.BytesIO()
        remaining = image_len
        while remaining > 0:
            chunk = cl_socket.recv(min(remaining, 4096))
            if not chunk:
                print("连接中断,未接收完整图像")
                remaining = -1
                break
            image_bytes.write(chunk)
            remaining -= len(chunk)
        
        if remaining != 0:
            break
        
        # 解码并显示图像
        image_bytes.seek(0)
        x = decode_image(image_bytes.read())
        cv2.imshow('树莓派摄像头画面', x)
        
        # 回传处理结果
        cl_socket.send("图像处理完成".encode('utf-8'))
        
        # 按q键退出
        if cv2.waitKey(1) & 0xFF == ord('q'):
            cl_socket.send("q".encode('utf-8'))
            break
        
finally:
    cv2.destroyAllWindows()
    cl_socket.close()
    print("连接已关闭")

内容的提问来源于stack exchange,提问作者Emir Ünalan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 09:40:57