【问题标题】:Sending live video frame over network in python opencv在 python opencv 中通过网络发送实时视频帧
【发布时间】:2015-09-08 09:09:36
【问题描述】:

我正在尝试将我用相机捕捉到的实时视频帧发送到服务器并进行处理。我使用 opencv 进行图像处理,使用 python 作为语言。这是我的代码

client_cv.py

import cv2
import numpy as np
import socket
import sys
import pickle
cap=cv2.VideoCapture(0)
clientsocket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
clientsocket.connect(('localhost',8089))
while True:
    ret,frame=cap.read()
    print sys.getsizeof(frame)
    print frame
    clientsocket.send(pickle.dumps(frame))

server_cv.py

import socket
import sys
import cv2
import pickle
import numpy as np
HOST=''
PORT=8089

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
print 'Socket created'

s.bind((HOST,PORT))
print 'Socket bind complete'
s.listen(10)
print 'Socket now listening'

conn,addr=s.accept()

while True:
    data=conn.recv(80)
    print sys.getsizeof(data)
    frame=pickle.loads(data)
    print frame
    cv2.imshow('frame',frame)

这段代码给了我文件结束错误,这是合乎逻辑的,因为数据总是不断地进入服务器,而 pickle 不知道何时完成。我在互联网上的搜索让我使用了泡菜,但到目前为止它不起作用。

注意:我将 conn.recv 设置为 80,因为这是我说 print sys.getsizeof(frame) 时得到的数字。

【问题讨论】:

  • 宁可使用 cv2.imencode() / cv2.imdecode() 而不是pickle
  • 任何尝试过此直播的人,是否可以将其发送到服务器?还是在本地处理框架更好?比如连续的face_recognition,一检测到人脸就应该ping一下?

标签: python opencv numpy


【解决方案1】:

几件事:

  • 请使用sendall 而不是send,因为您不能保证一次性发送所有内容
  • pickle 可以用于数据序列化,但您必须制定一个协议 您拥有您在客户端和服务器之间交换的消息,这 您可以提前知道要读取的数据量以进行 unpickling 的方式(请参阅 下面)
  • 对于recv,如果您收到大块,您将获得更好的性能,因此将 80 替换为 4096 甚至更多
  • 注意sys.getsizeof:它返回内存中对象的大小,不是 与通过网络发送的字节的大小(长度)相同;为一个 Python字符串这两个值根本不一样
  • 请注意您发送的帧的大小。下面的代码支持最大为 65535 的帧。如果您有更大的帧,请将“H”更改为“L”。

协议示例:

client_cv.py

import cv2
import numpy as np
import socket
import sys
import pickle
import struct ### new code
cap=cv2.VideoCapture(0)
clientsocket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
clientsocket.connect(('localhost',8089))
while True:
    ret,frame=cap.read()
    data = pickle.dumps(frame) ### new code
    clientsocket.sendall(struct.pack("H", len(data))+data) ### new code

server_cv.py

import socket
import sys
import cv2
import pickle
import numpy as np
import struct ## new

HOST=''
PORT=8089

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
print 'Socket created'

s.bind((HOST,PORT))
print 'Socket bind complete'
s.listen(10)
print 'Socket now listening'

conn,addr=s.accept()

### new
data = ""
payload_size = struct.calcsize("H") 
while True:
    while len(data) < payload_size:
        data += conn.recv(4096)
    packed_msg_size = data[:payload_size]
    data = data[payload_size:]
    msg_size = struct.unpack("H", packed_msg_size)[0]
    while len(data) < msg_size:
        data += conn.recv(4096)
    frame_data = data[:msg_size]
    data = data[msg_size:]
    ###

    frame=pickle.loads(frame_data)
    print frame
    cv2.imshow('frame',frame)

您可能可以对所有这些进行很多优化(减少复制,使用缓冲区接口等),但至少您可以理解。

【讨论】:

  • len(data) 是多少?尝试使用 L 而不是 H - H 表示无符号短
  • 是的,当我将 H 更改为 L 时,它成功了。现在,当我说打印帧时,服务器端会打印数组。最后一个问题是修复 cv2.imread('frame',frame) 因为它没有打开任何窗口也没有给出任何错误,但我想我可以自己修复它。感谢您的帮助,非常感谢。
  • 此代码不适用于 Python 3,尽管它与 python 2 完美兼容。
  • @Saikat 我很乐意将 Python 3 版本添加到答案中,或者至少发布您自己的 Python 3 答案以帮助他人:)
  • 能否请您发布 Python 3 版本?
【解决方案2】:

经过几个月的互联网搜索,这是我想出的,我已经将它整齐地打包成类,单元测试和文档为SmoothStream 看看,这是我唯一简单且有效的流媒体版本到处都能找到。

我使用了这段代码并将我的代码包裹起来。

查看器.py

import cv2
import zmq
import base64
import numpy as np

context = zmq.Context()
footage_socket = context.socket(zmq.SUB)
footage_socket.bind('tcp://*:5555')
footage_socket.setsockopt_string(zmq.SUBSCRIBE, np.unicode(''))

