| name | defect-detection-ai |
| description | AI-powered construction defect detection using computer vision. Identify cracks, spalling, corrosion, and other defects in concrete, steel, and building components from images and video. |
| homepage | https://datadrivenconstruction.io |
| metadata | {"openclaw":{"emoji":"🚀","os":["darwin","linux","win32"],"homepage":"https://datadrivenconstruction.io","requires":{"bins":"[Truncated]"}}} |
AI Defect Detection
Overview
This skill implements deep learning-based defect detection for construction quality control. Analyze images and video to automatically identify structural and surface defects, classify severity, and generate inspection reports.
Detectable Defects:
- Concrete: Cracks, spalling, honeycombing, efflorescence
- Steel: Corrosion, weld defects, deformation
- Masonry: Mortar deterioration, displacement
- Finishes: Surface defects, coating failures
- MEP: Insulation damage, pipe corrosion
Quick Start
import torch
import torch.nn as nn
from torchvision import transforms, models
from PIL import Image
from dataclasses import dataclass
from typing import List, Dict, Tuple
from enum import Enum
class DefectType(Enum):
CRACK = "crack"
SPALLING = "spalling"
CORROSION = "corrosion"
HONEYCOMBING = "honeycombing"
EFFLORESCENCE = "efflorescence"
DEFORMATION = "deformation"
SURFACE_DAMAGE = "surface_damage"
NO_DEFECT = "no_defect"
class SeverityLevel(Enum):
MINOR = "minor"
MODERATE = "moderate"
SEVERE = "severe"
CRITICAL = "critical"
@dataclass
class DefectDetection:
defect_type: DefectType
confidence: float
severity: SeverityLevel
bounding_box: Tuple[int, int, int, int]
area_ratio: float
class SimpleDefectClassifier:
def __init__(self, num_classes: int = 8):
self.model = models.resnet18(pretrained=True)
self.model.fc = nn.Linear(self.model.fc.in_features, num_classes)
self.model.eval()
self.transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
self.classes = list(DefectType)
def predict(self, image_path: str) -> DefectDetection:
"""Classify defect in image"""
image = Image.open(image_path).convert('RGB')
input_tensor = self.transform(image).unsqueeze(0)
with torch.no_grad():
outputs = self.model(input_tensor)
probs = torch.softmax(outputs, dim=1)
confidence, predicted = torch.max(probs, 1)
defect_type = self.classes[predicted.item()]
return DefectDetection(
defect_type=defect_type,
confidence=confidence.item(),
severity=self._estimate_severity(confidence.item()),
bounding_box=(0, 0, image.width, image.height),
area_ratio=1.0
)
def _estimate_severity(self, confidence: float) -> SeverityLevel:
if confidence > 0.9:
return SeverityLevel.CRITICAL
elif confidence > 0.7:
return SeverityLevel.SEVERE
elif confidence > 0.5:
return SeverityLevel.MODERATE
else:
return SeverityLevel.MINOR
classifier = SimpleDefectClassifier()
Comprehensive Defect Detection System
Object Detection Model
import torch
import torch.nn as nn
from torchvision import transforms
from torchvision.models.detection import fasterrcnn_resnet50_fpn
from PIL import Image
import numpy as np
from dataclasses import dataclass, field
from typing import List, Dict, Tuple, Optional
from datetime import datetime
import json
@dataclass
class BoundingBox:
x1: int
y1: int
x2: int
y2: int
@property
def width(self) -> int:
return self.x2 - self.x1
@property
def height(self) -> int:
return self.y2 - self.y1
@property
def area(self) -> int:
return self.width * .height
() -> [, ]:
((.x1 + .x2) // , (.y1 + .y2) // )
:
defect_id:
defect_type: DefectType
confidence:
severity: SeverityLevel
bounding_box: BoundingBox
area_sqm: [] =
dimensions_mm: [[, ]] =
metadata: = field(default_factory=)
:
inspection_id:
image_path:
timestamp: datetime
location:
element_type:
defects: [DetectedDefect]
overall_condition:
recommended_actions: []
:
DEFECT_CLASSES = {
: DefectType.CRACK,
: DefectType.SPALLING,
: DefectType.CORROSION,
: DefectType.HONEYCOMBING,
: DefectType.EFFLORESCENCE,
: DefectType.DEFORMATION,
: DefectType.SURFACE_DAMAGE
}
():
.device = torch.device(device)
.model = fasterrcnn_resnet50_fpn(pretrained=)
num_classes = (.DEFECT_CLASSES) +
in_features = .model.roi_heads.box_predictor.cls_score.in_features
.model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)
model_path:
.model.load_state_dict(torch.load(model_path, map_location=.device))
.model.to(.device)
.model.()
.transform = transforms.Compose([
transforms.ToTensor()
])
() -> [DetectedDefect]:
image = Image.(image_path).convert()
image_tensor = .transform(image).to(.device)
torch.no_grad():
predictions = .model([image_tensor])
pred = predictions[]
defects = []
i ((pred[])):
score = pred[][i].item()
score < confidence_threshold:
label = pred[][i].item()
box = pred[][i].cpu().numpy()
defect_type = .DEFECT_CLASSES.get(label, DefectType.SURFACE_DAMAGE)
bbox = BoundingBox(
x1=(box[]),
y1=(box[]),
x2=(box[]),
y2=(box[])
)
dimensions_mm =
pixels_per_mm:
width_mm = bbox.width / pixels_per_mm
height_mm = bbox.height / pixels_per_mm
dimensions_mm = (width_mm, height_mm)
severity = ._classify_severity(defect_type, bbox, image.size)
defects.append(DetectedDefect(
defect_id=,
defect_type=defect_type,
confidence=score,
severity=severity,
bounding_box=bbox,
dimensions_mm=dimensions_mm
))
defects
() -> SeverityLevel:
image_area = image_size[] * image_size[]
defect_ratio = bbox.area / image_area
thresholds = {
DefectType.CRACK: {: , : , : },
DefectType.SPALLING: {: , : , : },
DefectType.CORROSION: {: , : , : },
DefectType.HONEYCOMBING: {: , : , : },
DefectType.DEFORMATION: {: , : , : }
}
t = thresholds.get(defect_type, {: , : , : })
defect_ratio >= t[]:
SeverityLevel.CRITICAL
defect_ratio >= t[]:
SeverityLevel.SEVERE
defect_ratio >= t[]:
SeverityLevel.MODERATE
:
SeverityLevel.MINOR
(nn.Module):
():
().__init__()
.cls_score = nn.Linear(in_channels, num_classes)
.bbox_pred = nn.Linear(in_channels, num_classes * )
():
scores = .cls_score(x)
bbox_deltas = .bbox_pred(x)
scores, bbox_deltas
Crack Analysis System
import cv2
import numpy as np
from typing import List, Tuple, Dict
class CrackAnalyzer:
"""Specialized crack detection and measurement"""
def __init__(self):
self.min_crack_length = 10
self.min_crack_width = 2
def detect_cracks(self, image_path: str,
pixels_per_mm: float = 1.0) -> List[Dict]:
"""Detect and measure cracks in image"""
image = cv2.imread(image_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
enhanced = clahe.apply(gray)
edges = cv2.Canny(enhanced, 50, 150)
kernel = np.ones((3, 3), np.uint8)
dilated = cv2.dilate(edges, kernel, iterations=1)
closed = cv2.morphologyEx(dilated, cv2.MORPH_CLOSE, kernel)
contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cracks = []
for i, contour (contours):
arc_length = cv2.arcLength(contour, )
arc_length < .min_crack_length:
x, y, w, h = cv2.boundingRect(contour)
length_px = arc_length
width_px = ._estimate_crack_width(gray, contour)
length_mm = length_px / pixels_per_mm
width_mm = width_px / pixels_per_mm
crack_type = ._classify_crack(length_mm, width_mm, contour)
cracks.append({
: ,
: crack_type,
: length_mm,
: width_mm,
: (x, y, x + w, y + h),
: contour.tolist(),
: ._get_crack_severity(width_mm, length_mm),
: ._get_crack_orientation(contour)
})
cracks
() -> :
mask = np.zeros(gray_image.shape, dtype=np.uint8)
cv2.drawContours(mask, [contour], -, , )
dist = cv2.distanceTransform(mask, cv2.DIST_L2, )
nonzero = dist[dist > ]
(nonzero) > :
np.mean(nonzero) *
() -> :
[vx, vy, x, y] = cv2.fitLine(contour, cv2.DIST_L2, , , )
angle = np.arctan2(vy, vx) * / np.pi
(angle) < (angle) > :
orientation =
< (angle) < :
orientation =
:
orientation =
width_mm > :
orientation == length_mm > :
orientation == :
:
() -> :
width_mm > :
width_mm > :
width_mm > :
:
() -> :
[vx, vy, x, y] = cv2.fitLine(contour, cv2.DIST_L2, , , )
(np.arctan2(vy, vx) * / np.pi)
() -> :
cracks:
{: }
total_length = (c[] c cracks)
max_width = (c[] c cracks)
severity_counts = {}
c cracks:
sev = c[]
severity_counts[sev] = severity_counts.get(sev, ) +
{
: (cracks),
: total_length,
: max_width,
: (c[] c cracks) / (cracks),
: severity_counts,
: ._group_by_type(cracks),
: (cracks, key= c: c[])
}
() -> :
grouped = {}
c cracks:
t = c[]
t grouped:
grouped[t] = []
grouped[t].append(c[])
grouped
Inspection Report Generator
from datetime import datetime
import pandas as pd
class DefectInspectionSystem:
"""Complete defect inspection and reporting system"""
def __init__(self, detection_model: DefectDetectionModel):
self.model = detection_model
self.crack_analyzer = CrackAnalyzer()
self.inspections: List[InspectionResult] = []
def perform_inspection(self, image_path: str,
location: str,
element_type: str,
pixels_per_mm: float = None) -> InspectionResult:
"""Perform complete inspection on image"""
defects = self.model.detect(image_path, pixels_per_mm=pixels_per_mm)
if element_type.lower() in ['concrete', 'slab', 'wall', 'column', 'beam']:
cracks = self.crack_analyzer.detect_cracks(image_path, pixels_per_mm or 1.0)
for defect in defects:
if defect.defect_type == DefectType.CRACK:
for crack in cracks:
._boxes_overlap(defect.bounding_box, crack[]):
defect.metadata[] = crack
overall_condition = ._assess_overall_condition(defects)
recommendations = ._generate_recommendations(defects, element_type)
result = InspectionResult(
inspection_id=,
image_path=image_path,
timestamp=datetime.now(),
location=location,
element_type=element_type,
defects=defects,
overall_condition=overall_condition,
recommended_actions=recommendations
)
.inspections.append(result)
result
() -> :
x1_1, y1_1, x2_1, y2_1 = box1.x1, box1.y1, box1.x2, box1.y2
x1_2, y1_2, x2_2, y2_2 = box2
(x2_1 < x1_2 x2_2 < x1_1 y2_1 < y1_2 y2_2 < y1_1)
() -> :
defects:
severity_scores = {
SeverityLevel.MINOR: ,
SeverityLevel.MODERATE: ,
SeverityLevel.SEVERE: ,
SeverityLevel.CRITICAL:
}
max_severity = (severity_scores[d.severity] d defects)
total_defects = (defects)
max_severity >= total_defects > :
max_severity >= total_defects > :
max_severity >= total_defects > :
:
() -> []:
recommendations = []
defect_groups = {}
d defects:
t = d.defect_type
t defect_groups:
defect_groups[t] = []
defect_groups[t].append(d)
defect_type, group defect_groups.items():
max_severity = (d.severity d group)
defect_type == DefectType.CRACK:
max_severity [SeverityLevel.CRITICAL, SeverityLevel.SEVERE]:
recommendations.append(
)
:
recommendations.append(
)
defect_type == DefectType.SPALLING:
recommendations.append(
)
defect_type == DefectType.CORROSION:
recommendations.append(
)
defect_type == DefectType.HONEYCOMBING:
recommendations.append(
)
defect_type == DefectType.EFFLORESCENCE:
recommendations.append(
)
recommendations:
recommendations.append()
recommendations
() -> :
inspection = (
(i i .inspections i.inspection_id == inspection_id),
)
inspection:
ValueError()
pd.ExcelWriter(output_path, engine=) writer:
summary = pd.DataFrame([{
: inspection.inspection_id,
: inspection.timestamp.strftime(),
: inspection.location,
: inspection.element_type,
: inspection.overall_condition,
: (inspection.defects),
: inspection.image_path
}])
summary.to_excel(writer, sheet_name=, index=)
inspection.defects:
defect_data = [{
: d.defect_id,
: d.defect_type.value,
: d.severity.value,
: ,
: ,
: ,
: d.dimensions_mm d.dimensions_mm
} d inspection.defects]
pd.DataFrame(defect_data).to_excel(writer, sheet_name=, index=)
rec_data = [{: i+, : r}
i, r (inspection.recommended_actions)]
pd.DataFrame(rec_data).to_excel(writer, sheet_name=, index=)
output_path
() -> :
filtered = .inspections
start_date:
filtered = [i i filtered i.timestamp >= start_date]
end_date:
filtered = [i i filtered i.timestamp <= end_date]
all_defects = []
inspection filtered:
all_defects.extend(inspection.defects)
all_defects:
{: }
by_type = {}
by_severity = {}
d all_defects:
t = d.defect_type.value
s = d.severity.value
by_type[t] = by_type.get(t, ) +
by_severity[s] = by_severity.get(s, ) +
{
: {
: start_date.isoformat() start_date ,
: end_date.isoformat() end_date
},
: (filtered),
: (all_defects),
: by_type,
: by_severity,
: (all_defects) / (filtered) filtered
}
Model Training
import torch
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
import os
from PIL import Image
class DefectDataset(Dataset):
"""Dataset for training defect detection model"""
def __init__(self, root_dir: str, annotations_file: str, transform=None):
self.root_dir = root_dir
self.annotations = self._load_annotations(annotations_file)
self.transform = transform or transforms.Compose([
transforms.Resize((800, 800)),
transforms.ToTensor()
])
def _load_annotations(self, path: str) -> List[Dict]:
"""Load COCO-format annotations"""
import json
with open(path, 'r') as f:
data = json.load(f)
return data['annotations']
def __len__(self):
return len(self.annotations)
def __getitem__():
ann = .annotations[idx]
image_path = os.path.join(.root_dir, ann[])
image = Image.(image_path).convert()
.transform:
image = .transform(image)
boxes = torch.tensor(ann[], dtype=torch.float32)
labels = torch.tensor(ann[], dtype=torch.int64)
target = {
: boxes,
: labels
}
image, target
():
device = torch.device( torch.cuda.is_available() )
model = fasterrcnn_resnet50_fpn(pretrained=)
num_classes =
in_features = model.roi_heads.box_predictor.cls_score.in_features
model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)
model.to(device)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=,
collate_fn= x: ((*x)))
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=,
collate_fn= x: ((*x)))
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate,
momentum=, weight_decay=)
epoch (num_epochs):
model.train()
total_loss =
images, targets train_loader:
images = [img.to(device) img images]
targets = [{k: v.to(device) k, v t.items()} t targets]
loss_dict = model(images, targets)
losses = (loss loss loss_dict.values())
optimizer.zero_grad()
losses.backward()
optimizer.step()
total_loss += losses.item()
avg_loss = total_loss / (train_loader)
()
model
Quick Reference
| Defect Type | Detection Method | Typical Severity |
|---|
| Crack | Edge detection + CNN | Varies by width |
| Spalling | Object detection | Moderate-Severe |
| Corrosion | Color + texture analysis | Moderate-Critical |
| Honeycombing | Object detection | Severe |
| Efflorescence | Color analysis | Minor-Moderate |
ACI 224R Crack Width Guidelines
| Width (mm) | Condition | Exposure |
|---|
| < 0.1 | Acceptable | Any |
| 0.1 - 0.2 | Acceptable | Dry |
| 0.2 - 0.4 | Repair recommended | Humid |
| > 0.4 | Repair required | Any |
| > 1.0 | Structural concern | Any |
Resources
Next Steps
- See
progress-monitoring-cv for construction progress analysis
- See
safety-compliance-checker for safety defect integration
- See
bim-validation-pipeline for model-based quality control