6--OpenCV:基础交互

一、鼠标交互

1.鼠标事件响应

void setMouseCallback(const String& winname, MouseCallback onMouse, void* userdata = 0);
/*******************************************************************
*           winname:            监听窗口名称
*           onMouse:            鼠标事件回调函数
*           userdata:           递给回调函数的可选参数
*********************************************************************/

2.鼠标事件回调函数

typedef void (*MouseCallback)(int event, int x, int y, int flags, void* userdata);
//MouseCallback onMouse
void onMouse(int event,int x,int y,int flag,void *param)
/*******************************************************************
*           event:          事件类型
*           x:              鼠标所在图像的坐标
*           y:              
*           flags:          代表拖拽事件
*           param:          自己定义的onMouse事件的ID
*********************************************************************/ 

3.鼠标事件类型

enum MouseEventTypes {
       EVENT_MOUSEMOVE      = 0,    //鼠标移动
       EVENT_LBUTTONDOWN    = 1,    //鼠标左键按下
       EVENT_RBUTTONDOWN    = 2,    //鼠标右键按下 
       EVENT_MBUTTONDOWN    = 3,    //鼠标中键按下
       EVENT_LBUTTONUP      = 4,    //鼠标左键弹起
       EVENT_RBUTTONUP      = 5,    //鼠标右键弹起
       EVENT_MBUTTONUP      = 6,    //鼠标中键弹起
       EVENT_LBUTTONDBLCLK  = 7,    //鼠标左键双击
       EVENT_RBUTTONDBLCLK  = 8,    //鼠标右键双击
       EVENT_MBUTTONDBLCLK  = 9,    //鼠标中间双击
       EVENT_MOUSEWHEEL     = 10,   //鼠标滚轮 正数和负数分别表示向前和向后滚动
       EVENT_MOUSEHWHEEL    = 11    //鼠标滚轮 正数和负数分别表示向右和向左滚动  
     };

4.鼠标拖拽类型

enum MouseEventFlags {
       EVENT_FLAG_LBUTTON   = 1,    //左键拖动
       EVENT_FLAG_RBUTTON   = 2,    //右键拖动
       EVENT_FLAG_MBUTTON   = 4,    //中键拖动
       EVENT_FLAG_CTRLKEY   = 8,    //ctr拖动
       EVENT_FLAG_SHIFTKEY  = 16,   //shift拖动
       EVENT_FLAG_ALTKEY    = 32    //alt拖动
     };

5.☆综合代码

注意点:

        ①将onMouse函数(回调函数)设置为类中的静态函数--->不用创建对象直接调用。

        ②onMouse(回调函数)的参数是固定的最后一个参数是void* param,此处是通过DrawShape这个类生成的对象来进行绘制操作的,所以param传入的是其生成的对象。最后在onMouse函数中第一步,就必须得强制类型转换才行

        ③namedWindow函数是用来生成一个窗口的,参数对应setMouseCallback函数的第一个参数const String& winname(窗口的名称)

        ④无需写鼠标事件响应函数的函数体(直接调用),setMouseCallback直接传参即可,注意第二个参数就是回调函数,第三个参数是param

        ⑤Rect类实例化后是一个存储矩形基本信息的对象。x,y,width,height

//Opencv鼠标左键画圆,右键画方
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
class DrawShape 
{
public:
    DrawShape() :mat(imread("mm.jpg")) {}
    void Show(string wName = "drawShape") 
    {
        imshow(wName, mat);
    }
    void DrawCircle(int x = 300, int y = 300, int r = 10) 
    {
        circle(mat, Point(x, y), r, Scalar(0, 255, 0));
    }
    void DrawRecagnle(int x = 200, int y = 200, int w = 40, int h = 40) 
    {
        rectangle(mat, Rect(x, y, w, h), Scalar(255, 0, 0));
    }
    static void OnMouse(int event, int x, int y, int flag, void* param);
protected:
    Mat mat;
};
void DrawShape::OnMouse(int event, int x, int y, int flag, void* param)
{
    DrawShape* pshape = (DrawShape*)param;
    switch(event)
    {
    case EVENT_LBUTTONDOWN:
        cout << "左键按下......." << endl;
        pshape->DrawCircle(x,y,10);
        break;
    case EVENT_RBUTTONDOWN:
        cout << "右键按下......." << endl;
        pshape->DrawRecagnle(x-5,y-5,10,10);
        break;
    }
}
int main() 
{
    DrawShape* pshape = new DrawShape;
    namedWindow("drawShape");
    setMouseCallback("drawShape", &DrawShape::OnMouse, pshape);
    //主循环
    while (1) 
    {
        pshape->Show();
        if (waitKey(10) == 27) 
        {
            break;
        }
    }
    delete pshape;
    pshape = nullptr;
    return 0;
}

