BRICS-FS-56

人工智能技术应用

项目式教学教材 —— 从零基础到独立完成竞赛项目

面向赛项:2026 金砖国家职业技能大赛 · 人工智能技术应用

覆盖模块:模块A 智能图像处理 / 模块B 智能制造与工业质检 / 模块C 智能客服与舆情分析 / 模块D AI低空创新应用

适用对象:零基础初学者 · 中职 / 高职 / 应用型本科学生

教材版本:v1.0 · 2026年

目录

  1. 前言
  2. 第0章 开发环境搭建
    1. 0.1 Python 与 Anaconda 安装
    2. 0.2 核心库安装(OpenCV / PyTorch / Transformers / Ultralytics)
    3. 0.3 Jupyter Notebook 使用入门
    4. 0.4 数据集与项目文件准备
  3. 项目一:智能图像处理应用开发(模块A)
    1. 1.1 项目导读与学习目标
    2. 1.2 知识准备
    3. 1.3 环境与数据准备
    4. 1.4 项目实施
    5. 1.5 项目总结与拓展
  4. 项目二:智能制造与工业质检(模块B)
    1. 2.1 项目导读与学习目标
    2. 2.2 知识准备
    3. 2.3 环境与数据准备
    4. 2.4 项目实施
    5. 2.5 项目总结与拓展
  5. 项目三:智能客服与舆情分析(模块C)
    1. 3.1 项目导读与学习目标
    2. 3.2 知识准备
    3. 3.3 环境与数据准备
    4. 3.4 项目实施
    5. 3.5 项目总结与拓展
  6. 项目四:AI低空创新应用(模块D)
    1. 4.1 项目导读与学习目标
    2. 4.2 知识准备
    3. 4.3 环境与数据准备
    4. 4.4 项目实施
    5. 4.5 项目总结与拓展
  7. 附录
    1. 附录A:Python 基础速查
    2. 附录B:常用库 API 速查
    3. 附录C:常见错误与解决方案

前言

为什么要编写这本教材

2026年金砖国家职业技能大赛(金砖国家未来技能和技术挑战赛)人工智能技术应用赛项(BRICS-FS-56)聚焦"人工智能+行业"应用场景,涵盖智能图像处理、智能制造与工业质检、智能客服与舆情分析、AI低空创新应用四大模块。然而,对于零基础的学生而言,直接面对竞赛题目往往不知从何入手——缺乏对OpenCV、PyTorch、Transformer、YOLO等框架的基本认知,也不清楚迁移学习、目标检测、情感分析等技术的工作原理。

本教材采用项目式教学(Project-Based Learning, PBL)理念,以竞赛样题为蓝本,将每个模块拆解为可独立完成的子任务,从环境搭建到代码实现逐步引导,确保零基础学生能够跟随教材独立完成全部项目

教材特色

1. 零基础友好

每个任务先讲"为什么",再讲"怎么做"。所有代码均配有逐行注释,关键概念以"知识链接"形式穿插讲解,不预设读者有任何AI编程经验。

2. 紧贴竞赛真题

教材以选拔赛和国际总决赛两套样题为基础,覆盖全部4个模块共40余道编程任务,每道题提供完整的思路分析、代码实现和易错提示。

3. 全流程覆盖

不仅讲解"填空式"答题代码,更覆盖数据准备、模型训练、结果可视化、模型保存与加载的完整工作流,帮助学生理解项目全貌。

4. 学以致用

每个项目末尾设有"拓展练习",引导学生在掌握基础任务后进行进阶探索,培养独立解决未知问题的能力。

如何使用本教材

  1. 第一阶段(第0章):跟随环境搭建章节,完成Python、核心库和Jupyter Notebook的安装配置。这是所有项目的前置条件。
  2. 第二阶段(项目一):从最简单的OpenCV图像处理入手,建立对AI编程的直观感受。建议用时1-2天。
  3. 第三阶段(项目二~四):按难度递增顺序学习工业质检、舆情分析和低空AI项目。每个项目建议用时2-3天。
  4. 第四阶段(拓展与附录):完成拓展练习,查阅附录中的速查表和常见错误解决方案,巩固所学知识。
学习建议 建议在阅读每个任务时,先尝试自己思考和编写代码,遇到困难再查看教材中的参考实现。这种"先试后看"的方式能显著提升学习效果。

竞赛模块一览

模块名称核心技术难度建议学习顺序
模块A智能图像处理应用开发OpenCV、matplotlib入门第1个
模块B智能制造与工业质检PyTorch、MobileNetV2、迁移学习中等第2个
模块C智能客服与舆情分析HuggingFace、TinyBERT、NLP中等第3个
模块DAI低空创新应用YOLOv8、Ultralytics、目标检测进阶第4个

第0章 开发环境搭建

在开始任何项目之前,我们需要搭建一个完整的Python AI开发环境。本章将手把手引导你完成所有安装步骤。请务必按照顺序操作,遇到问题可参考附录C的常见错误解决方案。

0.1 Python 与 Anaconda 安装

0.1.1 为什么选择 Anaconda

Anaconda 是一个面向数据科学和机器学习的Python发行版,它自带Python解释器、包管理器(conda)和大量预装的科学计算库。对于AI开发初学者来说,使用Anaconda可以避免复杂的依赖管理问题。

0.1.2 下载与安装

Windows 系统

  1. 访问 Anaconda 官网下载页面:https://www.anaconda.com/download
  2. 下载 Windows 版本的 Anaconda 安装包(64-Bit Graphical Installer)
  3. 双击运行安装程序,按提示完成安装。安装时建议勾选"Add Anaconda to my PATH environment variable"选项
  4. 安装完成后,打开"Anaconda Prompt"验证安装

macOS 系统

  1. 访问 Anaconda 官网下载页面:https://www.anaconda.com/download
  2. 下载 macOS 版本的安装包(64-Bit Graphical Installer)
  3. 双击 .pkg 文件按提示完成安装
  4. 打开"终端"验证安装

0.1.3 验证安装

打开终端(Windows为Anaconda Prompt,macOS为终端),输入以下命令:

# 检查 Python 版本
python --version
# 预期输出: Python 3.9.x 或更高版本

# 检查 conda 版本
conda --version
# 预期输出: conda 23.x.x 或更高版本

0.1.4 创建虚拟环境

虚拟环境可以为不同项目创建隔离的Python环境,避免库版本冲突。我们为竞赛创建一个名为 brics_ai 的专用环境:

# 创建名为 brics_ai 的虚拟环境,Python 版本为 3.9
conda create -n brics_ai python=3.9 -y

# 激活虚拟环境
conda activate brics_ai

# 激活后,命令行前面会出现 (brics_ai) 标识
# 后续所有操作都请在激活此环境后进行
注意 每次打开新的终端窗口时,都需要先执行 conda activate brics_ai 激活环境,否则找不到已安装的库。如果忘记了激活环境,运行代码时会报 ModuleNotFoundError 错误。

0.2 核心库安装

竞赛涉及四个模块,每个模块需要不同的Python库。以下按模块列出所需安装的库。

0.2.1 通用基础库

# 确保已激活 brics_ai 环境
conda activate brics_ai

# 安装 Jupyter Notebook(交互式编程环境)
conda install jupyter -y

# 安装 matplotlib(绘图库,各模块都会用到)
conda install matplotlib -y

# 安装 numpy(数值计算库)
conda install numpy -y

# 安装 pandas(数据处理库)
conda install pandas -y

# 安装 psutil(系统信息获取,模块D需要)
conda install psutil -y

0.2.2 模块A:OpenCV 安装

# 安装 OpenCV(opencv-python 是官方推荐的包名)
pip install opencv-python

# 如果需要额外的贡献模块(如扩展算法),可安装 contrib 版本
# pip install opencv-contrib-python

# 验证安装
python -c "import cv2; print('OpenCV版本:', cv2.__version__)"
# 预期输出: OpenCV版本: 4.x.x

0.2.3 模块B:PyTorch 安装

PyTorch 是深度学习框架,模块B(工业质检)和模块C(舆情分析)都需要使用。安装时需要根据你的电脑是否有NVIDIA显卡来选择版本:

有 NVIDIA 显卡(推荐,训练速度快很多)

# 安装支持 CUDA 的 PyTorch
conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia -y

无 NVIDIA 显卡(CPU 版本,训练较慢但功能完整)

# 安装 CPU 版本的 PyTorch
conda install pytorch torchvision torchaudio cpuonly -c pytorch -y
# 验证 PyTorch 安装
python -c "import torch; print('PyTorch版本:', torch.__version__); print('CUDA可用:', torch.cuda.is_available())"
# 预期输出:
# PyTorch版本: 2.x.x
# CUDA可用: True(有显卡)或 False(无显卡)
GPU vs CPU 有NVIDIA显卡时 torch.cuda.is_available() 返回 True,模型训练速度可提升5-20倍。如果没有显卡,CPU版本也能完成所有任务,只是训练时间更长。

0.2.4 模块C:Transformers 安装

# 安装 HuggingFace Transformers 库
pip install transformers

# 验证安装
python -c "from transformers import BertTokenizer; print('Transformers安装成功')"

0.2.5 模块D:Ultralytics (YOLOv8) 安装

# 安装 Ultralytics(YOLOv8 官方库)
pip install ultralytics

# 验证安装
python -c "from ultralytics import YOLO; print('Ultralytics安装成功')"

0.3 Jupyter Notebook 使用入门

0.3.1 什么是 Jupyter Notebook

Jupyter Notebook 是一个交互式编程环境,可以在浏览器中编写和运行Python代码,并即时查看运行结果(包括图像、表格等)。它是AI开发中最常用的工具之一,竞赛也基于此环境进行。

0.3.2 启动 Jupyter Notebook

# 在终端中执行(确保已激活 brics_ai 环境)
conda activate brics_ai
jupyter notebook

执行后,浏览器会自动打开Jupyter Notebook的主页。点击右上角的"New"按钮,选择"Python 3 (ipykernel)"即可创建一个新的Notebook。

0.3.3 基本操作

操作快捷键说明
运行当前单元格Shift + Enter执行代码并跳到下一个单元格
运行当前单元格(不跳转)Ctrl + Enter执行代码但停留在当前单元格
新建单元格(上方)A在当前单元格上方插入新单元格
新建单元格(下方)B在当前单元格下方插入新单元格
删除单元格D, D(按两次D)删除当前单元格
切换为代码模式Y将单元格设为代码类型
切换为Markdown模式M将单元格设为文本类型

0.3.4 第一个 Jupyter 程序

在新建的Notebook中,输入以下代码并按 Shift+Enter 运行:

# 第一个程序:验证环境是否正常
import sys
print("Python版本:", sys.version)

