Home | Benchmarks | Categories | Atom Feed

Posted on Thu 10 April 2025 under Artificial Intelligence

GeoDeep's AI Detection on Maxar's Satellite Imagery

GeoDeep is a Python package that can detect objects in satellite imagery. It's made up of 1,026 lines of Python and uses ONNX Runtime and Rasterio extensively.

GeoDeep was written by Piero Toffanin, who is based in Florida and is the co-founder of OpenDroneMap. He also wrote LibreTranslate which I covered in a post a while back.

Maxar is a satellite manufacturer and constellation operator. They run an open data programme and they often releases imagery from areas before and after natural disasters strike. Below are the locations of their releases to date.

GeoDeep Bangkok

On March 28th, an earthquake struck Myanmar and it reached as far away as Bangkok, Thailand. Spyridon Staridas, a cartographer based in Greece, put together this map of earthquake history around the world. Thailand is surrounded by countries that are earthquake-prone but these are relatively rare in Thailand compared to elsewhere in Asia.

GeoDeep Bangkok

Shortly after the earthquake, Maxar released historical satellite imagery from the affected areas and later included imagery taken after the earthquake. As of this writing, they've released almost 10 GB of GeoTIFFs.

Below are the imagery footprints in central Myanmar. The imagery spans from February 2nd till early April.

GeoDeep Bangkok

Below are the satellite footprints for Bangkok.

GeoDeep Bangkok

In this post, I'll run some of GeoDeep's built-in AI models on Maxar's satellite imagery of Myanmar and Bangkok, Thailand.

My Workstation

I'm using a 5.7 GHz AMD Ryzen 9 9950X CPU. It has 16 cores and 32 threads and 1.2 MB of L1, 16 MB of L2 and 64 MB of L3 cache. It has a liquid cooler attached and is housed in a spacious, full-sized Cooler Master HAF 700 computer case.

The system has 96 GB of DDR5 RAM clocked at 4,800 MT/s and a 5th-generation, Crucial T700 4 TB NVMe M.2 SSD which can read at speeds up to 12,400 MB/s. There is a heatsink on the SSD to help keep its temperature down. This is my system's C drive.

The system is powered by a 1,200-watt, fully modular Corsair Power Supply and is sat on an ASRock X870E Nova 90 Motherboard.

I'm running Ubuntu 24 LTS via Microsoft's Ubuntu for Windows on Windows 11 Pro. In case you're wondering why I don't run a Linux-based desktop as my primary work environment, I'm still using an Nvidia GTX 1080 GPU which has better driver support on Windows and I use ArcGIS Pro from time to time which only supports Windows natively.

Installing Prerequisites

I'll use Python 3.12.3 and jq to help analyse the data in this post.

$ sudo add-apt-repository ppa:deadsnakes/ppa
$ sudo apt update
$ sudo apt install \
    jq \
    python3-pip \
    python3.12-venv

I'll set up a Python Virtual Environment and install the latest GeoDeep release.

$ python3 -m venv ~/.geodeep
$ source ~/.geodeep/bin/activate
$ python3 -m pip install \
    geodeep

I'll use DuckDB, along with its H3, JSON, Lindel, Parquet and Spatial extensions, in this post.

$ cd ~
$ wget -c https://github.com/duckdb/duckdb/releases/download/v1.1.3/duckdb_cli-linux-amd64.zip
$ unzip -j duckdb_cli-linux-amd64.zip
$ chmod +x duckdb
$ ~/duckdb
INSTALL h3 FROM community;
INSTALL lindel FROM community;
INSTALL json;
INSTALL parquet;
INSTALL spatial;

I'll set up DuckDB to load every installed extension each time it launches.

$ vi ~/.duckdbrc
.timer on
.width 180
LOAD h3;
LOAD lindel;
LOAD json;
LOAD parquet;
LOAD spatial;

The maps in this post were rendered with QGIS version 3.42. QGIS is a desktop application that runs on Windows, macOS and Linux. The application has grown in popularity in recent years and has ~15M application launches from users all around the world each month.

