Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

20 Commits

Folders and files

Repository files navigation

πŸ“Ή CCTV AI μ˜μƒ 뢄석 μ•±

μ˜¨λ””λ°”μ΄μŠ€ AI 기반 μ‹€μ‹œκ°„ 객체 감지 및 좔적 μ‹œμŠ€ν…œ

Android Java TensorFlow Lite Socket.IO


πŸ“‹ λͺ©μ°¨


🎯 κ°œμš”

Store Shield CCTV 앱은 Android 기반 μ˜¨λ””λ°”μ΄μŠ€ AI μ˜μƒ 뢄석 μ‹œμŠ€ν…œμœΌλ‘œ, 무인점포 λ‚΄ 고객과 μƒν’ˆμ˜ μ‹€μ‹œκ°„ 좔적 및 행동 뢄석을 μˆ˜ν–‰ν•©λ‹ˆλ‹€. Qualcomm AI Hub μ΅œμ ν™” λͺ¨λΈμ„ ν™œμš©ν•˜μ—¬ λͺ¨λ°”일 ν™˜κ²½μ—μ„œλ„ λΉ λ₯΄κ³  μ •ν™•ν•œ 객체 감지 및 좔적이 κ°€λŠ₯ν•©λ‹ˆλ‹€.

핡심 μ—­ν• 

  • μ‹€μ‹œκ°„ μ˜μƒ 뢄석: μΉ΄λ©”λΌλ‘œ λ§€μž₯ λ‚΄λΆ€λ₯Ό μ΄¬μ˜ν•˜λ©° AI둜 객체 감지
  • 고객 ID 좔적: μž…μž₯ν•œ κ³ κ°μ—κ²Œ 고유 IDλ₯Ό λΆ€μ—¬ν•˜κ³  이동 경둜 좔적
  • μƒν’ˆ 행동 감지: 고객이 μƒν’ˆμ„ μ§‘κ±°λ‚˜ λ‚΄λ €λ†“λŠ” 행동 μžλ™ 인식
  • μ„œλ²„ 데이터 전솑: Socket.IOλ₯Ό 톡해 μ‹€μ‹œκ°„ 이벀트λ₯Ό Flask μ„œλ²„λ‘œ 전솑

✨ μ£Όμš” κΈ°λŠ₯

1️⃣ μ‹€μ‹œκ°„ 객체 감지

  • YOLO-NAS: μ‚¬λžŒ, μ»΅, λ°”λ‚˜λ‚˜, 사과 μ‹€μ‹œκ°„ 감지
  • Lightweight Face Detection: 고객 μ–Όκ΅΄ 인식 및 ID λΆ€μ—¬

2️⃣ 고객 ID 좔적 μ‹œμŠ€ν…œ

  • λ“±μž₯ νŒμ •: 10ν”„λ ˆμž„ 연속 감지 μ‹œ 고객 λ“±μž₯ ν™•μ •
  • 퇴μž₯ νŒμ •: 10ν”„λ ˆμž„ λ™μ•ˆ 미탐지 μ‹œ 고객 퇴μž₯ 처리
  • ID μœ μ§€: ꡐ차 μƒν™©μ—μ„œλ„ μ•ˆμ •μ μΈ ID 좔적

3️⃣ μ»€μŠ€ν…€ 닀쀑 객체 좔적 (MOT)

  • IoU 기반 λ§€μΉ­: ν”„λ ˆμž„ κ°„ 객체 μ—°κ²°
  • 속도 벑터 예츑: 이동 λ°©ν–₯ 및 속도 계산
  • 색상 νŠΉμ§•: 상체/ν•˜μ²΄ RGB ν‰κ· κ°’μœΌλ‘œ μœ μ‚¬ 객체 ꡬ별
  • λ°©ν–₯ 점수: 이동 λ°©ν–₯ μΌμΉ˜λ„ 계산

4️⃣ μƒν’ˆ ν”½μ—…/내렀놓기 μžλ™ 감지

  • 가상선 기반 감지: y=500 μˆ˜ν‰μ„ μ„ κΈ°μ€€μœΌλ‘œ μƒν’ˆ 이동 좔적
  • 이벀트 λ°œμƒ 쑰건:
    • μ„  μ•„λž˜ β†’ μœ„: μ§‘κΈ° (act=1)
    • μ„  μœ„ β†’ μ•„λž˜: 내렀놓기 (act=0)
  • μžλ™ λ§€μΉ­: 이벀트 λ°œμƒ μ‹œ κ°€μž₯ κ°€κΉŒμš΄ 고객 μžλ™ μ—°κ²°

5️⃣ ν‚€μ˜€μŠ€ν¬ 연동

  • κ³ μ • μ˜μ—­ μ„€μ •: ν‚€μ˜€μŠ€ν¬ μœ„μΉ˜ μ’Œν‘œ μ§€μ •
  • κ·Όμ ‘ 고객 탐지: ν‚€μ˜€μŠ€ν¬μ—μ„œ κ°€μž₯ κ°€κΉŒμš΄ 고객 μ°ΎκΈ°
  • 결제 ν”„λ‘œμ„ΈμŠ€: μ„œλ²„ μš”μ²­ μ‹œ κ·Όμ ‘ 고객 정보 전솑

πŸ—οΈ μ‹œμŠ€ν…œ μ•„ν‚€ν…μ²˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    CCTV Android App                         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚   Camera2    β”‚β†’β†’β†’β”‚ YOLO-NAS     β”‚β†’β†’β†’β”‚ SimpleTrackerβ”‚     β”‚
β”‚  β”‚   API        β”‚   β”‚ (INT8)       β”‚   β”‚ (MOT)        β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚         ↓                  ↓                    ↓           β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚ 100ms 간격   β”‚   β”‚ Face         β”‚   β”‚ 가상선 감지   β”‚     β”‚
β”‚  β”‚ ν”„λ ˆμž„ 캑처  β”‚   β”‚ Detection    β”‚   β”‚ (y=500)       β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                            ↓                                β”‚
β”‚                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                      β”‚
β”‚                   β”‚   Socket.IO      β”‚                      β”‚ 
β”‚                   β”‚   Client         β”‚                      β”‚
β”‚                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          ↓
                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                β”‚   Flask Server      β”‚
                β”‚   (Socket.IO)       β”‚
                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ€– AI λͺ¨λΈ

YOLO-NAS (INT8 μ–‘μžν™”)

  • 파일λͺ…: yolonas_quantized.tflite
  • μž…λ ₯ 크기: 640 Γ— 640 Γ— 3 (RGB)
  • 좜λ ₯:
    • Bounding Boxes: [1, 8400, 4]
    • Confidence Scores: [1, 8400]
    • Class Indices: [1, 8400]
  • 감지 객체: μ‚¬λžŒ, μ»΅, λ°”λ‚˜λ‚˜, 사과 (COCO 데이터셋 기반)
  • μ–‘μžν™”: INT8 (λͺ¨λΈ 크기 μΆ•μ†Œ 및 μΆ”λ‘  속도 ν–₯상)

Lightweight Face Detection

  • 파일λͺ…: face_det_lite_quantized.tflite
  • μž…λ ₯ 크기: 640 Γ— 480 (Grayscale)
  • 좜λ ₯:
    • Heatmap: [1, 60, 80, 1]
    • Boxes: [1, 60, 80, 4]
    • Landmarks: [1, 60, 80, 10]
  • μš©λ„: 고객 μ–Όκ΅΄ 이미지 μΆ”μΆœ 및 ID λΆ€μ—¬

