본문 바로가기
Python

opencv streaming 카메라로 받아서 localhost에서 띄우기

by jennyiscoding 2024. 5. 30.

서버 

# 필요한 패키지 import
# SERVER
import socket # 소켓 프로그래밍에 필요한 API를 제공하는 모듈
import struct # 바이트(bytes) 형식의 데이터 처리 모듈
import pickle # 객체의 직렬화 및 역직렬화 지원 모듈
import cv2 # OpenCV(실시간 이미지 프로세싱) 모듈

# 서버 ip 주소 및 port 번호
ip = '127.0.0.1'
port = 5000

# 1. 소켓 객체 생성
server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

# 2. 바인딩
server_socket.bind((ip, port))

# 3. 접속 대기 
server_socket.listen(10)

# 4. 접속 수락(클라이언트 연결안되면 여기서 멈춰있음)
client_socket, address = server_socket.accept()

# 수신한 데이터를 넣을 버퍼(바이트 객체)
data_buffer = b""

# calcsize : 데이터의 크기(byte)
# - L : 부호없는 긴 정수(unsigned long) 8 bytes
# data_size = struct.calcsize("L")
data_size = struct.calcsize("Q")

# struct.calcsize("L")은 부호 없는 긴 정수(unsigned long)의 크기를 바이트 단위로 계산하는 함수
# 64비트 아키텍처를 사용하고 있다면 8바이트가 나온다. 

print(f'{data_size} 나는 data_size 8')
print(f'{data_buffer} 나는 data_buffer')

while True:
    # 설정한 데이터의 크기보다 버퍼에 저장된 데이터의 크기가 작은 경우
    # 현재까지 수신된 데이터의 크기가 기대되는 데이터의 크기보다 작을 때까지 계속해서 데이터를 수신하는 무한 루프입니다
    while len(data_buffer) < data_size:
        # 5. 데이터 수신
        data_buffer += client_socket.recv(4096)
        # 이 부분은 클라이언트 소켓에서 최대 4096바이트의 데이터를 수신하여 data_buffer에 추가하는 역할을 합니다
        # recv() 메서드는 소켓으로부터 데이터를 읽어오는 역할

    # 버퍼의 저장된 데이터 분할
    packed_data_size = data_buffer[:data_size]
    # print(f'data_buffer: {data_buffer}')
    print(f'packed_data_size: {packed_data_size}') # \x00\x00T\x0e\x80\x04\x95\x03
    print(f'packed_data_size int로: {int.from_bytes(packed_data_size, byteorder="big")}')
    data_buffer = data_buffer[data_size:]
    # print(f'data_buffer 끝 배열: {data_buffer}')
    
    # struct.unpack : 변환된 바이트 객체를 원래의 데이터로 반환
    # - > : 빅 엔디안(big endian)
    #   - 엔디안(endian) : 컴퓨터의 메모리와 같은 1차원의 공간에 여러 개의 연속된 대상을 배열하는 방법
    #   - 빅 엔디안(big endian) : 최상위 바이트부터 차례대로 저장
    # - L : 부호없는 긴 정수(unsigned long) 4 bytes 
    frame_size = struct.unpack(">Q", packed_data_size)[0]
    print(f'frame_size: {frame_size}') # 23215 출력됨
    
    # 프레임 데이터의 크기보다 버퍼에 저장된 데이터의 크기가 작은 경우
    while len(data_buffer) < frame_size:
        # 데이터 수신
        data_buffer += client_socket.recv(4096)
    
    # 프레임 데이터 분할
    frame_data = data_buffer[:frame_size]
    data_buffer = data_buffer[frame_size:]
    
    print("수신 프레임 크기 : {} bytes".format(frame_size))
    
    # loads : 직렬화된 데이터를 역직렬화
    # - 역직렬화(de-serialization) : 직렬화된 파일이나 바이트 객체를 원래의 데이터로 복원하는 것
    frame = pickle.loads(frame_data)
    
    # imdecode : 이미지(프레임) 디코딩
    # 1) 인코딩된 이미지 배열
    # 2) 이미지 파일을 읽을 때의 옵션
    #    - IMREAD_COLOR : 이미지를 COLOR로 읽음
    frame = cv2.imdecode(frame, cv2.IMREAD_COLOR)
    
    # 프레임 출력
    cv2.imshow('Frame', frame)
    
    # 'q' 키를 입력하면 종료
    key = cv2.waitKey(1) & 0xFF
    if key == ord("q"):
        break

# 6. 접속종료
client_socket.close()
server_socket.close()
print('연결 종료')

# 모든 창 닫기
cv2.destroyAllWindows()

 

 

클라이언트

from flask import Flask, render_template, Response
import cv2
import struct
import pickle
import socket
import timeit

app = Flask(__name__)

# OpenCV를 사용하여 동영상 프레임을 수신하여 브라우저에 스트리밍하는 함수
def gen_frames():
    ip = '127.0.0.1'
    port = 6000

    # 1. 소켓 생성
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
        # 2. 바인딩
        server_socket.bind((ip, port))
        # 3. 접속 대기
        print('대기중이요!')
        server_socket.listen(10)
        
        # 4. 클라이언트 연결 대기 및 데이터 수신
        client_socket, address = server_socket.accept()

        while True:
            start_t = timeit.default_timer()
            # 데이터 수신
            data_buffer = b""
            data_size = struct.calcsize(">Q")

            while len(data_buffer) < data_size:
                data_buffer += client_socket.recv(4096)
            packed_data_size = data_buffer[:data_size]
            frame_size = struct.unpack(">Q", packed_data_size)[0]
            data_buffer = data_buffer[data_size:]

            while len(data_buffer) < frame_size:
                data_buffer += client_socket.recv(4096)
            frame_data = data_buffer[:frame_size]
            data_buffer = data_buffer[frame_size:]

            frame = pickle.loads(frame_data)
            frame = cv2.imdecode(frame, cv2.IMREAD_COLOR)
            
            # 프레임을 JPEG 이미지로 변환
            ret, buffer = cv2.imencode('.jpg', frame)

            terminate_t = timeit.default_timer()
            FPS = int(1./(terminate_t - start_t ))
            print(f'현재 FPS: {FPS}')

            frame = buffer.tobytes()
            
            # 이미지를 브라우저에 전송
            yield (b'--frame\r\n'
                   b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

# Flask 애플리케이션 루트 경로를 설정하고 이미지를 스트리밍하는 함수를 렌더링하는 라우트를 추가합니다.
@app.route('/')
def index():
    return render_template('./index.html')

@app.route('/video_feed')
def video_feed():
    return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')

if __name__ == '__main__':
    app.run(debug=True)