摘要
YOLO v2(You Only Look Once v2)是目标检测领域的重要里程碑,由 Joseph Redmon 和 Ali Farhadi 于 2016 年提出,也被称为 YOLO9000。本文系统阐述了 YOLO v2 的核心算法原理、网络架构设计、训练推理机制及代码实现。YOLO v2 通过引入 Batch Normalization、锚框机制、多尺度训练等创新技术,在保持实时检测速度的同时,将 PASCAL VOC 2007 数据集上的 mAP 从 YOLO v1 的 63.4% 提升至 78.6%(48)。本文深入分析了 Darknet-19 骨干网络的 19 层卷积结构,详细解释了维度聚类、直接位置预测、Passthrough 层等关键技术,并提供了 PyTorch 和 TensorFlow 的完整代码实现示例。
表1:主流目标检测算法性能对比(PASCAL VOC 2007)
| 算法 | 输入尺寸 | mAP(%) | FPS | 核心特点 |
|---|---|---|---|---|
| YOLO v1 | 448×448 | 63.4 | 45 | 单阶段、端到端回归 |
| YOLO v2 | 608×608 | 78.6 | 40 | 锚框、多尺度、特征融合 |
| Faster R-CNN | 1000×600 | 73.2 | 7 | 两阶段、RPN 生成候选框 |
| SSD 512 | 512×512 | 78.2 | 19 | 多尺度特征、锚框 |
研究表明,YOLO v2 在 608×608 输入下,mAP 与 SSD 512 持平,但 FPS 是其 2 倍;与 Faster R-CNN 相比,mAP 更高且速度提升近 6 倍,完美实现了 “实时性 + 高精度” 的平衡(29)。本文为深度学习初学者、研究人员和工程开发者提供了全面的技术参考。
1. 引言
1.1 目标检测技术发展背景
目标检测作为计算机视觉的核心任务之一,旨在识别图像或视频中的多个目标并确定其位置。随着深度学习技术的发展,目标检测方法经历了从传统手工特征到深度学习端到端学习的重大变革。早期的目标检测算法如 DPM(Deformable Part Model)基于手工设计的特征和滑动窗口方法,虽然在特定场景下表现良好,但计算效率低下且泛化能力有限。
深度学习时代的到来带来了革命性的变化。R-CNN 系列算法通过选择性搜索生成候选区域,然后使用 CNN 进行特征提取和分类,开创了基于深度学习的目标检测先河。Fast R-CNN 进一步改进了训练和推理流程,通过共享特征图大幅提升了效率。Faster R-CNN 则引入了区域提议网络(RPN),实现了端到端的训练,成为两阶段目标检测算法的代表。
与此同时,单阶段目标检测算法也在快速发展。YOLO(You Only Look Once)系列算法以其卓越的实时检测能力而闻名。YOLO v1 首次将目标检测问题转化为回归问题,通过一次前向传播直接预测边界框和类别概率,在保持 45 FPS 实时速度的同时达到了 63.4% 的 mAP。然而,YOLO v1 也存在一些明显的局限性,包括定位精度不高、小目标检测效果差、召回率相对较低等问题。
1.2 YOLO v2 的提出动机与创新点
基于 YOLO v1 的局限性,Joseph Redmon 和 Ali Farhadi 于 2016 年提出了 YOLO v2(也称为 YOLO9000),旨在保持实时检测速度优势的同时,大幅提升检测精度(2)。YOLO v2 的核心思想不是推倒重来,而是对 YOLO v1 进行系统性的 “手术式升级”,通过一系列精心设计的改进来解决 v1 版本的具体痛点(48)。
图1:YOLO v2 核心创新点逻辑图
YOLO v2 的主要创新点包括:引入 Batch Normalization 技术提升训练稳定性和收敛速度;采用高分辨率分类器预训练策略;引入锚框(Anchor Boxes)机制提高召回率;使用 K-means 聚类算法自动生成适配数据集的先验框尺寸;提出直接位置预测方法解决锚框训练不稳定问题;设计 Passthrough 层融合浅深层特征以提升小目标检测能力;实现多尺度训练策略增强模型适应性;创新提出 WordTree 结构实现跨数据集联合训练,使模型可检测超过 9000 个类别(48)。
这些改进使得 YOLO v2 在 PASCAL VOC 2007 数据集上以 40 FPS 的速度达到了 78.6% 的 mAP,在保持实时检测能力的同时,精度提升了 13.4 个百分点,显著超越了同期的 Faster R-CNN 和 SSD 等算法。
1.3 本文组织结构
本文将从算法原理、网络架构、训练推理机制、代码实现和性能评估五个维度全面解析 YOLO v2。第 2 章详细阐述 YOLO v2 的核心算法原理,包括 Batch Normalization、锚框机制、维度聚类、直接位置预测等关键技术;第 3 章深入分析 Darknet-19 网络架构的设计理念和具体结构;第 4 章介绍 YOLO v2 的训练策略和推理流程;第 5 章提供 PyTorch 和 TensorFlow 的完整代码实现示例;第 6 章评估 YOLO v2 在标准数据集上的性能表现并分析其应用场景;第 7 章总结 YOLO v2 的贡献并展望目标检测技术的发展趋势。
2. YOLO v2 核心算法原理详解
2.1 Batch Normalization 技术
Batch Normalization(批量归一化)是 YOLO v2 引入的第一项关键技术改进。在 YOLO v1 中,作者使用 Dropout 来防止过拟合,但 Dropout 会随机丢弃部分神经元的激活值,导致特征信息的损失(28)。YOLO v2 在所有卷积层后均加入 Batch Normalization 层,彻底移除了 Dropout,这一改变带来了多重 benefits。
Batch Normalization 的核心原理是对每一层的输入特征进行归一化处理,使其具有零均值和单位方差。公式1:Batch Normalization 计算式
μ
B
=
1
m
∑
i
=
1
m
x
i
(
1
)
\mu_B = \frac{1}{m} \sum_{i=1}^m x_i \quad (1)
μB=m1i=1∑mxi(1)
σ
B
2
=
1
m
∑
i
=
1
m
(
x
i
−
μ
B
)
2
(
2
)
\sigma_B^2 = \frac{1}{m} \sum_{i=1}^m (x_i - \mu_B)^2 \quad (2)
σB2=m1i=1∑m(xi−μB)2(2)
x
^
i
=
x
i
−
μ
B
σ
B
2
+
ϵ
(
3
)
\hat{x}_i = \frac{x_i - \mu_B}{\sqrt{\sigma_B^2 + \epsilon}} \quad (3)
x^i=σB2+ϵxi−μB(3)
y
i
=
γ
x
^
i
+
β
(
4
)
y_i = \gamma \hat{x}_i + \beta \quad (4)
yi=γx^i+β(4)
其中:
- m m m 为 mini-batch 样本数;
- μ B \mu_B μB 为批次均值, σ B 2 \sigma_B^2 σB2 为批次方差;
- ϵ \epsilon ϵ 为防止分母为 0 的极小值;
- γ \gamma γ、 β \beta β 为可学习的缩放和平移参数;
- y i y_i yi 为 BN 层输出。
这一技术在 YOLO v2 中带来了三个重要作用:首先,它稳定了训练过程,减少了内部协变量偏移(Internal Covariate Shift)问题,使得网络在训练时能够更快地收敛到更低的损失值;其次,BN 层具有加速收敛的效果,允许使用更高的学习率,使模型收敛速度提升 3 倍以上;第三,BN 层自带一定的正则化效果,可以减少对 Dropout 的依赖,提高模型的泛化能力(28)。
表2:Batch Normalization 对 YOLO v2 性能的影响(PASCAL VOC 2007)
| 配置 | mAP(%) | 训练收敛速度 | 过拟合风险 |
|---|---|---|---|
| 无 BN + Dropout | 66.4 | 基准 | 高 |
| 有 BN + 移除 Dropout | 68.4 | 提升 3 倍 | 低 |
实验结果表明,仅通过在所有卷积层后添加 Batch Normalization,YOLO v2 就在 PASCAL VOC 数据集上实现了 2% 的 mAP 提升(30)。这一看似简单的改进,实际上对整个网络的训练稳定性和最终性能产生了深远影响,成为 YOLO v2 成功的重要基石之一。
2.2 高分辨率分类器预训练
YOLO v1 在分类器预训练时使用 224×224 分辨率的图像,而在检测任务中却使用 448×448 分辨率,这种分辨率的突然变化导致网络需要同时适应新的输入分辨率和学习目标检测任务,增加了训练的难度。YOLO v2 通过高分辨率分类器预训练策略解决了这一问题。
图2:YOLO v2 高分辨率预训练流程
具体实施步骤如下:首先,在 ImageNet 数据集上使用 224×224 分辨率预训练分类网络;然后,将分辨率提升至 448×448,在 ImageNet 上进行 10 个 epoch 的微调,让网络适应高分辨率输入;最后,使用这个高分辨率预训练的网络进行目标检测任务的微调。
这一改进的理论依据在于,让网络提前适应高分辨率输入,可以使其更好地学习和提取高分辨率下的特征信息。当网络已经熟悉了 448×448 分辨率下的图像特征分布后,再进行检测任务的训练,就可以更专注于学习边界框回归和类别预测,而不必同时适应分辨率的变化。
实验结果显示,通过高分辨率分类器预训练,YOLO v2 获得了近 4% 的 mAP 提升。这一改进虽然简单,但效果显著,特别是在小目标检测方面表现更为突出,因为高分辨率输入能够更好地保留小目标的细节信息。
2.3 锚框机制与维度聚类
YOLO v1 直接预测边界框的坐标,这种方法虽然简单直接,但存在定位精度不高和召回率低的问题。YOLO v2 借鉴了 Faster R-CNN 的锚框(Anchor Boxes)机制,通过预定义的先验框来辅助边界框的预测(28)。
锚框机制的核心思想是在每个网格单元中预设多个不同尺寸和比例的先验框,网络不再直接预测边界框的绝对坐标,而是预测相对于这些先验框的偏移量。具体实现中,YOLO v2 将输入图像划分为 13×13 的网格,每个网格预测 5 个锚框,总共产生 13×13×5=845 个边界框预测,相比 YOLO v1 的 98 个预测框大幅增加了目标的覆盖范围(28)。
然而,锚框的尺寸选择至关重要。传统的方法是手工设计锚框的尺寸和比例,如 Faster R-CNN 使用的 1:1、1:2、2:1 等比例,但这种方法存在很大的主观性,可能无法很好地适配特定数据集的目标分布。YOLO v2 创新性地提出使用 K-means 聚类算法从训练数据中自动学习最优的锚框尺寸。
在聚类过程中,YOLO v2 使用了一个巧妙的距离度量公式:公式2:YOLO v2 聚类距离度量
d
(
b
o
x
,
c
e
n
t
r
o
i
d
)
=
1
−
I
o
U
(
b
o
x
,
c
e
n
t
r
o
i
d
)
d(box, centroid) = 1 - IoU(box, centroid)
d(box,centroid)=1−IoU(box,centroid)
这与传统的 K-means 使用欧氏距离不同,因为我们真正关心的是锚框与真实边界框的匹配程度,而不是它们的绝对尺寸差异。
表3:PASCAL VOC 数据集 K-means 聚类锚框尺寸
| 聚类数 k | 平均 IoU | 锚框尺寸(w×h) |
|---|---|---|
| 3 | 59.1 | (10×13), (16×30), (33×23) |
| 5 | 61.0 | (10×13), (16×30), (33×23), (30×61), (62×45) |
| 9 | 60.9 | (8×9), (10×19), (15×11), (20×36), (25×17), (37×28), (36×75), (76×55), (73×146) |
通过对 PASCAL VOC 和 COCO 数据集进行聚类分析,YOLO v2 发现选择 k=5 作为聚类中心数量是一个很好的平衡点,既能保持模型的复杂度在合理范围内,又能获得较高的召回率。聚类得到的锚框尺寸与手工设计的锚框有显著差异,更多地呈现高瘦的形状,这反映了数据集中目标的实际分布特征。
图3:YOLO v2 锚框聚类可视化
┌─────────────────────────┐
│ 手工设计锚框(9个) │ ┌─────────────────────────┐
│ □ ▢ ▛ ▟ ■ ░ ▒ ▓ █ │ │ 聚类生成锚框(5个) │
│ (均匀分布,无数据适配) │ │ □ ▢ ▛ ▟ ■ │
└─────────────────────────┘ │ (贴合数据分布,高瘦为主)│
└─────────────────────────┘
┌─────────────────────────┐
│ 召回率对比 │
│ 手工锚框:81% │
│ 聚类锚框:88% │
└─────────────────────────┘
使用 K-means 聚类生成的锚框带来了显著的性能提升。在仅使用 5 个聚类中心的情况下,平均 IOU 达到 61.0,与手工设计的 9 个锚框的 60.9 基本持平,这说明自动学习的锚框能够更有效地覆盖目标的尺寸分布。引入锚框机制后,YOLO v2 的召回率从 81% 提升至 88%,虽然 mAP 略有下降(从 69.5% 降至 69.2%),但召回率的大幅提升为后续的改进提供了更大的空间。
2.4 直接位置预测方法
虽然锚框机制提高了召回率,但直接采用 Faster R-CNN 的锚框偏移预测方法会导致训练不稳定的问题。这是因为传统的偏移预测方法没有对预测值进行约束,导致边界框可能 “乱跑”,预测结果不稳定。
YOLO v2 提出了直接位置预测(Direct Location Prediction)方法来解决这一问题。该方法沿用了 YOLO v1 的一些思路,预测边界框中心点相对于网格单元的偏移量,并使用 sigmoid 函数将偏移量约束在 0-1 之间,确保预测的边界框中心位于对应的网格单元内。
公式3:YOLO v2 直接位置预测完整公式
b
x
=
σ
(
t
x
)
+
c
x
(
1
)
b_x = \sigma(t_x) + c_x \quad (1)
bx=σ(tx)+cx(1)
b
y
=
σ
(
t
y
)
+
c
y
(
2
)
b_y = \sigma(t_y) + c_y \quad (2)
by=σ(ty)+cy(2)
b
w
=
p
w
×
e
t
w
(
3
)
b_w = p_w \times e^{t_w} \quad (3)
bw=pw×etw(3)
b
h
=
p
h
×
e
t
h
(
4
)
b_h = p_h \times e^{t_h} \quad (4)
bh=ph×eth(4)
其中:
- ( b x , b y ) (b_x, b_y) (bx,by):预测框中心坐标;
- ( b w , b h ) (b_w, b_h) (bw,bh):预测框宽高;
- ( t x , t y , t w , t h ) (t_x, t_y, t_w, t_h) (tx,ty,tw,th):网络预测的偏移量;
- ( c x , c y ) (c_x, c_y) (cx,cy):网格单元左上角坐标;
- ( p w , p h ) (p_w, p_h) (pw,ph):锚框(先验框)宽高;
- σ \sigma σ:sigmoid 函数(约束 t x / t y ∈ [ 0 , 1 ] t_x/t_y \in [0,1] tx/ty∈[0,1])。
图4:直接位置预测 vs 传统锚框预测
┌─────────────────────┐ ┌─────────────────────┐
│ 传统锚框预测 │ │ YOLO v2 直接预测 │
│ t_x/t_y 无约束 │ │ σ(t_x/t_y) ∈ [0,1] │
│ → 框中心易偏离网格 │ │ → 框中心锁定网格内 │
│ 训练波动大 │ │ 训练稳定 │
└─────────────────────┘ └─────────────────────┘
这一设计的巧妙之处在于:首先,通过 sigmoid 函数将 tx 和 ty 约束在 0-1 范围内,确保预测的边界框中心不会偏离其所在的网格单元太远,提高了训练的稳定性;其次,对于宽高的预测,使用指数函数确保缩放后的宽高始终为正值;最后,预测的偏移量都是相对于先验框的,而不是绝对坐标,降低了回归问题的难度。
直接位置预测方法的引入,不仅解决了锚框训练不稳定的问题,还提高了定位精度。实验结果表明,这一改进带来了约 5% 的性能提升。更重要的是,这种方法使得网络的训练过程更加稳定可靠,为后续的多尺度训练等技术的成功实施奠定了基础。
2.5 多尺度训练策略
YOLO v2 移除了全连接层,使得网络能够处理任意尺寸的输入图像。基于这一特性,作者提出了多尺度训练(Multi-Scale Training)策略,这是 YOLO v2 的又一重要创新。
图5:YOLO v2 多尺度训练流程图
多尺度训练的具体实施方法是:每 10 个 batch,网络随机选择一个新的输入图像尺寸。由于 YOLO v2 的网络下采样总步长为 32,所以输入尺寸必须是 32 的倍数,选择范围为 {320, 352, …, 608}。例如,当选择 320×320 作为输入尺寸时,网络的输出特征图尺寸为 10×10;当选择 608×608 时,输出特征图尺寸为 19×19。
表4:YOLO v2 不同输入分辨率下的性能表现
| 输入分辨率 | 特征图尺寸 | mAP(%) | FPS(Titan X) | 适用场景 |
|---|---|---|---|---|
| 320×320 | 10×10 | 69.2 | 91 | 低算力设备、实时监控 |
| 416×416 | 13×13 | 76.8 | 67 | 平衡速度与精度 |
| 512×512 | 16×16 | 78.0 | 48 | 高精度需求场景 |
| 608×608 | 19×19 | 78.6 | 40 | 小目标检测、静态场景 |
这种训练策略带来了多重好处:首先,模型在训练过程中接触到不同尺度的目标,增强了对各种尺寸目标的适应能力;其次,同一模型可以适应不同分辨率的需求,在推理时可以根据实际应用场景选择合适的输入尺寸;第三,多尺度训练相当于进行了数据增强,提高了模型的泛化能力。
在不同分辨率下,YOLO v2 展现出了良好的适应性:在低分辨率(288×288)下,模型运行速度超过 90 FPS,mAP 接近 Fast R-CNN 的水平,非常适合计算资源受限的场景;在高分辨率(608×608)下,模型达到 78.6% 的 mAP,同时仍保持实时检测速度,满足高精度检测需求。
多尺度训练策略的成功实施,得益于 YOLO v2 全卷积网络的设计和直接位置预测方法的稳定性。这种策略不仅提升了模型的性能,还大大增加了模型的应用灵活性,使其能够在不同的硬件平台和应用场景中发挥作用。
2.6 Passthrough 层特征融合
小目标检测一直是目标检测领域的难题,YOLO v1 在这方面表现尤其不佳。YOLO v2 通过引入 Passthrough 层来融合浅深层特征,显著提升了小目标的检测能力。
图6:Passthrough 层特征融合示意图
┌─────────────────────────┐ ┌─────────────────────────┐
│ 浅层特征(26×26×512) │ │ 深层特征(13×13×1024) │
│ (边缘、纹理等细节) │ │ (语义、轮廓等高层特征)│
└───────────┬─────────────┘ └───────────┬─────────────┘
│ │
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐
│ 2×2 像素拆分 → 4×(13×13×128) │ │ 保持原尺寸 │
└───────────┬─────────────┘ └───────────┬─────────────┘
│ │
└───────────────────┬────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ 融合特征(13×13×1536)= 1024 + 4×128 │
│ (细节+语义,适配小目标检测) │
└─────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ 边界框+类别预测 │
└─────────────────────────────────────────────────┘
Passthrough 层的设计灵感来源于特征金字塔网络的思想。在 YOLO v2 中,浅层特征图(如 26×26×512)包含丰富的细节信息(如边缘、纹理),适合检测小目标;而深层特征图(如 13×13×1024)包含高级的语义信息,适合检测大目标。Passthrough 层的作用是将浅层的高分辨率特征与深层的低分辨率特征进行融合,实现 “高低分辨率特征互补”。
具体的实现过程如下:首先,将 26×26×512 的浅层特征图按照 2×2 像素块进行拆分,得到 4 个 13×13×128 的子特征图,这个过程类似于 “像素洗牌”;然后,将这 4 个 13×13×128 的子特征图与 13×13×1024 的深层特征图在通道维度上进行拼接,得到 13×13×(1024+4×128)=13×13×1536 的融合特征图;最后,基于这个融合特征图进行边界框和类别的预测。
Passthrough 层的引入带来了显著的效果提升。通过融合浅层的细粒度特征和深层的语义特征,模型既能 “看得清”(定位准),又能 “懂得多”(分类准),小目标的漏检率降低了约 10%。这一改进对于实际应用具有重要意义,特别是在需要检测大量小目标的场景中,如人群检测、交通标志识别等。
2.7 YOLO9000 跨数据集联合训练
YOLO9000 是 YOLO v2 的一个重要扩展版本,它实现了一个前所未有的创新:跨数据集联合训练,使模型能够检测超过 9000 个不同类别的目标。
传统的目标检测模型通常只能在单一数据集上训练,检测固定数量的类别(如 PASCAL VOC 的 20 类或 COCO 的 80 类)。YOLO9000 通过引入 WordTree(词树)结构,巧妙地解决了不同数据集之间的类别差异问题。WordTree 基于 WordNet(语义词典)构建,将 ImageNet 的 1000 个分类标签和 COCO 的 80 个检测标签整合到一个层次化的树结构中。
图7:YOLO9000 WordTree 结构示意图
根节点(object)
/ \
animal vehicle
/ | \ / \
mammal bird fish car bike truck
/ |
dog cat
/ | \
labrador husky golden
在 WordTree 中,每个节点代表一个概念,例如 “狗” 是 “哺乳动物” 的子节点,“哺乳动物” 是 “动物” 的子节点。这种层次化结构允许模型学习不同类别之间的语义关系,即使某些类别在训练集中没有检测标注,也可以通过语义关系进行推理。
表5:YOLO9000 跨数据集训练性能
| 数据集类别 | 有检测标注 | 无检测标注 | mAP(%) |
|---|---|---|---|
| ImageNet 检测集 | 44 类 | 156 类 | 19.7(整体) |
| 有标注子集 | - | - | 25.0 |
| 无标注子集 | - | - | 16.0 |
联合训练的具体方法是:同时使用 COCO 检测数据集和 ImageNet 分类数据集进行训练。对于检测图像,按照正常的目标检测流程进行训练,计算边界框回归损失和类别损失;对于分类图像,找到预测该类别概率最高的边界框,假设其与真实标签的 IOU 至少为 0.3,并基于此计算置信度损失和分类损失。
YOLO9000 的性能评估结果令人印象深刻:在 ImageNet 检测验证集上,尽管只有 44 个类别有检测数据,模型仍达到了 19.7% 的 mAP;在 156 个没有检测数据的类别上,mAP 为 16.0%。这表明,通过跨数据集联合训练,模型确实能够学习到新类别的检测能力,实现了从 “见过” 到 “没见过” 类别的泛化。
YOLO9000 的成功不仅在于技术创新,更在于其对目标检测领域发展方向的启示。它展示了如何利用大量的分类数据来增强检测模型的能力,为解决检测数据集稀缺的问题提供了新思路。这种跨数据集联合训练的思想,对后续的目标检测算法发展产生了深远影响。
3. YOLO v2 网络架构分析
3.1 Darknet-19 骨干网络设计理念
YOLO v1 使用基于 GoogLeNet 修改的网络作为特征提取器,虽然速度较快,但在特征提取能力上存在局限。YOLO v2 设计了全新的 Darknet-19 作为骨干网络,其名称中的 “19” 代表了 19 个卷积层(48)。
Darknet-19 的设计理念可以概括为 “简洁高效”。与 VGG-16 相比,Darknet-19 的参数量更少,但精度不弱于 VGG-16,同时浮点运算量减少到约 1/5,这保证了更快的运算速度(48)。网络的核心设计是使用连续的 3×3 卷积和 1×1 卷积交替堆叠,这种设计既能够有效提取空间特征,又能够通过 1×1 卷积进行通道压缩,减少计算量(48)。
Darknet-19 的另一个重要设计是引入了批量归一化(Batch Normalization)技术来稳定训练过程。在每个卷积层后都添加了 BN 层,不仅加速了收敛,还提高了模型的泛化能力(48)。此外,网络采用了全卷积设计,移除了所有的全连接层,使得网络可以接受任意尺寸的输入图像,为多尺度训练奠定了基础(14)。
从整体架构来看,Darknet-19 可以分为两个主要部分:前端特征提取部分包含 16 层卷积(含 1×1 降维)和 4 层池化,负责从低级特征(边缘、颜色)逐步提取高级特征(目标部件、轮廓);后端预测头部分包含 3 层卷积和 1 层池化,负责将高级特征映射为边界框和类别的预测结果。
3.2 Darknet-19 网络结构详解
Darknet-19 的具体网络结构如下表所示,以 416×416×3 的图像作为输入:
表6:Darknet-19 网络结构(输入 416×416×3)
| 网络阶段 | 层数与类型 | 核心作用 | 输出特征图尺寸 | 参数量(M) | 计算量(GFlops) |
|---|---|---|---|---|---|
| 输入层 | - | 416×416×3 RGB 图像 | 416×416×3 | 0 | 0 |
| 第 1 阶段 | 卷积层 1:32 个 3×3 卷积 卷积层 2:64 个 3×3 卷积 池化层 1:2×2 最大池化 | 初始特征提取 通道翻倍 下采样 | 208×208×64 | 0.08 | 0.15 |
| 第 2 阶段 | 卷积层 3:128 个 3×3 卷积 卷积层 4:64 个 1×1 卷积 卷积层 5:128 个 3×3 卷积 池化层 2:2×2 最大池化 | 特征增强 通道压缩 特征提取 下采样 | 104×104×128 | 0.29 | 0.31 |
| 第 3 阶段 | 卷积层 6:256 个 3×3 卷积 卷积层 7:128 个 1×1 卷积 卷积层 8:256 个 3×3 卷积 池化层 3:2×2 最大池化 | 特征增强 通道压缩 特征提取 下采样 | 52×52×256 | 1.17 | 0.62 |
| 第 4 阶段 | 卷积层 9:512 个 3×3 卷积 卷积层 10:256 个 1×1 卷积 卷积层 11:512 个 3×3 卷积 卷积层 12:256 个 1×1 卷积 卷积层 13:512 个 3×3 卷积 池化层 4:2×2 最大池化 | 深度特征提取 多次通道压缩 特征增强 下采样 | 26×26×512 | 4.69 | 1.24 |
| 第 5 阶段 | 卷积层 14:1024 个 3×3 卷积 卷积层 15:512 个 1×1 卷积 卷积层 16:1024 个 3×3 卷积 卷积层 17:512 个 1×1 卷积 卷积层 18:1024 个 3×3 卷积 | 高层语义特征提取 通道调整 特征融合 通道调整 最终特征提取 | 13×13×1024 | 18.76 | 2.48 |
| 预测层 | 卷积层 19:1024 个 3×3 卷积 Passthrough 层 输出层:1×1 卷积 | 最终特征处理 特征融合 边界框和类别预测 | 13×13×125 | 2.91 | 0.85 |
| 总计 | 19 个卷积层 + 4 个池化层 + 1 个 Passthrough 层 | - | - | 27.92 | 5.68 |
图8:Darknet-19 网络结构可视化
Input (416×416×3)
↓
Conv(32,3×3) → BN → LeakyReLU → Conv(64,3×3) → BN → LeakyReLU → MaxPool(2×2)
↓ (208×208×64)
Conv(128,3×3) → BN → LeakyReLU → Conv(64,1×1) → BN → LeakyReLU → Conv(128,3×3) → BN → LeakyReLU → MaxPool(2×2)
↓ (104×104×128)
Conv(256,3×3) → BN → LeakyReLU → Conv(128,1×1) → BN → LeakyReLU → Conv(256,3×3) → BN → LeakyReLU → MaxPool(2×2)
↓ (52×52×256)
Conv(512,3×3) → BN → LeakyReLU → Conv(256,1×1) → BN → LeakyReLU → Conv(512,3×3) → BN → LeakyReLU → Conv(256,1×1) → BN → LeakyReLU → Conv(512,3×3) → BN → LeakyReLU → MaxPool(2×2)
↓ (26×26×512)
Conv(1024,3×3) → BN → LeakyReLU → Conv(512,1×1) → BN → LeakyReLU → Conv(1024,3×3) → BN → LeakyReLU → Conv(512,1×1) → BN → LeakyReLU → Conv(1024,3×3) → BN → LeakyReLU
↓ (13×13×1024)
Conv(1024,3×3) → BN → LeakyReLU → Conv(1024,3×3) → BN → LeakyReLU
↓ (13×13×1024)
Passthrough(26×26×512 → 13×13×256) → Concat → Conv(125,1×1)
↓ (13×13×125)
Output (边界框+置信度+类别)
从上表可以看出,Darknet-19 采用了 “3×3 卷积 + 1×1 卷积” 的重复模块设计。3×3 卷积负责提取空间特征,1×1 卷积负责通道压缩和跨通道信息融合。每个池化层将特征图尺寸减半,通道数翻倍,这种设计使得网络能够逐步提取更抽象的特征表示。
值得注意的是,Darknet-19 中的每个卷积层都采用了 CBL(卷积 - 批归一化 - 激活函数)结构,即卷积层后面紧跟 Batch Normalization 层和 Leaky ReLU 激活函数。这种结构设计不仅提高了训练的稳定性,还增强了网络的表达能力。
3.3 从分类网络到检测网络的转换
Darknet-19 最初是为图像分类任务设计的,在 ImageNet 上进行预训练时,网络的最后会有一个全连接层输出 1000 个类别的概率。当将其转换为目标检测网络时,需要进行一系列的修改(52)。
图9:Darknet-19 从分类到检测的转换示意图
┌─────────────────────────┐ ┌─────────────────────────┐
│ 分类网络(ImageNet) │ │ 检测网络(VOC/COCO) │
├─────────────────────────┤ ├─────────────────────────┤
│ Darknet-19 卷积层 │ → │ Darknet-19 卷积层 │
│ 全连接层(1000类) │ 移除 │ 移除全连接层 │
│ Softmax 输出 │ │ 添加检测头+Passthrough层 │
│ │ │ 1×1 卷积输出(13×13×125)│
└─────────────────────────┘ └─────────────────────────┘
首先,移除了最后的全连接层,改为全卷积结构。这样做的好处是网络可以接受任意尺寸的输入,而不仅仅是固定尺寸的 224×224。其次,在网络的末端添加了一些额外的卷积层来生成检测所需的边界框和类别预测。具体来说,在 13×13×1024 的特征图基础上,先经过两个卷积层(卷积层 19:1024 个 3×3 卷积),然后通过 Passthrough 层融合浅层特征,最后通过一个 1×1 卷积层输出检测结果。
检测网络的最终输出是一个 13×13×125 的特征图,其中 125 = 5×(4+1+20)。这里的 5 表示每个网格预测 5 个锚框,4 表示边界框的 4 个坐标(x, y, w, h),1 表示置信度(confidence),20 表示 PASCAL VOC 数据集的 20 个类别。每个锚框的预测包含:4 个坐标参数(通过直接位置预测公式计算)、1 个置信度(表示该框包含目标的概率乘以预测框与真实框的 IOU)、20 个类别概率(在该框包含目标的前提下,属于各个类别的条件概率)。
这种从分类网络到检测网络的转换设计非常巧妙,既充分利用了在大规模分类数据集上预训练的权重,又通过简单的修改适应了目标检测任务的需求。这种设计思路对后续的目标检测算法产生了深远影响,许多现代检测算法都采用了类似的 “backbone+head” 架构。
4. 训练与推理机制
4.1 训练流程概述
YOLO v2 的训练流程可以分为预训练和微调两个阶段。预训练阶段在 ImageNet 分类数据集上进行,使用 224×224 分辨率训练 Darknet-19 分类网络;然后使用 448×448 分辨率进行 10 个 epoch 的微调,让网络适应高分辨率输入;最后在目标检测数据集(如 PASCAL VOC 或 COCO)上进行微调,将分类网络转换为检测网络(55)。
图10:YOLO v2 完整训练流程图
在目标检测的微调阶段,YOLO v2 采用了多尺度训练策略。每 10 个 batch,网络会随机选择一个新的输入尺寸,从 {320, 352, …, 608} 中选择 32 的倍数。这种策略迫使模型学习在不同分辨率下都能有效检测目标,大大增强了模型的适应性。
训练过程中使用的优化器通常是随机梯度下降(SGD),动量设置为 0.9,权重衰减为 0.0005。学习率的设置采用了阶梯式策略:初始学习率设为 0.001,在第 10 个 epoch 时降低到 0.0001,在第 30 个 epoch 时进一步降低到 0.00001。这种学习率调度策略有助于模型在训练初期快速收敛,在后期进行精细调整。
表7:YOLO v2 训练超参数设置
| 超参数 | 取值 | 作用 |
|---|---|---|
| 优化器 | SGD | 稳定收敛,泛化性好 |
| 动量 | 0.9 | 加速收敛,减少震荡 |
| 权重衰减 | 0.0005 | 防止过拟合 |
| 初始学习率 | 0.001 | 前期快速收敛 |
| 第10 epoch学习率 | 0.0001 | 中期精细调整 |
| 第30 epoch学习率 | 0.00001 | 后期稳定收敛 |
| 训练 epoch 数 | 40 | 完整训练周期 |
| 批量大小 | 64 | 平衡显存与训练稳定性 |
数据增强是训练过程中的重要环节。YOLO v2 采用了多种数据增强技术,包括随机水平翻转、随机裁剪、随机缩放、颜色抖动等。这些技术不仅增加了训练数据的多样性,还有助于防止过拟合,提高模型的泛化能力。
4.2 损失函数设计
YOLO v2 的损失函数是一个多部分组成的复合函数,包含了边界框坐标损失、置信度损失和类别损失三个主要部分。与 YOLO v1 相比,损失函数进行了一些重要的调整以适应锚框机制和直接位置预测方法。
公式4:YOLO v2 完整损失函数
L
=
λ
c
o
o
r
d
(
∑
L
o
s
s
x
y
+
∑
L
o
s
s
w
h
)
+
∑
L
o
s
s
c
o
n
f
o
b
j
+
λ
n
o
o
b
j
∑
L
o
s
s
c
o
n
f
n
o
o
b
j
+
∑
L
o
s
s
c
l
a
s
s
L = \lambda_{coord} \left( \sum Loss_{xy} + \sum Loss_{wh} \right) + \sum Loss_{conf}^{obj} + \lambda_{noobj} \sum Loss_{conf}^{noobj} + \sum Loss_{class}
L=λcoord(∑Lossxy+∑Losswh)+∑Lossconfobj+λnoobj∑Lossconfnoobj+∑Lossclass
其中各子损失的详细定义:
1. 坐标偏移损失(Loss_xy)
L o s s x y = ∑ i = 0 S 2 ∑ j = 0 B 1 i j o b j ( σ ( t x i j ) − t x i j , g t ) 2 + ( σ ( t y i j ) − t y i j , g t ) 2 Loss_{xy} = \sum_{i=0}^{S^2} \sum_{j=0}^{B} \mathbb{1}_{ij}^{obj} \left( \sigma(t_x^{ij}) - t_x^{ij,gt} \right)^2 + \left( \sigma(t_y^{ij}) - t_y^{ij,gt} \right)^2 Lossxy=i=0∑S2j=0∑B1ijobj(σ(txij)−txij,gt)2+(σ(tyij)−tyij,gt)2
- 1 i j o b j \mathbb{1}_{ij}^{obj} 1ijobj:指示函数,网格 i i i 的锚框 j j j 负责预测目标时为 1,否则为 0;
- t x i j , g t = x g t − c x p w t_x^{ij,gt} = \frac{x^{gt} - c_x}{p_w} txij,gt=pwxgt−cx, t y i j , g t = y g t − c y p h t_y^{ij,gt} = \frac{y^{gt} - c_y}{p_h} tyij,gt=phygt−cy(真实偏移量)。
2. 宽高缩放损失(Loss_wh)
L o s s w h = ∑ i = 0 S 2 ∑ j = 0 B 1 i j o b j ( t w i j − t w i j , g t ) 2 + ( t h i j − t h i j , g t ) 2 Loss_{wh} = \sum_{i=0}^{S^2} \sum_{j=0}^{B} \mathbb{1}_{ij}^{obj} \left( t_w^{ij} - t_w^{ij,gt} \right)^2 + \left( t_h^{ij} - t_h^{ij,gt} \right)^2 Losswh=i=0∑S2j=0∑B1ijobj(twij−twij,gt)2+(thij−thij,gt)2
- t w i j , g t = log ( w g t p w ) t_w^{ij,gt} = \log\left( \frac{w^{gt}}{p_w} \right) twij,gt=log(pwwgt), t h i j , g t = log ( h g t p h ) t_h^{ij,gt} = \log\left( \frac{h^{gt}}{p_h} \right) thij,gt=log(phhgt)(真实缩放因子)。
3. 有目标置信度损失(Loss_conf^obj)
L o s s c o n f o b j = ∑ i = 0 S 2 ∑ j = 0 B 1 i j o b j ( c ^ i j − c i j g t ) 2 Loss_{conf}^{obj} = \sum_{i=0}^{S^2} \sum_{j=0}^{B} \mathbb{1}_{ij}^{obj} \left( \hat{c}_{ij} - c_{ij}^{gt} \right)^2 Lossconfobj=i=0∑S2j=0∑B1ijobj(c^ij−cijgt)2
- c ^ i j \hat{c}_{ij} c^ij:预测置信度; c i j g t = I o U ( b i j , b g t ) c_{ij}^{gt} = IoU(b_{ij}, b^{gt}) cijgt=IoU(bij,bgt)(真实置信度)。
4. 无目标置信度损失(Loss_conf^noobj)
L o s s c o n f n o o b j = ∑ i = 0 S 2 ∑ j = 0 B 1 i j n o o b j ( c ^ i j − 0 ) 2 Loss_{conf}^{noobj} = \sum_{i=0}^{S^2} \sum_{j=0}^{B} \mathbb{1}_{ij}^{noobj} \left( \hat{c}_{ij} - 0 \right)^2 Lossconfnoobj=i=0∑S2j=0∑B1ijnoobj(c^ij−0)2
- 1 i j n o o b j \mathbb{1}_{ij}^{noobj} 1ijnoobj:指示函数,锚框 j j j 与所有真实框 IoU < 0.6 时为 1; λ n o o b j = 0.5 \lambda_{noobj}=0.5 λnoobj=0.5。
5. 类别损失(Loss_class)
L o s s c l a s s = ∑ i = 0 S 2 ∑ j = 0 B 1 i j o b j ∑ c ∈ c l a s s e s ( p ^ i j c − p i j c g t ) 2 Loss_{class} = \sum_{i=0}^{S^2} \sum_{j=0}^{B} \mathbb{1}_{ij}^{obj} \sum_{c \in classes} \left( \hat{p}_{ijc} - p_{ijc}^{gt} \right)^2 Lossclass=i=0∑S2j=0∑B1ijobjc∈classes∑(p^ijc−pijcgt)2
- p ^ i j c \hat{p}_{ijc} p^ijc:预测类别概率; p i j c g t p_{ijc}^{gt} pijcgt:真实类别标签(one-hot)。
注: λ c o o r d = 5 \lambda_{coord}=5 λcoord=5(坐标损失权重), λ n o o b j = 0.5 \lambda_{noobj}=0.5 λnoobj=0.5(无目标置信度损失权重), S = 13 S=13 S=13(网格数), B = 5 B=5 B=5(每个网格锚框数)。
损失函数中的参数 λ_coord=5 是一个权重系数,用于提升坐标精度的重要性。这个系数的设置反映了在目标检测任务中,准确的边界框定位比分类更重要的特点。
4.3 推理过程详解
YOLO v2 的推理过程可以分为三个主要步骤:输入预处理、网络预测和后处理。
图11:YOLO v2 推理流程图
输入预处理阶段,首先根据应用需求选择合适的输入尺寸(通常为 416×416),然后将原始图像 resize 到该尺寸。需要注意的是,resize 操作可能会改变图像的宽高比,因此通常会在图像周围添加黑边来保持原始比例。接下来对像素值进行归一化处理(除以 255),使其范围在 [0, 1] 之间。最后,应用 Batch Normalization 的均值和方差进行标准化,这些参数在训练时已经学习完成。
网络预测阶段,预处理后的图像通过 Darknet-19 网络进行特征提取,得到 13×13×1024 的高层特征图。然后通过 Passthrough 层融合浅层的 26×26×512 特征,得到 13×13×1536 的融合特征图。最后通过卷积层和 1×1 卷积层,输出 13×13×125 的预测特征图。
后处理阶段是将网络预测结果转换为最终的检测结果,主要包括以下步骤:
-
置信度计算:每个锚框的类别置信度等于网格的类别概率乘以锚框的置信度,即:Class_Confidence = Class_Probability × Objectness_Confidence。
-
置信度阈值过滤:设置一个置信度阈值(通常为 0.5),过滤掉类别置信度低于阈值的锚框,只保留置信度较高的检测结果。
-
坐标映射:将预测的偏移量转换为图像中的绝对坐标。使用直接位置预测公式(公式3)。
-
非极大值抑制(NMS):对每个类别单独执行 NMS 操作,IoU 阈值通常设为 0.5。NMS 的作用是删除重叠度高的冗余边界框,只保留置信度最高的检测结果。
后处理的最后一步是将检测结果以可视化的方式展示出来,通常是在原始图像上绘制边界框,并标注类别和置信度信息。整个推理过程在现代 GPU 上可以达到 40-67 FPS 的速度,完全满足实时检测的需求。
5. 代码实现示例
5.1 PyTorch 实现
以下是 YOLO v2 的 PyTorch 实现示例,包括 Darknet-19 网络定义、损失函数计算和推理过程。
Darknet-19 网络定义:
import torch
import torch.nn as nn
import torch.nn.functional as F
class Darknet19(nn.Module):
def __init__(self, num_classes=20, anchors=5):
super(Darknet19, self).__init__()
# 定义Darknet-19的卷积层
self.conv_layers = nn.Sequential(
# 第1阶段:416x416 -> 208x208
CBL(3, 32, 3, 1),
CBL(32, 64, 3, 1),
nn.MaxPool2d(2, 2),
# 第2阶段:208x208 -> 104x104
CBL(64, 128, 3, 1),
CBL(128, 64, 1, 1),
CBL(64, 128, 3, 1),
nn.MaxPool2d(2, 2),
# 第3阶段:104x104 -> 52x52
CBL(128, 256, 3, 1),
CBL(256, 128, 1, 1),
CBL(128, 256, 3, 1),
nn.MaxPool2d(2, 2),
# 第4阶段:52x52 -> 26x26
CBL(256, 512, 3, 1),
CBL(512, 256, 1, 1),
CBL(256, 512, 3, 1),
CBL(512, 256, 1, 1),
CBL(256, 512, 3, 1),
nn.MaxPool2d(2, 2),
# 第5阶段:26x26 -> 13x13
CBL(512, 1024, 3, 1),
CBL(1024, 512, 1, 1),
CBL(512, 1024, 3, 1),
CBL(1024, 512, 1, 1),
CBL(512, 1024, 3, 1)
)
# 检测头部分
self.detection_head = nn.Sequential(
CBL(1024, 1024, 3, 1),
CBL(1024, 1024, 3, 1)
)
# 最终的检测层
self.final_detection = nn.Conv2d(1024 + 256, anchors * (5 + num_classes), 1)
# 用于Passthrough层的1x1卷积
self.passthrough_conv = CBL(512, 64, 1, 1)
def forward(self, x):
# 保存中间特征图用于Passthrough
features = []
# 前向传播通过Darknet-19
for i, layer in enumerate(self.conv_layers):
x = layer(x)
if i in [6, 11, 16, 23]: # 在特定层后保存特征
features.append(x)
# 检测头
x = self.detection_head(x)
# Passthrough层
passthrough = self._passthrough(features[3]) # 26x26x512
x = torch.cat([x, passthrough], dim=1)
# 最终检测
x = self.final_detection(x)
return x
def _passthrough(self, x):
"""Passthrough层实现"""
# 26x26x512 -> 26x26x64 (1x1卷积)
x = self.passthrough_conv(x)
# 26x26x64 -> 13x13x256 (像素重组)
batch, channel, height, width = x.size()
x = x.view(batch, channel, height//2, 2, width//2, 2)
x = x.permute(0, 1, 3, 5, 2, 4).contiguous()
x = x.view(batch, channel*4, height//2, width//2)
return x
class CBL(nn.Module):
"""卷积+BN+LeakyReLU"""
def __init__(self, in_channels, out_channels, kernel_size, stride):
super(CBL, self).__init__()
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride,
padding=kernel_size//2, bias=False)
self.bn = nn.BatchNorm2d(out_channels)
self.act = nn.LeakyReLU(0.1, inplace=True)
def forward(self, x):
return self.act(self.bn(self.conv(x)))
损失函数实现:
class YOLOLoss(nn.Module):
def __init__(self, anchors, num_classes=20, img_size=416):
super(YOLOLoss, self).__init__()
self.anchors = torch.Tensor(anchors) # 锚框尺寸
self.num_anchors = len(anchors)
self.num_classes = num_classes
self.img_size = img_size
self.ignore_threshold = 0.6 # IOU阈值,用于忽略部分预测框
# 损失权重
self.lambda_coord = 5.0
self.lambda_noobj = 0.5
# 计算网格尺寸
self.grid_size = 0
self.stride = 0
def forward(self, predictions, targets):
"""
predictions: (batch, 5*anchors, grid_size, grid_size)
targets: 包含边界框和类别的标签
"""
batch_size = predictions.size(0)
self.grid_size = predictions.size(2)
self.stride = self.img_size / self.grid_size
# 调整预测格式:(batch, anchors, grid, grid, 5+num_classes)
predictions = predictions.view(batch_size, self.num_anchors,
5 + self.num_classes,
self.grid_size, self.grid_size)
predictions = predictions.permute(0, 1, 3, 4, 2).contiguous()
# 提取预测值
tx = predictions[..., 0:1] # 中心x偏移
ty = predictions[..., 1:2] # 中心y偏移
tw = predictions[..., 2:3] # 宽度缩放
th = predictions[..., 3:4] # 高度缩放
tconf = predictions[..., 4:5] # 置信度
tcls = predictions[..., 5:] # 类别概率
# 生成网格
grid_x = torch.arange(self.grid_size).repeat(self.grid_size, 1).view([1, 1, self.grid_size, self.grid_size]).float()
grid_y = torch.arange(self.grid_size).repeat(self.grid_size, 1).t().view([1, 1, self.grid_size, self.grid_size]).float()
# 如果在GPU上,将网格移动到GPU
if predictions.is_cuda:
grid_x = grid_x.cuda()
grid_y = grid_y.cuda()
self.anchors = self.anchors.cuda()
# 计算边界框坐标
pred_xy = (torch.sigmoid(tx) + torch.cat([grid_x, grid_y], dim=2)) * self.stride
pred_wh = (torch.exp(torch.cat([tw, th], dim=4)) * self.anchors.view(1, self.num_anchors, 1, 1, 2))
pred_boxes = torch.cat([pred_xy, pred_wh], dim=4)
# 初始化损失
loss_xy = 0
loss_wh = 0
loss_conf = 0
loss_cls = 0
num_pos = 0
# 遍历每个batch
for i in range(batch_size):
# 获取当前batch的target
target = targets[i]
if len(target) == 0:
continue
# 提取真实框和类别
gxy = target[:, :2] # 真实框中心坐标
gxy = gxy / self.stride # 转换到网格坐标
gxi = gxy.long()
# 计算IOU(省略,完整实现需补充IOU计算逻辑)
# ...
# 汇总损失
total_loss = self.lambda_coord * (loss_xy + loss_wh) + loss_conf + loss_cls
return total_loss
推理代码实现:
def yolo_inference(model, image, anchors, img_size=416, conf_thresh=0.5, nms_thresh=0.5):
"""
YOLO v2 推理函数
:param model: 训练好的Darknet-19模型
:param image: 输入图像(PIL Image)
:param anchors: 锚框尺寸
:param img_size: 输入尺寸
:param conf_thresh: 置信度阈值
:param nms_thresh: NMS阈值
:return: 检测结果(边界框、类别、置信度)
"""
# 1. 输入预处理
original_size = image.size
# 调整尺寸并保持宽高比
img = letterbox_image(image, (img_size, img_size))
# 转换为张量
img = torch.from_numpy(np.transpose(img / 255.0, (2, 0, 1))).float().unsqueeze(0)
# 2. 网络预测
model.eval()
with torch.no_grad():
predictions = model(img)
# 3. 后处理
# 解析预测结果
detections = parse_predictions(predictions, anchors, conf_thresh)
# NMS
detections = non_max_suppression(detections, nms_thresh)
# 坐标映射回原始图像尺寸
detections = resize_boxes(detections, (img_size, img_size), original_size)
return detections
def letterbox_image(image, size):
"""保持宽高比的resize,补黑边"""
iw, ih = image.size
w, h = size
scale = min(w/iw, h/ih)
nw = int(iw * scale)
nh = int(ih * scale)
image = image.resize((nw, nh), Image.BICUBIC)
new_image = Image.new('RGB', size, (128, 128, 128))
new_image.paste(image, ((w - nw) // 2, (h - nh) // 2))
return np.array(new_image)
def parse_predictions(predictions, anchors, conf_thresh):
"""解析预测特征图"""
# 省略解析逻辑,完整实现需根据公式3转换坐标、计算置信度等
pass
def non_max_suppression(detections, nms_thresh):
"""非极大值抑制"""
# 省略NMS实现逻辑
pass
def resize_boxes(detections, model_size, original_size):
"""将检测框映射回原始图像尺寸"""
# 省略坐标映射逻辑
pass
&spm=1001.2101.3001.5002&articleId=159356220&d=1&t=3&u=2dd1f254c6fa4b6e900267b89bac2dee)
8万+

被折叠的 条评论
为什么被折叠?



