在python opencv中通过网络发送实时视频帧

ata*_*nel 15 python opencv numpy

我正在尝试将我用相机捕获的实时视频帧发送到服务器并处理它们.我是用于图像处理的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))
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

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

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

mgu*_*arr 15

一些事情:

  • 使用sendall而不是send因为你不能保证一切都会一次发送
  • pickle 对于数据序列化是可以的,但是你必须为你在客户端和服务器之间交换的消息制作自己的协议,这样你就可以提前知道要读取的数据量(见下文)
  • 对于recv如果您收到大块,你会得到更好的性能,所以4096或更更换80
  • 要注意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
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

你可以大大优化所有这些(减少复制,使用缓冲接口等),但至少你可以得到这个想法.

  • @Saikat我很乐意将Python 3版本添加到答案中,或者至少发布你自己的Python 3答案来帮助其他人:) (2认同)
  • 您能否发布Python 3版本? (2认同)

nar*_*dyt 8

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

  • data现在是字节文字而不是字符串文字
  • 将“H”更改为“L”以发送更大的帧尺寸。根据文档,我们现在可以发送大小为 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)
Run Code Online (Sandbox Code Playgroud)

客户端.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)
Run Code Online (Sandbox Code Playgroud)

  • 我认为这是一种更好的做事方式!如果您愿意向 https://github.com/CT83/SmoothStream/ 提交 PR,那么其他许多人也会从中受益。 (3认同)
  • 请注意,“L”并非所有架构都相同,如果您想让 Raspberry Pi 向 PC 发送消息,您可以使用“=L”。 (2认同)

Roh*_*ant 5

经过几个月的互联网搜索,这就是我想出来的,我把它整齐地打包成了类,单元测试和文档作为SmoothStream检查出来,它是我能找到的唯一简单且有效的流媒体版本.

我用这个代码把它包裹起来.

Viewer.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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

  • 将 jpeg 数据编码为 base64 会将有效负载乘以 3 左右……为什么不发送原始 jpeg 二进制文件? (2认同)