二、opencv视频操作

opencv里对视频的编码解码等支持并不是很良好,所以不要希望用opencv 做多媒体开发,opencv是一个强大的计算机视觉库,而不是视频流编码器或者解码器。希望大家不要走入这个误区,可以把这部分简单单独看待。而且生成的视频文件不能大于2GB,而且不能添加音频。如果想搞音视频处理可以使用FFmpeg

1.视频读取

opencv中通过VideoCaptrue类对视频进行读取操作以及调用摄像头,类如下

class VideoCapture
{
public:
    VideoCapture();
    explicit VideoCapture(const String& filename, int apiPreference = CAP_ANY);
    explicit VideoCapture(const String& filename, int apiPreference, const std::vector<int>& params);
    explicit VideoCapture(int index, int apiPreference = CAP_ANY);
    explicit VideoCapture(int index, int apiPreference, const std::vector<int>& params);
    virtual ~VideoCapture();
    virtual bool open(const String& filename, int apiPreference = CAP_ANY);
    virtual bool open(const String& filename, int apiPreference, const std::vector<int>& params);
    virtual bool open(int index, int apiPreference = CAP_ANY);
    virtual bool open(int index, int apiPreference, const std::vector<int>& params);
    virtual bool isOpened() const;
    virtual void release();
    virtual bool grab();
    virtual bool retrieve(OutputArray image, int flag = 0);
    virtual VideoCapture& operator >> (CV_OUT Mat& image);
    virtual VideoCapture& operator >> (CV_OUT UMat& image);
    virtual bool read(OutputArray image);
    virtual bool set(int propId, double value);
    virtual double get(int propId) const;
    String getBackendName() const;
    void setExceptionMode(bool enable) { throwOnFail = enable; }
    bool getExceptionMode() { return throwOnFail; }
    bool waitAny(const std::vector<VideoCapture>& streams,CV_OUT std::vector<int>& readyIndex,int64 timeoutNs = 0);
protected:
    Ptr<CvCapture> cap;
    Ptr<IVideoCapture> icap;
    bool throwOnFail;
    friend class internal::VideoCapturePrivateAccessor;
};

打开视频与捕获设备

①创建对象除了下面代码创建的方式(构造函数)以外,还可以使用内部的成员函数open函数

参数是一个路径--->对应视频   参数若是0--->对应摄像头

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
​
int main() 
{
    VideoCapture cap = VideoCapture("test.mp4");
    if(!cap.isOpened())
    {
        cout << "打开失败!" << endl;
        return 0;
    }
    VideoCapture camera = VideoCapture(0);
    if (!camera.isOpened()) 
    {
        cout << "摄像头打开失败!" << endl;
        return 0;
    }
    return 0;
}

获取视频属性

获得视频有诸多属性,比如:帧率、总帧数、尺寸、格式

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
​
int main() 
{
    VideoCapture cap = VideoCapture("test.mp4");
    if(!cap.isOpened())
    {
        cout << "打开失败!" << endl;
        return 0;
    }
    cout << "宽度:" << cap.get(CAP_PROP_FRAME_WIDTH) << endl;
    cout << "高度:" << cap.get(CAP_PROP_FRAME_HEIGHT) << endl;
    cout << "帧数:" << cap.get(CAP_PROP_FRAME_COUNT) << endl;
    cout << "帧率:" << cap.get(CAP_PROP_FPS) << endl;
​
    //VideoCapture camera = VideoCapture(0);
    //if (!camera.isOpened()) 
    //{
    //  cout << "摄像头打开失败!" << endl;
    //  return 0;
    //}
    return 0;
}

