【机器学习入门】使用YOLO模型进行物体检测

news2024/12/27 13:09:11

系列文章目录

第1章 专家系统
第2章 决策树
第3章 神经元和感知机
识别手写数字——感知机
第4章 线性回归
第5章 逻辑斯蒂回归和分类
第5章 支持向量机
第6章 人工神经网络(一)
第6章 人工神经网络(二) 卷积和池化
第6章 使用pytorch进行手写数字识别


文章目录

  • 系列文章目录
  • 前言
  • 一、物体检测技术
  • 二、YOLO模型
    • 模型设计思路
    • YOLO模型的损失函数
    • 缩微YOLO模型的网络结构
  • 三、实现缩微YOLO模型
    • 定义模型类TinyYoloNetwork
    • 定义模型的前向计算过程
    • 定义YOLO 输出 (yolo) 方法
    • 加载模型权值数据
      • `load_weight_to` 函数
      • `load_weights` 函数
    • 处理真实图像
  • 总结


前言

在此之前,我们都是用模型解决简单的二分类、多分类问题或者是回归问题。这一篇开始解决稍微复杂的问题:物体检测。
不同于图片分类时的输入图片仅包含一个物体,并且位于图片中央,占据图片的大部分未知;物体检测的任务恰与之相反,任务目标主要是识别图像中的物体位置,用矩形框标注出物体的位置,同时给出物体类别。
在这里插入图片描述
物体检测技术,已经广泛应用于人脸检测 \行人检测系统 \辅助驾驶 车辆检测等等中.


一、物体检测技术

在传统方法中,物体识别可以拆分为两个步骤。第1步是从图像中识别局部特征,物体由局部特征组合构成。第2步是找到能够组合成物体的局部特征,判断它们所属的物体类别,进而确定物体的位置和大小。
图像的局部特征通常是将局部图像颜色和梯度分布描述为向量,相似的纹理或者形状通常具有类似的分布。将物体描述为局部特征组合的方法大致可以分为两类,一类方法类似于自然语言处理中的"词袋模型"。词袋模型将句子和文章描述为单词出现的频率,忽略了单词之间的位置关系。我们也可以忽略局部特征之间的位置关系,将物体视为局部特征的无序组合。另一类方法则把位置关系作为约束条件,那么寻找能够构成物体的特征组合就变成了有约束的优化问题。
人们在使用神经网络解决物体检测问题的时候,最初也采取了分步的策略。由于神经网络已经解决图像分类问题,于是可以将图片的局部拿来进行分类。只要用分类器扫描整幅图像的各个位置,就可以找到物体并将它的类别识别出来。暴力扫描的方式显然是效率低下的,于是人们提出了各种算法来筛选可能存在物体的候选框,减少候选框的数量来提高算法的性能。另一种策略是单步的方法,也叫作端到端的方法,即将整幅图像直接作为输入,同时输出物体框和类别,没有中间步骤。两种方法各有千秋。分步方法通常具有更高的准确率,可以处理大量小物体,但是提取候选框的过程中无法利用物体类别信息,进行物体分类时无法利用图像其他位置的背景信息。端到端的单步方法实现起来更为直接,运行速度通常更快,在识别物体时能够利用整幅图像的背景信息,但是有时会漏掉一些数量较多的小物体。

物体检测算法的发展经历了从传统方法到深度学习方法的转变。以下是一些重要的算法和它们的特点:

  1. 传统物体检测算法
    Haar特征+Adaboost:2001年Viola和Jones提出了基于Haar特征和Adaboost的快速人脸检测方法。这种方法通过集成多个弱分类器来构建一个强分类器,能够实现实时检测。
    HOG(Histogram of Oriented Gradients):HOG是一种描述图像局部特征的方法,通过统计图像局部区域的梯度方向直方图来构建特征,广泛应用于物体检测和行人检测。
    SVM(Support Vector Machine):支持向量机是一种监督学习模型,用于分类和回归分析。在物体检测中,SVM可以作为分类器来识别图像中的目标。
  2. 深度学习物体检测算法
    R-CNN(Regions with CNN features):R-CNN首先使用选择性搜索(Selective Search)提取候选区域,然后使用CNN提取特征,最后通过SVM进行分类。R-CNN开启了深度学习在物体检测领域的应用。
    Fast R-CNN:Fast R-CNN改进了R-CNN的效率问题,通过RoI(Region of Interest)Pooling层来提取固定大小的特征,并且实现了网络的端到端训练。
    Faster R-CNN:Faster R-CNN引入了RPN(Region Proposal Network)来自动生成高质量的候选区域,进一步提高了检测速度。
    YOLO(You Only Look Once):YOLO将物体检测问题视为一个回归问题,通过单个神经网络直接从图像像素到边界框坐标和类别概率的映射,实现了实时检测。
    SSD(Single Shot MultiBox Detector):SSD在不同尺度的特征图上进行检测,能够同时处理不同大小的目标,也适用于实时检测场景。

