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

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1 услугВсе 1566 услуг
Разработка AI-системы анализа дефектов конструкций по фотографиям
Средняя
~1-2 недели
Часто задаваемые вопросы
Направления AI-разработки
Этапы разработки AI-решения
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1218
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    853
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1047
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    825

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 недель