opencv/c++的一些简单的操作(入门)

7 篇文章 0 订阅

目录

读取图片

读取视频

读取摄像头

图像处理

腐蚀

膨胀

调整图像大小

裁剪和缩放

 绘制

绘制矩形

绘制圆形

绘制线条

透视变换

颜色检测

轮廓查找

人脸检测

检测人脸

检测嘴巴

可适当调整参数


读取图片

读取路径widows使用vis sto一定是\斜杠

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
​
using namespace cv;
using namespace std;
​
int main()
{
    string path = "Resources\test.png";
    Mat img = imread(path);
    imshow("Image", img);
    waitKey(0); 
​
    return 0;
}
​

读取视频

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
​
using namespace cv;
using namespace std;
​
int main()
{
    string path = "Resources/test_video.mp4";
    VideoCapture cap(path); //视频捕捉对象
    Mat img;
    while (true) {
​
        cap.read(img);
​
        imshow("Image", img);
        waitKey(1);
    }
    return 0;
}
​

读取摄像头

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
​
using namespace cv;
using namespace std;
​
int main()
{
    VideoCapture cap(0);
    Mat img;
​
    while (true) {
​
        cap.read(img);
​
        imshow("Image", img);
        waitKey(1);
    }
​
    return 0;
}
​

图像处理

腐蚀

图像腐蚀的主要作用包括:

  • 去除图像中的小噪声点。

  • 使物体边界收缩,细化物体的形状。

#include <iostream>
#include <opencv2/opencv.hpp>
​
using namespace std;
using namespace cv;
​
int main() {
    // 读取图像
    Mat img = imread("D:/桌面文件/fushi.png");
    if (img.empty()) {
        cout << "Could not open or find the image." << endl;
        return -1;
    }
​
    // 转换为灰度图像(腐蚀操作通常在灰度图像上进行效果更好)
    Mat grayImg;
    cvtColor(img, grayImg, COLOR_BGR2GRAY);
​
    // 创建结构元素(核)
    Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));
​
    // 进行腐蚀操作
    Mat erodedImg;
    erode(grayImg, erodedImg, kernel);
​
    // 显示原始图像和腐蚀后的图像
    imshow("Original Image", img);
    imshow("Eroded Image", erodedImg);
    waitKey(0);
    destroyAllWindows();
​
    return 0;
}
  1. getStructuringElement(MORPH_RECT, Size(5, 5))

    • 用于创建一个特定形状和大小的结构元素(也称为核),用于形态学操作。

    • MORPH_RECT表示创建一个矩形形状的结构元素。还可以选择其他形状,如MORPH_ELLIPSE(椭圆)、MORPH_CROSS(十字形)等。

    • Size(5, 5)指定了结构元素的大小,这里是一个 5x5 的矩形。

膨胀

图像膨胀的主要作用包括:

  • 使物体边界扩张,连接断开的部分。

  • 填充物体内部的小孔和狭窄的缝隙。

#include <iostream>
#include <opencv2/opencv.hpp>
​
using namespace std;
using namespace cv;
​
int main() {
    // 读取图像
    Mat img = imread("D:/桌面文件/peng.png");
    if (img.empty()) {
        cout << "Could not open or find the image." << endl;
        return -1;
    }
​
    // 转换为灰度图像(膨胀操作通常在灰度图像上进行效果更好)
    Mat grayImg;
    cvtColor(img, grayImg, COLOR_BGR2GRAY);
​
    // 创建结构元素(核)
    Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));
​
    // 进行膨胀操作
    Mat dilatedImg;
    dilate(grayImg, dilatedImg, kernel);
​
    // 显示原始图像和膨胀后的图像
    imshow("Original Image", img);
    imshow("Dilated Image", dilatedImg);
    waitKey(0);
    destroyAllWindows();
​
    return 0;
}

 

调整图像大小