import numpy as np
print("NumPy版本:", np.__version__)

import matplotlib.pyplot as plt
plt.figure(figsize=(4,3))
plt.plot([1,2,3], [1,4,9])
plt.title("Test Plot")
plt.show()

如果能看到Python版本信息和一张折线图,说明环境配置成功!

0.4 数据集与项目文件准备

竞赛中每个模块都需要特定的数据集和项目文件。以下是各模块所需的数据准备说明:

模块数据集/文件说明
模块Abutterfly.jpg蝴蝶图像,用于图像处理练习
模块BNEU-DET 钢材表面缺陷数据集含6类缺陷:crazing、inclusion、patches、pitted、rolled、scratches
模块C中文电商评论情感分析数据集含正向/负向二分类标注的中文评论
模块D无人机航拍行人数据集YOLO格式标注的航拍行人检测数据

0.4.1 项目目录结构

建议在电脑上创建如下目录结构来管理项目文件:

brics_ai_projects/          # 项目根目录
├── data/                   # 数据文件夹
│   ├── butterfly.jpg       # 模块A图像
│   ├── NEU-DET/            # 模块B数据集
│   │   ├── train/
│   │   │   └── images/
│   │   └── val/
│   │       └── images/
│   ├── sentiment_data/     # 模块C数据集
│   │   ├── train.csv
│   │   └── val.csv
│   └── drone_dataset/      # 模块D数据集
│       ├── images/
│       │   ├── train/
│       │   └── val/
│       └── labels/
│           ├── train/
│           └── val/
├── module_a.ipynb          # 模块A Jupyter Notebook
├── module_b.ipynb          # 模块B Jupyter Notebook
├── module_c.ipynb          # 模块C Jupyter Notebook
└── module_d.ipynb          # 模块D Jupyter Notebook
竞赛环境提示 在实际竞赛中,数据集和项目文件会预装在竞赛平台上,你只需要在指定位置(***标记处)填写代码即可。本教材的练习环境需要你自行准备数据,可以在网上搜索对应的数据集名称进行下载。
模块 A

项目一:智能图像处理应用开发

使用 OpenCV 完成图像读取、颜色空间转换、滤波、边缘检测、阈值分割等核心操作

1.1 项目导读与学习目标

项目背景

图像处理是人工智能视觉领域的基础。无论是工业质检中的表面缺陷检测、医疗影像中的病灶识别,还是自动驾驶中的车道线检测,都离不开图像处理技术。OpenCV(Open Source Computer Vision Library)是最流行的开源计算机视觉库,提供了超过2500种图像处理算法。

本项目以经典的 butterfly(蝴蝶)图像为处理对象,通过一系列基础图像处理操作,帮助你建立对图像处理流程的直观理解。这些操作是后续深度学习视觉任务(如模块B的工业质检和模块D的目标检测)的基础。

学习目标

  • 掌握 OpenCV 库的导入与基本使用方法
  • 能够使用 OpenCV 读取、显示和保存图像
  • 理解 BGR 与 RGB 颜色空间的区别并进行转换
  • 掌握灰度化、高斯滤波、Canny边缘检测、阈值二值化等核心操作
  • 能够使用 matplotlib 展示图像处理结果

1.2 知识准备

1.2.1 数字图像基础

在计算机中,图像是由一个个像素(pixel)组成的二维矩阵。每个像素是一个数值,表示该位置的颜色或亮度。对于彩色图像,每个像素通常由三个通道(Channel)组成——红色(R)、绿色(G)、蓝色(B),即RGB模型。每个通道的取值范围通常是0~255(8位无符号整数)。

关键概念

  • 分辨率:图像的宽×高,如1920×1080表示宽1920像素、高1080像素
  • 通道:彩色图像有3个通道(RGB),灰度图像只有1个通道
  • 像素值:0表示黑色,255表示白色,中间值表示灰色或不同颜色
  • 位深:每个像素值用多少位表示,常见为8位(0~255)

1.2.2 OpenCV 的颜色空间:BGR vs RGB

这是一个非常重要的知识点,也是初学者最容易踩的"坑"。在日常生活中,我们习惯用 RGB(红、绿、蓝)顺序来描述颜色。但 OpenCV 读取图像时默认使用 BGR(蓝、绿、红)顺序——也就是说,第一个通道是蓝色,第三个通道是红色。

而 matplotlib(Python最常用的绘图库)显示图像时默认使用 RGB 顺序。如果直接用 matplotlib 显示 OpenCV 读取的图像,红色和蓝色会互换,导致颜色不自然。

易错点 用 matplotlib 显示 OpenCV 读取的图像前,必须先将 BGR 转为 RGB,否则红色和蓝色会颠倒。这是竞赛中的高频考点!

1.2.3 灰度图像

灰度图像只包含亮度信息,不包含颜色信息。将彩色图像转为灰度图像是图像处理中常见的预处理步骤,可以减少数据量、突出形状和纹理特征。OpenCV 中常用的转换公式为:

Gray = 0.299 × R + 0.587 × G + 0.114 × B

这个公式反映了人眼对不同颜色的敏感度——对绿色最敏感,对蓝色最不敏感。

1.2.4 图像滤波

图像滤波是图像处理中的基本操作,用于去噪、平滑或增强图像特征。常见的滤波方法有:

滤波方法原理适用场景
均值滤波用邻域像素的平均值替代中心像素简单去噪
高斯滤波用高斯加权平均替代中心像素平滑去噪(最常用)
中值滤波用邻域像素的中位数替代中心像素去除椒盐噪声

本项目使用高斯滤波(Gaussian Blur),它对邻域像素进行加权平均,距离中心越近的像素权重越大,符合高斯分布(钟形曲线)。高斯核大小决定了滤波的范围——核越大,模糊效果越强。

1.2.5 边缘检测(Canny算法)

边缘是图像中像素值急剧变化的位置,通常对应物体的轮廓。Canny边缘检测是最经典的边缘检测算法,它包含五个步骤:高斯滤波去噪→计算梯度幅值和方向→非极大值抑制→双阈值检测→滞后边界跟踪。

双阈值机制

Canny算法使用两个阈值:高阈值低阈值。梯度值高于高阈值的像素被标记为"强边缘"(一定是边缘);低于低阈值的被丢弃;介于两者之间的像素,只有当与强边缘相连时才被保留为边缘。这种机制可以有效减少噪声干扰。

1.2.6 阈值分割(二值化)

阈值分割是将灰度图像转换为二值图像(只有黑和白两种颜色)的过程。设定一个阈值T,像素值大于T的设为255(白色),小于等于T的设为0(黑色)。这种操作可以将目标物体从背景中分离出来。

1.3 环境与数据准备

确保已完成第0章的环境搭建。本项目需要以下文件:

创建一个新的 Jupyter Notebook,命名为 module_a.ipynb,在其中编写代码。

1.4 项目实施

以下按照竞赛样题的题目顺序,逐题讲解。每道题包含题目要求思路分析代码实现代码详解四个部分。选拔赛和国际总决赛的题目合并讲解,以"【选拔赛第x题】"或"【总决赛第x题】"标注来源。

选拔赛 第1题 导入 OpenCV 库
导入 OpenCV 库。

思路分析

导入库是所有Python程序的第一步。OpenCV 库在 Python 中的导入名不是"opencv",而是"cv2"。这个名称来源于OpenCV的C++ API版本号(C++ API是第2版,因此叫cv2)。

代码实现

import cv2

代码详解

import cv2 将OpenCV库导入到当前Python环境中。导入后,我们可以通过 cv2.函数名 的方式调用OpenCV的所有功能。

验证

可以在导入后添加一行代码来验证OpenCV是否安装成功:

import cv2
print("OpenCV版本:", cv2.__version__)
常见错误 如果报 ModuleNotFoundError: No module named 'cv2',说明OpenCV未安装或未激活正确的虚拟环境。解决方法:在终端中执行 conda activate brics_ai 后重新运行 pip install opencv-python
选拔赛 第2题 读取图像
使用 OpenCV 读取图像 butterfly.jpg,并赋值给变量 image,图像在 data 文件夹中。

思路分析

读取图像使用 cv2.imread() 函数。该函数接收一个文件路径参数,返回一个NumPy数组(表示图像的像素矩阵)。需要注意路径的正确性——如果文件路径错误,imread 不会报错,而是返回 None,这在后续操作中会导致难以调试的错误。

代码实现

image = cv2.imread('data/butterfly.jpg')

代码详解

  • cv2.imread('data/butterfly.jpg'):读取 data 文件夹下的 butterfly.jpg 文件
  • 返回值是一个三维NumPy数组,形状为 (高度, 宽度, 3),其中3表示BGR三个通道
  • 将读取结果赋值给变量 image,后续操作都基于这个变量

验证

import cv2

image = cv2.imread('data/butterfly.jpg')

# 检查是否读取成功
if image is None:
    print("错误:图像读取失败,请检查文件路径!")
else:
    print("图像读取成功!")
    print("图像形状:", image.shape)  # (高, 宽, 通道数)
    print("图像数据类型:", image.dtype)  # uint8
易错点 cv2.imread() 读取失败时返回 None 而不是报错!养成读取后检查 image is not None 的好习惯,可以避免后续操作出现 'NoneType' object has no attribute 'shape' 等令人困惑的错误。
选拔赛 第3题 BGR 转 RGB 并显示
将读取的 BGR 格式图像 image 转换为 RGB 格式,并用 matplotlib 展示原始图像。

思路分析

这道题考查两个知识点:(1)BGR到RGB的颜色空间转换;(2)使用matplotlib显示图像。

如前所述,OpenCV读取的图像是BGR格式,而matplotlib显示图像时默认使用RGB格式。如果不转换,图像中的红色和蓝色会颠倒。转换使用 cv2.cvtColor() 函数,指定转换码为 cv2.COLOR_BGR2RGB

matplotlib 显示图像使用 plt.imshow() 函数,配合 plt.show() 显示。

代码实现

import matplotlib.pyplot as plt

# 将 BGR 转换为 RGB
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

# 使用 matplotlib 展示图像
plt.imshow(image_rgb)
plt.title('Original Image')
plt.axis('off')  # 隐藏坐标轴
plt.show()

代码详解

  • import matplotlib.pyplot as plt:导入matplotlib的绘图模块,约定俗成缩写为 plt
  • cv2.cvtColor(image, cv2.COLOR_BGR2RGB):颜色空间转换函数。第一个参数是输入图像,第二个参数指定转换类型。COLOR_BGR2RGB 表示从BGR转到RGB
  • plt.imshow(image_rgb):将图像数据显示在绘图区域中
  • plt.title('Original Image'):设置图像标题
  • plt.axis('off'):隐藏坐标轴刻度,让图像显示更干净
  • plt.show():渲染并显示图像。在Jupyter Notebook中,这行代码会触发图像的内联显示

