{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# ๐ฏ [ISR01] ๊ณต๊ฐ ์์ฑ์์ ์ฅ๋นยท์ฐจ๋ ๊ฐ์ฒด๋ถ๋ฅ ์ ํ๋ ํ๊ฐ\n",
"### Google Colab ์ค์ต ํ๊ฒฝ (์ ๋ณดยท๊ฐ์ยท์ ์ฐฐ(ISR) / Level 3)\n",
"\n",
"[](https://colab.research.google.com/github/edunlab/milicontent2/blob/main/uploads/ISR01/ISR01_solution.ipynb)\n",
"\n",
"---\n",
"\n",
"## ๐ 1. ํ๋ก์ ํธ ๊ฐ์ ๋ฐ ๋ฐฐ๊ฒฝ\n",
"\n",
"| ํญ๋ชฉ | ๋ด์ฉ |\n",
"| :--- | :--- |\n",
"| **๊ณผ์ ๋ถ๋ฅ** | ์ ๋ณดยท๊ฐ์ยท์ ์ฐฐ(ISR) (Level 3) |\n",
"| **ํ์ต ์์ ์๊ฐ** | 10๊ฐ ๋ฏธ์
(5์ผ / ์ด 27.5์๊ฐ) |\n",
"| **ํ๊ฒฝ ์ฌ์** | Python 3.10+, numpy, pandas, matplotlib, scipy, scikit-learn |\n",
"\n",
"### ๐ ํ๋ก์ ํธ ๋ชฉํ\n",
"๋ฌ์์-์ฐํฌ๋ผ์ด๋ ์ ์์์๋ ์์ฉยท๊ณต๊ฐ ์์ฑ์์์ด ๋๋์ผ๋ก ๊ณต๊ฐ๋๋ฉด์ ์์์์ ์ฅ๋น์ ์ฐจ๋์ ๋น ๋ฅด๊ฒ ๋ถ๋ฅํ๋ ๋ฅ๋ ฅ๊ณผ ํจ๊ป, ์์ฅ๋งยท์๋ชฉ ์ฐจํยท๊ตฌ๋ฆยท๋ชจ์ ์์ฅ ํ์ ๋๋ฌธ์ ์๊ธฐ๋ ์คํ๋
์ ๊ฑธ๋ฌ๋ด๋ ๋ฅ๋ ฅ์ด ์ค์ํด์ก๋ค. ๋ณธ ํ๋ก์ ํธ์์๋ ์ค์ ์์ฑ์์ ๋์ ์ฝ๋๋ก ๋ง๋ ๊ต์ก์ฉ ํฉ์ฑ ์์(๊ดํยทSAR ํ ์)์ ์ฌ์ฉํด ๋ฉํ๋ฐ์ดํฐ ์คํค๋ง ์ ์, ํด์๋ยท๊ตฌ๋ฆยท๋ฐฉ์ ์ ๊ทํ ์ ์ฒ๋ฆฌ, 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. ์์ฉ ์์ฑ(๊ดํ/SAR) ์์ ํน์ฑ ๋ถ์ ๋ฐ ๊ตฐ์ฉ ์ฅ๋น/์ฐจ๋ ๋ฉํ๋ฐ์ดํฐ ๊ท๊ฒฉ ์ ์\n",
"**์์์๊ฐ**: ์ฝ 1.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์คํ์์ค ๊ดํ ๋ฐ SAR ์์ฑ์์์ ํด์๋, ๋ฉํ๋ฐ์ดํฐ, ๊ตฐ์ฉ ์ฅ๋น/์ฐจ๋(์ ์ฐจ, ์์ฃผํฌ, ํธ๋ญ) ๋ถ๋ฅ ๊ธฐ์ค์ ๋ถ์ํ๊ณ ๋ฐ์ดํฐ์
์คํค๋ง๋ฅผ ์ค๊ณํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๊ณต๊ฐยท์์ฉ ๊ดํ ๋ฐ SAR ์์ฑ์์์ ํด์๋, ์ดฌ์ ์กฐ๊ฑด, ์ถ์ฒ ๋ฉํ๋ฐ์ดํฐ์ ๊ตฐ์ฉ ์ฅ๋นยท์ฐจ๋(์ ์ฐจ, ์์ฃผํฌ, ํธ๋ญ) ๋ถ๋ฅ ๊ธฐ์ค์ ์ ๋ฆฌํด ๋ฐ์ดํฐ์
์คํค๋ง์ ๊ฒ์ฆ๊ธฐ๋ฅผ ๋ง๋ ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์์ ์ถ์ฒ(์ ๊ณต์ฒยท๋ผ์ด์ ์คยท์ดฌ์ ์๊ฐ), ์ผ์(๊ดํ/SAR, ์ง์ ํด์๋, ๋น์ค๋ฌ ์ดฌ์ ๊ฐ๋, ํ์ ๊ณ ๋, ๊ตฌ๋ฆ ๋น์จ), ์ ์ ํ๋ผ๋ฏธํฐ(๊ธฐ์, ์์น ์ค์ฐจ, ์ ๋ฌ ์ง์ฐ), ๊ฐ์ฒด ๋ผ๋ฒจ(๊ธฐ์ข
, ์์น, ๋ฐฉ์, ์ํ ์ํ, ํ๋
์ํ) ํ๋ ์ ์ํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `data/schema/isr01_input_schema.json ๋ฐ docs/isr01_data_spec.md` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์ ๋ณดยท๊ฐ์ยท์ ์ฐฐ(ISR) ์ค๋ฌด ๋ฐ ๋ฌ์์-์ฐํฌ๋ผ์ด๋ ์ ์ ์ ์ฅ ํ๊ฒฝ๊ณผ์ ๊ตฐ์ฌ์ ๋ถํฉ์ฑ\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": [
"\"\"\"๊ณต๊ฐยท์์ฉ ์์ฑ์์(๊ดํ/SAR)์ ํด์๋ยท์ดฌ์ ์กฐ๊ฑด ๋ฉํ๋ฐ์ดํฐ์ ๊ตฐ์ฉ ์ฅ๋นยท์ฐจ๋(์ ์ฐจ, ์์ฃผํฌ, ํธ๋ญ) ๋ผ๋ฒจ์ ๋ฐ์ดํฐ์
์คํค๋ง๋ฅผ ์ ์ํ๊ณ , ๊ฐ์ ์ํ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ฆํ๋ค.\"\"\"\n",
"import json\n",
"import re\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# 1) ์คํค๋ง ์ ์: ์์ ์ถ์ฒยท๋ผ์ด์ ์ค, ์ผ์ยท์ดฌ์ ์กฐ๊ฑด, ์ ์ ํ๋ผ๋ฏธํฐ, ๊ฐ์ฒด ๋ผ๋ฒจ(ํด๋์คยท์์นยท๋ฐฉ์ยท์ํ ์ํ)\n",
"SCHEMA = {\n",
" \"required\": [\"image_id\", \"source\", \"sensor\", \"tactical\", \"labels\"],\n",
" \"fields\": {\n",
" \"image_id\": {\"type\": str, \"pattern\": r\"^IMG\\d{4}$\"},\n",
" },\n",
" \"source\": {\n",
" \"provider\": {\"type\": str, \"enum\": [\"open_data\", \"commercial_sample\", \"partner_share\"]},\n",
" \"license\": {\"type\": str, \"pattern\": r\"^\\S.{2,}$\"},\n",
" \"acquired_at\": {\"type\": str, \"pattern\": r\"^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}Z$\"},\n",
" },\n",
" \"sensor\": {\n",
" \"type\": {\"type\": str, \"enum\": [\"optical\", \"SAR\"]},\n",
" \"gsd_m\": {\"type\": (int, float), \"min\": 0.1, \"max\": 30},\n",
" \"off_nadir_deg\": {\"type\": (int, float), \"min\": 0, \"max\": 60},\n",
" \"sun_elevation_deg\": {\"type\": (int, float), \"min\": -10, \"max\": 90},\n",
" \"cloud_cover_pct\": {\"type\": (int, float), \"min\": 0, \"max\": 100},\n",
" },\n",
" \"tactical\": {\n",
" \"weather\": {\"type\": str, \"enum\": [\"clear\", \"haze\", \"cloudy\", \"snow\"]},\n",
" \"geo_error_m\": {\"type\": (int, float), \"min\": 0, \"max\": 1000},\n",
" \"delivery_latency_h\": {\"type\": (int, float), \"min\": 0, \"max\": 720},\n",
" },\n",
" \"label\": {\n",
" \"class\": {\"type\": str, \"enum\": [\"tank\", \"spg\", \"truck\", \"civil_vehicle\", \"decoy\", \"unknown\"]},\n",
" \"bbox\": {\"type\": list, \"length\": 4},\n",
" \"orientation_deg\": {\"type\": (int, float), \"min\": 0, \"max\": 360},\n",
" \"concealment\": {\"type\": str, \"enum\": [\"none\", \"camo_net\", \"tree\", \"cloud\", \"partial\"]},\n",
" \"status\": {\"type\": str, \"enum\": [\"auto_draft\", \"analyst_verified\"]},\n",
" },\n",
"}\n",
"\n",
"\n",
"def check(name, value, rule):\n",
" \"\"\"๊ฐ ํ๋๋ฅผ ๊ท์น(type, enum, min, max, length, pattern)์ผ๋ก ๊ฒ์ฌํด ์ค๋ฅ ๋ชฉ๋ก์ ๋๋ ค์ค๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 1] ์์ฉ ์์ฑ(๊ดํ/SAR) ์์ ํน์ฑ ๋ถ์ ๋ฐ ๊ตฐ์ฉ ์ฅ๋น/์ฐจ๋ ๋ฉํ๋ฐ์ดํฐ ๊ท๊ฒฉ ์ ์ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def validate(rec):\n",
" # [TODO : ํ์ ์ค์ต 1] ์์ฉ ์์ฑ(๊ดํ/SAR) ์์ ํน์ฑ ๋ถ์ ๋ฐ ๊ตฐ์ฉ ์ฅ๋น/์ฐจ๋ ๋ฉํ๋ฐ์ดํฐ ๊ท๊ฒฉ ์ ์ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"rng = np.random.default_rng(1)\n",
"classes = SCHEMA[\"label\"][\"class\"][\"enum\"]\n",
"records = []\n",
"for i in range(20):\n",
" sensor = str(rng.choice([\"optical\", \"SAR\"]))\n",
" labels = [{\"class\": str(rng.choice(classes)), \"bbox\": [int(rng.integers(0, 480)), int(rng.integers(0, 480)), 32, 32],\n",
" \"orientation_deg\": float(round(rng.uniform(0, 360), 1)),\n",
" \"concealment\": str(rng.choice(SCHEMA[\"label\"][\"concealment\"][\"enum\"])), \"status\": \"auto_draft\"} for _ in range(3)]\n",
" records.append({\"image_id\": f\"IMG{i:04d}\",\n",
" \"source\": {\"provider\": str(rng.choice(SCHEMA[\"source\"][\"provider\"][\"enum\"])), \"license\": \"CC-BY-4.0(๊ฐ์)\",\n",
" \"acquired_at\": f\"2026-0{int(rng.integers(1, 9))}-1{int(rng.integers(0, 9))}T0{int(rng.integers(0, 9))}:30Z\"},\n",
" \"sensor\": {\"type\": sensor, \"gsd_m\": float(round(rng.uniform(0.3, 3.0), 2)), \"off_nadir_deg\": float(round(rng.uniform(0, 40), 1)),\n",
" \"sun_elevation_deg\": float(round(rng.uniform(15, 70), 1)), \"cloud_cover_pct\": float(round(rng.uniform(0, 60), 1))},\n",
" \"tactical\": {\"weather\": str(rng.choice([\"clear\", \"haze\", \"cloudy\", \"snow\"])),\n",
" \"geo_error_m\": float(round(rng.uniform(1, 30), 1)), \"delivery_latency_h\": float(round(rng.uniform(1, 72), 1))},\n",
" \"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[\"sensor\"][\"gsd_m\"] = 0.0\n",
"bad[\"sensor\"][\"type\"] = \"IR\"\n",
"del bad[\"tactical\"][\"geo_error_m\"]\n",
"bad[\"labels\"][0][\"class\"] = \"bmp\"\n",
"bad[\"labels\"][1][\"bbox\"] = [1, 2, 3]\n",
"print(\"\\n[์ค๋ฅ ์ฃผ์
๊ฒฐ๊ณผ] ์ฃผ์
5์ข
\")\n",
"for e in validate(bad):\n",
" print(\" -\", e)\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 (\"source\", \"sensor\", \"tactical\", \"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",
"spec = pd.DataFrame(rows, columns=[\"๊ทธ๋ฃน\", \"ํ๋\", \"์๋ฃํ\", \"๊ท์น\"])\n",
"print(\"\\n[๋ฐ์ดํฐ ๋ช
์ธ ์์ฝ]\")\n",
"print(spec.to_string(index=False))\n",
"print(f\"\\nํ๋ ์ ์ {len(spec)}๊ฐ(์์ฑ๋ 100%). ๋ผ๋ฒจ status๋ 'auto_draft'๋ก ์์ฑ๋๊ณ ์์๋ถ์๊ด ํ๋
ํ์๋ง 'analyst_verified'๋ก ๋ฐ๋๋ค.\")\n",
"print(\"๋ถ๋ฅ ๊ธฐ์ค: tank(ํฌํ+๊ธด ํฌ์ ), spg(ํ๋ฐฉ ๋ํ ํฌํ+๋งค์ฐ ๊ธด ํฌ์ ), truck(์ด์ ์+์ ์ฌํจ ๋ถ๋ฆฌ), civil_vehicle(์ํยท๋ฐ์/์ด๋์ด ๋จ์), decoy(๋ชจ์ ์์ฅ ํ์ )\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
01 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"์ ์ ์ํ 20๊ฑด ์ค ๊ฒ์ฆ ํต๊ณผ 20๊ฑด, ์ค๋ฅ 0๊ฑด (๋ชฉํ: ์ค๋ฅ 0๊ฑด ๋ฌ์ฑ)\n",
"\n",
"[์ค๋ฅ ์ฃผ์
๊ฒฐ๊ณผ] ์ฃผ์
5์ข
\n",
" - sensor.type: ํ์ฉ๋์ง ์์ ๊ฐ 'IR'\n",
" - sensor.gsd_m: ์ต์๊ฐ 0.1 ๋ฏธ๋ง\n",
" - tactical.geo_error_m: ํ์ ํ๋ ๋๋ฝ\n",
" - labels[0].class: ํ์ฉ๋์ง ์์ ๊ฐ 'bmp'\n",
" - labels[1].bbox: ๊ธธ์ด 4 ํ์\n",
"\n",
"[๋ฐ์ดํฐ ๋ช
์ธ ์์ฝ]\n",
" ๊ทธ๋ฃน ํ๋ ์๋ฃํ ๊ท์น\n",
" ๊ธฐ๋ณธ image_id str {'pattern': '^IMG\\\\d{4}$'}\n",
" source provider str {'enum': ['open_data', 'commercial_sample', 'partner_share']}\n",
" source license str {'pattern': '^\\\\S.{2,}$'}\n",
" source acquired_at str {'pattern': '^\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}:\\\\d{2}Z$'}\n",
" sensor type str {'enum': ['optical', 'SAR']}\n",
" sensor gsd_m number {'min': 0.1, 'max': 30}\n",
" sensor off_nadir_deg number {'min': 0, 'max': 60}\n",
" sensor sun_elevation_deg number {'min': -10, 'max': 90}\n",
" sensor cloud_cover_pct number {'min': 0, 'max': 100}\n",
"tactical weather str {'enum': ['clear', 'haze', 'cloudy', 'snow']}\n",
"tactical geo_error_m number {'min': 0, 'max': 1000}\n",
"tactical delivery_latency_h number {'min': 0, 'max': 720}\n",
" label class str {'enum': ['tank', 'spg', 'truck', 'civil_vehicle', 'decoy', 'unknown']}\n",
" label bbox list {'length': 4}\n",
" label orientation_deg number {'min': 0, 'max': 360}\n",
" label concealment str {'enum': ['none', 'camo_net', 'tree', 'cloud', 'partial']}\n",
" label status str {'enum': ['auto_draft', 'analyst_verified']}\n",
"\n",
"ํ๋ ์ ์ 17๊ฐ(์์ฑ๋ 100%). ๋ผ๋ฒจ status๋ 'auto_draft'๋ก ์์ฑ๋๊ณ ์์๋ถ์๊ด ํ๋
ํ์๋ง 'analyst_verified'๋ก ๋ฐ๋๋ค.\n",
"๋ถ๋ฅ ๊ธฐ์ค: tank(ํฌํ+๊ธด ํฌ์ ), spg(ํ๋ฐฉ ๋ํ ํฌํ+๋งค์ฐ ๊ธด ํฌ์ ), truck(์ด์ ์+์ ์ฌํจ ๋ถ๋ฆฌ), civil_vehicle(์ํยท๋ฐ์/์ด๋์ด ๋จ์), decoy(๋ชจ์ ์์ฅ ํ์ )\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
02. ์์ฑ์์ ํด์๋ ํ์คํ, ๊ตฌ๋ฆ ์ฐจํ ์ ๊ฑฐ ๋ฐ ๋ฐฉ์/์ค์ผ์ผ ์ ๊ทํ ์ ์ฒ๋ฆฌ ๋ชจ๋ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๊ตฌ๋ฆ ์ฐจํ, ๊ทธ๋ฆผ์ ์๊ณก, ์์ฑ ์ดฌ์ ๊ฐ๋์ ๋ฐ๋ฅธ ๊ธฐํํ์ ์๊ณก์ ๋ณด์ ํ๊ณ ์์ ํด์๋๋ฅผ ํ์คํํ๋ ์ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ์ ๊ตฌ์ถํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๊ตฌ๋ฆ ์ฐจํ, ๋น์ค๋ฌ ์ดฌ์ ์๊ณก, ํด์๋ ์ฐจ์ด, ๊ฒฐ์ธก ํฝ์
์ ๋ณด์ ํ๊ณ ๊ฐ์ฒด ๋ฐฉ์์ ์ถ์ฒ์ ์ ๊ทํํ๋ ์์ฑ์์ ์ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ์ ๊ตฌ์ถํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ง์ ํด์๋์ ์ดฌ์ ๊ฐ๋ ๋ฉํ๋ฐ์ดํฐ๋ก ์์์ ํ์ค ํด์๋(0.5m/ํฝ์
, ๊ฐ์ ๊ธฐ์ค)๋ก ์ฌํ๋ณธํํ๊ณ , ๊ดํ์์ ๋ฐ๊ณ ๋งค๋ํ์ง๋ง SAR์๋ ๋ฐ์ฌ๊ฐ ์๋ ์์ญ์ ๊ตฌ๋ฆ์ผ๋ก ์ฐพ์ ๋ฐฐ๊ฒฝ๊ฐ์ผ๋ก ์ฑ์ด๋ค ๊ณผ์ ์ ์งํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 5๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/preprocess/isr01_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": [
"\"\"\"์์ฑ์์ ์กฐ๊ฐ์ ํด์๋(GSD)ยท๋น์ค๋ฌ ์ดฌ์ ์๊ณก์ ํ์คํํ๊ณ , ๊ตฌ๋ฆ ์ฐจํ ์์ญ ์ฒ๋ฆฌ, ๊ฒฐ์ธก ํฝ์
๋ณด์ , ๊ฐ์ฒด ์ฃผ์ถ ๊ธฐ์ค ๋ฐฉ์ ์ ๊ทํ, 0~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",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํฉ์ฑ ์์ฑ์์ ์กฐ๊ฐ(chip) ์์ฑ๊ธฐ (์ค์ ์์ฑ์์ยท์ฅ๋น ์ ์ยท์์น ์์)\n",
"# 32x32 ๊ดํ(EO) ์์๊ณผ SAR ์์ ํ ์์ ์ถ์ ๋ํ์ผ๋ก ๋ง๋ ๊ฐ์ฒด๋ฅผ ๊ทธ๋ฆฐ๋ค. ๋ํ ์น์๋ ๊ฐ์๊ฐ์ด๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"N = 32\n",
"STD_GSD = 0.5 # ํ์ค ํด์๋(m/ํฝ์
, ๊ฐ์ ๊ธฐ์ค)\n",
"CLASSES = [\"tank\", \"spg\", \"truck\", \"civil\"]\n",
"MILITARY = {\"tank\", \"spg\", \"truck\"}\n",
"# ๊ฐ์ฒด ์ขํ๊ณ(u: ๊ธธ์ด ๋ฐฉํฅ, v: ํญ ๋ฐฉํฅ, ํ์ค ํด์๋ ํฝ์
) ๊ฐ์ ๋ํ: (u์ค์ฌ, v์ค์ฌ, ๊ธธ์ด, ํญ, ๊ดํ ๋ฐ๊ธฐ ์ฐจ์ด)\n",
"SHAPES = {\n",
" \"tank\": [(0, 0, 11, 7, -40), (0.5, 0, 5, 5, -55), (6, 0, 7, 1.2, -60)],\n",
" \"spg\": [(0, 0, 16, 7.5, -38), (-4, 0, 6, 6.5, -52), (5, 0, 13, 1.4, -60)],\n",
" \"truck\": [(5, 0, 3.5, 5.5, -45), (-1.8, 0, 8.5, 6, -25)],\n",
" \"civil\": [(0, 0, 8, 3.8, 0), (1.2, 0, 2, 3.4, 0)],\n",
" \"decoy\": [(0, 0, 11, 7, -40), (0.5, 0, 5, 5, -42), (6, 0, 7, 1.2, -45)],\n",
" \"none\": [], # ๊ฐ์ฒด ์๋ ๋ฐฐ๊ฒฝ ํ์ผ\n",
"}\n",
"SAR_RETURN = {\"tank\": 0.85, \"spg\": 0.85, \"truck\": 0.7, \"civil\": 0.45, \"decoy\": 0.18, \"none\": 0.12} # ๊ธ์ ๋ฐ์ฌ ์ธ๊ธฐ(๊ฐ์)\n",
"\n",
"\n",
"def _mask(cls, angle, scale, squash=1.0):\n",
" \"\"\"ํ์ (angle, ๋)ยท์ถ์ฒ(scale)ยท๋น์ค๋ฌ ์ดฌ์ ์์ถ(squash)์ ์ ์ฉํด ๋ํ๋ณ ๋ง์คํฌ์ ๋ฐ๊ธฐ ์ฐจ์ด๋ฅผ ๋ง๋ ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 2] ์์ฑ์์ ํด์๋ ํ์คํ, ๊ตฌ๋ฆ ์ฐจํ ์ ๊ฑฐ ๋ฐ ๋ฐฉ์/์ค์ผ์ผ ์ ๊ทํ ์ ์ฒ๋ฆฌ ๋ชจ๋ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_chip(cls, rng, cond=None):\n",
" \"\"\"chip 1๊ฐ = (๊ดํ 32x32, SAR 32x32, ๋ฉํ๋ฐ์ดํฐ). cond: camo, occl, cloud, low_sun, off_nadir, low_res, speckle\"\"\"\n",
" c = {\"camo\": 0.0, \"occl\": 0.0, \"cloud\": 0.0, \"low_sun\": False, \"off_nadir\": 0.0, \"low_res\": False, \"speckle\": 1.0, **(cond or {})}\n",
" gsd = rng.uniform(0.9, 1.2) if c[\"low_res\"] else rng.uniform(0.4, 0.6)\n",
" scale = STD_GSD / gsd\n",
" angle, off = rng.uniform(0, 360), c[\"off_nadir\"] or rng.uniform(0, 15)\n",
" squash = np.cos(np.radians(off))\n",
" sun_el = rng.uniform(12, 22) if c[\"low_sun\"] else rng.uniform(40, 70)\n",
" sun_az = rng.uniform(0, 360)\n",
" bg = 115 + ndimage.gaussian_filter(rng.normal(0, 30, (N, N)), 2) + rng.normal(0, 5, (N, N))\n",
" parts = _mask(cls, angle, scale, squash)\n",
" body = np.any([m for m, _ in parts], axis=0) if parts else np.zeros((N, N), bool)\n",
" opt = bg.copy()\n",
" # ๊ทธ๋ฆผ์: ํ์ ๋ฐ๋ ๋ฐฉํฅ์ผ๋ก ๋ฐ๋ฆฐ ๊ฐ์ฒด ๋ชจ์(ํ์ ๊ณ ๋๊ฐ ๋ฎ์์๋ก ๊ธธ๋ค)\n",
" L = min(8.0, 2.5 / np.tan(np.radians(sun_el))) * scale\n",
" sh = ndimage.shift(body.astype(float), (L * np.cos(np.radians(sun_az)), -L * np.sin(np.radians(sun_az))), order=0) > 0.5\n",
" opt[sh & ~body] -= 35 if cls != \"decoy\" else 25\n",
" if cls == \"civil\":\n",
" base = rng.choice([200.0, 60.0]) # ๋ฏผ๊ฐ ์ฐจ๋ ์์: ๋ฐ๊ฑฐ๋ ์ด๋์\n",
" opt[body] = base + rng.normal(0, 8, body.sum())\n",
" roof = parts[1][0]\n",
" opt[roof] = base * 0.8\n",
" else:\n",
" ref = bg[body].mean() if body.any() else bg.mean()\n",
" for m, d in parts:\n",
" opt[m] = ref + d + rng.normal(0, 4 if cls != \"decoy\" else 2, m.sum())\n",
" # ์์ฅ๋ง: ๊ฐ์ฒด ์ฃผ๋ณ์ ๋ฐฐ๊ฒฝ ์ง๊ฐ์ผ๋ก ๋ฎ์ด ๋๋น๋ฅผ ๋ฎ์ถ๋ค\n",
" if c[\"camo\"] > 0 and cls != \"civil\":\n",
" net = ndimage.binary_dilation(body, iterations=2)\n",
" tex = 105 + ndimage.gaussian_filter(rng.normal(0, 25, (N, N)), 1)\n",
" opt[net] = (1 - c[\"camo\"]) * opt[net] + c[\"camo\"] * tex[net]\n",
" # ์๋ชฉ ์ฐจํ: ๊ฐ์ฒด์ ์ผ๋ถ๋ฅผ ์ด๋์ด ๋๋ฌด ๋ฉ์ด๋ฆฌ๋ก ๊ฐ๋ฆฐ๋ค\n",
" if c[\"occl\"] > 0:\n",
" trees = ndimage.gaussian_filter(rng.random((N, N)), 2)\n",
" near = ndimage.binary_dilation(body, iterations=3)\n",
" cover = near & (trees > np.quantile(trees[body], 1 - c[\"occl\"])) if body.any() else np.zeros((N, N), bool)\n",
" opt[cover] = 70 + rng.normal(0, 6, cover.sum())\n",
" # ๊ตฌ๋ฆ: ๋ฐ๊ณ ๋งค๋ํ ๋ฉ์ด๋ฆฌ(๊ดํ๋ง ๊ฐ๋ฆผ, SAR๋ ํต๊ณผ)\n",
" cmask = np.zeros((N, N), bool)\n",
" if c[\"cloud\"] > 0:\n",
" blob = ndimage.gaussian_filter(rng.random((N, N)), 4)\n",
" cmask = blob > np.quantile(blob, 1 - c[\"cloud\"])\n",
" opt[cmask] = 0.25 * opt[cmask] + 0.75 * (235 + rng.normal(0, 3, cmask.sum()))\n",
" # SAR: ๊ณฑ์
ํ ์คํํด ์ก์ + ๊ธ์ ๋ฐ์ฌ(๋ชจ์๋ฆฌ ์ด์ค ๋ฐ์ฌ๋ก ๋ฐ์ ์ )\n",
" sar = np.full((N, N), 0.12)\n",
" sar[body] = SAR_RETURN[cls] * rng.uniform(0.8, 1.2)\n",
" edge = body & ~ndimage.binary_erosion(body)\n",
" sar[edge] *= 1.3 if cls in MILITARY else 1.0\n",
" sar = sar * rng.gamma(4 / c[\"speckle\"], c[\"speckle\"] / 4, (N, N))\n",
" if c[\"low_res\"]: # ์ ํด์๋: ์ ๋ณด ์์ค(๋ธ๋ฌ)\n",
" down = lambda a: np.kron(a.reshape(N // 2, 2, N // 2, 2).mean((1, 3)), np.ones((2, 2)))\n",
" opt, sar = ndimage.gaussian_filter(down(opt), 0.7), ndimage.gaussian_filter(down(sar), 0.7)\n",
" meta = {\"cls\": cls, \"gsd\": round(float(gsd), 2), \"off_nadir\": round(float(off), 1), \"sun_el\": round(float(sun_el), 1),\n",
" \"sun_az\": round(float(sun_az), 1), \"angle\": round(float(angle), 1), \"cloud_frac\": float(cmask.mean()),\n",
" \"cloud_on_obj\": float((cmask & body).sum() / max(body.sum(), 1)), **{k: c[k] for k in (\"camo\", \"occl\", \"low_sun\", \"low_res\")}}\n",
" return np.clip(opt, 0, 255), sar, meta\n",
"\n",
"\n",
"SCEN = { # ์ํยท์ดฌ์ ์กฐ๊ฑด ์๋๋ฆฌ์ค 10์ข
\n",
" \"clean\": {}, \"camo\": {\"camo\": 0.7}, \"occl30\": {\"occl\": 0.3}, \"occl50\": {\"occl\": 0.5}, \"cloud\": {\"cloud\": 0.25},\n",
" \"low_sun\": {\"low_sun\": True}, \"off_nadir\": {\"off_nadir\": 35.0}, \"low_res\": {\"low_res\": True},\n",
" \"speckle\": {\"speckle\": 3.0}, \"camo_occl\": {\"camo\": 0.6, \"occl\": 0.3}}\n",
"\n",
"\n",
"def make_dataset(n=400, seed=7, scen=(\"clean\",), classes=CLASSES):\n",
" rng = np.random.default_rng(seed)\n",
" O, S, M = np.zeros((n, N, N)), np.zeros((n, N, N)), []\n",
" for i in range(n):\n",
" cls, sc = classes[i % len(classes)], scen[(i // len(classes)) % len(scen)]\n",
" O[i], S[i], m = make_chip(cls, rng, SCEN[sc])\n",
" m.update({\"id\": f\"IMG{i:04d}\", \"scen\": sc})\n",
" M.append(m)\n",
" return O, S, M\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ์ ์ฒ๋ฆฌ(ํด์๋ยท๊ธฐํ ๋ณด์ , ๊ตฌ๋ฆ ์ฐจํ ์ฒ๋ฆฌ, ๋ฐฉ์ ์ ๊ทํ) ๊ณตํต ํจ์ ----\n",
"def _fit(a, fill):\n",
" \"\"\"์ค์ ๊ธฐ์ค์ผ๋ก 32x32์ ๋ง๊ฒ ์๋ฅด๊ฑฐ๋ ์ฑ์ด๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 2] ์์ฑ์์ ํด์๋ ํ์คํ, ๊ตฌ๋ฆ ์ฐจํ ์ ๊ฑฐ ๋ฐ ๋ฐฉ์/์ค์ผ์ผ ์ ๊ทํ ์ ์ฒ๋ฆฌ ๋ชจ๋ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def cloud_mask(opt, sm, min_area=15):\n",
" \"\"\"๊ดํ์์ ๋ฐ๊ณ (185 ์ด๊ณผ) ๋งค๋ํ๋ฐ SAR์๋ ๋ฐ์ฌ๊ฐ ์๋ ์์ญ์ ๊ตฌ๋ฆ์ผ๋ก ๋ณธ๋ค(SAR๋ ๊ตฌ๋ฆ์ ํต๊ณผํ๋ฏ๋ก ๋ฐ์ ์ฐจ๋๊ณผ ๊ตฌ๋ถ๋๋ค).\"\"\"\n",
" bright = opt > 185\n",
" if bright.sum() < min_area:\n",
" return np.zeros_like(bright)\n",
" local_sd = np.sqrt(np.maximum(ndimage.uniform_filter(opt ** 2, 3) - ndimage.uniform_filter(opt, 3) ** 2, 0))\n",
" cand = bright & (local_sd < 12) & (sm < 0.3)\n",
" lab, n = ndimage.label(cand)\n",
" if n == 0:\n",
" return cand\n",
" sizes = ndimage.sum(cand.astype(float), lab, range(1, n + 1))\n",
" return ndimage.binary_dilation(np.isin(lab, 1 + np.where(sizes >= min_area)[0]), iterations=2) & (opt > 140)\n",
"\n",
"\n",
"def orientation(opt, sm):\n",
" \"\"\"SAR ๋ฐ์ฌ(์์ฅยท๊ตฌ๋ฆ ์ํฅ ์ ์, sm์ ํํํํ SAR)์ ๊ดํ ๋๋น๋ฅผ ํฉ์น ๊ฐ์ฒด ๋ง์คํฌ์ ์ฃผ์ถ ๋ฐฉํฅ(๋).\"\"\"\n",
" m = (sm > 0.3) | (np.abs(opt - opt.mean()) > 30)\n",
" lab, n = ndimage.label(m)\n",
" if n == 0:\n",
" return 0.0, m\n",
" c = lab[N // 2 - 6:N // 2 + 6, N // 2 - 6:N // 2 + 6]\n",
" k = np.bincount(c[c > 0]).argmax() if (c > 0).any() else 1 + int(np.argmax(ndimage.sum(m.astype(float), lab, range(1, n + 1))))\n",
" m = lab == k\n",
" yy, xx = np.nonzero(m)\n",
" if len(yy) < 5:\n",
" return 0.0, m\n",
" dy, dx = yy - yy.mean(), xx - xx.mean() # 2์ฐจ ๋ชจ๋ฉํธ๋ก ์ฃผ์ถ ๊ฐ๋ ๊ณ์ฐ\n",
" return float(np.degrees(0.5 * np.arctan2(2 * (dx * dy).mean(), (dx * dx).mean() - (dy * dy).mean()))), m\n",
"\n",
"\n",
"_YY, _XX = np.mgrid[0:N, 0:N] - (N - 1) / 2\n",
"\n",
"\n",
"def warp(stack, zy, zx, ang):\n",
" \"\"\"์ฌ๋ฌ ์ฅ(B,์ฑ๋,32,32)์ ์ฅ๋ณ ํ๋ยท์ถ์(zy, zx)์ ํ์ (ang, ๋)์ ํ ๋ฒ์ ์์ ํ ๋ณด๊ฐ์ผ๋ก ์ ์ฉํ๋ค(๋ฒกํฐ ์ฐ์ฐ).\"\"\"\n",
" a = np.radians(ang)[:, None, None]\n",
" zy, zx = np.asarray(zy)[:, None, None], np.asarray(zx)[:, None, None]\n",
" sy = np.clip((np.cos(a) * _YY + np.sin(a) * _XX) / zy + (N - 1) / 2, 0, N - 1.001) # ์ถ๋ ฅ ์ขํ -> ์๋ณธ ์ขํ\n",
" sx = np.clip((-np.sin(a) * _YY + np.cos(a) * _XX) / zx + (N - 1) / 2, 0, N - 1.001)\n",
" y0, x0 = sy.astype(int), sx.astype(int)\n",
" fy, fx = (sy - y0).reshape(len(stack), 1, -1), (sx - x0).reshape(len(stack), 1, -1)\n",
" flat = stack.reshape(len(stack), stack.shape[1], -1)\n",
" lin = (y0 * N + x0).reshape(len(stack), 1, -1)\n",
" g = lambda d: np.take_along_axis(flat, np.broadcast_to(lin + d, (len(stack), stack.shape[1], lin.shape[2])), axis=2)\n",
" out = g(0) * (1 - fy) * (1 - fx) + g(N) * fy * (1 - fx) + g(1) * (1 - fy) * fx + g(N + 1) * fy * fx\n",
" return out.reshape(stack.shape)\n",
"\n",
"\n",
"def preprocess_batch(O, S, M):\n",
" \"\"\"์ฌ๋ฌ ์ฅ์ ํ ๋ฒ์ ์ ์ฒ๋ฆฌํ๋ค. 0) ๊ฒฐ์ธก ๋ณด์ 1) ํด์๋ยท๋น์ค๋ฌ ์ดฌ์ ๋ณด์ (ํ์ค 0.5m/ํฝ์
)\n",
" 2) ๊ตฌ๋ฆ ์ฐจํ ์์ญ์ ๋ฐฐ๊ฒฝ๊ฐ์ผ๋ก ์ฑ์ 3) ๊ฐ์ฒด ์ฃผ์ถ์ ๊ฐ๋ก๋ก ํ์ 4) 0~1 ์ ๊ทํ.\"\"\"\n",
" src = np.stack([np.asarray(O, float), np.asarray(S, float)], axis=1)\n",
" if np.isnan(src).any(): # 0) ์ผ์ ๊ฒฐ์ธก ํฝ์
๋ณด์ \n",
" mean = np.nanmean(src, axis=(2, 3), keepdims=True)\n",
" src = np.where(np.isnan(src), mean, src)\n",
" zy = np.array([m[\"gsd\"] / STD_GSD / np.cos(np.radians(m[\"off_nadir\"])) for m in M])\n",
" zx = np.array([m[\"gsd\"] / STD_GSD for m in M])\n",
" out = warp(src, zy, zx, np.zeros(len(M)))\n",
" sm = ndimage.uniform_filter(out[:, 1], (1, 3, 3))\n",
" angs, cloud = np.zeros(len(M)), np.zeros(len(M))\n",
" for i in range(len(M)):\n",
" cm = cloud_mask(out[i, 0], sm[i])\n",
" if cm.any():\n",
" fill = out[i, 0][~cm].mean() if (~cm).any() else 115.0\n",
" out[i, 0][cm] = fill\n",
" back = warp(cm[None, None].astype(float), [1 / zy[i]], [1 / zx[i]], np.zeros(1))[0, 0] > 0.5\n",
" src[i, 0][back] = fill # ์๋ณธ์ ๊ฐ์ ๊ตฌ๋ฆ ์์ญ๋ ์ฑ์\n",
" cloud[i] = cm.mean()\n",
" angs[i] = orientation(out[i, 0], sm[i])[0]\n",
" rot = np.abs(angs) > 3 # 3๋ ์ดํ๋ ํ์ ์๋ต\n",
" if rot.any():\n",
" out[rot] = warp(src[rot], zy[rot], zx[rot], angs[rot])\n",
" return [(np.clip(o / 255, 0, 1), np.clip(s / 1.5, 0, 1), float(c)) for (o, s), c in zip(out, cloud)]\n",
"\n",
"\n",
"def preprocess(opt, sar, meta):\n",
" \"\"\"ํ ์ฅ ์ ์ฒ๋ฆฌ(preprocess_batch์ ๊ฐ์ ์ฒ๋ฆฌ).\"\"\"\n",
" return preprocess_batch([opt], [sar], [meta])[0]\n",
"\n",
"\n",
"def extent(s):\n",
" \"\"\"ํํํํ SAR ๋ฐ์ฌ ๋ง์คํฌ์ ๊ฐ๋กยท์ธ๋ก ๊ธธ์ด(ํฝ์
): ๊ฐ์ฒด ํฌ๊ธฐยท๋ฐฉ์ ํ์ธ์ฉ.\"\"\"\n",
" m = ndimage.uniform_filter(s, 3) > 0.2\n",
" return int(m.any(axis=0).sum()), int(m.any(axis=1).sum())\n",
"\n",
"\n",
"O, S, M = make_dataset(400, seed=7, scen=tuple(SCEN))\n",
"rng = np.random.default_rng(2)\n",
"for i in rng.choice(len(O), 20, replace=False): # ์ผ์ ๊ฒฐ์ธก(์ค ๋จ์ NaN) 20์ฅ ์ฃผ์
\n",
" r = int(rng.integers(0, N - 3)); O[i, r:r + 3] = np.nan\n",
"\n",
"# 1) ์ฒ๋ฆฌ ์๋์ ์ค๋ฅ์จ(๊ฐ์ 400์ฅ์ 3ํ ์ผ๊ด ์ฒ๋ฆฌํด ๊ฐ์ฅ ๋น ๋ฅธ ํ์ฐจ๋ก ์ฒ๋ฆฌ๋์ ์ฐ๋ค: ๋ค๋ฅธ ํ๋ก๊ทธ๋จ ๋ถํ ์ํฅ ์ต์ํ)\n",
"best, fails, out = 1e9, 0, []\n",
"for rep in range(3):\n",
" t0, f = time.perf_counter(), 0\n",
" try:\n",
" res = preprocess_batch(O, S, M) # 400์ฅ์ ํ ๋ฒ์(๋ฒกํฐ ์ฐ์ฐ) ์ฒ๋ฆฌ\n",
" except Exception:\n",
" res, f = [], len(O)\n",
" best, fails, out = min(best, time.perf_counter() - t0), max(fails, f), res\n",
"dt = best\n",
"nan_left = sum(int(np.isnan(p[0]).sum() + np.isnan(p[1]).sum()) for p in out)\n",
"print(f\"์ ์ฒ๋ฆฌ {len(O)}์ฅ, {len(O) / dt:,.0f}์ฅ/์ด (๋ชฉํ 1,000๊ฑด/์ด ์ด์), ์ฒ๋ฆฌ ์คํจ {fails}๊ฑด, ๋จ์ ๊ฒฐ์ธก {nan_left}ํฝ์
-> ์ค๋ฅ์จ {fails / len(O):.0%}\")\n",
"print(f\"๊ฒฐ์ธก ์ฃผ์
20์ฅ ๊ฒฐ์ธก์น ๋ณด์ ์จ {100 if nan_left == 0 else 0}%\")\n",
"\n",
"# 2) ์กฐ๊ฑด๋ณ ๋ณด์ ํจ๊ณผ\n",
"sc = np.array([m[\"scen\"] for m in M])\n",
"before_cloud = np.array([(o > 185).mean() for o in np.nan_to_num(O, nan=115)])\n",
"after_cloud = np.array([(p[0] > 185 / 255).mean() for p in out])\n",
"ang = np.array([orientation(p[0] * 255, ndimage.uniform_filter(p[1] * 1.5, 3))[0] for p in out])\n",
"horiz = (np.abs(ang) < 12) | (np.abs(ang) > 168)\n",
"ext = np.array([extent(p[1]) for p in out])\n",
"rows = []\n",
"for k in SCEN:\n",
" sel = sc == k\n",
" rows.append({\"์กฐ๊ฑด\": k, \"๋ฐ์ ๊ตฌ๋ฆ ํฝ์
(์ )\": f\"{before_cloud[sel].mean():.1%}\", \"(ํ)\": f\"{after_cloud[sel].mean():.1%}\",\n",
" \"๋ฐฉ์ ์ ๊ทํ ์ฑ๊ณต\": f\"{horiz[sel].mean():.0%}\", \"๊ฐ์ฒด ๊ธธ์ด/ํญ(px)\": f\"{ext[sel, 0].mean():.1f}/{ext[sel, 1].mean():.1f}\"})\n",
"print(pd.DataFrame(rows).to_string(index=False))\n",
"\n",
"# 3) ๋จ์ ํ
์คํธ 5์ข
\n",
"raw_len = np.array([extent(s / 1.5)[0] for s in S])\n",
"tests = {\n",
" \"์ถ๋ ฅ ํฌ๊ธฐ 32x32(๊ดํยทSAR)\": all(p[0].shape == (N, N) and p[1].shape == (N, N) for p in out),\n",
" \"๊ฐ ๋ฒ์ 0~1, ๊ฒฐ์ธก 0\": all(p[0].min() >= 0 and p[0].max() <= 1 and not np.isnan(p[0]).any() for p in out),\n",
" \"๊ตฌ๋ฆ ์กฐ๊ฑด ๋ฐ์ ํฝ์
50% ์ด์ ๊ฐ์\": after_cloud[sc == \"cloud\"].mean() < 0.5 * before_cloud[sc == \"cloud\"].mean(),\n",
" \"๋ง์ ์กฐ๊ฑด ๋ฐฉ์ ์ ๊ทํ 90% ์ด์\": horiz[sc == \"clean\"].mean() >= 0.9,\n",
" \"์ ํด์๋ ๊ฐ์ฒด ๊ธธ์ด ํ์คํ(๋ง์ ์กฐ๊ฑด ๋๋น 30% ์ด๋ด)\": abs(ext[sc == \"low_res\", 0].mean() / ext[sc == \"clean\", 0].mean() - 1) < 0.3,\n",
"}\n",
"for k, v in tests.items():\n",
" print(f\"[{'PASS' if v else 'FAIL'}] {k}\")\n",
"print(f\"๋จ์ ํ
์คํธ {sum(tests.values())}/5 ํต๊ณผ\")\n",
"print(f\"์ฐธ๊ณ : ์ ํด์๋ ์๋ณธ์ ๊ฐ์ฒด ๊ธธ์ด {raw_len[sc == 'low_res'].mean():.1f}px -> ๋ณด์ ํ {ext[sc == 'low_res', 0].mean():.1f}px\")\n",
"\n",
"# 4) ์ ํ ๋น๊ต ๊ทธ๋ฆผ\n",
"fig, ax = plt.subplots(2, 5, figsize=(14, 6))\n",
"for j, k in enumerate([\"clean\", \"cloud\", \"off_nadir\", \"low_res\", \"camo\"]):\n",
" i = int(np.where(sc == k)[0][0])\n",
" ax[0, j].imshow(np.nan_to_num(O[i], nan=0), cmap=\"gray\", vmin=0, vmax=255); ax[0, j].set_title(f\"{k} raw ({M[i]['cls']})\")\n",
" ax[1, j].imshow(out[i][0], cmap=\"gray\", vmin=0, vmax=1); ax[1, j].set_title(\"preprocessed\")\n",
"for a in ax.ravel():\n",
" a.axis(\"off\")\n",
"plt.suptitle(\"Synthetic satellite chips: before / after preprocessing (training data)\"); plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
02 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"์ ์ฒ๋ฆฌ 400์ฅ, 1,395์ฅ/์ด (๋ชฉํ 1,000๊ฑด/์ด ์ด์), ์ฒ๋ฆฌ ์คํจ 0๊ฑด, ๋จ์ ๊ฒฐ์ธก 0ํฝ์
-> ์ค๋ฅ์จ 0%\n",
"๊ฒฐ์ธก ์ฃผ์
20์ฅ ๊ฒฐ์ธก์น ๋ณด์ ์จ 100%\n",
" ์กฐ๊ฑด ๋ฐ์ ๊ตฌ๋ฆ ํฝ์
(์ ) (ํ) ๋ฐฉ์ ์ ๊ทํ ์ฑ๊ณต ๊ฐ์ฒด ๊ธธ์ด/ํญ(px)\n",
" clean 0.2% 0.1% 98% 14.8/8.1\n",
" camo 0.4% 0.3% 100% 14.8/8.1\n",
" occl30 0.2% 0.1% 100% 14.8/8.0\n",
" occl50 0.1% 0.1% 100% 14.7/8.1\n",
" cloud 25.1% 2.1% 100% 14.7/8.2\n",
" low_sun 0.3% 0.2% 95% 14.4/8.5\n",
"off_nadir 0.3% 0.2% 100% 14.7/8.1\n",
" low_res 0.0% 0.0% 95% 14.2/9.3\n",
" speckle 0.3% 0.3% 100% 15.4/8.9\n",
"camo_occl 0.1% 0.0% 98% 14.6/8.2\n",
"[PASS] ์ถ๋ ฅ ํฌ๊ธฐ 32x32(๊ดํยทSAR)\n",
"[PASS] ๊ฐ ๋ฒ์ 0~1, ๊ฒฐ์ธก 0\n",
"[PASS] ๊ตฌ๋ฆ ์กฐ๊ฑด ๋ฐ์ ํฝ์
50% ์ด์ ๊ฐ์\n",
"[PASS] ๋ง์ ์กฐ๊ฑด ๋ฐฉ์ ์ ๊ทํ 90% ์ด์\n",
"[PASS] ์ ํด์๋ ๊ฐ์ฒด ๊ธธ์ด ํ์คํ(๋ง์ ์กฐ๊ฑด ๋๋น 30% ์ด๋ด)\n",
"๋จ์ ํ
์คํธ 5/5 ํต๊ณผ\n",
"์ฐธ๊ณ : ์ ํด์๋ ์๋ณธ์ ๊ฐ์ฒด ๊ธธ์ด 5.6px -> ๋ณด์ ํ 14.2px\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
03. ์์ฑ์์ ๋ด ๊ธฐ๊ฐ/ํฌ๋ณ ์ฅ๋น ๊ฐ์ฒด ํ์ง Baseline CNN/Vision ๋ชจ๋ธ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์์ฑ์์ ๋ด ์ฃผ์ ๊ตฐ์ฉ ์ฅ๋น ๋ฐ ์ฐจ๋ ๊ฐ์ฒด๋ฅผ 1์ฐจ ์๋ณํ๋ Baseline ํฉ์ฑ๊ณฑ ์ ๊ฒฝ๋ง(CNN) ๊ธฐ๋ฐ ๋ถ๋ฅ ๋ชจ๋ธ์ ๊ตฌํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์์ฑ์์ ์ ์ฃผ์ ๊ตฐ์ฉ ์ฅ๋น์ ์ฐจ๋์ 1์ฐจ ์๋ณํ๋ Baseline ํฉ์ฑ๊ณฑ(CNN) ๋ฐฉ์ ๋ถ๋ฅ ๋ชจ๋ธ์ ๊ตฌํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **SobelยทLaplacianยท๋๊ฐ์ ยทํ๊ท ํํฐ๋ก ์ด๋ฃจ์ด์ง ํฉ์ฑ๊ณฑ ํํฐ ๋ฑ
ํฌ, ReLU, ์ต๋ ํ๋ง์ผ๋ก ๊ฐ์ด CNN ํน์ง์ ๋ง๋ค๊ณ ๋ก์ง์คํฑ ํ๊ท ๋ถ๋ฅ๊ธฐ๋ฅผ ํ์ต ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/isr01_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": [
"\"\"\"์์ฑ์์ ์กฐ๊ฐ์์ ์ ์ฐจยท์์ฃผํฌยทํธ๋ญยท๋ฏผ๊ฐ ์ฐจ๋์ 1์ฐจ ์๋ณํ๋ Baseline ๋ถ๋ฅ ๋ชจ๋ธ์ ๊ตฌํํ๋ค. ํฉ์ฑ๊ณฑ ํํฐ ๋ฑ
ํฌ(SobelยทLaplacianยท๋๊ฐ์ ยทํ๊ท ) -> ReLU -> ์ต๋ ํ๋ง์ผ๋ก ๋ง๋ ๊ฐ์ด CNN ํน์ง์ ๋ก์ง์คํฑ ํ๊ท ๋ถ๋ฅ๊ธฐ๋ฅผ ๋ถ์ด๊ณ , ์ ํ๋ยทํผ๋ํ๋ ฌยทํ๋จ ์ง์ฐ์๊ฐ์ ์ธก์ ํ๋ค.\"\"\"\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.metrics import confusion_matrix\n",
"from sklearn.pipeline import make_pipeline\n",
"from sklearn.preprocessing import StandardScaler\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํฉ์ฑ ์์ฑ์์ ์กฐ๊ฐ(chip) ์์ฑ๊ธฐ (์ค์ ์์ฑ์์ยท์ฅ๋น ์ ์ยท์์น ์์)\n",
"# 32x32 ๊ดํ(EO) ์์๊ณผ SAR ์์ ํ ์์ ์ถ์ ๋ํ์ผ๋ก ๋ง๋ ๊ฐ์ฒด๋ฅผ ๊ทธ๋ฆฐ๋ค. ๋ํ ์น์๋ ๊ฐ์๊ฐ์ด๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"N = 32\n",
"STD_GSD = 0.5 # ํ์ค ํด์๋(m/ํฝ์
, ๊ฐ์ ๊ธฐ์ค)\n",
"CLASSES = [\"tank\", \"spg\", \"truck\", \"civil\"]\n",
"MILITARY = {\"tank\", \"spg\", \"truck\"}\n",
"# ๊ฐ์ฒด ์ขํ๊ณ(u: ๊ธธ์ด ๋ฐฉํฅ, v: ํญ ๋ฐฉํฅ, ํ์ค ํด์๋ ํฝ์
) ๊ฐ์ ๋ํ: (u์ค์ฌ, v์ค์ฌ, ๊ธธ์ด, ํญ, ๊ดํ ๋ฐ๊ธฐ ์ฐจ์ด)\n",
"SHAPES = {\n",
" \"tank\": [(0, 0, 11, 7, -40), (0.5, 0, 5, 5, -55), (6, 0, 7, 1.2, -60)],\n",
" \"spg\": [(0, 0, 16, 7.5, -38), (-4, 0, 6, 6.5, -52), (5, 0, 13, 1.4, -60)],\n",
" \"truck\": [(5, 0, 3.5, 5.5, -45), (-1.8, 0, 8.5, 6, -25)],\n",
" \"civil\": [(0, 0, 8, 3.8, 0), (1.2, 0, 2, 3.4, 0)],\n",
" \"decoy\": [(0, 0, 11, 7, -40), (0.5, 0, 5, 5, -42), (6, 0, 7, 1.2, -45)],\n",
" \"none\": [], # ๊ฐ์ฒด ์๋ ๋ฐฐ๊ฒฝ ํ์ผ\n",
"}\n",
"SAR_RETURN = {\"tank\": 0.85, \"spg\": 0.85, \"truck\": 0.7, \"civil\": 0.45, \"decoy\": 0.18, \"none\": 0.12} # ๊ธ์ ๋ฐ์ฌ ์ธ๊ธฐ(๊ฐ์)\n",
"\n",
"\n",
"def _mask(cls, angle, scale, squash=1.0):\n",
" \"\"\"ํ์ (angle, ๋)ยท์ถ์ฒ(scale)ยท๋น์ค๋ฌ ์ดฌ์ ์์ถ(squash)์ ์ ์ฉํด ๋ํ๋ณ ๋ง์คํฌ์ ๋ฐ๊ธฐ ์ฐจ์ด๋ฅผ ๋ง๋ ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 3] ์์ฑ์์ ๋ด ๊ธฐ๊ฐ/ํฌ๋ณ ์ฅ๋น ๊ฐ์ฒด ํ์ง Baseline CNN/Vision ๋ชจ๋ธ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_chip(cls, rng, cond=None):\n",
" \"\"\"chip 1๊ฐ = (๊ดํ 32x32, SAR 32x32, ๋ฉํ๋ฐ์ดํฐ). cond: camo, occl, cloud, low_sun, off_nadir, low_res, speckle\"\"\"\n",
" c = {\"camo\": 0.0, \"occl\": 0.0, \"cloud\": 0.0, \"low_sun\": False, \"off_nadir\": 0.0, \"low_res\": False, \"speckle\": 1.0, **(cond or {})}\n",
" gsd = rng.uniform(0.9, 1.2) if c[\"low_res\"] else rng.uniform(0.4, 0.6)\n",
" scale = STD_GSD / gsd\n",
" angle, off = rng.uniform(0, 360), c[\"off_nadir\"] or rng.uniform(0, 15)\n",
" squash = np.cos(np.radians(off))\n",
" sun_el = rng.uniform(12, 22) if c[\"low_sun\"] else rng.uniform(40, 70)\n",
" sun_az = rng.uniform(0, 360)\n",
" bg = 115 + ndimage.gaussian_filter(rng.normal(0, 30, (N, N)), 2) + rng.normal(0, 5, (N, N))\n",
" parts = _mask(cls, angle, scale, squash)\n",
" body = np.any([m for m, _ in parts], axis=0) if parts else np.zeros((N, N), bool)\n",
" opt = bg.copy()\n",
" # ๊ทธ๋ฆผ์: ํ์ ๋ฐ๋ ๋ฐฉํฅ์ผ๋ก ๋ฐ๋ฆฐ ๊ฐ์ฒด ๋ชจ์(ํ์ ๊ณ ๋๊ฐ ๋ฎ์์๋ก ๊ธธ๋ค)\n",
" L = min(8.0, 2.5 / np.tan(np.radians(sun_el))) * scale\n",
" sh = ndimage.shift(body.astype(float), (L * np.cos(np.radians(sun_az)), -L * np.sin(np.radians(sun_az))), order=0) > 0.5\n",
" opt[sh & ~body] -= 35 if cls != \"decoy\" else 25\n",
" if cls == \"civil\":\n",
" base = rng.choice([200.0, 60.0]) # ๋ฏผ๊ฐ ์ฐจ๋ ์์: ๋ฐ๊ฑฐ๋ ์ด๋์\n",
" opt[body] = base + rng.normal(0, 8, body.sum())\n",
" roof = parts[1][0]\n",
" opt[roof] = base * 0.8\n",
" else:\n",
" ref = bg[body].mean() if body.any() else bg.mean()\n",
" for m, d in parts:\n",
" opt[m] = ref + d + rng.normal(0, 4 if cls != \"decoy\" else 2, m.sum())\n",
" # ์์ฅ๋ง: ๊ฐ์ฒด ์ฃผ๋ณ์ ๋ฐฐ๊ฒฝ ์ง๊ฐ์ผ๋ก ๋ฎ์ด ๋๋น๋ฅผ ๋ฎ์ถ๋ค\n",
" if c[\"camo\"] > 0 and cls != \"civil\":\n",
" net = ndimage.binary_dilation(body, iterations=2)\n",
" tex = 105 + ndimage.gaussian_filter(rng.normal(0, 25, (N, N)), 1)\n",
" opt[net] = (1 - c[\"camo\"]) * opt[net] + c[\"camo\"] * tex[net]\n",
" # ์๋ชฉ ์ฐจํ: ๊ฐ์ฒด์ ์ผ๋ถ๋ฅผ ์ด๋์ด ๋๋ฌด ๋ฉ์ด๋ฆฌ๋ก ๊ฐ๋ฆฐ๋ค\n",
" if c[\"occl\"] > 0:\n",
" trees = ndimage.gaussian_filter(rng.random((N, N)), 2)\n",
" near = ndimage.binary_dilation(body, iterations=3)\n",
" cover = near & (trees > np.quantile(trees[body], 1 - c[\"occl\"])) if body.any() else np.zeros((N, N), bool)\n",
" opt[cover] = 70 + rng.normal(0, 6, cover.sum())\n",
" # ๊ตฌ๋ฆ: ๋ฐ๊ณ ๋งค๋ํ ๋ฉ์ด๋ฆฌ(๊ดํ๋ง ๊ฐ๋ฆผ, SAR๋ ํต๊ณผ)\n",
" cmask = np.zeros((N, N), bool)\n",
" if c[\"cloud\"] > 0:\n",
" blob = ndimage.gaussian_filter(rng.random((N, N)), 4)\n",
" cmask = blob > np.quantile(blob, 1 - c[\"cloud\"])\n",
" opt[cmask] = 0.25 * opt[cmask] + 0.75 * (235 + rng.normal(0, 3, cmask.sum()))\n",
" # SAR: ๊ณฑ์
ํ ์คํํด ์ก์ + ๊ธ์ ๋ฐ์ฌ(๋ชจ์๋ฆฌ ์ด์ค ๋ฐ์ฌ๋ก ๋ฐ์ ์ )\n",
" sar = np.full((N, N), 0.12)\n",
" sar[body] = SAR_RETURN[cls] * rng.uniform(0.8, 1.2)\n",
" edge = body & ~ndimage.binary_erosion(body)\n",
" sar[edge] *= 1.3 if cls in MILITARY else 1.0\n",
" sar = sar * rng.gamma(4 / c[\"speckle\"], c[\"speckle\"] / 4, (N, N))\n",
" if c[\"low_res\"]: # ์ ํด์๋: ์ ๋ณด ์์ค(๋ธ๋ฌ)\n",
" down = lambda a: np.kron(a.reshape(N // 2, 2, N // 2, 2).mean((1, 3)), np.ones((2, 2)))\n",
" opt, sar = ndimage.gaussian_filter(down(opt), 0.7), ndimage.gaussian_filter(down(sar), 0.7)\n",
" meta = {\"cls\": cls, \"gsd\": round(float(gsd), 2), \"off_nadir\": round(float(off), 1), \"sun_el\": round(float(sun_el), 1),\n",
" \"sun_az\": round(float(sun_az), 1), \"angle\": round(float(angle), 1), \"cloud_frac\": float(cmask.mean()),\n",
" \"cloud_on_obj\": float((cmask & body).sum() / max(body.sum(), 1)), **{k: c[k] for k in (\"camo\", \"occl\", \"low_sun\", \"low_res\")}}\n",
" return np.clip(opt, 0, 255), sar, meta\n",
"\n",
"\n",
"SCEN = { # ์ํยท์ดฌ์ ์กฐ๊ฑด ์๋๋ฆฌ์ค 10์ข
\n",
" \"clean\": {}, \"camo\": {\"camo\": 0.7}, \"occl30\": {\"occl\": 0.3}, \"occl50\": {\"occl\": 0.5}, \"cloud\": {\"cloud\": 0.25},\n",
" \"low_sun\": {\"low_sun\": True}, \"off_nadir\": {\"off_nadir\": 35.0}, \"low_res\": {\"low_res\": True},\n",
" \"speckle\": {\"speckle\": 3.0}, \"camo_occl\": {\"camo\": 0.6, \"occl\": 0.3}}\n",
"\n",
"\n",
"def make_dataset(n=400, seed=7, scen=(\"clean\",), classes=CLASSES):\n",
" rng = np.random.default_rng(seed)\n",
" O, S, M = np.zeros((n, N, N)), np.zeros((n, N, N)), []\n",
" for i in range(n):\n",
" cls, sc = classes[i % len(classes)], scen[(i // len(classes)) % len(scen)]\n",
" O[i], S[i], m = make_chip(cls, rng, SCEN[sc])\n",
" m.update({\"id\": f\"IMG{i:04d}\", \"scen\": sc})\n",
" M.append(m)\n",
" return O, S, M\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ์ ์ฒ๋ฆฌ(ํด์๋ยท๊ธฐํ ๋ณด์ , ๊ตฌ๋ฆ ์ฐจํ ์ฒ๋ฆฌ, ๋ฐฉ์ ์ ๊ทํ) ๊ณตํต ํจ์ ----\n",
"def _fit(a, fill):\n",
" \"\"\"์ค์ ๊ธฐ์ค์ผ๋ก 32x32์ ๋ง๊ฒ ์๋ฅด๊ฑฐ๋ ์ฑ์ด๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 3] ์์ฑ์์ ๋ด ๊ธฐ๊ฐ/ํฌ๋ณ ์ฅ๋น ๊ฐ์ฒด ํ์ง Baseline CNN/Vision ๋ชจ๋ธ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def cloud_mask(opt, sm, min_area=15):\n",
" \"\"\"๊ดํ์์ ๋ฐ๊ณ (185 ์ด๊ณผ) ๋งค๋ํ๋ฐ SAR์๋ ๋ฐ์ฌ๊ฐ ์๋ ์์ญ์ ๊ตฌ๋ฆ์ผ๋ก ๋ณธ๋ค(SAR๋ ๊ตฌ๋ฆ์ ํต๊ณผํ๋ฏ๋ก ๋ฐ์ ์ฐจ๋๊ณผ ๊ตฌ๋ถ๋๋ค).\"\"\"\n",
" bright = opt > 185\n",
" if bright.sum() < min_area:\n",
" return np.zeros_like(bright)\n",
" local_sd = np.sqrt(np.maximum(ndimage.uniform_filter(opt ** 2, 3) - ndimage.uniform_filter(opt, 3) ** 2, 0))\n",
" cand = bright & (local_sd < 12) & (sm < 0.3)\n",
" lab, n = ndimage.label(cand)\n",
" if n == 0:\n",
" return cand\n",
" sizes = ndimage.sum(cand.astype(float), lab, range(1, n + 1))\n",
" return ndimage.binary_dilation(np.isin(lab, 1 + np.where(sizes >= min_area)[0]), iterations=2) & (opt > 140)\n",
"\n",
"\n",
"def orientation(opt, sm):\n",
" \"\"\"SAR ๋ฐ์ฌ(์์ฅยท๊ตฌ๋ฆ ์ํฅ ์ ์, sm์ ํํํํ SAR)์ ๊ดํ ๋๋น๋ฅผ ํฉ์น ๊ฐ์ฒด ๋ง์คํฌ์ ์ฃผ์ถ ๋ฐฉํฅ(๋).\"\"\"\n",
" m = (sm > 0.3) | (np.abs(opt - opt.mean()) > 30)\n",
" lab, n = ndimage.label(m)\n",
" if n == 0:\n",
" return 0.0, m\n",
" c = lab[N // 2 - 6:N // 2 + 6, N // 2 - 6:N // 2 + 6]\n",
" k = np.bincount(c[c > 0]).argmax() if (c > 0).any() else 1 + int(np.argmax(ndimage.sum(m.astype(float), lab, range(1, n + 1))))\n",
" m = lab == k\n",
" yy, xx = np.nonzero(m)\n",
" if len(yy) < 5:\n",
" return 0.0, m\n",
" dy, dx = yy - yy.mean(), xx - xx.mean() # 2์ฐจ ๋ชจ๋ฉํธ๋ก ์ฃผ์ถ ๊ฐ๋ ๊ณ์ฐ\n",
" return float(np.degrees(0.5 * np.arctan2(2 * (dx * dy).mean(), (dx * dx).mean() - (dy * dy).mean()))), m\n",
"\n",
"\n",
"_YY, _XX = np.mgrid[0:N, 0:N] - (N - 1) / 2\n",
"\n",
"\n",
"def warp(stack, zy, zx, ang):\n",
" \"\"\"์ฌ๋ฌ ์ฅ(B,์ฑ๋,32,32)์ ์ฅ๋ณ ํ๋ยท์ถ์(zy, zx)์ ํ์ (ang, ๋)์ ํ ๋ฒ์ ์์ ํ ๋ณด๊ฐ์ผ๋ก ์ ์ฉํ๋ค(๋ฒกํฐ ์ฐ์ฐ).\"\"\"\n",
" a = np.radians(ang)[:, None, None]\n",
" zy, zx = np.asarray(zy)[:, None, None], np.asarray(zx)[:, None, None]\n",
" sy = np.clip((np.cos(a) * _YY + np.sin(a) * _XX) / zy + (N - 1) / 2, 0, N - 1.001) # ์ถ๋ ฅ ์ขํ -> ์๋ณธ ์ขํ\n",
" sx = np.clip((-np.sin(a) * _YY + np.cos(a) * _XX) / zx + (N - 1) / 2, 0, N - 1.001)\n",
" y0, x0 = sy.astype(int), sx.astype(int)\n",
" fy, fx = (sy - y0).reshape(len(stack), 1, -1), (sx - x0).reshape(len(stack), 1, -1)\n",
" flat = stack.reshape(len(stack), stack.shape[1], -1)\n",
" lin = (y0 * N + x0).reshape(len(stack), 1, -1)\n",
" g = lambda d: np.take_along_axis(flat, np.broadcast_to(lin + d, (len(stack), stack.shape[1], lin.shape[2])), axis=2)\n",
" out = g(0) * (1 - fy) * (1 - fx) + g(N) * fy * (1 - fx) + g(1) * (1 - fy) * fx + g(N + 1) * fy * fx\n",
" return out.reshape(stack.shape)\n",
"\n",
"\n",
"def preprocess_batch(O, S, M):\n",
" \"\"\"์ฌ๋ฌ ์ฅ์ ํ ๋ฒ์ ์ ์ฒ๋ฆฌํ๋ค. 0) ๊ฒฐ์ธก ๋ณด์ 1) ํด์๋ยท๋น์ค๋ฌ ์ดฌ์ ๋ณด์ (ํ์ค 0.5m/ํฝ์
)\n",
" 2) ๊ตฌ๋ฆ ์ฐจํ ์์ญ์ ๋ฐฐ๊ฒฝ๊ฐ์ผ๋ก ์ฑ์ 3) ๊ฐ์ฒด ์ฃผ์ถ์ ๊ฐ๋ก๋ก ํ์ 4) 0~1 ์ ๊ทํ.\"\"\"\n",
" src = np.stack([np.asarray(O, float), np.asarray(S, float)], axis=1)\n",
" if np.isnan(src).any(): # 0) ์ผ์ ๊ฒฐ์ธก ํฝ์
๋ณด์ \n",
" mean = np.nanmean(src, axis=(2, 3), keepdims=True)\n",
" src = np.where(np.isnan(src), mean, src)\n",
" zy = np.array([m[\"gsd\"] / STD_GSD / np.cos(np.radians(m[\"off_nadir\"])) for m in M])\n",
" zx = np.array([m[\"gsd\"] / STD_GSD for m in M])\n",
" out = warp(src, zy, zx, np.zeros(len(M)))\n",
" sm = ndimage.uniform_filter(out[:, 1], (1, 3, 3))\n",
" angs, cloud = np.zeros(len(M)), np.zeros(len(M))\n",
" for i in range(len(M)):\n",
" cm = cloud_mask(out[i, 0], sm[i])\n",
" if cm.any():\n",
" fill = out[i, 0][~cm].mean() if (~cm).any() else 115.0\n",
" out[i, 0][cm] = fill\n",
" back = warp(cm[None, None].astype(float), [1 / zy[i]], [1 / zx[i]], np.zeros(1))[0, 0] > 0.5\n",
" src[i, 0][back] = fill # ์๋ณธ์ ๊ฐ์ ๊ตฌ๋ฆ ์์ญ๋ ์ฑ์\n",
" cloud[i] = cm.mean()\n",
" angs[i] = orientation(out[i, 0], sm[i])[0]\n",
" rot = np.abs(angs) > 3 # 3๋ ์ดํ๋ ํ์ ์๋ต\n",
" if rot.any():\n",
" out[rot] = warp(src[rot], zy[rot], zx[rot], angs[rot])\n",
" return [(np.clip(o / 255, 0, 1), np.clip(s / 1.5, 0, 1), float(c)) for (o, s), c in zip(out, cloud)]\n",
"\n",
"\n",
"def preprocess(opt, sar, meta):\n",
" \"\"\"ํ ์ฅ ์ ์ฒ๋ฆฌ(preprocess_batch์ ๊ฐ์ ์ฒ๋ฆฌ).\"\"\"\n",
" return preprocess_batch([opt], [sar], [meta])[0]\n",
"\n",
"# ---- ํน์ง ์ถ์ถ ๊ณตํต ํจ์ ----\n",
"KERNELS = [np.array(k, float) for k in (\n",
" [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], [[-1, -2, -1], [0, 0, 0], [1, 2, 1]], [[0, 1, 0], [1, -4, 1], [0, 1, 0]],\n",
" [[2, -1, -1], [-1, 2, -1], [-1, -1, 2]], [[-1, -1, 2], [-1, 2, -1], [2, -1, -1]], [[1, 1, 1], [1, 1, 1], [1, 1, 1]])]\n",
"\n",
"\n",
"def conv_features(o):\n",
" \"\"\"๊ฐ์ด CNN ํน์ง: ๊ณ ์ ํฉ์ฑ๊ณฑ ํํฐ 6์ข
-> ReLU -> 4x4 ์ต๋ ํ๋ง(8x8 ์ถ๋ ฅ).\"\"\"\n",
" feats = []\n",
" for k in KERNELS:\n",
" r = np.maximum(ndimage.correlate(o - o.mean(), k / np.abs(k).sum()), 0)\n",
" feats.append(r.reshape(8, 4, 8, 4).max(axis=(1, 3)).ravel())\n",
" feats.append(np.maximum(-ndimage.correlate(o - o.mean(), k / np.abs(k).sum()), 0).reshape(8, 4, 8, 4).max(axis=(1, 3)).ravel())\n",
" return np.concatenate(feats)\n",
"\n",
"\n",
"def hog(o, cell=8, bins=8):\n",
" gy, gx = np.gradient(o)\n",
" mag, ang = np.hypot(gx, gy), np.mod(np.arctan2(gy, gx), np.pi)\n",
" b = np.minimum((ang / np.pi * bins).astype(int), bins - 1)\n",
" out = np.zeros((N // cell, N // cell, bins))\n",
" for i in range(N // cell):\n",
" for j in range(N // cell):\n",
" sl = (slice(i * cell, (i + 1) * cell), slice(j * cell, (j + 1) * cell))\n",
" out[i, j] = np.bincount(b[sl].ravel(), mag[sl].ravel(), bins)\n",
" return (out / (np.linalg.norm(out) + 1e-6)).ravel()\n",
"\n",
"\n",
"def shape_features(o, s):\n",
" \"\"\"๊ฐ์ฒด ๋ชจ์ ํน์ง: ๊ฐ๋ก ๋ฐฉํฅ ํฌ์์ผ๋ก ์ ์ฒด ๊ธธ์ดยท์ฐจ์ฒด ๊ธธ์ดยท๊ฐ๋ ๋์ถ๋ถ(ํฌ์ ) ๊ธธ์ดยท๋๊บผ์ด ๋ถ๋ถ(ํฌํ) ์์น๋ฅผ ์ฐ๋ค.\n",
" ๋ฐฉ์ ์ ๊ทํ ๋ค์๋ ์๋ค(180๋)๊ฐ ๋ค์งํ ์ ์์ผ๋ฏ๋ก, ๊ฐ๋ ๋์ถ๋ถ๊ฐ ์ค๋ฅธ์ชฝ์ ์ค๋๋ก ๋ง์ถ๋ค.\"\"\"\n",
" sm = ndimage.uniform_filter(s, 3)\n",
" dark = np.abs(o - np.median(o))\n",
" m = (sm > 0.22) | (dark > 0.12)\n",
" lab, n = ndimage.label(m)\n",
" if n:\n",
" c = lab[10:22, 8:24]\n",
" k = np.bincount(c[c > 0]).argmax() if (c > 0).any() else 1\n",
" m = lab == k\n",
" p = m.sum(axis=0).astype(float)\n",
" cols = np.nonzero(p)[0]\n",
" if len(cols) == 0:\n",
" return np.zeros(8), o, s\n",
" thin = (p > 0) & (p <= 2)\n",
" left, right = thin[:N // 2].sum(), thin[N // 2:].sum()\n",
" if left > right:\n",
" o, s, m, p = o[:, ::-1], s[:, ::-1], m[:, ::-1], p[::-1]\n",
" cols = np.nonzero(p)[0]\n",
" body = np.nonzero(p > 3)[0]\n",
" d = np.where(m, dark if left <= right else dark[:, ::-1], 0)\n",
" heavy = d.sum(axis=0)\n",
" centre = (body[0] + body[-1]) / 2 if len(body) else N / 2\n",
" turret = (heavy * np.arange(N)).sum() / max(heavy.sum(), 1e-6) - centre\n",
" return np.array([cols[-1] - cols[0] + 1, len(body), ((p > 0) & (p <= 2)).sum(), p.max(), turret,\n",
" m.sum(), (cols[-1] - (body[-1] if len(body) else cols[-1])), m.sum() / max(len(cols) * p.max(), 1)]), o, s\n",
"\n",
"\n",
"def adv_features(o, s):\n",
" \"\"\"์ ๋ฐ ํน์ง: ๋ชจ์(๊ธธ์ดยทํฌ์ ยทํฌํ ์์น) + HOG(์ค๊ณฝ) + SAR ๋ฐ์ฌ ํต๊ณ(์์ฅ๋งยท๊ตฌ๋ฆ ์ํฅ ์ ์) + ํฌ์ ์ค๊ณฝ.\"\"\"\n",
" shp, o, s = shape_features(o, s)\n",
" c = s[8:24, 4:28]\n",
" sm = ndimage.uniform_filter(s, 3)\n",
" m = sm > 0.2\n",
" dark = np.abs(o - np.median(o))\n",
" return np.concatenate([shp, hog(o), [c.mean(), c.max(), (sm > 0.25).mean(), (sm > 0.4).mean(), m.sum(axis=1).max(), m.sum(axis=0).max()],\n",
" sm.mean(axis=0).reshape(8, 4).mean(1), sm.mean(axis=1).reshape(8, 4).mean(1),\n",
" dark.mean(axis=0).reshape(8, 4).mean(1), dark.mean(axis=1).reshape(8, 4).mean(1),\n",
" [o.mean(), o.std(), dark.max(), (o > 0.65).mean(), (o < 0.3).mean()]])\n",
"\n",
"\n",
"def features(O, S, M):\n",
" return np.array([conv_features(p[0]) for p in preprocess_batch(O, S, M)])\n",
"\n",
"\n",
"# 1) ๊ธฐ๋ณธ ๋ฐ์ดํฐ์
: ์ํ ์๋ ์์(ํ์ต 800์ฅ: ๋ง์ ์กฐ๊ฑด / ์ํ 400์ฅ: ๋ง์ ์กฐ๊ฑด + ๋ฎ์ ํ์๊ณ ๋ + ๋น์ค๋ฌ ์ดฌ์)\n",
"Otr, Str, Mtr = make_dataset(800, seed=1)\n",
"Ote, Ste, Mte = make_dataset(400, seed=2, scen=(\"clean\", \"clean\", \"low_sun\", \"off_nadir\"))\n",
"Xtr, ytr = features(Otr, Str, Mtr), [m[\"cls\"] for m in Mtr]\n",
"t0 = time.perf_counter()\n",
"Xte = features(Ote, Ste, Mte)\n",
"yte = np.array([m[\"cls\"] for m in Mte])\n",
"\n",
"# 2) ๊ท์ ๊ฐ๋(C) 1์ฐจ ํ๋: ํ์ต ์๋ฃ์ ์ผ๋ถ(๊ฒ์ฆ์ฉ 200์ฅ)๋ก ์ ํ\n",
"best_c, best = None, -1\n",
"for c in [0.01, 0.05, 0.2, 1.0]:\n",
" mdl = make_pipeline(StandardScaler(), LogisticRegression(C=c, max_iter=3000)).fit(Xtr[:600], ytr[:600])\n",
" acc = mdl.score(Xtr[600:], ytr[600:])\n",
" print(f\"C={c:<5} ๊ฒ์ฆ ์ ํ๋ {acc:.3f}\")\n",
" if acc > best:\n",
" best_c, best = c, acc\n",
"model = make_pipeline(StandardScaler(), LogisticRegression(C=best_c, max_iter=3000)).fit(Xtr, ytr)\n",
"pred = model.predict(Xte)\n",
"lat = (time.perf_counter() - t0) / len(Xte) * 1000\n",
"acc = (pred == yte).mean()\n",
"print(f\"\\n์ ํ C={best_c}, ๊ธฐ๋ณธ ์ํ์
์ ํ๋ {acc:.1%} (๋ชฉํ 75% ์ด์: {'๋ฌ์ฑ' if acc >= 0.75 else '๋ฏธ๋ฌ'}), 1์ฅ๋น ํ๋จ {lat:.2f} ms(์ ์ฒ๋ฆฌ ํฌํจ)\")\n",
"\n",
"print(\"\\n[ํผ๋ํ๋ ฌ] ํ=์ค์ , ์ด=์์ธก\")\n",
"print(pd.DataFrame(confusion_matrix(yte, pred, labels=CLASSES), index=CLASSES, columns=CLASSES).to_string())\n",
"mil, pm = np.isin(yte, list(MILITARY)), np.isin(pred, list(MILITARY))\n",
"print(f\"๊ตฐ์ฉ ์ฅ๋น ์ฌ๋ถ ํ์ ์ ํ๋ {(mil == pm).mean():.1%}, ํด๋์ค๋ณ ์ฌํ์จ:\",\n",
" {c: round(float((pred[yte == c] == c).mean()), 2) for c in CLASSES})\n",
"\n",
"# 3) ํ๊ณ: ์์ฅ๋งยท์๋ชฉ ์ฐจํยท์ ํด์๋ ๋ฑ ์ํ ์กฐ๊ฑด์์๋ ์ ํ๋๊ฐ ๋จ์ด์ง๋ค(๋ฏธ์
4์ ๋๊ธฐ)\n",
"Oh, Sh, Mh = make_dataset(400, seed=3, scen=(\"camo\", \"occl50\", \"low_res\", \"camo_occl\"))\n",
"ph = model.predict(features(Oh, Sh, Mh))\n",
"yh, sh = np.array([m[\"cls\"] for m in Mh]), np.array([m[\"scen\"] for m in Mh])\n",
"print(\"[์ํ ์กฐ๊ฑด ์ ํ๋]\", {k: round(float((ph[sh == k] == yh[sh == k]).mean()), 2) for k in (\"camo\", \"occl50\", \"low_res\", \"camo_occl\")})\n",
"print(\"ํ๋จ ๊ทผ๊ฑฐ: ํํฐ ๋ฐ์(์ค๊ณฝยทํฌ์ ๊ฐ์ ๊ฐ๋ ์ )์ ์์น ๋ถํฌ. ์์ฅ๋ง์ด ์ค๊ณฝ์ ์ง์ฐ๋ฉด ๊ทผ๊ฑฐ๊ฐ ์ฌ๋ผ์ง๋ค.\")\n",
"\n",
"# 4) ๊ทธ๋ฆผ: ํด๋์ค๋ณ ์์์ ํํฐ ๋ฐ์\n",
"fig, ax = plt.subplots(2, 4, figsize=(12, 6))\n",
"for j, c in enumerate(CLASSES):\n",
" i = int(np.where(yte == c)[0][0])\n",
" o = preprocess(Ote[i], Ste[i], Mte[i])[0]\n",
" ax[0, j].imshow(o, cmap=\"gray\"); ax[0, j].set_title(f\"{c} -> pred {pred[i]}\")\n",
" ax[1, j].imshow(np.maximum(ndimage.correlate(o - o.mean(), KERNELS[0] / 8), 0), cmap=\"magma\"); ax[1, j].set_title(\"Sobel-x response\")\n",
"for a in ax.ravel():\n",
" a.axis(\"off\")\n",
"plt.suptitle(\"Baseline conv-feature classifier (synthetic chips)\"); plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
03 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"C=0.01 ๊ฒ์ฆ ์ ํ๋ 1.000\n",
"C=0.05 ๊ฒ์ฆ ์ ํ๋ 1.000\n",
"C=0.2 ๊ฒ์ฆ ์ ํ๋ 1.000\n",
"C=1.0 ๊ฒ์ฆ ์ ํ๋ 1.000\n",
"\n",
"์ ํ C=0.01, ๊ธฐ๋ณธ ์ํ์
์ ํ๋ 96.5% (๋ชฉํ 75% ์ด์: ๋ฌ์ฑ), 1์ฅ๋น ํ๋จ 5.38 ms(์ ์ฒ๋ฆฌ ํฌํจ)\n",
"\n",
"[ํผ๋ํ๋ ฌ] ํ=์ค์ , ์ด=์์ธก\n",
" tank spg truck civil\n",
"tank 91 8 1 0\n",
"spg 1 99 0 0\n",
"truck 1 3 96 0\n",
"civil 0 0 0 100\n",
"๊ตฐ์ฉ ์ฅ๋น ์ฌ๋ถ ํ์ ์ ํ๋ 100.0%, ํด๋์ค๋ณ ์ฌํ์จ: {'tank': 0.91, 'spg': 0.99, 'truck': 0.96, 'civil': 1.0}\n",
"[์ํ ์กฐ๊ฑด ์ ํ๋] {'camo': 0.5, 'occl50': 0.52, 'low_res': 0.25, 'camo_occl': 0.61}\n",
"ํ๋จ ๊ทผ๊ฑฐ: ํํฐ ๋ฐ์(์ค๊ณฝยทํฌ์ ๊ฐ์ ๊ฐ๋ ์ )์ ์์น ๋ถํฌ. ์์ฅ๋ง์ด ์ค๊ณฝ์ ์ง์ฐ๋ฉด ๊ทผ๊ฑฐ๊ฐ ์ฌ๋ผ์ง๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
04. ์์ฅ๋ง/์ฐจํ ํ๊ฒฝ ํ ๊ตฐ์ฉ ์ฅ๋น ๋ฏธ์ธ ํน์ง ์ถ์ถ ๋ฐ ์ ๋ฐ ๊ฐ์ฒด๋ถ๋ฅ ๋ชจ๋ธ ๊ณ ๋ํ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์์ฅ๋ง ์ค์น, ์๋ชฉ ์ฐจํ, ๋ถ๋ถ ์ํ ์ํ์ ๊ตฐ์ฉ ์ฅ๋น ๋ฏธ์ธ ํน์ง์ ์ถ์ถํ์ฌ ๊ฐ์ฒด๋ถ๋ฅ ์ ํ๋๋ฅผ ๊ทน๋ํํ๋ ์ ๋ฐ ๋ชจ๋ธ์ ๊ณ ๋ํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์์ฅ๋ง ์ค์น, ์๋ชฉ ์ฐจํ, ๋ถ๋ถ ์ํ ์ํ์ ์ฅ๋น์์๋ ๊ธฐ์ข
์ ์๋ณํ๋๋ก ๋ฏธ์ธ ํน์ง์ ์ถ์ถํ๋ ์ ๋ฐ ๋ถ๋ฅ ๋ชจ๋ธ๋ก ๊ณ ๋ํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๊ฐ์ฒด ๋ชจ์(์ ์ฒด ๊ธธ์ดยท์ฐจ์ฒด ๊ธธ์ดยทํฌ์ ๋์ถยทํฌํ ์์น), ์ค๊ณฝ ๋ฐฉํฅ ํ์คํ ๊ทธ๋จ(HOG), ์์ฅ๋ง๊ณผ ๊ตฌ๋ฆ์ ๊ฐํ SAR ๋ฐ์ฌ ํต๊ณ, ํฌ์ ์ค๊ณฝ ํน์ง์ ๊ฒฐํฉํ๊ณ ์ํ ์กฐ๊ฑด 10์ข
์ ์์ ์ฆ๊ฐ ์๋ฃ๋ก ๋๋ค ํฌ๋ ์คํธ๋ฅผ ํ์ต ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/isr01_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": [
"\"\"\"์์ฅ๋งยท์๋ชฉ ์ฐจํยท๊ตฌ๋ฆยท์ ํด์๋ยทSAR ์ก์ ๋ฑ ์ํ ์กฐ๊ฑด 10์ข
์์ ๊ตฐ์ฉ ์ฅ๋น๋ฅผ ๋์น์ง ์๋๋ก, ์ค๊ณฝ(HOG)ยทSAR ๋ฐ์ฌ ํต๊ณยทํฌ์ ์ค๊ณฝ ํน์ง๊ณผ ์ํ ์กฐ๊ฑด ์ฆ๊ฐ ํ์ต์ ๊ฒฐํฉํ ์ ๋ฐ ๊ฐ์ฒด๋ถ๋ฅ ๋ชจ๋ธ(๋๋ค ํฌ๋ ์คํธ)๋ก ๊ณ ๋ํํ๊ณ ์กฐ๊ฑด๋ณ ์ฌํ์จ๊ณผ ์ฒ๋ฆฌ ์ง์ฐ์ ์ธก์ ํ๋ค.\"\"\"\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.ensemble import RandomForestClassifier\n",
"from sklearn.linear_model import LogisticRegression\n",
"from sklearn.pipeline import make_pipeline\n",
"from sklearn.preprocessing import StandardScaler\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํฉ์ฑ ์์ฑ์์ ์กฐ๊ฐ(chip) ์์ฑ๊ธฐ (์ค์ ์์ฑ์์ยท์ฅ๋น ์ ์ยท์์น ์์)\n",
"# 32x32 ๊ดํ(EO) ์์๊ณผ SAR ์์ ํ ์์ ์ถ์ ๋ํ์ผ๋ก ๋ง๋ ๊ฐ์ฒด๋ฅผ ๊ทธ๋ฆฐ๋ค. ๋ํ ์น์๋ ๊ฐ์๊ฐ์ด๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"N = 32\n",
"STD_GSD = 0.5 # ํ์ค ํด์๋(m/ํฝ์
, ๊ฐ์ ๊ธฐ์ค)\n",
"CLASSES = [\"tank\", \"spg\", \"truck\", \"civil\"]\n",
"MILITARY = {\"tank\", \"spg\", \"truck\"}\n",
"# ๊ฐ์ฒด ์ขํ๊ณ(u: ๊ธธ์ด ๋ฐฉํฅ, v: ํญ ๋ฐฉํฅ, ํ์ค ํด์๋ ํฝ์
) ๊ฐ์ ๋ํ: (u์ค์ฌ, v์ค์ฌ, ๊ธธ์ด, ํญ, ๊ดํ ๋ฐ๊ธฐ ์ฐจ์ด)\n",
"SHAPES = {\n",
" \"tank\": [(0, 0, 11, 7, -40), (0.5, 0, 5, 5, -55), (6, 0, 7, 1.2, -60)],\n",
" \"spg\": [(0, 0, 16, 7.5, -38), (-4, 0, 6, 6.5, -52), (5, 0, 13, 1.4, -60)],\n",
" \"truck\": [(5, 0, 3.5, 5.5, -45), (-1.8, 0, 8.5, 6, -25)],\n",
" \"civil\": [(0, 0, 8, 3.8, 0), (1.2, 0, 2, 3.4, 0)],\n",
" \"decoy\": [(0, 0, 11, 7, -40), (0.5, 0, 5, 5, -42), (6, 0, 7, 1.2, -45)],\n",
" \"none\": [], # ๊ฐ์ฒด ์๋ ๋ฐฐ๊ฒฝ ํ์ผ\n",
"}\n",
"SAR_RETURN = {\"tank\": 0.85, \"spg\": 0.85, \"truck\": 0.7, \"civil\": 0.45, \"decoy\": 0.18, \"none\": 0.12} # ๊ธ์ ๋ฐ์ฌ ์ธ๊ธฐ(๊ฐ์)\n",
"\n",
"\n",
"def _mask(cls, angle, scale, squash=1.0):\n",
" \"\"\"ํ์ (angle, ๋)ยท์ถ์ฒ(scale)ยท๋น์ค๋ฌ ์ดฌ์ ์์ถ(squash)์ ์ ์ฉํด ๋ํ๋ณ ๋ง์คํฌ์ ๋ฐ๊ธฐ ์ฐจ์ด๋ฅผ ๋ง๋ ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 4] ์์ฅ๋ง/์ฐจํ ํ๊ฒฝ ํ ๊ตฐ์ฉ ์ฅ๋น ๋ฏธ์ธ ํน์ง ์ถ์ถ ๋ฐ ์ ๋ฐ ๊ฐ์ฒด๋ถ๋ฅ ๋ชจ๋ธ ๊ณ ๋ํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_chip(cls, rng, cond=None):\n",
" \"\"\"chip 1๊ฐ = (๊ดํ 32x32, SAR 32x32, ๋ฉํ๋ฐ์ดํฐ). cond: camo, occl, cloud, low_sun, off_nadir, low_res, speckle\"\"\"\n",
" c = {\"camo\": 0.0, \"occl\": 0.0, \"cloud\": 0.0, \"low_sun\": False, \"off_nadir\": 0.0, \"low_res\": False, \"speckle\": 1.0, **(cond or {})}\n",
" gsd = rng.uniform(0.9, 1.2) if c[\"low_res\"] else rng.uniform(0.4, 0.6)\n",
" scale = STD_GSD / gsd\n",
" angle, off = rng.uniform(0, 360), c[\"off_nadir\"] or rng.uniform(0, 15)\n",
" squash = np.cos(np.radians(off))\n",
" sun_el = rng.uniform(12, 22) if c[\"low_sun\"] else rng.uniform(40, 70)\n",
" sun_az = rng.uniform(0, 360)\n",
" bg = 115 + ndimage.gaussian_filter(rng.normal(0, 30, (N, N)), 2) + rng.normal(0, 5, (N, N))\n",
" parts = _mask(cls, angle, scale, squash)\n",
" body = np.any([m for m, _ in parts], axis=0) if parts else np.zeros((N, N), bool)\n",
" opt = bg.copy()\n",
" # ๊ทธ๋ฆผ์: ํ์ ๋ฐ๋ ๋ฐฉํฅ์ผ๋ก ๋ฐ๋ฆฐ ๊ฐ์ฒด ๋ชจ์(ํ์ ๊ณ ๋๊ฐ ๋ฎ์์๋ก ๊ธธ๋ค)\n",
" L = min(8.0, 2.5 / np.tan(np.radians(sun_el))) * scale\n",
" sh = ndimage.shift(body.astype(float), (L * np.cos(np.radians(sun_az)), -L * np.sin(np.radians(sun_az))), order=0) > 0.5\n",
" opt[sh & ~body] -= 35 if cls != \"decoy\" else 25\n",
" if cls == \"civil\":\n",
" base = rng.choice([200.0, 60.0]) # ๋ฏผ๊ฐ ์ฐจ๋ ์์: ๋ฐ๊ฑฐ๋ ์ด๋์\n",
" opt[body] = base + rng.normal(0, 8, body.sum())\n",
" roof = parts[1][0]\n",
" opt[roof] = base * 0.8\n",
" else:\n",
" ref = bg[body].mean() if body.any() else bg.mean()\n",
" for m, d in parts:\n",
" opt[m] = ref + d + rng.normal(0, 4 if cls != \"decoy\" else 2, m.sum())\n",
" # ์์ฅ๋ง: ๊ฐ์ฒด ์ฃผ๋ณ์ ๋ฐฐ๊ฒฝ ์ง๊ฐ์ผ๋ก ๋ฎ์ด ๋๋น๋ฅผ ๋ฎ์ถ๋ค\n",
" if c[\"camo\"] > 0 and cls != \"civil\":\n",
" net = ndimage.binary_dilation(body, iterations=2)\n",
" tex = 105 + ndimage.gaussian_filter(rng.normal(0, 25, (N, N)), 1)\n",
" opt[net] = (1 - c[\"camo\"]) * opt[net] + c[\"camo\"] * tex[net]\n",
" # ์๋ชฉ ์ฐจํ: ๊ฐ์ฒด์ ์ผ๋ถ๋ฅผ ์ด๋์ด ๋๋ฌด ๋ฉ์ด๋ฆฌ๋ก ๊ฐ๋ฆฐ๋ค\n",
" if c[\"occl\"] > 0:\n",
" trees = ndimage.gaussian_filter(rng.random((N, N)), 2)\n",
" near = ndimage.binary_dilation(body, iterations=3)\n",
" cover = near & (trees > np.quantile(trees[body], 1 - c[\"occl\"])) if body.any() else np.zeros((N, N), bool)\n",
" opt[cover] = 70 + rng.normal(0, 6, cover.sum())\n",
" # ๊ตฌ๋ฆ: ๋ฐ๊ณ ๋งค๋ํ ๋ฉ์ด๋ฆฌ(๊ดํ๋ง ๊ฐ๋ฆผ, SAR๋ ํต๊ณผ)\n",
" cmask = np.zeros((N, N), bool)\n",
" if c[\"cloud\"] > 0:\n",
" blob = ndimage.gaussian_filter(rng.random((N, N)), 4)\n",
" cmask = blob > np.quantile(blob, 1 - c[\"cloud\"])\n",
" opt[cmask] = 0.25 * opt[cmask] + 0.75 * (235 + rng.normal(0, 3, cmask.sum()))\n",
" # SAR: ๊ณฑ์
ํ ์คํํด ์ก์ + ๊ธ์ ๋ฐ์ฌ(๋ชจ์๋ฆฌ ์ด์ค ๋ฐ์ฌ๋ก ๋ฐ์ ์ )\n",
" sar = np.full((N, N), 0.12)\n",
" sar[body] = SAR_RETURN[cls] * rng.uniform(0.8, 1.2)\n",
" edge = body & ~ndimage.binary_erosion(body)\n",
" sar[edge] *= 1.3 if cls in MILITARY else 1.0\n",
" sar = sar * rng.gamma(4 / c[\"speckle\"], c[\"speckle\"] / 4, (N, N))\n",
" if c[\"low_res\"]: # ์ ํด์๋: ์ ๋ณด ์์ค(๋ธ๋ฌ)\n",
" down = lambda a: np.kron(a.reshape(N // 2, 2, N // 2, 2).mean((1, 3)), np.ones((2, 2)))\n",
" opt, sar = ndimage.gaussian_filter(down(opt), 0.7), ndimage.gaussian_filter(down(sar), 0.7)\n",
" meta = {\"cls\": cls, \"gsd\": round(float(gsd), 2), \"off_nadir\": round(float(off), 1), \"sun_el\": round(float(sun_el), 1),\n",
" \"sun_az\": round(float(sun_az), 1), \"angle\": round(float(angle), 1), \"cloud_frac\": float(cmask.mean()),\n",
" \"cloud_on_obj\": float((cmask & body).sum() / max(body.sum(), 1)), **{k: c[k] for k in (\"camo\", \"occl\", \"low_sun\", \"low_res\")}}\n",
" return np.clip(opt, 0, 255), sar, meta\n",
"\n",
"\n",
"SCEN = { # ์ํยท์ดฌ์ ์กฐ๊ฑด ์๋๋ฆฌ์ค 10์ข
\n",
" \"clean\": {}, \"camo\": {\"camo\": 0.7}, \"occl30\": {\"occl\": 0.3}, \"occl50\": {\"occl\": 0.5}, \"cloud\": {\"cloud\": 0.25},\n",
" \"low_sun\": {\"low_sun\": True}, \"off_nadir\": {\"off_nadir\": 35.0}, \"low_res\": {\"low_res\": True},\n",
" \"speckle\": {\"speckle\": 3.0}, \"camo_occl\": {\"camo\": 0.6, \"occl\": 0.3}}\n",
"\n",
"\n",
"def make_dataset(n=400, seed=7, scen=(\"clean\",), classes=CLASSES):\n",
" rng = np.random.default_rng(seed)\n",
" O, S, M = np.zeros((n, N, N)), np.zeros((n, N, N)), []\n",
" for i in range(n):\n",
" cls, sc = classes[i % len(classes)], scen[(i // len(classes)) % len(scen)]\n",
" O[i], S[i], m = make_chip(cls, rng, SCEN[sc])\n",
" m.update({\"id\": f\"IMG{i:04d}\", \"scen\": sc})\n",
" M.append(m)\n",
" return O, S, M\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ์ ์ฒ๋ฆฌ(ํด์๋ยท๊ธฐํ ๋ณด์ , ๊ตฌ๋ฆ ์ฐจํ ์ฒ๋ฆฌ, ๋ฐฉ์ ์ ๊ทํ) ๊ณตํต ํจ์ ----\n",
"def _fit(a, fill):\n",
" \"\"\"์ค์ ๊ธฐ์ค์ผ๋ก 32x32์ ๋ง๊ฒ ์๋ฅด๊ฑฐ๋ ์ฑ์ด๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 4] ์์ฅ๋ง/์ฐจํ ํ๊ฒฝ ํ ๊ตฐ์ฉ ์ฅ๋น ๋ฏธ์ธ ํน์ง ์ถ์ถ ๋ฐ ์ ๋ฐ ๊ฐ์ฒด๋ถ๋ฅ ๋ชจ๋ธ ๊ณ ๋ํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def cloud_mask(opt, sm, min_area=15):\n",
" \"\"\"๊ดํ์์ ๋ฐ๊ณ (185 ์ด๊ณผ) ๋งค๋ํ๋ฐ SAR์๋ ๋ฐ์ฌ๊ฐ ์๋ ์์ญ์ ๊ตฌ๋ฆ์ผ๋ก ๋ณธ๋ค(SAR๋ ๊ตฌ๋ฆ์ ํต๊ณผํ๋ฏ๋ก ๋ฐ์ ์ฐจ๋๊ณผ ๊ตฌ๋ถ๋๋ค).\"\"\"\n",
" bright = opt > 185\n",
" if bright.sum() < min_area:\n",
" return np.zeros_like(bright)\n",
" local_sd = np.sqrt(np.maximum(ndimage.uniform_filter(opt ** 2, 3) - ndimage.uniform_filter(opt, 3) ** 2, 0))\n",
" cand = bright & (local_sd < 12) & (sm < 0.3)\n",
" lab, n = ndimage.label(cand)\n",
" if n == 0:\n",
" return cand\n",
" sizes = ndimage.sum(cand.astype(float), lab, range(1, n + 1))\n",
" return ndimage.binary_dilation(np.isin(lab, 1 + np.where(sizes >= min_area)[0]), iterations=2) & (opt > 140)\n",
"\n",
"\n",
"def orientation(opt, sm):\n",
" \"\"\"SAR ๋ฐ์ฌ(์์ฅยท๊ตฌ๋ฆ ์ํฅ ์ ์, sm์ ํํํํ SAR)์ ๊ดํ ๋๋น๋ฅผ ํฉ์น ๊ฐ์ฒด ๋ง์คํฌ์ ์ฃผ์ถ ๋ฐฉํฅ(๋).\"\"\"\n",
" m = (sm > 0.3) | (np.abs(opt - opt.mean()) > 30)\n",
" lab, n = ndimage.label(m)\n",
" if n == 0:\n",
" return 0.0, m\n",
" c = lab[N // 2 - 6:N // 2 + 6, N // 2 - 6:N // 2 + 6]\n",
" k = np.bincount(c[c > 0]).argmax() if (c > 0).any() else 1 + int(np.argmax(ndimage.sum(m.astype(float), lab, range(1, n + 1))))\n",
" m = lab == k\n",
" yy, xx = np.nonzero(m)\n",
" if len(yy) < 5:\n",
" return 0.0, m\n",
" dy, dx = yy - yy.mean(), xx - xx.mean() # 2์ฐจ ๋ชจ๋ฉํธ๋ก ์ฃผ์ถ ๊ฐ๋ ๊ณ์ฐ\n",
" return float(np.degrees(0.5 * np.arctan2(2 * (dx * dy).mean(), (dx * dx).mean() - (dy * dy).mean()))), m\n",
"\n",
"\n",
"_YY, _XX = np.mgrid[0:N, 0:N] - (N - 1) / 2\n",
"\n",
"\n",
"def warp(stack, zy, zx, ang):\n",
" \"\"\"์ฌ๋ฌ ์ฅ(B,์ฑ๋,32,32)์ ์ฅ๋ณ ํ๋ยท์ถ์(zy, zx)์ ํ์ (ang, ๋)์ ํ ๋ฒ์ ์์ ํ ๋ณด๊ฐ์ผ๋ก ์ ์ฉํ๋ค(๋ฒกํฐ ์ฐ์ฐ).\"\"\"\n",
" a = np.radians(ang)[:, None, None]\n",
" zy, zx = np.asarray(zy)[:, None, None], np.asarray(zx)[:, None, None]\n",
" sy = np.clip((np.cos(a) * _YY + np.sin(a) * _XX) / zy + (N - 1) / 2, 0, N - 1.001) # ์ถ๋ ฅ ์ขํ -> ์๋ณธ ์ขํ\n",
" sx = np.clip((-np.sin(a) * _YY + np.cos(a) * _XX) / zx + (N - 1) / 2, 0, N - 1.001)\n",
" y0, x0 = sy.astype(int), sx.astype(int)\n",
" fy, fx = (sy - y0).reshape(len(stack), 1, -1), (sx - x0).reshape(len(stack), 1, -1)\n",
" flat = stack.reshape(len(stack), stack.shape[1], -1)\n",
" lin = (y0 * N + x0).reshape(len(stack), 1, -1)\n",
" g = lambda d: np.take_along_axis(flat, np.broadcast_to(lin + d, (len(stack), stack.shape[1], lin.shape[2])), axis=2)\n",
" out = g(0) * (1 - fy) * (1 - fx) + g(N) * fy * (1 - fx) + g(1) * (1 - fy) * fx + g(N + 1) * fy * fx\n",
" return out.reshape(stack.shape)\n",
"\n",
"\n",
"def preprocess_batch(O, S, M):\n",
" \"\"\"์ฌ๋ฌ ์ฅ์ ํ ๋ฒ์ ์ ์ฒ๋ฆฌํ๋ค. 0) ๊ฒฐ์ธก ๋ณด์ 1) ํด์๋ยท๋น์ค๋ฌ ์ดฌ์ ๋ณด์ (ํ์ค 0.5m/ํฝ์
)\n",
" 2) ๊ตฌ๋ฆ ์ฐจํ ์์ญ์ ๋ฐฐ๊ฒฝ๊ฐ์ผ๋ก ์ฑ์ 3) ๊ฐ์ฒด ์ฃผ์ถ์ ๊ฐ๋ก๋ก ํ์ 4) 0~1 ์ ๊ทํ.\"\"\"\n",
" src = np.stack([np.asarray(O, float), np.asarray(S, float)], axis=1)\n",
" if np.isnan(src).any(): # 0) ์ผ์ ๊ฒฐ์ธก ํฝ์
๋ณด์ \n",
" mean = np.nanmean(src, axis=(2, 3), keepdims=True)\n",
" src = np.where(np.isnan(src), mean, src)\n",
" zy = np.array([m[\"gsd\"] / STD_GSD / np.cos(np.radians(m[\"off_nadir\"])) for m in M])\n",
" zx = np.array([m[\"gsd\"] / STD_GSD for m in M])\n",
" out = warp(src, zy, zx, np.zeros(len(M)))\n",
" sm = ndimage.uniform_filter(out[:, 1], (1, 3, 3))\n",
" angs, cloud = np.zeros(len(M)), np.zeros(len(M))\n",
" for i in range(len(M)):\n",
" cm = cloud_mask(out[i, 0], sm[i])\n",
" if cm.any():\n",
" fill = out[i, 0][~cm].mean() if (~cm).any() else 115.0\n",
" out[i, 0][cm] = fill\n",
" back = warp(cm[None, None].astype(float), [1 / zy[i]], [1 / zx[i]], np.zeros(1))[0, 0] > 0.5\n",
" src[i, 0][back] = fill # ์๋ณธ์ ๊ฐ์ ๊ตฌ๋ฆ ์์ญ๋ ์ฑ์\n",
" cloud[i] = cm.mean()\n",
" angs[i] = orientation(out[i, 0], sm[i])[0]\n",
" rot = np.abs(angs) > 3 # 3๋ ์ดํ๋ ํ์ ์๋ต\n",
" if rot.any():\n",
" out[rot] = warp(src[rot], zy[rot], zx[rot], angs[rot])\n",
" return [(np.clip(o / 255, 0, 1), np.clip(s / 1.5, 0, 1), float(c)) for (o, s), c in zip(out, cloud)]\n",
"\n",
"\n",
"def preprocess(opt, sar, meta):\n",
" \"\"\"ํ ์ฅ ์ ์ฒ๋ฆฌ(preprocess_batch์ ๊ฐ์ ์ฒ๋ฆฌ).\"\"\"\n",
" return preprocess_batch([opt], [sar], [meta])[0]\n",
"\n",
"# ---- ํน์ง ์ถ์ถ ๊ณตํต ํจ์ ----\n",
"KERNELS = [np.array(k, float) for k in (\n",
" [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], [[-1, -2, -1], [0, 0, 0], [1, 2, 1]], [[0, 1, 0], [1, -4, 1], [0, 1, 0]],\n",
" [[2, -1, -1], [-1, 2, -1], [-1, -1, 2]], [[-1, -1, 2], [-1, 2, -1], [2, -1, -1]], [[1, 1, 1], [1, 1, 1], [1, 1, 1]])]\n",
"\n",
"\n",
"def conv_features(o):\n",
" \"\"\"๊ฐ์ด CNN ํน์ง: ๊ณ ์ ํฉ์ฑ๊ณฑ ํํฐ 6์ข
-> ReLU -> 4x4 ์ต๋ ํ๋ง(8x8 ์ถ๋ ฅ).\"\"\"\n",
" feats = []\n",
" for k in KERNELS:\n",
" r = np.maximum(ndimage.correlate(o - o.mean(), k / np.abs(k).sum()), 0)\n",
" feats.append(r.reshape(8, 4, 8, 4).max(axis=(1, 3)).ravel())\n",
" feats.append(np.maximum(-ndimage.correlate(o - o.mean(), k / np.abs(k).sum()), 0).reshape(8, 4, 8, 4).max(axis=(1, 3)).ravel())\n",
" return np.concatenate(feats)\n",
"\n",
"\n",
"def hog(o, cell=8, bins=8):\n",
" gy, gx = np.gradient(o)\n",
" mag, ang = np.hypot(gx, gy), np.mod(np.arctan2(gy, gx), np.pi)\n",
" b = np.minimum((ang / np.pi * bins).astype(int), bins - 1)\n",
" out = np.zeros((N // cell, N // cell, bins))\n",
" for i in range(N // cell):\n",
" for j in range(N // cell):\n",
" sl = (slice(i * cell, (i + 1) * cell), slice(j * cell, (j + 1) * cell))\n",
" out[i, j] = np.bincount(b[sl].ravel(), mag[sl].ravel(), bins)\n",
" return (out / (np.linalg.norm(out) + 1e-6)).ravel()\n",
"\n",
"\n",
"def shape_features(o, s):\n",
" \"\"\"๊ฐ์ฒด ๋ชจ์ ํน์ง: ๊ฐ๋ก ๋ฐฉํฅ ํฌ์์ผ๋ก ์ ์ฒด ๊ธธ์ดยท์ฐจ์ฒด ๊ธธ์ดยท๊ฐ๋ ๋์ถ๋ถ(ํฌ์ ) ๊ธธ์ดยท๋๊บผ์ด ๋ถ๋ถ(ํฌํ) ์์น๋ฅผ ์ฐ๋ค.\n",
" ๋ฐฉ์ ์ ๊ทํ ๋ค์๋ ์๋ค(180๋)๊ฐ ๋ค์งํ ์ ์์ผ๋ฏ๋ก, ๊ฐ๋ ๋์ถ๋ถ๊ฐ ์ค๋ฅธ์ชฝ์ ์ค๋๋ก ๋ง์ถ๋ค.\"\"\"\n",
" sm = ndimage.uniform_filter(s, 3)\n",
" dark = np.abs(o - np.median(o))\n",
" m = (sm > 0.22) | (dark > 0.12)\n",
" lab, n = ndimage.label(m)\n",
" if n:\n",
" c = lab[10:22, 8:24]\n",
" k = np.bincount(c[c > 0]).argmax() if (c > 0).any() else 1\n",
" m = lab == k\n",
" p = m.sum(axis=0).astype(float)\n",
" cols = np.nonzero(p)[0]\n",
" if len(cols) == 0:\n",
" return np.zeros(8), o, s\n",
" thin = (p > 0) & (p <= 2)\n",
" left, right = thin[:N // 2].sum(), thin[N // 2:].sum()\n",
" if left > right:\n",
" o, s, m, p = o[:, ::-1], s[:, ::-1], m[:, ::-1], p[::-1]\n",
" cols = np.nonzero(p)[0]\n",
" body = np.nonzero(p > 3)[0]\n",
" d = np.where(m, dark if left <= right else dark[:, ::-1], 0)\n",
" heavy = d.sum(axis=0)\n",
" centre = (body[0] + body[-1]) / 2 if len(body) else N / 2\n",
" turret = (heavy * np.arange(N)).sum() / max(heavy.sum(), 1e-6) - centre\n",
" return np.array([cols[-1] - cols[0] + 1, len(body), ((p > 0) & (p <= 2)).sum(), p.max(), turret,\n",
" m.sum(), (cols[-1] - (body[-1] if len(body) else cols[-1])), m.sum() / max(len(cols) * p.max(), 1)]), o, s\n",
"\n",
"\n",
"def adv_features(o, s):\n",
" \"\"\"์ ๋ฐ ํน์ง: ๋ชจ์(๊ธธ์ดยทํฌ์ ยทํฌํ ์์น) + HOG(์ค๊ณฝ) + SAR ๋ฐ์ฌ ํต๊ณ(์์ฅ๋งยท๊ตฌ๋ฆ ์ํฅ ์ ์) + ํฌ์ ์ค๊ณฝ.\"\"\"\n",
" shp, o, s = shape_features(o, s)\n",
" c = s[8:24, 4:28]\n",
" sm = ndimage.uniform_filter(s, 3)\n",
" m = sm > 0.2\n",
" dark = np.abs(o - np.median(o))\n",
" return np.concatenate([shp, hog(o), [c.mean(), c.max(), (sm > 0.25).mean(), (sm > 0.4).mean(), m.sum(axis=1).max(), m.sum(axis=0).max()],\n",
" sm.mean(axis=0).reshape(8, 4).mean(1), sm.mean(axis=1).reshape(8, 4).mean(1),\n",
" dark.mean(axis=0).reshape(8, 4).mean(1), dark.mean(axis=1).reshape(8, 4).mean(1),\n",
" [o.mean(), o.std(), dark.max(), (o > 0.65).mean(), (o < 0.3).mean()]])\n",
"\n",
"\n",
"def prep(O, S, M):\n",
" return preprocess_batch(O, S, M)\n",
"\n",
"\n",
"ALL = tuple(SCEN)\n",
"# 1) ํ์ต: ์ํ ์กฐ๊ฑด 10์ข
์ ๊ณ ๋ฅด๊ฒ ์์ ์ฆ๊ฐ ํ์ต ์๋ฃ 1,200์ฅ\n",
"Otr, Str, Mtr = make_dataset(1200, seed=3, scen=ALL)\n",
"Ptr = prep(Otr, Str, Mtr); ytr = [m[\"cls\"] for m in Mtr]\n",
"adv = RandomForestClassifier(n_estimators=200, min_samples_leaf=2, random_state=0, n_jobs=1)\n",
"adv.fit(np.array([adv_features(o, s) for o, s, _ in Ptr]), ytr)\n",
"base = make_pipeline(StandardScaler(), LogisticRegression(C=0.05, max_iter=3000)) # ๋น๊ต์ฉ Baseline(๋ง์ ์กฐ๊ฑด๋ง ํ์ต)\n",
"Oc, Sc, Mc = make_dataset(600, seed=1)\n",
"base.fit(np.array([conv_features(p[0]) for p in prep(Oc, Sc, Mc)]), [m[\"cls\"] for m in Mc])\n",
"\n",
"# 2) ์ํ: ์กฐ๊ฑด 10์ข
x 4ํด๋์ค x 15์ฅ = 600์ฅ\n",
"Ote, Ste, Mte = make_dataset(600, seed=4, scen=ALL)\n",
"t0 = time.perf_counter()\n",
"Pte = prep(Ote, Ste, Mte)\n",
"pa = adv.predict(np.array([adv_features(o, s) for o, s, _ in Pte]))\n",
"lat = (time.perf_counter() - t0) / len(Pte) * 1000\n",
"pb = base.predict(np.array([conv_features(p[0]) for p in Pte]))\n",
"y, sc = np.array([m[\"cls\"] for m in Mte]), np.array([m[\"scen\"] for m in Mte])\n",
"mil = np.isin(y, list(MILITARY))\n",
"rec = lambda p, sel: ((p == y) & mil & sel).sum() / (mil & sel).sum() # ๊ตฐ์ฉ ์ฅ๋น๋ฅผ '์ ํํ ๊ธฐ์ข
'์ผ๋ก ์๋ณํ ๋น์จ\n",
"allsel = np.ones(len(y), bool)\n",
"print(f\"๊ตฐ์ฉ ์ฅ๋น ๊ธฐ์ข
์๋ณ ์ฌํ์จ: Baseline {rec(pb, allsel):.1%} -> ์ ๋ฐ ๋ชจ๋ธ {rec(pa, allsel):.1%} (๋ชฉํ 85% ์ด์: {'๋ฌ์ฑ' if rec(pa, allsel) >= 0.85 else '๋ฏธ๋ฌ'})\")\n",
"print(f\"4ํด๋์ค ์ ํ๋: Baseline {(pb == y).mean():.1%} -> ์ ๋ฐ ๋ชจ๋ธ {(pa == y).mean():.1%}, 1์ฅ๋น ์ฒ๋ฆฌ {lat:.1f} ms (๋ชฉํ 200 ms ์ดํ)\")\n",
"\n",
"# 3) ์ํ ์กฐ๊ฑด 10์ข
์ํ(๊ตฐ์ฉ ๊ธฐ์ข
์๋ณ ์ฌํ์จ 85% ์ด์์ด๋ฉด ํต๊ณผ)\n",
"rows = []\n",
"for k in ALL:\n",
" s = sc == k\n",
" rows.append({\"์กฐ๊ฑด\": k, \"Baseline ์ฌํ์จ\": round(rec(pb, s), 2), \"์ ๋ฐ ์ฌํ์จ\": round(rec(pa, s), 2),\n",
" \"์ ๋ฐ ์ ํ๋\": round(float((pa[s] == y[s]).mean()), 2), \"๋ฏผ๊ฐ->๊ตฐ์ฉ ์ค๋ถ๋ฅ\": int((~mil & s & np.isin(pa, list(MILITARY))).sum()),\n",
" \"ํ์ \": \"ํต๊ณผ\" if rec(pa, s) >= 0.85 else \"๋ฏธํต๊ณผ\"})\n",
"t = pd.DataFrame(rows)\n",
"print(\"\\n\" + t.to_string(index=False))\n",
"print(f\"ํต๊ณผ {int((t['ํ์ '] == 'ํต๊ณผ').sum())}/10์ข
\")\n",
"\n",
"# 4) ์ค๊ฒฝ๋ณด ์ ๋ฐ ํจํด๊ณผ ์ค์ ํน์ง\n",
"print(\"\\n[ํผ๋์ด ๋ง์ ์(์ค์ ->์์ธก)]\", pd.Series([f\"{a}->{b}\" for a, b in zip(y, pa) if a != b]).value_counts().head(4).to_dict())\n",
"names = [\"obj_len\", \"body_len\", \"thin_cols\", \"max_width\", \"turret_pos\", \"area\", \"barrel_len\", \"fill\"] + [f\"hog{i}\" for i in range(128)] + [\"sar_mean\", \"sar_max\", \"sar>0.25\", \"sar>0.4\", \"sar_width\", \"sar_len\"] + \\\n",
" [f\"sar_colprof{i}\" for i in range(8)] + [f\"sar_rowprof{i}\" for i in range(8)] + [f\"opt_colprof{i}\" for i in range(8)] + \\\n",
" [f\"opt_rowprof{i}\" for i in range(8)] + [\"opt_mean\", \"opt_std\", \"opt_contrast\", \"bright_frac\", \"dark_frac\"]\n",
"imp = pd.Series(adv.feature_importances_, index=names).sort_values(ascending=False)\n",
"print(\"์ค์ ํน์ง ์์ 5:\", {k: round(v, 3) for k, v in imp.head(5).items()})\n",
"print(\"-> SAR ๋ฐ์ฌ ํน์ง์ด ์์ฅ๋งยท๊ตฌ๋ฆ์ ๊ฐํด ์ฌํ์จ์ ์งํฑํ๋ค. ๋จ์ ์ค๋ฅ๋ ์ ํผ๋ ์์ฒ๋ผ ๋น์ทํ ํฌ๊ธฐ ๊ธฐ์ข
๊ฐ ํผ๋์ด๋ฉฐ ๋ฏธ์
5์์ ์ ๋ขฐ๋๋ก ๊ฑฐ๋ฅธ๋ค.\")\n",
"\n",
"fig, ax = plt.subplots(1, 2, figsize=(12, 4))\n",
"x = np.arange(len(ALL))\n",
"ax[0].bar(x - 0.2, t[\"Baseline ์ฌํ์จ\"], 0.4, label=\"baseline\"); ax[0].bar(x + 0.2, t[\"์ ๋ฐ ์ฌํ์จ\"], 0.4, label=\"advanced\")\n",
"ax[0].axhline(0.85, ls=\"--\", c=\"k\"); ax[0].set_xticks(x); ax[0].set_xticklabels(ALL, rotation=45, fontsize=8)\n",
"ax[0].set_ylabel(\"military recall\"); ax[0].legend(); ax[0].set_title(\"Recall by concealment scenario\")\n",
"imp.head(10)[::-1].plot.barh(ax=ax[1]); ax[1].set_title(\"Top feature importances\")\n",
"plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
04 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"๊ตฐ์ฉ ์ฅ๋น ๊ธฐ์ข
์๋ณ ์ฌํ์จ: Baseline 66.0% -> ์ ๋ฐ ๋ชจ๋ธ 97.3% (๋ชฉํ 85% ์ด์: ๋ฌ์ฑ)\n",
"4ํด๋์ค ์ ํ๋: Baseline 69.2% -> ์ ๋ฐ ๋ชจ๋ธ 98.0%, 1์ฅ๋น ์ฒ๋ฆฌ 1.5 ms (๋ชฉํ 200 ms ์ดํ)\n",
"\n",
" ์กฐ๊ฑด Baseline ์ฌํ์จ ์ ๋ฐ ์ฌํ์จ ์ ๋ฐ ์ ํ๋ ๋ฏผ๊ฐ->๊ตฐ์ฉ ์ค๋ถ๋ฅ ํ์ \n",
" clean 1.00 0.98 0.98 0 ํต๊ณผ\n",
" camo 0.33 0.96 0.97 0 ํต๊ณผ\n",
" occl30 0.73 1.00 1.00 0 ํต๊ณผ\n",
" occl50 0.53 0.98 0.98 0 ํต๊ณผ\n",
" cloud 0.33 1.00 1.00 0 ํต๊ณผ\n",
" low_sun 0.82 0.96 0.97 0 ํต๊ณผ\n",
"off_nadir 0.98 1.00 1.00 0 ํต๊ณผ\n",
" low_res 0.33 0.87 0.90 0 ํต๊ณผ\n",
" speckle 1.00 1.00 1.00 0 ํต๊ณผ\n",
"camo_occl 0.53 1.00 1.00 0 ํต๊ณผ\n",
"ํต๊ณผ 10/10์ข
\n",
"\n",
"[ํผ๋์ด ๋ง์ ์(์ค์ ->์์ธก)] {'truck->tank': 6, 'tank->truck': 5, 'tank->spg': 1}\n",
"์ค์ ํน์ง ์์ 5: {'sar>0.25': 0.083, 'sar_width': 0.08, 'sar_len': 0.07, 'sar_colprof2': 0.064, 'sar_colprof4': 0.057}\n",
"-> SAR ๋ฐ์ฌ ํน์ง์ด ์์ฅ๋งยท๊ตฌ๋ฆ์ ๊ฐํด ์ฌํ์จ์ ์งํฑํ๋ค. ๋จ์ ์ค๋ฅ๋ ์ ํผ๋ ์์ฒ๋ผ ๋น์ทํ ํฌ๊ธฐ ๊ธฐ์ข
๊ฐ ํผ๋์ด๋ฉฐ ๋ฏธ์
5์์ ์ ๋ขฐ๋๋ก ๊ฑฐ๋ฅธ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
05. ์์ฑ ์ดฌ์ ๊ฐ๋/๊ทธ๋ฆผ์ ์ํฅ์ ๋ฐ๋ฅธ ๊ฐ์ฒด๋ถ๋ฅ ์ ๋ขฐ๋ ์ ์ ์ฐ์ถ ๋ฐ ์ค๋ถ๋ฅ ํํฐ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์์ฑ ์ผ์์ ํด์๋ ํ๊ณ ๋ฐ ๊ธฐ์ ์ํฅ์ ๋ฐ์ํ์ฌ ์ฅ๋น ๋ถ๋ฅ ๊ฒฐ๊ณผ์ ๋ํ ์ ๋ขฐ๋ ์ ์๋ฅผ ๊ณ์ฐํ๊ณ ์ค๋ถ๋ฅ๋ฅผ ํํฐ๋งํ๋ ์์ง์ ๊ฐ๋ฐํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ผ์ ํด์๋ ํ๊ณ, ์ดฌ์ ๊ฐ๋, ํ์ ๊ณ ๋(๊ทธ๋ฆผ์), ๊ตฌ๋ฆ ์ํฅ์ ๋ฐ์ํด ๋ถ๋ฅ ๊ฒฐ๊ณผ์ ์ ๋ขฐ๋ ์ ์๋ฅผ ๊ณ์ฐํ๊ณ ์ค๋ถ๋ฅ๋ฅผ ๊ฑธ๋ฌ๋ด๋ ์์ง์ ๊ฐ๋ฐํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๋ถ๋ฅ ํ๋ฅ ์ ์ต๋๊ฐยท์ฐจ์ดยท์ํธ๋กํผ์ ๊ตฌ๋ฆ ๋น์จ, ํด์๋, ์ดฌ์ ๊ฐ๋, ํ์ ๊ณ ๋, SAR ๋ฐ์ฌ ์ธ๊ธฐ๋ฅผ ํ์ง ์งํ๋ก ์ผ์ ๋ณ๋ ๋ณด์ ์๋ฃ์์ '๋ถ๋ฅ๊ฐ ๋ง์ ํ๋ฅ '์ ํ์ตํ๊ณ ๊ทธ ๊ณ์๋ฅผ ์ ๋ขฐ๋ ๊ฐ์ค์น ๋งคํธ๋ฆญ์ค๋ก ๊ณต๊ฐ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/verification/isr01_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)์ ๊ณ์ฐํ๊ณ , ๋ฎ์ ์ ๋ขฐ๋ ๊ฒฐ๊ณผ๋ฅผ ํ๋
๋ณด๋ฅ๋ก ๋๋ ค ์ค๋ถ๋ฅ ๋ณด๊ณ ๋ฅผ ์ค์ด๋ ์์ง์ ๊ฐ๋ฐํ๋ค.\"\"\"\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\n",
"from sklearn.pipeline import make_pipeline\n",
"from sklearn.preprocessing import StandardScaler\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํฉ์ฑ ์์ฑ์์ ์กฐ๊ฐ(chip) ์์ฑ๊ธฐ (์ค์ ์์ฑ์์ยท์ฅ๋น ์ ์ยท์์น ์์)\n",
"# 32x32 ๊ดํ(EO) ์์๊ณผ SAR ์์ ํ ์์ ์ถ์ ๋ํ์ผ๋ก ๋ง๋ ๊ฐ์ฒด๋ฅผ ๊ทธ๋ฆฐ๋ค. ๋ํ ์น์๋ ๊ฐ์๊ฐ์ด๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"N = 32\n",
"STD_GSD = 0.5 # ํ์ค ํด์๋(m/ํฝ์
, ๊ฐ์ ๊ธฐ์ค)\n",
"CLASSES = [\"tank\", \"spg\", \"truck\", \"civil\"]\n",
"MILITARY = {\"tank\", \"spg\", \"truck\"}\n",
"# ๊ฐ์ฒด ์ขํ๊ณ(u: ๊ธธ์ด ๋ฐฉํฅ, v: ํญ ๋ฐฉํฅ, ํ์ค ํด์๋ ํฝ์
) ๊ฐ์ ๋ํ: (u์ค์ฌ, v์ค์ฌ, ๊ธธ์ด, ํญ, ๊ดํ ๋ฐ๊ธฐ ์ฐจ์ด)\n",
"SHAPES = {\n",
" \"tank\": [(0, 0, 11, 7, -40), (0.5, 0, 5, 5, -55), (6, 0, 7, 1.2, -60)],\n",
" \"spg\": [(0, 0, 16, 7.5, -38), (-4, 0, 6, 6.5, -52), (5, 0, 13, 1.4, -60)],\n",
" \"truck\": [(5, 0, 3.5, 5.5, -45), (-1.8, 0, 8.5, 6, -25)],\n",
" \"civil\": [(0, 0, 8, 3.8, 0), (1.2, 0, 2, 3.4, 0)],\n",
" \"decoy\": [(0, 0, 11, 7, -40), (0.5, 0, 5, 5, -42), (6, 0, 7, 1.2, -45)],\n",
" \"none\": [], # ๊ฐ์ฒด ์๋ ๋ฐฐ๊ฒฝ ํ์ผ\n",
"}\n",
"SAR_RETURN = {\"tank\": 0.85, \"spg\": 0.85, \"truck\": 0.7, \"civil\": 0.45, \"decoy\": 0.18, \"none\": 0.12} # ๊ธ์ ๋ฐ์ฌ ์ธ๊ธฐ(๊ฐ์)\n",
"\n",
"\n",
"def _mask(cls, angle, scale, squash=1.0):\n",
" \"\"\"ํ์ (angle, ๋)ยท์ถ์ฒ(scale)ยท๋น์ค๋ฌ ์ดฌ์ ์์ถ(squash)์ ์ ์ฉํด ๋ํ๋ณ ๋ง์คํฌ์ ๋ฐ๊ธฐ ์ฐจ์ด๋ฅผ ๋ง๋ ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 5] ์์ฑ ์ดฌ์ ๊ฐ๋/๊ทธ๋ฆผ์ ์ํฅ์ ๋ฐ๋ฅธ ๊ฐ์ฒด๋ถ๋ฅ ์ ๋ขฐ๋ ์ ์ ์ฐ์ถ ๋ฐ ์ค๋ถ๋ฅ ํํฐ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_chip(cls, rng, cond=None):\n",
" \"\"\"chip 1๊ฐ = (๊ดํ 32x32, SAR 32x32, ๋ฉํ๋ฐ์ดํฐ). cond: camo, occl, cloud, low_sun, off_nadir, low_res, speckle\"\"\"\n",
" c = {\"camo\": 0.0, \"occl\": 0.0, \"cloud\": 0.0, \"low_sun\": False, \"off_nadir\": 0.0, \"low_res\": False, \"speckle\": 1.0, **(cond or {})}\n",
" gsd = rng.uniform(0.9, 1.2) if c[\"low_res\"] else rng.uniform(0.4, 0.6)\n",
" scale = STD_GSD / gsd\n",
" angle, off = rng.uniform(0, 360), c[\"off_nadir\"] or rng.uniform(0, 15)\n",
" squash = np.cos(np.radians(off))\n",
" sun_el = rng.uniform(12, 22) if c[\"low_sun\"] else rng.uniform(40, 70)\n",
" sun_az = rng.uniform(0, 360)\n",
" bg = 115 + ndimage.gaussian_filter(rng.normal(0, 30, (N, N)), 2) + rng.normal(0, 5, (N, N))\n",
" parts = _mask(cls, angle, scale, squash)\n",
" body = np.any([m for m, _ in parts], axis=0) if parts else np.zeros((N, N), bool)\n",
" opt = bg.copy()\n",
" # ๊ทธ๋ฆผ์: ํ์ ๋ฐ๋ ๋ฐฉํฅ์ผ๋ก ๋ฐ๋ฆฐ ๊ฐ์ฒด ๋ชจ์(ํ์ ๊ณ ๋๊ฐ ๋ฎ์์๋ก ๊ธธ๋ค)\n",
" L = min(8.0, 2.5 / np.tan(np.radians(sun_el))) * scale\n",
" sh = ndimage.shift(body.astype(float), (L * np.cos(np.radians(sun_az)), -L * np.sin(np.radians(sun_az))), order=0) > 0.5\n",
" opt[sh & ~body] -= 35 if cls != \"decoy\" else 25\n",
" if cls == \"civil\":\n",
" base = rng.choice([200.0, 60.0]) # ๋ฏผ๊ฐ ์ฐจ๋ ์์: ๋ฐ๊ฑฐ๋ ์ด๋์\n",
" opt[body] = base + rng.normal(0, 8, body.sum())\n",
" roof = parts[1][0]\n",
" opt[roof] = base * 0.8\n",
" else:\n",
" ref = bg[body].mean() if body.any() else bg.mean()\n",
" for m, d in parts:\n",
" opt[m] = ref + d + rng.normal(0, 4 if cls != \"decoy\" else 2, m.sum())\n",
" # ์์ฅ๋ง: ๊ฐ์ฒด ์ฃผ๋ณ์ ๋ฐฐ๊ฒฝ ์ง๊ฐ์ผ๋ก ๋ฎ์ด ๋๋น๋ฅผ ๋ฎ์ถ๋ค\n",
" if c[\"camo\"] > 0 and cls != \"civil\":\n",
" net = ndimage.binary_dilation(body, iterations=2)\n",
" tex = 105 + ndimage.gaussian_filter(rng.normal(0, 25, (N, N)), 1)\n",
" opt[net] = (1 - c[\"camo\"]) * opt[net] + c[\"camo\"] * tex[net]\n",
" # ์๋ชฉ ์ฐจํ: ๊ฐ์ฒด์ ์ผ๋ถ๋ฅผ ์ด๋์ด ๋๋ฌด ๋ฉ์ด๋ฆฌ๋ก ๊ฐ๋ฆฐ๋ค\n",
" if c[\"occl\"] > 0:\n",
" trees = ndimage.gaussian_filter(rng.random((N, N)), 2)\n",
" near = ndimage.binary_dilation(body, iterations=3)\n",
" cover = near & (trees > np.quantile(trees[body], 1 - c[\"occl\"])) if body.any() else np.zeros((N, N), bool)\n",
" opt[cover] = 70 + rng.normal(0, 6, cover.sum())\n",
" # ๊ตฌ๋ฆ: ๋ฐ๊ณ ๋งค๋ํ ๋ฉ์ด๋ฆฌ(๊ดํ๋ง ๊ฐ๋ฆผ, SAR๋ ํต๊ณผ)\n",
" cmask = np.zeros((N, N), bool)\n",
" if c[\"cloud\"] > 0:\n",
" blob = ndimage.gaussian_filter(rng.random((N, N)), 4)\n",
" cmask = blob > np.quantile(blob, 1 - c[\"cloud\"])\n",
" opt[cmask] = 0.25 * opt[cmask] + 0.75 * (235 + rng.normal(0, 3, cmask.sum()))\n",
" # SAR: ๊ณฑ์
ํ ์คํํด ์ก์ + ๊ธ์ ๋ฐ์ฌ(๋ชจ์๋ฆฌ ์ด์ค ๋ฐ์ฌ๋ก ๋ฐ์ ์ )\n",
" sar = np.full((N, N), 0.12)\n",
" sar[body] = SAR_RETURN[cls] * rng.uniform(0.8, 1.2)\n",
" edge = body & ~ndimage.binary_erosion(body)\n",
" sar[edge] *= 1.3 if cls in MILITARY else 1.0\n",
" sar = sar * rng.gamma(4 / c[\"speckle\"], c[\"speckle\"] / 4, (N, N))\n",
" if c[\"low_res\"]: # ์ ํด์๋: ์ ๋ณด ์์ค(๋ธ๋ฌ)\n",
" down = lambda a: np.kron(a.reshape(N // 2, 2, N // 2, 2).mean((1, 3)), np.ones((2, 2)))\n",
" opt, sar = ndimage.gaussian_filter(down(opt), 0.7), ndimage.gaussian_filter(down(sar), 0.7)\n",
" meta = {\"cls\": cls, \"gsd\": round(float(gsd), 2), \"off_nadir\": round(float(off), 1), \"sun_el\": round(float(sun_el), 1),\n",
" \"sun_az\": round(float(sun_az), 1), \"angle\": round(float(angle), 1), \"cloud_frac\": float(cmask.mean()),\n",
" \"cloud_on_obj\": float((cmask & body).sum() / max(body.sum(), 1)), **{k: c[k] for k in (\"camo\", \"occl\", \"low_sun\", \"low_res\")}}\n",
" return np.clip(opt, 0, 255), sar, meta\n",
"\n",
"\n",
"SCEN = { # ์ํยท์ดฌ์ ์กฐ๊ฑด ์๋๋ฆฌ์ค 10์ข
\n",
" \"clean\": {}, \"camo\": {\"camo\": 0.7}, \"occl30\": {\"occl\": 0.3}, \"occl50\": {\"occl\": 0.5}, \"cloud\": {\"cloud\": 0.25},\n",
" \"low_sun\": {\"low_sun\": True}, \"off_nadir\": {\"off_nadir\": 35.0}, \"low_res\": {\"low_res\": True},\n",
" \"speckle\": {\"speckle\": 3.0}, \"camo_occl\": {\"camo\": 0.6, \"occl\": 0.3}}\n",
"\n",
"\n",
"def make_dataset(n=400, seed=7, scen=(\"clean\",), classes=CLASSES):\n",
" rng = np.random.default_rng(seed)\n",
" O, S, M = np.zeros((n, N, N)), np.zeros((n, N, N)), []\n",
" for i in range(n):\n",
" cls, sc = classes[i % len(classes)], scen[(i // len(classes)) % len(scen)]\n",
" O[i], S[i], m = make_chip(cls, rng, SCEN[sc])\n",
" m.update({\"id\": f\"IMG{i:04d}\", \"scen\": sc})\n",
" M.append(m)\n",
" return O, S, M\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ์ ์ฒ๋ฆฌ(ํด์๋ยท๊ธฐํ ๋ณด์ , ๊ตฌ๋ฆ ์ฐจํ ์ฒ๋ฆฌ, ๋ฐฉ์ ์ ๊ทํ) ๊ณตํต ํจ์ ----\n",
"def _fit(a, fill):\n",
" \"\"\"์ค์ ๊ธฐ์ค์ผ๋ก 32x32์ ๋ง๊ฒ ์๋ฅด๊ฑฐ๋ ์ฑ์ด๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 5] ์์ฑ ์ดฌ์ ๊ฐ๋/๊ทธ๋ฆผ์ ์ํฅ์ ๋ฐ๋ฅธ ๊ฐ์ฒด๋ถ๋ฅ ์ ๋ขฐ๋ ์ ์ ์ฐ์ถ ๋ฐ ์ค๋ถ๋ฅ ํํฐ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def cloud_mask(opt, sm, min_area=15):\n",
" \"\"\"๊ดํ์์ ๋ฐ๊ณ (185 ์ด๊ณผ) ๋งค๋ํ๋ฐ SAR์๋ ๋ฐ์ฌ๊ฐ ์๋ ์์ญ์ ๊ตฌ๋ฆ์ผ๋ก ๋ณธ๋ค(SAR๋ ๊ตฌ๋ฆ์ ํต๊ณผํ๋ฏ๋ก ๋ฐ์ ์ฐจ๋๊ณผ ๊ตฌ๋ถ๋๋ค).\"\"\"\n",
" bright = opt > 185\n",
" if bright.sum() < min_area:\n",
" return np.zeros_like(bright)\n",
" local_sd = np.sqrt(np.maximum(ndimage.uniform_filter(opt ** 2, 3) - ndimage.uniform_filter(opt, 3) ** 2, 0))\n",
" cand = bright & (local_sd < 12) & (sm < 0.3)\n",
" lab, n = ndimage.label(cand)\n",
" if n == 0:\n",
" return cand\n",
" sizes = ndimage.sum(cand.astype(float), lab, range(1, n + 1))\n",
" return ndimage.binary_dilation(np.isin(lab, 1 + np.where(sizes >= min_area)[0]), iterations=2) & (opt > 140)\n",
"\n",
"\n",
"def orientation(opt, sm):\n",
" \"\"\"SAR ๋ฐ์ฌ(์์ฅยท๊ตฌ๋ฆ ์ํฅ ์ ์, sm์ ํํํํ SAR)์ ๊ดํ ๋๋น๋ฅผ ํฉ์น ๊ฐ์ฒด ๋ง์คํฌ์ ์ฃผ์ถ ๋ฐฉํฅ(๋).\"\"\"\n",
" m = (sm > 0.3) | (np.abs(opt - opt.mean()) > 30)\n",
" lab, n = ndimage.label(m)\n",
" if n == 0:\n",
" return 0.0, m\n",
" c = lab[N // 2 - 6:N // 2 + 6, N // 2 - 6:N // 2 + 6]\n",
" k = np.bincount(c[c > 0]).argmax() if (c > 0).any() else 1 + int(np.argmax(ndimage.sum(m.astype(float), lab, range(1, n + 1))))\n",
" m = lab == k\n",
" yy, xx = np.nonzero(m)\n",
" if len(yy) < 5:\n",
" return 0.0, m\n",
" dy, dx = yy - yy.mean(), xx - xx.mean() # 2์ฐจ ๋ชจ๋ฉํธ๋ก ์ฃผ์ถ ๊ฐ๋ ๊ณ์ฐ\n",
" return float(np.degrees(0.5 * np.arctan2(2 * (dx * dy).mean(), (dx * dx).mean() - (dy * dy).mean()))), m\n",
"\n",
"\n",
"_YY, _XX = np.mgrid[0:N, 0:N] - (N - 1) / 2\n",
"\n",
"\n",
"def warp(stack, zy, zx, ang):\n",
" \"\"\"์ฌ๋ฌ ์ฅ(B,์ฑ๋,32,32)์ ์ฅ๋ณ ํ๋ยท์ถ์(zy, zx)์ ํ์ (ang, ๋)์ ํ ๋ฒ์ ์์ ํ ๋ณด๊ฐ์ผ๋ก ์ ์ฉํ๋ค(๋ฒกํฐ ์ฐ์ฐ).\"\"\"\n",
" a = np.radians(ang)[:, None, None]\n",
" zy, zx = np.asarray(zy)[:, None, None], np.asarray(zx)[:, None, None]\n",
" sy = np.clip((np.cos(a) * _YY + np.sin(a) * _XX) / zy + (N - 1) / 2, 0, N - 1.001) # ์ถ๋ ฅ ์ขํ -> ์๋ณธ ์ขํ\n",
" sx = np.clip((-np.sin(a) * _YY + np.cos(a) * _XX) / zx + (N - 1) / 2, 0, N - 1.001)\n",
" y0, x0 = sy.astype(int), sx.astype(int)\n",
" fy, fx = (sy - y0).reshape(len(stack), 1, -1), (sx - x0).reshape(len(stack), 1, -1)\n",
" flat = stack.reshape(len(stack), stack.shape[1], -1)\n",
" lin = (y0 * N + x0).reshape(len(stack), 1, -1)\n",
" g = lambda d: np.take_along_axis(flat, np.broadcast_to(lin + d, (len(stack), stack.shape[1], lin.shape[2])), axis=2)\n",
" out = g(0) * (1 - fy) * (1 - fx) + g(N) * fy * (1 - fx) + g(1) * (1 - fy) * fx + g(N + 1) * fy * fx\n",
" return out.reshape(stack.shape)\n",
"\n",
"\n",
"def preprocess_batch(O, S, M):\n",
" \"\"\"์ฌ๋ฌ ์ฅ์ ํ ๋ฒ์ ์ ์ฒ๋ฆฌํ๋ค. 0) ๊ฒฐ์ธก ๋ณด์ 1) ํด์๋ยท๋น์ค๋ฌ ์ดฌ์ ๋ณด์ (ํ์ค 0.5m/ํฝ์
)\n",
" 2) ๊ตฌ๋ฆ ์ฐจํ ์์ญ์ ๋ฐฐ๊ฒฝ๊ฐ์ผ๋ก ์ฑ์ 3) ๊ฐ์ฒด ์ฃผ์ถ์ ๊ฐ๋ก๋ก ํ์ 4) 0~1 ์ ๊ทํ.\"\"\"\n",
" src = np.stack([np.asarray(O, float), np.asarray(S, float)], axis=1)\n",
" if np.isnan(src).any(): # 0) ์ผ์ ๊ฒฐ์ธก ํฝ์
๋ณด์ \n",
" mean = np.nanmean(src, axis=(2, 3), keepdims=True)\n",
" src = np.where(np.isnan(src), mean, src)\n",
" zy = np.array([m[\"gsd\"] / STD_GSD / np.cos(np.radians(m[\"off_nadir\"])) for m in M])\n",
" zx = np.array([m[\"gsd\"] / STD_GSD for m in M])\n",
" out = warp(src, zy, zx, np.zeros(len(M)))\n",
" sm = ndimage.uniform_filter(out[:, 1], (1, 3, 3))\n",
" angs, cloud = np.zeros(len(M)), np.zeros(len(M))\n",
" for i in range(len(M)):\n",
" cm = cloud_mask(out[i, 0], sm[i])\n",
" if cm.any():\n",
" fill = out[i, 0][~cm].mean() if (~cm).any() else 115.0\n",
" out[i, 0][cm] = fill\n",
" back = warp(cm[None, None].astype(float), [1 / zy[i]], [1 / zx[i]], np.zeros(1))[0, 0] > 0.5\n",
" src[i, 0][back] = fill # ์๋ณธ์ ๊ฐ์ ๊ตฌ๋ฆ ์์ญ๋ ์ฑ์\n",
" cloud[i] = cm.mean()\n",
" angs[i] = orientation(out[i, 0], sm[i])[0]\n",
" rot = np.abs(angs) > 3 # 3๋ ์ดํ๋ ํ์ ์๋ต\n",
" if rot.any():\n",
" out[rot] = warp(src[rot], zy[rot], zx[rot], angs[rot])\n",
" return [(np.clip(o / 255, 0, 1), np.clip(s / 1.5, 0, 1), float(c)) for (o, s), c in zip(out, cloud)]\n",
"\n",
"\n",
"def preprocess(opt, sar, meta):\n",
" \"\"\"ํ ์ฅ ์ ์ฒ๋ฆฌ(preprocess_batch์ ๊ฐ์ ์ฒ๋ฆฌ).\"\"\"\n",
" return preprocess_batch([opt], [sar], [meta])[0]\n",
"\n",
"# ---- ํน์ง ์ถ์ถ ๊ณตํต ํจ์ ----\n",
"KERNELS = [np.array(k, float) for k in (\n",
" [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], [[-1, -2, -1], [0, 0, 0], [1, 2, 1]], [[0, 1, 0], [1, -4, 1], [0, 1, 0]],\n",
" [[2, -1, -1], [-1, 2, -1], [-1, -1, 2]], [[-1, -1, 2], [-1, 2, -1], [2, -1, -1]], [[1, 1, 1], [1, 1, 1], [1, 1, 1]])]\n",
"\n",
"\n",
"def conv_features(o):\n",
" \"\"\"๊ฐ์ด CNN ํน์ง: ๊ณ ์ ํฉ์ฑ๊ณฑ ํํฐ 6์ข
-> ReLU -> 4x4 ์ต๋ ํ๋ง(8x8 ์ถ๋ ฅ).\"\"\"\n",
" feats = []\n",
" for k in KERNELS:\n",
" r = np.maximum(ndimage.correlate(o - o.mean(), k / np.abs(k).sum()), 0)\n",
" feats.append(r.reshape(8, 4, 8, 4).max(axis=(1, 3)).ravel())\n",
" feats.append(np.maximum(-ndimage.correlate(o - o.mean(), k / np.abs(k).sum()), 0).reshape(8, 4, 8, 4).max(axis=(1, 3)).ravel())\n",
" return np.concatenate(feats)\n",
"\n",
"\n",
"def hog(o, cell=8, bins=8):\n",
" gy, gx = np.gradient(o)\n",
" mag, ang = np.hypot(gx, gy), np.mod(np.arctan2(gy, gx), np.pi)\n",
" b = np.minimum((ang / np.pi * bins).astype(int), bins - 1)\n",
" out = np.zeros((N // cell, N // cell, bins))\n",
" for i in range(N // cell):\n",
" for j in range(N // cell):\n",
" sl = (slice(i * cell, (i + 1) * cell), slice(j * cell, (j + 1) * cell))\n",
" out[i, j] = np.bincount(b[sl].ravel(), mag[sl].ravel(), bins)\n",
" return (out / (np.linalg.norm(out) + 1e-6)).ravel()\n",
"\n",
"\n",
"def shape_features(o, s):\n",
" \"\"\"๊ฐ์ฒด ๋ชจ์ ํน์ง: ๊ฐ๋ก ๋ฐฉํฅ ํฌ์์ผ๋ก ์ ์ฒด ๊ธธ์ดยท์ฐจ์ฒด ๊ธธ์ดยท๊ฐ๋ ๋์ถ๋ถ(ํฌ์ ) ๊ธธ์ดยท๋๊บผ์ด ๋ถ๋ถ(ํฌํ) ์์น๋ฅผ ์ฐ๋ค.\n",
" ๋ฐฉ์ ์ ๊ทํ ๋ค์๋ ์๋ค(180๋)๊ฐ ๋ค์งํ ์ ์์ผ๋ฏ๋ก, ๊ฐ๋ ๋์ถ๋ถ๊ฐ ์ค๋ฅธ์ชฝ์ ์ค๋๋ก ๋ง์ถ๋ค.\"\"\"\n",
" sm = ndimage.uniform_filter(s, 3)\n",
" dark = np.abs(o - np.median(o))\n",
" m = (sm > 0.22) | (dark > 0.12)\n",
" lab, n = ndimage.label(m)\n",
" if n:\n",
" c = lab[10:22, 8:24]\n",
" k = np.bincount(c[c > 0]).argmax() if (c > 0).any() else 1\n",
" m = lab == k\n",
" p = m.sum(axis=0).astype(float)\n",
" cols = np.nonzero(p)[0]\n",
" if len(cols) == 0:\n",
" return np.zeros(8), o, s\n",
" thin = (p > 0) & (p <= 2)\n",
" left, right = thin[:N // 2].sum(), thin[N // 2:].sum()\n",
" if left > right:\n",
" o, s, m, p = o[:, ::-1], s[:, ::-1], m[:, ::-1], p[::-1]\n",
" cols = np.nonzero(p)[0]\n",
" body = np.nonzero(p > 3)[0]\n",
" d = np.where(m, dark if left <= right else dark[:, ::-1], 0)\n",
" heavy = d.sum(axis=0)\n",
" centre = (body[0] + body[-1]) / 2 if len(body) else N / 2\n",
" turret = (heavy * np.arange(N)).sum() / max(heavy.sum(), 1e-6) - centre\n",
" return np.array([cols[-1] - cols[0] + 1, len(body), ((p > 0) & (p <= 2)).sum(), p.max(), turret,\n",
" m.sum(), (cols[-1] - (body[-1] if len(body) else cols[-1])), m.sum() / max(len(cols) * p.max(), 1)]), o, s\n",
"\n",
"\n",
"def adv_features(o, s):\n",
" \"\"\"์ ๋ฐ ํน์ง: ๋ชจ์(๊ธธ์ดยทํฌ์ ยทํฌํ ์์น) + HOG(์ค๊ณฝ) + SAR ๋ฐ์ฌ ํต๊ณ(์์ฅ๋งยท๊ตฌ๋ฆ ์ํฅ ์ ์) + ํฌ์ ์ค๊ณฝ.\"\"\"\n",
" shp, o, s = shape_features(o, s)\n",
" c = s[8:24, 4:28]\n",
" sm = ndimage.uniform_filter(s, 3)\n",
" m = sm > 0.2\n",
" dark = np.abs(o - np.median(o))\n",
" return np.concatenate([shp, hog(o), [c.mean(), c.max(), (sm > 0.25).mean(), (sm > 0.4).mean(), m.sum(axis=1).max(), m.sum(axis=0).max()],\n",
" sm.mean(axis=0).reshape(8, 4).mean(1), sm.mean(axis=1).reshape(8, 4).mean(1),\n",
" dark.mean(axis=0).reshape(8, 4).mean(1), dark.mean(axis=1).reshape(8, 4).mean(1),\n",
" [o.mean(), o.std(), dark.max(), (o > 0.65).mean(), (o < 0.3).mean()]])\n",
"\n",
"Q_NAMES = [\"max_prob\", \"margin\", \"entropy\", \"cloud_frac\", \"gsd\", \"off_nadir\", \"sun_el\", \"sar_mean\"]\n",
"\n",
"\n",
"def run(O, S, M, clf):\n",
" P = preprocess_batch(O, S, M)\n",
" X = np.array([adv_features(o, s) for o, s, _ in P])\n",
" pr = clf.predict_proba(X)\n",
" srt = np.sort(pr, axis=1)\n",
" ent = -(pr * np.log(np.clip(pr, 1e-9, 1))).sum(1) / np.log(pr.shape[1])\n",
" Q = np.c_[srt[:, -1], srt[:, -1] - srt[:, -2], ent, [p[2] for p in P], [m[\"gsd\"] for m in M],\n",
" [m[\"off_nadir\"] for m in M], [m[\"sun_el\"] for m in M], [p[1][8:24, 4:28].mean() for p in P]]\n",
" return clf.classes_[pr.argmax(1)], Q\n",
"\n",
"\n",
"def grade(c):\n",
" return np.where(c >= 0.9, \"High\", np.where(c >= 0.6, \"Medium\", \"Low\"))\n",
"\n",
"\n",
"ALL = tuple(SCEN)\n",
"Otr, Str, Mtr = make_dataset(1200, seed=3, scen=ALL)\n",
"clf = RandomForestClassifier(n_estimators=200, min_samples_leaf=2, random_state=0, n_jobs=1)\n",
"clf.fit(np.array([adv_features(o, s) for o, s, _ in preprocess_batch(Otr, Str, Mtr)]), [m[\"cls\"] for m in Mtr])\n",
"\n",
"# 1) ์ ๋ขฐ๋ ๊ฐ์ค์น ํ์ต: ๋ณ๋ ๋ณด์ ์๋ฃ 600์ฅ์์ '๋ถ๋ฅ๊ฐ ๋ง์๋๊ฐ'๋ฅผ ํ์ง ์งํ๋ก ์์ธก\n",
"Oc, Sc, Mc = make_dataset(600, seed=6, scen=ALL)\n",
"pc, Qc = run(Oc, Sc, Mc, clf)\n",
"conf_model = make_pipeline(StandardScaler(), LogisticRegression(max_iter=2000)).fit(Qc, pc == np.array([m[\"cls\"] for m in Mc]))\n",
"w = pd.Series(conf_model[-1].coef_[0], index=Q_NAMES).round(2)\n",
"print(\"[์ ๋ขฐ๋ ๊ฐ์ค์น(ํ์คํ ๊ณ์)]\", w.to_dict())\n",
"\n",
"# 2) ์ํ 800์ฅ: ๊ธฐ์กด(๋ถ๋ฅ ๊ฒฐ๊ณผ ๊ทธ๋๋ก ๋ณด๊ณ ) vs ์์ง(High๋ง ์๋ ๋ณด๊ณ , ๋๋จธ์ง๋ ํ๋
๋ณด๋ฅ)\n",
"Ot, St, Mt = make_dataset(800, seed=5, scen=ALL)\n",
"pt, Qt = run(Ot, St, Mt, clf)\n",
"y = np.array([m[\"cls\"] for m in Mt])\n",
"conf = conf_model.predict_proba(Qt)[:, 1]\n",
"G = grade(conf)\n",
"wrong = pt != y\n",
"rep0, rep1 = np.isin(pt, list(MILITARY)), np.isin(pt, list(MILITARY)) & (G == \"High\")\n",
"far0, far1 = (wrong & rep0).sum() / rep0.sum(), (wrong & rep1).sum() / rep1.sum()\n",
"print(f\"\\n๊ธฐ์กด: ๊ตฐ์ฉ ๋ณด๊ณ {rep0.sum()}๊ฑด ์ค ์ค๋ถ๋ฅ {(wrong & rep0).sum()}๊ฑด (์ค๋ถ๋ฅ์จ {far0:.1%})\")\n",
"print(f\"์์ง: ๊ตฐ์ฉ ์๋ ๋ณด๊ณ (High) {rep1.sum()}๊ฑด ์ค ์ค๋ถ๋ฅ {(wrong & rep1).sum()}๊ฑด (์ค๋ถ๋ฅ์จ {far1:.1%}), ํ๋
๋ณด๋ฅ(MediumยทLow) {(rep0 & ~rep1).sum()}๊ฑด\")\n",
"cut = 1 - far1 / far0\n",
"print(f\"์ค๋ถ๋ฅ(์ค๊ฒฝ๋ณด) ๊ฐ์ถ {cut:.1%} (๋ชฉํ 40% ์ด์: {'๋ฌ์ฑ' if cut >= 0.4 else '๋ฏธ๋ฌ'}), ROC-AUC(์ ๋ขฐ๋ vs ์ ๋ต ์ฌ๋ถ) {roc_auc_score(~wrong, conf):.3f}\")\n",
"\n",
"# 3) ๋ฑ๊ธ๋ณ ์ค์ ์ ๋ต๋ฅ ๊ณผ ์๊ณ๊ฐ ๋ฏผ๊ฐ๋\n",
"sc = np.array([m[\"scen\"] for m in Mt])\n",
"print(\"\\n๋ฑ๊ธ ๊ฑด์ ์ค์ ์ ๋ต๋ฅ ์ฃผ์ ์กฐ๊ฑด\")\n",
"for g in [\"High\", \"Medium\", \"Low\"]:\n",
" s = G == g\n",
" print(f\"{g:6s} {s.sum():4d} {(~wrong[s]).mean() if s.any() else 0:9.1%} {pd.Series(sc[s]).value_counts().head(2).to_dict()}\")\n",
"print(\"\\nHigh ๊ธฐ์ค ์๋๋ณด๊ณ ์ค๋ถ๋ฅ์จ ํ๋
๋ณด๋ฅ\")\n",
"for t in [0.8, 0.85, 0.9, 0.95]:\n",
" r = rep0 & (conf >= t)\n",
" print(f\" {t:.2f} {r.sum():6d} {(wrong & r).sum() / max(r.sum(), 1):6.1%} {(rep0 & ~r).sum():6d}\")\n",
"\n",
"# 4) ์ผ๊ด์ฑ: ๊ฐ์ ์์์ ๋ฏธ์ธํ ํฝ์
์ก์(+-1%)์ ๋ฃ์ด๋ ๊ฐ์ ๋ฑ๊ธ์ด ๋์ค๋ ๋น์จ\n",
"rng = np.random.default_rng(0)\n",
"sub = np.arange(200)\n",
"_, Qn = run(Ot[sub] * (1 + rng.normal(0, 0.01, Ot[sub].shape)), St[sub], [Mt[i] for i in sub], clf)\n",
"same = (grade(conf_model.predict_proba(Qn)[:, 1]) == G[sub]).mean()\n",
"print(f\"\\n์ ๋ขฐ๋ ๋ฑ๊ธ ์ฐ์ถ ์ผ๊ด์ฑ {same:.1%} (๋ชฉํ 90% ์ด์: {'๋ฌ์ฑ' if same >= 0.9 else '๋ฏธ๋ฌ'})\")\n",
"print(\"High๋ ์ต์ข
ํ๋
์ด ์๋๋ฉฐ, ๊ณ ๊ฐ์น ํ์ ์ ๋ฏธ์
7์ ์์๋ถ์๊ด ํ๋
์ ๊ฑฐ์น๋ค.\")\n",
"\n",
"fig, ax = plt.subplots(1, 2, figsize=(12, 4))\n",
"ax[0].hist([conf[~wrong], conf[wrong]], bins=20, range=(0, 1), label=[\"correct\", \"misclassified\"], stacked=True)\n",
"ax[0].axvline(0.9, ls=\"--\", c=\"k\"); ax[0].set_xlabel(\"confidence score\"); ax[0].legend(); ax[0].set_title(\"Confidence vs correctness\")\n",
"w.plot.barh(ax=ax[1], color=[\"tab:green\" if v > 0 else \"tab:red\" for v in w]); ax[1].set_title(\"Confidence weight matrix (std. coef)\")\n",
"plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
05 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[์ ๋ขฐ๋ ๊ฐ์ค์น(ํ์คํ ๊ณ์)] {'max_prob': 0.69, 'margin': 0.81, 'entropy': -0.62, 'cloud_frac': 0.2, 'gsd': -0.06, 'off_nadir': -0.54, 'sun_el': -0.52, 'sar_mean': 0.24}\n",
"\n",
"๊ธฐ์กด: ๊ตฐ์ฉ ๋ณด๊ณ 600๊ฑด ์ค ์ค๋ถ๋ฅ 19๊ฑด (์ค๋ถ๋ฅ์จ 3.2%)\n",
"์์ง: ๊ตฐ์ฉ ์๋ ๋ณด๊ณ (High) 541๊ฑด ์ค ์ค๋ถ๋ฅ 3๊ฑด (์ค๋ถ๋ฅ์จ 0.6%), ํ๋
๋ณด๋ฅ(MediumยทLow) 59๊ฑด\n",
"์ค๋ถ๋ฅ(์ค๊ฒฝ๋ณด) ๊ฐ์ถ 82.5% (๋ชฉํ 40% ์ด์: ๋ฌ์ฑ), ROC-AUC(์ ๋ขฐ๋ vs ์ ๋ต ์ฌ๋ถ) 0.961\n",
"\n",
"๋ฑ๊ธ ๊ฑด์ ์ค์ ์ ๋ต๋ฅ ์ฃผ์ ์กฐ๊ฑด\n",
"High 741 99.6% {'occl50': 79, 'clean': 78}\n",
"Medium 38 84.2% {'low_res': 13, 'occl30': 5}\n",
"Low 21 52.4% {'low_res': 9, 'speckle': 4}\n",
"\n",
"High ๊ธฐ์ค ์๋๋ณด๊ณ ์ค๋ถ๋ฅ์จ ํ๋
๋ณด๋ฅ\n",
" 0.80 555 1.1% 45\n",
" 0.85 552 0.9% 48\n",
" 0.90 541 0.6% 59\n",
" 0.95 521 0.6% 79\n",
"\n",
"์ ๋ขฐ๋ ๋ฑ๊ธ ์ฐ์ถ ์ผ๊ด์ฑ 98.5% (๋ชฉํ 90% ์ด์: ๋ฌ์ฑ)\n",
"High๋ ์ต์ข
ํ๋
์ด ์๋๋ฉฐ, ๊ณ ๊ฐ์น ํ์ ์ ๋ฏธ์
7์ ์์๋ถ์๊ด ํ๋
์ ๊ฑฐ์น๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
06. ์์ฑ์์ ํ์ ํ์ง ์์น ๋ฐ ์๋ณ ํ๋ฅ ์ง๋(Heatmap) ๊ฐ์ํ UI ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์๋ณ๋ ์ฅ๋น์ ์์น, ๊ธฐ์ข
, ์ ๋ขฐ๋ ๋ฐ ์ง๊ฒฐ์ง ๋ฐ์ง๋๋ฅผ ์ง๋์์ ์ค๋ฒ๋ ์ด ํ์ํ๋ ์์ฑ ISR ์์๋ถ์ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์๋ณ๋ ์ฅ๋น์ ์์นยท๊ธฐ์ข
ยท์๋ณ ํ๋ฅ ๊ณผ ์ง๊ฒฐ์ง ๋ฐ์ง๋๋ฅผ ์ง๋์ ๊ฒน์ณ ๋ณด์ฌ ์ฃผ๋ ์์ฑ ISR ์์๋ถ์ ํ๋ฉด์ ๊ตฌํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๊ฐ์ ๊ด์ญ ์์(ํ์ผ 10x10)์ ํ ์ค์ฉ ๋ฐ์ ํ๋
ํ๋ ๊ฐฑ์ ํ๋ฆ์ ๋ชจ์ํ๊ณ ์ฒ๋ฆฌ์๊ฐ์ ์ฐ๋ค ๊ณผ์ ์ ์งํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/ui/isr01_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": [
"\"\"\"๊ฐ์ ๊ด์ญ ์์ฑ์์(32x32 ํ์ผ 10x10)์ ํ์ผ ๋จ์๋ก ํ๋
ํด ์๋ณ ์ฅ๋น์ ์์นยท๊ธฐ์ข
ยท์๋ณ ํ๋ฅ ์ ์ง๋์ ๊ฒน์ณ ํ์ํ๊ณ , ๊ตฐ์ฉ ์ฅ๋น ์ง๊ฒฐ์ง ๋ฐ์ง๋(Heatmap)์ ๊ธฐ์ข
๋ณ ํํฉ์ ํจ๊ป ๋ณด์ฌ ์ฃผ๋ ์์ฑ ISR ์์๋ถ์ ํ๋ฉด์ ๊ตฌํํ๋ฉฐ ๊ฐฑ์ ์ฒ๋ฆฌ์๊ฐ๊ณผ ๋ ๋๋ง ์๊ฐ์ ์ธก์ ํ๋ค.\"\"\"\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.ensemble import RandomForestClassifier\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํฉ์ฑ ์์ฑ์์ ์กฐ๊ฐ(chip) ์์ฑ๊ธฐ (์ค์ ์์ฑ์์ยท์ฅ๋น ์ ์ยท์์น ์์)\n",
"# 32x32 ๊ดํ(EO) ์์๊ณผ SAR ์์ ํ ์์ ์ถ์ ๋ํ์ผ๋ก ๋ง๋ ๊ฐ์ฒด๋ฅผ ๊ทธ๋ฆฐ๋ค. ๋ํ ์น์๋ ๊ฐ์๊ฐ์ด๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"N = 32\n",
"STD_GSD = 0.5 # ํ์ค ํด์๋(m/ํฝ์
, ๊ฐ์ ๊ธฐ์ค)\n",
"CLASSES = [\"tank\", \"spg\", \"truck\", \"civil\"]\n",
"MILITARY = {\"tank\", \"spg\", \"truck\"}\n",
"# ๊ฐ์ฒด ์ขํ๊ณ(u: ๊ธธ์ด ๋ฐฉํฅ, v: ํญ ๋ฐฉํฅ, ํ์ค ํด์๋ ํฝ์
) ๊ฐ์ ๋ํ: (u์ค์ฌ, v์ค์ฌ, ๊ธธ์ด, ํญ, ๊ดํ ๋ฐ๊ธฐ ์ฐจ์ด)\n",
"SHAPES = {\n",
" \"tank\": [(0, 0, 11, 7, -40), (0.5, 0, 5, 5, -55), (6, 0, 7, 1.2, -60)],\n",
" \"spg\": [(0, 0, 16, 7.5, -38), (-4, 0, 6, 6.5, -52), (5, 0, 13, 1.4, -60)],\n",
" \"truck\": [(5, 0, 3.5, 5.5, -45), (-1.8, 0, 8.5, 6, -25)],\n",
" \"civil\": [(0, 0, 8, 3.8, 0), (1.2, 0, 2, 3.4, 0)],\n",
" \"decoy\": [(0, 0, 11, 7, -40), (0.5, 0, 5, 5, -42), (6, 0, 7, 1.2, -45)],\n",
" \"none\": [], # ๊ฐ์ฒด ์๋ ๋ฐฐ๊ฒฝ ํ์ผ\n",
"}\n",
"SAR_RETURN = {\"tank\": 0.85, \"spg\": 0.85, \"truck\": 0.7, \"civil\": 0.45, \"decoy\": 0.18, \"none\": 0.12} # ๊ธ์ ๋ฐ์ฌ ์ธ๊ธฐ(๊ฐ์)\n",
"\n",
"\n",
"def _mask(cls, angle, scale, squash=1.0):\n",
" \"\"\"ํ์ (angle, ๋)ยท์ถ์ฒ(scale)ยท๋น์ค๋ฌ ์ดฌ์ ์์ถ(squash)์ ์ ์ฉํด ๋ํ๋ณ ๋ง์คํฌ์ ๋ฐ๊ธฐ ์ฐจ์ด๋ฅผ ๋ง๋ ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 6] ์์ฑ์์ ํ์ ํ์ง ์์น ๋ฐ ์๋ณ ํ๋ฅ ์ง๋(Heatmap) ๊ฐ์ํ UI ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_chip(cls, rng, cond=None):\n",
" \"\"\"chip 1๊ฐ = (๊ดํ 32x32, SAR 32x32, ๋ฉํ๋ฐ์ดํฐ). cond: camo, occl, cloud, low_sun, off_nadir, low_res, speckle\"\"\"\n",
" c = {\"camo\": 0.0, \"occl\": 0.0, \"cloud\": 0.0, \"low_sun\": False, \"off_nadir\": 0.0, \"low_res\": False, \"speckle\": 1.0, **(cond or {})}\n",
" gsd = rng.uniform(0.9, 1.2) if c[\"low_res\"] else rng.uniform(0.4, 0.6)\n",
" scale = STD_GSD / gsd\n",
" angle, off = rng.uniform(0, 360), c[\"off_nadir\"] or rng.uniform(0, 15)\n",
" squash = np.cos(np.radians(off))\n",
" sun_el = rng.uniform(12, 22) if c[\"low_sun\"] else rng.uniform(40, 70)\n",
" sun_az = rng.uniform(0, 360)\n",
" bg = 115 + ndimage.gaussian_filter(rng.normal(0, 30, (N, N)), 2) + rng.normal(0, 5, (N, N))\n",
" parts = _mask(cls, angle, scale, squash)\n",
" body = np.any([m for m, _ in parts], axis=0) if parts else np.zeros((N, N), bool)\n",
" opt = bg.copy()\n",
" # ๊ทธ๋ฆผ์: ํ์ ๋ฐ๋ ๋ฐฉํฅ์ผ๋ก ๋ฐ๋ฆฐ ๊ฐ์ฒด ๋ชจ์(ํ์ ๊ณ ๋๊ฐ ๋ฎ์์๋ก ๊ธธ๋ค)\n",
" L = min(8.0, 2.5 / np.tan(np.radians(sun_el))) * scale\n",
" sh = ndimage.shift(body.astype(float), (L * np.cos(np.radians(sun_az)), -L * np.sin(np.radians(sun_az))), order=0) > 0.5\n",
" opt[sh & ~body] -= 35 if cls != \"decoy\" else 25\n",
" if cls == \"civil\":\n",
" base = rng.choice([200.0, 60.0]) # ๋ฏผ๊ฐ ์ฐจ๋ ์์: ๋ฐ๊ฑฐ๋ ์ด๋์\n",
" opt[body] = base + rng.normal(0, 8, body.sum())\n",
" roof = parts[1][0]\n",
" opt[roof] = base * 0.8\n",
" else:\n",
" ref = bg[body].mean() if body.any() else bg.mean()\n",
" for m, d in parts:\n",
" opt[m] = ref + d + rng.normal(0, 4 if cls != \"decoy\" else 2, m.sum())\n",
" # ์์ฅ๋ง: ๊ฐ์ฒด ์ฃผ๋ณ์ ๋ฐฐ๊ฒฝ ์ง๊ฐ์ผ๋ก ๋ฎ์ด ๋๋น๋ฅผ ๋ฎ์ถ๋ค\n",
" if c[\"camo\"] > 0 and cls != \"civil\":\n",
" net = ndimage.binary_dilation(body, iterations=2)\n",
" tex = 105 + ndimage.gaussian_filter(rng.normal(0, 25, (N, N)), 1)\n",
" opt[net] = (1 - c[\"camo\"]) * opt[net] + c[\"camo\"] * tex[net]\n",
" # ์๋ชฉ ์ฐจํ: ๊ฐ์ฒด์ ์ผ๋ถ๋ฅผ ์ด๋์ด ๋๋ฌด ๋ฉ์ด๋ฆฌ๋ก ๊ฐ๋ฆฐ๋ค\n",
" if c[\"occl\"] > 0:\n",
" trees = ndimage.gaussian_filter(rng.random((N, N)), 2)\n",
" near = ndimage.binary_dilation(body, iterations=3)\n",
" cover = near & (trees > np.quantile(trees[body], 1 - c[\"occl\"])) if body.any() else np.zeros((N, N), bool)\n",
" opt[cover] = 70 + rng.normal(0, 6, cover.sum())\n",
" # ๊ตฌ๋ฆ: ๋ฐ๊ณ ๋งค๋ํ ๋ฉ์ด๋ฆฌ(๊ดํ๋ง ๊ฐ๋ฆผ, SAR๋ ํต๊ณผ)\n",
" cmask = np.zeros((N, N), bool)\n",
" if c[\"cloud\"] > 0:\n",
" blob = ndimage.gaussian_filter(rng.random((N, N)), 4)\n",
" cmask = blob > np.quantile(blob, 1 - c[\"cloud\"])\n",
" opt[cmask] = 0.25 * opt[cmask] + 0.75 * (235 + rng.normal(0, 3, cmask.sum()))\n",
" # SAR: ๊ณฑ์
ํ ์คํํด ์ก์ + ๊ธ์ ๋ฐ์ฌ(๋ชจ์๋ฆฌ ์ด์ค ๋ฐ์ฌ๋ก ๋ฐ์ ์ )\n",
" sar = np.full((N, N), 0.12)\n",
" sar[body] = SAR_RETURN[cls] * rng.uniform(0.8, 1.2)\n",
" edge = body & ~ndimage.binary_erosion(body)\n",
" sar[edge] *= 1.3 if cls in MILITARY else 1.0\n",
" sar = sar * rng.gamma(4 / c[\"speckle\"], c[\"speckle\"] / 4, (N, N))\n",
" if c[\"low_res\"]: # ์ ํด์๋: ์ ๋ณด ์์ค(๋ธ๋ฌ)\n",
" down = lambda a: np.kron(a.reshape(N // 2, 2, N // 2, 2).mean((1, 3)), np.ones((2, 2)))\n",
" opt, sar = ndimage.gaussian_filter(down(opt), 0.7), ndimage.gaussian_filter(down(sar), 0.7)\n",
" meta = {\"cls\": cls, \"gsd\": round(float(gsd), 2), \"off_nadir\": round(float(off), 1), \"sun_el\": round(float(sun_el), 1),\n",
" \"sun_az\": round(float(sun_az), 1), \"angle\": round(float(angle), 1), \"cloud_frac\": float(cmask.mean()),\n",
" \"cloud_on_obj\": float((cmask & body).sum() / max(body.sum(), 1)), **{k: c[k] for k in (\"camo\", \"occl\", \"low_sun\", \"low_res\")}}\n",
" return np.clip(opt, 0, 255), sar, meta\n",
"\n",
"\n",
"SCEN = { # ์ํยท์ดฌ์ ์กฐ๊ฑด ์๋๋ฆฌ์ค 10์ข
\n",
" \"clean\": {}, \"camo\": {\"camo\": 0.7}, \"occl30\": {\"occl\": 0.3}, \"occl50\": {\"occl\": 0.5}, \"cloud\": {\"cloud\": 0.25},\n",
" \"low_sun\": {\"low_sun\": True}, \"off_nadir\": {\"off_nadir\": 35.0}, \"low_res\": {\"low_res\": True},\n",
" \"speckle\": {\"speckle\": 3.0}, \"camo_occl\": {\"camo\": 0.6, \"occl\": 0.3}}\n",
"\n",
"\n",
"def make_dataset(n=400, seed=7, scen=(\"clean\",), classes=CLASSES):\n",
" rng = np.random.default_rng(seed)\n",
" O, S, M = np.zeros((n, N, N)), np.zeros((n, N, N)), []\n",
" for i in range(n):\n",
" cls, sc = classes[i % len(classes)], scen[(i // len(classes)) % len(scen)]\n",
" O[i], S[i], m = make_chip(cls, rng, SCEN[sc])\n",
" m.update({\"id\": f\"IMG{i:04d}\", \"scen\": sc})\n",
" M.append(m)\n",
" return O, S, M\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ์ ์ฒ๋ฆฌ(ํด์๋ยท๊ธฐํ ๋ณด์ , ๊ตฌ๋ฆ ์ฐจํ ์ฒ๋ฆฌ, ๋ฐฉ์ ์ ๊ทํ) ๊ณตํต ํจ์ ----\n",
"def _fit(a, fill):\n",
" \"\"\"์ค์ ๊ธฐ์ค์ผ๋ก 32x32์ ๋ง๊ฒ ์๋ฅด๊ฑฐ๋ ์ฑ์ด๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 6] ์์ฑ์์ ํ์ ํ์ง ์์น ๋ฐ ์๋ณ ํ๋ฅ ์ง๋(Heatmap) ๊ฐ์ํ UI ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def cloud_mask(opt, sm, min_area=15):\n",
" \"\"\"๊ดํ์์ ๋ฐ๊ณ (185 ์ด๊ณผ) ๋งค๋ํ๋ฐ SAR์๋ ๋ฐ์ฌ๊ฐ ์๋ ์์ญ์ ๊ตฌ๋ฆ์ผ๋ก ๋ณธ๋ค(SAR๋ ๊ตฌ๋ฆ์ ํต๊ณผํ๋ฏ๋ก ๋ฐ์ ์ฐจ๋๊ณผ ๊ตฌ๋ถ๋๋ค).\"\"\"\n",
" bright = opt > 185\n",
" if bright.sum() < min_area:\n",
" return np.zeros_like(bright)\n",
" local_sd = np.sqrt(np.maximum(ndimage.uniform_filter(opt ** 2, 3) - ndimage.uniform_filter(opt, 3) ** 2, 0))\n",
" cand = bright & (local_sd < 12) & (sm < 0.3)\n",
" lab, n = ndimage.label(cand)\n",
" if n == 0:\n",
" return cand\n",
" sizes = ndimage.sum(cand.astype(float), lab, range(1, n + 1))\n",
" return ndimage.binary_dilation(np.isin(lab, 1 + np.where(sizes >= min_area)[0]), iterations=2) & (opt > 140)\n",
"\n",
"\n",
"def orientation(opt, sm):\n",
" \"\"\"SAR ๋ฐ์ฌ(์์ฅยท๊ตฌ๋ฆ ์ํฅ ์ ์, sm์ ํํํํ SAR)์ ๊ดํ ๋๋น๋ฅผ ํฉ์น ๊ฐ์ฒด ๋ง์คํฌ์ ์ฃผ์ถ ๋ฐฉํฅ(๋).\"\"\"\n",
" m = (sm > 0.3) | (np.abs(opt - opt.mean()) > 30)\n",
" lab, n = ndimage.label(m)\n",
" if n == 0:\n",
" return 0.0, m\n",
" c = lab[N // 2 - 6:N // 2 + 6, N // 2 - 6:N // 2 + 6]\n",
" k = np.bincount(c[c > 0]).argmax() if (c > 0).any() else 1 + int(np.argmax(ndimage.sum(m.astype(float), lab, range(1, n + 1))))\n",
" m = lab == k\n",
" yy, xx = np.nonzero(m)\n",
" if len(yy) < 5:\n",
" return 0.0, m\n",
" dy, dx = yy - yy.mean(), xx - xx.mean() # 2์ฐจ ๋ชจ๋ฉํธ๋ก ์ฃผ์ถ ๊ฐ๋ ๊ณ์ฐ\n",
" return float(np.degrees(0.5 * np.arctan2(2 * (dx * dy).mean(), (dx * dx).mean() - (dy * dy).mean()))), m\n",
"\n",
"\n",
"_YY, _XX = np.mgrid[0:N, 0:N] - (N - 1) / 2\n",
"\n",
"\n",
"def warp(stack, zy, zx, ang):\n",
" \"\"\"์ฌ๋ฌ ์ฅ(B,์ฑ๋,32,32)์ ์ฅ๋ณ ํ๋ยท์ถ์(zy, zx)์ ํ์ (ang, ๋)์ ํ ๋ฒ์ ์์ ํ ๋ณด๊ฐ์ผ๋ก ์ ์ฉํ๋ค(๋ฒกํฐ ์ฐ์ฐ).\"\"\"\n",
" a = np.radians(ang)[:, None, None]\n",
" zy, zx = np.asarray(zy)[:, None, None], np.asarray(zx)[:, None, None]\n",
" sy = np.clip((np.cos(a) * _YY + np.sin(a) * _XX) / zy + (N - 1) / 2, 0, N - 1.001) # ์ถ๋ ฅ ์ขํ -> ์๋ณธ ์ขํ\n",
" sx = np.clip((-np.sin(a) * _YY + np.cos(a) * _XX) / zx + (N - 1) / 2, 0, N - 1.001)\n",
" y0, x0 = sy.astype(int), sx.astype(int)\n",
" fy, fx = (sy - y0).reshape(len(stack), 1, -1), (sx - x0).reshape(len(stack), 1, -1)\n",
" flat = stack.reshape(len(stack), stack.shape[1], -1)\n",
" lin = (y0 * N + x0).reshape(len(stack), 1, -1)\n",
" g = lambda d: np.take_along_axis(flat, np.broadcast_to(lin + d, (len(stack), stack.shape[1], lin.shape[2])), axis=2)\n",
" out = g(0) * (1 - fy) * (1 - fx) + g(N) * fy * (1 - fx) + g(1) * (1 - fy) * fx + g(N + 1) * fy * fx\n",
" return out.reshape(stack.shape)\n",
"\n",
"\n",
"def preprocess_batch(O, S, M):\n",
" \"\"\"์ฌ๋ฌ ์ฅ์ ํ ๋ฒ์ ์ ์ฒ๋ฆฌํ๋ค. 0) ๊ฒฐ์ธก ๋ณด์ 1) ํด์๋ยท๋น์ค๋ฌ ์ดฌ์ ๋ณด์ (ํ์ค 0.5m/ํฝ์
)\n",
" 2) ๊ตฌ๋ฆ ์ฐจํ ์์ญ์ ๋ฐฐ๊ฒฝ๊ฐ์ผ๋ก ์ฑ์ 3) ๊ฐ์ฒด ์ฃผ์ถ์ ๊ฐ๋ก๋ก ํ์ 4) 0~1 ์ ๊ทํ.\"\"\"\n",
" src = np.stack([np.asarray(O, float), np.asarray(S, float)], axis=1)\n",
" if np.isnan(src).any(): # 0) ์ผ์ ๊ฒฐ์ธก ํฝ์
๋ณด์ \n",
" mean = np.nanmean(src, axis=(2, 3), keepdims=True)\n",
" src = np.where(np.isnan(src), mean, src)\n",
" zy = np.array([m[\"gsd\"] / STD_GSD / np.cos(np.radians(m[\"off_nadir\"])) for m in M])\n",
" zx = np.array([m[\"gsd\"] / STD_GSD for m in M])\n",
" out = warp(src, zy, zx, np.zeros(len(M)))\n",
" sm = ndimage.uniform_filter(out[:, 1], (1, 3, 3))\n",
" angs, cloud = np.zeros(len(M)), np.zeros(len(M))\n",
" for i in range(len(M)):\n",
" cm = cloud_mask(out[i, 0], sm[i])\n",
" if cm.any():\n",
" fill = out[i, 0][~cm].mean() if (~cm).any() else 115.0\n",
" out[i, 0][cm] = fill\n",
" back = warp(cm[None, None].astype(float), [1 / zy[i]], [1 / zx[i]], np.zeros(1))[0, 0] > 0.5\n",
" src[i, 0][back] = fill # ์๋ณธ์ ๊ฐ์ ๊ตฌ๋ฆ ์์ญ๋ ์ฑ์\n",
" cloud[i] = cm.mean()\n",
" angs[i] = orientation(out[i, 0], sm[i])[0]\n",
" rot = np.abs(angs) > 3 # 3๋ ์ดํ๋ ํ์ ์๋ต\n",
" if rot.any():\n",
" out[rot] = warp(src[rot], zy[rot], zx[rot], angs[rot])\n",
" return [(np.clip(o / 255, 0, 1), np.clip(s / 1.5, 0, 1), float(c)) for (o, s), c in zip(out, cloud)]\n",
"\n",
"\n",
"def preprocess(opt, sar, meta):\n",
" \"\"\"ํ ์ฅ ์ ์ฒ๋ฆฌ(preprocess_batch์ ๊ฐ์ ์ฒ๋ฆฌ).\"\"\"\n",
" return preprocess_batch([opt], [sar], [meta])[0]\n",
"\n",
"# ---- ํน์ง ์ถ์ถ ๊ณตํต ํจ์ ----\n",
"KERNELS = [np.array(k, float) for k in (\n",
" [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], [[-1, -2, -1], [0, 0, 0], [1, 2, 1]], [[0, 1, 0], [1, -4, 1], [0, 1, 0]],\n",
" [[2, -1, -1], [-1, 2, -1], [-1, -1, 2]], [[-1, -1, 2], [-1, 2, -1], [2, -1, -1]], [[1, 1, 1], [1, 1, 1], [1, 1, 1]])]\n",
"\n",
"\n",
"def conv_features(o):\n",
" \"\"\"๊ฐ์ด CNN ํน์ง: ๊ณ ์ ํฉ์ฑ๊ณฑ ํํฐ 6์ข
-> ReLU -> 4x4 ์ต๋ ํ๋ง(8x8 ์ถ๋ ฅ).\"\"\"\n",
" feats = []\n",
" for k in KERNELS:\n",
" r = np.maximum(ndimage.correlate(o - o.mean(), k / np.abs(k).sum()), 0)\n",
" feats.append(r.reshape(8, 4, 8, 4).max(axis=(1, 3)).ravel())\n",
" feats.append(np.maximum(-ndimage.correlate(o - o.mean(), k / np.abs(k).sum()), 0).reshape(8, 4, 8, 4).max(axis=(1, 3)).ravel())\n",
" return np.concatenate(feats)\n",
"\n",
"\n",
"def hog(o, cell=8, bins=8):\n",
" gy, gx = np.gradient(o)\n",
" mag, ang = np.hypot(gx, gy), np.mod(np.arctan2(gy, gx), np.pi)\n",
" b = np.minimum((ang / np.pi * bins).astype(int), bins - 1)\n",
" out = np.zeros((N // cell, N // cell, bins))\n",
" for i in range(N // cell):\n",
" for j in range(N // cell):\n",
" sl = (slice(i * cell, (i + 1) * cell), slice(j * cell, (j + 1) * cell))\n",
" out[i, j] = np.bincount(b[sl].ravel(), mag[sl].ravel(), bins)\n",
" return (out / (np.linalg.norm(out) + 1e-6)).ravel()\n",
"\n",
"\n",
"def shape_features(o, s):\n",
" \"\"\"๊ฐ์ฒด ๋ชจ์ ํน์ง: ๊ฐ๋ก ๋ฐฉํฅ ํฌ์์ผ๋ก ์ ์ฒด ๊ธธ์ดยท์ฐจ์ฒด ๊ธธ์ดยท๊ฐ๋ ๋์ถ๋ถ(ํฌ์ ) ๊ธธ์ดยท๋๊บผ์ด ๋ถ๋ถ(ํฌํ) ์์น๋ฅผ ์ฐ๋ค.\n",
" ๋ฐฉ์ ์ ๊ทํ ๋ค์๋ ์๋ค(180๋)๊ฐ ๋ค์งํ ์ ์์ผ๋ฏ๋ก, ๊ฐ๋ ๋์ถ๋ถ๊ฐ ์ค๋ฅธ์ชฝ์ ์ค๋๋ก ๋ง์ถ๋ค.\"\"\"\n",
" sm = ndimage.uniform_filter(s, 3)\n",
" dark = np.abs(o - np.median(o))\n",
" m = (sm > 0.22) | (dark > 0.12)\n",
" lab, n = ndimage.label(m)\n",
" if n:\n",
" c = lab[10:22, 8:24]\n",
" k = np.bincount(c[c > 0]).argmax() if (c > 0).any() else 1\n",
" m = lab == k\n",
" p = m.sum(axis=0).astype(float)\n",
" cols = np.nonzero(p)[0]\n",
" if len(cols) == 0:\n",
" return np.zeros(8), o, s\n",
" thin = (p > 0) & (p <= 2)\n",
" left, right = thin[:N // 2].sum(), thin[N // 2:].sum()\n",
" if left > right:\n",
" o, s, m, p = o[:, ::-1], s[:, ::-1], m[:, ::-1], p[::-1]\n",
" cols = np.nonzero(p)[0]\n",
" body = np.nonzero(p > 3)[0]\n",
" d = np.where(m, dark if left <= right else dark[:, ::-1], 0)\n",
" heavy = d.sum(axis=0)\n",
" centre = (body[0] + body[-1]) / 2 if len(body) else N / 2\n",
" turret = (heavy * np.arange(N)).sum() / max(heavy.sum(), 1e-6) - centre\n",
" return np.array([cols[-1] - cols[0] + 1, len(body), ((p > 0) & (p <= 2)).sum(), p.max(), turret,\n",
" m.sum(), (cols[-1] - (body[-1] if len(body) else cols[-1])), m.sum() / max(len(cols) * p.max(), 1)]), o, s\n",
"\n",
"\n",
"def adv_features(o, s):\n",
" \"\"\"์ ๋ฐ ํน์ง: ๋ชจ์(๊ธธ์ดยทํฌ์ ยทํฌํ ์์น) + HOG(์ค๊ณฝ) + SAR ๋ฐ์ฌ ํต๊ณ(์์ฅ๋งยท๊ตฌ๋ฆ ์ํฅ ์ ์) + ํฌ์ ์ค๊ณฝ.\"\"\"\n",
" shp, o, s = shape_features(o, s)\n",
" c = s[8:24, 4:28]\n",
" sm = ndimage.uniform_filter(s, 3)\n",
" m = sm > 0.2\n",
" dark = np.abs(o - np.median(o))\n",
" return np.concatenate([shp, hog(o), [c.mean(), c.max(), (sm > 0.25).mean(), (sm > 0.4).mean(), m.sum(axis=1).max(), m.sum(axis=0).max()],\n",
" sm.mean(axis=0).reshape(8, 4).mean(1), sm.mean(axis=1).reshape(8, 4).mean(1),\n",
" dark.mean(axis=0).reshape(8, 4).mean(1), dark.mean(axis=1).reshape(8, 4).mean(1),\n",
" [o.mean(), o.std(), dark.max(), (o > 0.65).mean(), (o < 0.3).mean()]])\n",
"\n",
"T = 10 # 10x10 ํ์ผ = 320x320 ํฝ์
๊ฐ์ ์ฅ๋ฉด\n",
"rng = np.random.default_rng(12)\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ์ฅ๋ฉด ๊ตฌ์ฑ: ๊ฐ์ ์ง๊ฒฐ์ง 2๊ณณ(๊ตฐ์ฉ), ๊ฐ์ ๋๋ก(๋ฏผ๊ฐ ์ฐจ๋), ๋๋จธ์ง๋ ๋น ํ์ผ\n",
"layout = np.full((T, T), \"none\", dtype=object)\n",
"for (r0, c0), n in [((1, 1), 9), ((6, 6), 7)]:\n",
" for _ in range(n):\n",
" layout[r0 + rng.integers(0, 3), c0 + rng.integers(0, 3)] = rng.choice([\"tank\", \"spg\", \"truck\"], p=[.4, .3, .3])\n",
"for c in rng.choice(T, 5, replace=False):\n",
" layout[4, c] = \"civil\"\n",
"scen = rng.choice([\"clean\", \"camo\", \"occl30\", \"cloud\", \"low_sun\"], (T, T), p=[.4, .2, .15, .15, .1])\n",
"tiles = {}\n",
"for r in range(T):\n",
" for c in range(T):\n",
" o, s, m = make_chip(layout[r, c], rng, SCEN[scen[r, c]])\n",
" tiles[(r, c)] = (o, s, m)\n",
"\n",
"# ํ๋
๋ชจ๋ธ(์ด ํ์ผ ์์์ ํ์ต: ๋น ํ์ผ ํฌํจ 5๊ฐ ํด๋์ค)\n",
"Otr, Str, Mtr = make_dataset(1000, seed=3, scen=tuple(SCEN), classes=CLASSES + [\"none\"])\n",
"clf = RandomForestClassifier(n_estimators=150, min_samples_leaf=2, random_state=0, n_jobs=1)\n",
"clf.fit(np.array([adv_features(o, s) for o, s, _ in preprocess_batch(Otr, Str, Mtr)]), [m[\"cls\"] for m in Mtr])\n",
"\n",
"# 1) 1์ด ์ฃผ๊ธฐ ๊ฐฑ์ ๋ชจ์: ๋งค ๊ฐฑ์ ๋ง๋ค ์๋ก ๋ค์ด์จ ํ์ผ ํ ์ค(10์ฅ)์ ํ๋
ํด ํ๋ฉด ์๋ฃ ๊ฐฑ์ \n",
"dets, upd = [], []\n",
"for r in range(T):\n",
" t0 = time.perf_counter()\n",
" row = [tiles[(r, c)] for c in range(T)]\n",
" X = np.array([adv_features(o, s) for o, s, _ in preprocess_batch(*zip(*row))])\n",
" P = clf.predict_proba(X) # ํ ์ค์ ๋ฌถ์ด์ ํ๋
(ํธ์ถ ํ์ ๊ฐ์)\n",
" for c, p in enumerate(P):\n",
" k = int(p.argmax())\n",
" if clf.classes_[k] != \"none\":\n",
" dets.append({\"ํ์ผ\": f\"R{r}C{c}\", \"x\": c * N + N / 2, \"y\": r * N + N / 2, \"๊ธฐ์ข
(์ถ์ )\": clf.classes_[k],\n",
" \"์๋ณํ๋ฅ \": round(float(p[k]), 2), \"์ค์ \": layout[r, c], \"์กฐ๊ฑด\": scen[r, c]})\n",
" upd.append((time.perf_counter() - t0) * 1000)\n",
"D = pd.DataFrame(dets)\n",
"print(f\"ํ์ผ ํ ์ค(10์ฅ) ๊ฐฑ์ ์ฒ๋ฆฌ ํ๊ท {np.mean(upd):.0f} ms, ์ต๋ {np.max(upd):.0f} ms (๋ชฉํ: ๊ฐฑ์ 1์ด ์ด๋ด, ์ฒ๋ฆฌ ์๋ต 100 ms ์ด๋ด)\")\n",
"print(f\"์๋ณ {len(D)}๊ฑด / ์ค์ ๊ฐ์ฒด {int((layout != 'none').sum())}๊ฑด, ๊ธฐ์ข
์ผ์น {int((D['๊ธฐ์ข
(์ถ์ )'] == D['์ค์ ']).sum())}๊ฑด\")\n",
"print(\"\\n[์๋ณ ๋ชฉ๋ก: ์๋ณํ๋ฅ ๋ฎ์ ์(๋ถ์๊ด ์ฐ์ ํ์ธ)]\")\n",
"print(D.sort_values(\"์๋ณํ๋ฅ \")[[\"ํ์ผ\", \"๊ธฐ์ข
(์ถ์ )\", \"์๋ณํ๋ฅ \", \"์กฐ๊ฑด\"]].head(8).to_string(index=False))\n",
"\n",
"# 2) ์ง๊ฒฐ์ง ๋ฐ์ง๋: ๊ตฐ์ฉ ์๋ณ ์์น๋ฅผ ์๋ณํ๋ฅ ๋ก ๊ฐ์คํด ๊ฐ์ฐ์์ ํํ\n",
"mil = D[D[\"๊ธฐ์ข
(์ถ์ )\"].isin(list(MILITARY))]\n",
"grid = np.zeros((T * N, T * N))\n",
"for _, d in mil.iterrows():\n",
" grid[int(d[\"y\"]), int(d[\"x\"])] += d[\"์๋ณํ๋ฅ \"]\n",
"heat = ndimage.gaussian_filter(grid, 24)\n",
"peak = np.unravel_index(heat.argmax(), heat.shape)\n",
"print(f\"\\n์ต๋ ๋ฐ์ง ์ง์ (๊ฐ์ ํฝ์
์ขํ) x={peak[1]}, y={peak[0]}, ๊ตฐ์ฉ ์๋ณ {len(mil)}๊ฑด\")\n",
"\n",
"# 3) ๋ถ์ ํ๋ฉด(์ผ๊ฐ ์์ ์ฉ ์ด๋์ด ํ๋ฉด, 4๊ฐ ๊ตฌ์ฑ์์)\n",
"scene = np.block([[tiles[(r, c)][0] for c in range(T)] for r in range(T)])\n",
"plt.style.use(\"dark_background\")\n",
"fig, ax = plt.subplots(2, 2, figsize=(13, 11))\n",
"mk = {\"tank\": \"s\", \"spg\": \"D\", \"truck\": \"^\", \"civil\": \"o\"}\n",
"ax[0, 0].imshow(scene, cmap=\"gray\")\n",
"for k, g in D.groupby(\"๊ธฐ์ข
(์ถ์ )\"):\n",
" ax[0, 0].scatter(g[\"x\"], g[\"y\"], s=40 + 200 * g[\"์๋ณํ๋ฅ \"], marker=mk[k], facecolors=\"none\",\n",
" edgecolors=\"red\" if k in MILITARY else \"deepskyblue\", lw=1.5, label=k)\n",
"ax[0, 0].legend(fontsize=8, loc=\"lower right\"); ax[0, 0].set_title(\"Detections (marker size = probability)\"); ax[0, 0].axis(\"off\")\n",
"ax[0, 1].imshow(scene, cmap=\"gray\"); ax[0, 1].imshow(heat, cmap=\"inferno\", alpha=0.6); ax[0, 1].set_title(\"Military vehicle density (heatmap)\"); ax[0, 1].axis(\"off\")\n",
"vc = D[\"๊ธฐ์ข
(์ถ์ )\"].value_counts()\n",
"ax[1, 0].bar(vc.index.tolist(), vc.values, color=\"deepskyblue\"); ax[1, 0].set_title(\"Detections by type\")\n",
"ax[1, 1].hist(D[\"์๋ณํ๋ฅ \"], bins=10, range=(0, 1), color=\"orange\"); ax[1, 1].axvline(0.6, color=\"red\", ls=\"--\")\n",
"ax[1, 1].set_title(\"Identification probability (red line = review threshold)\")\n",
"fig.suptitle(\"ISR01 satellite imagery analysis view - synthetic training data\")\n",
"t0 = time.perf_counter(); fig.canvas.draw(); draw_ms = (time.perf_counter() - t0) * 1000\n",
"plt.tight_layout(); plt.show()\n",
"print(f\"ํ๋ฉด ๋ ๋๋ง {draw_ms:.0f} ms (๊ฐฑ์ ์ฃผ๊ธฐ 1์ด ์ด๋ด: {'๋ฌ์ฑ' if draw_ms < 1000 else '๋ฏธ๋ฌ'}), ์๊ฐํ ๊ตฌ์ฑ์์ 4์ข
(์๋ณ ์ค๋ฒ๋ ์ดยท๋ฐ์ง๋ยท๊ธฐ์ข
ํํฉยท์๋ณํ๋ฅ ๋ถํฌ)\")\n",
"print(\"ํ์๋ ๊ธฐ์ข
ยท๋ฐ์ง๋๋ ํ๋
๋ณด์กฐ ์ ๋ณด์ด๋ฉฐ, ๊ณ ๊ฐ์น ํ์ ์ ์์๋ถ์๊ด์ ์ต์ข
ํ๋
(๋ฏธ์
7)์ ๊ฑฐ์น๋ค.\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
06 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"ํ์ผ ํ ์ค(10์ฅ) ๊ฐฑ์ ์ฒ๋ฆฌ ํ๊ท 33 ms, ์ต๋ 45 ms (๋ชฉํ: ๊ฐฑ์ 1์ด ์ด๋ด, ์ฒ๋ฆฌ ์๋ต 100 ms ์ด๋ด)\n",
"์๋ณ 17๊ฑด / ์ค์ ๊ฐ์ฒด 17๊ฑด, ๊ธฐ์ข
์ผ์น 17๊ฑด\n",
"\n",
"[์๋ณ ๋ชฉ๋ก: ์๋ณํ๋ฅ ๋ฎ์ ์(๋ถ์๊ด ์ฐ์ ํ์ธ)]\n",
" ํ์ผ ๊ธฐ์ข
(์ถ์ ) ์๋ณํ๋ฅ ์กฐ๊ฑด\n",
"R1C1 tank 0.80 camo\n",
"R2C2 tank 0.87 clean\n",
"R8C8 tank 0.90 clean\n",
"R3C3 spg 0.92 camo\n",
"R7C6 tank 0.94 occl30\n",
"R3C1 tank 0.94 cloud\n",
"R8C7 truck 0.95 clean\n",
"R4C0 civil 0.95 clean\n",
"\n",
"์ต๋ ๋ฐ์ง ์ง์ (๊ฐ์ ํฝ์
์ขํ) x=68, y=68, ๊ตฐ์ฉ ์๋ณ 12๊ฑด\n",
"ํ๋ฉด ๋ ๋๋ง 201 ms (๊ฐฑ์ ์ฃผ๊ธฐ 1์ด ์ด๋ด: ๋ฌ์ฑ), ์๊ฐํ ๊ตฌ์ฑ์์ 4์ข
(์๋ณ ์ค๋ฒ๋ ์ดยท๋ฐ์ง๋ยท๊ธฐ์ข
ํํฉยท์๋ณํ๋ฅ ๋ถํฌ)\n",
"ํ์๋ ๊ธฐ์ข
ยท๋ฐ์ง๋๋ ํ๋
๋ณด์กฐ ์ ๋ณด์ด๋ฉฐ, ๊ณ ๊ฐ์น ํ์ ์ ์์๋ถ์๊ด์ ์ต์ข
ํ๋
(๋ฏธ์
7)์ ๊ฑฐ์น๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
07. ๊ณ ๊ฐ์น ํ์ (HVT) ์๋ณ ์ ์์๋ถ์๊ด ์ต์ข
ํ๋
(HITL) ๋ฐ ๊ต์ฐจ ๊ฒ์ฆ ์ ์ฐจ ์ค๊ณ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๊ณ ๊ฐ์น ๊ตฐ์ฉ ์ฅ๋น ์๋ณ ์ ์์ ํ๋
๊ด์ด ๊ณ ํด์๋ ํ๋ ๋ฐ ๊ต์ฐจ ๊ฒ์ฆ์ ํตํด ์ต์ข
์น์ธํ๋ HITL ํ๋
์ํฌํ๋ก์ฐ๋ฅผ ์ค๊ณํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๊ณ ๊ฐ์น ํ์ (HVT) ํ๋ณด๊ฐ ์๋ณ๋๋ฉด ์์๋ถ์๊ด์ด ๊ณ ํด์๋ ํ๋ ํ๋
๊ณผ ๊ต์ฐจ ๊ฒ์ฆ์ ๊ฑฐ์ณ ์ต์ข
์น์ธํ๋ HITL ํ๋
์ํฌํ๋ก์ฐ๋ฅผ ์ค๊ณํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๋ชจ๋ธ ์๋ณ ๊ฒฐ๊ณผ๋ ๋ชจ๋ ํ๋
๋๊ธฐ๋ก ๋ฑ๋กํ๊ณ HVT ํ๋ณด๋ฅผ ์ฐ์ ์ ๋ ฌ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/hitl/isr01_approval_workflow.py ๋ฐ docs/isr01_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] ๊ณ ๊ฐ์น ํ์ (HVT) ์๋ณ ์ ์์๋ถ์๊ด ์ต์ข
ํ๋
(HITL) ๋ฐ ๊ต์ฐจ ๊ฒ์ฆ ์ ์ฐจ ์ค๊ณ\n",
"# ๋ฏธ์
์ง์นจ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค์ ํ์ธํ๊ณ ์ฝ๋๋ฅผ ์คํ ๋ฐ ํ๋ํ์ธ์.\n",
"\"\"\"๊ณ ๊ฐ์น ํ์ (HVT) ํ๋ณด ์ฅ๋น๊ฐ ์๋ณ๋๋ฉด ์์๋ถ์๊ด์ด ๊ณ ํด์๋ ํ๋ ํ๋
๊ณผ ๋ค๋ฅธ ์ถ์ฒ(SARยท๋ค๋ฅธ ์ดฌ์ ํ์ฐจ) ๊ต์ฐจ ๊ฒ์ฆ์ ๊ฑฐ์ณ ์ต์ข
์น์ธยท๋ณด๋ฅยท๋ฐ๋ คํ๋ ์ธ๊ฐ ๊ฐ์
(HITL) ํ๋
์ํฌํ๋ก์ฐ๋ฅผ ๊ตฌํํ๋ค. HVT๋ 2์ธ ํ๋
์ด ํ์ํ๋ฉฐ ๋ชจ๋ ๊ฒฐ์ ์ ์ยท๋ณ์กฐ ํ์ธ์ด ๊ฐ๋ฅํ ๊ฐ์ฌ ๋ก๊ทธ์ ๋จ๋๋ค.\"\"\"\n",
"import hashlib\n",
"import json\n",
"import time\n",
"import pandas as pd\n",
"\n",
"ACTIONS = {\"APPROVE\": \"ํ๋
์น์ธ(์ ๋ณด ๋ณด๊ณ )\", \"HOLD\": \"๋ณด๋ฅ(์ถ๊ฐ ์ดฌ์ยท์ถ์ฒ ํ์)\", \"REJECT\": \"๋ฐ๋ ค(์ค์๋ณยท๋ชจ์ ํ์ )\"}\n",
"PRIORITY = {\"High\": 0, \"Medium\": 1, \"Low\": 2}\n",
"HVT_TYPES = {\"spg\"} # ๊ต์ก์ฉ ๊ฐ์ : ์์ฃผํฌ ์๋ณ์ ๊ณ ๊ฐ์น ํ์ ํ๋ณด๋ก ์ทจ๊ธ\n",
"HOLD_LIMIT_S = 3600\n",
"\n",
"\n",
"class WorkflowError(ValueError):\n",
" pass\n",
"\n",
"\n",
"class ReadingQueue:\n",
" def __init__(self, clock=time.time):\n",
" self.items, self.log, self.clock = {}, [], clock\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",
" if rec[\"prev\"] != prev or rec[\"hash\"] != hashlib.sha256((prev + json.dumps(body, ensure_ascii=False, sort_keys=True)).encode()).hexdigest():\n",
" return False, n\n",
" prev = rec[\"hash\"]\n",
" return True, -1\n",
"\n",
" def submit(self, iid, pred, grade, conf):\n",
" \"\"\"๋ชจ๋ธ ์๋ณ ๊ฒฐ๊ณผ ๋ฑ๋ก: ํญ์ PENDING. ๊ธฐ์ข
์ด HVT ํ๋ณด๋ฉด 2์ธ ํ๋
๋์์ผ๋ก ํ์ํ๋ค.\"\"\"\n",
" self.items[iid] = {\"id\": iid, \"pred\": pred, \"grade\": grade, \"conf\": conf, \"hvt\": pred in HVT_TYPES, \"status\": \"PENDING\",\n",
" \"submitted\": self.clock(), \"zoom\": False, \"cross\": None, \"first\": None, \"escalated\": False, \"hold_since\": None}\n",
" self._record({\"event\": \"SUBMIT\", \"id\": iid, \"pred\": pred, \"grade\": grade, \"by\": \"MODEL\"})\n",
"\n",
" def zoom_review(self, iid, analyst, note):\n",
" self.items[iid][\"zoom\"] = True\n",
" self._record({\"event\": \"ZOOM_REVIEW\", \"id\": iid, \"analyst\": analyst, \"note\": note})\n",
"\n",
" def cross_check(self, iid, analyst, source, agrees):\n",
" self.items[iid][\"cross\"] = bool(agrees)\n",
" self._record({\"event\": \"CROSS_CHECK\", \"id\": iid, \"analyst\": analyst, \"source\": source, \"agrees\": bool(agrees)})\n",
"\n",
" def pending(self):\n",
" todo = [i for i in self.items.values() if i[\"status\"] in (\"PENDING\", \"HOLD\", \"APPROVE_1ST\")]\n",
" return sorted(todo, key=lambda i: (not i[\"escalated\"], not i[\"hvt\"], PRIORITY[i[\"grade\"]], -i[\"conf\"]))\n",
"\n",
" def decide(self, iid, action, analyst, reason):\n",
" it = self.items.get(iid)\n",
" if action not in ACTIONS:\n",
" raise WorkflowError(f\"ํ์ฉ๋์ง ์์ ์กฐ์น: {action}\")\n",
" if not analyst or analyst.upper() in (\"AI\", \"MODEL\", \"SYSTEM\"):\n",
" raise WorkflowError(\"์์๋ถ์๊ด ID ํ์(์๋ ํ๋
ํ์ ๊ธ์ง)\")\n",
" if it is None or it[\"status\"] in (\"APPROVED\", \"REJECTED\"):\n",
" raise WorkflowError(\"๋๊ธฐ์ด์ ์๊ฑฐ๋ ์ด๋ฏธ ์ข
๊ฒฐ๋ ๊ฑด\")\n",
" if len(reason.strip()) < (15 if it[\"hvt\"] else 8):\n",
" raise WorkflowError(\"ํ๋
๊ทผ๊ฑฐ ๋ถ์กฑ(HVT๋ 15์ ์ด์)\")\n",
" if action == \"APPROVE\":\n",
" if not it[\"zoom\"]:\n",
" raise WorkflowError(\"๊ณ ํด์๋ ํ๋ ํ๋
๊ธฐ๋ก ์์ด ์น์ธ ๋ถ๊ฐ\")\n",
" if it[\"hvt\"] and it[\"cross\"] is not True:\n",
" raise WorkflowError(\"HVT๋ ๋ค๋ฅธ ์ถ์ฒ ๊ต์ฐจ ๊ฒ์ฆ ์ผ์น ํ์๋ง ์น์ธ ๊ฐ๋ฅ\")\n",
" if it[\"hvt\"] and it[\"first\"] is None:\n",
" it[\"first\"], it[\"status\"] = analyst, \"APPROVE_1ST\"\n",
" self._record({\"event\": \"APPROVE_1ST\", \"id\": iid, \"analyst\": analyst, \"reason\": reason})\n",
" return\n",
" if it[\"hvt\"] and it[\"first\"] == analyst:\n",
" raise WorkflowError(\"HVT 2์ธ ํ๋
: 1์ฐจ ํ๋
๊ด๊ณผ ๋ค๋ฅธ ๋ถ์๊ด ํ์\")\n",
" it[\"status\"] = {\"APPROVE\": \"APPROVED\", \"HOLD\": \"HOLD\", \"REJECT\": \"REJECTED\"}[action]\n",
" it[\"hold_since\"] = self.clock() if action == \"HOLD\" else None\n",
" it[\"decided\"] = self.clock()\n",
" self._record({\"event\": \"DECIDE\", \"id\": iid, \"action\": action, \"analyst\": analyst, \"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\", \"to\": \"์ ์ ํ๋
๊ด\"})\n",
" return out\n",
"\n",
" def feedback(self, iid, commander, text):\n",
" self._record({\"event\": \"FEEDBACK\", \"id\": iid, \"commander\": commander, \"text\": text})\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 = ReadingQueue(clock)\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋ฏธ์
5 ์์ง์์ ๋์ด์จ ์๋ณ ๊ฒฐ๊ณผ 5๊ฑด\n",
"for iid, pred, g, c in [(\"IMG0101\", \"spg\", \"High\", 0.97), (\"IMG0102\", \"tank\", \"High\", 0.95), (\"IMG0103\", \"spg\", \"Medium\", 0.71),\n",
" (\"IMG0104\", \"truck\", \"Low\", 0.48), (\"IMG0105\", \"tank\", \"Medium\", 0.66)]:\n",
" q.submit(iid, pred, g, c)\n",
"print(\"ํ๋
์์(HVT ์ฐ์ ):\", [i[\"id\"] for i in q.pending()])\n",
"print(\"์๋ ์น์ธ๋ ๊ฑด์:\", sum(i[\"status\"] == \"APPROVED\" for i in q.items.values()))\n",
"\n",
"for args in [(\"IMG0101\", \"APPROVE\", \"MODEL\", \"์ ๋ขฐ๋ 0.97๋ก ์๋ ์น์ธ ์๋\"), (\"IMG0102\", \"APPROVE\", \"ia01\", \"ํฌํ ์ค์ ๋ฐฐ์น ํ์ธํจ\"),\n",
" (\"IMG0104\", \"DELETE\", \"ia01\", \"ํ์ฉ๋์ง ์์ ์กฐ์น ์๋\")]:\n",
" try:\n",
" q.decide(*args)\n",
" except WorkflowError as e:\n",
" print(\"์ฐจ๋จ:\", e)\n",
"\n",
"clock.t += 120; q.zoom_review(\"IMG0101\", \"ia01\", \"ํ๋ฐฉ ๋ํ ํฌํยท๊ธด ํฌ์ ์ค๊ณฝ ํ์ธ\")\n",
"try:\n",
" q.decide(\"IMG0101\", \"APPROVE\", \"ia01\", \"ํ๋ ํ๋
๊ฒฐ๊ณผ ํ๋ฐฉ ํฌํ๊ณผ ๊ธด ํฌ์ ์ผ์น\")\n",
"except WorkflowError as e:\n",
" print(\"์ฐจ๋จ:\", e)\n",
"q.cross_check(\"IMG0101\", \"ia01\", \"SAR ๋์ผ ์ง์ (๊ฐ์ ํ์ฐจ 2)\", True)\n",
"clock.t += 60; q.decide(\"IMG0101\", \"APPROVE\", \"ia01\", \"ํ๋ ํ๋
๊ฒฐ๊ณผ ํ๋ฐฉ ํฌํ๊ณผ ๊ธด ํฌ์ ์ผ์น, SAR ๊ธ์ ๋ฐ์ฌ ์ผ์น\")\n",
"try:\n",
" q.decide(\"IMG0101\", \"APPROVE\", \"ia01\", \"๊ฐ์ ํ๋
๊ด์ 2์ฐจ ์น์ธ ์๋์
๋๋ค\")\n",
"except WorkflowError as e:\n",
" print(\"์ฐจ๋จ:\", e)\n",
"clock.t += 300; q.decide(\"IMG0101\", \"APPROVE\", \"ia02\", \"๋
๋ฆฝ ํ๋
๊ฒฐ๊ณผ ๋์, ์์น ์ค์ฐจ ๋ฒ์ ๋ด ๋์ผ ๊ฐ์ฒด๋ก ํ๋จ\")\n",
"q.zoom_review(\"IMG0103\", \"ia02\", \"ํฌ์ ๊ธธ์ด ๋ถ๋ช
ํ\"); q.cross_check(\"IMG0103\", \"ia02\", \"SAR(๊ฐ์ ํ์ฐจ 2)\", False)\n",
"q.decide(\"IMG0103\", \"HOLD\", \"ia02\", \"SAR ๋ฐ์ฌ ์ฝํจ, ๋ชจ์ ํ์ ๊ฐ๋ฅ์ฑ ํ์ธ ํ์\")\n",
"clock.t += 200; q.zoom_review(\"IMG0102\", \"ia01\", \"ํฌํ ์ค์ ๋ฐฐ์น\"); q.decide(\"IMG0102\", \"APPROVE\", \"ia01\", \"ํฌํ ์ค์, ์งง์ ํฌ์ ํ์ธ\")\n",
"clock.t += 100; q.decide(\"IMG0104\", \"REJECT\", \"ia02\", \"๊ตฌ๋ฆ ๊ฒฝ๊ณ ์ค์๋ณ\")\n",
"clock.t += 4000\n",
"print(\"๋ณด๋ฅ ๊ธฐํ ์ด๊ณผ ์์ :\", q.check_holds())\n",
"q.feedback(\"IMG0101\", \"cmd01\", \"๋ค์ ์ดฌ์ ํ์ฐจ์์ ์ด๋ ์ฌ๋ถ ์ฌํ์ธ ์์ฒญ\")\n",
"\n",
"df = pd.DataFrame(q.items.values())\n",
"df[\"๋๊ธฐ์๊ฐ(s)\"] = (df[\"decided\"] - df[\"submitted\"]).round(0)\n",
"print(\"\\n\" + df[[\"id\", \"pred\", \"grade\", \"hvt\", \"status\", \"cross\", \"escalated\", \"๋๊ธฐ์๊ฐ(s)\"]].fillna(\"-\").to_string(index=False))\n",
"ok, _ = q.verify_log()\n",
"print(f\"\\n๊ฐ์ฌ ๋ก๊ทธ {len(q.log)}๊ฑด(๋ฑ๋กยทํ๋ํ๋
ยท๊ต์ฐจ๊ฒ์ฆยท๊ฒฐ์ ยท์์ ยทํผ๋๋ฐฑ), ๋ก๊น
์ฑ๊ณต๋ฅ {len(q.log) / len(q.log):.0%}, ํด์ ์ฒด์ธ ๊ฒ์ฆ: {ok}\")\n",
"q.log[5][\"analyst\"] = \"ia99\"\n",
"print(\"6๋ฒ์งธ ๊ธฐ๋ก ๋ณ์กฐ ํ ๊ฒ์ฆ(False, ๊นจ์ง ์์น):\", q.verify_log())\n",
"print(\"\\n์น์ธ์ 'ํ๋
๊ฒฐ๊ณผ ๋ณด๊ณ ' ๋จ๊ณ๊น์ง์ด๋ฉฐ, ์ดํ ํ์ฉ ํ๋จ์ ์งํ๊ณํต์ ๋ณ๋ ๊ฒํ ๋ฅผ ๋ฐ๋ฅธ๋ค.\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
07 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"ํ๋
์์(HVT ์ฐ์ ): ['IMG0101', 'IMG0103', 'IMG0102', 'IMG0105', 'IMG0104']\n",
"์๋ ์น์ธ๋ ๊ฑด์: 0\n",
"์ฐจ๋จ: ์์๋ถ์๊ด ID ํ์(์๋ ํ๋
ํ์ ๊ธ์ง)\n",
"์ฐจ๋จ: ๊ณ ํด์๋ ํ๋ ํ๋
๊ธฐ๋ก ์์ด ์น์ธ ๋ถ๊ฐ\n",
"์ฐจ๋จ: ํ์ฉ๋์ง ์์ ์กฐ์น: DELETE\n",
"์ฐจ๋จ: HVT๋ ๋ค๋ฅธ ์ถ์ฒ ๊ต์ฐจ ๊ฒ์ฆ ์ผ์น ํ์๋ง ์น์ธ ๊ฐ๋ฅ\n",
"์ฐจ๋จ: HVT 2์ธ ํ๋
: 1์ฐจ ํ๋
๊ด๊ณผ ๋ค๋ฅธ ๋ถ์๊ด ํ์\n",
"๋ณด๋ฅ ๊ธฐํ ์ด๊ณผ ์์ : ['IMG0103']\n",
"\n",
" id pred grade hvt status cross escalated ๋๊ธฐ์๊ฐ(s)\n",
"IMG0101 spg High True APPROVED True False 480.0\n",
"IMG0102 tank High False APPROVED - False 680.0\n",
"IMG0103 spg Medium True HOLD False True 480.0\n",
"IMG0104 truck Low False REJECTED - False 780.0\n",
"IMG0105 tank Medium False PENDING - False -\n",
"\n",
"๊ฐ์ฌ ๋ก๊ทธ 17๊ฑด(๋ฑ๋กยทํ๋ํ๋
ยท๊ต์ฐจ๊ฒ์ฆยท๊ฒฐ์ ยท์์ ยทํผ๋๋ฐฑ), ๋ก๊น
์ฑ๊ณต๋ฅ 100%, ํด์ ์ฒด์ธ ๊ฒ์ฆ: True\n",
"6๋ฒ์งธ ๊ธฐ๋ก ๋ณ์กฐ ํ ๊ฒ์ฆ(False, ๊นจ์ง ์์น): (False, 6)\n",
"\n",
"์น์ธ์ 'ํ๋
๊ฒฐ๊ณผ ๋ณด๊ณ ' ๋จ๊ณ๊น์ง์ด๋ฉฐ, ์ดํ ํ์ฉ ํ๋จ์ ์งํ๊ณํต์ ๋ณ๋ ๊ฒํ ๋ฅผ ๋ฐ๋ฅธ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
08. ์
๊ธฐ์ ๋ฐ ์ ํด์๋ ์์ฑ์์ ๋๋ ๋ฐฐ์น ์ฒ๋ฆฌ ์คํธ๋ ์ค ํ
์คํธ ๋ฐ ํ๊ณ์น ๋์ถ\n",
"**์์์๊ฐ**: ์ฝ 3.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๋๋ฉด์ ์์ฑ์์ ํ์ผ ๋๋ ๋ฐฐ์น ์ฒ๋ฆฌ ์๋ฎฌ๋ ์ด์
์ ์ํํ์ฌ ์์คํ
์ ์ถ๋ก ๋ณ๋ชฉ ๋ฐ ์ฒ๋ฆฌ ํ๊ณ์น๋ฅผ ๊ฒ์ฆํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๋๋ฉด์ ์์ฑ์์ ํ์ผ์ ๋๋ ๋ฐฐ์น๋ก ์ฒ๋ฆฌํด ์ถ๋ก ๋ณ๋ชฉ๊ณผ ์ฒ๋ฆฌ ํ๊ณ์น, ๊ธฐ๋ฅ ์ถ์ ์๋์ ๊ฒ์ฆํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **ํ์ ๋๋น 500% ์ฆ๊ฐํ ํ์ผ, ์
๊ธฐ์ยท์ ํด์๋ ํ์ผ, ์์ ํ์ผ(์๋ฆผยท์ ์ก ์ค๋ฅยท๋น ์์) ํผ์
, ์ฒ๋ฆฌ ์์ฐ ๋ถ์กฑ ์ํฉ์ ๊ฐ์์ผ๋ก ์ฃผ์
ํ์ฌ ์ฒ๋ฆฌ ์ง์ฐ ๋ฐ ๋ณต์๋ ฅ์ ์ํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `tests/isr01_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": [
"\"\"\"๋๋ฉด์ ์์ฑ์์ ํ์ผ์ ๋๋ ๋ฐฐ์น๋ก ์ฃผ์
ํด ํ์ ๋๋น 500% ์ฆ๊ฐ ๋ถํ, ์
๊ธฐ์ยท์ ํด์๋ ํ์ผ, ์์ ํ์ผ ํผ์
, ์ฒ๋ฆฌ ์๊ฐ ์์ฐ ๋ถ์กฑ ์ํฉ์์ ์์คํ
๋ค์ด ์ฌ๋ถยท๋ฐ์ดํฐ ์ ์ค๋ฅ ยท์ฒ๋ฆฌ๋ยท๋ฉ๋ชจ๋ฆฌ ์์ ์ฑยท๋ณ๋ชฉ ๊ตฌ๊ฐยท๊ธฐ๋ฅ ์ถ์ ๋ชจ๋๋ฅผ ๊ฒ์ฆํ๊ณ ์ฒ๋ฆฌ ํ๊ณ์น๋ฅผ ๋์ถํ๋ค.\"\"\"\n",
"import time\n",
"import tracemalloc\n",
"from collections import deque\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",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํฉ์ฑ ์์ฑ์์ ์กฐ๊ฐ(chip) ์์ฑ๊ธฐ (์ค์ ์์ฑ์์ยท์ฅ๋น ์ ์ยท์์น ์์)\n",
"# 32x32 ๊ดํ(EO) ์์๊ณผ SAR ์์ ํ ์์ ์ถ์ ๋ํ์ผ๋ก ๋ง๋ ๊ฐ์ฒด๋ฅผ ๊ทธ๋ฆฐ๋ค. ๋ํ ์น์๋ ๊ฐ์๊ฐ์ด๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"N = 32\n",
"STD_GSD = 0.5 # ํ์ค ํด์๋(m/ํฝ์
, ๊ฐ์ ๊ธฐ์ค)\n",
"CLASSES = [\"tank\", \"spg\", \"truck\", \"civil\"]\n",
"MILITARY = {\"tank\", \"spg\", \"truck\"}\n",
"# ๊ฐ์ฒด ์ขํ๊ณ(u: ๊ธธ์ด ๋ฐฉํฅ, v: ํญ ๋ฐฉํฅ, ํ์ค ํด์๋ ํฝ์
) ๊ฐ์ ๋ํ: (u์ค์ฌ, v์ค์ฌ, ๊ธธ์ด, ํญ, ๊ดํ ๋ฐ๊ธฐ ์ฐจ์ด)\n",
"SHAPES = {\n",
" \"tank\": [(0, 0, 11, 7, -40), (0.5, 0, 5, 5, -55), (6, 0, 7, 1.2, -60)],\n",
" \"spg\": [(0, 0, 16, 7.5, -38), (-4, 0, 6, 6.5, -52), (5, 0, 13, 1.4, -60)],\n",
" \"truck\": [(5, 0, 3.5, 5.5, -45), (-1.8, 0, 8.5, 6, -25)],\n",
" \"civil\": [(0, 0, 8, 3.8, 0), (1.2, 0, 2, 3.4, 0)],\n",
" \"decoy\": [(0, 0, 11, 7, -40), (0.5, 0, 5, 5, -42), (6, 0, 7, 1.2, -45)],\n",
" \"none\": [], # ๊ฐ์ฒด ์๋ ๋ฐฐ๊ฒฝ ํ์ผ\n",
"}\n",
"SAR_RETURN = {\"tank\": 0.85, \"spg\": 0.85, \"truck\": 0.7, \"civil\": 0.45, \"decoy\": 0.18, \"none\": 0.12} # ๊ธ์ ๋ฐ์ฌ ์ธ๊ธฐ(๊ฐ์)\n",
"\n",
"\n",
"def _mask(cls, angle, scale, squash=1.0):\n",
" \"\"\"ํ์ (angle, ๋)ยท์ถ์ฒ(scale)ยท๋น์ค๋ฌ ์ดฌ์ ์์ถ(squash)์ ์ ์ฉํด ๋ํ๋ณ ๋ง์คํฌ์ ๋ฐ๊ธฐ ์ฐจ์ด๋ฅผ ๋ง๋ ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 8] ์
๊ธฐ์ ๋ฐ ์ ํด์๋ ์์ฑ์์ ๋๋ ๋ฐฐ์น ์ฒ๋ฆฌ ์คํธ๋ ์ค ํ
์คํธ ๋ฐ ํ๊ณ์น ๋์ถ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_chip(cls, rng, cond=None):\n",
" \"\"\"chip 1๊ฐ = (๊ดํ 32x32, SAR 32x32, ๋ฉํ๋ฐ์ดํฐ). cond: camo, occl, cloud, low_sun, off_nadir, low_res, speckle\"\"\"\n",
" c = {\"camo\": 0.0, \"occl\": 0.0, \"cloud\": 0.0, \"low_sun\": False, \"off_nadir\": 0.0, \"low_res\": False, \"speckle\": 1.0, **(cond or {})}\n",
" gsd = rng.uniform(0.9, 1.2) if c[\"low_res\"] else rng.uniform(0.4, 0.6)\n",
" scale = STD_GSD / gsd\n",
" angle, off = rng.uniform(0, 360), c[\"off_nadir\"] or rng.uniform(0, 15)\n",
" squash = np.cos(np.radians(off))\n",
" sun_el = rng.uniform(12, 22) if c[\"low_sun\"] else rng.uniform(40, 70)\n",
" sun_az = rng.uniform(0, 360)\n",
" bg = 115 + ndimage.gaussian_filter(rng.normal(0, 30, (N, N)), 2) + rng.normal(0, 5, (N, N))\n",
" parts = _mask(cls, angle, scale, squash)\n",
" body = np.any([m for m, _ in parts], axis=0) if parts else np.zeros((N, N), bool)\n",
" opt = bg.copy()\n",
" # ๊ทธ๋ฆผ์: ํ์ ๋ฐ๋ ๋ฐฉํฅ์ผ๋ก ๋ฐ๋ฆฐ ๊ฐ์ฒด ๋ชจ์(ํ์ ๊ณ ๋๊ฐ ๋ฎ์์๋ก ๊ธธ๋ค)\n",
" L = min(8.0, 2.5 / np.tan(np.radians(sun_el))) * scale\n",
" sh = ndimage.shift(body.astype(float), (L * np.cos(np.radians(sun_az)), -L * np.sin(np.radians(sun_az))), order=0) > 0.5\n",
" opt[sh & ~body] -= 35 if cls != \"decoy\" else 25\n",
" if cls == \"civil\":\n",
" base = rng.choice([200.0, 60.0]) # ๋ฏผ๊ฐ ์ฐจ๋ ์์: ๋ฐ๊ฑฐ๋ ์ด๋์\n",
" opt[body] = base + rng.normal(0, 8, body.sum())\n",
" roof = parts[1][0]\n",
" opt[roof] = base * 0.8\n",
" else:\n",
" ref = bg[body].mean() if body.any() else bg.mean()\n",
" for m, d in parts:\n",
" opt[m] = ref + d + rng.normal(0, 4 if cls != \"decoy\" else 2, m.sum())\n",
" # ์์ฅ๋ง: ๊ฐ์ฒด ์ฃผ๋ณ์ ๋ฐฐ๊ฒฝ ์ง๊ฐ์ผ๋ก ๋ฎ์ด ๋๋น๋ฅผ ๋ฎ์ถ๋ค\n",
" if c[\"camo\"] > 0 and cls != \"civil\":\n",
" net = ndimage.binary_dilation(body, iterations=2)\n",
" tex = 105 + ndimage.gaussian_filter(rng.normal(0, 25, (N, N)), 1)\n",
" opt[net] = (1 - c[\"camo\"]) * opt[net] + c[\"camo\"] * tex[net]\n",
" # ์๋ชฉ ์ฐจํ: ๊ฐ์ฒด์ ์ผ๋ถ๋ฅผ ์ด๋์ด ๋๋ฌด ๋ฉ์ด๋ฆฌ๋ก ๊ฐ๋ฆฐ๋ค\n",
" if c[\"occl\"] > 0:\n",
" trees = ndimage.gaussian_filter(rng.random((N, N)), 2)\n",
" near = ndimage.binary_dilation(body, iterations=3)\n",
" cover = near & (trees > np.quantile(trees[body], 1 - c[\"occl\"])) if body.any() else np.zeros((N, N), bool)\n",
" opt[cover] = 70 + rng.normal(0, 6, cover.sum())\n",
" # ๊ตฌ๋ฆ: ๋ฐ๊ณ ๋งค๋ํ ๋ฉ์ด๋ฆฌ(๊ดํ๋ง ๊ฐ๋ฆผ, SAR๋ ํต๊ณผ)\n",
" cmask = np.zeros((N, N), bool)\n",
" if c[\"cloud\"] > 0:\n",
" blob = ndimage.gaussian_filter(rng.random((N, N)), 4)\n",
" cmask = blob > np.quantile(blob, 1 - c[\"cloud\"])\n",
" opt[cmask] = 0.25 * opt[cmask] + 0.75 * (235 + rng.normal(0, 3, cmask.sum()))\n",
" # SAR: ๊ณฑ์
ํ ์คํํด ์ก์ + ๊ธ์ ๋ฐ์ฌ(๋ชจ์๋ฆฌ ์ด์ค ๋ฐ์ฌ๋ก ๋ฐ์ ์ )\n",
" sar = np.full((N, N), 0.12)\n",
" sar[body] = SAR_RETURN[cls] * rng.uniform(0.8, 1.2)\n",
" edge = body & ~ndimage.binary_erosion(body)\n",
" sar[edge] *= 1.3 if cls in MILITARY else 1.0\n",
" sar = sar * rng.gamma(4 / c[\"speckle\"], c[\"speckle\"] / 4, (N, N))\n",
" if c[\"low_res\"]: # ์ ํด์๋: ์ ๋ณด ์์ค(๋ธ๋ฌ)\n",
" down = lambda a: np.kron(a.reshape(N // 2, 2, N // 2, 2).mean((1, 3)), np.ones((2, 2)))\n",
" opt, sar = ndimage.gaussian_filter(down(opt), 0.7), ndimage.gaussian_filter(down(sar), 0.7)\n",
" meta = {\"cls\": cls, \"gsd\": round(float(gsd), 2), \"off_nadir\": round(float(off), 1), \"sun_el\": round(float(sun_el), 1),\n",
" \"sun_az\": round(float(sun_az), 1), \"angle\": round(float(angle), 1), \"cloud_frac\": float(cmask.mean()),\n",
" \"cloud_on_obj\": float((cmask & body).sum() / max(body.sum(), 1)), **{k: c[k] for k in (\"camo\", \"occl\", \"low_sun\", \"low_res\")}}\n",
" return np.clip(opt, 0, 255), sar, meta\n",
"\n",
"\n",
"SCEN = { # ์ํยท์ดฌ์ ์กฐ๊ฑด ์๋๋ฆฌ์ค 10์ข
\n",
" \"clean\": {}, \"camo\": {\"camo\": 0.7}, \"occl30\": {\"occl\": 0.3}, \"occl50\": {\"occl\": 0.5}, \"cloud\": {\"cloud\": 0.25},\n",
" \"low_sun\": {\"low_sun\": True}, \"off_nadir\": {\"off_nadir\": 35.0}, \"low_res\": {\"low_res\": True},\n",
" \"speckle\": {\"speckle\": 3.0}, \"camo_occl\": {\"camo\": 0.6, \"occl\": 0.3}}\n",
"\n",
"\n",
"def make_dataset(n=400, seed=7, scen=(\"clean\",), classes=CLASSES):\n",
" rng = np.random.default_rng(seed)\n",
" O, S, M = np.zeros((n, N, N)), np.zeros((n, N, N)), []\n",
" for i in range(n):\n",
" cls, sc = classes[i % len(classes)], scen[(i // len(classes)) % len(scen)]\n",
" O[i], S[i], m = make_chip(cls, rng, SCEN[sc])\n",
" m.update({\"id\": f\"IMG{i:04d}\", \"scen\": sc})\n",
" M.append(m)\n",
" return O, S, M\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ์ ์ฒ๋ฆฌ(ํด์๋ยท๊ธฐํ ๋ณด์ , ๊ตฌ๋ฆ ์ฐจํ ์ฒ๋ฆฌ, ๋ฐฉ์ ์ ๊ทํ) ๊ณตํต ํจ์ ----\n",
"def _fit(a, fill):\n",
" \"\"\"์ค์ ๊ธฐ์ค์ผ๋ก 32x32์ ๋ง๊ฒ ์๋ฅด๊ฑฐ๋ ์ฑ์ด๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 8] ์
๊ธฐ์ ๋ฐ ์ ํด์๋ ์์ฑ์์ ๋๋ ๋ฐฐ์น ์ฒ๋ฆฌ ์คํธ๋ ์ค ํ
์คํธ ๋ฐ ํ๊ณ์น ๋์ถ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def cloud_mask(opt, sm, min_area=15):\n",
" \"\"\"๊ดํ์์ ๋ฐ๊ณ (185 ์ด๊ณผ) ๋งค๋ํ๋ฐ SAR์๋ ๋ฐ์ฌ๊ฐ ์๋ ์์ญ์ ๊ตฌ๋ฆ์ผ๋ก ๋ณธ๋ค(SAR๋ ๊ตฌ๋ฆ์ ํต๊ณผํ๋ฏ๋ก ๋ฐ์ ์ฐจ๋๊ณผ ๊ตฌ๋ถ๋๋ค).\"\"\"\n",
" bright = opt > 185\n",
" if bright.sum() < min_area:\n",
" return np.zeros_like(bright)\n",
" local_sd = np.sqrt(np.maximum(ndimage.uniform_filter(opt ** 2, 3) - ndimage.uniform_filter(opt, 3) ** 2, 0))\n",
" cand = bright & (local_sd < 12) & (sm < 0.3)\n",
" lab, n = ndimage.label(cand)\n",
" if n == 0:\n",
" return cand\n",
" sizes = ndimage.sum(cand.astype(float), lab, range(1, n + 1))\n",
" return ndimage.binary_dilation(np.isin(lab, 1 + np.where(sizes >= min_area)[0]), iterations=2) & (opt > 140)\n",
"\n",
"\n",
"def orientation(opt, sm):\n",
" \"\"\"SAR ๋ฐ์ฌ(์์ฅยท๊ตฌ๋ฆ ์ํฅ ์ ์, sm์ ํํํํ SAR)์ ๊ดํ ๋๋น๋ฅผ ํฉ์น ๊ฐ์ฒด ๋ง์คํฌ์ ์ฃผ์ถ ๋ฐฉํฅ(๋).\"\"\"\n",
" m = (sm > 0.3) | (np.abs(opt - opt.mean()) > 30)\n",
" lab, n = ndimage.label(m)\n",
" if n == 0:\n",
" return 0.0, m\n",
" c = lab[N // 2 - 6:N // 2 + 6, N // 2 - 6:N // 2 + 6]\n",
" k = np.bincount(c[c > 0]).argmax() if (c > 0).any() else 1 + int(np.argmax(ndimage.sum(m.astype(float), lab, range(1, n + 1))))\n",
" m = lab == k\n",
" yy, xx = np.nonzero(m)\n",
" if len(yy) < 5:\n",
" return 0.0, m\n",
" dy, dx = yy - yy.mean(), xx - xx.mean() # 2์ฐจ ๋ชจ๋ฉํธ๋ก ์ฃผ์ถ ๊ฐ๋ ๊ณ์ฐ\n",
" return float(np.degrees(0.5 * np.arctan2(2 * (dx * dy).mean(), (dx * dx).mean() - (dy * dy).mean()))), m\n",
"\n",
"\n",
"_YY, _XX = np.mgrid[0:N, 0:N] - (N - 1) / 2\n",
"\n",
"\n",
"def warp(stack, zy, zx, ang):\n",
" \"\"\"์ฌ๋ฌ ์ฅ(B,์ฑ๋,32,32)์ ์ฅ๋ณ ํ๋ยท์ถ์(zy, zx)์ ํ์ (ang, ๋)์ ํ ๋ฒ์ ์์ ํ ๋ณด๊ฐ์ผ๋ก ์ ์ฉํ๋ค(๋ฒกํฐ ์ฐ์ฐ).\"\"\"\n",
" a = np.radians(ang)[:, None, None]\n",
" zy, zx = np.asarray(zy)[:, None, None], np.asarray(zx)[:, None, None]\n",
" sy = np.clip((np.cos(a) * _YY + np.sin(a) * _XX) / zy + (N - 1) / 2, 0, N - 1.001) # ์ถ๋ ฅ ์ขํ -> ์๋ณธ ์ขํ\n",
" sx = np.clip((-np.sin(a) * _YY + np.cos(a) * _XX) / zx + (N - 1) / 2, 0, N - 1.001)\n",
" y0, x0 = sy.astype(int), sx.astype(int)\n",
" fy, fx = (sy - y0).reshape(len(stack), 1, -1), (sx - x0).reshape(len(stack), 1, -1)\n",
" flat = stack.reshape(len(stack), stack.shape[1], -1)\n",
" lin = (y0 * N + x0).reshape(len(stack), 1, -1)\n",
" g = lambda d: np.take_along_axis(flat, np.broadcast_to(lin + d, (len(stack), stack.shape[1], lin.shape[2])), axis=2)\n",
" out = g(0) * (1 - fy) * (1 - fx) + g(N) * fy * (1 - fx) + g(1) * (1 - fy) * fx + g(N + 1) * fy * fx\n",
" return out.reshape(stack.shape)\n",
"\n",
"\n",
"def preprocess_batch(O, S, M):\n",
" \"\"\"์ฌ๋ฌ ์ฅ์ ํ ๋ฒ์ ์ ์ฒ๋ฆฌํ๋ค. 0) ๊ฒฐ์ธก ๋ณด์ 1) ํด์๋ยท๋น์ค๋ฌ ์ดฌ์ ๋ณด์ (ํ์ค 0.5m/ํฝ์
)\n",
" 2) ๊ตฌ๋ฆ ์ฐจํ ์์ญ์ ๋ฐฐ๊ฒฝ๊ฐ์ผ๋ก ์ฑ์ 3) ๊ฐ์ฒด ์ฃผ์ถ์ ๊ฐ๋ก๋ก ํ์ 4) 0~1 ์ ๊ทํ.\"\"\"\n",
" src = np.stack([np.asarray(O, float), np.asarray(S, float)], axis=1)\n",
" if np.isnan(src).any(): # 0) ์ผ์ ๊ฒฐ์ธก ํฝ์
๋ณด์ \n",
" mean = np.nanmean(src, axis=(2, 3), keepdims=True)\n",
" src = np.where(np.isnan(src), mean, src)\n",
" zy = np.array([m[\"gsd\"] / STD_GSD / np.cos(np.radians(m[\"off_nadir\"])) for m in M])\n",
" zx = np.array([m[\"gsd\"] / STD_GSD for m in M])\n",
" out = warp(src, zy, zx, np.zeros(len(M)))\n",
" sm = ndimage.uniform_filter(out[:, 1], (1, 3, 3))\n",
" angs, cloud = np.zeros(len(M)), np.zeros(len(M))\n",
" for i in range(len(M)):\n",
" cm = cloud_mask(out[i, 0], sm[i])\n",
" if cm.any():\n",
" fill = out[i, 0][~cm].mean() if (~cm).any() else 115.0\n",
" out[i, 0][cm] = fill\n",
" back = warp(cm[None, None].astype(float), [1 / zy[i]], [1 / zx[i]], np.zeros(1))[0, 0] > 0.5\n",
" src[i, 0][back] = fill # ์๋ณธ์ ๊ฐ์ ๊ตฌ๋ฆ ์์ญ๋ ์ฑ์\n",
" cloud[i] = cm.mean()\n",
" angs[i] = orientation(out[i, 0], sm[i])[0]\n",
" rot = np.abs(angs) > 3 # 3๋ ์ดํ๋ ํ์ ์๋ต\n",
" if rot.any():\n",
" out[rot] = warp(src[rot], zy[rot], zx[rot], angs[rot])\n",
" return [(np.clip(o / 255, 0, 1), np.clip(s / 1.5, 0, 1), float(c)) for (o, s), c in zip(out, cloud)]\n",
"\n",
"\n",
"def preprocess(opt, sar, meta):\n",
" \"\"\"ํ ์ฅ ์ ์ฒ๋ฆฌ(preprocess_batch์ ๊ฐ์ ์ฒ๋ฆฌ).\"\"\"\n",
" return preprocess_batch([opt], [sar], [meta])[0]\n",
"\n",
"# ---- ํน์ง ์ถ์ถ ๊ณตํต ํจ์ ----\n",
"KERNELS = [np.array(k, float) for k in (\n",
" [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], [[-1, -2, -1], [0, 0, 0], [1, 2, 1]], [[0, 1, 0], [1, -4, 1], [0, 1, 0]],\n",
" [[2, -1, -1], [-1, 2, -1], [-1, -1, 2]], [[-1, -1, 2], [-1, 2, -1], [2, -1, -1]], [[1, 1, 1], [1, 1, 1], [1, 1, 1]])]\n",
"\n",
"\n",
"def conv_features(o):\n",
" \"\"\"๊ฐ์ด CNN ํน์ง: ๊ณ ์ ํฉ์ฑ๊ณฑ ํํฐ 6์ข
-> ReLU -> 4x4 ์ต๋ ํ๋ง(8x8 ์ถ๋ ฅ).\"\"\"\n",
" feats = []\n",
" for k in KERNELS:\n",
" r = np.maximum(ndimage.correlate(o - o.mean(), k / np.abs(k).sum()), 0)\n",
" feats.append(r.reshape(8, 4, 8, 4).max(axis=(1, 3)).ravel())\n",
" feats.append(np.maximum(-ndimage.correlate(o - o.mean(), k / np.abs(k).sum()), 0).reshape(8, 4, 8, 4).max(axis=(1, 3)).ravel())\n",
" return np.concatenate(feats)\n",
"\n",
"\n",
"def hog(o, cell=8, bins=8):\n",
" gy, gx = np.gradient(o)\n",
" mag, ang = np.hypot(gx, gy), np.mod(np.arctan2(gy, gx), np.pi)\n",
" b = np.minimum((ang / np.pi * bins).astype(int), bins - 1)\n",
" out = np.zeros((N // cell, N // cell, bins))\n",
" for i in range(N // cell):\n",
" for j in range(N // cell):\n",
" sl = (slice(i * cell, (i + 1) * cell), slice(j * cell, (j + 1) * cell))\n",
" out[i, j] = np.bincount(b[sl].ravel(), mag[sl].ravel(), bins)\n",
" return (out / (np.linalg.norm(out) + 1e-6)).ravel()\n",
"\n",
"\n",
"def shape_features(o, s):\n",
" \"\"\"๊ฐ์ฒด ๋ชจ์ ํน์ง: ๊ฐ๋ก ๋ฐฉํฅ ํฌ์์ผ๋ก ์ ์ฒด ๊ธธ์ดยท์ฐจ์ฒด ๊ธธ์ดยท๊ฐ๋ ๋์ถ๋ถ(ํฌ์ ) ๊ธธ์ดยท๋๊บผ์ด ๋ถ๋ถ(ํฌํ) ์์น๋ฅผ ์ฐ๋ค.\n",
" ๋ฐฉ์ ์ ๊ทํ ๋ค์๋ ์๋ค(180๋)๊ฐ ๋ค์งํ ์ ์์ผ๋ฏ๋ก, ๊ฐ๋ ๋์ถ๋ถ๊ฐ ์ค๋ฅธ์ชฝ์ ์ค๋๋ก ๋ง์ถ๋ค.\"\"\"\n",
" sm = ndimage.uniform_filter(s, 3)\n",
" dark = np.abs(o - np.median(o))\n",
" m = (sm > 0.22) | (dark > 0.12)\n",
" lab, n = ndimage.label(m)\n",
" if n:\n",
" c = lab[10:22, 8:24]\n",
" k = np.bincount(c[c > 0]).argmax() if (c > 0).any() else 1\n",
" m = lab == k\n",
" p = m.sum(axis=0).astype(float)\n",
" cols = np.nonzero(p)[0]\n",
" if len(cols) == 0:\n",
" return np.zeros(8), o, s\n",
" thin = (p > 0) & (p <= 2)\n",
" left, right = thin[:N // 2].sum(), thin[N // 2:].sum()\n",
" if left > right:\n",
" o, s, m, p = o[:, ::-1], s[:, ::-1], m[:, ::-1], p[::-1]\n",
" cols = np.nonzero(p)[0]\n",
" body = np.nonzero(p > 3)[0]\n",
" d = np.where(m, dark if left <= right else dark[:, ::-1], 0)\n",
" heavy = d.sum(axis=0)\n",
" centre = (body[0] + body[-1]) / 2 if len(body) else N / 2\n",
" turret = (heavy * np.arange(N)).sum() / max(heavy.sum(), 1e-6) - centre\n",
" return np.array([cols[-1] - cols[0] + 1, len(body), ((p > 0) & (p <= 2)).sum(), p.max(), turret,\n",
" m.sum(), (cols[-1] - (body[-1] if len(body) else cols[-1])), m.sum() / max(len(cols) * p.max(), 1)]), o, s\n",
"\n",
"\n",
"def adv_features(o, s):\n",
" \"\"\"์ ๋ฐ ํน์ง: ๋ชจ์(๊ธธ์ดยทํฌ์ ยทํฌํ ์์น) + HOG(์ค๊ณฝ) + SAR ๋ฐ์ฌ ํต๊ณ(์์ฅ๋งยท๊ตฌ๋ฆ ์ํฅ ์ ์) + ํฌ์ ์ค๊ณฝ.\"\"\"\n",
" shp, o, s = shape_features(o, s)\n",
" c = s[8:24, 4:28]\n",
" sm = ndimage.uniform_filter(s, 3)\n",
" m = sm > 0.2\n",
" dark = np.abs(o - np.median(o))\n",
" return np.concatenate([shp, hog(o), [c.mean(), c.max(), (sm > 0.25).mean(), (sm > 0.4).mean(), m.sum(axis=1).max(), m.sum(axis=0).max()],\n",
" sm.mean(axis=0).reshape(8, 4).mean(1), sm.mean(axis=1).reshape(8, 4).mean(1),\n",
" dark.mean(axis=0).reshape(8, 4).mean(1), dark.mean(axis=1).reshape(8, 4).mean(1),\n",
" [o.mean(), o.std(), dark.max(), (o > 0.65).mean(), (o < 0.3).mean()]])\n",
"\n",
"Otr, Str, Mtr = make_dataset(600, seed=3, scen=tuple(SCEN), classes=CLASSES + [\"none\"])\n",
"clf = RandomForestClassifier(n_estimators=100, min_samples_leaf=2, random_state=0, n_jobs=1)\n",
"clf.fit(np.array([adv_features(o, s) for o, s, _ in preprocess_batch(Otr, Str, Mtr)]), [m[\"cls\"] for m in Mtr])\n",
"\n",
"\n",
"def process(batch, budget_ms, prof):\n",
" \"\"\"๋ฐฐ์น ์ฒ๋ฆฌ: ํ์ผ ๊ฒ์ฌ -> ์ ์ฒ๋ฆฌ -> ํน์ง -> ์ผ๊ด ๋ถ๋ฅ. ์์ฐ์ ๋๊ธฐ๋ฉด ์ ๋ฐ ๋ถ๋ฅ๋ฅผ ๋ฏธ๋ฃจ๊ณ '๊ฐ์ฒด ์ ๋ฌด'๋ง ๋จผ์ ํ์(๊ธฐ๋ฅ ์ถ์).\"\"\"\n",
" ok, quarantine, X = [], [], []\n",
" t_start = time.perf_counter()\n",
" for o, s, m in batch:\n",
" try:\n",
" if o.shape != (N, N) or s.shape != (N, N) or not np.isfinite(o).mean() > 0.5 or o.max() <= 0:\n",
" raise ValueError(\"์์ ํ์ผ\")\n",
" t0 = time.perf_counter(); po, ps, _ = preprocess(o, s, m); prof[\"์ ์ฒ๋ฆฌ\"] += time.perf_counter() - t0\n",
" if (time.perf_counter() - t_start) * 1000 > budget_ms:\n",
" ok.append((\"๊ฐ์ฒด์ ๋ฌด\", float(ndimage.uniform_filter(ps, 3).max() > 0.25))); continue\n",
" t0 = time.perf_counter(); X.append(adv_features(po, ps)); prof[\"ํน์ง\"] += time.perf_counter() - t0\n",
" ok.append((\"์ ๋ฐ\", None))\n",
" except Exception as e:\n",
" quarantine.append((m[\"id\"], str(e))) # ๊ฒฉ๋ฆฌ ๋ณด๊ด(์ ์ค ์๋)\n",
" if X:\n",
" t0 = time.perf_counter(); clf.predict(np.array(X)); prof[\"๋ถ๋ฅ\"] += time.perf_counter() - t0\n",
" return ok, quarantine\n",
"\n",
"\n",
"def workload(n, scen=(\"clean\",), broken=0.0, seed=0):\n",
" O, S, M = make_dataset(n, seed=seed, scen=scen, classes=CLASSES + [\"none\"])\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for o, s, m in zip(O, S, M):\n",
" r = rng.random()\n",
" if r < broken / 3:\n",
" o = o[:20] # ์๋ฆฐ ํ์ผ\n",
" elif r < 2 * broken / 3:\n",
" o = np.full_like(o, np.nan) # ์ ์ก ์ค๋ฅ(์ ๋ถ ๊ฒฐ์ธก)\n",
" elif r < broken:\n",
" o = np.zeros_like(o) # ๋น ํ์ผ\n",
" out.append((o, s, m))\n",
" return out\n",
"\n",
"\n",
"bad_wx = (\"cloud\", \"low_res\", \"speckle\", \"camo_occl\")\n",
"cases = [(\"ํ์ 100์ฅ(๊ธฐ์ค)\", 100, (\"clean\",), 0, 1e9), (\"500% ์ฆ๊ฐ(600์ฅ)\", 600, (\"clean\",), 0, 1e9),\n",
" (\"600์ฅ ์
๊ธฐ์ยท์ ํด์๋\", 600, bad_wx, 0, 1e9), (\"600์ฅ + ์์ ํ์ผ 5%\", 600, (\"clean\",), 0.05, 1e9),\n",
" (\"600์ฅ + ์ฒ๋ฆฌ ์์ฐ ๋ถ์กฑ\", 600, (\"clean\",), 0, 150)]\n",
"rows, prof = [], {\"์ ์ฒ๋ฆฌ\": 0.0, \"ํน์ง\": 0.0, \"๋ถ๋ฅ\": 0.0}\n",
"for i, (name, n, sc, br, budget) in enumerate(cases):\n",
" q = deque(workload(n, sc, br, seed=10 + i))\n",
" received, done, quar, degraded, down = len(q), 0, 0, 0, 0\n",
" t0 = time.perf_counter()\n",
" try:\n",
" while q:\n",
" chunk = [q.popleft() for _ in range(min(100, len(q)))]\n",
" ok, qu = process(chunk, budget, prof)\n",
" done += len(ok); quar += len(qu); degraded += sum(k == \"๊ฐ์ฒด์ ๋ฌด\" for k, _ in ok)\n",
" except Exception:\n",
" down = 1\n",
" dt = time.perf_counter() - t0\n",
" rows.append({\"์ํฉ\": name, \"์์ \": received, \"์ฒ๋ฆฌ\": done, \"๊ฒฉ๋ฆฌ\": quar, \"์ ์ค\": received - done - quar, \"๋ค์ด\": down,\n",
" \"์ฒ๋ฆฌ๋(์ฅ/์ด)\": int(received / dt), \"์ฑ๋ฅ์ ํ ๋ชจ๋\": f\"์ ๋ฐ๋ถ๋ฅ ์ง์ฐ {degraded}์ฅ\" if degraded else \"-\"})\n",
"df = pd.DataFrame(rows)\n",
"print(df.to_string(index=False))\n",
"print(f\"\\n์์คํ
๋ค์ด {df['๋ค์ด'].sum()}๊ฑด, ๋ฐ์ดํฐ ์ ์ค๋ฅ {df['์ ์ค'].sum() / df['์์ '].sum():.1%} (๋ชฉํ 0%), ์์ ํ์ผ์ ์๋ณธ ๋ณด๊ด ํ ์ฌ์์ฒญ\")\n",
"\n",
"# ๋ฉ๋ชจ๋ฆฌ ์์ ์ฑ: ๊ฐ์ ๋ฐฐ์น(100์ฅ)๋ฅผ 5ํ ๋ฐ๋ณต ์ฒ๋ฆฌํ๋ฉฐ ์ถ์ ๋ฉ๋ชจ๋ฆฌ ๋ณํ ํ์ธ\n",
"batch = workload(100, seed=99)\n",
"tracemalloc.start(); mem = []\n",
"for _ in range(5):\n",
" process(batch, 1e9, {\"์ ์ฒ๋ฆฌ\": 0, \"ํน์ง\": 0, \"๋ถ๋ฅ\": 0}); mem.append(tracemalloc.get_traced_memory()[0] / 1e6)\n",
"tracemalloc.stop()\n",
"print(\"๋ฉ๋ชจ๋ฆฌ(๋์ผ ๋ฐฐ์น 5ํ, MB):\", [round(m, 2) for m in mem], f\"-> ์ฆ๊ฐ {mem[-1] - mem[0]:.2f} MB (๋์ ํ๋จ ๊ธฐ์ค 5MB)\")\n",
"tot = sum(prof.values())\n",
"print(\"๋ณ๋ชฉ ๊ตฌ๊ฐ(๋์ ์ฒ๋ฆฌ์๊ฐ ๋น์จ):\", {k: f\"{v / tot:.0%}\" for k, v in sorted(prof.items(), key=lambda x: -x[1])})\n",
"thr = df.loc[1, \"์ฒ๋ฆฌ๋(์ฅ/์ด)\"]\n",
"area_km2 = (N * STD_GSD / 1000) ** 2\n",
"print(f\"์ฒ๋ฆฌ ํ๊ณ์น ํ์ฐ: ๋จ์ผ ์ฝ์ด {thr}์ฅ/์ด = ์๊ฐ๋น ์ฝ {thr * 3600 * area_km2:,.0f} km2 (ํ์ผ 1์ฅ {area_km2 * 1e6:.0f} m2, ํ์ค ํด์๋ 0.5m ๊ธฐ์ค)\")\n",
"\n",
"plt.figure(figsize=(8, 4))\n",
"plt.bar(range(len(df)), df[\"์ฒ๋ฆฌ๋(์ฅ/์ด)\"], color=[\"tab:red\" if m != \"-\" else \"tab:blue\" for m in df[\"์ฑ๋ฅ์ ํ ๋ชจ๋\"]])\n",
"plt.xticks(range(len(df)), [f\"case{i + 1}\" for i in range(len(df))]); plt.ylabel(\"tiles / s\")\n",
"plt.title(\"Tile batch stress test (red = degraded mode)\"); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
08 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
" ์ํฉ ์์ ์ฒ๋ฆฌ ๊ฒฉ๋ฆฌ ์ ์ค ๋ค์ด ์ฒ๋ฆฌ๋(์ฅ/์ด) ์ฑ๋ฅ์ ํ ๋ชจ๋\n",
" ํ์ 100์ฅ(๊ธฐ์ค) 100 100 0 0 0 525 -\n",
" 500% ์ฆ๊ฐ(600์ฅ) 600 600 0 0 0 494 -\n",
" 600์ฅ ์
๊ธฐ์ยท์ ํด์๋ 600 600 0 0 0 464 -\n",
"600์ฅ + ์์ ํ์ผ 5% 600 569 31 0 0 546 -\n",
"600์ฅ + ์ฒ๋ฆฌ ์์ฐ ๋ถ์กฑ 600 600 0 0 0 561 ์ ๋ฐ๋ถ๋ฅ ์ง์ฐ 111์ฅ\n",
"\n",
"์์คํ
๋ค์ด 0๊ฑด, ๋ฐ์ดํฐ ์ ์ค๋ฅ 0.0% (๋ชฉํ 0%), ์์ ํ์ผ์ ์๋ณธ ๋ณด๊ด ํ ์ฌ์์ฒญ\n",
"๋ฉ๋ชจ๋ฆฌ(๋์ผ ๋ฐฐ์น 5ํ, MB): [0.02, 0.03, 0.04, 0.05, 0.06] -> ์ฆ๊ฐ 0.04 MB (๋์ ํ๋จ ๊ธฐ์ค 5MB)\n",
"๋ณ๋ชฉ ๊ตฌ๊ฐ(๋์ ์ฒ๋ฆฌ์๊ฐ ๋น์จ): {'์ ์ฒ๋ฆฌ': '52%', 'ํน์ง': '43%', '๋ถ๋ฅ': '5%'}\n",
"์ฒ๋ฆฌ ํ๊ณ์น ํ์ฐ: ๋จ์ผ ์ฝ์ด 494์ฅ/์ด = ์๊ฐ๋น ์ฝ 455 km2 (ํ์ผ 1์ฅ 256 m2, ํ์ค ํด์๋ 0.5m ๊ธฐ์ค)\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
09. AAR ๊ธฐ๋ฐ ๋ชจ์ ์์ฅ ํ์ ์คํ๋
์ฌ๋ก ๋ถ์ ๋ฐ ๊ฐ์ฒด๋ถ๋ฅ ๋ชจ๋ธ ํ๋ผ๋ฏธํฐ ์ฌ๋ณด์ \n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"AAR ์ฌํ๊ฒํ ๋ฅผ ํตํด ๋ชจ์ ์์ฅ ์ฅ๋น ์คํ๋
์ฌ๋ก๋ฅผ ๋ถ์ํ๊ณ ๋น์ ๋ชจ๋ธ์ ํน์ง ์ถ์ถ ๊ฐ์ค์น์ ์๊ณ๊ฐ์ ์ฌ๋ณด์ ํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **AAR ์ฌํ๊ฒํ ๋ก ๋ชจ์ ์์ฅ ํ์ ์คํ๋
์ฌ๋ก๋ฅผ ๋ถ์ํ๊ณ ๋ถ๋ฅ ๋ชจ๋ธ์ ํน์ง ๊ฐ์ค์น์ ํ์ ์๊ณ๊ฐ์ ์ฌ๋ณด์ ํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๋ชจ์ ์์ฅ ํ์ ์ด ์์ธ ํ๋
ํ๋ จ ๊ฒฐ๊ณผ๋ฅผ AAR ์ผ์ง๋ก ์ ๋ฆฌํ๊ณ ์คํ๋
์ ์์ธ(๋ชจ์ ํ์ ์คํ๋
, ๊ธฐ์ข
ํผ๋๊ณผ ์กฐ๊ฑด)๋ณ๋ก ๋ถ๋ฅ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `docs/isr01_aar_report.md ๋ฐ configs/isr01_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(์ฌํ๊ฒํ ) ์ผ์ง ํ์์ผ๋ก ๋ชจ์ ์์ฅ ํ์ (decoy)์ด ์์ธ ํ๋
ํ๋ จ ๊ฒฐ๊ณผ์ ์คํ๋
์ฌ๋ก๋ฅผ ์์ธ๋ณ๋ก ๋ถ๋ฅํ๊ณ , ํน์ง ๊ฐ์ค์น(๋ชจ์ ํ์ ํ์ต ์ถ๊ฐ)์ SAR-๊ดํ ์ผ์น์ฑ ์๊ณ๊ฐ์ ์ฌ๋ณด์ ํด ์ข
ํฉ ์ ํ๋ ํฅ์๊ณผ ์ฌ๋ฐ ์ฌ๋ถ๋ฅผ ํ๊ท ์ํ์ผ๋ก ํ์ธํ๋ค.\"\"\"\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",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํฉ์ฑ ์์ฑ์์ ์กฐ๊ฐ(chip) ์์ฑ๊ธฐ (์ค์ ์์ฑ์์ยท์ฅ๋น ์ ์ยท์์น ์์)\n",
"# 32x32 ๊ดํ(EO) ์์๊ณผ SAR ์์ ํ ์์ ์ถ์ ๋ํ์ผ๋ก ๋ง๋ ๊ฐ์ฒด๋ฅผ ๊ทธ๋ฆฐ๋ค. ๋ํ ์น์๋ ๊ฐ์๊ฐ์ด๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"N = 32\n",
"STD_GSD = 0.5 # ํ์ค ํด์๋(m/ํฝ์
, ๊ฐ์ ๊ธฐ์ค)\n",
"CLASSES = [\"tank\", \"spg\", \"truck\", \"civil\"]\n",
"MILITARY = {\"tank\", \"spg\", \"truck\"}\n",
"# ๊ฐ์ฒด ์ขํ๊ณ(u: ๊ธธ์ด ๋ฐฉํฅ, v: ํญ ๋ฐฉํฅ, ํ์ค ํด์๋ ํฝ์
) ๊ฐ์ ๋ํ: (u์ค์ฌ, v์ค์ฌ, ๊ธธ์ด, ํญ, ๊ดํ ๋ฐ๊ธฐ ์ฐจ์ด)\n",
"SHAPES = {\n",
" \"tank\": [(0, 0, 11, 7, -40), (0.5, 0, 5, 5, -55), (6, 0, 7, 1.2, -60)],\n",
" \"spg\": [(0, 0, 16, 7.5, -38), (-4, 0, 6, 6.5, -52), (5, 0, 13, 1.4, -60)],\n",
" \"truck\": [(5, 0, 3.5, 5.5, -45), (-1.8, 0, 8.5, 6, -25)],\n",
" \"civil\": [(0, 0, 8, 3.8, 0), (1.2, 0, 2, 3.4, 0)],\n",
" \"decoy\": [(0, 0, 11, 7, -40), (0.5, 0, 5, 5, -42), (6, 0, 7, 1.2, -45)],\n",
" \"none\": [], # ๊ฐ์ฒด ์๋ ๋ฐฐ๊ฒฝ ํ์ผ\n",
"}\n",
"SAR_RETURN = {\"tank\": 0.85, \"spg\": 0.85, \"truck\": 0.7, \"civil\": 0.45, \"decoy\": 0.18, \"none\": 0.12} # ๊ธ์ ๋ฐ์ฌ ์ธ๊ธฐ(๊ฐ์)\n",
"\n",
"\n",
"def _mask(cls, angle, scale, squash=1.0):\n",
" \"\"\"ํ์ (angle, ๋)ยท์ถ์ฒ(scale)ยท๋น์ค๋ฌ ์ดฌ์ ์์ถ(squash)์ ์ ์ฉํด ๋ํ๋ณ ๋ง์คํฌ์ ๋ฐ๊ธฐ ์ฐจ์ด๋ฅผ ๋ง๋ ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 9] AAR ๊ธฐ๋ฐ ๋ชจ์ ์์ฅ ํ์ ์คํ๋
์ฌ๋ก ๋ถ์ ๋ฐ ๊ฐ์ฒด๋ถ๋ฅ ๋ชจ๋ธ ํ๋ผ๋ฏธํฐ ์ฌ๋ณด์ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_chip(cls, rng, cond=None):\n",
" \"\"\"chip 1๊ฐ = (๊ดํ 32x32, SAR 32x32, ๋ฉํ๋ฐ์ดํฐ). cond: camo, occl, cloud, low_sun, off_nadir, low_res, speckle\"\"\"\n",
" c = {\"camo\": 0.0, \"occl\": 0.0, \"cloud\": 0.0, \"low_sun\": False, \"off_nadir\": 0.0, \"low_res\": False, \"speckle\": 1.0, **(cond or {})}\n",
" gsd = rng.uniform(0.9, 1.2) if c[\"low_res\"] else rng.uniform(0.4, 0.6)\n",
" scale = STD_GSD / gsd\n",
" angle, off = rng.uniform(0, 360), c[\"off_nadir\"] or rng.uniform(0, 15)\n",
" squash = np.cos(np.radians(off))\n",
" sun_el = rng.uniform(12, 22) if c[\"low_sun\"] else rng.uniform(40, 70)\n",
" sun_az = rng.uniform(0, 360)\n",
" bg = 115 + ndimage.gaussian_filter(rng.normal(0, 30, (N, N)), 2) + rng.normal(0, 5, (N, N))\n",
" parts = _mask(cls, angle, scale, squash)\n",
" body = np.any([m for m, _ in parts], axis=0) if parts else np.zeros((N, N), bool)\n",
" opt = bg.copy()\n",
" # ๊ทธ๋ฆผ์: ํ์ ๋ฐ๋ ๋ฐฉํฅ์ผ๋ก ๋ฐ๋ฆฐ ๊ฐ์ฒด ๋ชจ์(ํ์ ๊ณ ๋๊ฐ ๋ฎ์์๋ก ๊ธธ๋ค)\n",
" L = min(8.0, 2.5 / np.tan(np.radians(sun_el))) * scale\n",
" sh = ndimage.shift(body.astype(float), (L * np.cos(np.radians(sun_az)), -L * np.sin(np.radians(sun_az))), order=0) > 0.5\n",
" opt[sh & ~body] -= 35 if cls != \"decoy\" else 25\n",
" if cls == \"civil\":\n",
" base = rng.choice([200.0, 60.0]) # ๋ฏผ๊ฐ ์ฐจ๋ ์์: ๋ฐ๊ฑฐ๋ ์ด๋์\n",
" opt[body] = base + rng.normal(0, 8, body.sum())\n",
" roof = parts[1][0]\n",
" opt[roof] = base * 0.8\n",
" else:\n",
" ref = bg[body].mean() if body.any() else bg.mean()\n",
" for m, d in parts:\n",
" opt[m] = ref + d + rng.normal(0, 4 if cls != \"decoy\" else 2, m.sum())\n",
" # ์์ฅ๋ง: ๊ฐ์ฒด ์ฃผ๋ณ์ ๋ฐฐ๊ฒฝ ์ง๊ฐ์ผ๋ก ๋ฎ์ด ๋๋น๋ฅผ ๋ฎ์ถ๋ค\n",
" if c[\"camo\"] > 0 and cls != \"civil\":\n",
" net = ndimage.binary_dilation(body, iterations=2)\n",
" tex = 105 + ndimage.gaussian_filter(rng.normal(0, 25, (N, N)), 1)\n",
" opt[net] = (1 - c[\"camo\"]) * opt[net] + c[\"camo\"] * tex[net]\n",
" # ์๋ชฉ ์ฐจํ: ๊ฐ์ฒด์ ์ผ๋ถ๋ฅผ ์ด๋์ด ๋๋ฌด ๋ฉ์ด๋ฆฌ๋ก ๊ฐ๋ฆฐ๋ค\n",
" if c[\"occl\"] > 0:\n",
" trees = ndimage.gaussian_filter(rng.random((N, N)), 2)\n",
" near = ndimage.binary_dilation(body, iterations=3)\n",
" cover = near & (trees > np.quantile(trees[body], 1 - c[\"occl\"])) if body.any() else np.zeros((N, N), bool)\n",
" opt[cover] = 70 + rng.normal(0, 6, cover.sum())\n",
" # ๊ตฌ๋ฆ: ๋ฐ๊ณ ๋งค๋ํ ๋ฉ์ด๋ฆฌ(๊ดํ๋ง ๊ฐ๋ฆผ, SAR๋ ํต๊ณผ)\n",
" cmask = np.zeros((N, N), bool)\n",
" if c[\"cloud\"] > 0:\n",
" blob = ndimage.gaussian_filter(rng.random((N, N)), 4)\n",
" cmask = blob > np.quantile(blob, 1 - c[\"cloud\"])\n",
" opt[cmask] = 0.25 * opt[cmask] + 0.75 * (235 + rng.normal(0, 3, cmask.sum()))\n",
" # SAR: ๊ณฑ์
ํ ์คํํด ์ก์ + ๊ธ์ ๋ฐ์ฌ(๋ชจ์๋ฆฌ ์ด์ค ๋ฐ์ฌ๋ก ๋ฐ์ ์ )\n",
" sar = np.full((N, N), 0.12)\n",
" sar[body] = SAR_RETURN[cls] * rng.uniform(0.8, 1.2)\n",
" edge = body & ~ndimage.binary_erosion(body)\n",
" sar[edge] *= 1.3 if cls in MILITARY else 1.0\n",
" sar = sar * rng.gamma(4 / c[\"speckle\"], c[\"speckle\"] / 4, (N, N))\n",
" if c[\"low_res\"]: # ์ ํด์๋: ์ ๋ณด ์์ค(๋ธ๋ฌ)\n",
" down = lambda a: np.kron(a.reshape(N // 2, 2, N // 2, 2).mean((1, 3)), np.ones((2, 2)))\n",
" opt, sar = ndimage.gaussian_filter(down(opt), 0.7), ndimage.gaussian_filter(down(sar), 0.7)\n",
" meta = {\"cls\": cls, \"gsd\": round(float(gsd), 2), \"off_nadir\": round(float(off), 1), \"sun_el\": round(float(sun_el), 1),\n",
" \"sun_az\": round(float(sun_az), 1), \"angle\": round(float(angle), 1), \"cloud_frac\": float(cmask.mean()),\n",
" \"cloud_on_obj\": float((cmask & body).sum() / max(body.sum(), 1)), **{k: c[k] for k in (\"camo\", \"occl\", \"low_sun\", \"low_res\")}}\n",
" return np.clip(opt, 0, 255), sar, meta\n",
"\n",
"\n",
"SCEN = { # ์ํยท์ดฌ์ ์กฐ๊ฑด ์๋๋ฆฌ์ค 10์ข
\n",
" \"clean\": {}, \"camo\": {\"camo\": 0.7}, \"occl30\": {\"occl\": 0.3}, \"occl50\": {\"occl\": 0.5}, \"cloud\": {\"cloud\": 0.25},\n",
" \"low_sun\": {\"low_sun\": True}, \"off_nadir\": {\"off_nadir\": 35.0}, \"low_res\": {\"low_res\": True},\n",
" \"speckle\": {\"speckle\": 3.0}, \"camo_occl\": {\"camo\": 0.6, \"occl\": 0.3}}\n",
"\n",
"\n",
"def make_dataset(n=400, seed=7, scen=(\"clean\",), classes=CLASSES):\n",
" rng = np.random.default_rng(seed)\n",
" O, S, M = np.zeros((n, N, N)), np.zeros((n, N, N)), []\n",
" for i in range(n):\n",
" cls, sc = classes[i % len(classes)], scen[(i // len(classes)) % len(scen)]\n",
" O[i], S[i], m = make_chip(cls, rng, SCEN[sc])\n",
" m.update({\"id\": f\"IMG{i:04d}\", \"scen\": sc})\n",
" M.append(m)\n",
" return O, S, M\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ์ ์ฒ๋ฆฌ(ํด์๋ยท๊ธฐํ ๋ณด์ , ๊ตฌ๋ฆ ์ฐจํ ์ฒ๋ฆฌ, ๋ฐฉ์ ์ ๊ทํ) ๊ณตํต ํจ์ ----\n",
"def _fit(a, fill):\n",
" \"\"\"์ค์ ๊ธฐ์ค์ผ๋ก 32x32์ ๋ง๊ฒ ์๋ฅด๊ฑฐ๋ ์ฑ์ด๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 9] AAR ๊ธฐ๋ฐ ๋ชจ์ ์์ฅ ํ์ ์คํ๋
์ฌ๋ก ๋ถ์ ๋ฐ ๊ฐ์ฒด๋ถ๋ฅ ๋ชจ๋ธ ํ๋ผ๋ฏธํฐ ์ฌ๋ณด์ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def cloud_mask(opt, sm, min_area=15):\n",
" \"\"\"๊ดํ์์ ๋ฐ๊ณ (185 ์ด๊ณผ) ๋งค๋ํ๋ฐ SAR์๋ ๋ฐ์ฌ๊ฐ ์๋ ์์ญ์ ๊ตฌ๋ฆ์ผ๋ก ๋ณธ๋ค(SAR๋ ๊ตฌ๋ฆ์ ํต๊ณผํ๋ฏ๋ก ๋ฐ์ ์ฐจ๋๊ณผ ๊ตฌ๋ถ๋๋ค).\"\"\"\n",
" bright = opt > 185\n",
" if bright.sum() < min_area:\n",
" return np.zeros_like(bright)\n",
" local_sd = np.sqrt(np.maximum(ndimage.uniform_filter(opt ** 2, 3) - ndimage.uniform_filter(opt, 3) ** 2, 0))\n",
" cand = bright & (local_sd < 12) & (sm < 0.3)\n",
" lab, n = ndimage.label(cand)\n",
" if n == 0:\n",
" return cand\n",
" sizes = ndimage.sum(cand.astype(float), lab, range(1, n + 1))\n",
" return ndimage.binary_dilation(np.isin(lab, 1 + np.where(sizes >= min_area)[0]), iterations=2) & (opt > 140)\n",
"\n",
"\n",
"def orientation(opt, sm):\n",
" \"\"\"SAR ๋ฐ์ฌ(์์ฅยท๊ตฌ๋ฆ ์ํฅ ์ ์, sm์ ํํํํ SAR)์ ๊ดํ ๋๋น๋ฅผ ํฉ์น ๊ฐ์ฒด ๋ง์คํฌ์ ์ฃผ์ถ ๋ฐฉํฅ(๋).\"\"\"\n",
" m = (sm > 0.3) | (np.abs(opt - opt.mean()) > 30)\n",
" lab, n = ndimage.label(m)\n",
" if n == 0:\n",
" return 0.0, m\n",
" c = lab[N // 2 - 6:N // 2 + 6, N // 2 - 6:N // 2 + 6]\n",
" k = np.bincount(c[c > 0]).argmax() if (c > 0).any() else 1 + int(np.argmax(ndimage.sum(m.astype(float), lab, range(1, n + 1))))\n",
" m = lab == k\n",
" yy, xx = np.nonzero(m)\n",
" if len(yy) < 5:\n",
" return 0.0, m\n",
" dy, dx = yy - yy.mean(), xx - xx.mean() # 2์ฐจ ๋ชจ๋ฉํธ๋ก ์ฃผ์ถ ๊ฐ๋ ๊ณ์ฐ\n",
" return float(np.degrees(0.5 * np.arctan2(2 * (dx * dy).mean(), (dx * dx).mean() - (dy * dy).mean()))), m\n",
"\n",
"\n",
"_YY, _XX = np.mgrid[0:N, 0:N] - (N - 1) / 2\n",
"\n",
"\n",
"def warp(stack, zy, zx, ang):\n",
" \"\"\"์ฌ๋ฌ ์ฅ(B,์ฑ๋,32,32)์ ์ฅ๋ณ ํ๋ยท์ถ์(zy, zx)์ ํ์ (ang, ๋)์ ํ ๋ฒ์ ์์ ํ ๋ณด๊ฐ์ผ๋ก ์ ์ฉํ๋ค(๋ฒกํฐ ์ฐ์ฐ).\"\"\"\n",
" a = np.radians(ang)[:, None, None]\n",
" zy, zx = np.asarray(zy)[:, None, None], np.asarray(zx)[:, None, None]\n",
" sy = np.clip((np.cos(a) * _YY + np.sin(a) * _XX) / zy + (N - 1) / 2, 0, N - 1.001) # ์ถ๋ ฅ ์ขํ -> ์๋ณธ ์ขํ\n",
" sx = np.clip((-np.sin(a) * _YY + np.cos(a) * _XX) / zx + (N - 1) / 2, 0, N - 1.001)\n",
" y0, x0 = sy.astype(int), sx.astype(int)\n",
" fy, fx = (sy - y0).reshape(len(stack), 1, -1), (sx - x0).reshape(len(stack), 1, -1)\n",
" flat = stack.reshape(len(stack), stack.shape[1], -1)\n",
" lin = (y0 * N + x0).reshape(len(stack), 1, -1)\n",
" g = lambda d: np.take_along_axis(flat, np.broadcast_to(lin + d, (len(stack), stack.shape[1], lin.shape[2])), axis=2)\n",
" out = g(0) * (1 - fy) * (1 - fx) + g(N) * fy * (1 - fx) + g(1) * (1 - fy) * fx + g(N + 1) * fy * fx\n",
" return out.reshape(stack.shape)\n",
"\n",
"\n",
"def preprocess_batch(O, S, M):\n",
" \"\"\"์ฌ๋ฌ ์ฅ์ ํ ๋ฒ์ ์ ์ฒ๋ฆฌํ๋ค. 0) ๊ฒฐ์ธก ๋ณด์ 1) ํด์๋ยท๋น์ค๋ฌ ์ดฌ์ ๋ณด์ (ํ์ค 0.5m/ํฝ์
)\n",
" 2) ๊ตฌ๋ฆ ์ฐจํ ์์ญ์ ๋ฐฐ๊ฒฝ๊ฐ์ผ๋ก ์ฑ์ 3) ๊ฐ์ฒด ์ฃผ์ถ์ ๊ฐ๋ก๋ก ํ์ 4) 0~1 ์ ๊ทํ.\"\"\"\n",
" src = np.stack([np.asarray(O, float), np.asarray(S, float)], axis=1)\n",
" if np.isnan(src).any(): # 0) ์ผ์ ๊ฒฐ์ธก ํฝ์
๋ณด์ \n",
" mean = np.nanmean(src, axis=(2, 3), keepdims=True)\n",
" src = np.where(np.isnan(src), mean, src)\n",
" zy = np.array([m[\"gsd\"] / STD_GSD / np.cos(np.radians(m[\"off_nadir\"])) for m in M])\n",
" zx = np.array([m[\"gsd\"] / STD_GSD for m in M])\n",
" out = warp(src, zy, zx, np.zeros(len(M)))\n",
" sm = ndimage.uniform_filter(out[:, 1], (1, 3, 3))\n",
" angs, cloud = np.zeros(len(M)), np.zeros(len(M))\n",
" for i in range(len(M)):\n",
" cm = cloud_mask(out[i, 0], sm[i])\n",
" if cm.any():\n",
" fill = out[i, 0][~cm].mean() if (~cm).any() else 115.0\n",
" out[i, 0][cm] = fill\n",
" back = warp(cm[None, None].astype(float), [1 / zy[i]], [1 / zx[i]], np.zeros(1))[0, 0] > 0.5\n",
" src[i, 0][back] = fill # ์๋ณธ์ ๊ฐ์ ๊ตฌ๋ฆ ์์ญ๋ ์ฑ์\n",
" cloud[i] = cm.mean()\n",
" angs[i] = orientation(out[i, 0], sm[i])[0]\n",
" rot = np.abs(angs) > 3 # 3๋ ์ดํ๋ ํ์ ์๋ต\n",
" if rot.any():\n",
" out[rot] = warp(src[rot], zy[rot], zx[rot], angs[rot])\n",
" return [(np.clip(o / 255, 0, 1), np.clip(s / 1.5, 0, 1), float(c)) for (o, s), c in zip(out, cloud)]\n",
"\n",
"\n",
"def preprocess(opt, sar, meta):\n",
" \"\"\"ํ ์ฅ ์ ์ฒ๋ฆฌ(preprocess_batch์ ๊ฐ์ ์ฒ๋ฆฌ).\"\"\"\n",
" return preprocess_batch([opt], [sar], [meta])[0]\n",
"\n",
"# ---- ํน์ง ์ถ์ถ ๊ณตํต ํจ์ ----\n",
"KERNELS = [np.array(k, float) for k in (\n",
" [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], [[-1, -2, -1], [0, 0, 0], [1, 2, 1]], [[0, 1, 0], [1, -4, 1], [0, 1, 0]],\n",
" [[2, -1, -1], [-1, 2, -1], [-1, -1, 2]], [[-1, -1, 2], [-1, 2, -1], [2, -1, -1]], [[1, 1, 1], [1, 1, 1], [1, 1, 1]])]\n",
"\n",
"\n",
"def conv_features(o):\n",
" \"\"\"๊ฐ์ด CNN ํน์ง: ๊ณ ์ ํฉ์ฑ๊ณฑ ํํฐ 6์ข
-> ReLU -> 4x4 ์ต๋ ํ๋ง(8x8 ์ถ๋ ฅ).\"\"\"\n",
" feats = []\n",
" for k in KERNELS:\n",
" r = np.maximum(ndimage.correlate(o - o.mean(), k / np.abs(k).sum()), 0)\n",
" feats.append(r.reshape(8, 4, 8, 4).max(axis=(1, 3)).ravel())\n",
" feats.append(np.maximum(-ndimage.correlate(o - o.mean(), k / np.abs(k).sum()), 0).reshape(8, 4, 8, 4).max(axis=(1, 3)).ravel())\n",
" return np.concatenate(feats)\n",
"\n",
"\n",
"def hog(o, cell=8, bins=8):\n",
" gy, gx = np.gradient(o)\n",
" mag, ang = np.hypot(gx, gy), np.mod(np.arctan2(gy, gx), np.pi)\n",
" b = np.minimum((ang / np.pi * bins).astype(int), bins - 1)\n",
" out = np.zeros((N // cell, N // cell, bins))\n",
" for i in range(N // cell):\n",
" for j in range(N // cell):\n",
" sl = (slice(i * cell, (i + 1) * cell), slice(j * cell, (j + 1) * cell))\n",
" out[i, j] = np.bincount(b[sl].ravel(), mag[sl].ravel(), bins)\n",
" return (out / (np.linalg.norm(out) + 1e-6)).ravel()\n",
"\n",
"\n",
"def shape_features(o, s):\n",
" \"\"\"๊ฐ์ฒด ๋ชจ์ ํน์ง: ๊ฐ๋ก ๋ฐฉํฅ ํฌ์์ผ๋ก ์ ์ฒด ๊ธธ์ดยท์ฐจ์ฒด ๊ธธ์ดยท๊ฐ๋ ๋์ถ๋ถ(ํฌ์ ) ๊ธธ์ดยท๋๊บผ์ด ๋ถ๋ถ(ํฌํ) ์์น๋ฅผ ์ฐ๋ค.\n",
" ๋ฐฉ์ ์ ๊ทํ ๋ค์๋ ์๋ค(180๋)๊ฐ ๋ค์งํ ์ ์์ผ๋ฏ๋ก, ๊ฐ๋ ๋์ถ๋ถ๊ฐ ์ค๋ฅธ์ชฝ์ ์ค๋๋ก ๋ง์ถ๋ค.\"\"\"\n",
" sm = ndimage.uniform_filter(s, 3)\n",
" dark = np.abs(o - np.median(o))\n",
" m = (sm > 0.22) | (dark > 0.12)\n",
" lab, n = ndimage.label(m)\n",
" if n:\n",
" c = lab[10:22, 8:24]\n",
" k = np.bincount(c[c > 0]).argmax() if (c > 0).any() else 1\n",
" m = lab == k\n",
" p = m.sum(axis=0).astype(float)\n",
" cols = np.nonzero(p)[0]\n",
" if len(cols) == 0:\n",
" return np.zeros(8), o, s\n",
" thin = (p > 0) & (p <= 2)\n",
" left, right = thin[:N // 2].sum(), thin[N // 2:].sum()\n",
" if left > right:\n",
" o, s, m, p = o[:, ::-1], s[:, ::-1], m[:, ::-1], p[::-1]\n",
" cols = np.nonzero(p)[0]\n",
" body = np.nonzero(p > 3)[0]\n",
" d = np.where(m, dark if left <= right else dark[:, ::-1], 0)\n",
" heavy = d.sum(axis=0)\n",
" centre = (body[0] + body[-1]) / 2 if len(body) else N / 2\n",
" turret = (heavy * np.arange(N)).sum() / max(heavy.sum(), 1e-6) - centre\n",
" return np.array([cols[-1] - cols[0] + 1, len(body), ((p > 0) & (p <= 2)).sum(), p.max(), turret,\n",
" m.sum(), (cols[-1] - (body[-1] if len(body) else cols[-1])), m.sum() / max(len(cols) * p.max(), 1)]), o, s\n",
"\n",
"\n",
"def adv_features(o, s):\n",
" \"\"\"์ ๋ฐ ํน์ง: ๋ชจ์(๊ธธ์ดยทํฌ์ ยทํฌํ ์์น) + HOG(์ค๊ณฝ) + SAR ๋ฐ์ฌ ํต๊ณ(์์ฅ๋งยท๊ตฌ๋ฆ ์ํฅ ์ ์) + ํฌ์ ์ค๊ณฝ.\"\"\"\n",
" shp, o, s = shape_features(o, s)\n",
" c = s[8:24, 4:28]\n",
" sm = ndimage.uniform_filter(s, 3)\n",
" m = sm > 0.2\n",
" dark = np.abs(o - np.median(o))\n",
" return np.concatenate([shp, hog(o), [c.mean(), c.max(), (sm > 0.25).mean(), (sm > 0.4).mean(), m.sum(axis=1).max(), m.sum(axis=0).max()],\n",
" sm.mean(axis=0).reshape(8, 4).mean(1), sm.mean(axis=1).reshape(8, 4).mean(1),\n",
" dark.mean(axis=0).reshape(8, 4).mean(1), dark.mean(axis=1).reshape(8, 4).mean(1),\n",
" [o.mean(), o.std(), dark.max(), (o > 0.65).mean(), (o < 0.3).mean()]])\n",
"\n",
"EX_SCEN = (\"clean\", \"camo\", \"occl30\", \"low_sun\", \"cloud\")\n",
"\n",
"\n",
"def featurize(O, S, M):\n",
" P = preprocess_batch(O, S, M)\n",
" return np.array([adv_features(o, s) for o, s, _ in P]), np.array([p[1][8:24, 4:28].mean() for p in P])\n",
"\n",
"\n",
"def train(with_decoy, seed=3):\n",
" cls = CLASSES + ([\"decoy\"] if with_decoy else [])\n",
" O, S, M = make_dataset(250 * len(cls), seed=seed, scen=tuple(SCEN), classes=cls)\n",
" X, _ = featurize(O, S, M)\n",
" return RandomForestClassifier(n_estimators=150, min_samples_leaf=2, random_state=0, n_jobs=1).fit(X, [m[\"cls\"] for m in M])\n",
"\n",
"\n",
"def decide(model, X, sar, tau):\n",
" \"\"\"๋ถ๋ฅ ๊ฒฐ๊ณผ + SAR-๊ดํ ์ผ์น์ฑ ๊ท์น(์ ํ): ์ ์ฐจยท์์ฃผํฌ๋ก ๋ถ๋ฅํ๋๋ฐ SAR ๊ธ์ ๋ฐ์ฌ๊ฐ tau ๋ฏธ๋ง์ด๋ฉด '๋ชจ์ ํ์ ์์ฌ'.\"\"\"\n",
" p = model.predict(X).astype(object)\n",
" if tau is not None:\n",
" p[np.isin(p, [\"tank\", \"spg\"]) & (sar < tau)] = \"decoy\"\n",
" return p\n",
"\n",
"\n",
"def aar_log(y, p, sc, ids):\n",
" rows = []\n",
" for yi, pi, s, i in zip(y, p, sc, ids):\n",
" if yi == pi:\n",
" continue\n",
" if yi == \"decoy\":\n",
" cause = f\"๋ชจ์ ํ์ ์ {pi}(์ผ)๋ก ์คํ๋
\"\n",
" elif pi == \"decoy\":\n",
" cause = f\"์ค์ {yi}๋ฅผ ๋ชจ์ ํ์ ์ผ๋ก ์คํ\"\n",
" else:\n",
" cause = f\"{yi}->{pi} ๊ธฐ์ข
ํผ๋({s})\"\n",
" rows.append({\"์์ธ\": cause, \"์์\": i})\n",
" return pd.DataFrame(rows, columns=[\"์์ธ\", \"์์\"])\n",
"\n",
"\n",
"# ๋ชจ์ ํ๋
ํ๋ จ ์๋ฃ(๋ชจ์ ์์ฅ ํ์ 20% ํฌํจ): AAR ๋์ 500์ฅ + ํ๊ท ์ํ 500์ฅ\n",
"Oa, Sa, Ma = make_dataset(500, seed=21, scen=EX_SCEN, classes=CLASSES + [\"decoy\"])\n",
"Ob, Sb, Mb = make_dataset(500, seed=22, scen=EX_SCEN, classes=CLASSES + [\"decoy\"])\n",
"Xa, sar_a = featurize(Oa, Sa, Ma); ya, sca, ida = np.array([m[\"cls\"] for m in Ma]), [m[\"scen\"] for m in Ma], [m[\"id\"] for m in Ma]\n",
"Xb, sar_b = featurize(Ob, Sb, Mb); yb = np.array([m[\"cls\"] for m in Mb])\n",
"\n",
"# 1) ์ด๊ธฐ ์ค์ : ๋ชจ์ ํ์ ์ ํ์ตํ์ง ์์ ๋ชจ๋ธ, ์ผ์น์ฑ ๊ท์น ์์\n",
"m0 = train(False)\n",
"p0 = decide(m0, Xa, sar_a, None)\n",
"acc0 = (p0 == ya).mean()\n",
"log0 = aar_log(ya, p0, sca, ida)\n",
"print(f\"[1. ๋ฌด์์ด ์ผ์ด๋ฌ๋] ์ด๊ธฐ ์ข
ํฉ ์ ํ๋ {acc0:.3f}, ์คํ๋
{len(log0)}๊ฑด\")\n",
"print(log0[\"์์ธ\"].value_counts().head(6).to_string())\n",
"print(f\"\\n[2. ์ ์ผ์ด๋ฌ๋] ๋ชจ์ ํ์ SAR ๋ฐ์ฌ ํ๊ท {sar_a[ya == 'decoy'].mean():.3f} vs ์ค์ ์ ์ฐจยท์์ฃผํฌ {sar_a[np.isin(ya, ['tank', 'spg'])].mean():.3f}\"\n",
" \" -> ๊ดํ ์ค๊ณฝ์ ์ ์ฐจ์ ๊ฐ์ง๋ง ๊ธ์ ๋ฐ์ฌ๊ฐ ์ฝํด, ๋ชจ์ ํ์ ์ ๋ชจ๋ฅด๋ ๋ชจ๋ธ์ ๋ฐ์ฌ๊ฐ ์ฝํ ๋ฏผ๊ฐ ์ฐจ๋์ผ๋ก ๋ชฐ์ ๋ฒ๋ฆฐ๋ค.\")\n",
"\n",
"# 2) ์ฌ๋ณด์ : (๋ชจ์ ํ์ ํ์ต ์ฌ๋ถ) x (SAR ์ผ์น์ฑ ์๊ณ๊ฐ) ๊ฒฉ์ ํ์\n",
"m1 = train(True)\n",
"grid = []\n",
"for name, mdl in [(\"๋ชจ์ํ์ ๋ฏธํ์ต\", m0), (\"๋ชจ์ํ์ ํ์ต\", m1)]:\n",
" for tau in [None, 0.12, 0.15, 0.18, 0.22]:\n",
" p = decide(mdl, Xa, sar_a, tau)\n",
" mil = np.isin(ya, list(MILITARY))\n",
" grid.append({\"๋ชจ๋ธ\": name, \"SAR ์๊ณ๊ฐ\": \"-\" if tau is None else tau, \"์ข
ํฉ์ ํ๋\": round(float((p == ya).mean()), 3),\n",
" \"๋ชจ์ํ์ ์ ๋ฐ๋ฅ \": round(float((p[ya == \"decoy\"] == \"decoy\").mean()), 3),\n",
" \"์ค์ ์ฅ๋น๋ฅผ ๋ชจ์๋ก ์คํ\": int((mil & (p == \"decoy\")).sum())})\n",
"g = pd.DataFrame(grid).sort_values([\"์ข
ํฉ์ ํ๋\", \"์ค์ ์ฅ๋น๋ฅผ ๋ชจ์๋ก ์คํ\"], ascending=[False, True])\n",
"print(\"\\n[3. ๋ฌด์์ ๋ฐ๊พธ๋] ๊ฒฉ์ ํ์ ์์ 5๊ฐ\")\n",
"print(g.head().to_string(index=False))\n",
"best = g.iloc[0]\n",
"mdl = m1 if best[\"๋ชจ๋ธ\"] == \"๋ชจ์ํ์ ํ์ต\" else m0\n",
"tau = None if best[\"SAR ์๊ณ๊ฐ\"] == \"-\" else float(best[\"SAR ์๊ณ๊ฐ\"])\n",
"p1 = decide(mdl, Xa, sar_a, tau)\n",
"acc1 = (p1 == ya).mean()\n",
"print(f\"\\n์ฌ๋ณด์ ๊ฐ: {best['๋ชจ๋ธ']}, SAR ์ผ์น์ฑ ์๊ณ๊ฐ {best['SAR ์๊ณ๊ฐ']}\")\n",
"print(f\"์ข
ํฉ ์ ํ๋ {acc0:.3f} -> {acc1:.3f} ({(acc1 - acc0) * 100:+.1f}%p, ๋ชฉํ +10%p: {'๋ฌ์ฑ' if acc1 - acc0 >= 0.1 else '๋ฏธ๋ฌ'})\")\n",
"b0, b1 = (decide(m0, Xb, sar_b, None) == yb).mean(), (decide(mdl, Xb, sar_b, tau) == yb).mean()\n",
"print(f\"ํ๊ท ์ํ(๋ณ๋ 500์ฅ): {b0:.3f} -> {b1:.3f}\")\n",
"log1 = aar_log(ya, p1, sca, ida)\n",
"rep = set(log0[\"์์\"]) & set(log1[\"์์\"])\n",
"print(f\"์ฌ๋ณด์ ํ ๋จ์ ์คํ๋
{len(log1)}๊ฑด, ๊ทธ์ค ์ฌ๋ฐ {len(rep)}๊ฑด:\", log1[log1[\"์์\"].isin(rep)][\"์์ธ\"].value_counts().head(3).to_dict())\n",
"print(\"SAR ์ผ์น์ฑ ๊ท์น์ ๋ชจ์ ํ์ ํ์ต ํ ์ถ๊ฐ ์ด๋์ด ์๊ณ ์๊ณ๊ฐ์ ๋์ด๋ฉด ์ค์ ์ฅ๋น๋ฅผ ๋ชจ์ ํ์ ์ผ๋ก ์คํํ๋ฏ๋ก ์ ์ฉํ์ง ์๋๋ค(-).\")\n",
"print(\"[4. ๋จ์ ๊ณผ์ ] ์ฌ๋ฐ ๊ฑด์ ์์ฅ๋ง ์๋ ๊ธฐ์ข
ํผ๋์ผ๋ก, ๋ค๋ฅธ ์ดฌ์ ํ์ฐจ ๊ต์ฐจ ํ๋
(๋ฏธ์
7)๊ณผ ์ง์ ํ์ต ๋์ ๋ฑ๋ก์ผ๋ก ๋์ํ๋ค.\")\n",
"\n",
"tuned = f\"model:\\n train_with_decoy: {best['๋ชจ๋ธ'] == '๋ชจ์ํ์ ํ์ต'}\\n rf_trees: 150\\nrules:\\n sar_consistency_tau: {best['SAR ์๊ณ๊ฐ']}\\nconfidence:\\n high: 0.9\\n medium: 0.6\"\n",
"print(\"\\n[์ฌ๋ณด์ ํ๋ผ๋ฏธํฐ(isr01_tuned_params.yaml ๋ด์ฉ)]\\n\" + tuned)\n",
"\n",
"plt.figure(figsize=(7, 4))\n",
"plt.hist([sar_a[np.isin(ya, [\"tank\", \"spg\"])], sar_a[ya == \"decoy\"]], bins=25, label=[\"tank/spg\", \"decoy\"], alpha=.8)\n",
"if tau:\n",
" plt.axvline(tau, ls=\"--\", c=\"k\", label=f\"tau={tau}\")\n",
"plt.xlabel(\"mean SAR return (center)\"); plt.ylabel(\"chips\"); plt.legend(); plt.title(\"SAR-optical consistency for decoy screening\")\n",
"plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
09 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[1. ๋ฌด์์ด ์ผ์ด๋ฌ๋] ์ด๊ธฐ ์ข
ํฉ ์ ํ๋ 0.786, ์คํ๋
107๊ฑด\n",
"์์ธ\n",
"๋ชจ์ ํ์ ์ civil(์ผ)๋ก ์คํ๋
100\n",
"tank->truck ๊ธฐ์ข
ํผ๋(camo) 4\n",
"truck->tank ๊ธฐ์ข
ํผ๋(cloud) 1\n",
"tank->truck ๊ธฐ์ข
ํผ๋(clean) 1\n",
"tank->truck ๊ธฐ์ข
ํผ๋(cloud) 1\n",
"\n",
"[2. ์ ์ผ์ด๋ฌ๋] ๋ชจ์ ํ์ SAR ๋ฐ์ฌ ํ๊ท 0.089 vs ์ค์ ์ ์ฐจยท์์ฃผํฌ 0.226 -> ๊ดํ ์ค๊ณฝ์ ์ ์ฐจ์ ๊ฐ์ง๋ง ๊ธ์ ๋ฐ์ฌ๊ฐ ์ฝํด, ๋ชจ์ ํ์ ์ ๋ชจ๋ฅด๋ ๋ชจ๋ธ์ ๋ฐ์ฌ๊ฐ ์ฝํ ๋ฏผ๊ฐ ์ฐจ๋์ผ๋ก ๋ชฐ์ ๋ฒ๋ฆฐ๋ค.\n",
"\n",
"[3. ๋ฌด์์ ๋ฐ๊พธ๋] ๊ฒฉ์ ํ์ ์์ 5๊ฐ\n",
" ๋ชจ๋ธ SAR ์๊ณ๊ฐ ์ข
ํฉ์ ํ๋ ๋ชจ์ํ์ ์ ๋ฐ๋ฅ ์ค์ ์ฅ๋น๋ฅผ ๋ชจ์๋ก ์คํ\n",
"๋ชจ์ํ์ ํ์ต - 0.990 1.0 0\n",
"๋ชจ์ํ์ ํ์ต 0.12 0.990 1.0 0\n",
"๋ชจ์ํ์ ํ์ต 0.15 0.990 1.0 0\n",
"๋ชจ์ํ์ ํ์ต 0.18 0.960 1.0 16\n",
"๋ชจ์ํ์ ํ์ต 0.22 0.788 1.0 102\n",
"\n",
"์ฌ๋ณด์ ๊ฐ: ๋ชจ์ํ์ ํ์ต, SAR ์ผ์น์ฑ ์๊ณ๊ฐ -\n",
"์ข
ํฉ ์ ํ๋ 0.786 -> 0.990 (+20.4%p, ๋ชฉํ +10%p: ๋ฌ์ฑ)\n",
"ํ๊ท ์ํ(๋ณ๋ 500์ฅ): 0.790 -> 0.984\n",
"์ฌ๋ณด์ ํ ๋จ์ ์คํ๋
5๊ฑด, ๊ทธ์ค ์ฌ๋ฐ 5๊ฑด: {'tank->truck ๊ธฐ์ข
ํผ๋(camo)': 4, 'truck->tank ๊ธฐ์ข
ํผ๋(cloud)': 1}\n",
"SAR ์ผ์น์ฑ ๊ท์น์ ๋ชจ์ ํ์ ํ์ต ํ ์ถ๊ฐ ์ด๋์ด ์๊ณ ์๊ณ๊ฐ์ ๋์ด๋ฉด ์ค์ ์ฅ๋น๋ฅผ ๋ชจ์ ํ์ ์ผ๋ก ์คํํ๋ฏ๋ก ์ ์ฉํ์ง ์๋๋ค(-).\n",
"[4. ๋จ์ ๊ณผ์ ] ์ฌ๋ฐ ๊ฑด์ ์์ฅ๋ง ์๋ ๊ธฐ์ข
ํผ๋์ผ๋ก, ๋ค๋ฅธ ์ดฌ์ ํ์ฐจ ๊ต์ฐจ ํ๋
(๋ฏธ์
7)๊ณผ ์ง์ ํ์ต ๋์ ๋ฑ๋ก์ผ๋ก ๋์ํ๋ค.\n",
"\n",
"[์ฌ๋ณด์ ํ๋ผ๋ฏธํฐ(isr01_tuned_params.yaml ๋ด์ฉ)]\n",
"model:\n",
" train_with_decoy: True\n",
" rf_trees: 150\n",
"rules:\n",
" sar_consistency_tau: -\n",
"confidence:\n",
" high: 0.9\n",
" medium: 0.6\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
10. ๊ณต๊ฐ ์์ฑ์์ ์ฅ๋น ๊ฐ์ฒด๋ถ๋ฅ ์์คํ
ํตํฉ ๋ฐ ์ ๋ต ISR ํ๊ฐ ๋ธ๋ฆฌํ ํจํค์ง ์์ฑ\n",
"**์์์๊ฐ**: ์ฝ 3.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๊ณต๊ฐ ์์ฑ์์ ์ฅ๋น ๊ฐ์ฒด๋ถ๋ฅ ์์คํ
์ ์ต์ข
ํตํฉ ๊ฒ์ฆํ๊ณ ์ ๋ต ์ ๋ณด ํ๋จ์ฉ ๋ธ๋ฆฌํ ํจํค์ง๋ฅผ ์์ฑํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๋ฉํ๋ฐ์ดํฐ ๊ฒ์ฆ๋ถํฐ ์ ์ฒ๋ฆฌ, ์ ๋ฐ ๋ถ๋ฅ, ์ ๋ขฐ๋ ์์ง, ํ๋
๋๊ธฐ์ด, ๋ถ์ ํ๋ฉด๊น์ง ์์คํ
์ ํตํฉ ๊ฒ์ฆํ๊ณ ์ ๋ต ISR ํ๊ฐ ๋ธ๋ฆฌํ ํจํค์ง๋ฅผ ์์ฑํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ฌ๋ณด์ ์ค์ ์ผ๋ก ์ ์ฒด ๊ณผ์ ์ ํ ๋ฒ์ ์คํํ๋ ํตํฉ ์ฝ๋๋ฅผ ์์ฑํ๊ณ ์์ ํ์ผ๊ณผ ๋ฉํ๋ฐ์ดํฐ ์ค๋ฅ๊ฐ ์๋ ์ฐจ๋จ๋๋์ง ํ์ธํ๊ณ ์ ํจ์ฑ์ ์ ๊ฒํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/main_isr01.py, README.md, docs/isr01_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": [
"\"\"\"๋ฉํ๋ฐ์ดํฐ ๊ฒ์ฆยท์ ์ฒ๋ฆฌยท์ ๋ฐ ๋ถ๋ฅ(๋ชจ์ ํ์ ํฌํจ)ยท์ ๋ขฐ๋ ์์งยท์์๋ถ์๊ด ํ๋
๋๊ธฐ์ดยท๋ถ์ ํ๋ฉด๊น์ง ๊ณต๊ฐ ์์ฑ์์ ์ฅ๋น ๊ฐ์ฒด๋ถ๋ฅ ์์คํ
์ ์ฒด๋ฅผ ํ ํ์ผ๋ก ํตํฉ ์คํํ๊ณ , ์์ฒด ์ ๊ฒ(๊ธฐ๋ฅ ์ปค๋ฒ๋ฆฌ์ง ํฌํจ)๊ณผ ์ ๋ต ISR ํ๊ฐ ๋ธ๋ฆฌํ ์์ฝ์ ์ถ๋ ฅํ๋ค.\"\"\"\n",
"import hashlib\n",
"import json\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.ensemble import RandomForestClassifier\n",
"from sklearn.linear_model import LogisticRegression\n",
"from sklearn.pipeline import make_pipeline\n",
"from sklearn.preprocessing import StandardScaler\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํฉ์ฑ ์์ฑ์์ ์กฐ๊ฐ(chip) ์์ฑ๊ธฐ (์ค์ ์์ฑ์์ยท์ฅ๋น ์ ์ยท์์น ์์)\n",
"# 32x32 ๊ดํ(EO) ์์๊ณผ SAR ์์ ํ ์์ ์ถ์ ๋ํ์ผ๋ก ๋ง๋ ๊ฐ์ฒด๋ฅผ ๊ทธ๋ฆฐ๋ค. ๋ํ ์น์๋ ๊ฐ์๊ฐ์ด๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"N = 32\n",
"STD_GSD = 0.5 # ํ์ค ํด์๋(m/ํฝ์
, ๊ฐ์ ๊ธฐ์ค)\n",
"CLASSES = [\"tank\", \"spg\", \"truck\", \"civil\"]\n",
"MILITARY = {\"tank\", \"spg\", \"truck\"}\n",
"# ๊ฐ์ฒด ์ขํ๊ณ(u: ๊ธธ์ด ๋ฐฉํฅ, v: ํญ ๋ฐฉํฅ, ํ์ค ํด์๋ ํฝ์
) ๊ฐ์ ๋ํ: (u์ค์ฌ, v์ค์ฌ, ๊ธธ์ด, ํญ, ๊ดํ ๋ฐ๊ธฐ ์ฐจ์ด)\n",
"SHAPES = {\n",
" \"tank\": [(0, 0, 11, 7, -40), (0.5, 0, 5, 5, -55), (6, 0, 7, 1.2, -60)],\n",
" \"spg\": [(0, 0, 16, 7.5, -38), (-4, 0, 6, 6.5, -52), (5, 0, 13, 1.4, -60)],\n",
" \"truck\": [(5, 0, 3.5, 5.5, -45), (-1.8, 0, 8.5, 6, -25)],\n",
" \"civil\": [(0, 0, 8, 3.8, 0), (1.2, 0, 2, 3.4, 0)],\n",
" \"decoy\": [(0, 0, 11, 7, -40), (0.5, 0, 5, 5, -42), (6, 0, 7, 1.2, -45)],\n",
" \"none\": [], # ๊ฐ์ฒด ์๋ ๋ฐฐ๊ฒฝ ํ์ผ\n",
"}\n",
"SAR_RETURN = {\"tank\": 0.85, \"spg\": 0.85, \"truck\": 0.7, \"civil\": 0.45, \"decoy\": 0.18, \"none\": 0.12} # ๊ธ์ ๋ฐ์ฌ ์ธ๊ธฐ(๊ฐ์)\n",
"\n",
"\n",
"def _mask(cls, angle, scale, squash=1.0):\n",
" \"\"\"ํ์ (angle, ๋)ยท์ถ์ฒ(scale)ยท๋น์ค๋ฌ ์ดฌ์ ์์ถ(squash)์ ์ ์ฉํด ๋ํ๋ณ ๋ง์คํฌ์ ๋ฐ๊ธฐ ์ฐจ์ด๋ฅผ ๋ง๋ ๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 10] ๊ณต๊ฐ ์์ฑ์์ ์ฅ๋น ๊ฐ์ฒด๋ถ๋ฅ ์์คํ
ํตํฉ ๋ฐ ์ ๋ต ISR ํ๊ฐ ๋ธ๋ฆฌํ ํจํค์ง ์์ฑ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def make_chip(cls, rng, cond=None):\n",
" \"\"\"chip 1๊ฐ = (๊ดํ 32x32, SAR 32x32, ๋ฉํ๋ฐ์ดํฐ). cond: camo, occl, cloud, low_sun, off_nadir, low_res, speckle\"\"\"\n",
" c = {\"camo\": 0.0, \"occl\": 0.0, \"cloud\": 0.0, \"low_sun\": False, \"off_nadir\": 0.0, \"low_res\": False, \"speckle\": 1.0, **(cond or {})}\n",
" gsd = rng.uniform(0.9, 1.2) if c[\"low_res\"] else rng.uniform(0.4, 0.6)\n",
" scale = STD_GSD / gsd\n",
" angle, off = rng.uniform(0, 360), c[\"off_nadir\"] or rng.uniform(0, 15)\n",
" squash = np.cos(np.radians(off))\n",
" sun_el = rng.uniform(12, 22) if c[\"low_sun\"] else rng.uniform(40, 70)\n",
" sun_az = rng.uniform(0, 360)\n",
" bg = 115 + ndimage.gaussian_filter(rng.normal(0, 30, (N, N)), 2) + rng.normal(0, 5, (N, N))\n",
" parts = _mask(cls, angle, scale, squash)\n",
" body = np.any([m for m, _ in parts], axis=0) if parts else np.zeros((N, N), bool)\n",
" opt = bg.copy()\n",
" # ๊ทธ๋ฆผ์: ํ์ ๋ฐ๋ ๋ฐฉํฅ์ผ๋ก ๋ฐ๋ฆฐ ๊ฐ์ฒด ๋ชจ์(ํ์ ๊ณ ๋๊ฐ ๋ฎ์์๋ก ๊ธธ๋ค)\n",
" L = min(8.0, 2.5 / np.tan(np.radians(sun_el))) * scale\n",
" sh = ndimage.shift(body.astype(float), (L * np.cos(np.radians(sun_az)), -L * np.sin(np.radians(sun_az))), order=0) > 0.5\n",
" opt[sh & ~body] -= 35 if cls != \"decoy\" else 25\n",
" if cls == \"civil\":\n",
" base = rng.choice([200.0, 60.0]) # ๋ฏผ๊ฐ ์ฐจ๋ ์์: ๋ฐ๊ฑฐ๋ ์ด๋์\n",
" opt[body] = base + rng.normal(0, 8, body.sum())\n",
" roof = parts[1][0]\n",
" opt[roof] = base * 0.8\n",
" else:\n",
" ref = bg[body].mean() if body.any() else bg.mean()\n",
" for m, d in parts:\n",
" opt[m] = ref + d + rng.normal(0, 4 if cls != \"decoy\" else 2, m.sum())\n",
" # ์์ฅ๋ง: ๊ฐ์ฒด ์ฃผ๋ณ์ ๋ฐฐ๊ฒฝ ์ง๊ฐ์ผ๋ก ๋ฎ์ด ๋๋น๋ฅผ ๋ฎ์ถ๋ค\n",
" if c[\"camo\"] > 0 and cls != \"civil\":\n",
" net = ndimage.binary_dilation(body, iterations=2)\n",
" tex = 105 + ndimage.gaussian_filter(rng.normal(0, 25, (N, N)), 1)\n",
" opt[net] = (1 - c[\"camo\"]) * opt[net] + c[\"camo\"] * tex[net]\n",
" # ์๋ชฉ ์ฐจํ: ๊ฐ์ฒด์ ์ผ๋ถ๋ฅผ ์ด๋์ด ๋๋ฌด ๋ฉ์ด๋ฆฌ๋ก ๊ฐ๋ฆฐ๋ค\n",
" if c[\"occl\"] > 0:\n",
" trees = ndimage.gaussian_filter(rng.random((N, N)), 2)\n",
" near = ndimage.binary_dilation(body, iterations=3)\n",
" cover = near & (trees > np.quantile(trees[body], 1 - c[\"occl\"])) if body.any() else np.zeros((N, N), bool)\n",
" opt[cover] = 70 + rng.normal(0, 6, cover.sum())\n",
" # ๊ตฌ๋ฆ: ๋ฐ๊ณ ๋งค๋ํ ๋ฉ์ด๋ฆฌ(๊ดํ๋ง ๊ฐ๋ฆผ, SAR๋ ํต๊ณผ)\n",
" cmask = np.zeros((N, N), bool)\n",
" if c[\"cloud\"] > 0:\n",
" blob = ndimage.gaussian_filter(rng.random((N, N)), 4)\n",
" cmask = blob > np.quantile(blob, 1 - c[\"cloud\"])\n",
" opt[cmask] = 0.25 * opt[cmask] + 0.75 * (235 + rng.normal(0, 3, cmask.sum()))\n",
" # SAR: ๊ณฑ์
ํ ์คํํด ์ก์ + ๊ธ์ ๋ฐ์ฌ(๋ชจ์๋ฆฌ ์ด์ค ๋ฐ์ฌ๋ก ๋ฐ์ ์ )\n",
" sar = np.full((N, N), 0.12)\n",
" sar[body] = SAR_RETURN[cls] * rng.uniform(0.8, 1.2)\n",
" edge = body & ~ndimage.binary_erosion(body)\n",
" sar[edge] *= 1.3 if cls in MILITARY else 1.0\n",
" sar = sar * rng.gamma(4 / c[\"speckle\"], c[\"speckle\"] / 4, (N, N))\n",
" if c[\"low_res\"]: # ์ ํด์๋: ์ ๋ณด ์์ค(๋ธ๋ฌ)\n",
" down = lambda a: np.kron(a.reshape(N // 2, 2, N // 2, 2).mean((1, 3)), np.ones((2, 2)))\n",
" opt, sar = ndimage.gaussian_filter(down(opt), 0.7), ndimage.gaussian_filter(down(sar), 0.7)\n",
" meta = {\"cls\": cls, \"gsd\": round(float(gsd), 2), \"off_nadir\": round(float(off), 1), \"sun_el\": round(float(sun_el), 1),\n",
" \"sun_az\": round(float(sun_az), 1), \"angle\": round(float(angle), 1), \"cloud_frac\": float(cmask.mean()),\n",
" \"cloud_on_obj\": float((cmask & body).sum() / max(body.sum(), 1)), **{k: c[k] for k in (\"camo\", \"occl\", \"low_sun\", \"low_res\")}}\n",
" return np.clip(opt, 0, 255), sar, meta\n",
"\n",
"\n",
"SCEN = { # ์ํยท์ดฌ์ ์กฐ๊ฑด ์๋๋ฆฌ์ค 10์ข
\n",
" \"clean\": {}, \"camo\": {\"camo\": 0.7}, \"occl30\": {\"occl\": 0.3}, \"occl50\": {\"occl\": 0.5}, \"cloud\": {\"cloud\": 0.25},\n",
" \"low_sun\": {\"low_sun\": True}, \"off_nadir\": {\"off_nadir\": 35.0}, \"low_res\": {\"low_res\": True},\n",
" \"speckle\": {\"speckle\": 3.0}, \"camo_occl\": {\"camo\": 0.6, \"occl\": 0.3}}\n",
"\n",
"\n",
"def make_dataset(n=400, seed=7, scen=(\"clean\",), classes=CLASSES):\n",
" rng = np.random.default_rng(seed)\n",
" O, S, M = np.zeros((n, N, N)), np.zeros((n, N, N)), []\n",
" for i in range(n):\n",
" cls, sc = classes[i % len(classes)], scen[(i // len(classes)) % len(scen)]\n",
" O[i], S[i], m = make_chip(cls, rng, SCEN[sc])\n",
" m.update({\"id\": f\"IMG{i:04d}\", \"scen\": sc})\n",
" M.append(m)\n",
" return O, S, M\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ์ ์ฒ๋ฆฌ(ํด์๋ยท๊ธฐํ ๋ณด์ , ๊ตฌ๋ฆ ์ฐจํ ์ฒ๋ฆฌ, ๋ฐฉ์ ์ ๊ทํ) ๊ณตํต ํจ์ ----\n",
"def _fit(a, fill):\n",
" \"\"\"์ค์ ๊ธฐ์ค์ผ๋ก 32x32์ ๋ง๊ฒ ์๋ฅด๊ฑฐ๋ ์ฑ์ด๋ค.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 10] ๊ณต๊ฐ ์์ฑ์์ ์ฅ๋น ๊ฐ์ฒด๋ถ๋ฅ ์์คํ
ํตํฉ ๋ฐ ์ ๋ต ISR ํ๊ฐ ๋ธ๋ฆฌํ ํจํค์ง ์์ฑ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def cloud_mask(opt, sm, min_area=15):\n",
" \"\"\"๊ดํ์์ ๋ฐ๊ณ (185 ์ด๊ณผ) ๋งค๋ํ๋ฐ SAR์๋ ๋ฐ์ฌ๊ฐ ์๋ ์์ญ์ ๊ตฌ๋ฆ์ผ๋ก ๋ณธ๋ค(SAR๋ ๊ตฌ๋ฆ์ ํต๊ณผํ๋ฏ๋ก ๋ฐ์ ์ฐจ๋๊ณผ ๊ตฌ๋ถ๋๋ค).\"\"\"\n",
" bright = opt > 185\n",
" if bright.sum() < min_area:\n",
" return np.zeros_like(bright)\n",
" local_sd = np.sqrt(np.maximum(ndimage.uniform_filter(opt ** 2, 3) - ndimage.uniform_filter(opt, 3) ** 2, 0))\n",
" cand = bright & (local_sd < 12) & (sm < 0.3)\n",
" lab, n = ndimage.label(cand)\n",
" if n == 0:\n",
" return cand\n",
" sizes = ndimage.sum(cand.astype(float), lab, range(1, n + 1))\n",
" return ndimage.binary_dilation(np.isin(lab, 1 + np.where(sizes >= min_area)[0]), iterations=2) & (opt > 140)\n",
"\n",
"\n",
"def orientation(opt, sm):\n",
" \"\"\"SAR ๋ฐ์ฌ(์์ฅยท๊ตฌ๋ฆ ์ํฅ ์ ์, sm์ ํํํํ SAR)์ ๊ดํ ๋๋น๋ฅผ ํฉ์น ๊ฐ์ฒด ๋ง์คํฌ์ ์ฃผ์ถ ๋ฐฉํฅ(๋).\"\"\"\n",
" m = (sm > 0.3) | (np.abs(opt - opt.mean()) > 30)\n",
" lab, n = ndimage.label(m)\n",
" if n == 0:\n",
" return 0.0, m\n",
" c = lab[N // 2 - 6:N // 2 + 6, N // 2 - 6:N // 2 + 6]\n",
" k = np.bincount(c[c > 0]).argmax() if (c > 0).any() else 1 + int(np.argmax(ndimage.sum(m.astype(float), lab, range(1, n + 1))))\n",
" m = lab == k\n",
" yy, xx = np.nonzero(m)\n",
" if len(yy) < 5:\n",
" return 0.0, m\n",
" dy, dx = yy - yy.mean(), xx - xx.mean() # 2์ฐจ ๋ชจ๋ฉํธ๋ก ์ฃผ์ถ ๊ฐ๋ ๊ณ์ฐ\n",
" return float(np.degrees(0.5 * np.arctan2(2 * (dx * dy).mean(), (dx * dx).mean() - (dy * dy).mean()))), m\n",
"\n",
"\n",
"_YY, _XX = np.mgrid[0:N, 0:N] - (N - 1) / 2\n",
"\n",
"\n",
"def warp(stack, zy, zx, ang):\n",
" \"\"\"์ฌ๋ฌ ์ฅ(B,์ฑ๋,32,32)์ ์ฅ๋ณ ํ๋ยท์ถ์(zy, zx)์ ํ์ (ang, ๋)์ ํ ๋ฒ์ ์์ ํ ๋ณด๊ฐ์ผ๋ก ์ ์ฉํ๋ค(๋ฒกํฐ ์ฐ์ฐ).\"\"\"\n",
" a = np.radians(ang)[:, None, None]\n",
" zy, zx = np.asarray(zy)[:, None, None], np.asarray(zx)[:, None, None]\n",
" sy = np.clip((np.cos(a) * _YY + np.sin(a) * _XX) / zy + (N - 1) / 2, 0, N - 1.001) # ์ถ๋ ฅ ์ขํ -> ์๋ณธ ์ขํ\n",
" sx = np.clip((-np.sin(a) * _YY + np.cos(a) * _XX) / zx + (N - 1) / 2, 0, N - 1.001)\n",
" y0, x0 = sy.astype(int), sx.astype(int)\n",
" fy, fx = (sy - y0).reshape(len(stack), 1, -1), (sx - x0).reshape(len(stack), 1, -1)\n",
" flat = stack.reshape(len(stack), stack.shape[1], -1)\n",
" lin = (y0 * N + x0).reshape(len(stack), 1, -1)\n",
" g = lambda d: np.take_along_axis(flat, np.broadcast_to(lin + d, (len(stack), stack.shape[1], lin.shape[2])), axis=2)\n",
" out = g(0) * (1 - fy) * (1 - fx) + g(N) * fy * (1 - fx) + g(1) * (1 - fy) * fx + g(N + 1) * fy * fx\n",
" return out.reshape(stack.shape)\n",
"\n",
"\n",
"def preprocess_batch(O, S, M):\n",
" \"\"\"์ฌ๋ฌ ์ฅ์ ํ ๋ฒ์ ์ ์ฒ๋ฆฌํ๋ค. 0) ๊ฒฐ์ธก ๋ณด์ 1) ํด์๋ยท๋น์ค๋ฌ ์ดฌ์ ๋ณด์ (ํ์ค 0.5m/ํฝ์
)\n",
" 2) ๊ตฌ๋ฆ ์ฐจํ ์์ญ์ ๋ฐฐ๊ฒฝ๊ฐ์ผ๋ก ์ฑ์ 3) ๊ฐ์ฒด ์ฃผ์ถ์ ๊ฐ๋ก๋ก ํ์ 4) 0~1 ์ ๊ทํ.\"\"\"\n",
" src = np.stack([np.asarray(O, float), np.asarray(S, float)], axis=1)\n",
" if np.isnan(src).any(): # 0) ์ผ์ ๊ฒฐ์ธก ํฝ์
๋ณด์ \n",
" mean = np.nanmean(src, axis=(2, 3), keepdims=True)\n",
" src = np.where(np.isnan(src), mean, src)\n",
" zy = np.array([m[\"gsd\"] / STD_GSD / np.cos(np.radians(m[\"off_nadir\"])) for m in M])\n",
" zx = np.array([m[\"gsd\"] / STD_GSD for m in M])\n",
" out = warp(src, zy, zx, np.zeros(len(M)))\n",
" sm = ndimage.uniform_filter(out[:, 1], (1, 3, 3))\n",
" angs, cloud = np.zeros(len(M)), np.zeros(len(M))\n",
" for i in range(len(M)):\n",
" cm = cloud_mask(out[i, 0], sm[i])\n",
" if cm.any():\n",
" fill = out[i, 0][~cm].mean() if (~cm).any() else 115.0\n",
" out[i, 0][cm] = fill\n",
" back = warp(cm[None, None].astype(float), [1 / zy[i]], [1 / zx[i]], np.zeros(1))[0, 0] > 0.5\n",
" src[i, 0][back] = fill # ์๋ณธ์ ๊ฐ์ ๊ตฌ๋ฆ ์์ญ๋ ์ฑ์\n",
" cloud[i] = cm.mean()\n",
" angs[i] = orientation(out[i, 0], sm[i])[0]\n",
" rot = np.abs(angs) > 3 # 3๋ ์ดํ๋ ํ์ ์๋ต\n",
" if rot.any():\n",
" out[rot] = warp(src[rot], zy[rot], zx[rot], angs[rot])\n",
" return [(np.clip(o / 255, 0, 1), np.clip(s / 1.5, 0, 1), float(c)) for (o, s), c in zip(out, cloud)]\n",
"\n",
"\n",
"def preprocess(opt, sar, meta):\n",
" \"\"\"ํ ์ฅ ์ ์ฒ๋ฆฌ(preprocess_batch์ ๊ฐ์ ์ฒ๋ฆฌ).\"\"\"\n",
" return preprocess_batch([opt], [sar], [meta])[0]\n",
"\n",
"# ---- ํน์ง ์ถ์ถ ๊ณตํต ํจ์ ----\n",
"KERNELS = [np.array(k, float) for k in (\n",
" [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], [[-1, -2, -1], [0, 0, 0], [1, 2, 1]], [[0, 1, 0], [1, -4, 1], [0, 1, 0]],\n",
" [[2, -1, -1], [-1, 2, -1], [-1, -1, 2]], [[-1, -1, 2], [-1, 2, -1], [2, -1, -1]], [[1, 1, 1], [1, 1, 1], [1, 1, 1]])]\n",
"\n",
"\n",
"def conv_features(o):\n",
" \"\"\"๊ฐ์ด CNN ํน์ง: ๊ณ ์ ํฉ์ฑ๊ณฑ ํํฐ 6์ข
-> ReLU -> 4x4 ์ต๋ ํ๋ง(8x8 ์ถ๋ ฅ).\"\"\"\n",
" feats = []\n",
" for k in KERNELS:\n",
" r = np.maximum(ndimage.correlate(o - o.mean(), k / np.abs(k).sum()), 0)\n",
" feats.append(r.reshape(8, 4, 8, 4).max(axis=(1, 3)).ravel())\n",
" feats.append(np.maximum(-ndimage.correlate(o - o.mean(), k / np.abs(k).sum()), 0).reshape(8, 4, 8, 4).max(axis=(1, 3)).ravel())\n",
" return np.concatenate(feats)\n",
"\n",
"\n",
"def hog(o, cell=8, bins=8):\n",
" gy, gx = np.gradient(o)\n",
" mag, ang = np.hypot(gx, gy), np.mod(np.arctan2(gy, gx), np.pi)\n",
" b = np.minimum((ang / np.pi * bins).astype(int), bins - 1)\n",
" out = np.zeros((N // cell, N // cell, bins))\n",
" for i in range(N // cell):\n",
" for j in range(N // cell):\n",
" sl = (slice(i * cell, (i + 1) * cell), slice(j * cell, (j + 1) * cell))\n",
" out[i, j] = np.bincount(b[sl].ravel(), mag[sl].ravel(), bins)\n",
" return (out / (np.linalg.norm(out) + 1e-6)).ravel()\n",
"\n",
"\n",
"def shape_features(o, s):\n",
" \"\"\"๊ฐ์ฒด ๋ชจ์ ํน์ง: ๊ฐ๋ก ๋ฐฉํฅ ํฌ์์ผ๋ก ์ ์ฒด ๊ธธ์ดยท์ฐจ์ฒด ๊ธธ์ดยท๊ฐ๋ ๋์ถ๋ถ(ํฌ์ ) ๊ธธ์ดยท๋๊บผ์ด ๋ถ๋ถ(ํฌํ) ์์น๋ฅผ ์ฐ๋ค.\n",
" ๋ฐฉ์ ์ ๊ทํ ๋ค์๋ ์๋ค(180๋)๊ฐ ๋ค์งํ ์ ์์ผ๋ฏ๋ก, ๊ฐ๋ ๋์ถ๋ถ๊ฐ ์ค๋ฅธ์ชฝ์ ์ค๋๋ก ๋ง์ถ๋ค.\"\"\"\n",
" sm = ndimage.uniform_filter(s, 3)\n",
" dark = np.abs(o - np.median(o))\n",
" m = (sm > 0.22) | (dark > 0.12)\n",
" lab, n = ndimage.label(m)\n",
" if n:\n",
" c = lab[10:22, 8:24]\n",
" k = np.bincount(c[c > 0]).argmax() if (c > 0).any() else 1\n",
" m = lab == k\n",
" p = m.sum(axis=0).astype(float)\n",
" cols = np.nonzero(p)[0]\n",
" if len(cols) == 0:\n",
" return np.zeros(8), o, s\n",
" thin = (p > 0) & (p <= 2)\n",
" left, right = thin[:N // 2].sum(), thin[N // 2:].sum()\n",
" if left > right:\n",
" o, s, m, p = o[:, ::-1], s[:, ::-1], m[:, ::-1], p[::-1]\n",
" cols = np.nonzero(p)[0]\n",
" body = np.nonzero(p > 3)[0]\n",
" d = np.where(m, dark if left <= right else dark[:, ::-1], 0)\n",
" heavy = d.sum(axis=0)\n",
" centre = (body[0] + body[-1]) / 2 if len(body) else N / 2\n",
" turret = (heavy * np.arange(N)).sum() / max(heavy.sum(), 1e-6) - centre\n",
" return np.array([cols[-1] - cols[0] + 1, len(body), ((p > 0) & (p <= 2)).sum(), p.max(), turret,\n",
" m.sum(), (cols[-1] - (body[-1] if len(body) else cols[-1])), m.sum() / max(len(cols) * p.max(), 1)]), o, s\n",
"\n",
"\n",
"def adv_features(o, s):\n",
" \"\"\"์ ๋ฐ ํน์ง: ๋ชจ์(๊ธธ์ดยทํฌ์ ยทํฌํ ์์น) + HOG(์ค๊ณฝ) + SAR ๋ฐ์ฌ ํต๊ณ(์์ฅ๋งยท๊ตฌ๋ฆ ์ํฅ ์ ์) + ํฌ์ ์ค๊ณฝ.\"\"\"\n",
" shp, o, s = shape_features(o, s)\n",
" c = s[8:24, 4:28]\n",
" sm = ndimage.uniform_filter(s, 3)\n",
" m = sm > 0.2\n",
" dark = np.abs(o - np.median(o))\n",
" return np.concatenate([shp, hog(o), [c.mean(), c.max(), (sm > 0.25).mean(), (sm > 0.4).mean(), m.sum(axis=1).max(), m.sum(axis=0).max()],\n",
" sm.mean(axis=0).reshape(8, 4).mean(1), sm.mean(axis=1).reshape(8, 4).mean(1),\n",
" dark.mean(axis=0).reshape(8, 4).mean(1), dark.mean(axis=1).reshape(8, 4).mean(1),\n",
" [o.mean(), o.std(), dark.max(), (o > 0.65).mean(), (o < 0.3).mean()]])\n",
"\n",
"CONFIG = {\"train_with_decoy\": True, \"high\": 0.9, \"medium\": 0.6, \"hvt_types\": [\"spg\"]} # ๋ฏธ์
9 ์ฌ๋ณด์ ๊ฐ\n",
"CALLED = set()\n",
"\n",
"\n",
"def cover(fn):\n",
" \"\"\"๊ธฐ๋ฅ ์ปค๋ฒ๋ฆฌ์ง ์ธก์ ์ฉ ํ์: ํตํฉ ์คํ ์ค ํธ์ถ๋ ๊ธฐ๋ฅ์ ๊ธฐ๋กํ๋ค.\"\"\"\n",
" def wrap(*a, **k):\n",
" CALLED.add(fn.__name__)\n",
" return fn(*a, **k)\n",
" wrap.__name__ = fn.__name__\n",
" return wrap\n",
"\n",
"\n",
"preprocess, adv_features, shape_features, hog, cloud_mask, orientation = map(cover, (preprocess, adv_features, shape_features, hog, cloud_mask, orientation))\n",
"\n",
"\n",
"@cover\n",
"def validate_tile(o, s, m):\n",
" errs = []\n",
" if o.shape != (N, N) or s.shape != (N, N):\n",
" errs.append(\"ํ์ผ ํฌ๊ธฐ ์ค๋ฅ\")\n",
" if not 0.1 <= m[\"gsd\"] <= 30:\n",
" errs.append(\"gsd ๋ฒ์ ์ค๋ฅ\")\n",
" if not np.isfinite(o).all():\n",
" errs.append(\"๊ฒฐ์ธก ํฝ์
\")\n",
" return errs\n",
"\n",
"\n",
"@cover\n",
"def quality(P, prob):\n",
" srt = np.sort(prob, axis=1)\n",
" ent = -(prob * np.log(np.clip(prob, 1e-9, 1))).sum(1) / np.log(prob.shape[1])\n",
" return np.c_[srt[:, -1], srt[:, -1] - srt[:, -2], ent, [p[2] for p in P], [p[1][8:24, 4:28].mean() for p in P]]\n",
"\n",
"\n",
"@cover\n",
"def grade(c):\n",
" return \"High\" if c >= CONFIG[\"high\"] else (\"Medium\" if c >= CONFIG[\"medium\"] else \"Low\")\n",
"\n",
"\n",
"class Queue:\n",
" def __init__(self):\n",
" self.items, self.log = [], []\n",
"\n",
" @cover\n",
" def submit(self, iid, pred, g, conf):\n",
" prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n",
" body = {\"event\": \"SUBMIT\", \"id\": iid, \"pred\": pred, \"grade\": g, \"conf\": round(conf, 3), \"hvt\": pred in CONFIG[\"hvt_types\"]}\n",
" self.log.append({**body, \"prev\": prev, \"hash\": hashlib.sha256((prev + json.dumps(body, sort_keys=True)).encode()).hexdigest()})\n",
" self.items.append({**body, \"status\": \"PENDING\"})\n",
"\n",
" @cover\n",
" def verify(self):\n",
" prev = \"GENESIS\"\n",
" for r in self.log:\n",
" body = {k: v for k, v in r.items() if k not in (\"prev\", \"hash\")}\n",
" if r[\"prev\"] != prev or r[\"hash\"] != hashlib.sha256((prev + json.dumps(body, sort_keys=True)).encode()).hexdigest():\n",
" return False\n",
" prev = r[\"hash\"]\n",
" return True\n",
"\n",
"\n",
"TARGETS = {\"validate_tile\", \"preprocess\", \"cloud_mask\", \"orientation\", \"adv_features\", \"shape_features\", \"hog\", \"quality\", \"grade\", \"submit\", \"verify\", \"dashboard\"}\n",
"LABELS = CLASSES + [\"decoy\", \"none\"]\n",
"\n",
"# ---------------- 1. ํ์ต(๋ถ๋ฅ ๋ชจ๋ธ + ์ ๋ขฐ๋ ๊ฐ์ค์น) ----------------\n",
"t_start = time.perf_counter()\n",
"O, S, M = make_dataset(200 * len(LABELS), seed=3, scen=tuple(SCEN), classes=LABELS)\n",
"clf = RandomForestClassifier(n_estimators=150, min_samples_leaf=2, random_state=0, n_jobs=1)\n",
"clf.fit(np.array([adv_features(o, s) for o, s, _ in preprocess_batch(O, S, M)]), [m[\"cls\"] for m in M])\n",
"Oc, Sc, Mc = make_dataset(480, seed=6, scen=tuple(SCEN), classes=LABELS)\n",
"Pc = preprocess_batch(Oc, Sc, Mc)\n",
"prc = clf.predict_proba(np.array([adv_features(o, s) for o, s, _ in Pc]))\n",
"conf_model = make_pipeline(StandardScaler(), LogisticRegression(max_iter=2000)).fit(quality(Pc, prc), clf.classes_[prc.argmax(1)] == np.array([m[\"cls\"] for m in Mc]))\n",
"\n",
"# ---------------- 2. ์ด์ฉ ์๋ฃ(๊ฐ์ ํ๋
์ง์ญ 300ํ์ผ) ์ฒ๋ฆฌ ----------------\n",
"Oo, So, Mo = make_dataset(300, seed=31, scen=(\"clean\", \"camo\", \"occl30\", \"cloud\", \"low_sun\", \"low_res\"), classes=LABELS)\n",
"ops = list(zip(Oo, So, Mo))\n",
"ops[5] = (Oo[5][:20], So[5], Mo[5]) # ์์ ํ์ผ 1๊ฑด(์๋ฆผ)\n",
"ops[9] = (Oo[9], So[9], {**Mo[9], \"gsd\": 0.0}) # ๋ฉํ๋ฐ์ดํฐ ์ค๋ฅ 1๊ฑด\n",
"queue, rejected, res, ms = Queue(), {}, [], []\n",
"for o, s, m in ops:\n",
" errs = validate_tile(o, s, m)\n",
" if errs:\n",
" rejected[m[\"id\"]] = errs; continue\n",
" t0 = time.perf_counter()\n",
" P = [preprocess(o, s, m)]\n",
" pr = clf.predict_proba([adv_features(P[0][0], P[0][1])])\n",
" pred = clf.classes_[pr.argmax()]\n",
" conf = float(conf_model.predict_proba(quality(P, pr))[0, 1])\n",
" g = grade(conf)\n",
" if pred != \"none\":\n",
" queue.submit(m[\"id\"], pred, g, conf)\n",
" ms.append((time.perf_counter() - t0) * 1000)\n",
" res.append({\"id\": m[\"id\"], \"y\": m[\"cls\"], \"p\": pred, \"grade\": g, \"conf\": conf, \"scen\": m[\"scen\"]})\n",
"R = pd.DataFrame(res)\n",
"acc = (R.y == R.p).mean()\n",
"milR = R[R.y.isin(list(MILITARY))]\n",
"rec = (milR.y == milR.p).mean()\n",
"decoy_catch = (R[R.y == \"decoy\"].p == \"decoy\").mean()\n",
"hi = R[(R.grade == \"High\") & R.p.isin(list(MILITARY))]\n",
"hi_err = (hi.y != hi.p).mean() if len(hi) else 0.0\n",
"\n",
"\n",
"# ---------------- 3. ๋ถ์ ํ๋ฉด ----------------\n",
"@cover\n",
"def dashboard():\n",
" plt.style.use(\"dark_background\")\n",
" fig, ax = plt.subplots(2, 2, figsize=(12, 9))\n",
" T = 10\n",
" mosaic = np.block([[np.asarray(Oo[r * T + c]) for c in range(T)] for r in range(T)])\n",
" ax[0, 0].imshow(mosaic, cmap=\"gray\")\n",
" for k, r in R[R.id.isin([f\"IMG{i:04d}\" for i in range(100)])].iterrows():\n",
" i = int(r.id[3:])\n",
" if r.p != \"none\":\n",
" ax[0, 0].scatter(i % T * N + 16, i // T * N + 16, s=60, facecolors=\"none\",\n",
" edgecolors=\"red\" if r.p in MILITARY else (\"yellow\" if r.p == \"decoy\" else \"deepskyblue\"))\n",
" ax[0, 0].set_title(\"First 100 tiles (red = military, yellow = decoy)\"); ax[0, 0].axis(\"off\")\n",
" vc = R[R.p != \"none\"].p.value_counts()\n",
" ax[0, 1].bar(vc.index.tolist(), vc.values, color=\"deepskyblue\"); ax[0, 1].set_title(\"Detections by type\")\n",
" gc = R[R.p != \"none\"].grade.value_counts().reindex([\"High\", \"Medium\", \"Low\"]).fillna(0)\n",
" ax[1, 0].bar(gc.index.tolist(), gc.values, color=[\"red\", \"orange\", \"gray\"]); ax[1, 0].set_title(\"Review queue by grade (all pending)\")\n",
" ax[1, 1].axis(\"off\")\n",
" ax[1, 1].text(0, .9, f\"Accuracy {acc:.1%}\\nMilitary rec. {rec:.1%}\\nDecoy caught {decoy_catch:.1%}\\nQueue {len(queue.items)} pending\\n\"\n",
" f\"Latency {np.mean(ms):.1f} ms/tile\", family=\"monospace\", fontsize=13, va=\"top\")\n",
" fig.suptitle(\"ISR01 integrated satellite object classification - synthetic data only\"); plt.tight_layout(); plt.show()\n",
"\n",
"\n",
"dashboard()\n",
"\n",
"# ---------------- 4. ์์ฒด ์ ๊ฒ(ํตํฉ ํ
์คํธ) ----------------\n",
"log_ok = queue.verify()\n",
"cov = len(CALLED & TARGETS) / len(TARGETS)\n",
"chk = {\n",
" \"์์ ํ์ผยท๋ฉํ๋ฐ์ดํฐ ์ค๋ฅ ์ฐจ๋จ\": set(rejected) == {\"IMG0005\", \"IMG0009\"},\n",
" \"์ฒ๋ฆฌ ํ์ผ 298์ฅ\": len(res) == 298,\n",
" \"์ข
ํฉ ์ ํ๋ 90% ์ด์\": acc >= 0.9,\n",
" \"๊ตฐ์ฉ ๊ธฐ์ข
์๋ณ ์ฌํ์จ 85% ์ด์\": rec >= 0.85,\n",
" \"๋ชจ์ ํ์ ์ ๋ฐ๋ฅ 80% ์ด์\": decoy_catch >= 0.8,\n",
" \"์๋ ์น์ธ 0๊ฑด(์ ๋ถ ํ๋
๋๊ธฐ)\": all(i[\"status\"] == \"PENDING\" for i in queue.items),\n",
" \"์๋ณ ๊ฑด ์ ๋ ๋๊ธฐ์ด ๋ฑ๋ก\": len(queue.items) == int((R.p != \"none\").sum()),\n",
" \"๊ฐ์ฌ ๋ก๊ทธ ๋ฌด๊ฒฐ์ฑ\": log_ok,\n",
" \"ํ์ผ๋น 200ms ์ด๋ด\": float(np.mean(ms)) < 200,\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)} ํต๊ณผ, ๊ธฐ๋ฅ ์ปค๋ฒ๋ฆฌ์ง {cov:.0%} ({len(CALLED & TARGETS)}/{len(TARGETS)}), ํ์ดํ๋ผ์ธ ์คํ ์ฑ๊ณต {'์' if passed == len(chk) else '์๋์ค'}\")\n",
"\n",
"# ---------------- 5. ์ ๋ต ISR ํ๊ฐ ๋ธ๋ฆฌํ ์์ฝ ----------------\n",
"print(f\"\"\"\n",
"[์์ฑ์์ ์ฅ๋น ๊ฐ์ฒด๋ถ๋ฅ ํ๊ฐ ๋ธ๋ฆฌํ ์์ฝ - ๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
"1. ์ฒ๋ฆฌ: ํ์ผ {len(ops)}์ฅ ์ค {len(res)}์ฅ ํ๋
, ์์ยท์ค๋ฅ {len(rejected)}๊ฑด ์ฐจ๋จ({', '.join(rejected)})\n",
"2. ์ฑ๋ฅ: ์ข
ํฉ ์ ํ๋ {acc:.1%}, ๊ตฐ์ฉ ๊ธฐ์ข
์๋ณ ์ฌํ์จ {rec:.1%}, ๋ชจ์ ํ์ ์ ๋ฐ {decoy_catch:.1%}, ํ์ผ๋น {np.mean(ms):.1f} ms, ์ ์ฒด {time.perf_counter() - t_start:.0f} s\n",
"3. ํ๋
๋๊ธฐ: {len(queue.items)}๊ฑด (High {int((R[R.p != 'none'].grade == 'High').sum())} / Medium {int((R[R.p != 'none'].grade == 'Medium').sum())} / Low {int((R[R.p != 'none'].grade == 'Low').sum())}), HVT ํ๋ณด {sum(i['hvt'] for i in queue.items)}๊ฑด, ์๋ ์น์ธ 0๊ฑด\n",
"4. ์ ๋ขฐ๋: High ๋ฑ๊ธ ๊ตฐ์ฉ ์๋ณ {len(hi)}๊ฑด ์ค ์ค๋ถ๋ฅ {hi_err:.1%} -> ๋ฑ๊ธ์ ํ๋
์์ ๊ฒฐ์ ์ฉ์ด๋ฉฐ ์ต์ข
ํ๋
์ ์์๋ถ์๊ด์ด ํ๋ค\n",
"5. ํ๊ณ: ํฉ์ฑ 32x32 ์์ ๊ธฐ์ค ๊ฒฐ๊ณผ๋ก, ์ค์ ์์ฑ์์ยท์ฅ๋น์๋ ์ฌํ์ต๊ณผ ์ฌ๊ฒ์ฆ์ด ํ์ํ๋ค. ์ธ์ ์ฒด๊ณ ์ฐ๋์ ํ๋
์น์ธ ๊ฒฐ๊ณผ๋ง ํ์ค ํ๋๋ก ์ ๋ฌํ๋ค.\"\"\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
10 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[PASS] ์์ ํ์ผยท๋ฉํ๋ฐ์ดํฐ ์ค๋ฅ ์ฐจ๋จ\n",
"[PASS] ์ฒ๋ฆฌ ํ์ผ 298์ฅ\n",
"[PASS] ์ข
ํฉ ์ ํ๋ 90% ์ด์\n",
"[PASS] ๊ตฐ์ฉ ๊ธฐ์ข
์๋ณ ์ฌํ์จ 85% ์ด์\n",
"[PASS] ๋ชจ์ ํ์ ์ ๋ฐ๋ฅ 80% ์ด์\n",
"[PASS] ์๋ ์น์ธ 0๊ฑด(์ ๋ถ ํ๋
๋๊ธฐ)\n",
"[PASS] ์๋ณ ๊ฑด ์ ๋ ๋๊ธฐ์ด ๋ฑ๋ก\n",
"[PASS] ๊ฐ์ฌ ๋ก๊ทธ ๋ฌด๊ฒฐ์ฑ\n",
"[PASS] ํ์ผ๋น 200ms ์ด๋ด\n",
"[PASS] ๊ธฐ๋ฅ ์ปค๋ฒ๋ฆฌ์ง 85% ์ด์\n",
"์์ฒด ์ ๊ฒ 10/10 ํต๊ณผ, ๊ธฐ๋ฅ ์ปค๋ฒ๋ฆฌ์ง 100% (12/12), ํ์ดํ๋ผ์ธ ์คํ ์ฑ๊ณต ์\n",
"\n",
"[์์ฑ์์ ์ฅ๋น ๊ฐ์ฒด๋ถ๋ฅ ํ๊ฐ ๋ธ๋ฆฌํ ์์ฝ - ๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
"1. ์ฒ๋ฆฌ: ํ์ผ 300์ฅ ์ค 298์ฅ ํ๋
, ์์ยท์ค๋ฅ 2๊ฑด ์ฐจ๋จ(IMG0005, IMG0009)\n",
"2. ์ฑ๋ฅ: ์ข
ํฉ ์ ํ๋ 96.6%, ๊ตฐ์ฉ ๊ธฐ์ข
์๋ณ ์ฌํ์จ 96.0%, ๋ชจ์ ํ์ ์ ๋ฐ 100.0%, ํ์ผ๋น 14.0 ms, ์ ์ฒด 10 s\n",
"3. ํ๋
๋๊ธฐ: 251๊ฑด (High 223 / Medium 21 / Low 7), HVT ํ๋ณด 51๊ฑด, ์๋ ์น์ธ 0๊ฑด\n",
"4. ์ ๋ขฐ๋: High ๋ฑ๊ธ ๊ตฐ์ฉ ์๋ณ 133๊ฑด ์ค ์ค๋ถ๋ฅ 2.3% -> ๋ฑ๊ธ์ ํ๋
์์ ๊ฒฐ์ ์ฉ์ด๋ฉฐ ์ต์ข
ํ๋
์ ์์๋ถ์๊ด์ด ํ๋ค\n",
"5. ํ๊ณ: ํฉ์ฑ 32x32 ์์ ๊ธฐ์ค ๊ฒฐ๊ณผ๋ก, ์ค์ ์์ฑ์์ยท์ฅ๋น์๋ ์ฌํ์ต๊ณผ ์ฌ๊ฒ์ฆ์ด ํ์ํ๋ค. ์ธ์ ์ฒด๊ณ ์ฐ๋์ ํ๋
์น์ธ ๊ฒฐ๊ณผ๋ง ํ์ค ํ๋๋ก ์ ๋ฌํ๋ค.\n",
"```\n",
" \n"
]
}
],
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}