Qualcomm AI Hub μ΅œμ ν™”

  • 좜처: Qualcomm AI Hubμ—μ„œ μ œκ³΅ν•˜λŠ” 사전 μ΅œμ ν™” λͺ¨λΈ
  • νƒ€κ²Ÿ: Snapdragon μΉ©μ…‹μ˜ NPU (Neural Processing Unit)
  • μ΅œμ ν™” λ‚΄μš©:
    • INT8 μ–‘μžν™”λ‘œ λͺ¨λΈ 크기 μΆ•μ†Œ
    • λͺ¨λ°”일 ν™˜κ²½μ— μ΅œμ ν™”λœ μ—°μ‚° ꡬ쑰
    • NNAPI ν™œμš©μœΌλ‘œ ν•˜λ“œμ›¨μ–΄ 가속

🎯 객체 좔적 μ•Œκ³ λ¦¬μ¦˜

좔적 μ•Œκ³ λ¦¬μ¦˜ κ°œμš”

μ»€μŠ€ν…€ MOT (Multi-Object Tracking)

  • DeepSORTκ°€ μ•„λ‹Œ IoU + 속도 예츑 + 색상 νŠΉμ§• 기반 κ²½λŸ‰ 좔적 μ•Œκ³ λ¦¬μ¦˜
  • λͺ¨λ°”일 ν™˜κ²½μ—μ„œ μ‹€μ‹œκ°„ 처리 κ°€λŠ₯ν•˜λ„λ‘ μ΅œμ ν™”

μ£Όμš” ꡬ성 μš”μ†Œ

1️⃣ IoU 기반 λ§€μΉ­

ν˜„μž¬ ν”„λ ˆμž„μ˜ 탐지 κ²°κ³Ό ↔ 이전 ν”„λ ˆμž„μ˜ 좔적 객체
└─ IoU (Intersection over Union) 계산
   └─ μž„κ³„κ°’ 0.25 이상이면 같은 객체둜 νŒμ •

2️⃣ 속도 벑터 예츑

// 이동 평균 ν•„ν„° 적용 (κ°€μ€‘μΉ˜ 0.7)
velocityX = 0.7 * (ν˜„μž¬X - 이전X) + 0.3 * 이전velocityX
velocityY = 0.7 * (ν˜„μž¬Y - 이전Y) + 0.3 * 이전velocityY

// λ‹€μŒ ν”„λ ˆμž„ μœ„μΉ˜ 예츑
predictedX = currentX + velocityX
predictedY = currentY + velocityY

λΉ λ₯Έ μ›€μ§μž„ λŒ€μ‘

  • 속도가 큰 객체에 더 높은 λ°©ν–₯ 점수 λΆ€μ—¬
  • ꡐ차 μƒν™©μ—μ„œ 속도 λΆ€μŠ€νŠΈ (1.5λ°°) 적용

3️⃣ 색상 νŠΉμ§• μΆ”μΆœ (μ‚¬λžŒ μ „μš©)

μ‚¬λžŒ λ°”μš΄λ”© λ°•μŠ€μ—μ„œ:
β”œβ”€ 상체 μ˜μ—­: 25%~50% 높이
β”œβ”€ ν•˜μ²΄ μ˜μ—­: 60%~85% 높이
└─ 쀑앙 μΆ”μΆœ: 쒌우 20% μ œμ™Έ (λ°°κ²½ λ…Έμ΄μ¦ˆ λ°©μ§€)

각 μ˜μ—­μ˜ RGB 평균값 μΆ”μΆœ
└─ 색상 μœ μ‚¬λ„ 계산 (μœ ν΄λ¦¬λ“œ 거리 μ—­μˆ˜)

μ€‘μ•™λ§Œ μΆ”μΆœν•˜λŠ” 이유

  • μ •ν™•ν•œ 상체/ν•˜μ²΄ 색상 μΆ”μΆœ
  • λ°°κ²½μ΄λ‚˜ λ‹€λ₯Έ 객체의 κ°„μ„­ μ΅œμ†Œν™”

4️⃣ λ§€μΉ­ 점수 계산

μ‚¬λžŒ 객체 (일반 상황)

점수 = IoU Γ— 0.4 + ν¬κΈ°μœ μ‚¬λ„ Γ— 0.1 + μƒ‰μƒμœ μ‚¬λ„ Γ— 0.25 + λ°©ν–₯점수 Γ— 0.25

μ‚¬λžŒ 객체 (ꡐ차 상황)

점수 = IoU Γ— 0.4 + ν¬κΈ°μœ μ‚¬λ„ Γ— 0.1 + μƒ‰μƒμœ μ‚¬λ„ Γ— 0.25 + λ°©ν–₯점수 Γ— 0.25

+ 속도 λΆ€μŠ€νŠΈ 1.5λ°° 적용
+ λ°©ν–₯ 정보 κ°€μ€‘μΉ˜ 증가

기타 객체 (μ»΅, λ°”λ‚˜λ‚˜, 사과)

점수 = IoU Γ— 0.1 + ν¬κΈ°μœ μ‚¬λ„ Γ— 0.2 + 거리 Γ— 0.4 + λ°©ν–₯점수 Γ— 0.3

κ°€μ€‘μΉ˜ κ²°μ •: μ‹€ν—˜μ μœΌλ‘œ μ΅œμ ν™”

5️⃣ ꡐ차 상황 처리

감지 쑰건

두 μ‚¬λžŒμ΄ κ²ΉμΉ¨ (거리 < 평균 λ„ˆλΉ„ Γ— 1.2)
AND
μ„œλ‘œ λ°˜λŒ€ λ°©ν–₯으둜 이동 (속도 내적 < 0)

처리 방법

1. 속도 벑터 1.5λ°° λΆ€μŠ€νŠΈ
2. λ°©ν–₯ 정보 κ°€μ€‘μΉ˜ 증가
3. ID μŠ€μœ„μΉ­ λ°©μ§€

1.5λ°° λΆ€μŠ€νŠΈ 이유

  • 2D ν™”λ©΄μ—μ„œ λΉ λ₯Έ μ›€μ§μž„μ„ 더 μ •ν™•ν•˜κ²Œ 좔적
  • ꡐ차 μ‹œ λ°©ν–₯ μ •λ³΄μ˜ 신뒰도 ν–₯상

λ“±μž₯/퇴μž₯ νŒμ •

λ“±μž₯ νŒμ • (10ν”„λ ˆμž„ 연속 감지)

μƒˆλ‘œμš΄ ID 감지
└─ 연속 탐지 카운트 +1
   └─ 10ν”„λ ˆμž„ 연속 감지
      └─ λ“±μž₯ ν™•μ • β†’ μ„œλ²„ 전솑

10ν”„λ ˆμž„ κΈ°μ€€ 이유: μΌμ‹œμ  μ˜€νƒμ§€ λ°©μ§€

퇴μž₯ νŒμ • (10ν”„λ ˆμž„ 미탐지)

κΈ°μ‘΄ ID 미탐지
└─ 미탐지 카운트 +1
   └─ 10ν”„λ ˆμž„ λ™μ•ˆ 미탐지
      └─ 퇴μž₯ ν™•μ • β†’ μ„œλ²„ 전솑

πŸ“¦ μƒν’ˆ ν”½μ—… 감지 둜직

가상선 μ„€μ •

// μˆ˜ν‰ 가상선 (y = 500, 640x640 μ’Œν‘œκ³„ κΈ°μ€€)
virtualLineStartX = 640;
virtualLineStartY = 500;
virtualLineEndX = 0;
virtualLineEndY = 500;

y=500 μ„€μ • 이유

  • ν…Œμ΄λΈ” 높이에 맞좰 μ„€μ •
  • μƒν’ˆμ΄ ν…Œμ΄λΈ” μœ„μ—μ„œ λ“€μ–΄μ˜¬λ¦΄ λ•Œ μ •ν™•νžˆ κ°μ§€λ˜λ„λ‘ μ΅œμ ν™”

