如何在Python中使用tcp套接字发送和接收网络摄像头流?

如何在Python中使用tcp套接字发送和接收网络摄像头流?,python,sockets,opencv,raspberry-pi3,socketserver,Python,Sockets,Opencv,Raspberry Pi3,Socketserver,我正试图重新创造。我有一台服务器(我的计算机)和一台客户端(我的raspberry pi)。与最初的项目不同的是,我尝试使用一个简单的网络摄像头而不是raspberry pi摄像头将图像从我的rpi传输到服务器。我知道我必须: 从相机获取opencv图像帧 将帧(numpy数组)转换为字节 将字节从客户端传输到服务器 将字节转换回帧和视图 请举例说明 self\u driver.py import SocketServer import threading import numpy as np

我正试图重新创造。我有一台服务器(我的计算机)和一台客户端(我的raspberry pi)。与最初的项目不同的是,我尝试使用一个简单的网络摄像头而不是raspberry pi摄像头将图像从我的rpi传输到服务器。我知道我必须:

  • 从相机获取opencv图像帧
  • 将帧(numpy数组)转换为字节
  • 将字节从客户端传输到服务器
  • 将字节转换回帧和视图
  • 请举例说明

    self\u driver.py

    import SocketServer
    import threading
    import numpy as np
    import cv2
    import sys
    
    
    ultrasonic_data = None
    
    #BaseRequestHandler is used to process incoming requests
    class UltrasonicHandler(SocketServer.BaseRequestHandler):
    
        data = " "
    
        def handle(self):
    
            while self.data:
                self.data = self.request.recv(1024)
                ultrasonic_data = float(self.data.split('.')[0])
                print(ultrasonic_data)
    
    
    #VideoStreamHandler uses streams which are file-like objects for communication
    class VideoStreamHandler(SocketServer.StreamRequestHandler):
    
        def handle(self):
            stream_bytes = b''
    
            try:
                stream_bytes += self.rfile.read(1024)
                image = np.frombuffer(stream_bytes, dtype="B")
                print(image.shape)
                cv2.imshow('F', image)
                cv2.waitKey(0)
    
            finally:
                cv2.destroyAllWindows()
                sys.exit()
    
    
    class Self_Driver_Server:
    
        def __init__(self, host, portUS, portCam):
            self.host = host
            self.portUS = portUS
            self.portCam = portCam
    
        def startUltrasonicServer(self):
            # Create the Ultrasonic server, binding to localhost on port 50001
            server = SocketServer.TCPServer((self.host, self.portUS), UltrasonicHandler)
            server.serve_forever()
    
        def startVideoServer(self):
            # Create the video server, binding to localhost on port 50002
            server = SocketServer.TCPServer((self.host, self.portCam), VideoStreamHandler)
            server.serve_forever()
    
        def start(self):
            ultrasonic_thread = threading.Thread(target=self.startUltrasonicServer)
            ultrasonic_thread.daemon = True
            ultrasonic_thread.start()
            self.startVideoServer()
    
    
    if __name__ == "__main__":
    
        #From SocketServer documentation
        HOST, PORTUS, PORTCAM = '192.168.0.18', 50001, 50002
        sdc = Self_Driver_Server(HOST, PORTUS, PORTCAM)
    
        sdc.start()
    
    import socket
    import time
    import cv2
    
    
    client_sock = socket.socket()
    client_sock.connect(('192.168.0.18', 50002))
    #We are going to 'write' to a file in 'binary' mode
    conn = client_sock.makefile('wb')
    
    try:
        cap = cv2.VideoCapture(0)
        cap.set(cv2.cv.CV_CAP_PROP_FRAME_WIDTH,320)
        cap.set(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT,240)
    
        start = time.time()
    
        while(cap.isOpened()):
            conn.flush()
            ret, frame = cap.read()
            byteImage = frame.tobytes()
            conn.write(byteImage)
    
    
    finally:
        finish = time.time()
        cap.release()
        client_sock.close()
        conn.close()
    
    视频客户端.py

    import SocketServer
    import threading
    import numpy as np
    import cv2
    import sys
    
    
    ultrasonic_data = None
    
    #BaseRequestHandler is used to process incoming requests
    class UltrasonicHandler(SocketServer.BaseRequestHandler):
    
        data = " "
    
        def handle(self):
    
            while self.data:
                self.data = self.request.recv(1024)
                ultrasonic_data = float(self.data.split('.')[0])
                print(ultrasonic_data)
    
    
    #VideoStreamHandler uses streams which are file-like objects for communication
    class VideoStreamHandler(SocketServer.StreamRequestHandler):
    
        def handle(self):
            stream_bytes = b''
    
            try:
                stream_bytes += self.rfile.read(1024)
                image = np.frombuffer(stream_bytes, dtype="B")
                print(image.shape)
                cv2.imshow('F', image)
                cv2.waitKey(0)
    
            finally:
                cv2.destroyAllWindows()
                sys.exit()
    
    
    class Self_Driver_Server:
    
        def __init__(self, host, portUS, portCam):
            self.host = host
            self.portUS = portUS
            self.portCam = portCam
    
        def startUltrasonicServer(self):
            # Create the Ultrasonic server, binding to localhost on port 50001
            server = SocketServer.TCPServer((self.host, self.portUS), UltrasonicHandler)
            server.serve_forever()
    
        def startVideoServer(self):
            # Create the video server, binding to localhost on port 50002
            server = SocketServer.TCPServer((self.host, self.portCam), VideoStreamHandler)
            server.serve_forever()
    
        def start(self):
            ultrasonic_thread = threading.Thread(target=self.startUltrasonicServer)
            ultrasonic_thread.daemon = True
            ultrasonic_thread.start()
            self.startVideoServer()
    
    
    if __name__ == "__main__":
    
        #From SocketServer documentation
        HOST, PORTUS, PORTCAM = '192.168.0.18', 50001, 50002
        sdc = Self_Driver_Server(HOST, PORTUS, PORTCAM)
    
        sdc.start()
    
    import socket
    import time
    import cv2
    
    
    client_sock = socket.socket()
    client_sock.connect(('192.168.0.18', 50002))
    #We are going to 'write' to a file in 'binary' mode
    conn = client_sock.makefile('wb')
    
    try:
        cap = cv2.VideoCapture(0)
        cap.set(cv2.cv.CV_CAP_PROP_FRAME_WIDTH,320)
        cap.set(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT,240)
    
        start = time.time()
    
        while(cap.isOpened()):
            conn.flush()
            ret, frame = cap.read()
            byteImage = frame.tobytes()
            conn.write(byteImage)
    
    
    finally:
        finish = time.time()
        cap.release()
        client_sock.close()
        conn.close()
    

    您不能将接收到的每个1-1024字节的缓冲区显示为图像;您必须将它们串联起来,并且仅在缓冲区完成时显示图像

    如果您知道带外图像将是固定数量的字节,您可以执行以下操作:

    IMAGE_SIZE = 320*240*3
    
    def handle(self):
        stream_bytes = b''
    
        try:
            stream_bytes += self.rfile.read(1024)
            while len(stream_bytes) >= IMAGE_SIZE:
                image = np.frombuffer(stream_bytes[:IMAGE_SIZE], dtype="B")
                stream_bytes = stream_bytes[IMAGE_SIZE:]
                print(image.shape)
                cv2.imshow('F', image)
                cv2.waitKey(0)
        finally:
            cv2.destroyAllWindows()
            sys.exit()
    
    如果您不知道这一点,则必须添加某种帧协议,例如在每个帧之前将帧大小作为uint32发送,这样服务器就可以知道每个帧要接收多少字节


    接下来,如果只是发送原始字节,而没有任何数据类型、形状或顺序信息,则需要将数据类型和形状信息嵌入到服务器中。如果您知道它应该是,例如,特定形状中以C顺序排列的字节,您可以手动执行此操作:

    image = np.frombuffer(stream_bytes, dtype="B").reshape(320, 240, 3)
    
    …但如果没有,您也必须将该信息作为帧协议的一部分发送

    或者,您可以发送缓冲区的
    pickle.dumps
    ,然后
    pickle.loads
    在另一侧,或者
    np.save
    BytesIO
    np.loads
    结果。无论哪种方式,都包括数据类型、形状、顺序和步幅信息以及原始字节,因此您不必担心它


    下一个问题是,只要显示一个图像,您就会退出。这真的是你想要的吗?如果没有…就不要那样做



    但这只会引发另一个问题。你真的想用这个
    cv.waitKey
    阻止整个服务器吗?您的客户正在捕获图像并尽可能快地发送它们;当然,您要么想让服务器在它们到达时立即显示它们,要么改变设计,让客户端只按需发送帧。否则,您只会得到一堆几乎相同的帧,然后在客户端被阻塞等待您耗尽缓冲区时,会有几秒钟的间隔,然后重复。

    回答得很好!