Передача видео в реальном времени через сеть в python opencv

Я пытаюсь отправить видеокадр в режиме реального времени, который ловлю своей камерой, на сервер и обработать их. Я использую 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).

Ответ 1

Немного вещей:

  • используйте sendall вместо send, поскольку вам не гарантировано, что все будет отправлено за один раз.
  • pickle подходит для сериализации данных, но вы должны сделать протокол вы владеете сообщениями, которые вы обмениваете между клиентом и сервером, это вы заранее можете узнать количество данных для чтения для рассыпания (см. ниже)
  • для 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)

Вы можете, вероятно, оптимизировать все это (меньше копирования, используя буферный интерфейс и т.д.), но по крайней мере вы можете получить эту идею.

Ответ 2

После нескольких месяцев поиска в Интернете, это то, что я придумал, я аккуратно упаковал его в классы, с юнит-тестами и документацией, как 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

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

Ответ 3

Я изменил код с @mguijarr для работы с Python 3. Изменения, внесенные в код:

  • data теперь является байтовым литералом вместо строкового литерала
  • Изменил "H" на "L" для отправки кадров большего размера. На основании документации теперь мы можем отправлять кадры размером 2 ^ 32 вместо 2 ^ 16.

Server.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)

Client.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)

Ответ 4

Я сделал это, чтобы работать на моем 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

Ответ 5

Я немного опоздал, но моя мощная и многопоточная библиотека Python VidGear Video Processing теперь предоставляет 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. Конец клиента: (Пример Bare-Minimum)

Затем откройте другой терминал в той же системе и выполните следующий код 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()

В настоящее время NetGear поддерживает два шаблона обмена сообщениями zmq.PAIR: то есть zmq.PAIR и zmq.REQ and zmq.REP Поддерживаются следующие протоколы: 'tcp', 'upd', 'pgm', 'inproc', 'ipc'

Более подробное использование можно найти здесь: https://github.com/abhiTronix/vidgear/wiki/NetGear