{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ๐ŸŽฏ [ISR01] ๊ณต๊ฐœ ์œ„์„ฑ์˜์ƒ ์žฅ๋น„ยท์ฐจ๋Ÿ‰ ๊ฐ์ฒด๋ถ„๋ฅ˜ ์ •ํ™•๋„ ํ‰๊ฐ€\n", "### Google Colab ์‹ค์Šต ํ™˜๊ฒฝ (์ •๋ณดยท๊ฐ์‹œยท์ •์ฐฐ(ISR) / Level 3)\n", "\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edunlab/milicontent2/blob/main/uploads/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", " if not isinstance(value, rule[\"type\"]) or isinstance(value, bool):\n", " return [f\"{name}: ์ž๋ฃŒํ˜• ์˜ค๋ฅ˜({type(value).__name__})\"]\n", " errs = []\n", " if \"enum\" in rule and value not in rule[\"enum\"]:\n", " errs.append(f\"{name}: ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ๊ฐ’ '{value}'\")\n", " if \"min\" in rule and value < rule[\"min\"]:\n", " errs.append(f\"{name}: ์ตœ์†Œ๊ฐ’ {rule['min']} ๋ฏธ๋งŒ\")\n", " if \"max\" in rule and value > rule[\"max\"]:\n", " errs.append(f\"{name}: ์ตœ๋Œ€๊ฐ’ {rule['max']} ์ดˆ๊ณผ\")\n", " if \"length\" in rule and len(value) != rule[\"length\"]:\n", " errs.append(f\"{name}: ๊ธธ์ด {rule['length']} ํ•„์š”\")\n", " if \"pattern\" in rule and not re.match(rule[\"pattern\"], value):\n", " errs.append(f\"{name}: ํ˜•์‹ ๋ถˆ์ผ์น˜ '{value}'\")\n", " return errs\n", "\n", "\n", "def validate(rec):\n", " errs = [f\"{k}: ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\" for k in SCHEMA[\"required\"] if k not in rec]\n", " for k, rule in SCHEMA[\"fields\"].items():\n", " if k in rec:\n", " errs += check(k, rec[k], rule)\n", " for group in (\"source\", \"sensor\", \"tactical\"):\n", " for k, rule in SCHEMA[group].items():\n", " if k not in rec.get(group, {}):\n", " errs.append(f\"{group}.{k}: ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\")\n", " else:\n", " errs += check(f\"{group}.{k}\", rec[group][k], rule)\n", " for i, lab in enumerate(rec.get(\"labels\", [])):\n", " for k, rule in SCHEMA[\"label\"].items():\n", " if k not in lab:\n", " errs.append(f\"labels[{i}].{k}: ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\")\n", " else:\n", " errs += check(f\"labels[{i}].{k}\", lab[k], rule)\n", " return errs\n", "\n", "\n", "# 2) [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์˜์ƒ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ 20๊ฑด ์ƒ์„ฑ (๊ฐ€์ƒ ๋‚ ์งœยท์ˆ˜์น˜, ์‹ค์ œ ์ดฌ์˜ ์ •๋ณด ์•„๋‹˜)\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", " yy, xx = np.mgrid[0:N, 0:N] - (N - 1) / 2\n", " yy = yy / squash\n", " a = np.radians(angle)\n", " u = (xx * np.cos(a) + yy * np.sin(a)) / scale\n", " v = (-xx * np.sin(a) + yy * np.cos(a)) / scale\n", " return [((np.abs(u - cu) <= L / 2) & (np.abs(v - cv) <= W / 2), d) for cu, cv, L, W, d in SHAPES[cls]]\n", "\n", "\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", " out = np.full((N, N), fill, float)\n", " h, w = a.shape\n", " ys, xs = max(0, (h - N) // 2), max(0, (w - N) // 2)\n", " yd, xd = max(0, (N - h) // 2), max(0, (N - w) // 2)\n", " hh, ww = min(N, h), min(N, w)\n", " out[yd:yd + hh, xd:xd + ww] = a[ys:ys + hh, xs:xs + ww]\n", " return out\n", "\n", "\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", " yy, xx = np.mgrid[0:N, 0:N] - (N - 1) / 2\n", " yy = yy / squash\n", " a = np.radians(angle)\n", " u = (xx * np.cos(a) + yy * np.sin(a)) / scale\n", " v = (-xx * np.sin(a) + yy * np.cos(a)) / scale\n", " return [((np.abs(u - cu) <= L / 2) & (np.abs(v - cv) <= W / 2), d) for cu, cv, L, W, d in SHAPES[cls]]\n", "\n", "\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", " out = np.full((N, N), fill, float)\n", " h, w = a.shape\n", " ys, xs = max(0, (h - N) // 2), max(0, (w - N) // 2)\n", " yd, xd = max(0, (N - h) // 2), max(0, (N - w) // 2)\n", " hh, ww = min(N, h), min(N, w)\n", " out[yd:yd + hh, xd:xd + ww] = a[ys:ys + hh, xs:xs + ww]\n", " return out\n", "\n", "\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", " yy, xx = np.mgrid[0:N, 0:N] - (N - 1) / 2\n", " yy = yy / squash\n", " a = np.radians(angle)\n", " u = (xx * np.cos(a) + yy * np.sin(a)) / scale\n", " v = (-xx * np.sin(a) + yy * np.cos(a)) / scale\n", " return [((np.abs(u - cu) <= L / 2) & (np.abs(v - cv) <= W / 2), d) for cu, cv, L, W, d in SHAPES[cls]]\n", "\n", "\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", " out = np.full((N, N), fill, float)\n", " h, w = a.shape\n", " ys, xs = max(0, (h - N) // 2), max(0, (w - N) // 2)\n", " yd, xd = max(0, (N - h) // 2), max(0, (N - w) // 2)\n", " hh, ww = min(N, h), min(N, w)\n", " out[yd:yd + hh, xd:xd + ww] = a[ys:ys + hh, xs:xs + ww]\n", " return out\n", "\n", "\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", " yy, xx = np.mgrid[0:N, 0:N] - (N - 1) / 2\n", " yy = yy / squash\n", " a = np.radians(angle)\n", " u = (xx * np.cos(a) + yy * np.sin(a)) / scale\n", " v = (-xx * np.sin(a) + yy * np.cos(a)) / scale\n", " return [((np.abs(u - cu) <= L / 2) & (np.abs(v - cv) <= W / 2), d) for cu, cv, L, W, d in SHAPES[cls]]\n", "\n", "\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", " out = np.full((N, N), fill, float)\n", " h, w = a.shape\n", " ys, xs = max(0, (h - N) // 2), max(0, (w - N) // 2)\n", " yd, xd = max(0, (N - h) // 2), max(0, (N - w) // 2)\n", " hh, ww = min(N, h), min(N, w)\n", " out[yd:yd + hh, xd:xd + ww] = a[ys:ys + hh, xs:xs + ww]\n", " return out\n", "\n", "\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", " yy, xx = np.mgrid[0:N, 0:N] - (N - 1) / 2\n", " yy = yy / squash\n", " a = np.radians(angle)\n", " u = (xx * np.cos(a) + yy * np.sin(a)) / scale\n", " v = (-xx * np.sin(a) + yy * np.cos(a)) / scale\n", " return [((np.abs(u - cu) <= L / 2) & (np.abs(v - cv) <= W / 2), d) for cu, cv, L, W, d in SHAPES[cls]]\n", "\n", "\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", " out = np.full((N, N), fill, float)\n", " h, w = a.shape\n", " ys, xs = max(0, (h - N) // 2), max(0, (w - N) // 2)\n", " yd, xd = max(0, (N - h) // 2), max(0, (N - w) // 2)\n", " hh, ww = min(N, h), min(N, w)\n", " out[yd:yd + hh, xd:xd + ww] = a[ys:ys + hh, xs:xs + ww]\n", " return out\n", "\n", "\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": [ "\"\"\"๊ณ ๊ฐ€์น˜ ํ‘œ์ (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", " yy, xx = np.mgrid[0:N, 0:N] - (N - 1) / 2\n", " yy = yy / squash\n", " a = np.radians(angle)\n", " u = (xx * np.cos(a) + yy * np.sin(a)) / scale\n", " v = (-xx * np.sin(a) + yy * np.cos(a)) / scale\n", " return [((np.abs(u - cu) <= L / 2) & (np.abs(v - cv) <= W / 2), d) for cu, cv, L, W, d in SHAPES[cls]]\n", "\n", "\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", " out = np.full((N, N), fill, float)\n", " h, w = a.shape\n", " ys, xs = max(0, (h - N) // 2), max(0, (w - N) // 2)\n", " yd, xd = max(0, (N - h) // 2), max(0, (N - w) // 2)\n", " hh, ww = min(N, h), min(N, w)\n", " out[yd:yd + hh, xd:xd + ww] = a[ys:ys + hh, xs:xs + ww]\n", " return out\n", "\n", "\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", " yy, xx = np.mgrid[0:N, 0:N] - (N - 1) / 2\n", " yy = yy / squash\n", " a = np.radians(angle)\n", " u = (xx * np.cos(a) + yy * np.sin(a)) / scale\n", " v = (-xx * np.sin(a) + yy * np.cos(a)) / scale\n", " return [((np.abs(u - cu) <= L / 2) & (np.abs(v - cv) <= W / 2), d) for cu, cv, L, W, d in SHAPES[cls]]\n", "\n", "\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", " out = np.full((N, N), fill, float)\n", " h, w = a.shape\n", " ys, xs = max(0, (h - N) // 2), max(0, (w - N) // 2)\n", " yd, xd = max(0, (N - h) // 2), max(0, (N - w) // 2)\n", " hh, ww = min(N, h), min(N, w)\n", " out[yd:yd + hh, xd:xd + ww] = a[ys:ys + hh, xs:xs + ww]\n", " return out\n", "\n", "\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", " yy, xx = np.mgrid[0:N, 0:N] - (N - 1) / 2\n", " yy = yy / squash\n", " a = np.radians(angle)\n", " u = (xx * np.cos(a) + yy * np.sin(a)) / scale\n", " v = (-xx * np.sin(a) + yy * np.cos(a)) / scale\n", " return [((np.abs(u - cu) <= L / 2) & (np.abs(v - cv) <= W / 2), d) for cu, cv, L, W, d in SHAPES[cls]]\n", "\n", "\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", " out = np.full((N, N), fill, float)\n", " h, w = a.shape\n", " ys, xs = max(0, (h - N) // 2), max(0, (w - N) // 2)\n", " yd, xd = max(0, (N - h) // 2), max(0, (N - w) // 2)\n", " hh, ww = min(N, h), min(N, w)\n", " out[yd:yd + hh, xd:xd + ww] = a[ys:ys + hh, xs:xs + ww]\n", " return out\n", "\n", "\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 }