AI Aerospace Component Quality Control System

We design and deploy artificial intelligence systems: from prototype to production-ready solutions. Our team combines expertise in machine learning, data engineering and MLOps to make AI work not in the lab, but in real business.
Showing 1 of 1 servicesAll 1566 services
AI Aerospace Component Quality Control System
Medium
~2-4 weeks
FAQ
AI Development Areas
AI Solution Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1212
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822

Разработка AI для контроля качества авиационных компонентов

Авиационная промышленность имеет нулевую терпимость к дефектам: один необнаруженный дефект турбинной лопатки может привести к катастрофе. AS9100/DO-178C стандарты требуют 100% инспекцию критических деталей. AI дополняет NDT (Non-Destructive Testing): визуальная инспекция поверхности перед / после обработки, контроль геометрии, детекция трещин на лопатках турбин, анализ CT-сканов.

Инспектор авиационных компонентов

import numpy as np
import cv2
import torch
from anomalib.models import Patchcore
from ultralytics import YOLO
from dataclasses import dataclass
from typing import Optional
import uuid

@dataclass
class AerospaceDefect:
    defect_id: str
    component_id: str
    defect_type: str         # crack / pit / scratch / foreign_material / dimension_error
    severity: str            # acceptable / rejectable / critical
    location: str            # leading_edge / trailing_edge / blade_tip / root / airfoil
    size_mm: Optional[float]
    depth_estimate: Optional[str]  # surface / subsurface
    disposition: str         # accept / rework / scrap
    confidence: float

