项目选择:

对cifar,coco数据集进行训练和识别,用训练好的网络做一下应用化的识别工作,如面部识别,车辆识别,标志标牌识别。

项目要求:

1 给出不同的算法,进行比较,最好三种,给出不同情况下的表现。
2 给出成果展示化的结果,如多子图,xy轴坐标,标签,图标签,不同曲线使用不同颜色,线性。
3 成果展示的时候按重要性提供正确率训练曲线,损失函数曲线,混淆矩阵,分类和识别样例。
4 尽量使用数据集进行训练。
5 展示使用训练的网络,应用到实际场景的画面,可以给出自动驾驶的实现,如特斯拉自动驾驶的实现方式(包括室外公路和室内SLAM,二维和三维),包括多个传感器采集数据的融合展示。
(说人话就是,模型用不同的算法进行训练,对训练后的结果进行分析,分析需要包含很多曲线,也需要用脚本运行训练后的结果,进行识别图片或者视频)

项目介绍:

技术、依赖选择:

绝大多数机器学习都是用Python做脚本语言。
一般机器学习的框架:数据集+模型+验证脚本。
这个项目所用到的大致框架:数据集+模型+验证脚本+训练结果分析
这里的模型可以通过变更训练指令参数选择不同的算法。

硬件环境:

训练设备:NVIDIA RTX4060 8G

运行内存:16GB

存储空间:30GB

系统环境:Windows 11

软件环境:

训练环境:Anaconda3 2022-10 + Python 3.9

深度学习框架:PyTorch 2.8.0(CUDA 12.6)

目标检测框架:Ultralytics 8.3.226(支持 YOLOv5/YOLOv8/RT-DETR)

数据处理库:numpy、pandas、scipy

可视化库:matplotlib、seaborn

图像处理库:opencv-python

运行环境:

Anaconda,是个科学计算与数据分析包,内置了Python解释器、科学计算工具、包管理与环境环境工具,简单说,就是为科学计算例如机器学习量身定做的环境,里面一些必要的包与解释器已经安装完毕,同时增添或修改其他依赖包也十分简便,它保证了不同项目的解释器版本隔离与项目的顺利运行。

数据集:

COCO(Common Objects in Context)是目前最常用的计算机视觉数据集之一,由微软发布,专注于真实场景下的目标检测、实例分割、关键点检测等任务。这里数据集用的coco2017(开源且好用,纯图片),下载好的数据集分为训练集与验证集,同时有对应的json文件(用于标注数据集中图片的信息,例如,分类、分辨率、id等,包含整个训练集或者验证集中所有图片信息,但在这个项目所使用的模型中,无法直接读取json,所以需要先利用Python脚本转换为每张图片对应的txt文件,模型才能进行读取),在模型训练时需要同时读取图片与对应信息。

模型:

这里的模型选择的是yolo系列(这里的模型是指在进行验证时所使用的权重文件+算法,也就是说,训练之前,它只是一个算法,在经过训练集与验证集训练完善以后,得到了训练结果也就是权重文件,那么再次利用训练结果进行验证时,这个时候才叫做模型,简单说,模型=算法+训练权重),我们选用的是封装了yolo系列算法的依赖包ultralytics,除此之外,还封装一些数据分析工具,能够在训练以后,同时生成权重文件与一些训练中的曲线文件(反正是能够反应模型训练是否成功,表现它的反应能力与预测能力),直接在Python环境中pip指令下载这个包即可使用。

验证脚本:

实际上对训练模型的应用就是对算法+训练结果权重的应用。在通过训练后,结果文件夹中会生成权重文件,那么在进行实际应用时,只需要在写一个脚本,写明使用到的权重文件路径、需要识别的图片或者视频路径,再通过opencv分析图片视频,传给模型识别运行,通过Python语法表示即可。

训练结果分析:

对每种模型的训练结果进行对比分析,以此才对比不同模型的性能差异,这里的脚本用到了pandas进行表格制作,matplotlib进行可视化。

整体实现流程:

运行环境搭建与配置→数据集选择下载与转换→模型训练→验证脚本创建与运行→训练结果分析

那么以上就是基础介绍,下面我将对这次课设的项目进行展示介绍,包括但不限于项目目录,目录内容,验证展示等。

