1

我使用 python socket 在我的 Raspberry Pi 3 (Raspbian) 上创建了一个服务器,并在我的笔记本电脑 (Windows 10) 上创建了一个客户端。服务器以 10fps 的速率将图像流式传输到笔记本电脑,如果我推送它可以达到 15fps。问题是当我希望笔记本电脑根据图像发回命令时,帧速率急剧下降到 3fps。过程是这样的:

Pi 发送 img => 笔记本电脑接收 img => 快速处理 => 根据处理结果发送命令 => Pi 接收命令,打印它 => Pi 发送 img => ...

每帧的处理时间不会导致这种情况(每帧最多0.02s),所以目前我不知道为什么帧率下降这么多。图像相当大,大约 200kB,命令只是一个 3B 的短字符串。图像为矩阵形式,在发送前被腌制,而命令则按原样发送。

有人可以向我解释为什么发回这么短的命令会使帧率下降这么多吗?如果可能的话,这个问题的解决方案。我尝试制作了两台服务器,一台专门用于发送图像,一台用于接收命令,但结果是一样的。

服务器:

import socket
import pickle
import time
import cv2
import numpy as np
from picamera.array import PiRGBArray
from picamera import PiCamera
from SendFrameInOO import PiImageServer

def main():
    # initialize the server and time stamp
    ImageServer = PiImageServer()
    ImageServer2 = PiImageServer()
    ImageServer.openServer('192.168.0.89', 50009)
    ImageServer2.openServer('192.168.0.89', 50002)

    # Initialize the camera object
    camera = PiCamera()
    camera.resolution = (320, 240)
    camera.framerate = 10 # it seems this cannot go higher than 10
                          # unless special measures are taken, which may
                          # reduce image quality
    camera.exposure_mode = 'sports' #reduce blur
    rawCapture = PiRGBArray(camera)

    # allow the camera to warmup
    time.sleep(1)

    # capture frames from the camera
    print('<INFO> Preparing to stream video...')
    timeStart = time.time()
    for frame in camera.capture_continuous(rawCapture, format="bgr",
                                           use_video_port = True):
        # grab the raw NumPy array representing the image, then initialize 
        # the timestamp and occupied/unoccupied text
        image = frame.array 
        imageData = pickle.dumps(image) 
        ImageServer.sendFrame(imageData) # send the frame data

        # receive command from laptop and print it
        command = ImageServer2.recvCommand()
        if command == 'BYE':
            print('BYE received, ending stream session...')
            break
        print(command)

        # clear the stream in preparation for the next one
        rawCapture.truncate(0) 

    print('<INFO> Video stream ended')
    ImageServer.closeServer()

    elapsedTime = time.time() - timeStart
    print('<INFO> Total elapsed time is: ', elapsedTime)

if __name__ == '__main__': main()

客户:

from SupFunctions.ServerClientFunc import PiImageClient
import time
import pickle
import cv2

def main():
    # Initialize
    result = 'STP'
    ImageClient = PiImageClient()
    ImageClient2 = PiImageClient()

    # Connect to server
    ImageClient.connectClient('192.168.0.89', 50009)
    ImageClient2.connectClient('192.168.0.89', 50002)
    print('<INFO> Connection established, preparing to receive frames...')
    timeStart = time.time()

    # Receiving and processing frames
    while(1):
        # Receive and unload a frame
        imageData = ImageClient.receiveFrame()
        image = pickle.loads(imageData)        

        cv2.imshow('Frame', image)
        key = cv2.waitKey(1) & 0xFF

        # Exit when q is pressed
        if key == ord('q'):
            ImageClient.sendCommand('BYE')
            break

        ImageClient2.sendCommand(result)

    ImageClient.closeClient()

    elapsedTime = time.time() - timeStart
    print('<INFO> Total elapsed time is: ', elapsedTime)
    print('Press any key to exit the program')
    #cv2.imshow('Picture from server', image)
    cv2.waitKey(0)  

if __name__ == '__main__': main()

PiImageServer 和 PiImageClient:

import socket
import pickle
import time