常用的物体检测数据集

  • PASCAL VOC:PASCAL VOC挑战赛是物体检测领域的一个重要基准,提供了丰富的图像和标注,用于评估和训练物体检测算法。
  • COCO(Common Objects in Context):COCO数据集包含了大量图像,每张图像中都包含多个目标,提供了更加复杂和多样的场景,是目前最流行的物体检测数据集之一。
  • ImageNet:虽然ImageNet主要以分类任务著称,但它也提供了物体检测的挑战,即ImageNet Large Scale Visual Recognition Challenge(ILSVRC)中的物体检测任务。
  • Objects365:由旷视科技发布的Objects365数据集是目前最大的物体检测数据集之一,包含63万张图像,覆盖365个类别,提供了更加丰富和多样的数据用于训练和测试物体检测算法。

这些算法和数据集共同推动了物体检测技术的发展和进步,使得计算机视觉系统能够更好地理解和解释图像内容。随着技术的不断演进,未来可能会出现更多高效、准确的物体检测算法和更加丰富多样的数据集。

二、YOLO模型

YOLO模型的全称是You Only Look Once,也就是说,神经网络模型只“看”一次,就输出物体检测的结果,是一种端到端的方法。
前反馈神经网络的输出长度一般是固定的(带有反馈的循环神经网络确实可以产生不固定长度的输出,也可以用于物体检测任务),然而一张图像中物体的数量是不确定的,如何将数量不确定的物体用固定长度的输出向量表示出来,这就是YOLO的关键。

模型设计思路

模型的思路是:将图像划分为大小相等的网格,每个网格负责输出中心点落在其中的物体框。假设物体类别数量为 K K K,那么,每个物体框可以用一个长度为 5 + K 5+K 5+K的向量表示,即 ( t x , t y , t w , t h , c , p 1 , p 2 , . . . , p K ) (t_x,t_y,t_w,t_h,c,p_1,p_2,...,p_K) (tx,ty,tw,th,c,p1,p2,...,pK)。前4个元素分别用来计算物体框的中心坐标和物体框的尺寸,第5个元素用于表示物体框中是否识别出物体的置信度(confidence);剩余 K K K个元素表示物体属于各个类别的概率。如果将图片切割为 S × S S \times S S×S个网格,那么神经网络的输出维度为 S × S × ( 5 + K ) S \times S \times (5+K) S×S×(5+K)
实际物体框的形状并不是完全随机的,如果对图片数据集中的标记进行统计,可以发现,物体框总是接近一些“常见”的尺寸。通过对训练数据集中的物体框尺寸进行聚类,可以得到若干个最常见的物体框尺寸,其他物体框可以看作这些常见物体框上进行“微调”的结果。这些聚类得出的常见物体框被称作“先验物体框”(prior)或“锚定物体框”(anchor)。
假设取A个先验物体框,那么神经网络的输出维度应该为 S × S × A × ( 5 + K ) S \times S \times A \times (5+K) S×S×A×(5+K),即每个网格输出A个物体框,分别基于先验物体框进行微调。

物体框的位置计算方式
设每个网格的长宽为单位1,每个网格输出的前两维 ( t x , t y ) (t_x,t_y) (tx,ty)经过Sigmoid函数之后,变成 ( 0 , 1 ) (0,1) (0,1)之间的数值,用来表示物体中心距离网格左上角的偏移量, 加上网格左上角的坐标 ( c x , c y ) (c_x,c_y) (cx,cy),就得到了物体框中心的坐标 ( b x , b y ) = ( c x + σ ( t x ) , c y + σ ( t y ) ) (b_x,b_y)=(c_x+\sigma(t_x),c_y+\sigma(t_y)) (bx,by)=(cx+σ(tx),cy+σ(ty))。对于物体框的尺寸,网格输出的第3、4维 ( t w , t h ) (t_w,t_h) (tw,th)经过指数函数,得到正实数值 ( e t w , e t h ) (e^{t_w},e^{t_h}) (etw,eth),再乘以先验物体框尺寸 ( p w , p h ) (p_w,p_h) (pw,ph),就得到了预测物体框的尺寸 ( b w , b h ) = ( p w e t w , p h e t h ) (b_w,b_h)=(p_we^{t_w},p_he^{t_h}) (bw,bh)=(pwetw,pheth)
在这里插入图片描述

YOLO模型的损失函数

YOLO的损失函数分为3个部分:物体框位置误差 识别物体的置信度误差 物体类别的误差.
物体框位置误差使用朴素的均方误差 , 只在有物体的框中计算位置误差. 我们用标记变量 1 i o b j \mathbb{1}_i^{obj} 1iobj表示编号为i的物体框中是否有物体,当有物体时该变量为1,反之为0. 物体框位置的预测值用 ( x i ^ , y i ^ , w i ^ , h i ^ ) (\hat{x_i},\hat{y_i},\hat{w_i},\hat{h_i}) (xi^,yi^,wi^,hi^)表示, 真实物体框尺寸表示为 ( x i , y i , w i , h i ) ({x_i},{y_i},{w_i},{h_i}) (xi,yi,wi,hi).
于是:
L coord  = λ coord  ∑ i 1 i obj  [ ( x i − x ^ i ) 2 + ( y i − y ^ i ) 2 ] + λ coord  ∑ i 1 i obj  [ ( w i − w ^ i ) 2 + ( h i − h ^ i ) 2 ] \begin{aligned} L_{\text {coord }}= & \lambda_{\text {coord }} \sum_{i} \mathbb{1}_{i}^{\text {obj }}\left[\left(x_{i}-\hat{x}_{i}\right)^{2}+\left(y_{i}-\hat{y}_{i}\right)^{2}\right] \\ & +\lambda_{\text {coord }} \sum_{i} \mathbb{1}_{i}^{\text {obj }}\left[\left(\sqrt{w_{i}}-\sqrt{\hat{w}_{i}}\right)^{2}+\left(\sqrt{h_{i}}-\sqrt{\hat{h}_{i}}\right)^{2}\right] \end{aligned} Lcoord =λcoord i1iobj [(xix^i)2+(yiy^i)2]+λcoord i1iobj [(wi w^i )2+(hi h^i )2]
其中, λ c o o r d \lambda_{coord} λcoord是用来调整位置误差所占比例的系数.

