关于python版本的Faster Rcnn的使用

第一篇博客,水平有限,如有错误之处还望各位指出,有问题欢迎大家一起讨论。大笑大笑大笑

本文主要写了一些关于py-faster rcnn的使用心得,用它来训练自己的数据集,同时使用官方提供的一些工具,来完成一些检测任务。

0、前期准备

已搭建好caffe、已安装python,opencv等。

已安装好Py-Faster-Rcnn,并已完成配置,可使用官方提供的模型和demo.py来检测图片。

我们使用的系统是ubuntu 14.04版本,GPU为GTX980ti ,6G显存,亲测可跑VGG16(batchsize 16).(注,该显卡在windows下的matlab版本的Faster Rcnn无法跑VGG16,显存不够。。。。敲打)

一、准备数据集
我们使用的是pascal VOC2007 格式的数据集。需要准备图片,包含图片标注信息的xml文件,设置的训练验证集和测试集的txt文档。
在路径py-faster-rcnn/data/VOCdevkit2007/VOC2007/ 下:
Annotations:放入xml文件。
JPEGImages:放入图片。
ImageSets/Main:放入 trainval.txt 和 test.txt
我们是直接使用自己的数据集代替官方的pascal VOC2007数据集来进行训练的。所以在数据集的准备上参考pascal voc2007的格式即可。

二、训练方式选择
py-faster rcnn 提供了两种训练方式:
1.四步交替训练方法(官方的matlab版本只有这种训练方式)

该训练方法第一步是训练RPN网络,第二步训练Fast-rcnn网络,第三步固定卷积层(开始共享卷积)只训练RPN独有的层,第四部只训练Fast-Rcnn特有的层。详细过程可参考论文,和训练代码: py-faster-rcnn/tools/train_faster_rcnn_alt_opt.py。该方法训练时间相对较久一些,且以后的使用中参数设置,和网络设置相对麻烦一点。但其检测效果稍微优于端对端的训练方法。

2.端对端的近似训练方法

该方法直接将RPN网络和Fast-Rcnn网络融合为一个整体进行端对端的训练。训练收敛速度快,网络设置修改简单,效果稍微差于四步训练的方法。详细过程可参考论文,以及训练代码:py-faster-rcnn/tools/train_net.py。

综上所属推荐使用端对端的训练方式,操作简单,易于修改,训练速度快。

三、网络的选择

官方提供了ZF网络和VGG网络(VGG16和VGG_M_1024)的网络结构,和相应的在Imagenet数据集下的预训练网络模型。(只要熟悉以后我们也可以自己设置网络模型,不用预训练模型从初始状态开始训练)
在确定了训练方式之后,进入所选择的训练方式的模型下可查看相应的网络模型,以及训练超参数。
例如:我们选择使用端对端的训练方式,ZF网络。
则在路径:py-faster-rcnn/models/pascal_voc/ZF/faster_rcnn_end2end/ 下有solver.prototxt,test.prototxt,train.prototxt 三个文件。其中,train.prototxt 即是ZF网络的训练模型。test.prototxt 即ZF网络的测试模型。修改模型可在这两个文件中修改,同时在多类别检测任务中,类别数量需要在这两个文件中修改。若训练中需加载预训练模型来Fine-turning,则所需加载的网络层的名称应该与预训练模型的网络层名称相同。solver.prototxt中则是CNN的训练超参数。
可将test.prototxt或train.prototxt中的内容复制网页 http://ethereon.github.io/netscope/#/editor 中来观看网络结构的可视化情况,用于直观的理解网络结构,以及检查修改后的网络结构是否正确。

由于类别数量改变所需修改处:(由于类别数量的改变,在加载与训练模型时需要修改与类别相关层的名称)
在test.prototxt和train.prototxt中,类别数量和bbox_pred处需要修改。



四、开始训练

