- κ°μ
- μ£Όμ κΈ°λ₯
- μμ€ν μν€ν μ²
- AI λͺ¨λΈ
- κ°μ²΄ μΆμ μκ³ λ¦¬μ¦
- μν ν½μ κ°μ§ λ‘μ§
- μλ² ν΅μ νλ‘ν μ½
- κΈ°μ μ€ν
- λλ ν 리 ꡬ쑰
- μ£Όμ ν΄λμ€ μ€λͺ
Store Shield CCTV μ±μ Android κΈ°λ° μ¨λλ°μ΄μ€ AI μμ λΆμ μμ€ν μΌλ‘, 무μΈμ ν¬ λ΄ κ³ κ°κ³Ό μνμ μ€μκ° μΆμ λ° νλ λΆμμ μνν©λλ€. Qualcomm AI Hub μ΅μ ν λͺ¨λΈμ νμ©νμ¬ λͺ¨λ°μΌ νκ²½μμλ λΉ λ₯΄κ³ μ νν κ°μ²΄ κ°μ§ λ° μΆμ μ΄ κ°λ₯ν©λλ€.
- μ€μκ° μμ λΆμ: μΉ΄λ©λΌλ‘ λ§€μ₯ λ΄λΆλ₯Ό 촬μνλ©° AIλ‘ κ°μ²΄ κ°μ§
- κ³ κ° ID μΆμ : μ μ₯ν κ³ κ°μκ² κ³ μ IDλ₯Ό λΆμ¬νκ³ μ΄λ κ²½λ‘ μΆμ
- μν νλ κ°μ§: κ³ κ°μ΄ μνμ μ§κ±°λ λ΄λ €λλ νλ μλ μΈμ
- μλ² λ°μ΄ν° μ μ‘: Socket.IOλ₯Ό ν΅ν΄ μ€μκ° μ΄λ²€νΈλ₯Ό Flask μλ²λ‘ μ μ‘
- YOLO-NAS: μ¬λ, μ»΅, λ°λλ, μ¬κ³Ό μ€μκ° κ°μ§
- Lightweight Face Detection: κ³ κ° μΌκ΅΄ μΈμ λ° ID λΆμ¬
- λ±μ₯ νμ : 10νλ μ μ°μ κ°μ§ μ κ³ κ° λ±μ₯ νμ
- ν΄μ₯ νμ : 10νλ μ λμ λ―Ένμ§ μ κ³ κ° ν΄μ₯ μ²λ¦¬
- ID μ μ§: κ΅μ°¨ μν©μμλ μμ μ μΈ ID μΆμ
- IoU κΈ°λ° λ§€μΉ: νλ μ κ° κ°μ²΄ μ°κ²°
- μλ λ²‘ν° μμΈ‘: μ΄λ λ°©ν₯ λ° μλ κ³μ°
- μμ νΉμ§: μ체/ν체 RGB νκ· κ°μΌλ‘ μ μ¬ κ°μ²΄ ꡬλ³
- λ°©ν₯ μ μ: μ΄λ λ°©ν₯ μΌμΉλ κ³μ°
- κ°μμ κΈ°λ° κ°μ§: y=500 μνμ μ κΈ°μ€μΌλ‘ μν μ΄λ μΆμ
- μ΄λ²€νΈ λ°μ 쑰건:
- μ μλ β μ: μ§κΈ° (act=1)
- μ μ β μλ: λ΄λ €λκΈ° (act=0)
- μλ λ§€μΉ: μ΄λ²€νΈ λ°μ μ κ°μ₯ κ°κΉμ΄ κ³ κ° μλ μ°κ²°
- κ³ μ μμ μ€μ : ν€μ€μ€ν¬ μμΉ μ’ν μ§μ
- κ·Όμ κ³ κ° νμ§: ν€μ€μ€ν¬μμ κ°μ₯ κ°κΉμ΄ κ³ κ° μ°ΎκΈ°
- κ²°μ νλ‘μΈμ€: μλ² μμ² μ κ·Όμ κ³ κ° μ 보 μ μ‘
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CCTV Android App β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Camera2 βββββ YOLO-NAS βββββ SimpleTrackerβ β
β β API β β (INT8) β β (MOT) β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β β β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β 100ms κ°κ²© β β Face β β κ°μμ κ°μ§ β β
β β νλ μ μΊ‘μ² β β Detection β β (y=500) β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β β
β ββββββββββββββββββββ β
β β Socket.IO β β
β β Client β β
β ββββββββββββββββββββ β
βββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββ
β Flask Server β
β (Socket.IO) β
βββββββββββββββββββββββ
- νμΌλͺ
:
yolonas_quantized.tflite - μ λ ₯ ν¬κΈ°: 640 Γ 640 Γ 3 (RGB)
- μΆλ ₯:
- Bounding Boxes: [1, 8400, 4]
- Confidence Scores: [1, 8400]
- Class Indices: [1, 8400]
- κ°μ§ κ°μ²΄: μ¬λ, μ»΅, λ°λλ, μ¬κ³Ό (COCO λ°μ΄ν°μ κΈ°λ°)
- μμν: INT8 (λͺ¨λΈ ν¬κΈ° μΆμ λ° μΆλ‘ μλ ν₯μ)
- νμΌλͺ
:
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μμ μ 곡νλ μ¬μ μ΅μ ν λͺ¨λΈ
- νκ²: Snapdragon μΉ©μ μ NPU (Neural Processing Unit)
- μ΅μ ν λ΄μ©:
- INT8 μμνλ‘ λͺ¨λΈ ν¬κΈ° μΆμ
- λͺ¨λ°μΌ νκ²½μ μ΅μ νλ μ°μ° ꡬ쑰
- NNAPI νμ©μΌλ‘ νλμ¨μ΄ κ°μ
컀μ€ν MOT (Multi-Object Tracking)
- DeepSORTκ° μλ IoU + μλ μμΈ‘ + μμ νΉμ§ κΈ°λ° κ²½λ μΆμ μκ³ λ¦¬μ¦
- λͺ¨λ°μΌ νκ²½μμ μ€μκ° μ²λ¦¬ κ°λ₯νλλ‘ μ΅μ ν
νμ¬ νλ μμ νμ§ κ²°κ³Ό β μ΄μ νλ μμ μΆμ κ°μ²΄
ββ IoU (Intersection over Union) κ³μ°
ββ μκ³κ° 0.25 μ΄μμ΄λ©΄ κ°μ κ°μ²΄λ‘ νμ
// μ΄λ νκ· νν° μ μ© (κ°μ€μΉ 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λ°°) μ μ©
μ¬λ λ°μ΄λ© λ°μ€μμ:
ββ μ체 μμ: 25%~50% λμ΄
ββ ν체 μμ: 60%~85% λμ΄
ββ μ€μ μΆμΆ: μ’μ° 20% μ μΈ (λ°°κ²½ λ
Έμ΄μ¦ λ°©μ§)
κ° μμμ RGB νκ· κ° μΆμΆ
ββ μμ μ μ¬λ κ³μ° (μ ν΄λ¦¬λ 거리 μμ)
μ€μλ§ μΆμΆνλ μ΄μ
- μ νν μ체/ν체 μμ μΆμΆ
- λ°°κ²½μ΄λ λ€λ₯Έ κ°μ²΄μ κ°μ μ΅μν
μ¬λ κ°μ²΄ (μΌλ° μν©)
μ μ = 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
κ°μ€μΉ κ²°μ : μ€νμ μΌλ‘ μ΅μ ν
κ°μ§ 쑰건
λ μ¬λμ΄ κ²ΉμΉ¨ (거리 < νκ· λλΉ Γ 1.2)
AND
μλ‘ λ°λ λ°©ν₯μΌλ‘ μ΄λ (μλ λ΄μ < 0)
μ²λ¦¬ λ°©λ²
1. μλ λ²‘ν° 1.5λ°° λΆμ€νΈ
2. λ°©ν₯ μ 보 κ°μ€μΉ μ¦κ°
3. ID μ€μμΉ λ°©μ§
1.5λ°° λΆμ€νΈ μ΄μ
- 2D νλ©΄μμ λΉ λ₯Έ μμ§μμ λ μ ννκ² μΆμ
- κ΅μ°¨ μ λ°©ν₯ μ 보μ μ λ’°λ ν₯μ
μλ‘μ΄ ID κ°μ§
ββ μ°μ νμ§ μΉ΄μ΄νΈ +1
ββ 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 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 νμ±λ¨
});// 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 μ£Όμ λ³κ²½ λ°©λ²
MainActivity.javaνμΌ μ΄κΈ°connectUrlλ³μ μμ - μ± μ¬λΉλ λ° μ€ν
μ°κ²° μν μ΄λ²€νΈ 리μ€λ
// μ°κ²° μ±κ³΅
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, "μλ² μ°κ²° λκΉ");
});// 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());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);// 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νλ μ μ°μ κ°μ§
// 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νλ μ λμ λ―Ένμ§
// 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: νλμ ν κ³ κ° IDobject: μν μ΄λ¦ (μ»΅/λ°λλ/μ¬κ³Ό)act:0: λ΄λ €λκΈ° (μ μ β μλ)1: μ§κΈ° (μ μλ β μ)
λ°μ 쑰건: κ°μμ (y=500) κ΅μ°¨ μ
// 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% μ§μ β μΌκ΅΄ μ€μ¬μ
ββ μ ν΄λ¦¬λ 거리 κ³μ°
ββ μ΅μ 거리μ μΌκ΅΄ μ ν
// 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 μ΄λ²€νΈλ‘ μ μ‘
from flask_socketio import emit
# νΉμ κ³ κ°λ€μ μΌκ΅΄ μ΄λ―Έμ§ μμ²
person_ids = [3, 5, 8]
emit('requestPersonFaceFind',
{'personIds': person_ids},
broadcast=True)// 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 μ΄λ²€νΈλ‘ μΌκ΅΄ μ΄λ―Έμ§ μ μ‘
# νΉμ ν€μ€μ€ν¬μμ κ°μ₯ κ°κΉμ΄ κ³ κ° μ°ΎκΈ° μμ²
emit('find_nearest_person',
{'kioskId': 'kiosk_1'},
broadcast=True)// 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 μ΄λ²€νΈλ‘ κ³ κ° μ 보 μ μ‘
| μ΄λ²€νΈλͺ | μ©λ | 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 μμ λ°©λ² |
|---|---|---|---|---|
| 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: μλμΌλ‘ μ¬μ°κ²° μλ
mSocket.connect(); // λμ΄μ§λ©΄ μλμΌλ‘ λ€μ μ°κ²°
// μΌλ° Socket: μλμΌλ‘ μ¬μ°κ²° λ‘μ§ κ΅¬ν νμ
while (true) {
try {
socket = new Socket("server", 5000);
break;
} catch (IOException e) {
Thread.sleep(1000); // μ§μ ꡬνν΄μΌ ν¨
}
}// Socket.IO: μ΄λ²€νΈλͺ
μΌλ‘ ꡬλΆ
mSocket.emit("message", data);
mSocket.emit("findPersonFace", faceData);
mSocket.on("requestPersonFaceFind", handler);
// μΌλ° Socket: νλ‘ν μ½ μ§μ μ€κ³ νμ
outputStream.write("MSG|" + data); // 컀μ€ν
νλ‘ν μ½
outputStream.write("FACE|" + faceData);// 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);Socket.IO μ°κ²° μλ:
1. WebSocket μ°κ²° μλ
2. μ€ν¨ μ HTTP Long PollingμΌλ‘ μλ μ ν
3. μμ μ μΈ ν΅μ 보μ₯
μΌλ° Socket:
- TCPλ§ μ§μ
- λ°©νλ²½/νλ‘μ νκ²½μμ μ°¨λ¨λ μ μμ
# μλ²μμ λͺ¨λ 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'
}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
μν : μ±μ λ©μΈ 컨νΈλ‘€λ¬
μ£Όμ κΈ°λ₯
- μΉ΄λ©λΌ μ μ΄: 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();
});
}μν : 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)μν : 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
)μν : 컀μ€ν λ€μ€ κ°μ²΄ μΆμ (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()μν : 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, λΌλ²¨, μ λ’°λ νμ
ββ κ°μμ νμ
ββ ν€μ€μ€ν¬ μμ νμ