사람 수 카운트는 POST api 로 5초에 한 번씩 숫자만 보내주면 되는 형식
대표님 (ㄷㄷ) 요구사항으로 추가되는 기능이 CCTV 처럼 카메라가 비추는 화면을 확인할 수 있으면 좋을 것 같다고 하심
나는 그냥 사진 post 하던거랑 비슷하게 프레임 하나하나 post 하면 되는건가? 했는데 숫자 하나 보내는거랑은 다르다고 해서 ai 한테 무슨 차이인지 물어봤다
1) 프레임마다 POST 를 따로 날리면 요청마다 헤더 + 왕복시간 + 초당 10~15 프레임 뽑으려면 부담이 커짐
2) count POST 요청은 5초에 한번이라 가능하지만... 얘는 초당 몇 장씩 백엔드 -> 프론트 로 넘어가는거니까 트래픽 과도
-> 터널 URL 로 MJPEG 스트림을 순서대로 받는 개념은 똑같지만, 요청을 매번 새로 여는 것이 아니라 연결 하나를 계속 열어둔 채로 프레임을 이어붙여 보낸느 방식이 필요해짐 (요청-응답 오버헤드 TCP, TLS handshake 등이 X)
추가로 영상 스트림 주소 (camera_url) 이 노트북 로컬 IP 로 적혀있으면 같은 wifi 에 있는 사람만 배포서버 front 에 띄워지는 영상을 볼 수 있는 문제가 있기 때문에
로컬 스트림도 외부에서 접근 가능한 주소 필요 !!!!
결론: cloudflared 를 씁시다...
먼저 cloudflared 는 터널링 도구
내 로컬 서버 localhost 를 외부 접근 가능 공개 URL 로 노출시켜주는 역할
(1) cloudflared 가 내 컴퓨터에서 cloudflare edge server 로 outbound 연결을 먼저 맺고
(2) 연결을 하면 공개 URL 이 생김
(3) 그 URL 로 들어오는 요청을 내 localhost 포트로 그대로 relay
-> 내가 따로 inbound port 열 필요 X,
ngrok 이랑 비슷한 느낌
포트포워딩, 공유기 설정 없이 바로 사용할 수 있음 (공개 IP 없어도 , CGNAT 이어도 가능)
| [카메라] → [메인 스레드] ─ YOLO 검출 → count 백엔드로 POST └ 프레임 JPEG 인코딩 → 공유 버퍼 [서브 스레드(HTTP 서버)] ─ GET /video_feed → 공유 버퍼를 MJPEG로 스트리밍 |
설치방법...
최근에 맥이랑 윈도우 왔다갔다 쓰려니까 정신 나갈 것 같다........
둘 다 적어놓는걸로
# mac
brew install cloudflared
# windows
winget install --id Cloudflare.cloudflared
실행은
cloudflared tunnel --url http://localhost:8090
이렇게 하면 된다

이런 화면이 쭉쭉 내려가는데 (시간이 약간 소요됨)
첫번째 네모에 있는 quick tunnel 메시지 아래 https:// xxxx.trycloudflare.com 주소가 공개 URL 이다
이제 저 공개 URL 을 복사해서
python3 camera_daemon.py \
--source 0 \
--public-url https://ind-agent-poison-founded.trycloudflare.com/video_feed \
--backend https://xxx/api/crowd/live
이런식으로 명령을 보내주면 영상이 송출되는것을 확인할 수 있음
(참고로 이 때 생기는 공개 url 은 cloudflared 를 실행시키는 터미널이 꺼지면 같이 꺼지니까 매번 실행시켜서 새로운 값을 넣어줘야함)
사용한 코드는 대충 아래 부분을 필수적으로 넣어줬다
_latest_jpeg = None
_lock = threading.Lock()
# --- MJPEG 서버 (stdlib만, flask 불필요) ---
class MJPEGHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "multipart/x-mixed-replace; boundary=frame")
self.end_headers()
while True:
with _lock:
jpg = _latest_jpeg
if jpg is None:
time.sleep(0.05); continue
self.wfile.write(b"--frame\r\n")
self.wfile.write(b"Content-Type: image/jpeg\r\n\r\n")
self.wfile.write(jpg + b"\r\n")
# 카메라 루프를 안 막게 데몬 스레드로 서버 실행
server = ThreadingHTTPServer(("0.0.0.0", 8090), MJPEGHandler)
threading.Thread(target=server.serve_forever, daemon=True).start()
# --- 카메라 루프 (카메라 소유자) ---
while True:
ret, frame = cap.read()
results = model(frame, classes=[0]) # YOLO 사람 검출
count = len(results[0].boxes) # → 백엔드로 POST
ok, buf = cv2.imencode(".jpg", results[0].plot()) # 박스 그린 프레임
if ok:
with _lock:
_latest_jpeg = buf.tobytes() # 공유 버퍼 갱신
... 사실 AI 한테 짜달라고 하면 잘 짜준다
짠 코드를 적당히 읽어보고 어색한 부분 물어보면서 고치면 됨...!!!!

좀 밤티같긴 하지만 잘 나옴, 인식도 잘 되고 프레임도 나쁘지않게 들어온다
원래 목표였던 제 3의 client 도 잘 보이는지 확인


데이터 키고도 잘 보인다 화면 !!
돌리다보면 cloudflared 를 켜둔 터미널창에 아래와 같은 오류가 뜨는데 (가끔????)

| 시각 | 에러 | 의미 | 심각도 |
| 07:15 | connection refused | 8090에 서버 없음 | ❌ (근데 이제 해결됨) |
| 07:26 | stream canceled, code 0 | 브라우저가 스트림 연결 정상 종료 | ⚠️ 대체로 무해 |
요런거라고 하고
나는 자동으로 해결됐다 아마 초반에 렉걸려서 8090 서버 못 찾아서 생긴 문제인듯?!