其他属性获取

enum VideoCaptureProperties {
       CAP_PROP_POS_MSEC       =0, //视频文件的当前位置,单位为毫秒  
       CAP_PROP_POS_FRAMES     =1, //解码/捕获的帧的基于0的索引
       CAP_PROP_POS_AVI_RATIO  =2, //视频文件的相对位置:0=影片开始,1=影片结束
       CAP_PROP_FRAME_WIDTH    =3, //视频宽度
       CAP_PROP_FRAME_HEIGHT   =4, //视频高度
       CAP_PROP_FPS            =5, //帧率
       CAP_PROP_FOURCC         =6, //4个字符的编解码器代码
       CAP_PROP_FRAME_COUNT    =7, //视频文件中的帧数
       CAP_PROP_FORMAT         =8, //视频格式
                                  
       CAP_PROP_MODE           =9, 
       CAP_PROP_BRIGHTNESS    =10, //图像亮度(摄像模式)
       CAP_PROP_CONTRAST      =11, //图像对比度(摄像模式)
       CAP_PROP_SATURATION    =12, //图像饱和度(摄像模式)
       CAP_PROP_HUE           =13, //图像的色调(摄像模式)
       CAP_PROP_GAIN          =14, //图像增益(摄像模式)
       CAP_PROP_EXPOSURE      =15, //曝光(摄像模式)
       CAP_PROP_CONVERT_RGB   =16, //图像是否应该转换为RGB的布尔标记
                                   
       CAP_PROP_WHITE_BALANCE_BLUE_U =17,
       CAP_PROP_RECTIFICATION =18, 
       CAP_PROP_MONOCHROME    =19,
       CAP_PROP_SHARPNESS     =20,
       CAP_PROP_AUTO_EXPOSURE =21, 
       CAP_PROP_GAMMA         =22,
       CAP_PROP_TEMPERATURE   =23,
       CAP_PROP_TRIGGER       =24,
       CAP_PROP_TRIGGER_DELAY =25,
       CAP_PROP_WHITE_BALANCE_RED_V =26,
       CAP_PROP_ZOOM          =27,
       CAP_PROP_FOCUS         =28,
       CAP_PROP_GUID          =29,
       CAP_PROP_ISO_SPEED     =30,
       CAP_PROP_BACKLIGHT     =32,
       CAP_PROP_PAN           =33,
       CAP_PROP_TILT          =34,
       CAP_PROP_ROLL          =35,
       CAP_PROP_IRIS          =36,
       CAP_PROP_SETTINGS      =37, 
       CAP_PROP_BUFFERSIZE    =38,
       CAP_PROP_AUTOFOCUS     =39,
       CAP_PROP_SAR_NUM       =40, 
       CAP_PROP_SAR_DEN       =41, 
       CAP_PROP_BACKEND       =42, 
       CAP_PROP_CHANNEL       =43, 
       CAP_PROP_AUTO_WB       =44, 
       CAP_PROP_WB_TEMPERATURE=45, 
       CAP_PROP_CODEC_PIXEL_FORMAT =46,    
       CAP_PROP_BITRATE       =47, 
       CAP_PROP_ORIENTATION_META=48, 
       CAP_PROP_ORIENTATION_AUTO=49, 
       CAP_PROP_HW_ACCELERATION=50, 
       CAP_PROP_HW_DEVICE      =51, 
       CAP_PROP_HW_ACCELERATION_USE_OPENCL=52, 
       CAP_PROP_OPEN_TIMEOUT_MSEC=53, 
       CAP_PROP_READ_TIMEOUT_MSEC=54, 
       CAP_PROP_STREAM_OPEN_TIME_USEC =55, 
       CAP_PROP_VIDEO_TOTAL_CHANNELS = 56, 
       CAP_PROP_VIDEO_STREAM = 57, 
       CAP_PROP_AUDIO_STREAM = 58, 
       CAP_PROP_AUDIO_POS = 59, 
       CAP_PROP_AUDIO_SHIFT_NSEC = 60, 
       CAP_PROP_AUDIO_DATA_DEPTH = 61, 
       CAP_PROP_AUDIO_SAMPLES_PER_SECOND = 62, 
       CAP_PROP_AUDIO_BASE_INDEX = 63, 
       CAP_PROP_AUDIO_TOTAL_CHANNELS = 64, 
       CAP_PROP_AUDIO_TOTAL_STREAMS = 65, 
       CAP_PROP_AUDIO_SYNCHRONIZE = 66, 
       CAP_PROP_LRF_HAS_KEY_FRAME = 67,
       CAP_PROP_CODEC_EXTRADATA_INDEX = 68,
#ifndef CV_DOXYGEN
       CV__CAP_PROP_LATEST
#endif
     };