이벀트 감지 원리

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          μœ„ (μ„  μœ„)                  β”‚  ← 손에 λ“  μƒνƒœ
β”‚  ─────────────────────────────────  β”‚  ← y = 500 (가상선)
β”‚          μ•„λž˜ (μ„  μ•„λž˜)              β”‚  ← ν…Œμ΄λΈ” μœ„
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

이벀트 λ°œμƒ 쑰건:
1. μ•„λž˜ β†’ μœ„: μ§‘κΈ° (act = 1)
2. μœ„ β†’ μ•„λž˜: 내렀놓기 (act = 0)

감지 μ•Œκ³ λ¦¬μ¦˜

// 1. 객체 쀑심점과 κ°€μƒμ„ μ˜ 거리 계산 (λΆ€ν˜Έ 있음)
float signedDistance = (lineEndY - lineStartY) * pointX 
                     - (lineEndX - lineStartX) * pointY 
                     + lineEndX * lineStartY 
                     - lineEndY * lineStartX;

// 2. μ„  μœ„/μ•„λž˜ νŒλ‹¨
boolean isAbove = (signedDistance > 0);

// 3. 이전 ν”„λ ˆμž„κ³Ό λΉ„κ΅ν•˜μ—¬ 선을 λ„˜μ—ˆλŠ”μ§€ 확인
if (wasAbove != isAbove) {
    // 선을 λ„˜μ€ 이벀트 λ°œμƒ!
    
    // 4. κ°€μž₯ κ°€κΉŒμš΄ μ‚¬λžŒ μ°ΎκΈ°
    SimpleTracker.TrackedObject nearestPerson = findNearestPerson(...);
    
    // 5. μ„œλ²„λ‘œ 전솑
    JSONObject actionData = new JSONObject();
    actionData.put("type", "action");
    actionData.put("personId", nearestPerson.getId());
    actionData.put("object", "μ»΅");
    actionData.put("act", isAbove ? 1 : 0);
    
    mSocket.emit("message", actionData.toString());
}

이벀트 μΏ¨λ‹€μš΄

private static final long EVENT_COOLDOWN = 1000; // 1초

μΏ¨λ‹€μš΄ λͺ©μ 

  • 같은 객체가 1초 이내에 쀑볡 이벀트λ₯Ό λ°œμƒμ‹œν‚€μ§€ μ•Šλ„λ‘ λ°©μ§€
  • 예: 손이 λ–¨λ €μ„œ 선을 μ—¬λŸ¬ 번 μ˜€κ°€λŠ” 경우

ν‚€μ˜€μŠ€ν¬ μ˜μ—­ μ„€μ •

// ν‚€μ˜€μŠ€ν¬ κ³ μ • μ˜μ—­ (640x640 μ’Œν‘œκ³„ κΈ°μ€€)
kioskLeft = 30;
kioskTop = 370;
kioskRight = 100;
kioskBottom = 420;

μ„€μ • 이유

  • μ„œλ²„μ—μ„œ "ν‚€μ˜€μŠ€ν¬μ—μ„œ κ°€μž₯ κ°€κΉŒμš΄ μ‚¬λžŒ μ°ΎκΈ°" μš”μ²­ μ‹œ μ‚¬μš©
  • CCTV ν™”λ©΄ λ‚΄ ν‚€μ˜€μŠ€ν¬ μœ„μΉ˜λ₯Ό 미리 μ§€μ •ν•˜μ—¬ μ •ν™•ν•œ 거리 계산

πŸ”Œ μ„œλ²„ 톡신 ν”„λ‘œν† μ½œ

Socket.IOλž€?

Socket.IO vs 일반 Socket (TCP/UDP)

ꡬ뢄 일반 Socket (TCP/UDP) Socket.IO
레벨 μ €μˆ˜μ€€ λ„€νŠΈμ›Œν¬ 톡신 κ³ μˆ˜μ€€ μ‹€μ‹œκ°„ 톡신 라이브러리
ν”„λ‘œν† μ½œ μ›μ‹œ λ°”μ΄νŠΈ 슀트림 WebSocket 기반 + HTTP 폴백
κ΅¬ν˜„ ν”„λ‘œν† μ½œ 직접 κ΅¬ν˜„ ν•„μš” 이벀트 기반 μ‹œμŠ€ν…œ 제곡
μž¬μ—°κ²° μˆ˜λ™μœΌλ‘œ κ΅¬ν˜„ μžλ™ μž¬μ—°κ²°
데이터 ν˜•μ‹ λ°”μ΄νŠΈ λ°°μ—΄ JSON μžλ™ 직렬화/역직렬화
λ°©(Room) 직접 κ΅¬ν˜„ ν•„μš” λ‚΄μž₯ κΈ°λŠ₯
μ‚¬μš© μ˜ˆμ‹œ socket.write(bytes) socket.emit("event", data)

Socket.IO ꡬ쑰

Socket.IO
  β”œβ”€ WebSocket (μ£Ό 전솑 방식)
  β”œβ”€ HTTP Long Polling (폴백)
  β”œβ”€ μžλ™ μž¬μ—°κ²°
  β”œβ”€ 이벀트 μ‹œμŠ€ν…œ (emit/on)
  β”œβ”€ λ„€μž„μŠ€νŽ˜μ΄μŠ€ 관리
  └─ λ°©(Room) κΈ°λŠ₯

일반 Socket vs Socket.IO μ½”λ“œ 비ꡐ

// ❌ 일반 TCP Socket (μ €μˆ˜μ€€)
Socket socket = new Socket("server.com", 5000);
OutputStream out = socket.getOutputStream();
out.write("Hello".getBytes());  // 직접 λ°”μ΄νŠΈ 전솑
byte[] response = new byte[1024];
socket.getInputStream().read(response);  // 직접 μˆ˜μ‹ 

// βœ… Socket.IO (κ³ μˆ˜μ€€)
Socket mSocket = IO.socket("https://server.com");
mSocket.emit("message", jsonData);  // 이벀트 기반 전솑
mSocket.on("response", args -> {    // 이벀트 λ¦¬μŠ€λ„ˆ
    // μžλ™μœΌλ‘œ JSON νŒŒμ‹±λ¨
});

Socket.IO μ—°κ²° μ„€μ •

// MainActivity.javaμ—μ„œ Socket.IO ν΄λΌμ΄μ–ΈνŠΈ μ΄ˆκΈ°ν™”
final String connectUrl = "https://97f1-223-194-133-239.ngrok-free.app";

try {
    mSocket = IO.socket(connectUrl);
    mSocket.connect();
    
    Log.d(TAG, "Socket.IO μ—°κ²° μ‹œλ„: " + connectUrl);
} catch (URISyntaxException e) {
    Log.e(TAG, "Socket.IO URI 였λ₯˜", e);
}

ngrok μ£Όμ†Œ λ³€κ²½ 방법

  1. MainActivity.java 파일 μ—΄κΈ°
  2. connectUrl λ³€μˆ˜ μˆ˜μ •
  3. μ•± μž¬λΉŒλ“œ 및 μ‹€ν–‰

μ—°κ²° μƒνƒœ 이벀트 λ¦¬μŠ€λ„ˆ

// μ—°κ²° 성곡
mSocket.on(Socket.EVENT_CONNECT, args -> {
    Log.d(TAG, "μ„œλ²„ μ—°κ²° 성곡");
});

// μ—°κ²° μ‹€νŒ¨
mSocket.on(Socket.EVENT_CONNECT_ERROR, args -> {
    Log.e(TAG, "μ„œλ²„ μ—°κ²° μ‹€νŒ¨: " + args[0]);
});

// μ—°κ²° λŠκΉ€
mSocket.on(Socket.EVENT_DISCONNECT, args -> {
    Log.w(TAG, "μ„œλ²„ μ—°κ²° λŠκΉ€");
});

