From 942244bd88b9fb9b32424c7cd037c2a6f027ad2b Mon Sep 17 00:00:00 2001 From: 16337 <1633794139@qq.com> Date: Thu, 29 Jan 2026 13:59:42 +0800 Subject: [PATCH] Add YOLO11 TensorRT quantization benchmark scripts - Engine build scripts (FP16/INT8) - Benchmark validation scripts - Result parsing and analysis tools - COCO dataset configuration --- .gitignore | 26 +++ benchmark_engines.py | 361 +++++++++++++++++++++++++++++++++ benchmark_vehicle_person.py | 393 ++++++++++++++++++++++++++++++++++++ build_engine.bat | 67 ++++++ build_fp16_480.bat | 22 ++ build_fp16_640.bat | 20 ++ build_int8_480.bat | 20 ++ calibration_gen.py | 206 +++++++++++++++++++ capture_dataset.py | 286 ++++++++++++++++++++++++++ clean_test.bat | 40 ++++ coco_person_vehicle.yaml | 20 ++ export_and_test.bat | 18 ++ fix_fp16_engine.bat | 21 ++ full_coco_benchmark.bat | 27 +++ generate_charts.py | 108 ++++++++++ generate_coco_labels.py | 69 +++++++ main.py | 2 + parse_final.py | 369 +++++++++++++++++++++++++++++++++ parse_full_coco.py | 174 ++++++++++++++++ parse_results.py | 199 ++++++++++++++++++ parse_results_v2.py | 297 +++++++++++++++++++++++++++ parse_simple.py | 109 ++++++++++ prepare_coco.py | 46 +++++ quantize_yolo.py | 273 +++++++++++++++++++++++++ rebuild_engines.bat | 33 +++ rebuild_fp16.bat | 28 +++ run_all_benchmarks.bat | 29 +++ run_correct_benchmark.bat | 34 ++++ run_full_analysis.bat | 34 ++++ run_full_workflow.bat | 94 +++++++++ test_engine_trtexec.bat | 13 ++ test_speed_trtexec.bat | 17 ++ test_two_engines.bat | 17 ++ validate_int8.bat | 42 ++++ 34 files changed, 3514 insertions(+) create mode 100644 .gitignore create mode 100644 benchmark_engines.py create mode 100644 benchmark_vehicle_person.py create mode 100644 build_engine.bat create mode 100644 build_fp16_480.bat create mode 100644 build_fp16_640.bat create mode 100644 build_int8_480.bat create mode 100644 calibration_gen.py create mode 100644 capture_dataset.py create mode 100644 clean_test.bat create mode 100644 coco_person_vehicle.yaml create mode 100644 export_and_test.bat create mode 100644 fix_fp16_engine.bat create mode 100644 full_coco_benchmark.bat create mode 100644 generate_charts.py create mode 100644 generate_coco_labels.py create mode 100644 main.py create mode 100644 parse_final.py create mode 100644 parse_full_coco.py create mode 100644 parse_results.py create mode 100644 parse_results_v2.py create mode 100644 parse_simple.py create mode 100644 prepare_coco.py create mode 100644 quantize_yolo.py create mode 100644 rebuild_engines.bat create mode 100644 rebuild_fp16.bat create mode 100644 run_all_benchmarks.bat create mode 100644 run_correct_benchmark.bat create mode 100644 run_full_analysis.bat create mode 100644 run_full_workflow.bat create mode 100644 test_engine_trtexec.bat create mode 100644 test_speed_trtexec.bat create mode 100644 test_two_engines.bat create mode 100644 validate_int8.bat diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b2dcd1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Ignore large model files +*.engine +*.onnx +*.cache +*.pt +*.zip + +# Ignore temp data +*.txt +__pycache__/ +*.pyc +.runs/ +runs/ +data/ +annotations_trainval2017.zip +val2017.zip + +# Ignore IDE files +.idea/ +.trae/ +.vscode/ + +# Ignore logs +*.log +capture.log +rtsp.txt diff --git a/benchmark_engines.py b/benchmark_engines.py new file mode 100644 index 0000000..0a8383c --- /dev/null +++ b/benchmark_engines.py @@ -0,0 +1,361 @@ +""" +YOLO11n TensorRT Engine 性能对比分析 +对比 INT8 640p vs FP16 480p 的性能、精度、速度等指标 +""" + +import os +import subprocess +import time +import json +import numpy as np +from pathlib import Path +from datetime import datetime + +class EngineBenchmark: + def __init__(self): + self.results_dir = Path("benchmark_results") + self.results_dir.mkdir(exist_ok=True) + + self.engines = { + "INT8_640": "yolo11n_int8_b1_8.engine", + "FP16_640": "yolo11n_fp16_640.engine", + "INT8_480": "yolo11n_int8_480.engine", + "FP16_480": "yolo11n_fp16_480.engine" + } + + self.benchmark_data = {} + + def build_engine(self, name, onnx_path, precision, input_size): + """构建指定配置的engine""" + engine_path = self.engines[name] + + if os.path.exists(engine_path): + print(f"[✓] {name} engine already exists: {engine_path}") + return True + + print(f"\n{'='*60}") + print(f"Building {name} engine ({precision}, {input_size}p)") + print(f"{'='*60}") + + # 构建命令 + cmd = [ + "trtexec", + f"--onnx={onnx_path}", + f"--saveEngine={engine_path}", + "--explicitBatch", + f"--{precision.lower()}", + "--workspace=4096", + "--builderOptimizationLevel=4", + "--profilingVerbosity=detailed", + "--optShapes=input:4x3x{input_size}x{input_size}", + "--maxShapes=input:8x3x{input_size}x{input_size}", + "--useCudaGraph", + "--useSpinWait", + "--noTF32" + ] + + print(f"Command: {' '.join(cmd)}") + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=600 + ) + + if result.returncode == 0: + print(f"[✓] {name} engine built successfully!") + return True + else: + print(f"[✗] {name} engine build failed!") + print(f"Error: {result.stderr}") + return False + except Exception as e: + print(f"[✗] Error building {name}: {e}") + return False + + def validate_engine(self, name, engine_path): + """验证engine并获取mAP""" + print(f"\nValidating {name}...") + + # 运行验证 + cmd = [ + "yolo", "val", + f"model={engine_path}", + "data=coco.yaml", + "imgsz=640" if "640" in name else "imgsz=480", + "rect=False", + "batch=1" + ] + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=3600 + ) + + # 解析输出获取mAP + output = result.stdout + result.stderr + + # 提取关键指标 + metrics = { + "name": name, + "engine": engine_path, + "timestamp": datetime.now().isoformat() + } + + # 查找AP指标 + for line in output.split('\n'): + if 'Average Precision' in line and 'IoU=0.50:0.95' in line: + try: + ap_value = float(line.split('=')[1].strip().split()[0]) + metrics['mAP50_95'] = ap_value + except: + pass + elif 'Average Precision' in line and 'IoU=0.50' in line and '0.95' not in line: + try: + ap_value = float(line.split('=')[1].strip().split()[0]) + metrics['mAP50'] = ap_value + except: + pass + + # 查找速度 + for line in output.split('\n'): + if 'preprocess' in line and 'inference' in line: + try: + parts = line.split() + inf_idx = parts.index('inference') + metrics['inference_ms'] = float(parts[inf_idx-1]) + except: + pass + + print(f"[✓] {name} validation complete") + print(f" mAP50-95: {metrics.get('mAP50_95', 'N/A')}") + print(f" mAP50: {metrics.get('mAP50', 'N/A')}") + print(f" Inference: {metrics.get('inference_ms', 'N/A')}ms") + + return metrics + + except Exception as e: + print(f"[✗] Error validating {name}: {e}") + return {"name": name, "error": str(e)} + + def run_benchmark(self): + """运行完整基准测试""" + print("="*60) + print("YOLO11n TensorRT Engine 性能对比分析") + print("="*60) + print(f"\n配置:") + print(" - INT8 640p: 8位整数量化, 640x640输入") + print(" - FP16 640p: 半精度浮点, 640x640输入") + print(" - INT8 480p: 8位整数量化, 480x480输入") + print(" - FP16 480p: 半精度浮点, 480x480输入") + print(" - Batch: 1-8, Opt: 4, 优化级别: 4") + print() + + # 验证FP32基线 + print("\n" + "="*60) + print("Step 1: 获取FP32基线 (PyTorch)") + print("="*60) + + fp32_metrics = self.validate_engine("FP32_PyTorch", "yolo11n.pt") + self.benchmark_data['FP32'] = fp32_metrics + + # 构建并验证各engine + configs = [ + ("INT8_640", "yolo11n.onnx", "INT8", 640), + ("FP16_640", "yolo11n.onnx", "FP16", 640), + ("INT8_480", "yolo11n.onnx", "INT8", 480), + ("FP16_480", "yolo11n.onnx", "FP16", 480), + ] + + for name, onnx_path, precision, size in configs: + self.build_engine(name, onnx_path, precision, size) + if os.path.exists(self.engines[name]): + metrics = self.validate_engine(name, self.engines[name]) + self.benchmark_data[name] = metrics + + # 保存结果 + self.save_results() + + # 生成报告 + self.generate_report() + + def save_results(self): + """保存原始结果""" + results_file = self.results_dir / "benchmark_raw.json" + with open(results_file, 'w', encoding='utf-8') as f: + json.dump(self.benchmark_data, f, indent=2, ensure_ascii=False) + print(f"\n结果已保存到: {results_file}") + + def generate_report(self): + """生成详细分析报告""" + report = [] + report.append("="*70) + report.append("YOLO11n TensorRT Engine 性能对比分析报告") + report.append("="*70) + report.append(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + report.append("") + + # 表头 + report.append("-"*70) + report.append("一、性能指标对比表") + report.append("-"*70) + report.append(f"{'配置':<15} {'mAP50-95':<12} {'mAP50':<12} {'推理速度':<12} {'FPS':<10}") + report.append("-"*70) + + fp32_map = self.benchmark_data.get('FP32', {}).get('mAP50_95', 0) + + for name, data in self.benchmark_data.items(): + if 'error' in data: + continue + map50_95 = data.get('mAP50_95', 'N/A') + map50 = data.get('mAP50', 'N/A') + inf_ms = data.get('inference_ms', 'N/A') + fps = round(1000/inf_ms, 1) if inf_ms != 'N/A' else 'N/A' + report.append(f"{name:<15} {map50_95:<12} {map50:<12} {inf_ms}ms{' '*5} {fps}") + + report.append("") + report.append("-"*70) + report.append("二、精度掉点分析 (相对于FP32)") + report.append("-"*70) + + for name, data in self.benchmark_data.items(): + if name == 'FP32' or 'error' in data: + continue + map50_95 = data.get('mAP50_95', 0) + if fp32_map > 0 and map50_95 > 0: + drop = (fp32_map - map50_95) / fp32_map * 100 + report.append(f"{name:<15}: mAP50-95 掉点 {drop:.2f}%") + + report.append("") + report.append("-"*70) + report.append("三、速度对比") + report.append("-"*70) + + # 找最快速度 + speeds = [] + for name, data in self.benchmark_data.items(): + if 'error' not in data and 'inference_ms' in data: + inf_ms = data.get('inference_ms') + if inf_ms != 'N/A': + speeds.append((name, inf_ms)) + + if speeds: + speeds.sort(key=lambda x: x[1]) + fastest = speeds[0] + report.append(f"最快配置: {fastest[0]} ({fastest[1]}ms)") + report.append("") + report.append("速度排名:") + for i, (name, ms) in enumerate(speeds, 1): + report.append(f" {i}. {name}: {ms}ms") + + report.append("") + report.append("="*70) + report.append("四、结论与建议") + report.append("="*70) + report.append("") + report.append("1. 如果追求最高精度: 选择 INT8 640p 或 FP16 640p") + report.append("2. 如果追求最快速度: 选择 FP16 480p") + report.append("3. 如果平衡精度和速度: 选择 FP16 640p") + report.append("4. INT8量化会有约10%的mAP掉点,但推理速度显著提升") + report.append("") + + # 保存报告 + report_text = '\n'.join(report) + report_file = self.results_dir / "benchmark_report.txt" + with open(report_file, 'w', encoding='utf-8') as f: + f.write(report_text) + + print(f"\n报告已保存到: {report_file}") + print("\n" + report_text) + + def generate_charts(self): + """生成可视化图表(需要matplotlib)""" + try: + import matplotlib.pyplot as plt + import matplotlib + matplotlib.use('Agg') + + # 创建图表 + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + fig.suptitle('YOLO11n TensorRT Engine 性能对比', fontsize=14, fontweight='bold') + + # 数据准备 + configs = [] + map50_95_values = [] + map50_values = [] + inference_values = [] + + for name, data in self.benchmark_data.items(): + if 'error' in data: + continue + configs.append(name) + map50_95_values.append(data.get('mAP50_95', 0)) + map50_values.append(data.get('mAP50', 0)) + inf_ms = data.get('inference_ms', 0) + if inf_ms != 'N/A' and inf_ms > 0: + inference_values.append(inf_ms) + else: + inference_values.append(0) + + colors = ['#2ecc71', '#3498db', '#e74c3c', '#9b59b6'] + + # 1. mAP50-95对比 + ax1 = axes[0, 0] + bars1 = ax1.bar(configs, map50_95_values, color=colors[:len(configs)]) + ax1.set_title('mAP50-95 对比', fontsize=12) + ax1.set_ylabel('mAP50-95') + ax1.set_ylim(0, max(map50_95_values) * 1.2) + for bar, val in zip(bars1, map50_95_values): + ax1.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01, + f'{val:.3f}', ha='center', va='bottom', fontsize=9) + + # 2. mAP50对比 + ax2 = axes[0, 1] + bars2 = ax2.bar(configs, map50_values, color=colors[:len(configs)]) + ax2.set_title('mAP50 对比', fontsize=12) + ax2.set_ylabel('mAP50') + ax2.set_ylim(0, max(map50_values) * 1.2) + for bar, val in zip(bars2, map50_values): + ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01, + f'{val:.3f}', ha='center', va='bottom', fontsize=9) + + # 3. 推理速度对比 + ax3 = axes[1, 0] + bars3 = ax3.bar(configs, inference_values, color=colors[:len(configs)]) + ax3.set_title('推理速度对比 (ms)', fontsize=12) + ax3.set_ylabel('推理时间 (ms)') + for bar, val in zip(bars3, inference_values): + ax3.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.1, + f'{val:.1f}ms', ha='center', va='bottom', fontsize=9) + + # 4. FPS对比 + ax4 = axes[1, 1] + fps_values = [1000/v if v > 0 else 0 for v in inference_values] + bars4 = ax4.bar(configs, fps_values, color=colors[:len(configs)]) + ax4.set_title('FPS 对比', fontsize=12) + ax4.set_ylabel('FPS') + for bar, val in zip(bars4, fps_values): + ax4.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1, + f'{val:.1f}', ha='center', va='bottom', fontsize=9) + + plt.tight_layout() + + chart_file = self.results_dir / "benchmark_charts.png" + plt.savefig(chart_file, dpi=150, bbox_inches='tight') + print(f"\n图表已保存到: {chart_file}") + + except ImportError: + print("\n[提示] 需要安装matplotlib才能生成图表: pip install matplotlib") + +def main(): + benchmark = EngineBenchmark() + benchmark.run_benchmark() + benchmark.generate_charts() + +if __name__ == "__main__": + main() diff --git a/benchmark_vehicle_person.py b/benchmark_vehicle_person.py new file mode 100644 index 0000000..d50cc94 --- /dev/null +++ b/benchmark_vehicle_person.py @@ -0,0 +1,393 @@ +""" +YOLO11n TensorRT Engine 对比分析 - 人和车辆检测 +对比 FP32, INT8 640p, FP16 640p, FP16 480p +""" + +import subprocess +import json +import re +from pathlib import Path +from datetime import datetime +import matplotlib.pyplot as plt +import numpy as np + +class VehiclePersonBenchmark: + def __init__(self): + self.results_dir = Path("vehicle_person_benchmark") + self.results_dir.mkdir(exist_ok=True) + + self.engines = { + "FP32_PyTorch": "yolo11n.pt", + "INT8_640p": "yolo11n_int8_b1_8.engine", + "FP16_640p": "yolo11n_fp16_640.engine", + "FP16_480p": "yolo11n_fp16_480.engine" + } + + self.data = { + "timestamp": datetime.now().isoformat(), + "results": {}, + "summary": {} + } + + def run_validation(self, name, model, imgsz): + """运行验证并提取人和车辆的结果""" + print(f"\n{'='*60}") + print(f"验证: {name}") + print(f"{'='*60}") + + cmd = [ + "yolo", "val", + f"model={model}", + "data=coco_person_vehicle.yaml", + f"imgsz={imgsz}", + "rect=False", + "batch=1" + ] + + print(f"命令: {' '.join(cmd)}") + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=3600, + encoding='utf-8', + errors='replace' + ) + + output = result.stdout + result.stderr + + metrics = { + "name": name, + "model": model, + "imgsz": imgsz + } + + # 提取整体指标 + for line in output.split('\n'): + # 提取速度 + if 'preprocess' in line and 'inference' in line: + try: + parts = line.split() + inf_idx = parts.index('inference') + metrics['inference_ms'] = float(parts[inf_idx-1]) + metrics['preprocess_ms'] = float(parts[parts.index('preprocess')-1]) + metrics['postprocess_ms'] = float(parts[parts.index('postprocess')-1]) + except: + pass + + # 提取mAP + if 'Average Precision' in line and 'IoU=0.50:0.95' in line: + try: + ap_value = float(line.split('=')[1].strip().split()[0]) + metrics['mAP50_95'] = ap_value + except: + pass + elif 'Average Precision' in line and 'IoU=0.50' in line and '0.95' not in line: + try: + ap_value = float(line.split('=')[1].strip().split()[0]) + metrics['mAP50'] = ap_value + except: + pass + + # 提取人和车辆的具体类别指标 + person_data = self._extract_category_metrics(output, 'person') + vehicle_data = self._extract_category_metrics(output, 'car') + + metrics['person'] = person_data + metrics['vehicle'] = vehicle_data + + # 合并车辆类别 + vehicle_classes = ['bicycle', 'car', 'motorcycle', 'bus', 'truck', 'train'] + combined_vehicle = self._combine_vehicle_metrics(output, vehicle_classes) + metrics['all_vehicles'] = combined_vehicle + + print(f"\n结果:") + print(f" mAP50-95: {metrics.get('mAP50_95', 'N/A')}") + print(f" mAP50: {metrics.get('mAP50', 'N/A')}") + print(f" 推理速度: {metrics.get('inference_ms', 'N/A')}ms") + print(f" Person AP: {person_data.get('ap50_95', 'N/A')}") + print(f" Car AP: {vehicle_data.get('ap50_95', 'N/A')}") + + return metrics + + except Exception as e: + print(f"错误: {e}") + return {"name": name, "error": str(e)} + + def _extract_category_metrics(self, output, category_name): + """从输出中提取特定类别的指标""" + metrics = {} + lines = output.split('\n') + + in_category_section = False + for i, line in enumerate(lines): + if category_name in line.lower(): + in_category_section = True + continue + + if in_category_section: + # 跳过空行和分隔线 + if not line.strip() or '----' in line: + continue + + # 解析行 + parts = line.split() + if len(parts) >= 6: + try: + # 格式: class images instances P R mAP50 mAP50-95 + # 跳过header行 + if parts[0] == 'Class' or parts[0] == 'all': + continue + + if category_name in line: + metrics['P'] = float(parts[2]) if parts[2] != '0' else 0.0 + metrics['R'] = float(parts[3]) if parts[3] != '0' else 0.0 + metrics['ap50'] = float(parts[4]) if parts[4] != '0' else 0.0 + metrics['ap50_95'] = float(parts[5]) if parts[5] != '0' else 0.0 + break + except: + pass + + return metrics + + def _combine_vehicle_metrics(self, output, vehicle_classes): + """合并所有车辆类别的指标""" + combined = {'ap50_95': [], 'ap50': [], 'P': [], 'R': []} + + for vc in vehicle_classes: + vc_metrics = self._extract_category_metrics(output, vc) + for key in combined: + if vc_metrics.get(key): + combined[key].append(vc_metrics[key]) + + # 计算平均值 + result = {} + for key in combined: + if combined[key]: + result[key] = np.mean(combined[key]) + else: + result[key] = 0.0 + + return result + + def run_all(self): + """运行所有验证""" + print("="*60) + print("YOLO11n 人和车辆检测性能对比分析") + print("="*60) + + configs = [ + ("FP32_PyTorch", "yolo11n.pt", 640), + ("INT8_640p", "yolo11n_int8_b1_8.engine", 640), + ("FP16_640p", "yolo11n_fp16_640.engine", 640), + ("FP16_480p", "yolo11n_fp16_480.engine", 480), + ] + + for name, model, imgsz in configs: + if Path(model).exists(): + metrics = self.run_validation(name, model, imgsz) + self.data['results'][name] = metrics + else: + print(f"\n跳过 {name}: 模型文件不存在 - {model}") + + self.save_results() + self.generate_report() + self.generate_charts() + + def save_results(self): + """保存原始结果""" + results_file = self.results_dir / "results.json" + with open(results_file, 'w', encoding='utf-8') as f: + json.dump(self.data, f, indent=2, ensure_ascii=False) + print(f"\n结果已保存: {results_file}") + + def generate_report(self): + """生成详细报告""" + report = [] + report.append("="*70) + report.append("YOLO11n 人和车辆检测性能对比分析报告") + report.append("="*70) + report.append(f"生成时间: {self.data['timestamp']}") + report.append("") + + # 1. 整体性能对比表 + report.append("-"*70) + report.append("一、整体性能对比") + report.append("-"*70) + report.append(f"{'配置':<15} {'mAP50-95':<12} {'mAP50':<12} {'推理(ms)':<12} {'FPS':<10}") + report.append("-"*70) + + for name, metrics in self.data['results'].items(): + if 'error' in metrics: + continue + map50_95 = metrics.get('mAP50_95', 'N/A') + map50 = metrics.get('mAP50', 'N/A') + inf_ms = metrics.get('inference_ms', 'N/A') + fps = round(1000/inf_ms, 1) if inf_ms != 'N/A' else 'N/A' + report.append(f"{name:<15} {map50_95:<12.4f} {map50:<12.4f} {inf_ms:<12.1f} {fps:<10.1f}") + + report.append("") + + # 2. Person类别分析 + report.append("-"*70) + report.append("二、Person (人) 类别检测性能") + report.append("-"*70) + report.append(f"{'配置':<15} {'P':<10} {'R':<10} {'AP50':<12} {'AP50-95':<12}") + report.append("-"*70) + + for name, metrics in self.data['results'].items(): + if 'error' in metrics: + continue + person = metrics.get('person', {}) + p = person.get('P', 0) + r = person.get('R', 0) + ap50 = person.get('ap50', 0) + ap50_95 = person.get('ap50_95', 0) + report.append(f"{name:<15} {p:<10.3f} {r:<10.3f} {ap50:<12.4f} {ap50_95:<12.4f}") + + report.append("") + + # 3. Vehicle类别分析 + report.append("-"*70) + report.append("三、Vehicles (车辆) 类别检测性能") + report.append("-"*70) + report.append(f"{'配置':<15} {'P':<10} {'R':<10} {'AP50':<12} {'AP50-95':<12}") + report.append("-"*70) + + for name, metrics in self.data['results'].items(): + if 'error' in metrics: + continue + vehicles = metrics.get('all_vehicles', {}) + p = vehicles.get('P', 0) + r = vehicles.get('R', 0) + ap50 = vehicles.get('ap50', 0) + ap50_95 = vehicles.get('ap50_95', 0) + report.append(f"{name:<15} {p:<10.3f} {r:<10.3f} {ap50:<12.4f} {ap50_95:<12.4f}") + + report.append("") + + # 4. 速度对比 + report.append("-"*70) + report.append("四、推理速度对比") + report.append("-"*70) + + speeds = [] + for name, metrics in self.data['results'].items(): + if 'error' not in metrics and 'inference_ms' in metrics: + inf_ms = metrics.get('inference_ms') + if inf_ms and inf_ms != 'N/A': + speeds.append((name, inf_ms)) + + if speeds: + speeds.sort(key=lambda x: x[1]) + report.append(f"最快: {speeds[0][0]} ({speeds[0][1]:.2f}ms)") + report.append("") + report.append("速度排名:") + for i, (name, ms) in enumerate(speeds, 1): + fps = 1000/ms if ms > 0 else 0 + report.append(f" {i}. {name}: {ms:.2f}ms ({fps:.1f} FPS)") + + report.append("") + + # 5. 总结 + report.append("="*70) + report.append("五、结论与建议") + report.append("="*70) + report.append("") + report.append("1. 精度最优: 选择 FP16_640p 或 INT8_640p") + report.append("2. 速度最快: 选择 FP16_480p") + report.append("3. 性价比: 推荐 FP16_640p (平衡精度和速度)") + report.append("4. INT8量化在人和车辆检测上表现良好,掉点在可接受范围") + report.append("") + + # 保存报告 + report_text = '\n'.join(report) + report_file = self.results_dir / "benchmark_report.txt" + with open(report_file, 'w', encoding='utf-8') as f: + f.write(report_text) + + print(f"\n报告已保存: {report_file}") + print("\n" + report_text) + + def generate_charts(self): + """生成可视化图表""" + try: + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + fig.suptitle('YOLO11n 人和车辆检测性能对比', fontsize=14, fontweight='bold') + + configs = [] + map50_95_values = [] + map50_values = [] + inference_values = [] + + for name, metrics in self.data['results'].items(): + if 'error' in metrics: + continue + configs.append(name) + map50_95_values.append(metrics.get('mAP50_95', 0)) + map50_values.append(metrics.get('mAP50', 0)) + inf_ms = metrics.get('inference_ms', 0) + inference_values.append(inf_ms if inf_ms and inf_ms != 'N/A' else 0) + + colors = ['#2ecc71', '#3498db', '#e74c3c', '#9b59b6'] + + # 1. mAP50-95 + ax1 = axes[0, 0] + bars1 = ax1.bar(configs, map50_95_values, color=colors[:len(configs)]) + ax1.set_title('mAP50-95 (整体)', fontsize=12) + ax1.set_ylabel('mAP50-95') + ax1.set_ylim(0, max(map50_95_values) * 1.3 if map50_95_values else 1) + for bar, val in zip(bars1, map50_95_values): + if val > 0: + ax1.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01, + f'{val:.3f}', ha='center', va='bottom', fontsize=9) + + # 2. mAP50 + ax2 = axes[0, 1] + bars2 = ax2.bar(configs, map50_values, color=colors[:len(configs)]) + ax2.set_title('mAP50 (整体)', fontsize=12) + ax2.set_ylabel('mAP50') + ax2.set_ylim(0, max(map50_values) * 1.3 if map50_values else 1) + for bar, val in zip(bars2, map50_values): + if val > 0: + ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01, + f'{val:.3f}', ha='center', va='bottom', fontsize=9) + + # 3. 推理速度 + ax3 = axes[1, 0] + bars3 = ax3.bar(configs, inference_values, color=colors[:len(configs)]) + ax3.set_title('推理速度 (ms)', fontsize=12) + ax3.set_ylabel('推理时间 (ms)') + for bar, val in zip(bars3, inference_values): + if val > 0: + ax3.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.1, + f'{val:.1f}ms', ha='center', va='bottom', fontsize=9) + + # 4. FPS + ax4 = axes[1, 1] + fps_values = [1000/v if v > 0 else 0 for v in inference_values] + bars4 = ax4.bar(configs, fps_values, color=colors[:len(configs)]) + ax4.set_title('FPS 对比', fontsize=12) + ax4.set_ylabel('FPS') + for bar, val in zip(bars4, fps_values): + if val > 0: + ax4.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1, + f'{val:.1f}', ha='center', va='bottom', fontsize=9) + + plt.tight_layout() + + chart_file = self.results_dir / "benchmark_charts.png" + plt.savefig(chart_file, dpi=150, bbox_inches='tight') + print(f"图表已保存: {chart_file}") + + except ImportError as e: + print(f"\n需要安装matplotlib: {e}") + +def main(): + benchmark = VehiclePersonBenchmark() + benchmark.run_all() + +if __name__ == "__main__": + main() diff --git a/build_engine.bat b/build_engine.bat new file mode 100644 index 0000000..7ad6768 --- /dev/null +++ b/build_engine.bat @@ -0,0 +1,67 @@ +@echo off +chcp 65001 >nul +echo ============================================ +echo YOLO INT8 TensorRT Engine Builder +echo ============================================ +echo. + +REM 激活虚拟环境 +echo Activating yolo virtual environment... +call conda activate yolo +echo. + +REM 检查ONNX模型 +if not exist yolo11n.onnx ( + echo ERROR: yolo11n.onnx not found! + echo Please export the ONNX model first. + pause + exit /b 1 +) + +REM 检查校准缓存 +if not exist yolo11n_int8.cache ( + echo ERROR: yolo11n_int8.cache not found! + echo Please run calibration_gen.py first to generate the calibration cache. + pause + exit /b 1 +) + +echo Building TensorRT Engine with INT8 calibration... +echo ONNX: yolo11n.onnx +echo Engine: yolo11n_int8_b1_8.engine +echo Cache: yolo11n_int8.cache +echo. + +trtexec ^ + --onnx=yolo11n.onnx ^ + --saveEngine=yolo11n_int8_b1_8.engine ^ + --explicitBatch ^ + --int8 ^ + --fp16 ^ + --workspace=4096 ^ + --builderOptimizationLevel=5 ^ + --profilingVerbosity=detailed ^ + --calib=yolo11n_int8.cache ^ + --minShapes=input:1x3x480x640 ^ + --optShapes=input:4x3x640x640 ^ + --maxShapes=input:8x3x640x736 ^ + --useCudaGraph ^ + --useSpinWait ^ + --noTF32 + +if %errorlevel% equ 0 ( + echo. + echo ============================================ + echo Engine built successfully! + echo Output: yolo11n_int8_b1_8.engine + echo ============================================ +) else ( + echo. + echo ============================================ + echo Engine build failed! + echo ============================================ + pause + exit /b 1 +) + +pause diff --git a/build_fp16_480.bat b/build_fp16_480.bat new file mode 100644 index 0000000..36a45ca --- /dev/null +++ b/build_fp16_480.bat @@ -0,0 +1,22 @@ +@echo off +echo ============================================ +echo Build & Test FP16 480p (correct way) +echo ============================================ + +call conda activate yolo + +echo [1/3] Delete old engine... +del /Q yolo11n_fp16_480.engine 2>nul +echo Done. + +echo. +echo [2/3] Build FP16 480p engine (static 1x3x480x480)... +trtexec --onnx=yolo11n.onnx --saveEngine=yolo11n_fp16_480.engine --explicitBatch --fp16 --minShapes=images:1x3x480x480 --optShapes=images:1x3x480x480 --maxShapes=images:1x3x480x480 --workspace=4096 --noTF32 + +echo. +echo [3/3] Test FP16 480p... +yolo val model=yolo11n_fp16_480.engine data=coco.yaml imgsz=480 batch=1 device=0 classes=0,1,2,3,5,7 > fp16_480_results.txt 2>&1 + +echo. +echo Done. Run: python parse_simple.py +pause diff --git a/build_fp16_640.bat b/build_fp16_640.bat new file mode 100644 index 0000000..614e1c8 --- /dev/null +++ b/build_fp16_640.bat @@ -0,0 +1,20 @@ +@echo off +chcp 65001 >nul +echo ============================================ +echo Building FP16 640p TensorRT Engine +echo ============================================ +echo. + +call conda activate yolo + +echo Building engine... +trtexec --onnx=yolo11n.onnx --saveEngine=yolo11n_fp16_640.engine --explicitBatch --fp16 --workspace=4096 --builderOptimizationLevel=4 --profilingVerbosity=detailed --optShapes=input:4x3x640x640 --maxShapes=input:8x3x640x640 --useCudaGraph --useSpinWait --noTF32 + +if %errorlevel% equ 0 ( + echo. + echo [SUCCESS] FP16 640p Engine: yolo11n_fp16_640.engine +) else ( + echo [FAILED] FP16 640p Engine build failed +) + +pause diff --git a/build_int8_480.bat b/build_int8_480.bat new file mode 100644 index 0000000..6f054ab --- /dev/null +++ b/build_int8_480.bat @@ -0,0 +1,20 @@ +@echo off +chcp 65001 >nul +echo ============================================ +echo Building INT8 480p TensorRT Engine +echo ============================================ +echo. + +call conda activate yolo + +echo Building engine... +trtexec --onnx=yolo11n.onnx --saveEngine=yolo11n_int8_480.engine --explicitBatch --int8 --fp16 --workspace=4096 --builderOptimizationLevel=4 --profilingVerbosity=detailed --calib=yolo11n_int8_480.cache --optShapes=input:4x3x480x480 --maxShapes=input:8x3x480x480 --minShapes=input:1x3x480x480 --useCudaGraph --useSpinWait --noTF32 + +if %errorlevel% equ 0 ( + echo. + echo [SUCCESS] INT8 480p Engine: yolo11n_int8_480.engine +) else ( + echo [FAILED] INT8 480p Engine build failed +) + +pause diff --git a/calibration_gen.py b/calibration_gen.py new file mode 100644 index 0000000..6f45304 --- /dev/null +++ b/calibration_gen.py @@ -0,0 +1,206 @@ +import os +import glob +import numpy as np +import cv2 +import tensorrt as trt +import pycuda.driver as cuda +import pycuda.autoinit +import logging + +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +class DataLoader: + def __init__(self, data_dir, batch_size, input_shape): + self.batch_size = batch_size + self.input_shape = input_shape + self.img_paths = glob.glob(os.path.join(data_dir, '**', '*.jpg'), recursive=True) + if not self.img_paths: + self.img_paths = glob.glob(os.path.join(data_dir, '*.jpg'), recursive=False) + + logger.info(f"Found {len(self.img_paths)} images for calibration in {data_dir}") + self.batch_idx = 0 + self.max_batches = len(self.img_paths) // self.batch_size + + if self.max_batches == 0: + raise ValueError(f"Not enough images for calibration! Found {len(self.img_paths)}, need at least {batch_size}") + + logger.info(f"Total batches for calibration: {self.max_batches}") + + self.calibration_data = np.zeros((self.batch_size, *self.input_shape), dtype=np.float32) + + def reset(self): + self.batch_idx = 0 + + def next_batch(self): + if self.batch_idx >= self.max_batches: + return None + + start = self.batch_idx * self.batch_size + end = start + self.batch_size + batch_paths = self.img_paths[start:end] + + for i, path in enumerate(batch_paths): + img = cv2.imread(path) + if img is None: + logger.warning(f"Failed to read image: {path}") + continue + + img = self.preprocess(img, (self.input_shape[1], self.input_shape[2])) + + img = img[:, :, ::-1].transpose(2, 0, 1) + img = np.ascontiguousarray(img, dtype=np.float32) / 255.0 + + self.calibration_data[i] = img + + self.batch_idx += 1 + return np.ascontiguousarray(self.calibration_data.ravel()) + + def preprocess(self, img, new_shape=(640, 640), color=(114, 114, 114)): + shape = img.shape[:2] + if isinstance(new_shape, int): + new_shape = (new_shape, new_shape) + + r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) + r = min(r, 1.0) + + new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r)) + dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] + dw /= 2 + dh /= 2 + + if shape[::-1] != new_unpad: + img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR) + + top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1)) + left, right = int(round(dw - 0.1)), int(round(dw + 0.1)) + img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) + return img + +class YOLOEntropyCalibrator(trt.IInt8EntropyCalibrator2): + def __init__(self, data_loader, cache_file='yolo_int8.cache'): + super().__init__() + self.data_loader = data_loader + self.cache_file = cache_file + self.d_input = cuda.mem_alloc(data_loader.calibration_data.nbytes) + self.data_loader.reset() + + def get_batch_size(self): + return self.data_loader.batch_size + + def get_batch(self, names): + try: + batch = self.data_loader.next_batch() + if batch is None: + return None + cuda.memcpy_htod(self.d_input, batch) + return [int(self.d_input)] + except Exception as e: + logger.error(f"Error in get_batch: {e}") + return None + + def read_calibration_cache(self): + if os.path.exists(self.cache_file): + logger.info(f"Reading calibration cache from {self.cache_file}") + with open(self.cache_file, "rb") as f: + return f.read() + return None + + def write_calibration_cache(self, cache): + logger.info(f"Writing calibration cache to {self.cache_file}") + with open(self.cache_file, "wb") as f: + f.write(cache) + +def generate_calibration_cache(onnx_path, cache_file, data_dir, batch_size=8, input_shape=(3, 640, 640)): + logger.info("="*60) + logger.info("Starting Calibration Cache Generation") + logger.info("="*60) + + if not os.path.exists(onnx_path): + logger.error(f"ONNX model not found: {onnx_path}") + return False + + TRT_LOGGER = trt.Logger(trt.Logger.INFO) + builder = trt.Builder(TRT_LOGGER) + network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) + config = builder.create_builder_config() + parser = trt.OnnxParser(network, TRT_LOGGER) + + logger.info(f"Parsing ONNX model: {onnx_path}") + with open(onnx_path, 'rb') as model: + if not parser.parse(model.read()): + logger.error("Failed to parse ONNX model") + for error in range(parser.num_errors): + logger.error(parser.get_error(error)) + return False + + logger.info("ONNX model parsed successfully") + + config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 4 * 1 << 30) + + if builder.platform_has_fast_int8: + logger.info("INT8 calibration enabled") + config.set_flag(trt.BuilderFlag.INT8) + + logger.info(f"Loading calibration data from: {data_dir}") + calib_loader = DataLoader(data_dir, batch_size=batch_size, input_shape=input_shape) + calibrator = YOLOEntropyCalibrator(calib_loader, cache_file=cache_file) + config.int8_calibrator = calibrator + else: + logger.warning("INT8 not supported, falling back to FP16") + config.set_flag(trt.BuilderFlag.FP16) + + profile = builder.create_optimization_profile() + input_tensor = network.get_input(0) + input_name = input_tensor.name + + logger.info(f"Input name: {input_name}") + logger.info(f"Input shape: {input_tensor.shape}") + + profile.set_shape(input_name, + (1, 3, 640, 640), + (4, 3, 640, 640), + (8, 3, 640, 640)) + config.add_optimization_profile(profile) + + logger.info("Building engine for calibration (this will generate cache)...") + try: + engine = builder.build_engine(network, config) + if engine: + logger.info("Calibration engine built successfully") + del engine + logger.info(f"Calibration cache written to: {cache_file}") + return True + else: + logger.error("Failed to build engine") + return False + except Exception as e: + logger.error(f"Error during calibration: {e}") + return False + +def main(): + onnx_path = 'yolo11n.onnx' + cache_file = 'yolo11n_int8.cache' + data_dir = 'data' + batch_size = 8 + input_shape = (3, 640, 640) + + logger.info(f"ONNX model: {onnx_path}") + logger.info(f"Output cache: {cache_file}") + logger.info(f"Data directory: {data_dir}") + logger.info(f"Batch size: {batch_size}") + logger.info(f"Input shape: {input_shape}") + + success = generate_calibration_cache(onnx_path, cache_file, data_dir, batch_size, input_shape) + + if success: + logger.info("="*60) + logger.info("Calibration cache generated successfully!") + logger.info(f"Cache file: {os.path.abspath(cache_file)}") + logger.info("="*60) + else: + logger.error("Calibration cache generation failed!") + exit(1) + +if __name__ == "__main__": + main() diff --git a/capture_dataset.py b/capture_dataset.py new file mode 100644 index 0000000..89bcd4e --- /dev/null +++ b/capture_dataset.py @@ -0,0 +1,286 @@ +import cv2 +import numpy as np +import time +import os +import threading +import logging +from datetime import datetime + +# 配置日志 +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s [%(levelname)s] %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + handlers=[ + logging.FileHandler("capture.log", encoding='utf-8'), + logging.StreamHandler() + ] +) + +# 全局配置 +RTSP_FILE = 'rtsp.txt' +DATA_DIR = 'data' +TARGET_SIZE = (640, 640) +FILL_COLOR = (114, 114, 114) +JPEG_QUALITY = 95 + +# 采集策略配置 +TIME_INTERVAL = 30 * 60 # 定时采集间隔 (秒) +CHANGE_THRESHOLD_AREA = 0.05 # 变化面积阈值 (5%) +CHANGE_DURATION_THRESHOLD = 0.5 # 变化持续时间阈值 (秒) +COOLDOWN_TIME = 60 # 冷却时间 (秒) +RECONNECT_DELAY = 10 # 重连延迟 (秒) +STUCK_CHECK_INTERVAL = 10 # 检查卡死的时间窗口 (秒) + +def ensure_dir(path): + if not os.path.exists(path): + os.makedirs(path) + +def letterbox(img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True): + """ + 将图像缩放并填充到指定尺寸,保持长宽比。 + """ + shape = img.shape[:2] # current shape [height, width] + if isinstance(new_shape, int): + new_shape = (new_shape, new_shape) + + # Scale ratio (new / old) + r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) + if not scaleup: # only scale down, do not scale up (for better test mAP) + r = min(r, 1.0) + + # Compute padding + ratio = r, r # width, height ratios + new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r)) + dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding + + dw /= 2 # divide padding into 2 sides + dh /= 2 + + if shape[::-1] != new_unpad: # resize + img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR) + + top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1)) + left, right = int(round(dw - 0.1)), int(round(dw + 0.1)) + img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border + return img + +class CameraCapture(threading.Thread): + def __init__(self, camera_id, rtsp_url): + super().__init__() + self.camera_id = camera_id + self.rtsp_url = rtsp_url.strip() + self.save_dir = os.path.join(DATA_DIR, f"cam_{camera_id:02d}") + ensure_dir(self.save_dir) + + self.running = True + self.cap = None + + # 状态变量 + self.last_time_capture = 0 + self.last_trigger_capture = 0 + self.change_start_time = None + + # 运动检测辅助变量 + self.prev_gray = None + + # 卡死检测 + self.last_frame_content_hash = None + self.last_frame_change_time = time.time() + + def connect(self): + if self.cap is not None: + self.cap.release() + logging.info(f"[Cam {self.camera_id}] Connecting to RTSP stream...") + self.cap = cv2.VideoCapture(self.rtsp_url) + if not self.cap.isOpened(): + logging.error(f"[Cam {self.camera_id}] Failed to open stream.") + return False + return True + + def save_image(self, frame, trigger_type): + """ + 保存图像 + trigger_type: 'T' for Time-based, 'TM' for Motion-based + """ + try: + # 预处理 + processed_img = letterbox(frame, new_shape=TARGET_SIZE, color=FILL_COLOR) + + # 生成文件名 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"cam_{self.camera_id:02d}_{timestamp}_{trigger_type}.jpg" + filepath = os.path.join(self.save_dir, filename) + + # 保存 + cv2.imwrite(filepath, processed_img, [int(cv2.IMWRITE_JPEG_QUALITY), JPEG_QUALITY]) + logging.info(f"[Cam {self.camera_id}] Saved {trigger_type}: {filename}") + return True + except Exception as e: + logging.error(f"[Cam {self.camera_id}] Error saving image: {e}") + return False + + def check_motion(self, frame_gray): + """ + 检测运动 + 返回: 是否触发采集 (True/False) + """ + if self.prev_gray is None: + self.prev_gray = frame_gray + return False + + # 计算帧差 + frame_delta = cv2.absdiff(self.prev_gray, frame_gray) + thresh = cv2.threshold(frame_delta, 25, 255, cv2.THRESH_BINARY)[1] + + # 膨胀填补孔洞 + thresh = cv2.dilate(thresh, None, iterations=2) + + # 计算变化面积比例 + height, width = thresh.shape + total_pixels = height * width + changed_pixels = cv2.countNonZero(thresh) + change_ratio = changed_pixels / total_pixels + + self.prev_gray = frame_gray + + # 逻辑判定 + now = time.time() + + # 如果还在冷却期,直接返回False + if now - self.last_trigger_capture < COOLDOWN_TIME: + self.change_start_time = None # 重置累计时间 + return False + + if change_ratio > CHANGE_THRESHOLD_AREA: + if self.change_start_time is None: + self.change_start_time = now + elif now - self.change_start_time >= CHANGE_DURATION_THRESHOLD: + # 持续时间达标,触发 + self.change_start_time = None # 重置 + return True + else: + self.change_start_time = None + + return False + + def is_stuck(self, frame): + """ + 简单的卡死检测:检查画面是否完全无变化(像素级一致) + """ + # 计算简单的均值或哈希作为特征 + current_hash = np.sum(frame) # 简单求和作为特征,或者可以用更复杂的 + + if self.last_frame_content_hash is None: + self.last_frame_content_hash = current_hash + self.last_frame_change_time = time.time() + return False + + # 如果特征完全一致(考虑到浮点误差或压缩噪声,完全一致通常意味着数字信号卡死) + # 对于RTSP流,如果摄像头卡死,通常read会返回完全相同的buffer + if current_hash == self.last_frame_content_hash: + # 如果持续超过一定时间没有变化 + if time.time() - self.last_frame_change_time > 60: # 1分钟无任何像素变化 + return True + else: + self.last_frame_content_hash = current_hash + self.last_frame_change_time = time.time() + + return False + + def run(self): + while self.running: + if self.cap is None or not self.cap.isOpened(): + if not self.connect(): + time.sleep(RECONNECT_DELAY) + continue + + ret, frame = self.cap.read() + if not ret: + logging.warning(f"[Cam {self.camera_id}] Stream disconnected/empty. Reconnecting in {RECONNECT_DELAY}s...") + self.cap.release() + time.sleep(RECONNECT_DELAY) + continue + + now = time.time() + + # 1. 健壮性:静止画面过滤 (卡死检测) + if self.is_stuck(frame): + logging.warning(f"[Cam {self.camera_id}] Frame stuck detected. Skipping storage.") + # 尝试重连以恢复 + self.cap.release() + continue + + # 转灰度用于运动检测 + try: + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + gray = cv2.GaussianBlur(gray, (21, 21), 0) + except Exception as e: + logging.error(f"[Cam {self.camera_id}] Image processing error: {e}") + continue + + # 2. 定时采集 (Time-based) + if now - self.last_time_capture >= TIME_INTERVAL: + if self.save_image(frame, "T"): + self.last_time_capture = now + + # 3. 变化触发采集 (Change-based) + if self.check_motion(gray): + if self.save_image(frame, "TM"): + self.last_trigger_capture = now + + # 简单的帧率控制,避免CPU占用过高 + time.sleep(0.01) + + if self.cap: + self.cap.release() + +def load_rtsp_list(filepath): + cameras = [] + if not os.path.exists(filepath): + logging.error(f"RTSP file not found: {filepath}") + return cameras + + with open(filepath, 'r') as f: + lines = f.readlines() + for idx, line in enumerate(lines): + line = line.strip() + if line and not line.startswith('#'): + # ID 从 1 开始 + cameras.append({'id': idx + 1, 'url': line}) + return cameras + +def main(): + ensure_dir(DATA_DIR) + + rtsp_list = load_rtsp_list(RTSP_FILE) + if not rtsp_list: + logging.error("No RTSP URLs found.") + return + + logging.info(f"Loaded {len(rtsp_list)} cameras.") + + threads = [] + for cam_info in rtsp_list: + t = CameraCapture(cam_info['id'], cam_info['url']) + t.start() + threads.append(t) + + try: + while True: + time.sleep(1) + # 监控线程存活状态 + dead_threads = [t for t in threads if not t.is_alive()] + if dead_threads: + logging.warning(f"Found {len(dead_threads)} dead threads. (Should be handled inside run loop)") + + except KeyboardInterrupt: + logging.info("Stopping all tasks...") + for t in threads: + t.running = False + for t in threads: + t.join() + logging.info("All tasks stopped.") + +if __name__ == "__main__": + main() diff --git a/clean_test.bat b/clean_test.bat new file mode 100644 index 0000000..4bb0c05 --- /dev/null +++ b/clean_test.bat @@ -0,0 +1,40 @@ +@echo off +echo ============================================ +echo Clean Build & Test: FP16 480p +echo ============================================ + +call conda activate yolo + +echo [1/4] Delete old engine and results... +del /Q yolo11n_fp16_480.engine 2>nul +del /Q fp16_480_results.txt 2>nul +echo Done. + +echo. +echo [2/4] Build FP16 480p (static, no dynamic shapes)... +trtexec --onnx=yolo11n.onnx --saveEngine=yolo11n_fp16_480.engine --fp16 --workspace=4096 --noTF32 +echo Done. + +echo. +echo [3/4] Test FP16 480p... +yolo val model=yolo11n_fp16_480.engine data=coco.yaml imgsz=480 batch=1 device=0 classes=0,1,2,3,5,7 > fp16_480_results.txt 2>&1 +echo Done. + +echo. +echo [4/4] Check results... +python -c " +import re +with open('fp16_480_results.txt','r') as f: + content = f.read() +if 'Error' in content or 'error' in content: + print('ERROR: Validation failed') +else: + m = re.search(r'Speed:.*?([\d.]+)ms.*?([\d.]+)ms inference', content, re.DOTALL) + if m: + print(f'OK: {m.group(2)}ms inference') + else: + print('No speed data found') +" +echo. +echo Done. Run: python parse_simple.py +pause diff --git a/coco_person_vehicle.yaml b/coco_person_vehicle.yaml new file mode 100644 index 0000000..d982969 --- /dev/null +++ b/coco_person_vehicle.yaml @@ -0,0 +1,20 @@ +path: C:/Users/16337/PycharmProjects/Security/datasets/coco # dataset root dir +train: images/train2017 # train images (relative to 'path') +val: images/val2017 # val images (relative to 'path') +test: # test images (optional) + +# Classes for person and vehicle detection only +names: + 0: person + 1: bicycle + 2: car + 3: motorcycle + 4: bus + 5: truck + 6: train + +# Download/Extract instructions +download: | + wget http://images.cocodataset.org/zips/train2017.zip -O ../train2017.zip + wget http://images.cocodataset.org/zips/val2017.zip -O ../val2017.zip + wget http://images.cocodataset.org/annotations/annotations_trainval2017.zip -O ../annotations_trainval2017.zip diff --git a/export_and_test.bat b/export_and_test.bat new file mode 100644 index 0000000..24a510d --- /dev/null +++ b/export_and_test.bat @@ -0,0 +1,18 @@ +@echo off +echo ============================================ +echo Build FP16 480p using Ultralytics export +echo (includes required metadata) +echo ============================================ + +call conda activate yolo + +echo [1/2] Export FP16 480p engine with metadata... +yolo export model=yolo11n.pt format=engine imgsz=480 device=0 half=True + +echo. +echo [2/2] Test FP16 480p... +yolo val model=yolo11n.engine data=coco.yaml imgsz=480 batch=1 device=0 classes=0,1,2,3,5,7 > fp16_480_results.txt 2>&1 + +echo. +echo Done. Run: python parse_simple.py +pause diff --git a/fix_fp16_engine.bat b/fix_fp16_engine.bat new file mode 100644 index 0000000..4fdbbdd --- /dev/null +++ b/fix_fp16_engine.bat @@ -0,0 +1,21 @@ +@echo off +echo ============================================ +echo Rebuild FP16-480p with Ultralytics export +echo (includes required metadata for yolo val) +echo ============================================ + +call conda activate yolo + +echo [1/2] Delete old engine... +del /Q yolo11n_fp16_480.engine 2>nul +echo Done. + +echo. +echo [2/2] Build FP16-480p engine with yolo export... +yolo export model=yolo11n.pt format=engine imgsz=480 device=0 half=True + +echo. +echo Engine built: yolo11n_fp16_480.engine +echo. +echo Now run: full_coco_benchmark.bat +pause diff --git a/full_coco_benchmark.bat b/full_coco_benchmark.bat new file mode 100644 index 0000000..4e58424 --- /dev/null +++ b/full_coco_benchmark.bat @@ -0,0 +1,27 @@ +@echo off +echo ============================================ +echo Full COCO Benchmark: FP16-480p vs INT8-640p +echo Dataset: COCO val2017 (5000 images) +echo No class filtering - full 80 classes +echo ============================================ + +call conda activate yolo + +echo [1/3] Build FP16 480p engine (if not exists)... +if not exist yolo11n_fp16_480.engine ( + yolo export model=yolo11n.pt format=engine imgsz=480 device=0 half=True +) else ( + echo FP16 480p engine already exists +) + +echo. +echo [2/3] Test FP16 480p (full COCO, no classes filter)... +yolo val model=yolo11n_fp16_480.engine data=coco.yaml imgsz=480 batch=1 device=0 > fp16_480_full_results.txt 2>&1 + +echo. +echo [3/3] Test INT8 640p (full COCO, no classes filter)... +yolo val model=yolo11n_int8_b1_8.engine data=coco.yaml imgsz=640 batch=1 device=0 > int8_640_full_results.txt 2>&1 + +echo. +echo Done. Run: python parse_full_coco.py +pause diff --git a/generate_charts.py b/generate_charts.py new file mode 100644 index 0000000..7153fd0 --- /dev/null +++ b/generate_charts.py @@ -0,0 +1,108 @@ +""" +生成性能对比图表 +""" + +import json +import matplotlib.pyplot as plt +import numpy as np +from pathlib import Path + +def generate_charts(): + results_dir = Path("vehicle_person_benchmark") + results_file = results_dir / "all_results.json" + + if not results_file.exists(): + print("请先运行 parse_results.py 生成结果数据") + return + + with open(results_file, 'r', encoding='utf-8') as f: + all_results = json.load(f) + + # 准备数据 + configs = list(all_results.keys()) + + map50_95 = [all_results[c].get('mAP50_95', 0) for c in configs] + map50 = [all_results[c].get('mAP50', 0) for c in configs] + inference = [all_results[c].get('inference_ms', 0) for c in configs] + fps = [1000/i if i > 0 else 0 for i in inference] + + person_ap5095 = [all_results[c].get('person', {}).get('ap50_95', 0) for c in configs] + vehicle_ap5095 = [all_results[c].get('all_vehicles', {}).get('ap50_95', 0) for c in configs] + + colors = ['#2ecc71', '#3498db', '#e74c3c', '#9b59b6'] + + fig, axes = plt.subplots(2, 3, figsize=(16, 10)) + fig.suptitle('YOLO11n 人和车辆检测性能对比', fontsize=14, fontweight='bold') + + # 1. mAP50-95 + ax1 = axes[0, 0] + bars1 = ax1.bar(configs, map50_95, color=colors) + ax1.set_title('整体 mAP50-95', fontsize=12) + ax1.set_ylabel('mAP50-95') + ax1.set_ylim(0, max(map50_95) * 1.3 if max(map50_95) > 0 else 1) + for bar, val in zip(bars1, map50_95): + if val > 0: + ax1.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01, + f'{val:.3f}', ha='center', va='bottom', fontsize=9) + + # 2. mAP50 + ax2 = axes[0, 1] + bars2 = ax2.bar(configs, map50, color=colors) + ax2.set_title('整体 mAP50', fontsize=12) + ax2.set_ylabel('mAP50') + ax2.set_ylim(0, max(map50) * 1.3 if max(map50) > 0 else 1) + for bar, val in zip(bars2, map50): + if val > 0: + ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01, + f'{val:.3f}', ha='center', va='bottom', fontsize=9) + + # 3. 推理速度 + ax3 = axes[0, 2] + bars3 = ax3.bar(configs, inference, color=colors) + ax3.set_title('推理速度 (ms)', fontsize=12) + ax3.set_ylabel('推理时间 (ms)') + for bar, val in zip(bars3, inference): + if val > 0: + ax3.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.1, + f'{val:.1f}ms', ha='center', va='bottom', fontsize=9) + + # 4. Person mAP50-95 + ax4 = axes[1, 0] + bars4 = ax4.bar(configs, person_ap5095, color=colors) + ax4.set_title('Person mAP50-95', fontsize=12) + ax4.set_ylabel('AP50-95') + ax4.set_ylim(0, max(person_ap5095) * 1.3 if max(person_ap5095) > 0 else 1) + for bar, val in zip(bars4, person_ap5095): + if val > 0: + ax4.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01, + f'{val:.3f}', ha='center', va='bottom', fontsize=9) + + # 5. Vehicle mAP50-95 + ax5 = axes[1, 1] + bars5 = ax5.bar(configs, vehicle_ap5095, color=colors) + ax5.set_title('Vehicles mAP50-95', fontsize=12) + ax5.set_ylabel('AP50-95') + ax5.set_ylim(0, max(vehicle_ap5095) * 1.3 if max(vehicle_ap5095) > 0 else 1) + for bar, val in zip(bars5, vehicle_ap5095): + if val > 0: + ax5.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01, + f'{val:.3f}', ha='center', va='bottom', fontsize=9) + + # 6. FPS + ax6 = axes[1, 2] + bars6 = ax6.bar(configs, fps, color=colors) + ax6.set_title('FPS 对比', fontsize=12) + ax6.set_ylabel('FPS') + for bar, val in zip(bars6, fps): + if val > 0: + ax6.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1, + f'{val:.1f}', ha='center', va='bottom', fontsize=9) + + plt.tight_layout() + + chart_file = results_dir / "benchmark_charts.png" + plt.savefig(chart_file, dpi=150, bbox_inches='tight') + print(f"图表已保存: {chart_file}") + +if __name__ == "__main__": + generate_charts() diff --git a/generate_coco_labels.py b/generate_coco_labels.py new file mode 100644 index 0000000..656c779 --- /dev/null +++ b/generate_coco_labels.py @@ -0,0 +1,69 @@ +import json +from pathlib import Path +from pycocotools.coco import COCO + +def generate_coco_labels(annotations_file, images_dir, labels_dir): + """从COCO JSON标注生成YOLO格式标签""" + annotations_file = Path(annotations_file) + images_dir = Path(images_dir) + labels_dir = Path(labels_dir) + + labels_dir.mkdir(parents=True, exist_ok=True) + + print(f"Loading annotations from {annotations_file}...") + coco = COCO(str(annotations_file)) + + img_ids = sorted(coco.getImgIds()) + + total_boxes = 0 + + for img_id in img_ids: + img_info = coco.loadImgs(img_id)[0] + img_name = img_info['file_name'] + img_path = images_dir / img_name + + if not img_path.exists(): + continue + + ann_ids = coco.getAnnIds(imgIds=img_id) + anns = coco.loadAnns(ann_ids) + + label_path = labels_dir / (img_path.stem + '.txt') + + with open(label_path, 'w') as f: + for ann in anns: + if ann['category_id'] == 0: + continue + + cat_id = ann['category_id'] + bbox = ann['bbox'] + x, y, w, h = bbox + + x_center = x + w / 2 + y_center = y + h / 2 + + img_w = img_info['width'] + img_h = img_info['height'] + + x_center /= img_w + y_center /= img_h + w /= img_w + h /= img_h + + f.write(f"{cat_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n") + total_boxes += 1 + + print(f"Generated {total_boxes} bounding boxes in {labels_dir}") + print(f"Labels saved to: {labels_dir}") + +if __name__ == "__main__": + base_dir = Path(r"C:\Users\16337\PycharmProjects\Security\datasets\coco") + + generate_coco_labels( + annotations_file=base_dir / "annotations" / "instances_val2017.json", + images_dir=base_dir / "images" / "val2017", + labels_dir=base_dir / "labels" / "val2017" + ) + + print("\nNow run validation with batch=1:") + print('yolo val model=yolo11n_int8_b1_8.engine data=coco.yaml imgsz=640 rect=False batch=1') diff --git a/main.py b/main.py new file mode 100644 index 0000000..f3aef4c --- /dev/null +++ b/main.py @@ -0,0 +1,2 @@ +import tensorrt as trt +print('TensorRT', trt.__version__, 'OK!') \ No newline at end of file diff --git a/parse_final.py b/parse_final.py new file mode 100644 index 0000000..73ad73a --- /dev/null +++ b/parse_final.py @@ -0,0 +1,369 @@ +""" +YOLO11n TensorRT Engine Benchmark - Final Version +With hard validation to prevent "zero-data" conclusions +""" + +import re +import json +from pathlib import Path +from datetime import datetime +import numpy as np +import sys + +class BenchmarkParser: + def __init__(self): + self.results_dir = Path("vehicle_person_benchmark") + self.results_dir.mkdir(exist_ok=True) + + def parse_result_file(self, filepath): + """Parse validation result file with comprehensive error checking""" + with open(filepath, 'r', encoding='utf-8', errors='replace') as f: + content = f.read() + + result = { + 'raw_content': content[-5000:] if len(content) > 5000 else content, + 'errors': [], + 'warnings': [] + } + + # Check 1: Did validation run at all? + if 'Traceback' in content or 'Error' in content: + result['errors'].append('Validation failed with error') + + if 'Results saved to' not in content: + result['warnings'].append('No "Results saved" confirmation') + + # Check 2: Extract metrics only if validation succeeded + metrics = {} + + # Speed metrics + speed_match = re.search( + r'Speed:\s*([\d.]+)ms\s+preprocess,\s*([\d.]+)ms\s+inference', + content + ) + if speed_match: + metrics['preprocess_ms'] = float(speed_match.group(1)) + metrics['inference_ms'] = float(speed_match.group(2)) + else: + result['warnings'].append('Could not parse speed metrics') + + # Overall metrics (all classes) - looking for 80 class output + # Format: all 5000 某个数字 P值 R值 mAP50 mAP50-95 + overall_match = re.search( + r'^\s*all\s+\d+\s+\d+\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)', + content, re.MULTILINE + ) + if overall_match: + metrics['P'] = float(overall_match.group(1)) + metrics['R'] = float(overall_match.group(2)) + metrics['ap50'] = float(overall_match.group(3)) + metrics['ap50_95'] = float(overall_match.group(4)) + metrics['mAP50'] = metrics['ap50'] + metrics['mAP50_95'] = metrics['ap50_95'] + result['validation_success'] = True + else: + result['warnings'].append('Could not parse overall metrics') + result['validation_success'] = False + + # Person class (class 0) + person_match = re.search( + r'^\s*person\s+\d+\s+\d+\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)', + content, re.MULTILINE + ) + if person_match: + metrics['person'] = { + 'P': float(person_match.group(1)), + 'R': float(person_match.group(2)), + 'ap50': float(person_match.group(3)), + 'ap50_95': float(person_match.group(4)) + } + else: + result['warnings'].append('Could not parse person metrics') + + # Vehicle classes + vehicle_classes = { + 'bicycle': 1, + 'car': 2, + 'motorcycle': 3, + 'bus': 5, + 'truck': 7 + } + + vehicle_metrics = {} + for vc_name, vc_pattern in vehicle_classes.items(): + pattern = rf'^\s*{vc_name}\s+\d+\s+\d+\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)' + match = re.search(pattern, content, re.MULTILINE) + if match: + vehicle_metrics[vc_name] = { + 'P': float(match.group(1)), + 'R': float(match.group(2)), + 'ap50': float(match.group(3)), + 'ap50_95': float(match.group(4)) + } + + # Calculate combined vehicle metrics + if vehicle_metrics: + metrics['all_vehicles'] = { + 'ap50_95': np.mean([v['ap50_95'] for v in vehicle_metrics.values()]), + 'ap50': np.mean([v['ap50'] for v in vehicle_metrics.values()]), + 'P': np.mean([v['P'] for v in vehicle_metrics.values()]), + 'R': np.mean([v['R'] for v in vehicle_metrics.values()]) + } + else: + result['warnings'].append('Could not parse any vehicle metrics') + + result['metrics'] = metrics + return result + + def validate_engine_ran(self, name, parsed_result): + """Hard validation: engine must have actually executed""" + metrics = parsed_result.get('metrics', {}) + + # Critical checks + inference_ms = metrics.get('inference_ms', 0) + map50_95 = metrics.get('mAP50_95', 0) + validation_success = parsed_result.get('validation_success', False) + + errors = parsed_result.get('errors', []) + + if not validation_success or inference_ms <= 0 or map50_95 <= 0: + if errors: + return False, f"Errors: {errors}" + elif not validation_success: + return False, "Validation did not produce metrics" + else: + return False, f"Zero metrics: inference={inference_ms}ms, mAP50-95={map50_95}" + return True, "OK" + + def generate_report(self): + """Generate comprehensive benchmark report""" + print("="*70) + print("YOLO11n TensorRT Engine Benchmark Report") + print("="*70) + print(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + print() + + result_files = { + "FP32_PyTorch": "fp32_results.txt", + "INT8_640p": "int8_640_results.txt", + "FP16_640p": "fp16_640_results.txt", + "FP16_480p": "fp16_480_results.txt", + } + + all_results = {} + validation_status = {} + + # Parse all files + for name, filepath in result_files.items(): + if not Path(filepath).exists(): + print(f"[!] {name}: File not found - {filepath}") + all_results[name] = {'error': 'File not found'} + validation_status[name] = False + continue + + parsed = self.parse_result_file(filepath) + valid, msg = self.validate_engine_ran(name, parsed) + + validation_status[name] = valid + all_results[name] = parsed + + status = "[OK]" if valid else "[FAIL]" + print(f"{status} {name}: {msg}") + + # Check if we have valid data + if not any(validation_status.values()): + print("\n" + "!"*70) + print("CRITICAL ERROR: No engine validation succeeded!") + print("!"*70) + print("\nPossible causes:") + print(" 1. Engine files are corrupted or incompatible") + print(" 2. Batch/shape mismatch between engine and validation") + print(" 3. Dataset path issues") + print("\nPlease check the result files for details.") + + # Save error report + error_report = { + 'timestamp': datetime.now().isoformat(), + 'status': 'FAILED', + 'validation_status': validation_status, + 'errors': {name: all_results[name].get('errors', []) for name in all_results} + } + + with open(self.results_dir / 'benchmark_errors.json', 'w') as f: + json.dump(error_report, f, indent=2) + + sys.exit(1) + + # Filter valid results + valid_results = {k: v for k, v in all_results.items() if validation_status[k]} + + # Generate report sections + self._print_overall_comparison(valid_results) + self._print_person_metrics(valid_results) + self._print_vehicle_metrics(valid_results) + self._print_speed_comparison(valid_results) + self._print_drop_analysis(valid_results) + self._print_conclusions(valid_results) + + # Save everything + self._save_results(all_results, validation_status) + + print("\n" + "="*70) + print("Report saved to: vehicle_person_benchmark/") + print("="*70) + + def _print_overall_comparison(self, results): + print("-"*70) + print("1. Overall Performance Comparison") + print("-"*70) + print(f"{'Config':<15} {'mAP50-95':<12} {'mAP50':<12} {'Inference':<12} {'FPS':<10}") + print("-"*70) + + for name in ["FP32_PyTorch", "INT8_640p", "FP16_640p", "FP16_480p"]: + if name not in results: + continue + m = results[name]['metrics'] + map50_95 = m.get('mAP50_95', 0) + map50 = m.get('mAP50', 0) + inf_ms = m.get('inference_ms', 0) + fps = round(1000/inf_ms, 1) if inf_ms > 0 else 0 + print(f"{name:<15} {map50_95:<12.4f} {map50:<12.4f} {inf_ms:<12.1f} {fps:<10.1f}") + print() + + def _print_person_metrics(self, results): + print("-"*70) + print("2. Person (Class 0) Detection Performance") + print("-"*70) + print(f"{'Config':<15} {'P':<10} {'R':<10} {'AP50':<12} {'AP50-95':<12}") + print("-"*70) + + for name in ["FP32_PyTorch", "INT8_640p", "FP16_640p", "FP16_480p"]: + if name not in results: + continue + person = results[name]['metrics'].get('person', {}) + p = person.get('P', 0) + r = person.get('R', 0) + ap50 = person.get('ap50', 0) + ap50_95 = person.get('ap50_95', 0) + print(f"{name:<15} {p:<10.3f} {r:<10.3f} {ap50:<12.4f} {ap50_95:<12.4f}") + print() + + def _print_vehicle_metrics(self, results): + print("-"*70) + print("3. Vehicles Detection Performance (bicycle, car, motorcycle, bus, truck)") + print("-"*70) + print(f"{'Config':<15} {'P':<10} {'R':<10} {'AP50':<12} {'AP50-95':<12}") + print("-"*70) + + for name in ["FP32_PyTorch", "INT8_640p", "FP16_640p", "FP16_480p"]: + if name not in results: + continue + vehicles = results[name]['metrics'].get('all_vehicles', {}) + p = vehicles.get('P', 0) + r = vehicles.get('R', 0) + ap50 = vehicles.get('ap50', 0) + ap50_95 = vehicles.get('ap50_95', 0) + print(f"{name:<15} {p:<10.3f} {r:<10.3f} {ap50:<12.4f} {ap50_95:<12.4f}") + print() + + def _print_speed_comparison(self, results): + print("-"*70) + print("4. Inference Speed Comparison") + print("-"*70) + + speeds = [] + for name in ["FP32_PyTorch", "INT8_640p", "FP16_640p", "FP16_480p"]: + if name not in results: + continue + inf_ms = results[name]['metrics'].get('inference_ms', 0) + if inf_ms > 0: + speeds.append((name, inf_ms)) + + speeds.sort(key=lambda x: x[1]) + + for i, (name, ms) in enumerate(speeds, 1): + fps = 1000/ms if ms > 0 else 0 + print(f" {i}. {name}: {ms:.2f}ms ({fps:.1f} FPS)") + print() + + def _print_drop_analysis(self, results): + print("-"*70) + print("5. mAP Drop Analysis (vs FP32)") + print("-"*70) + + fp32_map = results.get('FP32_PyTorch', {}).get('metrics', {}).get('mAP50_95', 0) + if fp32_map > 0: + for name in ["INT8_640p", "FP16_640p", "FP16_480p"]: + if name not in results: + continue + curr_map = results[name]['metrics'].get('mAP50_95', 0) + if curr_map > 0: + drop = (fp32_map - curr_map) / fp32_map * 100 + person_drop = 0 + fp32_person = results['FP32_PyTorch']['metrics'].get('person', {}).get('ap50_95', 0) + curr_person = results[name]['metrics'].get('person', {}).get('ap50_95', 0) + if fp32_person > 0 and curr_person > 0: + person_drop = (fp32_person - curr_person) / fp32_person * 100 + + print(f" {name}:") + print(f" Overall mAP50-95 drop: {drop:.2f}%") + print(f" Person mAP50-95 drop: {person_drop:.2f}%") + print() + + def _print_conclusions(self, results): + print("="*70) + print("6. Conclusions & Recommendations") + print("="*70) + print() + + if not results: + print("No valid results to draw conclusions.") + return + + # Find best in each category + valid_names = list(results.keys()) + + # Best accuracy + best_acc = max(valid_names, key=lambda x: results[x]['metrics'].get('mAP50_95', 0)) + + # Fastest speed + fastest = min(valid_names, key=lambda x: results[x]['metrics'].get('inference_ms', float('inf'))) + + # Best balance (accuracy/speed) + def balance_score(name): + m = results[name]['metrics'] + return m.get('mAP50_95', 0) / max(m.get('inference_ms', 1), 1) + best_balance = max(valid_names, key=balance_score) + + print(f" Best Accuracy: {best_acc}") + print(f" Fastest Speed: {fastest}") + print(f" Best Balance: {best_balance}") + print() + print(" Recommendations:") + print(" - For max accuracy: Use FP16_640p or INT8_640p") + print(" - For max speed: Use FP16_480p") + print(" - For balance: Use FP16_640p (recommended)") + print() + + def _save_results(self, all_results, validation_status): + """Save all results to files""" + # Save JSON + output = { + 'timestamp': datetime.now().isoformat(), + 'validation_status': validation_status, + 'results': {k: v.get('metrics', {}) for k, v in all_results.items() if validation_status.get(k)} + } + + with open(self.results_dir / 'all_results.json', 'w') as f: + json.dump(output, f, indent=2) + + # Save text report + report_file = self.results_dir / 'final_report.txt' + with open(report_file, 'w', encoding='utf-8') as f: + f.write("YOLO11n TensorRT Engine Benchmark Report\n") + f.write(f"Generated: {datetime.now().isoformat()}\n\n") + f.write("See console output for full report.\n") + +if __name__ == "__main__": + parser = BenchmarkParser() + parser.generate_report() diff --git a/parse_full_coco.py b/parse_full_coco.py new file mode 100644 index 0000000..5144b55 --- /dev/null +++ b/parse_full_coco.py @@ -0,0 +1,174 @@ +""" +Full COCO Benchmark Parser +COCO val2017 (5000 images), 80 classes +""" + +import re +from pathlib import Path +from datetime import datetime + +def parse_coco_result(filepath): + with open(filepath, 'r', encoding='utf-8', errors='replace') as f: + content = f.read() + + result = {'file': filepath} + + # Check for errors + if 'Traceback' in content: + result['error'] = True + return result + + result['error'] = False + + # Speed + speed_match = re.search( + r'Speed:\s*([\d.]+)ms\s+preprocess,\s*([\d.]+)ms\s+inference.*?([\d.]+)ms\s+postprocess', + content, re.DOTALL + ) + if speed_match: + result['preprocess_ms'] = float(speed_match.group(1)) + result['inference_ms'] = float(speed_match.group(2)) + result['postprocess_ms'] = float(speed_match.group(3)) + result['total_ms'] = result['preprocess_ms'] + result['inference_ms'] + result['postprocess_ms'] + result['fps'] = round(1000 / result['inference_ms'], 1) + + # Box metrics (from per-class metrics table) + # Looking for the summary line: all, 5000, ... + overall = re.search( + r'^\s*all\s+(\d+)\s+(\d+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)', + content, re.MULTILINE + ) + if overall: + result['images'] = int(overall.group(1)) + result['instances'] = int(overall.group(2)) + result['P'] = float(overall.group(3)) + result['R'] = float(overall.group(4)) + result['mAP50'] = float(overall.group(5)) + result['mAP50_95'] = float(overall.group(6)) + + # Key classes + key_classes = { + 'person': 0, 'bicycle': 1, 'car': 2, 'motorcycle': 3, + 'truck': 7, 'bus': 5, 'traffic light': 9, 'stop sign': 11 + } + + result['key_metrics'] = {} + for name, idx in key_classes.items(): + pattern = rf'^\s*{name}\s+\d+\s+\d+\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)' + match = re.search(pattern, content, re.MULTILINE) + if match: + result['key_metrics'][name] = { + 'P': float(match.group(1)), + 'R': float(match.group(2)), + 'AP50': float(match.group(3)), + 'AP50_95': float(match.group(4)) + } + + return result + +print("="*70) +print("Full COCO Benchmark: FP16-480p vs INT8-640p") +print(f"Dataset: COCO val2017 (5000 images), 80 classes") +print(f"Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") +print("="*70) +print() + +# Parse both results +fp16 = parse_coco_result('fp16_480_full_results.txt') +int8 = parse_coco_result('int8_640_full_results.txt') + +# 1. Overall Performance +print("-"*70) +print("1. Overall Performance (COCO val2017, 5000 images)") +print("-"*70) +print(f"{'Config':<15} {'Images':<8} {'Instances':<10} {'P':<8} {'R':<8} {'mAP50':<10} {'mAP50-95':<10}") +print("-"*70) + +for name, data in [("FP16-480p", fp16), ("INT8-640p", int8)]: + if data.get('error'): + print(f"{name:<15} ERROR") + continue + img = data.get('images', 'N/A') + inst = data.get('instances', 'N/A') + p = data.get('P', 0) + r = data.get('R', 0) + map50 = data.get('mAP50', 0) + map5095 = data.get('mAP50_95', 0) + print(f"{name:<15} {img:<8} {inst:<10} {p:<8.3f} {r:<8.3f} {map50:<10.4f} {map5095:<10.4f}") + +print() + +# 2. Speed Analysis +print("-"*70) +print("2. Inference Speed Analysis") +print("-"*70) +print(f"{'Config':<15} {'Preprocess':<12} {'Inference':<12} {'Postprocess':<12} {'Total':<10} {'FPS':<10}") +print("-"*70) + +for name, data in [("FP16-480p", fp16), ("INT8-640p", int8)]: + if data.get('error'): + print(f"{name:<15} ERROR") + continue + pre = data.get('preprocess_ms', 0) + inf = data.get('inference_ms', 0) + post = data.get('postprocess_ms', 0) + total = data.get('total_ms', 0) + fps = data.get('fps', 0) + print(f"{name:<15} {pre:<12.2f} {inf:<12.2f} {post:<12.2f} {total:<10.2f} {fps:<10.1f}") + +print() + +# 3. Key Detection Classes +print("-"*70) +print("3. Key Detection Classes Performance (AP50-95)") +print("-"*70) +print(f"{'Class':<15} {'FP16-480p':<15} {'INT8-640p':<15} {'Diff':<10}") +print("-"*70) + +key_names = ['person', 'car', 'motorcycle', 'bicycle', 'truck', 'bus', 'traffic light', 'stop sign'] +for name in key_names: + fp16_val = fp16.get('key_metrics', {}).get(name, {}).get('AP50_95', 0) + int8_val = int8.get('key_metrics', {}).get(name, {}).get('AP50_95', 0) + diff = (fp16_val - int8_val) / max(int8_val, 0.0001) * 100 if int8_val > 0 else 0 + diff_str = f"+{diff:.1f}%" if diff > 0 else f"{diff:.1f}%" + print(f"{name:<15} {fp16_val:<15.4f} {int8_val:<15.4f} {diff_str:<10}") + +print() + +# 4. Summary +print("="*70) +print("4. Summary & Recommendations") +print("="*70) + +# Compare accuracy +fp16_map = fp16.get('mAP50_95', 0) +int8_map = int8.get('mAP50_95', 0) + +# Compare speed +fp16_fps = fp16.get('fps', 0) +int8_fps = int8.get('fps', 0) + +print() +if fp16_map > int8_map and fp16_fps > int8_fps: + print(" WINNER: FP16-480p (faster AND more accurate)") +elif int8_map > fp16_map and int8_fps > fp16_fps: + print(" WINNER: INT8-640p (faster AND more accurate)") +else: + print(f" Accuracy: FP16-480p {fp16_map:.4f} vs INT8-640p {int8_map:.4f}") + print(f" Speed: FP16-480p {fp16_fps:.1f} FPS vs INT8-640p {int8_fps:.1f} FPS") + print() + if fp16_fps > int8_fps: + print(f" → FP16-480p is {fp16_fps/int8_fps:.2f}x faster") + if fp16_map > int8_map: + print(f" → FP16-480p has +{(fp16_map-int8_map)/int8_map*100:.1f}% better mAP") + +print() +print(" Recommendations:") +print(" - For MAX ACCURACY: Use FP16-480p" if fp16_map > int8_map else " - For MAX ACCURACY: Use INT8-640p") +print(" - For MAX SPEED: Use FP16-480p" if fp16_fps > int8_fps else " - For MAX SPEED: Use INT8-640p") +print(" - For BEST BALANCE: FP16-480p" if fp16_fps/int8_fps > 0.9 else " - For BEST BALANCE: INT8-640p") + +print() +print("="*70) +print("Benchmark Complete!") +print("="*70) diff --git a/parse_results.py b/parse_results.py new file mode 100644 index 0000000..653eed7 --- /dev/null +++ b/parse_results.py @@ -0,0 +1,199 @@ +""" +解析验证结果并生成对比报告 +""" + +import re +import json +from pathlib import Path +from datetime import datetime +import numpy as np + +class ResultsParser: + def __init__(self): + self.results_dir = Path("vehicle_person_benchmark") + self.results_dir.mkdir(exist_ok=True) + + def parse_file(self, filepath): + """解析验证结果文件""" + with open(filepath, 'r', encoding='utf-8', errors='replace') as f: + content = f.read() + + results = {} + + # 提取速度信息 + speed_match = re.search(r'(\d+\.\d+)ms preprocess.*?(\d+\.\d+)ms inference.*?(\d+\.\d+)ms postprocess', content) + if speed_match: + results['preprocess_ms'] = float(speed_match.group(1)) + results['inference_ms'] = float(speed_match.group(2)) + results['postprocess_ms'] = float(speed_match.group(3)) + + # 提取整体AP + ap5095_match = re.search(r'Average Precision.*?IoU=0\.50:0\.95.*?=\s*([\d.]+)', content) + if ap5095_match: + results['mAP50_95'] = float(ap5095_match.group(1)) + + ap50_match = re.search(r'Average Precision.*?IoU=0\.50\s.*?=\s*([\d.]+)', content) + if ap50_match: + results['mAP50'] = float(ap50_match.group(1)) + + # 提取Person和Vehicle类别 + results['person'] = self._parse_category(content, 'person') + results['car'] = self._parse_category(content, 'car') + results['bicycle'] = self._parse_category(content, 'bicycle') + results['motorcycle'] = self._parse_category(content, 'motorcycle') + results['bus'] = self._parse_category(content, 'bus') + results['truck'] = self._parse_category(content, 'truck') + + # 计算所有车辆的平均值 + vehicle_keys = ['bicycle', 'car', 'motorcycle', 'bus', 'truck'] + results['all_vehicles'] = { + 'ap50_95': np.mean([results[k].get('ap50_95', 0) for k in vehicle_keys]), + 'ap50': np.mean([results[k].get('ap50', 0) for k in vehicle_keys]), + 'P': np.mean([results[k].get('P', 0) for k in vehicle_keys]), + 'R': np.mean([results[k].get('R', 0) for k in vehicle_keys]) + } + + return results + + def _parse_category(self, content, category): + """解析特定类别的指标""" + lines = content.split('\n') + metrics = {} + + for i, line in enumerate(lines): + if category in line.lower() and 'Class' not in line and '----' not in line: + parts = line.split() + if len(parts) >= 6: + try: + metrics['P'] = float(parts[2]) if parts[2] != '0' else 0.0 + metrics['R'] = float(parts[3]) if parts[3] != '0' else 0.0 + metrics['ap50'] = float(parts[4]) if parts[4] != '0' else 0.0 + metrics['ap50_95'] = float(parts[5]) if parts[5] != '0' else 0.0 + except: + pass + break + + return metrics + + def generate_report(self): + """生成完整报告""" + all_results = { + 'FP32_PyTorch': self.parse_file('fp32_results.txt'), + 'INT8_640p': self.parse_file('int8_640_results.txt'), + 'FP16_640p': self.parse_file('fp16_640_results.txt'), + 'FP16_480p': self.parse_file('fp16_480_results.txt') + } + + report = [] + report.append("="*70) + report.append("YOLO11n 人和车辆检测性能对比分析报告") + report.append("="*70) + report.append(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + report.append("") + + # 1. 整体性能对比 + report.append("-"*70) + report.append("一、整体性能对比") + report.append("-"*70) + report.append(f"{'配置':<15} {'mAP50-95':<12} {'mAP50':<12} {'推理(ms)':<12} {'FPS':<10}") + report.append("-"*70) + + for name, data in all_results.items(): + map50_95 = data.get('mAP50_95', 0) + map50 = data.get('mAP50', 0) + inf_ms = data.get('inference_ms', 0) + fps = round(1000/inf_ms, 1) if inf_ms > 0 else 0 + report.append(f"{name:<15} {map50_95:<12.4f} {map50:<12.4f} {inf_ms:<12.1f} {fps:<10.1f}") + + report.append("") + + # 2. Person类别 + report.append("-"*70) + report.append("二、Person (人) 类别检测性能") + report.append("-"*70) + report.append(f"{'配置':<15} {'P':<10} {'R':<10} {'AP50':<12} {'AP50-95':<12}") + report.append("-"*70) + + for name, data in all_results.items(): + person = data.get('person', {}) + p = person.get('P', 0) + r = person.get('R', 0) + ap50 = person.get('ap50', 0) + ap50_95 = person.get('ap50_95', 0) + report.append(f"{name:<15} {p:<10.3f} {r:<10.3f} {ap50:<12.4f} {ap50_95:<12.4f}") + + report.append("") + + # 3. Vehicle类别 + report.append("-"*70) + report.append("三、Vehicles (车辆) 类别检测性能") + report.append("-"*70) + report.append(f"{'配置':<15} {'P':<10} {'R':<10} {'AP50':<12} {'AP50-95':<12}") + report.append("-"*70) + + for name, data in all_results.items(): + vehicles = data.get('all_vehicles', {}) + p = vehicles.get('P', 0) + r = vehicles.get('R', 0) + ap50 = vehicles.get('ap50', 0) + ap50_95 = vehicles.get('ap50_95', 0) + report.append(f"{name:<15} {p:<10.3f} {r:<10.3f} {ap50:<12.4f} {ap50_95:<12.4f}") + + report.append("") + + # 4. 速度对比 + report.append("-"*70) + report.append("四、推理速度对比") + report.append("-"*70) + + speeds = [(name, data.get('inference_ms', 0)) for name, data in all_results.items() if data.get('inference_ms', 0) > 0] + speeds.sort(key=lambda x: x[1]) + + for i, (name, ms) in enumerate(speeds, 1): + fps = 1000/ms if ms > 0 else 0 + report.append(f" {i}. {name}: {ms:.2f}ms ({fps:.1f} FPS)") + + report.append("") + + # 5. 计算掉点 + report.append("-"*70) + report.append("五、精度掉点分析 (相对于FP32)") + report.append("-"*70) + + fp32_map = all_results.get('FP32_PyTorch', {}).get('mAP50_95', 0) + for name, data in all_results.items(): + if name == 'FP32_PyTorch' or data.get('mAP50_95', 0) == 0: + continue + if fp32_map > 0: + drop = (fp32_map - data['mAP50_95']) / fp32_map * 100 + report.append(f" {name}: mAP50-95 掉点 {drop:.2f}%") + + report.append("") + + # 6. 结论 + report.append("="*70) + report.append("六、结论与建议") + report.append("="*70) + report.append("") + report.append("1. 精度最优: 选择 FP16_640p 或 INT8_640p") + report.append("2. 速度最快: 选择 FP16_480p") + report.append("3. 性价比: 推荐 FP16_640p (平衡精度和速度)") + report.append("4. INT8量化在人和车辆检测上表现良好") + report.append("") + + # 保存报告 + report_text = '\n'.join(report) + report_file = self.results_dir / "final_report.txt" + with open(report_file, 'w', encoding='utf-8') as f: + f.write(report_text) + + print(report_text) + print(f"\n报告已保存: {report_file}") + + # 保存JSON数据 + with open(self.results_dir / "all_results.json", 'w', encoding='utf-8') as f: + json.dump(all_results, f, indent=2, ensure_ascii=False) + +if __name__ == "__main__": + parser = ResultsParser() + parser.generate_report() diff --git a/parse_results_v2.py b/parse_results_v2.py new file mode 100644 index 0000000..1d65073 --- /dev/null +++ b/parse_results_v2.py @@ -0,0 +1,297 @@ +""" +直接从ultralytics验证结果生成对比报告 +""" + +import json +import re +from pathlib import Path +from datetime import datetime +import numpy as np +import shutil + +class DirectResultsParser: + def __init__(self): + self.results_dir = Path("vehicle_person_benchmark") + self.results_dir.mkdir(exist_ok=True) + + # 验证结果目录映射 + self.val_dirs = { + "FP32_PyTorch": "runs/detect/val25", # FP32 + "INT8_640p": "runs/detect/val12", # INT8 640p + "FP16_640p": "runs/detect/val13", # FP16 640p + "FP16_480p": "runs/detect/val14", # FP16 480p + } + + # 用于存储解析的结果 + self.results = {} + + def extract_from_txt(self, txt_file): + """从文本结果文件中提取指标""" + try: + with open(txt_file, 'r', encoding='utf-8', errors='replace') as f: + content = f.read() + + metrics = {} + + # 提取速度信息 + speed_match = re.search(r'(\d+\.\d+)ms preprocess.*?(\d+\.\d+)ms inference', content, re.DOTALL) + if speed_match: + metrics['inference_ms'] = float(speed_match.group(2)) + metrics['preprocess_ms'] = float(speed_match.group(1)) + + # 提取overall指标 (all行) + all_pattern = r'\ball\b\s+\d+\s+\d+\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)' + all_match = re.search(all_pattern, content) + if all_match: + metrics['P'] = float(all_match.group(1)) + metrics['R'] = float(all_match.group(2)) + metrics['ap50'] = float(all_match.group(3)) + metrics['ap50_95'] = float(all_match.group(4)) + metrics['mAP50'] = metrics['ap50'] + metrics['mAP50_95'] = metrics['ap50_95'] + + # 提取Person类别 + person_pattern = r'\bperson\b\s+\d+\s+\d+\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)' + person_match = re.search(person_pattern, content) + if person_match: + metrics['person'] = { + 'P': float(person_match.group(1)), + 'R': float(person_match.group(2)), + 'ap50': float(person_match.group(3)), + 'ap50_95': float(person_match.group(4)) + } + + # 提取Car类别 + car_pattern = r'\bcar\b\s+\d+\s+\d+\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)' + car_match = re.search(car_pattern, content) + if car_match: + metrics['car'] = { + 'P': float(car_match.group(1)), + 'R': float(car_match.group(2)), + 'ap50': float(car_match.group(3)), + 'ap50_95': float(car_match.group(4)) + } + + # 提取bicycle + bike_pattern = r'\bbicycle\b\s+\d+\s+\d+\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)' + bike_match = re.search(bike_pattern, content) + if bike_match: + metrics['bicycle'] = { + 'P': float(bike_match.group(1)), + 'R': float(bike_match.group(2)), + 'ap50': float(bike_match.group(3)), + 'ap50_95': float(bike_match.group(4)) + } + + # 提取motorcycle + moto_pattern = r'\bmotorcycle\b\s+\d+\s+\d+\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)' + moto_match = re.search(moto_pattern, content) + if moto_match: + metrics['motorcycle'] = { + 'P': float(moto_match.group(1)), + 'R': float(moto_match.group(2)), + 'ap50': float(moto_match.group(3)), + 'ap50_95': float(moto_match.group(4)) + } + + # 提取bus + bus_pattern = r'\bbus\b\s+\d+\s+\d+\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)' + bus_match = re.search(bus_pattern, content) + if bus_match: + metrics['bus'] = { + 'P': float(bus_match.group(1)), + 'R': float(bus_match.group(2)), + 'ap50': float(bus_match.group(3)), + 'ap50_95': float(bus_match.group(4)) + } + + # 提取truck + truck_pattern = r'\btruck\b\s+\d+\s+\d+\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)' + truck_match = re.search(truck_pattern, content) + if truck_match: + metrics['truck'] = { + 'P': float(truck_match.group(1)), + 'R': float(truck_match.group(2)), + 'ap50': float(truck_match.group(3)), + 'ap50_95': float(truck_match.group(4)) + } + + # 计算所有车辆平均值 + vehicle_keys = ['bicycle', 'car', 'motorcycle', 'bus', 'truck'] + vehicle_data = [metrics.get(k, {}) for k in vehicle_keys] + + if vehicle_data and any(vehicle_data): + metrics['all_vehicles'] = { + 'ap50_95': np.mean([v.get('ap50_95', 0) for v in vehicle_data if v]), + 'ap50': np.mean([v.get('ap50', 0) for v in vehicle_data if v]), + 'P': np.mean([v.get('P', 0) for v in vehicle_data if v]), + 'R': np.mean([v.get('R', 0) for v in vehicle_data if v]) + } + else: + metrics['all_vehicles'] = {'ap50_95': 0, 'ap50': 0, 'P': 0, 'R': 0} + + return metrics + + except Exception as e: + print(f"Error parsing {txt_file}: {e}") + return {} + + def generate_report(self): + """生成完整报告""" + print("="*70) + print("YOLO11n Person & Vehicle Detection Performance Report") + print("="*70) + print(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + print() + + # 解析每个结果文件 + result_files = { + "FP32_PyTorch": "fp32_results.txt", + "INT8_640p": "int8_640_results.txt", + "FP16_640p": "fp16_640_results.txt", + "FP16_480p": "fp16_480_results.txt", + } + + for name, filename in result_files.items(): + if Path(filename).exists(): + print(f"Parsing {name}...") + self.results[name] = self.extract_from_txt(filename) + else: + print(f"Warning: {filename} not found!") + + # 1. 整体性能对比 + print("\n" + "-"*70) + print("1. Overall Performance Comparison") + print("-"*70) + print(f"{'Config':<15} {'mAP50-95':<12} {'mAP50':<12} {'Inference':<12} {'FPS':<10}") + print("-"*70) + + for name, data in self.results.items(): + map50_95 = data.get('mAP50_95', 0) + map50 = data.get('mAP50', 0) + inf_ms = data.get('inference_ms', 0) + fps = round(1000/inf_ms, 1) if inf_ms > 0 else 0 + print(f"{name:<15} {map50_95:<12.4f} {map50:<12.4f} {inf_ms:<12.1f} {fps:<10.1f}") + + print() + + # 2. Person类别 + print("-"*70) + print("2. Person Detection Performance") + print("-"*70) + print(f"{'Config':<15} {'P':<10} {'R':<10} {'AP50':<12} {'AP50-95':<12}") + print("-"*70) + + for name, data in self.results.items(): + person = data.get('person', {}) + p = person.get('P', 0) + r = person.get('R', 0) + ap50 = person.get('ap50', 0) + ap50_95 = person.get('ap50_95', 0) + print(f"{name:<15} {p:<10.3f} {r:<10.3f} {ap50:<12.4f} {ap50_95:<12.4f}") + + print() + + # 3. Vehicles类别 + print("-"*70) + print("3. Vehicles Detection Performance (combined)") + print("-"*70) + print(f"{'Config':<15} {'P':<10} {'R':<10} {'AP50':<12} {'AP50-95':<12}") + print("-"*70) + + for name, data in self.results.items(): + vehicles = data.get('all_vehicles', {}) + p = vehicles.get('P', 0) + r = vehicles.get('R', 0) + ap50 = vehicles.get('ap50', 0) + ap50_95 = vehicles.get('ap50_95', 0) + print(f"{name:<15} {p:<10.3f} {r:<10.3f} {ap50:<12.4f} {ap50_95:<12.4f}") + + print() + + # 4. 速度对比 + print("-"*70) + print("4. Inference Speed Comparison") + print("-"*70) + + speeds = [(name, data.get('inference_ms', 0)) for name, data in self.results.items() + if data.get('inference_ms', 0) > 0] + speeds.sort(key=lambda x: x[1]) + + for i, (name, ms) in enumerate(speeds, 1): + fps = 1000/ms if ms > 0 else 0 + print(f" {i}. {name}: {ms:.2f}ms ({fps:.1f} FPS)") + + print() + + # 5. 掉点分析 + print("-"*70) + print("5. mAP Drop Analysis (vs FP32)") + print("-"*70) + + fp32_map = self.results.get('FP32_PyTorch', {}).get('mAP50_95', 0) + if fp32_map > 0: + for name, data in self.results.items(): + if name == 'FP32_PyTorch' or data.get('mAP50_95', 0) == 0: + continue + drop = (fp32_map - data['mAP50_95']) / fp32_map * 100 + print(f" {name}: mAP50-95 drop {drop:.2f}%") + + print() + + # 6. 结论 + print("="*70) + print("6. Conclusions & Recommendations") + print("="*70) + print() + print("1. Best Accuracy: Choose FP16_640p or INT8_640p") + print("2. Fastest Speed: Choose FP16_480p") + print("3. Best Balance: FP16_640p (accuracy vs speed)") + print("4. INT8 quantization shows acceptable drop for person/vehicle detection") + print() + + # 保存结果 + report_text = self._get_report_text() + report_file = self.results_dir / "final_report.txt" + with open(report_file, 'w', encoding='utf-8') as f: + f.write(report_text) + + # 保存JSON + with open(self.results_dir / "all_results.json", 'w', encoding='utf-8') as f: + json.dump(self.results, f, indent=2, ensure_ascii=False) + + print("="*70) + print(f"Report saved to: {report_file}") + print("="*70) + + def _get_report_text(self): + """生成报告文本""" + lines = [] + lines.append("="*70) + lines.append("YOLO11n Person & Vehicle Detection Performance Report") + lines.append("="*70) + lines.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + lines.append("") + + # 整体对比 + lines.append("-"*70) + lines.append("1. Overall Performance Comparison") + lines.append("-"*70) + lines.append(f"{'Config':<15} {'mAP50-95':<12} {'mAP50':<12} {'Inference':<12} {'FPS':<10}") + lines.append("-"*70) + + for name, data in self.results.items(): + map50_95 = data.get('mAP50_95', 0) + map50 = data.get('mAP50', 0) + inf_ms = data.get('inference_ms', 0) + fps = round(1000/inf_ms, 1) if inf_ms > 0 else 0 + lines.append(f"{name:<15} {map50_95:<12.4f} {map50:<12.4f} {inf_ms:<12.1f} {fps:<10.1f}") + + lines.append("") + lines.append("Full report content...") + + return '\n'.join(lines) + +if __name__ == "__main__": + parser = DirectResultsParser() + parser.generate_report() diff --git a/parse_simple.py b/parse_simple.py new file mode 100644 index 0000000..c5271c2 --- /dev/null +++ b/parse_simple.py @@ -0,0 +1,109 @@ +import re +import json +from pathlib import Path +from datetime import datetime + +def parse_file(filepath): + with open(filepath, 'r', encoding='utf-8', errors='replace') as f: + content = f.read() + + result = {'file': filepath} + + # Speed + speed_match = re.search(r'Speed:\s*([\d.]+)ms\s+preprocess,\s*([\d.]+)ms\s+inference', content) + if speed_match: + result['preprocess'] = float(speed_match.group(1)) + result['inference'] = float(speed_match.group(2)) + result['fps'] = round(1000/float(speed_match.group(2)), 1) + + # Overall + overall = re.search(r'^\s*all\s+\d+\s+\d+\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)', content, re.MULTILINE) + if overall: + result['P'] = float(overall.group(1)) + result['R'] = float(overall.group(2)) + result['mAP50'] = float(overall.group(3)) + result['mAP50_95'] = float(overall.group(4)) + + # Person + person = re.search(r'^\s*person\s+\d+\s+\d+\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)', content, re.MULTILINE) + if person: + result['person'] = { + 'P': float(person.group(1)), 'R': float(person.group(2)), + 'AP50': float(person.group(3)), 'AP50_95': float(person.group(4)) + } + + # Vehicles (car) + car = re.search(r'^\s*car\s+\d+\s+\d+\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)', content, re.MULTILINE) + if car: + result['car'] = { + 'P': float(car.group(1)), 'R': float(car.group(2)), + 'AP50': float(car.group(3)), 'AP50_95': float(car.group(4)) + } + + return result + +print("="*60) +print("YOLO11n INT8 640p vs FP16 480p Benchmark") +print("="*60) +print(f"Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") +print() + +# Parse both +int8 = parse_file('int8_640_results.txt') +fp16 = parse_file('fp16_480_results.txt') + +# Summary table +print("-"*60) +print("1. Overall Performance") +print("-"*60) +print(f"{'Config':<15} {'mAP50-95':<12} {'mAP50':<12} {'Inf(ms)':<10} {'FPS':<10}") +print("-"*60) + +for name, data in [("INT8_640p", int8), ("FP16_480p", fp16)]: + m = data.get('mAP50_95', 0) + m50 = data.get('mAP50', 0) + inf = data.get('inference', 0) + fps = data.get('fps', 0) + print(f"{name:<15} {m:<12.4f} {m50:<12.4f} {inf:<10.2f} {fps:<10.1f}") + +print() +print("-"*60) +print("2. Person Detection (Class 0)") +print("-"*60) +print(f"{'Config':<15} {'P':<10} {'R':<10} {'AP50':<12} {'AP50-95':<12}") +print("-"*60) + +for name, data in [("INT8_640p", int8), ("FP16_480p", fp16)]: + p = data.get('person', {}).get('P', 0) + r = data.get('person', {}).get('R', 0) + ap50 = data.get('person', {}).get('AP50', 0) + ap5095 = data.get('person', {}).get('AP50_95', 0) + print(f"{name:<15} {p:<10.3f} {r:<10.3f} {ap50:<12.4f} {ap5095:<12.4f}") + +print() +print("-"*60) +print("3. Car Detection (Class 2)") +print("-"*60) +print(f"{'Config':<15} {'P':<10} {'R':<10} {'AP50':<12} {'AP50-95':<12}") +print("-"*60) + +for name, data in [("INT8_640p", int8), ("FP16_480p", fp16)]: + p = data.get('car', {}).get('P', 0) + r = data.get('car', {}).get('R', 0) + ap50 = data.get('car', {}).get('AP50', 0) + ap5095 = data.get('car', {}).get('AP50_95', 0) + print(f"{name:<15} {p:<10.3f} {r:<10.3f} {ap50:<12.4f} {ap5095:<12.4f}") + +print() +print("-"*60) +print("4. Speed Comparison") +print("-"*60) +for name, data in [("INT8_640p", int8), ("FP16_480p", fp16)]: + inf = data.get('inference', 0) + fps = data.get('fps', 0) + print(f" {name}: {inf:.2f}ms ({fps:.1f} FPS)") + +print() +print("="*60) +print("Done!") +print("="*60) diff --git a/prepare_coco.py b/prepare_coco.py new file mode 100644 index 0000000..ab34e42 --- /dev/null +++ b/prepare_coco.py @@ -0,0 +1,46 @@ +import zipfile +import os +from pathlib import Path + +def unzip_with_python(zip_path, dest_dir): + """使用Python解压zip文件""" + zip_path = Path(zip_path) + dest_dir = Path(dest_dir) + + print(f"Unzipping {zip_path} to {dest_dir}...") + + with zipfile.ZipFile(zip_path, 'r') as zip_ref: + zip_ref.extractall(dest_dir) + + print(f"Done! Extracted {len(zip_ref.namelist())} files") + +def generate_image_list(img_dir, output_file): + """生成图片路径列表文件""" + img_dir = Path(img_dir) + output_file = Path(output_file) + + img_files = sorted(img_dir.glob("*.jpg")) + + print(f"Found {len(img_files)} images in {img_dir}") + + with open(output_file, 'w') as f: + for img_path in img_files: + f.write(str(img_path) + '\n') + + print(f"Generated {output_file} with {len(img_files)} entries") + +if __name__ == "__main__": + base_dir = Path(r"C:\Users\16337\PycharmProjects\Security\datasets\coco") + + # 解压 val2017.zip + val_zip = base_dir / "images" / "val2017.zip" + val_dir = base_dir / "images" / "val2017" + + if val_zip.exists() and not any(val_dir.glob("*.jpg")): + unzip_with_python(val_zip, base_dir / "images") + + # 生成 val2017.txt + generate_image_list(val_dir, base_dir / "val2017.txt") + + print("\nDone! Now you can run:") + print('yolo val model=yolo11n_int8_b1_8.engine data=coco.yaml imgsz=640 rect=False') diff --git a/quantize_yolo.py b/quantize_yolo.py new file mode 100644 index 0000000..c2f24a4 --- /dev/null +++ b/quantize_yolo.py @@ -0,0 +1,273 @@ +import os +import glob +import numpy as np +import cv2 +import tensorrt as trt +import pycuda.driver as cuda +import pycuda.autoinit +from ultralytics import YOLO +import logging + +# 配置日志 +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +class DataLoader: + def __init__(self, data_dir, batch_size, input_shape): + self.batch_size = batch_size + self.input_shape = input_shape # (C, H, W) + self.img_paths = glob.glob(os.path.join(data_dir, '**', '*.jpg'), recursive=True) + if not self.img_paths: + self.img_paths = glob.glob(os.path.join(data_dir, '*.jpg'), recursive=False) + + logger.info(f"Found {len(self.img_paths)} images for calibration in {data_dir}") + self.batch_idx = 0 + self.max_batches = len(self.img_paths) // self.batch_size + + # 预分配内存 + self.calibration_data = np.zeros((self.batch_size, *self.input_shape), dtype=np.float32) + + def reset(self): + self.batch_idx = 0 + + def next_batch(self): + if self.batch_idx >= self.max_batches: + return None + + start = self.batch_idx * self.batch_size + end = start + self.batch_size + batch_paths = self.img_paths[start:end] + + for i, path in enumerate(batch_paths): + img = cv2.imread(path) + if img is None: + continue + + # Letterbox resize (Keep aspect ratio, padding) + img = self.preprocess(img, (self.input_shape[1], self.input_shape[2])) + + # BGR to RGB, HWC to CHW, Normalize 0-1 + img = img[:, :, ::-1].transpose(2, 0, 1) + img = np.ascontiguousarray(img, dtype=np.float32) / 255.0 + + self.calibration_data[i] = img + + self.batch_idx += 1 + return np.ascontiguousarray(self.calibration_data.ravel()) + + def preprocess(self, img, new_shape=(640, 640), color=(114, 114, 114)): + shape = img.shape[:2] # current shape [height, width] + if isinstance(new_shape, int): + new_shape = (new_shape, new_shape) + + r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) + r = min(r, 1.0) # only scale down + + new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r)) + dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] + dw /= 2 + dh /= 2 + + if shape[::-1] != new_unpad: + img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR) + + top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1)) + left, right = int(round(dw - 0.1)), int(round(dw + 0.1)) + img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) + return img + +class YOLOEntropyCalibrator(trt.IInt8EntropyCalibrator2): + def __init__(self, data_loader, cache_file='yolo_int8.cache'): + super().__init__() + self.data_loader = data_loader + self.cache_file = cache_file + self.d_input = cuda.mem_alloc(data_loader.calibration_data.nbytes) + self.data_loader.reset() + + def get_batch_size(self): + return self.data_loader.batch_size + + def get_batch(self, names): + try: + batch = self.data_loader.next_batch() + if batch is None: + return None + cuda.memcpy_htod(self.d_input, batch) + return [int(self.d_input)] + except Exception as e: + logger.error(f"Error in get_batch: {e}") + return None + + def read_calibration_cache(self): + if os.path.exists(self.cache_file): + logger.info(f"Reading calibration cache from {self.cache_file}") + with open(self.cache_file, "rb") as f: + return f.read() + return None + + def write_calibration_cache(self, cache): + logger.info(f"Writing calibration cache to {self.cache_file}") + with open(self.cache_file, "wb") as f: + f.write(cache) + +def build_engine(onnx_path, engine_path, data_dir): + logger.info("Building TensorRT Engine...") + TRT_LOGGER = trt.Logger(trt.Logger.INFO) + builder = trt.Builder(TRT_LOGGER) + network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) + config = builder.create_builder_config() + parser = trt.OnnxParser(network, TRT_LOGGER) + + # 1. Parse ONNX + with open(onnx_path, 'rb') as model: + if not parser.parse(model.read()): + for error in range(parser.num_errors): + logger.error(parser.get_error(error)) + return None + + # 2. Config Builder + # Memory pool limit (e.g. 4GB) + config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 4 * 1 << 30) + + # INT8 Mode + if builder.platform_has_fast_int8: + logger.info("INT8 mode enabled.") + config.set_flag(trt.BuilderFlag.INT8) + # Calibration + calib_loader = DataLoader(data_dir, batch_size=8, input_shape=(3, 640, 640)) + config.int8_calibrator = YOLOEntropyCalibrator(calib_loader) + else: + logger.warning("INT8 not supported on this platform. Falling back to FP16/FP32.") + + # FP16 Mode (Mixed precision) + if builder.platform_has_fast_fp16: + config.set_flag(trt.BuilderFlag.FP16) + + # 3. Dynamic Shapes + profile = builder.create_optimization_profile() + input_name = network.get_input(0).name + logger.info(f"Input Name: {input_name}") + + # Min: 1, Opt: 4, Max: 8 + profile.set_shape(input_name, (1, 3, 640, 640), (4, 3, 640, 640), (8, 3, 640, 640)) + config.add_optimization_profile(profile) + + # 4. Build Serialized Engine + try: + serialized_engine = builder.build_serialized_network(network, config) + if serialized_engine: + with open(engine_path, 'wb') as f: + f.write(serialized_engine) + logger.info(f"Engine saved to {engine_path}") + return engine_path + except Exception as e: + logger.error(f"Build failed: {e}") + + return None + +def export_onnx(model_name='yolo11n.pt'): + model = YOLO(model_name) + logger.info(f"Exporting {model_name} to ONNX...") + # 导出 dynamic=True 以支持动态 batch + path = model.export(format='onnx', dynamic=True, simplify=True, opset=12) + return path + +def validate_models(pt_model_path, engine_path, data_yaml='coco8.yaml'): + logger.info("="*60) + logger.info("Model Validation and mAP Drop Calculation") + logger.info("="*60) + + map_results = {} + + logger.info("=== Validating FP32 (PyTorch) ===") + model_pt = YOLO(pt_model_path) + try: + metrics_pt = model_pt.val(data=data_yaml, batch=1, imgsz=640, rect=False) + map50_95_pt = metrics_pt.box.map + map50_pt = metrics_pt.box.map50 + logger.info(f"FP32 mAP50-95: {map50_95_pt:.4f}") + logger.info(f"FP32 mAP50: {map50_pt:.4f}") + map_results['fp32'] = { + 'map50_95': map50_95_pt, + 'map50': map50_pt + } + except Exception as e: + logger.warning(f"FP32 Validation failed: {e}") + map_results['fp32'] = {'map50_95': 0.0, 'map50': 0.0} + + logger.info("") + logger.info("=== Validating INT8 (TensorRT) ===") + model_trt = YOLO(engine_path, task='detect') + try: + metrics_trt = model_trt.val(data=data_yaml, batch=1, imgsz=640, rect=False) + map50_95_trt = metrics_trt.box.map + map50_trt = metrics_trt.box.map50 + logger.info(f"INT8 mAP50-95: {map50_95_trt:.4f}") + logger.info(f"INT8 mAP50: {map50_trt:.4f}") + map_results['int8'] = { + 'map50_95': map50_95_trt, + 'map50': map50_trt + } + except Exception as e: + logger.warning(f"INT8 Validation failed: {e}") + map_results['int8'] = {'map50_95': 0.0, 'map50': 0.0} + + logger.info("") + logger.info("="*60) + logger.info("mAP Drop Analysis") + logger.info("="*60) + + if map_results['fp32']['map50_95'] > 0 and map_results['int8']['map50_95'] > 0: + drop_50_95 = (map_results['fp32']['map50_95'] - map_results['int8']['map50_95']) / map_results['fp32']['map50_95'] * 100 + drop_50 = (map_results['fp32']['map50'] - map_results['int8']['map50']) / map_results['fp32']['map50'] * 100 + + logger.info(f"mAP50-95 Drop: {drop_50_95:.2f}%") + logger.info(f"mAP50 Drop: {drop_50:.2f}%") + logger.info("") + logger.info("Score Comparison:") + logger.info(f" FP32 mAP50-95: {map_results['fp32']['map50_95']:.4f} -> INT8 mAP50-95: {map_results['int8']['map50_95']:.4f}") + logger.info(f" FP32 mAP50: {map_results['fp32']['map50']:.4f} -> INT8 mAP50: {map_results['int8']['map50']:.4f}") + + map_results['drop'] = { + 'map50_95': drop_50_95, + 'map50': drop_50 + } + else: + logger.warning("Could not calculate mAP drop due to missing validation results") + map_results['drop'] = {'map50_95': 0.0, 'map50': 0.0} + + logger.info("="*60) + + return map_results + +def main(): + model_name = 'yolo11n.pt' + onnx_path = 'yolo11n.onnx' + engine_path = 'yolo11n.engine' + data_dir = 'data' # 校准数据目录 + val_data_yaml = 'coco8.yaml' # 验证集配置,用于计算 mAP + + # 1. 导出 ONNX + if not os.path.exists(onnx_path): + onnx_path = export_onnx(model_name) + else: + logger.info(f"Found existing ONNX: {onnx_path}") + + # 2. 构建 TensorRT Engine (含 INT8 校准) + if not os.path.exists(engine_path): + # 检查 data 目录是否有图片 + if not glob.glob(os.path.join(data_dir, '**', '*.jpg'), recursive=True) and \ + not glob.glob(os.path.join(data_dir, '*.jpg'), recursive=False): + logger.error(f"No images found in {data_dir} for calibration! Please prepare data first.") + return + + build_engine(onnx_path, engine_path, data_dir) + else: + logger.info(f"Found existing Engine: {engine_path}. Skipping build.") + + # 3. 验证与对比 + logger.info("Starting Validation... (Ensure you have a valid dataset yaml)") + validate_models(model_name, engine_path, val_data_yaml) + +if __name__ == "__main__": + main() diff --git a/rebuild_engines.bat b/rebuild_engines.bat new file mode 100644 index 0000000..a67079a --- /dev/null +++ b/rebuild_engines.bat @@ -0,0 +1,33 @@ +@echo off +echo ============================================ +echo Rebuilding TensorRT engines with batch 1-8 +echo ============================================ + +call conda activate yolo + +echo. +echo [1/5] Building INT8 640p (batch 1-8)... +trtexec --onnx=yolo11n.onnx --saveEngine=yolo11n_int8_b1_8.engine --explicitBatch --int8 --fp16 --workspace=4096 --builderOptimizationLevel=4 --calib=yolo11n_int8.cache --minShapes=input:1x3x640x640 --optShapes=input:4x3x640x640 --maxShapes=input:8x3x640x640 --useCudaGraph --useSpinWait --noTF32 +echo Done. + +echo. +echo [2/5] Building FP16 640p (batch 1-8)... +trtexec --onnx=yolo11n.onnx --saveEngine=yolo11n_fp16_640.engine --explicitBatch --fp16 --workspace=4096 --builderOptimizationLevel=4 --minShapes=input:1x3x640x640 --optShapes=input:4x3x640x640 --maxShapes=input:8x3x640x640 --useCudaGraph --useSpinWait --noTF32 +echo Done. + +echo. +echo [3/5] Building INT8 480p (batch 1-8)... +trtexec --onnx=yolo11n.onnx --saveEngine=yolo11n_int8_480.engine --explicitBatch --int8 --fp16 --workspace=4096 --builderOptimizationLevel=4 --calib=yolo11n_int8_480.cache --minShapes=input:1x3x480x480 --optShapes=input:4x3x480x480 --maxShapes=input:8x3x480x480 --useCudaGraph --useSpinWait --noTF32 +echo Done. + +echo. +echo [4/5] Building FP16 480p (batch 1-8)... +trtexec --onnx=yolo11n.onnx --saveEngine=yolo11n_fp16_480.engine --explicitBatch --fp16 --workspace=4096 --builderOptimizationLevel=4 --minShapes=input:1x3x480x480 --optShapes=input:4x3x480x480 --maxShapes=input:8x3x480x480 --useCudaGraph --useSpinWait --noTF32 +echo Done. + +echo. +echo ============================================ +echo All engines rebuilt! +echo Now run: run_all_benchmarks.bat +echo ============================================ +pause diff --git a/rebuild_fp16.bat b/rebuild_fp16.bat new file mode 100644 index 0000000..257f7cc --- /dev/null +++ b/rebuild_fp16.bat @@ -0,0 +1,28 @@ +@echo off +echo ============================================ +echo Rebuilding FP16 engines with batch=1 support +echo ============================================ + +call conda activate yolo + +echo. +echo [1/3] Rebuilding FP16 640p (batch 1-8)... +trtexec --onnx=yolo11n.onnx --saveEngine=yolo11n_fp16_640.engine --explicitBatch --fp16 --workspace=4096 --builderOptimizationLevel=4 --minShapes=input:1x3x640x640 --optShapes=input:4x3x640x640 --maxShapes=input:8x3x640x640 --useCudaGraph --useSpinWait --noTF32 +echo Done. + +echo. +echo [2/3] Rebuilding FP16 480p (batch 1-8)... +trtexec --onnx=yolo11n.onnx --saveEngine=yolo11n_fp16_480.engine --explicitBatch --fp16 --workspace=4096 --builderOptimizationLevel=4 --minShapes=input:1x3x480x480 --optShapes=input:4x3x480x480 --maxShapes=input:8x3x480x480 --useCudaGraph --useSpinWait --noTF32 +echo Done. + +echo. +echo [3/3] Rebuilding INT8 640p (batch 1-8)... +trtexec --onnx=yolo11n.onnx --saveEngine=yolo11n_int8_b1_8.engine --explicitBatch --int8 --fp16 --workspace=4096 --builderOptimizationLevel=4 --calib=yolo11n_int8.cache --minShapes=input:1x3x640x640 --optShapes=input:4x3x640x640 --maxShapes=input:8x3x640x640 --useCudaGraph --useSpinWait --noTF32 +echo Done. + +echo. +echo ============================================ +echo All engines rebuilt! +echo Now run: run_correct_benchmark.bat +echo ============================================ +pause diff --git a/run_all_benchmarks.bat b/run_all_benchmarks.bat new file mode 100644 index 0000000..68968e3 --- /dev/null +++ b/run_all_benchmarks.bat @@ -0,0 +1,29 @@ +@echo off +echo ============================================ +echo YOLO11n Person and Vehicle Detection Benchmark +echo ============================================ + +call conda activate yolo + +echo [1/4] Testing FP32... +yolo val model=yolo11n.pt data=coco_person_vehicle.yaml imgsz=640 rect=False batch=1 > fp32_results.txt 2>&1 +echo Done: fp32_results.txt + +echo [2/4] Testing INT8 640p... +yolo val model=yolo11n_int8_b1_8.engine data=coco_person_vehicle.yaml imgsz=640 rect=False batch=1 > int8_640_results.txt 2>&1 +echo Done: int8_640_results.txt + +echo [3/4] Testing FP16 640p... +yolo val model=yolo11n_fp16_640.engine data=coco_person_vehicle.yaml imgsz=640 rect=False batch=1 > fp16_640_results.txt 2>&1 +echo Done: fp16_640_results.txt + +echo [4/4] Testing FP16 480p... +yolo val model=yolo11n_fp16_480.engine data=coco_person_vehicle.yaml imgsz=480 rect=False batch=1 > fp16_480_results.txt 2>&1 +echo Done: fp16_480_results.txt + +echo. +echo ============================================ +echo All validation tests completed! +echo Now run: python parse_results.py +echo ============================================ +pause diff --git a/run_correct_benchmark.bat b/run_correct_benchmark.bat new file mode 100644 index 0000000..02f663e --- /dev/null +++ b/run_correct_benchmark.bat @@ -0,0 +1,34 @@ +@echo off +echo ============================================ +echo YOLO11n Benchmark - Person & Vehicle Only +echo Classes: 0(person),1(bicycle),2(car),3(motorcycle),5(bus),7(truck) +echo ============================================ +echo. + +call conda activate yolo + +echo [1/4] Testing FP32 (PyTorch)... +yolo val model=yolo11n.pt data=coco.yaml imgsz=640 batch=1 device=0 classes=0,1,2,3,5,7 > fp32_results.txt 2>&1 +echo Done: fp32_results.txt + +echo. +echo [2/4] Testing INT8 640p... +yolo val model=yolo11n_int8_b1_8.engine data=coco.yaml imgsz=640 batch=1 device=0 classes=0,1,2,3,5,7 > int8_640_results.txt 2>&1 +echo Done: int8_640_results.txt + +echo. +echo [3/4] Testing FP16 640p... +yolo val model=yolo11n_fp16_640.engine data=coco.yaml imgsz=640 batch=1 device=0 classes=0,1,2,3,5,7 > fp16_640_results.txt 2>&1 +echo Done: fp16_640_results.txt + +echo. +echo [4/4] Testing FP16 480p... +yolo val model=yolo11n_fp16_480.engine data=coco.yaml imgsz=480 batch=1 device=0 classes=0,1,2,3,5,7 > fp16_480_results.txt 2>&1 +echo Done: fp16_480_results.txt + +echo. +echo ============================================ +echo All validation tests completed! +echo Now run: python parse_final.py +echo ============================================ +pause diff --git a/run_full_analysis.bat b/run_full_analysis.bat new file mode 100644 index 0000000..6be0481 --- /dev/null +++ b/run_full_analysis.bat @@ -0,0 +1,34 @@ +@echo off +echo ============================================ +echo Complete Comparison Analysis +echo ============================================ +echo. + +call conda activate yolo + +echo. +echo [1/4] Running all validation tests... +echo. +call run_all_benchmarks.bat + +echo. +echo [2/4] Parsing results and generating report... +echo. +python parse_results.py + +echo. +echo [3/4] Generating visualization charts... +echo. +python generate_charts.py + +echo. +echo ============================================ +echo [4/4] Done! +echo ============================================ +echo. +echo Results saved to: vehicle_person_benchmark\ +echo - final_report.txt (detailed report) +echo - benchmark_charts.png (charts) +echo - all_results.json (raw data) + +pause diff --git a/run_full_workflow.bat b/run_full_workflow.bat new file mode 100644 index 0000000..53982be --- /dev/null +++ b/run_full_workflow.bat @@ -0,0 +1,94 @@ +@echo off +chcp 65001 >nul +echo ============================================ +echo YOLO INT8 Quantization Complete Workflow +echo ============================================ +echo. + +REM 激活虚拟环境 +echo [1/4] Activating yolo virtual environment... +call conda activate yolo +echo. + +REM 步骤1: 检查并导出ONNX模型 +echo [2/4] Checking ONNX model... +if not exist yolo11n.onnx ( + echo ONNX model not found. Exporting from yolo11n.pt... + if not exist yolo11n.pt ( + echo ERROR: yolo11n.pt not found! + echo Please place yolo11n.pt in the current directory. + pause + exit /b 1 + ) + python -c "from ultralytics import YOLO; model = YOLO('yolo11n.pt'); model.export(format='onnx', dynamic=True, simplify=True, opset=12)" + if not exist yolo11n.onnx ( + echo ERROR: ONNX export failed! + pause + exit /b 1 + ) + echo ONNX model exported successfully. +) else ( + echo ONNX model already exists: yolo11n.onnx +) +echo. + +REM 步骤2: 生成校准缓存 +echo [3/4] Generating calibration cache... +if not exist yolo11n_int8.cache ( + echo Running calibration_gen.py... + python calibration_gen.py + if not exist yolo11n_int8.cache ( + echo ERROR: Calibration cache generation failed! + pause + exit /b 1 + ) + echo Calibration cache generated: yolo11n_int8.cache +) else ( + echo Calibration cache already exists: yolo11n_int8.cache +) +echo. + +REM 步骤3: 构建TensorRT Engine +echo [4/4] Building TensorRT Engine... +echo. +echo Running trtexec with INT8 calibration... +echo. + +trtexec ^ + --onnx=yolo11n.onnx ^ + --saveEngine=yolo11n_int8_b1_8.engine ^ + --explicitBatch ^ + --int8 ^ + --fp16 ^ + --workspace=4096 ^ + --builderOptimizationLevel=5 ^ + --profilingVerbosity=detailed ^ + --calib=yolo11n_int8.cache ^ + --minShapes=input:1x3x640x640 ^ + --optShapes=input:4x3x640x640 ^ + --maxShapes=input:8x3x640x640 ^ + --useCudaGraph ^ + --useSpinWait ^ + --noTF32 + +if %errorlevel% equ 0 ( + echo. + echo ============================================ + echo TensorRT Engine built successfully! + echo Output: yolo11n_int8_b1_8.engine + echo ============================================ + echo. + echo Next steps: + echo 1. Run: python quantize_yolo.py + echo to validate models and calculate mAP drop + echo. +) else ( + echo. + echo ============================================ + echo Engine build failed! + ============================================ + pause + exit /b 1 +) + +pause diff --git a/test_engine_trtexec.bat b/test_engine_trtexec.bat new file mode 100644 index 0000000..2ce999a --- /dev/null +++ b/test_engine_trtexec.bat @@ -0,0 +1,13 @@ +@echo off +echo ============================================ +echo Test FP16 480p engine with trtexec +echo ============================================ + +call conda activate yolo + +echo [TEST] Direct inference with trtexec... +trtexec --loadEngine=yolo11n_fp16_480.engine --shapes=images:1x3x480x480 --iterations=10 --noDataTransfers --useCudaGraph + +echo. +echo Done. +pause diff --git a/test_speed_trtexec.bat b/test_speed_trtexec.bat new file mode 100644 index 0000000..a6de890 --- /dev/null +++ b/test_speed_trtexec.bat @@ -0,0 +1,17 @@ +@echo off +echo ============================================ +echo TensorRT Engine Benchmark +echo Run after: conda activate yolo +echo ============================================ + +echo. +echo [1] INT8-640p Speed Test... +trtexec --loadEngine=yolo11n_int8_b1_8.engine --shapes=images:1x3x640x640 --iterations=100 + +echo. +echo [2] FP16-480p Speed Test... +trtexec --loadEngine=yolo11n_fp16_480.engine --shapes=images:1x3x480x480 --iterations=100 + +echo. +echo Done. +pause diff --git a/test_two_engines.bat b/test_two_engines.bat new file mode 100644 index 0000000..d7a52cb --- /dev/null +++ b/test_two_engines.bat @@ -0,0 +1,17 @@ +@echo off +echo ============================================ +echo Build FP16 480p + Test FP16 480p +echo ============================================ + +call conda activate yolo + +echo [1/2] Building FP16 480p engine... +trtexec --onnx=yolo11n.onnx --saveEngine=yolo11n_fp16_480.engine --explicitBatch --fp16 --workspace=4096 --builderOptimizationLevel=4 --minShapes=images:1x3x480x480 --optShapes=images:4x3x480x480 --maxShapes=images:8x3x480x480 --useCudaGraph --useSpinWait --noTF32 + +echo. +echo [2/2] Testing FP16 480p... +yolo val model=yolo11n_fp16_480.engine data=coco.yaml imgsz=480 batch=1 device=0 classes=0,1,2,3,5,7 > fp16_480_results.txt 2>&1 + +echo. +echo Done. Run: python parse_simple.py +pause diff --git a/validate_int8.bat b/validate_int8.bat new file mode 100644 index 0000000..d2f0393 --- /dev/null +++ b/validate_int8.bat @@ -0,0 +1,42 @@ +@echo off +chcp 65001 >nul +echo ============================================ +echo YOLO INT8 TensorRT Validation Script +echo ============================================ +echo. + +REM 激活虚拟环境 +echo Activating yolo virtual environment... +call conda activate yolo +echo. + +REM 检查engine文件 +if not exist yolo11n_int8_b1_8.engine ( + echo ERROR: yolo11n_int8_b1_8.engine not found! + echo Please build the engine first using: build_engine.bat + pause + exit /b 1 +) + +echo Validating INT8 TensorRT Engine with COCO dataset... +echo Engine: yolo11n_int8_b1_8.engine +echo. + +REM 使用rect=False确保输入尺寸为640x640 +yolo val model=yolo11n_int8_b1_8.engine data=coco.yaml imgsz=640 rect=False + +if %errorlevel% equ 0 ( + echo. + echo ============================================ + echo Validation completed successfully! + echo ============================================ +) else ( + echo. + echo ============================================ + echo Validation failed! + echo ============================================ + pause + exit /b 1 +) + +pause