[DIY 스마트팜] 파이썬 Flask + MJPEG + Cloudflare Tunnel을 이용한 간이 실시간 CCTV 구축


개요

USB 웹캠이 연결된 PC에서 영상을 캡처하여 Flask 서버를 통해 MJPEG 스트리밍으로 제공하고, Cloudflare Tunnel을 이용하여 외부에서 휴대폰으로 실시간 확인할 수 있는 간이 CCTV 시스템을 구축한다.

특징:

  • 무료

  • 포트포워딩 불필요

  • 공인 IP 불필요

  • 공유기 설정 불필요

  • 별도 앱 설치 불필요

  • 웹 브라우저만 있으면 접속 가능

  • 거의 실시간 영상 확인 가능


시스템 구성

USB Webcam
    ↓
OpenCV
    ↓
Flask MJPEG Server
    ↓
localhost:8000
    ↓
Cloudflare Tunnel
    ↓
https://xxxxx.trycloudflare.com
    ↓
휴대폰 브라우저

프로젝트 폴더 구조

camera_server/

└── camera_server.py

필요 패키지 설치

pip install flask
pip install opencv-python

확인:

python -c "import cv2; print(cv2.__version__)"

CCTV 서버 코드

from flask import Flask, Response
import cv2
from datetime import datetime

app = Flask(__name__)

cap = cv2.VideoCapture(0)

if not cap.isOpened():
    raise RuntimeError("카메라 열기 실패")

# Logitech C525 권장 설정
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

def generate_frames():

    while True:

        success, frame = cap.read()

        if not success:
            continue

        timestamp = datetime.now().strftime(
            "%Y-%m-%d %H:%M:%S"
        )

        cv2.putText(
            frame,
            timestamp,
            (20, 40),
            cv2.FONT_HERSHEY_SIMPLEX,
            1,
            (0, 255, 0),
            2,
            cv2.LINE_AA
        )

        ret, buffer = cv2.imencode(
            '.jpg',
            frame,
            [cv2.IMWRITE_JPEG_QUALITY, 80]
        )

        if not ret:
            continue

        frame_bytes = buffer.tobytes()

        yield (
            b'--frame\r\n'
            b'Content-Type: image/jpeg\r\n\r\n'
            + frame_bytes +
            b'\r\n'
        )

@app.route('/')
def index():

    return """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport"
      content="width=device-width,initial-scale=1">

<style>

html,body{
    margin:0;
    width:100%;
    height:100%;
    background:black;
}

img{
    width:100vw;
    height:100vh;
    object-fit:contain;
}

</style>
</head>
<body>

<img src="/video">

</body>
</html>
"""

@app.route('/video')
def video():

    return Response(
        generate_frames(),
        mimetype='multipart/x-mixed-replace; boundary=frame'
    )

if __name__ == '__main__':

    app.run(
        host='0.0.0.0',
        port=8000,
        threaded=True
    )

서버 실행

python camera_server.py

출력 예:

* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:8000
* Running on http://192.168.0.63:8000

로컬 확인

브라우저 접속:

http://localhost:8000

실시간 영상이 보이면 성공.


Cloudflare Tunnel 설치

다운로드:

Cloudflare Tunnel 다운로드

Windows:

cloudflared-windows-amd64.exe

Cloudflare Tunnel 실행

cloudflared-windows-amd64.exe tunnel --url http://localhost:8000

출력 예:

Your quick Tunnel has been created!

https://partly-flexibility-raid-experiences.trycloudflare.com

외부 접속

휴대폰 브라우저:

https://partly-flexibility-raid-experiences.trycloudflare.com

접속.

현재 PC 웹캠 영상이 실시간으로 표시된다.


실행 순서

창 1

python camera_server.py

창 2

cloudflared-windows-amd64.exe tunnel --url http://localhost:8000

장점

  • 이미지 파일 저장 불필요

  • SSD/HDD 수명 영향 거의 없음

  • 브라우저에서 바로 확인 가능

  • Firebase 불필요

  • 데이터베이스 불필요

  • 앱 개발 불필요

  • 무료


확장 아이디어

  • 움직임 감지(Motion Detection)

  • 사람 감지(YOLO)

  • 식물 성장 타임랩스

  • 온도/습도 표시

  • PLC 상태 표시

  • 다중 카메라 지원

  • QR 코드 접속

  • 홈 화면 바로가기(PWA)


MJPEG가 실시간 영상처럼 보이는 원리

많은 사람들이 처음 보면

"이게 영상 파일도 아닌데 왜 실시간으로 보이지?"

라는 의문을 갖는다.

실제로는 영상 스트리밍이 아니라 JPEG 이미지를 매우 빠르게 연속 전송하는 방식이다.


일반적인 웹 페이지

보통 브라우저는:

GET /image.jpg
    ↓
서버 응답
    ↓
연결 종료

과정을 수행한다.

즉 한 번 요청하고 끝난다.


MJPEG 방식

MJPEG는:

브라우저
    ↓
GET /video

서버
    ↓
연결 유지

상태를 계속 유지한다.

연결을 끊지 않는다.


서버는 같은 연결 안에서:

JPEG #1
JPEG #2
JPEG #3
JPEG #4
...

를 계속 흘려보낸다.


실제로는 다음과 같은 형태다.

--frame
Content-Type: image/jpeg

[첫 번째 JPEG]

--frame
Content-Type: image/jpeg

[두 번째 JPEG]

--frame
Content-Type: image/jpeg

[세 번째 JPEG]

브라우저는:

JPEG 수신
↓
화면 표시

JPEG 수신
↓
화면 교체

JPEG 수신
↓
화면 교체

를 반복한다.


결과적으로:

정지 이미지
+
정지 이미지
+
정지 이미지
+
정지 이미지

가 매우 빠르게 바뀌어

실시간 영상

처럼 보인다.


현재 프로젝트에서의 데이터 흐름

Logitech C525
    ↓
OpenCV

cap.read()

    ↓

현재 프레임

    ↓

JPEG 압축

cv2.imencode()

    ↓

Flask Response

    ↓

MJPEG Stream

    ↓

Cloudflare Tunnel

    ↓

휴대폰 브라우저

왜 최신 사진 방식보다 부드러운가?

이전 방식:

latest.jpg 저장
↓
브라우저 새로고침
↓
latest.jpg 재다운로드

1초마다 반복

실질적으로:

1 FPS

MJPEG 방식:

캡처
↓
즉시 전송
↓
즉시 표시

반복

보통:

10 ~ 30 FPS

가능


실제 CCTV와의 관계

많은 저가형 IP 카메라와 초기 네트워크 CCTV도 MJPEG 방식을 사용했다.

장점:

  • 구현 단순

  • 브라우저 호환성 좋음

  • 디코더 필요 없음

단점:

  • 대역폭 사용량 큼

하지만 개인용 스마트팜 CCTV나 간이 모니터링 용도로는 지금도 매우 실용적인 방식이다.

댓글

이 블로그의 인기 게시물

공압 속도 제어: 미터인 vs 미터아웃

[투자] ETF 투자 가이드 : 카테고리별 ETF 선택 전략

[아두이노] 가변저항(Potential Divider)과 전압분배(Voltage Divider)

[주식] 한국거래소(KRX) 데이터 API 입문 가이드

NPN, PNP 트랜지스터 차이점

PLC 출력 형태

제너 다이오드에 저항을 연결하는 이유

[PLC] 센서 NPN, PNP 출력 타입별 결선방법 (OMRON E2E-X 시리즈 3선식 배선)

공압회로 기호

전력(kW) 계산하기 (직류, 교류 단상, 교류 삼상)