class PiImageClient:
    def __init__(self):
        self.s = None
        self.counter = 0

    def connectClient(self, serverIP, serverPort):
        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.s.connect((serverIP, serverPort))

    def closeClient(self):
        self.s.close()

    def receiveOneImage(self):
        imageData = b''
        lenData = self.s.recv(8)
        length = pickle.loads(lenData) # should be 921764 for 640x480 images
        print('Data length is:', length)
        while len(imageData) < length:
            toRead = length-len(imageData)
            imageData += self.s.recv(4096 if toRead>4096 else toRead)
            #if len(imageData)%200000 <= 4096:
            #    print('Received: {} of {}'.format(len(imageData), length))
        return imageData

    def receiveFrame(self):        
        imageData = b''
        lenData = self.s.recv(8) 
        length = pickle.loads(lenData)
        print('Data length is:', length)
        '''length = 921764 # for 640x480 images
        length = 230563 # for 320x240 images'''
        while len(imageData) < length:
            toRead = length-len(imageData)
            imageData += self.s.recv(4096 if toRead>4096 else toRead)
            #if len(imageData)%200000 <= 4096:
            #    print('Received: {} of {}'.format(len(imageData), length))
        self.counter += 1
        if len(imageData) == length: 
            print('Successfully received frame {}'.format(self.counter))                
        return imageData

    def sendCommand(self, command):
        if len(command) != 3:
            print('<WARNING> Length of command string is different from 3')
        self.s.send(command.encode())
        print('Command {} sent'.format(command))


class PiImageServer:
    def __init__(self):
        self.s = None
        self.conn = None
        self.addr = None
        #self.currentTime = time.time()
        self.currentTime = time.asctime(time.localtime(time.time()))
        self.counter = 0

    def openServer(self, serverIP, serverPort):
        print('<INFO> Opening image server at {}:{}'.format(serverIP,
                                                            serverPort))
        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.s.bind((serverIP, serverPort))
        self.s.listen(1)
        print('Waiting for client...')
        self.conn, self.addr = self.s.accept()
        print('Connected by', self.addr)

    def closeServer(self):
        print('<INFO> Closing server...')
        self.conn.close()
        self.s.close()
        #self.currentTime = time.time()
        self.currentTime = time.asctime(time.localtime(time.time()))
        print('Server closed at', self.currentTime)

    def sendOneImage(self, imageData):
        print('<INFO> Sending only one image...')
        imageDataLen = len(imageData)
        lenData = pickle.dumps(imageDataLen)
        print('Sending image length')
        self.conn.send(lenData)
        print('Sending image data')
        self.conn.send(imageData)

    def sendFrame(self, frameData):
        self.counter += 1
        print('Sending frame ', self.counter)
        frameDataLen = len(frameData)
        lenData = pickle.dumps(frameDataLen)        
        self.conn.send(lenData)        
        self.conn.send(frameData)

    def recvCommand(self):
        commandData = self.conn.recv(3)
        command = commandData.decode()
        return command
4

1 回答 1

0

我相信问题是双重的。首先,您正在序列化所有活动:服务器正在发送一个完整的图像,而不是继续发送下一个图像(这更符合“流”的定义),它正在停止,等待前一个的所有字节图像通过网络到达客户端,然后客户端接收图像的所有字节,解压缩它,发送响应,然后响应通过线路到达服务器。

您是否有理由需要他们像这样步调一致?如果不是,请尝试使两侧平行。让您的服务器创建一个单独的线程来侦听返回的命令(或仅用于select确定命令套接字何时可以接收某些内容)。

其次,您可能会被 Nagle 的算法 ( https://en.wikipedia.org/wiki/Nagle%27s_algorithm ) 所困扰,该算法旨在防止通过网络发送大量具有小负载(但开销很大)的数据包。因此,您的客户端内核已经获取了您的三字节命令数据并对其进行了缓冲,等待您在将数据发送到服务器之前提供更多数据(它最终还是会在延迟后发送)。要改变这一点,您需要TCP_NODELAY在客户端使用套接字选项(请参阅https://stackoverflow.com/a/31827588/1076479)。

于 2018-05-02T18:28:13.927 回答