123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- import csv
- import xlsxwriter
- from openpyxl import load_workbook
- from datetime import datetime
- from typing import List, Dict
- class ReportGenerator:
- def __init__(self, detector):
- self.metadata = {
- 'model_name': detector.model_path.split('/')[-1],
- 'model_version': detector.model_version,
- 'input_size': detector.input_size,
- 'test_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
- 'total_images': detector.image_count,
- 'confidence_threshold': detector.threshold
- }
- self.detections = detector.detection_records
- def generate_csv(self, output_path: str):
- with open(output_path, 'w', newline='', encoding='utf-8') as f:
- writer = csv.writer(f)
- writer.writerow(['Image File', 'Object Count', 'Max Confidence', 'BBox Center X', 'BBox Center Y', 'BBox Width', 'BBox Height', 'Normalized Coordinates'])
- for record in self.detections:
- if record['has_detection']:
- for detection in record['detections']:
- x1, y1, x2, y2 = detection['bbox']
- orig_w = detection.get('orig_w', 0)
- orig_h = detection.get('orig_h', 0)
- center_x = round((x1 + x2) / 2, 2)
- center_y = round((y1 + y2) / 2, 2)
- width = round(x2 - x1, 2)
- height = round(y2 - y1, 2)
- norm_cx = round(center_x / orig_w, 4) if orig_w > 0 else 0
- norm_cy = round(center_y / orig_h, 4) if orig_h > 0 else 0
- writer.writerow([
- record['image_path'],
- len(record['detections']),
- detection['confidence'],
- center_x,
- center_y,
- width,
- height,
- f"{norm_cx},{norm_cy},{width/orig_w},{height/orig_h}"
- ])
- else:
- writer.writerow([record['image_path'], 0, 0])
- def calculate_metrics(self, ground_truth: Dict[str, bool]) -> Dict[str, float]:
- """
- 计算模型评估指标
- :param ground_truth: 字典形式的地面真实值 {图像路径: 是否存在目标}
- :return: 包含各项指标的字典
- """
- tp = fp = tn = fn = 0
- for record in self.detections:
- actual = ground_truth.get(record['image_path'], False)
- predicted = record['has_detection']
- if actual and predicted:
- tp += 1
- elif actual and not predicted:
- fn += 1
- elif not actual and predicted:
- fp += 1
- else:
- tn += 1
- # 防止除零错误
- fpr = fp / (fp + tn) if (fp + tn) > 0 else 0
- fnr = fn / (fn + tp) if (fn + tp) > 0 else 0
- return {
- 'true_positive': tp,
- 'false_positive': fp,
- 'true_negative': tn,
- 'false_negative': fn,
- 'fpr': round(fpr, 4),
- 'fnr': round(fnr, 4)
- }
- def generate_excel(self, output_path: str, calculate_metrics: bool = False, ground_truth_file: str = None):
- workbook = xlsxwriter.Workbook(output_path)
- meta_ws = workbook.add_worksheet('Model Info')
- detail_ws = workbook.add_worksheet('Detection Results')
- # 写入元数据
- meta_headers = ['Model Name', 'Version', 'Input Size', 'Test Time', 'Image Count', 'Confidence Threshold']
- meta_ws.write_row(0, 0, meta_headers)
- meta_ws.write_row(1, 0, [
- self.metadata['model_name'],
- self.metadata['model_version'],
- self.metadata['input_size'],
- self.metadata['test_time'],
- self.metadata['total_images'],
- self.metadata['confidence_threshold']
- ])
- # 写入检测明细
- detail_headers = ['Image File', 'Object Count', 'Max Confidence', 'Avg Confidence', 'BBox Center X', 'BBox Center Y', 'BBox Width', 'BBox Height', 'Normalized Coordinates', 'Review Result', 'Remarks']
- detail_ws.write_row(0, 0, detail_headers)
- for row_idx, record in enumerate(self.detections, start=1):
- if not record['has_detection']:
- detail_ws.write_row(row_idx, 0, [
- record['image_path'],
- 0, 0, 0, '', '', '', '', '', '', ''
- ])
- continue
- # 每个检测框单独生成一行数据
- for detection in record['detections']:
- x1, y1, x2, y2 = detection['bbox']
- orig_w = detection.get('orig_w', 0)
- orig_h = detection.get('orig_h', 0)
- # 计算绝对坐标
- center_x = round((x1 + x2) / 2, 2)
- center_y = round((y1 + y2) / 2, 2)
- width = round(x2 - x1, 2)
- height = round(y2 - y1, 2)
- # 计算归一化坐标
- norm_cx = round(center_x / orig_w, 4) if orig_w > 0 else 0
- norm_cy = round(center_y / orig_h, 4) if orig_h > 0 else 0
- norm_w = round(width / orig_w, 4) if orig_w > 0 else 0
- norm_h = round(height / orig_h, 4) if orig_h > 0 else 0
- detail_ws.write_row(row_idx, 0, [
- record['image_path'],
- len(record['detections']),
- detection['confidence'],
- '', # Avg Confidence占位
- orig_w, # 新增原始宽度
- orig_h, # 新增原始高度
- center_x,
- center_y,
- width,
- height,
- f"{norm_cx},{norm_cy},{norm_w},{norm_h}",
- '',
- ''
- ])
- row_idx += 1
- # 仅在需要时计算指标
- if calculate_metrics and ground_truth_file:
- metrics = self.calculate_metrics(
- self.parse_excel_ground_truth(ground_truth_file)
- )
- metrics_ws = workbook.add_worksheet('Model Evaluation')
- metrics_headers = ['True Positive', 'False Positive', 'True Negative', 'False Negative', 'False Positive Rate', 'False Negative Rate']
- metrics_ws.write_row(0, 0, metrics_headers)
- metrics_ws.write_row(1, 0, [
- metrics['true_positive'],
- metrics['false_positive'],
- metrics['true_negative'],
- metrics['false_negative'],
- metrics['fpr'],
- metrics['fnr']
- ])
-
- workbook.close()
- def parse_excel_ground_truth(self, excel_path: str) -> Dict[str, bool]:
- """
- 从Excel文件解析地面真实值
- :param excel_path: detection_report_0.3.xlsx文件路径
- :return: 包含图像路径和真实检测结果的字典
- """
- wb = load_workbook(excel_path)
- ws = wb.active
- ground_truth = {}
-
- for row in ws.iter_rows(min_row=2, values_only=True):
- image_path = row[0]
- # 假设第二列是真实标签(例如1表示存在目标,0表示不存在)
- actual_value = bool(row[1]) if len(row) > 1 else False
- ground_truth[image_path] = actual_value
- return ground_truth
|