πŸ“€ CCTV β†’ μ„œλ²„ 이벀트

1️⃣ 이미지 슀트리밍

CCTV μ•±μ—μ„œ 전솑

// MainActivity.java
JSONObject imageData = new JSONObject();
imageData.put("type", "image");
imageData.put("image", base64Image);  // Base64 μΈμ½”λ”©λœ JPEG
imageData.put("timestamp", System.currentTimeMillis());

mSocket.emit("message", imageData.toString());

μ„œλ²„μ—μ„œ μˆ˜μ‹  (Flask + Socket.IO)

from flask_socketio import SocketIO, emit
import json
import base64

@socketio.on('message')
def handle_message(message):
    data = json.loads(message)
    message_type = data.get('type')
    
    if message_type == 'image':
        # Base64 λ””μ½”λ”©
        image_data = base64.b64decode(data['image'])
        timestamp = data['timestamp']
        
        # 이미지 μ €μž₯ λ˜λŠ” 처리
        with open(f'frame_{timestamp}.jpg', 'wb') as f:
            f.write(image_data)
        
        print(f"이미지 μˆ˜μ‹ : {len(image_data)} bytes")

데이터 ν˜•μ‹

{
  "type": "image",
  "image": "iVBORw0KGgoAAAANSUhEUgAA...",
  "timestamp": 1234567890123
}

전솑 μ£ΌκΈ°: 100msλ§ˆλ‹€ (10 FPS)

이미지 μ΅œμ ν™”

// 1. λΉ„νŠΈλ§΅ 50% 리사이징
Bitmap resized = Bitmap.createScaledBitmap(
    bitmap, 
    bitmap.getWidth() / 2, 
    bitmap.getHeight() / 2, 
    true
);

// 2. JPEG μ••μΆ• (ν’ˆμ§ˆ 70%)
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
resized.compress(Bitmap.CompressFormat.JPEG, 70, outputStream);

// 3. Base64 인코딩
byte[] byteArray = outputStream.toByteArray();
String base64Image = Base64.encodeToString(byteArray, Base64.DEFAULT);

2️⃣ μ‚¬λžŒ λ“±μž₯ 이벀트

CCTV μ•±μ—μ„œ 전솑

// MainActivity.java
JSONObject appearanceData = new JSONObject();
appearanceData.put("type", "personAppearance");
appearanceData.put("timestamp", System.currentTimeMillis());

JSONArray personIdsArray = new JSONArray();
for (Integer personId : newPersonIds) {
    personIdsArray.put(personId);
}
appearanceData.put("personIds", personIdsArray);

// 썸넀일 이미지 μΆ”κ°€
appearanceData.put("thumbnail", base64Thumbnail);

mSocket.emit("message", appearanceData.toString());

μ„œλ²„μ—μ„œ μˆ˜μ‹ 

@socketio.on('message')
def handle_message(message):
    data = json.loads(message)
    message_type = data.get('type')
    
    if message_type == 'personAppearance':
        person_ids = data.get('personIds', [])
        thumbnail = data.get('thumbnail')
        timestamp = data.get('timestamp')
        
        print(f"μƒˆλ‘œμš΄ 고객 λ“±μž₯: {person_ids}")
        
        # λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯
        for person_id in person_ids:
            save_person_to_db(person_id, timestamp)
        
        # μ–Όκ΅΄ 이미지 μš”μ²­
        emit('requestPersonFaceFind', 
             {'personIds': person_ids}, 
             broadcast=True)

데이터 ν˜•μ‹

{
  "type": "personAppearance",
  "timestamp": 1234567890123,
  "personIds": [3, 5, 8],
  "thumbnail": "iVBORw0KGgoAAAANSUhEUgAA..."
}

λ°œμƒ 쑰건: 10ν”„λ ˆμž„ 연속 감지


3️⃣ μ‚¬λžŒ 퇴μž₯ 이벀트

CCTV μ•±μ—μ„œ 전솑

// MainActivity.java
JSONObject disappearanceData = new JSONObject();
disappearanceData.put("type", "personDisappearance");
disappearanceData.put("timestamp", System.currentTimeMillis());

JSONArray personIdsArray = new JSONArray();
for (Integer personId : departedPersonIds) {
    personIdsArray.put(personId);
}
disappearanceData.put("personIds", personIdsArray);

mSocket.emit("message", disappearanceData.toString());

μ„œλ²„μ—μ„œ μˆ˜μ‹ 

@socketio.on('message')
def handle_message(message):
    data = json.loads(message)
    message_type = data.get('type')
    
    if message_type == 'personDisappearance':
        person_ids = data.get('personIds', [])
        timestamp = data.get('timestamp')
        
        print(f"고객 퇴μž₯: {person_ids}")
        
        # λ°μ΄ν„°λ² μ΄μŠ€ μ—…λ°μ΄νŠΈ (퇴μž₯ μ‹œκ° 기둝)
        for person_id in person_ids:
            update_person_departure(person_id, timestamp)

데이터 ν˜•μ‹

{
  "type": "personDisappearance",
  "timestamp": 1234567890123,
  "personIds": [3, 5]
}

λ°œμƒ 쑰건: 10ν”„λ ˆμž„ λ™μ•ˆ 미탐지


4️⃣ μƒν’ˆ 행동 이벀트

CCTV μ•±μ—μ„œ 전솑

// MainActivity.java
JSONObject actionData = new JSONObject();
actionData.put("type", "action");
actionData.put("personId", nearestPerson.getId());
actionData.put("object", detection.label);  // "μ»΅", "λ°”λ‚˜λ‚˜", "사과"
actionData.put("act", isAbove ? 1 : 0);     // 1=μ§‘κΈ°, 0=내렀놓기

mSocket.emit("message", actionData.toString());

μ„œλ²„μ—μ„œ μˆ˜μ‹ 

@socketio.on('message')
def handle_message(message):
    data = json.loads(message)
    message_type = data.get('type')
    
    if message_type == 'action':
        person_id = data.get('personId')
        object_name = data.get('object')
        action = data.get('act')  # 0 λ˜λŠ” 1
        
        action_text = "μ§‘κΈ°" if action == 1 else "내렀놓기"
        print(f"고객 {person_id}κ°€ {object_name}을(λ₯Ό) {action_text}")
        
        # μž₯λ°”κ΅¬λ‹ˆ μ—…λ°μ΄νŠΈ
        if action == 1:  # μ§‘κΈ°
            add_to_cart(person_id, object_name)
        else:  # 내렀놓기
            remove_from_cart(person_id, object_name)

데이터 ν˜•μ‹

{
  "type": "action",
  "personId": 3,
  "object": "μ»΅",
  "act": 1
}

ν•„λ“œ μ„€λͺ…

  • personId: 행동을 ν•œ 고객 ID
  • object: μƒν’ˆ 이름 (μ»΅/λ°”λ‚˜λ‚˜/사과)
  • act:
    • 0: 내렀놓기 (μ„  μœ„ β†’ μ•„λž˜)
    • 1: μ§‘κΈ° (μ„  μ•„λž˜ β†’ μœ„)

λ°œμƒ 쑰건: 가상선(y=500) ꡐ차 μ‹œ


5️⃣ μ–Όκ΅΄ 이미지 전솑

CCTV μ•±μ—μ„œ 전솑

// MainActivity.java
JSONObject facesData = new JSONObject();
JSONObject facesObject = new JSONObject();

for (Integer personId : personIds) {
    String faceImage = capturedFaces.get(personId);
    if (faceImage != null) {
        facesObject.put(String.valueOf(personId), faceImage);
    } else {
        facesObject.put(String.valueOf(personId), JSONObject.NULL);
    }
}

facesData.put("faces", facesObject);

mSocket.emit("findPersonFace", facesData.toString());