(以端对端训练为例,四步训练法同理)
在选择好训练方式,修改好网络模型,设置好训练超参数后。可开始训练。推荐使用官方例程的脚本来启动训练。
端对端训练脚本位于:py-faster-rcnn/experiments/scripts/faster_rcnn_end2end.sh (四步训练则为faster_rcnn_alt_opt.sh)。该脚本程序集成了模型训练,测试集测试,日志保存这几个功能,同时也可以设置训练函数py-faster-rcnn/tools/train_net.py 中所需输入的参数(例如最大迭代次数,是否使用GPU等)。
在该脚本文件中,我们针对自己的数据级需要一些修改:
1.第30行最大迭代次数:ITERS=40000 ;由于我们使用的是pascal-voc的格式,所以修改此处。
2.第53行预训练模型加载地址:  --weights data/imagenet_models/ZF_faster_rcnn_final.caffemodel \(此处表示加载了一个ZF模型为预训练模型,用于做迁移训练。)
其他地方需要注意几个路径地址即可。
训练命令: 在Faster-Rcnn的根目录下打开终端,输入./experiments/scripts/faster_rcnn_end2end.sh 0 ZF pascal_voc  开始使用GPU 0 和ZF网络,以及pascal_voc的格式启动faster_rcnn_end2end.sh脚本程序开始训练我们的数据集。
除此之外,我们也可以使用脚本所调用的训练代码:py-faster-rcnn/tools/train_net.py 来直接进行训练。这样我们需要在命令行中提供一些参数。
例如:python tools/train_./tools/demo.py --gpu 0 --net zffaster_rcnn_alt_opt.py --gpu 0 --net_name ZF --weights data/faster_rcnn_models/ZF_faster_rcnn_final.caffemodel --cfg experiments/cfgs/faster_rcnn_alt_opt.yml --imdb voc_2007_trainval (这是一个四步训练的命令,端对端的方式同理,注意根据train_net.py 中所需的参数来输入)
注:在训练前需清空之前训练的缓存,py-faster-rcnn/data/cache 中的内容 和 py-faster-rcnn/data/VOCdevkit2007/annotations_cache 中的内容。且保证输出py-faster-rcnn/output 为空,或将之前的训练结果保存在其他文件。
训练完成后,模型保存于py-faster-rcnn/output 下。日志保存于py-faster-rcnn/experiments/logs下。

下面是faster_rcnn_end2end.sh:

#!/bin/bash
# Usage:
# ./experiments/scripts/faster_rcnn_end2end.sh GPU NET DATASET [options args to {train,test}_net.py]
# DATASET is either pascal_voc or coco.
#
# Example:
# ./experiments/scripts/faster_rcnn_end2end.sh 0 VGG_CNN_M_1024 pascal_voc \
#   --set EXP_DIR foobar RNG_SEED 42 TRAIN.SCALES "[400, 500, 600, 700]"

set -x
set -e

export PYTHONUNBUFFERED="True"

GPU_ID=$1
NET=$2
NET_lc=${NET,,}
DATASET=$3

array=( $@ )
len=${#array[@]}
EXTRA_ARGS=${array[@]:3:$len}
EXTRA_ARGS_SLUG=${EXTRA_ARGS// /_}

case $DATASET in
  pascal_voc)
    TRAIN_IMDB="voc_2007_trainval"
    TEST_IMDB="voc_2007_test"
    PT_DIR="pascal_voc"
    ITERS=40000
    ;;
  coco)
    # This is a very long and slow training schedule
    # You can probably use fewer iterations and reduce the
    # time to the LR drop (set in the solver to 350,000 iterations).
    TRAIN_IMDB="coco_2014_train"
    TEST_IMDB="coco_2014_minival"
    PT_DIR="coco"
    ITERS=500
    ;;
  *)
    echo "No dataset given"
    exit
    ;;
esac

LOG="experiments/logs/faster_rcnn_end2end_${NET}_${EXTRA_ARGS_SLUG}.txt.`date +'%Y-%m-%d_%H-%M-%S'`"
exec &> >(tee -a "$LOG")
echo Logging output to "$LOG"

time ./tools/train_net.py --gpu ${GPU_ID} \
  --solver models/${PT_DIR}/${NET}/faster_rcnn_end2end/solver.prototxt \
  --weights data/imagenet_models/VGG16_faster_rcnn_final.caffemodel \
  --imdb ${TRAIN_IMDB} \
  --iters ${ITERS} \
  --cfg experiments/cfgs/faster_rcnn_end2end.yml \
  ${EXTRA_ARGS}

