{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ๐ŸŽฏ [SEC01] ๊ธฐ์ง€ CCTV ๋“œ๋ก ยท๋น„์ธ๊ฐ€ ์ ‘๊ทผ ๊ฐ์ฒด ํƒ์ง€ ํ”„๋กœํ† ํƒ€์ž…\n", "### Google Colab ์‹ค์Šต ํ™˜๊ฒฝ (์‹œ์„คยท๋ฐฉํ˜ธยท๊ฒฝ๊ณ„ / Level 3)\n", "\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](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 }