裁剪和缩放

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
​
using namespace cv;
using namespace std;
​
int main()
{
    string path = "resources/test.png";
    Mat img = imread(path);
    Mat imgResize, imgCrop;
​
    cout << img.size() << endl;
    resize(img, imgResize, Size(), 0.5, 0.5);
​
    Rect roi(200, 100, 300, 300);
    imgCrop = img(roi);
​
    imshow("Image", img);
    imshow("ImageResieze", imgResize);
    imshow("ImageCrop", imgCrop);
    waitKey(0);
​
    return 0;
}
​

 绘制

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main() {
    // 读取图像
    Mat img = imread("your_image.jpg");
    if (img.empty()) {
        cout << "Could not open or find the image." << endl;
        return -1;
    }

    // 绘制矩形
    rectangle(img, Rect(50, 50, 200, 150), Scalar(0, 255, 0), 2);

    // 绘制圆形
    circle(img, Point(300, 300), 100, Scalar(255, 0, 0), 2);

    // 绘制线条
    line(img, Point(100, 400), Point(400, 400), Scalar(0, 0, 255), 2);

    // 显示图像
    imshow("Image with Shapes", img);
    waitKey(0);
    destroyAllWindows();

    return 0;
}

绘制矩形

  • 使用rectangle函数可以绘制矩形。

  • 语法:void rectangle(Mat& img, Point pt1, Point pt2, const Scalar& color, int thickness = 1, int lineType = LINE_8, int shift = 0);

  • 参数解释:

    • img:要在其上绘制矩形的图像。

    • pt1:矩形的一个顶点。

    • pt2:矩形的对角顶点。

    • color:矩形的颜色。

    • thickness:矩形边框的粗细。如果为负值,则绘制填充的矩形。

    • lineType:线条类型。

    • shift:坐标点的小数位数。

绘制圆形

  • 使用circle函数可以绘制圆形。

  • 语法:void circle(Mat& img, Point center, int radius, const Scalar& color, int thickness = 1, int lineType = LINE_8, int shift = 0);

  • 参数解释:

    • img:要在其上绘制圆形的图像。

    • center:圆形的圆心。

    • radius:圆形的半径。

    • color:圆形的颜色。、

    • thickness:圆形边框的粗细。如果为负值,则绘制填充的圆形。

    • lineType:线条类型。

    • shift:坐标点的小数位数。

例:  circle(img, Point(256, 256), 155, Scalar(0, 69, 255), FILLED);

绘制线条

  • 使用line函数可以绘制线条。

  • 语法:void line(Mat& img, Point pt1, Point pt2, const Scalar& color, int thickness = 1, int lineType = LINE_8, int shift = 0);

  • 参数解释:

    • img:要在其上绘制线条的图像。

    • pt1:线条的一个端点。

    • pt2:线条的另一个端点。

    • color:线条的颜色。

    • thickness:线条的粗细。

    • lineType:线条类型。

    • shift:坐标点的小数位数。

例:  line(img, Point(130, 296), Point(382, 296), Scalar(255, 255, 0), 2);

使用putText函数可以在图像上绘制文字。

  • 语法:void putText(Mat& img, const String& text, Point org, int fontFace, double fontScale, Scalar color, int thickness = 1, int lineType = LINE_8, bool bottomLeftOrigin = false);

  • 参数解释:

    • img:要在其上绘制文字的图像。

    • text:要绘制的文字内容。

    • org:文字的左下角坐标。

    • fontFace:字体类型。可以使用cv::HersheyFonts中的预定义字体。

    • fontScale:字体大小的缩放因子。

    • color:文字的颜色。

    • thickness:文字的粗细。

    • lineType:线条类型。

    • bottomLeftOrigin:如果为true,则文字的原点在图像的左下角,否则在左上角。

例:  putText(img, "你好 Opencv", Point(137, 262), FONT_HERSHEY_DUPLEX, 0.95, Scalar(0, 69, 255), 2);
​

 

若不支持中文的话,可以换第三方库试试。

透视变换

(简单理解就是把斜的东西摆正)