知识链接:cv2.cvtColor() 常用转换码

转换码功能
cv2.COLOR_BGR2RGBBGR → RGB(显示用)
cv2.COLOR_RGB2BGRRGB → BGR
cv2.COLOR_BGR2GRAYBGR → 灰度
cv2.COLOR_GRAY2BGR灰度 → BGR
cv2.COLOR_BGR2HSVBGR → HSV
选拔赛第4题 / 总决赛第1题 彩色图像转灰度图像
将彩色图像 image 转换为灰度图像 gray_image。

思路分析

灰度化是将三通道的彩色图像转换为单通道的灰度图像。使用 cv2.cvtColor() 函数,转换码为 cv2.COLOR_BGR2GRAY。转换后图像从三维数组(H, W, 3)变为二维数组(H, W)。

代码实现

gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

代码详解

  • cv2.cvtColor(image, cv2.COLOR_BGR2GRAY):将BGR彩色图像转换为灰度图像
  • OpenCV内部使用公式 Gray = 0.299R + 0.587G + 0.114B 进行转换
  • 返回的 gray_image 是一个二维NumPy数组,形状为 (高度, 宽度),没有通道维度
  • 赋值给变量 gray_image,后续的滤波、边缘检测等操作将基于灰度图像进行

验证

print("彩色图像形状:", image.shape)      # 例如: (400, 400, 3)
print("灰度图像形状:", gray_image.shape)  # 例如: (400, 400)

# 显示灰度图像
plt.imshow(gray_image, cmap='gray')
plt.title('Gray Image')
plt.axis('off')
plt.show()
技巧 用 matplotlib 显示灰度图像时,必须指定 cmap='gray'(颜色映射为灰度),否则matplotlib会默认使用伪彩色(偏绿偏黄),导致灰度图像看起来颜色奇怪。
选拔赛第5题 / 总决赛第2题 高斯模糊(高斯滤波)
使用高斯模糊对灰度图像 gray_image 进行平滑处理,生成模糊后的图像 blurred。高斯核大小设置为 5×5,自动计算高斯核在 X 方向的标准差。

思路分析

高斯模糊使用 cv2.GaussianBlur() 函数。需要指定三个关键参数:输入图像、高斯核大小(ksize)、X方向标准差(sigmaX)。

题目要求"高斯核大小设置为5×5",即 ksize=(5, 5)。"自动计算X方向标准差"意味着将 sigmaX 设为 0,OpenCV会根据核大小自动计算合适的标准差。

代码实现

blurred = cv2.GaussianBlur(gray_image, (5, 5), 0)

代码详解

  • cv2.GaussianBlur(src, ksize, sigmaX):高斯模糊函数
  • 第一个参数 gray_image:输入图像(灰度图像)
  • 第二个参数 (5, 5):高斯核大小,必须为正奇数。核越大,模糊效果越明显。5×5表示在5×5的窗口内进行高斯加权平均
  • 第三个参数 0:X方向标准差 sigmaX。设为0时,OpenCV自动根据核大小计算标准差(公式:σ = 0.3×((ksize-1)×0.5 - 1) + 0.8)
  • 返回值赋给 blurred,即模糊后的图像

知识链接:为什么核大小必须是正奇数?

高斯核是一个方阵,需要一个中心点。只有奇数大小的方阵才有唯一的中心点(如3×3的中心是(1,1),5×5的中心是(2,2))。偶数大小的核没有唯一中心,因此OpenCV要求核大小必须为正奇数(1, 3, 5, 7, ...)。

验证

# 对比显示原始灰度图和高斯模糊后的图像
fig, axes = plt.subplots(1, 2, figsize=(12, 5))

axes[0].imshow(gray_image, cmap='gray')
axes[0].set_title('Original Gray')
axes[0].axis('off')

axes[1].imshow(blurred, cmap='gray')
axes[1].set_title('Gaussian Blurred (5x5)')
axes[1].axis('off')

plt.show()
总决赛 第3题 Canny 边缘检测
使用 Canny 算法提取图像 blurred 边缘,并赋值给变量 edges。低阈值设置为 100,高阈值设置为 200。

思路分析

Canny边缘检测使用 cv2.Canny() 函数,需要指定输入图像、低阈值和高阈值。题目明确给出了阈值参数值:低阈值100,高阈值200。

注意:题目要求对模糊后的图像 blurred 进行边缘检测,而不是原始灰度图像。先滤波再检测边缘是标准流程——滤波可以减少噪声对边缘检测的干扰。

代码实现

edges = cv2.Canny(blurred, 100, 200)

代码详解

  • cv2.Canny(image, threshold1, threshold2):Canny边缘检测函数
  • 第一个参数 blurred:输入图像(高斯模糊后的灰度图像)
  • 第二个参数 100:低阈值(threshold1)。梯度值低于此值的像素被丢弃
  • 第三个参数 200:高阈值(threshold2)。梯度值高于此值的像素被标记为强边缘
  • 梯度值在100~200之间的像素,只有与强边缘相连时才被保留
  • 返回的 edges 是一个二值图像:边缘像素为255(白色),非边缘像素为0(黑色)

知识链接:阈值选择经验

高阈值与低阈值的比例通常推荐为 2:1 或 3:1。本题中 200:100 = 2:1,符合推荐比例。如果边缘检测效果不理想,可以尝试调整阈值——阈值越高,检测到的边缘越少但噪声也越少;阈值越低,检测到的边缘越多但可能包含噪声。

总决赛 第4题 固定阈值二值化
采用固定阈值的方法将图像 gray_image 转换为二值图像(黑白图像),返回可忽略的固定阈值和二值化图像 thresh。阈值大小设置为 127,最大值设置为 255。

思路分析

二值化使用 cv2.threshold() 函数。该函数返回两个值:第一个是使用的阈值(与输入阈值相同),第二个是二值化后的图像。题目要求将二值化图像赋值给 thresh,阈值可以忽略(用 _ 接收)。

参数:阈值=127,最大值=255,二值化类型使用 cv2.THRESH_BINARY(标准二值化:大于阈值设为最大值,小于阈值设为0)。

代码实现

_, thresh = cv2.threshold(gray_image, 127, 255, cv2.THRESH_BINARY)

代码详解

  • cv2.threshold(src, thresh, maxval, type):阈值分割函数
  • 第一个参数 gray_image:输入图像(灰度图像)
  • 第二个参数 127:阈值。像素值大于127的设为最大值,小于等于127的设为0
  • 第三个参数 255:最大值。满足阈值条件时赋予的值
  • 第四个参数 cv2.THRESH_BINARY:二值化类型。表示标准二值化
  • 返回值:函数返回一个元组 (ret, dst)ret 是实际使用的阈值(本题中就是127),dst 是二值化后的图像。使用 _ 接收第一个返回值表示忽略它,thresh 接收二值化图像

知识链接:cv2.threshold 的二值化类型

类型说明
cv2.THRESH_BINARY大于阈值→maxval,小于阈值→0
cv2.THRESH_BINARY_INV大于阈值→0,小于阈值→maxval(反转)
cv2.THRESH_TRUNC大于阈值→阈值,小于阈值→不变
cv2.THRESH_TOZERO大于阈值→不变,小于阈值→0
cv2.THRESH_OTSU大津法自动阈值(需与BINARY组合使用)
总决赛 第5题 保存处理后的图像
将处理后的图像分别命名为 gray_image.jpg,blurred.jpg,edges.jpg,thresh.jpg 保存在 data 文件夹中。

思路分析

保存图像使用 cv2.imwrite() 函数。该函数接收两个参数:保存路径(含文件名)和图像数据。需要分别保存四张图像。

代码实现

cv2.imwrite('data/gray_image.jpg', gray_image)
cv2.imwrite('data/blurred.jpg', blurred)
cv2.imwrite('data/edges.jpg', edges)
cv2.imwrite('data/thresh.jpg', thresh)

代码详解

  • cv2.imwrite(path, image):将图像保存到指定路径
  • 第一个参数是保存路径(字符串),包含文件夹路径和文件名
  • 第二个参数是要保存的图像数据(NumPy数组)
  • 函数返回 True 表示保存成功,False 表示失败
  • 保存的图像格式由文件扩展名决定:.jpg 为JPEG格式,.png 为PNG格式
注意 cv2.imwrite() 保存灰度图像时直接保存即可(单通道)。如果保存彩色图像,OpenCV默认使用BGR格式保存。如果图像之前做过RGB转换,保存前可能需要转回BGR格式,否则保存的图片颜色会不正确。

1.5 项目总结与拓展

项目总结

通过本项目,我们完成了一个完整的图像处理流程:读取图像→颜色空间转换→灰度化→高斯滤波→边缘检测→阈值分割→保存结果。这是计算机视觉中最基础也是最重要的处理链路,几乎所有的视觉AI项目都会包含其中某些步骤。

本项目学到的核心API

函数功能
cv2.imread()读取图像
cv2.cvtColor()颜色空间转换
cv2.GaussianBlur()高斯模糊
cv2.Canny()Canny边缘检测
cv2.threshold()阈值分割
cv2.imwrite()保存图像
plt.imshow()显示图像

拓展练习

  1. 尝试使用 cv2.medianBlur() 中值滤波处理图像,对比与高斯滤波的效果差异
  2. 尝试使用不同的Canny阈值(如50,150 和 30,100),观察边缘检测结果的差异
  3. 尝试使用 cv2.THRESH_OTSU(大津法)自动计算最优阈值进行二值化,对比与固定阈值127的效果
  4. 将灰度图像转换为HSV颜色空间,分离出H(色调)、S(饱和度)、V(明度)三个通道并分别显示
模块 B

项目二:智能制造与工业质检

基于 PyTorch 和 MobileNetV2 在 NEU-DET 钢材表面缺陷数据集上实现六分类缺陷检测

2.1 项目导读与学习目标

项目背景

在钢铁制造行业中,钢材表面可能出现各种缺陷(裂纹、夹杂、斑块、麻点、轧入氧化铁、划痕等),这些缺陷会影响钢材的力学性能和使用寿命。传统的人工目视检测效率低、主观性强、容易漏检。利用深度学习技术实现自动化的表面缺陷检测,可以大幅提升检测效率和准确率。

本项目使用东北大学(NEU)发布的钢材表面缺陷数据集 NEU-DET,包含6类缺陷共1800张图像。我们采用迁移学习策略——使用在ImageNet上预训练的 MobileNetV2 作为特征提取器,在其基础上训练一个六分类器来识别缺陷类型。