while True:
    try:
        frame = footage_socket.recv_string()
        img = base64.b64decode(frame)
        npimg = np.fromstring(img, dtype=np.uint8)
        source = cv2.imdecode(npimg, 1)
        cv2.imshow("Stream", source)
        cv2.waitKey(1)

    except KeyboardInterrupt:
        cv2.destroyAllWindows()
        break

Streamer.py

import base64
import cv2
import zmq

context = zmq.Context()
footage_socket = context.socket(zmq.PUB)
footage_socket.connect('tcp://localhost:5555')

camera = cv2.VideoCapture(0)  # init the camera

while True:
    try:
        grabbed, frame = camera.read()  # grab the current frame
        frame = cv2.resize(frame, (640, 480))  # resize the frame
        encoded, buffer = cv2.imencode('.jpg', frame)
        jpg_as_text = base64.b64encode(buffer)
        footage_socket.send(jpg_as_text)

    except KeyboardInterrupt:
        camera.release()
        cv2.destroyAllWindows()
        break

【讨论】:

  • 将 jpeg 数据编码为 base64 将有效负载乘以 3 左右...为什么不发送原始 jpeg 二进制文件?
  • 老实说,我不知道该怎么做,你能告诉我怎么做吗?将numpy数组转换为字符串?
  • 我们的朋友@mguijarr 注意到的是,使用 imencode + b64encode 对帧进行两次编码所产生的开销。如果您关心性能并且您的流媒体只需将图像发送到客户端/查看器。我建议您直接发送帧原始数据。然后,在您的客户端/查看器端,您只需要根据需要将原始帧编码为 jpeg。
  • 在 b64decode 期间收到“TypeError: Incorrect padding”。
【解决方案3】:

我将代码从 @mguijarr 更改为使用 Python 3。对代码所做的更改:

  • data 现在是 byte literal 而不是字符串文字
  • 将“H”更改为“L”以发送更大的帧大小。基于the documentation,我们现在可以发送大小为 2^32 的帧,而不仅仅是 2^16。

服务器.py

import pickle
import socket
import struct

import cv2

HOST = ''
PORT = 8089

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print('Socket created')

s.bind((HOST, PORT))
print('Socket bind complete')
s.listen(10)
print('Socket now listening')

conn, addr = s.accept()

data = b'' ### CHANGED
payload_size = struct.calcsize("L") ### CHANGED

while True:

    # Retrieve message size
    while len(data) < payload_size:
        data += conn.recv(4096)

    packed_msg_size = data[:payload_size]
    data = data[payload_size:]
    msg_size = struct.unpack("L", packed_msg_size)[0] ### CHANGED

    # Retrieve all data based on message size
    while len(data) < msg_size:
        data += conn.recv(4096)

    frame_data = data[:msg_size]
    data = data[msg_size:]

    # Extract frame
    frame = pickle.loads(frame_data)

    # Display
    cv2.imshow('frame', frame)
    cv2.waitKey(1)

客户端.py

import cv2
import numpy as np
import socket
import sys
import pickle
import struct

cap=cv2.VideoCapture(0)
clientsocket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
clientsocket.connect(('localhost',8089))

while True:
    ret,frame=cap.read()
    # Serialize frame
    data = pickle.dumps(frame)

    # Send message length first
    message_size = struct.pack("L", len(data)) ### CHANGED

    # Then data
    clientsocket.sendall(message_size + data)

【讨论】:

  • 我认为这是一种更好的做事方式!如果您愿意向github.com/CT83/SmoothStream 提交 PR,那么许多其他人也会从中受益。
  • 目前,我似乎对 ZeroMQ 有不必要的依赖,你的代码解决了这个问题!
  • 请注意,“L”并非所有架构都相同,如果您想让 Raspberry Pi 向 PC 发送消息,您可以改用“=L”。
  • 谢谢@Charles-ÉdouardCoste,这是救命稻草,除非我阅读您的评论,否则当我尝试从 Raspberry Pi 发送数据时,我不知道为什么数据大小不同。非常感谢。
【解决方案4】:

我有点晚了,但我强大的线程VidGear 视频处理python 库现在提供NetGear API,它专门用于通过网络在互连系统之间实时同步传输视频帧。这是一个例子:

A.服务器端:(Bare-Minimum 示例)

打开您喜欢的终端并执行以下 python 代码:

注意: 您可以随时在服务器端和客户端通过在服务器端按键盘上的 [Ctrl+c] 来结束流式传输!

# import libraries
from vidgear.gears import VideoGear
from vidgear.gears import NetGear

stream = VideoGear(source='test.mp4').start() #Open any video stream
server = NetGear() #Define netgear server with default settings

# infinite loop until [Ctrl+C] is pressed
while True:
    try: 
        frame = stream.read()
        # read frames

        # check if frame is None
        if frame is None:
            #if True break the infinite loop
            break

        # do something with frame here

        # send frame to server
        server.send(frame)

    except KeyboardInterrupt:
        #break the infinite loop
        break

# safely close video stream
stream.stop()
# safely close server
writer.close()

B.客户端:(最简单的例子)

然后在同一系统上打开另一个终端并执行以下python代码并查看输出:

