진행하고 있는 프로젝트 중에 Webcam 영상 데이터를 서버로 보낼 필요가 있어 자료를 조사하기 시작했다.

 

그리고 자료를 조사하던 중 아래 사이트에서 Webcam 영상 데이터를 OpenCV를 이용해 보낼 수 있는 코드를 발견했다.

 

https://stackoverflow.com/questions/46912475/python-webcam-stream-over-udp-socket?rq=1

 

Python webcam stream over Udp socket

im trying to make a webcam stream over two computers in the same network, so i did some research on the internet and i found this client and server code this is the Client import socket import num...

stackoverflow.com

해당 코드는 Python2를 기준으로 작성되어있어서 Python3에서 사용할 수 있게 조금 코드를 변경했다. 아래는 변경한 코드이다.

 

Client

import socket
import cv2

UDP_IP = '127.0.0.1'
UDP_PORT = 9505

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    d = frame.flatten()
    s = d.tostring()

    for i in range(20):
        sock.sendto(s[i*46080:(i+1)*46080], (UDP_IP, UDP_PORT))

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

 

Server

import socket
import numpy
import cv2

UDP_IP = "127.0.0.1"
UDP_PORT = 9505

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))

s = b''

while True:
    data, addr = sock.recvfrom(46080)
    s += data

    if len(s) == (46080 * 20):
        frame = numpy.fromstring(s, dtype=numpy.uint8)
        frame = frame.reshape(480, 640, 3)
        cv2.imshow("frame", frame)
        s = b''

        if cv2.waitKey(1) & 0xFF == ord('q'):
            cv2.destroyAllWindows()
            break

 

※ 참고로 위의 코드에서 46080이 자주 나오는데 이것이 의미하는 바는 다음과 같다.

Client의 Webcam에서 생성하는 각 프레임은 640x480 RGB 픽셀을 가지는데 이것의 실제 데이터 크기는 640 x 480 x 3 = 921,600 Byte이다. 이 때 3은 RGB 즉, 빨강, 초록,  파랑을 나타내기 위해 사용하는 것이다.

 

그런데 UDP는 한번에 데이터를 65,535 Byte 까지 보낼 수 있어 위의 921,600 Byte를 한 번에 보낼 수 없다. 그래서 데이터를 나눠서 보내야하는데 해당 코드에서는 921,600 Byte를 20으로 나눈 46,080 Byte를 보내주고 있다. 

 

그래서 46080이 코드에서 계속 나오는 것이다. 

 

그래서 만약 OpenCV에서 생성하는 프레임이 다르다면 그에 맞게 고쳐주면 된다.

 

실행해보면 실행은 되나 영상이 조금씩 내려가는 현상을 볼 수 있는데 원인은 UDP 프로토콜의 특성 때문이다. 서버로 가는 데이터 중 일부를 못 받아도 그냥 무시하고 데이터의 순서가 잘못되어있더라도 데이터를 받는 순서대로 이미지를 생성하기 떄문이다.

 

그래서 이를 해결하기 위해 코드를 다음과 같이 수정했다.

 

Client

import socket
import cv2

UDP_IP = '127.0.0.1'
UDP_PORT = 9505

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    d = frame.flatten()
    s = d.tostring()

    for i in range(20):
        sock.sendto(bytes([i]) + s[i*46080:(i+1)*46080], (UDP_IP, UDP_PORT))

 

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

 

Server

import socket
import numpy
import cv2

UDP_IP = "127.0.0.1"
UDP_PORT = 9505

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))

s = [b'\xff' * 46080 for x in range(20)]

fourcc = cv2.VideoWriter_fourcc(*'DIVX')
out = cv2.VideoWriter('output.avi', fourcc, 25.0, (640, 480))

while True:
    picture = b''

    data, addr = sock.recvfrom(46081)
    s[data[0]] = data[1:46081]

    if data[0] == 19:
        for i in range(20):
            picture += s[i]

        frame = numpy.fromstring(picture, dtype=numpy.uint8)
        frame = frame.reshape(480, 640, 3)
        cv2.imshow("frame", frame)
        out.write(frame)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            cv2.destroyAllWindows()
            break

 

코드를 보면 Client에서 데이터를 보낼 때 앞의 1 Byte에 0 ~ 19까지의 숫자를 포함시켜 보냈는데 이는 Server에게 해당 데이터가 프레임의 어디 부분의 데이터인지 알려주기 위해서이다.

 

그리고 Server에서는 크기가 20인 프레임에 대한 리스트를 만들어 Client에게서 데이터를 받으면 앞의 1 Byte를 읽어 s[data[0]] = data[1:46081]와 같이 리스트의 해당 위치에 앞의 1 Byte를 제외한 나머지 데이터를 할당한다.

 

그리고 앞의 1 Byte가 19 즉, 프레임의 마지막 데이터이면 리스트의 데이터들을 전부 합쳐 이미지로 보여주게 했다.

 

그리고 이번에는 서버에서 보여지는 영상이 서버에 저장될 수 있도록 VideoWriter 함수를 사용해서 Server에 대한 코드가 있는 곳에 output.avi라는 파일로 저장하게 하였다.

 

실제 실행시켜보면 이전의 코드에서 발생한 영상이 조금씩 내려가는 현상이 사라진 것을 볼 수 있다.

'Language > Python' 카테고리의 다른 글

[Python] 파일 크기 구하기  (0) 2019.09.25

+ Recent posts