学习目标

  • 理解卷积神经网络(CNN)和迁移学习的基本原理
  • 掌握使用 torchvision 进行数据加载与预处理的方法
  • 能够加载预训练模型并修改网络结构以适应新任务
  • 掌握模型参数冻结与解冻的策略
  • 能够定义损失函数、优化器和学习率调度器
  • 掌握模型的保存与加载

2.2 知识准备

2.2.1 深度学习与神经网络基础

神经网络是一种模仿人脑神经元连接方式的数学模型。最基本的单元是"神经元"(也叫节点),它接收输入,进行加权求和,再通过一个激活函数输出结果。多个神经元按层组织,就构成了神经网络。

卷积神经网络(CNN)是专门用于处理图像数据的神经网络。它的核心是"卷积层"——用一个小的卷积核(如3×3)在图像上滑动,提取局部特征(如边缘、纹理、形状)。低层卷积提取简单特征(如边缘),高层卷积提取复杂特征(如物体形状)。

CNN 的关键组件

  • 卷积层(Conv2d):提取图像特征。通过卷积核在图像上滑动,计算局部特征的加权和
  • 池化层(Pool):降维。最大池化取局部区域最大值,平均池化取平均值
  • 全连接层(Linear):将特征映射到分类结果。通常是网络的最后一层
  • 激活函数(ReLU):引入非线性。ReLU(x) = max(0, x),是最常用的激活函数
  • Dropout:随机丢弃部分神经元,防止过拟合

2.2.2 迁移学习(Transfer Learning)

迁移学习是深度学习中最实用的技术之一。核心思想是:将在大规模数据集上训练好的模型,迁移到自己的小数据集任务上

想象你学会了骑自行车(源任务),再学骑摩托车(目标任务)就会容易很多——因为平衡、转向等技能可以迁移过来。同样,在ImageNet(120万张图片,1000类)上训练好的模型,已经学会了识别边缘、纹理、形状等通用视觉特征,这些特征对于钢材缺陷检测同样有用。

迁移学习的两种策略

  1. 特征提取(Feature Extraction):冻结预训练模型的卷积层(不更新权重),只训练新添加的分类层。适用于数据量小、与源任务相似的场景
  2. 微调(Fine-Tuning):解冻部分卷积层,与分类层一起训练。适用于数据量较大、与源任务有一定差异的场景

本项目采用两阶段策略:先冻结卷积基训练分类头(特征提取),再解冻部分层进行微调。

2.2.3 MobileNetV2 架构

MobileNetV2 是Google提出的轻量级CNN架构,专为移动端和嵌入式设备设计。它的核心创新是倒残差结构(Inverted Residual)线性瓶颈(Linear Bottleneck),在保持较高准确率的同时大幅减少了参数量和计算量。

MobileNetV2 的最后一层卷积输出1280维特征向量,我们将其替换为自定义的分类头来适配6类缺陷检测任务。

2.2.4 NEU-DET 数据集

NEU-DET 数据集由东北大学发布,包含6类钢材表面缺陷,每类300张图像,共1800张。6类缺陷分别为:

英文名中文名说明
crazing裂纹表面细小的龟裂状裂纹
inclusion夹杂非金属杂质压入钢材表面
patches斑块表面色斑或氧化色斑
pitted麻点表面凹坑
rolled轧入氧化铁氧化铁皮压入钢材表面
scratches划痕表面划伤

2.2.5 PyTorch 核心 API

模块/类功能
torchvision.datasets.ImageFolder从文件夹自动加载分类数据集
torchvision.transforms数据预处理(缩放、归一化等)
torchvision.models预训练模型(MobileNetV2, ResNet等)
torch.utils.data.DataLoader批量数据加载器
torch.nn神经网络层(Linear, ReLU, Dropout等)
torch.optim优化器(Adam, SGD等)
torch.nn.CrossEntropyLoss交叉熵损失函数(分类任务标配)

2.3 环境与数据准备

确保已安装 PyTorch 和 torchvision。需要准备 NEU-DET 数据集,目录结构如下:

data/NEU-DET/
├── train/
│   └── images/
│       ├── crazing/          # 裂纹类
│       │   ├── 1.jpg
│       │   ├── 2.jpg
│       │   └── ...
│       ├── inclusion/        # 夹杂类
│       ├── patches/          # 斑块类
│       ├── pitted/           # 麻点类
│       ├── rolled/           # 轧入类
│       └── scratches/        # 划痕类
└── val/
    └── images/
        ├── crazing/
        ├── inclusion/
        └── ...
ImageFolder 的要求 torchvision.datasets.ImageFolder 要求数据按文件夹组织——每个子文件夹名就是类别名,文件夹内的图片属于该类别。这种组织方式使得数据加载非常方便。

2.4 项目实施

选拔赛 第1题 导入 torchvision 模块
从 torchvision 库中导入 datasets、transforms、models 模块。

思路分析

这是最基础的导入操作。torchvision 是 PyTorch 的视觉工具库,包含三个核心子模块:datasets(数据集加载)、transforms(数据变换)、models(预训练模型)。

代码实现

from torchvision import datasets, transforms, models

代码详解

  • datasets:提供常用数据集的下载和加载功能,以及 ImageFolder 用于加载自定义文件夹数据
  • transforms:提供图像预处理功能,如缩放、裁剪、归一化、转张量等
  • models:提供预训练模型,如 MobileNetV2, ResNet, VGG 等
选拔赛 第2题 定义数据预处理流程
使用 torchvision.transforms 定义数据预处理流程 transform,要求:将图片缩放至(200, 200),转为张量,并使用 ImageNet 均值[0.485, 0.456, 0.406]和标准差[0.229, 0.224, 0.225]进行归一化。

思路分析

数据预处理是深度学习中非常重要的一步。transforms.Compose() 可以将多个预处理操作串联成一个流程。题目要求三个操作:(1)缩放到200×200;(2)转为张量;(3)用ImageNet统计量归一化。

归一化的目的是将像素值从0~255的范围缩放到以0为中心的范围内,这有助于模型训练时梯度下降的稳定性。使用ImageNet的均值和标准差是因为我们使用的是在ImageNet上预训练的模型。

代码实现

transform = transforms.Compose([
    transforms.Resize((200, 200)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406],
                         std=[0.229, 0.224, 0.225])
])

代码详解

  • transforms.Compose([...]):将列表中的变换操作按顺序组合成一个流水线。图像会依次通过每个变换
  • transforms.Resize((200, 200)):将图像缩放到200×200像素。无论原图是什么尺寸,输出统一为200×200
  • transforms.ToTensor():将PIL图像或NumPy数组转为PyTorch张量。同时会将像素值从0~255缩放到0.0~1.0(除以255),并将通道维度从(H,W,C)调整为(C,H,W)
  • transforms.Normalize(mean, std):对张量进行归一化。公式:(x - mean) / std。使用ImageNet的统计量是因为预训练模型就是在这些统计量上训练的

知识链接:为什么是这三个数?

[0.485, 0.456, 0.406] 是ImageNet数据集所有图片在R、G、B三个通道上的平均值(已归一化到0~1),[0.229, 0.224, 0.225] 是对应的标准差。这些数值已经成为使用ImageNet预训练模型时的标准归一化参数,几乎所有教程和代码都使用这组数值。

选拔赛 第3题 加载训练数据并创建 DataLoader
使用 torchvision.datasets.ImageFolder 加载 'data/NEU-DET/train/images' 目录下的训练数据,应用上述 transform,并用 DataLoader 创建批量大小为 16、打乱顺序的训练加载器 train_loader。

思路分析

这道题包含两步操作:(1)用 ImageFolder 加载数据集;(2)用 DataLoader 创建批量加载器。

ImageFolder 会自动扫描目录下的子文件夹,每个子文件夹名作为一个类别。 DataLoader 负责将数据集分成小批量(batch),在训练时逐批提供数据。

代码实现

from torch.utils.data import DataLoader

train_dataset = datasets.ImageFolder('data/NEU-DET/train/images', transform=transform)

train_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)

代码详解

  • datasets.ImageFolder('data/NEU-DET/train/images', transform=transform):从指定目录加载图像数据。transform 参数指定预处理流程,加载时自动应用
  • DataLoader(train_dataset, batch_size=16, shuffle=True):创建数据加载器
  • batch_size=16:每批16张图片。训练时模型每次处理16张图片而不是全部数据,这既节省内存又能让梯度下降更稳定
  • shuffle=True:每个epoch开始时打乱数据顺序。这很重要——如果不打乱,模型可能记住数据的排列顺序而不是学习特征
验证数据加载
# 查看数据集信息
print("训练集大小:", len(train_dataset))
print("类别名称:", train_dataset.classes)
print("类别映射:", train_dataset.class_to_idx)

# 获取一个批次的数据
images, labels = next(iter(train_loader))
print("一个批次的图像形状:", images.shape)   # torch.Size([16, 3, 200, 200])
print("一个批次的标签:", labels)
选拔赛 第4题 加载预训练 MobileNetV2 并修改第一层
从 torchvision.models 中加载预训练的 MobileNetV2 模型作为基础网络,并修改其第一层卷积,将输入通道从 3 改为 32,卷积核大小为 3,步长为 2,填充为 1。

思路分析

使用 models.mobilenet_v2(pretrained=True) 加载预训练模型。然后修改模型的第一层卷积层 features[0][0],用新的 nn.Conv2d 替换它。新的卷积层参数:输入通道32(原为3)、输出通道32、卷积核3×3、步长2、填充1。

代码实现

import torch.nn as nn

base_model = models.mobilenet_v2(pretrained=True)

# 修改第一层卷积
base_model.features[0][0] = nn.Conv2d(
    3, 32, kernel_size=3, stride=2, padding=1, bias=False
)

代码详解

  • models.mobilenet_v2(pretrained=True):加载在ImageNet上预训练的MobileNetV2。pretrained=True 表示加载预训练权重
  • base_model.features[0][0]:访问模型的第一个特征提取模块中的第一个卷积层。MobileNetV2 的 features 是一个 nn.Sequentialfeatures[0] 是第一个块(也是一个Sequential),features[0][0] 是其中的卷积层
  • nn.Conv2d(3, 32, kernel_size=3, stride=2, padding=1, bias=False):创建新的卷积层
  • 3:输入通道数。虽然题目说"将输入通道从3改为32",但实际上输入通道仍然是3(RGB图像),输出通道改为32。这里理解题目意图:修改卷积层的参数配置
  • 32:输出通道数(卷积核数量)
  • kernel_size=3:卷积核大小3×3
  • stride=2:步长2,卷积核每次移动2个像素,使输出尺寸减半
  • padding=1:填充1,在图像边缘补一圈0,保证卷积后尺寸计算正确
  • bias=False:不使用偏置。MobileNetV2的卷积层后接BN层,BN层已包含偏置功能,因此卷积层不需要额外的偏置
