痤疮分类-yolov5 学习过程

实验思路:

实验任务是实现痤疮分类任务,并嵌入在PyQT上。

在目标检测、语义分割、实例分割面前我们选择了目标检测的yolov5.但是我们json文件的标注框全是多边形的,通过脚本文件将json文件转化为yolo模型能够识别的标注框为矩形的txt文件。实验结果的mAPI只有不到20%。所以还需要研究。

在PyQt方面,本来想把实验产生的结果图片显示在PyQt上的,但是目标是痤疮,检测出来的标注信息实在太小看不清楚,所以只是在PyQt上显示了文本信息,并且给出结果图片的生成路径。

实验过程

1、在github上下载yolov5-5.0的源码。

GitHub - ultralytics/yolov5: YOLOv5 🚀 in PyTorch > ONNX > CoreML > TFLite

2、制作数据集

借鉴:

Yolov5 多边形标签转换,所有json文件自动转成txt格式[详细过程]_json格式转txt格式_Pandas_007的博客-CSDN博客

yolov5的数据集要求:

数据集文件夹结构

mydata:

接受的文件:

类型:txt文件,

检测框类型:四边形

原始数据集格式

文件夹内:

 json文件内:
{
  "version": "4.5.6",
  "flags": {},
  "shapes": [
    {
      "label": "papule",
      "points": [
        [
          1345.0132018448694,
          2377.567482115941
        ],
        [
          1324.995411499284,
          2388.9108966451063
        ],
        [
          1324.995411499284,
          2390.2454160014786
        ],
        [
          1324.995411499284,
          2392.2471950360373
        ],
        [
          1324.995411499284,
          2393.5817143924096
        ],
        [
          1324.995411499284,
          2394.916233748782
        ],
        [
          1328.3317098902148,
          2416.9358031289257
        ],
        [
          1328.998969568401,
          2420.2721015198567
        ],
        [
          1330.3334889247735,
          2422.2738805544154
        ],
        [
          1330.3334889247735,
          2423.6083999107877
        ],
        [
          1331.0007486029597,
          2425.6101789453464
        ],
        [
          1357.691135730407,
          2437.6208531526977
        ],
        [
          1359.0256550867794,
          2437.6208531526977
        ],
        [
          1361.0274341213378,
          2437.6208531526977
        ],
        [
          1361.694693799524,
          2436.2863337963254
        ],
        [
          1363.0292131558963,
          2434.951814439953
        ],
        [
          1381.7124841451096,
          2412.932245059809
        ],
        [
          1381.7124841451096,
          2411.5977257034365
        ],
        [
          1381.7124841451096,
          2410.263206347064
        ],
        [
          1381.7124841451096,
          2408.928686990692
        ],
        [
          1379.7107051105509,
          2406.926907956133
        ],
        [
          1379.0434454323647,
          2404.9251289215745
        ],
        [
          1366.3655115468273,
          2391.579935357851
        ],
        [
          1365.030992190455,
          2390.912675679665
        ],
        [
          1363.6964728340827,
          2390.2454160014786
        ],
        [
          1362.3619534777101,
          2389.5781563232927
        ]
      ],
      "group_id": null,
      "shape_type": "polygon",
      "flags": {}
    },
检测框类型:

标注的检测框为多边形

json文件标注信息:

Labelme是一种常用的开源工具,用于图像标注和语义分割任务。它的标注数据以JSON格式存储,下面是对Labelme标注的JSON格式的解释:

Labelme的JSON文件包含了图像的标注信息,通常包括以下主要字段:

1、"version": 标注数据的版本号。
2、"flags": 可选字段,用于存储标注的一些标志或注释。
3、"shapes": 包含一个数组,每个元素表示一个标注的形状。

"label": 标注的类别或标签名称。
"points": 形状的顶点坐标列表。对于矩形,可以使用四个顶点坐标来表示。
"group_id": 可选字段,用于标识形状所属的组。
"shape_type": 形状的类型,如"rectangle"表示矩形,"polygon"表示多边形等。
"flags": 可选字段,用于存储形状的一些标志或注释。
"imagePath": 图像文件的路径或名称。
4、"imageData": 图像的原始数据,可以是Base64编码的图像数据。

5、"imageHeight": 图像的高度。

6、"imageWidth": 图像的宽度。

7、"imageDepth": 图像的通道数,通常为3(RGB图像)或1(灰度图像)。

这些是Labelme标注的JSON格式的主要字段和解释。通过解析JSON文件,可以提取出图像的标注信息,以便进行后续的处理和分析。

每个points是有很多歌列表组成,其中每个列表由两个数组成,怎么解释:

在Labelme的JSON格式中,"points"字段表示标注形状的顶点坐标列表。每个"points"列表由两个数组组成,其中每个数组表示一个点的坐标。

具体而言,每个"points"列表由一系列的点组成,每个点由两个值构成,通常是(x, y)坐标。这些点按照顺序连接起来,形成一个封闭的多边形或者一个线段。

例如,对于一个矩形的标注,可以使用四个点的坐标来表示。在"points"字段中,会有一个包含四个列表的数组,每个列表由两个值组成,表示一个点的坐标。这些点按照顺序连接起来,形成一个矩形的边界。

对于多边形的标注,"points"字段中的数组会包含多个列表,每个列表由两个值表示一个点的坐标。这些点按照顺序连接起来,形成一个封闭的多边形。

总之,"points"字段中的每个列表由两个数组组成,表示一个点的坐标,这些点按照顺序连接起来形成一个形状的边界或轮廓。

 提取有用的文件

       根据labelme_20220329文件夹中的文件名找到image文件夹下同名的图片文件,并将其存放在新的文件夹中:

import os
import shutil

# 指定存放一堆JSON文件的文件夹路径和存放图片的文件夹路径
json_folder = 'labelme_20220329'
image_folder = 'image'

# 指定新创建的文件夹路径用于存放找到的图片
output_folder = 'found_images'
os.makedirs(output_folder, exist_ok=True)

# 遍历labelme_20220329文件夹中的所有文件
for filename in os.listdir(json_folder):
    json_path = os.path.join(json_folder, filename)
    
    # 提取文件名(不包含扩展名)
    file_name = os.path.splitext(filename)[0]
    
    # 构造对应的图片文件路径
    image_path = os.path.join(image_folder, file_name + '.jpg')  # 假设图片扩展名为.jpg
    
    # 如果图片文件存在,则将其复制到新创建的文件夹中
    if os.path.exists(image_path):
        shutil.copy(image_path, output_folder)

json文件转为txt文件

参考文章:

Yolov5 多边形标签转换,所有json文件自动转成txt格式[详细过程]_json格式转txt格式_Pandas_007的博客-CSDN博客

检测框由多边形转为矩形

#处理labelme多边形矩阵的标注  json转化txt
import json
import os
name2id={'closed_comedo':0,'opend_comedo':1,'papule':2,'pustule':3,'nodule':4,'atrophic_scar':5,'hypertrophic_scar':6,'melasma':7,'nevus':8,'other':9}
 
def convert(img_size, box):
    dw = 1. / (img_size[0])
    dh = 1. / (img_size[1])
    x = (box[0] + box[2]) / 2.0 
    y = (box[1] + box[3]) / 2.0 
    w = abs(box[2] - box[0])
    h = abs(box[3] - box[1])
    x = x * dw
    w = w * dw
    y = y * dh
    h = h * dh
    return (x, y, w, h)
 
def decode_json(json_floder_path,txt_outer_path, json_name):
    txt_name = os.path.join(txt_outer_path, json_name[:-5] + '.txt')
    with open(txt_name,'w') as f:
        json_path = os.path.join(json_floder_path, json_name)#os路径融合
        data = json.load(open(json_path, 'r', encoding='gb2312',errors='ignore'))
        img_w = data['imageWidth']#图片的高
        img_h = data['imageHeight']#图片的宽
        isshape_type=data['shapes'][0]['shape_type']
        print(isshape_type)
        #print(isshape_type)
        #TODO print('下方判断根据这里的值可以设置为你自己的类型,我这里是polygon'多边形)
        for i in data['shapes']:
            label_name = i['label']#得到json中你标记的类名
            if (i['shape_type'] == 'polygon'):#数据类型为多边形 需要转化为矩形
                x_max=0
                y_max=0
                x_min=100000
                y_min=100000
                for lk in range(len(i['points'])):
                    x1=float(i['points'][lk][0])
                    y1=float(i['points'][lk][1])
                   # print(x1)
                    if x_max<x1:
                        x_max=x1
                    if y_max<y1:
                        y_max=y1
                    if y_min>y1:
                        y_min=y1
                    if x_min>x1:
                        x_min=x1
                bb = (x_min, y_max, x_max, y_min)
            if (i['shape_type'] == 'rectangle'):#为矩形不需要转换
                x1 = float(i['points'][0][0])
                y1 = float(i['points'][0][1])
                x2 = float(i['points'][1][0])
                y2 = float(i['points'][1][1])
                bb = (x1, y1, x2, y2)
            bbox = convert((img_w, img_h), bb)
            try:
                f.write(str(name2id[label_name]) + " " + " ".join([str(a) for a in bbox]) + '\n')
            except:
                pass
 
if __name__ == "__main__":
    json_floder_path = './labelme_20220329'#存放json的文件夹的绝对路径
    txt_outer_path='./labelme_txt'#存放txt的文件夹绝对路径
    json_names = os.listdir(json_floder_path)
    print("共有:{}个文件待转化".format(len(json_names)))
    flagcount=0
    for json_name in json_names:
        decode_json(json_floder_path,txt_outer_path,json_name)
        flagcount+=1
        print("还剩下{}个文件未转化".format(len(json_names)-flagcount))
       # break
    print('转化全部完毕')

将整个数据集化为9:1比例的训练集和测试集

split_data.py

import os
import random
import shutil

# 设置文件夹路径和划分比例
image_folder = "images"
label_folder = "labelme_txt"
train_folder = "train"
test_folder = "test"
split_ratio = 0.9

# 创建目标文件夹
os.makedirs(os.path.join(train_folder, "images"), exist_ok=True)
os.makedirs(os.path.join(train_folder, "labels"), exist_ok=True)
os.makedirs(os.path.join(test_folder, "images"), exist_ok=True)
os.makedirs(os.path.join(test_folder, "labels"), exist_ok=True)

# 获取图片文件列表
image_files = os.listdir(image_folder)

# 随机打乱文件列表
random.shuffle(image_files)

# 计算划分的索引
split_index = int(len(image_files) * split_ratio)

# 划分并移动文件到相应的文件夹
for i, image_file in enumerate(image_files):
    image_path = os.path.join(image_folder, image_file)
    label_file = image_file.replace(".jpg", ".txt")
    label_path = os.path.join(label_folder, label_file)

    if i < split_index:
        # 移动到训练集文件夹
        shutil.move(image_path, os.path.join(train_folder, "images", image_file))
        shutil.move(label_path, os.path.join(train_folder, "labels", label_file))
    else:
        # 移动到测试集文件夹
        shutil.move(image_path, os.path.join(test_folder, "images", image_file))
        shutil.move(label_path, os.path.join(test_folder, "labels", label_file))

3、结果显示调整

借鉴文章:

yolov5/v7修改标签和检测框显示【最全】_plots.py中的box_label函数-CSDN博客

都在plots.py文件中修改

def plot_one_box(x, img, color=None, label=None, line_thickness=3):
    # Plots one bounding box on image img
    tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1  # line/font thickness
    color = color or [random.randint(0, 255) for _ in range(3)]
    c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
    ################################################################################
    # cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
    if 'closed_comedo' in label:
        color = (242,140,165)
        cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
    if 'opend_comedo' in label:
        color = (157, 217, 165)
        cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
    if 'papule' in label:
        color = (255, 240, 140)
        cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
    if 'pustule' in label:
        color = (161, 177, 235)
        cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
    if 'nodule' in label:
        color = (250, 192, 152)
        cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
    if 'atrophic_scar' in label:
        color = (200, 142, 217)
        cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
    if 'hypertrophic_scagr' in label:
        color = (160, 233, 249)
        cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
    if 'melasma' in label:
        color = (247, 152, 242)
        cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
    if 'nevus' in label:
        color = (223, 247, 162)
        cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
    if 'other' in label:
        color = (252, 222, 233)
        cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
    ################################################################################
    if label:
        tf = max(tl - 1, 1)  # font thickness
        t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
        c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
        ################################################################################
        # cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA)  # filled
        # cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)
        ################################################################################
        cv2.putText(img, label, (c1[0], c1[1] - 12), 0, tl / 3, color, thickness=tf, lineType=cv2.LINE_AA)
  • 注意:因为后面是cv2保存图像,cv2保存图像的顺序是BGR,所以颜色卡需要先手动把RGB顺序变为BGR。(ctrl+alt+A的截图快捷键显示的是RGB)

4、计数

参考文章:

YOLOv5实现目标分类计数并显示在图像上_图像识别计数-CSDN博客

YOLOv5实现目标计数_yolov5计数_Albert_yeager的博客-CSDN博客

查找'cup'names列表中的索引:

names = ['person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light',
         'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow',
         'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',
         'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard',
         'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
         'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',
         'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone',
         'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear',
         'hair drier', 'toothbrush']

cup_index = names.index('cup')
print(cup_index)

各类别计数:

在detect.py里修改,如果要嵌入PyQt,则直接在当时界面的后端里面修改,因为我的detect.py文件直接嵌入在了enterTest3.py文件中。

               ############################################################################################
                # Write results+计数
                # count=0
                # person_count = 0
                # cup_count = 0
                closed_comedo_count = 0
                opend_comedo_count = 0
                papule_count = 0
                pustule_count = 0
                nodule_count = 0
                atrophic_scar_count = 0
                hypertrophic_scar_count = 0
                melasma_count = 0
                nevus_count = 0
                other_count = 0
                for *xyxy, conf, cls in reversed(det):
                    if save_txt:  # Write to file
                        xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # normalized xywh
                        line = (cls, *xywh, conf) if opt.save_conf else (cls, *xywh)  # label format
                        with open(txt_path + '.txt', 'a') as f:
                            f.write(('%g ' * len(line)).rstrip() % line + '\n')

                    if save_img or view_img:  # Add bbox to image
                        #c = int(cls)# integer class分类数
                        #label = '%s %.2f  num: %d' % (names[int(cls)], conf, person_count)
                        label = f'{names[int(cls)]} {conf:.2f}'
                        plot_one_box(xyxy, im0, label=label, color=colors[int(cls)], line_thickness=3)
                    ##########################分类计数##########################
                        # if int(cls) == 0:
                        #     person_count += 1
                        # if int(cls) == 41:
                        #     cup_count += 1
                        if int(cls) == 0:
                            closed_comedo_count += 1
                        if int(cls) == 1:
                            opend_comedo_count += 1
                        if int(cls) == 2:
                            papule_count += 1
                        if int(cls) == 3:
                            pustule_count += 1
                        if int(cls) == 4:
                            nodule_count += 1
                        if int(cls) == 5:
                            atrophic_scar_count += 1
                        if int(cls) == 6:
                            hypertrophic_scar_count += 1
                        if int(cls) == 7:
                            melasma_count += 1
                        if int(cls) == 8:
                            nevus_count += 1
                        if int(cls) == 9:
                            other_count += 1
                        # count = count+1
                ############################################################################################

结果显示的修改

            if view_img:
                ##############################视频识别显示计数内容####################################
                text = 'person_num:%d ' % (person_count)
                cv2.putText(im0, text, (180, 50), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 255), 5)
                text = 'cup_num:%d ' % (cup_count)
                cv2.putText(im0, text, (180, 120), cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 0), 5)
                ####################################################################################
                cv2.imshow(str(p), im0)
                cv2.waitKey(1)  # 1 millisecond

            # Save results (image with detections)
            if save_img:
                ##############################视频识别显示计数内容####################################
                # text = 'person_num:%d ' % (person_count)
                # cv2.putText(im0, text, (180, 50), cv2.FONT_HERSHEY_SIMPLEX, 0.2, (0, 0, 255), 2)
                # print(text)
                # text = 'cup_num:%d ' % (cup_count)
                # cv2.putText(im0, text, (180, 120), cv2.FONT_HERSHEY_SIMPLEX, 0.2, (255, 0, 0), 2)
                # print(text)
                text = 'closed_comedo_count:%d ' % (closed_comedo_count)
                cv2.putText(im0, text, (180, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 255), 2)
                print(text)
                text = 'opend_comedo_count:%d ' % (opend_comedo_count)
                cv2.putText(im0, text, (180, 120), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 255), 2)
                print(text)
                text = 'papule_count:%d ' % (papule_count)
                cv2.putText(im0, text, (180, 190), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 255), 2)
                print(text)
                text = 'pustule_count:%d ' % (pustule_count)
                cv2.putText(im0, text, (180, 260), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 255), 2)
                print(text)
                text = 'nodule_count:%d ' % (nodule_count)
                cv2.putText(im0, text, (180, 330), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 255), 2)
                print(text)
                text = 'atrophic_scar_count:%d ' % (atrophic_scar_count)
                cv2.putText(im0, text, (180, 400), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 255), 2)
                print(text)
                text = 'hypertrophic_scar_count:%d ' % (hypertrophic_scar_count)
                cv2.putText(im0, text, (180, 470), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 255), 2)
                print(text)
                text = 'melasma_count:%d ' % (melasma_count)
                cv2.putText(im0, text, (180, 540), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 255), 2)
                print(text)
                text = 'nevus_count:%d ' % (nevus_count)
                cv2.putText(im0, text, (180, 610), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 255), 2)
                print(text)
                text = 'other_count:%d ' % (other_count)
                cv2.putText(im0, text, (180, 680), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 255), 2)
                print(text)
                ####################################################################################
                if dataset.mode == 'image':
                    cv2.imwrite(save_path, im0)
                else:  # 'video' or 'stream'
                    if vid_path != save_path:  # new video
                        vid_path = save_path
                        if isinstance(vid_writer, cv2.VideoWriter):
                            vid_writer.release()  # release previous video writer
                        if vid_cap:  # video
                            fps = vid_cap.get(cv2.CAP_PROP_FPS)
                            w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
                            h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
                        else:  # stream
                            fps, w, h = 30, im0.shape[1], im0.shape[0]
                            save_path += '.mp4'
                        vid_writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h))
                    vid_writer.write(im0)

