Розробка AI-системи аналізу дефектів конструкцій за фотографіями

Проектуємо та впроваджуємо системи штучного інтелекту: від прототипу до production-ready рішення. Наша команда поєднує експертизу в машинному навчанні, дата-інжинірингу та MLOps, щоб AI працював не в лабораторії, а в реальному бізнесі.
Показано 1 з 1Усі 1566 послуг
Розробка AI-системи аналізу дефектів конструкцій за фотографіями
Середній
~1-2 тижні
Часті запитання

Напрямки AI-розробки

Етапи розробки AI-рішення

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1286
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    902
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1122
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    859

AI-виявлення структурних дефектів за фотографіями

Тріщини в бетоні, корозія арматури, розшарування кладки, деформації несучих елементів — рання діагностика є критичною для безпеки будівель. Традиційна ручна інспекція суб'єктивна: різні інспектори класифікують одну й ту саму тріщину по-різному. AI-система дає відтворювану оцінку з кількісними метриками.

Завдання: класифікація та сегментація дефектів

Структурні дефекти вимагають піксельної точності, а не лише bbox – нам важливі довжина тріщини, ширина, орієнтація. Це завдання семантичної сегментації.

import torch
import numpy as np
import cv2
import segmentation_models_pytorch as smp
from PIL import Image
from torchvision import transforms
from dataclasses import dataclass
from typing import Optional

@dataclass
class DefectAnalysis:
    defect_type: str
    severity: str          # 'hairline', 'minor', 'moderate', 'severe', 'critical'
    area_px: int
    area_ratio: float
    max_width_px: Optional[float]
    max_length_px: Optional[float]
    orientation: Optional[float]  # градусы от вертикали
    bounding_box: list

class StructuralDefectDetector:
    def __init__(self, model_path: str):
        """
        UNet++ с EfficientNet-B5 энкодером.
        Дообучен на Concrete Crack Images Dataset (40k изображений)
        + собственный датасет с коррозией и расслоением.
        """
        self.model = smp.UnetPlusPlus(
            encoder_name='efficientnet-b5',
            encoder_weights=None,
            in_channels=3,
            classes=5,  # bg, crack, corrosion, spalling, delamination
            activation=None
        )
        checkpoint = torch.load(model_path, map_location='cpu')
        self.model.load_state_dict(checkpoint['model'])
        self.model.eval()

        self.class_names = {
            0: 'background',
            1: 'crack',
            2: 'corrosion',
            3: 'spalling',
            4: 'delamination'
        }

        self.transform = transforms.Compose([
            transforms.Resize((512, 512)),
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406],
                                  [0.229, 0.224, 0.225])
        ])

    @torch.no_grad()
    def analyze(self, image: np.ndarray,
                 gsd_mm_per_pixel: Optional[float] = None) -> list[DefectAnalysis]:
        """
        gsd_mm_per_pixel: масштаб (из метаданных съёмки с дрона или лазера).
        Позволяет давать размеры в мм, а не пикселях.
        """
        h, w = image.shape[:2]
        pil_img = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
        tensor = self.transform(pil_img).unsqueeze(0)

        logits = self.model(tensor)  # (1, 5, 512, 512)
        mask = logits.argmax(dim=1)[0].numpy()  # (512, 512)

        # Масштабируем маску обратно к исходному размеру
        mask_full = cv2.resize(
            mask.astype(np.uint8), (w, h), interpolation=cv2.INTER_NEAREST
        )

        defects = []
        for cls_id in range(1, 5):
            cls_mask = (mask_full == cls_id).astype(np.uint8)
            if cls_mask.sum() < 100:  # фильтр шума
                continue

            contours, _ = cv2.findContours(cls_mask, cv2.RETR_EXTERNAL,
                                            cv2.CHAIN_APPROX_SIMPLE)

            for cnt in contours:
                area = int(cv2.contourArea(cnt))
                if area < 50:
                    continue

                x, y, cw, ch = cv2.boundingRect(cnt)
                area_ratio = area / (w * h)

                # Для трещин — скелетонизация для длины/ширины
                max_width = None
                max_length = None
                orientation = None

                if cls_id == 1:  # crack
                    max_width, max_length, orientation = self._analyze_crack(
                        cls_mask[y:y+ch, x:x+cw]
                    )

                defects.append(DefectAnalysis(
                    defect_type=self.class_names[cls_id],
                    severity=self._classify_severity(cls_id, area_ratio,
                                                      max_width, gsd_mm_per_pixel),
                    area_px=area,
                    area_ratio=area_ratio,
                    max_width_px=max_width,
                    max_length_px=max_length,
                    orientation=orientation,
                    bounding_box=[x, y, x+cw, y+ch]
                ))

        return defects

    def _analyze_crack(self, crack_roi: np.ndarray) -> tuple:
        """Скелетонизация трещины для измерения ширины и длины"""
        from skimage.morphology import skeletonize

        skeleton = skeletonize(crack_roi > 0)
        length = float(skeleton.sum())  # пикселей скелета ≈ длина

        # Ширина через distance transform
        dist = cv2.distanceTransform(crack_roi, cv2.DIST_L2, 5)
        max_width = float(dist.max() * 2) if dist.max() > 0 else 0

        # Ориентация через PCA
        pts = np.column_stack(np.where(skeleton))
        if len(pts) > 10:
            mean = pts.mean(axis=0)
            centered = pts - mean
            _, _, vt = np.linalg.svd(centered)
            angle = np.degrees(np.arctan2(vt[0, 0], vt[0, 1]))
        else:
            angle = 0.0

        return max_width, length, angle

    def _classify_severity(self, cls_id: int, area_ratio: float,
                             width_px: Optional[float],
                             gsd: Optional[float]) -> str:
        if cls_id == 1:  # crack severity по ширине (мм)
            width_mm = (width_px * gsd) if (width_px and gsd) else None
            if width_mm:
                if width_mm < 0.2:   return 'hairline'
                if width_mm < 0.5:   return 'minor'
                if width_mm < 1.5:   return 'moderate'
                if width_mm < 5.0:   return 'severe'
                return 'critical'

        # Для остальных — по площади
        if area_ratio < 0.005: return 'minor'
        if area_ratio < 0.02:  return 'moderate'
        if area_ratio < 0.05:  return 'severe'
        return 'critical'

Норми оцінки дефектів

Тип дефекту Критерій тяжкості Норматив (ГОСТ/СП)
Тріщина в бетоні Ширина розкриття > 0.3мм СП 20.13330
Тріщина в ЗБ (вигин) > 0.2мм нормальна, > 0.1мм коса ГОСТ Р 55961
Корозія арматури Площа > 10% перерізу СП 28.13330
Спалінг/скіл бетону Глибина > 20мм -

Кейс: обстеження 120 опор шляхопроводу

Завдання: оцінка технічного стану шляхопроводу через фотозйомку з дрону.

  • 120 опор, 3500 фотографій із GSD 0.5–1.5 мм/піксель
  • Обробка: 2.5 години на RTX 3090
  • Знайдено: 847 тріщин (з них 23 критичні, ширина > 1мм), 156 зон корозії
  • ручна перевірка 5% випадкових результатів: 94% точність класифікації тяжкості
Тип проекту Термін
Детектор тріщин (сегментація) 4–6 тижнів
Повна система (4 типи дефектів + метрики) 7–12 тижнів
З вимірами в мм та нормативною оцінкою 10-16 тижнів