I used QGIS' Tile+ plugin to add geospatial context with OpenStreetMap's (OSM) basemap tiles as well as CARTO's to the maps.

The dark, non-satellite map of Maxar's imagery locations above is mostly made up of vector data from Natural Earth and Overture.

I've used this GeoJSON file from Kaggle to outline Bangkok's districts.

I've used EPSG:32647 for the map projection in this post. Below is QGIS' overview of this projection.

GeoDeep Bangkok

Maxar's Bangkok Satellite Imagery

I'll download one of Maxar's images of Bangkok along with its metadata.

$ wget -O ard_47_122022102202_2025-02-14_10400100A39C6A00-visual.tif \
    https://maxar-opendata.s3.amazonaws.com/events/Earthquake-Myanmar-March-2025/ard/47/122022102202/2025-02-14/10400100A39C6A00-visual.tif
$ wget https://raw.githubusercontent.com/opengeos/maxar-open-data/refs/heads/master/datasets/Earthquake-Myanmar-March-2025/10400100A39C6A00_union.geojson

The image is a 17408 x 17408-pixel, 62 MB GeoTIFF. It has a 5.3 KM x 3.7 KM footprint and captures a area North West of Bangkok's Bang Sue district. Below is the image in relation to the rest of Bangkok.

GeoDeep Bangkok

Below is a zoomed-in section of the image.

GeoDeep Bangkok

Below is the metadata for the image. It was taken on February 14th at 11:02AM local time. The image is free of any cloud cover.

$ jq -S .features[0].properties \
    10400100A39C6A00_union.geojson
{
  "ard_metadata_version": "0.0.1",
  "catalog_id": "10400100A39C6A00",
  "data-mask": "https://maxar-opendata.s3.amazonaws.com/events/Earthquake-Myanmar-March-2025/ard/47/122022102202/2025-02-14/10400100A39C6A00-data-mask.gpkg",
  "datetime": "2025-02-14T04:02:00Z",
  "grid:code": "MXRA-Z47-122022102202",
  "gsd": 0.35,
  "ms_analytic": "https://maxar-opendata.s3.amazonaws.com/events/Earthquake-Myanmar-March-2025/ard/47/122022102202/2025-02-14/10400100A39C6A00-ms.tif",
  "pan_analytic": "https://maxar-opendata.s3.amazonaws.com/events/Earthquake-Myanmar-March-2025/ard/47/122022102202/2025-02-14/10400100A39C6A00-pan.tif",
  "platform": "WV03",
  "proj:bbox": "659843.75,1529843.75,665156.25,1533587.620632182",
  "proj:code": "EPSG:32647",
  "proj:geometry": {
    "coordinates": [
      [
        [
          665156.25,
          1529843.75
        ],
        [
          659843.75,
          1529843.75
        ],
        [
          659843.75,
          1533553.3890408671
        ],
        [
          665156.25,
          1533587.6206321821
        ],
        [
          665156.25,
          1529843.75
        ]
      ]
    ],
    "type": "Polygon"
  },
  "quadkey": "122022102202",
  "tile:clouds_area": 0.0,
  "tile:clouds_percent": 0,
  "tile:data_area": 19.7,
  "utm_zone": 47,
  "view:azimuth": 262.1,
  "view:incidence_angle": 64.7,
  "view:off_nadir": 22.9,
  "view:sun_azimuth": 139.1,
  "view:sun_elevation": 55.2,
  "visual": "https://maxar-opendata.s3.amazonaws.com/events/Earthquake-Myanmar-March-2025/ard/47/122022102202/2025-02-14/10400100A39C6A00-visual.tif"
}

Detection Models

Below are the models that come packaged with GeoDeep. These are listed in geodeep/models.py. I've sorted this list for clarity.

MODELS = {
    'aerovision':   'aerovision16-yolo8.onnx',
    'birds':        'bird_detection_retinanet_deepforest.onnx',
    'buildings':    'buildings_ramp_XUnet_256.onnx',
    'cars':         'car_aerial_detection_yolo7_ITCVD_deepness.onnx',
    'planes':       'model_yolov7_tiny_planes_256.onnx',
    'roads':        'road_segmentation_model_with_metadata_26_10_22.onnx',
    'trees':        'tree_crown_detection_retinanet_deepforest.onnx',
    'trees_yolov9': 'yolov9_trees.onnx',
    'utilities':    'utilities-811-yolo8.onnx',
}

