지난번에는 스트리트뷰 이미지를 딥러닝으로 분할해서 도로, 보도, 자동차, 나무를 구분하고, 그중 식생 픽셀 비율로 녹시율(GVI)을 계산해 봤다. 이번에는 한 장 한 장 이미지 단위로 끝내는 게 아니라, 좌표와 함께 모아서 지도 위에 시각화 해보려고 한다.
즉 “보행자가 체감하는 도시 녹시율”을 한눈에 보여주는 지도를 만드는 거다. 레츠꼬 🌳🗺️
이전 게시글 보기 👉
https://happy-metamong.tistory.com/3
스트리트뷰 이미지에서 도로/보도/자동차/나무 의미론적 분할해보기🌳🚗 [딥러닝 Semantic segmenta
얼마 전엔 따릉이 데이터로 지도 시각화를 했는데, 이번엔 조금 색다른 걸 해보려 한다.스트리트 뷰 이미지를 가져와서 도로, 보도, 자동차, 나무, 건축물 이런 요소들을 이미지에서 분할하는 거
happy-metamong.tistory.com
1. 데이터 준비하기
녹시율을 지도에 그리려면 스트리트뷰 이미지 + 좌표 정보가 필요하다. 먼저, 시각화하고 싶은 영역의 도로 라인이 필요하므로 V-World에서 도로중심선 정보를 다운받아준다.

shp 파일을 불러와 필요한 구간만 추출해 준다. 나는 예시로 잠실 석촌호수와 방이동 근처만 시각화 해보려고 한다.

그다음 포인트 생성 도구로 도로망을 따라 일정 간격(10m)으로 포인트를 생성한다. 이 포인트는 스트리트뷰 이미지를 매칭할 기준 좌표가 된다.

이제 이 좌표에 해당하는 스트리트뷰 이미지를 다운받으면 되는데, 원래는 구글 스트리트뷰 API를 사용하려고 했으나.. 얼마나 나올지 과금이 두려워 (전에 과제하면서 구글 API 썼다가 10만 원이 나왔다..!) 그냥 카카오맵에서 내가 캡처했다. 약간의 노다가는 정신을 맑게 해 준다ㅎㅎ 무튼 아래처럼 녹시율을 계산할 스트리트뷰 이미지를 준비하면 된다.

2. 녹시율 (Green View Index) 계산
이제 준비된 스트리트뷰 이미지를 딥러닝 모델에 넣어서 녹시율(Green View Index, GVI)을 계산해 준다.
여기서는 지난번에 사용했던 SegFormer (nvidia/segformer-b4-finetuned-ade-512-512) 모델을 그대로 활용한다. ADE20K 데이터셋으로 학습된 모델이라 도로·보도·차량·건물·식생 같은 도시 장면을 잘 분할한다.
녹시율 계산 과정은 간단하다:
- 이미지를 모델에 입력해 픽셀 단위 분할 결과 획득
- 클래스 중 tree, plant, grass 픽셀만 필터링
- 전체 픽셀 대비 식생 픽셀 비율(%) 계산
- 좌표와 매칭 후 ID, 위도/경도, GVI 값을 CSV로 저장
# from transformers import SegformerFeatureExtractor, SegformerForSemanticSegmentation
# from PIL import Image
# import torch
# import matplotlib.pyplot as plt
# import numpy as np
import pandas as pd
import os
# 1. 경로설정
INPUT_CSV = "gis data/잠실_도로중심선_part2_point.csv" # 원본 CSV
OUTPUT_CSV = "gis data/잠실_도로중심선_part2_point_GVI.csv" # 결과 CSV
ID_COL = "ID" # ID 컬럼명
IMAGE_DIR = "raw data" # 이미지 폴더
IMAGE_EXT = ".png" # 확장자
# 2. 유니크 ID만 GVI 계산 → CSV에 조인
def compute_gvi(image_path: str) -> float:
"""단일 이미지의 GVI(%) 계산. 실패 시 NaN."""
try:
img = Image.open(image_path).convert("RGB")
except Exception as e:
print(f"[WARN] 이미지 열기 실패: {image_path} ({e})")
return np.nan
ow, oh = img.size
with torch.no_grad():
_inputs = feature_extractor(images=img, return_tensors="pt")
_outputs = model(**_inputs)
_pred = _outputs.logits.argmax(dim=1)[0].cpu().numpy()
_pred_resized = Image.fromarray(_pred.astype(np.uint8)).resize((ow, oh), Image.NEAREST)
_pred_np = np.array(_pred_resized)
_green_mask = np.isin(_pred_np, green_ids)
g = float(_green_mask.sum())
t = float(_pred_np.size)
return (g / t * 100.0) if t > 0 else np.nan
# 3. CSV에서 유니크 ID만 추출
df = pd.read_csv(INPUT_CSV)
unique_ids = pd.Series(df[ID_COL].astype(str).unique())
# 4. GVI 계산
gvi_records = []
for sid in unique_ids:
img_path = os.path.join(IMAGE_DIR, f"{sid}{IMAGE_EXT}")
gvi = compute_gvi(img_path)
gvi_records.append({"_ID_str": sid, "GVI": round(gvi, 2) if pd.notna(gvi) else np.nan})
gvi_df = pd.DataFrame(gvi_records)
# 5. 원본 df에 ID로 조인(타입 혼동 방지)
df["_ID_str"] = df[ID_COL].astype(str)
df = df.merge(gvi_df, on="_ID_str", how="left").drop(columns=["_ID_str"])
# 6. 저장
df.to_csv(OUTPUT_CSV, index=True, encoding="utf-8")
녹시율이 제대로 생성되었는지 몇 개만 예시로 보면, 첫 번째 뷰는 녹시율 0.00%, 두 번째 뷰는 49.21%로 계산이 아주 잘 된 걸 볼 수 있다.

3. QGIS에서 시각화하기
좌표(lat, lon)와 각 지점별 GVI 값이 붙었으니 이제 QGIS에서 불러와 시각화하면된다. 좌표가 있는 CSV 파일 불러오는 방법은 데이터 소스 관리자에서 "구분자로 분리된 텍스트 레이어 추가" 로 가져오면 된다.

짜잔. 불러와진 모습. 이제 레이어 스타일만 주면 된다.

어두운 배경지도에 단계구분도로 GVI 색상을 줬다. 시각화는 간단해서 뭐 설명할 게 없다.

인쇄 조판에서 축척이랑 범례를 넣어준다. 제목이랑 범례는 포토샵 가서 넣어주겠다.

짜잔 진짜 완성!
이렇게 해서 잠실 일대 스트리트뷰 녹시율 지도를 만들어봤다. Mit senseable city lab에서 예전에 Treepedia라는 프로젝트로 전 세계 도시 녹시율을 비교했었는데, 나는 좀 더 로컬하게 동네 단위로 계산해 본 셈이다. 보행자의 시선에서 본 체감 녹지를 이렇게 지도 위에 표현할 수 있다는 게 재밌다. 지역별로 비교하거나 아니면 NDVI 같은 지표와 비교해 보면 더 흥미로운 결과가 나올 것 같다. 끝! 🌳🗺️

'Moments > GIS' 카테고리의 다른 글
| SGIS 격자 데이터로 전국 인구 분포 인포그래픽 지도 만들기 🗺️ [격자 데이터 다운로드/폴리곤 중심점 생성] (0) | 2025.11.10 |
|---|---|
| [QGIS] 모델 설계자(Model builder)를 활용하여 지하철역 반경 500m 내 공시지가 계산하기 ⚙️ (0) | 2025.09.22 |
| [네트워크 거리/Iso Map] 지하철역 접근성 Isochrone 분석 👣 + QNEAT3 오류 해결 (0) | 2025.09.05 |
| 사람들이 따릉이타고 어디로 가는지는 오디(OD) 데이터로! 🚲 [서울시 자전거 데이터 시각화] (1) | 2025.08.30 |
| 따릉이를 제일 많이 타는 동네는 어디일까? 🚲 [서울시 자전거 데이터 시각화] (2) | 2025.08.29 |