版本提示 在较新版本的 torchvision 中,pretrained=True 参数已被弃用,替换为 weights=models.MobileNet_V2_Weights.DEFAULT。但竞赛环境中通常使用旧版本API。如果报错,可以改为 weights='DEFAULT'pretrained=True 中的可用方式。
选拔赛 第5题 冻结卷积基参数
遍历 base_model 的所有参数,将 requires_grad 属性设为 False,以冻结卷积基的权重。

思路分析

迁移学习的第一阶段是"特征提取"——冻结卷积基的所有参数,只训练新添加的分类头。PyTorch 中通过设置 requires_grad=False 来冻结参数,使优化器在反向传播时不会更新这些参数。

代码实现

for param in base_model.parameters():
    param.requires_grad = False

代码详解

  • base_model.parameters():返回模型所有参数的迭代器
  • param.requires_grad = False:将每个参数的 requires_grad 属性设为 False,表示该参数不需要计算梯度,因此在反向传播时不会被更新
  • 这样做的好处:(1)减少计算量,训练更快;(2)保护预训练的特征提取能力不被破坏;(3)减少需要训练的参数量,降低过拟合风险

知识链接:requires_grad 机制

PyTorch 使用自动微分机制(autograd)计算梯度。每个张量都有一个 requires_grad 属性——设为 True 时,PyTorch 会跟踪对该张量的所有操作,以便在反向传播时计算梯度。设为 False 则不跟踪,既节省内存又加速计算。在迁移学习中,我们冻结预训练层的 requires_grad,只让新添加的层参与梯度计算。

总决赛 第1题 构建完整模型
使用 nn.Sequential 构建完整模型,结构为:base_model → AdaptiveAvgPool2d((1,1)) → Flatten → Linear(1280, 256) → ReLU → Dropout(0.5) → Linear(256, 6)。

思路分析

这是一个完整的模型构建任务。使用 nn.Sequential 将各层按顺序串联。模型结构从预训练的base_model开始,经过自适应平均池化、展平、两个全连接层(中间有ReLU激活和Dropout),最终输出6个类别的分数。

代码实现

model = nn.Sequential(
    base_model,
    nn.AdaptiveAvgPool2d((1, 1)),
    nn.Flatten(),
    nn.Linear(1280, 256),
    nn.ReLU(),
    nn.Dropout(0.5),
    nn.Linear(256, 6)
)

代码详解

  • nn.Sequential(...):按顺序组合各层,数据从第一层开始依次通过每一层
  • base_model:预训练的MobileNetV2特征提取器。输出形状为 (batch, 1280, H, W)
  • nn.AdaptiveAvgPool2d((1, 1)):自适应平均池化,将空间维度压缩到1×1。无论输入H×W多大,输出都是1×1。输出形状变为 (batch, 1280, 1, 1)
  • nn.Flatten():展平多维张量为一维。(batch, 1280, 1, 1) → (batch, 1280)
  • nn.Linear(1280, 256):全连接层,将1280维特征映射到256维。这是第一个分类层的权重矩阵
  • nn.ReLU():ReLU激活函数,引入非线性。ReLU(x) = max(0, x)
  • nn.Dropout(0.5):训练时随机丢弃50%的神经元,防止过拟合。测试时自动关闭
  • nn.Linear(256, 6):最后一个全连接层,将256维特征映射到6维(对应6类缺陷)。输出的是每类的原始分数

知识链接:为什么是1280?

MobileNetV2 的最后一层卷积输出通道数为1280,这是该架构的设计参数。经过自适应平均池化后,1280个通道各取一个平均值,形成1280维特征向量,作为分类器的输入。

总决赛 第2题 定义损失函数、优化器和学习率调度器
定义交叉熵损失函数 criterion,Adam 优化器(学习率 0.001)optimizer,以及指数衰减学习率调度器(衰减系数 0.9)scheduler。

思路分析

训练深度学习模型需要三个核心组件:损失函数(衡量预测与真实值的差距)、优化器(根据梯度更新参数)、学习率调度器(动态调整学习率)。

代码实现

import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.9)

代码详解

  • nn.CrossEntropyLoss():交叉熵损失函数。这是多分类任务的标准损失函数。它内部集成了Softmax操作,因此模型的输出不需要提前经过Softmax
  • optim.Adam(model.parameters(), lr=0.001):Adam优化器。
  • model.parameters():传入需要优化的参数(只有requires_grad=True的参数会被优化)
  • lr=0.001:学习率,控制每次参数更新的步长。0.001是Adam的常用默认值
  • optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.9):指数衰减学习率调度器。每个epoch后学习率乘以gamma(0.9),即学习率按指数逐渐减小

知识链接:学习率调度

训练初期使用较大的学习率可以快速接近最优解,但随着训练进行,大学习率可能导致在最优解附近震荡。学习率调度器在训练过程中逐步减小学习率,使模型在后期更精细地调整参数。指数衰减是最简单的调度策略:lr_new = lr_old × gamma。

总决赛 第3题 解冻并冻结前50层
解冻 base_model 的所有参数,然后冻结前 50 层的参数。

思路分析

这是迁移学习第二阶段——微调(Fine-Tuning)的准备工作。先解冻所有参数(requires_grad=True),然后只冻结前50层(保留低层通用特征),让高层特征参与微调以适应钢材缺陷检测任务。

代码实现

# 第一步:解冻所有参数
for param in base_model.parameters():
    param.requires_grad = True

# 第二步:冻结前50层
for i, param in enumerate(base_model.parameters()):
    if i < 50:
        param.requires_grad = False

代码详解

  • 第一步遍历所有参数并设为 True,恢复梯度计算
  • 第二步使用 enumerate 获取参数的序号和值。前50个参数(序号0~49)设为 False(冻结),其余保持 True(可训练)
  • 这样做的效果:MobileNetV2的低层(提取边缘、纹理等通用特征)保持冻结,高层(提取更抽象的特征)参与微调,适应钢材缺陷检测任务
为什么冻结前面的层? CNN的低层提取的是通用视觉特征(边缘、角点、纹理),这些特征对任何视觉任务都有用,不需要修改。高层提取的是与具体任务相关的特征(如ImageNet中的猫狗特征),需要微调以适应钢材缺陷检测。这种策略既保留了通用特征提取能力,又能适应新任务。
总决赛 第4题 创建微调优化器
创建学习率为 0.0001 的新优化器 optimizer_fine,只优化需要训练的参数。

思路分析

微调阶段使用更小的学习率(0.0001 vs 第一阶段的0.001),因为此时是在已有预训练权重的基础上做精细调整,大学习率可能破坏已学到的特征。需要只优化 requires_grad=True 的参数。

代码实现

optimizer_fine = optim.Adam(
    filter(lambda p: p.requires_grad, model.parameters()),
    lr=0.0001
)

代码详解

  • filter(lambda p: p.requires_grad, model.parameters()):过滤出所有 requires_grad=True 的参数。filter 函数根据条件筛选可迭代对象中的元素
  • lambda p: p.requires_grad:匿名函数,判断参数是否需要梯度。返回 True 的参数被保留
  • optim.Adam(..., lr=0.0001):创建新的Adam优化器,学习率为0.0001(比第一阶段小10倍)
  • 只优化需要训练的参数,可以减少计算量并避免意外修改冻结的参数
总决赛 第5题 保存模型参数
请将训练好的模型参数保存到文件 'neu_model.pth' 中。

思路分析

训练完成后需要保存模型参数,以便后续加载使用(如推理预测)。PyTorch 使用 torch.save() 保存模型,推荐只保存参数字典(state_dict)而不是整个模型对象。

代码实现

torch.save(model.state_dict(), 'neu_model.pth')

代码详解

  • model.state_dict():返回模型所有参数的字典。键是层名,值是对应的参数张量
  • torch.save(obj, path):将对象保存到文件。.pth.pt 是PyTorch模型文件的常用扩展名
  • 保存的是参数字典而非整个模型,这样更灵活——加载时需要先创建相同结构的模型,再加载参数

知识链接:加载模型参数

# 加载模型参数的完整流程
model = nn.Sequential(...)  # 先创建相同结构的模型
model.load_state_dict(torch.load('neu_model.pth'))
model.eval()  # 切换到评估模式(关闭Dropout等)

2.5 项目总结与拓展

项目总结

本项目完成了一个完整的迁移学习工作流:数据加载→预处理→加载预训练模型→修改网络结构→冻结参数→定义损失函数和优化器→训练→解冻微调→保存模型。这是深度学习图像分类的标准流程,掌握了这个流程,你就能应对大多数图像分类任务。

两阶段训练策略总结

阶段策略学习率目的
第一阶段冻结卷积基,只训练分类头0.001快速训练分类头,适应新任务
第二阶段解冻高层卷积,联合微调0.0001精细调整特征提取,提升准确率

拓展练习

  1. 尝试使用 models.resnet50(pretrained=True) 替换 MobileNetV2,对比模型大小和准确率
  2. 在训练循环中添加验证步骤,每个epoch结束后在验证集上评估模型,保存验证准确率最高的模型
  3. 使用 matplotlib 绘制训练过程中的损失曲线和准确率曲线
  4. 实现单张图片的推理预测功能:加载保存的模型,对一张新的钢材图片进行缺陷分类
模块 C

项目三:智能客服与舆情分析

基于预训练 TinyBERT 在中文电商评论数据集上实现情感二分类分析

3.1 项目导读与学习目标

项目背景

随着电商的蓬勃发展,每天产生海量用户评论。人工分析这些评论既费时又费力。利用自然语言处理(NLP)技术自动判断评论的情感倾向(正面/负面),可以帮助商家快速了解用户反馈,优化产品和服务。

本项目聚焦于利用预训练语言模型 TinyBERT,在中文电商评论情感分析数据集上开展迁移学习。TinyBERT 是 BERT 的轻量级压缩版本,参数量小、推理速度快,同时保留了较强的语言理解能力,非常适合教学和实际应用。

学习目标

  • 理解自然语言处理(NLP)和预训练语言模型的基本概念
  • 掌握 BERT/TinyBERT 的 Tokenizer 和模型使用方法
  • 能够构建自定义数据集类并使用 DataLoader 加载
  • 掌握 AdamW 优化器和 Warmup 学习率调度的配置
  • 理解梯度裁剪、模型保存与加载等训练技巧

3.2 知识准备

3.2.1 自然语言处理(NLP)基础

自然语言处理是让计算机理解和处理人类语言的技术。在深度学习时代,NLP的核心范式是:将文本转换为数值向量→用神经网络处理向量→输出结果

