GIS나 데이터를 다루다 보면 DSM, DEM, DTM이라는 용어가 계속 튀어나온다. 얼핏 비슷해 보이지만 실제로는 쓰임새가 꽤 다르다. 이번 글에서는 세 가지 개념의 차이와 함께, DSM에서 DTM을 어떻게 만들어내는지 그 원리까지 한 번에 정리해보려고 한다. 레츠꼬 🏔️
1. 세 가지 모델 한눈에 비교
먼저 큰 그림부터 잡고 가자. 세 모델 모두 지표면의 고도(elevation) 정보를 담은 래스터 데이터인데, 무엇을 기준으로 고도를 측정하느냐가 다르다.
| 모델 | 한국어 명칭 | 측정 대상 | 건물/식생 포함 |
| DSM | 수치표면모델 | 지표면 위 모든 객체의 최상단 | ✅ 포함 |
| DEM | 수치표고모델 | 지표면 고도 (광의의 개념) | 경우에 따라 다름 |
| DTM | 수치지형모델 | 나지(裸地) 지면만의 고도 | ❌ 제거됨 |

참고로 DEM은 포괄적인 용어로 쓰이는 경우가 많아서, DSM과 DTM을 모두 DEM이라고 부르는 경우도 있다. 문맥에 따라 의미가 달라질 수 있으니 주의가 필요하다.
2. DSM (Digital Surface Model) — 수치표면모델
DSM은 지표면 위에 존재하는 모든 것의 최상단 고도를 기록한 모델이다.
건물 옥상, 나무 꼭대기, 교량 위 — 이 모든 게 포함된다. 드론으로 촬영한 사진을 포토그래메트리 처리하면 기본적으로 DSM이 나온다고 이해하면 된다.
주요 특징
- 드론, 항공 LiDAR, 위성 데이터로 생성
- 건물·식생·인공구조물의 높이 정보가 살아있음
- 도시 3D 모델링, 일조량 분석, 가시권 분석, 태양광 입지 분석 등에 활용

3. DTM (Digital Terrain Model) — 수치지형모델
DTM은 건물, 나무 같은 지상물을 전부 제거한 순수 지면의 고도만 담은 모델이다.
"땅 자체가 얼마나 높은가"를 보고 싶을 때 쓴다. LiDAR 데이터에서 포인트 클라우드를 분류해 ground 포인트만 남기면 DTM이 만들어지는데, 이 과정을 Ground Filtering이라고 한다.
주요 특징
- 지상물 필터링(Ground Filtering) 과정이 필요
- 홍수 시뮬레이션, 유역 분석, 경사도 분석, 산사태 위험도 분석에 활용
- DSM에서 DTM을 빼면 → nDSM(정규화 수치표면모델)을 구할 수 있음 (건물·식생 높이만 추출)

4. DEM (Digital Elevation Model) — 수치표고모델
DEM은 사실 DSM과 DTM을 모두 포괄하는 상위 개념이다.
좁은 의미로는 DTM과 거의 동의어로 쓰이고, 넓은 의미로는 고도 정보를 담은 모든 래스터 데이터를 DEM이라고 부르기도 한다. 그래서 헷갈리는 거다 ㅎㅎ
정리해보면 아래처럼 이해하면 된다.
| 상황 | DEM의 의미 |
| 논문/공식 문서 | DSM, DTM을 포괄하는 상위 개념 |
| GIS 실무 | DTM과 거의 동의어 (지면 고도 데이터) |
| SRTM, ASTER 데이터 | 실질적으로는 DSM에 가까움 (식생·건물 반영) |

5. Ground Filtering — DSM에서 DTM 만드는 원리
DSM 데이터를 갖고 있을 때, Ground Filtering 과정을 거치면 DTM을 만들어낼 수 있다.
쉽게 말하면 포인트 클라우드에서 "이건 건물이다", "이건 나무다"를 판별해서 걷어내고, 진짜 땅 포인트만 남기는 작업이다.
대표적인 알고리즘은 아래 세 가지다.
| 알고리즘 | 원리 및 특징 |
| PMF (Progressive Morphological Filter) |
모폴로지 연산으로 점점 큰 구조 요소를 적용해 비지면 포인트를 제거. 평탄한 지형에서 효과적. |
| CSF (Cloth Simulation Filter) |
천(Cloth)을 하늘에서 뒤집어 지형 위에 떨어뜨리는 물리 시뮬레이션으로 지면을 추정. 직관적이고 파라미터 조정이 쉬움. |
| SMRF (Simple Morphological Filter) |
PMF를 단순화한 버전. 경사가 있는 지형에서도 비교적 안정적인 결과를 냄. |

