AI Detection of Structural Defects from Photos
AI diagnosis of structural defects — cracks in concrete, rebar corrosion, masonry delamination — is critical for building safety. Traditional manual inspection is subjective: different inspectors classify the same crack differently. A neural network provides reproducible assessment with quantitative metrics. For example, crack width is measured to 0.1 mm accuracy, and corrosion area with less than 5% error. Our team specializes in such solutions: over 5 years of experience, over 50 deployments for construction expertise. We guarantee severity classification accuracy of 94%.
Task: Classification and Segmentation of Defects
Structural defects require pixel-level accuracy, not just bounding boxes — we care about crack length, width, orientation. This is a computer vision task, specifically semantic segmentation (see Wikipedia). Comparison: a neural network works 20 times faster than a manual inspector, with severity classification accuracy reaching 94%.
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] # degrees from vertical
bounding_box: list
class StructuralDefectDetector:
def __init__(self, model_path: str):
"""
UNet++ with EfficientNet-B5 encoder.
Fine-tuned on Concrete Crack Images Dataset (40k images)
+ own dataset with corrosion and delamination.
"""
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: scale (from drone or laser metadata).
Allows returning sizes in mm instead of pixels.
"""
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)
# Scale mask back to original size
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: # noise filter
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)
# For cracks — skeletonization for length/width
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:
"""Skeletonization of crack for width and length measurement"""
from skimage.morphology import skeletonize
skeleton = skeletonize(crack_roi > 0)
length = float(skeleton.sum()) # skeleton pixels ≈ length
# Width via distance transform
dist = cv2.distanceTransform(crack_roi, cv2.DIST_L2, 5)
max_width = float(dist.max() * 2) if dist.max() > 0 else 0
# Orientation via 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 by width (mm)
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'
# For others — by area
if area_ratio < 0.005: return 'minor'
if area_ratio < 0.02: return 'moderate'
if area_ratio < 0.05: return 'severe'
return 'critical'
Why Segmentation is More Accurate than Bounding Boxes for Cracks?
Cracks occupy a small area, often less than 1% of pixels. Bounding boxes capture much background, distorting metrics. Segmentation provides a mask for each pixel, allowing accurate measurement of length, width, and orientation. For hairline cracks (width < 0.2 mm), a bounding box can show 10 times the actual area. Segmentation solves this.
How Does the Neural Network Determine Defect Type?
The model is trained on 40,000 images from open datasets and our own collection, labeled by construction experts. It uses the UNet++ architecture with an EfficientNet-B5 encoder — it achieves the best IoU for small objects. It detects 4 types of defects: cracks, corrosion, spalling, and delamination.
Model Quality Metrics
- IoU for cracks: 0.78 - IoU for corrosion: 0.72 - F1-score: 0.85 - Precision/Recall: 0.83/0.87Defect Assessment Standards
| Defect Type | Severity Criteria | Standard (GOST/SP) |
|---|---|---|
| Crack in concrete | Crack width > 0.3mm | SP 20.13330 |
| Crack in reinforced concrete (bending) | > 0.2mm normal, > 0.1mm diagonal | GOST R 55961 |
| Rebar corrosion | Area > 10% of cross-section | SP 28.13330 |
| Spalling / concrete chipping | Depth > 20mm | — |
Case Study: Inspection of 120 Overpass Supports
From our practice: technical condition assessment of an overpass using drone photography.
- 120 supports, 3500 photos with GSD 0.5–1.5 mm/pixel
- Processing: 2.5 hours on RTX 3090
- Found: 847 cracks (23 critical, width > 1 mm), 156 corrosion areas
- Manual verification of 5% random results: 94% severity classification accuracy
- Budget savings for expertise amounted to 200,000 RUB per object
- For another object with 50 supports, savings reached 180,000 RUB
- System payback period — less than one year due to reduced costs for on-site inspections and re-inspections
Implementation Process: Step by Step
- Object analysis and requirements gathering — determine defect types, shooting conditions, required accuracy.
- Data collection and labeling — at least 2000 images for your object. Labeling is done by construction engineers.
- Model training — use UNet++ with augmentation, fine-tuning for your domain.
- Integration — REST API on FastAPI, support for PNG/JPG, EXIF metadata.
- Testing and validation — check on independent test set, compare with manual expertise.
- Documentation and handover — API description, shooting requirements, training of your specialists.
Checklist: What Matters for Accurate Diagnosis
- Quality shooting with GSD 0.5–1.5 mm/pixel
- Uniform lighting, no glare
- Metadata (focal length, shooting height) for pixel-to-mm conversion
- At least 200 images of your object for fine-tuning (if needed)
What Is Included in Our Work
- Data collection and labeling (minimum 2000 images for your object).
- Training segmentation model with augmentation and scale (GSD) recognition.
- Integration: REST API on FastAPI, support for PNG/JPG, EXIF metadata.
- Documentation: API description, shooting requirements, integration examples.
- Warranty: 6 months free model refinement if shooting conditions change.
Estimated Timelines
| Project Type | Timeline |
|---|---|
| Crack detector (segmentation) | 4–6 weeks |
| Full system (4 defect types + metrics) | 7–12 weeks |
| With mm measurements and standard assessment | 10–16 weeks |
Order a pilot inspection of one object — we will demonstrate accuracy on your data. Contact us to evaluate your project. Get a consultation from an engineer on automating building inspection with AI.







