- 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 타입 오류 수정
15 KiB
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
- mx15hdi:
- 헤더 이후 첫 번째 데이터 행을 쉼표로 이어 문자열 반환
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필드(초 없음)
- mx15hdi:
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 - 다음 스크립트를 순서대로 실행:
../Detect/Inference.py../Metadata/Scripts/Export_Metadata_mx15hdi.py../Georeference/Scripts/Create_Georeferenced_Images_nadir.py../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클래스)
- 처리 흐름:
Original_Images/{fileId}/내 이미지 파일 열거inference_segmentor()로 세그멘테이션 맵 생성- 팔레트 기반 컬러 마스크 생성 (alpha=0.6 블렌딩)
- 블렌딩 이미지 →
Detect/result/{fileId}/ - 컬러 마스크 이미지 →
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" 센서)
- 처리 흐름:
mx15hdi_interpolation.csv에서 위경도(DMS) 및 고도(feet) 로드- EPSG:4326 → EPSG:5187(한국 TM 좌표계) 변환
- 핀홀 카메라 투영으로 픽셀 → 지상 좌표 매핑
- GSD(지상 샘플 거리) 계산 및 출력 해상도 결정
- 블렌딩 이미지 →
Georeference/Tif/{fileId}/*_gsd.tif - 마스크 이미지 →
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) |
처리 흐름
camTy유효성 검사- 이미지를
{camTy}/Metadata/Image/Original_Images/{fileId}/에 저장 - GPS EXIF 검증 — GPS 없으면
{"detail": "GPS Infomation Not Found"}반환 Combine_module.py {fileId}subprocess 실행 (타임아웃 300초)get_metadata()+get_oil_type()호출
출력 (200 OK)
{
"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/와 동일한 응답 구조.
{
"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 | 분석 세션 식별자 |
처리:
{camTy}/Georeference/Tif/{fileId}/*.tif파일 탐색- rasterio로 CRS 및 Bounds 추출
- CRS가 EPSG:4326이 아닌 경우 pyproj로 변환
- raster 데이터를 PNG로 변환 후 Base64 인코딩
출력 (200 OK)
{
"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 | ✅ | 저장 디렉토리 식별자 |
처리 흐름
- 파일 수 최소 2장 검증
- 각 파일의 카메라 모델명 추출 (
check_camera_info) stitch/{fileId}/디렉토리에 파일 저장 (파일명 형식:{model}_{idx:03d}_{원본파일명})- 가장 많이 나온 카메라 모델을
--model인자로 사용 pic_gps.py --mode drone --input ... --out ... --model ... --enhancesubprocess 실행 (타임아웃 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 | 데이터 처리 |