视频转图像

视频转图像显示

方法:一帧一帧的流操作(已重载)即可

        (注意:流操作结束之后,原对象的相关信息都会丢失,若还要继续使用,需要重新获取。)

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
​
​
int main() 
{
    VideoCapture cap = VideoCapture("test.mp4");
    if(!cap.isOpened())
    {
        cout << "打开失败!" << endl;
        return 0;
    }
    Mat img;
    while (true) 
    {
        cap >> img;     //flip
        if (img.empty()) 
        {
            break;
        }
        imshow("图像", img);
        waitKey(30);
    }
    cap.release();
    return 0;
}

视频转图像保存(每一帧图像的保存)

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
​
​
int main() 
{
    VideoCapture cap = VideoCapture("test.mp4");
    if(!cap.isOpened())
    {
        cout << "打开失败!" << endl;
        return 0;
    }
    Mat img;
    int index = 1;
    while (true) 
    {
        cap >> img;     //flip
        if (img.empty()) 
        {
            break;
        }
        //imshow("图像", img);
        string name = "mm/img" + to_string(index++) + ".jpg";
        imwrite(name, img);
        waitKey(30);
    }
    cap.release();
    return 0;
}

摄像头转图像(构造函数的参数传入0即可)

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main() 
{
    VideoCapture cap = VideoCapture(0);
    if(!cap.isOpened())
    {
        cout << "打开失败!" << endl;
        return 0;
    }
    Mat img;
    int index = 1;
    while (true) 
    {
        cap >> img;     
        if (img.empty()) 
        {
            break;
        }
        imshow("图像", img);
        waitKey(30);
    }
    cap.release();
    return 0;
}

视频保存

opencv中通过VideoWriter类对视频进行读取操作以及调用摄像头,该类的API与VideoCapture类似,该类的主要API除了构造函数外,提供了open、isOpened、release、write和重载操作符<<

class CV_EXPORTS_W VideoWriter
{
public:
    CV_WRAP VideoWriter();
    CV_WRAP VideoWriter(const String& filename, int fourcc, double fps,
                Size frameSize, bool isColor = true);
    CV_WRAP VideoWriter(const String& filename, int apiPreference, int fourcc, double fps,
                Size frameSize, bool isColor = true);
    CV_WRAP VideoWriter(const String& filename, int fourcc, double fps, const Size& frameSize,
                        const std::vector<int>& params);
    CV_WRAP VideoWriter(const String& filename, int apiPreference, int fourcc, double fps,
                        const Size& frameSize, const std::vector<int>& params);
    virtual ~VideoWriter();
    CV_WRAP virtual bool open(const String& filename, int fourcc, double fps,
                      Size frameSize, bool isColor = true);
    CV_WRAP bool open(const String& filename, int apiPreference, int fourcc, double fps,
                      Size frameSize, bool isColor = true);
    CV_WRAP bool open(const String& filename, int fourcc, double fps, const Size& frameSize,
                      const std::vector<int>& params);
    CV_WRAP bool open(const String& filename, int apiPreference, int fourcc, double fps,
                      const Size& frameSize, const std::vector<int>& params);
    CV_WRAP virtual bool isOpened() const;
    CV_WRAP virtual void release();
    virtual VideoWriter& operator << (const Mat& image);
    virtual VideoWriter& operator << (const UMat& image);
    CV_WRAP virtual void write(InputArray image);
    CV_WRAP virtual bool set(int propId, double value);
    CV_WRAP virtual double get(int propId) const;
    CV_WRAP static int fourcc(char c1, char c2, char c3, char c4);
    CV_WRAP String getBackendName() const;
protected:
    Ptr<CvVideoWriter> writer;
    Ptr<IVideoWriter> iwriter;
​
    static Ptr<IVideoWriter> create(const String& filename, int fourcc, double fps,
                                    Size frameSize, bool isColor = true);
};

 针对静态函数 fourcc函数,四个参数如何传入,见下面的网站,挑一个格式,分解成四个字符传入即可。--->常作为open的第二个参数。

