What was compared
Classical computer vision, SAM2 and MobileSAM, an open-source segmentation model with Apache-2.0 weights. SAM2 was disqualified: about 115 seconds an image on a CPU, and it segmented roads as readily as roofs.
Roof detection CV
Custom GPU-trained computer-vision models that detect roofs from aerial imagery and automate solar system design, trained across entire cities. Turns raw imagery into roof outlines, including internal edges and geometry. It runs inside the company’s in-house solar design and quoting tool, where it draws the roof when no other roof data exists for an address, and staff check and refine the outline it draws.
Every solar design starts from a roof outline
No outline, no design. Panels are laid out on roof faces, and production, price and payback all follow from that layout. The design tool’s automatic mode relied on a third-party solar API for its roof faces, and that API has no coverage for many newer estates and regional addresses. For those, automatic design simply stopped.
The address is not the roof. A geocoded address often lands on the street or the kerb rather than the house, as I found on real Australian addresses. A detector that trusts the point outlines the road, so this one searches the neighbourhood around it for the dwelling.
The goal. Get a usable outline from the aerial photo alone, fast enough to sit in the middle of a quote, and honest about what a single photo cannot tell you.
A bake-off on real Australian roofs
Three approaches went head to head on six real Australian addresses, each photographed by two imagery sources: twelve scene pairs. The bar was decision-level: the right dwelling found, with a tight outline, starting from the geocoded address point.
Classical computer vision, SAM2 and MobileSAM, an open-source segmentation model with Apache-2.0 weights. SAM2 was disqualified: about 115 seconds an image on a CPU, and it segmented roads as readily as roofs.
The model alone does not know which shape is a roof. The winning recipe probes in rings around the address point, scores each candidate for roof-likeness from its colour and shape, grows it facet by facet, refines the prompt to an interior point and traces the result to a clean polygon.
Ten of twelve scene pairs gave the correct dwelling outline in the bake-off, including prompts geocoded onto the street. Re-run later on newly fetched imagery, the same detector reached eleven of twelve.
Both early misses were one scene: an address geocoded onto a commercial roundabout, where red-painted pavement looks exactly like terracotta tiles from above. So the tool always tells staff to verify the outline, and the outline stays editable.
A small Rust sidecar, no Python on the hot path
Checked, not assumed. A parity harness runs the Rust binary over every scene pair and compares its decisions with the Python reference, and the API’s tests cover the sidecar contract with a stand-in binary plus an end-to-end run of the real one. The test imagery is re-fetched when needed rather than committed to the repository, because of the imagery licences.
Custom models, trained on GPUs, from whole cities of 3D roofs
The custom models are trained to find roof faces, the way each one faces and its pitch, from open, government-published 3D building models paired with 10 cm aerial photography, covering an entire German state in one-kilometre tiles.
A render job streams each tile pair, rasterises every roof plane with its compass direction and pitch worked out from the 3D geometry, cuts 512-pixel training crops and then deletes the source. Roughly 900 GB of imagery is never stored. Ten workers run in parallel and share the work by claiming tiles, since the platform gives a worker no index of its own.
Every pixel carries one of sixteen compass directions, or “flat”, or “background”, plus a pitch and a flag for the boundary between two faces, which is how the model learns the internal edges. Flip or rotate a training image and the compass labels have to rotate with it, so the augmentation tables are derived from the angle maths rather than written by hand.
A SegFormer-B0 encoder with separate heads for roof segments and for superstructures such as dormers, windows and existing solar panels, warmed up on an open roof dataset. Training runs as a serverless A100 GPU job on Azure that starts, trains, saves its best checkpoint and its metrics, and stops.
A trained model replaces nothing until it beats the MobileSAM baseline on the same twelve Australian scenes, under the same decision rules. Every training source’s licence was checked by download, and non-commercial or unlicensed datasets were rejected outright.
It learns from the people who use it. Whenever staff save a design with corrected roof faces, the corrected outline and the details needed to fetch the same imagery again are kept as an append-only training label, behind a switch. Ordinary quoting builds an Australian fine-tuning set as a side effect.
Pitch is the hard part, so I measured it
A single overhead photo cannot measure a roof’s slope, so detection assumes a typical pitch. To find out how good that assumption is, I measured 150 roofs across five Australian capital cities by fitting planes to a third-party elevation dataset, used for evaluation only, inside my own outlines. The plane fits have residuals of 0.03–0.35 m.
Median pitch 22.7°, with the middle half between 18.4° and 25.4°: the trade’s rule of thumb of 22.5° holds up. 9.3% of roofs are flat, under 10°, and that minority is where all the error lives. Assuming 22° everywhere is out by 6.08° on average, and by 16.9° on flat roofs.
You cannot lay tiles on a flat roof. One threshold on colour saturation catches 79% of flat roofs at a 7% false alarm rate and cuts the error to 5.28° on held-out roofs, beating the constant and a learned pitch head, which sat near 8.4° on its own validation set.
The National Construction Code sets a 15° floor for tiled roofs, and only 1% of roofs the rule calls tile fall below it. Steel sheet can go down to 1°, and metal roofs split into two populations, a median of 17.3° across a range of 2–33°. The remaining problem is one question: is this metal roof flat or pitched?
Reading pitch from tile-texture foreshortening, inferring each face’s direction from the outline’s straight skeleton (right on four of eight faces, a coin toss), snapping pitch to standard truss angles, and reading cladding profile from rib spacing. Each was measured and written down with why it failed, so none is built on by mistake.
The lesson. Everything that tried to infer roof geometry from a photograph failed or was a coin flip; what held up either measured it or came from how roofs are actually built. Before building on a geometric idea, I now run the cheapest measurement that would kill it. Elevation data also covered every metropolitan test site and none of the regional towns tested, which is why the photo alone has to work.
Planned, in order
A pitch head trained on the true slopes in the 3D city models, aimed squarely at the flat-or-pitched metal roof question and always labelled as an estimate. It has to beat 5.28°, the bar the one-line rule set.
Chimneys, dormers, skylights and existing panels as extra classes in the same model, so panels are never placed on an obstruction and a roof that already has solar is recognised.
Canopy mapping from open LiDAR height data and vegetation segmentation, feeding the design tool’s shading model with real canopy shapes in place of hand-placed tree circles.
Per-location solar exposure from public Bureau of Meteorology and NASA datasets, modelled hour by hour, in place of the yield rule of thumb the tool uses today.