class AerospaceQualityInspector:
    """
    Контроль качества авиационных компонентов:
    - Турбинные лопатки: трещины, питтинг, эрозия кромки
    - Обшивка фюзеляжа: царапины, коррозия, дентинги
    - Крепёжные отверстия: правильность диаметра, отсутствие заусенцев
    Стандарты: AMS 2750, AC 43.13-1B.
    """
    # Принятые/отбракованные размеры по AMS стандартам
    DEFECT_ACCEPTANCE_CRITERIA = {
        'crack': {'accept_mm': 0, 'rejectable_mm': 0},  # любая трещина = reject
        'pit': {'accept_mm': 0.5, 'rejectable_mm': 1.0},
        'scratch': {'accept_mm': 1.0, 'rejectable_mm': 2.5},
        'erosion': {'accept_mm': 0.8, 'rejectable_mm': 2.0},
        'dent': {'accept_mm': 0.3, 'rejectable_mm': 0.8},
    }

    DEFECT_CLASSES = {
        0: 'crack',
        1: 'pit',
        2: 'scratch',
        3: 'erosion',
        4: 'foreign_material',
        5: 'corrosion',
        6: 'dent',
        7: 'coating_damage',
        8: 'dimension_anomaly'
    }

    def __init__(self, anomaly_model_path: str,
                  defect_model_path: Optional[str] = None,
                  gsd_um_per_px: float = 10.0,  # 10 мкм/пиксель типично для макро
                  device: str = 'cuda'):
        self.anomaly_model = Patchcore.load_from_checkpoint(anomaly_model_path)
        self.anomaly_model.eval()
        self.defect_model = YOLO(defect_model_path) if defect_model_path else None
        self.gsd_mm = gsd_um_per_px / 1000  # мкм → мм/пкс
        self.device = device

    def inspect_turbine_blade(self, image: np.ndarray,
                               component_id: str) -> dict:
        """
        Инспекция турбинной лопатки.
        Камера: 50 МПикс macro + телецентрический объектив, GSD ~5-20 мкм/пкс.
        """
        defects = []

        # Anomaly map
        anomaly_score, anomaly_map = self._run_anomaly(image)

        # YOLO детекция (если обучена)
        if self.defect_model:
            yolo_defects = self._run_yolo(image, component_id)
            defects.extend(yolo_defects)
        else:
            # Только аномальные регионы
            defects.extend(self._anomaly_to_defects(anomaly_map, image, component_id))

        # Классификация серьёзности по AMS стандартам
        for defect in defects:
            defect['disposition'] = self._get_disposition(
                defect['defect_type'], defect.get('size_mm', 0)
            )

        # Общее решение: принять / отправить на доработку / списать
        has_crack = any(d['defect_type'] == 'crack' for d in defects)
        has_critical = any(d['disposition'] == 'scrap' for d in defects)
        has_rework = any(d['disposition'] == 'rework' for d in defects)

        overall_disposition = ('scrap' if has_crack or has_critical else
                               'rework' if has_rework else 'accept')

        return {
            'component_id': component_id,
            'total_defects': len(defects),
            'defects': defects,
            'anomaly_score': round(anomaly_score, 4),
            'overall_disposition': overall_disposition,
            'pass': overall_disposition == 'accept',
            'compliance_standard': 'AMS2750 / AC43.13-1B'
        }

    def _run_anomaly(self, image: np.ndarray) -> tuple[float, np.ndarray]:
        from torchvision import transforms
        from PIL import Image

        pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
        transform = transforms.Compose([
            transforms.Resize((256, 256)),
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ])
        tensor = transform(pil).unsqueeze(0)
        with torch.no_grad():
            output = self.anomaly_model({'image': tensor})
            score = float(torch.sigmoid(output['pred_score']).item())
            amap = output.get('anomaly_map')
            if amap is not None:
                amap_np = amap.squeeze().cpu().numpy()
                amap_np = cv2.resize(amap_np, (image.shape[1], image.shape[0]))
            else:
                amap_np = np.zeros(image.shape[:2], dtype=np.float32)
        return score, amap_np

    def _run_yolo(self, image: np.ndarray,
                   component_id: str) -> list[dict]:
        defects = []
        results = self.defect_model(image, conf=0.30, verbose=False)
        for box in results[0].boxes:
            cls_id = int(box.cls.item())
            cls_name = self.DEFECT_CLASSES.get(cls_id, 'unknown')
            conf = float(box.conf.item())
            x1, y1, x2, y2 = map(int, box.xyxy[0])
            size_px = max(x2-x1, y2-y1)
            size_mm = size_px * self.gsd_mm

            defects.append({
                'defect_id': str(uuid.uuid4())[:8],
                'component_id': component_id,
                'defect_type': cls_name,
                'severity': self._get_severity(cls_name, size_mm),
                'bbox': [x1, y1, x2, y2],
                'size_mm': round(size_mm, 3),
                'confidence': conf
            })
        return defects

    def _anomaly_to_defects(self, anomaly_map: np.ndarray,
                              image: np.ndarray,
                              component_id: str) -> list[dict]:
        defects = []
        if anomaly_map.max() < 0.4:
            return defects

        norm = (anomaly_map / anomaly_map.max() * 255).astype(np.uint8)
        _, thresh = cv2.threshold(norm, 100, 255, cv2.THRESH_BINARY)
        contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

        for cnt in contours:
            if cv2.contourArea(cnt) < 50:
                continue
            x, y, w, h = cv2.boundingRect(cnt)
            size_mm = max(w, h) * self.gsd_mm
            defects.append({
                'defect_id': str(uuid.uuid4())[:8],
                'component_id': component_id,
                'defect_type': 'anomaly',
                'severity': 'unknown',
                'bbox': [x, y, x+w, y+h],
                'size_mm': round(size_mm, 3),
                'confidence': float(anomaly_map[y:y+h, x:x+w].max())
            })
        return defects

    def _get_disposition(self, defect_type: str, size_mm: float) -> str:
        criteria = self.DEFECT_ACCEPTANCE_CRITERIA.get(defect_type)
        if criteria is None:
            return 'rework'
        if defect_type == 'crack':
            return 'scrap'
        if size_mm > criteria['rejectable_mm']:
            return 'scrap'
        elif size_mm > criteria['accept_mm']:
            return 'rework'
        return 'accept'

    def _get_severity(self, defect_type: str, size_mm: float) -> str:
        if defect_type == 'crack':
            return 'critical'
        criteria = self.DEFECT_ACCEPTANCE_CRITERIA.get(defect_type, {})
        if size_mm > criteria.get('rejectable_mm', 999):
            return 'critical'
        elif size_mm > criteria.get('accept_mm', 999):
            return 'major'
        return 'minor'
Метрика Стандарт Значение
Crack Detection AUROC AMS2750 0.983–0.997
Pit/Scratch AP AC 43.13-1B 88–94%
False Reject Rate (FRR) AS9100 Rev D <1–2%
False Accept Rate (FAR) AS9100 Rev D <0.1%
Throughput 30–60 дет/час
Задача Срок
PatchCore инспектор одного типа компонента 6–10 недель
YOLO + классификация по стандартам 12–18 недель
AS9100 certified inspection system 24–40 недель