The first time you use these models, they'll be downloaded to ~/.cache/geodeep folder.

$ ls -lhS ~/.cache/geodeep
194M .. yolov9_trees.onnx
 94M .. road_segmentation_model_with_metadata_26_10_22.onnx
 79M .. buildings_ramp_XUnet_256.onnx
 32M .. tree_crown_detection_retinanet_deepforest.onnx
 24M .. car_aerial_detection_yolo7_ITCVD_deepness.onnx
 23M .. model_yolov7_tiny_planes_256.onnx
 11M .. aerovision16-yolo8.onnx

Models can be inspected with the following command.

$ geodeep-inspect cars
det_type: YOLO_v5_or_v7_default
det_conf: 0.3
det_iou_thresh: 0.8
classes: []
seg_thresh: 0.5
seg_small_segment: 11
resolution: 10.0
class_names: {'0': 'car'}
model_type: Detector
tiles_overlap: 10.0
tiles_size: 640
input_shape: [1, 3, 640, 640]
input_name: images

The following will show an overview of all the models.

$ python3
import json

from geodeep.inference import create_session
from geodeep           import models
from geodeep.models    import get_model_file


with open('models.json', 'w') as f:
    for model in models.list_models():
        _, config = create_session(get_model_file(model))
        config['model_name'] = model
        f.write(json.dumps(config, sort_keys=True) + '\n')
$ ~/duckdb
SELECT   * EXCLUDE(class_names),
         class_names::TEXT AS class_names