误差的第2部分是检测到物体的置信度误差. 在实际图片中, 有标记物体的网格但愿是户量要远小于没有物体的网格单元数量, 这实际上是一个不平衡的二分类问题. 为了改进平衡性, 防止误差函数引导网络输出的置信度向0靠近, 我们需要减弱没有物体的网格单元的误差惩罚. 这是通过引入系数 λ n o o b j \lambda _{noobj} λnoobj实现的,这个系数小于1. 下面是置信度误差, 其中 c i ^ \hat{c_i} ci^ 表示物体框 i i i 有物体的预测值, 1 i n o o b j = 1 − 1 i o b j \mathbb{1}_i^{noobj}=1-\mathbb{1}_i^{obj} 1inoobj=11iobj表示物体框 i i i中没有物体的标记变量.
L c o n f i d e n c e = − ∑ i 1 i o b j ln ⁡ c i ^ − ∑ i 1 i n o o b j ln ⁡ ( 1 − c i ^ ) L_{confidence}=-\sum_i \mathbb{1}_i ^{obj} \ln{\hat{c_i}}-\sum_i \mathbb{1}_i^{noobj} \ln{(1-\hat{c_i})} Lconfidence=i1iobjlnci^i1inoobjln(1ci^)

第3部分是物体分类误差,其中, p i , k p_{i,k} pi,k是第 i i i个物体属于类别 k k k的概率, 为神经网络的Soft Max输出值, k i k_i ki 为物体框里面的物体的真实类标签. 物体分类误差只在有物体的框中计算.
L c l a s s = ∑ i 1 o b j ln ⁡ p i , k i L_{class}=\sum_i \mathbb{1}_obj \ln{p_{i,k_i}} Lclass=i1objlnpi,ki

YOLO模型最终的损失函数为上面3部分的和 .
L c o o r d + L c o n f i d e n c e + L c l a s s L_{coord}+L_{confidence}+L_{class} Lcoord+Lconfidence+Lclass

缩微YOLO模型的网络结构

YOLO模型的神经网络结构是一个以卷积为主的多层前馈神经网络。这里我们介绍一个缩微版本模型,这个模型叫作YOLO v2tiny (模型配置文件下载地址),它是使用Pascal VOC数据集(数据集主页)训练得到的,可以识别20个类别的不同物体。
采用缩微版本,我们可以在CPU机器上快速实现物体识别,甚至可以使用CPU机器实现模型训练。Pascal VOC数据集包含 10000多幅图片,图片中包含了 20000多个20个不同类别的物体。在该数据集上完整训练一轮,CPU机器大约耗时十几个到几十个小时不等。而采用GPU 则能够极大地加速训练过程,以几十倍的比例压缩训练时间。
这个网络有9个卷积层,除了最后一个卷积层,每个卷积层后都增加了批归一化处理,用来加速训练过程,防止过拟合和梯度爆炸;激活函数采用了带泄露的修正线性单元(LeakyReLU),当卷积之后有池化层时,激活函数安排在池化层之后,对于没有池化层的卷积,激活函数直接作用于卷积层输出。
网络的前8个卷积层采用了大小为3×3的卷积核,较小的卷积核级联叠加,可以实现与较大的卷积核相似的效果,但是参数量较少,降低了模型的复杂度。比如,5×5的卷积核, 每个卷积核需要25个权值; 如果采用两个 3×3 的卷积核级联处理, 同样可以实现每个输出单元覆盖 5×5 范围内的输入, 但是, 卷积核的权值数量减少到 2×3×3=18 个. 压缩权值数量可以有效避免模型参数规模过度增长, 从而加速训练过程, 避免过拟合.

在这里插入图片描述
为了保证卷积输出结果尺寸的确定性, 卷积层都在输入的行列方向各补齐一行(或者一列)数据, 经过数据补齐, 3x3 的卷积输出尺寸就不会发生变化, 否则, 没经过一次3x3 卷积, 行列数都会各减少2, 控制网络输出尺寸会变得更加复杂. 因此, 数据补齐是一个简化网络设计的技巧.
卷积输出的尺寸不发生变化,只有通道数量不断增加。这是因为随着卷积和池化的不断级联叠加处理,每个神经元所感知的图像区域不断扩大,描述图像局部特征需要的容量(特征向量的长度)也相应增长。卷积结果从“通道”这个维度看,每个位置就是一个局部特征向量,向量长度就是通道的数量(即卷积核的数量)。因此,卷积层的输出通道数逐层加倍,从第1层到第7层,卷积层从16个输出通道增加到1024个输出通道。