这将输出48,表示'cup'names列表中的索引为48

5、yolov5可视化训练结果以及result.txt解析:

参考文献:

yolov5的训练过程中的打印信息“P”,"R","mAP@.5","mAP@.5:.95:100%"是什么意思:
  1. "P":这个指标表示精确率(Precision),即检测到的目标中真实目标的比例。它是通过计算模型预测的正样本中真实目标的比例得到的。

  2. "R":这个指标表示召回率(Recall),即成功检测到的目标在所有真实目标中的比例。它是通过计算模型成功检测到的正样本在所有真实正样本中的比例得到的。

  3. "mAP@.5":这个指标表示在IoU阈值为0.5时的平均精确率(mean Average Precision)。它是通过计算每个类别在IoU阈值为0.5时的Average Precision(AP),然后对所有类别的AP进行平均得到的。

  4. "mAP@.5:.95:100%":这个指标表示在IoU阈值从0.5到0.95以及步长为0.05的范围内的平均精确率。它是通过计算每个类别在不同IoU阈值下的AP,并在整个范围内进行插值得到的。

result.txt解析

yolov5-3.1中train.py第322行有如下的代码

# Write
with open(results_file, 'a') as f:
	f.write(s + '%10.4g' * 7 % results + '\n')  # P, R, mAP@.5, mAP@.5-.95, val_loss(box, obj, cls)
if len(opt.name) and opt.bucket:
	os.system('gsutil cp %s gs://%s/results/results%s.txt' % (results_file, opt.bucket, opt.name))

这部分代码应该是把每次迭代结果写入result.txt

根据相应注释可推测result.txt中

倒数第7列至倒数第4列是每次迭代后相应的Precision、Recall、mAP@0.5、mAP@0.5:0.95

6、PyQt

借鉴文章:

7、Jupyter

在Jupyter里压缩文件

# 压缩当前路径所有文件,输出zip文件
path='./autodl-tmp'

import zipfile,os
zipName = 'yolov5_learn.zip' #压缩后文件的位置及名称
f = zipfile.ZipFile( zipName, 'w', zipfile.ZIP_DEFLATED )
for dirpath, dirnames, filenames in os.walk(path):
    for filename in filenames:
        print(filename)
        f.write(os.path.join(dirpath,filename))
f.close()

完整代码

链接:https://pan.baidu.com/s/1F8Nme9Tawnil0FGb4dQhQg 
提取码:jr27

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值