FROM     models.json
ORDER BY model_name;
┌─────────┬──────────┬────────────────┬──────────────────────┬────────────┬────────────────────┬──────────────┬────────────┬────────────┬───────────────────┬────────────┬───────────────┬────────────┬────────────────────────────────────────────────────┐
│ classes │ det_conf │ det_iou_thresh │       det_type       │ input_name │    input_shape     │  model_name  │ model_type │ resolution │ seg_small_segment │ seg_thresh │ tiles_overlap │ tiles_size │                    class_names                     │
│ json[]  │  double  │     double     │       varchar        │  varchar   │      int64[]       │   varchar    │  varchar   │   double   │       int64       │   double   │    double     │   int64    │                      varchar                       │
├─────────┼──────────┼────────────────┼──────────────────────┼────────────┼────────────────────┼──────────────┼────────────┼────────────┼───────────────────┼────────────┼───────────────┼────────────┼────────────────────────────────────────────────────┤
│ []      │      0.3 │            0.3 │ YOLO_v8              │ images     │ [1, 3, 640, 640]   │ aerovision   │ Detector   │       30.0 │                11 │        0.5 │          25.0 │        640 │ {'0': small-vehicle, '1': large-vehicle, '10': b…  │
│ []      │      0.4 │            0.4 │ retinanet            │ images     │ [1, 3, 1000, 1000] │ birds        │ Detector   │        2.0 │                11 │        0.5 │           5.0 │       1000 │ {'0': bird, '1': NULL, '10': NULL, '11': NULL, '…  │
│ []      │      0.3 │            0.8 │ YOLO_v5_or_v7_defa…  │ input      │ [1, 3, 256, 256]   │ buildings    │ Segmentor  │       50.0 │                11 │        0.5 │           5.0 │        256 │ {'0': Background, '1': Building, '10': NULL, '11…  │
│ []      │      0.3 │            0.8 │ YOLO_v5_or_v7_defa…  │ images     │ [1, 3, 640, 640]   │ cars         │ Detector   │       10.0 │                11 │        0.5 │          10.0 │        640 │ {'0': car, '1': NULL, '10': NULL, '11': NULL, '1…  │
│ []      │      0.3 │            0.3 │ YOLO_v5_or_v7_defa…  │ images     │ [1, 3, 256, 256]   │ planes       │ Detector   │       70.0 │                11 │        0.5 │           5.0 │        256 │ {'0': plane, '1': NULL, '10': NULL, '11': NULL, …  │
│ []      │      0.3 │            0.8 │ YOLO_v5_or_v7_defa…  │ input      │ [1, 3, 512, 512]   │ roads        │ Segmentor  │       21.0 │                11 │        0.5 │          15.0 │        512 │ {'0': not_road, '1': road, '10': NULL, '11': NUL…  │
│ []      │      0.3 │            0.4 │ retinanet            │ images     │ [1, 3, 400, 400]   │ trees        │ Detector   │       10.0 │                11 │        0.5 │           5.0 │        400 │ {'0': tree, '1': NULL, '10': NULL, '11': NULL, '…  │
│ []      │      0.5 │            0.4 │ YOLO_v9              │ images     │ [1, 3, 640, 640]   │ trees_yolov9 │ Detector   │       10.0 │                11 │        0.5 │          25.0 │        640 │ {'0': Tree, '1': NULL, '10': NULL, '11': NULL, '…  │
│ []      │      0.3 │            0.3 │ YOLO_v8              │ images     │ [1, 3, 640, 640]   │ utilities    │ Detector   │        3.0 │                11 │        0.5 │          10.0 │        640 │ {'0': Gas, '1':  Manhole, '10': NULL, '11': NULL…  │
└─────────┴──────────┴────────────────┴──────────────────────┴────────────┴────────────────────┴──────────────┴────────────┴────────────┴───────────────────┴────────────┴───────────────┴────────────┴────────────────────────────────────────────────────┘

GeoDeep's README on GitHub lists some details about the pre-built models as well as information on how you can create your own models using YOLO and at least 1,000 images.

Detecting Cars

The following car detection model only took a few moments to run on Maxar's image. It detected 304 cars.

$ geodeep \
    ard_47_122022102202_2025-02-14_10400100A39C6A00-visual.tif \
    cars \
    --output cars.geojson

$ jq -S '.features|length' cars.geojson # 304

The following is an overview of where the detections were.

GeoDeep Bangkok

There were a large number of cars not detected by the model.

GeoDeep Bangkok

There were also a number of false-positives near the Chao Phraya River.

GeoDeep Bangkok

There was only a single detection class of "car" that appeared in the results. Below is the distribution of confidence scores.

$ jq '.features|.[]' \
     cars.geojson \
     > cars.unrolled.json
$ ~/duckdb
SELECT   ROUND(properties.score * 10)::int * 10 AS percent,
         COUNT(*) num_detections
FROM     READ_JSON('cars.unrolled.json')
GROUP BY 1
ORDER BY 1;
┌─────────┬────────────────┐
│ percent │ num_detections │
│  int32  │     int64      │
├─────────┼────────────────┤
│      30 │             86 │
│      40 │             97 │
│      50 │             50 │
│      60 │             34 │
│      70 │             25 │
│      80 │             10 │
│      90 │              2 │
└─────────┴────────────────┘

Detecting Trees

The tree detection model found 14,136 trees in Maxar's image.

$ geodeep \
    ard_47_122022102202_2025-02-14_10400100A39C6A00-visual.tif \
    trees \
    --output trees.geojson

$ jq -S '.features|length' trees.geojson # 14136

The model took a few minutes to run due to it running entirely on my CPU and not my GPU.

GeoDeep Bangkok

I couldn't see a flag to change the inference device to my Nvidia GPU in GeoDeep's codebase. I've raised an issue to see if GPU inference is in fact possible or could be supported.

Here is an overview of the tree detections.

GeoDeep Bangkok

There are very few false-positives but a lot of trees are left undetected.

GeoDeep Bangkok GeoDeep Bangkok

Few tree detections had a confidence value above 50% even though they're often spot-on.

GeoDeep Bangkok GeoDeep Bangkok

There was only a single detection class of "tree" that appeared in the results. Below is the distribution of confidence scores.

$ jq '.features|.[]' \
     trees.geojson \
     > trees.unrolled.json
$ ~/duckdb
SELECT   ROUND(properties.score * 10)::int * 10 AS percent,
         COUNT(*) num_detections
FROM     READ_JSON('trees.unrolled.json')
GROUP BY 1
ORDER BY 1;
┌─────────┬────────────────┐
│ percent │ num_detections │
│  int32  │     int64      │
├─────────┼────────────────┤
│      30 │           4412 │
│      40 │           5656 │
│      50 │           2678 │
│      60 │           1073 │
│      70 │            287 │
│      80 │             29 │
│      90 │              1 │
└─────────┴────────────────┘

Detecting Trees with YOLOv9

The following ran a lot faster than the previous model. It only took amount a minute to run. But with that said, only 402 trees were detected, two orders of magnitude less than the previous model.

$ geodeep \
    ard_47_122022102202_2025-02-14_10400100A39C6A00-visual.tif \
    trees_yolov9 \
    --output trees_yolov9.geojson

$ jq -S '.features|length' trees_yolov9.geojson # 402

There was only a single detection class of "Tree" that appeared in the results. Below is the distribution of confidence scores.

$ jq '.features|.[]' \
     trees_yolov9.geojson \
     > trees_yolov9.unrolled.json
$ ~/duckdb
SELECT   ROUND(properties.score * 10)::int * 10 AS percent,
         COUNT(*) num_detections
FROM     READ_JSON('trees_yolov9.unrolled.json')
GROUP BY 1
ORDER BY 1;
┌─────────┬────────────────┐
│ percent │ num_detections │
│  int32  │     int64      │
├─────────┼────────────────┤
│      50 │            106 │
│      60 │            187 │
│      70 │             92 │
│      80 │             15 │
│      90 │              2 │
└─────────┴────────────────┘

QGIS complained that the GeoJSON file was invalid so I converted it into a GPKG file.

$ ~/duckdb
COPY (
    SELECT ST_GEOMFROMGEOJSON(geometry) geom,
           ROUND(properties.score * 10)::int * 10 AS percent
    FROM   READ_JSON('trees_yolov9.unrolled.json',
                     maximum_object_size=100000000)
) TO 'trees_yolov9.gpkg'
        WITH (FORMAT GDAL,
              DRIVER 'GPKG',
              LAYER_CREATION_OPTIONS 'WRITE_BBOX=YES');

This is an overview of the detections.

GeoDeep Bangkok

I didn't see many false-positives but lots of trees weren't detected.

GeoDeep Bangkok

Detecting Buildings

The following model detected 23,561 buildings in Maxar's imagery.

$ geodeep \
    ard_47_122022102202_2025-02-14_10400100A39C6A00-visual.tif \
    buildings \
    --output buildings.geojson

$ jq -S '.features|length' buildings.geojson # 23561

This model doesn't report confidence values.

$  jq -S '.features[0]|del(.geometry)' \
         buildings.geojson
{
  "properties": {
    "class": "Building"
  },
  "type": "Feature"
}

The resulting GeoJSON file is 437 MB. When I tried to drag it into my QGIS project QGIS complained that it wasn't a valid data source. Below I converted the results into a 49 MB GeoPackage (GPKG) file.

There were 15 records classed as "Background" that I excluded from the GPKG file.

$ jq '.features|.[]' \
     buildings.geojson \
     > buildings.unrolled.json
$ ~/duckdb
COPY (
    SELECT ST_GEOMFROMGEOJSON(geometry) geom
    FROM   READ_JSON('buildings.unrolled.json',
                     maximum_object_size=100000000)
    WHERE  properties.class = 'Building'
) TO 'buildings.gpkg'
        WITH (FORMAT GDAL,
              DRIVER 'GPKG',
              LAYER_CREATION_OPTIONS 'WRITE_BBOX=YES');

Overview across the image.

GeoDeep Bangkok

Some buildings are merged together but generally, anything that can be seen through the dense foliage is well-detected. A second-pass through an orthogonalising algorithm might help produce less wobbly outlines.

GeoDeep Bangkok

I did notice a large building wasn't picked out properly by the model.

GeoDeep Bangkok

Below is another example of a large structure not being detected properly.

GeoDeep Bangkok

Detecting Roads

The following model detected 2,842 roads in the above image.

$ geodeep \
    ard_47_122022102202_2025-02-14_10400100A39C6A00-visual.tif \
    roads \
    --output roads.geojson
$ jq '.features|.[]' \
     roads.geojson \
     > roads.unrolled.json
$ ~/duckdb

There weren't any confidence scores reported but there are two classifications: "road" and "not_road."

SELECT   COUNT(*) num_detections,
         properties.class
FROM     READ_JSON('roads.unrolled.json',
                   maximum_object_size=1000000000)
GROUP BY 2;
┌────────────────┬──────────┐
│ num_detections │  class   │
│     int64      │ varchar  │
├────────────────┼──────────┤
│            136 │ not_road │
│           2842 │ road     │
└────────────────┴──────────┘

The resulting GeoJSON file is 181 MB and causes QGIS to slow down considerably. I'll convert it into a GPKG file so it renders faster.

$ ~/duckdb
COPY (
    SELECT ST_GEOMFROMGEOJSON(geometry) geom
    FROM   READ_JSON('roads.unrolled.json',
                     maximum_object_size=100000000)
    WHERE  properties.class = 'road'
) TO 'roads.gpkg'
  WITH (FORMAT GDAL,
        DRIVER 'GPKG',
        LAYER_CREATION_OPTIONS 'WRITE_BBOX=YES');

Below is an overview of the detected roads.

GeoDeep Bangkok

There are a lot of false-positives, incomplete detections and non-plausible outlines in the model's detections.

GeoDeep Bangkok

Detecting Planes

The Bangkok imagery from Maxar doesn't cover any of their airports. I'll download one of their images from Myanmar that has airports within its footprint.

$ wget -O ard_46_122000331100_2025-02-07_103001010E61BB00-visual.tif \
        https://maxar-opendata.s3.amazonaws.com/events/Earthquake-Myanmar-March-2025/ard/46/122000331100/2025-02-07/103001010E61BB00-visual.tif

Below is the metadata for the above image.

$ wget https://raw.githubusercontent.com/opengeos/maxar-open-data/refs/heads/master/datasets/Earthquake-Myanmar-March-2025/103001010E61BB00.geojson
$ jq -c .features[].properties 103001010E61BB00.geojson \
    | grep 122000331100 \
    | jq -S .
{
  "ard_metadata_version": "0.0.1",
  "catalog_id": "103001010E61BB00",
  "data-mask": "https://maxar-opendata.s3.amazonaws.com/events/Earthquake-Myanmar-March-2025/ard/46/122000331100/2025-02-07/103001010E61BB00-data-mask.gpkg",
  "datetime": "2025-02-07T04:01:56Z",
  "grid:code": "MXRA-Z46-122000331100",
  "gsd": 0.56,
  "ms_analytic": "https://maxar-opendata.s3.amazonaws.com/events/Earthquake-Myanmar-March-2025/ard/46/122000331100/2025-02-07/103001010E61BB00-ms.tif",
  "pan_analytic": "https://maxar-opendata.s3.amazonaws.com/events/Earthquake-Myanmar-March-2025/ard/46/122000331100/2025-02-07/103001010E61BB00-pan.tif",
  "platform": "WV02",
  "proj:bbox": "799843.75,2314843.75,805156.25,2318012.03454448",
  "proj:code": "EPSG:32646",
  "proj:geometry": {
    "coordinates": [
      [
        [
          805156.25,
          2314843.75
        ],
        [
          799843.75,
          2314843.75
        ],
        [
          799843.75,
          2317914.8673873823
        ],
        [
          805156.25,
          2318012.03454448
        ],
        [
          805156.25,
          2314843.75
        ]
      ]
    ],
    "type": "Polygon"
  },
  "quadkey": "122000331100",
  "tile:clouds_area": 0.0,
  "tile:clouds_percent": 0,
  "tile:data_area": 16.5,
  "utm_zone": 46,
  "view:azimuth": 135.0,
  "view:incidence_angle": 61.0,
  "view:off_nadir": 25.6,
  "view:sun_azimuth": 141.4,
  "view:sun_elevation": 45.2,
  "visual": "https://maxar-opendata.s3.amazonaws.com/events/Earthquake-Myanmar-March-2025/ard/46/122000331100/2025-02-07/103001010E61BB00-visual.tif"
}

The following model took less than a minute to run and detected 29 planes.

$ geodeep \
    ard_46_122000331100_2025-02-07_103001010E61BB00-visual.tif \
    planes \
    --output planes.geojson

$ jq -S '.features|length' planes.geojson # 29
$ jq '.features|.[]' \
     planes.geojson \
     > planes.unrolled.json
$ ~/duckdb

There is only a single detection class of "plane" in the results. Below are the distribution of confidence scores.

SELECT   ROUND(properties.score * 10)::int * 10 AS percent,
         COUNT(*) num_detections
FROM     READ_JSON('planes.unrolled.json')
GROUP BY 1
ORDER BY 1;
┌─────────┬────────────────┐
│ percent │ num_detections │
│  int32  │     int64      │
├─────────┼────────────────┤
│      30 │              9 │
│      40 │              8 │
│      50 │              4 │
│      60 │              1 │
│      70 │              3 │
│      80 │              3 │
│      90 │              1 │
└─────────┴────────────────┘

Most detections were false-positives but it did manage to detect most of the aircraft in the image. I've labelled the detections with their confidence value.

GeoDeep Myanmar GeoDeep Myanmar

Some of the false-positives had pretty high confidence scores as well.

GeoDeep Myanmar GeoDeep Myanmar

Multi-Class Object Detection

The following model took less than a minute to run and detected 44 features.

$ geodeep \
    ard_46_122000331100_2025-02-07_103001010E61BB00-visual.tif \
    aerovision \
    --output aerovision.geojson

$ jq -S '.features|length' aerovision.geojson # 44

Below is a breakdown of detections by classification and confidence value.

$ jq '.features|.[]' \
     aerovision.geojson \
     > aerovision.unrolled.json
$ ~/duckdb
WITH a AS (
    SELECT   ROUND(properties.score * 10)::int * 10 AS percent,
             properties.class AS classification,
             COUNT(*) num_detections
    FROM     READ_JSON('aerovision.unrolled.json')
    GROUP BY 1, 2
)
PIVOT    a
ON       percent
USING    SUM(num_detections)
GROUP BY classification;
┌────────────────┬────────┬────────┬────────┬────────┬────────┬────────┬────────┐
│ classification │   30   │   40   │   50   │   60   │   70   │   80   │   90   │
│    varchar     │ int128 │ int128 │ int128 │ int128 │ int128 │ int128 │ int128 │
├────────────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┤
│ small-vehicle  │      1 │      1 │      1 │      4 │        │        │        │
│ baseball-field │      1 │      1 │      1 │      3 │        │        │        │
│ road-circle    │        │        │        │      1 │        │        │        │
│ large-vehicle  │      3 │      1 │        │        │        │        │        │
│ swimming-pool  │        │      3 │      7 │      2 │      1 │      1 │      1 │
│ plane          │        │        │      2 │      1 │      4 │      2 │        │
│ tennis-court   │        │      1 │        │      1 │        │        │        │
└────────────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────┘

The plane detection was pretty good.

GeoDeep Myanmar GeoDeep Myanmar

All of the "large-vehicle" detections were buildings. I couldn't see any baseball fields, swimming polls or tennis courts in Maxar's image so I'll have to call all those detections false-positives.

Half of the "small-vehicle" detections were spot on. Below is a screen shot of them.

GeoDeep Myanmar
Thank you for taking the time to read this post. I offer both consulting and hands-on development services to clients in North America and Europe. If you'd like to discuss how my offerings can help your business please contact me via LinkedIn.

Copyright © 2014 - 2025 Mark Litwintschik. This site's template is based off a template by Giulio Fidente.