# import libraries
from vidgear.gears import NetGear
import cv2

#define netgear client with `receive_mode = True` and default settings
client = NetGear(receive_mode = True)

# infinite loop
while True:
    # receive frames from network
    frame = client.recv()

    # check if frame is None
    if frame is None:
        #if True break the infinite loop
        break

    # do something with frame here

    # Show output window
    cv2.imshow("Output Frame", frame)

    key = cv2.waitKey(1) & 0xFF
    # check for 'q' key-press
    if key == ord("q"):
        #if 'q' key-pressed break out
        break

# close output window
cv2.destroyAllWindows()
# safely close client
client.close()

更高级的用法和相关文档可以在这里找到:https://github.com/abhiTronix/vidgear/wiki/NetGear

【讨论】:

  • 感谢您实现了如此强大的库。我想在将帧发送到服务器后收到响应。您能否提供代码 sn-ps 以从服务器获得响应。谢谢
  • @Balavenkatesh 已经在我们的gitter 社区频道上回复了您。
  • 谢谢。我明白了。
【解决方案5】:

正如@Rohan Sawant 所说,我使用 zmq 库而不使用 base64 编码。这是新代码

Streamer.py

import base64
import cv2
import zmq
import numpy as np
import time

context = zmq.Context()
footage_socket = context.socket(zmq.PUB)
footage_socket.connect('tcp://192.168.1.3:5555')

camera = cv2.VideoCapture(0)  # init the camera

while True:
        try:
                grabbed, frame = camera.read()  # grab the current frame
                frame = cv2.resize(frame, (640, 480))  # resize the frame
                encoded, buffer = cv2.imencode('.jpg', frame)
                footage_socket.send(buffer)


        except KeyboardInterrupt:
                camera.release()
                cv2.destroyAllWindows()
                break

查看器.py

import cv2
import zmq
import base64
import numpy as np

context = zmq.Context()
footage_socket = context.socket(zmq.SUB)
footage_socket.bind('tcp://*:5555')
footage_socket.setsockopt_string(zmq.SUBSCRIBE, np.unicode(''))

while True:
    try:
        frame = footage_socket.recv()
        npimg = np.frombuffer(frame, dtype=np.uint8)
        #npimg = npimg.reshape(480,640,3)
        source = cv2.imdecode(npimg, 1)
        cv2.imshow("Stream", source)
        cv2.waitKey(1)

    except KeyboardInterrupt:
        cv2.destroyAllWindows()
        break

【讨论】:

    【解决方案6】:

    最近我发布了 imagiz 包,用于使用 OpenCV 和 ZMQ 通过网络进行快速且无阻塞的实时视频流。

    https://pypi.org/project/imagiz/

    客户:

    import imagiz
    import cv2
    
    
    client=imagiz.Client("cc1",server_ip="localhost")
    vid=cv2.VideoCapture(0)
    encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 90]
    
    while True:
        r,frame=vid.read()
        if r:
            r, image = cv2.imencode('.jpg', frame, encode_param)
            client.send(image)
        else:
            break
    

    服务器:

    import imagiz
    import cv2
    
    server=imagiz.Server()
    while True:
        message=server.recive()
        frame=cv2.imdecode(message.image,1)
        cv2.imshow("",frame)
        cv2.waitKey(1)
    

    【讨论】:

      【解决方案7】:

      我已经让它在我的 MacOS 上运行。

      我使用来自@mguijarr 的代码并将 struct.pack 从“H”更改为“L”。

      # Server.py:
      import socket
      import sys
      import cv2
      import pickle
      import numpy as np
      import struct ## new
      
      
      HOST=''
      PORT=8089
          
      s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
      print 'Socket created'
          
      s.bind((HOST,PORT))
      print 'Socket bind complete'
      s.listen(10)
      print 'Socket now listening'
          
      conn,addr=s.accept()
          
      # new
      data = ""
      payload_size = struct.calcsize("L") 
      while True:
          while len(data) < payload_size:
              data += conn.recv(4096)
          packed_msg_size = data[:payload_size]
          data = data[payload_size:]
          msg_size = struct.unpack("L", packed_msg_size)[0]
          while len(data) < msg_size:
              data += conn.recv(4096)
          frame_data = data[:msg_size]
          data = data[msg_size:]
          
          
          frame=pickle.loads(frame_data)
          print frame
          cv2.imshow('frame',frame)
              
          key = cv2.waitKey(10)
          if (key == 27) or (key == 113):
              break
          
      cv2.destroyAllWindows()
      
      # Client.py
      import cv2
      import numpy as np
      import socket
      import sys
      import pickle
      import struct ### new code
      
      
      cap=cv2.VideoCapture(0)
      clientsocket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
      clientsocket.connect(('localhost',8089))
      
      while True:
          ret,frame=cap.read()
          data = pickle.dumps(frame) ### new code
          clientsocket.sendall(struct.pack("L", len(data))+data) ### new code
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-08-11
        • 2018-02-16
        • 2012-04-12
        • 2020-12-25
        • 2017-12-31
        相关资源
        最近更新 更多