import io import base64 import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import numpy as np from PIL import Image from flask import Blueprint, jsonify, request bp = Blueprint('water_body_segmentation', __name__, url_prefix='/api/water_body_segmentation') # DeepLabV3 모델 (나중에 실제 모델로 교체) # from app.AI_modules.DeepLabV3 import create_model, load_model deeplabv3_model = None def generate_satellite_image(lat, lng): """위성 이미지 시뮬레이션 생성 (INPUT)""" fig, ax = plt.subplots(figsize=(8, 8)) np.random.seed(int((lat + lng) * 1000) % 10000) # 배경 (녹지/농경지) terrain = np.random.rand(100, 100) * 0.3 + 0.3 # 도로/경계선 추가 for i in range(5): start = np.random.randint(0, 100) terrain[start:start+2, :] = 0.9 terrain[:, start:start+2] = 0.9 # 물 영역 생성 (불규칙한 형태) y, x = np.ogrid[:100, :100] # 주요 수역 1 (호수/저수지) center1_x, center1_y = 35 + np.random.randint(-5, 5), 50 + np.random.randint(-5, 5) dist1 = np.sqrt((x - center1_x)**2 + (y - center1_y)**2) water_mask1 = dist1 < (15 + np.random.rand(100, 100) * 5) # 주요 수역 2 (강/하천) river_path = 60 + np.sin(np.linspace(0, 4*np.pi, 100)) * 10 for i in range(100): width = 3 + int(np.random.rand() * 3) river_col = int(river_path[i]) if 0 <= river_col < 100: terrain[i, max(0, river_col-width):min(100, river_col+width)] = 0.2 terrain[water_mask1] = 0.15 + np.random.rand(np.sum(water_mask1)) * 0.1 # 녹색 채널 강조 (위성 이미지처럼) rgb_image = np.zeros((100, 100, 3)) rgb_image[:, :, 0] = terrain * 0.4 + 0.1 # R rgb_image[:, :, 1] = terrain * 0.8 + 0.1 # G (녹지 강조) rgb_image[:, :, 2] = terrain * 0.3 + 0.1 # B # 물 영역은 파란색으로 water_full_mask = (terrain < 0.3) rgb_image[water_full_mask, 0] = 0.1 rgb_image[water_full_mask, 1] = 0.3 rgb_image[water_full_mask, 2] = 0.5 ax.imshow(rgb_image, extent=[lng-0.05, lng+0.05, lat-0.05, lat+0.05]) ax.set_xlabel(f'경도 (Longitude)', fontsize=10) ax.set_ylabel(f'위도 (Latitude)', fontsize=10) ax.set_title(f'위성 이미지 - ({lat:.4f}, {lng:.4f})', fontsize=12) plt.tight_layout() buf = io.BytesIO() fig.savefig(buf, format='png', dpi=100, bbox_inches='tight') buf.seek(0) image_base64 = base64.b64encode(buf.getvalue()).decode('utf-8') plt.close(fig) return image_base64, water_full_mask def generate_segmentation_image(lat, lng, water_mask): """Water Body 세그멘테이션 결과 생성 (OUTPUT)""" fig, ax = plt.subplots(figsize=(8, 8)) np.random.seed(int((lat + lng) * 1000) % 10000) # 배경 (원본 이미지 흐리게) terrain = np.random.rand(100, 100) * 0.3 + 0.3 # 도로/경계선 for i in range(5): start = np.random.randint(0, 100) terrain[start:start+2, :] = 0.9 terrain[:, start:start+2] = 0.9 # RGB 이미지 생성 (흐린 배경) rgb_image = np.zeros((100, 100, 3)) rgb_image[:, :, 0] = terrain * 0.5 + 0.3 rgb_image[:, :, 1] = terrain * 0.6 + 0.3 rgb_image[:, :, 2] = terrain * 0.5 + 0.3 # Water Body를 밝은 파란색/보라색으로 하이라이트 rgb_image[water_mask, 0] = 0.4 rgb_image[water_mask, 1] = 0.5 rgb_image[water_mask, 2] = 0.95 # 경계선 추가 from scipy import ndimage edges = ndimage.binary_dilation(water_mask) & ~water_mask rgb_image[edges, 0] = 1.0 rgb_image[edges, 1] = 1.0 rgb_image[edges, 2] = 1.0 ax.imshow(rgb_image, extent=[lng-0.05, lng+0.05, lat-0.05, lat+0.05]) ax.set_xlabel(f'경도 (Longitude)', fontsize=10) ax.set_ylabel(f'위도 (Latitude)', fontsize=10) ax.set_title(f'Water Body 추출 결과', fontsize=12) # 범례 추가 from matplotlib.patches import Patch legend_elements = [Patch(facecolor='#6680f2', edgecolor='white', label='Water Body')] ax.legend(handles=legend_elements, loc='upper right') plt.tight_layout() buf = io.BytesIO() fig.savefig(buf, format='png', dpi=100, bbox_inches='tight') buf.seek(0) image_base64 = base64.b64encode(buf.getvalue()).decode('utf-8') plt.close(fig) # 면적 계산 (시뮬레이션) water_pixel_count = np.sum(water_mask) total_pixels = water_mask.size water_ratio = (water_pixel_count / total_pixels) * 100 return image_base64, water_pixel_count, water_ratio @bp.route('/extract', methods=['GET']) def extract(): """Water Body 추출 결과 반환""" try: lat = float(request.args.get('lat', 34.6047)) lng = float(request.args.get('lng', 127.2855)) radius = float(request.args.get('radius', 5)) # 입력 이미지 생성 input_image, water_mask = generate_satellite_image(lat, lng) # 출력 이미지 생성 output_image, water_pixels, water_ratio = generate_segmentation_image(lat, lng, water_mask) # 면적 계산 (시뮬레이션: 1 pixel = 약 100m²) area_m2 = water_pixels * 100 water_body_count = np.random.randint(2, 8) return jsonify({ 'status': 'success', 'message': 'Water Body 추출 완료', 'input_image': input_image, 'output_image': output_image, 'data': { 'area': f'{area_m2:,} ㎡', 'ratio': f'{water_ratio:.1f}%', 'count': water_body_count, 'lat': lat, 'lng': lng } }) except Exception as e: return jsonify({ 'status': 'error', 'message': str(e), 'input_image': None, 'output_image': None }) def simulate_water_segmentation(image_array): """ Water Body 세그멘테이션 시뮬레이션 (실제 DeepLabV3 모델로 교체 예정) """ height, width = image_array.shape[:2] # 파란색 계열 검출 (간단한 색상 기반 검출) if len(image_array.shape) == 3: # RGB -> HSV 변환 대신 간단한 파란색 검출 r = image_array[:, :, 0].astype(float) g = image_array[:, :, 1].astype(float) b = image_array[:, :, 2].astype(float) # 파란색/청록색 영역 검출 blue_ratio = b / (r + g + b + 1e-6) green_blue_diff = np.abs(g - b) / 255.0 # 물로 추정되는 영역 (파란색 비율이 높고, 전체적으로 어두운 영역) brightness = (r + g + b) / 3.0 water_mask = (blue_ratio > 0.35) | ((brightness < 100) & (b > r)) # 노이즈 제거를 위한 morphological operation 시뮬레이션 from scipy import ndimage water_mask = ndimage.binary_opening(water_mask, iterations=2) water_mask = ndimage.binary_closing(water_mask, iterations=2) else: # 그레이스케일 이미지 water_mask = image_array < 100 return water_mask def generate_prediction_overlay(original_image, water_mask): """ 원본 이미지에 Water Body 예측 결과 오버레이 """ fig, ax = plt.subplots(figsize=(8, 8)) # 원본 이미지 표시 ax.imshow(original_image) # Water Body 마스크를 반투명 파란색으로 오버레이 overlay = np.zeros((*water_mask.shape, 4)) overlay[water_mask, 0] = 0.2 # R overlay[water_mask, 1] = 0.5 # G overlay[water_mask, 2] = 1.0 # B overlay[water_mask, 3] = 0.5 # Alpha ax.imshow(overlay) # 경계선 추가 from scipy import ndimage edges = ndimage.binary_dilation(water_mask, iterations=2) & ~water_mask edge_overlay = np.zeros((*water_mask.shape, 4)) edge_overlay[edges, 0] = 0.0 edge_overlay[edges, 1] = 1.0 edge_overlay[edges, 2] = 1.0 edge_overlay[edges, 3] = 1.0 ax.imshow(edge_overlay) ax.axis('off') ax.set_title('Water Body Detection Result', fontsize=14, fontweight='bold') # 범례 from matplotlib.patches import Patch legend_elements = [ Patch(facecolor='#3380ff', alpha=0.5, edgecolor='cyan', label='Water Body') ] ax.legend(handles=legend_elements, loc='upper right', fontsize=10) plt.tight_layout() buf = io.BytesIO() fig.savefig(buf, format='png', dpi=100, bbox_inches='tight') buf.seek(0) image_base64 = base64.b64encode(buf.getvalue()).decode('utf-8') plt.close(fig) return image_base64 @bp.route('/predict', methods=['POST']) def predict(): """ 업로드된 이미지에서 Water Body 추출 POST /api/water_body_segmentation/predict Form Data: image (file) """ try: # 이미지 파일 확인 if 'image' not in request.files: return jsonify({ 'status': 'error', 'message': '이미지 파일이 필요합니다.' }), 400 file = request.files['image'] if file.filename == '': return jsonify({ 'status': 'error', 'message': '파일이 선택되지 않았습니다.' }), 400 # 이미지 로드 image = Image.open(file.stream) if image.mode != 'RGB': image = image.convert('RGB') # numpy 배열로 변환 image_array = np.array(image) # Water Body 세그멘테이션 (현재는 시뮬레이션) # TODO: 실제 DeepLabV3 모델로 교체 # if deeplabv3_model is not None: # water_mask = run_deeplabv3_prediction(deeplabv3_model, image_array) # else: water_mask = simulate_water_segmentation(image_array) # 결과 이미지 생성 output_image = generate_prediction_overlay(image_array, water_mask) # 통계 계산 water_pixel_count = np.sum(water_mask) total_pixels = water_mask.size water_ratio = (water_pixel_count / total_pixels) * 100 # 연결된 영역 수 계산 (Water Body 개수) from scipy import ndimage labeled_array, num_features = ndimage.label(water_mask) # 면적 추정 (픽셀 기반, 실제 스케일은 이미지 메타데이터 필요) # 가정: 1 pixel = 약 1 m² (해상도에 따라 조정 필요) pixel_area = 1.0 # m²/pixel estimated_area = water_pixel_count * pixel_area # 신뢰도 (시뮬레이션) confidence = min(95.0, 70.0 + water_ratio * 0.5) return jsonify({ 'status': 'success', 'message': 'Water Body 추출 완료', 'output_image': output_image, 'data': { 'area': f'{estimated_area:,.0f} ㎡', 'ratio': f'{water_ratio:.1f}%', 'count': num_features, 'confidence': f'{confidence:.1f}%', 'pixels': int(water_pixel_count) } }) except Exception as e: import traceback traceback.print_exc() return jsonify({ 'status': 'error', 'message': str(e) }), 500 @bp.route('/load-model', methods=['POST']) def load_deeplabv3_model(): """ DeepLabV3 모델 로드 POST /api/water_body_segmentation/load-model JSON: { "model_path": "path/to/model.h5" } """ global deeplabv3_model try: data = request.get_json() model_path = data.get('model_path') if not model_path: return jsonify({ 'status': 'error', 'message': '모델 경로가 필요합니다.' }), 400 # TODO: 실제 모델 로드 # from app.AI_modules.DeepLabV3 import load_model # deeplabv3_model = load_model(model_path) return jsonify({ 'status': 'success', 'message': f'모델 로드 완료: {model_path}' }) except Exception as e: return jsonify({ 'status': 'error', 'message': str(e) }), 500