项目展示:

总目录:分为训练数据、模型训练结果、模型验证结果、项目有关脚本四大部分。

项目总目录解析

训练数据data目录:

data目录详解

模型训练结果outputs目录:

outputs目录详解

模型训练结果部分曲线说明:
1. 精确率与召回率的调和平均(F1-score)

F1-score 是精确率(Precision)与召回率(Recall)的调和平均,用于综合评价模型的查准率与查全率之间的平衡程度。

  • 取值范围:0 ~ 1,越高表示模型整体表现越好
  • 适用于类别不平衡任务
  • 当 Precision 与 Recall 差距较大时,F1-score 是更稳健的指标

2. 精确率曲线(Precision Curve)

该曲线展示模型在不同阈值设置下的精确率变化情况。

  • 精确率(Precision)表示:被模型预测为某一类别的样本中有多少是真的
  • 随阈值提高,Precision 通常上升
  • 曲线越高说明误报越少,模型判断越“准确”

3. 召回率曲线(Recall Curve)

该曲线展示模型在不同阈值设置下的召回率变化情况。

  • 召回率(Recall)表示:真实属于某类别的样本中有多少被成功识别
  • 阈值降低时,Recall 通常上升
  • 曲线越高表示漏检越少,模型“覆盖能力”更强

4. P-R 曲线(Precision–Recall Curve)

P-R 曲线综合展示精确率与召回率的整体表现。

  • 曲线越靠近右上角越好
  • 面积越大(AP 越高)说明模型性能越稳定
  • 适用于类别不平衡场景下的整体指标对比

5. 混淆矩阵(Confusion Matrix)

混淆矩阵展示模型在各个类别上的预测情况,包括正确分类与错误分类的分布。

  • 主对角线表示正确分类数量,越高越好
  • 非对角线的值表示类别之间的混淆
  • 可用于定位模型在哪些类别上性能较弱

6. 归一化混淆矩阵(Normalized Confusion Matrix)

在混淆矩阵的基础上进行比例归一化,使每个类别以百分比展示其预测正确率。

  • 更适用于类别不均衡的数据集
  • 对角线接近 1 表示该类识别准确
  • 能清晰发现模型最容易混淆的类别

7. 标签可视化图(Label / Prediction Visualization)

展示模型在真实图像上的预测结果,可包含:

  • 原图与预测标签
  • 热力图(如 Grad-CAM)
  • 数据类别分布可视化

用于辅助分析模型关注区域及数据整体特征。


8. 训练过程详细数值(Training Log / Metrics)

训练过程中记录的关键数值,如:

  • Train Loss / Validation Loss(损失曲线)
  • Train Accuracy / Validation Accuracy(准确率曲线)
  • 学习率(LR)变化
  • F1 / mAP 随 epoch 的变化

这些数值反映训练稳定性、收敛情况及是否存在过拟合/欠拟合。


9. 综合训练曲线(Overall Training Curves)

将多个指标(Loss、Accuracy、F1、mAP 等)整合在一张图中,用于整体观察模型训练效果。

  • Loss 应整体下降
  • Acc/F1/mAP 应整体上升
  • Train 与 Val 曲线差距小 → 模型泛化能力好
  • 无明显震荡 → 模型训练稳定

模型验证结果runs目录:

runs目录详解

项目有关脚本scripts目录:

scripts目录详解

模型性能对比脚本代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import os
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

# ----------------- 配置 -----------------
models = [
r"D:\Code-project\pycharm\pythonProject1\outputs\rtdetr_4class",
r"D:\Code-project\pycharm\pythonProject1\outputs\yolov5_4class",
r"D:\Code-project\pycharm\pythonProject1\outputs\yolov8_4class"
]

output_dir = r"D:\Code-project\pycharm\pythonProject1\outputs\comparison_plots"
os.makedirs(output_dir, exist_ok=True)

ROLLING_WINDOW = 3

# ----------------- 函数 -----------------
def load_results(csv_path):
df = pd.read_csv(csv_path)
# 替换 / 和 (B) 为 _
df.columns = [c.replace('(B)','').replace('/','_').strip() for c in df.columns]
return df