数据的空间压缩过程由池化层完成。前5层卷积之后的最大池化层,采用2×9大小的核,以2为步长进行扫描,每经过一次这样的池化层,可以将数据长宽尺寸各缩小一半。这样5层过后,数据的尺寸缩小到原来的 1 / 32 1/32 1/32。后面的卷积层和池化层不再继续缩小数据尺寸,采取了步长为1,配合补齐数据,保持了输出数据的尺寸。其中,第6层卷积之后的池化层只在行列方向单侧补齐一行或者一列数据,这样,对于步长为1、尺寸为2×2的池化层,就可以保持输/入输出的尺寸相同。

网络最终输出的数据尺寸是原始输入的1/32。举例来说,如果输入图像大小为320×320像素,那么,输出网格的数量则为10×10。在实际训练过程中,训练图片被调整为 416×416像素作为输入,产生13×13个网格。模型有A=5个先验物体框,所以,每个网格单元产生5个物体框,最终将产生13×13×5个物体框。

值得注意的是最后一个卷积层,这一层卷积核大小为1×1。这是一种特殊的卷积核尺寸,它不进行空间上的数据整合,而是在每个局部的“点”(也就是网格单元)的输入通道维度上进行数据整合。它可以看作输入通道维度上的“全连接层”,用于产生每个网格单元的最终输出。当先验物体框数量为A,物体类别数量为K时,每个网格单元的输出值数量为Ax(5+K)。由于 Pascal VOC数据集中有20类物体,同时,模型聚类产生了5个先验物体框,因此,每个网格单元的输出长度为5×(5+20)=125。网络的最后一个卷积层的输出通道数就是125。

三、实现缩微YOLO模型

接下来使用pytorch实现缩微版本的YOLO模型.

定义模型类TinyYoloNetwork

首先,定义模型类TinyYolo-Network, 在构造函数中定义先验物体框 物体类别数量和网络的各层单元模块.

import torch
import torch.nn as nn

# 缩微YOLO网络模型
class TinyYoloNetwork(nn.Module):
    def __init__(self):
        super(TinyYoloNetwork, self).__init__()
        # 从训练数据集中聚类得到的 先验物体框尺寸
        # 这些是物体框 最有可能的各种尺寸
        anchors = (
            (1.08,1.19),(3.42,4.41),(6.63,11.38),(9.42,5.11),(16.62,10.52)
        )
        self.register_buffer("anchors",torch.tensor(anchors))
        # 物体类别数
        self.num_classes = 20
        # LeakyReLU 作为激活函数, 输入小于0时, 斜率为0.1
        self.relu = torch.nn.LeakyReLU(0.1, inplace=True)
        # 最大值池化层
        self.pool = torch.nn.MaxPool2d(2, stride=2)
        self.lastpool = torch.nn.MaxPool2d(2, 1)
        # 最后一个 池化层之前的数据补齐
        self.pad = torch.nn.ReflectionPad2d((0,1,0,1))
        # 批归一化层和卷积层
        self.norm1 = torch.nn.BatchNorm2d(16)
        self.conv1 = torch.nn.Conv2d(3, 16, 3, stride=1, padding=1, bias=False)

        self.norm2 = torch.nn.BatchNorm2d(32)
        self.conv2 = torch.nn.Conv2d(16, 32, 3, stride=1, padding=1, bias=False)

        self.norm3 = torch.nn.BatchNorm2d(64)
        self.conv3 = torch.nn.Conv2d(32, 64, 3, stride=1, padding=1, bias=False)

        self.norm4 = torch.nn.BatchNorm2d(128)
        self.conv4 = torch.nn.Conv2d(64, 128, 3, stride=1, padding=1, bias=False)

        self.norm5 = torch.nn.BatchNorm2d(256)
        self.conv5 = torch.nn.Conv2d(128, 256, 3, stride=1, padding=1, bias=False)

        self.norm6 = torch.nn.BatchNorm2d(512)
        self.conv6 = torch.nn.Conv2d(256, 512, 3, stride=1, padding=1, bias=False)

        self.norm7 = torch.nn.BatchNorm2d(1024)
        self.conv7 = torch.nn.Conv2d(512, 1024, 3, stride=1, padding=1, bias=False)

        self.norm8 = torch.nn.BatchNorm2d(1024)
        self.conv8 = torch.nn.Conv2d(1024, 1024, 3, stride=1, padding=1, bias=False)

        # 最后一个卷积层
        self.conv9 = torch.nn.Conv2d(1024, len(anchors)*(5+self.num_classes), 1, stride=1, padding=0)

关于上面代码的几点解释:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 批归一化层BatchNorm2d是作用于卷积输出的,在每个通道上独立进行归一化,它的参数是通道数量。
  • 最后一个池化层使用特殊的数据补齐ReflectionPad2d,在行列方向的单侧各补齐一行或者一列数据。

定义模型的前向计算过程