Python에서는 laspy + PDAL 조합이나, CloudCompare 같은 GUI 툴로 Ground Filtering을 적용할 수 있다. QGIS에서는 LAStools 플러그인의 lasground 툴을 쓰면 된다.
CSF 필터를 PDAL로 적용하는 예시는 아래와 같다.
import pdal
import json
pipeline_def = {
"pipeline": [
"input.las",
{
"type": "filters.csf", # Cloth Simulation Filter
"resolution": 1.0, # 천의 해상도 (m) — 낮을수록 세밀
"rigidness": 2, # 천의 강성도 (1~3, 높을수록 평탄)
"threshold": 0.5, # 지면 판별 임계값 (m)
"time_step": 0.65,
"iterations": 500,
"classify": True, # 포인트에 분류 코드 부여
"returns": "last,only"
},
{
"type": "filters.range",
"limits": "Classification[2:2]" # ground 포인트(코드 2)만 추출
},
{
"type": "writers.las",
"filename": "ground_only.las"
}
]
}
pipeline = pdal.Pipeline(json.dumps(pipeline_def))
pipeline.execute()
print("Ground filtering 완료!")
resolution과 threshold 값은 지형 특성에 따라 조정이 필요하다. 도심처럼 건물이 빽빽한 지역은 threshold를 낮게, 산지처럼 식생이 울창한 곳은 resolution을 높여가며 테스트해보는 게 좋다.
6. nDSM — 건물/식생 높이만 뽑아내기
DSM과 DTM을 모두 갖고 있다면 아래 수식 하나로 건물이나 나무의 실제 높이를 구할 수 있다.
# nDSM = DSM - DTM
# nDSM: normalized Digital Surface Model (정규화 수치표면모델)
import rasterio
import numpy as np
with rasterio.open("dsm.tif") as dsm_src:
dsm = dsm_src.read(1)
profile = dsm_src.profile
with rasterio.open("dtm.tif") as dtm_src:
dtm = dtm_src.read(1)
ndsm = dsm - dtm
ndsm = np.where(ndsm < 0, 0, ndsm) # 음수 제거 (좌표계 불일치 등으로 발생 가능)
profile.update(dtype=rasterio.float32)
with rasterio.open("ndsm.tif", "w", **profile) as dst:
dst.write(ndsm.astype(rasterio.float32), 1)
print("nDSM 생성 완료!")
결과로 나온 nDSM에서 픽셀값이 곧 지면으로부터의 높이(m)가 된다. 건물 높이 추출, 수목 높이 분석, 태양광 패널 설치 가능 면적 산정 등에 바로 활용 가능하다.
단, DSM과 DTM의 해상도와 좌표계가 일치해야 정확한 결과가 나온다는 점을 꼭 확인하자.
7. 정리 — 어떤 상황에 뭘 써야 할까?
헷갈릴 때는 이것만 기억하면 된다.
① 전체적인 지형 고도 데이터를 포괄적으로 다룰 때 → DEM
DTM·DSM을 포함하는 개념으로, 데이터 출처가 명확하지 않거나 단순 고도 표현/기초 분석 단계에서 사용한다.
② 지형 자체를 분석할 때 → DTM
경사, 향, 유역, 홍수 침수 시뮬레이션, 산사태 위험도 등 순수 지형 분석에는 DTM이 맞다.
③ 지표 위 모든 객체의 높이가 필요할 때 → DSM
일조량 분석, 가시권 분석, 3D 도시 모델, 태양광 입지 분석에는 DSM을 써야 정확하다.
④ 건물·나무 높이만 따로 뽑고 싶을 때 → nDSM (DSM - DTM)
두 데이터를 빼기만 하면 바로 구할 수 있다. (해상도/정렬/좌표계 일치)
⑤ DSM에서 DTM을 직접 만들어야 할 때 → Ground Filtering
CSF, PMF, SMRF 알고리즘 중 지형 특성에 맞는 걸 골라 적용하면 된다.
DSM, DEM, DTM은 GIS·리모트센싱 분야에서 워낙 자주 나오는 개념이라 처음에 확실히 잡아두면 도움이 될거다. 이상 끝! 🏔️