Разработка AI для контроля качества авиационных компонентов
Один необнаруженный subsurface-дефект на турбинной лопатке — и через 500 циклов двигатель теряет лопатку. Каждый пропущенный дефект обходится в среднем в $15 000 на ремонт или замену, а ежегодные потери от брака достигают миллионов долларов. Авиационная промышленность давно перешла на 100% инспекцию по стандартам AS9100/DO-178C, но традиционное NDT (люминесцентный контроль, ультразвук) даёт до 3% ложных пропусков (FAR). Разрабатываемая нами AI-система контроля качества аэрокосмических компонентов снижает FAR до <0,1% и работает на линии со скоростью 30–60 деталей в час. Под ключ — от сбора данных до сертификации процесса. Средний чек устранения дефекта после детекции на линии — $1 200 против $12 000 при ручном контроле.
Почему AI лучше традиционного NDT?
Человек-инспектор пропускает 3–5% дефектов из-за утомления. Ультразвук не видит поверхностные трещины <0,5 мм. AI на базе PatchCore и YOLO детектирует дефекты от 10 мкм, а anomaly‑map показывает даже скрытые аномалии. Система работает 24/7 без снижения точности и выдаёт цифровой протокол для каждого компонента. Согласно AS9100 Rev D, максимально допустимый False Accept Rate составляет 0,1%.AS9100 Rev D, раздел 8.2.4
Какие дефекты обнаруживает AI-система?
Модель обучена на 5000+ снимков с макро- и телецентрических камер, CT-сканеров, эндоскопов. Система классифицирует дефекты по типам и размерам:
| Тип дефекта | Описание | Критерий отбраковки |
|---|---|---|
| crack | Трещина любого размера | Scrap (немедленный брак) |
| pit | Питтинг >1,0 мм | Scrap, 0,5–1,0 мм — rework |
| scratch | Царапина >2,5 мм | Scrap, 1,0–2,5 мм — rework |
| erosion/coating_damage | Эрозия >2,0 мм | Scrap (по AMS 2750) |
| dimension_anomaly | Отклонение геометрии >0,3 мм | Scrap/rework |
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 дет/час |
Как мы гарантируем соответствие AS9100?
Каждое решение проходит валидацию на референсных наборах с известными дефектами. Мы используем технику cross-validation по партиям (batch-wise), чтобы исключить перекос по типу аномалий. Итоговые метрики FAR и FRR подтверждаются независимым аудитом. На выходе вы получаете model card с полной спецификацией: ROC-кривые, latency p99 при batch size 1, профиль GPU utilization на NVIDIA A10G.
Полная документация по стандартам
В документацию включены: критерии AMS 2750, AC 43.13-1B, AS9100 Rev D, DO-178C. Для каждого дефекта приведены пороговые значения, методика валидации и протокол аудита.Что входит в работу
Мы поставляем не модель — а готовое решение. В deliverables входят:
- dataset разметки (5000+ изображений с bounding box и anomaly-метками)
- обученный PatchCore + YOLO model (weights + config)
- pipeline инференса на GPU (Triton Inference Server или ONNX Runtime)
- API для интеграции в AET (интерфейс оператора)
- документация по стандартам (AMS 2750, AC 43.13-1B, AS9100)
- обучение персонала и поддержка 3 месяца
Процесс внедрения
- Аналитика — обследуем линию, замеряем GSD, освещение, типы дефектов.
- Сбор данных — делаем 1000+ снимков с дефектами и без, CT-сканы.
- Разметка — инженеры размечают по AMS-критериям.
- Разработка — обучаем PatchCore для anomaly-детекции и YOLO для классификации.
- Тестирование — прогоняем на валидационной выборке, добиваемся FAR <0.1%.
- Деплой — контейнеризация, интеграция с оборудованием, сертификация.
Сроки и гарантии
| Задача | Срок |
|---|---|
| PatchCore инспектор одного типа компонента | 6–10 недель |
| YOLO + классификация по стандартам | 12–18 недель |
| AS9100 certified inspection system | 24–40 недель |
Гарантируем точность в спецификации. За 10 лет в компьютерном зрении и 5 лет в аэрокосмических решениях мы накопили опыт, который позволяет внедрять системы с нулевым риском. Внедрение AI-системы окупается за 8–12 месяцев за счёт снижения FAR и уменьшения ручного труда. Экономия на инспекции может достигать $250 000 в год при объёме 50 000 деталей. Свяжитесь с нами для оценки вашего проекта — приложите фото дефектов и требования к throughput. Закажите консультацию, чтобы узнать, как AI-контроль впишется в вашу линию.