定义模型的前向计算过程,将各个模块连接在一起。

    def forward(self, x, yolo=True):
        # 将各个模块组织 为 神经网络
        x = self.relu(self.pool(self.norm1(self.conv1(x))))
        x = self.relu(self.pool(self.norm2(self.conv2(x))))
        x = self.relu(self.pool(self.norm3(self.conv3(x))))
        x = self.relu(self.pool(self.norm4(self.conv4(x))))
        x = self.relu(self.pool(self.norm5(self.conv5(x))))
        x = self.relu(self.lastpool(self.pad(self.conv6(x))))
        x = self.relu(self.norm7(self.conv7(x)))
        x = self.relu(self.norm8(self.conv8(x)))
        x = self.conv9(x)
        # 从神经网络的输出计算物体框位置和物体类别
        return self.yolo(x)

定义YOLO 输出 (yolo) 方法

下面定义yolo方法,将网络输出重新组织,使其包含每个锚框的位置、宽高、置信度和类别概率(第一部分提到过的形式),使用 torch.cat 将这些信息合并为一个张量,并返回。

# 将卷积层的输出结果转换为物体框的位置、检测到物体的置信度和分类结果
    def yolo(self, x):
        # 神经网络输出的结果形状如下:
        # 各维度依次是批次样本索引,  输出通道, 输出高度, 输出宽度
        n_batch, n_channel, height, width = x.shape
        # 将输出通道拆分为 若干先验物体框
        x = x.view(n_batch, self.anchors.shape[0], -1, height, width)
        # 重新调整 各个维度的顺序如下
        # 样本索引, 先验物体框编号, 网格纵向和横向序号, 物体框维度
        # 其中, 位置及分类输出的维度尺寸是 (5+类别数)
        x = x.permute(0, 1, 3, 4, 2)
        # 准备用于计算物体框位置的 辅助张量
        # 首先是先验物体框的尺寸
        anchors = self.anchors.to(dtype=x.dtype, device=x.device)
        anchor_width, anchor_height = anchors[:, 0], anchors[:, 1]
        # 然后是网格的偏移量
        grid_y, grid_x = torch.meshgrid(
            torch.arange(height, dtype=x.dtype, device=x.device),
            torch.arange(width, dtype=x.dtype, device=x.device),
        )
        # 计算物体框位置 和 物体分类输出, 最后一维各列分别是:
        # 中心位置坐标, 物体框宽高, 检测到物体的置信度, 物体类别概率
        return torch.cat([
            (x[:, :, :, :, 0:1].sigmoid()+grid_x[None, None, :, :, None])/width,
            (x[:, :, :, :, 1:2].sigmoid()+grid_y[None, None, :, :, None])/height,
            (x[:, :, :, :, 2:3].exp()*anchor_width[None, :, None, None, None])/width,
            (x[:, :, :, :, 3:4].exp()*anchor_height[None, :, None, None, None])/height,
            x[:, :, :, :, 4:5].sigmoid(),
            x[:, :, :, :, 5:].softmax(-1),
        ], -1)

在这里插入图片描述
关于softmax(-1)的理解:查到一个不错的解答,点此跳转
简单说,softmax(-1) 表示 softmax 函数将在张量的最后一个维度上操作。在 PyTorch 中,-1 被用作维度的占位符,它告诉函数在输入张量的最后一个维度上应用 softmax 函数。

现在测试一下网络模型是否工作正常,

# 先测试一下网络模型工作是否正常, 填入一个320x320像素的随机图像作为输入, 输出的网格数量为10x10
# 每个网格单元的物体框数量为5, 每个物体框表示为长度为25的向量
x = torch.rand((1, 3, 320, 320))
net = TinyYoloNetwork()
y = net(x)
print(y.shape)

使用Debugger工具追踪,可以得到以下输出,在上面的测试代码中,我们随机生成了一个形状为(1,3,320,320)的张量, 其中第一个维度 1 表示批次大小(batch size),在这里是 1,意味着这个张量代表一个单独的图像样本。第二个维度 3 表示图像的通道数(channels),在这里是 3,对应于 RGB 彩色图像的三个颜色通道:红色(R)、绿色(G)和蓝色(B)。第三个维度 320 表示图像的高度(height),即图像有 320 像素高。第四个维度 320 表示图像的宽度(width),即图像有 320 像素宽。
在这里插入图片描述
在这里插入图片描述

加载模型权值数据

这里我们直接加载YOLO模型的作者提供的预训练好的模型权值文件,直接省去了训练模型的过程。但如果我们需要处理自己的数据集,还是需要在这个的基础上进行增量训练。与从头开始训练相比,这种方法可以节省训练时间。YOLO模型权值文件下载地址:https://pjreddie.com/media/files/yolov2-tiny-voc.weights

# 读取权值的辅助函数
# 从weights中offset位置读取权值到target
def load_weight_to(weights, offset, target):
    n = target.numel()
    target.data[:] = torch.from_numpy(weights[ offset : offset+n]).view_as(target.data)
    return offset+n

import numpy