视频其他格式http://mp4ra.org/#/codecs

摄像头转视频保存.

①VideoWriter的open需要四个参数:

        1)保存的路径名

        2)int fourcc--->解编码格式

        3)fps帧率

        4)Size对象(宽度和高度来构造)--->常通过get方法来获取。

②将在cap中         流出每一帧的图片img

                             再流入save对象即可实现。

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main() 
{
    VideoCapture cap = VideoCapture(0);
    if(!cap.isOpened())
    {
        cout << "打开失败!" << endl;
        return 0;
    }
    int width = cap.get(CAP_PROP_FRAME_WIDTH);
    int height = cap.get(CAP_PROP_FRAME_HEIGHT);
    VideoWriter save;
    save.open("save.avi", VideoWriter::fourcc('M', 'J', 'P', 'G'), 30, Size(width, height), true);
    Mat img;
    while (true) 
    {
        cap >> img;
        imshow("摄像头", img);
        save << img;
        //按ESC退出
        if (waitKey(10) == 27) 
        {
            break;
        }
    }
    cap.release();
    save.release();
    return 0;
}

综合代码(封装成一个Video类)

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
class Video 
{
public:
    Video();
    ~Video();
    void GetVideo(string fileName = "test.mp4");//初始化cap
    void SaveToVideo(string fileName = "out.avi");
    void SaveToImg(string preName = "mm/img");
    void Camera(string fileName = "录像.avi");
​
​
protected:
    VideoCapture cap;
    VideoWriter save;
};
Video::Video()
{
​
}
Video::~Video()
{
    cap.release();
    save.release();
}
void Video::GetVideo(string fileName)
{
    cap = VideoCapture(fileName);
    if (!cap.isOpened()) 
    {
        cout << "视频获取失败!" << endl;
    }
}
​
void Video::SaveToVideo(string fileName)
{
    int w = cap.get(CAP_PROP_FRAME_WIDTH);
    int h = cap.get(CAP_PROP_FRAME_HEIGHT);
    int fps = cap.get(CAP_PROP_FPS);
    save.open(fileName, VideoWriter::fourcc('M', 'J', 'P', 'G'), fps, Size(w, h), true);
    Mat img;
    while (true) 
    {
        cap >> img;
        if (img.empty())
            break;
        save << img;
    }
}
​
void Video::SaveToImg(string preName)
{
    Mat img;
    int index = 0;
    string name;
    while (true)
    {
        cap >> img;
        if (img.empty())
        {
            break;
        }
        name = preName + to_string(index++) + ".jpg";
        imwrite(name, img);
    }
}
​
void Video::Camera(string fileName)
{
    cap = VideoCapture(0);
    if (!cap.isOpened()) 
    {
        cout << "摄像头打开失败!" << endl;
        return;
    }
    int w = cap.get(CAP_PROP_FRAME_WIDTH);
    int h = cap.get(CAP_PROP_FRAME_HEIGHT);
    save.open(fileName, VideoWriter::fourcc('M', 'J', 'P', 'G'), 30, Size(w, h), true);
    Mat img;
    while (true)
    {
        cap >> img;
        imshow("摄像头", img);
        save << img;
        if (waitKey(10) == 27) 
        {
            break;
        }
    }
}
​
​
int main()
{
    Video* pvideo = new Video;
    pvideo->GetVideo();
    pvideo->SaveToImg();
    //刚才读到的video已经全部流出去了。
    pvideo->GetVideo();
    pvideo->SaveToVideo();
    
    pvideo->Camera();
    delete pvideo;
    return 0;
}