NLP 处理流程

  1. 分词:将句子切分成一个个词或子词
  2. 编码:将每个词映射为一个整数ID
  3. 嵌入:将ID转换为稠密向量
  4. 建模:用神经网络处理向量序列
  5. 输出:根据任务输出分类、生成等结果

3.2.2 BERT 与 TinyBERT

BERT(Bidirectional Encoder Representations from Transformers)是Google于2018年提出的预训练语言模型,它通过"掩码语言模型"(MLM)和"下一句预测"(NSP)两个任务在大规模文本上进行预训练,学习到了丰富的语言表征。

TinyBERT 是 BERT 的知识蒸馏版本,通过将大BERT的知识"蒸馏"到小模型中,在参数量减少约7.5倍的情况下仍能保持大部分性能。本项目使用的中文 TinyBERT 模型参数量约 14.5M,远小于标准 BERT 的 110M。

BERT 的输入格式

BERT 的输入由三部分组成:

  • input_ids:词的整数ID序列。[CLS]标记在开头(ID=101),[SEP]标记在结尾(ID=102)
  • attention_mask:注意力掩码。实际词为1,填充部分为0
  • token_type_ids:句子类型ID(单句任务全为0)

例如:"很好用" → tokens: [CLS] 很 好 用 [SEP] → input_ids: [101, 2523, 1962, 4680, 102]

3.2.3 情感分析

情感分析是NLP中最常见的任务之一,目标是判断文本的情感倾向。本项目是二分类情感分析——判断评论是正面(1)还是负面(0)。使用 BERT 的序列分类头(Sequence Classification Head),在[CLS]标记的输出上接一个线性层进行分类。

3.2.4 HuggingFace Transformers 库

HuggingFace Transformers 是最流行的NLP库,提供了大量预训练模型的统一接口。本项目使用的核心组件:

组件功能
BertTokenizer分词器,将文本转为BERT输入格式
BertForSequenceClassificationBERT序列分类模型(内置分类头)
from_pretrained()从预训练模型加载权重

3.2.5 AdamW 优化器与 Warmup

AdamW 是 Adam 优化器的改进版本,修正了权重衰减(weight decay)的实现方式,在Transformer模型训练中表现更好。

Warmup(预热)是一种学习率调度策略:训练初期学习率从0线性增长到预设值,然后再按一定策略衰减。这可以防止训练初期因随机初始化的参数导致梯度不稳定。

3.3 环境与数据准备

确保已安装 transformerstorch。需要准备中文电商评论数据集(CSV格式,包含text和label两列)。同时需要定义以下全局变量:

# 全局配置(竞赛环境中通常已预定义)
MODEL_NAME = 'huawei-noah/TinyBERT_General_4L_312D'  # 预训练模型名称
MAX_LEN = 128        # 最大序列长度
BATCH_SIZE = 32     # 批次大小
EPOCHS = 3          # 训练轮数
WARMUP_RATIO = 0.1  # 预热比例
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

3.4 项目实施

选拔赛 第1题 判断 CUDA 并设置设备
使用 torch.cuda.is_available() 判断 CUDA 是否可用,利用三元表达式(条件为真返回"cuda",否则返回"cpu"),结合 torch.device() 为变量 DEVICE 赋值。

思路分析

这是深度学习项目的标准开头——检测GPU是否可用,并选择计算设备。有GPU时使用GPU(训练快很多),没有时使用CPU。使用三元表达式在一行代码中完成条件判断和赋值。

代码实现

import torch

DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

代码详解

  • torch.cuda.is_available():检测系统是否有可用的NVIDIA GPU及CUDA环境。返回 TrueFalse
  • 'cuda' if torch.cuda.is_available() else 'cpu':Python 三元表达式。条件为真返回 'cuda',否则返回 'cpu'
  • torch.device(...):创建设备对象。后续可以将模型和数据通过 .to(DEVICE) 移动到指定设备上
  • 将设备对象赋值给 DEVICE,在整个项目中统一使用
为什么要统一设备? 模型和数据必须在同一个设备上才能计算。如果模型在GPU上而数据在CPU上,会报错。通过统一 DEVICE 变量,确保所有操作在同一设备上执行。
选拔赛 第2题 加载 BertTokenizer
使用 BertTokenizer 类调用 from_pretrained 方法,传入变量 MODEL_NAME,将结果赋值给变量 tokenizer。

思路分析

Tokenizer(分词器)负责将文本转换为模型能理解的数字ID。from_pretrained 方法会自动从HuggingFace Hub下载指定模型的分词器配置和词表。

代码实现

from transformers import BertTokenizer

tokenizer = BertTokenizer.from_pretrained(MODEL_NAME)

代码详解

  • BertTokenizer:BERT的分词器类,专门处理BERT格式的分词
  • from_pretrained(MODEL_NAME):从预训练模型加载分词器。MODEL_NAME 是模型名称字符串(如 'huawei-noah/TinyBERT_General_4L_312D')。首次使用时会自动下载,之后从本地缓存加载
  • 赋值给 tokenizer,后续用它将中文评论文本转换为BERT输入格式

知识链接:Tokenizer 的工作过程

# 分词器使用示例
text = "这个商品很好用"
encoding = tokenizer(text, max_length=128, padding='max_length', truncation=True, return_tensors='pt')

print("input_ids:", encoding['input_ids'])
# tensor([[101, 6821, 7028, 765, 3766, 1962, 4680, 102, 0, 0, ...]])
# 101=[CLS], 102=[SEP], 0=[PAD]

print("attention_mask:", encoding['attention_mask'])
# tensor([[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, ...]])
# 1=有效词, 0=填充
选拔赛 第3题 实例化数据集
实例化 SentimentDataset 类,依次传入变量 val_texts、val_labels、tokenizer 和 MAX_LEN 作为参数,将结果赋值给变量 val_dataset。

思路分析

这是一个简单的对象实例化操作。SentimentDataset 是竞赛环境中预定义的自定义数据集类(继承自 torch.utils.data.Dataset),接收文本列表、标签列表、分词器和最大长度作为参数。

代码实现

val_dataset = SentimentDataset(val_texts, val_labels, tokenizer, MAX_LEN)

代码详解

  • SentimentDataset(...):创建验证数据集实例
  • val_texts:验证集文本列表,如 ['很好用', '质量差', ...]
  • val_labels:验证集标签列表,如 [1, 0, ...](1=正面,0=负面)
  • tokenizer:上一步加载的BertTokenizer,用于在数据集内部将文本转为ID序列
  • MAX_LEN:最大序列长度,超长截断,不足填充

知识链接:自定义 Dataset 类的结构

class SentimentDataset(torch.utils.data.Dataset):
    def __init__(self, texts, labels, tokenizer, max_len):
        self.texts = texts
        self.labels = labels
        self.tokenizer = tokenizer
        self.max_len = max_len

    def __len__(self):
        return len(self.texts)

    def __getitem__(self, item):
        text = self.texts[item]
        label = self.labels[item]
        encoding = self.tokenizer(text, max_length=self.max_len,
                       padding='max_length', truncation=True,
                       return_tensors='pt')
        return {
            'input_ids': encoding['input_ids'].flatten(),
            'attention_mask': encoding['attention_mask'].flatten(),
            'labels': torch.tensor(label, dtype=torch.long)
        }
选拔赛 第4题 创建训练 DataLoader
使用 DataLoader 封装数据集 train_dataset,要求设置批次大小为 BATCH_SIZE,并在加载时打乱数据顺序,将结果赋给变量 train_loader。

思路分析

与模块B中的DataLoader使用方式相同,只是这里是NLP数据集。创建DataLoader时指定批次大小和是否打乱顺序。

代码实现

from torch.utils.data import DataLoader

train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)

代码详解

  • DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True):创建训练数据加载器
  • train_dataset:训练数据集(SentimentDataset实例)
  • batch_size=BATCH_SIZE:使用预定义的批次大小(通常为32)
  • shuffle=True:打乱数据顺序,防止模型学习到数据的排列规律
选拔赛 第5题 加载 BertForSequenceClassification
使用 BertForSequenceClassification 类加载预训练模型 MODEL_NAME,指定分类标签数为 2 赋值给变量 model。

思路分析

BertForSequenceClassification 是HuggingFace提供的BERT序列分类模型,在BERT的基础上内置了一个线性分类头。num_labels=2 指定二分类任务(正面/负面)。

代码实现

from transformers import BertForSequenceClassification

model = BertForSequenceClassification.from_pretrained(MODEL_NAME, num_labels=2)

代码详解

  • BertForSequenceClassification:BERT序列分类模型类。它在BERT编码器之上添加了一个线性分类层,直接输出分类结果
  • from_pretrained(MODEL_NAME, num_labels=2):加载预训练模型。num_labels=2 指定分类头输出2维(对应正面/负面两个类别)
  • 模型会自动在BERT编码器顶部添加一个线性分类层,将[CLS]标记的隐藏状态映射到2维输出
  • 赋值给 model,后续通过 model.to(DEVICE) 移动到GPU或CPU
首次加载提示 首次运行 from_pretrained 时会从HuggingFace Hub下载模型权重(约50-100MB),需要网络连接。如果下载缓慢,可以提前下载模型到本地,然后传入本地路径。
总决赛 第1题 配置 AdamW 优化器
配置 AdamW 优化器,传入模型参数,设置学习率为 2e-5,eps 为 1e-8。

思路分析

AdamW 是 Transformer 模型训练的标准优化器。2e-5(即0.00002)是BERT微调的推荐学习率,比CNN训练用的学习率小得多,因为预训练语言模型的权重已经很好,只需要轻微调整。

代码实现

from transformers import AdamW

optimizer = AdamW(model.parameters(), lr=2e-5, eps=1e-8)

代码详解

  • AdamW:Adam优化器的改进版本,修正了权重衰减的实现。Transformers库提供的AdamW与PyTorch内置的 torch.optim.AdamW 功能相同
  • model.parameters():传入模型所有需要优化的参数
  • lr=2e-5:学习率。科学计数法,2×10⁻⁵ = 0.00002。BERT微调的典型学习率范围是2e-5到5e-5
  • eps=1e-8:数值稳定性常数。防止分母为零的情况,1e-8是默认推荐值
总决赛 第2题 计算 Warmup 步数
将预热比例 WARMUP_RATIO 与总训练步数 total_steps 相乘,并对乘积取整,将结果赋值给变量 warmup_steps。

思路分析

Warmup 步数是训练初期学习率从0线性增长到预设值的步数。通常设为总步数的10%(WARMUP_RATIO=0.1)。需要将浮点数结果取整,因为步数必须是整数。

代码实现

warmup_steps = int(WARMUP_RATIO * total_steps)

