【目标检测——YOLO系列】YOLOv1 —《You Only Look Once: Unified, Real-Time Object Detection》

这篇具有很好参考价值的文章主要介绍了【目标检测——YOLO系列】YOLOv1 —《You Only Look Once: Unified, Real-Time Object Detection》。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

YOLOv1 —《You Only Look Once: Unified, Real-Time Object Detection》


论文地址:1506.02640] You Only Look Once: Unified, Real-Time Object Detection (arxiv.org)

代码地址:pjreddie/darknet: Convolutional Neural Networks (github.com)

1、YOLOv1概述


YOLOv1是一种end to end目标检测算法,由Joseph Redmon等人于2015年提出。它是一种基于单个神经网络的实时目标检测算法。

YOLOv1的中文名称是"你只看一次",这个名字源于算法的工作原理。相比于传统的目标检测算法,YOLOv1采用了全新的思路。**它将目标检测问题转化为一个回归问题,并将整个图像作为输入,一次性地在图像上进行目标检测和定位(单阶段检测模型)。这与传统的滑动窗口或区域提议方法不同,传统方法如RCNN系列(两阶段检测模型)**需要在图像上进行多次检测。

YOLOv1的网络结构由卷积层和全连接层组成,可以将输入图像分割成较小的网格(grid cell)。对于每个网格,YOLOv1预测多个边界框以及每个边界框中是否包含目标物体及其类别。通过对预测结果进行置信度评估和非极大值抑制,可以得到最终的目标检测结果。

相比于传统的目标检测算法,YOLOv1具有较快的检测速度可以实现实时目标检测。然而,由于网络结构的限制,YOLOv1在小目标检测和物体定位精度上可能存在一定的问题(主要因为YOLO中的一个gird cell只能预测判别一个物体,后文细说)。在后续的版本中,如YOLOv2和YOLOv3等,一些改进措施被提出来解决这些问题(比如从Faster Rcnn中引入anchor等),我们在后续文章中会对YOLO其他版本进行继续讲解。

【目标检测——YOLO系列】YOLOv1 —《You Only Look Once: Unified, Real-Time Object Detection》

我们可以将YOLOv1网络看作一个黑盒,在推理阶段,我们将图像resize到448 * 448后输入到网络中,输出的是一个SxSx(B*5+C)的张量,该张量是关于检测框的位置信息以及检测目标的类别信息等,再经过非极大值抑制处理(关于非极大值抑制NMS,如果不理解,可以参考我们以前的内容:【目标检测】 非极大值抑制—NMS_卖报的大地主的博客-CSDN博客),得到最后的检测结果(最终的预测框位置信息、类别及其置信度信息)。

【目标检测——YOLO系列】YOLOv1 —《You Only Look Once: Unified, Real-Time Object Detection》


2、YOLOv1进行目标检测的详细步骤


  1. 网络输入和分割:将一幅待检测图像分割分割成S*S个网格,,比如7 * 7, 13 * 13,在原文中S=7。

  2. 边界框预测: 每个网格预测B个边界框(bounding box),在原文中B=2,**每个边界框包含5个基本参数:位置(x、y坐标)、宽度w、高度h和置信度C。**其中:(x、y)表示边界框的中心相对于其所属网格左上角的位置偏移量;宽度w、高度h表示相对于整个图像的比例;C表示边界框的置信度,confidence = Pr(object) * IOU (pred, truth), Pr(object)代表边界框中存在物体的概率,非0即1,IOU (pred, truth)代表预测框与真实标签框的交并比。

    在训练时,某物体的真实边界框的中心落在哪个网格上就由哪个网格负责预测该物体,并且在网格所生成的B个边界框中,与真实边界框的IOU最大的边界框负责预测该物体,这一部分在loss函数中有所体现。每个网格只能预测一个物体(这也是造成YOLOv1对小物体和密集型物体检测效果差的主要原因)。

  3. 类别预测: 每个网格还预测一组类别的条件概率Pr(class | object) ,即在当前边界框已包含物体的先验条件下该物体为各类别的概率,用于确定边界框中物体的类别。

    在进行推理预测时,每个边界框的类别预测概率与该边界框的置信度相乘,得到最终的类别置信度,即Pr(class | object) * Pr(object) * IOU (pred, truth) = Pr(class) * IOU (pred, truth)。根据同样的方法可以计算得到7 x 7 x 2 = 98个边界框的confidence score(当S =7, B =2时),然后根据confidence score对预测得到的98个边界框进行非极大值抑制,得到最终的检测结果。

    【目标检测——YOLO系列】YOLOv1 —《You Only Look Once: Unified, Real-Time Object Detection》

  4. 网络输出: 网络的输出是一个SxSx(B*5+C)的张量,相当于每个网格都输出一个形状为B * 5+C的张量,包含B个边界框中每个边界框的位置信息和置信度信息(边界框的中心点xy坐标和宽w高h以及置信度信息),以及该网格所负责预测物体的类别概率(C个值,所属C个类别的概率,原文中C等于20,即检测20个类别)。这些位置预测结果是相对于输入图像的尺度的,因此可以通过将相对尺度乘以图像的宽度和高度来得到实际的边界框。

  5. 置信度评估和非极大值抑制: 对于每个边界框,根据其置信度和类别置信度进行综合评估。通常,可以使用阈值来筛选置信度较高的边界框,并使用非极大值抑制来消除重叠的边界框,从而得到最终的目标检测结果。