set +x
NET_FINAL=`grep -B 1 "done solving" ${LOG} | grep "Wrote snapshot" | awk '{print $4}'`
set -x

time ./tools/test_net.py --gpu ${GPU_ID} \
  --def models/${PT_DIR}/${NET}/faster_rcnn_end2end/test.prototxt \
  --net ${NET_FINAL} \
  --imdb ${TEST_IMDB} \
  --cfg experiments/cfgs/faster_rcnn_end2end.yml \
  ${EXTRA_ARGS}



五、图片检测

图片检测代码位于:py-faster-rcnn/tools/demo.py  。将待测图片存放于:py-faster-rcnn/data/demo/  下。
若直接使用该程序检测,首先需要将我们训练保存的模型复制到py-faster-rcnn/data/faster_rcnn_models/ 路径下。然后再在demo.py中修改模型名称,类别名称,CONF_THRESH,NMS_THRESH阈值等。详细参考demo.py(此处已非原版,现可一张图检测显示多个类别)
注:第132行,端对端训练方式和四步训练方式是不同的,此处是加载网络结构,使用不同训练方式时需要修改。
调用命令:./tools/demo.py --gpu 0 --net zf

下面是我的demo.py

#!/usr/bin/env python
#coding=utf8
# 因为代码中我加了中文注释,所以 上面这行用于指定编码 ,否则python代码执行会报错 (一张图显示多个类别)
# --------------------------------------------------------
# Faster R-CNN
# Copyright (c) 2015 Microsoft
# Licensed under The MIT License [see LICENSE for details]
# Written by Ross Girshick
# --------------------------------------------------------

"""
Demo script showing detections in sample images.

See README.md for installation instructions before running.
"""

import _init_paths
from fast_rcnn.config import cfg
from fast_rcnn.test import im_detect
from fast_rcnn.nms_wrapper import nms
from utils.timer import Timer
import matplotlib.pyplot as plt
import numpy as np
import scipy.io as sio
import caffe, os, sys, cv2
import argparse

#CLASSES = ('__background__',
#           'aeroplane', 'bicycle', 'bird', 'boat',
#           'bottle', 'bus', 'car', 'cat', 'chair',
#           'cow', 'diningtable', 'dog', 'horse',
#           'motorbike', 'person', 'pottedplant',
#           'sheep', 'sofa', 'train', 'tvmonitor')
CLASSES = ('__background__',
           'standing','sitting','sternal_recumb','ventral_recumb','lateral_recumb')
NETS = {'vgg16': ('VGG16',
                  'vgg16_faster_rcnn_iter_40000.caffemodel'),
        'zf': ('ZF',
                  'zf_faster_rcnn_iter_40000.caffemodel')}
#zf_fast_rcnn_iter_1000.caffemodel  ZF_faster_rcnn_final-1.caffemodel  VGG16_faster_rcnn_final.caffemodel
#增加ax
def vis_detections(im, class_name, dets, ax, thresh=0.5):
    """Draw detected bounding boxes."""
    inds = np.where(dets[:, -1] >= thresh)[0]
    if len(inds) == 0:
        return
# 删除这三行
#    im = im[:, :, (2, 1, 0)]
#    fig, ax = plt.subplots(figsize=(12, 12))
#    ax.imshow(im, aspect='equal')
    for i in inds:
        bbox = dets[i, :4]
        score = dets[i, -1]

        ax.add_patch(
            plt.Rectangle((bbox[0], bbox[1]),
                          bbox[2] - bbox[0],
                          bbox[3] - bbox[1], fill=False,
                          edgecolor='red', linewidth=3.5)
            )
        ax.text(bbox[0], bbox[1] - 2,
                '{:s} {:.3f}'.format(class_name, score),
                bbox=dict(facecolor='blue', alpha=0.5),
                fontsize=14, color='white')

    ax.set_title(('{} detections with '
                  'p({} | box) >= {:.1f}').format(class_name, class_name,
                                                  thresh),
                  fontsize=14)
# 删除这三行
#    plt.axis('off')
#    plt.tight_layout()
#    plt.draw()

