Socket图像传输异常:Mac端无法识别树莓派发送的图像文件
解决Socket图像传输的两个问题:卡住与无法识别图像
问题根源
- 接收端卡住:TCP是流式协议,无数据边界,服务器无法判断图像传输何时结束,
recv会一直阻塞等待数据;而客户端发送完图像后仍在等待服务器返回结果,导致双向死锁。 - BytesIO无法识别图像:写入图像数据后,BytesIO的文件指针停在末尾,
Image.open从当前位置读取时无有效内容,自然无法识别图像格式。 - 原代码中
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
相关产品推荐
相关产品推荐