代码详解

  • WARMUP_RATIO * total_steps:预热比例乘以总训练步数,得到浮点数结果。例如 total_steps=1000, WARMUP_RATIO=0.1,结果为100.0
  • int(...):将浮点数取整为整数。例如 100.0 → 100
  • 赋值给 warmup_steps,用于后续构建学习率调度器

知识链接:total_steps 的计算

# 总训练步数 = 每轮步数 × 训练轮数
# 每轮步数 = 数据量 ÷ 批次大小(向上取整)
total_steps = len(train_loader) * EPOCHS
总决赛 第3题 从 batch 获取标签并转移设备
从 batch 中获取键为 'labels' 的数据,将其转移至 device 指定的设备上,并把结果赋给变量 labels。

思路分析

训练循环中,每个batch是一个字典,包含 input_ids、attention_mask 和 labels。需要将标签数据移到与模型相同的设备(GPU/CPU)上才能进行计算。

代码实现

labels = batch['labels'].to(device)

代码详解

  • batch['labels']:从batch字典中获取键为 'labels' 的张量(真实标签)
  • .to(device):将张量移动到指定设备。如果 device'cuda',张量被移到GPU;如果是 'cpu',保持在CPU上
  • 赋值给 labels,用于后续计算损失和评估准确率
总决赛 第4题 转移预测结果到 CPU 并扩展列表
将张量 preds 转移至 CPU 并转换为列表,然后将该列表中的元素扩展添加到列表 predictions 中。

思路分析

验证阶段收集所有预测结果。GPU上的张量不能直接转换为Python列表,需要先移到CPU。extend 方法可以将一个列表的所有元素添加到另一个列表中。

代码实现

predictions.extend(preds.cpu().tolist())

代码详解

  • preds.cpu():将GPU上的预测张量转移到CPU。这一步是必须的——GPU张量不能直接调用 .tolist()
  • .tolist():将张量转换为Python列表。例如 tensor([1, 0, 1])[1, 0, 1]
  • predictions.extend(...):将列表中的所有元素逐个添加到 predictions 列表末尾。注意 extendappend 的区别:extend([1,0,1]) 添加三个元素,append([1,0,1]) 添加一个列表作为单个元素
总决赛 第5题 载入最优模型权重
将变量 best_model_state 中存储的参数状态字典载入到 model 中,以恢复模型的权重。

思路分析

训练过程中通常会保存验证集上表现最好的模型权重。训练结束后,需要将最优权重重新加载到模型中,以获得最佳性能的模型用于推理。

代码实现

model.load_state_dict(best_model_state)

代码详解

  • best_model_state:存储最优模型参数的状态字典(通常在训练循环中通过 best_model_state = model.state_dict() 保存)
  • model.load_state_dict(best_model_state):将状态字典中的参数值加载到模型的对应层中,覆盖当前参数
  • 加载后模型恢复到验证集上表现最好的状态,而不是最后一个epoch的状态(最后一个epoch可能过拟合)

3.5 项目总结与拓展

项目总结

本项目完成了一个基于预训练语言模型的NLP情感分析流程:加载Tokenizer→构建数据集→加载预训练模型→配置优化器和调度器→训练与验证→保存最优模型→加载模型推理。这个流程是NLP文本分类的标准范式,适用于情感分析、意图识别、垃圾邮件检测等各种分类任务。

NLP vs CV 迁移学习对比

对比项CV(模块B)NLP(模块C)
预训练模型MobileNetV2(CNN)TinyBERT(Transformer)
数据预处理缩放、归一化Tokenization、填充
优化器AdamAdamW
学习率0.0012e-5(小得多)
学习率调度指数衰减Warmup + 线性衰减

拓展练习

  1. 尝试使用完整的 bert-base-chinese 模型替代 TinyBERT,对比准确率和推理速度
  2. 实现一个完整的推理函数,输入一条评论文本,输出情感预测结果和置信度
  3. 在训练循环中添加梯度裁剪 torch.nn.utils.clip_grad_norm_,防止梯度爆炸
  4. 使用 sklearn 的 classification_report 生成详细的评估报告(精确率、召回率、F1值)
模块 D

项目四:AI低空创新应用

基于 YOLOv8n 在无人机航拍数据集上实现小目标行人检测

4.1 项目导读与学习目标

项目背景

"低空经济"是快速发展的新兴产业,涵盖无人机物流、航拍测绘、安防巡逻、灾害搜救等领域。在无人机应用中,目标检测是最核心的AI能力——无人机需要从高空视角识别地面的人员、车辆等目标。无人机航拍图像的特殊性在于目标尺度极小视角独特(俯视)、背景复杂,使得常规检测模型效果不佳。

本项目使用轻量级 YOLOv8n 模型,加载COCO预训练权重,通过"冻结主干+微调检测头"的迁移学习策略,在无人机航拍行人数据集上训练小目标检测模型。

学习目标

  • 理解目标检测任务的基本概念(边界框、mAP、精确率、召回率)
  • 掌握 Ultralytics YOLOv8 的训练、验证和推理方法
  • 理解"冻结主干+微调检测头"的迁移学习策略
  • 掌握余弦退火学习率调度和 Mosaic 数据增强的作用
  • 掌握 psutil 获取系统信息、OpenCV 图像缩放等辅助操作

4.2 知识准备

4.2.1 目标检测基础

目标检测不仅要识别图像中有什么物体(分类),还要定位物体在哪里(定位)。输出是边界框——用矩形框标出物体位置和类别。

核心概念

  • 边界框:矩形框,表示为 (x, y, w, h) 或 (x1, y1, x2, y2)
  • IoU:交并比,预测框与真实框的交集面积/并集面积
  • mAP@0.5:IoU阈值为0.5时的平均精度均值,目标检测核心指标
  • 精确率:预测为正的样本中真正为正的比例
  • 召回率:真正为正的样本中被正确预测的比例

4.2.2 YOLO 系列与 YOLOv8n

YOLO(You Only Look Once)将目标检测视为单次回归问题,速度极快。YOLOv8 由 Ultralytics 于2023年发布,统一了检测/分割/分类/姿态估计。本项目使用 YOLOv8n(nano版本),参数量仅3.2M,适合资源受限环境。

4.2.3 小目标检测策略

本项目采用的策略

  1. 加载COCO预训练权重:COCO包含"person"类别,预训练权重已具备行人检测先验
  2. 冻结主干+微调检测头:保留特征提取能力,只调整检测头
  3. 高初始学习率+余弦退火:加速检测头适配,后期平滑收敛
  4. 关闭Mosaic增强:防止小目标在拼接中变得更小

4.2.4 psutil 库

psutil(Python System and Process Utilities)是跨平台系统信息库,可获取CPU核心数、内存使用量等。本项目用 psutil.cpu_count(logical=False) 获取物理CPU核心数(不包括超线程)。

4.3 环境与数据准备

确保已安装 ultralyticsopencv-pythonpsutil。需要无人机航拍行人数据集(YOLO格式)和数据集配置文件 YAML。竞赛环境预定义了 cfgYAML_PATHIMGSZBATCH_SIZEDEVICEVAL_CONFIOU_THRESHOLD 等变量。

4.4 项目实施

选拔赛 第1题 启动模型训练流程
调用 train_model 函数,启动模型的训练流程。

思路分析

竞赛环境中预定义了 train_model 函数,封装了 YOLOv8 的训练逻辑(包括冻结主干、设置余弦退火调度、关闭Mosaic等)。只需调用该函数即可启动训练。

代码实现

train_model()

代码详解

  • train_model():调用预定义的训练函数。该函数内部完成:加载YOLOv8n预训练模型→冻结主干参数→配置余弦退火学习率→关闭Mosaic增强→执行训练循环→保存最优权重
  • 训练过程中会在控制台输出每个epoch的损失、学习率、mAP等指标
选拔赛 第2题 执行模型验证
调用模型对象 model 的 val 方法执行模型验证,依次传入配置项 cfg 中的数据集路径 YAML_PATH、图像尺寸 IMGSZ、批大小 BATCH_SIZE、计算设备 DEVICE、置信度阈值 VAL_CONF 与 IoU 阈值 IOU_THRESHOLD,并将 verbose 参数设为 False,最后将返回的验证指标结果赋值给变量 metrics。

思路分析

YOLOv8 的 model.val() 方法用于在验证集上评估模型性能。需要传入多个配置参数,所有参数从配置对象 cfg 中获取。verbose=False 关闭详细输出。

代码实现

metrics = model.val(
    data=cfg.YAML_PATH,
    imgsz=cfg.IMGSZ,
    batch=cfg.BATCH_SIZE,
    device=cfg.DEVICE,
    conf=cfg.VAL_CONF,
    iou=cfg.IOU_THRESHOLD,
    verbose=False
)

代码详解

  • model.val(...):YOLOv8 模型验证方法,在验证集上计算 mAP、精确率、召回率等指标
  • data=cfg.YAML_PATH:数据集配置文件路径,指定验证集位置和类别信息
  • imgsz=cfg.IMGSZ:输入图像尺寸(如640),模型会将图像缩放到此尺寸
  • batch=cfg.BATCH_SIZE:验证时的批次大小
  • device=cfg.DEVICE:计算设备(如 'cuda:0' 或 'cpu')
  • conf=cfg.VAL_CONF:置信度阈值,低于此值的检测结果被过滤
  • iou=cfg.IOU_THRESHOLD:NMS(非极大值抑制)的IoU阈值,用于去除重叠框
  • verbose=False:关闭详细输出,只返回指标不打印日志
  • 返回值赋给 metrics,包含 mAP@0.5、mAP@0.5:0.95、精确率、召回率等
选拔赛 第3题 获取最优权重路径
调用模型评估函数 evaluate_model,将返回的最优权重路径赋值给变量 best_weight_path。

思路分析

竞赛环境预定义了 evaluate_model 函数,用于在验证集上评估不同epoch保存的权重,返回表现最优的权重文件路径。

代码实现

best_weight_path = evaluate_model()

代码详解

  • evaluate_model():预定义的评估函数,遍历训练过程中保存的权重文件,在验证集上逐一评估,返回mAP最高的权重路径
  • 赋值给 best_weight_path,后续用于加载最优模型进行推理
选拔赛 第4题 OpenCV 图像缩放
使用 OpenCV 的 resize 函数对图像 display_img 进行缩放,目标尺寸的宽和高分别为 w_disp 与 h_disp 乘以缩放比例 scale 后取整,并将缩放后的结果重新赋值给变量 display_img。

思路分析

使用 cv2.resize() 缩放图像。目标尺寸通过将原始宽高乘以缩放比例并取整得到。int() 确保尺寸为整数(resize函数要求整数尺寸)。

代码实现

display_img = cv2.resize(display_img, (int(w_disp * scale), int(h_disp * scale)))