三、opencv滑块交互操作

opencv滑块交互操作

滑动条(Trackbar)是opencv动态调节参数特别好用的一种工具,虽然看起来着实有点丑

滑动条创建函数

int createTrackbar(const String& trackbarname, const String& winname,int* value, int count,TrackbarCallback onChange = 0,void* userdata = 0);
/*******************************************************************
*           trackbarname:       滑动条名字
*           winname:            依附窗口名
*           value:              滑块位置
*           count:               滑块最大值(最小值是0)
*           onChange:           滑块回调函数
*           userdata:           用户回传给回调函数的数据
*********************************************************************/

滑动条回调函数

typedef void (*TrackbarCallback)(int pos, void* userdata);
void On_Trackbar(int pos, void* userdata);
/*******************************************************************
*           pos:                位置
*           userdata:           用户回传给回调函数的数据
*********************************************************************/

① 主要实现滑动条回调函数,然后直接调用createTrackerbar函数即可。

②下面主要实现,亮度条的调节滑动条---->本质上通过at函数提高每个像素点的像素值即可.

综合代码

//滑块 去操作一张图片的像素点,改变图片的连读即可
#include <iostream>
#include <string>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
class TrackBar 
{
public:
    TrackBar() :img(imread("mm.jpg")), curBright(6), maxBright(30) 
    {
    }
    void Show(string wName = "trackBar") 
    {
        imshow(wName, img);
    }
    int*  GetData()
    {
        return &curBright;
    }
    int GetMaxBright() 
    {
        return maxBright;
    }
    static void On_Trackbar(int pos, void* userdata);
private:
    Mat img;
    int curBright;
    int maxBright;
};
void TrackBar::On_Trackbar(int pos, void* userdata) 
{
    TrackBar* pBar = (TrackBar*)userdata;
    if (!pBar->img.data) 
    {
        return;
    }
    int rows = pBar->img.rows;
    int cols = pBar->img.cols;
    int dims = pBar->img.channels();
    cout << dims << endl;
    Mat mat = Mat::zeros(pBar->img.size(), pBar->img.type());
    float beta = 30;
    float alpha = 0.1f + (float)pBar->curBright / 10.0;
    for (int i = 0; i < rows; i++) 
    {
        for (int j = 0; j < cols; j++) 
        {
            if (dims == 1)
            {
                float bgr = pBar->img.at<uchar>(i, j);
                mat.at<uchar>(i, j) = saturate_cast<uchar>(bgr * alpha + beta);
            }
            else if (dims == 3)
            {
                float g = pBar->img.at<Vec3b>(i, j)[0];
                float b = pBar->img.at<Vec3b>(i, j)[1];
                float r = pBar->img.at<Vec3b>(i, j)[2];
                mat.at<Vec3b>(i, j)[0] = saturate_cast<uchar>(g * alpha + beta);
                mat.at<Vec3b>(i, j)[1] = saturate_cast<uchar>(b * alpha + beta);
                mat.at<Vec3b>(i, j)[2] = saturate_cast<uchar>(r * alpha + beta);
            }
            else
                return;
        }
    }
    imshow("trackBar", mat);
}
int main() 
{
    TrackBar* pBar = new TrackBar;
    namedWindow("trackBar");
    createTrackbar("亮度调整", "trackBar", pBar->GetData(), pBar->GetMaxBright(), &TrackBar::On_Trackbar, pBar);
    waitKey(0);
    return 0;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_Ocean__

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

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

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

打赏作者

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

抵扣说明:

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

余额充值