def get_common_columns(df_dict):
# 找出所有模型共有的列
common_cols = set.intersection(*(set(df.columns) for df in df_dict.values()))
return list(common_cols)

def plot_common_metrics(df_dict, common_cols, save_dir):
for col in common_cols:
if col in ['epoch', 'time', 'lr_pg0', 'lr_pg1', 'lr_pg2']:
continue # 排除不绘图的列
plt.figure(figsize=(8,5))
for model_name, df in df_dict.items():
if col in df.columns:
plt.plot(df[col].rolling(ROLLING_WINDOW, min_periods=1).mean(), label=model_name)
plt.title(col)
plt.xlabel("Epoch")
plt.ylabel(col)
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.savefig(os.path.join(save_dir, f"{col}.png"))
plt.close()

def plot_confusion_matrices(models, save_dir, normalized=False):
plt.figure(figsize=(12,4))
n_models = len(models)
for i, model_path in enumerate(models):
model_name = os.path.basename(model_path)
if normalized:
cm_file = os.path.join(model_path, "confusion_matrix_normalized.png")
else:
cm_file = os.path.join(model_path, "confusion_matrix.png")
if not os.path.exists(cm_file):
print(f"⚠️ {cm_file} 不存在,跳过")
continue
img = mpimg.imread(cm_file)
plt.subplot(1, n_models, i+1)
plt.imshow(img)
plt.axis('off')
plt.title(model_name)
plt.tight_layout()
suffix = "_normalized" if normalized else ""
plt.savefig(os.path.join(save_dir, f"confusion_matrix_comparison{suffix}.png"))
plt.close()
print(f"✅ 混淆矩阵对比图已生成 {suffix}")

# ----------------- 主逻辑 -----------------
df_dict = {}
for model_path in models:
model_name = os.path.basename(model_path)
csv_file = os.path.join(model_path, "results.csv")
if not os.path.exists(csv_file):
print(f"⚠️ {csv_file} 不存在,跳过")
continue
df = load_results(csv_file)
df_dict[model_name] = df

if not df_dict:
print("❌ 没有可用的结果文件,退出")
exit()

# 绘制指标对比曲线
common_cols = get_common_columns(df_dict)
print("✅ 公共列:", common_cols)
plot_common_metrics(df_dict, common_cols, output_dir)

# 绘制混淆矩阵对比(原始 & 归一化)
plot_confusion_matrices(models, output_dir, normalized=False)
plot_confusion_matrices(models, output_dir, normalized=True)
数据集标签处理脚本代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import json
import os
from tqdm import tqdm

# 路径配置
json_path = "data/annotations/instances_val2017.json"
images_dir = "data/images/val2017"
labels_dir = "data/labels/val2017"

os.makedirs(labels_dir, exist_ok=True)

# 读取 JSON 文件
with open(json_path, "r", encoding="utf-8") as f:
data = json.load(f)

# 类别 id → 连续索引(假设 COCO,从 1 开始)
categories = {cat["id"]: cat["id"] - 1 for cat in data["categories"]}

# 图片 id → 文件名、尺寸
images = {img["id"]: {"file_name": img["file_name"], "width": img["width"], "height": img["height"]}
for img in data["images"]}

# 标注信息
annotations = {}
for ann in data["annotations"]:
img_id = ann["image_id"]
bbox = ann["bbox"]
category_id = ann["category_id"]

if img_id not in annotations:
annotations[img_id] = []
annotations[img_id].append((category_id, bbox))

# 转换为 YOLO 格式
for img_id, info in tqdm(images.items(), desc="Converting JSON → YOLO labels"):
if img_id not in annotations:
continue

txt_path = os.path.join(labels_dir, os.path.splitext(info["file_name"])[0] + ".txt")
w, h = info["width"], info["height"]

lines = []
for category_id, bbox in annotations[img_id]:
x_min, y_min, bw, bh = bbox
x_center = (x_min + bw / 2) / w
y_center = (y_min + bh / 2) / h
width = bw / w
height = bh / h
class_idx = categories[category_id]
lines.append(f"{class_idx} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}")

with open(txt_path, "w") as f:
f.write("\n".join(lines))