def demo(net, image_name):
    """Detect object classes in an image using pre-computed object proposals."""

    # Load the demo image
    im_file = os.path.join(cfg.DATA_DIR, 'demo', image_name)
    im = cv2.imread(im_file)

    # Detect all object classes and regress object bounds
    timer = Timer()
    timer.tic()
    scores, boxes = im_detect(net, im)
    timer.toc()
    print ('Detection took {:.3f}s for '
           '{:d} object proposals').format(timer.total_time, boxes.shape[0])

    # Visualize detections for each class
    CONF_THRESH = 0.8
    NMS_THRESH = 0.3
    # 将vis_detections 函数中for 循环之前的3行代码移动到这里
    im = im[:, :, (2, 1, 0)]
    fig,ax = plt.subplots(figsize=(12, 12))
    ax.imshow(im, aspect='equal')
    for cls_ind, cls in enumerate(CLASSES[1:]):
        cls_ind += 1 # because we skipped background
        cls_boxes = boxes[:, 4*cls_ind:4*(cls_ind + 1)]
        cls_scores = scores[:, cls_ind]
        dets = np.hstack((cls_boxes,
                          cls_scores[:, np.newaxis])).astype(np.float32)
        keep = nms(dets, NMS_THRESH)
        dets = dets[keep, :]
        vis_detections(im, cls, dets, ax, thresh=CONF_THRESH)
    # 将vis_detections 函数中for 循环之后的3行代码移动到这里
    plt.axis('off')
    plt.tight_layout()
    plt.draw()

def parse_args():
    """Parse input arguments."""
    parser = argparse.ArgumentParser(description='Faster R-CNN demo')
    parser.add_argument('--gpu', dest='gpu_id', help='GPU device id to use [0]',
                        default=0, type=int)
    parser.add_argument('--cpu', dest='cpu_mode',
                        help='Use CPU mode (overrides --gpu)',
                        action='store_true')
    parser.add_argument('--net', dest='demo_net', help='Network to use [vgg16]',
                        choices=NETS.keys(), default='vgg16')

    args = parser.parse_args()

    return args

if __name__ == '__main__':
    cfg.TEST.HAS_RPN = True  # Use RPN for proposals

    args = parse_args()

    prototxt = os.path.join(cfg.MODELS_DIR, NETS[args.demo_net][0],
                            'faster_rcnn_end2end', 'test.prototxt')
    caffemodel = os.path.join(cfg.DATA_DIR, 'faster_rcnn_models',
                              NETS[args.demo_net][1])

    if not os.path.isfile(caffemodel):
        raise IOError(('{:s} not found.\nDid you run ./data/script/'
                       'fetch_faster_rcnn_models.sh?').format(caffemodel))

    if args.cpu_mode:
        caffe.set_mode_cpu()
    else:
        caffe.set_mode_gpu()
        caffe.set_device(args.gpu_id)
        cfg.GPU_ID = args.gpu_id
    net = caffe.Net(prototxt, caffemodel, caffe.TEST)

    print '\n\nLoaded network {:s}'.format(caffemodel)

    # Warmup on a dummy image
    im = 128 * np.ones((300, 500, 3), dtype=np.uint8)
    for i in xrange(2):
        _, _= im_detect(net, im)

#    im_names = ['000456.jpg', '000542.jpg', '001150.jpg',
#                '001763.jpg', '004545.jpg']
    im_names = ['8172355(13).jpg','12238055(11).jpg',
                '8185056(15).jpg', '8499359(12).jpg','8193000(14).jpg']
    for im_name in im_names:
        print '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
        print 'Demo for data/demo/{}'.format(im_name)
        demo(net, im_name)

    plt.show()


六、测试集评估

测试集评估代码位于:py-faster-rcnn/tools/test_net.py 。该函数用于检测整个测试集的图片,计算评价指标AP和mAP。
使用它,(由于def parse_args():)同样需要在终端给它一些所需的参数。
例如:调用命令 python tools/test_net.py --gpu 0 --def models/pascal_voc/ZF/faster_rcnn_alt_opt/faster_rcnn_test.pt --net output/faster_rcnn_alt_opt/voc_2007_trainval/ZF_faster_rcnn_final.caffemodel --cfg experiments/cfgs/faster_rcnn_alt_opt.yml --imdb voc_2007_test


七、卷积特征可视化

