基于OpenCV的人脸识别考勤系统

本文介绍了一种使用OpenCV实现的人脸识别考勤系统,通过图像采集、预处理、特征提取和模型训练,实现人脸识别并进行考勤记录。系统包括UI界面设计、数据库管理和具体功能实现,如人脸信息采集、模型训练和识别,以及考勤信息存储。该系统提高了考勤效率,减少了人为误差。
部署运行你感兴趣的模型镜像

考勤系统设计

学生上课考勤系统最初的方式是采用的人工纸质点名,目前仍旧有一部分学校依旧采用此种方法点名,这种方法也一直是被认为最有效的签到点名方式。但由于课程繁多加上学生人数众多, 代替点名现象普遍存在, 而且传统的现场点名签到方式费时费力, 直接影响到授课质量。人脸签到系统解决了这一问题,可以实时监测所到的成员数量和质量。
其整体结构图为

在这里插入图片描述

  1. 人脸数据库的建立
    系统采集成员的人脸图像,将这些人脸分类标号保存,并建立人脸库。

  2. 图像采集
    摄像头采集人脸图像,前期用于训练模型,后期用于对人脸的预测。

  3. 图像预处理
    由于摄像头设备存在采集图像方法、提取人脸角度、图像背景以及光照变化等干扰信号,使得识别正确率出现不同程度的降低。所以,需将采集到的图像以及检测出来的人脸通过图像处理算法处理。其中处理方法有尺度归一化、图像灰度化、灰度变换、图像增强、以及图像降噪等。

  4. 人脸特征提取及模型训练
    图像特征提取和特征描述是图像目标识别的关键技术,特征提取结果的好坏,直接影响模型训练结果,进而影响了目标识别的效果,在图像检索系统中,特征描述的好坏也会直接影响目标匹配和图像检索的精度。图像处理中特征点的检测与匹配是机器视觉最重要的部分。
    特征提取指的是使用计算机提取视频中的图像信息,决定每个图像的点是否属于一个图像特征。特征提取的结果是把图像上的点分为不同的子集,这些子集往往属于孤立的点、连续的曲线或者连续的区域。特征的好坏对泛化性能有至关重要的影响。
    图像的模型训练即是通过对所提取的人脸特征,不断调整模型参数使人脸图像针对于模型之间的误差值达到最小。用于之后对人脸的预测。

  5. 人脸识别
    提取的人脸图像的特征数据与数据库中存储的特征模板进行搜索匹配,通过设定一个阈值,当相似度超过这一阈值,则把匹配得到的结果输出。人脸识别就是将待识别的人脸特征与已得到的人脸特征模板进行比较,根据相似程度对人脸的身份信息进行判断。这一过程又分为两类:一类是确认,是一对一进行图像比较的过程,另一类是辨认,是一对多进行图像匹配对比的过程。在图像匹配过程中有一个重要特征是:每个识别匹配都具有转置信(confidence)评分,因此可在实际应用中通过对其设置阈值来进行筛选。
    人脸识别是利用提取好的特征,进行身份确认或在人脸库中比对搜索最巧似者。因此,需要选择合适的算法进行识别匹配。在OpenCV中有三种人脸识别的方法,它们分别基于三种不同的算法:Eigenfaces、FisherFaces和Local Binary Pattern Histogram(LBPH)。
    (1)Eigenfaces算法是通过PCA来处理。PCA的本质是识别某个训练集上的主成分,并计算出训练集(图像或帧中的检测到的人脸)相对于数据库的发散程度,并输出一个值。该值越小,表明人脸数据库和检测到的人脸之间的差别就越小;0值表示完全匹配。
    (2)Fisherfaces算法是从PCA中衍生发展出来的,采用更复杂的逻辑;尽管计算更加密集,但比Eigenfaces更容易得到准确效果。
    (3)LBPH算法将检测到的人脸分为小单元,并将其与模型中的对应单元比较,对每个区域匹配值产生一个直方图。
    predict()函数返回含有两个元素的数组:第一个元素是所识别个体的标签,第二个是信度评分。所有的算法都有一个置信度评分阈值,置信度评分用来衡量所识别人脸与原模型的差距,0表示完全匹配。

    可能有时不想保留所有的识别结果,则需进一步处理,因此可用自己的算法来估算识别的置信度评分;例如,如果正在试图识别视频中的人,则可能要分析后续帧的置信度评分来估计识别是否成功。在这种情况下,可通过算法来检查得到的置信度评分,然后得出自己的结论。

  6. 显示签到结果
    分析人脸的特征通过所训练的模型与数据库中进行对比,得到具体人的具体信息,进行签到记入数据库