μ„œλ²„μ—μ„œ μˆ˜μ‹ 

@socketio.on('findPersonFace')
def handle_find_person_face(data):
    faces_data = data.get('faces', {})
    
    for person_id, face_image in faces_data.items():
        if face_image is not None:
            # Base64 λ””μ½”λ”©
            face_bytes = base64.b64decode(face_image)
            
            # μ–Όκ΅΄ 이미지 μ €μž₯
            with open(f'faces/person_{person_id}.jpg', 'wb') as f:
                f.write(face_bytes)
            
            print(f"고객 {person_id} μ–Όκ΅΄ 이미지 μ €μž₯ μ™„λ£Œ")
        else:
            print(f"고객 {person_id} μ–Όκ΅΄ μ—†μŒ")

데이터 ν˜•μ‹

{
  "faces": {
    "3": "iVBORw0KGgoAAAANSUhEUgAA...",
    "5": "iVBORw0KGgoAAAANSUhEUgAA...",
    "8": null
  }
}

이벀트λͺ… 주의: findPersonFace (λ‹€λ₯Έ μ΄λ²€νŠΈμ™€ 달리 별도 이벀트λͺ… μ‚¬μš©)

처리 흐름

1. μ„œλ²„μ—μ„œ μ–Όκ΅΄ μš”μ²­ (requestPersonFaceFind)
2. CCTV μ•±μ—μ„œ Face Detection μˆ˜ν–‰
3. YOLO μ‚¬λžŒ 객체와 거리 기반 λ§€μΉ­
4. μ–Όκ΅΄ μ˜μ—­ 크둭 β†’ Base64 인코딩
5. findPersonFace 이벀트둜 전솑

λ§€μΉ­ μ•Œκ³ λ¦¬μ¦˜

μ‚¬λžŒ λ°”μš΄λ”© λ°•μŠ€ 상단 20% 지점 ↔ μ–Όκ΅΄ 쀑심점
└─ μœ ν΄λ¦¬λ“œ 거리 계산
   └─ μ΅œμ†Œ 거리의 μ–Όκ΅΄ 선택

6️⃣ ν‚€μ˜€μŠ€ν¬ κ·Όμ ‘ 고객 전솑

CCTV μ•±μ—μ„œ 전솑

// MainActivity.java
JSONObject nearestData = new JSONObject();
nearestData.put("type", "nearest_person_found");
nearestData.put("personId", nearestPerson.getId());
nearestData.put("distance", distance);

mSocket.emit("nearest_person_found", nearestData.toString());

μ„œλ²„μ—μ„œ μˆ˜μ‹ 

@socketio.on('nearest_person_found')
def handle_nearest_person(data):
    person_id = data.get('personId')
    distance = data.get('distance')
    
    print(f"ν‚€μ˜€μŠ€ν¬ κ·Όμ ‘ 고객: ID {person_id}, 거리 {distance}px")
    
    # ν•΄λ‹Ή 고객의 μž₯λ°”κ΅¬λ‹ˆ 정보 전솑
    cart_items = get_cart(person_id)
    emit('display_cart', {'personId': person_id, 'items': cart_items})

데이터 ν˜•μ‹

{
  "type": "nearest_person_found",
  "personId": 5,
  "distance": 123.45
}

이벀트λͺ… 주의: nearest_person_found (별도 이벀트λͺ…)

처리 흐름

1. μ„œλ²„μ—μ„œ ν‚€μ˜€μŠ€ν¬ μš”μ²­ (find_nearest_person)
2. CCTV μ•±μ—μ„œ ν‚€μ˜€μŠ€ν¬ μ˜μ—­ 쀑심점 계산
3. λͺ¨λ“  μ‚¬λžŒ 객체와 μœ ν΄λ¦¬λ“œ 거리 계산
4. μ΅œμ†Œ 거리의 고객 선택
5. nearest_person_found 이벀트둜 전솑

πŸ“₯ μ„œλ²„ β†’ CCTV 이벀트

1️⃣ μ–Όκ΅΄ 이미지 μš”μ²­

μ„œλ²„μ—μ„œ 전솑

from flask_socketio import emit

# νŠΉμ • κ³ κ°λ“€μ˜ μ–Όκ΅΄ 이미지 μš”μ²­
person_ids = [3, 5, 8]
emit('requestPersonFaceFind', 
     {'personIds': person_ids}, 
     broadcast=True)

CCTV μ•±μ—μ„œ μˆ˜μ‹ 

// MainActivity.java
mSocket.on("requestPersonFaceFind", args -> {
    try {
        JSONObject data = new JSONObject(args[0].toString());
        JSONArray personIdsArray = data.getJSONArray("personIds");
        
        List<Integer> personIds = new ArrayList<>();
        for (int i = 0; i < personIdsArray.length(); i++) {
            personIds.add(personIdsArray.getInt(i));
        }
        
        // μ–Όκ΅΄ 캑처 및 전솑
        captureAndSendNewFaces(personIds);
        
    } catch (JSONException e) {
        Log.e(TAG, "μ–Όκ΅΄ μš”μ²­ νŒŒμ‹± 였λ₯˜", e);
    }
});

데이터 ν˜•μ‹

{
  "personIds": [3, 5, 8]
}

응닡: findPersonFace 이벀트둜 μ–Όκ΅΄ 이미지 전솑


2️⃣ ν‚€μ˜€μŠ€ν¬ κ·Όμ ‘ 고객 μš”μ²­

μ„œλ²„μ—μ„œ 전솑

# νŠΉμ • ν‚€μ˜€μŠ€ν¬μ—μ„œ κ°€μž₯ κ°€κΉŒμš΄ 고객 μ°ΎκΈ° μš”μ²­
emit('find_nearest_person', 
     {'kioskId': 'kiosk_1'}, 
     broadcast=True)

CCTV μ•±μ—μ„œ μˆ˜μ‹ 

// MainActivity.java
mSocket.on("find_nearest_person", args -> {
    try {
        JSONObject data = new JSONObject(args[0].toString());
        String kioskId = data.getString("kioskId");
        
        Log.d(TAG, "ν‚€μ˜€μŠ€ν¬ κ·Όμ ‘ 고객 μš”μ²­: " + kioskId);
        
        // ν‚€μ˜€μŠ€ν¬ κ·Όμ ‘ 고객 μ°ΎκΈ°
        findNearestPersonToKiosk();
        
    } catch (JSONException e) {
        Log.e(TAG, "ν‚€μ˜€μŠ€ν¬ μš”μ²­ νŒŒμ‹± 였λ₯˜", e);
    }
});

데이터 ν˜•μ‹

{
  "kioskId": "kiosk_1"
}

응닡: nearest_person_found 이벀트둜 고객 정보 전솑


πŸ“Š 전체 이벀트 μš”μ•½ν‘œ

CCTV β†’ μ„œλ²„

이벀트λͺ… μš©λ„ type ν•„λ“œ λ°œμƒ 쑰건 μ„œλ²„ μˆ˜μ‹  방법 CCTV 전솑 방법
message 이미지 슀트리밍 "image" 100msλ§ˆλ‹€ @socketio.on('message') mSocket.emit("message", data)
message μ‚¬λžŒ λ“±μž₯ "personAppearance" 10ν”„λ ˆμž„ 연속 감지 @socketio.on('message') mSocket.emit("message", data)
message μ‚¬λžŒ 퇴μž₯ "personDisappearance" 10ν”„λ ˆμž„ 미탐지 @socketio.on('message') mSocket.emit("message", data)
message μƒν’ˆ 행동 "action" 가상선 ꡐ차 @socketio.on('message') mSocket.emit("message", data)
findPersonFace μ–Όκ΅΄ 이미지 (μ—†μŒ) μ„œλ²„ μš”μ²­ μ‹œ @socketio.on('findPersonFace') mSocket.emit("findPersonFace", data)
nearest_person_found ν‚€μ˜€μŠ€ν¬ κ·Όμ ‘ 고객 "nearest_person_found" μ„œλ²„ μš”μ²­ μ‹œ @socketio.on('nearest_person_found') mSocket.emit("nearest_person_found", data)