图片识别脚本代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# scripts/predict.py
from ultralytics import YOLO
import os
import time
import shutil

def main():
# -------------------------
# 配置部分
# -------------------------
model_path = r"D:\Code-project\pycharm\pythonProject1\outputs\yolov5_4class\weights\best.pt"
source = r"D:\Code-project\pycharm\pythonProject1\data\images\test_images"

# 在保存目录后面添加时间戳
timestamp = time.strftime("%Y-%m-%d_%H-%M-%S")
save_dir = fr"D:\Code-project\pycharm\pythonProject1\runs\detect\predict_{timestamp}"

show_results = False # 是否显示预测图片
save_results = True # 是否保存带标注图片
save_txt_results = False # 是否保存 YOLO txt 格式

# 创建保存目录
os.makedirs(save_dir, exist_ok=True)

# -------------------------
# 加载模型
# -------------------------
model = YOLO(model_path)

# -------------------------
# 执行预测
# -------------------------
# 注意:指定 save=True 会让 YOLO 默认存 runs/detect/expX,
# 所以我们先让它保存在临时文件夹,再手动移动结果。
temp_dir = os.path.join(os.path.dirname(save_dir), "temp_results")
if os.path.exists(temp_dir):
shutil.rmtree(temp_dir)

results = model(source, save=True, project=temp_dir, name="temp", exist_ok=True)

# -------------------------
# 遍历每张图片的结果
# -------------------------
for idx, r in enumerate(results):
img_name = os.path.basename(r.path)
txt_path = os.path.join(save_dir, os.path.splitext(img_name)[0] + ".txt")

print(f"预测图片路径: {r.path}")
print(r.summary()) # 类别统计信息

if show_results:
r.show()

if save_txt_results:
r.save_txt(txt_file=txt_path) # 保存 txt,必须指定路径

# -------------------------
# 将 YOLO 默认保存的图片移动到我们指定的 save_dir
# -------------------------
temp_output_dir = os.path.join(temp_dir, "temp")
if os.path.exists(temp_output_dir):
for file in os.listdir(temp_output_dir):
shutil.move(os.path.join(temp_output_dir, file), save_dir)
shutil.rmtree(temp_dir)

print(f"✅ 所有预测结果已保存到: {save_dir}")

if __name__ == "__main__":
main()

视频识别脚本代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# scripts/predict_video_colorfix.py
from ultralytics import YOLO
import cv2
import numpy as np

def main():
# -------------------------
# 配置部分
# -------------------------
model_path = r"D:\Code-project\pycharm\pythonProject1\outputs\yolov8_4class\weights\best.pt"
source = r"D:\Code-project\pycharm\pythonProject1\data\videos\1.mp4" # 视频文件路径
# source = 0 # 若要使用摄像头,请改为 0

# -------------------------
# 加载模型
# -------------------------
model = YOLO(model_path)

# -------------------------
# 打开视频源
# -------------------------
cap = cv2.VideoCapture(source)
if not cap.isOpened():
print(f"❌ 无法打开视频源: {source}")
return

width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS) or 30
print(f"🎥 正在处理视频: {source}")
print(f"分辨率: {width}x{height}, FPS: {fps}")

# -------------------------
# 实时推理与显示
# -------------------------
while True:
ret, frame = cap.read()
if not ret:
print("✅ 视频结束或无法读取帧。")
break

# ⚠️ OpenCV 读取的帧是 BGR,需先转为 RGB 才能传入 YOLO
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

# 模型推理
results = model(frame_rgb)
r = results[0]

# YOLO 输出为 RGB 图像(我们强制确保 dtype 正确)
annotated_frame = np.array(r.plot(), dtype=np.uint8)

# 转回 BGR,用于 OpenCV 显示
annotated_bgr = cv2.cvtColor(annotated_frame, cv2.COLOR_RGB2BGR)

# 显示结果
cv2.imshow("YOLO Video Detection (Press Q to quit)", annotated_bgr)

# 按 q 键退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break

cap.release()
cv2.destroyAllWindows()
print("✅ 检测结束,窗口已关闭。")

if __name__ == "__main__":
main()

识别成果展示(一张为例):

待识别图片:

000000002006

已识别结果:

000000002006