Add YOLO11 TensorRT quantization benchmark scripts
- Engine build scripts (FP16/INT8) - Benchmark validation scripts - Result parsing and analysis tools - COCO dataset configuration
This commit is contained in:
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
@@ -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
|
||||
361
benchmark_engines.py
Normal file
361
benchmark_engines.py
Normal file
@@ -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()
|
||||
393
benchmark_vehicle_person.py
Normal file
393
benchmark_vehicle_person.py
Normal file
@@ -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()
|
||||
67
build_engine.bat
Normal file
67
build_engine.bat
Normal file
@@ -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
|
||||
22
build_fp16_480.bat
Normal file
22
build_fp16_480.bat
Normal file
@@ -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
|
||||
20
build_fp16_640.bat
Normal file
20
build_fp16_640.bat
Normal file
@@ -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
|
||||
20
build_int8_480.bat
Normal file
20
build_int8_480.bat
Normal file
@@ -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
|
||||
206
calibration_gen.py
Normal file
206
calibration_gen.py
Normal file
@@ -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()
|
||||
286
capture_dataset.py
Normal file
286
capture_dataset.py
Normal file
@@ -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()
|
||||
40
clean_test.bat
Normal file
40
clean_test.bat
Normal file
@@ -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
|
||||
20
coco_person_vehicle.yaml
Normal file
20
coco_person_vehicle.yaml
Normal file
@@ -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
|
||||
18
export_and_test.bat
Normal file
18
export_and_test.bat
Normal file
@@ -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
|
||||
21
fix_fp16_engine.bat
Normal file
21
fix_fp16_engine.bat
Normal file
@@ -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
|
||||
27
full_coco_benchmark.bat
Normal file
27
full_coco_benchmark.bat
Normal file
@@ -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
|
||||
108
generate_charts.py
Normal file
108
generate_charts.py
Normal file
@@ -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()
|
||||
69
generate_coco_labels.py
Normal file
69
generate_coco_labels.py
Normal file
@@ -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')
|
||||
2
main.py
Normal file
2
main.py
Normal file
@@ -0,0 +1,2 @@
|
||||
import tensorrt as trt
|
||||
print('TensorRT', trt.__version__, 'OK!')
|
||||
369
parse_final.py
Normal file
369
parse_final.py
Normal file
@@ -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()
|
||||
174
parse_full_coco.py
Normal file
174
parse_full_coco.py
Normal file
@@ -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)
|
||||
199
parse_results.py
Normal file
199
parse_results.py
Normal file
@@ -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()
|
||||
297
parse_results_v2.py
Normal file
297
parse_results_v2.py
Normal file
@@ -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()
|
||||
109
parse_simple.py
Normal file
109
parse_simple.py
Normal file
@@ -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)
|
||||
46
prepare_coco.py
Normal file
46
prepare_coco.py
Normal file
@@ -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')
|
||||
273
quantize_yolo.py
Normal file
273
quantize_yolo.py
Normal file
@@ -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()
|
||||
33
rebuild_engines.bat
Normal file
33
rebuild_engines.bat
Normal file
@@ -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
|
||||
28
rebuild_fp16.bat
Normal file
28
rebuild_fp16.bat
Normal file
@@ -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
|
||||
29
run_all_benchmarks.bat
Normal file
29
run_all_benchmarks.bat
Normal file
@@ -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
|
||||
34
run_correct_benchmark.bat
Normal file
34
run_correct_benchmark.bat
Normal file
@@ -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
|
||||
34
run_full_analysis.bat
Normal file
34
run_full_analysis.bat
Normal file
@@ -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
|
||||
94
run_full_workflow.bat
Normal file
94
run_full_workflow.bat
Normal file
@@ -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
|
||||
13
test_engine_trtexec.bat
Normal file
13
test_engine_trtexec.bat
Normal file
@@ -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
|
||||
17
test_speed_trtexec.bat
Normal file
17
test_speed_trtexec.bat
Normal file
@@ -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
|
||||
17
test_two_engines.bat
Normal file
17
test_two_engines.bat
Normal file
@@ -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
|
||||
42
validate_int8.bat
Normal file
42
validate_int8.bat
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user