μ„œλ²„ β†’ CCTV

이벀트λͺ… μš©λ„ 데이터 μ„œλ²„ 전솑 방법 CCTV μˆ˜μ‹  방법
requestPersonFaceFind μ–Όκ΅΄ 이미지 μš”μ²­ {personIds: [3,5,8]} emit('requestPersonFaceFind', data) mSocket.on("requestPersonFaceFind", handler)
find_nearest_person ν‚€μ˜€μŠ€ν¬ κ·Όμ ‘ 고객 μš”μ²­ {kioskId: "kiosk_1"} emit('find_nearest_person', data) mSocket.on("find_nearest_person", handler)

πŸ”„ 톡신 흐름도

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   CCTV App      β”‚                          β”‚  Flask Server   β”‚
β”‚  (Socket.IO     β”‚                          β”‚  (Socket.IO)    β”‚
β”‚   Client)       β”‚                          β”‚                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜                          β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                                            β”‚
         β”‚  1. μ—°κ²° (Socket.IO)                       β”‚
         β”‚  mSocket.connect()                         β”‚
         │───────────────────────────────────────────>β”‚
         β”‚                                            β”‚
         β”‚  EVENT_CONNECT                             β”‚
         β”‚<───────────────────────────────────────────│
         β”‚                                            β”‚
         β”‚  2. 이미지 슀트리밍 (100msλ§ˆλ‹€)             β”‚
         β”‚  emit('message', {type:'image',...})       β”‚
         │───────────────────────────────────────────>β”‚
         β”‚                                            β”‚
         β”‚                  @socketio.on('message')   β”‚
         β”‚                  if type == 'image': ...   β”‚
         β”‚                                            β”‚
         β”‚  3. μ‚¬λžŒ λ“±μž₯ 이벀트                        β”‚
         β”‚  emit('message', {type:'personAppearance'})β”‚
         │───────────────────────────────────────────>β”‚
         β”‚                                            β”‚
         β”‚                  if type == 'personAppearance':β”‚
         β”‚                    emit('requestPersonFaceFind')β”‚
         β”‚                                            β”‚
         β”‚  4. μ–Όκ΅΄ 이미지 μš”μ²­                        β”‚
         β”‚  on('requestPersonFaceFind')               β”‚
         β”‚<───────────────────────────────────────────│
         β”‚                                            β”‚
         β”‚                  emit('requestPersonFaceFind', ...)β”‚
         β”‚                                            β”‚
         β”‚  5. μ–Όκ΅΄ 이미지 전솑                        β”‚
         β”‚  emit('findPersonFace', {faces:{...}})     β”‚
         │───────────────────────────────────────────>β”‚
         β”‚                                            β”‚
         β”‚                  @socketio.on('findPersonFace')β”‚
         β”‚                  save_faces(...)           β”‚
         β”‚                                            β”‚
         β”‚  6. μƒν’ˆ 행동 이벀트                        β”‚
         β”‚  emit('message', {type:'action',...})      β”‚
         │───────────────────────────────────────────>β”‚
         β”‚                                            β”‚
         β”‚                  if type == 'action':      β”‚
         β”‚                    update_cart(...)        β”‚
         β”‚                                            β”‚
         β”‚  7. ν‚€μ˜€μŠ€ν¬ κ·Όμ ‘ 고객 μš”μ²­                 β”‚
         β”‚  on('find_nearest_person')                 β”‚
         β”‚<───────────────────────────────────────────│
         β”‚                                            β”‚
         β”‚                  emit('find_nearest_person', ...)β”‚
         β”‚                                            β”‚
         β”‚  8. κ·Όμ ‘ 고객 정보 전솑                     β”‚
         β”‚  emit('nearest_person_found', {...})       β”‚
         │───────────────────────────────────────────>β”‚
         β”‚                                            β”‚
         β”‚             @socketio.on('nearest_person_found')β”‚
         β”‚                  display_cart(...)         β”‚
         β”‚                                            β”‚
         β”‚  9. μ‚¬λžŒ 퇴μž₯ 이벀트                        β”‚
         β”‚  emit('message', {type:'personDisappearance'})β”‚
         │───────────────────────────────────────────>β”‚
         β”‚                                            β”‚
         β”‚                  if type == 'personDisappearance':β”‚
         β”‚                    update_departure(...)   β”‚
         β”‚                                            β”‚

πŸ’‘ Socket.IO μ‚¬μš© 이유

μ™œ 일반 Socket이 μ•„λ‹Œ Socket.IOλ₯Ό μ„ νƒν–ˆλ‚˜?

1️⃣ μžλ™ μž¬μ—°κ²°

// Socket.IO: μžλ™μœΌλ‘œ μž¬μ—°κ²° μ‹œλ„
mSocket.connect();  // λŠμ–΄μ§€λ©΄ μžλ™μœΌλ‘œ λ‹€μ‹œ μ—°κ²°

// 일반 Socket: μˆ˜λ™μœΌλ‘œ μž¬μ—°κ²° 둜직 κ΅¬ν˜„ ν•„μš”
while (true) {
    try {
        socket = new Socket("server", 5000);
        break;
    } catch (IOException e) {
        Thread.sleep(1000);  // 직접 κ΅¬ν˜„ν•΄μ•Ό 함
    }
}

2️⃣ 이벀트 기반 톡신

// Socket.IO: 이벀트λͺ…μœΌλ‘œ ꡬ뢄
mSocket.emit("message", data);
mSocket.emit("findPersonFace", faceData);
mSocket.on("requestPersonFaceFind", handler);

// 일반 Socket: ν”„λ‘œν† μ½œ 직접 섀계 ν•„μš”
outputStream.write("MSG|" + data);  // μ»€μŠ€ν…€ ν”„λ‘œν† μ½œ
outputStream.write("FACE|" + faceData);

3️⃣ JSON μžλ™ 처리

// Socket.IO: JSON μžλ™ 직렬화
JSONObject data = new JSONObject();
data.put("type", "action");
mSocket.emit("message", data.toString());  // μžλ™ λ³€ν™˜

// 일반 Socket: μˆ˜λ™ λ³€ν™˜
String json = data.toString();
byte[] bytes = json.getBytes("UTF-8");
outputStream.write(bytes.length);  // 길이 λ¨Όμ € 전솑
outputStream.write(bytes);

4️⃣ HTTP 폴백

Socket.IO μ—°κ²° μ‹œλ„:
1. WebSocket μ—°κ²° μ‹œλ„
2. μ‹€νŒ¨ μ‹œ HTTP Long Polling으둜 μžλ™ μ „ν™˜
3. μ•ˆμ •μ μΈ 톡신 보μž₯

일반 Socket:
- TCP만 지원
- λ°©ν™”λ²½/ν”„λ‘μ‹œ ν™˜κ²½μ—μ„œ 차단될 수 있음

5️⃣ λΈŒλ‘œλ“œμΊμŠ€νŠΈ κΈ°λŠ₯

# μ„œλ²„μ—μ„œ λͺ¨λ“  CCTV에 λ™μ‹œ 전솑
emit('find_nearest_person', data, broadcast=True)

# 일반 Socket: 각 ν΄λΌμ΄μ–ΈνŠΈμ— κ°œλ³„ 전솑 ν•„μš”
for client in clients:
    client.send(data)

πŸ› οΈ 기술 μŠ€νƒ