# 从文件加载训练好的网络权值
def load_weights(network, filename="G:\\BrowserDownload\\2024-03\\yolov2-tiny-voc.weights"):
    with open(filename, "rb") as file:
        # 读取版本号 和 训练状态记录
        header = numpy.fromfile(file, count=4, dtype=numpy.int32)
        # 其余所有值都是网络权值
        weights = numpy.fromfile(file, dtype=numpy.float32)
        idx = 0
        for layer in network.children():
            # 读取卷积层权值
            if isinstance(layer, torch.nn.Conv2d):
                if layer.bias is not None:
                    idx = load_weight_to(weights, idx, layer.bias)
                idx = load_weight_to(weights, idx, layer.weight)
            # 读取批归一化层权值
            if isinstance(layer, torch.nn.BatchNorm2d):
                idx = load_weight_to(weights, idx, layer.bias)
                idx = load_weight_to(weights, idx, layer.weight)
                idx = load_weight_to(weights, idx, layer.running_mean)
                idx = load_weight_to(weights, idx, layer.running_var)

# 调用函数加载权值
load_weights(net)

这段代码提供了两个函数,用于将训练好的 YOLOv2-tiny 模型权重从 Darknet 格式加载到 PyTorch 模型中。

load_weight_to 函数

这是一个辅助函数,用于从权重数组中加载权重到 PyTorch 模型的特定参数中。它接受三个参数:

  • weights:一个 NumPy 数组,包含了从权重文件中读取的所有权重。
  • offset:一个整数,表示在 weights 数组中当前要读取的起始位置。
  • target:一个 PyTorch 参数(例如,nn.Conv2d 的权重或偏置),它是要更新的目标参数。

函数执行以下步骤:

  1. 使用 target.numel() 获取目标参数中的元素数量 n
  2. 使用 torch.from_numpy 将权重数组的一部分转换为 PyTorch 张量,并使用 view_as 方法调整张量的形状以匹配目标参数的形状。
  3. 将转换后的张量赋值给目标参数的数据。
  4. 返回更新后的偏移量 offset+n,用于后续加载其他层的权重。

load_weights 函数

这个函数用于遍历 PyTorch 模型的所有层,并加载预训练的权重。它接受两个参数:

  • network:要加载权重的 PyTorch 网络模型。
  • filename:包含预训练权重的文件路径。

函数执行以下步骤:

  1. 打开权重文件进行二进制读取。
  2. 读取文件头部信息,通常包括版本号和训练状态记录,但在此代码中未使用。
  3. 读取剩余的文件内容作为权重值。
  4. 遍历网络的每一层,对于每种类型的层(卷积层和批归一化层),使用 load_weight_to 函数加载权重和偏置。
  5. 对于批归一化层,除了权重和偏置外,还需要加载 running_meanrunning_var

这个权重加载过程假设 Darknet 格式的权重文件与 PyTorch 模型的结构是匹配的。如果结构不匹配,需要对加载过程进行相应的调整。这个过程是将预训练模型的权重迁移到 PyTorch 框架中,以便可以在 PyTorch 环境中使用这些模型进行推理或继续训练。

处理真实图像

真实图像的尺寸各不相同,网络模型通常需要特定输入的尺寸。YOLO模型需要图像的长度和宽度都是32的整倍数,每32x32个像素对应一个网格。
在YOLO模型训练阶段,输入图像都为426x416, 所以在使用模型时, 也要尽量输入与训练图片尺寸接近或一致的图片.
下面的代码用来加载真实图像, 并且对图像进行缩放和补齐, 使得图像与我们期望的输入尺寸一致, 是以哦那个Pillow包进行图像的读取; 再使用PyTorch软件包中的torchvision视觉模块进行图像缩放,补齐操作, 将图像转化为网络模型可以接受的张量.

import torchvision
from PIL import Image