功能实现

UI界面设计

人脸识别考勤系统共有三个界面:人脸识别考勤系统主界面、人脸数据采集及训练界面和进入考勤系统三部分。具体见下图:人脸识别考勤系统界面整体设计。
在这里插入图片描述

(1)人脸识别考勤系统主界面

人脸识别考勤系统主界面(具体见下图)包含三个按钮,分别是“人脸数据采集及训练”、“进入考勤系统”和“退出考勤”。点击“人脸数据采集及训练”即可进入“人脸数据采集及训练”界面。

在这里插入图片描述

(2)人脸数据采集及训练界面

人脸数据采集及训练界面(具体见下图)包含三个按钮分别是“开始采集”、“人脸采集训练”和“信息采集”(信息采集指text文本框中学生基本信息的输入)。在这一界面首先要输入信息采集框内的具体信息,然后点击“开始采集”按钮即可打开摄像头采集人脸信息并保存至人脸信息数据集;点击“人脸采集训练”按钮即进行人脸数据集信息模型训练;

在这里插入图片描述

(3) 进入人脸识别考勤界面

进入人脸识别考勤界面(具体见下图)包含三个按钮分别是“显示审核信息”(显示审核信息指text文本框中学生基本信息的输出)、“考勤”和“考勤表”。在课前考勤是需先打开本系统,点击“考勤”即可加载摄像头进行人脸识别,人脸识别成功后“显示审核信息”栏内会输出学生基本信息,即代表签到成功。“考勤表”是为方便老师上课时清点签到人数而设置,点击“考勤表”。即显示已签到人人数和具体人员基本信息。

在这里插入图片描述

数据库设计

数据库设计主要分为学生信息和考勤信息两部分。

(1)学生信息

学生信息总表(具体见下表)录入信息时将学号sid和姓名name录入并设置考勤总次数times为0。当进行考勤时,考勤一次将对应人的总次数times增加1。

在这里插入图片描述

(2)考勤表

考勤表(具体见表2)记录考勤人员名单,当进行考勤时,后台将视频识别到的考勤人员信息插入此表,老师可根据查询考勤名单查看考勤人员。
在这里插入图片描述

系统具体功能

  1. 人脸信息采集及存储

首先,需要录入班级所有同学基本信息:姓名、学号并进行对应的人脸信息采集,并保存人脸图像信息,以便后期进行模型训练与人脸识别,具体人脸信息采集及存储流程图见图8.

人脸信息采集首先需要输入被采集学生的基本信息,打开摄像头进行图像采集,如果此学生已存在则会提示已经存在,如若没有则会对此学生进行过人脸采集。人脸采集是在摄像头采集的图片的基础上进行的,由于采集的图片信息周围环境嘈杂,需要进行专门的人脸检测采集人脸信息,在本系统中采用Cascade级联分类器进行人脸检测,主要步骤为先加载OpenCV自带人脸检测器,使用cvtColor函数对图片灰度化处理,在人脸检测中,有效图像的预处理不仅可以提高人脸检测正确率,还可以极大地缩减检测时间,再使用detectMultiScale函数进行人脸识别检测人脸,如若检测到人脸信息,则裁剪灰度帧的区域,大小为200*200像素。整个采集过程中,人脸信息会自动编号保存,采集200张。然后将采集到的人脸信息保存到以此人名称命名的文件夹下,文件名后缀为.pgm,具体如下图所示。
在这里插入图片描述
在这里插入图片描述

  1. 人脸数据集模型训练处理

由于基础的人脸图像无法完成人脸识别匹配,因此需要对采集到的人脸信息图像进行特征分析与统计建立分类模型并进行模型训练。具体人脸数据集模型训练处理流程图见图10。

首先需要从上一步保存的人脸信息数据集中进行图像检索,如若没有图像文件信息则会提示文件不存在并跳出训练过程,若检测到文件存在则会创建特征检测器;使用IMREAD_GRAYSCALE将原始图像转化为灰度化图像打开;然后resize调整大小;返回图像和标签列表;并对其进行人脸特征提取,特征提取结果的好坏,直接影响模型训练结果,进而影响了目标识别的效果;此时我们对获取得人脸信息进行模型训练,并将训练结果保存,具体结果见图11。

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

  1. 人脸识别

人脸识别是整个系统的关键,本系统中采用LBPH进行人脸识别,需要在课前打开摄像头进行人脸考勤,具体人脸识别流程图见图12。

在打开人脸考勤系统时,后台会首先调取数据库中学生基本信息;初始化LBPH人脸识别模型;导入上一步训练好的人脸信息模型训练集;然后调用摄像头,对考勤学生进行人脸信息采集时相同步骤的人脸检测,对摄像头中检测到的人脸进行对比分析和算法比对;预测读取到的人脸信息,返回label和置信度评分。人脸识别中通过置信度评分来丢弃结果,LBPH算法中好的识别参考值要低于50,任何高于80的参考值都会被认为是低的置信度评分。根据置信度评分低于50和label返回数据库中学生信息,人脸识别结束。

在这里插入图片描述

  1. 人脸识别信息与数据库连接,签到表反馈

在上一步的人脸识别出考勤学生后,会将考勤学生的基本学生信息通过数据库插入考勤表中,通过点击考勤表按钮,直观的给教师反馈出实时到的考勤人员信息,具体考勤表、点击考勤表后控制台反馈信息见下图。

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

主要代码

主函数

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from MainWindow0 import MyWindow

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    w = MyWindow()
    w.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowCloseButtonHint)
    w.setFixedSize(w.width(), w.height())
    w.show()
    sys.exit(app.exec_())


功能函数

import sys
import cv2
import os
import numpy as np
from PyQt5.QtCore import QCoreApplication
from PyQt5 import QtWidgets,QtMultimediaWidgets
from MainWindow import Ui_MainWindow
from kaoqin_list import Ui_kaoqin_list
from face_data_collect import Ui_face_data_collect
from face_recognition_sys import Ui_face_recognition_sys
import face_data_collect
import kaoqin_list
import face_recognition_sys
import pymysql
import re

#from collect import *
#from training import *
#from recognition import *
#这是考勤导出窗体
class Mykaoqin(QtWidgets.QWidget, Ui_kaoqin_list):
    def __init__(self, parent=None):
        super(Mykaoqin, self).__init__(parent)
        self.setupUi(self)
    def ButtonEvenbinding(self):
        self.Button.clicked.connect(self.pushButtonclick)
    def pushButton1click(self):
        print('kaoqin')
        #这里面写功能

class mysqloper():


    def __init__(self, parent=None):

        # super(mysqloper, self).__init__(parent)
        self.db = pymysql.connect(host="127.0.0.1", port=3306, user="root", passwd="199808", db="sa")
        # 得到一个可以执行SQL语句的光标对象
        self.cursor = self.db.cursor()  # 使用 execute() 方法执行 SQL 查询
        self.cursor.execute('SELECT VERSION()')
        self.data = self.cursor.fetchone()
        print("Database version : %s " % self.data)
        # 关闭光标对象
        self.cursor.close()

    # 链接数据库
    def mysqlconnect(host="127.0.0.1", port=3306, user="root", passwd="199808", db="sa"):
        db = pymysql.connect(host=host, port=port, user=user, passwd=passwd, db=db)
        return db

    # 执行语句
    def executeCreate(self,con, sql):
        cursor = con.cursor()  # 使用 execute() 方法执行 SQL 查询
        ret = cursor.execute(sql)
        cursor.close()
        return ret

    # 执行插入和更新
    def executeInsertAndUpdate(self,con, sql, listParams):
        try:
            cursor = con.cursor()  # 使用 execute() 方法执行 SQL 查询
            print("前")
            ret = cursor.execute(sql, listParams)
            print("hou")
            # 提交事务
            self.db.commit()
            cursor.close()
            return ret
        except:
            print("跳出")
            return 0




    # 执行查询
    def executeQuery(self,con, sql, listParams):
        cursor = con.cursor()  # 使用 execute() 方法执行 SQL 查询
        cursor.execute(sql, listParams)
        results = cursor.fetchall()
        cursor.close()
        return results

    def table_exists(self,con, table_name):  # 这个函数用来判断表是否存在
        sql = "show tables;"
        cursor = con.cursor()
        cursor.execute(sql)
        tables = [cursor.fetchall()]
        table_list = re.findall('(\'.*?\')', str(tables))
        table_list = [re.sub("'", '', each) for each in table_list]
        cursor.close()
        if table_name in table_list:
            return 1  # 存在返回1
        else:
            return 0  # 不存在返回0

    # 插入函数
    def Insert(self,table, sid, name):
        times = 0
        # 判断是否存在表user1
        existflag = self.table_exists(self.db, table)
        if existflag == 0:  # 不存在
            # 创建表 # 定义要执行的SQL语句
            sql = """CREATE TABLE {0}(
            sid INT(15) PRIMARY KEY ,
            name CHAR(10) NOT NULL UNIQUE,
            times TINYINT 
            );
            """
            # 执行SQL语句
            ret = self.executeCreate(self.db, sql.format(table))
            if ret:
                print('执行成功')
            else:
                print('执行失败')

        # 插入 更新、删除
        sql = "INSERT INTO {0}(name,sid,times) VALUES (%s,%s, %s);"
        # 执行SQL语句
        # print(sql.format(table))
        ret = self.executeInsertAndUpdate(self.db, sql.format(table), [name, sid, times])
        if ret:
            print('执行成功')
        else:
            print('执行失败')

        # 关闭数据库连接
        self.db.close()

    def InsertOnly(self,table,sid,name):
        existflag = self.table_exists(self.db, table)
        # print(existflag)
        if existflag == 0:  # 不存在
            # 创建表 # 定义要执行的SQL语句
            sql = """CREATE TABLE {0}(
                    sid INT PRIMARY KEY,
                    name CHAR(10) NOT NULL UNIQUE
                    );
                    """
            # 执行SQL语句
            ret = self.executeCreate(self.db, sql.format(table))
            if ret:
                print('考勤表创建成功')
            else:
                print('考勤表shibai')
        # 插入 更新、删除

        sql = "INSERT INTO {0}(sid,name) VALUES (%s,%s);"
        # 执行SQL语句
        print(sql.format(table))
        ret = self.executeInsertAndUpdate(self.db, sql.format(table), [sid,name])
        print("AAA")
        if ret:
            print('考勤成功')
        else:
            print('已考勤')


        # 关闭数据库连接
        self.db.close()

    #
    def QueryID(self,table, name):
        # 查询
        sql = "select sid from %s where name={0}"
        results = self.executeQuery(self.db, sql.format(name), table)
        print(results)
        # 关闭数据库连接
        self.db.close()
        return results

    # 查询表中的名字
    def Query(self,table):
        # 查询
        sql = "select * from {0}"
        results = self.executeQuery(self.db, sql.format(table), [])
        names = []
        sids=[]
        # 遍历结果
        for row in results:
            sids.append(row[0])
            names.append(row[1])
        # 关闭数据库连接
        self.db.close()
        return names,sids

    def Increase(self,table,face_ID):
        sql = "update {0} set times=times+1 where sid={1}"
        ret = self.executeInsertAndUpdate(self.db, sql.format(table,face_ID), [])
        if ret:
            print('总表插入成功')
        else:
            print('总表插入失败')

        # 关闭数据库连接
        self.db.close()


#这是识别窗体
class myrecognition(QtWidgets.QWidget, Ui_face_recognition_sys):

    def __init__(self, parent=None):
        super(myrecognition, self).__init__(parent)
        self.setupUi(self)
    def ButtonEvenbinding(self):
        self.Button1.clicked.connect(self.pushButton1click)
        self.Button2.clicked.connect(self.pushButton2click)

    def NameList(self):
        # 查询数据库中的名字
        self.mysql=mysqloper()
        names,sid=self.mysql.Query('student')
        return names,sid

    def cognize(self):
        names,sid = self.NameList()
        recognizer = cv2.face.LBPHFaceRecognizer_create()
        # 读模型
        recognizer.read('./trainer/trainer.yml')
        cascadePath = "./haarcascade_frontalface_default.xml"
        faceCascade = cv2.CascadeClassifier(cascadePath)
        font = cv2.FONT_HERSHEY_SIMPLEX

        # 窗口
        self.cap = cv2.VideoCapture(0)
        minW = 0.1 * self.cap.get(3)
        minH = 0.1 * self.cap.get(4)
        count=0
        while True:
            count=count+1
            ret, img = self.cap.read()
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            faces = faceCascade.detectMultiScale(
                gray,
                scaleFactor=1.2,
                minNeighbors=5,
                minSize=(int(minW), int(minH))
            )

            for (x, y, w, h) in faces:
                cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)

                label, confidence = recognizer.predict(gray[y:y + h, x:x + w])
                confidence = "{0}%".format(round(confidence))
                print(names[label],confidence)
                cv2.putText(img, str(names[label]), (x + 5, y - 5), font, 1, (0, 0, 255), 1)
                cv2.putText(img, str(confidence), (x + 5, y + h - 5), font, 1, (0, 0, 0), 1)

                cv2.imshow('camera', img)
            k = cv2.waitKey(10)
            if k == 27:
                break
            if k == ord('q'):
                break
            # if count > 20:
            #     break
        self.cap.release()
        cv2.destroyAllWindows()
        return names[label],sid[label]

    def pushButton1click(self):
        face_name,face_ID=self.cognize()
        print("------")
        print(face_name)
        print(face_ID)
        print("------")
        self.lineEdit6.setText(str(face_name))
        self.lineEdit7.setText(str(face_ID))
        print("-------")
        face_ID=int(face_ID)
        mysqloper().InsertOnly('student1', face_ID, face_name)
        print("--")
        mysqloper().Increase('student',face_ID)
        print("考勤成功")


        #这里面写功能
    def pushButton2click(self):
        name,sid=mysqloper().Query('student1')

        print("考勤列表")
        print(name)
#这是人脸数据采集及训练窗体
class Myface_collect(QtWidgets.QWidget, Ui_face_data_collect):
    #button1开始采集,button2人脸数据采集训练
    def __init__(self, parent=None):
        super(Myface_collect, self).__init__(parent)
        self.setupUi(self)
    def ButtonEvenbinding(self):
        self.Button1.clicked.connect(self.pushButton1click)
        self.Button2.clicked.connect(self.pushButton2click)
    #点击采集后实现的功能函数
    def video_demo_collect(self):
        self.cap = cv2.VideoCapture(0)
        face_detector = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
        face_name = self.lineEdit1.text()
        # print(face_name)
        face_ID = self.lineEdit2.text()
        # print(face_ID)
        #face_name = input('请输入你要采集的人的姓名(拼音):')
        if not os.path.exists('./Facedata/%s/' % str(face_ID)):
            os.mkdir('./Facedata/%s' % str(face_ID))
            mysqloper().Insert('student', face_ID,face_name)
        else:
            print("已经存在此文件,正在重新读入数据")
        count = 0
        while (True):
            ref, frame = self.cap.read()
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            faces = face_detector.detectMultiScale(gray, 1.3, 5)
            if count > 200:
                break
            for (x, y, w, h) in faces:
                img = cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)
                # font = cv2.FONT_HERSHEY_TRIPLEX
                f = cv2.resize(gray[y:y + h, x:x + w], (200, 200))
                # 保存到路径下
                # 保存图像
                cv2.imwrite('./Facedata/%s/%s.pgm' % (str(face_ID), str(count)), f)
                print(count)
                count += 1
            cv2.imshow('image', frame)
            # 保持画面的持续。
            k = cv2.waitKey(10) & 0xff
            if k == 27:
                break
        print("退出")
        self.cap.release()
        cv2.destroyAllWindows()

    def read_images(self,path, sz=None):
        c = 0
        X, y = [], []
        for dirname, dirnames, filenames in os.walk(path):
            for subdirname in dirnames:
                print(subdirname)
                # subdirname指文件名即人名
                subject_path = os.path.join(dirname, subdirname)
                for filename in os.listdir(subject_path):
                    try:
                        if (filename == ".directory"):
                            continue
                        filepath = os.path.join(subject_path, filename)
                        im = cv2.imread(os.path.join(subject_path, filename), cv2.IMREAD_GRAYSCALE)
                        if (im is None):
                            print("image " + filepath + " is none")
                        # resize to given size (if given)
                        if (sz is not None):
                            im = cv2.resize(im, sz)
                        X.append(np.asarray(im, dtype=np.uint8))
                        y.append(c)
                        # data[subdirname] = data.get(subdirname, []) + X
                    except IOError as e:
                        print("I/O error({0}): {1}".format(e.errno, e.strerror))
                    except:
                        print("Unexpected error:", sys.exc_info()[0])
                        raise
                c = c + 1
        return [X, y]

    def video_demo_train(self):
        path = './Facedata/'
        [X, y] = self.read_images(path)
        y = np.asarray(y, dtype=np.int32)
        # 创建模型
        model = cv2.face.LBPHFaceRecognizer_create()
        # 训练模型
        model.train(np.asarray(X), np.asarray(y))
        # 模型预测
        [p_label, p_confidence] = model.predict(np.asarray(X[0]))
        # 保存模型
        model.write('./trainer/trainer.yml')
        print("训练完成")

    def pushButton1click(self):
        self.video_demo_collect()
        #这里面写功能

    def pushButton2click(self):
        self.video_demo_train()

#这是主窗体
class MyWindow(QtWidgets.QWidget, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MyWindow, self).__init__(parent)
        self.setupUi(self)
        self.ButtonEvenbinding()
        self._isWinodowCreated = True
        self._shouldDrawDebugRects = True
    @property
    def isWindowCreated(self):
        return self._isWindowCreated
    # 每加一个界面就要增加一句self.模块名字.clicked.connect(lambda:self.自己定义的名字())
    # 然后在下面加
    # def 自定义名字1(self):
    # self.form1=QtWidgets.QWidget() 实例化
    # self.s = 界面Py文件名字.界面文件名字的类()
    # self.s.setupUi(self.form1)
    # self.form1.show()
    def ButtonEvenbinding(self):
        self.Button1.clicked.connect(self.pushButton1click)
        self.Button2.clicked.connect(self.pushButton2click)
        self.Button3.clicked.connect(QCoreApplication.instance().quit)
#这个地方其实就是相当于pushButton1click的main函数
    def pushButton1click(self):
        self.form1=QtWidgets.QWidget()
        self.s = Myface_collect()
        self.s.setupUi(self.form1)
        self.s.ButtonEvenbinding()
        self.form1.show()
#这个地方就相当于pushButton2click的main函数
    def pushButton2click(self):
        self.form2 = QtWidgets.QWidget()
        self.s = myrecognition()
        self.s.setupUi(self.form2)
        self.s.ButtonEvenbinding()
        self.form2.show()


您可能感兴趣的与本文相关的镜像

Yolo-v5

Yolo-v5

Yolo

YOLO(You Only Look Once)是一种流行的物体检测和图像分割模型,由华盛顿大学的Joseph Redmon 和Ali Farhadi 开发。 YOLO 于2015 年推出,因其高速和高精度而广受欢迎

评论 102
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

筱文rr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值