变换后

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
​
using namespace cv;
using namespace std;
​
float w = 750, h = 170;
Mat matrix, imgWarp;
​
int main()
{
    string path = "D:/桌面文件/card.jpg";
    Mat img = imread(path);
​
    Point2f src[4] = { {67, 519}, {452, 31}, {197, 609}, {701, 167} };
    Point2f dst[4] = { {0.0f, 0.0f}, {w, 0.0f}, {0.0f, h}, {w, h} };
​
    matrix = getPerspectiveTransform(src, dst);
    warpPerspective(img, imgWarp, matrix, Point(w, h));
​
    for (int i = 0; i < 4; i++) {
        circle(img, src[i], 10, Scalar(0, 0, 255), FILLED);
    }
​
    imshow("Image", img);
    imshow("ImageWarp", imgWarp);
    waitKey(0);
​
    return 0;
}
​
  1. matrix = getPerspectiveTransform(src, dst);

    • 使用getPerspectiveTransform函数计算从原始图像的四个角点(src)到目标图像的四个角点(dst)的透视变换矩阵,并将结果存储在matrix中。

  2. warpPerspective(img, imgWarp, matrix, Point(w, h));

    • 使用计算得到的透视变换矩阵matrix对原始图像img进行透视变换,得到变换后的图像imgWarp

    • 第三个参数是透视变换矩阵。

    • 最后一个参数Point(w, h)指定了输出图像的大小,这里是宽度为w,高度为h