def load_image(filename, width, height):
    img = Image.open(filename)
    scale = min(width / img.width, height / img.height)
    new_width, new_height = int(img.width * scale), int(img.height * scale)
    diff_width, diff_height = width-new_width, height-new_height
    padding = (diff_width // 2, diff_height // 2,
               diff_width // 2 + diff_width % 2,
               diff_height // 2 + diff_height % 2)
    transform = torchvision.transforms.Compose([
        torchvision.transforms.Resize(size=(new_height, new_width)),
        torchvision.transforms.Pad(padding=padding),
        torchvision.transforms.ToTensor()
    ])
    # 用unsqueeze 方法增加一维样本编号
    # 网络模型是成批接受输入的
    # 即 一批只有一个样本
    return transform(img).unsqueeze(0)

下面准备绘制物体框,要在输出绘制的物体框中对物体类别进行标注, 我们需要提前准备VOC数据集中的20个物体类别的名字.

# 准备工作: 将网络模型的输出可视化
class_labels = (
    "aeroplane", "bicycle", "bird", "boat", "bottle",
    "bus", "car", "cat", "chair",  "cow",
    "diningtable", "dog", "horse", "motorbike", "person",
    "pottedplant", "sheep", "sofa", "train", "tvmonitor",
)

网络模型会产生大量物体框, 以320x320像素的输入图像为例, 网络会产生100个网格, 每个网格产生5个物体框. 显然, 我们不能把所有500个物体框都绘制出来, 只需要通过一个threshold过滤掉置信度不高的物体框, 同时, 我们希望物体类别的softmax输出也比较大, 因此, 过滤时比较的是置信度与softmax输出的乘积,将这个乘积显示出来,用来观察网络模型认为某处存在某种物体的概率大小.

# 下面是绘制物体框的函数

def show_images_with_boxes(input_tensor, output_tensor, class_labels, threshold):
    # 区分不同物体框的颜色表
    colors = ['r', 'g', 'b', 'y', 'c', 'm', 'k']
    to_img = torchvision.transforms.ToPILImage()
    img = to_img(input_tensor[0])
    # 显示图片
    plt.imshow(img)
    axis = plt.gca()
    # 将网络输出提取为numpy数组
    output = output_tensor[0].cpu().detach().numpy().reshape((-1, 5+len(class_labels)))
    classes = numpy.argmax(output[:, 5:], axis=-1)
    confidences = output[:, 4] * numpy.max(output[:, 5:], axis=-1)
    # 将物体框调整到输入图片的尺寸
    boxes = output[:, 0:4]
    boxes[:, 0::2] *= img.width
    boxes[:, 1::2] *= img.height
    boxes[:, 0:2] -= boxes[:, 2:4] / 2
    # 逐个显示物体框
    for box, confidence, class_id in zip(boxes, confidences, classes):
        # 忽略置信度较低的物体框
        if confidence < threshold: continue
        # 绘制物体框
        color = colors[class_id % len(colors)]
        rect = patches.Rectangle(box[0:2], box[2], box[3],
                                 linewidth=1, edgecolor=color, facecolor='none')
        axis.add_patch(rect)
        label = class_labels[class_id]
        label = '{0}{1:.2f}'.format(label, confidence)
        plt.text(box[0], box[1], label, color='w', backgroundcolor=color)
    plt.show()

在这里插入图片描述
接下来,输入真实的图像,测试一下啊

net = TinyYoloNetwork()
load_weights(net)
imgs = load_image('D:\\For_Study\\SelfLearn\\ForPythonArcgis\\demo1\\data\\dogs.jpg', 320, 320)
output_tensor = net(imgs)
show_images_with_boxes(imgs, output_tensor, class_labels, 0.3)

在这里插入图片描述
从输出结果中,我们可以看到有若干个位置相似的物体框套在同一个物体上,它们可能来自相邻的网格单元, 或者来自同一网格单元内不同的先验物体框。更加完善的处理是,计算这些重叠物体框的重叠比例,当重叠比例超过一定阈值时,认为它们描述的时同一个物体,选择其中置信度最高的物体框,忽略其他物体框。这个过程叫作最大值抑制,可以有效地过滤重复的物体框。


总结

今天学习的是使用预训练好的YOLO模型对真实图片进行物体检测的代码.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1574195.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ECAI 2024投稿指南

诸神缄默不语-个人CSDN博文目录 ECAI也写一下&#xff0c;作为备胎。毕竟ECAI是CCF B会。 ECAI dblp官网&#xff1a;https://dblp.uni-trier.de/db/conf/ecai/index.html 征文网址&#xff1a;https://www.ecai2024.eu/calls/main-track ECAI 2024在西班牙开&#xff0c;如…

伦敦银行情上涨时投资盈利

在讨论如何根据伦敦银行情上涨时机投资盈利之前&#xff0c;投资者需要了解伦敦银的特性以及影响其价格波动的因素。伦敦银&#xff0c;即银的伦敦市场交易价格&#xff0c;是全球贵金属交易中的重要参考价。银的价格受到多种因素的影响&#xff0c;包括全球经济状况、货币政策…

FJSP:巨型犰狳优化算法(Giant Armadillo Optimization,GAO)求解柔性作业车间调度问题(FJSP),提供MATLAB代码

一、柔性作业车间调度问题 柔性作业车间调度问题&#xff08;Flexible Job Shop Scheduling Problem&#xff0c;FJSP&#xff09;&#xff0c;是一种经典的组合优化问题。在FJSP问题中&#xff0c;有多个作业需要在多个机器上进行加工&#xff0c;每个作业由一系列工序组成&a…

网络安全流量平台_优缺点分析

FlowShadow&#xff08;流影&#xff09;&#xff0c;Ntm&#xff08;派网&#xff09;&#xff0c;Elastiflow。 Arkimesuricata&#xff0c;QNSMsuricata&#xff0c;Malcolm套件。 Malcolm套件优点&#xff1a;支持文件还原反病毒引擎&#xff08;clamav/yara&#xff09;…

基于单片机冬季供暖室温调节控制系统

**单片机设计介绍&#xff0c;基于单片机冬季供暖室温调节控制系统 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的冬季供暖室温调节控制系统是一种集温度检测、控制和显示功能于一体的智能化系统。该系统以单片机为…

Kubernetes(k8s):Pod 的 Node Selector详解

Kubernetes&#xff08;k8s&#xff09;&#xff1a;Pod 的 Node Selector详解 1、什么是Node Selector&#xff1f;2、Node Selector的工作原理3、Node Selector的用法1、例如&#xff1a;给node01 、node02 分别打上标签2、使用标签调度Pod3、删除节点的标签 &#x1f496;Th…

SystemC入门学习Demo用例的工程化配置

背景&#xff1a;对不同的用例文件&#xff0c;使用CMakeLists.txt进行工程化管理的演示&#xff0c;这样开发者可以更加关注在代码开发上。 1&#xff0c;首先安装好系统环境的systemC库&#xff1a;ubuntu系统安装systemc-2.3.4流程-CSDN博客 2&#xff0c;准备好一个demo用…

Golang | Leetcode Golang题解之第12题整数转罗马数字

题解&#xff1a; 题解&#xff1a; var (thousands []string{"", "M", "MM", "MMM"}hundreds []string{"", "C", "CC", "CCC", "CD", "D", "DC", "…

Python最简单的图片爬虫

Python最简单的图片爬虫&#xff0c;20行代码带你爬遍整个网站-腾讯云开发者社区-腾讯云 (tencent.com) import urllib.parse import json import requests import jsonpath url https://www.duitang.com/napi/blog/list/by_search/?kw{}&start{} label 美女 label url…

剑指Offer题目笔记27(动态规划单序列问题)

面试题89&#xff1a; 问题&#xff1a; ​ 输入一个数组表示某条街道上的一排房屋内财产的数量。相邻两栋房屋不能同时被盗&#xff0c;问小偷能偷取到的最多财物。 解决方案一&#xff08;带缓存的递归&#xff09;&#xff1a; 解决方案&#xff1a; 由于有报警系统&…

【保姆级教程】如何在 Windows 上实现和 Linux 子系统的端口映射

写在前面 上次分享【保姆级教程】Windows上安装Linux子系统&#xff0c;搞台虚拟机玩玩&#xff0c;向大家介绍了什么是虚拟机以及如何在Windows上安装Linux虚拟机。对于开发同学而言&#xff0c;经常遇到的一个问题是&#xff1a;很多情况下代码开发需要依赖 Linux 系统&…

八股面试速成—计算机网络部分

暑期实习面试在即&#xff0c;这几天八股和算法轮扁我>_ 八股部分打算先找学习视屏跟着画下思维导图&#xff0c;然后看详细的面试知识点&#xff0c;最后刷题 其中导图包含的是常考的题&#xff0c;按照思维导图形式整理&#xff0c;会在复盘后更新 细节研究侧重补全&a…

kubelet安装

安装 在大致了解了一些k8s的基本概念之后&#xff0c;我们实际部署一个k8s集群&#xff0c;做进一步的了解 1. 裸机安装 采用三台机器&#xff0c;一台机器为Master&#xff08;控制面板组件&#xff09;两台机器为Node&#xff08;工作节点&#xff09; 机器的准备有两种方式…

阿里巴巴中国站获得1688商品详情 API:如何通过API接口批量获取价格、标题、图片、库存等数据

在数字化时代&#xff0c;数据的重要性不言而喻。对于电商从业者来说&#xff0c;获取商品详情数据是提升业务效率和用户体验的关键。阿里巴巴中国站作为电商行业的巨头&#xff0c;提供了丰富的API接口&#xff0c;方便开发者们批量获取商品信息。本文将详细叙述如何通过阿里巴…

透射菊池衍射(TKD)技术优势明显 在冶金领域发展潜力较大

透射菊池衍射&#xff08;TKD&#xff09;技术优势明显 在冶金领域发展潜力较大 透射菊池衍射&#xff08;TKD&#xff09;&#xff0c;基本原理与电子背散射衍射&#xff08;EBSD&#xff09;大致相同&#xff0c;但信号接收方式存在差异&#xff0c;也称为透射电子背散射衍射…

「36」如何让直播间的文字,图片动起来?

OBS中的滚动滤镜是一种视频滤镜效果,用于在直播或录制过程中创建滚动字幕或滚动文本效果。 滚动滤镜,可以让您在视频画面中显示滚动的文本内容,以提供额外的信息或注释。你经常看到,直播间的「文字和图片」在匀速的滚动,怎么做的呢?现在教你…… 实操步骤 一、文字走动 …

以Kotti项目为例使用pytest测试项目

在维护和构建大型项目时&#xff0c;单独一个一个手工测试代码已经不适用了&#xff0c;这时候就要用专门的测试框架进行测试。让我们以Kotti项目为例&#xff0c;用pytest这个测试框架进行实践测试吧。 使用python3.10 Ubuntu 系统 准备工作 下载和安装kotti库 pip install…

软件测试下的AI之路(4)

&#x1f60f;作者简介&#xff1a;博主是一位测试管理者&#xff0c;同时也是一名对外企业兼职讲师。 &#x1f4e1;主页地址&#xff1a;【Austin_zhai】 &#x1f646;目的与景愿&#xff1a;旨在于能帮助更多的测试行业人员提升软硬技能&#xff0c;分享行业相关最新信息。…

智慧公厕升级为多功能城市智慧驿站,助力智慧城市发展

在现代城市的建设中&#xff0c;公共厕所作为基础必备的民生设施&#xff0c;一直是城市管理的重要组成部分。随着科技的不断发展&#xff0c;智慧公厕应运而生&#xff0c;成为了公共厕所信息化、数字化、智慧化的应用解决方案。而近年来&#xff0c;智慧公厕也进行了升级发展…

Java设计模式—享元(FlyWeight)模式

享元模式&#xff08;Flyweight&#xff09;&#xff0c;运用共享技术有效地支持大量细粒度的对象 public abstract class Piece {protected PieceColor m_color;protected PiecePos m_pos;public Piece(PieceColor color ,PiecePos pos){m_color color;m_pos pos;}public ab…