yolocar / src /comparison.py
minh9972t12's picture
Upload 12 files
c8a046c
raw
history blame
7.68 kB
import numpy as np
from typing import List, Dict
from scipy.optimize import linear_sum_assignment
import yaml
class DamageComparator:
"""Compare damages between before and after images"""
def __init__(self, config_path: str = "config.yaml"):
"""Initialize comparator with configuration"""
with open(config_path, 'r') as f:
self.config = yaml.safe_load(f)
self.iou_threshold = self.config['comparison']['iou_match_threshold']
self.position_tolerance = self.config['comparison']['position_tolerance']
def calculate_iou(self, box1: List[int], box2: List[int]) -> float:
"""
Calculate Intersection over Union between two boxes
Args:
box1, box2: Bounding boxes in format [x1, y1, x2, y2]
Returns:
IoU value between 0 and 1
"""
# Calculate intersection area
x1 = max(box1[0], box2[0])
y1 = max(box1[1], box2[1])
x2 = min(box1[2], box2[2])
y2 = min(box1[3], box2[3])
if x2 < x1 or y2 < y1:
return 0.0
intersection = (x2 - x1) * (y2 - y1)
# Calculate union area
box1_area = (box1[2] - box1[0]) * (box1[3] - box1[1])
box2_area = (box2[2] - box2[0]) * (box2[3] - box2[1])
union = box1_area + box2_area - intersection
# Calculate IoU
if union == 0:
return 0.0
return intersection / union
def match_damages(self, detections1: Dict, detections2: Dict) -> Dict:
"""
Match damages between two sets of detections using Hungarian algorithm
Args:
detections1: First detection results (before)
detections2: Second detection results (after)
Returns:
Matching results with paired and unpaired damages
"""
boxes1 = detections1['boxes']
boxes2 = detections2['boxes']
if len(boxes1) == 0 and len(boxes2) == 0:
return {
'matched_pairs': [],
'unmatched_before': [],
'unmatched_after': [],
'iou_matrix': None
}
if len(boxes1) == 0:
return {
'matched_pairs': [],
'unmatched_before': [],
'unmatched_after': list(range(len(boxes2))),
'iou_matrix': None
}
if len(boxes2) == 0:
return {
'matched_pairs': [],
'unmatched_before': list(range(len(boxes1))),
'unmatched_after': [],
'iou_matrix': None
}
# Calculate IoU matrix
iou_matrix = np.zeros((len(boxes1), len(boxes2)))
for i, box1 in enumerate(boxes1):
for j, box2 in enumerate(boxes2):
iou_matrix[i, j] = self.calculate_iou(box1, box2)
# Use Hungarian algorithm for optimal matching
# Convert to cost matrix (1 - IoU)
cost_matrix = 1 - iou_matrix
row_indices, col_indices = linear_sum_assignment(cost_matrix)
# Filter matches by IoU threshold
matched_pairs = []
matched_rows = set()
matched_cols = set()
for i, j in zip(row_indices, col_indices):
if iou_matrix[i, j] >= self.iou_threshold:
# Also check if damage types match
if detections1['classes'][i] == detections2['classes'][j]:
matched_pairs.append((i, j, iou_matrix[i, j]))
matched_rows.add(i)
matched_cols.add(j)
# Find unmatched damages
unmatched_before = [i for i in range(len(boxes1)) if i not in matched_rows]
unmatched_after = [j for j in range(len(boxes2)) if j not in matched_cols]
return {
'matched_pairs': matched_pairs,
'unmatched_before': unmatched_before,
'unmatched_after': unmatched_after,
'iou_matrix': iou_matrix.tolist()
}
def analyze_damage_status(self, before_detections: Dict, after_detections: Dict) -> Dict:
"""
Analyze damage status between before and after images
Returns detailed analysis with case classification
"""
matching = self.match_damages(before_detections, after_detections)
# Extract damage information
matched_damages = []
for i, j, iou in matching['matched_pairs']:
matched_damages.append({
'type': before_detections['classes'][i],
'confidence_before': before_detections['confidences'][i],
'confidence_after': after_detections['confidences'][j],
'box_before': before_detections['boxes'][i],
'box_after': after_detections['boxes'][j],
'iou': iou
})
existing_damages = []
for i in matching['unmatched_before']:
existing_damages.append({
'type': before_detections['classes'][i],
'confidence': before_detections['confidences'][i],
'box': before_detections['boxes'][i]
})
new_damages = []
for j in matching['unmatched_after']:
new_damages.append({
'type': after_detections['classes'][j],
'confidence': after_detections['confidences'][j],
'box': after_detections['boxes'][j]
})
# Determine case
case = self._determine_case(matched_damages, existing_damages, new_damages)
return {
'case': case['type'],
'message': case['message'],
'matched_damages': matched_damages,
'repaired_damages': existing_damages, # Damages that were there before but not after
'new_damages': new_damages,
'statistics': {
'total_before': len(before_detections['boxes']),
'total_after': len(after_detections['boxes']),
'matched': len(matched_damages),
'repaired': len(existing_damages),
'new': len(new_damages)
}
}
def _determine_case(self, matched: List, repaired: List, new: List) -> Dict:
"""Determine which case the comparison falls into"""
# Case 3: Happy case - no damages at all
if len(matched) == 0 and len(repaired) == 0 and len(new) == 0:
return {
'type': 'CASE_3_SUCCESS',
'message': 'Successful delivery - No damage detected'
}
# Case 1: Existing damages remain (with or without repairs/new damages)
if len(matched) > 0 and len(new) == 0:
return {
'type': 'CASE_1_EXISTING',
'message': 'Error from the beginning, not during the delivery process -> Delivery completed'
}
# Case 2: New damages detected
if len(new) > 0:
return {
'type': 'CASE_2_NEW_DAMAGE',
'message': 'Delivery Defect - New Damage Discovered'
}
# Special case: All damages repaired
if len(repaired) > 0 and len(new) == 0 and len(matched) == 0:
return {
'type': 'CASE_REPAIRED',
'message': 'All damage repaired - Vehicle delivered successfully'
}
return {
'type': 'CASE_UNKNOWN',
'message': 'Status Undetermined'
}