3、YOLOv1的网络结构及pytorch实现


YOLOv1的网络结构比较简单,有24个卷积层以及两个全连接层组成。

【目标检测——YOLO系列】YOLOv1 —《You Only Look Once: Unified, Real-Time Object Detection》

如下给出网络的PyTorch实现:

import torch
import torch.nn as nn
import torch.nn.functional as F

class YOLOv1(nn.Module):
    def __init__(self, num_classes, num_boxes):
        super(YOLOv1, self).__init__()
        self.num_classes = num_classes
        self.num_boxes = num_boxes

        # 网络的各层定义
        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3)
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
        
        self.conv2 = nn.Conv2d(64, 192, kernel_size=3, stride=1, padding=1)
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
        
        self.conv3 = nn.Conv2d(192, 128, kernel_size=1, stride=1, padding=0)
        self.conv4 = nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1)
        self.conv5 = nn.Conv2d(256, 256, kernel_size=1, stride=1, padding=0)
        self.conv6 = nn.Conv2d(256, 512, kernel_size=3, stride=1, padding=1)
        self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2)
        
        self.conv7 = nn.Conv2d(512, 256, kernel_size=1, stride=1, padding=0)
        self.conv8 = nn.Conv2d(256, 512, kernel_size=3, stride=1, padding=1)
        self.conv9 = nn.Conv2d(512, 256, kernel_size=1, stride=1, padding=0)
        self.conv10 = nn.Conv2d(256, 512, kernel_size=3, stride=1, padding=1)
        self.conv11 = nn.Conv2d(512, 256, kernel_size=1, stride=1, padding=0)
        self.conv12 = nn.Conv2d(256, 512, kernel_size=3, stride=1, padding=1)
        self.conv13 = nn.Conv2d(512, 256, kernel_size=1, stride=1, padding=0)
        self.conv14 = nn.Conv2d(256, 512, kernel_size=3, stride=1, padding=1)
        self.conv15 = nn.Conv2d(512, 512, kernel_size=1, stride=1, padding=0)
        self.conv16 = nn.Conv2d(512, 1024, kernel_size=3, stride=1, padding=1)
        self.pool4 = nn.MaxPool2d(kernel_size=2, stride=2)
        
        self.conv17 = nn.Conv2d(1024, 512, kernel_size=1, stride=1, padding=0)
        self.conv18 = nn.Conv2d(512, 1024, kernel_size=3, stride=1, padding=1)
        self.conv19 = nn.Conv2d(1024, 512, kernel_size=1, stride=1, padding=0)
        self.conv20 = nn.Conv2d(512, 1024, kernel_size=3, stride=1, padding=1)
        self.conv21 = nn.Conv2d(1024, 1024, kernel_size=3, stride=1, padding=1)
        self.conv22 = nn.Conv2d(1024, 1024, kernel_size=3, stride=2, padding=1)
        
        self.conv23 = nn.Conv2d(1024, 1024, kernel_size=3, stride=1, padding=1)
        self.conv24 = nn.Conv2d(1024, 1024, kernel_size=3, stride=1, padding=1)
        
        self.fc1 = nn.Linear(7 * 7 * 1024, 4096)
        self.fc2 = nn.Linear(4096, 7 * 7 * (self.num_classes + 5 * self.num_boxes))
        self.softmax = nn.Softmax(dim=1)

    def forward(self, x):
        x = F.leaky_relu(self.conv1(x), negative_slope=0.1)
        x = self.pool1(x)
        x = F.leaky_relu(self.conv2(x), negative_slope=0.1))
        x = self.pool2(x)
        x = F.leaky_relu(self.conv3(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv4(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv5(x), negative_slope=0.1))
        x = self.pool3(x)
        x = F.leaky_relu(self.conv6(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv7(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv8(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv9(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv10(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv11(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv12(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv13(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv14(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv15(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv16(x), negative_slope=0.1))
        x = self.pool4(x)
        x = F.leaky_relu(self.conv17(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv18(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv19(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv20(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv21(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv22(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv23(x), negative_slope=0.1))
        x = F.leaky_relu(self.conv24(x), negative_slope=0.1))
        x = x.view(x.size(0), -1)
        x = F.leaky_relu(self.fc1(x), negative_slope=0.1))
        x = self.fc2(x)
        x = self.softmax(x)
        return x

4、YOLOv1的loss函数及pytorch实现


网络的Loss部分:

【目标检测——YOLO系列】YOLOv1 —《You Only Look Once: Unified, Real-Time Object Detection》

YOLOv1将检测视为回归任务,所以选用回归常用的平方和损失作为loss函数,主要由位置坐标回归误差、置信度回归误差、类别预测误差三部分组成。

  1. 位置坐标误差只统计负责检测物体的边界框的定位误差,由边界框的中心坐标定位误差和宽高定位误差组成。

  2. 置信度回归误差对负责检测物体的边界框和不负责的边界框都进行惩罚,公式中的置信度预测值为模型正向计算的输出结果中的置信度,置信度真实值为confidence = Pr(object) * IOU (pred, truth)。

  3. 类别预测误差仅对存在物体的网格(真实边界框的中心点落在该网格中)进行错误惩罚.

Note:

  • 平方和loss对各类误差一视同仁,图像中不包含物体的网格的边界框置信度等于0,容易超过包含物体网格的gradient,会导致模型训练不稳定,所以论文中采用了不同的权重因子,对定位误差和分类误差进行权衡。λcoord=5 — 增强边界框定位误差;λnoobj=5 — 削弱不包含物体的边界框置信度误差;

  • 平方和loss对大小边界框的惩罚力度一致,但我们应该使得大框的小偏差比小框中的偏差对loss训练优化的影响要小,原文中将Loss中的宽高进行了开平方根,而不是直接使用宽高,使得小框的偏差对总体误差的影响更加敏感。

YOLOv1 Loss的Pytorch实现:文章来源地址https://www.toymoban.com/news/detail-479956.html

import torch
import numpy as np
import torch.nn as nn
import torch.nn.functional as F


class MSEWithLogitsLoss(nn.Module):
    def __init__(self, ):
        super(MSEWithLogitsLoss, self).__init__()

    def forward(self, logits, targets):
        inputs = torch.clamp(torch.sigmoid(logits), min=1e-4, max=1.0 - 1e-4)

        pos_id = (targets==1.0).float()
        neg_id = (targets==0.0).float()
        pos_loss = pos_id * (inputs - targets)**2
        neg_loss = neg_id * (inputs)**2
        loss = 5.0*pos_loss + 1.0*neg_loss

        return loss


def generate_dxdywh(gt_label, w, h, s):
    xmin, ymin, xmax, ymax = gt_label[:-1]
    # 计算边界框的中心点
    c_x = (xmax + xmin) / 2 * w
    c_y = (ymax + ymin) / 2 * h
    box_w = (xmax - xmin) * w
    box_h = (ymax - ymin) * h

    if box_w < 1e-4 or box_h < 1e-4:
        # print('Not a valid data !!!')
        return False    

    # 计算中心点所在的网格坐标
    c_x_s = c_x / s
    c_y_s = c_y / s
    grid_x = int(c_x_s)
    grid_y = int(c_y_s)
    # 计算中心点偏移量和宽高的标签
    tx = c_x_s - grid_x
    ty = c_y_s - grid_y
    tw = np.log(box_w)
    th = np.log(box_h)
    # 计算边界框位置参数的损失权重
    weight = 2.0 - (box_w / w) * (box_h / h)

    return grid_x, grid_y, tx, ty, tw, th, weight


def gt_creator(input_size, stride, label_lists=[]):
    # 必要的参数
    batch_size = len(label_lists)
    w = input_size
    h = input_size
    ws = w // stride
    hs = h // stride
    s = stride
    gt_tensor = np.zeros([batch_size, hs, ws, 1+1+4+1])

    # 制作训练标签
    for batch_index in range(batch_size):
        for gt_label in label_lists[batch_index]:
            gt_class = int(gt_label[-1])
            result = generate_dxdywh(gt_label, w, h, s)
            if result:
                grid_x, grid_y, tx, ty, tw, th, weight = result

                if grid_x < gt_tensor.shape[2] and grid_y < gt_tensor.shape[1]:
                    gt_tensor[batch_index, grid_y, grid_x, 0] = 1.0
                    gt_tensor[batch_index, grid_y, grid_x, 1] = gt_class
                    gt_tensor[batch_index, grid_y, grid_x, 2:6] = np.array([tx, ty, tw, th])
                    gt_tensor[batch_index, grid_y, grid_x, 6] = weight


    gt_tensor = gt_tensor.reshape(batch_size, -1, 1+1+4+1)

    return torch.from_numpy(gt_tensor).float()


def compute_loss(pred_conf, pred_cls, pred_txtytwth, targets):
    batch_size = pred_conf.size(0)
    # 损失函数
    conf_loss_function = MSEWithLogitsLoss()
    cls_loss_function = nn.CrossEntropyLoss(reduction='none')
    txty_loss_function = nn.BCEWithLogitsLoss(reduction='none')
    twth_loss_function = nn.MSELoss(reduction='none')

    # 预测
    pred_conf = pred_conf[:, :, 0]           # [B, HW,]
    pred_cls = pred_cls.permute(0, 2, 1)     # [B, C, HW]
    pred_txty = pred_txtytwth[:, :, :2]      # [B, HW, 2]
    pred_twth = pred_txtytwth[:, :, 2:]      # [B, HW, 2]
    
    # 标签
    gt_obj = targets[:, :, 0]                  # [B, HW,]
    gt_cls = targets[:, :, 1].long()           # [B, HW,]
    gt_txty = targets[:, :, 2:4]               # [B, HW, 2]
    gt_twth = targets[:, :, 4:6]               # [B, HW, 2]
    gt_box_scale_weight = targets[:, :, 6]     # [B, HW,]

    batch_size = pred_conf.size(0)
    # 置信度损失
    conf_loss = conf_loss_function(pred_conf, gt_obj)
    conf_loss = conf_loss.sum() / batch_size
    
    # 类别损失
    cls_loss = cls_loss_function(pred_cls, gt_cls) * gt_obj
    cls_loss = cls_loss.sum() / batch_size
    
    # 边界框txty的损失
    txty_loss = txty_loss_function(pred_txty, gt_txty).sum(-1) * gt_obj * gt_box_scale_weight
    txty_loss = txty_loss.sum() / batch_size

    # 边界框twth的损失
    twth_loss = twth_loss_function(pred_twth, gt_twth).sum(-1) * gt_obj * gt_box_scale_weight
    twth_loss = twth_loss.sum() / batch_size
    bbox_loss = txty_loss + twth_loss

    # 总的损失
    total_loss = conf_loss + cls_loss + bbox_loss

    return conf_loss, cls_loss, bbox_loss, total_loss

5、YOLOv1的不足


  • 因为YOLO中每个cell只预测两个边界框和一个物体,使得对小物体以及密集型物体检测效果差。
  • 此外,不像Faster R-CNN一样预测offset,YOLO是直接预测边界框的位置的,这就增加了训练的难度,并且识别精度弱于Faster R-CNN,但是快。
  • YOLO是根据训练数据来预测边界框的,但是当测试数据中的物体出现了训练数据中的物体没有的长宽比时,YOLO的泛化能力低。
  • 同时经过多次下采样,使得最终得到的feature的分辨率比较低,就是得到深语义的粗特征信息,忽略了细语义特征,造成定位精度下降。
  • 损失函数的设计存在缺陷,使得物体的定位误差有点儿大,尤其在不同尺寸大小的物体的处理上还有待加强。

6、其他


另外,YOLOv1中的训练过程中还是用到了Warm up、Dropout、数据增强(缩放、平移、HSV变换)等技巧,这些我们会在以后关于模型训练技巧的篇章中进行讲解。

参考链接:

  • 【精读AI论文】YOLO V1目标检测,看我就够了_哔哩哔哩_bilibili
  • 【目标检测论文阅读】YOLOv1 - 知乎 (zhihu.com)
  • yjh0410/PyTorch_YOLOv1: A new version of YOLOv1 (github.com)

到了这里,关于【目标检测——YOLO系列】YOLOv1 —《You Only Look Once: Unified, Real-Time Object Detection》的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 目标检测——Yolo系列(YOLOv1/2/v3/4/5/x/6/7/8)

    什么是目标检测? 滑动窗口(Sliding Window) 滑动窗口的效率问题和改进 滑动窗口的效率问题:计算成本很大 改进思路 1:使用启发式算法替换暴力遍历 例如 R-CNN,Fast R-CNN 中使用 Selective Search 产生提议框 ( 区域提议 ) 依赖外部算法,系统实现复杂,难以联合优化性能 改进

    2024年02月09日
    浏览(28)
  • 人工智能学习07--pytorch21--目标检测:YOLO系列理论合集(YOLOv1~v3)

    如果直接看yolov3论文的话,会发现有好多知识点没见过,所以跟着视频从头学一下。 学习up主霹雳吧啦Wz大佬的学习方法: 想学某个网络的代码时: 到网上搜这个网络的讲解 → 对这个网络大概有了印象 → 读论文原文 ( 很多细节都要依照原论文来实现, 自己看原论文十分

    2024年02月10日
    浏览(46)
  • 目标检测YOLO算法,先从yolov1开始

    有一套配套的学习资料,才能让我们的学习事半功倍。 yolov1论文原址:You Only Look Once: Unified, Real-Time Object Detection 代码地址:darknet: Convolutional Neural Networks (github.com) one-stage(单阶段):YOLO系列 最核心的优势:速度非常快,适合做实时检测任务! 但是缺点也是有的,效果通常

    2024年02月09日
    浏览(34)
  • 【目标检测系列】YOLOV1解读

    从R-CNN到Fast-RCNN,之前的目标检测工作都是分成两阶段,先提供位置信息在进行目标分类,精度很高但无法满足实时检测的要求。 而YoLo将目标检测看作回归问题,输入为一张图片,输出为S*S*(5*B+C)的三维向量。该向量结果既包含位置信息,又包含类别信息。可通过损失函数,

    2024年02月13日
    浏览(30)
  • YOLO系列目标检测算法-YOLOv6

    YOLO系列目标检测算法目录 - 文章链接 YOLO系列目标检测算法总结对比- 文章链接 YOLOv1- 文章链接 YOLOv2- 文章链接 YOLOv3- 文章链接 YOLOv4- 文章链接 Scaled-YOLOv4- 文章链接 YOLOv5- 文章链接 YOLOv6 - 文章链接 YOLOv7- 文章链接 PP-YOLO- 文章链接 PP-YOLOv2- 文章链接 YOLOR- 文章链接 YOLOS- 文章链

    2023年04月08日
    浏览(36)
  • YOLO物体检测-系列教程1:YOLOV1整体解读(预选框/置信度/分类任/回归任务/损失函数/公式解析/置信度/非极大值抑制)

    YOLOV1整体解读 YOLOV2整体解读 YOLOV1提出论文:You Only Look Once: Unified, Real-Time Object Detection two-stage(两阶段):Faster-rcnn Mask-Rcnn系列 one-stage(单阶段):YOLO系列 最核心的优势:速度非常快,适合做实时检测任务! 但是缺点也是有的,效果通常情况下不会太好! 机器学习 分类任

    2024年02月09日
    浏览(32)
  • YOLO系列概述(yolov1至yolov7)

    参考: 睿智的目标检测53——Pytorch搭建YoloX目标检测平台 YoloV7 首先我们来看一下yolo系列的发展历史,yolo v1和yolox是anchor free的方法,yolov2,yolov3,一直到yolov7是anchor base的方法。首选我们来回顾下每个版本的yolo都做了些什么 yolo v1是将 416 ∗ 416 416*416 4 1 6 ∗ 4 1 6 的图片,分

    2024年02月05日
    浏览(29)
  • YOLOv5目标检测学习(1):yolo系列算法的基础概念

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 关于深度学习目标检测,有许多概念性的东西需要先了解一下。这里主要以基于深度学习的目标检测算法的部署实现来学习。 以yolov5为例: 使用YOLOv5进行车辆和行人的目标检测通常涉及以下步骤: 数据

    2024年04月09日
    浏览(46)
  • 【YOLO系列】YOLOv1论文超详细解读(翻译 +学习笔记)

    从这篇开始,我们将进入YOLO的学习。YOLO是目前比较流行的目标检测算法,速度快且结构简单,其他的目标检测算法如RCNN系列,以后有时间的话再介绍。 本文主要介绍的是YOLOV1,这是由以Joseph Redmon为首的大佬们于2015年提出的一种新的目标检测算法。它与之前的目标检测算法

    2024年02月04日
    浏览(38)
  • YOLO系列算法全家桶——YOLOv1-YOLOv9详细介绍 !!

    文章目录 前言 一、YOLO算法的核心思想 1. YOLO系列算法的步骤 2. Backbone、Neck和Head 二、YOLO系列的算法 1. YOLOv1(2016) 1.1 模型介绍 1.2 网络结构 1.3 实现细节 1.4 性能表现 2. YOLOv2(2016) 2.1 改进部分 2.2 网络结构 2.3 性能表现 3. YOLOv3 (2018) 3.1 模型介绍 3.2 网络结构 3.3 改进部分

    2024年04月10日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包