训练好的模型每一层输出特征的可视化,这是一种检验是否训练了一个好模型的参考指标之一。如果卷积图片后特征明显,干净则说明网络有学习到特征,这往往有不错的检测效果表现。如果卷积图片后,特征显示杂乱无章,则往往是网络未收敛,参数设置不当,或网络设计不当等原因,这往往导致很差的检测效果。
代码位于:py-faster-rcnn/tools/feature_visualize.py 。
使用它,需要根据自己的数据修改四个地方:
1.29行等,需要修改类别名,网络地址等。
2.87行,输出卷积特征图的地址
3.183行,带测图片名(图片地址和检测时的地址相同)
4.155行,针对所选择的是四步训练还是端对端训练来修改。

下面是我的feature_visualize.py:

#!/usr/bin/env python
#coding=utf8
# 因为代码中我加了中文注释,所以 上面这行用于指定编码 ,否则python代码执行会报错 (一张图显示多个类别)
# --------------------------------------------------------
# Faster R-CNN
# Copyright (c) 2015 Microsoft
# Licensed under The MIT License [see LICENSE for details]
# Written by Ross Girshick
# --------------------------------------------------------

"""
Demo script showing detections in sample images.

See README.md for installation instructions before running.
"""

import _init_paths
from fast_rcnn.config import cfg
from fast_rcnn.test import im_detect
from fast_rcnn.nms_wrapper import nms
from utils.timer import Timer
import matplotlib.pyplot as plt
import numpy as np
import scipy.io as sio
import caffe, os, sys, cv2
import argparse
import math

CLASSES = ('__background__',
           'standing','sitting','sternal_recumb','ventral_recumb','lateral_recumb')
#CLASSES = ('__background__',
#           'mango')
NETS = {'vgg16': ('VGG16',
                  '/home/snowflake/Downloads/Faster-Rcnn-Muzy/py-faster-rcnn/tools/vgg16_faster_rcnn_iter_40000.caffemodel'),
        'zf': ('ZF',
                  '/home/snowflake/Downloads/Faster-Rcnn-Muzy/py-faster-rcnn/tools/zf_faster_rcnn_iter_40000.caffemodel')} #修改1 #zf_fast_rcnn_iter_1000  ZF_faster_rcnn_final


def vis_detections(im, class_name, dets, thresh=0.5):
    """Draw detected bounding boxes."""
    inds = np.where(dets[:, -1] >= thresh)[0]
    if len(inds) == 0:
        return

    im = im[:, :, (2, 1, 0)]
    fig, ax = plt.subplots(figsize=(12, 12))
    ax.imshow(im, aspect='equal')
    for i in inds:
        bbox = dets[i, :4]
        score = dets[i, -1]

        ax.add_patch(
            plt.Rectangle((bbox[0], bbox[1]),
                          bbox[2] - bbox[0],
                          bbox[3] - bbox[1], fill=False,
                          edgecolor='red', linewidth=3.5)
            )
        ax.text(bbox[0], bbox[1] - 2,
                '{:s} {:.3f}'.format(class_name, score),
                bbox=dict(facecolor='blue', alpha=0.5),
                fontsize=14, color='white')

    ax.set_title(('{} detections with '
                  'p({} | box) >= {:.1f}').format(class_name, class_name,
                                                  thresh),
                  fontsize=14)
    plt.axis('off')
    plt.tight_layout()
    #plt.draw()
def save_feature_picture(data, name, image_name=None, padsize = 1, padval = 1):
    data = data[0]
    #print "data.shape1: ", data.shape
    n = int(np.ceil(np.sqrt(data.shape[0])))
    padding = ((0, n ** 2 - data.shape[0]), (0, 0), (0, padsize)) + ((0, 0),) * (data.ndim - 3)
    #print "padding: ", padding
    data = np.pad(data, padding, mode='constant', constant_values=(padval, padval))
    #print "data.shape2: ", data.shape
    
    data = data.reshape((n, n) + data.shape[1:]).transpose((0, 2, 1, 3) + tuple(range(4, data.ndim + 1)))
    #print "data.shape3: ", data.shape, n
    data = data.reshape((n * data.shape[1], n * data.shape[3]) + data.shape[4:])
    #print "data.shape4: ", data.shape
    plt.figure()
    plt.imshow(data,cmap='gray')
    plt.axis('off')
    #plt.show() #修改2
    if image_name == None:
        img_path = './data/feature_picture/' 
    else:
        img_path = './data/feature_picture/' + image_name + "/"
        check_file(img_path)
    plt.savefig(img_path + name + ".jpg", dpi = 400, bbox_inches = "tight")
