一、怎么解决图片输入尺度不统一的问题
是指在训练时对输入图片进行尺寸调整,以提高模型处理长宽比差异较大的图片时的性能,同时避免过多的图像变形。具体来说,以下是矩形训练的处理过程:
1. 矩形训练的核心目标
- 方法:将图片按照短边进行缩放,同时保留长宽比例,然后在未填满的部分补零。
2. 小于输入尺度(如640×640)的图片
- 调整尺寸:根据图片的原始比例,将短边调整到目标尺寸(如640)。
- 比如,(保持比例,长边填满640)。
- 填充:将缩放后的图片。
- 填充分布在未被填充的一边,左右或上下对称填充。
3. 大于输入尺度(如640×640)的图片
- 调整尺寸:根据图片的原始比例,将图片等比例缩小到短边等于目标尺寸(如640)。
- 比如,图片
- 填充:缩小后,
4. 细节说明
- 零填充的作用:
- 零填充不改变目标物体的位置分布,仅扩展背景部分。
- 确保了最终图片符合模型输入的固定尺寸需求(如640×640)。
- 大图与小图的处理一致:
- 无论原图是大于还是小于目标尺寸,都通过缩放和填充统一处理为640×640的大小。
- 模型训练效果:
5. 实际应用中的注意点
- 填充的数值:通常为零,但在某些数据增强方法(如Mosaic训练)中,也可能填充随机颜色。
- 计算损失时的掩码处理:因为填充区域不包含真实信息,需要额外处理,以避免填充部分干扰模型的学习。
二、超参数解读
lr0: 0.01 # initial learning rate (SGD=1E-2, Adam=1E-3)
lrf: 0.1 # final OneCycleLR learning rate (lr0 * lrf)
momentum: 0.937 # SGD momentum/Adam beta1
weight_decay: 0.0005 # optimizer weight decay 5e-4
warmup_epochs: 3.0 # warmup epochs (fractions ok)
warmup_momentum: 0.8 # warmup initial momentum
warmup_bias_lr: 0.1 # warmup initial bias lr
box: 0.05 # box loss gain
cls: 0.3 # cls loss gain
cls_pw: 1.0 # cls BCELoss positive_weight
obj: 0.7 # obj loss gain (scale with pixels)
obj_pw: 1.0 # obj BCELoss positive_weight
iou_t: 0.20 # IoU training threshold
anchor_t: 4.0 # anchor-multiple threshold
# anchors: 3 # anchors per output layer (0 to ignore)
fl_gamma: 0.0 # focal loss gamma (efficientDet default gamma=1.5)
hsv_h: 0.015 # image HSV-Hue augmentation (fraction)
hsv_s: 0.7 # image HSV-Saturation augmentation (fraction)
hsv_v: 0.4 # image HSV-Value augmentation (fraction)
degrees: 0.0 # image rotation (+/- deg)
translate: 0.2 # image translation (+/- fraction)
scale: 0.9 # image scale (+/- gain)
shear: 0.0 # image shear (+/- deg)
perspective: 0.0 # image perspective (+/- fraction), range 0-0.001
flipud: 0.0 # image flip up-down (probability)
fliplr: 0.5 # image flip left-right (probability)
mosaic: 1.0 # image mosaic (probability)
mixup: 0.15 # image mixup (probability)
copy_paste: 0.0 # image copy paste (probability)
paste_in: 0.15 # image copy paste (probability), use 0 for faster training
loss_ota: 1 # use ComputeLossOTA, use 0 for faster training
这些超参数是YOLOv7的配置文件中用于训练过程的参数设置,它们分别控制优化器、损失函数、数据增强以及一些模型行为。以下是对每个超参数的详细解释:
1. 优化器相关参数
2. 损失函数相关参数
3. 数据增强相关参数
4. 模型训练行为相关参数
实际应用示例
- 快速训练:
- 降低
warmup_epochs
(如设置为1.0)。 - 禁用复杂的数据增强(如
mosaic=0
,mixup=0
,copy_paste=0
)。
- 降低
- 处理高分辨率目标或小目标:
- 增大
iou_t
(如设置为0.25)以确保更多小目标参与训练。 - 调整
scale
和translate
,避免目标被削减或超出边界。
- 增大
- 复杂数据增强:
- 启用
mosaic=1.0
,mixup=0.15
,copy_paste=0.1
,以增强训练集多样性。
- 启用
三、命令行参数解读
weights: yolov7.pt
cfg: cfg/training/yolov7.yaml
data: data/neu.yaml
hyp: data/hyp.scratch.p5.yaml
epochs: 300
batch_size: 16
img_size:
- 640
- 640
rect: false
resume: false
nosave: false
notest: false
noautoanchor: false
evolve: false
bucket: ''
cache_images: false
image_weights: false
device: cpu
multi_scale: false
single_cls: false
adam: false
sync_bn: false
local_rank: -1
workers: 8
project: runs/train
entity: null
name: exp
exist_ok: false
quad: false
linear_lr: false
label_smoothing: 0.0
upload_dataset: false
bbox_interval: -1
save_period: -1
artifact_alias: latest
freeze:
- 0
world_size: 1
global_rank: -1
save_dir: runs\train\exp26
total_batch_size: 16
这些参数是YOLOv7训练配置中的关键选项,用于控制模型训练的各个方面。以下是对每个参数的详细解释:
1. 基本参数
2. 特定训练行为
3. 数据与设备设置
4. 结果保存
5. 高级选项
6. 分布式训练与冻结层
7. 综合说明
这组参数的目的是为训练过程提供高度灵活的配置,既能满足简单任务的需要,又支持复杂的分布式训练和高性能优化。在实际训练中:
- 基础训练任务:仅需设置
weights
、cfg
、data
、hyp
、epochs
等核心参数。 - 高阶优化:可调整
adam
、evolve
、label_smoothing
、multi_scale
等参数。 - 分布式训练:需要配置
local_rank
、world_size
等分布式相关参数。
四、小知识点
1、rank
在分布式训练环境中,rank
通常用来标识每个进程的唯一性。具体而言:
rank = 0
:- 主进程通常会处理以下任务:
- 打印日志信息。
- 记录训练结果和模型权重。
- 进行可视化和监控(例如使用 TensorBoard 或 Weights & Biases)。
- 主进程通常会处理以下任务:
rank = -1
:- 在这种情况下,代码可能会以主进程的方式运行,处理所有训练任务。
特殊性
- 。
- 通过检查
rank
的值,代码能够更加灵活地处理分布式训练和单机训练的不同场景,
总结来说,rank
的不同数值(如 -1
和 0
)用于区分训练过程中的角色和任务,从而优化训练流程,提高效率。
2、W&B 日志
如果启用了 W&B 日志,并且提供了之前训练的权重文件以及 W&B 的运行 ID,代码确实可能会使用上次训练结束时的某些参数。具体行为取决于是否是从中断的训练恢复以及训练脚本如何设计。以下是详细解释:
2.1、恢复训练的逻辑
-
权重文件的作用
- 当提供权重文件时,代码会尝试从权重文件中加载模型参数,同时可能加载其他附加信息,例如:
- 模型状态(如网络权重)。
- 优化器状态(如学习率、动量)。
- 训练状态(如当前轮次、已用的学习率调度器参数)。
- W&B 的运行 ID(如
wandb_id
)。
示例代码中:
run_id = torch.load(weights, map_location=device).get('wandb_id') if weights.endswith('.pt') and os.path.isfile(weights) else None
- 当提供权重文件时,代码会尝试从权重文件中加载模型参数,同时可能加载其他附加信息,例如:
-
W&B 的作用
-
==W&B 平台不仅记录了超参数和训练指标,还可以在恢复训练时动态调整训练配置。==例如:
- 恢复上次运行的训练超参数(如学习率、批量大小)。
- 调整训练轮数,使新训练从中断的地方继续。
-
示例代码中:
weights, epochs, hyp = opt.weights, opt.epochs, opt.hyp # WandbLogger might update weights, epochs if resuming
- 这部分逻辑允许 W&B 在恢复训练时更新权重路径、总训练轮数和超参数。
-
- 2、
- 是的,如果是恢复训练:
- 如果启用了 W&B 且提供了权重文件,代码可能会使用权重文件中保存的优化器状态、学习率、动量等。
- W&B 的运行 ID 还可能让训练自动恢复中断点(如继续从上次中断的轮次训练)。
- 此外,超参数
hyp
和权重weights
也可能被动态更新为上次的值。
- 否,如果是从头开始训练:
- 如果权重文件不存在,或者权重文件中没有
wandb_id
,训练不会从之前的状态恢复。 - 在这种情况下,训练将根据当前提供的超参数和配置文件从头开始。
- 如果权重文件不存在,或者权重文件中没有
- 3、关键点
- 是否使用上次训练的参数 取决于以下条件:
- 是否提供了有效的权重文件。
- 权重文件中是否包含
wandb_id
或其他恢复所需的信息。 - 是否启用了 W&B,并且允许恢复运行。
- 如果希望明确从头开始训练,可以:
- 禁用 W&B(通过设置
wandb: None
或者命令行参数关闭)。 - 不提供权重文件,或者提供的是没有附加状态的预训练权重。
- 禁用 W&B(通过设置
- 4、实际应用中的恢复训练
场景 1:从中断点恢复
- 权重文件保存了上次训练结束时的状态(模型权重、优化器状态、W&B ID 等)。
- 训练从中断的轮次继续,并沿用之前的学习率、动量等。
场景 2:从头开始训练
- 即使启用了 W&B,但不提供权重文件或运行 ID,训练会从头开始。
- 此时,W&B 仍会记录新的训练过程,但不会恢复旧的参数。
场景 3:加载预训练权重但重新训练
- 如果权重文件中没有附加状态(如优化器状态),则只加载网络权重,训练其他参数(如学习率)会根据当前配置重新初始化。
总结
启用 W&B 日志是否会使用上次训练的参数,取决于是否提供了权重文件,以及权重文件中是否包含恢复训练所需的信息。如果是明确恢复训练(提供了 wandb_id
和权重),代码会自动使用上次训练的参数,并从中断的地方继续训练;如果从头开始训练,则不会使用上次的参数。
3、优化器
优化器是深度学习中用于调整模型参数(权重和偏置)的算法。它通过最小化损失函数,帮助模型更好地拟合数据。优化器的选择会显著影响模型的收敛速度、稳定性和最终的效果。
优化器的作用
- 梯度更新: 根据损失函数对参数计算的梯度,优化器决定如何调整参数。
- 学习率控制: 控制每次参数更新的幅度。
- 动态调整: 一些优化器可以根据梯度大小、自适应调整学习率。
SGD 与 Adam 的对比
其他常用优化器
如何选择优化器?
- 简单任务:
- 小数据集、简单模型:可以使用 SGD 或 SGD+Momentum。
- 复杂任务:
- 深度网络、大数据量:推荐 Adam 或 AdamW。
- 稀疏特征:
- 使用 Adagrad 或 RMSProp。
- 序列数据(如 RNN):
- 使用 RMSProp 或 Adam。
实际工作中的应用
- 深度网络训练: Adam 是首选,因为它收敛速度快,对超参数较为鲁棒。
- 对精度要求高的任务: SGD(+Momentum)更适合,尽管训练慢,但可能带来更高的精度。
- 大模型(如 BERT、Transformer): AdamW 表现优秀,因为它结合了权重衰减机制。
4、一阶动量与二阶动量
一阶动量和二阶动量的概念
在优化算法中,一阶动量和二阶动量是用来描述梯度变化的重要统计信息,目的是通过历史梯度信息提高模型优化的效率和稳定性。
5、Adam简单流程
是的,ADAM(Adaptive Moment Estimation)的整个优化过程可以概括为如下步骤:
1. 计算损失函数的梯度
每次通过前向传播计算出损失函数 L ( θ ) L(\theta) L(θ),然后通过反向传播计算参数 θ \theta θ 的梯度:
g t = ∇ θ L ( θ t ) g_t = \nabla_\theta L(\theta_t) gt=∇θL(θt)
- g t g_t gt:表示第 t 步的梯度。
2. 计算一阶动量(梯度的指数加权平均)
一阶动量 m t m_t mt 是梯度的移动平均,用来表示梯度的历史累积方向,类似于动量优化器中的动量项:
m t = β 1 m t − 1 + ( 1 − β 1 ) g t m_t = \beta_1 m_{t-1} + (1 - \beta_1) g_t mt=β1mt−1+(1−β1)gt
- β 1 \beta_1 β1:一阶动量的衰减系数,通常取 β 1 = 0.9 \beta_1 = 0.9 β1=0.9。
- m t m_t mt:表示梯度的历史累积(方向上的趋势)。
- 如果 β 1 \beta_1 β1 较大,算法更重视历史梯度;如果 β 1 \beta_1 β1 较小,更重视当前的梯度。
偏差修正:为了解决初始时动量值偏小的问题,对 m t m_t mt 进行偏差修正:
m ^ t = m t 1 − β 1 t \hat{m}_t = \frac{m_t}{1 - \beta_1^t} m^t=1−β1tmt
3. 计算二阶动量(梯度平方的指数加权平均)
二阶动量 $v_t $用来反映梯度波动的幅度,它是梯度平方的移动平均,自适应调整学习率:
v t = β 2 v t − 1 + ( 1 − β 2 ) g t 2 v_t = \beta_2 v_{t-1} + (1 - \beta_2) g_t^2 vt=β2vt−1+(1−β2)gt2
- β 2 \beta_2 β2:二阶动量的衰减系数,通常取 β 2 = 0.999 \beta_2 = 0.999 β2=0.999。
- v t v_t vt:表示梯度波动的历史趋势(梯度平方的平均值)。
偏差修正:同样对 v t v_t vt 进行偏差修正:
v ^ t = v t 1 − β 2 t \hat{v}_t = \frac{v_t}{1 - \beta_2^t} v^t=1−β2tvt
4. 根据一阶和二阶动量计算更新方向
将一阶动量 m ^ t \hat{m}_t m^t 表示的方向和二阶动量 v ^ t \hat{v}_t v^t 表示的幅度结合起来,计算出更新步长:
- η \eta η:基础学习率(用户指定的全局学习率)。
- ϵ \epsilon ϵ:一个很小的常数(如 1 0 − 8 10^{-8} 10−8),防止分母为零。
- 公式中的分母会动态调整步长,梯度波动较大时步长减小,梯度波动较小时步长增大。
5. 更新参数
根据上一步计算的更新方向 Δ θ t \Delta\theta_t Δθt 对参数进行更新:
θ t = θ t − 1 + Δ θ t \theta_t = \theta_{t-1} + \Delta\theta_t θt=θt−1+Δθt
即:
θ t = θ t − 1 − η m ^ t v ^ t + ϵ \theta_t = \theta_{t-1} - \eta \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} θt=θt−1−ηv^t +ϵm^t
关键点总结
- 梯度计算: 损失函数的梯度 g t g_t gt 是优化的基础。
- 一阶动量(方向): 表示梯度的移动平均,提供“方向上的趋势”。
- 二阶动量(幅度): 表示梯度波动的平方平均,调整“步长的大小”。
- 自适应调整: 使用二阶动量动态调节每个参数的学习率。
- 参数更新: 综合一阶动量(方向)和二阶动量(步长)进行最终更新。
6、反向传播
您的问题非常好,核心在于理解 梯度传播(backpropagation) 的过程。在神经网络训练过程中,损失函数不仅仅是根据最后一层的参数 w 和 b 来计算的,它实际上是通过网络的每一层(包括卷积层、全连接层等)来进行计算的。梯度的传播会通过反向传播算法(backpropagation)计算出每一层的梯度,从而更新每一层的参数。
损失函数与梯度的计算
首先,您提到的损失函数是:
L = ∑ ( w ⋅ x + b − y true ) 2 L = \sum (w \cdot x + b - y_{\text{true}})^2 L=∑(w⋅x+b−ytrue)2
这是一个典型的 均方误差(MSE) 损失函数。这个损失函数只会对最后的全连接层的权重 w 和偏置 b 产生直接的影响,确实,直接计算得到的梯度是这些参数的梯度。
但是,整个网络的训练是一个 端到端 的过程,损失函数不仅影响最后一层的参数,还会通过链式法则将影响逐层传播,最终影响到前面层(如卷积层、激活层等)的参数。
反向传播(Backpropagation)
,下面详细讲解:
- 前向传播(Forward Pass)
在神经网络训练的前向传播过程中,输入数据通过每一层,最终通过最后一层输出一个预测结果。假设是一个回归问题,最终预测为 y pred y_{\text{pred}} ypred,损失函数计算的是预测值与真实值之间的差异。
- 对于多层网络,前向传播会将输入数据通过一系列的层(包括卷积层、池化层、激活函数、全连接层等),得到输出。
- 损失函数
假设我们使用的是 均方误差(MSE)损失函数,其形式为:
L = 1 2 ∑ ( y pred − y true ) 2 L = \frac{1}{2} \sum (y_{\text{pred}} - y_{\text{true}})^2 L=21∑(ypred−ytrue)2
这里的损失函数计算的是最后输出层的预测值与真实标签之间的差异。注意,这里的 $y_{\text{pred}} $是网络最终输出的值,它依赖于整个网络的计算路径(从输入到输出层)。
- 反向传播(Backward Pass)
反向传播的过程是通过链式法则来计算每一层的梯度。链式法则的核心思想是:如果 z = f ( x ) z = f(x) z=f(x),那么:
∂ L ∂ x = ∂ L ∂ z ⋅ ∂ z ∂ x \frac{\partial L}{\partial x} = \frac{\partial L}{\partial z} \cdot \frac{\partial z}{\partial x} ∂x∂L=∂z∂L⋅∂x∂z
在神经网络中,假设最终损失函数 LL 依赖于网络的输出 y pred y_{\text{pred}} ypred,而 y pred y_{\text{pred}} ypred 又是由上一层的输出 a n − 1 a_{n-1} an−1 和权重矩阵 $W_{n-1} $等共同计算得到的,整个反向传播的过程会通过这些层依次计算梯度。
- 对于 最后一层的权重 W n W_n Wn,其梯度是直接根据损失函数计算得到的。
- 然后,损失函数对上一层的梯度会影响到前一层的梯度,依此类推。
在每一层,反向传播都会计算出该层参数(如权重和偏置)的梯度,这些梯度是损失函数对该层输出的 偏导数。
以简单的全连接层为例
假设一个简单的全连接层,输入是 x,权重是 W,偏置是 b,输出是 y pred = W x + b y_{\text{pred}} = W x + b ypred=Wx+b。
- 假设损失函数是均方误差: L = 1 2 ( y pred − y true ) 2 L = \frac{1}{2}(y_{\text{pred}} - y_{\text{true}})^2 L=21(ypred−ytrue)2。
- 对于 权重 W,损失函数对权重的梯度是:
- 对于 偏置 b,损失函数对偏置的梯度是:
这两个梯度会用来更新权重和偏置。
- 前一层的梯度计算
对于前面的一层,比如是卷积层,损失函数并没有直接对这些层的参数计算梯度,但是这些层的输出会影响到后面层的输出,因此它们的梯度也是通过反向传播计算得到的。
这个梯度会反向传播到前一层,依次计算出前面每一层的梯度。
卷积层的梯度计算
假设前面是一个卷积层,其输出是 a conv a_{\text{conv}} aconv,它影响了后面层的输出。在反向传播过程中,我们会计算卷积层的 梯度,并将其用于更新卷积核(权重)。
- 假设卷积层的输出$a_{\text{conv}} 是由前一层的输出经过卷积运算得到的: = = 是由前一层的输出经过卷积运算得到的:== 是由前一层的输出经过卷积运算得到的:==a_{\text{conv}} = W_{\text{conv}} \cdot x_{\text{input}} + b$==损失函数的梯度会通过反向传播依次计算出每个卷积核的梯度。
总结
- 损失函数(如均方误差)主要用来计算网络的输出与真实标签之间的差异,但它 不只 影响最后一层的参数。
- 反向传播通过链式法则计算每一层(包括卷积层、激活层等)的梯度。
- 通过每一层的梯度,我们可以更新网络中 所有层的参数,包括卷积层、全连接层等。
- 损失函数的梯度计算并不仅限于 w 和 b,而是逐层传播,最终更新所有层的参数。
反向传播是一个非常重要的过程,它确保了通过损失函数的反馈,每一层的参数都得到了适当的调整,从而使得整个网络能够在训练过程中逐步减小损失并优化性能。
五、优化器参数
SGD (
Parameter Group 0
dampening: 0
differentiable: False
foreach: None
fused: None
initial_lr: 0.01
lr: 0.01
maximize: False
momentum: 0.937
nesterov: True
weight_decay: 0
Parameter Group 1
dampening: 0
differentiable: False
foreach: None
fused: None
initial_lr: 0.01
lr: 0.01
maximize: False
momentum: 0.937
nesterov: True
weight_decay: 0.0005
Parameter Group 2
dampening: 0
differentiable: False
foreach: None
fused: None
initial_lr: 0.01
lr: 0.01
maximize: False
momentum: 0.937
nesterov: True
weight_decay: 0
)
六、关于 p t pt pt文件读取
torch.load(weights, map_location=device)
这行代码通常用于加载一个 PyTorch 模型的 checkpoint(.pt
或 .pth
文件),并将其加载到指定的设备(如 CPU 或 GPU)。这个 .pt
文件不仅包含模型的权重(即参数),还可以包含很多其他的内容,具体取决于保存时包含了哪些信息。
1、.pt
文件中包含的内容:
- :
- 这是最基本的内容,包含模型各层的权重和偏置(
state_dict
)。 - 格式通常是一个字典(
state_dict
),字典的键是模型中各个层的名称,值是该层的权重(tensor)。
- 这是最基本的内容,包含模型各层的权重和偏置(
- :
- 如果保存了优化器的状态,那么 checkpoint 会包含优化器的状态字典。这个字典中包含了优化器的超参数、动量、学习率等信息,帮助在训练中断后恢复优化器的状态。
- 优化器状态通常是包含
state
和param_groups
的字典。
- :
- 。
- :
- 其他训练相关信息:
- 除了上面提到的内容,checkpoint 还可以包含其他的元数据(如训练时使用的损失函数、开始时间、日志等),这通常是通过将这些信息封装到字典里一并保存的。
2、示例:保存和加载 checkpoint 的常见方式
保存时:
checkpoint = {
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'lr_scheduler_state_dict': scheduler.state_dict(),
'loss': loss,
}
torch.save(checkpoint, 'checkpoint.pth')
加载时:
checkpoint = torch.load('checkpoint.pth', map_location=device)
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
scheduler.load_state_dict(checkpoint['lr_scheduler_state_dict'])
epoch = checkpoint['epoch']
loss = checkpoint['loss']
2.1具体解析:
model.state_dict()
: 保存的是模型的所有参数(如卷积核、全连接层权重等)。optimizer.state_dict()
: 保存的是优化器的状态,包括动量、参数组等。lr_scheduler.state_dict()
: 保存的是学习率调度器的状态,包含学习率的调整历史和当前状态。epoch
和loss
: 保存当前训练的轮次和最后的损失值。
2.2加载时的注意事项:
- 在加载时,需要确保模型结构与保存时一致(即
model.load_state_dict()
要加载的是匹配的权重)。 - 优化器和学习率调度器的状态可以用于恢复训练,但如果不需要恢复训练,加载时可以忽略它们。
2.3其他可能包含的信息:
- 模型的超参数(hyperparameters):如学习率、批大小、优化器类型等,通常是在训练脚本中作为配置保存。
- 模型的评估指标:例如训练集和验证集的准确率、损失值等,可以用于后续评估。
总之,除了权重信息外,checkpoint 文件还可以包含很多有用的训练状态信息,这些信息有助于在训练过程中断后恢复训练状态。
七、代码语法层面解读
1、np.concatenate(dataset.labels, 0)[:, 0].max()
mlc = np.concatenate(dataset.labels, 0)[:, 0].max() # 得到max label class
2、多尺度训练
if opt.multi_scale:
# 随机生成尺寸,// gs * gs 确保生成的 sz 是 gs 的整数倍。
sz = random.randrange(imgsz * 0.5, imgsz * 1.5 + gs) // gs * gs
sf = sz / max(imgs.shape[2:]) # 计算随机尺寸(h,w)与最大尺寸的比例(缩放因子)
if sf != 1:
# 通过 math.ceil() 函数取上整,确保新尺寸是 gs 的整数倍。
ns = [math.ceil(x * sf / gs) * gs for x in imgs.shape[2:]]
# mode='bilinear' 表示使用双线性插值算法进行缩放。双线性插值是一种平滑的插值方法,常用于图像缩放。
imgs = F.interpolate(imgs, size=ns, mode='bilinear', align_corners=False)
八、双线性插值
双线性插值(Bilinear Interpolation)是一种常用的图像插值方法,主要用于图像缩放(resize)或者旋转等操作中,尤其在处理图像大小变化时,能够生成较为平滑的图像。
1、双线性插值的概念
双线性插值是对单线性插值(线性插值)的扩展,它在两个方向上同时进行插值,即 x轴方向和y轴方向。与单线性插值仅考虑一个方向的相邻点不同,双线性插值考虑了四个邻近像素,通过对这四个像素值的加权平均来推算新像素值。
数学原理
假设我们有一个目标图像的像素位置 (x, y),而这个像素位置位于原始图像上的四个邻近像素点的中间。我们可以通过以下步骤来计算目标像素值。
设原始图像中四个邻近像素点为:
- (x1,y1)(x_1, y_1): 左上角像素
- (x2,y1)(x_2, y_1): 右上角像素
- (x1,y2)(x_1, y_2): 左下角像素
- (x2,y2)(x_2, y_2): 右下角像素
这些像素的值分别为:
- Q11Q_{11} 对应于 (x1,y1)(x_1, y_1)
- Q21Q_{21} 对应于 (x2,y1)(x_2, y_1)
- Q12Q_{12} 对应于 (x1,y2)(x_1, y_2)
- Q22Q_{22} 对应于 (x2,y2)(x_2, y_2)
目标位置 (x,y)(x, y) 位于这四个点的矩形区域内,我们可以通过以下步骤进行双线性插值:
- 在x方向上进行插值:
首先,沿着水平(x轴)方向,使用线性插值来计算两对水平像素的加权平均。
对于 y1y_1 行:
R1=(x2−x)⋅Q11+(x−x1)⋅Q21x2−x1R_1 = \frac{(x_2 - x) \cdot Q_{11} + (x - x_1) \cdot Q_{21}}{x_2 - x_1}
对于 y2y_2 行:
R2=(x2−x)⋅Q12+(x−x1)⋅Q22x2−x1R_2 = \frac{(x_2 - x) \cdot Q_{12} + (x - x_1) \cdot Q_{22}}{x_2 - x_1}
- 在y方向上进行插值:
然后,沿着垂直(y轴)方向,对上述计算得到的 R1R_1 和 R2R_2 进行线性插值。
P(x,y)=(y2−y)⋅R1+(y−y1)⋅R2y2−y1P(x, y) = \frac{(y_2 - y) \cdot R_1 + (y - y_1) \cdot R_2}{y_2 - y_1}
其中,P(x,y)P(x, y) 就是目标像素位置 (x,y)(x, y) 的插值结果。
可视化说明
假设目标像素 (x,y)(x, y) 位于原始图像四个邻近像素的矩形区域内,那么双线性插值可以看作是两个阶段的插值:
- 第一阶段(x方向插值):对于每一行 y1y_1 和 y2y_2,分别在水平方向(x轴)对两个像素进行插值,得到 R1R_1 和 R2R_2。
- 第二阶段(y方向插值):然后再沿着垂直方向(y轴)对 R1R_1 和 R2R_2 进行插值,从而得到最终的目标像素值。
举个例子
假设原始图像中四个相邻像素值如下:
- Q11=100Q_{11} = 100
- Q21=150Q_{21} = 150
- Q12=200Q_{12} = 200
- Q22=250Q_{22} = 250
我们希望计算目标像素 (x,y)(x, y) 的值,其中 xx 和 yy 分别位于两个像素之间。例如,假设 x=1.5x = 1.5, y=1.5y = 1.5。
-
首先,进行水平方向插值:
R1=(2−1.5)⋅100+(1.5−1)⋅1502−1=125R_1 = \frac{(2 - 1.5) \cdot 100 + (1.5 - 1) \cdot 150}{2 - 1} = 125
R2=(2−1.5)⋅200+(1.5−1)⋅2502−1=225R_2 = \frac{(2 - 1.5) \cdot 200 + (1.5 - 1) \cdot 250}{2 - 1} = 225
-
然后,进行垂直方向插值:
P(1.5,1.5)=(2−1.5)⋅125+(1.5−1)⋅2252−1=175P(1.5, 1.5) = \frac{(2 - 1.5) \cdot 125 + (1.5 - 1) \cdot 225}{2 - 1} = 175
因此,目标位置 (x,y)=(1.5,1.5)(x, y) = (1.5, 1.5) 对应的像素值是 175。
应用场景
- 图像缩放:双线性插值常用于图像大小调整,尤其是缩放操作,如将图像从较小尺寸缩放到较大尺寸或从大尺寸缩小到小尺寸时,能平滑过渡,避免明显的锯齿。
- 图像旋转:当图像进行旋转时,也会涉及到像素的重新分布,通常会使用双线性插值来计算旋转后的像素值。
- 视频帧插值:在视频处理中,双线性插值也常用于将图像帧的尺寸转换到统一的大小。
优缺点
- 优点:双线性插值比最近邻插值(仅取最近的一个像素值)产生的图像效果更平滑,避免了锯齿现象。
- 缺点:相比于更高阶的插值方法(如立方插值),双线性插值可能会失去一些细节,且在大幅度缩放时可能产生模糊现象。
总结来说,双线性插值是一种基于邻近四个像素值的加权平均的方法,常用于图像缩放和旋转操作,能够生成相对平滑的图像。