개발 ν™˜κ²½

  • Android Studio: Latest version
  • Gradle: 8.0+
  • Compile SDK: 34
  • Min SDK: 24 (Android 7.0 Nougat)
  • Target SDK: 34
  • Java Version: 1.8

핡심 라이브러리

dependencies {
    // UI 및 기본 Android
    implementation 'androidx.appcompat:appcompat:1.7.0'
    implementation 'com.google.android.material:material:1.12.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
    
    // λ„€νŠΈμ›Œν¬ 톡신
    implementation 'com.squareup.okhttp3:okhttp:4.9.3'
    implementation 'io.socket:socket.io-client:2.0.0'
    
    // TensorFlow Lite (AI λͺ¨λΈ)
    implementation 'org.tensorflow:tensorflow-lite:2.12.0'
    implementation 'org.tensorflow:tensorflow-lite-support:0.4.3'
    implementation 'org.tensorflow:tensorflow-lite-metadata:0.4.3'
    implementation 'org.tensorflow:tensorflow-lite-gpu:2.12.0'
}

TFLite μ΅œμ ν™” μ„€μ •

Interpreter.Options options = new Interpreter.Options();

// CPU μŠ€λ ˆλ“œ μ΅œμ ν™” (μ΅œλŒ€ 4개)
int optimalThreads = Math.min(4, Runtime.getRuntime().availableProcessors());
options.setNumThreads(optimalThreads);

// FP16 정밀도 ν—ˆμš© (μ„±λŠ₯ ν–₯상)
options.setAllowFp16PrecisionForFp32(true);

// NNAPI ν™œμ„±ν™” (Android 9+)
options.setUseNNAPI(true);

πŸ“ 디렉토리 ꡬ쑰

yolo-nas-app/
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ main/
β”‚   β”‚   β”‚   β”œβ”€β”€ java/com/example/quantiztest/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ MainActivity.java           # 메인 μ•‘ν‹°λΉ„ν‹° (카메라, UI, 톡신 총괄)
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ YoloImageProcessor.java     # YOLO-NAS 객체 감지
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ FaceDetector.java           # μ–Όκ΅΄ 감지
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ SimpleTracker.java          # 닀쀑 객체 좔적 μ•Œκ³ λ¦¬μ¦˜
β”‚   β”‚   β”‚   β”‚   └── TFLiteLoader.java           # TFLite λͺ¨λΈ λ‘œλ”©
β”‚   β”‚   β”‚   β”œβ”€β”€ assets/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ yolonas_quantized.tflite    # YOLO-NAS λͺ¨λΈ (INT8)
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ face_det_lite_quantized.tflite  # Face Detection λͺ¨λΈ
β”‚   β”‚   β”‚   β”‚   └── labels.txt                  # COCO 클래슀 라벨
β”‚   β”‚   β”‚   └── res/
β”‚   β”‚   β”‚       β”œβ”€β”€ layout/
β”‚   β”‚   β”‚       β”‚   └── activity_main.xml        # UI λ ˆμ΄μ•„μ›ƒ
β”‚   β”‚   β”‚       └── values/
β”‚   β”‚   β”‚           └── strings.xml
β”‚   β”‚   └── test/
β”‚   └── build.gradle.kts                         # μ•± λΉŒλ“œ μ„€μ •
β”œβ”€β”€ gradle/
└── README.md

πŸ“š μ£Όμš” 클래슀 μ„€λͺ…

MainActivity.java

μ—­ν• : μ•±μ˜ 메인 컨트둀러

μ£Όμš” κΈ°λŠ₯

  • 카메라 μ œμ–΄: Camera2 APIλ₯Ό μ΄μš©ν•œ μ‹€μ‹œκ°„ μ˜μƒ 캑처
  • AI λͺ¨λΈ μ‹€ν–‰: YOLO-NAS 및 Face Detection μˆ˜ν–‰
  • 객체 좔적: SimpleTrackerλ₯Ό μ΄μš©ν•œ ID 관리
  • μ„œλ²„ 톡신: Socket.IOλ₯Ό ν†΅ν•œ μ‹€μ‹œκ°„ 데이터 전솑
  • UI μ—…λ°μ΄νŠΈ: μ˜€λ²„λ ˆμ΄μ— λ°”μš΄λ”© λ°•μŠ€ 및 정보 ν‘œμ‹œ

μ£Όμš” λ©”μ„œλ“œ

// ν”„λ ˆμž„ 처리 (100msλ§ˆλ‹€ 호좜)
private void processFrameForOverlay(Bitmap bitmap)

// μƒν’ˆ ν”½μ—… 감지
private void detectLineCrossing(List<TrackedObject> objects, ...)

// ν‚€μ˜€μŠ€ν¬ κ·Όμ ‘ 고객 μ°ΎκΈ°
private void findNearestPersonToKiosk()

// μ–Όκ΅΄ 캑처 및 전솑
private void captureAndSendNewFaces(List<Integer> personIds)

// Socket.IO 이벀트 λ¦¬μŠ€λ„ˆ μ„€μ •
private void setupSocketListeners()

Socket.IO 이벀트 처리

private void setupSocketListeners() {
    // μ„œλ²„ μ—°κ²° 성곡
    mSocket.on(Socket.EVENT_CONNECT, args -> {
        Log.d(TAG, "μ„œλ²„ μ—°κ²° 성곡");
    });
    
    // μ–Όκ΅΄ 이미지 μš”μ²­ μˆ˜μ‹ 
    mSocket.on("requestPersonFaceFind", args -> {
        JSONObject data = new JSONObject(args[0].toString());
        JSONArray personIds = data.getJSONArray("personIds");
        captureAndSendNewFaces(personIds);
    });
    
    // ν‚€μ˜€μŠ€ν¬ κ·Όμ ‘ 고객 μš”μ²­ μˆ˜μ‹ 
    mSocket.on("find_nearest_person", args -> {
        findNearestPersonToKiosk();
    });
}

YoloImageProcessor.java

μ—­ν• : YOLO-NAS λͺ¨λΈμ„ μ΄μš©ν•œ 객체 감지

μ£Όμš” κΈ°λŠ₯

  • λΉ„νŠΈλ§΅ 이미지λ₯Ό 640Γ—640으둜 리사이징
  • INT8 μ–‘μžν™” λͺ¨λΈ μž…λ ₯ 버퍼 생성
  • λͺ¨λΈ μΆ”λ‘  μ‹€ν–‰
  • Dequantization (INT8 β†’ float)
  • NMS (Non-Maximum Suppression) 적용

좜λ ₯ ν˜•μ‹

public static class Detection {
    String label;        // 객체 이름
    float confidence;    // 신뒰도 (0~1)
    float left, top;     // λ°”μš΄λ”© λ°•μŠ€ μ’Œν‘œ
    float right, bottom;
}

μ£Όμš” λ©”μ„œλ“œ

// 객체 감지 μˆ˜ν–‰
public List<Detection> detect(Bitmap bitmap)

// NMS 적용
private List<Detection> applyNMS(List<Detection> detections, float iouThreshold)

FaceDetector.java

μ—­ν• : Lightweight Face Detection λͺ¨λΈμ„ μ΄μš©ν•œ μ–Όκ΅΄ 감지

μ£Όμš” κΈ°λŠ₯

  • κ·Έλ ˆμ΄μŠ€μΌ€μΌ 이미지 μ „μ²˜λ¦¬
  • Heatmap 기반 μ–Όκ΅΄ μœ„μΉ˜ 탐지
  • λ°”μš΄λ”© λ°•μŠ€ μ’Œν‘œ 계산
  • NMS 적용으둜 쀑볡 제거

좜λ ₯ ν˜•μ‹

public static class Face {
    float confidence;
    float left, top;
    float right, bottom;
}

μ£Όμš” λ©”μ„œλ“œ

// μ–Όκ΅΄ 감지 μˆ˜ν–‰
public List<Face> detectFaces(Bitmap bitmap)