代码详解

  • cv2.resize(src, dsize):图像缩放函数。src 是输入图像,dsize 是目标尺寸元组 (width, height)
  • w_disp * scale:原始宽度乘以缩放比例。例如 w_disp=1920, scale=0.5 → 960.0
  • int(...):取整。resize要求尺寸为整数,960.0 → 960
  • 目标尺寸元组 (int(w_disp * scale), int(h_disp * scale)) 同时指定了宽和高
  • 缩放结果重新赋值给 display_img,覆盖原图像数据
注意 OpenCV 中尺寸的顺序是 (宽度, 高度),与 NumPy 数组的 (高度, 宽度) 顺序相反。这是初学者常见的混淆点!
选拔赛 第5题 调用预测与分析函数
调用预测与分析函数 predict_and_analyze,以变量 best_weight_path 作为权重路径参数,并传入关键字参数 num_samples 指定待分析样本数量为 2。

思路分析

竞赛环境预定义了 predict_and_analyze 函数,用于加载最优权重、对样本图像进行推理预测,并可视化分析结果。需要传入权重路径和样本数量。

代码实现

predict_and_analyze(best_weight_path, num_samples=2)

代码详解

  • predict_and_analyze(...):预定义的预测分析函数
  • best_weight_path:作为位置参数传入,指定最优权重文件路径
  • num_samples=2:关键字参数,指定分析2个样本图像。关键字参数的格式是 参数名=值
  • 函数内部:加载权重→选取样本→模型推理→绘制检测框→输出分析结果
总决赛 第1题 获取物理CPU核心数
使用 psutil 库获取计算机的物理 CPU 核心数量(不包括超线程),并将结果存储在变量 cpu_count 中。

思路分析

psutil.cpu_count(logical=False) 返回物理CPU核心数。logical=False 表示不计算超线程核心。

代码实现

import psutil

cpu_count = psutil.cpu_count(logical=False)

代码详解

  • psutil.cpu_count(logical=False):获取物理CPU核心数
  • logical=False:不计算超线程。例如4核8线程的CPU,logical=False 返回4,logical=True 返回8
  • 赋值给 cpu_count,用于后续设置 DataLoader 的 num_workers 参数
总决赛 第2题 调用环境检查函数
调用环境检查函数来验证系统配置是否符合要求。

思路分析

竞赛环境预定义了环境检查函数(如 check_environment),用于验证GPU、内存、依赖库等是否满足训练要求。

代码实现

check_environment()

代码详解

  • check_environment():调用预定义的环境检查函数
  • 该函数检查:PyTorch版本、CUDA可用性、GPU显存、磁盘空间、依赖库完整性等
  • 如果环境不满足要求,函数会输出警告信息
总决赛 第3题 随机抽样训练图像
使用 random 模块的 sample 函数,从训练图像列表 train_imgs 中随机抽取图像,抽取数量取 200 与 train_imgs 长度的最小值,并将抽取结果赋值给变量 sample_imgs。

思路分析

random.sample(population, k) 从列表中随机抽取k个不重复元素。需要处理数据量不足200的情况——取 min(200, len(train_imgs)) 确保不超过列表长度。

代码实现

import random

sample_imgs = random.sample(train_imgs, min(200, len(train_imgs)))

代码详解

  • random.sample(population, k):从 population 中随机选取 k 个不重复元素,返回新列表
  • train_imgs:训练图像路径列表
  • min(200, len(train_imgs)):取200和列表长度的较小值。如果列表只有150张图,则取150;如果有1000张,则取200
  • 这样做的目的是防止当数据量不足200时 random.sampleValueError: Sample larger than population
总决赛 第4题 读取图像并转换颜色空间
使用 OpenCV 的 imread 函数读取路径 img_path 下的图像,并使用 cvtColor 函数结合 COLOR_BGR2RGB 参数将其颜色通道从 BGR 转换为 RGB,将最终结果赋值给变量 img。

思路分析

分两步操作:(1)用 cv2.imread() 读取图像(BGR格式);(2)用 cv2.cvtColor() 将BGR转为RGB。这是模块A学过的操作的直接应用。

代码实现

img = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2RGB)

代码详解

  • cv2.imread(img_path):读取指定路径的图像,返回BGR格式的NumPy数组
  • cv2.cvtColor(..., cv2.COLOR_BGR2RGB):将BGR转为RGB。这在用matplotlib显示图像时是必须的
  • 两步操作可以嵌套在一行中完成:先 imread 再 cvtColor
  • 赋值给 img,用于后续的可视化展示
总决赛 第5题 调用可视化真值函数
调用可视化真值函数 visualize_ground_truth,通过传入关键字参数指定单次可视化的样本数量为 2。

思路分析

竞赛环境预定义了 visualize_ground_truth 函数,用于在图像上绘制标注框(ground truth)并显示。通过关键字参数指定样本数量。

代码实现

visualize_ground_truth(num_samples=2)

代码详解

  • visualize_ground_truth(...):预定义的可视化函数,在样本图像上绘制真实标注框
  • num_samples=2:关键字参数,指定可视化2个样本。关键字参数格式为 参数名=值
  • 该函数通常用于训练前检查数据标注是否正确
总决赛 第6题 实例化 YOLO 模型
根据配置对象 cfg 中的 MODEL_NAME 属性实例化 YOLO 类,并将生成的模型对象赋值给变量 model。

思路分析

使用 Ultralytics 的 YOLO 类加载模型。模型名称存储在配置对象 cfg.MODEL_NAME 中(如 'yolov8n.pt')。

代码实现

from ultralytics import YOLO

model = YOLO(cfg.MODEL_NAME)

代码详解

  • YOLO(cfg.MODEL_NAME):实例化YOLO模型。cfg.MODEL_NAME 是模型名称/路径,如 'yolov8n.pt'
  • 如果传入的是预训练权重文件名(如 yolov8n.pt),Ultralytics 会自动下载(首次使用时)并加载COCO预训练权重
  • 赋值给 model,后续调用 model.train()model.val()model.predict() 等方法

知识链接:YOLOv8 模型变体

模型参数量速度精度
YOLOv8n (nano)3.2M最快最低
YOLOv8s (small)11.2M较低
YOLOv8m (medium)25.9M
YOLOv8l (large)43.7M较高
YOLOv8x (xlarge)68.2M最慢最高

4.5 项目总结与拓展

项目总结

本项目完成了一个基于YOLOv8的目标检测全流程:环境检查→加载预训练模型→训练(冻结主干+微调检测头)→验证评估→获取最优权重→推理预测→可视化分析。同时涉及了psutil系统信息获取、random随机抽样、OpenCV图像读取与缩放等辅助操作。

四大模块技术对比

模块任务类型核心技术关键指标
模块A图像处理OpenCV处理效果
模块B图像分类MobileNetV2+迁移学习分类准确率
模块C文本分类TinyBERT+微调分类准确率
模块D目标检测YOLOv8n+微调mAP@0.5

拓展练习

  1. 尝试使用 YOLOv8s 替代 YOLOv8n,对比模型大小、训练时间和mAP
  2. 开启Mosaic数据增强重新训练,观察对小目标检测的影响
  3. 实现一个完整的推理脚本:输入一张无人机航拍图像,输出带检测框的结果图
  4. 使用 matplotlib 绘制训练过程中的 mAP 变化曲线

附录

附录A:Python 基础速查

变量与数据类型

# 基本数据类型
x = 10            # 整数 int
y = 3.14          # 浮点数 float
s = "hello"      # 字符串 str
b = True          # 布尔值 bool
lst = [1, 2, 3]  # 列表 list
d = {'a': 1}    # 字典 dict

# 三元表达式
result = 'cuda' if condition else 'cpu'

# 列表推导式
squares = [x**2 for x in range(10)]

import 与模块

import cv2                      # 导入整个模块
import numpy as np           # 导入并设置别名
from torch import nn           # 从模块导入特定子模块
from transformers import BERT  # 从包导入特定类

附录B:常用库 API 速查

OpenCV (cv2)

函数说明示例
cv2.imread(path)读取图像(BGR)img = cv2.imread('a.jpg')
cv2.imwrite(path, img)保存图像cv2.imwrite('b.jpg', img)
cv2.cvtColor(img, code)颜色空间转换cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
cv2.GaussianBlur(img, ksize, sigmaX)高斯模糊cv2.GaussianBlur(img, (5,5), 0)
cv2.Canny(img, t1, t2)Canny边缘检测cv2.Canny(img, 100, 200)
cv2.threshold(img, t, max, type)阈值分割_, t = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
cv2.resize(img, dsize)图像缩放cv2.resize(img, (100, 200))

PyTorch (torch)

函数/类说明
torch.device('cuda')创建GPU设备对象
torch.cuda.is_available()检查GPU是否可用
tensor.to(device)将张量移到指定设备
tensor.cpu()将张量移到CPU
model.state_dict()获取模型参数字典
model.load_state_dict(d)加载参数字典
torch.save(obj, path)保存对象到文件
torch.load(path)从文件加载对象
model.parameters()获取模型所有参数
param.requires_grad参数是否需要梯度

Transformers (HuggingFace)

类/方法说明
BertTokenizer.from_pretrained(name)加载分词器
BertForSequenceClassification.from_pretrained(name, num_labels=N)加载分类模型
tokenizer(text, max_length, padding, truncation, return_tensors)文本编码

Ultralytics (YOLOv8)

方法说明
YOLO('yolov8n.pt')加载模型
model.train(data, epochs, imgsz)训练模型
model.val(data, imgsz, conf, iou)验证模型
model(image)推理预测

附录C:常见错误与解决方案

错误信息原因解决方案
ModuleNotFoundError: No module named 'cv2'OpenCV未安装或环境未激活conda activate brics_ai 然后 pip install opencv-python
ModuleNotFoundError: No module named 'torch'PyTorch未安装conda install pytorch torchvision -c pytorch
RuntimeError: CUDA out of memoryGPU显存不足减小batch_size,或使用CPU训练
'NoneType' object has no attribute 'shape'图像读取失败(路径错误)检查文件路径是否正确,使用 img is not None 验证
ValueError: Sample larger than populationrandom.sample抽样数超过列表长度使用 min(N, len(list)) 限制抽样数
RuntimeError: Expected all tensors on the same device模型和数据在不同设备上确保 model.to(DEVICE)data.to(DEVICE)
matplotlib显示灰度图颜色异常未指定灰度颜色映射添加 cmap='gray' 参数
matplotlib显示彩色图红蓝颠倒OpenCV BGR与matplotlib RGB顺序不同使用 cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 转换
TypeError: integer argument expectedcv2.resize尺寸不是整数使用 int() 取整
HuggingFace模型下载缓慢网络问题提前下载模型到本地,传入本地路径