wing-ops/prediction/image/project_brief.md
jeonghyo.k 3946ff6a25 feat(prediction): 이미지 분석 서버 Docker 패키징 + DB 코드 제거
- prediction/image/ FastAPI 서버 Docker 환경 구성
  - Dockerfile: PyTorch 2.1 + CUDA 12.1 기반 GPU 이미지
  - docker-compose.yml: GPU 할당 + 데이터 볼륨 마운트
  - requirements.txt: 서버 의존성 목록
  - .env.example: 환경변수 템플릿
  - DOCKER_USAGE.md: 빌드/실행/API 사용법 문서
  - Dockerfile에 .dockerignore 제외 폴더 mkdir -p 추가
- .gitignore: prediction/image 결과물 및 모델 가중치(.pth) 제외 추가
- dbInsert_csv.py, dbInsert_shp.py 삭제 (미사용 DB 로직)
- api.py: dbInsert import 및 주석 처리된 DB 호출 코드 제거
- aerialRouter.ts: req.params 타입 오류 수정
2026-03-10 18:37:36 +09:00

396 lines
15 KiB
Markdown

# Project Brief — prediction/image
## 1. 프로젝트 목적
항공/드론 카메라로 촬영된 해양 유류 오염 이미지를 자동 분석하여 유류 확산 정보를 추출·반환하는 **이미지 분석 백엔드 서비스**이다.
- 드론(mx15hdi) 또는 열화상(starsafire) 카메라 이미지를 입력받아
- AI 세그멘테이션으로 유류 유형(검정/갈색/무지개/은색)을 탐지하고
- 지리참조(GeoTIFF) 변환 후 유류 면적·부피가 담긴 Shapefile을 생성하며
- 최종적으로 위치 메타데이터와 유류 분석 결과를 JSON으로 반환한다.
지원 카메라 타입: `mx15hdi` (EO/나디르 드론), `starsafire` (열화상 카메라)
---
## 2. 전체 처리 흐름
```
클라이언트
│ POST /run-script/ (camTy, fileId, image 파일)
api.py
├─ GPS EXIF 검증 (check_gps_info)
├─ 이미지 저장 → {camTy}/Metadata/Image/Original_Images/{fileId}/
└─ subprocess: Combine_module.py {fileId}
├── [1] Detect/Inference.py AI 세그멘테이션
│ └─ 결과: Detect/result/{fileId}/ (블렌딩 이미지)
│ Detect/Mask_result/{fileId}/ (컬러 마스크)
├── [2] Metadata/Scripts/Export_Metadata_mx15hdi.py
│ └─ EXIF 추출 + 보간 저장
│ Metadata/CSV/{fileId}/mx15hdi.csv
│ Metadata/CSV/{fileId}/mx15hdi_interpolation.csv
├── [3] Georeference/Scripts/Create_Georeferenced_Images_nadir.py
│ └─ 핀홀 투영 → GeoTIFF 저장
│ Georeference/Tif/{fileId}/ (컬러 블렌딩 TIF)
│ Georeference/Mask_Tif/{fileId}/ (마스크 TIF)
└── [4] Polygon/Scripts/Oilshape.py
└─ 마스크 TIF → 폴리곤 추출 → Shapefile 저장
Polygon/Shp/{fileId}/*.shp
├─ extract_data.get_metadata() → CSV에서 첫 번째 행 추출
└─ extract_data.get_oil_type() → Shapefile에서 유류 폴리곤 목록 추출
JSON 응답 { meta: "...", data: [...] }
```
---
## 3. 주요 스크립트 설명
### 3-1. `api.py` — FastAPI 서버 (포트 5001)
프로젝트의 진입점. 모든 엔드포인트를 정의하며 파이프라인 실행 및 결과 반환을 담당한다.
**보조 함수**
| 함수 | 설명 |
|------|------|
| `check_gps_info(image_path)` | 이미지 EXIF에서 GPS IFD 존재 여부 확인 |
| `check_camera_info(image_file)` | 이미지 EXIF에서 카메라 모델명 추출 |
---
### 3-2. `extract_data.py` — 결과 데이터 추출
파이프라인 완료 후 CSV·Shapefile에서 결과를 읽어 API 응답에 포함시키는 유틸리티 모듈.
**`get_metadata(camTy, fileId)`**
- CSV 파일 경로를 카메라 타입에 따라 선택
- mx15hdi: `{camTy}/Metadata/CSV/{fileId}/mx15hdi_interpolation.csv`
- starsafire: `{camTy}/Metadata/CSV/{fileId}/Metadata_Extracted.csv`
- 헤더 이후 첫 번째 데이터 행을 쉼표로 이어 문자열 반환
**`get_oil_type(camTy, fileId)`**
- `{camTy}/Polygon/Shp/{fileId}/*.shp` 에서 첫 번째 Shapefile 로드
- CRS를 EPSG:4326으로 변환
- 각 피처에서 `class_id`, `area_m2`, `volume_m3`, `note` 추출
- 유류 유형별 두께 매핑 적용 (mm → m 변환)
- 1(검정/Emulsion): 1.0mm, 2(갈색/Crude): 0.1mm, 3(무지개): 0.0003mm, 4(은색): 0.0001mm
- 반환값: `[{classId, area, volume, note, thickness, wkt}, ...]`
---
### 3-3. `dbInsert_csv.py` — CSV 메타데이터 DB 저장
현재 API에서는 주석 처리되어 있으며 독립 실행(CLI)으로도 사용 가능한 스크립트.
- CSV에서 촬영 일시 및 대상 위경도(DMS → 십진수 변환)를 파싱
- `env_safe.unmnd_poll_info` 테이블에 INSERT
- 카메라 타입에 따라 CSV 컬럼 파싱 방식이 다름
- mx15hdi: `Date1/Date2/Date3`, `Time1/Time2/Time3`, DMS 6필드
- starsafire: `Date`, `Time`, DMS 4필드(초 없음)
---
### 3-4. `dbInsert_shp.py` — Shapefile 유류 폴리곤 DB 저장
현재 API에서는 주석 처리되어 있으며 독립 실행(CLI)으로도 사용 가능한 스크립트.
- Shapefile에서 유류 폴리곤 피처를 읽어 `env_safe.poll_mat_info` 테이블에 배치 INSERT
- 폴리곤 지오메트리를 `ST_GeomFromText(wkt, 4326)`으로 저장
- 컬럼: `poll_id`, `algo_ty(유류명)`, `mat_ty(note)`, `mat_area`, `mat_thick`, `mat_vol`, `mat_geom`
---
### 3-5. `pic_gps.py` — 드론/폰 사진 스티칭 + GPS 보존
`/stitch` 엔드포인트가 호출하는 이미지 합성 전용 스크립트. CLI 독립 실행도 지원.
- **스티칭 모드**: drone(SCANS 우선) / phone(PANORAMA 우선), 실패 시 폴백 가능
- **전처리**: 최대 해상도 리사이즈, CLAHE 대비 보정(`--enhance`)
- **GPS 전략**: center(GPS 중앙값), first(첫 이미지 GPS), none
- Haversine 공식으로 중심 좌표에서 가장 가까운 이미지 선택
- **출력**: GPS EXIF가 삽입된 JPEG 합성 이미지
---
### 3-6. `mx15hdi/Main/Combine_module.py` — 파이프라인 오케스트레이터
`/run-script/` 엔드포인트가 subprocess로 실행하는 메인 파이프라인 스크립트.
- 커맨드라인 인자: `fileId`
- 다음 스크립트를 순서대로 실행:
1. `../Detect/Inference.py`
2. `../Metadata/Scripts/Export_Metadata_mx15hdi.py`
3. `../Georeference/Scripts/Create_Georeferenced_Images_nadir.py`
4. `../Polygon/Scripts/Oilshape.py`
---
### 3-7. `mx15hdi/Detect/Inference.py` — AI 세그멘테이션 추론
MMSegmentation 기반 유류 세그멘테이션 모델을 실행하는 스크립트.
- 모델: `V7_SPECIAL.py` 설정 + `epoch_165.pth` 가중치, `cuda:0` 디바이스
- 분류 클래스: background, black, brown, rainbow, silver (5클래스)
- 처리 흐름:
1. `Original_Images/{fileId}/` 내 이미지 파일 열거
2. `inference_segmentor()`로 세그멘테이션 맵 생성
3. 팔레트 기반 컬러 마스크 생성 (alpha=0.6 블렌딩)
4. 블렌딩 이미지 → `Detect/result/{fileId}/`
5. 컬러 마스크 이미지 → `Detect/Mask_result/{fileId}/`
---
### 3-8. `mx15hdi/Metadata/Scripts/Export_Metadata_mx15hdi.py` — 메타데이터 추출
이미지의 EXIF 정보를 파싱하여 CSV로 저장하는 스크립트.
- **`meta_info.extract_and_save_image_metadata()`**: Pillow로 EXIF 읽기
- 촬영 시각(DateTimeOriginal), GPS(위도/경도/고도) 추출
- DMS 형식으로 변환하여 CSV 저장 (`mx15hdi.csv`)
- 컬럼: `Filename`, `Tlat_d/m/s`, `Tlon_d/m/s`, `Alat_d/m/s`, `Alon_d/m/s`, `Az`, `El`, `Alt`, `Date1/2/3`, `Time1/2/3`
- **`meta_info.geo_info()`**: OCR(PaddleOCR)로 이미지 HUD에서 메타데이터 추출 (현재 주석 처리)
- **`meta_info.interpolation()`**: 결측값 전방/후방 보간 후 `mx15hdi_interpolation.csv` 저장
---
### 3-9. `mx15hdi/Georeference/Scripts/Create_Georeferenced_Images_nadir.py` — 지리참조 변환
나디르(수직 하향) 촬영 이미지를 GeoTIFF로 변환하는 스크립트.
- 카메라 파라미터: Hasselblad L2D-20c 기준 (초점거리 12.29mm, 4/3" 센서)
- 처리 흐름:
1. `mx15hdi_interpolation.csv`에서 위경도(DMS) 및 고도(feet) 로드
2. EPSG:4326 → EPSG:5187(한국 TM 좌표계) 변환
3. 핀홀 카메라 투영으로 픽셀 → 지상 좌표 매핑
4. GSD(지상 샘플 거리) 계산 및 출력 해상도 결정
5. 블렌딩 이미지 → `Georeference/Tif/{fileId}/*_gsd.tif`
6. 마스크 이미지 → `Georeference/Mask_Tif/{fileId}/*_gsd.tif`
- 결측 픽셀 보완: binary_dilation + cv2.dilate 2회 반복
---
### 3-10. `mx15hdi/Polygon/Scripts/Oilshape.py` — 유류 폴리곤 생성
마스크 GeoTIFF에서 유류 영역을 폴리곤으로 추출하여 Shapefile로 저장하는 스크립트.
- **`get_class_mask()`**: RGB 마스크를 유클리드 거리 기반으로 클래스 ID 마스크로 변환
- **`mask_to_polygons()`**: `cv2.findContours(RETR_CCOMP)`로 외곽선 추출, 홀(내부 폴리곤) 처리, `approxPolyDP`로 단순화
- **`save_polygons_to_shapefile()`**:
- 픽셀 좌표 → 지리 좌표 변환 (`rasterio.transform.xy`)
- Shapely Polygon 생성 + `.buffer(0)`으로 geometry 정규화
- 면적(m²), 두께(mm→m), 부피(m³), note 계산 후 GeoDataFrame 저장
- 출력: `Polygon/Shp/{fileId}/*.shp`
---
## 4. API 엔드포인트 상세
### `POST /run-script/`
전체 분석 파이프라인을 실행하고 결과를 반환하는 메인 엔드포인트.
**입력 (multipart/form-data)**
| 파라미터 | 타입 | 필수 | 설명 |
|----------|------|------|------|
| `camTy` | string | ✅ | 카메라 타입. `"mx15hdi"` 또는 `"starsafire"` |
| `fileId` | string | ✅ | 분석 세션 식별자 (파일 저장 폴더명으로 사용) |
| `image` | file | ✅ | 분석할 이미지 파일 (PNG/JPG) |
**처리 흐름**
1. `camTy` 유효성 검사
2. 이미지를 `{camTy}/Metadata/Image/Original_Images/{fileId}/`에 저장
3. GPS EXIF 검증 — GPS 없으면 `{"detail": "GPS Infomation Not Found"}` 반환
4. `Combine_module.py {fileId}` subprocess 실행 (타임아웃 300초)
5. `get_metadata()` + `get_oil_type()` 호출
**출력 (200 OK)**
```json
{
"meta": "filename,lat_d,lat_m,lat_s,...",
"data": [
{
"classId": "검정",
"area": 152.3,
"volume": 0.1523,
"note": "Black - Emulsion",
"thickness": 0.001,
"wkt": "POLYGON ((...))"
}
]
}
```
**에러 응답**
| 코드 | 조건 |
|------|------|
| 400 | camTy가 유효하지 않은 경우 |
| 404 | Combine_module.py 파일이 없는 경우 |
| 500 | subprocess 타임아웃 또는 기타 오류 |
---
### `GET /get-metadata/{camTy}/{fileId}`
이미 처리된 결과에서 메타데이터와 유류 정보를 조회하는 엔드포인트 (파이프라인 실행 없음).
**입력 (Path Parameters)**
| 파라미터 | 타입 | 설명 |
|----------|------|------|
| `camTy` | string | 카메라 타입 (`mx15hdi` / `starsafire`) |
| `fileId` | string | 분석 세션 식별자 |
**출력 (200 OK)**
`/run-script/`와 동일한 응답 구조.
```json
{
"meta": "filename,lat_d,lat_m,...",
"data": [ { "classId": "갈색", "area": ..., "wkt": "..." } ]
}
```
---
### `GET /get-original-image/{camTy}/{fileId}`
저장된 원본 이미지를 Base64 문자열로 반환하는 엔드포인트.
**입력 (Path Parameters)**
| 파라미터 | 타입 | 설명 |
|----------|------|------|
| `camTy` | string | 카메라 타입 |
| `fileId` | string | 분석 세션 식별자 |
**처리**: `{camTy}/Metadata/Image/Original_Images/{fileId}/` 디렉토리에서 `.png` 또는 `.jpg` 파일을 찾아 Base64 인코딩
**출력 (200 OK)**
```
"<base64 인코딩된 이미지 문자열>"
```
---
### `GET /get-image/{camTy}/{fileId}`
지리참조된 GeoTIFF를 PNG로 변환하여 좌표 경계(Bounding Box)와 함께 반환하는 엔드포인트.
**입력 (Path Parameters)**
| 파라미터 | 타입 | 설명 |
|----------|------|------|
| `camTy` | string | 카메라 타입 |
| `fileId` | string | 분석 세션 식별자 |
**처리**:
1. `{camTy}/Georeference/Tif/{fileId}/*.tif` 파일 탐색
2. rasterio로 CRS 및 Bounds 추출
3. CRS가 EPSG:4326이 아닌 경우 pyproj로 변환
4. raster 데이터를 PNG로 변환 후 Base64 인코딩
**출력 (200 OK)**
```json
{
"minLon": 126.123456,
"minLat": 34.123456,
"maxLon": 126.234567,
"maxLat": 34.234567,
"image": "<base64 인코딩된 PNG 문자열>"
}
```
---
### `POST /stitch`
여러 장의 드론/폰 사진을 스티칭하여 합성 이미지를 반환하는 엔드포인트.
**입력 (multipart/form-data)**
| 파라미터 | 타입 | 필수 | 설명 |
|----------|------|------|------|
| `files` | file[] | ✅ | 합성할 이미지 파일 목록 (최소 2장) |
| `fileId` | string | ✅ | 저장 디렉토리 식별자 |
**처리 흐름**
1. 파일 수 최소 2장 검증
2. 각 파일의 카메라 모델명 추출 (`check_camera_info`)
3. `stitch/{fileId}/` 디렉토리에 파일 저장 (파일명 형식: `{model}_{idx:03d}_{원본파일명}`)
4. 가장 많이 나온 카메라 모델을 `--model` 인자로 사용
5. `pic_gps.py --mode drone --input ... --out ... --model ... --enhance` subprocess 실행 (타임아웃 300초)
**출력 (200 OK)**
합성된 JPEG 이미지 파일 (`FileResponse`, `image/jpeg`)
**에러 응답**
| 코드 | 조건 |
|------|------|
| 400 | 이미지가 2장 미만인 경우 |
| 500 | 스티칭 subprocess 실패 |
---
## 5. 디렉토리 구조
```
prediction/image/
├── api.py FastAPI 서버 (포트 5001)
├── extract_data.py 결과 데이터 추출 유틸리티
├── dbInsert_csv.py CSV → PostgreSQL (현재 비활성화)
├── dbInsert_shp.py Shapefile → PostgreSQL (현재 비활성화)
├── pic_gps.py 이미지 스티칭 + GPS 보존
├── mx15hdi/ mx15hdi 카메라 처리 모듈
│ ├── Main/
│ │ └── Combine_module.py 파이프라인 오케스트레이터
│ ├── Detect/
│ │ ├── Inference.py MMSeg AI 세그멘테이션
│ │ ├── V7_SPECIAL.py 모델 설정
│ │ ├── epoch_165.pth 학습된 모델 가중치
│ │ └── mmsegmentation/ MMSegmentation 라이브러리
│ ├── Metadata/
│ │ ├── Image/Original_Images/ 업로드 이미지 저장소
│ │ ├── CSV/ 메타데이터 CSV 출력
│ │ └── Scripts/
│ │ └── Export_Metadata_mx15hdi.py
│ ├── Georeference/
│ │ ├── Tif/ 컬러 GeoTIFF 출력
│ │ ├── Mask_Tif/ 마스크 GeoTIFF 출력
│ │ └── Scripts/
│ │ └── Create_Georeferenced_Images_nadir.py
│ ├── Polygon/
│ │ ├── Shp/ Shapefile 출력
│ │ └── Scripts/
│ │ └── Oilshape.py
│ └── GSD/ GSD 중간 데이터
├── starsafire/ starsafire 카메라 처리 모듈 (구조 동일)
└── stitch/ 스티칭 결과 저장소
```
---
## 6. 주요 의존성
| 라이브러리 | 용도 |
|------------|------|
| fastapi, uvicorn | API 서버 |
| rasterio, pyproj, osgeo(GDAL) | 지리참조·좌표 변환 |
| geopandas, shapely | Shapefile 처리 |
| mmsegmentation, torch | AI 세그멘테이션 |
| paddleocr | HUD OCR (starsafire 전용) |
| opencv-contrib-python | 이미지 처리·스티칭 |
| Pillow, piexif | EXIF 메타데이터 |
| psycopg2 | PostgreSQL 연결 |
| pandas, numpy | 데이터 처리 |