{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# ๐ฏ [SEC01] ๊ธฐ์ง CCTV ๋๋ก ยท๋น์ธ๊ฐ ์ ๊ทผ ๊ฐ์ฒด ํ์ง ํ๋กํ ํ์
\n",
"### Google Colab ์ค์ต ํ๊ฒฝ (์์คยท๋ฐฉํธยท๊ฒฝ๊ณ / Level 3)\n",
"\n",
"[](https://colab.research.google.com/github/edunlab/milicontent2/blob/main/uploads/SEC01/SEC01_solution.ipynb)\n",
"\n",
"---\n",
"\n",
"## ๐ 1. ํ๋ก์ ํธ ๊ฐ์ ๋ฐ ๋ฐฐ๊ฒฝ\n",
"\n",
"| ํญ๋ชฉ | ๋ด์ฉ |\n",
"| :--- | :--- |\n",
"| **๊ณผ์ ๋ถ๋ฅ** | ์์คยท๋ฐฉํธยท๊ฒฝ๊ณ (Level 3) |\n",
"| **ํ์ต ์์ ์๊ฐ** | 10๊ฐ ๋ฏธ์
(5์ผ / ์ด 27.5์๊ฐ) |\n",
"| **ํ๊ฒฝ ์ฌ์** | Python 3.10+, numpy, pandas, matplotlib, scipy, scikit-learn |\n",
"\n",
"### ๐ ํ๋ก์ ํธ ๋ชฉํ\n",
"๋ฏธ๊ตญ-์ด๋ ์ ์์์๋ ์ํ ๋๋ก ๊ณผ ์ฐจ๋ยท์ธ์์ ์ ๊ทผ ์๋๊ฐ ๊ธฐ์ง ์ธ๊ณฝ ๊ฒฝ๊ณ์ ์ฃผ์ ์ํ์ผ๋ก ๋ถ๊ฐ๋์๊ณ , ๋ง์ CCTV ํ๋ฉด์์ ์ค์ ์นจ์
๊ณผ ์๋ชฉ ํ๋ค๋ฆผยท์ผ์๋๋ฌผ์ ๋น ๋ฅด๊ฒ ๊ตฌ๋ถํ๋ ์ผ์ด ์์คยท๋ฐฉํธ ์
๋ฌด์ ํต์ฌ์ด ๋์๋ค. ๋ณธ ํ๋ก์ ํธ์์๋ ๊ต์ก์ฉ ๊ฐ์ CCTV ์์์ ์ฝ๋๋ก ์ง์ ๊ทธ๋ ค, ์์ ๋ฐ์ดํฐ ์คํค๋ง ์ ์, ์กฐ๋ยท๊ธฐ์ ์ ์ฒ๋ฆฌ์ ๊ฒฝ๊ณ๊ตฌ์ญ(ROI) ์ค์ , ์ธํ๋ฆฌ ์ ๊ทผ ๊ฐ์ฒด ํ์ง Baseline, ์ํ ๋๋ก ยท์์ฅ ์นจ์
์ ์๋ณ ๊ณ ๋ํ, ์ค๊ฒฝ๋ณด ํํฐ์ ์ ๋ขฐ๋ ์ ์, ๋ค์ฑ๋ ๊ด์ ์ํฉํ, ๊ฒฝ๊ณ๋ณ ์ก์ ํ์ธ ์ค์ฌ์ HITL ์น์ธ, ์
์ฒํยท๋ค์ง์ ๋์ ์นจ์
์คํธ๋ ์ค ์ํ, AAR ์ฌ๋ณด์ , ์ต์ข
ํตํฉ๊น์ง ๋น์ธ๊ฐ ์ ๊ทผ ํ์ง ํ๋กํ ํ์
์ ๋จ๊ณ๋ณ๋ก ๊ตฌํํ๋ค. ์ต์ข
๋ชฉํ๋ ์นจ์
๊ฐ์ฒด๋ฅผ ๋์น์ง ์์ผ๋ฉด์ ์ค๊ฒฝ๋ณด๋ฅผ ์ค์ด๊ณ , ๊ฒฝ๋ณด ํด์ ์ ์ถ๋ ๊ฒฐ์ฌ์ ๋ฐ๋์ ์ฌ๋์ด ํ์ธํ๋๋ก ์ค๊ณํ๋ ๊ฒ์ด๋ค.\n",
"\n",
"> **โ ๏ธ [๊ต์ก์ฉ ๊ฐ์ ๋ฐ์ดํฐ์
์์น]**\n",
"> - ๋ณธ ๋
ธํธ๋ถ์ ๋ชจ๋ ๋ฐ์ดํฐ(์ ์, ๋ก๊ทธ, ์๋๋ฆฌ์ค ๋ฑ)๋ ๋ณด์ ๊ท์ ์ ์ค์ํ์ฌ **์ฝ๋ ๋ด ๋์ ๊ธฐ๋ฐ ๊ฐ์ ๋ฐ์ดํฐ๋ก ์์ฑ**๋ฉ๋๋ค.\n",
"> - ์ค์ ๋ถ๋ ์์น, ์์ ์ธ๊ฐ, ์ค์ ์ ์ฅ ์ ๋ณด, ๊ตฐ์ฌ ๊ธฐ๋ฐ ๋ฐ์ดํฐ๋ ์ผ์ฒด ํฌํจ๋์ง ์์ต๋๋ค.\n",
"\n",
"---\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ============================================================================\n",
"# [Google Colab ํ๊ฒฝ ์ค์ ] ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น ๋ฐ ์ํฌํธ\n",
"# ============================================================================\n",
"!pip install -q numpy pandas matplotlib scipy scikit-learn\n",
"\n",
"import os\n",
"import sys\n",
"import time\n",
"import math\n",
"import json\n",
"import re\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"import warnings\n",
"warnings.filterwarnings('ignore')\n",
"\n",
"# ๊ทธ๋ํ ์๋ฌธ ํฐํธ ๊ธฐ๋ณธ ์ค์ (ํ๊ฒฝ ๋
๋ฆฝ์ฑ ๋ณด์ฅ)\n",
"plt.rcParams['font.sans-serif'] = 'DejaVu Sans'\n",
"plt.rcParams['axes.unicode_minus'] = False\n",
"\n",
"print(f\"์คํ ํ๊ฒฝ ์ค๋น ์๋ฃ (Python {sys.version.split()[0]})\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
01. ๊ตฐ์ฌ๊ธฐ์ง CCTV ์์ ๊ท๊ฒฉ ๋ถ์ ๋ฐ ๋น์ธ๊ฐ ์นจ์
๊ฐ์ฒด(๋๋ก /์ฐจ๋/์ธ์) ๋ฐ์ดํฐ์
์ ์\n",
"**์์์๊ฐ**: ์ฝ 1.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๊ตฐ์ฌ๊ธฐ์ง ์ธ๊ณฝ ๊ฐ์ CCTV ์์ ๊ท๊ฒฉ๊ณผ ๋น์ธ๊ฐ ์นจ์
๊ฐ์ฒด(๋๋ก , ์ฐจ๋, ๊ฑฐ๋์์์) ๋ฐ์ดํฐ์
์ ์ ์ํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๊ธฐ์ง ์ธ๊ณฝ ๊ณ ์ CCTV ์์์ ๊ท๊ฒฉ๊ณผ ๋น์ธ๊ฐ ์นจ์
๊ฐ์ฒด(๋๋ก ยท์ฐจ๋ยท์ธ์) ๋ผ๋ฒจ ๊ตฌ์กฐ๋ฅผ ์ ์ํ๊ณ , ์๋ชป๋ ๋ฐ์ดํฐ๋ฅผ ๊ฑธ๋ฌ๋ด๋ ๊ฒ์ฆ๊ธฐ๋ฅผ ๋ง๋ ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ธ์, ์ฐจ๋, ๋๋ก ์ ์นจ์
๋์์ผ๋ก, ๋๋ฌผ๊ณผ ์กฐ๋ฅ๋ฅผ ์ค๊ฒฝ๋ณด ์ ๋ฐ ๋์์ผ๋ก ์ ํด ํด๋์ค ์ฒด๊ณ๋ฅผ ์ธ์ฐ๊ณ ์นด๋ฉ๋ผ(ํด์๋, fps), ์ ์ ํ๋ผ๋ฏธํฐ(๊ธฐ์, ๋ฐ๋, ์์น์ค์ฐจ, ํต์ ์ง์ฐ), ๊ฒฝ๊ณ๊ตฌ์ญ ํ ๋ฒ์, ๋ฐ์ด๋ฉ ๋ฐ์ค ๋ผ๋ฒจ ํ๋๋ฅผ ๋ด์ ์คํค๋ง ์ค๊ณํ๊ณ ์์ฑํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `data/schema/sec01_input_schema.json ๋ฐ docs/sec01_data_spec.md` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์์คยท๋ฐฉํธยท๊ฒฝ๊ณ ์ค๋ฌด ๋ฐ ๋ฏธ๊ตญ-์ด๋ ์ ์ ์ ์ฅ ํ๊ฒฝ๊ณผ์ ๊ตฐ์ฌ์ ๋ถํฉ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์คํค๋ง ํ๋ ์ ์ ์์ฑ๋ 100%, ์ํ ๋ฐ์ดํฐ ๊ฒ์ฆ ์ค๋ฅ 0๊ฑด\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์
๋ ฅ ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฆ ์คํฌ๋ฆฝํธ ํต๊ณผ ๋ฐ ๋ช
์ธ์ ๋ฌธ์ํ ์๋ฃ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: JSON Schema ๊ฒ์ฆ ๋๊ตฌ ๋ฐ ์ํ ๋ฐ์ดํฐ ํ์ฑ ๋จ์ ํ
์คํธ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"๊ธฐ์ง ์ธ๊ณฝ CCTV ์์ ๋ฉํ๋ฐ์ดํฐ์ ๋น์ธ๊ฐ ์นจ์
๊ฐ์ฒด(๋๋ก ยท์ฐจ๋ยท์ธ์) ๋ผ๋ฒจ์ ์คํค๋ง(์นด๋ฉ๋ผ ๊ท๊ฒฉ, ์ ์ ํ๋ผ๋ฏธํฐ, ๊ฒฝ๊ณ๊ตฌ์ญ, ๋ฐ์ด๋ฉ ๋ฐ์ค)๋ฅผ ์ ์ํ๊ณ , ์ฝ๋๋ก ๊ทธ๋ฆฐ ํฉ์ฑ ์์์์ ๋ง๋ ์ํ 20๊ฑด์ ๊ฒ์ฆํ๋ค.\"\"\"\n",
"import json\n",
"import re\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# ---------------- [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํฉ์ฑ CCTV ์์ ์์ฑ๊ธฐ ----------------\n",
"# ์ค์ ์์ ๋์ ์ฝ๋๋ก ๊ทธ๋ฆฐ ๊ฐ์ฒด(์ฌ๊ฐํยท์ )๋ก ๊ธฐ์ง ์ธ๊ณฝ ๊ณ ์ ์นด๋ฉ๋ผ ํ๋ฉด์ ํ๋ด ๋ธ๋ค.\n",
"H, W, T = 64, 96, 10 # ํ๋ฉด ํฌ๊ธฐ(px), ์ํ์ค ๊ธธ์ด(ํ๋ ์)\n",
"HORIZON, FENCE = 20, 46 # ํ๋/์ง๋ฉด ๊ฒฝ๊ณ, ์ธํ๋ฆฌ ํ\n",
"ROI_ROWS = (32, 48) # ์ธํ๋ฆฌ ์ ๊ทผ ๊ฒฝ๊ณ๊ตฌ์ญ(ROI): ์ง๋ฉด 32~47ํ\n",
"CLASSES = [\"person\", \"vehicle\", \"drone\", \"animal\", \"bird\"]\n",
"INTRUDER = {\"person\", \"vehicle\", \"drone\"} # ๋น์ธ๊ฐ ์ ๊ทผ(๊ฒฝ๋ณด ๋์)\n",
"CONDS = [\"day\", \"night\", \"rain\", \"fog\"]\n",
"\n",
"\n",
"def background(rng):\n",
" # [TODO : ํ์ ์ค์ต 1] ๊ตฐ์ฌ๊ธฐ์ง CCTV ์์ ๊ท๊ฒฉ ๋ถ์ ๋ฐ ๋น์ธ๊ฐ ์นจ์
๊ฐ์ฒด(๋๋ก /์ฐจ๋/์ธ์) ๋ฐ์ดํฐ์
์ ์ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def tree(img, t, wind):\n",
" \"\"\"์ข์ธก ์๋ชฉ ์๊ด: ๋ฐ๋ ์ธ๊ธฐ์ ๋ฐ๋ผ ์ข์ฐ๋ก ํ๋ค๋ ค ์ค๊ฒฝ๋ณด๋ฅผ ๋ง๋ ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 1] ๊ตฐ์ฌ๊ธฐ์ง CCTV ์์ ๊ท๊ฒฉ ๋ถ์ ๋ฐ ๋น์ธ๊ฐ ์นจ์
๊ฐ์ฒด(๋๋ก /์ฐจ๋/์ธ์) ๋ฐ์ดํฐ์
์ ์ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_seq(seed, cond=None, objs=None, wind=None, camo=False):\n",
" rng = np.random.default_rng(seed)\n",
" cond = cond or str(rng.choice(CONDS))\n",
" wind = rng.uniform(0, 2) if wind is None else wind\n",
" bg = background(rng)\n",
" n = int(rng.integers(1, 4)) if objs is None else len(objs)\n",
" kinds = objs or list(rng.choice(CLASSES, n, p=[0.3, 0.15, 0.25, 0.15, 0.15]))\n",
" obs = []\n",
" for k in kinds:\n",
" if k == \"person\":\n",
" o = dict(y=rng.uniform(30, 36), x=rng.uniform(15, 85), vy=rng.uniform(0.8, 1.2), vx=rng.uniform(-0.6, 0.6), h=7, w=3,\n",
" val=72 if camo else rng.uniform(35, 50))\n",
" elif k == \"vehicle\":\n",
" o = dict(y=rng.uniform(33, 38), x=rng.uniform(10, 30), vy=0.2, vx=rng.uniform(2, 4), h=5, w=10, val=rng.uniform(150, 170))\n",
" elif k == \"drone\":\n",
" o = dict(y=rng.uniform(3, 15), x=rng.uniform(10, 80), vy=rng.uniform(0, 0.6), vx=rng.uniform(-2, 2), h=2, w=2 if camo else 3,\n",
" val=rng.uniform(80, 120))\n",
" elif k == \"animal\":\n",
" o = dict(y=rng.uniform(34, 42), x=rng.uniform(15, 80), vy=0, vx=rng.uniform(-1.5, 1.5), h=2, w=4, val=rng.uniform(60, 75))\n",
" else:\n",
" o = dict(y=rng.uniform(3, 15), x=rng.uniform(10, 80), vy=0, vx=rng.uniform(-3, 3), h=1, w=2, val=rng.uniform(100, 130))\n",
" o[\"cls\"] = str(k); obs.append(o)\n",
" frames, labels = [], []\n",
" for t in range(-8, T): # ์์ ์๊ฐ = ๊ฐ์ฒด ์๋ ๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ(์ฌ์ ๊ด์ธก)\n",
" f = bg.copy(); tree(f, t, wind); lab = []\n",
" for oi, o in enumerate(obs if t >= 0 else []):\n",
" jit = rng.normal(0, 1.2, 2) if o[\"cls\"] in (\"animal\", \"bird\") else (0, 0) # ๋ถ๊ท์น ์์ง์\n",
" y, x = int(o[\"y\"] + o[\"vy\"] * t + jit[0]), int(o[\"x\"] + o[\"vx\"] * t + jit[1])\n",
" y0, x0, y1, x1 = max(y, 0), max(x, 0), min(y + o[\"h\"], H), min(x + o[\"w\"], W)\n",
" if y1 > y0 and x1 > x0:\n",
" f[y0:y1, x0:x1] = o[\"val\"]\n",
" if o[\"cls\"] == \"person\":\n",
" f[max(y0 - 2, 0):y0, x0 + 1:x0 + 2] = o[\"val\"] # ๋จธ๋ฆฌ\n",
" lab.append({\"cls\": o[\"cls\"], \"oid\": oi, \"bbox\": (x0, y0, x1, y1), \"intruder\": o[\"cls\"] in INTRUDER})\n",
" if cond == \"night\":\n",
" f = f * 0.5 + 10 + rng.normal(0, 3.5, f.shape)\n",
" else:\n",
" f = f + rng.normal(0, 2.5, f.shape)\n",
" if cond == \"rain\":\n",
" m = rng.random(f.shape) < 0.006\n",
" for d in range(3):\n",
" f[d:][m[:H - d]] += 45\n",
" if cond == \"fog\":\n",
" f = 0.45 * f + 0.55 * 150\n",
" frames.append(np.clip(f, 0, 255)); labels.append(lab)\n",
" fr = np.array(frames)\n",
" return {\"id\": f\"CAM-{seed:04d}\", \"cond\": cond, \"wind\": wind, \"ref\": fr[:8], \"frames\": fr[8:], \"labels\": labels[8:]}\n",
"\n",
"# 1) ์คํค๋ง ์ ์: ์นด๋ฉ๋ผ ๊ท๊ฒฉ, ์ ์ ํ๋ผ๋ฏธํฐ(๊ธฐ์ยท์์น์ค์ฐจยทํต์ ์ง์ฐ), ๊ฒฝ๊ณ๊ตฌ์ญ(ROI), ๊ฐ์ฒด ๋ผ๋ฒจ\n",
"SCHEMA = {\n",
" \"required\": [\"camera_id\", \"n_frames\", \"camera\", \"tactical\", \"roi\", \"labels\"],\n",
" \"fields\": {\n",
" \"camera_id\": {\"type\": str, \"pattern\": r\"^CAM-\\d{4}$\"},\n",
" \"n_frames\": {\"type\": int, \"min\": 1, \"max\": 3000},\n",
" },\n",
" \"camera\": {\n",
" \"mode\": {\"type\": str, \"enum\": [\"EO\", \"IR\"]},\n",
" \"resolution\": {\"type\": list, \"length\": 2},\n",
" \"fps\": {\"type\": (int, float), \"min\": 1, \"max\": 60},\n",
" },\n",
" \"tactical\": {\n",
" \"condition\": {\"type\": str, \"enum\": [\"day\", \"night\", \"rain\", \"fog\"]},\n",
" \"wind_level\": {\"type\": (int, float), \"min\": 0, \"max\": 10},\n",
" \"position_error_m\": {\"type\": (int, float), \"min\": 0, \"max\": 100},\n",
" \"comm_delay_ms\": {\"type\": (int, float), \"min\": 0, \"max\": 10000},\n",
" },\n",
" \"roi\": {\n",
" \"row_start\": {\"type\": int, \"min\": 0, \"max\": 200},\n",
" \"row_end\": {\"type\": int, \"min\": 1, \"max\": 200},\n",
" },\n",
" \"label\": {\n",
" \"class\": {\"type\": str, \"enum\": CLASSES},\n",
" \"bbox\": {\"type\": list, \"length\": 4},\n",
" \"frame\": {\"type\": int, \"min\": 0},\n",
" \"intruder\": {\"type\": bool},\n",
" },\n",
"}\n",
"\n",
"\n",
"def check(name, value, rule):\n",
" \"\"\"๊ฐ ํ๋๋ฅผ ๊ท์น(type, enum, min, max, length, pattern)์ผ๋ก ๊ฒ์ฌํด ์ค๋ฅ ๋ชฉ๋ก์ ๋๋ ค์ค๋ค.\"\"\"\n",
" if not isinstance(value, rule[\"type\"]) or (isinstance(value, bool) and rule[\"type\"] is not bool):\n",
" return [f\"{name}: ์๋ฃํ ์ค๋ฅ({type(value).__name__})\"]\n",
" errs = []\n",
" if \"enum\" in rule and value not in rule[\"enum\"]:\n",
" errs.append(f\"{name}: ํ์ฉ๋์ง ์์ ๊ฐ '{value}'\")\n",
" if \"min\" in rule and value < rule[\"min\"]:\n",
" errs.append(f\"{name}: ์ต์๊ฐ {rule['min']} ๋ฏธ๋ง\")\n",
" if \"max\" in rule and value > rule[\"max\"]:\n",
" errs.append(f\"{name}: ์ต๋๊ฐ {rule['max']} ์ด๊ณผ\")\n",
" if \"length\" in rule and len(value) != rule[\"length\"]:\n",
" errs.append(f\"{name}: ๊ธธ์ด {rule['length']} ํ์\")\n",
" if \"pattern\" in rule and not re.match(rule[\"pattern\"], value):\n",
" errs.append(f\"{name}: ํ์ ๋ถ์ผ์น '{value}'\")\n",
" return errs\n",
"\n",
"\n",
"def validate(rec):\n",
" errs = [f\"{k}: ํ์ ํ๋ ๋๋ฝ\" for k in SCHEMA[\"required\"] if k not in rec]\n",
" for k, rule in SCHEMA[\"fields\"].items():\n",
" if k in rec:\n",
" errs += check(k, rec[k], rule)\n",
" for group in (\"camera\", \"tactical\", \"roi\"):\n",
" for k, rule in SCHEMA[group].items():\n",
" if k not in rec.get(group, {}):\n",
" errs.append(f\"{group}.{k}: ํ์ ํ๋ ๋๋ฝ\")\n",
" else:\n",
" errs += check(f\"{group}.{k}\", rec[group][k], rule)\n",
" roi = rec.get(\"roi\", {})\n",
" if \"row_start\" in roi and \"row_end\" in roi and roi[\"row_start\"] >= roi[\"row_end\"]:\n",
" errs.append(\"roi: row_start๋ row_end๋ณด๋ค ์์์ผ ํจ\")\n",
" for i, lab in enumerate(rec.get(\"labels\", [])):\n",
" for k, rule in SCHEMA[\"label\"].items():\n",
" if k not in lab:\n",
" errs.append(f\"labels[{i}].{k}: ํ์ ํ๋ ๋๋ฝ\")\n",
" else:\n",
" errs += check(f\"labels[{i}].{k}\", lab[k], rule)\n",
" return errs\n",
"\n",
"\n",
"# 2) [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํฉ์ฑ CCTV ์ํ์ค 20๊ฐ์์ ๋ฉํ๋ฐ์ดํฐ ์ํ ์์ฑ\n",
"rng = np.random.default_rng(1)\n",
"records = []\n",
"for i in range(20):\n",
" seq = make_seq(100 + i)\n",
" labels = [{\"class\": l[\"cls\"], \"frame\": t, \"bbox\": [int(v) for v in l[\"bbox\"]], \"intruder\": bool(l[\"intruder\"])}\n",
" for t in (0, 5, 9) for l in seq[\"labels\"][t]]\n",
" records.append({\"camera_id\": seq[\"id\"], \"n_frames\": T,\n",
" \"camera\": {\"mode\": \"IR\" if seq[\"cond\"] == \"night\" else \"EO\", \"resolution\": [W, H], \"fps\": 10},\n",
" \"tactical\": {\"condition\": seq[\"cond\"], \"wind_level\": round(float(seq[\"wind\"]) * 2.5, 1),\n",
" \"position_error_m\": round(float(rng.uniform(1, 15)), 1), \"comm_delay_ms\": int(rng.integers(20, 400))},\n",
" \"roi\": {\"row_start\": ROI_ROWS[0], \"row_end\": ROI_ROWS[1]}, \"labels\": labels})\n",
"\n",
"# 3) ์ ์ ๋ฐ์ดํฐ ๊ฒ์ฆ\n",
"ok = sum(1 for r in records if not validate(r))\n",
"print(f\"์ ์ ์ํ {len(records)}๊ฑด ์ค ๊ฒ์ฆ ํต๊ณผ {ok}๊ฑด, ์ค๋ฅ {len(records) - ok}๊ฑด -> ๋ชฉํ(๊ฒ์ฆ ์ค๋ฅ 0๊ฑด): {'๋ฌ์ฑ' if ok == len(records) else '๋ฏธ๋ฌ'}\")\n",
"\n",
"# 4) ์ค๋ฅ ์ฃผ์
์ํ: ๊ฒ์ฆ๊ธฐ๊ฐ ์๋ชป๋ ๋ฐ์ดํฐ๋ฅผ ์ก์๋ด๋์ง ํ์ธ\n",
"bad = json.loads(json.dumps(records[0]))\n",
"bad[\"camera_id\"] = \"CAM-1\" # ํ์\n",
"bad[\"camera\"][\"fps\"] = 120 # ๋ฒ์\n",
"del bad[\"tactical\"][\"comm_delay_ms\"] # ๋๋ฝ\n",
"bad[\"labels\"][0][\"class\"] = \"ufo\" # ํ์ฉ๊ฐ\n",
"bad[\"roi\"][\"row_start\"] = 50 # ๋
ผ๋ฆฌ(์์ > ๋)\n",
"errs = validate(bad)\n",
"print(f\"\\n[์ค๋ฅ ์ฃผ์
๊ฒฐ๊ณผ] ์ฃผ์
5์ข
-> ๊ฒ์ถ {len(errs)}๊ฑด\")\n",
"for e in errs:\n",
" print(\" -\", e)\n",
"print(f\"์ฃผ์
์ค๋ฅ ์ ๋ถ ๊ฒ์ถ: {'์' if len(errs) >= 5 else '์๋์ค'}\")\n",
"\n",
"# 5) ์คํค๋ง ํ๋ ์ ์์(๋ช
์ธ์ ์ด์)\n",
"rows = [(\"๊ธฐ๋ณธ\", k, r[\"type\"].__name__, str({x: y for x, y in r.items() if x != \"type\"})) for k, r in SCHEMA[\"fields\"].items()]\n",
"for g in (\"camera\", \"tactical\", \"roi\", \"label\"):\n",
" for k, r in SCHEMA[g].items():\n",
" t = r[\"type\"].__name__ if isinstance(r[\"type\"], type) else \"number\"\n",
" rows.append((g, k, t, str({x: y for x, y in r.items() if x != \"type\"})))\n",
"print(\"\\n[๋ฐ์ดํฐ ๋ช
์ธ ์์ฝ] ํ๋ ์\", len(rows))\n",
"print(pd.DataFrame(rows, columns=[\"๊ทธ๋ฃน\", \"ํ๋\", \"์๋ฃํ\", \"๊ท์น\"]).to_string(index=False))\n",
"print(\"\\n์นจ์
๋์ ํด๋์ค:\", sorted(INTRUDER), \"/ ์ค๊ฒฝ๋ณด ์ ๋ฐ ํด๋์ค: animal, bird, ์๋ชฉ ํ๋ค๋ฆผ(clutter)\")\n",
"print(\"ํด๋์ค๋ณ ๋ผ๋ฒจ ์:\", pd.Series([l[\"class\"] for r in records for l in r[\"labels\"]]).value_counts().to_dict())\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
01 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"์ ์ ์ํ 20๊ฑด ์ค ๊ฒ์ฆ ํต๊ณผ 20๊ฑด, ์ค๋ฅ 0๊ฑด -> ๋ชฉํ(๊ฒ์ฆ ์ค๋ฅ 0๊ฑด): ๋ฌ์ฑ\n",
"\n",
"[์ค๋ฅ ์ฃผ์
๊ฒฐ๊ณผ] ์ฃผ์
5์ข
-> ๊ฒ์ถ 5๊ฑด\n",
" - camera_id: ํ์ ๋ถ์ผ์น 'CAM-1'\n",
" - camera.fps: ์ต๋๊ฐ 60 ์ด๊ณผ\n",
" - tactical.comm_delay_ms: ํ์ ํ๋ ๋๋ฝ\n",
" - roi: row_start๋ row_end๋ณด๋ค ์์์ผ ํจ\n",
" - labels[0].class: ํ์ฉ๋์ง ์์ ๊ฐ 'ufo'\n",
"์ฃผ์
์ค๋ฅ ์ ๋ถ ๊ฒ์ถ: ์\n",
"\n",
"[๋ฐ์ดํฐ ๋ช
์ธ ์์ฝ] ํ๋ ์ 15\n",
" ๊ทธ๋ฃน ํ๋ ์๋ฃํ ๊ท์น\n",
" ๊ธฐ๋ณธ camera_id str {'pattern': '^CAM-\\\\d{4}$'}\n",
" ๊ธฐ๋ณธ n_frames int {'min': 1, 'max': 3000}\n",
" camera mode str {'enum': ['EO', 'IR']}\n",
" camera resolution list {'length': 2}\n",
" camera fps number {'min': 1, 'max': 60}\n",
"tactical condition str {'enum': ['day', 'night', 'rain', 'fog']}\n",
"tactical wind_level number {'min': 0, 'max': 10}\n",
"tactical position_error_m number {'min': 0, 'max': 100}\n",
"tactical comm_delay_ms number {'min': 0, 'max': 10000}\n",
" roi row_start int {'min': 0, 'max': 200}\n",
" roi row_end int {'min': 1, 'max': 200}\n",
" label class str {'enum': ['person', 'vehicle', 'drone', 'animal', 'bird']}\n",
" label bbox list {'length': 4}\n",
" label frame int {'min': 0}\n",
" label intruder bool {}\n",
"\n",
"์นจ์
๋์ ํด๋์ค: ['drone', 'person', 'vehicle'] / ์ค๊ฒฝ๋ณด ์ ๋ฐ ํด๋์ค: animal, bird, ์๋ชฉ ํ๋ค๋ฆผ(clutter)\n",
"ํด๋์ค๋ณ ๋ผ๋ฒจ ์: {'person': 57, 'vehicle': 27, 'drone': 21, 'bird': 15, 'animal': 15}\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
02. ์ฃผ/์ผ๊ฐ ์กฐ๋ ๋ณํ ๋ณด์ , ๊ธฐ์ ์ก์ ํํฐ๋ง ๋ฐ ์นจ์
๊ฒฝ๊ณ๊ตฌ์ญ(ROI) ์ค์ ์ ์ฒ๋ฆฌ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์ฃผ/์ผ๊ฐ ์กฐ๋ ๋ณํ ๋ณด์ , ๊ธฐ์ ์ก์ ์ ๊ฑฐ ๋ฐ ์ธํ๋ฆฌ ๊ด์ฌ๊ตฌ์ญ(ROI) ์ค์ ์ ์ฒ๋ฆฌ ๋ชจ๋์ ๊ตฌํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ฃผ์ผ๊ฐ ์กฐ๋ ๋ณํ, ๋นยท์ก์, ์ผ์ ๋จ์ ์ด ์์ธ CCTV ์์์ ์กฐ๊ฑด๋ณ๋ก ๋ณด์ ํ๊ณ ์ธํ๋ฆฌ ๊ฒฝ๊ณ๊ตฌ์ญ(ROI)์ ์ค์ ํ๋ ์ ์ฒ๋ฆฌ ๋ชจ๋์ ๊ตฌํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ผ์ ๋จ์ (์ ํ๋ฉด 0) ํ๋ ์์ ์ง์ ํ๋ ์์ผ๋ก ๋์ฒดํ๊ณ , ๋ฐฑ๋ถ์ ์ ์ฅ์ผ๋ก ์กฐ๋๋ฅผ ๋ง์ถ๋ฉฐ, ๋น ์กฐ๊ฑด์์๋ 3x3 ์ค์๊ฐ์ผ๋ก ๋น์ค๊ธฐ๋ฅผ ์ค์ด๊ณ , ํ๋๊ณผ ์ธํ๋ฆฌ ์ ๊ทผ ์ง๋ฉด ๋ ๋ฅผ ๊ฒฝ๊ณ๊ตฌ์ญ ๋ง์คํฌ๋ก ์ค์ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/preprocess/sec01_preprocessor.py ๋ฐ ์ ์ฒ๋ฆฌ ์ ํ ๋ฐ์ดํฐ์
` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์ ์ ํ๊ฒฝ ํน์ ์ ๋
ธ์ด์ฆ(์คํธํ, ๋ฐ์ฌํ, ์ผ์ ๋จ์ ๋ฑ) ์ ๊ฑฐ ํ๋น์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ๋ฐ์ดํฐ ์ ์ ์ฒ๋ฆฌ ์๋ 1,000๊ฑด/์ด ์ด์, ์ ์ฒ๋ฆฌ ์ค๋ฅ์จ 0%\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์ ์ ํ์ดํ๋ผ์ธ ์๋ํ ๋ฐ ๋จ์ ํ
์คํธ ์ผ์ด์ค 5์ข
ํต๊ณผ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: pytest ๊ธฐ๋ฐ ์ ์ฒ๋ฆฌ ์ ํฉ์ฑ ํ
์คํธ ๋ฐ ๊ฒฐ์ธก์น ๋ณด์ ์จ ํ์ธ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"์ฃผ/์ผ๊ฐ ์กฐ๋ ๋ณด์ , ๋นยท์ก์ ํํฐ, ์ผ์ ๋จ์ ํ๋ ์ ๋ณด์ , ์ธํ๋ฆฌ ๊ฒฝ๊ณ๊ตฌ์ญ(ROI) ๋ง์คํฌ๋ฅผ ๋ฌถ์ CCTV ์ ์ฒ๋ฆฌ ๋ชจ๋์ ๊ตฌํํ๊ณ , ์ฒ๋ฆฌ ์๋ยท๋จ์ ํ
์คํธ 5์ข
ยท์กฐ๊ฑด๋ณ ์ ์ฒ๋ฆฌ ์ ํ ํ์ง ์ฑ๋ฅ์ ์ธก์ ํ๋ค.\"\"\"\n",
"import time\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from scipy import ndimage\n",
"\n",
"# ---------------- [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํฉ์ฑ CCTV ์์ ์์ฑ๊ธฐ ----------------\n",
"# ์ค์ ์์ ๋์ ์ฝ๋๋ก ๊ทธ๋ฆฐ ๊ฐ์ฒด(์ฌ๊ฐํยท์ )๋ก ๊ธฐ์ง ์ธ๊ณฝ ๊ณ ์ ์นด๋ฉ๋ผ ํ๋ฉด์ ํ๋ด ๋ธ๋ค.\n",
"H, W, T = 64, 96, 10 # ํ๋ฉด ํฌ๊ธฐ(px), ์ํ์ค ๊ธธ์ด(ํ๋ ์)\n",
"HORIZON, FENCE = 20, 46 # ํ๋/์ง๋ฉด ๊ฒฝ๊ณ, ์ธํ๋ฆฌ ํ\n",
"ROI_ROWS = (32, 48) # ์ธํ๋ฆฌ ์ ๊ทผ ๊ฒฝ๊ณ๊ตฌ์ญ(ROI): ์ง๋ฉด 32~47ํ\n",
"CLASSES = [\"person\", \"vehicle\", \"drone\", \"animal\", \"bird\"]\n",
"INTRUDER = {\"person\", \"vehicle\", \"drone\"} # ๋น์ธ๊ฐ ์ ๊ทผ(๊ฒฝ๋ณด ๋์)\n",
"CONDS = [\"day\", \"night\", \"rain\", \"fog\"]\n",
"\n",
"\n",
"def background(rng):\n",
" # [TODO : ํ์ ์ค์ต 2] ์ฃผ/์ผ๊ฐ ์กฐ๋ ๋ณํ ๋ณด์ , ๊ธฐ์ ์ก์ ํํฐ๋ง ๋ฐ ์นจ์
๊ฒฝ๊ณ๊ตฌ์ญ(ROI) ์ค์ ์ ์ฒ๋ฆฌ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def tree(img, t, wind):\n",
" \"\"\"์ข์ธก ์๋ชฉ ์๊ด: ๋ฐ๋ ์ธ๊ธฐ์ ๋ฐ๋ผ ์ข์ฐ๋ก ํ๋ค๋ ค ์ค๊ฒฝ๋ณด๋ฅผ ๋ง๋ ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 2] ์ฃผ/์ผ๊ฐ ์กฐ๋ ๋ณํ ๋ณด์ , ๊ธฐ์ ์ก์ ํํฐ๋ง ๋ฐ ์นจ์
๊ฒฝ๊ณ๊ตฌ์ญ(ROI) ์ค์ ์ ์ฒ๋ฆฌ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_seq(seed, cond=None, objs=None, wind=None, camo=False):\n",
" rng = np.random.default_rng(seed)\n",
" cond = cond or str(rng.choice(CONDS))\n",
" wind = rng.uniform(0, 2) if wind is None else wind\n",
" bg = background(rng)\n",
" n = int(rng.integers(1, 4)) if objs is None else len(objs)\n",
" kinds = objs or list(rng.choice(CLASSES, n, p=[0.3, 0.15, 0.25, 0.15, 0.15]))\n",
" obs = []\n",
" for k in kinds:\n",
" if k == \"person\":\n",
" o = dict(y=rng.uniform(30, 36), x=rng.uniform(15, 85), vy=rng.uniform(0.8, 1.2), vx=rng.uniform(-0.6, 0.6), h=7, w=3,\n",
" val=72 if camo else rng.uniform(35, 50))\n",
" elif k == \"vehicle\":\n",
" o = dict(y=rng.uniform(33, 38), x=rng.uniform(10, 30), vy=0.2, vx=rng.uniform(2, 4), h=5, w=10, val=rng.uniform(150, 170))\n",
" elif k == \"drone\":\n",
" o = dict(y=rng.uniform(3, 15), x=rng.uniform(10, 80), vy=rng.uniform(0, 0.6), vx=rng.uniform(-2, 2), h=2, w=2 if camo else 3,\n",
" val=rng.uniform(80, 120))\n",
" elif k == \"animal\":\n",
" o = dict(y=rng.uniform(34, 42), x=rng.uniform(15, 80), vy=0, vx=rng.uniform(-1.5, 1.5), h=2, w=4, val=rng.uniform(60, 75))\n",
" else:\n",
" o = dict(y=rng.uniform(3, 15), x=rng.uniform(10, 80), vy=0, vx=rng.uniform(-3, 3), h=1, w=2, val=rng.uniform(100, 130))\n",
" o[\"cls\"] = str(k); obs.append(o)\n",
" frames, labels = [], []\n",
" for t in range(-8, T): # ์์ ์๊ฐ = ๊ฐ์ฒด ์๋ ๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ(์ฌ์ ๊ด์ธก)\n",
" f = bg.copy(); tree(f, t, wind); lab = []\n",
" for oi, o in enumerate(obs if t >= 0 else []):\n",
" jit = rng.normal(0, 1.2, 2) if o[\"cls\"] in (\"animal\", \"bird\") else (0, 0) # ๋ถ๊ท์น ์์ง์\n",
" y, x = int(o[\"y\"] + o[\"vy\"] * t + jit[0]), int(o[\"x\"] + o[\"vx\"] * t + jit[1])\n",
" y0, x0, y1, x1 = max(y, 0), max(x, 0), min(y + o[\"h\"], H), min(x + o[\"w\"], W)\n",
" if y1 > y0 and x1 > x0:\n",
" f[y0:y1, x0:x1] = o[\"val\"]\n",
" if o[\"cls\"] == \"person\":\n",
" f[max(y0 - 2, 0):y0, x0 + 1:x0 + 2] = o[\"val\"] # ๋จธ๋ฆฌ\n",
" lab.append({\"cls\": o[\"cls\"], \"oid\": oi, \"bbox\": (x0, y0, x1, y1), \"intruder\": o[\"cls\"] in INTRUDER})\n",
" if cond == \"night\":\n",
" f = f * 0.5 + 10 + rng.normal(0, 3.5, f.shape)\n",
" else:\n",
" f = f + rng.normal(0, 2.5, f.shape)\n",
" if cond == \"rain\":\n",
" m = rng.random(f.shape) < 0.006\n",
" for d in range(3):\n",
" f[d:][m[:H - d]] += 45\n",
" if cond == \"fog\":\n",
" f = 0.45 * f + 0.55 * 150\n",
" frames.append(np.clip(f, 0, 255)); labels.append(lab)\n",
" fr = np.array(frames)\n",
" return {\"id\": f\"CAM-{seed:04d}\", \"cond\": cond, \"wind\": wind, \"ref\": fr[:8], \"frames\": fr[8:], \"labels\": labels[8:]}\n",
"\n",
"# ---------------- ์ ์ฒ๋ฆฌยทํ์ง ๊ณตํต ํจ์(๋ฐฐ๊ฒฝ ์ฐจ๋ถ ๊ธฐ๋ฐ) ----------------\n",
"def enhance(seq):\n",
" \"\"\"์กฐ๋ ๋ณด์ (๋ฐฑ๋ถ์ ์ ์ฅ) + ๋น ์กฐ๊ฑด์ ๋น์ค๊ธฐ ์ ๊ฑฐ(3x3 ์ค์๊ฐ). ๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ(ref)๊ณผ ๊ฐ์ ๊ธฐ์ค์ผ๋ก ๋ณด์ ํ๋ค.\"\"\"\n",
" allf = np.concatenate([seq[\"ref\"], seq[\"frames\"]])\n",
" f = ndimage.median_filter(allf, size=(1, 3, 3)) if seq[\"cond\"] == \"rain\" else allf\n",
" lo, hi = np.percentile(f, [1, 99.5])\n",
" f = np.clip((f - lo) / (hi - lo + 1e-6) * 255, 0, 255)\n",
" return f[:len(seq[\"ref\"])], f[len(seq[\"ref\"]):]\n",
"\n",
"\n",
"def detect(ref, frames, k=4.0, min_area=3, sky_k=None, smooth=False):\n",
" \"\"\"๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ์ ์ค์๊ฐ ๋ฐฐ๊ฒฝ๊ณผ ์ฐจ์ด๋ฅผ ๊ฐ๊ฑด ์๊ณ๊ฐ(k x MAD)์ผ๋ก ์ด์งํํด ์ฐ๊ฒฐ ์ฑ๋ถ์ ๊ฐ์ฒด ํ๋ณด๋ก ๋ง๋ ๋ค.\n",
" smooth=True ์ด๋ฉด ์ฐ์ 3ํ๋ ์ ํ๊ท ์ผ๋ก ์๊ฐ ์ก์์ ์ค์ฌ ์ ๋๋น ๊ฐ์ฒด๋ฅผ ์ด๋ฆฐ๋ค.\"\"\"\n",
" bg = np.median(ref, axis=0)\n",
" if smooth:\n",
" frames = ndimage.uniform_filter1d(frames, 3, axis=0, mode=\"nearest\")\n",
" out = []\n",
" for f in frames:\n",
" d = np.abs(f - bg)\n",
" mad = np.median(d) * 1.4826 + 1e-6\n",
" thr = np.full(d.shape, k * mad)\n",
" if sky_k is not None:\n",
" thr[:HORIZON] = sky_k * mad\n",
" lab, n = ndimage.label(d > thr)\n",
" dets = []\n",
" for i, sl in enumerate(ndimage.find_objects(lab), start=1):\n",
" area = int((lab[sl] == i).sum())\n",
" if area >= min_area:\n",
" dets.append({\"bbox\": (sl[1].start, sl[0].start, sl[1].stop, sl[0].stop), \"area\": area,\n",
" \"contrast\": float(d[sl][lab[sl] == i].mean() / mad)})\n",
" out.append(dets)\n",
" return out\n",
"\n",
"\n",
"def center(b):\n",
" return ((b[0] + b[2]) / 2, (b[1] + b[3]) / 2)\n",
"\n",
"\n",
"def match(dets, labels, dist=4.0):\n",
" \"\"\"ํ์ง์ ์ ๋ต์ ์ค์ฌ ๊ฑฐ๋ฆฌ๋ก 1:1 ๋์์ํจ๋ค. ๋ฐํ: [(det, label or None)], ๋์น label ๋ชฉ๋ก\"\"\"\n",
" used, pairs = set(), []\n",
" for d in dets:\n",
" cd = center(d[\"bbox\"]); best, bj = dist, None\n",
" for j, l in enumerate(labels):\n",
" cl = center(l[\"bbox\"]); e = np.hypot(cd[0] - cl[0], cd[1] - cl[1])\n",
" if j not in used and e < best:\n",
" best, bj = e, j\n",
" if bj is not None:\n",
" used.add(bj)\n",
" pairs.append((d, labels[bj] if bj is not None else None))\n",
" return pairs, [l for j, l in enumerate(labels) if j not in used]\n",
"\n",
"\n",
"def in_roi(b):\n",
" cy = (b[1] + b[3]) / 2\n",
" return cy < HORIZON or ROI_ROWS[0] <= cy < ROI_ROWS[1]\n",
"\n",
"\n",
"def rule_class(b, area):\n",
" \"\"\"Baseline ๊ท์น: ์์น(ํ๋/์ง๋ฉด)ยทํฌ๊ธฐยท์ข
ํก๋น๋ก 1์ฐจ ๋ถ๋ฅ(ํ์ต ์์, ํ๋จ ๊ทผ๊ฑฐ๊ฐ ๊ทธ๋๋ก ๋๋ฌ๋๋ค)\"\"\"\n",
" w, h = b[2] - b[0], b[3] - b[1]\n",
" cy = (b[1] + b[3]) / 2\n",
" if cy < HORIZON:\n",
" return \"drone\" if 3 <= area <= 12 else \"clutter\"\n",
" if area >= 30 and w > h:\n",
" return \"vehicle\"\n",
" if h >= w and 6 <= area <= 30:\n",
" return \"person\"\n",
" return \"clutter\"\n",
"\n",
"\n",
"def repair_frames(f):\n",
" \"\"\"์ผ์ ๋จ์ (์ ํ๋ฉด 0 ๋๋ NaN) ํ๋ ์์ ์ง์ ์ ์ ํ๋ ์์ผ๋ก ๋์ฒดํ๊ณ ๋ณด์ ๊ฑด์๋ฅผ ๋๋ ค์ค๋ค.\"\"\"\n",
" f = f.copy(); fixed = 0\n",
" for t in range(len(f)):\n",
" if np.isnan(f[t]).any() or f[t].max() < 1:\n",
" f[t] = f[t - 1] if t > 0 else np.nanmedian(f[1:], axis=0); fixed += 1\n",
" return f, fixed\n",
"\n",
"\n",
"def roi_mask():\n",
" \"\"\"๊ฒฝ๊ณ๊ตฌ์ญ: ํ๋(๋๋ก ์ ๊ทผ) + ์ธํ๋ฆฌ ์ ๊ทผ ์ง๋ฉด ๋ . ์๋ชฉ ์์ญ(์ข์ธก x<16)์ ๊ฐ์ ์ ์ธ ํ๋ณด๋ก ํ์๋ง ํ๋ค.\"\"\"\n",
" m = np.zeros((H, W), bool)\n",
" m[:HORIZON] = True; m[ROI_ROWS[0]:ROI_ROWS[1]] = True\n",
" return m\n",
"\n",
"\n",
"def preprocess(seq):\n",
" \"\"\"๋ฐํ: (๋ณด์ ๋ ๋ฐฐ๊ฒฝ ๊ตฌ๊ฐ, ๋ณด์ ๋ ๊ฐ์ ๊ตฌ๊ฐ, ๋ณด์ ๊ฑด์). ์กฐ๊ฑด๋ณ ์ฒ๋ฆฌ๋ฅผ ์๋ ์ ํํ๋ค.\"\"\"\n",
" allf, fixed = repair_frames(np.concatenate([seq[\"ref\"], seq[\"frames\"]]))\n",
" s2 = {**seq, \"ref\": allf[:len(seq[\"ref\"])], \"frames\": allf[len(seq[\"ref\"]):]}\n",
" ref, fr = enhance(s2)\n",
" return ref, fr, fixed\n",
"\n",
"\n",
"def corrupt(seq, rate, rng):\n",
" \"\"\"์ผ์ ๋จ์ ๋ชจ์: ๊ฐ์ ๊ตฌ๊ฐ ํ๋ ์ ์ผ๋ถ๋ฅผ 0์ผ๋ก ๋ง๋ ๋ค(์คํธํยท๋จ์ ์ก์์ ๋จ์ํ).\"\"\"\n",
" fr = seq[\"frames\"].copy()\n",
" for t in rng.choice(len(fr), int(round(rate * len(fr))), replace=False):\n",
" fr[t] = 0\n",
" return {**seq, \"frames\": fr}\n",
"\n",
"\n",
"def f1_of(seqs, use_pre, mask=None):\n",
" tp = fp = fn = 0\n",
" for q in seqs:\n",
" ref, fr = preprocess(q)[:2] if use_pre else (q[\"ref\"], q[\"frames\"])\n",
" for ds, labs in zip(detect(ref, fr), q[\"labels\"]):\n",
" labs = [l for l in labs if l[\"intruder\"]]\n",
" if mask is not None:\n",
" ds = [d for d in ds if in_roi(d[\"bbox\"])]\n",
" pairs, missed = match(ds, labs)\n",
" tp += sum(l is not None for _, l in pairs); fp += sum(l is None for _, l in pairs); fn += len(missed)\n",
" p, r = tp / max(tp + fp, 1), tp / max(tp + fn, 1)\n",
" return round(p, 3), round(r, 3), round(2 * p * r / max(p + r, 1e-9), 3)\n",
"\n",
"\n",
"rng = np.random.default_rng(5)\n",
"data = [make_seq(s) for s in range(300, 340)]\n",
"noisy = [corrupt(q, 0.2, rng) if i % 2 == 0 else q for i, q in enumerate(data)] # ์ ๋ฐ์ ์ผ์ ๋จ์ 20% ์ฃผ์
\n",
"\n",
"# 1) ์ฒ๋ฆฌ ์๋์ ์ค๋ฅ์จ: ํ๋ ์ ๋จ์ ์ ์ฒ๋ฆฌ(์กฐ๋ ๋ณด์ + ํํฐ)๋ฅผ ์ํ์ค 40๊ฐ์ ์ ์ฉ\n",
"t0 = time.perf_counter(); n_frames = 0; errors = 0\n",
"for q in noisy:\n",
" try:\n",
" ref, fr, _ = preprocess(q); n_frames += len(ref) + len(fr)\n",
" errors += int(not np.isfinite(fr).all() or fr.min() < 0 or fr.max() > 255)\n",
" except Exception:\n",
" errors += 1\n",
"sec = time.perf_counter() - t0\n",
"print(f\"์ ์ฒ๋ฆฌ {n_frames}ํ๋ ์ / {sec:.2f}์ด = {n_frames / sec:,.0f}ํ๋ ์/์ด (๋ชฉํ 1,000 ์ด์: {'๋ฌ์ฑ' if n_frames / sec >= 1000 else '๋ฏธ๋ฌ'})\")\n",
"print(f\"์ ์ฒ๋ฆฌ ์ค๋ฅ(NaNยท๋ฒ์ ์ดํ) {errors}๊ฑด -> ์ค๋ฅ์จ {errors / len(noisy):.0%} (๋ชฉํ 0%)\")\n",
"\n",
"# 2) ๋จ์ ํ
์คํธ 5์ข
\n",
"def t1():\n",
" f = np.full((4, H, W), 100.0); f[2] = 0\n",
" out, n = repair_frames(f); assert n == 1 and out[2].mean() == 100\n",
"def t2():\n",
" f = np.full((3, H, W), 100.0); f[1, 5, 5] = np.nan\n",
" out, n = repair_frames(f); assert n == 1 and np.isfinite(out).all()\n",
"def t3():\n",
" ref, fr, _ = preprocess(make_seq(1, cond=\"night\")); assert 0 <= fr.min() and fr.max() <= 255\n",
"def t4():\n",
" m = roi_mask(); assert m[10, 10] and m[40, 40] and not m[25, 40] and not m[55, 40]\n",
"def t5():\n",
" q = make_seq(2, cond=\"rain\"); ref, fr, _ = preprocess(q); assert fr.shape == q[\"frames\"].shape\n",
"tests = {\"๋จ์ ํ๋ ์ ๋ณด์ \": t1, \"NaN ํ๋ ์ ๋ณด์ \": t2, \"์ผ๊ฐ ๋ฐ๊ธฐ ๋ฒ์\": t3, \"ROI ๋ง์คํฌ ๊ฒฝ๊ณ\": t4, \"์ถ๋ ฅ ํ์ ๋ณด์กด\": t5}\n",
"passed = 0\n",
"for name, fn in tests.items():\n",
" try:\n",
" fn(); passed += 1; print(f\"[PASS] {name}\")\n",
" except AssertionError:\n",
" print(f\"[FAIL] {name}\")\n",
"print(f\"๋จ์ ํ
์คํธ {passed}/5 ํต๊ณผ\")\n",
"\n",
"# 3) ์กฐ๊ฑด๋ณ ์ ์ฒ๋ฆฌ ์ ํ ํ์ง ์ฑ๋ฅ(์ผ์ ๋จ์ ์ฃผ์
ํฌํจ)\n",
"rows = []\n",
"for c in CONDS + [\"์ ์ฒด\"]:\n",
" sub = [q for q in noisy if c == \"์ ์ฒด\" or q[\"cond\"] == c]\n",
" b, a = f1_of(sub, False), f1_of(sub, True)\n",
" ar = f1_of(sub, True, mask=True)\n",
" rows.append({\"์กฐ๊ฑด\": c, \"์ํ์ค\": len(sub), \"์ F1\": b[2], \"ํ F1\": a[2], \"ํ ์ฌํ์จ\": a[1], \"ํ+ROI ์ ๋ฐ๋\": ar[0], \"ํ+ROI F1\": ar[2]})\n",
"df = pd.DataFrame(rows)\n",
"print(\"\\n[์กฐ๊ฑด๋ณ ์ ์ฒ๋ฆฌ ์ ํ ํ์ง F1] (์ : ์๋ณธ ๊ทธ๋๋ก / ํ: ๋จ์ ๋ณด์ +์กฐ๋ ๋ณด์ +ํํฐ / ROI: ๊ฒฝ๊ณ๊ตฌ์ญ ๋ฐ ํ์ง ์ ์ธ)\")\n",
"print(df.to_string(index=False))\n",
"print(\"์ ์ ๋
ธ์ด์ฆ ๋์: ์ผ์ ๋จ์ ํ๋ ์์ ์ง์ ํ๋ ์์ผ๋ก ๋์ฒด, ๋น๋ 3x3 ์ค์๊ฐ, ์ผ๊ฐ ์กฐ๋๋ ๋ฐฑ๋ถ์ ์ ์ฅ์ผ๋ก ๋ณด์ ํ๋ฉฐ ์คํธํ ์์ ํ๋ณ์ ๋ณธ ๊ณผ์ ๋ฒ์ ๋ฐ์ด๋ค.\")\n",
"\n",
"q = noisy[2]; ref, fr, _ = preprocess(q)\n",
"fig, ax = plt.subplots(1, 3, figsize=(12, 3.5))\n",
"for a, im, ti in zip(ax, [q[\"frames\"][4], fr[4], fr[4] * roi_mask()], [\"raw\", \"preprocessed\", \"preprocessed + ROI mask\"]):\n",
" a.imshow(im, cmap=\"gray\", vmin=0, vmax=255); a.set_title(f\"{q['id']} {q['cond']}: {ti}\"); a.axis(\"off\")\n",
"plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
02 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"์ ์ฒ๋ฆฌ 720ํ๋ ์ / 0.38์ด = 1,890ํ๋ ์/์ด (๋ชฉํ 1,000 ์ด์: ๋ฌ์ฑ)\n",
"์ ์ฒ๋ฆฌ ์ค๋ฅ(NaNยท๋ฒ์ ์ดํ) 0๊ฑด -> ์ค๋ฅ์จ 0% (๋ชฉํ 0%)\n",
"[PASS] ๋จ์ ํ๋ ์ ๋ณด์ \n",
"[PASS] NaN ํ๋ ์ ๋ณด์ \n",
"[PASS] ์ผ๊ฐ ๋ฐ๊ธฐ ๋ฒ์\n",
"[PASS] ROI ๋ง์คํฌ ๊ฒฝ๊ณ\n",
"[PASS] ์ถ๋ ฅ ํ์ ๋ณด์กด\n",
"๋จ์ ํ
์คํธ 5/5 ํต๊ณผ\n",
"\n",
"[์กฐ๊ฑด๋ณ ์ ์ฒ๋ฆฌ ์ ํ ํ์ง F1] (์ : ์๋ณธ ๊ทธ๋๋ก / ํ: ๋จ์ ๋ณด์ +์กฐ๋ ๋ณด์ +ํํฐ / ROI: ๊ฒฝ๊ณ๊ตฌ์ญ ๋ฐ ํ์ง ์ ์ธ)\n",
" ์กฐ๊ฑด ์ํ์ค ์ F1 ํ F1 ํ ์ฌํ์จ ํ+ROI ์ ๋ฐ๋ ํ+ROI F1\n",
" day 13 0.623 0.639 0.956 0.493 0.649\n",
"night 14 0.484 0.493 0.868 0.488 0.625\n",
" rain 7 0.083 0.303 0.792 0.343 0.479\n",
" fog 6 0.389 0.387 0.940 0.247 0.390\n",
" ์ ์ฒด 40 0.247 0.463 0.886 0.419 0.568\n",
"์ ์ ๋
ธ์ด์ฆ ๋์: ์ผ์ ๋จ์ ํ๋ ์์ ์ง์ ํ๋ ์์ผ๋ก ๋์ฒด, ๋น๋ 3x3 ์ค์๊ฐ, ์ผ๊ฐ ์กฐ๋๋ ๋ฐฑ๋ถ์ ์ ์ฅ์ผ๋ก ๋ณด์ ํ๋ฉฐ ์คํธํ ์์ ํ๋ณ์ ๋ณธ ๊ณผ์ ๋ฒ์ ๋ฐ์ด๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
03. ๊ธฐ์ง ์ธ๊ณฝ ์ธํ๋ฆฌ ์ ๊ทผ ๊ฐ์ฒด ํ์ง Baseline ๋น์ ๋ชจ๋ธ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๊ธฐ์ง ์ธํ๋ฆฌ ์ ๊ทผ ๊ฐ์ฒด๋ฅผ 1์ฐจ ๊ฐ์งํ๋ Baseline ๊ฐ์ฒด ํ์ง ๋น์ ๋ชจ๋ธ์ ๊ตฌํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ธํ๋ฆฌ ์ ๊ทผ ๊ฐ์ฒด๋ฅผ 1์ฐจ๋ก ๊ฐ์งํ๋ ๊ท์น ๊ธฐ๋ฐ Baseline ๋น์ ๋ชจ๋ธ์ ๊ตฌํํ๊ณ ํ๋จ ๊ธฐ์ค์ ํฌ๋ช
ํ๊ฒ ๊ธฐ๋กํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **ํ์ตํ ๋ฐฐ๊ฒฝ๊ณผ์ ์ฐจ์ด๋ฅผ ์ก์ ์์ค์ 4๋ฐฐ(MAD ๊ธฐ์ค)๋ก ์ด์งํํ๊ณ ์ฐ๊ฒฐ ์์ญ์ ๊ฐ์ฒด ํ๋ณด๋ก ๋ง๋ ๋ค, ์์น(ํ๋/์ง๋ฉด)ยทํฌ๊ธฐยท์ข
ํก๋น ๊ท์น์ผ๋ก ์ธ์ยท์ฐจ๋ยท๋๋ก ์ ๋ถ๋ฅ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/sec01_baseline_model.py ๋ฐ ๊ธฐ๋ณธ ์ถ๋ก ๊ฒฐ๊ณผ ๋ณด๊ณ ์` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์๊ณ ๋ฆฌ์ฆ์ ๋
ผ๋ฆฌ์ ํ๋ฆ ๋ฐ ๊ตฐ์ฌ์ ํ๋จ ๊ธฐ์ค์ ํฌ๋ช
์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ๊ธฐ๋ณธ ๋ฐ์ดํฐ์
๋์ ํ์ง/์ฒ๋ฆฌ ์ ํ๋ 75% ์ด์ ๋ฌ์ฑ\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ๋จ์ ๊ธฐ๋ฅ ๋์ ํ์ธ ๋ฐ ๊ธฐ๋ณธ ์๋๋ฆฌ์ค ์คํ ๋ก๊ทธ ์์ฑ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ๊ธฐ์ค ๋ฒค์น๋งํฌ ๋ฐ์ดํฐ์
๋์ ์ถ๋ก ๊ฒฐ๊ณผ ์ค์ฐจ์จ ๋น๊ต\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"๊ธฐ์ง ์ธ๊ณฝ ๊ณ ์ CCTV ํฉ์ฑ ์์์์ ๋ฐฐ๊ฒฝ ์ฐจ๋ถ๊ณผ ์์นยทํฌ๊ธฐยท์ข
ํก๋น ๊ท์น์ผ๋ก ์ธํ๋ฆฌ ์ ๊ทผ ๊ฐ์ฒด(์ธ์ยท์ฐจ๋ยท๋๋ก )๋ฅผ 1์ฐจ ๊ฐ์งํ๋ Baseline ๋น์ ๋ชจ๋ธ์ ๊ตฌํํ๊ณ , ํ๋ ์ ๋จ์ ํ์ง ์ ํ๋์ ์ค๊ฒฝ๋ณด๋ฅผ ์ธก์ ํ๋ค.\"\"\"\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from scipy import ndimage\n",
"\n",
"# ---------------- [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํฉ์ฑ CCTV ์์ ์์ฑ๊ธฐ ----------------\n",
"# ์ค์ ์์ ๋์ ์ฝ๋๋ก ๊ทธ๋ฆฐ ๊ฐ์ฒด(์ฌ๊ฐํยท์ )๋ก ๊ธฐ์ง ์ธ๊ณฝ ๊ณ ์ ์นด๋ฉ๋ผ ํ๋ฉด์ ํ๋ด ๋ธ๋ค.\n",
"H, W, T = 64, 96, 10 # ํ๋ฉด ํฌ๊ธฐ(px), ์ํ์ค ๊ธธ์ด(ํ๋ ์)\n",
"HORIZON, FENCE = 20, 46 # ํ๋/์ง๋ฉด ๊ฒฝ๊ณ, ์ธํ๋ฆฌ ํ\n",
"ROI_ROWS = (32, 48) # ์ธํ๋ฆฌ ์ ๊ทผ ๊ฒฝ๊ณ๊ตฌ์ญ(ROI): ์ง๋ฉด 32~47ํ\n",
"CLASSES = [\"person\", \"vehicle\", \"drone\", \"animal\", \"bird\"]\n",
"INTRUDER = {\"person\", \"vehicle\", \"drone\"} # ๋น์ธ๊ฐ ์ ๊ทผ(๊ฒฝ๋ณด ๋์)\n",
"CONDS = [\"day\", \"night\", \"rain\", \"fog\"]\n",
"\n",
"\n",
"def background(rng):\n",
" # [TODO : ํ์ ์ค์ต 3] ๊ธฐ์ง ์ธ๊ณฝ ์ธํ๋ฆฌ ์ ๊ทผ ๊ฐ์ฒด ํ์ง Baseline ๋น์ ๋ชจ๋ธ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def tree(img, t, wind):\n",
" \"\"\"์ข์ธก ์๋ชฉ ์๊ด: ๋ฐ๋ ์ธ๊ธฐ์ ๋ฐ๋ผ ์ข์ฐ๋ก ํ๋ค๋ ค ์ค๊ฒฝ๋ณด๋ฅผ ๋ง๋ ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 3] ๊ธฐ์ง ์ธ๊ณฝ ์ธํ๋ฆฌ ์ ๊ทผ ๊ฐ์ฒด ํ์ง Baseline ๋น์ ๋ชจ๋ธ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_seq(seed, cond=None, objs=None, wind=None, camo=False):\n",
" rng = np.random.default_rng(seed)\n",
" cond = cond or str(rng.choice(CONDS))\n",
" wind = rng.uniform(0, 2) if wind is None else wind\n",
" bg = background(rng)\n",
" n = int(rng.integers(1, 4)) if objs is None else len(objs)\n",
" kinds = objs or list(rng.choice(CLASSES, n, p=[0.3, 0.15, 0.25, 0.15, 0.15]))\n",
" obs = []\n",
" for k in kinds:\n",
" if k == \"person\":\n",
" o = dict(y=rng.uniform(30, 36), x=rng.uniform(15, 85), vy=rng.uniform(0.8, 1.2), vx=rng.uniform(-0.6, 0.6), h=7, w=3,\n",
" val=72 if camo else rng.uniform(35, 50))\n",
" elif k == \"vehicle\":\n",
" o = dict(y=rng.uniform(33, 38), x=rng.uniform(10, 30), vy=0.2, vx=rng.uniform(2, 4), h=5, w=10, val=rng.uniform(150, 170))\n",
" elif k == \"drone\":\n",
" o = dict(y=rng.uniform(3, 15), x=rng.uniform(10, 80), vy=rng.uniform(0, 0.6), vx=rng.uniform(-2, 2), h=2, w=2 if camo else 3,\n",
" val=rng.uniform(80, 120))\n",
" elif k == \"animal\":\n",
" o = dict(y=rng.uniform(34, 42), x=rng.uniform(15, 80), vy=0, vx=rng.uniform(-1.5, 1.5), h=2, w=4, val=rng.uniform(60, 75))\n",
" else:\n",
" o = dict(y=rng.uniform(3, 15), x=rng.uniform(10, 80), vy=0, vx=rng.uniform(-3, 3), h=1, w=2, val=rng.uniform(100, 130))\n",
" o[\"cls\"] = str(k); obs.append(o)\n",
" frames, labels = [], []\n",
" for t in range(-8, T): # ์์ ์๊ฐ = ๊ฐ์ฒด ์๋ ๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ(์ฌ์ ๊ด์ธก)\n",
" f = bg.copy(); tree(f, t, wind); lab = []\n",
" for oi, o in enumerate(obs if t >= 0 else []):\n",
" jit = rng.normal(0, 1.2, 2) if o[\"cls\"] in (\"animal\", \"bird\") else (0, 0) # ๋ถ๊ท์น ์์ง์\n",
" y, x = int(o[\"y\"] + o[\"vy\"] * t + jit[0]), int(o[\"x\"] + o[\"vx\"] * t + jit[1])\n",
" y0, x0, y1, x1 = max(y, 0), max(x, 0), min(y + o[\"h\"], H), min(x + o[\"w\"], W)\n",
" if y1 > y0 and x1 > x0:\n",
" f[y0:y1, x0:x1] = o[\"val\"]\n",
" if o[\"cls\"] == \"person\":\n",
" f[max(y0 - 2, 0):y0, x0 + 1:x0 + 2] = o[\"val\"] # ๋จธ๋ฆฌ\n",
" lab.append({\"cls\": o[\"cls\"], \"oid\": oi, \"bbox\": (x0, y0, x1, y1), \"intruder\": o[\"cls\"] in INTRUDER})\n",
" if cond == \"night\":\n",
" f = f * 0.5 + 10 + rng.normal(0, 3.5, f.shape)\n",
" else:\n",
" f = f + rng.normal(0, 2.5, f.shape)\n",
" if cond == \"rain\":\n",
" m = rng.random(f.shape) < 0.006\n",
" for d in range(3):\n",
" f[d:][m[:H - d]] += 45\n",
" if cond == \"fog\":\n",
" f = 0.45 * f + 0.55 * 150\n",
" frames.append(np.clip(f, 0, 255)); labels.append(lab)\n",
" fr = np.array(frames)\n",
" return {\"id\": f\"CAM-{seed:04d}\", \"cond\": cond, \"wind\": wind, \"ref\": fr[:8], \"frames\": fr[8:], \"labels\": labels[8:]}\n",
"\n",
"# ---------------- ์ ์ฒ๋ฆฌยทํ์ง ๊ณตํต ํจ์(๋ฐฐ๊ฒฝ ์ฐจ๋ถ ๊ธฐ๋ฐ) ----------------\n",
"def enhance(seq):\n",
" \"\"\"์กฐ๋ ๋ณด์ (๋ฐฑ๋ถ์ ์ ์ฅ) + ๋น ์กฐ๊ฑด์ ๋น์ค๊ธฐ ์ ๊ฑฐ(3x3 ์ค์๊ฐ). ๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ(ref)๊ณผ ๊ฐ์ ๊ธฐ์ค์ผ๋ก ๋ณด์ ํ๋ค.\"\"\"\n",
" allf = np.concatenate([seq[\"ref\"], seq[\"frames\"]])\n",
" f = ndimage.median_filter(allf, size=(1, 3, 3)) if seq[\"cond\"] == \"rain\" else allf\n",
" lo, hi = np.percentile(f, [1, 99.5])\n",
" f = np.clip((f - lo) / (hi - lo + 1e-6) * 255, 0, 255)\n",
" return f[:len(seq[\"ref\"])], f[len(seq[\"ref\"]):]\n",
"\n",
"\n",
"def detect(ref, frames, k=4.0, min_area=3, sky_k=None, smooth=False):\n",
" \"\"\"๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ์ ์ค์๊ฐ ๋ฐฐ๊ฒฝ๊ณผ ์ฐจ์ด๋ฅผ ๊ฐ๊ฑด ์๊ณ๊ฐ(k x MAD)์ผ๋ก ์ด์งํํด ์ฐ๊ฒฐ ์ฑ๋ถ์ ๊ฐ์ฒด ํ๋ณด๋ก ๋ง๋ ๋ค.\n",
" smooth=True ์ด๋ฉด ์ฐ์ 3ํ๋ ์ ํ๊ท ์ผ๋ก ์๊ฐ ์ก์์ ์ค์ฌ ์ ๋๋น ๊ฐ์ฒด๋ฅผ ์ด๋ฆฐ๋ค.\"\"\"\n",
" bg = np.median(ref, axis=0)\n",
" if smooth:\n",
" frames = ndimage.uniform_filter1d(frames, 3, axis=0, mode=\"nearest\")\n",
" out = []\n",
" for f in frames:\n",
" d = np.abs(f - bg)\n",
" mad = np.median(d) * 1.4826 + 1e-6\n",
" thr = np.full(d.shape, k * mad)\n",
" if sky_k is not None:\n",
" thr[:HORIZON] = sky_k * mad\n",
" lab, n = ndimage.label(d > thr)\n",
" dets = []\n",
" for i, sl in enumerate(ndimage.find_objects(lab), start=1):\n",
" area = int((lab[sl] == i).sum())\n",
" if area >= min_area:\n",
" dets.append({\"bbox\": (sl[1].start, sl[0].start, sl[1].stop, sl[0].stop), \"area\": area,\n",
" \"contrast\": float(d[sl][lab[sl] == i].mean() / mad)})\n",
" out.append(dets)\n",
" return out\n",
"\n",
"\n",
"def center(b):\n",
" return ((b[0] + b[2]) / 2, (b[1] + b[3]) / 2)\n",
"\n",
"\n",
"def match(dets, labels, dist=4.0):\n",
" \"\"\"ํ์ง์ ์ ๋ต์ ์ค์ฌ ๊ฑฐ๋ฆฌ๋ก 1:1 ๋์์ํจ๋ค. ๋ฐํ: [(det, label or None)], ๋์น label ๋ชฉ๋ก\"\"\"\n",
" used, pairs = set(), []\n",
" for d in dets:\n",
" cd = center(d[\"bbox\"]); best, bj = dist, None\n",
" for j, l in enumerate(labels):\n",
" cl = center(l[\"bbox\"]); e = np.hypot(cd[0] - cl[0], cd[1] - cl[1])\n",
" if j not in used and e < best:\n",
" best, bj = e, j\n",
" if bj is not None:\n",
" used.add(bj)\n",
" pairs.append((d, labels[bj] if bj is not None else None))\n",
" return pairs, [l for j, l in enumerate(labels) if j not in used]\n",
"\n",
"\n",
"def in_roi(b):\n",
" cy = (b[1] + b[3]) / 2\n",
" return cy < HORIZON or ROI_ROWS[0] <= cy < ROI_ROWS[1]\n",
"\n",
"\n",
"def rule_class(b, area):\n",
" \"\"\"Baseline ๊ท์น: ์์น(ํ๋/์ง๋ฉด)ยทํฌ๊ธฐยท์ข
ํก๋น๋ก 1์ฐจ ๋ถ๋ฅ(ํ์ต ์์, ํ๋จ ๊ทผ๊ฑฐ๊ฐ ๊ทธ๋๋ก ๋๋ฌ๋๋ค)\"\"\"\n",
" w, h = b[2] - b[0], b[3] - b[1]\n",
" cy = (b[1] + b[3]) / 2\n",
" if cy < HORIZON:\n",
" return \"drone\" if 3 <= area <= 12 else \"clutter\"\n",
" if area >= 30 and w > h:\n",
" return \"vehicle\"\n",
" if h >= w and 6 <= area <= 30:\n",
" return \"person\"\n",
" return \"clutter\"\n",
"\n",
"# ํ๋จ ๊ธฐ์ค(ํฌ๋ช
์ฑ): โ ํ์ต๋ ๋ฐฐ๊ฒฝ๊ณผ ๋ฐ๊ธฐ ์ฐจ์ด๊ฐ ์ก์์ 4๋ฐฐ(k=4 x MAD) ์ด์์ธ ํฝ์
๋ฉ์ด๋ฆฌ โก 3ํฝ์
์ด์\n",
"# โข ํ๋ ์์ญ 3~12ํฝ์
= ๋๋ก , ์ง๋ฉด์ ๊ฐ๋ก๋ก ๊ธด 30ํฝ์
์ด์ = ์ฐจ๋, ์ธ๋ก๋ก ๊ธด 6~30ํฝ์
= ์ธ์\n",
"# โฃ ๊ฒฝ๊ณ๊ตฌ์ญ(ROI: ํ๋ + ์ธํ๋ฆฌ ์ ๊ทผ ๊ตฌ์ญ) ์์ ์ธ์ยท์ฐจ๋ยท๋๋ก ๋ง ๊ฒฝ๋ณด\n",
"rows, fa_src = [], {}\n",
"for s in range(500, 540):\n",
" seq = make_seq(s)\n",
" ref, fr = enhance(seq)\n",
" for t, (ds, labs) in enumerate(zip(detect(ref, fr), seq[\"labels\"])):\n",
" pairs, missed = match(ds, labs)\n",
" for d, l in pairs:\n",
" pred = rule_class(d[\"bbox\"], d[\"area\"])\n",
" alarm = pred in INTRUDER and in_roi(d[\"bbox\"])\n",
" truth = l[\"cls\"] if l else \"clutter\"\n",
" rows.append({\"cond\": seq[\"cond\"], \"truth\": truth, \"pred\": pred, \"alarm\": alarm})\n",
" for l in missed:\n",
" rows.append({\"cond\": seq[\"cond\"], \"truth\": l[\"cls\"], \"pred\": \"missed\", \"alarm\": False})\n",
"df = pd.DataFrame(rows)\n",
"intr = df[df[\"truth\"].isin(INTRUDER)]\n",
"acc = (intr[\"pred\"] == intr[\"truth\"]).mean()\n",
"det_rate = intr[\"alarm\"].mean()\n",
"fa = df[df[\"alarm\"] & ~df[\"truth\"].isin(INTRUDER)]\n",
"print(f\"[Baseline] ํฉ์ฑ CCTV ์ํ์ค 40๊ฐ(ํ๋ ์ {40 * T}์ฅ), ์นจ์
๊ฐ์ฒด ์ถํ {len(intr)}ํ\")\n",
"print(f\"์นจ์
๊ฐ์ฒด ํ์งยท๋ถ๋ฅ ์ ํ๋ {acc:.1%} (๋ชฉํ 75% ์ด์) -> {'๋ฌ์ฑ' if acc >= 0.75 else '๋ฏธ๋ฌ'}\")\n",
"print(f\"์นจ์
๊ฒฝ๋ณด ๋ฐ์๋ฅ {det_rate:.1%}, ์ค๊ฒฝ๋ณด {len(fa)}๊ฑด (์์ธ: {fa['truth'].value_counts().to_dict()})\")\n",
"print(\"\\n[์ ์ฒด๋ณ Baseline ํ์ ] (ํ: ์ค์ , ์ด: ํ์ )\")\n",
"print(pd.crosstab(df[\"truth\"], df[\"pred\"]).to_string())\n",
"print(\"\\n[์กฐ๊ฑด๋ณ ์นจ์
๊ฐ์ฒด ์ ํ๋]\")\n",
"print(intr.groupby(\"cond\").apply(lambda g: f\"{(g['pred'] == g['truth']).mean():.1%} ({len(g)}ํ)\").to_string())\n",
"print(\"ํ๊ณ: ๊ท์น์ ์์ฅ ์ธ์ยท์ํ ๋๋ก (์ ๋๋นยท์๋ฉด์ )๊ณผ ์๋ชฉยท๋๋ฌผ ์ค๊ฒฝ๋ณด์ ์ฝํ๋ค -> ๋ฏธ์
4ยท5์์ ๋ณด์ํ๋ค.\")\n",
"\n",
"seq = make_seq(503, cond=\"day\", objs=[\"person\", \"vehicle\", \"drone\", \"animal\"])\n",
"ref, fr = enhance(seq)\n",
"ds = detect(ref, fr)[6]\n",
"fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n",
"ax[0].imshow(seq[\"frames\"][6], cmap=\"gray\", vmin=0, vmax=255); ax[0].set_title(\"Synthetic CCTV frame (training data)\")\n",
"ax[1].imshow(fr[6], cmap=\"gray\")\n",
"ax[1].axhspan(ROI_ROWS[0], ROI_ROWS[1], color=\"yellow\", alpha=0.15)\n",
"for d in ds:\n",
" b, p = d[\"bbox\"], rule_class(d[\"bbox\"], d[\"area\"])\n",
" ax[1].add_patch(plt.Rectangle((b[0] - .5, b[1] - .5), b[2] - b[0], b[3] - b[1], fill=False, ec=\"red\" if p in INTRUDER else \"gray\"))\n",
" ax[1].text(b[0], b[1] - 1, p, color=\"red\" if p in INTRUDER else \"gray\", fontsize=8)\n",
"ax[1].set_title(\"Baseline detections (yellow = fence ROI)\")\n",
"plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
03 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[Baseline] ํฉ์ฑ CCTV ์ํ์ค 40๊ฐ(ํ๋ ์ 400์ฅ), ์นจ์
๊ฐ์ฒด ์ถํ 530ํ\n",
"์นจ์
๊ฐ์ฒด ํ์งยท๋ถ๋ฅ ์ ํ๋ 91.7% (๋ชฉํ 75% ์ด์) -> ๋ฌ์ฑ\n",
"์นจ์
๊ฒฝ๋ณด ๋ฐ์๋ฅ 90.6%, ์ค๊ฒฝ๋ณด 109๊ฑด (์์ธ: {'clutter': 107, 'animal': 2})\n",
"\n",
"[์ ์ฒด๋ณ Baseline ํ์ ] (ํ: ์ค์ , ์ด: ํ์ )\n",
"pred clutter drone missed person vehicle\n",
"truth \n",
"animal 92 0 16 2 0\n",
"bird 0 0 110 0 0\n",
"clutter 901 94 0 93 0\n",
"drone 0 134 36 0 0\n",
"person 6 0 1 243 0\n",
"vehicle 0 0 1 0 109\n",
"\n",
"[์กฐ๊ฑด๋ณ ์นจ์
๊ฐ์ฒด ์ ํ๋]\n",
"cond\n",
"day 99.0% (100ํ)\n",
"fog 100.0% (130ํ)\n",
"night 99.1% (110ํ)\n",
"rain 77.9% (190ํ)\n",
"ํ๊ณ: ๊ท์น์ ์์ฅ ์ธ์ยท์ํ ๋๋ก (์ ๋๋นยท์๋ฉด์ )๊ณผ ์๋ชฉยท๋๋ฌผ ์ค๊ฒฝ๋ณด์ ์ฝํ๋ค -> ๋ฏธ์
4ยท5์์ ๋ณด์ํ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
04. ์ ๊ณ ๋ ์นจํฌ ์ํ ๋๋ก ๋ฐ ์ํ ์นจ์
์ ์ ๋ฐ ์๋ณ ๋ณตํฉ ๋น์ ๋ชจ๋ธ ๊ณ ๋ํ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์ํ ์ ๊ณ ๋ ๋๋ก ๋ฐ ์์ฅ ์นจ์
์๋ฅผ ์ ๋ฐ ์๋ณํ๋ ๋ณตํฉ ๋ฅ๋ฌ๋ ํ์ง ๋ชจ๋ธ์ ๊ณ ๋ํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ ๊ณ ๋ ์ํ ๋๋ก ๊ณผ ์์ฅ(์ ๋๋น) ์นจ์
์๊น์ง ์๋ณํ๋๋ก ํ๋ ์ ๊ฐ ์ถ์ ๊ณผ ๊ถค์ ํน์ง ๊ธฐ๋ฐ ๋ณตํฉ ํ์ง ๋ชจ๋ธ๋ก ๊ณ ๋ํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **ํ๋ ์์ญ ๋ฏผ๊ฐ๋๋ฅผ ๋์ธ ํ์ง๋ก ์ํ ๊ฐ์ฒด๋ฅผ ์ด๋ฆฌ๊ณ , ์ต๊ทผ์ ์ด์ ์ถ์ ์ผ๋ก ๊ถค์ ์ ๋ง๋ค์ด ํฌ๊ธฐยท์ข
ํก๋นยท์๋ยท์ง์ง์ฑยท๋๋น ํน์ง์ ๊ณ์ฐํ ๋ค ๋๋คํฌ๋ ์คํธ๋ก ๋ถ๋ฅ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/sec01_advanced_detector.py ๋ฐ ์์ธ ํ์ง ํ
์คํธ ๊ฒฐ๊ณผ` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๊ธฐ๋ง/๊ต๋ ๊ณต๊ฒฉ์ ๋ํ ๋ฐฉ์ด ๋ก์ง์ ํ์ค์ฑ ๋ฐ ์ ๋ขฐ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ๋ณตํฉ ์ํ ํ์ง ์ฌํ์จ(Recall) 85% ์ด์, ์ฒ๋ฆฌ ์ง์ฐ 200ms ์ดํ\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ๋ณตํฉ ์ํ ์๋๋ฆฌ์ค 10์ข
์ ๋ํ ํ์ง ํ
์คํธ ํต๊ณผ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ํฉ์ฑ ์ํ ๋ฐ์ดํฐ์
์ฃผ์
์ ์ด์์น ํ์ง ์ ํ๋ ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"์ ๊ณ ๋ ์ํ ๋๋ก ๊ณผ ์์ฅ(์ ๋๋น) ์นจ์
์๋ฅผ ์๋ณํ๋๋ก, ํ๋ ์์ญ ๋ฏผ๊ฐ๋ ์ํฅยทํ๋ ์ ๊ฐ ์ถ์ ยท๊ถค์ ํน์ง ๊ธฐ๋ฐ ๋๋คํฌ๋ ์คํธ ๋ถ๋ฅ๋ฅผ ๊ฒฐํฉํ ๋ณตํฉ ํ์ง ๋ชจ๋ธ๋ก ๊ณ ๋ํํ๊ณ , ๊ฐ์ฒด ๋จ์ ์ฌํ์จ๊ณผ ํ๋ ์๋น ์ฒ๋ฆฌ ์ง์ฐ์ Baseline๊ณผ ๋น๊ตํ๋ค.\"\"\"\n",
"import time\n",
"import numpy as np\n",
"import pandas as pd\n",
"from scipy import ndimage\n",
"from sklearn.ensemble import RandomForestClassifier\n",
"\n",
"# ---------------- [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํฉ์ฑ CCTV ์์ ์์ฑ๊ธฐ ----------------\n",
"# ์ค์ ์์ ๋์ ์ฝ๋๋ก ๊ทธ๋ฆฐ ๊ฐ์ฒด(์ฌ๊ฐํยท์ )๋ก ๊ธฐ์ง ์ธ๊ณฝ ๊ณ ์ ์นด๋ฉ๋ผ ํ๋ฉด์ ํ๋ด ๋ธ๋ค.\n",
"H, W, T = 64, 96, 10 # ํ๋ฉด ํฌ๊ธฐ(px), ์ํ์ค ๊ธธ์ด(ํ๋ ์)\n",
"HORIZON, FENCE = 20, 46 # ํ๋/์ง๋ฉด ๊ฒฝ๊ณ, ์ธํ๋ฆฌ ํ\n",
"ROI_ROWS = (32, 48) # ์ธํ๋ฆฌ ์ ๊ทผ ๊ฒฝ๊ณ๊ตฌ์ญ(ROI): ์ง๋ฉด 32~47ํ\n",
"CLASSES = [\"person\", \"vehicle\", \"drone\", \"animal\", \"bird\"]\n",
"INTRUDER = {\"person\", \"vehicle\", \"drone\"} # ๋น์ธ๊ฐ ์ ๊ทผ(๊ฒฝ๋ณด ๋์)\n",
"CONDS = [\"day\", \"night\", \"rain\", \"fog\"]\n",
"\n",
"\n",
"def background(rng):\n",
" # [TODO : ํ์ ์ค์ต 4] ์ ๊ณ ๋ ์นจํฌ ์ํ ๋๋ก ๋ฐ ์ํ ์นจ์
์ ์ ๋ฐ ์๋ณ ๋ณตํฉ ๋น์ ๋ชจ๋ธ ๊ณ ๋ํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def tree(img, t, wind):\n",
" \"\"\"์ข์ธก ์๋ชฉ ์๊ด: ๋ฐ๋ ์ธ๊ธฐ์ ๋ฐ๋ผ ์ข์ฐ๋ก ํ๋ค๋ ค ์ค๊ฒฝ๋ณด๋ฅผ ๋ง๋ ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 4] ์ ๊ณ ๋ ์นจํฌ ์ํ ๋๋ก ๋ฐ ์ํ ์นจ์
์ ์ ๋ฐ ์๋ณ ๋ณตํฉ ๋น์ ๋ชจ๋ธ ๊ณ ๋ํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_seq(seed, cond=None, objs=None, wind=None, camo=False):\n",
" rng = np.random.default_rng(seed)\n",
" cond = cond or str(rng.choice(CONDS))\n",
" wind = rng.uniform(0, 2) if wind is None else wind\n",
" bg = background(rng)\n",
" n = int(rng.integers(1, 4)) if objs is None else len(objs)\n",
" kinds = objs or list(rng.choice(CLASSES, n, p=[0.3, 0.15, 0.25, 0.15, 0.15]))\n",
" obs = []\n",
" for k in kinds:\n",
" if k == \"person\":\n",
" o = dict(y=rng.uniform(30, 36), x=rng.uniform(15, 85), vy=rng.uniform(0.8, 1.2), vx=rng.uniform(-0.6, 0.6), h=7, w=3,\n",
" val=72 if camo else rng.uniform(35, 50))\n",
" elif k == \"vehicle\":\n",
" o = dict(y=rng.uniform(33, 38), x=rng.uniform(10, 30), vy=0.2, vx=rng.uniform(2, 4), h=5, w=10, val=rng.uniform(150, 170))\n",
" elif k == \"drone\":\n",
" o = dict(y=rng.uniform(3, 15), x=rng.uniform(10, 80), vy=rng.uniform(0, 0.6), vx=rng.uniform(-2, 2), h=2, w=2 if camo else 3,\n",
" val=rng.uniform(80, 120))\n",
" elif k == \"animal\":\n",
" o = dict(y=rng.uniform(34, 42), x=rng.uniform(15, 80), vy=0, vx=rng.uniform(-1.5, 1.5), h=2, w=4, val=rng.uniform(60, 75))\n",
" else:\n",
" o = dict(y=rng.uniform(3, 15), x=rng.uniform(10, 80), vy=0, vx=rng.uniform(-3, 3), h=1, w=2, val=rng.uniform(100, 130))\n",
" o[\"cls\"] = str(k); obs.append(o)\n",
" frames, labels = [], []\n",
" for t in range(-8, T): # ์์ ์๊ฐ = ๊ฐ์ฒด ์๋ ๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ(์ฌ์ ๊ด์ธก)\n",
" f = bg.copy(); tree(f, t, wind); lab = []\n",
" for oi, o in enumerate(obs if t >= 0 else []):\n",
" jit = rng.normal(0, 1.2, 2) if o[\"cls\"] in (\"animal\", \"bird\") else (0, 0) # ๋ถ๊ท์น ์์ง์\n",
" y, x = int(o[\"y\"] + o[\"vy\"] * t + jit[0]), int(o[\"x\"] + o[\"vx\"] * t + jit[1])\n",
" y0, x0, y1, x1 = max(y, 0), max(x, 0), min(y + o[\"h\"], H), min(x + o[\"w\"], W)\n",
" if y1 > y0 and x1 > x0:\n",
" f[y0:y1, x0:x1] = o[\"val\"]\n",
" if o[\"cls\"] == \"person\":\n",
" f[max(y0 - 2, 0):y0, x0 + 1:x0 + 2] = o[\"val\"] # ๋จธ๋ฆฌ\n",
" lab.append({\"cls\": o[\"cls\"], \"oid\": oi, \"bbox\": (x0, y0, x1, y1), \"intruder\": o[\"cls\"] in INTRUDER})\n",
" if cond == \"night\":\n",
" f = f * 0.5 + 10 + rng.normal(0, 3.5, f.shape)\n",
" else:\n",
" f = f + rng.normal(0, 2.5, f.shape)\n",
" if cond == \"rain\":\n",
" m = rng.random(f.shape) < 0.006\n",
" for d in range(3):\n",
" f[d:][m[:H - d]] += 45\n",
" if cond == \"fog\":\n",
" f = 0.45 * f + 0.55 * 150\n",
" frames.append(np.clip(f, 0, 255)); labels.append(lab)\n",
" fr = np.array(frames)\n",
" return {\"id\": f\"CAM-{seed:04d}\", \"cond\": cond, \"wind\": wind, \"ref\": fr[:8], \"frames\": fr[8:], \"labels\": labels[8:]}\n",
"\n",
"# ---------------- ์ ์ฒ๋ฆฌยทํ์ง ๊ณตํต ํจ์(๋ฐฐ๊ฒฝ ์ฐจ๋ถ ๊ธฐ๋ฐ) ----------------\n",
"def enhance(seq):\n",
" \"\"\"์กฐ๋ ๋ณด์ (๋ฐฑ๋ถ์ ์ ์ฅ) + ๋น ์กฐ๊ฑด์ ๋น์ค๊ธฐ ์ ๊ฑฐ(3x3 ์ค์๊ฐ). ๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ(ref)๊ณผ ๊ฐ์ ๊ธฐ์ค์ผ๋ก ๋ณด์ ํ๋ค.\"\"\"\n",
" allf = np.concatenate([seq[\"ref\"], seq[\"frames\"]])\n",
" f = ndimage.median_filter(allf, size=(1, 3, 3)) if seq[\"cond\"] == \"rain\" else allf\n",
" lo, hi = np.percentile(f, [1, 99.5])\n",
" f = np.clip((f - lo) / (hi - lo + 1e-6) * 255, 0, 255)\n",
" return f[:len(seq[\"ref\"])], f[len(seq[\"ref\"]):]\n",
"\n",
"\n",
"def detect(ref, frames, k=4.0, min_area=3, sky_k=None, smooth=False):\n",
" \"\"\"๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ์ ์ค์๊ฐ ๋ฐฐ๊ฒฝ๊ณผ ์ฐจ์ด๋ฅผ ๊ฐ๊ฑด ์๊ณ๊ฐ(k x MAD)์ผ๋ก ์ด์งํํด ์ฐ๊ฒฐ ์ฑ๋ถ์ ๊ฐ์ฒด ํ๋ณด๋ก ๋ง๋ ๋ค.\n",
" smooth=True ์ด๋ฉด ์ฐ์ 3ํ๋ ์ ํ๊ท ์ผ๋ก ์๊ฐ ์ก์์ ์ค์ฌ ์ ๋๋น ๊ฐ์ฒด๋ฅผ ์ด๋ฆฐ๋ค.\"\"\"\n",
" bg = np.median(ref, axis=0)\n",
" if smooth:\n",
" frames = ndimage.uniform_filter1d(frames, 3, axis=0, mode=\"nearest\")\n",
" out = []\n",
" for f in frames:\n",
" d = np.abs(f - bg)\n",
" mad = np.median(d) * 1.4826 + 1e-6\n",
" thr = np.full(d.shape, k * mad)\n",
" if sky_k is not None:\n",
" thr[:HORIZON] = sky_k * mad\n",
" lab, n = ndimage.label(d > thr)\n",
" dets = []\n",
" for i, sl in enumerate(ndimage.find_objects(lab), start=1):\n",
" area = int((lab[sl] == i).sum())\n",
" if area >= min_area:\n",
" dets.append({\"bbox\": (sl[1].start, sl[0].start, sl[1].stop, sl[0].stop), \"area\": area,\n",
" \"contrast\": float(d[sl][lab[sl] == i].mean() / mad)})\n",
" out.append(dets)\n",
" return out\n",
"\n",
"\n",
"def center(b):\n",
" return ((b[0] + b[2]) / 2, (b[1] + b[3]) / 2)\n",
"\n",
"\n",
"def match(dets, labels, dist=4.0):\n",
" \"\"\"ํ์ง์ ์ ๋ต์ ์ค์ฌ ๊ฑฐ๋ฆฌ๋ก 1:1 ๋์์ํจ๋ค. ๋ฐํ: [(det, label or None)], ๋์น label ๋ชฉ๋ก\"\"\"\n",
" used, pairs = set(), []\n",
" for d in dets:\n",
" cd = center(d[\"bbox\"]); best, bj = dist, None\n",
" for j, l in enumerate(labels):\n",
" cl = center(l[\"bbox\"]); e = np.hypot(cd[0] - cl[0], cd[1] - cl[1])\n",
" if j not in used and e < best:\n",
" best, bj = e, j\n",
" if bj is not None:\n",
" used.add(bj)\n",
" pairs.append((d, labels[bj] if bj is not None else None))\n",
" return pairs, [l for j, l in enumerate(labels) if j not in used]\n",
"\n",
"\n",
"def in_roi(b):\n",
" cy = (b[1] + b[3]) / 2\n",
" return cy < HORIZON or ROI_ROWS[0] <= cy < ROI_ROWS[1]\n",
"\n",
"\n",
"def rule_class(b, area):\n",
" \"\"\"Baseline ๊ท์น: ์์น(ํ๋/์ง๋ฉด)ยทํฌ๊ธฐยท์ข
ํก๋น๋ก 1์ฐจ ๋ถ๋ฅ(ํ์ต ์์, ํ๋จ ๊ทผ๊ฑฐ๊ฐ ๊ทธ๋๋ก ๋๋ฌ๋๋ค)\"\"\"\n",
" w, h = b[2] - b[0], b[3] - b[1]\n",
" cy = (b[1] + b[3]) / 2\n",
" if cy < HORIZON:\n",
" return \"drone\" if 3 <= area <= 12 else \"clutter\"\n",
" if area >= 30 and w > h:\n",
" return \"vehicle\"\n",
" if h >= w and 6 <= area <= 30:\n",
" return \"person\"\n",
" return \"clutter\"\n",
"\n",
"# ---------------- ํ๋ ์ ๊ฐ ์ถ์ ๊ณผ ๊ถค์ ํน์ง ----------------\n",
"def track(dets, gate=6.0):\n",
" \"\"\"์ต๊ทผ์ ์ด์์ผ๋ก ํ๋ ์๋ณ ํ์ง๋ฅผ ๊ถค์ ์ผ๋ก ์๋๋ค.\"\"\"\n",
" tracks = []\n",
" for t, ds in enumerate(dets):\n",
" free = list(range(len(ds)))\n",
" for tr in tracks:\n",
" if tr[\"last\"] != t - 1 or not free:\n",
" continue\n",
" c0 = center(tr[\"b\"][-1])\n",
" dd = [np.hypot(center(ds[i][\"bbox\"])[0] - c0[0], center(ds[i][\"bbox\"])[1] - c0[1]) for i in free]\n",
" j = int(np.argmin(dd))\n",
" if dd[j] < gate:\n",
" i = free.pop(j); tr[\"b\"].append(ds[i][\"bbox\"]); tr[\"a\"].append(ds[i][\"area\"]); tr[\"c\"].append(ds[i][\"contrast\"])\n",
" tr[\"t\"].append(t); tr[\"last\"] = t\n",
" for i in free:\n",
" tracks.append({\"b\": [ds[i][\"bbox\"]], \"a\": [ds[i][\"area\"]], \"c\": [ds[i][\"contrast\"]], \"t\": [t], \"last\": t})\n",
" return tracks\n",
"\n",
"\n",
"TFEATS = [\"n\", \"area\", \"aspect\", \"row\", \"sky\", \"speed\", \"straight\", \"disp\", \"contrast\", \"area_cv\", \"col\"]\n",
"\n",
"\n",
"def track_features(tr):\n",
" c = np.array([center(b) for b in tr[\"b\"]])\n",
" wh = np.array([(b[2] - b[0], b[3] - b[1]) for b in tr[\"b\"]])\n",
" steps = np.hypot(*np.diff(c, axis=0).T) if len(c) > 1 else np.array([0.0])\n",
" disp = float(np.hypot(*(c[-1] - c[0])))\n",
" return {\"n\": len(c), \"area\": float(np.mean(tr[\"a\"])), \"aspect\": float(np.mean(wh[:, 1] / wh[:, 0])), \"row\": float(c[:, 1].mean()),\n",
" \"sky\": float(c[:, 1].mean() < HORIZON), \"speed\": float(steps.mean()), \"straight\": disp / (steps.sum() + 1e-6),\n",
" \"disp\": disp, \"contrast\": float(np.mean(tr[\"c\"])), \"area_cv\": float(np.std(tr[\"a\"]) / (np.mean(tr[\"a\"]) + 1e-6)),\n",
" \"col\": float(c[:, 0].mean())}\n",
"\n",
"\n",
"def track_truth(tr, labels):\n",
" \"\"\"๊ถค์ ์ ๊ฐ ํ๋ ์์ ์ ๋ต๊ณผ ๋์์์ผ ๋ค์๊ฒฐ๋ก ์ ์ฒด๋ฅผ ์ ํ๋ค(์์ผ๋ฉด clutter).\"\"\"\n",
" votes = []\n",
" for b, t in zip(tr[\"b\"], tr[\"t\"]):\n",
" pairs, _ = match([{\"bbox\": b}], labels[t])\n",
" votes.append((pairs[0][1][\"cls\"], pairs[0][1][\"oid\"]) if pairs[0][1] else (\"clutter\", -1))\n",
" return max(set(votes), key=votes.count)\n",
"\n",
"\n",
"def seq_tracks(seq, k=4.0, min_area=3, sky_k=None, smooth=False):\n",
" ref, fr = enhance(seq)\n",
" tracks = track(detect(ref, fr, k, min_area, sky_k, smooth))\n",
" rows = []\n",
" for tr in tracks:\n",
" f = track_features(tr); f[\"truth\"], f[\"oid\"] = track_truth(tr, seq[\"labels\"]); f[\"seq\"] = seq[\"id\"]; f[\"cond\"] = seq[\"cond\"]\n",
" f[\"tr\"] = tr; rows.append(f)\n",
" return rows\n",
"\n",
"ADV = {\"k\": 3.0, \"min_area\": 2, \"sky_k\": 2.5} # ๊ณ ๋ํ ํ์ง ํ๋ผ๋ฏธํฐ(์ ๋๋น ๊ฐ์ฒด ๋์)\n",
"\n",
"\n",
"def make_set(seeds, camo_ratio):\n",
" return [make_seq(s, camo=(i % 10) < camo_ratio * 10) for i, s in enumerate(seeds)]\n",
"\n",
"\n",
"train = make_set(range(0, 80), 0.5)\n",
"tr = pd.DataFrame(sum([seq_tracks(q, **ADV) for q in train], []))\n",
"clf = RandomForestClassifier(n_estimators=150, min_samples_leaf=2, random_state=0).fit(tr[TFEATS], tr[\"truth\"])\n",
"\n",
"\n",
"def objects(seqs):\n",
" return {(q[\"id\"], l[\"oid\"]): l[\"cls\"] for q in seqs for labs in q[\"labels\"] for l in labs if l[\"intruder\"]}\n",
"\n",
"\n",
"def baseline_found(seqs):\n",
" found = set()\n",
" for q in seqs:\n",
" ref, fr = enhance(q)\n",
" hits = {}\n",
" for ds, labs in zip(detect(ref, fr), q[\"labels\"]):\n",
" for d, l in match(ds, labs)[0]:\n",
" if l and l[\"intruder\"] and rule_class(d[\"bbox\"], d[\"area\"]) == l[\"cls\"]:\n",
" hits[l[\"oid\"]] = hits.get(l[\"oid\"], 0) + 1\n",
" found |= {(q[\"id\"], o) for o, c in hits.items() if c >= 2}\n",
" return found\n",
"\n",
"\n",
"def advanced_found(seqs):\n",
" found, ms = set(), []\n",
" for q in seqs:\n",
" t0 = time.perf_counter()\n",
" rows = seq_tracks(q, **ADV)\n",
" if rows:\n",
" pred = clf.predict(pd.DataFrame(rows)[TFEATS])\n",
" ms.append((time.perf_counter() - t0) * 1000 / T)\n",
" for r, p in zip(rows, pred if rows else []):\n",
" if p in INTRUDER and r[\"n\"] >= 2 and r[\"oid\"] >= 0 and r[\"truth\"] in INTRUDER:\n",
" found.add((q[\"id\"], r[\"oid\"]))\n",
" return found, ms\n",
"\n",
"\n",
"res = []\n",
"for name, seqs in ((\"์ผ๋ฐ\", make_set(range(600, 630), 0.0)), (\"์์ฅยท์ํ\", make_set(range(700, 720), 1.0))):\n",
" obj = objects(seqs)\n",
" fb = baseline_found(seqs) & set(obj)\n",
" fa, ms = advanced_found(seqs)\n",
" for cls in (\"person\", \"vehicle\", \"drone\", \"์ ์ฒด\"):\n",
" keys = [k for k, c in obj.items() if cls in (c, \"์ ์ฒด\")]\n",
" if keys:\n",
" res.append({\"์ํ์
\": name, \"์ ์ฒด\": cls, \"๊ฐ์ฒด ์\": len(keys), \"Baseline ์ฌํ์จ\": np.mean([k in fb for k in keys]),\n",
" \"๊ณ ๋ํ ์ฌํ์จ\": np.mean([k in fa for k in keys])})\n",
" res[-1][\"์ง์ฐ(ms/ํ๋ ์)\"] = max(ms)\n",
"out = pd.DataFrame(res)\n",
"print(\"[๊ฐ์ฒด ๋จ์ ์ฌํ์จ ๋น๊ต] (2ํ๋ ์ ์ด์ ์ฌ๋ฐ๋ฅด๊ฒ ์๋ณ๋๋ฉด ํ์ง๋ก ์ธ์ )\")\n",
"print(out.round(3).fillna(\"-\").to_string(index=False))\n",
"rec = out[out[\"์ ์ฒด\"] == \"์ ์ฒด\"][\"๊ณ ๋ํ ์ฌํ์จ\"].min()\n",
"lat = out[\"์ง์ฐ(ms/ํ๋ ์)\"].dropna().max()\n",
"imp = pd.Series(clf.feature_importances_, index=TFEATS).sort_values(ascending=False)\n",
"print(\"\\n๊ถค์ ํน์ง ์ค์๋ ์์:\", \", \".join(f\"{k} {v:.2f}\" for k, v in imp.head(5).items()))\n",
"print(f\"\\n๋ณตํฉ ์ํ ํ์ง ์ฌํ์จ(์ต์ ์ํ์
) {rec:.1%} (๋ชฉํ 85% ์ด์) -> {'๋ฌ์ฑ' if rec >= 0.85 else '๋ฏธ๋ฌ'}\")\n",
"print(f\"์ฒ๋ฆฌ ์ง์ฐ ์ต๋ {lat:.1f} ms/ํ๋ ์ (๋ชฉํ 200 ms ์ดํ) -> {'๋ฌ์ฑ' if lat <= 200 else '๋ฏธ๋ฌ'}\")\n",
"print(\"๊ธฐ๋ง ๋์: ํ ํ๋ ์๋ง ๋ํ๋๋ ์ฌ๊ดยท๋น์ค๊ธฐ๋ 2ํ๋ ์ ์ด์ ์ง์ ์กฐ๊ฑด์ผ๋ก ๊ฑธ๋ฌ์ง๋ฉฐ, ์ต์ข
ํ์ธ์ ๊ฒฝ๊ณ๋ณ ์ก์ ํ์ธ(๋ฏธ์
7)์ผ๋ก ํ๋ค.\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
04 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๊ฐ์ฒด ๋จ์ ์ฌํ์จ ๋น๊ต] (2ํ๋ ์ ์ด์ ์ฌ๋ฐ๋ฅด๊ฒ ์๋ณ๋๋ฉด ํ์ง๋ก ์ธ์ )\n",
" ์ํ์
์ ์ฒด ๊ฐ์ฒด ์ Baseline ์ฌํ์จ ๊ณ ๋ํ ์ฌํ์จ ์ง์ฐ(ms/ํ๋ ์)\n",
" ์ผ๋ฐ person 18 1.000 1.000 -\n",
" ์ผ๋ฐ vehicle 9 1.000 1.000 -\n",
" ์ผ๋ฐ drone 21 0.952 1.000 -\n",
" ์ผ๋ฐ ์ ์ฒด 48 0.979 1.000 9.738\n",
"์์ฅยท์ํ person 11 0.727 0.909 -\n",
"์์ฅยท์ํ vehicle 7 1.000 0.857 -\n",
"์์ฅยท์ํ drone 9 0.889 1.000 -\n",
"์์ฅยท์ํ ์ ์ฒด 27 0.852 0.926 25.494\n",
"\n",
"๊ถค์ ํน์ง ์ค์๋ ์์: n 0.18, disp 0.14, row 0.12, area 0.10, aspect 0.10\n",
"\n",
"๋ณตํฉ ์ํ ํ์ง ์ฌํ์จ(์ต์ ์ํ์
) 92.6% (๋ชฉํ 85% ์ด์) -> ๋ฌ์ฑ\n",
"์ฒ๋ฆฌ ์ง์ฐ ์ต๋ 25.5 ms/ํ๋ ์ (๋ชฉํ 200 ms ์ดํ) -> ๋ฌ์ฑ\n",
"๊ธฐ๋ง ๋์: ํ ํ๋ ์๋ง ๋ํ๋๋ ์ฌ๊ดยท๋น์ค๊ธฐ๋ 2ํ๋ ์ ์ด์ ์ง์ ์กฐ๊ฑด์ผ๋ก ๊ฑธ๋ฌ์ง๋ฉฐ, ์ต์ข
ํ์ธ์ ๊ฒฝ๊ณ๋ณ ์ก์ ํ์ธ(๋ฏธ์
7)์ผ๋ก ํ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
05. ์๋ชฉ ํ๋ค๋ฆผ/์ผ์๋๋ฌผ ์ค๊ฒฝ๋ณด ํํฐ๋ง ๋ฐ ์นจ์
์ํ ์ ๋ขฐ๋ ์ ์ ์์ง ๊ฐ๋ฐ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์๋ชฉ ํ๋ค๋ฆผ, ์ผ์๋๋ฌผ ๋ฑ์ผ๋ก ์ธํ ์ค๊ฒฝ๋ณด๋ฅผ ํํฐ๋งํ๊ณ ์นจ์
์ํ ์ ๋ขฐ๋ ์ ์๋ฅผ ์ฐ์ถํ๋ ์์ง์ ๊ฐ๋ฐํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์๋ชฉ ํ๋ค๋ฆผ๊ณผ ์ผ์๋๋ฌผยท์กฐ๋ฅ๋ก ์ธํ ์ค๊ฒฝ๋ณด๋ฅผ ๊ฑธ๋ฌ๋ด๊ณ ์นจ์
์ํ ์ ๋ขฐ๋ ์ ์์ ๋ฑ๊ธ์ ์ฐ์ถํ๋ ์์ง์ ๊ฐ๋ฐํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๊ถค์ ํน์ง์ผ๋ก ๋ก์ง์คํฑ ํ๊ท ๊ธฐ๋ฐ ์นจ์
ํ๋ฅ ์ ๊ณ์ฐํด 0.8 ์ด์ High, 0.5 ์ด์ Medium, ๊ทธ ๋ฏธ๋ง Low ๋ฑ๊ธ์ ๋ถ์ฌ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/verification/sec01_confidence_engine.py` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์ ๋ขฐ๋ ๋ฑ๊ธ(High/Med/Low) ๋ถ์ฌ ๊ธฐ์ค์ ๊ตฐ์ฌ์ ํ๋น์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์ค๊ฒฝ๋ณด์จ(FAR) 40% ์ด์ ๊ฐ์ถ, ์ ๋ขฐ๋ ์ค์ฝ์ด ์ฐ์ถ ์ผ๊ด์ฑ 90% ์ด์\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์ค๊ฒฝ๋ณด ํํฐ๋ง ์ ํ ๋น๊ต ์งํ ์ฐ์ถ ๋ฐ ํตํฉ ํ
์คํธ ์๋ฃ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ROC-AUC ๊ณก์ ๋ถ์ ๋ฐ ์๊ณ๊ฐ ๋ฏผ๊ฐ๋ ํ
์คํธ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"์๋ชฉ ํ๋ค๋ฆผยท์ผ์๋๋ฌผยท์กฐ๋ฅ๋ก ์ธํ ์ค๊ฒฝ๋ณด๋ฅผ ๊ฑธ๋ฌ๋ด๊ธฐ ์ํด ๊ถค์ ํน์ง(ํฌ๊ธฐยท์ข
ํก๋นยท์๋ยท์ง์ง์ฑยท๋๋น ๋ฑ)์ผ๋ก ์นจ์
์ํ ์ ๋ขฐ๋ ์ ์์ ๋ฑ๊ธ(High/Medium/Low)์ ๊ณ์ฐํ๊ณ , ๊ธฐ์กด ๋ฐฉ์ ๋๋น ์ค๊ฒฝ๋ณด์จ(FAR) ๊ฐ์ถยท์ ์ ์ผ๊ด์ฑยทROC-AUC๋ฅผ ๊ฒ์ฆํ๋ค.\"\"\"\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from scipy import ndimage\n",
"from sklearn.ensemble import RandomForestClassifier\n",
"from sklearn.linear_model import LogisticRegression\n",
"from sklearn.metrics import roc_auc_score, roc_curve\n",
"from sklearn.pipeline import make_pipeline\n",
"from sklearn.preprocessing import StandardScaler\n",
"\n",
"# ---------------- [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํฉ์ฑ CCTV ์์ ์์ฑ๊ธฐ ----------------\n",
"# ์ค์ ์์ ๋์ ์ฝ๋๋ก ๊ทธ๋ฆฐ ๊ฐ์ฒด(์ฌ๊ฐํยท์ )๋ก ๊ธฐ์ง ์ธ๊ณฝ ๊ณ ์ ์นด๋ฉ๋ผ ํ๋ฉด์ ํ๋ด ๋ธ๋ค.\n",
"H, W, T = 64, 96, 10 # ํ๋ฉด ํฌ๊ธฐ(px), ์ํ์ค ๊ธธ์ด(ํ๋ ์)\n",
"HORIZON, FENCE = 20, 46 # ํ๋/์ง๋ฉด ๊ฒฝ๊ณ, ์ธํ๋ฆฌ ํ\n",
"ROI_ROWS = (32, 48) # ์ธํ๋ฆฌ ์ ๊ทผ ๊ฒฝ๊ณ๊ตฌ์ญ(ROI): ์ง๋ฉด 32~47ํ\n",
"CLASSES = [\"person\", \"vehicle\", \"drone\", \"animal\", \"bird\"]\n",
"INTRUDER = {\"person\", \"vehicle\", \"drone\"} # ๋น์ธ๊ฐ ์ ๊ทผ(๊ฒฝ๋ณด ๋์)\n",
"CONDS = [\"day\", \"night\", \"rain\", \"fog\"]\n",
"\n",
"\n",
"def background(rng):\n",
" # [TODO : ํ์ ์ค์ต 5] ์๋ชฉ ํ๋ค๋ฆผ/์ผ์๋๋ฌผ ์ค๊ฒฝ๋ณด ํํฐ๋ง ๋ฐ ์นจ์
์ํ ์ ๋ขฐ๋ ์ ์ ์์ง ๊ฐ๋ฐ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def tree(img, t, wind):\n",
" \"\"\"์ข์ธก ์๋ชฉ ์๊ด: ๋ฐ๋ ์ธ๊ธฐ์ ๋ฐ๋ผ ์ข์ฐ๋ก ํ๋ค๋ ค ์ค๊ฒฝ๋ณด๋ฅผ ๋ง๋ ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 5] ์๋ชฉ ํ๋ค๋ฆผ/์ผ์๋๋ฌผ ์ค๊ฒฝ๋ณด ํํฐ๋ง ๋ฐ ์นจ์
์ํ ์ ๋ขฐ๋ ์ ์ ์์ง ๊ฐ๋ฐ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_seq(seed, cond=None, objs=None, wind=None, camo=False):\n",
" rng = np.random.default_rng(seed)\n",
" cond = cond or str(rng.choice(CONDS))\n",
" wind = rng.uniform(0, 2) if wind is None else wind\n",
" bg = background(rng)\n",
" n = int(rng.integers(1, 4)) if objs is None else len(objs)\n",
" kinds = objs or list(rng.choice(CLASSES, n, p=[0.3, 0.15, 0.25, 0.15, 0.15]))\n",
" obs = []\n",
" for k in kinds:\n",
" if k == \"person\":\n",
" o = dict(y=rng.uniform(30, 36), x=rng.uniform(15, 85), vy=rng.uniform(0.8, 1.2), vx=rng.uniform(-0.6, 0.6), h=7, w=3,\n",
" val=72 if camo else rng.uniform(35, 50))\n",
" elif k == \"vehicle\":\n",
" o = dict(y=rng.uniform(33, 38), x=rng.uniform(10, 30), vy=0.2, vx=rng.uniform(2, 4), h=5, w=10, val=rng.uniform(150, 170))\n",
" elif k == \"drone\":\n",
" o = dict(y=rng.uniform(3, 15), x=rng.uniform(10, 80), vy=rng.uniform(0, 0.6), vx=rng.uniform(-2, 2), h=2, w=2 if camo else 3,\n",
" val=rng.uniform(80, 120))\n",
" elif k == \"animal\":\n",
" o = dict(y=rng.uniform(34, 42), x=rng.uniform(15, 80), vy=0, vx=rng.uniform(-1.5, 1.5), h=2, w=4, val=rng.uniform(60, 75))\n",
" else:\n",
" o = dict(y=rng.uniform(3, 15), x=rng.uniform(10, 80), vy=0, vx=rng.uniform(-3, 3), h=1, w=2, val=rng.uniform(100, 130))\n",
" o[\"cls\"] = str(k); obs.append(o)\n",
" frames, labels = [], []\n",
" for t in range(-8, T): # ์์ ์๊ฐ = ๊ฐ์ฒด ์๋ ๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ(์ฌ์ ๊ด์ธก)\n",
" f = bg.copy(); tree(f, t, wind); lab = []\n",
" for oi, o in enumerate(obs if t >= 0 else []):\n",
" jit = rng.normal(0, 1.2, 2) if o[\"cls\"] in (\"animal\", \"bird\") else (0, 0) # ๋ถ๊ท์น ์์ง์\n",
" y, x = int(o[\"y\"] + o[\"vy\"] * t + jit[0]), int(o[\"x\"] + o[\"vx\"] * t + jit[1])\n",
" y0, x0, y1, x1 = max(y, 0), max(x, 0), min(y + o[\"h\"], H), min(x + o[\"w\"], W)\n",
" if y1 > y0 and x1 > x0:\n",
" f[y0:y1, x0:x1] = o[\"val\"]\n",
" if o[\"cls\"] == \"person\":\n",
" f[max(y0 - 2, 0):y0, x0 + 1:x0 + 2] = o[\"val\"] # ๋จธ๋ฆฌ\n",
" lab.append({\"cls\": o[\"cls\"], \"oid\": oi, \"bbox\": (x0, y0, x1, y1), \"intruder\": o[\"cls\"] in INTRUDER})\n",
" if cond == \"night\":\n",
" f = f * 0.5 + 10 + rng.normal(0, 3.5, f.shape)\n",
" else:\n",
" f = f + rng.normal(0, 2.5, f.shape)\n",
" if cond == \"rain\":\n",
" m = rng.random(f.shape) < 0.006\n",
" for d in range(3):\n",
" f[d:][m[:H - d]] += 45\n",
" if cond == \"fog\":\n",
" f = 0.45 * f + 0.55 * 150\n",
" frames.append(np.clip(f, 0, 255)); labels.append(lab)\n",
" fr = np.array(frames)\n",
" return {\"id\": f\"CAM-{seed:04d}\", \"cond\": cond, \"wind\": wind, \"ref\": fr[:8], \"frames\": fr[8:], \"labels\": labels[8:]}\n",
"\n",
"# ---------------- ์ ์ฒ๋ฆฌยทํ์ง ๊ณตํต ํจ์(๋ฐฐ๊ฒฝ ์ฐจ๋ถ ๊ธฐ๋ฐ) ----------------\n",
"def enhance(seq):\n",
" \"\"\"์กฐ๋ ๋ณด์ (๋ฐฑ๋ถ์ ์ ์ฅ) + ๋น ์กฐ๊ฑด์ ๋น์ค๊ธฐ ์ ๊ฑฐ(3x3 ์ค์๊ฐ). ๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ(ref)๊ณผ ๊ฐ์ ๊ธฐ์ค์ผ๋ก ๋ณด์ ํ๋ค.\"\"\"\n",
" allf = np.concatenate([seq[\"ref\"], seq[\"frames\"]])\n",
" f = ndimage.median_filter(allf, size=(1, 3, 3)) if seq[\"cond\"] == \"rain\" else allf\n",
" lo, hi = np.percentile(f, [1, 99.5])\n",
" f = np.clip((f - lo) / (hi - lo + 1e-6) * 255, 0, 255)\n",
" return f[:len(seq[\"ref\"])], f[len(seq[\"ref\"]):]\n",
"\n",
"\n",
"def detect(ref, frames, k=4.0, min_area=3, sky_k=None, smooth=False):\n",
" \"\"\"๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ์ ์ค์๊ฐ ๋ฐฐ๊ฒฝ๊ณผ ์ฐจ์ด๋ฅผ ๊ฐ๊ฑด ์๊ณ๊ฐ(k x MAD)์ผ๋ก ์ด์งํํด ์ฐ๊ฒฐ ์ฑ๋ถ์ ๊ฐ์ฒด ํ๋ณด๋ก ๋ง๋ ๋ค.\n",
" smooth=True ์ด๋ฉด ์ฐ์ 3ํ๋ ์ ํ๊ท ์ผ๋ก ์๊ฐ ์ก์์ ์ค์ฌ ์ ๋๋น ๊ฐ์ฒด๋ฅผ ์ด๋ฆฐ๋ค.\"\"\"\n",
" bg = np.median(ref, axis=0)\n",
" if smooth:\n",
" frames = ndimage.uniform_filter1d(frames, 3, axis=0, mode=\"nearest\")\n",
" out = []\n",
" for f in frames:\n",
" d = np.abs(f - bg)\n",
" mad = np.median(d) * 1.4826 + 1e-6\n",
" thr = np.full(d.shape, k * mad)\n",
" if sky_k is not None:\n",
" thr[:HORIZON] = sky_k * mad\n",
" lab, n = ndimage.label(d > thr)\n",
" dets = []\n",
" for i, sl in enumerate(ndimage.find_objects(lab), start=1):\n",
" area = int((lab[sl] == i).sum())\n",
" if area >= min_area:\n",
" dets.append({\"bbox\": (sl[1].start, sl[0].start, sl[1].stop, sl[0].stop), \"area\": area,\n",
" \"contrast\": float(d[sl][lab[sl] == i].mean() / mad)})\n",
" out.append(dets)\n",
" return out\n",
"\n",
"\n",
"def center(b):\n",
" return ((b[0] + b[2]) / 2, (b[1] + b[3]) / 2)\n",
"\n",
"\n",
"def match(dets, labels, dist=4.0):\n",
" \"\"\"ํ์ง์ ์ ๋ต์ ์ค์ฌ ๊ฑฐ๋ฆฌ๋ก 1:1 ๋์์ํจ๋ค. ๋ฐํ: [(det, label or None)], ๋์น label ๋ชฉ๋ก\"\"\"\n",
" used, pairs = set(), []\n",
" for d in dets:\n",
" cd = center(d[\"bbox\"]); best, bj = dist, None\n",
" for j, l in enumerate(labels):\n",
" cl = center(l[\"bbox\"]); e = np.hypot(cd[0] - cl[0], cd[1] - cl[1])\n",
" if j not in used and e < best:\n",
" best, bj = e, j\n",
" if bj is not None:\n",
" used.add(bj)\n",
" pairs.append((d, labels[bj] if bj is not None else None))\n",
" return pairs, [l for j, l in enumerate(labels) if j not in used]\n",
"\n",
"\n",
"def in_roi(b):\n",
" cy = (b[1] + b[3]) / 2\n",
" return cy < HORIZON or ROI_ROWS[0] <= cy < ROI_ROWS[1]\n",
"\n",
"\n",
"def rule_class(b, area):\n",
" \"\"\"Baseline ๊ท์น: ์์น(ํ๋/์ง๋ฉด)ยทํฌ๊ธฐยท์ข
ํก๋น๋ก 1์ฐจ ๋ถ๋ฅ(ํ์ต ์์, ํ๋จ ๊ทผ๊ฑฐ๊ฐ ๊ทธ๋๋ก ๋๋ฌ๋๋ค)\"\"\"\n",
" w, h = b[2] - b[0], b[3] - b[1]\n",
" cy = (b[1] + b[3]) / 2\n",
" if cy < HORIZON:\n",
" return \"drone\" if 3 <= area <= 12 else \"clutter\"\n",
" if area >= 30 and w > h:\n",
" return \"vehicle\"\n",
" if h >= w and 6 <= area <= 30:\n",
" return \"person\"\n",
" return \"clutter\"\n",
"\n",
"# ---------------- ํ๋ ์ ๊ฐ ์ถ์ ๊ณผ ๊ถค์ ํน์ง ----------------\n",
"def track(dets, gate=6.0):\n",
" \"\"\"์ต๊ทผ์ ์ด์์ผ๋ก ํ๋ ์๋ณ ํ์ง๋ฅผ ๊ถค์ ์ผ๋ก ์๋๋ค.\"\"\"\n",
" tracks = []\n",
" for t, ds in enumerate(dets):\n",
" free = list(range(len(ds)))\n",
" for tr in tracks:\n",
" if tr[\"last\"] != t - 1 or not free:\n",
" continue\n",
" c0 = center(tr[\"b\"][-1])\n",
" dd = [np.hypot(center(ds[i][\"bbox\"])[0] - c0[0], center(ds[i][\"bbox\"])[1] - c0[1]) for i in free]\n",
" j = int(np.argmin(dd))\n",
" if dd[j] < gate:\n",
" i = free.pop(j); tr[\"b\"].append(ds[i][\"bbox\"]); tr[\"a\"].append(ds[i][\"area\"]); tr[\"c\"].append(ds[i][\"contrast\"])\n",
" tr[\"t\"].append(t); tr[\"last\"] = t\n",
" for i in free:\n",
" tracks.append({\"b\": [ds[i][\"bbox\"]], \"a\": [ds[i][\"area\"]], \"c\": [ds[i][\"contrast\"]], \"t\": [t], \"last\": t})\n",
" return tracks\n",
"\n",
"\n",
"TFEATS = [\"n\", \"area\", \"aspect\", \"row\", \"sky\", \"speed\", \"straight\", \"disp\", \"contrast\", \"area_cv\", \"col\"]\n",
"\n",
"\n",
"def track_features(tr):\n",
" c = np.array([center(b) for b in tr[\"b\"]])\n",
" wh = np.array([(b[2] - b[0], b[3] - b[1]) for b in tr[\"b\"]])\n",
" steps = np.hypot(*np.diff(c, axis=0).T) if len(c) > 1 else np.array([0.0])\n",
" disp = float(np.hypot(*(c[-1] - c[0])))\n",
" return {\"n\": len(c), \"area\": float(np.mean(tr[\"a\"])), \"aspect\": float(np.mean(wh[:, 1] / wh[:, 0])), \"row\": float(c[:, 1].mean()),\n",
" \"sky\": float(c[:, 1].mean() < HORIZON), \"speed\": float(steps.mean()), \"straight\": disp / (steps.sum() + 1e-6),\n",
" \"disp\": disp, \"contrast\": float(np.mean(tr[\"c\"])), \"area_cv\": float(np.std(tr[\"a\"]) / (np.mean(tr[\"a\"]) + 1e-6)),\n",
" \"col\": float(c[:, 0].mean())}\n",
"\n",
"\n",
"def track_truth(tr, labels):\n",
" \"\"\"๊ถค์ ์ ๊ฐ ํ๋ ์์ ์ ๋ต๊ณผ ๋์์์ผ ๋ค์๊ฒฐ๋ก ์ ์ฒด๋ฅผ ์ ํ๋ค(์์ผ๋ฉด clutter).\"\"\"\n",
" votes = []\n",
" for b, t in zip(tr[\"b\"], tr[\"t\"]):\n",
" pairs, _ = match([{\"bbox\": b}], labels[t])\n",
" votes.append((pairs[0][1][\"cls\"], pairs[0][1][\"oid\"]) if pairs[0][1] else (\"clutter\", -1))\n",
" return max(set(votes), key=votes.count)\n",
"\n",
"\n",
"def seq_tracks(seq, k=4.0, min_area=3, sky_k=None, smooth=False):\n",
" ref, fr = enhance(seq)\n",
" tracks = track(detect(ref, fr, k, min_area, sky_k, smooth))\n",
" rows = []\n",
" for tr in tracks:\n",
" f = track_features(tr); f[\"truth\"], f[\"oid\"] = track_truth(tr, seq[\"labels\"]); f[\"seq\"] = seq[\"id\"]; f[\"cond\"] = seq[\"cond\"]\n",
" f[\"tr\"] = tr; rows.append(f)\n",
" return rows\n",
"\n",
"ADV = {\"k\": 3.0, \"min_area\": 2, \"sky_k\": 2.5}\n",
"\n",
"\n",
"def collect(seeds, wind=None):\n",
" rows = []\n",
" for s in seeds:\n",
" q = make_seq(s, wind=wind)\n",
" for r in seq_tracks(q, **ADV):\n",
" if r[\"n\"] >= 2 and in_roi(r[\"tr\"][\"b\"][-1]): # 2ํ๋ ์ ์ด์ ์ง์ + ๊ฒฝ๊ณ๊ตฌ์ญ ์ = ๊ฒฝ๋ณด ํ๋ณด\n",
" r[\"intr\"] = r[\"truth\"] in INTRUDER\n",
" rows.append(r)\n",
" return rows\n",
"\n",
"\n",
"def X_of(rows):\n",
" return np.array([[r[k] for k in TFEATS] for r in rows])\n",
"\n",
"\n",
"def grade(p):\n",
" return \"High\" if p >= 0.8 else (\"Medium\" if p >= 0.5 else \"Low\")\n",
"\n",
"\n",
"train = collect(range(0, 100))\n",
"test = collect(range(1000, 1060))\n",
"windy = collect(range(2000, 2030), wind=2.0) # ๊ฐํ ์ํ์
: ์๋ชฉ ํ๋ค๋ฆผ์ด ๊ฐ์ฅ ์ฌํ ์กฐ๊ฑด\n",
"model = make_pipeline(StandardScaler(), LogisticRegression(class_weight=\"balanced\", C=2.0, max_iter=2000, random_state=0))\n",
"model.fit(X_of(train), [r[\"intr\"] for r in train])\n",
"\n",
"\n",
"def evaluate(rows, thr=0.5):\n",
" p = model.predict_proba(X_of(rows))[:, 1]\n",
" y = np.array([r[\"intr\"] for r in rows])\n",
" far0 = (~y).mean() # ๊ธฐ์กด: ํ๋ณด ์ ๋ถ ๊ฒฝ๋ณด -> ์ค๊ฒฝ๋ณด ๋น์จ\n",
" sel = p >= thr\n",
" far1 = (~y[sel]).sum() / max(sel.sum(), 1)\n",
" return p, y, far0, far1, y[sel].sum() / max(y.sum(), 1)\n",
"\n",
"\n",
"out = []\n",
"for name, rows in ((\"์ผ๋ฐ ์ํ์
\", test), (\"๊ฐํ ์ํ์
\", windy)):\n",
" p, y, far0, far1, rec = evaluate(rows)\n",
" out.append({\"์ํ์
\": name, \"ํ๋ณด ์\": len(rows), \"๊ธฐ์กด FAR\": round(far0, 3), \"์์ง FAR\": round(far1, 3),\n",
" \"FAR ๊ฐ์ถ\": f\"{1 - far1 / far0:.1%}\", \"์นจ์
์ฌํ์จ\": round(rec, 3), \"ROC-AUC\": round(roc_auc_score(y, p), 3)})\n",
"print(pd.DataFrame(out).to_string(index=False))\n",
"p, y, far0, far1, rec = evaluate(test)\n",
"cut = 1 - far1 / far0\n",
"print(f\"\\n์ค๊ฒฝ๋ณด์จ ๊ฐ์ถ {cut:.1%} (๋ชฉํ 40% ์ด์: {'๋ฌ์ฑ' if cut >= 0.4 else '๋ฏธ๋ฌ'})\")\n",
"\n",
"# ์ ์ ์ผ๊ด์ฑ: ํน์ง์ +-3% ์ธก์ ํ๋ค๋ฆผ์ ์ฃผ์ด๋ ๊ฐ์ ๋ฑ๊ธ์ด ์ ์ง๋๋ ๋น์จ\n",
"rng = np.random.default_rng(0)\n",
"X = X_of(test)\n",
"g0 = [grade(v) for v in p]\n",
"same = [np.mean([grade(v) == a for v, a in zip(model.predict_proba(X * (1 + rng.normal(0, 0.03, X.shape)))[:, 1], g0)]) for _ in range(10)]\n",
"print(f\"์ ๋ขฐ๋ ๋ฑ๊ธ ์ผ๊ด์ฑ {np.mean(same):.1%} (๊ฐ์ ์
๋ ฅ ์ฌ์คํ 100%, ๋ชฉํ 90% ์ด์: {'๋ฌ์ฑ' if np.mean(same) >= 0.9 else '๋ฏธ๋ฌ'})\")\n",
"\n",
"print(\"\\n[์ค์ ์ ์ฒด๋ณ ๋ฑ๊ธ ๋ถํฌ] (ํ: ์ค์ ์ ์ฒด)\")\n",
"print(pd.crosstab(pd.Series([r[\"truth\"] for r in test], name=\"์ค์ \"), pd.Series(g0, name=\"๋ฑ๊ธ\")).to_string())\n",
"\n",
"print(\"\\n[์๊ณ๊ฐ ๋ฏผ๊ฐ๋]\")\n",
"for thr in (0.3, 0.5, 0.7, 0.9):\n",
" _, _, _, f1_, r1_ = evaluate(test, thr)\n",
" print(f\"์๊ณ๊ฐ {thr}: ์ค๊ฒฝ๋ณด์จ {f1_:.3f}, ์นจ์
์ฌํ์จ {r1_:.3f}\")\n",
"\n",
"# ํ๋จ ๊ทผ๊ฑฐ: ์ ์์ ํฌ๊ฒ ๊ธฐ์ฌํ ํน์ง\n",
"sc, lr = model[0], model[-1]\n",
"i = int(np.argmax(p)); c = (X[i] - sc.mean_) / sc.scale_ * lr.coef_[0]\n",
"print(f\"\\n์ต๊ณ ์ ์ ํ๋ณด {test[i]['seq']}({test[i]['truth']}, ์ ์ {p[i]:.2f}) ๊ทผ๊ฑฐ:\", \", \".join(f\"{TFEATS[j]}({c[j]:+.2f})\" for j in np.argsort(-np.abs(c))[:3]))\n",
"print(\"๋ฑ๊ธ ๊ธฐ์ค: High(0.8 ์ด์)๋ ๊ฒฝ๊ณ๋ณ ์ฆ์ ํ์ธ, Medium(0.5~0.8)์ ์์ ์ฌํ์ธ, Low(0.5 ๋ฏธ๋ง)๋ ๊ธฐ๋ก ํ ๊ฐ์ ์ ์ง. ์ ์๋ ํ์ธ ์์๋ฅผ ์ ํ๋ ๋ณด์กฐ ์ ๋ณด์ด๋ฉฐ ์นจ์
ํ์ ์ด ์๋๋ค.\")\n",
"\n",
"fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n",
"for cls, col in [(\"person\", \"tab:red\"), (\"vehicle\", \"tab:orange\"), (\"drone\", \"tab:purple\"), (\"animal\", \"tab:green\"), (\"bird\", \"tab:blue\"), (\"clutter\", \"gray\")]:\n",
" v = [s for s, r in zip(p, test) if r[\"truth\"] == cls]\n",
" if v:\n",
" ax[0].hist(v, bins=10, range=(0, 1), alpha=0.6, label=cls, color=col)\n",
"ax[0].axvline(0.5, color=\"k\", ls=\"--\"); ax[0].set_xlabel(\"confidence score\"); ax[0].set_ylabel(\"tracks\"); ax[0].legend(); ax[0].set_title(\"Score by true class\")\n",
"fpr, tpr, _ = roc_curve(y, p); ax[1].plot(fpr, tpr); ax[1].plot([0, 1], [0, 1], \"k:\"); ax[1].set_title(f\"ROC (AUC {roc_auc_score(y, p):.3f})\")\n",
"ax[1].set_xlabel(\"false positive rate\"); ax[1].set_ylabel(\"true positive rate\")\n",
"plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
05 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
" ์ํ์
ํ๋ณด ์ ๊ธฐ์กด FAR ์์ง FAR FAR ๊ฐ์ถ ์นจ์
์ฌํ์จ ROC-AUC\n",
"์ผ๋ฐ ์ํ์
553 0.870 0.162 81.3% 0.931 0.984\n",
"๊ฐํ ์ํ์
194 0.784 0.162 79.3% 0.738 0.931\n",
"\n",
"์ค๊ฒฝ๋ณด์จ ๊ฐ์ถ 81.3% (๋ชฉํ 40% ์ด์: ๋ฌ์ฑ)\n",
"์ ๋ขฐ๋ ๋ฑ๊ธ ์ผ๊ด์ฑ 99.0% (๊ฐ์ ์
๋ ฅ ์ฌ์คํ 100%, ๋ชฉํ 90% ์ด์: ๋ฌ์ฑ)\n",
"\n",
"[์ค์ ์ ์ฒด๋ณ ๋ฑ๊ธ ๋ถํฌ] (ํ: ์ค์ ์ ์ฒด)\n",
"๋ฑ๊ธ High Low Medium\n",
"์ค์ \n",
"animal 0 25 0\n",
"bird 4 10 2\n",
"clutter 2 433 5\n",
"drone 30 1 0\n",
"person 25 2 1\n",
"vehicle 10 2 1\n",
"\n",
"[์๊ณ๊ฐ ๋ฏผ๊ฐ๋]\n",
"์๊ณ๊ฐ 0.3: ์ค๊ฒฝ๋ณด์จ 0.272, ์นจ์
์ฌํ์จ 0.931\n",
"์๊ณ๊ฐ 0.5: ์ค๊ฒฝ๋ณด์จ 0.163, ์นจ์
์ฌํ์จ 0.931\n",
"์๊ณ๊ฐ 0.7: ์ค๊ฒฝ๋ณด์จ 0.132, ์นจ์
์ฌํ์จ 0.917\n",
"์๊ณ๊ฐ 0.9: ์ค๊ฒฝ๋ณด์จ 0.016, ์นจ์
์ฌํ์จ 0.847\n",
"\n",
"์ต๊ณ ์ ์ ํ๋ณด CAM-1034(drone, ์ ์ 1.00) ๊ทผ๊ฑฐ: n(+4.27), row(+2.23), straight(+1.85)\n",
"๋ฑ๊ธ ๊ธฐ์ค: High(0.8 ์ด์)๋ ๊ฒฝ๊ณ๋ณ ์ฆ์ ํ์ธ, Medium(0.5~0.8)์ ์์ ์ฌํ์ธ, Low(0.5 ๋ฏธ๋ง)๋ ๊ธฐ๋ก ํ ๊ฐ์ ์ ์ง. ์ ์๋ ํ์ธ ์์๋ฅผ ์ ํ๋ ๋ณด์กฐ ์ ๋ณด์ด๋ฉฐ ์นจ์
ํ์ ์ด ์๋๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
06. CCTV ๋ค์ฑ๋ ์ค์๊ฐ ์นจ์
๊ฒฝ๋ณด ๋ฐ ์นจํฌ ๊ฒฝ๋ก ์ถ์ ๋์๋ณด๋ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๋ค์ฑ๋ CCTV ์ค์๊ฐ ์นจ์
๊ฒฝ๋ณด ๋ฐ ์นจํฌ ๊ฒฝ๋ก ์ถ์ ๊ด์ ๋์๋ณด๋๋ฅผ ๊ตฌํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๋ค์ฑ๋ CCTV์ ์นจ์
๊ฒฝ๋ณด์ ์นจํฌ ๊ฒฝ๋ก๋ฅผ ํ ํ๋ฉด์ ๋ณด์ฌ ์ฃผ๋ ๊ด์ ์ํฉํ์ ๊ตฌํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **6๊ฐ ์ฑ๋์ ํฉ์ฑ ์์์ ํฑ ๋จ์๋ก ์ฒ๋ฆฌํด ์ฑ๋ ์ํ(๊ฒฝ๋ณดยท์ฃผ์ยท์ ์), ์นจ์
๊ฐ์ฒด ๊ถค์ , ๊ฒฝ๋ณด ๋ชฉ๋ก์ ๊ฐฑ์ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/ui/sec01_dashboard.py (๋๋ ์น UI ์ปดํฌ๋ํธ)` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์ ์ ์์ ์ํฉํ์ผ๋ก์์ ์ง๊ด์ฑ ๋ฐ ์ธ์ง ๋ถํ ๊ฐ์๋\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ํ๋ฉด ๊ฐฑ์ ์ฃผ๊ธฐ 1์ด ์ด๋ด, UI ์๋ต์ฑ 100ms ์ด๋ด\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์ฃผ์ ์งํ ์๊ฐํ ์ปดํฌ๋ํธ 4์ข
์ด์ ๋ ๋๋ง ๋ฐ ์ค์๊ฐ ๋ฐ์ดํฐ ์ฐ๋\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ์ฌ์ฉ์ ์ธํฐ๋์
ํ
์คํธ ๋ฐ ๋ชจ์ ๋ฐ์ดํฐ ์คํธ๋ฆผ ๋ ๋๋ง ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"6์ฑ๋ CCTV ํฉ์ฑ ์์์ 1์ด ์ฃผ๊ธฐ๋ก ๊ฐฑ์ ํ๋ ๊ด์ ์ํฉํ์ ๊ตฌํํ๋ค. ์ฑ๋๋ณ ์ํ(๊ฒฝ๋ณด/์ฃผ์/์ ์), ์นจ์
๊ฐ์ฒด ๊ถค์ (์นจํฌ ๊ฒฝ๋ก), ๊ฒฝ๋ณด ๋ชฉ๋ก์ ํ ํ๋ฉด์ ๋ชจ์ผ๊ณ ๊ฐฑ์ ์ฒ๋ฆฌ์๊ฐ์ ์ธก์ ํ๋ค.\"\"\"\n",
"import time\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from scipy import ndimage\n",
"\n",
"# ---------------- [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํฉ์ฑ CCTV ์์ ์์ฑ๊ธฐ ----------------\n",
"# ์ค์ ์์ ๋์ ์ฝ๋๋ก ๊ทธ๋ฆฐ ๊ฐ์ฒด(์ฌ๊ฐํยท์ )๋ก ๊ธฐ์ง ์ธ๊ณฝ ๊ณ ์ ์นด๋ฉ๋ผ ํ๋ฉด์ ํ๋ด ๋ธ๋ค.\n",
"H, W, T = 64, 96, 10 # ํ๋ฉด ํฌ๊ธฐ(px), ์ํ์ค ๊ธธ์ด(ํ๋ ์)\n",
"HORIZON, FENCE = 20, 46 # ํ๋/์ง๋ฉด ๊ฒฝ๊ณ, ์ธํ๋ฆฌ ํ\n",
"ROI_ROWS = (32, 48) # ์ธํ๋ฆฌ ์ ๊ทผ ๊ฒฝ๊ณ๊ตฌ์ญ(ROI): ์ง๋ฉด 32~47ํ\n",
"CLASSES = [\"person\", \"vehicle\", \"drone\", \"animal\", \"bird\"]\n",
"INTRUDER = {\"person\", \"vehicle\", \"drone\"} # ๋น์ธ๊ฐ ์ ๊ทผ(๊ฒฝ๋ณด ๋์)\n",
"CONDS = [\"day\", \"night\", \"rain\", \"fog\"]\n",
"\n",
"\n",
"def background(rng):\n",
" # [TODO : ํ์ ์ค์ต 6] CCTV ๋ค์ฑ๋ ์ค์๊ฐ ์นจ์
๊ฒฝ๋ณด ๋ฐ ์นจํฌ ๊ฒฝ๋ก ์ถ์ ๋์๋ณด๋ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def tree(img, t, wind):\n",
" \"\"\"์ข์ธก ์๋ชฉ ์๊ด: ๋ฐ๋ ์ธ๊ธฐ์ ๋ฐ๋ผ ์ข์ฐ๋ก ํ๋ค๋ ค ์ค๊ฒฝ๋ณด๋ฅผ ๋ง๋ ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 6] CCTV ๋ค์ฑ๋ ์ค์๊ฐ ์นจ์
๊ฒฝ๋ณด ๋ฐ ์นจํฌ ๊ฒฝ๋ก ์ถ์ ๋์๋ณด๋ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_seq(seed, cond=None, objs=None, wind=None, camo=False):\n",
" rng = np.random.default_rng(seed)\n",
" cond = cond or str(rng.choice(CONDS))\n",
" wind = rng.uniform(0, 2) if wind is None else wind\n",
" bg = background(rng)\n",
" n = int(rng.integers(1, 4)) if objs is None else len(objs)\n",
" kinds = objs or list(rng.choice(CLASSES, n, p=[0.3, 0.15, 0.25, 0.15, 0.15]))\n",
" obs = []\n",
" for k in kinds:\n",
" if k == \"person\":\n",
" o = dict(y=rng.uniform(30, 36), x=rng.uniform(15, 85), vy=rng.uniform(0.8, 1.2), vx=rng.uniform(-0.6, 0.6), h=7, w=3,\n",
" val=72 if camo else rng.uniform(35, 50))\n",
" elif k == \"vehicle\":\n",
" o = dict(y=rng.uniform(33, 38), x=rng.uniform(10, 30), vy=0.2, vx=rng.uniform(2, 4), h=5, w=10, val=rng.uniform(150, 170))\n",
" elif k == \"drone\":\n",
" o = dict(y=rng.uniform(3, 15), x=rng.uniform(10, 80), vy=rng.uniform(0, 0.6), vx=rng.uniform(-2, 2), h=2, w=2 if camo else 3,\n",
" val=rng.uniform(80, 120))\n",
" elif k == \"animal\":\n",
" o = dict(y=rng.uniform(34, 42), x=rng.uniform(15, 80), vy=0, vx=rng.uniform(-1.5, 1.5), h=2, w=4, val=rng.uniform(60, 75))\n",
" else:\n",
" o = dict(y=rng.uniform(3, 15), x=rng.uniform(10, 80), vy=0, vx=rng.uniform(-3, 3), h=1, w=2, val=rng.uniform(100, 130))\n",
" o[\"cls\"] = str(k); obs.append(o)\n",
" frames, labels = [], []\n",
" for t in range(-8, T): # ์์ ์๊ฐ = ๊ฐ์ฒด ์๋ ๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ(์ฌ์ ๊ด์ธก)\n",
" f = bg.copy(); tree(f, t, wind); lab = []\n",
" for oi, o in enumerate(obs if t >= 0 else []):\n",
" jit = rng.normal(0, 1.2, 2) if o[\"cls\"] in (\"animal\", \"bird\") else (0, 0) # ๋ถ๊ท์น ์์ง์\n",
" y, x = int(o[\"y\"] + o[\"vy\"] * t + jit[0]), int(o[\"x\"] + o[\"vx\"] * t + jit[1])\n",
" y0, x0, y1, x1 = max(y, 0), max(x, 0), min(y + o[\"h\"], H), min(x + o[\"w\"], W)\n",
" if y1 > y0 and x1 > x0:\n",
" f[y0:y1, x0:x1] = o[\"val\"]\n",
" if o[\"cls\"] == \"person\":\n",
" f[max(y0 - 2, 0):y0, x0 + 1:x0 + 2] = o[\"val\"] # ๋จธ๋ฆฌ\n",
" lab.append({\"cls\": o[\"cls\"], \"oid\": oi, \"bbox\": (x0, y0, x1, y1), \"intruder\": o[\"cls\"] in INTRUDER})\n",
" if cond == \"night\":\n",
" f = f * 0.5 + 10 + rng.normal(0, 3.5, f.shape)\n",
" else:\n",
" f = f + rng.normal(0, 2.5, f.shape)\n",
" if cond == \"rain\":\n",
" m = rng.random(f.shape) < 0.006\n",
" for d in range(3):\n",
" f[d:][m[:H - d]] += 45\n",
" if cond == \"fog\":\n",
" f = 0.45 * f + 0.55 * 150\n",
" frames.append(np.clip(f, 0, 255)); labels.append(lab)\n",
" fr = np.array(frames)\n",
" return {\"id\": f\"CAM-{seed:04d}\", \"cond\": cond, \"wind\": wind, \"ref\": fr[:8], \"frames\": fr[8:], \"labels\": labels[8:]}\n",
"\n",
"# ---------------- ์ ์ฒ๋ฆฌยทํ์ง ๊ณตํต ํจ์(๋ฐฐ๊ฒฝ ์ฐจ๋ถ ๊ธฐ๋ฐ) ----------------\n",
"def enhance(seq):\n",
" \"\"\"์กฐ๋ ๋ณด์ (๋ฐฑ๋ถ์ ์ ์ฅ) + ๋น ์กฐ๊ฑด์ ๋น์ค๊ธฐ ์ ๊ฑฐ(3x3 ์ค์๊ฐ). ๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ(ref)๊ณผ ๊ฐ์ ๊ธฐ์ค์ผ๋ก ๋ณด์ ํ๋ค.\"\"\"\n",
" allf = np.concatenate([seq[\"ref\"], seq[\"frames\"]])\n",
" f = ndimage.median_filter(allf, size=(1, 3, 3)) if seq[\"cond\"] == \"rain\" else allf\n",
" lo, hi = np.percentile(f, [1, 99.5])\n",
" f = np.clip((f - lo) / (hi - lo + 1e-6) * 255, 0, 255)\n",
" return f[:len(seq[\"ref\"])], f[len(seq[\"ref\"]):]\n",
"\n",
"\n",
"def detect(ref, frames, k=4.0, min_area=3, sky_k=None, smooth=False):\n",
" \"\"\"๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ์ ์ค์๊ฐ ๋ฐฐ๊ฒฝ๊ณผ ์ฐจ์ด๋ฅผ ๊ฐ๊ฑด ์๊ณ๊ฐ(k x MAD)์ผ๋ก ์ด์งํํด ์ฐ๊ฒฐ ์ฑ๋ถ์ ๊ฐ์ฒด ํ๋ณด๋ก ๋ง๋ ๋ค.\n",
" smooth=True ์ด๋ฉด ์ฐ์ 3ํ๋ ์ ํ๊ท ์ผ๋ก ์๊ฐ ์ก์์ ์ค์ฌ ์ ๋๋น ๊ฐ์ฒด๋ฅผ ์ด๋ฆฐ๋ค.\"\"\"\n",
" bg = np.median(ref, axis=0)\n",
" if smooth:\n",
" frames = ndimage.uniform_filter1d(frames, 3, axis=0, mode=\"nearest\")\n",
" out = []\n",
" for f in frames:\n",
" d = np.abs(f - bg)\n",
" mad = np.median(d) * 1.4826 + 1e-6\n",
" thr = np.full(d.shape, k * mad)\n",
" if sky_k is not None:\n",
" thr[:HORIZON] = sky_k * mad\n",
" lab, n = ndimage.label(d > thr)\n",
" dets = []\n",
" for i, sl in enumerate(ndimage.find_objects(lab), start=1):\n",
" area = int((lab[sl] == i).sum())\n",
" if area >= min_area:\n",
" dets.append({\"bbox\": (sl[1].start, sl[0].start, sl[1].stop, sl[0].stop), \"area\": area,\n",
" \"contrast\": float(d[sl][lab[sl] == i].mean() / mad)})\n",
" out.append(dets)\n",
" return out\n",
"\n",
"\n",
"def center(b):\n",
" return ((b[0] + b[2]) / 2, (b[1] + b[3]) / 2)\n",
"\n",
"\n",
"def match(dets, labels, dist=4.0):\n",
" \"\"\"ํ์ง์ ์ ๋ต์ ์ค์ฌ ๊ฑฐ๋ฆฌ๋ก 1:1 ๋์์ํจ๋ค. ๋ฐํ: [(det, label or None)], ๋์น label ๋ชฉ๋ก\"\"\"\n",
" used, pairs = set(), []\n",
" for d in dets:\n",
" cd = center(d[\"bbox\"]); best, bj = dist, None\n",
" for j, l in enumerate(labels):\n",
" cl = center(l[\"bbox\"]); e = np.hypot(cd[0] - cl[0], cd[1] - cl[1])\n",
" if j not in used and e < best:\n",
" best, bj = e, j\n",
" if bj is not None:\n",
" used.add(bj)\n",
" pairs.append((d, labels[bj] if bj is not None else None))\n",
" return pairs, [l for j, l in enumerate(labels) if j not in used]\n",
"\n",
"\n",
"def in_roi(b):\n",
" cy = (b[1] + b[3]) / 2\n",
" return cy < HORIZON or ROI_ROWS[0] <= cy < ROI_ROWS[1]\n",
"\n",
"\n",
"def rule_class(b, area):\n",
" \"\"\"Baseline ๊ท์น: ์์น(ํ๋/์ง๋ฉด)ยทํฌ๊ธฐยท์ข
ํก๋น๋ก 1์ฐจ ๋ถ๋ฅ(ํ์ต ์์, ํ๋จ ๊ทผ๊ฑฐ๊ฐ ๊ทธ๋๋ก ๋๋ฌ๋๋ค)\"\"\"\n",
" w, h = b[2] - b[0], b[3] - b[1]\n",
" cy = (b[1] + b[3]) / 2\n",
" if cy < HORIZON:\n",
" return \"drone\" if 3 <= area <= 12 else \"clutter\"\n",
" if area >= 30 and w > h:\n",
" return \"vehicle\"\n",
" if h >= w and 6 <= area <= 30:\n",
" return \"person\"\n",
" return \"clutter\"\n",
"\n",
"# ---------------- ํ๋ ์ ๊ฐ ์ถ์ ๊ณผ ๊ถค์ ํน์ง ----------------\n",
"def track(dets, gate=6.0):\n",
" \"\"\"์ต๊ทผ์ ์ด์์ผ๋ก ํ๋ ์๋ณ ํ์ง๋ฅผ ๊ถค์ ์ผ๋ก ์๋๋ค.\"\"\"\n",
" tracks = []\n",
" for t, ds in enumerate(dets):\n",
" free = list(range(len(ds)))\n",
" for tr in tracks:\n",
" if tr[\"last\"] != t - 1 or not free:\n",
" continue\n",
" c0 = center(tr[\"b\"][-1])\n",
" dd = [np.hypot(center(ds[i][\"bbox\"])[0] - c0[0], center(ds[i][\"bbox\"])[1] - c0[1]) for i in free]\n",
" j = int(np.argmin(dd))\n",
" if dd[j] < gate:\n",
" i = free.pop(j); tr[\"b\"].append(ds[i][\"bbox\"]); tr[\"a\"].append(ds[i][\"area\"]); tr[\"c\"].append(ds[i][\"contrast\"])\n",
" tr[\"t\"].append(t); tr[\"last\"] = t\n",
" for i in free:\n",
" tracks.append({\"b\": [ds[i][\"bbox\"]], \"a\": [ds[i][\"area\"]], \"c\": [ds[i][\"contrast\"]], \"t\": [t], \"last\": t})\n",
" return tracks\n",
"\n",
"\n",
"TFEATS = [\"n\", \"area\", \"aspect\", \"row\", \"sky\", \"speed\", \"straight\", \"disp\", \"contrast\", \"area_cv\", \"col\"]\n",
"\n",
"\n",
"def track_features(tr):\n",
" c = np.array([center(b) for b in tr[\"b\"]])\n",
" wh = np.array([(b[2] - b[0], b[3] - b[1]) for b in tr[\"b\"]])\n",
" steps = np.hypot(*np.diff(c, axis=0).T) if len(c) > 1 else np.array([0.0])\n",
" disp = float(np.hypot(*(c[-1] - c[0])))\n",
" return {\"n\": len(c), \"area\": float(np.mean(tr[\"a\"])), \"aspect\": float(np.mean(wh[:, 1] / wh[:, 0])), \"row\": float(c[:, 1].mean()),\n",
" \"sky\": float(c[:, 1].mean() < HORIZON), \"speed\": float(steps.mean()), \"straight\": disp / (steps.sum() + 1e-6),\n",
" \"disp\": disp, \"contrast\": float(np.mean(tr[\"c\"])), \"area_cv\": float(np.std(tr[\"a\"]) / (np.mean(tr[\"a\"]) + 1e-6)),\n",
" \"col\": float(c[:, 0].mean())}\n",
"\n",
"\n",
"def track_truth(tr, labels):\n",
" \"\"\"๊ถค์ ์ ๊ฐ ํ๋ ์์ ์ ๋ต๊ณผ ๋์์์ผ ๋ค์๊ฒฐ๋ก ์ ์ฒด๋ฅผ ์ ํ๋ค(์์ผ๋ฉด clutter).\"\"\"\n",
" votes = []\n",
" for b, t in zip(tr[\"b\"], tr[\"t\"]):\n",
" pairs, _ = match([{\"bbox\": b}], labels[t])\n",
" votes.append((pairs[0][1][\"cls\"], pairs[0][1][\"oid\"]) if pairs[0][1] else (\"clutter\", -1))\n",
" return max(set(votes), key=votes.count)\n",
"\n",
"\n",
"def seq_tracks(seq, k=4.0, min_area=3, sky_k=None, smooth=False):\n",
" ref, fr = enhance(seq)\n",
" tracks = track(detect(ref, fr, k, min_area, sky_k, smooth))\n",
" rows = []\n",
" for tr in tracks:\n",
" f = track_features(tr); f[\"truth\"], f[\"oid\"] = track_truth(tr, seq[\"labels\"]); f[\"seq\"] = seq[\"id\"]; f[\"cond\"] = seq[\"cond\"]\n",
" f[\"tr\"] = tr; rows.append(f)\n",
" return rows\n",
"\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] 6๊ฐ ์ฑ๋: ์๋ก ๋ค๋ฅธ ์กฐ๊ฑดยท๊ฐ์ฒด ๊ตฌ์ฑ์ ํฉ์ฑ CCTV\n",
"SPECS = [(11, \"day\", [\"person\", \"drone\"]), (12, \"night\", [\"vehicle\"]), (13, \"rain\", [\"animal\", \"person\"]),\n",
" (14, \"fog\", [\"bird\", \"drone\", \"person\"]), (15, \"day\", [\"animal\"]), (16, \"night\", [\"drone\", \"vehicle\"])]\n",
"CH = {f\"CH{i + 1}\": make_seq(s, cond=c, objs=o, wind=1.5) for i, (s, c, o) in enumerate(SPECS)}\n",
"PRE = {name: enhance(q) for name, q in CH.items()}\n",
"BG = {name: np.median(ref, axis=0) for name, (ref, fr) in PRE.items()}\n",
"STATE = {name: {\"dets\": []} for name in CH}\n",
"\n",
"\n",
"def update(t):\n",
" \"\"\"tick t: ์ฑ๋๋ง๋ค ์ ํ๋ ์์ ํ์งํด ๊ถค์ ์ ๊ฐฑ์ ํ๊ณ ๊ฒฝ๋ณด ๋ชฉ๋ก์ ๋ง๋ ๋ค.\"\"\"\n",
" alerts, raw = [], 0\n",
" for name in CH:\n",
" ref, fr = PRE[name]\n",
" STATE[name][\"dets\"] += detect(ref, fr[t:t + 1])\n",
" tracks = track(STATE[name][\"dets\"])\n",
" raw += len(STATE[name][\"dets\"][-1])\n",
" STATE[name][\"tracks\"] = tracks\n",
" for i, tr in enumerate(tracks):\n",
" if len(tr[\"b\"]) >= 3 and in_roi(tr[\"b\"][-1]) and tr[\"last\"] == t:\n",
" f = track_features(tr)\n",
" cls = rule_class(tr[\"b\"][-1], tr[\"a\"][-1])\n",
" moving = f[\"disp\"] > 4\n",
" if cls in INTRUDER and moving:\n",
" alerts.append({\"์ฑ๋\": name, \"๋ถ๋ฅ\": cls, \"์ฐ์ ํ๋ ์\": len(tr[\"b\"]), \"์์น(x,y)\": tuple(int(v) for v in center(tr[\"b\"][-1])), \"tr\": tr})\n",
" return alerts, raw\n",
"\n",
"\n",
"# 1) 10ํฑ(10์ด) ๊ฐฑ์ ๋ชจ์: ์ฒ๋ฆฌ์๊ฐยทํ์ ๊ฑด์ ๊ธฐ๋ก\n",
"log = []\n",
"for t in range(T):\n",
" t0 = time.perf_counter()\n",
" alerts, raw = update(t)\n",
" ms = (time.perf_counter() - t0) * 1000\n",
" log.append({\"tick\": t + 1, \"์์ ํ์ง\": raw, \"๊ฒฝ๋ณด ํญ๋ชฉ\": len(alerts), \"๊ฐฑ์ ms\": round(ms, 1)})\n",
"df = pd.DataFrame(log)\n",
"print(df.to_string(index=False))\n",
"print(f\"\\n6์ฑ๋ ๊ฐฑ์ ํ๊ท {df['๊ฐฑ์ ms'].mean():.1f} ms, ์ต๋ {df['๊ฐฑ์ ms'].max():.1f} ms (๋ชฉํ: ๊ฐฑ์ ์ฃผ๊ธฐ 1์ด ์ด๋ด -> {'๋ฌ์ฑ' if df['๊ฐฑ์ ms'].max() < 1000 else '๋ฏธ๋ฌ'})\")\n",
"\n",
"# 2) ์ธ์ง ๋ถํ ๊ฐ์: ์ด์ฉ์๊ฐ ๋ด์ผ ํ ํญ๋ชฉ ์\n",
"print(f\"์ธ์ง ๋ถํ: ๋ง์ง๋ง ํฑ ์์ ํ์ง {log[-1]['์์ ํ์ง']}๊ฑด -> ๊ฒฝ๋ณด ํญ๋ชฉ {log[-1]['๊ฒฝ๋ณด ํญ๋ชฉ']}๊ฑด์ผ๋ก ์์ฝ\")\n",
"\n",
"# 3) ์ฑ๋ ์ํ ํ(๊ฒฝ๋ณด ์ฐ์ ์ ๋ ฌ)\n",
"board = []\n",
"for name in CH:\n",
" mine = [a for a in alerts if a[\"์ฑ๋\"] == name]\n",
" watch = sum(1 for tr in STATE[name][\"tracks\"] if len(tr[\"b\"]) >= 3 and in_roi(tr[\"b\"][-1]) and tr[\"last\"] == T - 1) - len(mine)\n",
" board.append({\"์ฑ๋\": name, \"์กฐ๊ฑด\": CH[name][\"cond\"], \"์ํ\": \"๊ฒฝ๋ณด\" if mine else (\"์ฃผ์\" if watch > 0 else \"์ ์\"),\n",
" \"๊ฒฝ๋ณด ๋ถ๋ฅ\": \",\".join(sorted({a[\"๋ถ๋ฅ\"] for a in mine})) or \"-\", \"์ฃผ์ ํญ๋ชฉ\": max(watch, 0)})\n",
"board = pd.DataFrame(board)\n",
"print(\"\\n[์ฑ๋ ์ํํ]\")\n",
"print(board.sort_values(\"์ํ\", key=lambda s: s.map({\"๊ฒฝ๋ณด\": 0, \"์ฃผ์\": 1, \"์ ์\": 2})).to_string(index=False))\n",
"\n",
"# 4) ๋ ๋๋ง ์๊ฐ(UI ์๋ต์ฑ): ํ ํ๋ฉด์ ๊ทธ๋ฆฌ๋ ์๊ฐ\n",
"t0 = time.perf_counter()\n",
"col = {\"๊ฒฝ๋ณด\": \"red\", \"์ฃผ์\": \"gold\", \"์ ์\": \"limegreen\"}\n",
"EN = {\"๊ฒฝ๋ณด\": \"ALERT\", \"์ฃผ์\": \"WATCH\", \"์ ์\": \"NORMAL\"}\n",
"fig, ax = plt.subplots(2, 3, figsize=(13, 6))\n",
"for a, name in zip(ax.ravel(), CH):\n",
" st = board[board[\"์ฑ๋\"] == name].iloc[0]\n",
" a.imshow(CH[name][\"frames\"][-1], cmap=\"gray\", vmin=0, vmax=255)\n",
" for al in [x for x in alerts if x[\"์ฑ๋\"] == name]:\n",
" c = np.array([center(b) for b in al[\"tr\"][\"b\"]])\n",
" a.plot(c[:, 0], c[:, 1], \"-o\", color=\"red\", ms=3, lw=2)\n",
" a.text(c[-1, 0] + 2, c[-1, 1], al[\"๋ถ๋ฅ\"], color=\"red\", fontsize=8)\n",
" for s in a.spines.values():\n",
" s.set_edgecolor(col[st[\"์ํ\"]]); s.set_linewidth(4)\n",
" a.set_title(f\"{name} [{EN[st['์ํ']]}] {CH[name]['cond']}\", fontsize=10); a.set_xticks([]); a.set_yticks([])\n",
"plt.suptitle(\"Multi-channel CCTV board (synthetic) - red path = intrusion track\")\n",
"plt.tight_layout(); fig.canvas.draw()\n",
"render = (time.perf_counter() - t0) * 1000\n",
"print(f\"\\nํ ํ๋ฉด ๋ ๋๋ง {render:.0f} ms (UI ์๋ต ๋ชฉํ 100 ms: ๋ฐ์ดํฐ ๊ฐฑ์ {df['๊ฐฑ์ ms'].mean():.1f} ms๋ ๋ฌ์ฑ, ๋ ๋๋ง์ ์คํ ํ๊ฒฝ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง)\")\n",
"print(\"์ํฉํ ๊ฒฝ๋ณด๋ ํ๋จ ๋ณด์กฐ ์ ๋ณด์ด๋ฉฐ, ์ถ๋ยท๊ฒฝ๋ณด ํด์ ๊ฒฐ์ฌ์ ๊ฒฝ๊ณ๋ณ ์ก์ ํ์ธ๊ณผ ์งํ๊ณํต(๋ฏธ์
7)์ ๋ฐ๋ฅธ๋ค.\")\n",
"plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
06 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
" tick ์์ ํ์ง ๊ฒฝ๋ณด ํญ๋ชฉ ๊ฐฑ์ ms\n",
" 1 17 0 16.8\n",
" 2 29 0 17.2\n",
" 3 33 2 20.6\n",
" 4 26 3 24.2\n",
" 5 31 4 22.4\n",
" 6 15 6 21.1\n",
" 7 31 6 22.2\n",
" 8 21 7 22.0\n",
" 9 28 6 22.7\n",
" 10 32 6 23.6\n",
"\n",
"6์ฑ๋ ๊ฐฑ์ ํ๊ท 21.3 ms, ์ต๋ 24.2 ms (๋ชฉํ: ๊ฐฑ์ ์ฃผ๊ธฐ 1์ด ์ด๋ด -> ๋ฌ์ฑ)\n",
"์ธ์ง ๋ถํ: ๋ง์ง๋ง ํฑ ์์ ํ์ง 32๊ฑด -> ๊ฒฝ๋ณด ํญ๋ชฉ 6๊ฑด์ผ๋ก ์์ฝ\n",
"\n",
"[์ฑ๋ ์ํํ]\n",
" ์ฑ๋ ์กฐ๊ฑด ์ํ ๊ฒฝ๋ณด ๋ถ๋ฅ ์ฃผ์ ํญ๋ชฉ\n",
"CH1 day ๊ฒฝ๋ณด person 1\n",
"CH2 night ๊ฒฝ๋ณด vehicle 0\n",
"CH4 fog ๊ฒฝ๋ณด drone,person 0\n",
"CH6 night ๊ฒฝ๋ณด drone,vehicle 0\n",
"CH3 rain ์ฃผ์ - 3\n",
"CH5 day ์ฃผ์ - 1\n",
"\n",
"ํ ํ๋ฉด ๋ ๋๋ง 246 ms (UI ์๋ต ๋ชฉํ 100 ms: ๋ฐ์ดํฐ ๊ฐฑ์ 21.3 ms๋ ๋ฌ์ฑ, ๋ ๋๋ง์ ์คํ ํ๊ฒฝ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง)\n",
"์ํฉํ ๊ฒฝ๋ณด๋ ํ๋จ ๋ณด์กฐ ์ ๋ณด์ด๋ฉฐ, ์ถ๋ยท๊ฒฝ๋ณด ํด์ ๊ฒฐ์ฌ์ ๊ฒฝ๊ณ๋ณ ์ก์ ํ์ธ๊ณผ ์งํ๊ณํต(๋ฏธ์
7)์ ๋ฐ๋ฅธ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
07. ๊ฒฝ๊ณ๋ณ ์ก์ ํ์ธ ๋ฐ ๊ฒฝ๋ณด ํด์ /์ถ๋ ๊ฒฐ์ฌ์ ์ํ HITL ์น์ธ ์ํฌํ๋ก์ฐ ์ค๊ณ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๊ฒฝ๊ณ๋ณ ์ก์ ํ์ธ ๋ฐ ์ถ๋ ๊ฒฐ์ฌ์ ์ง์ํ๋ HITL ์น์ธ/๋ณด๋ฅ ์ํฌํ๋ก์ฐ๋ฅผ ์ฐ๊ณํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๊ฒฝ๋ณด๋ฅผ ๊ฒฝ๊ณ๋ณ์ด ์ก์์ผ๋ก ํ์ธํ ๋ค ๊ฒฝ๋ณด ํด์ ยท์ถ๋ ๊ฒฐ์ฌ ์์ฒญยท๋ณด๋ฅ๋ฅผ ๊ฒฐ์ ํ๋ ์ธ๊ฐ ๊ฐ์
(HITL) ์น์ธ ์ํฌํ๋ก์ฐ๋ฅผ ์ค๊ณํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **AI ๊ฒฝ๋ณด๋ ์ ์์ ๋ฌด๊ดํ๊ฒ ๊ฒํ ๋๊ธฐ๋ก ๋ฑ๋กํ๊ณ ๋ฑ๊ธยท์ ์ ์์ผ๋ก ๊ฒํ ์์๋ฅผ ์ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/hitl/sec01_approval_workflow.py ๋ฐ docs/sec01_hitl_guide.md` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์งํ ์ฑ
์ ๋ถ๊ณ์ ๋ช
ํํ ๋ฐ ์ฑ๊ธํ ์๋ ๋จ์ ๋ฐฉ์ง ์ฒด๊ณ ๋ถํฉ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์ธ๊ฐ ๊ฒํ ํ(Queue) ๋๊ธฐ์๊ฐ ๋ฐ ์น์ธ/๋ฐ๋ ค ์ฒ๋ฆฌ ๋ก๊น
์ฑ๊ณต๋ฅ 100%\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์น์ธ/๋ฐ๋ ค ์ธํฐํ์ด์ค ๋์ ๋ฐ ๊ฐ์ฌ ๋ก๊ทธ(Audit Log) ์์ฑ ๊ฒ์ฆ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ๋ชจ์ ์กฐ์์ ์น์ธ/๋ฐ๋ ค ์๋ฎฌ๋ ์ด์
๋ฐ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# [TODO : ํ์ ์ค์ต 7] ๊ฒฝ๊ณ๋ณ ์ก์ ํ์ธ ๋ฐ ๊ฒฝ๋ณด ํด์ /์ถ๋ ๊ฒฐ์ฌ์ ์ํ HITL ์น์ธ ์ํฌํ๋ก์ฐ ์ค๊ณ\n",
"# ๋ฏธ์
์ง์นจ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค์ ํ์ธํ๊ณ ์ฝ๋๋ฅผ ์คํ ๋ฐ ํ๋ํ์ธ์.\n",
"\"\"\"CCTV ์นจ์
๊ฒฝ๋ณด๋ฅผ ๊ฒฝ๊ณ๋ณ์ด ์ก์์ผ๋ก ํ์ธํ ๋ค ๊ฒฝ๋ณด ํด์ ยท์ถ๋ ๊ฒฐ์ฌ ์์ฒญยท๋ณด๋ฅ๋ฅผ ๊ฒฐ์ ํ๋ ์ธ๊ฐ ๊ฐ์
(HITL) ์น์ธ ์ํฌํ๋ก์ฐ๋ฅผ ๊ตฌํํ๋ค. AI๋ ์๋์ผ๋ก ํด์ ํ๊ฑฐ๋ ์ถ๋์ ๊ฒฐ์ ํ์ง ์์ผ๋ฉฐ, ๋ชจ๋ ๊ฒฐ์ ์ ๋ด๋น์ IDยท์ญํ ยท๊ทผ๊ฑฐ์ ํจ๊ป ์ยท๋ณ์กฐ ํ์ธ์ด ๊ฐ๋ฅํ ๊ฐ์ฌ ๋ก๊ทธ์ ๊ธฐ๋ก๋๋ค.\"\"\"\n",
"import hashlib\n",
"import json\n",
"import pandas as pd\n",
"\n",
"ACTIONS = {\"RELEASE\": \"๊ฒฝ๋ณด ํด์ (์ก์ ํ์ธ ๊ฒฐ๊ณผ ์ค๊ฒฝ๋ณด)\", \"DISPATCH\": \"์ถ๋ ๊ฒฐ์ฌ ์์ฒญ(๋น์ง ์งํ๊ด ์น์ธ ๋์)\", \"HOLD\": \"๋ณด๋ฅ(์์ ์ฌํ์ธ)\"}\n",
"ROLES = {\"sentry\": \"๊ฒฝ๊ณ๋ณ\", \"duty_officer\": \"๋น์ง ์งํ๊ด\"}\n",
"PRIORITY = {\"High\": 0, \"Medium\": 1, \"Low\": 2}\n",
"HOLD_LIMIT_S = 60\n",
"\n",
"\n",
"class WorkflowError(ValueError):\n",
" pass\n",
"\n",
"\n",
"class ReviewQueue:\n",
" def __init__(self, clock):\n",
" self.items, self.log, self.clock = {}, [], clock\n",
"\n",
" # ---- ๊ฐ์ฌ ๋ก๊ทธ: ์ง์ ํด์๋ฅผ ํฌํจํ ํด์ ์ฒด์ธ(์ถ๊ฐ ์ ์ฉ) ----\n",
" def _record(self, body):\n",
" prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n",
" body = {**body, \"ts\": round(self.clock(), 3)}\n",
" h = hashlib.sha256((prev + json.dumps(body, ensure_ascii=False, sort_keys=True)).encode()).hexdigest()\n",
" self.log.append({**body, \"prev\": prev, \"hash\": h})\n",
"\n",
" def verify_log(self):\n",
" prev = \"GENESIS\"\n",
" for n, rec in enumerate(self.log, start=1):\n",
" body = {k: v for k, v in rec.items() if k not in (\"prev\", \"hash\")}\n",
" ok_hash = hashlib.sha256((prev + json.dumps(body, ensure_ascii=False, sort_keys=True)).encode()).hexdigest()\n",
" if rec[\"prev\"] != prev or rec[\"hash\"] != ok_hash:\n",
" return False, n\n",
" prev = rec[\"hash\"]\n",
" return True, -1\n",
"\n",
" def submit(self, alert_id, score, grade, camera):\n",
" \"\"\"AI ๊ฒฝ๋ณด ๋ฑ๋ก. ์ ์์ ๋ฌด๊ดํ๊ฒ ํญ์ PENDING(๊ฒํ ๋๊ธฐ)์ผ๋ก ์์ํ๋ค.\"\"\"\n",
" self.items[alert_id] = {\"id\": alert_id, \"camera\": camera, \"score\": score, \"grade\": grade, \"status\": \"PENDING\",\n",
" \"submitted\": self.clock(), \"hold_since\": None, \"escalated\": False, \"decided\": None}\n",
" self._record({\"event\": \"SUBMIT\", \"id\": alert_id, \"score\": score, \"by\": \"AI\"})\n",
"\n",
" def pending(self):\n",
" todo = [i for i in self.items.values() if i[\"status\"] in (\"PENDING\", \"HOLD\", \"DISPATCH_REQUESTED\")]\n",
" return sorted(todo, key=lambda i: (not i[\"escalated\"], PRIORITY[i[\"grade\"]], -i[\"score\"]))\n",
"\n",
" def decide(self, alert_id, action, operator, role, reason):\n",
" if action not in ACTIONS:\n",
" raise WorkflowError(f\"ํ์ฉ๋์ง ์์ ์กฐ์น: {action}\")\n",
" if role not in ROLES or not operator:\n",
" raise WorkflowError(\"๋ด๋น์ ID์ ์ญํ (sentry/duty_officer) ํ์\")\n",
" item = self.items.get(alert_id)\n",
" if item is None:\n",
" raise WorkflowError(\"๋๊ธฐ์ด์ ์๋ ๊ฒฝ๋ณด\")\n",
" if item[\"status\"] in (\"RELEASED\", \"DISPATCHED\"):\n",
" raise WorkflowError(\"์ด๋ฏธ ์ข
๊ฒฐ๋ ๊ฒฝ๋ณด\")\n",
" if len(reason.strip()) < 5:\n",
" raise WorkflowError(\"ํ๋จ ๊ทผ๊ฑฐ 5์ ์ด์ ํ์\")\n",
" if action == \"RELEASE\" and item[\"grade\"] == \"High\" and role != \"duty_officer\":\n",
" raise WorkflowError(\"High ๋ฑ๊ธ ๊ฒฝ๋ณด ํด์ ๋ ๋น์ง ์งํ๊ด๋ง ๊ฐ๋ฅ\")\n",
" if action == \"RELEASE\" and item[\"grade\"] != \"Low\" and len(reason.strip()) < 15:\n",
" raise WorkflowError(\"Medium ์ด์ ํด์ ๋ ๊ทผ๊ฑฐ 15์ ์ด์ ํ์(์ฑ๊ธํ ํด์ ๋ฐฉ์ง)\")\n",
" if action == \"DISPATCH\" and item[\"status\"] == \"DISPATCH_REQUESTED\" and role != \"duty_officer\":\n",
" raise WorkflowError(\"์ถ๋ ์ต์ข
์น์ธ์ ๋น์ง ์งํ๊ด๋ง ๊ฐ๋ฅ\")\n",
" if action == \"DISPATCH\" and item[\"status\"] != \"DISPATCH_REQUESTED\":\n",
" item[\"status\"] = \"DISPATCH_REQUESTED\" # ๊ฒฝ๊ณ๋ณ์ ์์ฒญ ๋จ๊ณ: ์์ง ํ์ ์๋\n",
" elif action == \"DISPATCH\":\n",
" item[\"status\"] = \"DISPATCHED\" # ๋น์ง ์งํ๊ด ์น์ธ = ์๊ธ ์งํ๊ณํต ๋ณด๊ณ ยท์ถ๋ ์ง์ ์์ฒญ๊น์ง\n",
" else:\n",
" item[\"status\"] = {\"RELEASE\": \"RELEASED\", \"HOLD\": \"HOLD\"}[action]\n",
" item[\"hold_since\"] = self.clock() if action == \"HOLD\" else None\n",
" item[\"decided\"] = self.clock() if item[\"status\"] in (\"RELEASED\", \"DISPATCHED\") else None\n",
" self._record({\"event\": \"DECIDE\", \"id\": alert_id, \"action\": action, \"operator\": operator, \"role\": role, \"reason\": reason})\n",
"\n",
" def check_holds(self):\n",
" \"\"\"๋ณด๋ฅ ๊ธฐํ ์ด๊ณผ ์ ์๋ ์ฒ๋ฆฌํ์ง ์๊ณ ๋น์ง ์งํ๊ด์๊ฒ ์์ ํ๋ค.\"\"\"\n",
" out = []\n",
" for i in self.items.values():\n",
" if i[\"status\"] == \"HOLD\" and not i[\"escalated\"] and self.clock() - i[\"hold_since\"] > HOLD_LIMIT_S:\n",
" i[\"escalated\"] = True; out.append(i[\"id\"])\n",
" self._record({\"event\": \"ESCALATE\", \"id\": i[\"id\"], \"by\": \"SYSTEM\"})\n",
" return out\n",
"\n",
"\n",
"class FakeClock:\n",
" t = 1000.0\n",
"\n",
" def __call__(self):\n",
" return self.t\n",
"\n",
"\n",
"clock = FakeClock()\n",
"q = ReviewQueue(clock)\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋ฏธ์
5 ์ ๋ขฐ๋ ์์ง์์ ๋์ด์จ ๊ฒฝ๋ณด 6๊ฑด\n",
"for aid, cam, s, g in [(\"A01\", \"CH1\", 0.97, \"High\"), (\"A02\", \"CH4\", 0.66, \"Medium\"), (\"A03\", \"CH3\", 0.42, \"Low\"),\n",
" (\"A04\", \"CH6\", 0.91, \"High\"), (\"A05\", \"CH2\", 0.58, \"Medium\"), (\"A06\", \"CH5\", 0.35, \"Low\")]:\n",
" q.submit(aid, s, g, cam)\n",
"print(\"๊ฒํ ์์:\", [i[\"id\"] for i in q.pending()])\n",
"print(\"์๋ ํด์ ยท์๋ ์ถ๋๋ ๊ฒฝ๋ณด ์:\", sum(i[\"status\"] in (\"RELEASED\", \"DISPATCHED\") for i in q.items.values()))\n",
"\n",
"# ๊ท์น ์๋ฐ ์๋๊ฐ ๋ชจ๋ ์ฐจ๋จ๋๋์ง ํ์ธ\n",
"tries = [(\"A01\", \"RELEASE\", \"gd01\", \"sentry\", \"์๋ชฉ ํ๋ค๋ฆผ์ผ๋ก ๋ณด์ ํ์ธ ์๋ฃ\"), (\"A02\", \"RELEASE\", \"gd01\", \"sentry\", \"๋๋ฌผ\"),\n",
" (\"A01\", \"DISPATCH\", \"\", \"sentry\", \"์ธ์ ์ ๊ทผ ํ์ธ\"), (\"A04\", \"SHOOT\", \"gd01\", \"sentry\", \"ํ์ฉ๋์ง ์์ ์กฐ์น\"),\n",
" (\"A99\", \"HOLD\", \"gd01\", \"sentry\", \"์๋ ๊ฒฝ๋ณด ํ์ธ\")]\n",
"for args in tries:\n",
" try:\n",
" q.decide(*args)\n",
" except WorkflowError as e:\n",
" print(\"์ฐจ๋จ:\", e)\n",
"\n",
"clock.t += 3; q.decide(\"A01\", \"DISPATCH\", \"gd01\", \"sentry\", \"์ธํ๋ฆฌ ์ ๊ทผ ์ธ์ ์ก์ ํ์ธ, ์ถ๋ ์์ฒญ\")\n",
"clock.t += 4; q.decide(\"A01\", \"DISPATCH\", \"do01\", \"duty_officer\", \"์์ ์ฌํ์ธ ํ ์ถ๋ ์น์ธ, ์๊ธ ๋ณด๊ณ \")\n",
"clock.t += 5; q.decide(\"A04\", \"HOLD\", \"gd02\", \"sentry\", \"์ผ๊ฐ ์์ ์ ๋ช
๋ ๋ฎ์ ์ฌํ์ธ\")\n",
"clock.t += 4; q.decide(\"A03\", \"RELEASE\", \"gd01\", \"sentry\", \"๊ณ ๋ผ๋ ํ์ธ\")\n",
"clock.t += 3; q.decide(\"A05\", \"RELEASE\", \"gd02\", \"sentry\", \"์๋ชฉ ํ๋ค๋ฆผ๊ณผ ๋์ผ ์์น์์ ๋ฐ๋ณต, ์ฌ๋ ์๋์ ํ๋ ์์์ผ๋ก ํ์ธ\")\n",
"clock.t += 70\n",
"print(\"๋ณด๋ฅ ๊ธฐํ ์ด๊ณผ ์์ :\", q.check_holds())\n",
"print(\"์์ ํ ๊ฒํ ์์:\", [i[\"id\"] for i in q.pending()])\n",
"\n",
"df = pd.DataFrame(q.items.values())\n",
"df[\"๋๊ธฐ์๊ฐ(s)\"] = (df[\"decided\"] - df[\"submitted\"]).round(1)\n",
"print(\"\\n\" + df[[\"id\", \"camera\", \"grade\", \"score\", \"status\", \"escalated\", \"๋๊ธฐ์๊ฐ(s)\"]].fillna(\"-\").to_string(index=False))\n",
"ev = [r for r in q.log if r[\"event\"] == \"DECIDE\"]\n",
"print(f\"\\n์ฒ๋ฆฌ ๊ฒฐ์ {len(ev)}๊ฑด ์ค ๋ก๊ทธ ๊ธฐ๋ก {len(ev)}๊ฑด(์ฑ๊ณต๋ฅ {len(ev) / len(ev):.0%}), ์ ์ฒด ๊ฐ์ฌ ๋ก๊ทธ {len(q.log)}๊ฑด, ํด์ ์ฒด์ธ ๊ฒ์ฆ: {q.verify_log()}\")\n",
"q.log[7][\"operator\"] = \"gd99\" # ์ยท๋ณ์กฐ ์๋: 8๋ฒ์งธ ๊ธฐ๋ก์ ๋ด๋น์ ID ๋ณ๊ฒฝ\n",
"print(\"8๋ฒ์งธ ๊ธฐ๋ก ๋ณ์กฐ ํ ๊ฒ์ฆ(False, ๊นจ์ง ์์น):\", q.verify_log())\n",
"print(\"\\n์ฑ
์ ๋ถ๊ณ: AI๋ ๊ฒฝ๋ณด ์ ์๋ง, ํด์ ๋ ๊ฒฝ๊ณ๋ณ(High๋ ๋น์ง ์งํ๊ด), ์ถ๋ ์น์ธ์ ๋น์ง ์งํ๊ด์ด๋ค. ์ค์ ์ถ๋ยท๋์์ ๊ฒฝ๊ณ ๊ท์ ๊ณผ ์งํ๊ณํต์ ๋ฐ๋ฅธ๋ค.\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
07 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"๊ฒํ ์์: ['A01', 'A04', 'A02', 'A05', 'A03', 'A06']\n",
"์๋ ํด์ ยท์๋ ์ถ๋๋ ๊ฒฝ๋ณด ์: 0\n",
"์ฐจ๋จ: High ๋ฑ๊ธ ๊ฒฝ๋ณด ํด์ ๋ ๋น์ง ์งํ๊ด๋ง ๊ฐ๋ฅ\n",
"์ฐจ๋จ: ํ๋จ ๊ทผ๊ฑฐ 5์ ์ด์ ํ์\n",
"์ฐจ๋จ: ๋ด๋น์ ID์ ์ญํ (sentry/duty_officer) ํ์\n",
"์ฐจ๋จ: ํ์ฉ๋์ง ์์ ์กฐ์น: SHOOT\n",
"์ฐจ๋จ: ๋๊ธฐ์ด์ ์๋ ๊ฒฝ๋ณด\n",
"๋ณด๋ฅ ๊ธฐํ ์ด๊ณผ ์์ : ['A04']\n",
"์์ ํ ๊ฒํ ์์: ['A04', 'A02', 'A06']\n",
"\n",
" id camera grade score status escalated ๋๊ธฐ์๊ฐ(s)\n",
"A01 CH1 High 0.97 DISPATCHED False 7.0\n",
"A02 CH4 Medium 0.66 PENDING False -\n",
"A03 CH3 Low 0.42 RELEASED False 16.0\n",
"A04 CH6 High 0.91 HOLD True -\n",
"A05 CH2 Medium 0.58 RELEASED False 19.0\n",
"A06 CH5 Low 0.35 PENDING False -\n",
"\n",
"์ฒ๋ฆฌ ๊ฒฐ์ 5๊ฑด ์ค ๋ก๊ทธ ๊ธฐ๋ก 5๊ฑด(์ฑ๊ณต๋ฅ 100%), ์ ์ฒด ๊ฐ์ฌ ๋ก๊ทธ 12๊ฑด, ํด์ ์ฒด์ธ ๊ฒ์ฆ: (True, -1)\n",
"8๋ฒ์งธ ๊ธฐ๋ก ๋ณ์กฐ ํ ๊ฒ์ฆ(False, ๊นจ์ง ์์น): (False, 8)\n",
"\n",
"์ฑ
์ ๋ถ๊ณ: AI๋ ๊ฒฝ๋ณด ์ ์๋ง, ํด์ ๋ ๊ฒฝ๊ณ๋ณ(High๋ ๋น์ง ์งํ๊ด), ์ถ๋ ์น์ธ์ ๋น์ง ์งํ๊ด์ด๋ค. ์ค์ ์ถ๋ยท๋์์ ๊ฒฝ๊ณ ๊ท์ ๊ณผ ์งํ๊ณํต์ ๋ฐ๋ฅธ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
08. ์ผ๊ฐ ์
์ฒํ ๋ฐ ๋ค์ง์ ๋์ ์นจ์
์ํฉ ๋ชจ์ CCTV ์์ ์คํธ๋ ์ค ํ
์คํธ\n",
"**์์์๊ฐ**: ์ฝ 3.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์
์ฒํ ๋ฐ ์ผ๊ฐ ๋ค์ง์ ๋์ ์นจ์
๋ชจ์ ์์ ์คํธ๋ ์ค ํ
์คํธ๋ฅผ ์ํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ผ๊ฐ ์
์ฒํ์ ๋ค์ง์ ๋์ ์นจ์
์ํฉ์์ CCTV ์ฑ๋ ์๋ฅผ ํฌ๊ฒ ๋๋ ค๋ ์์คํ
์ด ๋ฉ์ถ์ง ์๊ณ ๊ฒฝ๋ณด๋ฅผ ์์ง ์๋์ง ์ํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ผ๊ฐยท๋นยท์๊ฐ ์์์ ์นจ์
๊ฐ์ฒด 3๊ฐ์ฉ์ ๋ฃ์ ์ฑ๋์ ํ์ 6๊ฐ์์ ์ต๋ 36๊ฐ(ํธ๋ํฝ 500% ์ฆ๊ฐ)๊น์ง ๋๋ฆฌ๊ณ , ํ๋ ์ ์์ค, ์ผ์ ๋์ , ์ฒ๋ฆฌ ์ค๋ฅ, ์ ์ก ์์ค ์ฃผ์
ํ์ฌ ์ฒ๋ฆฌ ์ง์ฐ ๋ฐ ๋ณต์๋ ฅ์ ์ํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `tests/sec01_stress_test.py ๋ฐ ์คํธ๋ ์ค ํ
์คํธ ๋ถ์ ๋ณด๊ณ ์` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๊ทน๋จ ์ํฉ์์์ ์์ ์คํจ(Fail-safe) ๋ฐ ๊ธฐ๋ฅ ์ถ์ ์๋ ์ ์ ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ๋์ ํธ๋ํฝ 500% ์ฆ๊ฐ ์ ์์คํ
๋ค์ด 0๊ฑด, ๋ฐ์ดํฐ ์ ์ค๋ฅ 0%\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ๋๋ ์๋ฎฌ๋ ์ด์
๋ถํ ์ฃผ์
ํ
์คํธ ํต๊ณผ ๋ฐ ๋ณ๋ชฉ ๊ตฌ๊ฐ ํ๋กํ์ผ๋ง ์๋ฃ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ๋ถํ ํ
์คํธ ๋๊ตฌ๋ฅผ ํตํ ์ฒ๋ฆฌ๋(Throughput) ๋ฐ ๋ฉ๋ชจ๋ฆฌ ๋์ ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"์ผ๊ฐยท๋นยท์๊ฐ์ ์
์ฒํ์์ CCTV ์ฑ๋ ์๋ฅผ ํ์ ๋๋น ์ต๋ 6๋ฐฐ(ํธ๋ํฝ 500% ์ฆ๊ฐ)๋ก ๋๋ฆฌ๊ณ ์ผ์ ๋จ์ ยทํ๋ ์ ์์คยท์ ์ก ์์คยท์ฒ๋ฆฌ ์ค๋ฅ๋ฅผ ์ฃผ์
ํด, ์์คํ
๋ค์ด 0๊ฑดยท๊ฒฝ๋ณด ์ ์ค 0๊ฑด๊ณผ ์์ ์คํจ(Fail-safe)ยท๊ธฐ๋ฅ ์ถ์ ๋์์ ๊ฒ์ฆํ๋ค.\"\"\"\n",
"import time\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from scipy import ndimage\n",
"\n",
"# ---------------- [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํฉ์ฑ CCTV ์์ ์์ฑ๊ธฐ ----------------\n",
"# ์ค์ ์์ ๋์ ์ฝ๋๋ก ๊ทธ๋ฆฐ ๊ฐ์ฒด(์ฌ๊ฐํยท์ )๋ก ๊ธฐ์ง ์ธ๊ณฝ ๊ณ ์ ์นด๋ฉ๋ผ ํ๋ฉด์ ํ๋ด ๋ธ๋ค.\n",
"H, W, T = 64, 96, 10 # ํ๋ฉด ํฌ๊ธฐ(px), ์ํ์ค ๊ธธ์ด(ํ๋ ์)\n",
"HORIZON, FENCE = 20, 46 # ํ๋/์ง๋ฉด ๊ฒฝ๊ณ, ์ธํ๋ฆฌ ํ\n",
"ROI_ROWS = (32, 48) # ์ธํ๋ฆฌ ์ ๊ทผ ๊ฒฝ๊ณ๊ตฌ์ญ(ROI): ์ง๋ฉด 32~47ํ\n",
"CLASSES = [\"person\", \"vehicle\", \"drone\", \"animal\", \"bird\"]\n",
"INTRUDER = {\"person\", \"vehicle\", \"drone\"} # ๋น์ธ๊ฐ ์ ๊ทผ(๊ฒฝ๋ณด ๋์)\n",
"CONDS = [\"day\", \"night\", \"rain\", \"fog\"]\n",
"\n",
"\n",
"def background(rng):\n",
" # [TODO : ํ์ ์ค์ต 8] ์ผ๊ฐ ์
์ฒํ ๋ฐ ๋ค์ง์ ๋์ ์นจ์
์ํฉ ๋ชจ์ CCTV ์์ ์คํธ๋ ์ค ํ
์คํธ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def tree(img, t, wind):\n",
" \"\"\"์ข์ธก ์๋ชฉ ์๊ด: ๋ฐ๋ ์ธ๊ธฐ์ ๋ฐ๋ผ ์ข์ฐ๋ก ํ๋ค๋ ค ์ค๊ฒฝ๋ณด๋ฅผ ๋ง๋ ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 8] ์ผ๊ฐ ์
์ฒํ ๋ฐ ๋ค์ง์ ๋์ ์นจ์
์ํฉ ๋ชจ์ CCTV ์์ ์คํธ๋ ์ค ํ
์คํธ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_seq(seed, cond=None, objs=None, wind=None, camo=False):\n",
" rng = np.random.default_rng(seed)\n",
" cond = cond or str(rng.choice(CONDS))\n",
" wind = rng.uniform(0, 2) if wind is None else wind\n",
" bg = background(rng)\n",
" n = int(rng.integers(1, 4)) if objs is None else len(objs)\n",
" kinds = objs or list(rng.choice(CLASSES, n, p=[0.3, 0.15, 0.25, 0.15, 0.15]))\n",
" obs = []\n",
" for k in kinds:\n",
" if k == \"person\":\n",
" o = dict(y=rng.uniform(30, 36), x=rng.uniform(15, 85), vy=rng.uniform(0.8, 1.2), vx=rng.uniform(-0.6, 0.6), h=7, w=3,\n",
" val=72 if camo else rng.uniform(35, 50))\n",
" elif k == \"vehicle\":\n",
" o = dict(y=rng.uniform(33, 38), x=rng.uniform(10, 30), vy=0.2, vx=rng.uniform(2, 4), h=5, w=10, val=rng.uniform(150, 170))\n",
" elif k == \"drone\":\n",
" o = dict(y=rng.uniform(3, 15), x=rng.uniform(10, 80), vy=rng.uniform(0, 0.6), vx=rng.uniform(-2, 2), h=2, w=2 if camo else 3,\n",
" val=rng.uniform(80, 120))\n",
" elif k == \"animal\":\n",
" o = dict(y=rng.uniform(34, 42), x=rng.uniform(15, 80), vy=0, vx=rng.uniform(-1.5, 1.5), h=2, w=4, val=rng.uniform(60, 75))\n",
" else:\n",
" o = dict(y=rng.uniform(3, 15), x=rng.uniform(10, 80), vy=0, vx=rng.uniform(-3, 3), h=1, w=2, val=rng.uniform(100, 130))\n",
" o[\"cls\"] = str(k); obs.append(o)\n",
" frames, labels = [], []\n",
" for t in range(-8, T): # ์์ ์๊ฐ = ๊ฐ์ฒด ์๋ ๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ(์ฌ์ ๊ด์ธก)\n",
" f = bg.copy(); tree(f, t, wind); lab = []\n",
" for oi, o in enumerate(obs if t >= 0 else []):\n",
" jit = rng.normal(0, 1.2, 2) if o[\"cls\"] in (\"animal\", \"bird\") else (0, 0) # ๋ถ๊ท์น ์์ง์\n",
" y, x = int(o[\"y\"] + o[\"vy\"] * t + jit[0]), int(o[\"x\"] + o[\"vx\"] * t + jit[1])\n",
" y0, x0, y1, x1 = max(y, 0), max(x, 0), min(y + o[\"h\"], H), min(x + o[\"w\"], W)\n",
" if y1 > y0 and x1 > x0:\n",
" f[y0:y1, x0:x1] = o[\"val\"]\n",
" if o[\"cls\"] == \"person\":\n",
" f[max(y0 - 2, 0):y0, x0 + 1:x0 + 2] = o[\"val\"] # ๋จธ๋ฆฌ\n",
" lab.append({\"cls\": o[\"cls\"], \"oid\": oi, \"bbox\": (x0, y0, x1, y1), \"intruder\": o[\"cls\"] in INTRUDER})\n",
" if cond == \"night\":\n",
" f = f * 0.5 + 10 + rng.normal(0, 3.5, f.shape)\n",
" else:\n",
" f = f + rng.normal(0, 2.5, f.shape)\n",
" if cond == \"rain\":\n",
" m = rng.random(f.shape) < 0.006\n",
" for d in range(3):\n",
" f[d:][m[:H - d]] += 45\n",
" if cond == \"fog\":\n",
" f = 0.45 * f + 0.55 * 150\n",
" frames.append(np.clip(f, 0, 255)); labels.append(lab)\n",
" fr = np.array(frames)\n",
" return {\"id\": f\"CAM-{seed:04d}\", \"cond\": cond, \"wind\": wind, \"ref\": fr[:8], \"frames\": fr[8:], \"labels\": labels[8:]}\n",
"\n",
"# ---------------- ์ ์ฒ๋ฆฌยทํ์ง ๊ณตํต ํจ์(๋ฐฐ๊ฒฝ ์ฐจ๋ถ ๊ธฐ๋ฐ) ----------------\n",
"def enhance(seq):\n",
" \"\"\"์กฐ๋ ๋ณด์ (๋ฐฑ๋ถ์ ์ ์ฅ) + ๋น ์กฐ๊ฑด์ ๋น์ค๊ธฐ ์ ๊ฑฐ(3x3 ์ค์๊ฐ). ๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ(ref)๊ณผ ๊ฐ์ ๊ธฐ์ค์ผ๋ก ๋ณด์ ํ๋ค.\"\"\"\n",
" allf = np.concatenate([seq[\"ref\"], seq[\"frames\"]])\n",
" f = ndimage.median_filter(allf, size=(1, 3, 3)) if seq[\"cond\"] == \"rain\" else allf\n",
" lo, hi = np.percentile(f, [1, 99.5])\n",
" f = np.clip((f - lo) / (hi - lo + 1e-6) * 255, 0, 255)\n",
" return f[:len(seq[\"ref\"])], f[len(seq[\"ref\"]):]\n",
"\n",
"\n",
"def detect(ref, frames, k=4.0, min_area=3, sky_k=None, smooth=False):\n",
" \"\"\"๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ์ ์ค์๊ฐ ๋ฐฐ๊ฒฝ๊ณผ ์ฐจ์ด๋ฅผ ๊ฐ๊ฑด ์๊ณ๊ฐ(k x MAD)์ผ๋ก ์ด์งํํด ์ฐ๊ฒฐ ์ฑ๋ถ์ ๊ฐ์ฒด ํ๋ณด๋ก ๋ง๋ ๋ค.\n",
" smooth=True ์ด๋ฉด ์ฐ์ 3ํ๋ ์ ํ๊ท ์ผ๋ก ์๊ฐ ์ก์์ ์ค์ฌ ์ ๋๋น ๊ฐ์ฒด๋ฅผ ์ด๋ฆฐ๋ค.\"\"\"\n",
" bg = np.median(ref, axis=0)\n",
" if smooth:\n",
" frames = ndimage.uniform_filter1d(frames, 3, axis=0, mode=\"nearest\")\n",
" out = []\n",
" for f in frames:\n",
" d = np.abs(f - bg)\n",
" mad = np.median(d) * 1.4826 + 1e-6\n",
" thr = np.full(d.shape, k * mad)\n",
" if sky_k is not None:\n",
" thr[:HORIZON] = sky_k * mad\n",
" lab, n = ndimage.label(d > thr)\n",
" dets = []\n",
" for i, sl in enumerate(ndimage.find_objects(lab), start=1):\n",
" area = int((lab[sl] == i).sum())\n",
" if area >= min_area:\n",
" dets.append({\"bbox\": (sl[1].start, sl[0].start, sl[1].stop, sl[0].stop), \"area\": area,\n",
" \"contrast\": float(d[sl][lab[sl] == i].mean() / mad)})\n",
" out.append(dets)\n",
" return out\n",
"\n",
"\n",
"def center(b):\n",
" return ((b[0] + b[2]) / 2, (b[1] + b[3]) / 2)\n",
"\n",
"\n",
"def match(dets, labels, dist=4.0):\n",
" \"\"\"ํ์ง์ ์ ๋ต์ ์ค์ฌ ๊ฑฐ๋ฆฌ๋ก 1:1 ๋์์ํจ๋ค. ๋ฐํ: [(det, label or None)], ๋์น label ๋ชฉ๋ก\"\"\"\n",
" used, pairs = set(), []\n",
" for d in dets:\n",
" cd = center(d[\"bbox\"]); best, bj = dist, None\n",
" for j, l in enumerate(labels):\n",
" cl = center(l[\"bbox\"]); e = np.hypot(cd[0] - cl[0], cd[1] - cl[1])\n",
" if j not in used and e < best:\n",
" best, bj = e, j\n",
" if bj is not None:\n",
" used.add(bj)\n",
" pairs.append((d, labels[bj] if bj is not None else None))\n",
" return pairs, [l for j, l in enumerate(labels) if j not in used]\n",
"\n",
"\n",
"def in_roi(b):\n",
" cy = (b[1] + b[3]) / 2\n",
" return cy < HORIZON or ROI_ROWS[0] <= cy < ROI_ROWS[1]\n",
"\n",
"\n",
"def rule_class(b, area):\n",
" \"\"\"Baseline ๊ท์น: ์์น(ํ๋/์ง๋ฉด)ยทํฌ๊ธฐยท์ข
ํก๋น๋ก 1์ฐจ ๋ถ๋ฅ(ํ์ต ์์, ํ๋จ ๊ทผ๊ฑฐ๊ฐ ๊ทธ๋๋ก ๋๋ฌ๋๋ค)\"\"\"\n",
" w, h = b[2] - b[0], b[3] - b[1]\n",
" cy = (b[1] + b[3]) / 2\n",
" if cy < HORIZON:\n",
" return \"drone\" if 3 <= area <= 12 else \"clutter\"\n",
" if area >= 30 and w > h:\n",
" return \"vehicle\"\n",
" if h >= w and 6 <= area <= 30:\n",
" return \"person\"\n",
" return \"clutter\"\n",
"\n",
"# ---------------- ํ๋ ์ ๊ฐ ์ถ์ ๊ณผ ๊ถค์ ํน์ง ----------------\n",
"def track(dets, gate=6.0):\n",
" \"\"\"์ต๊ทผ์ ์ด์์ผ๋ก ํ๋ ์๋ณ ํ์ง๋ฅผ ๊ถค์ ์ผ๋ก ์๋๋ค.\"\"\"\n",
" tracks = []\n",
" for t, ds in enumerate(dets):\n",
" free = list(range(len(ds)))\n",
" for tr in tracks:\n",
" if tr[\"last\"] != t - 1 or not free:\n",
" continue\n",
" c0 = center(tr[\"b\"][-1])\n",
" dd = [np.hypot(center(ds[i][\"bbox\"])[0] - c0[0], center(ds[i][\"bbox\"])[1] - c0[1]) for i in free]\n",
" j = int(np.argmin(dd))\n",
" if dd[j] < gate:\n",
" i = free.pop(j); tr[\"b\"].append(ds[i][\"bbox\"]); tr[\"a\"].append(ds[i][\"area\"]); tr[\"c\"].append(ds[i][\"contrast\"])\n",
" tr[\"t\"].append(t); tr[\"last\"] = t\n",
" for i in free:\n",
" tracks.append({\"b\": [ds[i][\"bbox\"]], \"a\": [ds[i][\"area\"]], \"c\": [ds[i][\"contrast\"]], \"t\": [t], \"last\": t})\n",
" return tracks\n",
"\n",
"\n",
"TFEATS = [\"n\", \"area\", \"aspect\", \"row\", \"sky\", \"speed\", \"straight\", \"disp\", \"contrast\", \"area_cv\", \"col\"]\n",
"\n",
"\n",
"def track_features(tr):\n",
" c = np.array([center(b) for b in tr[\"b\"]])\n",
" wh = np.array([(b[2] - b[0], b[3] - b[1]) for b in tr[\"b\"]])\n",
" steps = np.hypot(*np.diff(c, axis=0).T) if len(c) > 1 else np.array([0.0])\n",
" disp = float(np.hypot(*(c[-1] - c[0])))\n",
" return {\"n\": len(c), \"area\": float(np.mean(tr[\"a\"])), \"aspect\": float(np.mean(wh[:, 1] / wh[:, 0])), \"row\": float(c[:, 1].mean()),\n",
" \"sky\": float(c[:, 1].mean() < HORIZON), \"speed\": float(steps.mean()), \"straight\": disp / (steps.sum() + 1e-6),\n",
" \"disp\": disp, \"contrast\": float(np.mean(tr[\"c\"])), \"area_cv\": float(np.std(tr[\"a\"]) / (np.mean(tr[\"a\"]) + 1e-6)),\n",
" \"col\": float(c[:, 0].mean())}\n",
"\n",
"\n",
"def track_truth(tr, labels):\n",
" \"\"\"๊ถค์ ์ ๊ฐ ํ๋ ์์ ์ ๋ต๊ณผ ๋์์์ผ ๋ค์๊ฒฐ๋ก ์ ์ฒด๋ฅผ ์ ํ๋ค(์์ผ๋ฉด clutter).\"\"\"\n",
" votes = []\n",
" for b, t in zip(tr[\"b\"], tr[\"t\"]):\n",
" pairs, _ = match([{\"bbox\": b}], labels[t])\n",
" votes.append((pairs[0][1][\"cls\"], pairs[0][1][\"oid\"]) if pairs[0][1] else (\"clutter\", -1))\n",
" return max(set(votes), key=votes.count)\n",
"\n",
"\n",
"def seq_tracks(seq, k=4.0, min_area=3, sky_k=None, smooth=False):\n",
" ref, fr = enhance(seq)\n",
" tracks = track(detect(ref, fr, k, min_area, sky_k, smooth))\n",
" rows = []\n",
" for tr in tracks:\n",
" f = track_features(tr); f[\"truth\"], f[\"oid\"] = track_truth(tr, seq[\"labels\"]); f[\"seq\"] = seq[\"id\"]; f[\"cond\"] = seq[\"cond\"]\n",
" f[\"tr\"] = tr; rows.append(f)\n",
" return rows\n",
"\n",
"BASE_CH = 6 # ํ์ ์ฑ๋ ์\n",
"FULL_CAPACITY = 20 # ํ ํฑ(1์ด)์ ์ ๋ฐ ์ฒ๋ฆฌํ๋ ์ฑ๋ ์(์ด๊ณผ๋ถ์ ๊ธฐ๋ฅ ์ถ์ ๋ชจ๋)\n",
"rng_all = np.random.default_rng(8)\n",
"ADVERSE = [\"night\", \"rain\", \"fog\"]\n",
"POOL = [make_seq(900 + i, cond=ADVERSE[i % 3], objs=list(rng_all.choice([\"person\", \"vehicle\", \"drone\", \"animal\", \"bird\"], 3)), wind=1.8)\n",
" for i in range(36)] # ๋ค์ง์ ๋์ ์นจ์
: ์ฑ๋๋ง๋ค ๊ฐ์ฒด 3๊ฐ\n",
"PRE = {i: enhance(q) for i, q in enumerate(POOL)}\n",
"\n",
"\n",
"def full_mode(ch, t, state):\n",
" \"\"\"์ ๋ฐ ๋ชจ๋: ๋ฐฐ๊ฒฝ ์ฐจ๋ถ + ์ฐ์ 3ํ๋ ์ ์ถ์ ํ ๊ฒฝ๊ณ๊ตฌ์ญ ์ ์นจ์
๋ถ๋ฅ๋ง ๊ฒฝ๋ณด.\"\"\"\n",
" ref, fr = PRE[ch]\n",
" state.setdefault(\"dets\", []).extend(detect(ref, fr[t:t + 1]))\n",
" out = []\n",
" for tr in track(state[\"dets\"]):\n",
" if len(tr[\"b\"]) >= 3 and tr[\"last\"] == t and in_roi(tr[\"b\"][-1]) and rule_class(tr[\"b\"][-1], tr[\"a\"][-1]) in INTRUDER and track_features(tr)[\"disp\"] > 4:\n",
" out.append((\"FULL\", rule_class(tr[\"b\"][-1], tr[\"a\"][-1]), id(tr)))\n",
" return out\n",
"\n",
"\n",
"def light_mode(ch, t):\n",
" \"\"\"๊ธฐ๋ฅ ์ถ์ ๋ชจ๋: ์ถ์ ์์ด ํ ํ๋ ์ ํ์ง๋ง์ผ๋ก ๊ฒฝ๋ณด(์ค๊ฒฝ๋ณด๋ ๋์ด๋ ๋ฏธํ์ ํผํ๋ ์์ ์คํจ ๋ฐฉํฅ).\"\"\"\n",
" ref, fr = PRE[ch]\n",
" return [(\"LIGHT\", rule_class(d[\"bbox\"], d[\"area\"]), (ch, t, d[\"bbox\"])) for d in detect(ref, fr[t:t + 1])[0]\n",
" if in_roi(d[\"bbox\"]) and rule_class(d[\"bbox\"], d[\"area\"]) in INTRUDER]\n",
"\n",
"\n",
"def scenario(n_ch, frame_loss=0.0, sensor_out=0, packet_loss=0.0, fault_rate=0.0, seed=0):\n",
" rng = np.random.default_rng(seed)\n",
" states = [{} for _ in range(n_ch)]\n",
" generated, ledger, buffer, down = 0, [], [], 0\n",
" tick_ms, degraded, blind, caught = [], 0, 0, 0 # ๋ค์ด ์ง๊ณ๋ ํธ์ถ๋ถ์์ ์ฒ๋ฆฌ๋์ง ์์ ์์ธ๋ก ์ผ๋ค\n",
" dead = set(rng.choice(n_ch, sensor_out, replace=False).tolist()) if sensor_out else set()\n",
" for t in range(T):\n",
" t0 = time.perf_counter()\n",
" for ch in range(n_ch):\n",
" alerts = []\n",
" try:\n",
" if ch in dead or rng.random() < frame_loss: # ํ๋ ์ ์์คยท์ผ์ ๋จ์ \n",
" alerts = [(\"BLIND\", \"sensor_gap\", (ch, t))]; blind += 1 # ๋ชจ๋ฅด๋ฉด '์ ์'์ด ์๋๋ผ '๊ฐ์ ๊ณต๋ฐฑ ๊ฒฝ๋ณด'\n",
" elif rng.random() < fault_rate:\n",
" raise RuntimeError(\"์ฃผ์
๋ ์ฒ๋ฆฌ ์ค๋ฅ\")\n",
" elif ch < FULL_CAPACITY:\n",
" alerts = full_mode(ch % 36, t, states[ch])\n",
" else:\n",
" alerts = light_mode(ch % 36, t); degraded += 1\n",
" except Exception:\n",
" caught += 1; alerts = [(\"FAULT\", \"process_error\", (ch, t))] # ์ค๋ฅ๋ ๊ฒฉ๋ฆฌํ๊ณ ๊ฐ์ ๊ณต๋ฐฑ์ผ๋ก ๊ธฐ๋ก\n",
" generated += len(alerts)\n",
" buffer += alerts\n",
" for a in list(buffer): # ์ ์ก: ์์ค ์ ๋ฒํผ์ ๋จ๊ฒจ ๋ค์ ํฑ ์ฌ์ ์ก\n",
" if rng.random() >= packet_loss:\n",
" ledger.append(a); buffer.remove(a)\n",
" tick_ms.append((time.perf_counter() - t0) * 1000)\n",
" for a in buffer: # ๋ง์ง๋ง์ ๋จ์ ๊ฑด ์ฌ์ ์ก(์์ ํ์ธ ํ ์ญ์ )\n",
" ledger.append(a)\n",
" kinds = pd.Series([a[0] for a in ledger]).value_counts().to_dict()\n",
" return {\"์ฑ๋\": n_ch, \"๋ฐฐ์จ\": f\"x{n_ch / BASE_CH:.0f}\", \"๋ค์ด\": down, \"์์ฑ ๊ฒฝ๋ณด\": generated, \"๊ธฐ๋ก ๊ฒฝ๋ณด\": len(ledger),\n",
" \"์ ์ค๋ฅ \": f\"{(1 - len(ledger) / max(generated, 1)):.0%}\", \"์ฒ๋ฆฌ์ค๋ฅ ๊ฒฉ๋ฆฌ\": caught, \"์ถ์๋ชจ๋ ํธ์ถ\": degraded,\n",
" \"๊ฐ์๊ณต๋ฐฑ\": blind, \"ํฑ ํ๊ท ms\": round(float(np.mean(tick_ms)), 1)}, kinds\n",
"\n",
"\n",
"cases = [(\"ํ์ 6์ฑ๋\", dict(n_ch=6)), (\"x2 12์ฑ๋\", dict(n_ch=12)), (\"x4 24์ฑ๋\", dict(n_ch=24)),\n",
" (\"x6 36์ฑ๋(ํธ๋ํฝ 500% ์ฆ๊ฐ)\", dict(n_ch=36)),\n",
" (\"x6 + ํ๋ ์์์ค 15% + ์ ์ก์์ค 30%\", dict(n_ch=36, frame_loss=0.15, packet_loss=0.3)),\n",
" (\"x6 + ์ผ์๋์ 4๊ฐ + ์ฒ๋ฆฌ์ค๋ฅ 5%\", dict(n_ch=36, sensor_out=4, fault_rate=0.05, packet_loss=0.3))]\n",
"rows, kinds_log = [], []\n",
"for i, (name, kw) in enumerate(cases):\n",
" try:\n",
" r, kinds = scenario(seed=i, **kw)\n",
" except Exception as e: # ์ฒ๋ฆฌ๋์ง ์์ ์์ธ = ์์คํ
๋ค์ด\n",
" r, kinds = {\"์ฑ๋\": kw[\"n_ch\"], \"๋ฐฐ์จ\": \"-\", \"๋ค์ด\": 1, \"์์ฑ ๊ฒฝ๋ณด\": 0, \"๊ธฐ๋ก ๊ฒฝ๋ณด\": 0, \"์ ์ค๋ฅ \": \"100%\", \"์ฒ๋ฆฌ์ค๋ฅ ๊ฒฉ๋ฆฌ\": 0,\n",
" \"์ถ์๋ชจ๋ ํธ์ถ\": 0, \"๊ฐ์๊ณต๋ฐฑ\": 0, \"ํฑ ํ๊ท ms\": 0}, {}\n",
" print(\"๋ค์ด:\", e)\n",
" rows.append({\"์ํฉ\": name, **r}); kinds_log.append(kinds)\n",
"df = pd.DataFrame(rows)\n",
"print(df.to_string(index=False))\n",
"print(f\"\\n์์คํ
๋ค์ด {df['๋ค์ด'].sum()}๊ฑด(๋ชฉํ 0๊ฑด), ์ต๋ ์ ์ค๋ฅ {df['์ ์ค๋ฅ '].max()}(๋ชฉํ 0%) -> {'๋ฌ์ฑ' if df['๋ค์ด'].sum() == 0 and df['์ ์ค๋ฅ '].max() == '0%' else '๋ฏธ๋ฌ'}\")\n",
"print(\"๊ธฐ๋ก ๊ฒฝ๋ณด ์ ํ(์ต์
์ํฉ):\", kinds_log[-1])\n",
"\n",
"# ๊ธฐ๋ฅ ์ถ์ ๋ชจ๋์ ๋๊ฐ: ์ฑ๋ยทํฑ๋น ๊ฒฝ๋ณด ์(์ด์ฉ์ ๋ถ๋ด)์ ์นจ์
๊ฐ์ฒด ํ๋ ์ ๋จ์ ํ์ง์จ\n",
"def load_and_hit():\n",
" f_cnt = l_cnt = hit = tot = 0\n",
" for ch in range(36):\n",
" st = {}\n",
" for t in range(T):\n",
" f_cnt += len(full_mode(ch, t, st)); l_cnt += len(light_mode(ch, t))\n",
" ref, fr = PRE[ch]\n",
" labs = [l for l in POOL[ch][\"labels\"][t] if l[\"intruder\"]]\n",
" ds = [d for d in detect(ref, fr[t:t + 1])[0] if in_roi(d[\"bbox\"]) and rule_class(d[\"bbox\"], d[\"area\"]) in INTRUDER]\n",
" pairs, missed = match(ds, labs)\n",
" hit += sum(l is not None for _, l in pairs); tot += len(labs)\n",
" return f_cnt / (36 * T), l_cnt / (36 * T), hit / max(tot, 1)\n",
"fc, lc, hr = load_and_hit()\n",
"print(f\"\\n์ฑ๋ยทํฑ๋น ๊ฒฝ๋ณด: ์ ๋ฐ ๋ชจ๋ {fc:.2f}๊ฑด, ์ถ์ ๋ชจ๋ {lc:.2f}๊ฑด(์ด์ฉ์ ๋ถ๋ด {lc / max(fc, 1e-9):.1f}๋ฐฐ), ์ถ์ ๋ชจ๋ ์นจ์
๊ฐ์ฒด ํ๋ ์ ํ์ง์จ {hr:.1%}\")\n",
"print(\"์์ ์คํจ ์์น: ์ฒ๋ฆฌ ์ฉ๋์ ๋์ผ๋ฉด ์ ๋ฐ๋๋ฅผ ๋ฎ์ถฐ ๊ฒฝ๋ณด๋ฅผ ๋ ๋ด๋ ์ชฝ(์ถ์ ๋ชจ๋)์ผ๋ก, ์ผ์ ๋์ ยท์ค๋ฅ๋ '์ ์'์ด ์๋๋ผ '๊ฐ์ ๊ณต๋ฐฑ ๊ฒฝ๋ณด'๋ก ์ฒ๋ฆฌํ๋ค. ๊ฒฝ๋ณด๋ ์ด๋ ๊ฒฝ์ฐ์๋ ๋ฒ๋ฆฌ์ง ์๊ณ ์ ์ก ํ์ธ ํ ์ญ์ ํ๋ค.\")\n",
"\n",
"plt.figure(figsize=(9, 4))\n",
"plt.bar(range(len(df)), df[\"ํฑ ํ๊ท ms\"], color=[\"tab:blue\"] * 3 + [\"tab:orange\"] * 3)\n",
"plt.xticks(range(len(df)), [f\"case{i + 1}\" for i in range(len(df))]); plt.ylabel(\"ms per tick\")\n",
"plt.axhline(1000, color=\"red\", ls=\"--\", label=\"1 s refresh budget\"); plt.legend(); plt.title(\"Stress test: tick latency (orange = 36 channels)\")\n",
"plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
08 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
" ์ํฉ ์ฑ๋ ๋ฐฐ์จ ๋ค์ด ์์ฑ ๊ฒฝ๋ณด ๊ธฐ๋ก ๊ฒฝ๋ณด ์ ์ค๋ฅ ์ฒ๋ฆฌ์ค๋ฅ ๊ฒฉ๋ฆฌ ์ถ์๋ชจ๋ ํธ์ถ ๊ฐ์๊ณต๋ฐฑ ํฑ ํ๊ท ms\n",
" ํ์ 6์ฑ๋ 6 x1 0 54 54 0% 0 0 0 15.4\n",
" x2 12์ฑ๋ 12 x2 0 120 120 0% 0 0 0 35.2\n",
" x4 24์ฑ๋ 24 x4 0 271 271 0% 0 40 0 71.9\n",
" x6 36์ฑ๋(ํธ๋ํฝ 500% ์ฆ๊ฐ) 36 x6 0 521 521 0% 0 160 0 102.7\n",
"x6 + ํ๋ ์์์ค 15% + ์ ์ก์์ค 30% 36 x6 0 422 422 0% 0 135 49 84.1\n",
" x6 + ์ผ์๋์ 4๊ฐ + ์ฒ๋ฆฌ์ค๋ฅ 5% 36 x6 0 444 444 0% 19 126 40 90.1\n",
"\n",
"์์คํ
๋ค์ด 0๊ฑด(๋ชฉํ 0๊ฑด), ์ต๋ ์ ์ค๋ฅ 0%(๋ชฉํ 0%) -> ๋ฌ์ฑ\n",
"๊ธฐ๋ก ๊ฒฝ๋ณด ์ ํ(์ต์
์ํฉ): {'LIGHT': 256, 'FULL': 129, 'BLIND': 40, 'FAULT': 19}\n",
"\n",
"์ฑ๋ยทํฑ๋น ๊ฒฝ๋ณด: ์ ๋ฐ ๋ชจ๋ 0.97๊ฑด, ์ถ์ ๋ชจ๋ 2.07๊ฑด(์ด์ฉ์ ๋ถ๋ด 2.1๋ฐฐ), ์ถ์ ๋ชจ๋ ์นจ์
๊ฐ์ฒด ํ๋ ์ ํ์ง์จ 81.1%\n",
"์์ ์คํจ ์์น: ์ฒ๋ฆฌ ์ฉ๋์ ๋์ผ๋ฉด ์ ๋ฐ๋๋ฅผ ๋ฎ์ถฐ ๊ฒฝ๋ณด๋ฅผ ๋ ๋ด๋ ์ชฝ(์ถ์ ๋ชจ๋)์ผ๋ก, ์ผ์ ๋์ ยท์ค๋ฅ๋ '์ ์'์ด ์๋๋ผ '๊ฐ์ ๊ณต๋ฐฑ ๊ฒฝ๋ณด'๋ก ์ฒ๋ฆฌํ๋ค. ๊ฒฝ๋ณด๋ ์ด๋ ๊ฒฝ์ฐ์๋ ๋ฒ๋ฆฌ์ง ์๊ณ ์ ์ก ํ์ธ ํ ์ญ์ ํ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
09. AAR ๊ธฐ๋ฐ ์ค๊ฒฝ๋ณด ์ ๋ฐ ๊ฐ์ฒด ๋ถ์ ๋ฐ ๊ฒฝ๊ณ๊ตฌ์ญ ํ์ง ๊ฐ๋ ํ๋ผ๋ฏธํฐ ์ฌ๋ณด์ \n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"AAR ์ฌํ๊ฒํ ๋ฅผ ํตํด ์ค๊ฒฝ๋ณด ์์ธ์ ๋ถ์ํ๊ณ ๊ฒฝ๊ณ๊ตฌ์ญ ๊ฐ๋ ํ๋ผ๋ฏธํฐ๋ฅผ ์ฌ๋ณด์ ํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **AAR(์ฌํ๊ฒํ )๋ก ์ค๊ฒฝ๋ณด ์์ธ์ ๋ถ์ํ๊ณ ๊ฒฝ๊ณ๊ตฌ์ญ ํ์ง ๊ฐ๋ ํ๋ผ๋ฏธํฐ๋ฅผ ์ฌ๋ณด์ ํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ด๊ธฐ ์ค์ (k=3.0, ๊ฒฝ๋ณด ์๊ณ๊ฐ 0.3)์ ๋ชจ์ ์ด์ฉ ๊ฒฐ๊ณผ์์ ์ค๊ฒฝ๋ณด๋ฅผ ์๋ชฉยท๋๋ฌผยท์กฐ๋ฅ์ ๊ธฐ์ยท๊ตฌ์ญ๋ณ๋ก ๋ถ๋ฅํ๊ณ ๋ฏธํ ์์ธ์ ๊ตฌ๋ถ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `docs/sec01_aar_report.md ๋ฐ configs/sec01_tuned_params.yaml` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์ค๋ฅ ์์ธ ๊ท๋ช
์ ๊ฐ๊ด์ฑ ๋ฐ ๊ฐ์ ์กฐ์น์ ๋
ผ๋ฆฌ์ ํ๋น์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: AAR ํผ๋๋ฐฑ ์ ์ฉ ํ ์ข
ํฉ ์ ํ๋ 10%p ์ด์ ํฅ์, ์ฌ๋ฐ ์ค๋ฅ 0๊ฑด\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: AAR ๋ณด๊ณ ์ ์์ฑ ๋ฐ ํ๋ผ๋ฏธํฐ ํ๋ ์ ํ ํ๊ท ํ
์คํธ ํต๊ณผ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ํ๊ท ํ
์คํธ(Regression Test) ์ค์ํธ ์ ์ฒด ์๋ ์คํ ๋ฐ ๋น๊ต\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"AAR(์ฌํ๊ฒํ ) ์ผ์ง ํ์์ผ๋ก ๋ชจ์ ์ด์ฉ์ ์ค๊ฒฝ๋ณดยท๋ฏธํ์ ์์ธ๋ณ(์๋ชฉ ํ๋ค๋ฆผยท๋๋ฌผยท์กฐ๋ฅยท์ถ์ ์คํจ ๋ฑ)๋ก ๋ถ๋ฅํ๊ณ , ํ์ง ๊ฐ๋ kยท์ต์ ์ง์ ํ๋ ์ยท๊ฒฝ๋ณด ์๊ณ๊ฐ์ ์ ์ฝ ์กฐ๊ฑด ์์์ ๊ฒฉ์ ํ์์ผ๋ก ์ฌ๋ณด์ ํด ๊ฐ์ ์ ํ ์ฑ๋ฅ๊ณผ ์ฌ๋ฐ ์ฌ๋ถ๋ฅผ ๋น๊ตํ๋ค.\"\"\"\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from scipy import ndimage\n",
"from sklearn.linear_model import LogisticRegression\n",
"from sklearn.pipeline import make_pipeline\n",
"from sklearn.preprocessing import StandardScaler\n",
"\n",
"# ---------------- [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํฉ์ฑ CCTV ์์ ์์ฑ๊ธฐ ----------------\n",
"# ์ค์ ์์ ๋์ ์ฝ๋๋ก ๊ทธ๋ฆฐ ๊ฐ์ฒด(์ฌ๊ฐํยท์ )๋ก ๊ธฐ์ง ์ธ๊ณฝ ๊ณ ์ ์นด๋ฉ๋ผ ํ๋ฉด์ ํ๋ด ๋ธ๋ค.\n",
"H, W, T = 64, 96, 10 # ํ๋ฉด ํฌ๊ธฐ(px), ์ํ์ค ๊ธธ์ด(ํ๋ ์)\n",
"HORIZON, FENCE = 20, 46 # ํ๋/์ง๋ฉด ๊ฒฝ๊ณ, ์ธํ๋ฆฌ ํ\n",
"ROI_ROWS = (32, 48) # ์ธํ๋ฆฌ ์ ๊ทผ ๊ฒฝ๊ณ๊ตฌ์ญ(ROI): ์ง๋ฉด 32~47ํ\n",
"CLASSES = [\"person\", \"vehicle\", \"drone\", \"animal\", \"bird\"]\n",
"INTRUDER = {\"person\", \"vehicle\", \"drone\"} # ๋น์ธ๊ฐ ์ ๊ทผ(๊ฒฝ๋ณด ๋์)\n",
"CONDS = [\"day\", \"night\", \"rain\", \"fog\"]\n",
"\n",
"\n",
"def background(rng):\n",
" # [TODO : ํ์ ์ค์ต 9] AAR ๊ธฐ๋ฐ ์ค๊ฒฝ๋ณด ์ ๋ฐ ๊ฐ์ฒด ๋ถ์ ๋ฐ ๊ฒฝ๊ณ๊ตฌ์ญ ํ์ง ๊ฐ๋ ํ๋ผ๋ฏธํฐ ์ฌ๋ณด์ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def tree(img, t, wind):\n",
" \"\"\"์ข์ธก ์๋ชฉ ์๊ด: ๋ฐ๋ ์ธ๊ธฐ์ ๋ฐ๋ผ ์ข์ฐ๋ก ํ๋ค๋ ค ์ค๊ฒฝ๋ณด๋ฅผ ๋ง๋ ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 9] AAR ๊ธฐ๋ฐ ์ค๊ฒฝ๋ณด ์ ๋ฐ ๊ฐ์ฒด ๋ถ์ ๋ฐ ๊ฒฝ๊ณ๊ตฌ์ญ ํ์ง ๊ฐ๋ ํ๋ผ๋ฏธํฐ ์ฌ๋ณด์ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_seq(seed, cond=None, objs=None, wind=None, camo=False):\n",
" rng = np.random.default_rng(seed)\n",
" cond = cond or str(rng.choice(CONDS))\n",
" wind = rng.uniform(0, 2) if wind is None else wind\n",
" bg = background(rng)\n",
" n = int(rng.integers(1, 4)) if objs is None else len(objs)\n",
" kinds = objs or list(rng.choice(CLASSES, n, p=[0.3, 0.15, 0.25, 0.15, 0.15]))\n",
" obs = []\n",
" for k in kinds:\n",
" if k == \"person\":\n",
" o = dict(y=rng.uniform(30, 36), x=rng.uniform(15, 85), vy=rng.uniform(0.8, 1.2), vx=rng.uniform(-0.6, 0.6), h=7, w=3,\n",
" val=72 if camo else rng.uniform(35, 50))\n",
" elif k == \"vehicle\":\n",
" o = dict(y=rng.uniform(33, 38), x=rng.uniform(10, 30), vy=0.2, vx=rng.uniform(2, 4), h=5, w=10, val=rng.uniform(150, 170))\n",
" elif k == \"drone\":\n",
" o = dict(y=rng.uniform(3, 15), x=rng.uniform(10, 80), vy=rng.uniform(0, 0.6), vx=rng.uniform(-2, 2), h=2, w=2 if camo else 3,\n",
" val=rng.uniform(80, 120))\n",
" elif k == \"animal\":\n",
" o = dict(y=rng.uniform(34, 42), x=rng.uniform(15, 80), vy=0, vx=rng.uniform(-1.5, 1.5), h=2, w=4, val=rng.uniform(60, 75))\n",
" else:\n",
" o = dict(y=rng.uniform(3, 15), x=rng.uniform(10, 80), vy=0, vx=rng.uniform(-3, 3), h=1, w=2, val=rng.uniform(100, 130))\n",
" o[\"cls\"] = str(k); obs.append(o)\n",
" frames, labels = [], []\n",
" for t in range(-8, T): # ์์ ์๊ฐ = ๊ฐ์ฒด ์๋ ๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ(์ฌ์ ๊ด์ธก)\n",
" f = bg.copy(); tree(f, t, wind); lab = []\n",
" for oi, o in enumerate(obs if t >= 0 else []):\n",
" jit = rng.normal(0, 1.2, 2) if o[\"cls\"] in (\"animal\", \"bird\") else (0, 0) # ๋ถ๊ท์น ์์ง์\n",
" y, x = int(o[\"y\"] + o[\"vy\"] * t + jit[0]), int(o[\"x\"] + o[\"vx\"] * t + jit[1])\n",
" y0, x0, y1, x1 = max(y, 0), max(x, 0), min(y + o[\"h\"], H), min(x + o[\"w\"], W)\n",
" if y1 > y0 and x1 > x0:\n",
" f[y0:y1, x0:x1] = o[\"val\"]\n",
" if o[\"cls\"] == \"person\":\n",
" f[max(y0 - 2, 0):y0, x0 + 1:x0 + 2] = o[\"val\"] # ๋จธ๋ฆฌ\n",
" lab.append({\"cls\": o[\"cls\"], \"oid\": oi, \"bbox\": (x0, y0, x1, y1), \"intruder\": o[\"cls\"] in INTRUDER})\n",
" if cond == \"night\":\n",
" f = f * 0.5 + 10 + rng.normal(0, 3.5, f.shape)\n",
" else:\n",
" f = f + rng.normal(0, 2.5, f.shape)\n",
" if cond == \"rain\":\n",
" m = rng.random(f.shape) < 0.006\n",
" for d in range(3):\n",
" f[d:][m[:H - d]] += 45\n",
" if cond == \"fog\":\n",
" f = 0.45 * f + 0.55 * 150\n",
" frames.append(np.clip(f, 0, 255)); labels.append(lab)\n",
" fr = np.array(frames)\n",
" return {\"id\": f\"CAM-{seed:04d}\", \"cond\": cond, \"wind\": wind, \"ref\": fr[:8], \"frames\": fr[8:], \"labels\": labels[8:]}\n",
"\n",
"# ---------------- ์ ์ฒ๋ฆฌยทํ์ง ๊ณตํต ํจ์(๋ฐฐ๊ฒฝ ์ฐจ๋ถ ๊ธฐ๋ฐ) ----------------\n",
"def enhance(seq):\n",
" \"\"\"์กฐ๋ ๋ณด์ (๋ฐฑ๋ถ์ ์ ์ฅ) + ๋น ์กฐ๊ฑด์ ๋น์ค๊ธฐ ์ ๊ฑฐ(3x3 ์ค์๊ฐ). ๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ(ref)๊ณผ ๊ฐ์ ๊ธฐ์ค์ผ๋ก ๋ณด์ ํ๋ค.\"\"\"\n",
" allf = np.concatenate([seq[\"ref\"], seq[\"frames\"]])\n",
" f = ndimage.median_filter(allf, size=(1, 3, 3)) if seq[\"cond\"] == \"rain\" else allf\n",
" lo, hi = np.percentile(f, [1, 99.5])\n",
" f = np.clip((f - lo) / (hi - lo + 1e-6) * 255, 0, 255)\n",
" return f[:len(seq[\"ref\"])], f[len(seq[\"ref\"]):]\n",
"\n",
"\n",
"def detect(ref, frames, k=4.0, min_area=3, sky_k=None, smooth=False):\n",
" \"\"\"๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ์ ์ค์๊ฐ ๋ฐฐ๊ฒฝ๊ณผ ์ฐจ์ด๋ฅผ ๊ฐ๊ฑด ์๊ณ๊ฐ(k x MAD)์ผ๋ก ์ด์งํํด ์ฐ๊ฒฐ ์ฑ๋ถ์ ๊ฐ์ฒด ํ๋ณด๋ก ๋ง๋ ๋ค.\n",
" smooth=True ์ด๋ฉด ์ฐ์ 3ํ๋ ์ ํ๊ท ์ผ๋ก ์๊ฐ ์ก์์ ์ค์ฌ ์ ๋๋น ๊ฐ์ฒด๋ฅผ ์ด๋ฆฐ๋ค.\"\"\"\n",
" bg = np.median(ref, axis=0)\n",
" if smooth:\n",
" frames = ndimage.uniform_filter1d(frames, 3, axis=0, mode=\"nearest\")\n",
" out = []\n",
" for f in frames:\n",
" d = np.abs(f - bg)\n",
" mad = np.median(d) * 1.4826 + 1e-6\n",
" thr = np.full(d.shape, k * mad)\n",
" if sky_k is not None:\n",
" thr[:HORIZON] = sky_k * mad\n",
" lab, n = ndimage.label(d > thr)\n",
" dets = []\n",
" for i, sl in enumerate(ndimage.find_objects(lab), start=1):\n",
" area = int((lab[sl] == i).sum())\n",
" if area >= min_area:\n",
" dets.append({\"bbox\": (sl[1].start, sl[0].start, sl[1].stop, sl[0].stop), \"area\": area,\n",
" \"contrast\": float(d[sl][lab[sl] == i].mean() / mad)})\n",
" out.append(dets)\n",
" return out\n",
"\n",
"\n",
"def center(b):\n",
" return ((b[0] + b[2]) / 2, (b[1] + b[3]) / 2)\n",
"\n",
"\n",
"def match(dets, labels, dist=4.0):\n",
" \"\"\"ํ์ง์ ์ ๋ต์ ์ค์ฌ ๊ฑฐ๋ฆฌ๋ก 1:1 ๋์์ํจ๋ค. ๋ฐํ: [(det, label or None)], ๋์น label ๋ชฉ๋ก\"\"\"\n",
" used, pairs = set(), []\n",
" for d in dets:\n",
" cd = center(d[\"bbox\"]); best, bj = dist, None\n",
" for j, l in enumerate(labels):\n",
" cl = center(l[\"bbox\"]); e = np.hypot(cd[0] - cl[0], cd[1] - cl[1])\n",
" if j not in used and e < best:\n",
" best, bj = e, j\n",
" if bj is not None:\n",
" used.add(bj)\n",
" pairs.append((d, labels[bj] if bj is not None else None))\n",
" return pairs, [l for j, l in enumerate(labels) if j not in used]\n",
"\n",
"\n",
"def in_roi(b):\n",
" cy = (b[1] + b[3]) / 2\n",
" return cy < HORIZON or ROI_ROWS[0] <= cy < ROI_ROWS[1]\n",
"\n",
"\n",
"def rule_class(b, area):\n",
" \"\"\"Baseline ๊ท์น: ์์น(ํ๋/์ง๋ฉด)ยทํฌ๊ธฐยท์ข
ํก๋น๋ก 1์ฐจ ๋ถ๋ฅ(ํ์ต ์์, ํ๋จ ๊ทผ๊ฑฐ๊ฐ ๊ทธ๋๋ก ๋๋ฌ๋๋ค)\"\"\"\n",
" w, h = b[2] - b[0], b[3] - b[1]\n",
" cy = (b[1] + b[3]) / 2\n",
" if cy < HORIZON:\n",
" return \"drone\" if 3 <= area <= 12 else \"clutter\"\n",
" if area >= 30 and w > h:\n",
" return \"vehicle\"\n",
" if h >= w and 6 <= area <= 30:\n",
" return \"person\"\n",
" return \"clutter\"\n",
"\n",
"# ---------------- ํ๋ ์ ๊ฐ ์ถ์ ๊ณผ ๊ถค์ ํน์ง ----------------\n",
"def track(dets, gate=6.0):\n",
" \"\"\"์ต๊ทผ์ ์ด์์ผ๋ก ํ๋ ์๋ณ ํ์ง๋ฅผ ๊ถค์ ์ผ๋ก ์๋๋ค.\"\"\"\n",
" tracks = []\n",
" for t, ds in enumerate(dets):\n",
" free = list(range(len(ds)))\n",
" for tr in tracks:\n",
" if tr[\"last\"] != t - 1 or not free:\n",
" continue\n",
" c0 = center(tr[\"b\"][-1])\n",
" dd = [np.hypot(center(ds[i][\"bbox\"])[0] - c0[0], center(ds[i][\"bbox\"])[1] - c0[1]) for i in free]\n",
" j = int(np.argmin(dd))\n",
" if dd[j] < gate:\n",
" i = free.pop(j); tr[\"b\"].append(ds[i][\"bbox\"]); tr[\"a\"].append(ds[i][\"area\"]); tr[\"c\"].append(ds[i][\"contrast\"])\n",
" tr[\"t\"].append(t); tr[\"last\"] = t\n",
" for i in free:\n",
" tracks.append({\"b\": [ds[i][\"bbox\"]], \"a\": [ds[i][\"area\"]], \"c\": [ds[i][\"contrast\"]], \"t\": [t], \"last\": t})\n",
" return tracks\n",
"\n",
"\n",
"TFEATS = [\"n\", \"area\", \"aspect\", \"row\", \"sky\", \"speed\", \"straight\", \"disp\", \"contrast\", \"area_cv\", \"col\"]\n",
"\n",
"\n",
"def track_features(tr):\n",
" c = np.array([center(b) for b in tr[\"b\"]])\n",
" wh = np.array([(b[2] - b[0], b[3] - b[1]) for b in tr[\"b\"]])\n",
" steps = np.hypot(*np.diff(c, axis=0).T) if len(c) > 1 else np.array([0.0])\n",
" disp = float(np.hypot(*(c[-1] - c[0])))\n",
" return {\"n\": len(c), \"area\": float(np.mean(tr[\"a\"])), \"aspect\": float(np.mean(wh[:, 1] / wh[:, 0])), \"row\": float(c[:, 1].mean()),\n",
" \"sky\": float(c[:, 1].mean() < HORIZON), \"speed\": float(steps.mean()), \"straight\": disp / (steps.sum() + 1e-6),\n",
" \"disp\": disp, \"contrast\": float(np.mean(tr[\"c\"])), \"area_cv\": float(np.std(tr[\"a\"]) / (np.mean(tr[\"a\"]) + 1e-6)),\n",
" \"col\": float(c[:, 0].mean())}\n",
"\n",
"\n",
"def track_truth(tr, labels):\n",
" \"\"\"๊ถค์ ์ ๊ฐ ํ๋ ์์ ์ ๋ต๊ณผ ๋์์์ผ ๋ค์๊ฒฐ๋ก ์ ์ฒด๋ฅผ ์ ํ๋ค(์์ผ๋ฉด clutter).\"\"\"\n",
" votes = []\n",
" for b, t in zip(tr[\"b\"], tr[\"t\"]):\n",
" pairs, _ = match([{\"bbox\": b}], labels[t])\n",
" votes.append((pairs[0][1][\"cls\"], pairs[0][1][\"oid\"]) if pairs[0][1] else (\"clutter\", -1))\n",
" return max(set(votes), key=votes.count)\n",
"\n",
"\n",
"def seq_tracks(seq, k=4.0, min_area=3, sky_k=None, smooth=False):\n",
" ref, fr = enhance(seq)\n",
" tracks = track(detect(ref, fr, k, min_area, sky_k, smooth))\n",
" rows = []\n",
" for tr in tracks:\n",
" f = track_features(tr); f[\"truth\"], f[\"oid\"] = track_truth(tr, seq[\"labels\"]); f[\"seq\"] = seq[\"id\"]; f[\"cond\"] = seq[\"cond\"]\n",
" f[\"tr\"] = tr; rows.append(f)\n",
" return rows\n",
"\n",
"TRAIN_SEEDS, OPS_SEEDS = range(0, 80), range(3000, 3060)\n",
"\n",
"\n",
"def tracks_of(seeds, k, wind=None):\n",
" out = []\n",
" for s in seeds:\n",
" q = make_seq(s, wind=wind)\n",
" for r in seq_tracks(q, k=k, min_area=2, sky_k=k - 0.5):\n",
" if in_roi(r[\"tr\"][\"b\"][-1]):\n",
" out.append(r)\n",
" return out\n",
"\n",
"\n",
"def X_of(rows):\n",
" return np.array([[r[k] for k in TFEATS] for r in rows])\n",
"\n",
"\n",
"_CACHE = {}\n",
"\n",
"\n",
"def run_system(k):\n",
" if k not in _CACHE:\n",
" _CACHE[k] = _run_system(k)\n",
" return _CACHE[k]\n",
"\n",
"\n",
"def _run_system(k):\n",
" \"\"\"ํ์ง ๊ฐ๋ k๋ก ํ์ตยท์ด์ฉ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๊ณ , ์ด์ฉ ๊ถค์ ๋ง๋ค ์นจ์
ํ๋ฅ (prob)์ ๋ถ์ธ๋ค.\"\"\"\n",
" tr, te = tracks_of(TRAIN_SEEDS, k), tracks_of(OPS_SEEDS, k)\n",
" m = make_pipeline(StandardScaler(), LogisticRegression(class_weight=\"balanced\", C=2.0, max_iter=2000, random_state=0))\n",
" m.fit(X_of(tr), [r[\"truth\"] in INTRUDER for r in tr])\n",
" for r, p in zip(te, m.predict_proba(X_of(te))[:, 1]):\n",
" r[\"prob\"] = float(p)\n",
" return te\n",
"\n",
"\n",
"def objects():\n",
" return {(q[\"id\"], l[\"oid\"]) for q in (make_seq(s) for s in OPS_SEEDS) for labs in q[\"labels\"] for l in labs if l[\"intruder\"]}\n",
"\n",
"\n",
"OBJS = objects()\n",
"\n",
"\n",
"def set_alerts(te, thr, min_n):\n",
" for r in te:\n",
" r[\"alert\"] = r[\"n\"] >= min_n and r[\"prob\"] >= thr\n",
" return te\n",
"\n",
"\n",
"def summarize(te):\n",
" al = [r for r in te if r[\"alert\"]]\n",
" far = sum(r[\"truth\"] not in INTRUDER for r in al) / max(len(al), 1)\n",
" found = {(r[\"seq\"], r[\"oid\"]) for r in al if r[\"truth\"] in INTRUDER}\n",
" rec = len(found & OBJS) / len(OBJS)\n",
" return {\"๊ฒฝ๋ณด\": len(al), \"์ค๊ฒฝ๋ณด์จ\": round(far, 3), \"์ฌํ์จ\": round(rec, 3), \"์ข
ํฉ์ ํ๋(F1)\": round(2 * (1 - far) * rec / max(1 - far + rec, 1e-9), 3)}\n",
"\n",
"\n",
"def aar_log(te):\n",
" rows = []\n",
" for r in te:\n",
" if r[\"alert\"] and r[\"truth\"] not in INTRUDER:\n",
" where = \"์๋ชฉ ๊ตฌ์ญ(x<16)\" if r[\"col\"] < 16 else \"๊ทธ ์ธ\"\n",
" rows.append({\"๊ตฌ๋ถ\": \"์ค๊ฒฝ๋ณด\", \"์์ธ\": {\"clutter\": \"์๋ชฉยท์ก์\", \"animal\": \"์ผ์๋๋ฌผ\", \"bird\": \"์กฐ๋ฅ\"}[r[\"truth\"]], \"์กฐ๊ฑด\": r[\"cond\"], \"๊ตฌ์ญ\": where, \"์ํ์ค\": r[\"seq\"]})\n",
" alerted = {(r[\"seq\"], r[\"oid\"]) for r in te if r[\"alert\"] and r[\"truth\"] in INTRUDER}\n",
" tracked = {(r[\"seq\"], r[\"oid\"]) for r in te if r[\"truth\"] in INTRUDER}\n",
" for o in sorted(OBJS):\n",
" if o not in alerted:\n",
" rows.append({\"๊ตฌ๋ถ\": \"๋ฏธํ\", \"์์ธ\": \"์ ์ ๋ฏธ๋ฌยท์ง์ ๋ถ์กฑ\" if o in tracked else \"์ถ์ ์คํจ(ํ์ง ๋๋ฝ)\", \"์กฐ๊ฑด\": \"-\", \"๊ตฌ์ญ\": \"-\", \"์ํ์ค\": o[0]})\n",
" return pd.DataFrame(rows, columns=[\"๊ตฌ๋ถ\", \"์์ธ\", \"์กฐ๊ฑด\", \"๊ตฌ์ญ\", \"์ํ์ค\"])\n",
"\n",
"\n",
"# 1) ์ด๊ธฐ ์ค์ ์ผ๋ก ๋ชจ์ ์ด์ฉ: '๋์น์ง ์๊ธฐ' ์ฐ์ (๋ฎ์ ์๊ณ๊ฐ, ์งง์ ์ง์ ์กฐ๊ฑด)\n",
"INIT = {\"k\": 3.0, \"alert_thr\": 0.3, \"min_frames\": 2}\n",
"te0 = set_alerts(run_system(INIT[\"k\"]), INIT[\"alert_thr\"], INIT[\"min_frames\"])\n",
"before = summarize(te0)\n",
"log = aar_log(te0)\n",
"print(\"[1. ๋ฌด์์ด ์ผ์ด๋ฌ๋] ์ด๊ธฐ ์ค์ \", INIT, \"->\", before)\n",
"print(log.groupby([\"๊ตฌ๋ถ\", \"์์ธ\"]).size().rename(\"๊ฑด์\").to_string())\n",
"print(\"\\n[2. ์ ์ผ์ด๋ฌ๋] ์ค๊ฒฝ๋ณด ์กฐ๊ฑด ๋ถํฌ:\", log[log[\"๊ตฌ๋ถ\"] == \"์ค๊ฒฝ๋ณด\"][\"์กฐ๊ฑด\"].value_counts().to_dict(), \"/ ๊ตฌ์ญ:\", log[log[\"๊ตฌ๋ถ\"] == \"์ค๊ฒฝ๋ณด\"][\"๊ตฌ์ญ\"].value_counts().to_dict())\n",
"\n",
"# 2) ์ฌ๋ณด์ : ์ ์ฝ(์ค๊ฒฝ๋ณด์จ 15% ์ดํ, ์ฌํ์จ 85% ์ด์) ์์์ F1 ์ต๋ ์กฐํฉ ํ์\n",
"grid = []\n",
"for k in (2.5, 3.0, 3.5):\n",
" te = run_system(k)\n",
" for thr in (0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8):\n",
" for mn in (2, 3, 4):\n",
" grid.append({\"k\": k, \"๊ฒฝ๋ณด์๊ณ๊ฐ\": thr, \"์ต์ํ๋ ์\": mn, **summarize(set_alerts(te, thr, mn))})\n",
"g = pd.DataFrame(grid)\n",
"seen = set(map(tuple, log[log[\"๊ตฌ๋ถ\"] == \"์ค๊ฒฝ๋ณด\"][[\"์ํ์ค\", \"์์ธ\"]].values))\n",
"\n",
"\n",
"def repeats(row):\n",
" \"\"\"AAR์์ ์๋ณํ ์ค๊ฒฝ๋ณด(์ํ์คยท์์ธ)๊ฐ ์ด ์ค์ ์์ ๋ค์ ๋์ค๋ ๊ฑด์\"\"\"\n",
" t = set_alerts(run_system(row[\"k\"]), row[\"๊ฒฝ๋ณด์๊ณ๊ฐ\"], int(row[\"์ต์ํ๋ ์\"]))\n",
" return len(seen & set(map(tuple, aar_log(t)[lambda d: d[\"๊ตฌ๋ถ\"] == \"์ค๊ฒฝ๋ณด\"][[\"์ํ์ค\", \"์์ธ\"]].values)))\n",
"\n",
"\n",
"ok = g[(g[\"์ค๊ฒฝ๋ณด์จ\"] <= 0.15) & (g[\"์ฌํ์จ\"] >= 0.85)].copy()\n",
"ok[\"์ฌ๋ฐ\"] = ok.apply(repeats, axis=1)\n",
"ok = ok.sort_values([\"์ฌ๋ฐ\", \"์ข
ํฉ์ ํ๋(F1)\", \"์ฌํ์จ\"], ascending=[True, False, False]) # ์ฌ๋ฐ ์ต์ -> F1 ์ต๋ ์\n",
"best = ok.iloc[0]\n",
"print(\"\\n[3. ๋ฌด์์ ๋ฐ๊พธ๋] ์ ์ฝ ๋ง์กฑ ์กฐํฉ ์์ 5๊ฐ(์ฌ๋ฐ ์ ์ ์) (์ ์ฒด ์กฐํฉ %d๊ฐ ์ค %d๊ฐ ๋ง์กฑ)\" % (len(g), len(ok)))\n",
"print(ok.head().to_string(index=False))\n",
"te1 = set_alerts(run_system(best[\"k\"]), best[\"๊ฒฝ๋ณด์๊ณ๊ฐ\"], int(best[\"์ต์ํ๋ ์\"]))\n",
"after = summarize(te1)\n",
"log1 = aar_log(te1)\n",
"gain = (after[\"์ข
ํฉ์ ํ๋(F1)\"] - before[\"์ข
ํฉ์ ํ๋(F1)\"]) * 100\n",
"print(f\"\\n์ฌ๋ณด์ ํ: {after}\")\n",
"print(f\"์ข
ํฉ์ ํ๋ ๋ณํ {before['์ข
ํฉ์ ํ๋(F1)']:.3f} -> {after['์ข
ํฉ์ ํ๋(F1)']:.3f} ({gain:+.1f}%p, ๋ชฉํ +10%p ์ด์: {'๋ฌ์ฑ' if gain >= 10 else '๋ฏธ๋ฌ'})\")\n",
"rep = log[log[\"๊ตฌ๋ถ\"] == \"์ค๊ฒฝ๋ณด\"].groupby([\"์ํ์ค\", \"์์ธ\"]).size().index.intersection(log1[log1[\"๊ตฌ๋ถ\"] == \"์ค๊ฒฝ๋ณด\"].groupby([\"์ํ์ค\", \"์์ธ\"]).size().index)\n",
"print(f\"AAR์์ ์๋ณํ ์ค๊ฒฝ๋ณด {log['๊ตฌ๋ถ'].eq('์ค๊ฒฝ๋ณด').sum()}๊ฑด -> ์ฌ๋ณด์ ํ {log1['๊ตฌ๋ถ'].eq('์ค๊ฒฝ๋ณด').sum()}๊ฑด, ๊ฐ์ ์ํ์คยท๊ฐ์ ์์ธ์ผ๋ก ์ฌ๋ฐํ ์ค๋ฅ {len(rep)}๊ฑด (๋ชฉํ 0๊ฑด: {'๋ฌ์ฑ' if len(rep) == 0 else '๋ฏธ๋ฌ'})\")\n",
"\n",
"# 3) ์ฌ๋ณด์ ํ๋ผ๋ฏธํฐ(yaml ๋ด์ฉ ๋ฌธ์์ด)\n",
"tuned = f\"detection_k: {best['k']}\\nalert_threshold: {best['๊ฒฝ๋ณด์๊ณ๊ฐ']}\\nmin_track_frames: {int(best['์ต์ํ๋ ์'])}\\nconstraints: {{max_far: 0.15, min_recall: 0.85}}\"\n",
"print(\"\\n[์ฌ๋ณด์ ํ๋ผ๋ฏธํฐ(sec01_tuned_params.yaml ๋ด์ฉ)]\\n\" + tuned)\n",
"print(\"๊ฐ์ ์กฐ์น: ํ์ง ๊ฐ๋ k๋ฅผ ์ฌ๋ฆฌ๊ณ ์ต์ ์ง์ ํ๋ ์ ์กฐ๊ฑด์ ๊ฐํํด ์๋ชฉยท๋๋ฌผยท์กฐ๋ฅ ์ค๊ฒฝ๋ณด๋ฅผ ์ค์ด๊ณ , ์ฌ๋ณด์ ๊ฐ์ ํ์ฅ ์์์ผ๋ก ์ฌ๊ฒ์ฆ ํ ์ ์ฉํ๋ค.\")\n",
"\n",
"plt.figure(figsize=(6, 4))\n",
"for k in (2.5, 3.0, 3.5):\n",
" s = g[g[\"k\"] == k]\n",
" plt.plot(s[\"์ค๊ฒฝ๋ณด์จ\"], s[\"์ฌํ์จ\"], \"o\", alpha=0.6, label=f\"k={k}\")\n",
"plt.axvline(0.15, ls=\"--\", c=\"gray\"); plt.axhline(0.85, ls=\"--\", c=\"gray\")\n",
"plt.xlabel(\"false alarm rate\"); plt.ylabel(\"intruder recall\"); plt.legend(); plt.title(\"AAR re-tuning grid (synthetic)\")\n",
"plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
09 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[1. ๋ฌด์์ด ์ผ์ด๋ฌ๋] ์ด๊ธฐ ์ค์ {'k': 3.0, 'alert_thr': 0.3, 'min_frames': 2} -> {'๊ฒฝ๋ณด': 135, '์ค๊ฒฝ๋ณด์จ': 0.43, '์ฌํ์จ': 0.963, '์ข
ํฉ์ ํ๋(F1)': 0.716}\n",
"๊ตฌ๋ถ ์์ธ \n",
"๋ฏธํ ์ ์ ๋ฏธ๋ฌยท์ง์ ๋ถ์กฑ 2\n",
" ์ถ์ ์คํจ(ํ์ง ๋๋ฝ) 1\n",
"์ค๊ฒฝ๋ณด ์๋ชฉยท์ก์ 31\n",
" ์ผ์๋๋ฌผ 13\n",
" ์กฐ๋ฅ 14\n",
"\n",
"[2. ์ ์ผ์ด๋ฌ๋] ์ค๊ฒฝ๋ณด ์กฐ๊ฑด ๋ถํฌ: {'rain': 37, 'fog': 10, 'night': 8, 'day': 3} / ๊ตฌ์ญ: {'๊ทธ ์ธ': 43, '์๋ชฉ ๊ตฌ์ญ(x<16)': 15}\n",
"\n",
"[3. ๋ฌด์์ ๋ฐ๊พธ๋] ์ ์ฝ ๋ง์กฑ ์กฐํฉ ์์ 5๊ฐ(์ฌ๋ฐ ์ ์ ์) (์ ์ฒด ์กฐํฉ 63๊ฐ ์ค 9๊ฐ ๋ง์กฑ)\n",
" k ๊ฒฝ๋ณด์๊ณ๊ฐ ์ต์ํ๋ ์ ๊ฒฝ๋ณด ์ค๊ฒฝ๋ณด์จ ์ฌํ์จ ์ข
ํฉ์ ํ๋(F1) ์ฌ๋ฐ\n",
"3.5 0.6 4 84 0.083 0.963 0.939 7\n",
"3.5 0.7 4 83 0.084 0.950 0.933 7\n",
"3.5 0.8 4 82 0.085 0.938 0.926 7\n",
"3.5 0.7 3 84 0.095 0.950 0.927 8\n",
"3.5 0.8 3 83 0.096 0.938 0.920 8\n",
"\n",
"์ฌ๋ณด์ ํ: {'๊ฒฝ๋ณด': 84, '์ค๊ฒฝ๋ณด์จ': 0.083, '์ฌํ์จ': 0.963, '์ข
ํฉ์ ํ๋(F1)': 0.939}\n",
"์ข
ํฉ์ ํ๋ ๋ณํ 0.716 -> 0.939 (+22.3%p, ๋ชฉํ +10%p ์ด์: ๋ฌ์ฑ)\n",
"AAR์์ ์๋ณํ ์ค๊ฒฝ๋ณด 58๊ฑด -> ์ฌ๋ณด์ ํ 7๊ฑด, ๊ฐ์ ์ํ์คยท๊ฐ์ ์์ธ์ผ๋ก ์ฌ๋ฐํ ์ค๋ฅ 7๊ฑด (๋ชฉํ 0๊ฑด: ๋ฏธ๋ฌ)\n",
"\n",
"[์ฌ๋ณด์ ํ๋ผ๋ฏธํฐ(sec01_tuned_params.yaml ๋ด์ฉ)]\n",
"detection_k: 3.5\n",
"alert_threshold: 0.6\n",
"min_track_frames: 4\n",
"constraints: {max_far: 0.15, min_recall: 0.85}\n",
"๊ฐ์ ์กฐ์น: ํ์ง ๊ฐ๋ k๋ฅผ ์ฌ๋ฆฌ๊ณ ์ต์ ์ง์ ํ๋ ์ ์กฐ๊ฑด์ ๊ฐํํด ์๋ชฉยท๋๋ฌผยท์กฐ๋ฅ ์ค๊ฒฝ๋ณด๋ฅผ ์ค์ด๊ณ , ์ฌ๋ณด์ ๊ฐ์ ํ์ฅ ์์์ผ๋ก ์ฌ๊ฒ์ฆ ํ ์ ์ฉํ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
10. ๊ธฐ์ง CCTV ๋น์ธ๊ฐ ์ ๊ทผ ํ์ง ์์คํ
ํตํฉ ๋ฐ ์์ค ๋ฐฉํธ ๋ธ๋ฆฌํ ํจํค์ง ์์ฑ\n",
"**์์์๊ฐ**: ์ฝ 3.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๊ธฐ์ง CCTV ๋น์ธ๊ฐ ์ ๊ทผ ํ์ง ์์คํ
์ ์ต์ข
ํตํฉํ๊ณ ๊ธฐ์ง ๋ฐฉํธ ๋ธ๋ฆฌํ ํจํค์ง๋ฅผ ์์ฑํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๋ฐ์ดํฐ ๊ฒ์ฆ๋ถํฐ ์ ์ฒ๋ฆฌ, ํ์ง, ์ ๋ขฐ๋, HITL, ์ํฉํ, ์ฌ๋ณด์ ๊ฐ ์ ์ฉ๊น์ง ์ ์ฒด ํ์ดํ๋ผ์ธ์ ํตํฉํ๊ณ ์์ค ๋ฐฉํธ ๋ธ๋ฆฌํ ํจํค์ง๋ฅผ ์์ฑํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ฌ๋ณด์ ํ๋ผ๋ฏธํฐ๋ก ์ ์ฒด ๊ณผ์ ์ ํ ๋ฒ์ ์คํํ๋ ํตํฉ ์ฝ๋๋ฅผ ์์ฑํ๊ณ ์์ ๋ฐ์ดํฐ์ ์ผ์ ๋จ์ ํ๋ ์์ด ์๋ ์ฒ๋ฆฌ๋๋์ง ํ์ธํ๊ณ ์ ํจ์ฑ์ ์ ๊ฒํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/main_sec01.py, README.md, docs/sec01_final_report.pdf(md)` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๊ตฐ ์ค๋ฌด ์ฆ์ ๋์
๊ฐ๋ฅ์ฑ, ์ด์ฉ ์ ๋ขฐ๋ ๋ฐ ์ ๋ต์ ๊ธฐ๋ํจ๊ณผ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: End-to-End ํตํฉ ํ์ดํ๋ผ์ธ ์คํ ์ฑ๊ณต๋ฅ 100%, ํ
์คํธ ์ปค๋ฒ๋ฆฌ์ง 85% ์ด์\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์ํด๋ฆญ ์คํ ์คํฌ๋ฆฝํธ ๋์ ํ์ธ ๋ฐ ์ต์ข
๋ฐํ ์ฐ์ถ๋ฌผ ์์ฑ ์๋ฃ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ์ต์ข
๊ฒ์ฆ ์์ํ(ํ๊ฐ๊ด) ๋ชจ์ ์์ฐ ๋ฐ ์ ์ฒด ๊ธฐ๋ฅ ํตํฉ ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"๋ฐ์ดํฐ ๊ฒ์ฆ, ์ผ์ ๋จ์ ๋ณด์ ยท์ ์ฒ๋ฆฌ, ๊ฐ์ฒด ํ์งยท์ถ์ , ์ ๋ขฐ๋ ์ ์, HITL ๊ฒํ ๋๊ธฐ์ด, ๊ด์ ์ํฉํ, AAR ์ฌ๋ณด์ ๊ฐ ์ ์ฉ๊น์ง ๋น์ธ๊ฐ ์ ๊ทผ ํ์ง ์ ์ฒด ํ์ดํ๋ผ์ธ์ ํ ํ์ผ๋ก ํตํฉ ์คํํ๊ณ , ์์ฒด ์ ๊ฒ(์คํ ๋ผ์ธ ์ปค๋ฒ๋ฆฌ์ง ํฌํจ)๊ณผ ์์ค ๋ฐฉํธ ๋ธ๋ฆฌํ ์์ฝ์ ์ถ๋ ฅํ๋ค.\"\"\"\n",
"import dis\n",
"import hashlib\n",
"import json\n",
"import sys\n",
"import time\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from scipy import ndimage\n",
"from sklearn.linear_model import LogisticRegression\n",
"from sklearn.pipeline import make_pipeline\n",
"from sklearn.preprocessing import StandardScaler\n",
"\n",
"# ---------------- [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํฉ์ฑ CCTV ์์ ์์ฑ๊ธฐ ----------------\n",
"# ์ค์ ์์ ๋์ ์ฝ๋๋ก ๊ทธ๋ฆฐ ๊ฐ์ฒด(์ฌ๊ฐํยท์ )๋ก ๊ธฐ์ง ์ธ๊ณฝ ๊ณ ์ ์นด๋ฉ๋ผ ํ๋ฉด์ ํ๋ด ๋ธ๋ค.\n",
"H, W, T = 64, 96, 10 # ํ๋ฉด ํฌ๊ธฐ(px), ์ํ์ค ๊ธธ์ด(ํ๋ ์)\n",
"HORIZON, FENCE = 20, 46 # ํ๋/์ง๋ฉด ๊ฒฝ๊ณ, ์ธํ๋ฆฌ ํ\n",
"ROI_ROWS = (32, 48) # ์ธํ๋ฆฌ ์ ๊ทผ ๊ฒฝ๊ณ๊ตฌ์ญ(ROI): ์ง๋ฉด 32~47ํ\n",
"CLASSES = [\"person\", \"vehicle\", \"drone\", \"animal\", \"bird\"]\n",
"INTRUDER = {\"person\", \"vehicle\", \"drone\"} # ๋น์ธ๊ฐ ์ ๊ทผ(๊ฒฝ๋ณด ๋์)\n",
"CONDS = [\"day\", \"night\", \"rain\", \"fog\"]\n",
"\n",
"\n",
"def background(rng):\n",
" # [TODO : ํ์ ์ค์ต 10] ๊ธฐ์ง CCTV ๋น์ธ๊ฐ ์ ๊ทผ ํ์ง ์์คํ
ํตํฉ ๋ฐ ์์ค ๋ฐฉํธ ๋ธ๋ฆฌํ ํจํค์ง ์์ฑ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def tree(img, t, wind):\n",
" \"\"\"์ข์ธก ์๋ชฉ ์๊ด: ๋ฐ๋ ์ธ๊ธฐ์ ๋ฐ๋ผ ์ข์ฐ๋ก ํ๋ค๋ ค ์ค๊ฒฝ๋ณด๋ฅผ ๋ง๋ ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 10] ๊ธฐ์ง CCTV ๋น์ธ๊ฐ ์ ๊ทผ ํ์ง ์์คํ
ํตํฉ ๋ฐ ์์ค ๋ฐฉํธ ๋ธ๋ฆฌํ ํจํค์ง ์์ฑ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_seq(seed, cond=None, objs=None, wind=None, camo=False):\n",
" rng = np.random.default_rng(seed)\n",
" cond = cond or str(rng.choice(CONDS))\n",
" wind = rng.uniform(0, 2) if wind is None else wind\n",
" bg = background(rng)\n",
" n = int(rng.integers(1, 4)) if objs is None else len(objs)\n",
" kinds = objs or list(rng.choice(CLASSES, n, p=[0.3, 0.15, 0.25, 0.15, 0.15]))\n",
" obs = []\n",
" for k in kinds:\n",
" if k == \"person\":\n",
" o = dict(y=rng.uniform(30, 36), x=rng.uniform(15, 85), vy=rng.uniform(0.8, 1.2), vx=rng.uniform(-0.6, 0.6), h=7, w=3,\n",
" val=72 if camo else rng.uniform(35, 50))\n",
" elif k == \"vehicle\":\n",
" o = dict(y=rng.uniform(33, 38), x=rng.uniform(10, 30), vy=0.2, vx=rng.uniform(2, 4), h=5, w=10, val=rng.uniform(150, 170))\n",
" elif k == \"drone\":\n",
" o = dict(y=rng.uniform(3, 15), x=rng.uniform(10, 80), vy=rng.uniform(0, 0.6), vx=rng.uniform(-2, 2), h=2, w=2 if camo else 3,\n",
" val=rng.uniform(80, 120))\n",
" elif k == \"animal\":\n",
" o = dict(y=rng.uniform(34, 42), x=rng.uniform(15, 80), vy=0, vx=rng.uniform(-1.5, 1.5), h=2, w=4, val=rng.uniform(60, 75))\n",
" else:\n",
" o = dict(y=rng.uniform(3, 15), x=rng.uniform(10, 80), vy=0, vx=rng.uniform(-3, 3), h=1, w=2, val=rng.uniform(100, 130))\n",
" o[\"cls\"] = str(k); obs.append(o)\n",
" frames, labels = [], []\n",
" for t in range(-8, T): # ์์ ์๊ฐ = ๊ฐ์ฒด ์๋ ๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ(์ฌ์ ๊ด์ธก)\n",
" f = bg.copy(); tree(f, t, wind); lab = []\n",
" for oi, o in enumerate(obs if t >= 0 else []):\n",
" jit = rng.normal(0, 1.2, 2) if o[\"cls\"] in (\"animal\", \"bird\") else (0, 0) # ๋ถ๊ท์น ์์ง์\n",
" y, x = int(o[\"y\"] + o[\"vy\"] * t + jit[0]), int(o[\"x\"] + o[\"vx\"] * t + jit[1])\n",
" y0, x0, y1, x1 = max(y, 0), max(x, 0), min(y + o[\"h\"], H), min(x + o[\"w\"], W)\n",
" if y1 > y0 and x1 > x0:\n",
" f[y0:y1, x0:x1] = o[\"val\"]\n",
" if o[\"cls\"] == \"person\":\n",
" f[max(y0 - 2, 0):y0, x0 + 1:x0 + 2] = o[\"val\"] # ๋จธ๋ฆฌ\n",
" lab.append({\"cls\": o[\"cls\"], \"oid\": oi, \"bbox\": (x0, y0, x1, y1), \"intruder\": o[\"cls\"] in INTRUDER})\n",
" if cond == \"night\":\n",
" f = f * 0.5 + 10 + rng.normal(0, 3.5, f.shape)\n",
" else:\n",
" f = f + rng.normal(0, 2.5, f.shape)\n",
" if cond == \"rain\":\n",
" m = rng.random(f.shape) < 0.006\n",
" for d in range(3):\n",
" f[d:][m[:H - d]] += 45\n",
" if cond == \"fog\":\n",
" f = 0.45 * f + 0.55 * 150\n",
" frames.append(np.clip(f, 0, 255)); labels.append(lab)\n",
" fr = np.array(frames)\n",
" return {\"id\": f\"CAM-{seed:04d}\", \"cond\": cond, \"wind\": wind, \"ref\": fr[:8], \"frames\": fr[8:], \"labels\": labels[8:]}\n",
"\n",
"# ---------------- ์ ์ฒ๋ฆฌยทํ์ง ๊ณตํต ํจ์(๋ฐฐ๊ฒฝ ์ฐจ๋ถ ๊ธฐ๋ฐ) ----------------\n",
"def enhance(seq):\n",
" \"\"\"์กฐ๋ ๋ณด์ (๋ฐฑ๋ถ์ ์ ์ฅ) + ๋น ์กฐ๊ฑด์ ๋น์ค๊ธฐ ์ ๊ฑฐ(3x3 ์ค์๊ฐ). ๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ(ref)๊ณผ ๊ฐ์ ๊ธฐ์ค์ผ๋ก ๋ณด์ ํ๋ค.\"\"\"\n",
" allf = np.concatenate([seq[\"ref\"], seq[\"frames\"]])\n",
" f = ndimage.median_filter(allf, size=(1, 3, 3)) if seq[\"cond\"] == \"rain\" else allf\n",
" lo, hi = np.percentile(f, [1, 99.5])\n",
" f = np.clip((f - lo) / (hi - lo + 1e-6) * 255, 0, 255)\n",
" return f[:len(seq[\"ref\"])], f[len(seq[\"ref\"]):]\n",
"\n",
"\n",
"def detect(ref, frames, k=4.0, min_area=3, sky_k=None, smooth=False):\n",
" \"\"\"๋ฐฐ๊ฒฝ ํ์ต ๊ตฌ๊ฐ์ ์ค์๊ฐ ๋ฐฐ๊ฒฝ๊ณผ ์ฐจ์ด๋ฅผ ๊ฐ๊ฑด ์๊ณ๊ฐ(k x MAD)์ผ๋ก ์ด์งํํด ์ฐ๊ฒฐ ์ฑ๋ถ์ ๊ฐ์ฒด ํ๋ณด๋ก ๋ง๋ ๋ค.\n",
" smooth=True ์ด๋ฉด ์ฐ์ 3ํ๋ ์ ํ๊ท ์ผ๋ก ์๊ฐ ์ก์์ ์ค์ฌ ์ ๋๋น ๊ฐ์ฒด๋ฅผ ์ด๋ฆฐ๋ค.\"\"\"\n",
" bg = np.median(ref, axis=0)\n",
" if smooth:\n",
" frames = ndimage.uniform_filter1d(frames, 3, axis=0, mode=\"nearest\")\n",
" out = []\n",
" for f in frames:\n",
" d = np.abs(f - bg)\n",
" mad = np.median(d) * 1.4826 + 1e-6\n",
" thr = np.full(d.shape, k * mad)\n",
" if sky_k is not None:\n",
" thr[:HORIZON] = sky_k * mad\n",
" lab, n = ndimage.label(d > thr)\n",
" dets = []\n",
" for i, sl in enumerate(ndimage.find_objects(lab), start=1):\n",
" area = int((lab[sl] == i).sum())\n",
" if area >= min_area:\n",
" dets.append({\"bbox\": (sl[1].start, sl[0].start, sl[1].stop, sl[0].stop), \"area\": area,\n",
" \"contrast\": float(d[sl][lab[sl] == i].mean() / mad)})\n",
" out.append(dets)\n",
" return out\n",
"\n",
"\n",
"def center(b):\n",
" return ((b[0] + b[2]) / 2, (b[1] + b[3]) / 2)\n",
"\n",
"\n",
"def match(dets, labels, dist=4.0):\n",
" \"\"\"ํ์ง์ ์ ๋ต์ ์ค์ฌ ๊ฑฐ๋ฆฌ๋ก 1:1 ๋์์ํจ๋ค. ๋ฐํ: [(det, label or None)], ๋์น label ๋ชฉ๋ก\"\"\"\n",
" used, pairs = set(), []\n",
" for d in dets:\n",
" cd = center(d[\"bbox\"]); best, bj = dist, None\n",
" for j, l in enumerate(labels):\n",
" cl = center(l[\"bbox\"]); e = np.hypot(cd[0] - cl[0], cd[1] - cl[1])\n",
" if j not in used and e < best:\n",
" best, bj = e, j\n",
" if bj is not None:\n",
" used.add(bj)\n",
" pairs.append((d, labels[bj] if bj is not None else None))\n",
" return pairs, [l for j, l in enumerate(labels) if j not in used]\n",
"\n",
"\n",
"def in_roi(b):\n",
" cy = (b[1] + b[3]) / 2\n",
" return cy < HORIZON or ROI_ROWS[0] <= cy < ROI_ROWS[1]\n",
"\n",
"\n",
"def rule_class(b, area):\n",
" \"\"\"Baseline ๊ท์น: ์์น(ํ๋/์ง๋ฉด)ยทํฌ๊ธฐยท์ข
ํก๋น๋ก 1์ฐจ ๋ถ๋ฅ(ํ์ต ์์, ํ๋จ ๊ทผ๊ฑฐ๊ฐ ๊ทธ๋๋ก ๋๋ฌ๋๋ค)\"\"\"\n",
" w, h = b[2] - b[0], b[3] - b[1]\n",
" cy = (b[1] + b[3]) / 2\n",
" if cy < HORIZON:\n",
" return \"drone\" if 3 <= area <= 12 else \"clutter\"\n",
" if area >= 30 and w > h:\n",
" return \"vehicle\"\n",
" if h >= w and 6 <= area <= 30:\n",
" return \"person\"\n",
" return \"clutter\"\n",
"\n",
"# ---------------- ํ๋ ์ ๊ฐ ์ถ์ ๊ณผ ๊ถค์ ํน์ง ----------------\n",
"def track(dets, gate=6.0):\n",
" \"\"\"์ต๊ทผ์ ์ด์์ผ๋ก ํ๋ ์๋ณ ํ์ง๋ฅผ ๊ถค์ ์ผ๋ก ์๋๋ค.\"\"\"\n",
" tracks = []\n",
" for t, ds in enumerate(dets):\n",
" free = list(range(len(ds)))\n",
" for tr in tracks:\n",
" if tr[\"last\"] != t - 1 or not free:\n",
" continue\n",
" c0 = center(tr[\"b\"][-1])\n",
" dd = [np.hypot(center(ds[i][\"bbox\"])[0] - c0[0], center(ds[i][\"bbox\"])[1] - c0[1]) for i in free]\n",
" j = int(np.argmin(dd))\n",
" if dd[j] < gate:\n",
" i = free.pop(j); tr[\"b\"].append(ds[i][\"bbox\"]); tr[\"a\"].append(ds[i][\"area\"]); tr[\"c\"].append(ds[i][\"contrast\"])\n",
" tr[\"t\"].append(t); tr[\"last\"] = t\n",
" for i in free:\n",
" tracks.append({\"b\": [ds[i][\"bbox\"]], \"a\": [ds[i][\"area\"]], \"c\": [ds[i][\"contrast\"]], \"t\": [t], \"last\": t})\n",
" return tracks\n",
"\n",
"\n",
"TFEATS = [\"n\", \"area\", \"aspect\", \"row\", \"sky\", \"speed\", \"straight\", \"disp\", \"contrast\", \"area_cv\", \"col\"]\n",
"\n",
"\n",
"def track_features(tr):\n",
" c = np.array([center(b) for b in tr[\"b\"]])\n",
" wh = np.array([(b[2] - b[0], b[3] - b[1]) for b in tr[\"b\"]])\n",
" steps = np.hypot(*np.diff(c, axis=0).T) if len(c) > 1 else np.array([0.0])\n",
" disp = float(np.hypot(*(c[-1] - c[0])))\n",
" return {\"n\": len(c), \"area\": float(np.mean(tr[\"a\"])), \"aspect\": float(np.mean(wh[:, 1] / wh[:, 0])), \"row\": float(c[:, 1].mean()),\n",
" \"sky\": float(c[:, 1].mean() < HORIZON), \"speed\": float(steps.mean()), \"straight\": disp / (steps.sum() + 1e-6),\n",
" \"disp\": disp, \"contrast\": float(np.mean(tr[\"c\"])), \"area_cv\": float(np.std(tr[\"a\"]) / (np.mean(tr[\"a\"]) + 1e-6)),\n",
" \"col\": float(c[:, 0].mean())}\n",
"\n",
"\n",
"def track_truth(tr, labels):\n",
" \"\"\"๊ถค์ ์ ๊ฐ ํ๋ ์์ ์ ๋ต๊ณผ ๋์์์ผ ๋ค์๊ฒฐ๋ก ์ ์ฒด๋ฅผ ์ ํ๋ค(์์ผ๋ฉด clutter).\"\"\"\n",
" votes = []\n",
" for b, t in zip(tr[\"b\"], tr[\"t\"]):\n",
" pairs, _ = match([{\"bbox\": b}], labels[t])\n",
" votes.append((pairs[0][1][\"cls\"], pairs[0][1][\"oid\"]) if pairs[0][1] else (\"clutter\", -1))\n",
" return max(set(votes), key=votes.count)\n",
"\n",
"\n",
"def seq_tracks(seq, k=4.0, min_area=3, sky_k=None, smooth=False):\n",
" ref, fr = enhance(seq)\n",
" tracks = track(detect(ref, fr, k, min_area, sky_k, smooth))\n",
" rows = []\n",
" for tr in tracks:\n",
" f = track_features(tr); f[\"truth\"], f[\"oid\"] = track_truth(tr, seq[\"labels\"]); f[\"seq\"] = seq[\"id\"]; f[\"cond\"] = seq[\"cond\"]\n",
" f[\"tr\"] = tr; rows.append(f)\n",
" return rows\n",
"\n",
"CONFIG = {\"detection_k\": 3.5, \"alert_threshold\": 0.6, \"min_track_frames\": 4} # ๋ฏธ์
9 ์ฌ๋ณด์ ๊ฐ\n",
"\n",
"\n",
"def validate(seq):\n",
" errs = []\n",
" if seq[\"cond\"] not in CONDS:\n",
" errs.append(\"condition ํ์ฉ๊ฐ ์๋\")\n",
" if seq[\"frames\"].ndim != 3 or seq[\"frames\"].shape[1:] != (H, W):\n",
" errs.append(\"ํด์๋ ์ค๋ฅ\")\n",
" if not str(seq[\"id\"]).startswith(\"CAM-\"):\n",
" errs.append(\"camera_id ํ์ ์ค๋ฅ\")\n",
" return errs\n",
"\n",
"\n",
"def repair(seq):\n",
" \"\"\"์ผ์ ๋จ์ (์ ํ๋ฉด 0) ํ๋ ์์ ์ง์ ํ๋ ์์ผ๋ก ๋์ฒดํ๊ณ ๋ณด์ ๊ฑด์๋ฅผ ๋๋ ค์ค๋ค.\"\"\"\n",
" fr = seq[\"frames\"].copy(); fixed = 0\n",
" for t in range(len(fr)):\n",
" if fr[t].max() < 1:\n",
" fr[t] = fr[t - 1] if t > 0 else seq[\"ref\"][-1]; fixed += 1\n",
" return {**seq, \"frames\": fr}, fixed\n",
"\n",
"\n",
"def X_of(rows):\n",
" return np.array([[r[k] for k in TFEATS] for r in rows])\n",
"\n",
"\n",
"def tracks_of(seq):\n",
" k = CONFIG[\"detection_k\"]\n",
" return [r for r in seq_tracks(seq, k=k, min_area=2, sky_k=k - 0.5) if in_roi(r[\"tr\"][\"b\"][-1])]\n",
"\n",
"\n",
"class Queue:\n",
" def __init__(self):\n",
" self.items, self.log = [], []\n",
"\n",
" def submit(self, aid, score, grade):\n",
" prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n",
" body = {\"event\": \"SUBMIT\", \"id\": aid, \"score\": round(score, 3), \"by\": \"AI\"}\n",
" self.log.append({**body, \"prev\": prev, \"hash\": hashlib.sha256((prev + json.dumps(body, sort_keys=True)).encode()).hexdigest()})\n",
" self.items.append({\"id\": aid, \"grade\": grade, \"status\": \"PENDING\"})\n",
"\n",
" def verify(self):\n",
" prev = \"GENESIS\"\n",
" for rec in self.log:\n",
" body = {k: v for k, v in rec.items() if k not in (\"prev\", \"hash\")}\n",
" if rec[\"prev\"] != prev or rec[\"hash\"] != hashlib.sha256((prev + json.dumps(body, sort_keys=True)).encode()).hexdigest():\n",
" return False\n",
" prev = rec[\"hash\"]\n",
" return True\n",
"\n",
"\n",
"def grade_of(p):\n",
" return \"High\" if p >= 0.8 else (\"Medium\" if p >= CONFIG[\"alert_threshold\"] else \"Low\")\n",
"\n",
"\n",
"def process(seq, engine, queue):\n",
" \"\"\"์ํ์ค 1๊ฐ: ๋จ์ ๋ณด์ -> ํ์งยท์ถ์ -> ์ ๋ขฐ๋ -> ๊ฒฝ๋ณด ๋๊ธฐ์ด ๋ฑ๋ก.\"\"\"\n",
" seq, fixed = repair(seq)\n",
" trs = tracks_of(seq)\n",
" if trs:\n",
" for r, p in zip(trs, engine.predict_proba(X_of(trs))[:, 1]):\n",
" r[\"score\"], r[\"grade\"] = float(p), grade_of(float(p))\n",
" r[\"alert\"] = r[\"n\"] >= CONFIG[\"min_track_frames\"] and r[\"score\"] >= CONFIG[\"alert_threshold\"]\n",
" if r[\"alert\"]:\n",
" queue.submit(f\"{seq['id']}-{len(queue.items):03d}\", r[\"score\"], r[\"grade\"])\n",
" return trs, fixed\n",
"\n",
"\n",
"t_start = time.perf_counter()\n",
"# ---------------- 1. ํ์ต(์์ง ์ค๋น) ----------------\n",
"train = [r for s in range(0, 160) for r in tracks_of(make_seq(s))]\n",
"engine = make_pipeline(StandardScaler(), LogisticRegression(class_weight=\"balanced\", C=2.0, max_iter=2000, random_state=0))\n",
"engine.fit(X_of(train), [r[\"truth\"] in INTRUDER for r in train])\n",
"\n",
"# ---------------- 2. ์ด์ฉ ๋ฐ์ดํฐ ์ฒ๋ฆฌ(์์ ๋ฐ์ดํฐ 2๊ฑด ์ฃผ์
) ----------------\n",
"ops = [make_seq(5000 + i) for i in range(30)]\n",
"ops[3] = {**ops[3], \"cond\": \"snow\"} # ํ์ฉ๋์ง ์์ ์กฐ๊ฑด\n",
"ops[7] = {**ops[7], \"frames\": np.where((np.arange(T) % 5 == 2)[:, None, None], 0.0, ops[7][\"frames\"])} # ์ผ์ ๋จ์ ํ๋ ์\n",
"queue, rejected, all_tracks, runs, fixed_total = Queue(), {}, [], [], 0\n",
"trace_lines = set()\n",
"codes = {f.__code__ for f in (validate, repair, tracks_of, grade_of, process, X_of, Queue.submit, Queue.verify,\n",
" enhance, detect, match, in_roi, rule_class, track, track_features, track_truth, seq_tracks, center)}\n",
"lines_total = {c: {ln for _, ln in dis.findlinestarts(c) if ln != c.co_firstlineno} for c in codes}\n",
"\n",
"\n",
"def tracer(frame, event, arg):\n",
" if frame.f_code in codes:\n",
" def local(fr, ev, ar):\n",
" if ev == \"line\":\n",
" trace_lines.add((fr.f_code, fr.f_lineno))\n",
" return local\n",
" return local\n",
"\n",
"\n",
"sys.settrace(tracer)\n",
"for s in ops:\n",
" errs = validate(s)\n",
" if errs:\n",
" rejected[s[\"id\"]] = errs; continue\n",
" t0 = time.perf_counter()\n",
" trs, fx = process(s, engine, queue)\n",
" fixed_total += fx\n",
" runs.append({\"seq\": s[\"id\"], \"ms_per_frame\": (time.perf_counter() - t0) * 1000 / T, \"alerts\": sum(r[\"alert\"] for r in trs)})\n",
" all_tracks += trs\n",
"queue.verify(); grade_of(0.9); X_of(all_tracks[:1])\n",
"sys.settrace(None)\n",
"covered = sum(len({ln for c2, ln in trace_lines if c2 is c} & lines_total[c]) for c in codes)\n",
"total_lines = sum(len(v) for v in lines_total.values())\n",
"\n",
"# ---------------- 3. ์ฑ๋ฅ ํ๊ฐ ----------------\n",
"done_ids = {s[\"id\"] for s in ops} - set(rejected)\n",
"objs = {(q[\"id\"], l[\"oid\"]) for q in ops if q[\"id\"] in done_ids for labs in q[\"labels\"] for l in labs if l[\"intruder\"]}\n",
"alerts = [r for r in all_tracks if r[\"alert\"]]\n",
"far = sum(r[\"truth\"] not in INTRUDER for r in alerts) / max(len(alerts), 1)\n",
"recall = len({(r[\"seq\"], r[\"oid\"]) for r in alerts if r[\"truth\"] in INTRUDER} & objs) / max(len(objs), 1)\n",
"ms = np.mean([r[\"ms_per_frame\"] for r in runs])\n",
"\n",
"# ---------------- 4. ์์ฒด ์ ๊ฒ(ํตํฉ ํ
์คํธ) ----------------\n",
"cov = covered / total_lines\n",
"chk = {\n",
" \"์์ ๋ฐ์ดํฐ ์ฐจ๋จ\": \"CAM-5003\" in rejected,\n",
" \"์ฒ๋ฆฌ ์ํ์ค 29๊ฐ ์ ๋ ์ฑ๊ณต\": len(runs) == 29,\n",
" \"์ผ์ ๋จ์ ํ๋ ์ ๋ณด์ \": fixed_total >= 1,\n",
" \"์นจ์
๊ฐ์ฒด ์ฌํ์จ 85% ์ด์\": recall >= 0.85,\n",
" \"์ค๊ฒฝ๋ณด์จ 15% ์ดํ\": far <= 0.15,\n",
" \"์๋ ํด์ ยท์ถ๋ 0๊ฑด\": all(i[\"status\"] == \"PENDING\" for i in queue.items),\n",
" \"๊ฒฝ๋ณด ์ ๋ ๋๊ธฐ์ด ๋ฑ๋ก\": len(queue.items) == len(alerts),\n",
" \"๊ฐ์ฌ ๋ก๊ทธ ๊ฑด์ ์ผ์นยท๋ฌด๊ฒฐ\": len(queue.log) == len(queue.items) and queue.verify(),\n",
" \"ํ๋ ์๋น 50ms ์ด๋ด\": ms < 50,\n",
" \"์คํ ๋ผ์ธ ์ปค๋ฒ๋ฆฌ์ง 85% ์ด์\": cov >= 0.85,\n",
"}\n",
"passed = sum(chk.values())\n",
"for k, v in chk.items():\n",
" print(f\"[{'PASS' if v else 'FAIL'}] {k}\")\n",
"print(f\"์์ฒด ์ ๊ฒ {passed}/{len(chk)} ํต๊ณผ ({passed / len(chk):.0%}), ํ์ดํ๋ผ์ธ ์คํ ์ฑ๊ณต {'์' if len(runs) + len(rejected) == len(ops) else '์๋์ค'}, ํต์ฌ ๋ชจ๋ ์คํ ๋ผ์ธ ์ปค๋ฒ๋ฆฌ์ง {cov:.1%}\")\n",
"\n",
"# ---------------- 5. ๊ด์ ์ํฉํ ----------------\n",
"focus = max({r[\"seq\"] for r in all_tracks}, key=lambda q: sum(r[\"alert\"] for r in all_tracks if r[\"seq\"] == q))\n",
"col = {\"High\": \"tab:red\", \"Medium\": \"tab:orange\", \"Low\": \"tab:gray\"}\n",
"fig, ax = plt.subplots(2, 2, figsize=(12, 8))\n",
"s = next(x for x in ops if x[\"id\"] == focus)\n",
"ax[0, 0].imshow(s[\"frames\"][-1], cmap=\"gray\", vmin=0, vmax=255)\n",
"for r in [r for r in all_tracks if r[\"seq\"] == focus and r[\"alert\"]]:\n",
" c = np.array([center(b) for b in r[\"tr\"][\"b\"]]); ax[0, 0].plot(c[:, 0], c[:, 1], color=col[r[\"grade\"]], lw=2)\n",
"ax[0, 0].set_title(f\"{focus} alert tracks (color = grade)\"); ax[0, 0].axis(\"off\")\n",
"g = pd.Series([r[\"grade\"] for r in alerts]).value_counts().reindex([\"High\", \"Medium\"]).fillna(0)\n",
"ax[0, 1].bar(g.index, g.values, color=[col[i] for i in g.index]); ax[0, 1].set_title(\"Alerts by grade (all pending human review)\")\n",
"ax[1, 0].bar([r[\"seq\"][-2:] for r in runs], [r[\"alerts\"] for r in runs]); ax[1, 0].set_title(\"Alerts per camera sequence\")\n",
"ax[1, 1].axis(\"off\")\n",
"ax[1, 1].text(0, 0.9, f\"Intruder recall {recall:.1%}\\nFalse alarm {far:.1%}\\nQueue {len(queue.items)} pending\\n\"\n",
" f\"Latency {ms:.2f} ms/frame\\nSelf-check {passed}/{len(chk)}\", family=\"monospace\", fontsize=13, va=\"top\")\n",
"plt.suptitle(\"SEC01 integrated pipeline - synthetic training data only\"); plt.tight_layout(); plt.show()\n",
"\n",
"# ---------------- 6. ์์ค ๋ฐฉํธ ๋ธ๋ฆฌํ ์์ฝ ----------------\n",
"print(f\"\"\"\n",
"[๊ธฐ์ง CCTV ๋น์ธ๊ฐ ์ ๊ทผ ํ์ง ๋ธ๋ฆฌํ ์์ฝ - ๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
"1. ์ฒ๋ฆฌ: ์ํ์ค {len(ops)}๊ฐ ์ค {len(runs)}๊ฐ ์ฒ๋ฆฌ, ์์ ๋ฐ์ดํฐ {len(rejected)}๊ฑด ์ฐจ๋จ({', '.join(rejected)}), ์ผ์ ๋จ์ ํ๋ ์ {fixed_total}์ฅ ๋ณด์ \n",
"2. ์ฑ๋ฅ: ์นจ์
๊ฐ์ฒด ์ฌํ์จ {recall:.1%}, ์ค๊ฒฝ๋ณด์จ {far:.1%}, ํ๋ ์๋น {ms:.2f} ms, ์ ์ฒด ์์ {time.perf_counter() - t_start:.1f} s\n",
"3. ๊ฒฝ๋ณด: {len(queue.items)}๊ฑด ๊ฒฝ๊ณ๋ณ ์ก์ ํ์ธ ๋๊ธฐ (High {sum(i['grade'] == 'High' for i in queue.items)} / Medium {sum(i['grade'] == 'Medium' for i in queue.items)}), ์๋ ํด์ ยท์ถ๋ 0๊ฑด\n",
"4. ์ด์ฉ ์ ์: ๊ฒฝ๋ณด ํด์ ยท์ถ๋ ๊ฒฐ์ฌ์ ๊ฒฝ๊ณ๋ณ๊ณผ ๋น์ง ์งํ๊ด์ ์น์ธ ํ์๋ง ์ด๋ฃจ์ด์ง๋ฉฐ, ์ค์ ๋์์ ๊ฒฝ๊ณ ๊ท์ ๊ณผ ์งํ๊ณํต์ ๋ฐ๋ฅธ๋ค.\n",
"5. ํ๊ณ: ์ฝ๋๋ก ๊ทธ๋ฆฐ ํฉ์ฑ ์์ ๊ธฐ์ค ๊ฒฐ๊ณผ๋ก, ์ค์ CCTVยท๊ธฐ์ยท๋ฐฐ๊ฒฝ์์๋ ์ฌํ์ต๊ณผ ํ์ฅ ๊ฒ์ฆ์ด ํ์ํ๋ค.\"\"\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
10 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[PASS] ์์ ๋ฐ์ดํฐ ์ฐจ๋จ\n",
"[PASS] ์ฒ๋ฆฌ ์ํ์ค 29๊ฐ ์ ๋ ์ฑ๊ณต\n",
"[PASS] ์ผ์ ๋จ์ ํ๋ ์ ๋ณด์ \n",
"[PASS] ์นจ์
๊ฐ์ฒด ์ฌํ์จ 85% ์ด์\n",
"[PASS] ์ค๊ฒฝ๋ณด์จ 15% ์ดํ\n",
"[PASS] ์๋ ํด์ ยท์ถ๋ 0๊ฑด\n",
"[PASS] ๊ฒฝ๋ณด ์ ๋ ๋๊ธฐ์ด ๋ฑ๋ก\n",
"[PASS] ๊ฐ์ฌ ๋ก๊ทธ ๊ฑด์ ์ผ์นยท๋ฌด๊ฒฐ\n",
"[PASS] ํ๋ ์๋น 50ms ์ด๋ด\n",
"[PASS] ์คํ ๋ผ์ธ ์ปค๋ฒ๋ฆฌ์ง 85% ์ด์\n",
"์์ฒด ์ ๊ฒ 10/10 ํต๊ณผ (100%), ํ์ดํ๋ผ์ธ ์คํ ์ฑ๊ณต ์, ํต์ฌ ๋ชจ๋ ์คํ ๋ผ์ธ ์ปค๋ฒ๋ฆฌ์ง 89.1%\n",
"\n",
"[๊ธฐ์ง CCTV ๋น์ธ๊ฐ ์ ๊ทผ ํ์ง ๋ธ๋ฆฌํ ์์ฝ - ๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
"1. ์ฒ๋ฆฌ: ์ํ์ค 30๊ฐ ์ค 29๊ฐ ์ฒ๋ฆฌ, ์์ ๋ฐ์ดํฐ 1๊ฑด ์ฐจ๋จ(CAM-5003), ์ผ์ ๋จ์ ํ๋ ์ 2์ฅ ๋ณด์ \n",
"2. ์ฑ๋ฅ: ์นจ์
๊ฐ์ฒด ์ฌํ์จ 90.5%, ์ค๊ฒฝ๋ณด์จ 11.6%, ํ๋ ์๋น 3.48 ms, ์ ์ฒด ์์ 6.5 s\n",
"3. ๊ฒฝ๋ณด: 43๊ฑด ๊ฒฝ๊ณ๋ณ ์ก์ ํ์ธ ๋๊ธฐ (High 41 / Medium 2), ์๋ ํด์ ยท์ถ๋ 0๊ฑด\n",
"4. ์ด์ฉ ์ ์: ๊ฒฝ๋ณด ํด์ ยท์ถ๋ ๊ฒฐ์ฌ์ ๊ฒฝ๊ณ๋ณ๊ณผ ๋น์ง ์งํ๊ด์ ์น์ธ ํ์๋ง ์ด๋ฃจ์ด์ง๋ฉฐ, ์ค์ ๋์์ ๊ฒฝ๊ณ ๊ท์ ๊ณผ ์งํ๊ณํต์ ๋ฐ๋ฅธ๋ค.\n",
"5. ํ๊ณ: ์ฝ๋๋ก ๊ทธ๋ฆฐ ํฉ์ฑ ์์ ๊ธฐ์ค ๊ฒฐ๊ณผ๋ก, ์ค์ CCTVยท๊ธฐ์ยท๋ฐฐ๊ฒฝ์์๋ ์ฌํ์ต๊ณผ ํ์ฅ ๊ฒ์ฆ์ด ํ์ํ๋ค.\n",
"```\n",
" \n"
]
}
],
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}