def check_file(path):
    if not os.path.exists(path):
        os.mkdir(path)
def demo(net, image_name):
    """Detect object classes in an image using pre-computed object proposals."""

    # Load the demo image
    im_file = os.path.join(cfg.DATA_DIR, 'demo', image_name)
    im = cv2.imread(im_file)
    #im = cv2.imread('/home/snowflake/Downloads/Faster-Rcnn-Muzy/py-faster-rcnn/tools/8478075(11).jpg')

    # Detect all object classes and regress object bounds
    timer = Timer()
    timer.tic()
    scores, boxes = im_detect(net, im)
    for k, v in net.blobs.items():
        if k.find("conv")>-1 or k.find("pool")>-1 or k.find("rpn")>-1:
            save_feature_picture(v.data, k.replace("/", ""), image_name)#net.blobs["conv1_1"].data, "conv1_1") 
    timer.toc()
    print ('Detection took {:.3f}s for '
           '{:d} object proposals').format(timer.total_time, boxes.shape[0])

    # Visualize detections for each class
    CONF_THRESH = 0.8
    NMS_THRESH = 0.3
    for cls_ind, cls in enumerate(CLASSES[1:]):
        cls_ind += 1 # because we skipped background
        cls_boxes = boxes[:, 4*cls_ind:4*(cls_ind + 1)]
        cls_scores = scores[:, cls_ind]
        dets = np.hstack((cls_boxes,
                          cls_scores[:, np.newaxis])).astype(np.float32)
        keep = nms(dets, NMS_THRESH)
        dets = dets[keep, :]
        vis_detections(im, cls, dets, thresh=CONF_THRESH)

def parse_args():
    """Parse input arguments."""
    parser = argparse.ArgumentParser(description='Faster R-CNN demo')
    parser.add_argument('--gpu', dest='gpu_id', help='GPU device id to use [0]',
                        default=0, type=int)
    parser.add_argument('--cpu', dest='cpu_mode',
                        help='Use CPU mode (overrides --gpu)',
                        action='store_true')
    parser.add_argument('--net', dest='demo_net', help='Network to use [zf]',
                        choices=NETS.keys(), default='zf')

    args = parser.parse_args()

    return args

def print_param(net):
    for k, v in net.blobs.items():
	print (k, v.data.shape)
    print ""
    for k, v in net.params.items():
	print (k, v[0].data.shape)  

if __name__ == '__main__':
    cfg.TEST.HAS_RPN = True  # Use RPN for proposals

    args = parse_args()
#修改4
    prototxt = os.path.join(cfg.MODELS_DIR, NETS[args.demo_net][0],
                            'faster_rcnn_end2end', 'test.prototxt')
    #print "prototxt: ", prototxt
    caffemodel = os.path.join(cfg.DATA_DIR, 'faster_rcnn_models',
                              NETS[args.demo_net][1])

    if not os.path.isfile(caffemodel):
        raise IOError(('{:s} not found.\nDid you run ./data/script/'
                       'fetch_faster_rcnn_models.sh?').format(caffemodel))

    if args.cpu_mode:
        caffe.set_mode_cpu()
    else:
        caffe.set_mode_gpu()
        caffe.set_device(args.gpu_id)
        cfg.GPU_ID = args.gpu_id
    net = caffe.Net(prototxt, caffemodel, caffe.TEST)
    
    #print_param(net)

    print '\n\nLoaded network {:s}'.format(caffemodel)

    # Warmup on a dummy image
    im = 128 * np.ones((300, 500, 3), dtype=np.uint8)
    for i in xrange(2):
        _, _= im_detect(net, im)

#    im_names = ['8478075(11).jpg','8185056(15).jpg','8193000(14).jpg','8499359(12).jpg']
    im_names = ['8478075(11).jpg']
    # 修改 3
    for im_name in im_names:
        print '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
        print 'Demo for data/demo/{}'.format(im_name)
        demo(net, im_name)

    #plt.show()




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值