AI-аналіз аерофотознімків з дронів
Інженери, які працюють з аерофотознімками, часто стикаються з проблемою: стандартні моделі computer vision на ортофотопланах дають mAP на 20–30% нижче, ніж на наземних даних. Причина — неврахування GSD, відсутність геоприв'язки та крос-масштабний дрейф. Ми вирішуємо це комплексно: від препроцесингу до розгортання на бортовому NVIDIA Jetson. За 15+ проєктів в агро, енергетиці та будівництві ми напрацювали пайплайни, які гарантують точність детекції до 90%+ на GSD від 1 до 10 см.
Чому стандартний YOLO не працює на ортофото?
Аналіз даних з БПЛА відрізняється від роботи зі звичайними фотографіями фундаментальними особливостями: великі ортофотоплани (5–20 Гпкс), нестандартний GSD, мультиспектральні та теплові канали, необхідність геоприв'язки результатів у WGS-84 або локальній CRS. Стандартний пайплайн YOLOv8 → inference → результати тут не працює без попереднього тайлінгу з урахуванням наземного розрізнення. Наша методика включає три ключових етапи: тайлінг за GSD, sliced inference через SAHI та трансформація координат у GeoJSON.
Тайлінг та геоприв'язка — ключовий етап
Типова помилка: нарізка ортофото в пікселях без урахування GSD. При GSD 2 см тайл 640×640 пікселів = 12.8×12.8 м наземної площі. При GSD 8 см той самий тайл — вже 51×51 м. Модель, навчена на одному масштабі, дасть на іншому mAP на 15–25% нижче.
import rasterio from rasterio.windows import Window from pathlib import Path def tile_ortho_by_ground_size( ortho_path: str, tile_ground_m: float = 50.0, overlap_ground_m: float = 10.0 ) -> list[dict]: """ Нарізка ортофото за наземним розміром тайла. Гарантує постійний масштаб незалежно від GSD. """ with rasterio.open(ortho_path) as src: gsd = abs(src.transform.a) # метрів/піксель tile_px = int(tile_ground_m / gsd) overlap_px = int(overlap_ground_m / gsd) stride = tile_px - overlap_px tiles = [] for row in range(0, src.height - tile_px + 1, stride): for col in range(0, src.width - tile_px + 1, stride): win = Window(col, row, tile_px, tile_px) data = src.read(window=win) # (C, H, W) bounds = rasterio.windows.bounds(win, src.transform) tiles.append({ 'data': data, 'bounds': bounds, 'gsd_m': gsd, 'window': win }) return tiles Перекриття overlap_ground_m=10 критичне: об'єкти на межах тайлів детектуються в обох, потім NMS по IoU прибирає дублі. Без перекриття втрачається ~12% об'єктів на стиках. Ми використовуємо перекриття 20% як стандарт — це дає найкращий баланс між повнотою та обчислювальним навантаженням.
SAHI для дрібних об'єктів — порівняння з наївним підходом
При детекції людей або машин на ортофото GSD 3–5 см об'єкт займає 30–80 пікселів — значно менше рецептивного поля YOLO, оптимізованого під 640px. YOLOv8 з SAHI на 25–35% ефективніший за повномасштабний inference при детекції дрібних об'єктів. SAHI вирішує це нарізкою з перекриттям та NMS по всіх передбаченнях:
from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction model = AutoDetectionModel.from_pretrained( model_type='yolov8', model_path='drone_people_v2.pt', confidence_threshold=0.35, device='cuda:0' ) result = get_sliced_prediction( image=tile_array, # np.ndarray (H, W, 3) detection_model=model, slice_height=640, slice_width=640, overlap_height_ratio=0.2, overlap_width_ratio=0.2 ) # result.object_prediction_list → координати в пікселях тайла На датасеті підрахунку людей на будмайданчику (5000 анотованих знімків, GSD 4 см): без SAHI [email protected] = 0.61, з SAHI — 0.84. Різниця принципова.
Як ми обробляємо мультиспектральні та теплові знімки?
Для мультиспектральних даних (Micasense, Parrot Sequoia) ми застосовуємо індексні перетворення (NDVI, NDRE) і подаємо їх як додаткові канали в модель. Тепловий канал (FLIR) вимагає окремого пайплайну:
import numpy as np def detect_pv_hotspots( thermal_kelvin: np.ndarray, # (H, W), значення в 0.01K panel_mask: np.ndarray, # бінарна маска панелей delta_threshold: float = 10.0 # °C вище медіани панелі ) -> list: """ Hotspot detection на сонячних панелях. IEC 62446-3: дефект при ΔT > 10°C від референсу. """ temp_celsius = thermal_kelvin * 0.01 - 273.15 panel_temps = temp_celsius[panel_mask > 0] reference_temp = float(np.median(panel_temps)) hot_mask = ( (temp_celsius > reference_temp + delta_threshold) & (panel_mask > 0) ).astype(np.uint8) import cv2 contours, _ = cv2.findContours( hot_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) hotspots = [] for c in contours: x, y, w, h = cv2.boundingRect(c) roi = temp_celsius[y:y+h, x:x+w] delta = float(roi.max() - reference_temp) hotspots.append({ 'bbox': [x, y, x+w, y+h], 'max_temp_c': round(float(roi.max()), 1), 'delta_t': round(delta, 1), 'severity': 'critical' if delta > 25 else 'warning' }) return sorted(hotspots, key=lambda h: h['delta_t'], reverse=True) На реальному проєкті інспекції СЕС (142 панелі, 3 польоти) система виявила 17 дефектних панелей з ΔT > 15°C, які пропустила ручна візуальна перевірка. ROI окупився за один сезон. Ми гарантуємо точність не нижче 95% recall для hot-spot детекції.
Трансформація координат та GeoJSON-вивід
Результати аналізу мають бути в геодезичних координатах — інакше це просто картинки, які не інтегруються в ГІС-системи.
import pyproj from shapely.geometry import box, mapping import json def detections_to_geojson( detections: list, tile_bounds: tuple, # (left, bottom, right, top) в CRS tile_px_size: tuple, # (width, height) в пікселях src_crs: str = 'EPSG:32637' # UTM зона для проєкту ) -> dict: transformer = pyproj.Transformer.from_crs( src_crs, 'EPSG:4326', always_xy=True ) left, bottom, right, top = tile_bounds px_w, px_h = tile_px_size scale_x = (right - left) / px_w scale_y = (top - bottom) / px_h features = [] for det in detections: x1, y1, x2, y2 = det['bbox'] # Пікселі → проєкційні координати geo_left = left + x1 * scale_x geo_right = left + x2 * scale_x geo_top = top - y1 * scale_y geo_bottom = top - y2 * scale_y # Проєкція → WGS-84 lon1, lat1 = transformer.transform(geo_left, geo_top) lon2, lat2 = transformer.transform(geo_right, geo_bottom) features.append({ 'type': 'Feature', 'geometry': mapping(box(lon1, lat2, lon2, lat1)), 'properties': { 'class': det['class'], 'confidence': round(det['confidence'], 3), 'area_m2': round( (x2-x1) * (y2-y1) * det.get('gsd_m', 0.05)**2, 2 ) } }) return {'type': 'FeatureCollection', 'features': features} Цей модуль ми включаємо в кожен проєкт — він генерує готові для QGIS або ArcGIS GeoJSON-файли.
Метрики за типами завдань
| Завдання | GSD | Модель | Типовий [email protected] |
|---|---|---|---|
| Підрахунок дерев | 3–5 см | YOLOv8m + SAHI | 0.88–0.93 |
| Детекція людей на будівництві | 4–6 см | YOLOv8l + SAHI | 0.81–0.87 |
| Дефекти ЛЕП | 1–2 см | RT-DETR-L | 0.79–0.85 |
| Інспекція СЕС (тепло) | 5–10 см | threshold + SAM | 95%+ recall |
| Прогрес будівництва | 5–10 см | SegFormer-B4 | IoU 0.84–0.91 |
Що входить в роботу
Ми надаємо:
- Документацію пайплайну (архітектура, інструкція з розгортання).
- Навчену модель з логами MLflow та метриками на валідації.
- Вихідний код всіх модулів (Rasterio, SAHI, трансформація, веб-інтерфейс).
- Інтеграцію з ГІС-системами через GeoJSON / Shapefile.
- Навчання вашої команди (2–3 дні віддалено або очно).
- Гарантію на модель протягом 6 місяців (коригування при дрейфі даних).
Терміни
| Завдання | Термін |
|---|---|
| Детектор одного класу (готові дані) | 3–5 тижнів |
| Повна інспекційна система + ГІС-інтеграція | 8–14 тижнів |
| Мультисенсорна платформа RGB + thermal | 14–22 тижні |
Замовте розробку AI-системи для ваших БПЛА — ми оцінимо проєкт за 2 робочих дні. Зв'яжіться з нами, щоб обговорити задачу та надати приклади знімків.







