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

Socket图像传输异常:Mac端无法识别树莓派发送的图像文件

解决Socket图像传输的两个问题:卡住与无法识别图像

问题根源

  1. 接收端卡住:TCP是流式协议,无数据边界,服务器无法判断图像传输何时结束,recv会一直阻塞等待数据;而客户端发送完图像后仍在等待服务器返回结果,导致双向死锁。
  2. BytesIO无法识别图像:写入图像数据后,BytesIO的文件指针停在末尾,Image.open从当前位置读取时无有效内容,自然无法识别图像格式。
  3. 原代码中setupConnection函数未传入服务器socket,存在未定义变量的bug,会直接导致运行报错。

修复方案

服务器端(MacBook)修改代码

import io
import socket
import os
from PIL import Image, ImageFile
import numpy as np
from skimage.color import rgb2gray
from skimage.transform import downscale_local_mean
from skimage.filters import unsharp_mask
import pickle

ImageFile.LOAD_TRUNCATED_IMAGES = True

loaded_model = pickle.load(open("best_classifier_model.pkl", 'rb'))

host = "my ip address"
port = 5000


def setupServer():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.setblocking(1)
    print("Socket created")
    try:
        s.bind((host, port))
    except socket.error as msg:
        print(msg)
    print("Socket bind complete")
    return s

# 修复:传入服务器socket作为参数
def setupConnection(s):
    s.listen(1) # 允许1个连接
    conn, address = s.accept()
    print("Connected to: " + address[0] + ":" + str(address[1]))
    return conn
     
try:  
    s = setupServer()
    while True:
        try:
            conn = setupConnection(s)
            while True:
                print("I'm starting")
               
                # 1. 先接收图像大小(4字节大端格式)
                size_data = conn.recv(4)
                if not size_data:
                    break
                image_size = int.from_bytes(size_data, byteorder='big')
                
                file_stream = io.BytesIO()
                received_size = 0
                
                # 2. 按指定大小接收图像数据,避免无限阻塞
                while received_size < image_size:
                    # 每次接收剩余大小和4096的较小值,避免带宽浪费
                    chunk_size = min(4096, image_size - received_size)
                    data = conn.recv(chunk_size)
                    if not data:
                        break
                    file_stream.write(data)
                    received_size += len(data)

                print("Image downloaded")
                
                # 3. 关键:将BytesIO指针移到起始位置
                file_stream.seek(0)
                img1 = Image.open(file_stream)
                img1.save("cerv.jpg", format="JPEG")
                img2= rgb2gray(img1)
                img3 = downscale_local_mean(img2, (10, 10))
                img4 = np.array(img3)
                img=unsharp_mask(img4, radius=20, amount=1)
                
                lx, ly=img.shape
                X=np.zeros((1, lx*ly))
                X=np.reshape(img, (1,lx*ly))
                dilation = loaded_model.predict(X)
                dil=str(dilation[0])
                conn.send(str.encode(dil))
                print(dil, " cm")
        except Exception as error:
            print(error)
            s.close()
            break
        
    s.close()

except KeyboardInterrupt:
    print("Server shutting down")
    s.close()

客户端(Raspberry Pi)修改代码

import socket
import os
from datetime import datetime
import threading

try:
        # 建立与服务器的连接
        host = "192.168.50.252"
        port = 5000
        s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
        s.connect((host, port))

        def my_function(s):
                # 每分钟执行一次
                now= datetime.now()
                current_time=now.strftime("%H_%M_%S")
                fname=f"image_{current_time}.jpg"
                image_path="/home/Documents/Data"
                file_path = os.path.join(image_path, fname)
                # 此处添加拍照逻辑
                file = open(file_path, "rb")

                # 1. 先发送图像大小(4字节大端格式)
                image_size = os.path.getsize(file_path)
                s.send(image_size.to_bytes(4, byteorder='big'))

                # 2. 发送图像数据
                image_data = file.read(4096)
                while image_data:
                        s.send(image_data)
                        image_data=file.read(4096)

                file.close()
                print("File sent")

                # 接收服务器返回的结果
                data=s.recv(4096)
                dilation=data.decode("utf-8")

                # 处理结果
                message=dilation + " cm"

                # 此处添加屏幕显示逻辑

        def run_function(s):
                # 修复:定时器参数传递错误,避免立即执行
                thread = threading.Timer(60.0, run_function, args=(s,))
                thread.start()
                my_function(s)

        run_function(s)

except Exception as e:
        print(f"Something went wrong: {e}")
        # OLED错误显示逻辑
        import busio
        from board import SCL, SDA
        import adafruit_ssd1306
        i2c = busio.I2C(SCL, SDA)
        disp = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
        disp.fill(0)
        disp.show()
        if 's' in locals():
                s.close()

修复说明

  • 解决卡住问题:通过先发送图像大小的方式,让服务器明确知道需要接收的数据总量,避免无限阻塞在recv调用上。
  • 解决图像无法识别问题:写入BytesIO后调用seek(0),将文件指针移到起始位置,确保Image.open能读取完整的图像数据。
  • 修复定时器bug:原代码中threading.Timer的参数传递错误,会导致定时器立即执行而非延迟60秒,修改为传递函数对象和参数元组。
  • 修复未定义变量bug:setupConnection函数添加服务器socket参数,避免运行时出现NameError。

内容的提问来源于stack exchange,提问作者Sandy Montgomery

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 10:57:32