一、原始图像中的四个点(src

  1. 这些点通常是在原始图像上手动选择或通过某种算法确定的。

  2. 它们定义了原始图像中要进行透视变换的区域。例如,如果要对原始图像中的一个矩形区域进行变换,这四个点就是该矩形的四个角点。

  3. 点的顺序通常是按照顺时针或逆时针方向排列,以确保正确定义四边形的形状和方向。

二、目标图像中的四个点(dst

  1. 这些点指定了透视变换后图像中相应四边形的四个角点的位置。

  2. 通过指定目标点的位置,可以控制透视变换后图像的形状和大小。

  3. 同样,点的顺序也应该与原始图像中的点的顺序相对应,以确保正确的变换。

颜色检测

颜色识别蓝色

效果如下

代码如下

#include <iostream>
#include <opencv2/opencv.hpp>
​
using namespace std;
using namespace cv;
​
int main() {
    Mat img = imread("your_image.jpg");
    if (img.empty()) {
        cout << "Could not open or find the image" << endl;
        return -1;
    }
​
    Mat hsvImg;
    cvtColor(img, hsvImg, COLOR_BGR2HSV);
​
    Scalar lowerBlue(100, 50, 50);
    Scalar upperBlue(130, 255, 255);
​
    Mat blueMask;
    inRange(hsvImg, lowerBlue, upperBlue, blueMask);
​
    imshow("Original Image", img);
    imshow("Blue Mask", blueMask);
    waitKey(0);
    destroyAllWindows();
​
    return 0;
}

1. 转换颜色空间

通常将图像从 BGR(Blue-Green-Red)颜色空间转换为 HSV(Hue-Saturation-Value)颜色空间,因为在 HSV 空间中更容易进行颜色检测。

Mat hsvImg;
cvtColor(img, hsvImg, COLOR_BGR2HSV);

2. 定义颜色范围

确定要检测的颜色范围,在 HSV 颜色空间中,颜色可以用一个范围来表示。例如,对于蓝色:

Scalar lowerBlue(100, 50, 50);
Scalar upperBlue(130, 255, 255);

这里,lowerBlueupperBlue分别定义了蓝色的下限和上限范围。你可以根据需要调整这些值来检测不同的颜色。

3. 进行颜色检测

使用inRange函数在 HSV 图像中检测指定颜色范围内的像素。

Mat blueMask;
inRange(hsvImg, lowerBlue, upperBlue, blueMask);

这将创建一个二值图像,其中在指定颜色范围内的像素为白色(255),其他像素为黑色(0)。

轮廓查找

为了进行形状和轮廓检测,通常将图像转换为灰度图像。

Mat grayImg;
cvtColor(img, grayImg, COLOR_BGR2GRAY);

应用阈值处理或边缘检测

  1. 阈值处理:可以使用cv::threshold函数将灰度图像转换为二值图像,以便更好地检测形状和轮廓。

   Mat binaryImg;
   threshold(grayImg, binaryImg, 127, 255, THRESH_BINARY);
  1. 边缘检测:也可以使用边缘检测算法,如 Canny 边缘检测,来突出图像中的边缘,从而更容易检测形状和轮廓。

   Mat edges;
   Canny(grayImg, edges, 50, 150);

四、查找轮廓

使用cv::findContours函数查找图像中的轮廓。

vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binaryImg, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
  • Vec4i是一个由四个整数组成的向量,在轮廓层次结构中,每个Vec4i元素代表一个轮廓的层次结构信息。

  • 这四个整数的含义分别是:

    • next:下一个同级轮廓的索引。如果当前轮廓是最后一个同级轮廓,则此值为 -1。

    • previous:上一个同级轮廓的索引。如果当前轮廓是第一个同级轮廓,则此值为 -1。

    • first_child:第一个子轮廓的索引。如果当前轮廓没有子轮廓,则此值为 -1。

    • parent:父轮廓的索引。如果当前轮廓是顶级轮廓(没有父轮廓),则此值为 -1。

参数解释:

  • binaryImg:输入的二值图像。

  • contours:检测到的轮廓,存储为点的向量的向量。

  • hierarchy:轮廓的层次结构信息。

  • RETR_TREE:轮廓检索模式,表示检索所有的轮廓并建立完整的层次结构。

  • CHAIN_APPROX_SIMPLE:轮廓逼近方法,表示压缩水平、垂直和对角线段,只保留它们的端点。

绘制轮廓

可以在原始图像上绘制检测到的轮廓,以便可视化。

Mat resultImg = img.clone();
drawContours(resultImg, contours, -1, Scalar(0, 255, 0), 2);

参数解释:

  • resultImg:要在其上绘制轮廓的图像。

  • contours:检测到的轮廓。

  • -1:表示绘制所有的轮廓。如果传入一个特定的整数索引值(比如 0、1、2 等),那么只会绘制contours集合中对应索引的那个轮廓。

  • Scalar(0, 255, 0):轮廓的颜色(这里是绿色)。

  • 2:轮廓的线宽。

六、显示结果

最后,显示原始图像和带有轮廓的图像。

imshow("Original Image", img);
imshow("Contours", resultImg);
waitKey(0);
destroyAllWindows();

Vec4i是一个模板类,表示一个由四个整数组成的向量。

具体来说,Vec4i可以存储四个整数值,通常用于表示具有四个分量的向量,例如在图像中表示一个具有四个坐标值的向量或者一个具有四个索引值的向量。

ps:如果你想让他的效果更好,可以用之前的图像膨胀和图像腐蚀

完整代码如下

#include <iostream>
#include <opencv2/opencv.hpp>
​
using namespace std;
using namespace cv;
​
int main() {
    Mat img = imread("D:/桌面文件/dog.png");
    if (img.empty()) {
        cout << "Could not open or find the image" << endl;
        return -1;
    }
​
    Mat grayImg;
    cvtColor(img, grayImg, COLOR_BGR2GRAY);
​
    Mat binaryImg;
    threshold(grayImg, binaryImg, 200, 255, THRESH_BINARY);
​
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(binaryImg, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
​
    Mat resultImg = img.clone();
    drawContours(resultImg, contours, -1, Scalar(0, 255, 0), 2);
​
    imshow("Original Image", img);
    imshow("Original grayImg", grayImg);
    imshow("Contours", resultImg);
    waitKey(0);
    destroyAllWindows();
​
    return 0;
}
Mat imgErode;
Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));
erode(binaryImg, imgErode, kernel);

腐蚀后的效果

人脸检测

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/objdetect.hpp>
#include <iostream>
​
using namespace cv;
using namespace std;
​
int main()
{
    string path = "D:/桌面文件/face.png";
    Mat img1 = imread(path);
​
    CascadeClassifier faceCascade;
    faceCascade.load("D:/桌面文件/md文件大集合/opencv/haarcascade_eye.xml");
    
​
    if (faceCascade.empty()) { cout << "XML file not loaded" << endl; }
​
    vector<Rect> faces;
    faceCascade.detectMultiScale(img1, faces, 1.1, 10);
​
    for (int i = 0; i < faces.size(); i++)
    {
        rectangle(img1, faces[i].tl(), faces[i].br(), Scalar(255, 0, 255), 3);
    }
​
    imshow("Image", img1);
    waitKey(0);
​
    return 0;
}
​

faceCascade.detectMultiScale是 OpenCV 中用于检测对象(这里通常是人脸)的函数。其函数原型如下:

void detectMultiScale( InputArray image,
                       CV_OUT std::vector<Rect>& objects,
                       double scaleFactor = 1.1,
                       int minNeighbors = 3,
                       int flags = 0,
                       Size minSize = Size(),
                       Size maxSize = Size() );

参数解释如下:

  1. image

    • 输入图像,可以是彩色图像(会在内部被转换为灰度图像进行处理)或灰度图像。

  2. objects

    • 输出参数,用于存储检测到的对象(如人脸)的矩形区域。它是一个std::vector<cv::Rect>类型的容器,其中每个cv::Rect对象表示一个检测到的对象的位置和大小。

  3. scaleFactor

    • 图像缩放比例因子。在进行多尺度检测时,每次对图像进行缩放的比例。较小的值会增加检测的时间,但可能检测到更小的对象;较大的值会减少检测时间,但可能错过一些较小的对象。在使用detectMultiScale函数时,缩放因子scaleFactor的取值范围通常在 1.01 到 1.5 之间,但这并不是绝对的,实际取值取决于具体的应用场景和图像特征。较小的缩放因子(如接近 1.01)会使检测过程更加精细,可能检测到更小的目标,但计算时间会增加。较大的缩放因子(如接近 1.5)会加快检测速度,但可能错过一些较小的目标或者不够准确地定位目标。

  4. minNeighbors

    • 最小邻域数量。在检测过程中,一个候选区域需要至少有这么多个相邻的区域认为它是对象,才会被确认为真正的对象。这个参数可以用来过滤掉一些误检测的区域。

  5. flags

    • 标志位,通常设置为 0 即可。它可以用来指定一些特殊的检测模式或参数,但在一般情况下不需要修改。

  6. minSize

    • 最小可能的对象尺寸。检测过程中会忽略比这个尺寸更小的区域,以减少误检测。

  7. maxSize

    • 最大可能的对象尺寸。检测过程中会忽略比这个尺寸更大的区域,可根据实际情况设置以减少不必要的检测时间或过滤掉过大的误检测区域。

检测眼睛

发现有重复的框,可以使用iou类似的方法删除有交集的框

具体代码如下

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/objdetect.hpp>
#include <iostream>
​
using namespace cv;
using namespace std;
double calculateIoU(const cv::Rect_<int>& rect1, const cv::Rect_<int>& rect2) {
    int interRectX = std::max(rect1.x, rect2.x);
    int interRectY = std::max(rect1.y, rect2.y);
    int interRectWidth = std::min(rect1.x + rect1.width, rect2.x + rect2.width) - interRectX;
    int interRectHeight = std::min(rect1.y + rect1.height, rect2.y + rect2.height) - interRectY;
​
    if (interRectWidth <= 0 || interRectHeight <= 0)
        return 0.0;
​
    int interArea = interRectWidth * interRectHeight;
    int unionArea = rect1.area() + rect2.area() - interArea;
​
    return static_cast<double>(interArea) / unionArea;
}
​
void calculateIoUsForVector( std::vector<cv::Rect_<int>>& rects) {
    for (size_t i = 0; i < rects.size(); ++i) {
        for (size_t j = i + 1; j < rects.size(); ++j) {
            double iou = calculateIoU(rects[i], rects[j]);
            if (iou > 0.1) {
                rects.erase(rects.begin()+i);
​
​
                
            }
            std::cout << "IoU between rectangle " << i << " and rectangle " << j << " is: " << iou << std::endl;
        }
    }
}int main()
{
    //std::vector<cv::Rect> faces = { cv::Rect(10, 10, 50, 50), cv::Rect(20, 20, 60, 60),cv::Rect(10, 12, 50, 50) };
    string path = "D:/桌面文件/faces.png";
    Mat img1 = imread(path);
​
    CascadeClassifier faceCascade;
    faceCascade.load("D:/桌面文件/md文件大集合/opencv/haarcascade_eye.xml");
    //
​
    if (faceCascade.empty()) { cout << "XML file not loaded" << endl; }
​
    vector<Rect> faces;
    //faceCascade.detectMultiScale(img1, faces);
    faceCascade.detectMultiScale(img1, faces,1.1, 4);
    calculateIoUsForVector(faces);
    for (int i = 0; i < faces.size(); i++)
    {
        rectangle(img1, faces[i].tl(), faces[i].br(), Scalar(255, 0, 255), 3);
    }
​
    imshow("Image", img1);
    waitKey(0);
​
    return 0;
}
​

 

检测人脸

检测嘴巴

效果不理想

设置最大最小嘴尺寸后

可适当调整参数

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/objdetect.hpp>
#include <iostream>
​
using namespace cv;
using namespace std;
double calculateIoU(const cv::Rect_<int>& rect1, const cv::Rect_<int>& rect2) {
    int interRectX = std::max(rect1.x, rect2.x);
    int interRectY = std::max(rect1.y, rect2.y);
    int interRectWidth = std::min(rect1.x + rect1.width, rect2.x + rect2.width) - interRectX;
    int interRectHeight = std::min(rect1.y + rect1.height, rect2.y + rect2.height) - interRectY;
​
    if (interRectWidth <= 0 || interRectHeight <= 0)
        return 0.0;
​
    int interArea = interRectWidth * interRectHeight;
    int unionArea = rect1.area() + rect2.area() - interArea;
​
    return static_cast<double>(interArea) / unionArea;
}
​
void calculateIoUsForVector( std::vector<cv::Rect_<int>>& rects) {
    vector<int> del_nums;
    for (size_t i = 0; i < rects.size(); ++i) {
        for (size_t j = i + 1; j < rects.size(); ++j) {
            double iou = calculateIoU(rects[i], rects[j]);
            if (iou > 0.1) {
                del_nums.push_back(i);
            }
            std::cout << "IoU between rectangle " << i << " and rectangle " << j << " is: " << iou << std::endl;
        }
        for (auto i : del_nums) {
            rects.erase(rects.begin() + i);
        }
    }
}
​
int main()
{
    //std::vector<cv::Rect> faces = { cv::Rect(10, 10, 50, 50), cv::Rect(20, 20, 60, 60),cv::Rect(10, 12, 50, 50) };
    string path = "D:/桌面文件/face.png";
    Mat img1 = imread(path);
​
    CascadeClassifier faceCascade;
    faceCascade.load("D:/桌面文件/md文件大集合/opencv/haarcascade_mcs_mouth.xml");
    //
​
    if (faceCascade.empty()) { cout << "XML file not loaded" << endl; }
​
    vector<Rect> faces;
    //faceCascade.detectMultiScale(img1, faces);
    faceCascade.detectMultiScale(img1, faces, 1.5, 3, 0, Size(50, 50),Size(150,150));
    calculateIoUsForVector(faces);
    for (int i = 0; i < faces.size(); i++)
    {
        rectangle(img1, faces[i].tl(), faces[i].br(), Scalar(255, 0, 255), 3);
    }
​
    imshow("Image", img1);
    waitKey(0);
​
    return 0;
}
​
  • 37
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

西柚与蓝莓

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

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

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

打赏作者

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

抵扣说明:

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

余额充值