Infrastructure Inspection with AI Drones
Aging infrastructure worldwide needs regular inspection. AI-powered drones can safely examine bridges, power lines, and buildings—detecting defects before they become catastrophic failures.
The Inspection System
Real-World Analogy
Think of infrastructure drones as "doctors with stethoscopes." Just as a doctor examines patients for early signs of disease, inspection drones scan structures for cracks, corrosion, and wear before they become critical.
SVG: Inspection Flight Path and Defect Classification
Bridge Inspection System
from typing import List, Dict, Tuple
from dataclasses import dataclass
from enum import Enum
class DefectType(Enum):
CRACK = "crack"
CORROSION = "corrosion"
DELAMINATION = "delamination"
DEFORMATION = "deformation"
WEAR = "wear"
class SeverityLevel(Enum):
MINOR = 1
MODERATE = 2
MAJOR = 3
CRITICAL = 4
@dataclass
class Defect:
defect_type: DefectType
severity: SeverityLevel
location: np.ndarray
dimensions: Tuple[float, float]
confidence: float
image_ref: str
class BridgeInspector:
def __init__(self, bridge_length: float, deck_width: float):
self.length = bridge_length
self.width = deck_width
self.defects: List[Defect] = []
def plan_inspection_path(self, altitude: float = 10.0,
overlap: float = 0.3) -> List[np.ndarray]:
"""Generate systematic inspection path"""
waypoints = []
track_spacing = 2.0 # meters between passes
# Under-deck inspection
for y in np.arange(0, self.width, track_spacing):
waypoints.append(np.array([0, y, -altitude]))
waypoints.append(np.array([self.length, y, -altitude]))
# Deck surface inspection
for y in np.arange(0, self.width, track_spacing):
waypoints.append(np.array([self.length, y, altitude]))
waypoints.append(np.array([0, y, altitude]))
# Side inspections
for x in np.arange(0, self.length, track_spacing * 2):
waypoints.append(np.array([x, 0, 0]))
waypoints.append(np.array([x, self.width, 0]))
return waypoints
def analyze_component(self, component_type: str,
sensor_data: Dict) -> List[Defect]:
"""Analyze a bridge component for defects"""
defects = []
if component_type == "deck":
defects.extend(self._analyze_deck(sensor_data))
elif component_type == "girder":
defects.extend(self._analyze_girder(sensor_data))
elif component_type == "bearing":
defects.extend(self._analyze_bearing(sensor_data))
return defects
def _analyze_deck(self, data: Dict) -> List[Defect]:
"""Analyze bridge deck for defects"""
defects = []
rgb = data.get('rgb')
thermal = data.get('thermal')
if rgb is not None:
# Crack detection using edge detection
cracks = self._detect_cracks(rgb)
for crack in cracks:
defects.append(Defect(
defect_type=DefectType.CRACK,
severity=self._classify_crack_severity(crack),
location=crack['center'],
dimensions=crack['dimensions'],
confidence=crack['confidence'],
image_ref=crack.get('image_id', '')
))
if thermal is not None:
# Delamination detection (trapped moisture)
delam = self._detect_delamination(thermal)
for d in delam:
defects.append(Defect(
defect_type=DefectType.DELAMINATION,
severity=SeverityLevel.MODERATE,
location=d['center'],
dimensions=d['area'],
confidence=d['confidence'],
image_ref=''
))
return defects
def _detect_cracks(self, rgb: np.ndarray) -> List[Dict]:
"""Detect cracks using computer vision"""
import cv2
gray = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)
edges = cv2.Canny(gray, 50, 150)
# Morphological operations to connect crack segments
kernel = np.ones((3, 3), np.uint8)
dilated = cv2.dilate(edges, kernel, iterations=2)
contours, _ = cv2.findContours(
dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cracks = []
for contour in contours:
area = cv2.contourArea(contour)
if 100 < area < 10000: # Filter by size
M = cv2.moments(contour)
if M["m00"] > 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
x, y, w, h = cv2.boundingRect(contour)
cracks.append({
'center': np.array([cx, cy]),
'dimensions': (w, h),
'area': area,
'confidence': min(0.95, area / 5000)
})
return cracks
def _classify_crack_severity(self, crack: Dict) -> SeverityLevel:
"""Classify crack severity based on dimensions"""
max_dim = max(crack['dimensions'])
if max_dim > 50:
return SeverityLevel.CRITICAL
elif max_dim > 20:
return SeverityLevel.MAJOR
elif max_dim > 5:
return SeverityLevel.MODERATE
return SeverityLevel.MINOR
def _detect_delamination(self, thermal: np.ndarray) -> List[Dict]:
"""Detect subsurface delamination using thermal"""
# Temperature anomalies indicate delamination
mean_temp = np.mean(thermal)
std_temp = np.std(thermal)
anomalies = []
mask = thermal > mean_temp + 2 * std_temp
from scipy import ndimage
labeled, num_features = ndimage.label(mask)
for i in range(1, num_features + 1):
component = labeled == i
area = np.sum(component)
if area > 50:
centroid = ndimage.center_of_mass(component)
anomalies.append({
'center': np.array(centroid),
'area': (np.sqrt(area), np.sqrt(area)),
'confidence': min(0.85, area / 500)
})
return anomalies
def generate_inspection_report(self) -> str:
"""Generate comprehensive inspection report"""
report = "=" * 60 + "\n"
report += "BRIDGE INSPECTION REPORT\n"
report += "=" * 60 + "\n\n"
report += f"Bridge Dimensions: {self.length}m x {self.width}m\n"
report += f"Total Defects Found: {len(self.defects)}\n\n"
# Summary by type
type_counts = {}
for defect in self.defects:
dtype = defect.defect_type.value
type_counts[dtype] = type_counts.get(dtype, 0) + 1
report += "Defects by Type:\n"
for dtype, count in type_counts.items():
report += f" - {dtype}: {count}\n"
# Summary by severity
severity_counts = {}
for defect in self.defects:
sev = defect.severity.name
severity_counts[sev] = severity_counts.get(sev, 0) + 1
report += "\nDefects by Severity:\n"
for sev, count in severity_counts.items():
report += f" - {sev}: {count}\n"
# Priority actions
critical = [d for d in self.defects
if d.severity == SeverityLevel.CRITICAL]
if critical:
report += f"\n*** {len(critical)} CRITICAL DEFECTS REQUIRE IMMEDIATE ATTENTION ***\n"
return report
Power Line Monitoring
from typing import List, Dict, Tuple
from dataclasses import dataclass
@dataclass
class ThermalAnomaly:
component: str
temperature: float
ambient_temp: float
rise_above_ambient: float
severity: str
location: np.ndarray
class PowerLineMonitor:
def __init__(self, voltage_level: str = "115kV"):
self.voltage = voltage_level
self.max_temperature = {
"115kV": 90,
"230kV": 100,
"500kV": 110
}
def inspect_conductor(self, thermal_data: np.ndarray,
ambient_temp: float,
position: np.ndarray) -> ThermalAnomaly:
"""Inspect power line conductor"""
max_temp = np.max(thermal_data)
mean_temp = np.mean(thermal_data)
rise = max_temp - ambient_temp
max_allowed = self.max_temperature.get(self.voltage, 100)
if rise > 40:
severity = "critical"
elif rise > 25:
severity = "warning"
elif rise > 15:
severity = "monitor"
else:
severity = "normal"
return ThermalAnomaly(
component="conductor",
temperature=max_temp,
ambient_temp=ambient_temp,
rise_above_ambient=rise,
severity=severity,
location=position
)
def inspect_insulator(self, thermal_data: np.ndarray,
visual_data: np.ndarray) -> Dict:
"""Inspect insulator for contamination or damage"""
thermal_analysis = {
'hot_spots': self._find_hot_spots(thermal_data),
'temperature_gradient': self._calc_gradient(thermal_data),
'contamination_risk': 'low'
}
visual_analysis = {
'cracks_detected': False,
'contamination_visible': False,
'flashover_risk': 'low'
}
# High temperature gradient may indicate contamination
if thermal_analysis['temperature_gradient'] > 5:
thermal_analysis['contamination_risk'] = 'high'
visual_analysis['contamination_visible'] = True
return {
'thermal': thermal_analysis,
'visual': visual_analysis,
'recommendation': self._get_insulator_recommendation(
thermal_analysis, visual_analysis)
}
def _find_hot_spots(self, thermal: np.ndarray,
threshold_factor: float = 1.5) -> List[Tuple[int, int]]:
"""Find thermal hot spots"""
mean_temp = np.mean(thermal)
threshold = mean_temp * threshold_factor
hot_spots = []
mask = thermal > threshold
from scipy import ndimage
labeled, num_features = ndimage.label(mask)
for i in range(1, num_features + 1):
centroid = ndimage.center_of_mass(labeled == i)
hot_spots.append((int(centroid[0]), int(centroid[1])))
return hot_spots
def _calc_gradient(self, thermal: np.ndarray) -> float:
"""Calculate temperature gradient"""
grad_y, grad_x = np.gradient(thermal)
return np.mean(np.sqrt(grad_x**2 + grad_y**2))
def _get_insulator_recommendation(self, thermal: Dict,
visual: Dict) -> str:
if thermal['contamination_risk'] == 'high':
return "Schedule cleaning/replacement"
elif visual['cracks_detected']:
return "Replace insulator"
return "Continue monitoring"
def plan_inspection_route(self, line_segments: List[Dict]) -> List[np.ndarray]:
"""Plan efficient inspection route along power lines"""
waypoints = []
for segment in line_segments:
start = np.array(segment['start'])
end = np.array(segment['end'])
# Generate points along segment
n_points = max(5, int(np.linalg.norm(end - start) / 20))
for i in range(n_points):
t = i / (n_points - 1)
point = start + t * (end - start)
waypoints.append(point)
return waypoints
Building Facade Analysis
from typing import List, Dict, Tuple
from dataclasses import dataclass
@dataclass
class FacadeIssue:
issue_type: str
severity: str
location: Tuple[int, int]
affected_area: float
description: str
class FacadeAnalyzer:
def __init__(self, building_height: float, num_floors: int):
self.height = building_height
self.floors = num_floors
self.floor_height = building_height / num_floors
def analyze_facade(self, rgb_data: np.ndarray,
depth_data: np.ndarray = None) -> List[FacadeIssue]:
"""Complete facade analysis"""
issues = []
# Window analysis
issues.extend(self._analyze_windows(rgb_data))
# Surface defects
issues.extend(self._detect_surface_defects(rgb_data))
# Structural deformation (if depth data available)
if depth_data is not None:
issues.extend(self._detect_deformation(depth_data))
return issues
def _analyze_windows(self, rgb: np.ndarray) -> List[FacadeIssue]:
"""Analyze windows for damage"""
issues = []
h, w, _ = rgb.shape
# Detect window regions (blue/glass color)
blue_mask = (rgb[:,:,2] > 150) & (rgb[:,:,0] < 100)
from scipy import ndimage
labeled, num_features = ndimage.label(blue_mask)
for i in range(1, num_features + 1):
component = labeled == i
area = np.sum(component)
if 500 < area < 5000: # Window size range
# Check for cracks or damage
centroid = ndimage.center_of_mass(component)
window_rgb = rgb[component]
# Check for discoloration
mean_color = np.mean(window_rgb, axis=0)
if mean_color[2] < 120: # Less blue than expected
issues.append(FacadeIssue(
issue_type="window_damage",
severity="moderate",
location=(int(centroid[0]), int(centroid[1])),
affected_area=area * 0.1, # Approximate pixel to m²
description="Possible cracked or damaged window"
))
return issues
def _detect_surface_defects(self, rgb: np.ndarray) -> List[FacadeIssue]:
"""Detect surface cracks, stains, etc."""
issues = []
gray = np.mean(rgb, axis=2)
# Edge detection for cracks
from scipy import ndimage
edges = ndimage.sobel(gray)
# Threshold for significant edges
crack_mask = edges > np.percentile(edges, 95)
labeled, num_features = ndimage.label(crack_mask)
for i in range(1, num_features + 1):
component = labeled == i
area = np.sum(component)
if 20 < area < 500: # Crack size range
centroid = ndimage.center_of_mass(component)
issues.append(FacadeIssue(
issue_type="surface_crack",
severity="minor" if area < 100 else "moderate",
location=(int(centroid[0]), int(centroid[1])),
affected_area=area * 0.01,
description="Surface crack detected"
))
return issues
def _detect_deformation(self, depth: np.ndarray) -> List[FacadeIssue]:
"""Detect structural deformation using depth data"""
issues = []
# Expected plane
h, w = depth.shape
x, y = np.meshgrid(np.arange(w), np.arange(h))
# Fit plane to depth data
A = np.column_stack([x.ravel(), y.ravel(), np.ones_like(x.ravel())])
result = np.linalg.lstsq(A, depth.ravel(), rcond=None)
plane = (result[0][0] * x + result[0][1] * y + result[0][2])
# Calculate deviation from plane
deviation = np.abs(depth - plane)
# Find significant deviations
threshold = np.std(deviation) * 2
deformed_mask = deviation > threshold
if np.sum(deformed_mask) > 100:
centroid = ndimage.center_of_mass(deformed_mask)
max_deviation = np.max(deviation)
issues.append(FacadeIssue(
issue_type="structural_deformation",
severity="major" if max_deviation > 0.1 else "moderate",
location=(int(centroid[0]), int(centroid[1])),
affected_area=np.sum(deformed_mask) * 0.01,
description=f"Structural deviation of {max_deviation:.3f}m detected"
))
return issues
Defect Detection AI Model
from typing import List, Dict, Tuple
from dataclasses import dataclass
@dataclass
class Detection:
bbox: Tuple[int, int, int, int]
class_name: str
confidence: float
defect_type: str
class DefectDetector:
def __init__(self, model_path: str = None):
self.classes = [
'crack', 'corrosion', 'spalling', 'efflorescence',
'delamination', 'hole', 'stain', 'deformation'
]
self.confidence_threshold = 0.5
def detect(self, image: np.ndarray) -> List[Detection]:
"""Run defect detection on image"""
# Placeholder for actual model inference
# In production, this would use a trained YOLO or similar model
detections = []
# Simulated detection based on color/texture analysis
gray = np.mean(image, axis=2)
# Simple crack-like feature detection
edges = self._simple_edge_detection(gray)
if np.sum(edges > 0.5) > 100:
detections.append(Detection(
bbox=(0, 0, image.shape[1], image.shape[0]),
class_name="crack",
confidence=0.75,
defect_type="structural"
))
return detections
def _simple_edge_detection(self, gray: np.ndarray) -> np.ndarray:
"""Simple edge detection"""
from scipy import ndimage
sobel_x = ndimage.sobel(gray, axis=0)
sobel_y = ndimage.sobel(gray, axis=1)
return np.sqrt(sobel_x**2 + sobel_y**2)
def estimate_repair_cost(self, detections: List[Detection]) -> Dict:
"""Estimate repair costs based on detections"""
cost_per_sqm = {
'crack': 150,
'corrosion': 200,
'spalling': 300,
'delamination': 250,
'deformation': 500
}
total_cost = 0
breakdown = {}
for det in detections:
area = (det.bbox[2] - det.bbox[0]) * (det.bbox[3] - det.bbox[1])
area_m2 = area * 0.001 # Convert to approximate m²
unit_cost = cost_per_sqm.get(det.defect_type, 200)
cost = area_m2 * unit_cost
total_cost += cost
breakdown[det.defect_type] = breakdown.get(det.defect_type, 0) + cost
return {
'total_estimate': total_cost,
'breakdown': breakdown,
'currency': 'USD'
}
Hands-On Project: Complete Bridge Inspection System
# bridge_inspection_system.py
from typing import List, Dict
from dataclasses import dataclass
from enum import Enum
class InspectionPhase(Enum):
PLANNING = "planning"
DATA_COLLECTION = "data_collection"
ANALYSIS = "analysis"
REPORTING = "reporting"
@dataclass
class InspectionTask:
phase: InspectionPhase
component: str
priority: int
estimated_time: float
class BridgeInspectionSystem:
def __init__(self, bridge_config: Dict):
self.config = bridge_config
self.inspector = BridgeInspector(
bridge_config['length'],
bridge_config['width']
)
self.defects = []
self.phase = InspectionPhase.PLANNING
def run_full_inspection(self) -> Dict:
"""Execute complete inspection workflow"""
results = {
'phases': {},
'defects': [],
'report': ''
}
# Phase 1: Planning
print("Phase 1: Planning inspection...")
flight_path = self.inspector.plan_inspection_path()
results['phases']['planning'] = {
'waypoints': len(flight_path),
'estimated_time': len(flight_path) * 0.5 # minutes
}
# Phase 2: Data Collection (simulated)
print("Phase 2: Collecting data...")
collected_data = self._simulate_data_collection(flight_path)
results['phases']['collection'] = {
'images': len(collected_data),
'coverage': '100%'
}
# Phase 3: Analysis
print("Phase 3: Analyzing data...")
for data_point in collected_data:
defects = self.inspector.analyze_component(
data_point['component'],
data_point['data']
)
self.defects.extend(defects)
results['phases']['analysis'] = {
'defects_found': len(self.defects)
}
# Phase 4: Reporting
print("Phase 4: Generating report...")
report = self.inspector.generate_inspection_report()
results['report'] = report
results['phases']['reporting'] = {'status': 'complete'}
return results
def _simulate_data_collection(self, waypoints: List) -> List[Dict]:
"""Simulate data collection at waypoints"""
data = []
components = ['deck', 'girder', 'bearing', 'abutment']
for i, wp in enumerate(waypoints[:20]): # Sample first 20
data.append({
'position': wp,
'component': components[i % len(components)],
'data': {
'rgb': np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8),
'thermal': np.random.uniform(20, 40, (100, 100))
}
})
return data
def get_critical_defects(self) -> List:
"""Get defects requiring immediate attention"""
return [d for d in self.defects
if d.severity in [SeverityLevel.CRITICAL, SeverityLevel.MAJOR]]
def estimate_repair_budget(self) -> Dict:
"""Estimate repair costs"""
# Simplified cost estimation
costs = {
SeverityLevel.MINOR: 1000,
SeverityLevel.MODERATE: 5000,
SeverityLevel.MAJOR: 20000,
SeverityLevel.CRITICAL: 50000
}
total = sum(costs.get(d.severity, 5000) for d in self.defects)
return {
'total_estimate': total,
'by_severity': {
sev.name: len([d for d in self.defects if d.severity == sev])
for sev in SeverityLevel
}
}
# Usage
bridge_config = {
'name': 'Main Street Bridge',
'length': 120,
'width': 12,
'type': 'girder',
'year_built': 1985
}
system = BridgeInspectionSystem(bridge_config)
results = system.run_full_inspection()
print(results['report'])
print(f"\nCritical defects: {len(system.get_critical_defects())}")
print(f"Estimated repair budget: ${system.estimate_repair_budget()['total_estimate']:,}")
Exercise
- Implement crack width measurement using pixel calibration
- Create a 3D point cloud analyzer for deformation detection
- Build a comparison system that tracks defect growth over multiple inspections
- Design an automated flight planning tool for complex structures
ℹ️
Infrastructure inspection drones can reduce inspection time by 80% while improving safety by removing inspectors from hazardous environments. The global market for inspection drones is expected to reach $15 billion by 2028.