// μ‚¬λžŒ 객체와 μ–Όκ΅΄ λ§€μΉ­
public Map<Integer, Bitmap> matchFacesToPersons(
    List<Face> faces, 
    List<TrackedObject> persons, 
    Bitmap bitmap
)

SimpleTracker.java

μ—­ν• : μ»€μŠ€ν…€ 닀쀑 객체 좔적 (MOT)

μ£Όμš” κΈ°λŠ₯

  • IoU 기반 객체 λ§€μΉ­
  • 속도 벑터 예츑 및 μœ„μΉ˜ μΆ”μ •
  • 색상 νŠΉμ§• μΆ”μΆœ (μ‚¬λžŒ μ „μš©)
  • λ°©ν–₯ 점수 계산
  • ꡐ차 상황 처리
  • 객체 λ‚˜μ΄ 관리 (λ“±μž₯/퇴μž₯ νŒμ •)

핡심 클래슀

public static class TrackedObject {
    int id;              // 고유 ID
    String label;        // 객체 이름
    float confidence;    // 신뒰도
    float left, top;     // ν˜„μž¬ μœ„μΉ˜
    float right, bottom;
    float velocityX, velocityY;  // 속도 벑터
    int age;             // ν”„λ ˆμž„ λ‚˜μ΄
    float[] upperBodyColors;  // 상체 색상 (RGB)
    float[] lowerBodyColors;  // ν•˜μ²΄ 색상 (RGB)
}

μ£Όμš” λ©”μ„œλ“œ

// ν”„λ ˆμž„ μ—…λ°μ΄νŠΈ
public List<TrackedObject> update(List<Detection> detections, Bitmap bitmap)

// IoU 계산 (속도 예츑 포함)
private float calculateIoUWithPrediction(TrackedObject tracked, Detection detection)

// 색상 νŠΉμ§• μΆ”μΆœ
private void extractColorFeatures(TrackedObject obj, Bitmap bitmap)

// 색상 μœ μ‚¬λ„ 계산
private float calculateColorSimilarity(TrackedObject obj, Detection det, Bitmap bitmap)

// ꡐ차 상황 감지
private void detectCrossings()

// λ“±μž₯/퇴μž₯ νŒμ •
public List<Integer> getNewlyAppearedPersons()
public List<Integer> getRecentlyDisappearedPersons()

TFLiteLoader.java

μ—­ν• : TensorFlow Lite λͺ¨λΈ λ‘œλ”© 및 μ΅œμ ν™”

μ£Όμš” κΈ°λŠ₯

  • Assetsμ—μ„œ λͺ¨λΈ 파일 λ‘œλ“œ
  • MappedByteBuffer 생성
  • Interpreter μ˜΅μ…˜ μ„€μ • (μŠ€λ ˆλ“œ, NNAPI, FP16)
  • λͺ¨λΈ ν…μ„œ 정보 λ‘œκΉ…

μ£Όμš” λ©”μ„œλ“œ

// λͺ¨λΈ 파일 λ‘œλ“œ
public static MappedByteBuffer loadModelFile(Context context, String modelPath)

// Interpreter μ˜΅μ…˜ 생성
public static Interpreter.Options createInterpreterOptions()

πŸ”§ μ„±λŠ₯ μ΅œμ ν™”

ν”„λ ˆμž„ 처리 μ£ΌκΈ°

backgroundHandler.postDelayed(this, 100); // 100ms = 10 FPS

μ„€μ • 이유

  • AI μΆ”λ‘  μ‹œκ°„: 100~200ms
  • λŒ€κΈ° μ‹œκ°„ μ΅œμ†Œν™”
  • 배터리 μ†Œλͺ¨μ™€ μ„±λŠ₯의 κ· ν˜•

이미지 μ••μΆ•

// 1. 크기 μΆ•μ†Œ (50%)
Bitmap resized = Bitmap.createScaledBitmap(bitmap, width/2, height/2, true);

// 2. JPEG ν’ˆμ§ˆ 70%
resized.compress(Bitmap.CompressFormat.JPEG, 70, outputStream);

λ„€νŠΈμ›Œν¬ λŒ€μ—­ν­ 절감: μ•½ 60~70%

λ©”λͺ¨λ¦¬ 관리

// λΉ„νŠΈλ§΅ μ¦‰μ‹œ ν•΄μ œ
bitmap.recycle();

// ν”„λ ˆμž„ 버퍼 정리
if (!active_persons.isEmpty()) {
    frame_buffer.clear();
}

🎯 전체 λ™μž‘ 흐름

1. μ•± μ‹œμž‘
   └─ AI λͺ¨λΈ λ‘œλ“œ (YOLO-NAS, Face Detection)
   └─ Socket.IO μ„œλ²„ μ—°κ²°
      └─ mSocket = IO.socket(connectUrl)
      └─ mSocket.connect()
      └─ 이벀트 λ¦¬μŠ€λ„ˆ 등둝 (on λ©”μ„œλ“œ)
   └─ 카메라 μ‹œμž‘

2. μ‹€μ‹œκ°„ 처리 (100msλ§ˆλ‹€)
   └─ ν”„λ ˆμž„ 캑처
   └─ YOLO-NAS 객체 감지
      └─ μ‚¬λžŒ, μ»΅, λ°”λ‚˜λ‚˜, 사과 감지
   └─ SimpleTracker μ—…λ°μ΄νŠΈ
      β”œβ”€ IoU λ§€μΉ­
      β”œβ”€ 속도 예츑
      β”œβ”€ 색상 νŠΉμ§• 비ꡐ
      └─ ID ν• λ‹Ή/μœ μ§€
   └─ 가상선 ꡐ차 감지
      └─ μƒν’ˆ ν”½μ—…/내렀놓기 이벀트
   └─ 이미지 Base64 인코딩
   └─ Socket.IO둜 μ„œλ²„ 전솑
      └─ mSocket.emit('message', jsonData)

3. 이벀트 처리
   β”œβ”€ λ“±μž₯ (10ν”„λ ˆμž„ 연속)
   β”‚  └─ mSocket.emit('message', {type:'personAppearance'})
   β”‚  └─ μ„œλ²„μ—μ„œ μ–Όκ΅΄ μš”μ²­
   β”‚     └─ mSocket.on('requestPersonFaceFind', handler)
   β”‚     └─ Face Detection μˆ˜ν–‰
   β”‚     └─ mSocket.emit('findPersonFace', {faces:{...}})
   β”‚
   β”œβ”€ 퇴μž₯ (10ν”„λ ˆμž„ 미탐지)
   β”‚  └─ mSocket.emit('message', {type:'personDisappearance'})
   β”‚
   β”œβ”€ μƒν’ˆ 행동
   β”‚  └─ 가상선 ꡐ차 감지
   β”‚  └─ κ°€μž₯ κ°€κΉŒμš΄ μ‚¬λžŒ μ°ΎκΈ°
   β”‚  └─ mSocket.emit('message', {type:'action',...})
   β”‚
   └─ ν‚€μ˜€μŠ€ν¬ μš”μ²­
      └─ mSocket.on('find_nearest_person', handler)
      └─ κ·Όμ ‘ 고객 μ°ΎκΈ°
      └─ mSocket.emit('nearest_person_found', {...})

4. UI μ—…λ°μ΄νŠΈ
   └─ μ˜€λ²„λ ˆμ΄μ— λ°”μš΄λ”© λ°•μŠ€ 그리기
   └─ ID, 라벨, 신뒰도 ν‘œμ‹œ
   └─ 가상선 ν‘œμ‹œ
   └─ ν‚€μ˜€μŠ€ν¬ μ˜μ—­ ν‘œμ‹œ

Store Shield CCTV App

GitHub

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages