Qt调用工业相机之巴斯勒相机

作者:billy
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处

开发环境

操作系统:Windows 10 1903 18362.778
相机型号:BASLER acA 1300-60gm
相机软件:pylon_Runtime_6.0.0.17906,Basler_pylon_5.0.10.10613
软件版本:Qt 5.12.8, vs2017 Community
通信方式:GigE - 千兆以太网通信协议

驱动与SDK

开发包和驱动下载地址
提取码:lg0i

驱动安装完成之后需要完成一些配置才能成功连接相机

  1. 将相机通电,使用网线将相机与电脑连接。确保相机与电脑处于连接状态。可以观察网
    络连接中,本地连接的状态;
  2. 网络连接中,右键本地连接,选择属性。在属性界面中点击”配置”按钮,进入”高级”选项卡,设置 Jumbo Packet (巨型帧)为当前可设置的最大值。点击确定保存参数;
    在这里插入图片描述
    在这里插入图片描述
  3. 在网络连接中,右键”本地连接”,双击 “Internet 协议版本 4”,在弹出界面中勾选 “自动获得 IP 地址”
    ,点击确定保存参数;
    在这里插入图片描述
    在这里插入图片描述
  4. 打开 “Pylon IP Configurator”工具,在列表中选择当前连接的相机。选择相机后,选择 “DHCP”,点击保存。”Refresh” 确保相机的 “Status” 为 ”OK”,并且界面右下角没有警告;
    在这里插入图片描述

安装驱动和开发包完成之后,可以找到以下目录:

  1. C:\Program Files\Basler\pylon 5
    在这里插入图片描述
  • Applications - 应用程序 pylon Viewer 和 pylon IP Configurator;
  • Development - 开发文档、头文件、库文件、示例程序源代码等等;
  • License - 开发许可证;
  • Runtime - 运行时需要的依赖库(打包程序时需要包含);
  • Release Notes.txt - 发行笔记;

巴斯勒相机介绍

巴斯勒相机实时获取图像的原理就是对每一帧数据的处理,即 拍照 - 处理数据 - 拍照 - 处理数据 …
SDK中提供了两种方式来处理图像,我们先来看一下最主要的开始抓取图像的函数 StartGrabbing:
virtual void StartGrabbing( EGrabStrategy strategy = GrabStrategy_OneByOne,
EGrabLoop grabLoopType = GrabLoop_ProvidedByUser);

来看一下函数的两个参数代表什么含义

  • One by One Grab Strategy
    这种模式是最简单的,也是 CInstantCamera 默认的图像获取策略。
    获取到的图像放到一个FIFO 队列中。每次我们从队列中取的(RetrieveResult)都是最早放到队列中的图像

  • Latest Image Only Grab Strategy
    这个策略下只有最新到的图像保存在输出队列中。
    如果一份旧图像还没被读取新图像就又来了,那么旧图像就被舍弃。这样可以保证读取到图像永远都是读取时刻最新的。

  • Latest Images Grab Strategy
    这个模式下输出队列可以保存不止一张图像,具体最多能保存几张图象由 CInstantCamera::OutputQueueSize 来指定。
    当新的图像来了,而队列已满时,旧的图像被舍弃。
    当 CInstantCamera::OutputQueueSize 为 1 时就是 Latest Image Only Grab Strategy。
    当 CInstantCamera::OutputQueueSize = CInstantCamera::MaxNumBuffer,就和 One by One Grab Strategy 一样了。

  • Upcoming Image Grab Strategy
    这个模式很特别,只有当 CInstantCamera::RetrieveResult() 被调用后才会采集图像。USB 相机不支持这种策略。

  • GrabLoop_ProvidedByUser
    用户在代码中循环调用RetrieveResult() 来处理抓取的图像和相机事件。默认使用这种模式

  • GrabLoop_ProvidedByInstantCamera
    抓取循环线程由即时相机提供。它会不断循环调用RetrieveResult()。抓取的图像由注册的图像事件处理程序处理。抓取循环线程在抓取启动时启动

StartGrabbing的第一个参数代表获取图像的策略,第二个参数代表的是使用哪个循环线程来抓取图像。由第二个参数我们就可以分两种方式来处理图像数据:

  1. 用户自己处理,步骤如下:
  • 调用 StartGrabbing 开始抓取图像
    m_baslerCamera.StartGrabbing(GrabStrategy_OneByOne, GrabLoop_ProvidedByUser);
  • 用户自己循环调用 RetrieveResult
    m_baslerCamera.RetrieveResult(1000, ptrGrabResult, TimeoutHandling_ThrowException);
  • 处理数据 ptrGrabResult
    CImagePersistence::Save(ImageFileFormat_Bmp, filename, ptrGrabResult);
  1. 使用相机的循化线程,步骤如下:
  • 注册图像事件处理程序
    m_baslerCamera.RegisterImageEventHandler(this, RegistrationMode_Append, Cleanup_Delete);
  • 重写图像处理函数 OnImageGrabbed
    virtual void OnImageGrabbed(CInstantCamera &camera, const CGrabResultPtr &grabResult) override;
  • 调用 StartGrabbing 开始抓取图像
    m_baslerCamera.StartGrabbing(GrabStrategy_OneByOne, GrabLoop_ProvidedByInstantCamera);
  • 注意事项:相机获取到图像就会调用回调函数 OnImageGrabbed,图像处理是在不同的线程所以需要加锁

我的代码

实现功能:相机图像的实时显示,并且可以在需要的时候获取当前帧数据,用于分析或者保存;

首先需要在pro中配置头文件和库文件

INCLUDEPATH += $$PWD/include/ \
               $$PWD/include/pylon

windows {
    contains(DEFINES, WIN64) {
        LIBS += -L$$PWD/lib/x64/ -lGCBase_MD_VC120_v3_0_Basler_pylon_v5_0
        LIBS += -L$$PWD/lib/x64/ -lGenApi_MD_VC120_v3_0_Basler_pylon_v5_0
        LIBS += -L$$PWD/lib/x64/ -lPylonBase_MD_VC120_v5_0
        LIBS += -L$$PWD/lib/x64/ -lPylonC_MD_VC120
        LIBS += -L$$PWD/lib/x64/ -lPylonGUI_MD_VC120_v5_0
        LIBS += -L$$PWD/lib/x64/ -lPylonUtility_MD_VC120_v5_0
    } else {
        LIBS += -L$$PWD/lib/Win32/ -lGCBase_MD_VC120_v3_0_Basler_pylon_v5_0
        LIBS += -L$$PWD/lib/Win32/ -lGenApi_MD_VC120_v3_0_Basler_pylon_v5_0
        LIBS += -L$$PWD/lib/Win32/ -lPylonBase_MD_VC120_v5_0
        LIBS += -L$$PWD/lib/Win32/ -lPylonC_MD_VC120
        LIBS += -L$$PWD/lib/Win32/ -lPylonGUI_MD_VC120_v5_0
        LIBS += -L$$PWD/lib/Win32/ -lPylonUtility_MD_VC120_v5_0
    }
}

自定义相机基类 Camera

#ifndef CAMERA_H
#define CAMERA_H

#include <QObject>
#include <QImage>
#include <QTimer>
#include <QMutex>

#include <opencv2/opencv.hpp>

class Camera : public QObject
{
    Q_OBJECT

public:
    enum CameraType {
        Basler = 1,                     // 巴斯勒相机
        IC_Imaging,                     // 映美精相机
        MV,                             // 海康威视相机
        Virtual                         // 虚拟相机
    };

    explicit Camera(CameraType type = Basler) : m_type(type) {}

    virtual void initCamera() = 0;      // 初始化相机
    virtual void destroyCamera() = 0;   // 销毁相机
    virtual void openCamera() = 0;      // 打开相机
    virtual void closeCamera() = 0;     // 关闭相机
    virtual void startWork() = 0;       // 开始工作
    virtual void stopWork() = 0;        // 停止工作
    virtual cv::Mat takeAPic() = 0;     // 获取当前图像

    void start() { m_timer.start(); }
    void stop() { m_timer.stop(); }
    void setInterval(int time) { m_timer.setInterval(time); }
    CameraType getCameraType() { return m_type; }

signals:
    void updateImage(QImage image);

protected:
    CameraType m_type;
    QMutex m_mutex;
    QTimer m_timer;
};

#endif // CAMERA_H

自定义 BBaslerCamerControl 相机控制类

#ifndef BBASLERCAMERCONTROL_H
#define BBASLERCAMERCONTROL_H

#include "../camera.h"
#include "PylonIncludes.h"

using namespace std;
using namespace Pylon;
using namespace GenApi;

class BBaslerCamerControl : public Camera
{
    Q_OBJECT
public:
    enum BaslerCameraProperty {
        DeviceModelName,            // 相机名称
        DeviceID,                   // 相机SN号
        ResultingFrameRateAbs,      // 相机帧率
        AcquisitionFrameRateAbs,    // 相机频率
        Freerun,                    // 相机内触发
        Line1,                      // 相机外触发
        ExposureTimeAbs,            // 相机曝光时间
        GainRaw,                    // 相机增益
        SensorWidth,                // 传感器宽度
        SensorHeight,               // 传感器高度
        Width,                      // 图片宽度
        Height,                     // 图片高度
        PixelFormat,                // 图片的格式
    };

    explicit BBaslerCamerControl(Camera::CameraType type = Camera::CameraType::Basler);

    virtual void initCamera() override;
    virtual void destroyCamera() override;

    virtual void openCamera() override;
    virtual void closeCamera() override;

    virtual void startWork() override;
    virtual void stopWork() override;

    virtual cv::Mat takeAPic() override;

    QString getCameraProperty(BBaslerCamerControl::BaslerCameraProperty type);                  // 获取相机参数
    void setCameraProperty(BBaslerCamerControl::BaslerCameraProperty type, double value = 0.0); // 设置相机参数

public slots:
    void updateFrame();

private:
    CInstantCamera m_baslerCamera;  // 实例化相机对象
    INodeMap *m_nodeMap;            // 相机属性节点
    QString m_currentMode;          // 相机触发模式
};

#endif // BBASLERCAMERCONTROL_H
#include "bbaslercamercontrol.h"
#include "globalfun.h"

BBaslerCamerControl::BBaslerCamerControl(Camera::CameraType type) : Camera(type)
{
    m_timer.setInterval(GlobalValue::cam_itl);
    connect(&m_timer, &QTimer::timeout, this, &BBaslerCamerControl::updateFrame);
}

void BBaslerCamerControl::initCamera()
{
    try {
        Pylon::PylonInitialize();   // 调用其他 pylon 函数之前必须调用 PylonInitialize 完成初始化
    } catch (GenICam::GenericException &e) {
        qDebug() << "initCamera error: " + QString::fromLocal8Bit(e.what());
    }
}

void BBaslerCamerControl::destroyCamera()
{
    try {
        stopWork();
        closeCamera();

        Pylon::PylonTerminate();    // 释放 pylon 运行时系统分配的资源
    } catch (GenICam::GenericException &e) {
        qDebug() << "destroyCamera error: " + QString::fromLocal8Bit(e.what());
    }
}

void BBaslerCamerControl::openCamera()
{
    try {
        m_baslerCamera.Attach(CTlFactory::GetInstance().CreateFirstDevice());   //实例化找到的第一个相机
        m_baslerCamera.Open();      // 打开相机

        m_nodeMap = &m_baslerCamera.GetNodeMap();   // 获取相机属性节点
        m_currentMode = getCameraProperty(Line1);   // 获取相机触发模式
    } catch (GenICam::GenericException &e) {
        qDebug() << "openCamera error: " + QString::fromLocal8Bit(e.what());
    }
}

void BBaslerCamerControl::closeCamera()
{
    try {
        if ( m_baslerCamera.IsOpen() ) {
            m_baslerCamera.DetachDevice();  // 分离连接的 pylon 设备
            m_baslerCamera.Close();         // 关闭连接的 pylon 设备

            m_nodeMap = nullptr;            // 相机属性节点置空
            m_currentMode = "";             // 相机触发模式置空
        }

    } catch (GenICam::GenericException &e) {
        qDebug() << "closeCamera error: " + QString::fromLocal8Bit(e.what());
    }
}

void BBaslerCamerControl::startWork()
{
    // Check if camera is open.
    if ( !m_baslerCamera.IsOpen() ) {
        return;
    }

    try {
        m_baslerCamera.StartGrabbing(GrabStrategy_LatestImageOnly, GrabLoop_ProvidedByUser);  // 开始抓取图像
        m_timer.start();
    } catch (GenICam::GenericException &e) {
        qDebug() << "startWork error: " + QString::fromLocal8Bit(e.what());
    }
}

void BBaslerCamerControl::stopWork()
{
    try {
        if ( m_baslerCamera.IsGrabbing() ) {
            m_baslerCamera.StopGrabbing();  // 停止抓取图像
            m_timer.stop();
        }
    } catch (GenICam::GenericException &e) {
        qDebug() << "stopWork error: " + QString::fromLocal8Bit(e.what());
    }
}

cv::Mat BBaslerCamerControl::takeAPic()
{
    // Check if camera is open.
    if ( !m_baslerCamera.IsOpen() ) {
        return cv::Mat();
    }

    QMutexLocker locker(&m_mutex);

    try {
        CGrabResultPtr ptrGrabResult;
        m_baslerCamera.RetrieveResult(1000, ptrGrabResult, TimeoutHandling_ThrowException);

        if ( ptrGrabResult->GrabSucceeded() ) {
            CPylonImage pylogimage;
            CImageFormatConverter formatconverter;
            formatconverter.OutputPixelFormat = PixelType_RGB8packed;
            formatconverter.Convert(pylogimage, ptrGrabResult);

            cv::Mat mat = cv::Mat(int( ptrGrabResult->GetHeight() ),
                                  int( ptrGrabResult->GetWidth() ),
                                  CV_8UC3,
                                  static_cast< uchar* >( pylogimage.GetBuffer() ));

            return mat.clone();
        } else {
            return cv::Mat();
        }
    } catch (GenICam::GenericException &e) {
        qDebug() << "takeAPic error: " + QString::fromLocal8Bit(e.what());
        return cv::Mat();
    }
}

QString BBaslerCamerControl::getCameraProperty(BBaslerCamerControl::BaslerCameraProperty type)
{
    // Check if camera is open.
    if ( !m_baslerCamera.IsOpen() ) {
        return "";
    }

    QString ret = "";

    try {
        switch (type) {
        case DeviceModelName: {
            const CStringPtr deviceModelName = m_nodeMap->GetNode("DeviceModelName");
            ret = QString::fromLocal8Bit(deviceModelName->ToString().c_str());
        } break;
        case DeviceID: {
            const CStringPtr deviceID = m_nodeMap->GetNode("DeviceID");
            ret = QString::fromLocal8Bit(deviceID->ToString().c_str());
        } break;
        case ResultingFrameRateAbs: {
            const CFloatPtr resultingFrameRateAbs = m_nodeMap->GetNode("ResultingFrameRateAbs");
            ret = QString::number(resultingFrameRateAbs->GetValue());
        } break;
        case AcquisitionFrameRateAbs: {
            const CBooleanPtr acquisitionFrameRateEnable = m_nodeMap->GetNode("AcquisitionFrameRateEnable");
            acquisitionFrameRateEnable->SetValue(TRUE);
            const CFloatPtr acquisitionFrameRateAbs = m_nodeMap->GetNode("AcquisitionFrameRateAbs");
            ret = QString::number(acquisitionFrameRateAbs->GetValue());
        } break;
        case Freerun:
        case Line1: {
            const CEnumerationPtr triggerSelector = m_nodeMap->GetNode("TriggerSelector");
            triggerSelector->FromString("FrameStart");
            CEnumerationPtr triggerSource = m_nodeMap->GetNode("TriggerSource");
            ret = QString::fromLocal8Bit(triggerSource->ToString().c_str());
        } break;
        case ExposureTimeAbs: {
            const CFloatPtr exposureTimeAbs = m_nodeMap->GetNode("ExposureTimeAbs");
            ret = QString::number(exposureTimeAbs->GetValue());
        } break;
        case GainRaw: {
            const CIntegerPtr gainRaw = m_nodeMap->GetNode("GainRaw");
            ret = QString::number(gainRaw->GetValue());
        } break;
        case SensorWidth: {
            const CIntegerPtr sensorWidth = m_nodeMap->GetNode("SensorWidth");
            ret = QString::number(sensorWidth->GetValue());
        } break;
        case SensorHeight: {
            const CIntegerPtr sensorHeight = m_nodeMap->GetNode("SensorHeight");
            ret = QString::number(sensorHeight->GetValue());
        } break;
        case Width: {
            const CIntegerPtr width = m_nodeMap->GetNode("Width");
            ret = QString::number(width->GetValue());
        } break;
        case Height: {
            const CIntegerPtr height = m_nodeMap->GetNode("Height");
            ret = QString::number(height->GetValue());
        } break;
        case PixelFormat: {
            const CEnumerationPtr pixelFormat = m_nodeMap->GetNode("PixelFormat");
            ret = QString::fromLocal8Bit(pixelFormat->ToString().c_str());
        } break;
        default: ret = ""; break;
        }
    } catch (GenICam::GenericException &e) {
        qDebug() << "getCameraProperty error: " + QString::fromLocal8Bit(e.what());
    }

    return ret;
}

void BBaslerCamerControl::setCameraProperty(BBaslerCamerControl::BaslerCameraProperty type, double value)
{
    // Check if camera is open.
    if ( !m_baslerCamera.IsOpen() ) {
        return;
    }

    try {
        switch (type) {
        case AcquisitionFrameRateAbs: {
            const CBooleanPtr acquisitionFrameRateEnable = m_nodeMap->GetNode("AcquisitionFrameRateEnable");
            acquisitionFrameRateEnable->SetValue(TRUE);
            const CFloatPtr acquisitionFrameRateAbs = m_nodeMap->GetNode("AcquisitionFrameRateAbs");
            acquisitionFrameRateAbs->SetValue(value);
        } break;
        case Freerun: {
            CEnumerationPtr triggerSelector = m_nodeMap->GetNode("TriggerSelector");
            triggerSelector->FromString("FrameStart");
            CEnumerationPtr triggerMode = m_nodeMap->GetNode("TriggerMode");
            triggerMode->SetIntValue(1);
            CEnumerationPtr triggerSource = m_nodeMap->GetNode("TriggerSource");
            triggerSource->FromString("Software");
        } break;
        case Line1: {
            CEnumerationPtr triggerSelector = m_nodeMap->GetNode("TriggerSelector");
            triggerSelector->FromString("FrameStart");
            CEnumerationPtr triggerMode = m_nodeMap->GetNode("TriggerMode");
            triggerMode->SetIntValue(1);
            CEnumerationPtr triggerSource = m_nodeMap->GetNode("TriggerSource");
            triggerSource->FromString("Line1");
        } break;
        case ExposureTimeAbs: {
            const CFloatPtr exposureTimeAbs = m_nodeMap->GetNode("ExposureTimeAbs");
            exposureTimeAbs->SetValue(value);
        } break;
        case GainRaw: {
            const CIntegerPtr gainRaw = m_nodeMap->GetNode("GainRaw");
            gainRaw->SetValue(value);
        } break;
        case Width: {
            const CIntegerPtr width = m_nodeMap->GetNode("Width");
            width->SetValue(value);
        } break;
        case Height: {
            const CIntegerPtr height = m_nodeMap->GetNode("Height");
            height->SetValue(value);
        } break;
        default: break;
        }
    } catch (GenICam::GenericException &e) {
        qDebug() << "setCameraProperty error: " + QString::fromLocal8Bit(e.what());
    }
}

void BBaslerCamerControl::updateFrame()
{
    QMutexLocker locker(&m_mutex);

    try {
        CGrabResultPtr ptrGrabResult;
        m_baslerCamera.RetrieveResult(1000, ptrGrabResult, TimeoutHandling_ThrowException);

        if ( ptrGrabResult->GrabSucceeded() ) {
            CPylonImage pylogimage;
            CImageFormatConverter formatconverter;
            formatconverter.OutputPixelFormat = PixelType_RGB8packed;
            formatconverter.Convert(pylogimage, ptrGrabResult);

            cv::Mat mat = cv::Mat(int( ptrGrabResult->GetHeight() ),
                                  int( ptrGrabResult->GetWidth() ),
                                  CV_8UC3,
                                  static_cast< uchar* >( pylogimage.GetBuffer() ));

            QImage image((const unsigned char *)(mat.data), mat.cols, mat.rows, mat.cols * 3, QImage::Format_RGB888);
            emit updateImage(image.rgbSwapped());
        }
    } catch (GenICam::GenericException &e) {
        qDebug() << "updateFrame error: " + QString::fromLocal8Bit(e.what());
    }
}

其他请参考

  • 21
    点赞
  • 100
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
### 回答1: Qt是一种跨平台的应用程序开发框架,可以用于开发各种类型的应用程序。当我们需要驱动多个巴斯相机时,可以使用Qt提供的相机接口和相应的驱动程序来实现。 首先,我们需要调用相机驱动程序,以便能够通过Qt访问巴斯相机。在Qt中,我们可以使用QCamera类来访问相机,并启动预览、拍照和录像等操作。 要驱动多个巴斯相机,我们需要首先枚举所有可用的相机设备,并打开它们。在Qt中,我们可以使用QCameraInfo类来获取可用的相机设备,并使用QCamera类来打开相机设备。 一旦我们打开了所有的相机设备,我们就可以使用多线程来管理多个相机实例。每个相机实例都应该运行在独立的线程中,以便相互独立地进行采集和处理。在Qt中,我们可以使用QThread类来创建线程,并使用信号和槽机制来实现线程之间的通信。 必须注意的是,在驱动多个巴斯相机时,我们需要考虑到硬件资源的限制。由于相机采集和处理需要消耗大量的CPU和内存资源,如果同时打开太多的相机设备,可能会导致系统崩溃或卡顿。因此,我们需要在选择要驱动的相机数量时进行谨慎的考虑。 总之,使用Qt驱动多个巴斯相机需要掌握相应的驱动接口和编程技巧,同时也需要充分考虑系统资源的限制。只有在充分准备和谨慎使用的情况下,我们才能成功地实现这一功能。 ### 回答2: Qt是一种开发跨平台应用程序的框架,它可以帮助开发人员轻松地针对不同的操作系统和硬件平台编写应用程序。如果要驱动多个Basler相机Qt提供了许多功能来实现这个目标。首先,可通过Qt的串口通信功能来连接巴斯相机,以便在Qt应用程序中控制和配置相机。这样可以实现多个相机同时操作,提高了相机的效率和灵活性。其次,Qt还提供了多线程功能,可以在单个应用程序中同时使用多个线程来控制多个相机。这些线程可以独立地运行,而不会相互干扰。此外,Qt还支持网络编程,可以使用Qt的网络模块来控制不同计算机上的相机。这样,即使不在同一个本地网络中,也可以通过网络连接来驱动多个相机。总的来说,Qt提供了多种方法来驱动多个Basler相机,这些方法可以帮助开发人员在应用程序中灵活控制和配置相机,从而为用户提供更好的使用体验和功能。 ### 回答3: Qt可以使用底层库来驱动多个巴斯相机。推荐使用GigE Vision接口,因为它可以用于控制数十个相机,同时可以实现较高帧速率和较低的延迟。 要使用Qt驱动多个巴斯相机,可以按照以下步骤进行计划: 1.安装巴斯相机的软件开发工具包(SDK)。 2.使用QtQThread类,将每个相机作为单独的线程运行。这使得每个相机都可以在其自己的线程上运行,并且不会对其他线程产生干扰。 3.使用QtQTimer类或QEventLoop类,周期性地调用图像捕获线程。这个步骤可以在主线程中完成。 4.使用Qt的QOpenGLWidget类或QGraphicsView类来显示每个相机的图像。这些类可以很容易地在Qt的用户界面中进行设计和使用。 5.检查巴斯相机SDK是否提供了处理多个相机的示例代码。这些示例代码可以帮助您更好地了解如何使用SDK来驱动多个相机。 总之,Qt可以很好地驱动多个巴斯相机,使用GigE Vision接口可以实现更好的帧速率和延迟。注重使用主线程调用周期性捕获图像的函数,并在用户界面中显示图像。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值