Crestline - skyline & inner ridgeline detection for mountain photos

In french : S.D.E.C.E. - Système de Détection de l’Emplacement par Comparaison des Escarpements - nom pris au hasard

Draws the skyline and the inner ridgelines (a nearer ridge silhouetted against farther terrain) on any mountain photo.

Crestline on a photo of the Mont Blanc massif: skyline in green, inner ridgelines in cyan

Method

  • Labels come from 3D terrain, not from hand drawing. We took 6,286 photos from smapshot, whose camera poses were aligned by volunteers on a 3D model of Switzerland.
  • From each pose we ray-traced a depth map in the Swiss elevation model (swissALTI3D 2 m, plus swissALTIRegio far away, with Earth curvature and refraction).
  • Skyline = where rays stop hitting terrain. Inner ridgelines = sharp depth jumps (near terrain occluding terrain ≥ 25 % and ≥ 50 m farther).
  • Each pose was refined by a small rotation (≤ 0.6°) to snap terrain lines onto image edges. Photos whose rendered skyline did not match the photo were discarded.
  • Ignored during training:
    • trees, buildings and people;
    • clouds hiding the terrain;
    • ridges with no visible edge in the photo.
  • Model: U-Net with a ConvNeXt-Base encoder (ImageNet-22k), 3 outputs (sky, skyline, inner ridges), trained 30 epochs on one RTX 5090 (~25 min).

Results

Per-column skyline error between the predicted and true sky boundary, in pixels at ~1024 px width.

  • CH1 (203 photos of the Swiss Alps, skylines corrected by hand, ETH Zurich) is fully independent of our pipeline. It is the reference.
  • smapshot-test (588 photos) comes from geographic areas never seen in training (10 km cells), with labels from our pipeline.
Model CH1 mean error CH1 ≤ 2 px smapshot-test mean error smapshot-test ≤ 2 px Inner-ridge F1
Crestline (this model) 1.1 px 91.2 % 2.2 px 82.0 % 0.35
SegFormer-B5 (ADE20K, sky class) 2.7 px 77.6 % 8.0 px 69.3 % —
Mask2Former Swin-L (ADE20K, sky class) 2.6 px 68.2 % 4.1 px 66.8 % —
Same pipeline trained on GeoPose3K (ConvNeXt-Large) 4.7 px 74.5 % 11.4 px 64.2 % 0.08

Public segmenters are already good on clear photos (median 1-2 px). They fail on clouds, snow against a white sky, haze and backlight. That is where this model gains most: its mean error is about 2.4× lower than theirs on CH1. No public model detects inner ridgelines; this is also where Crestline has the most room to improve.

Why not GeoPose3K?

We first trained on GeoPose3K (40 GB, 3,111 photos rendered from a DEM). Its labels are not precise enough:

  • the DEM is 24-30 m and most poses are semi-automatic, so rendered ridges are often off by tens of pixels, especially on terrain closer than ~3 km. A 30 m DEM cell seen from 500 m is already ~3.4°;
  • the same network trained on it is clearly worse (table above), and nearly blind to inner ridges (F1 0.08).

Swapping the label source (hand-aligned poses, 2 m DEM) mattered far more than model size: a ConvNeXt-Large brought no gain.

Usage

from PIL import Image
import crestline  # crestline.py from this repository

model, config = crestline.load("LPN64/crestline-convnext-base")
image, prob = crestline.predict(model, Image.open("photo.jpg"))       # prob: sky, skyline, inner ridges
skyline_rows, ridges = crestline.lines(prob, config["ridge_threshold"])  # 0.4
crestline.overlay(image, skyline_rows, ridges).save("lines.jpg")

More examples (holiday photos, Tour des Fiz, Haute-Savoie)

Limitations

  • Trained on Swiss Alps photos (1900s to 2020s, many black-and-white). Expect lower quality on very different terrain.
  • Inner ridgelines are recall-limited (F1 ≈ 0.35 on smapshot-test): faint ridges in haze are often missed.
  • Summits hidden in clouds: the skyline follows the visible boundary, which may be the cloud.

License and attribution

Weights released under CC BY-SA 4.0, because most training photos are CC BY-SA 4.0.

Training data licences, stated plainly. The 6,286 training photos are:

Licence Photos Share
CC BY-SA 4.0 (ETH-Bibliothek Zürich, Bildarchiv) 3,672 58 %
swisstopo ("Quelle: swisstopo") 1,666 27 %
No explicit licence in the source metadata (ETH-Bibliothek Zürich, Bildarchiv) 918 15 %
Public domain 30 < 1 %

The 918 photos without an explicit licence come from the same public archive. ETH-Bibliothek publishes its images under CC BY-SA 4.0 or as public domain, but the licence field of these particular records was empty. No photo is redistributed in this repository, only the trained weights. If you are a rights holder and object to this use, please open a discussion on this repository and we will retrain without the concerned images.

Attribution:

  • Labels derived from swissALTI3D / swissALTIRegio © swisstopo.
  • Photos: ETH-Bibliothek Zürich, Bildarchiv and swisstopo.
  • Camera poses: smapshot (HEIG-VD, ETH-Bibliothek, swisstopo) and its volunteer contributors.
  • Evaluation: CH1 (Baatz, Saurer, Köser, Pollefeys, ETH Zurich). Evaluation only, no image redistributed.
  • The holiday photos in this card belong to the repository author.
Downloads last month
14
Safetensors
Model size
92.7M params
Tensor type
BF16
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for LPN64/crestline-convnext-base

Finetuned
(2)
this model