Python ошибка сокета при отправке изображения. Не удается декодировать байт / неожиданный EOF - PullRequest
2 голосов
/ 05 апреля 2020

Я использую гнездо python для передачи изображений с камеры моего ноутбука (клиента) на мой raspberry pi (сервер). Вот как это происходит, я подключаюсь к розетке, а затем мой клиент ждет сигнала, чтобы сделать фотографию. Когда сигнал (с моего сервера) отправлен, мой клиент делает фотографию и отправляет следующие данные по порядку; Сначала длина числа, представляющего размер изображения (например, для 10000 байтов число равно 5, для 100000 байтов число равно 6 и т. Д.), Затем отправляет фактический размер и, наконец, отправляет фотографию в виде строки байтов. Процесс повторяется бесконечное количество раз

Client.py

import cv2
import socket
import os

def send_msg(s, msg):
    s.sendall(msg)


s = socket.socket()
port = 24999
ip = '192.168.1.3'
s.connect((ip, port))

video = cv2.VideoCapture(0)

s.recv(1) #Wait until data is sent
while True:
    _, img = video.read()
    img = cv2.resize(img, (0,0), fx = 0.4, fy = 0.4) #reduce image size
    cv2.imwrite("test.jpg", img) #save img
    size = os.path.getsize("test.jpg") #get image size
    img_str = cv2.imencode('.jpg', img)[1].tostring() #convert to bytes string
    sizenum = str(len(str(size))) #how many digits the image size contains
    send_msg(s, sizenum.encode())
    send_msg(s, str(size).encode('utf-8')) #send actual image size
    send_msg(s, img_str) #finally send the image
    s.recv(1) #Wait until data is sent

Server.py

import socket
import ast

def send_msg(client, msg):
    client.sendall(msg+b'\r\n')

def recvall(sock, n):
    # Helper function to recv n bytes or return None if EOF is hit
    data = bytearray()
    while len(data) < n:
        packet = sock.recv(n - len(data))
        if not packet:
            return None
        data.extend(packet)
    return data

def take_photo(cl):
    #RECEIVE LENGTH OF SIZE
    while True:
        lensize = cl.recv(1)
        lensize = lensize.decode('utf-8')
        if lensize != "":
            break
    print("Size is a",lensize,"-digit number")
    lensize = lensize.replace("\n","").replace(" ","").replace("\r","")
    #RECEIVE SIZE
    size_pic = recvall(cl,ast.literal_eval(lensize)).decode('utf-8')
    size_pic = size_pic.replace("\n","").replace(" ","").replace("\r","")
    print("Exact size is",size_pic)
    #RECEIVE PHOTO
    return lensize,size_pic,bytearray(recvall(cl,ast.literal_eval(size_pic)))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("192.168.1.3",24999))
s.listen(1)

while True:
    cl_image, addr = s.accept()
    break

while True:
    try:
        send_msg(cl_image, b"1") #Send signal
        size1, size2, photo = take_photo(cl_image)
        print(photo)

    except KeyboardInterrupt:
        print("error")
        s.close()

Проблема При запуске сервера проблема происходит через несколько секунд выполнения (иногда он запускается всего за 1 секунду до выдачи какого-либо исключения, а иногда запускается через 5 и более секунд)

Пример запуска 1 (сервер)

Size is a 5 -digit number
Exact size is 21263
7294 from 21263 Not all bytes were read
18974 from 21263 Not all bytes were read
Size is a 5 -digit number
Exact size is 21226
2915 from 21226 Not all bytes were read
4375 from 21226 Not all bytes were read
11675 from 21226 Not all bytes were read
18975 from 21226 Not all bytes were read
Size is a 5 -digit number
Exact size is 21412
2915 from 21412 Not all bytes were read
7295 from 21412 Not all bytes were read
14595 from 21412 Not all bytes were read
Size is a . -digit number
Traceback (most recent call last):
  File "sending_test.py", line 46, in <module>
    size1, size2, photo = take_photo(cl_image)
  File "sending_test.py", line 27, in take_photo
    size_pic = recvall(cl,ast.literal_eval(lensize)).decode('utf-8')
  File "/usr/lib/python3.7/ast.py", line 46, in literal_eval
    node_or_string = parse(node_or_string, mode='eval')
  File "/usr/lib/python3.7/ast.py", line 35, in parse
    return compile(source, filename, mode, PyCF_ONLY_AST)
  File "<unknown>", line 1
    .

Пример запуска 2 (сервер)

Size is a 5 -digit number
Exact size is 20653
7294 from 20653 Not all bytes were read
14594 from 20653 Not all bytes were read
18974 from 20653 Not all bytes were read
Size is a 5 -digit number
Exact size is 20595
2915 from 20595 Not all bytes were read
8755 from 20595 Not all bytes were read
10215 from 20595 Not all bytes were read
18975 from 20595 Not all bytes were read
Traceback (most recent call last):
  File "sending_test.py", line 46, in <module>
    size1, size2, photo = take_photo(cl_image)
  File "sending_test.py", line 21, in take_photo
    lensize = lensize.decode('utf-8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte

Я также вставил команду печати (фото) в l oop моего сервера, чтобы увидеть, что происходит и каков результат (см. последнюю строку по сравнению с другими)

Size is a 3 -digit number
Exact size is 828
bytearray(b'\xff\xd8\xff.......\xff\xd9')
Size is a 3 -digit number
Exact size is 831
bytearray(b'\xff\xd8\xff......\xff\xd9')
Size is a 3 -digit number
Exact size is 831
bytearray(b'\xff\xd8\xff.......\xff\xd9383')

Это означает, что он считывает (3) больше байтов, которые должен иметь (3 указывает на длину размера, а 83 - на размер изображения, что-то вроде (83x)

1 Ответ

1 голос
/ 08 апреля 2020

Исходя из комментариев, я сделал несколько изменений, как показано ниже:

  • Вместо того, чтобы записать JPEG на диск, а затем определить его размер, а затем снова закодировать его в память и преобразовать в строку и отправив ее, я просто кодирую JPEG в буфер памяти, получаю его размер и отправляю его

  • Вместо того, чтобы отправлять строку, чтобы сказать количество байтов во второй строке чтобы сказать количество байтов в видеокадре, я просто отправляю количество байтов в виде 4-байтового целого числа сетевого порядка. Это делает жизнь намного проще

Кажется, она работает очень надежно на моей машине.

Вот клиент:

#!/usr/bin/env python3

import cv2
import socket
import os
import struct

ip, port = '192.168.0.8', 24999
s = socket.socket()
s.connect((ip, port))

# Start video reader
video = cv2.VideoCapture(0)

while True:
    # Wait till data requested, as indicated by receipt of single byte
    s.recv(1)
    print('CLIENT: Image requested')

    # Read a frame of video and reduce size
    _, img = video.read()
    img = cv2.resize(img, (0,0), fx = 0.4, fy = 0.4)

    # JPEG-encode into memory buffer and get size
    _, buffer = cv2.imencode('.jpg', img)
    nBytes = buffer.size
    print(f'CLIENT: nBytes={nBytes}')

    # Send 4-byte network order frame size and image
    hdr = struct.pack('!i',nBytes)
    s.sendall(hdr)
    s.sendall(buffer)

Вот сервер:

#!/usr/bin/env python3

import time
import socket
import struct

def recvall(sock, n):
    # Helper function to recv n bytes or return None if EOF is hit
    data = bytearray()
    while len(data) < n:
        packet = sock.recv(n - len(data))
        if not packet:
            return None
        data.extend(packet)
    return data

def take_photo(cl):
    # Get header with number of bytes
    header = cl.recv(4)
    nBytes = struct.unpack('!i',header)[0]

    # Receive actual image
    img = recvall(cl, nBytes)
    return img

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("192.168.0.8",24999))
s.listen(1)

while True:
    cl_image, addr = s.accept()
    break

while True:
    try:
        # Request image by sending a single byte
        cl_image.sendall(b'1')
        photo = take_photo(cl_image)
        time.sleep(1)
        print(f'SERVER: photo received, {len(photo)} bytes')

    except KeyboardInterrupt:
        print("error")
        s.close()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...