opencv_图像分割与修复

图像的分割

图像分割的基本概念

图像分割: 将前景物体从背景中分离出来.

图像分割分为传统图像分割和基于深度学习的图像分割方法.

传统图像分割就是使用OpenCV进行的图像分割.

传统图像分割方法有:

  • 分水岭法
  • GrabCut法
  • MeanShift法
  • 背景扣除

分水岭法

分水岭分割方法是基于图像形态学和图像结构来实现的一种图像分割方法.

我们绘制灰度图像的梯度图, 可以得到近似下图的梯度走势.梯度低的地方我们可以认为是低洼区或者山谷, 梯度高的地方可以认为是山峰. 我们往山谷中注水, 为了防止山谷中的水溢出汇合我们可以在汇合的地方筑起堤坝, 可将堤坝看做是对图像分割后形成的边界. 这就是分水岭算法的基本原理.

在这里插入图片描述

分水岭法涉及的API

  • distanceTransform(img, distanceType, maskSize) 计算img中非零值到距离它最近的0值之间的距离

    • img 要处理的图像
    • distanceType 计算距离的方式: DIST_L1, DIST_L2
    • maskSize : 进行扫描时的kernel的大小, L1用3, L2用5
  • connectedComponents(image[, labels[, connectivity[, ltype]]]) 求连通域, 用0标记图像的背景,用大于0的整数标记其他对象

    • connectivity: 4, 8(默认)
  • watershed(image, markers) 执行分水岭法

    • markers:
      • 它是一个与原始图像大小相同的矩阵,int32数据类型,表示哪些是背景哪些是前景。
      • 分水岭算法将标记的0的区域视为不确定区域,将标记为1的区域视为背景区域,将标记大于1的正整数表示我们想得到的前景。
# 封装显示图片的函数
def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    cv2.waitKey(1)
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图片和灰度化
img = cv2.imread('water_coins.jpeg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 显示直方图
plt.hist(gray.ravel(), bins=256, range=[0, 255])

# 二值化处理, 这是一个典型的双峰结构, 我们使用大津算法进行二值化处理
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)

# 发现二值化之后的图片, 存在毛边, 和一些小的噪点.
# 做一个开运算 = 先腐蚀,再膨胀
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)


### 想办法找到图中的背景和前景:

## 传统方法:先找出边界区域
# 对opening进行膨胀与腐蚀,膨胀图 - 腐蚀图 = 边界线
bg = cv2.dilate(opening, kernel, iterations=2)
fg = cv2.erode(opening, kernel, iterations=2)

# 剩下的区域(硬币边界附近)不能确定是背景还是前景. 
unknown = cv2.subtract(bg, fg)
# cv_show('unknow',unknown)


## 分水岭法:应用其API
# 因为硬币之间彼此是接触的, 导致腐蚀之后的前景图不太对, 硬币和硬币之间形成了通道.
# 腐蚀在这里不适合,distanceTransform来确定前景. 
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)

# 对dist_transform做归一化方便展示结果
cv2.normalize(dist_transform, dist_transform, 0, 1.0, cv2.NORM_MINMAX)
# cv_show('dist_transform', dist_transform)

# 对dist_transform做二值化处理
_, fg = cv2.threshold(dist_transform, 0.5 * dist_transform.max(), 255, cv2.THRESH_BINARY)
fg = np.uint8(fg)
cv_show('fg',fg)

# 顺便把未知区域算出
unknown = cv2.subtract(bg, fg)
# cv_show('unknow',unknown)


# connectedComponents要求输入的图片是个单通道的0到255的图片.
# connectedComponents 用0来标记背景, 用大于0的整数来标记前景
_, markers = cv2.connectedComponents(fg) # 传入前景


# 因为watershed中 0认为是不确定区域, 1是背景, 大于1的是前景.
# markers + 1把原来的0变成1了. 
markers += 1

# 从markers中筛选出边缘区域, 标记为不确定区域,赋值为0.
markers[unknown == 255] = 0 

# 展示markers
markers_copy = markers.copy()

# 位置区域
markers_copy[markers == 0] = 150
# 背景
markers_copy[markers == 1] = 0
# 前景
markers_copy[markers > 1] = 255
markers_copy = np.uint8(markers_copy)
cv_show('markers_copy', markers_copy)


# 到此为止, 我们的markers就已经生成好了. 
# watershed返回的markers已经做了修改. 边界区域标记为-1了. 
markers = cv2.watershed(img, markers)

# 抠出硬币
# mask把要抠图的地方赋值为255, 其他位置, 即背景赋值为0
mask = np.zeros(shape=img.shape[:2], dtype=np.uint8)
mask[markers > 1] = 255
coins = cv2.bitwise_and(img, img, mask=mask)

cv_show('coins', coins)
cv_show('img', img)
# # 

在这里插入图片描述

把分水岭算法和边缘检测Canny和轮廓查找做个对比.

# 和canny对比
img_canny = cv2.Canny(img, 100, 150)
cv_show('img_canny', img_canny)

array([[0, 0, 0, …, 0, 0, 0],
[0, 0, 0, …, 0, 0, 0],
[0, 0, 0, …, 0, 0, 0],
…,
[0, 0, 0, …, 0, 0, 0],
[0, 0, 0, …, 0, 0, 0],
[0, 0, 0, …, 0, 0, 0]], dtype=uint8)

# 和findContours对比

# 把img变成灰度图
img = cv2.imread('water_coins.jpeg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 二值化
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)

# findContours要求是单通道, 0到255的整数的图片, 最好是二值化的图片. 
contours, _ = cv2.findContours(thresh, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_SIMPLE)

# 显示轮廓, 会直接修改img
cv2.drawContours(img, contours, -1, (0, 0, 255), 3)
cv_show('contours', img)

GrabCut

通过交互的方式获得前景物体.

  • 用户指定前景的大体区域, 剩下的为背景区域.
  • 用户可以明确指定某些地方为前景或背景.
  • GrabCut采用分段迭代的方法分析前景物体, 形成模型树.
  • 最后根据权重决定某个像素是前景还是背景.

这里介绍一些GrabCut算法的实现步骤:

  1. 在图片中定义(一个或者多个)包含物体的矩形。
  2. 矩形外的区域被自动认为是背景。
  3. 对于用户定义的矩形区域,可用背景中的数据来区分它里面的前景和背景区域。
  4. 用高斯混合模型(GMM)来对背景和前景建模,并将未定义的像素标记为可能的前景或者背景。
  5. 图像中的每一个像素都被看做通过虚拟边与周围像素相连接,而每条边都有一个属于前景或者背景的概率,这是基于它与周边像素颜色上的相似性。
  6. 每一个像素(即算法中的节点)会与一个前景或背景节点连接。
  7. 在节点完成连接后(可能与背景或前景连接),若节点之间的边属于不同终端(即一个节点属于前景,另一个节点属于背景),则会切断他们之间的边,这就能将图像各部分分割出来。下图能很好的说明该算法:

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

  • grabCut(img, mask, rect, bgdModel, fgdModel, iterCount[, mode]) -> mask, bgdModel, fgdModel

    • img——待分割的源图像,必须是8位3通道,在处理的过程中不会被修改

    • mask——掩码图像,如果使用掩码进行初始化,那么mask保存初始化掩码信息;在执行分割的时候,也可以将用户交互所设定的前景与背景保存到mask中,然后再传入grabCut函数;在处理结束之后,mask中会保存结果。mask只能取以下四种值:

      GCD_BGD(=0),背景;

      GCD_FGD(=1),前景;

      GCD_PR_BGD(=2),可能的背景;

      GCD_PR_FGD(=3),可能的前景。

      如果没有手工标记GCD_BGD或者GCD_FGD,那么结果只会有GCD_PR_BGD或GCD_PR_FGD;

    • rect——用于限定需要进行分割的图像范围,只有该矩形窗口内的图像部分才被处理;

    • bgdModel——背景模型,如果为None,函数内部会自动创建一个bgdModel;bgdModel必须是单通道浮点型图像,且行数只能为1,列数只能为13x5;

    • fgdModel——前景模型,如果为None,函数内部会自动创建一个fgdModel;fgdModel必须是单通道浮点型图像,且行数只能为1,列数只能为13x5;

    • iterCount——迭代次数,必须大于0;

    • mode——用于指示grabCut函数进行什么操作,可选的值有:

      GC_INIT_WITH_RECT(=0),用矩形窗初始化GrabCut;

      GC_INIT_WITH_MASK(=1),用掩码图像初始化GrabCut;

      GC_EVAL(=2),执行分割。

# 封装显示图片的函数
def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    cv2.waitKey(1)
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图片
img = cv2.imread('./lena.png')

# 利用plt展示 -- 色彩出现偏差
plt.imshow(img)

# 设置矩形框x, y, w, h
rect = (200, 170, 180, 210)

# 创建与原图同等大小的 掩码
mask = np.zeros(img.shape[:2], dtype=np.uint8)
# mask是grabcut保存分割结果的矩阵.分割完之后有以下四种值.
# GCD_BGD(=0),背景;
# GCD_FGD(=1),前景;
# GCD_PR_BGD(=2),可能的背景;
# GCD_PR_FGD(=3),可能的前景。

# 第一次使用grabcut是用户指定rect -- rect内存在前景,算法自动划分
cv2.grabCut(img, mask, rect, None, None, 5, mode=cv2.GC_INIT_WITH_RECT)
# 处理结果会存在mask里

# 我们把所有的前景抠出来 -- 如果是前景则变白,否则变黑
mask1 = np.where((mask == 1) | (mask == 3), 255, 0).astype(np.uint8)

# 使用与运算 -- 只显示前景,让背景变黑
output1 = cv2.bitwise_and(img, img, mask=mask1)

# 框出矩形区域
cv2.rectangle(img, (200, 170), (200 + 180, 170 + 210), (0, 0, 255), 3)

# 第二次使用grabcut, 对mask进行修改 -- 体现交互式
# 标记出新增的前景区域
mask[110:170, 200:380] = 1 

# 算法再次自动修改 mask
cv2.grabCut(img, mask, None, None, None, 5, mode=cv2.GC_INIT_WITH_MASK)

# 如果是前景则变白,否则变黑
mask2 = np.where((mask == 1) | (mask == 3), 255, 0).astype(np.uint8)

# 使用与运算 -- 只显示前景,让背景变黑
output2 = cv2.bitwise_and(img, img, mask=mask2)

# 框出新增区域
cv2.rectangle(img, (200, 110), (200 + 180, 60 + 110), (0, 255, 0), 3)

# 显示各阶段的图片
cv_show('output2', np.hstack((img, output1, output2)))

在这里插入图片描述

应用:

根据grabcut的特性, 设计一个交互式的抠图应用. 可以根据鼠标框选的区域自动抠图.

# 面向对象 : 把grabcut进行交互式抠图的功能封装成一个类.
import cv2
import numpy as np
import matplotlib.pyplot as plt

class App:
    def __init__(self, image):
        # 获取原图名称
        self.image = image 
        
        # 读取原图,用于涂画
        self.img = cv2.imread(self.image)
        
        # copy一份原图,保留原始状态
        self.img2 = self.img.copy()
        
        # 获取鼠标的起始位置
        self.start_x = 0
        self.start_y = 0
        
        # 是否需要绘制矩形的标志
        self.rect_flag = False
        
        # 记录要绘制矩形的大小
        self.rect = (0, 0, 0, 0)
        
        # 和原图同等大小的 掩码
        self.mask = np.zeros(shape=self.img.shape[:2], dtype=np.uint8)
        
        # 输出的结果 -- 前景和背景分离过的图片
        self.output = np.zeros(shape=self.img.shape[:2], dtype=np.uint8)
        
        # 记录GrabCut的次数
        self.count = 0
    
    # 实例方法, 第一个参数一定是self
    # @staticmethod 静态方法,类和实例对象不会自动传参数(self, cls)
    def on_mouse(self, event, x, y, flags, param):
        # 按下左键, 开始框选前景区域
        if event == cv2.EVENT_LBUTTONDOWN:
            # 记录起始的坐标
            self.start_x = x
            self.start_y = y
            # 将要绘制前景区域
            self.rect_flag = True
            
        # 松开鼠标
        elif event == cv2.EVENT_LBUTTONUP:
            # 停止绘制
            self.rect_flag = False
            # 记录用户的矩形大小
            self.rect = (min(self.start_x, x), min(self.start_y, y),
                         abs(self.start_x - x), abs(self.start_y - y))
            # 用红框,框出选定区域
            cv2.rectangle(self.img2, (self.start_x, self.start_y), (x, y), (0, 0, 255), 2)
            self.img = self.img2.copy()
        
        # 按住鼠标并移动
        elif event == cv2.EVENT_MOUSEMOVE and self.rect_flag:
            # 画矩形,不保留轨迹
            self.img = self.img2.copy()
            
            # 用绿框,框出选定区域
            cv2.rectangle(self.img, (self.start_x, self.start_y), (x, y), (0, 255, 0), 2)
            
    # 运行
    def run(self):
        # 创建窗口
        cv2.namedWindow('img')
        
        # 绑定鼠标事件
        cv2.setMouseCallback('img', self.on_mouse)
        
        while True:
            cv2.imshow('img', self.img)
            cv2.imshow('output', self.output)
            
            # 画面为30帧
            key = cv2.waitKey(1000 // 30)
            
            # 按下 'q'键退出APP
            if key == ord('q'):
                break
            # 按下 ‘g’键,开始分离前后景
            elif key == ord('g'):
                # 第一次分离
                if self.count == 0:
                    # 分离前后景
                    cv2.grabCut(self.img2, self.mask, self.rect, None, None, 5, 
                                mode=cv2.GC_INIT_WITH_RECT)
                else:
                    # 标记出新增的前景
                    self.mask[self.rect[1]: self.rect[1] + self.rect[3],
                         self.rect[0]: self.rect[0] + self.rect[2]] = 1
                    
                    # 分离前后景
                    cv2.grabCut(self.img2, self.mask, self.rect, None, None, 5, 
                                mode=cv2.GC_INIT_WITH_MASK)
                # 次数+1
                self.count = self.count + 1
                
            # 把前景或者可能是前景的位置设置为255, 
            mask2 = np.where((self.mask == 1) | (self.mask == 3), 255, 0).astype(np.uint8)
            
            # 使用与运算,展示算法的效果
            self.output = cv2.bitwise_and(self.img2, self.img2, mask=mask2)
            
        cv2.destroyAllWindows()
        cv2.waitKey(1)
        
        
app = App('./lena.png')
app.run()

MeanShift图像分割

MeanShift严格来说并不是用来对图像进行分割的, 而是在色彩层面进行平滑滤波的.它会中和色彩分布相近的颜色, 平滑色彩细节, 侵蚀掉面积较小的颜色区域.

它以图像上任一点p为圆心, 半径为sp, 色彩幅值为sr进行不断迭代.经过迭代,将收敛点的像素值代替原来的像素值,从而去除了局部相似的纹理,同时保留了边缘等差异较大的特征。
在这里插入图片描述

import cv2
import numpy as np

# 读取图片
img = cv2.imread('./flower.png')

# MeanShift使内部颜色更加平滑,通常配合其他算法使用
img_mean = cv2.pyrMeanShiftFiltering(img, 20, 30)

# canny算法,显示图像边缘信息
# MeanShift处理过的图像,可以更好显示边缘信息
img_canny = cv2.Canny(img_mean, 150, 300)
img_canny2 = cv2.Canny(img, 150, 300)

# 找轮廓
contours, _ = cv2.findContours(img_canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 在原图上画轮廓
cv2.drawContours(img, contours, -1, (0, 0, 255), 2)

cv2.imshow('img', np.hstack((img, img_mean)))
cv2.imshow('canny', np.hstack((img_canny, img_canny2)))

cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.waitKey(1)

-1

import cv2
import numpy as np

# 读取图片
img = cv2.imread('./key.png')

# MeanShift处理图片
img_mean = cv2.pyrMeanShiftFiltering(img, 20, 30)

# 分别找出 原图和处理过图片 的边缘
img_canny = cv2.Canny(img_mean, 150, 300)
img_canny2 = cv2.Canny(img, 150, 300)

# 利用处理过的图像 找轮廓
contours, _ = cv2.findContours(img_canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 在原图上画轮廓
cv2.drawContours(img, contours, -1, (0, 0, 255), 2)

# 显示图片
cv2.imshow('img', np.hstack((img, img_mean)))
cv2.imshow('canny', np.hstack((img_canny, img_canny2)))

cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.waitKey(1)

-1

# 直接找轮廓
import cv2
import numpy as np

# 读取图片
img = cv2.imread('./key.png')

# 灰度化
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 二值化
_ , thresh = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)

# 找出轮廓
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 画轮廓
cv2.drawContours(img, contours, -1, (0, 0, 255), 2)
cv2.imshow('img', img)

cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.waitKey(1)

-1

视频前后景分离

MOG

  • createBackgroundSubtractorMOG([, history[, nmixtures[, backgroundRatio[, noiseSigma]]]])
    • history: 进行建模的时候需要多长时间的参考帧, 默认是200ms
    • nmixtures: 高斯范围值, 默认为5.
    • backgroundRatio: 背景比例, 默认0.7
    • noiseSigma:降噪, 默认为0, 表示自动降噪.
  • 以上参数一般不需要修改, 默认即可.
import cv2
import numpy as np

# 读取视频
cap = cv2.VideoCapture('./vtest.avi')

# 创建 MOG对象
mog = cv2.bgsegm.createBackgroundSubtractorMOG()

while(True):
    # 读取视频画面
    ret, frame = cap.read()
    
    # 画面读取完毕则推出
    if not ret:
        break
    
    # 进行画面的前后景分离
    fgmask = mog.apply(frame)

    # 显示分离结果
    cv2.imshow('img',fgmask)

    # 按 'q'键退出
    k = cv2.waitKey(10) 
    if k == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
cv2.waitKey(1)

-1

MOG2

  • 同MOG类似, 不过对亮度产生的阴影有更好的识别, 缺点是会产生很多细小的噪点.

  • createBackgroundSubtractorMOG2

    • history: 默认500毫秒
    • detectShadows: 是否 检测阴影, 默认True
import cv2
import numpy as np

# 读取视频
cap = cv2.VideoCapture('./vtest.avi')
mog = cv2.createBackgroundSubtractorMOG2()

while(True):
    ret, frame = cap.read()
    if not ret:
        break
    
    fgmask = mog.apply(frame)

    cv2.imshow('img',fgmask)

    k = cv2.waitKey(10) 
    if k == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
cv2.waitKey(1)

-1

GMG

  • 去背景: 静态背景图像估计和每个像素的贝叶斯分割, 抗噪性更强.

  • createBackgroundSubtractorGMG

    • initializationFrames: 初始化帧数, 默认120帧.
import cv2
import numpy as np

cap = cv2.VideoCapture('./vtest.avi')
mog = cv2.bgsegm.createBackgroundSubtractorGMG()

while(True):
    ret, frame = cap.read()
    if not ret:
        break
    
    fgmask = mog.apply(frame)

    cv2.imshow('img',fgmask)

    k = cv2.waitKey(10) 
    if k == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
cv2.waitKey(1)

-1

图片修复

  • OpenCV中图像修复的技术——基本思想很简单:用相邻像素替换这些坏标记,使其看起来像邻居。

基本API

  • inpaint(src, inpaintMask, inpaintRadius, flags[, dst])

    • src要修复的图片

    • inpaintMask: 图像的掩码,单通道图像,大小跟原图像一致,inpaintMask图像上除了需要修复的部分之外其他部分的像素值全部为0

    • inpaintRadius: 每个点的圆心邻域半径.

    • flags: 修复的使用的算法. INPAINT_NS, INPAINT_TELEA

      • cv2.INPAINT_NS(Fluid Dynamics Method 流体力学算法)
      • cv2.INPAINT_TELEA(Fast Marching Method 快速行进算法)
import cv2
import numpy as np

# 读取原图
img = cv2.imread('./inpaint.png')

# 读取掩码 -- 标记图片损坏区域
mask = cv2.imread('./inpaint_mask.png', 0)

# 修复图像,返回修复结果
result = cv2.inpaint(img, mask, 5, flags=cv2.INPAINT_NS)

cv2.imshow('img', np.hstack((img, result)))

cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.waitKey(1)

-1

交互式抠图

import cv2
import numpy as np


class App:
    # 标记是否绘图
    drawing = False

    def __init__(self, ):
        self.mask = None

    # 对图像进行破坏
    def onmouse(self, event, x, y, flags, param):

        # 按下鼠标左键
        if event == cv2.EVENT_LBUTTONDOWN:
            # 开始绘图
            self.drawing = True
            
            # 标记鼠标位置
            self.ox, self.oy = x, y

        # 按住并移动鼠标
        elif event == cv2.EVENT_MOUSEMOVE and self.drawing == True:
            # 在图片上画线
            cv2.line(self.img_copy, (self.ox, self.oy), (x, y), (0,0,255),4)
            
            # 同步,在掩码上画线
            cv2.line(self.mask, (self.ox, self.oy), (x, y), (255,255,255),4)
            
            # 更新鼠标位置
            self.ox, self.oy = x, y
        
        # 松开鼠标
        elif event == cv2.EVENT_LBUTTONUP:
            # 结束绘图
            self.drawing = False

        
    def run(self):
        # 创建窗口
        cv2.namedWindow('input')
        cv2.namedWindow('output')
        
        # 对第一个窗口,绑定鼠标事件
        cv2.setMouseCallback('input', self.onmouse)
        
        # 读取图片
        self.img = cv2.imread('./1.jpg')
        
        # 调整图片大小
        self.img = cv2.resize(self.img, (1280, 720))
        
        # 拷贝一份图片,浅拷贝
        self.img_copy=self.img
        
        # 创建与原图同等大小的 掩码
        self.mask = np.zeros(self.img.shape[:2],dtype=np.uint8)

        while True:
            cv2.imshow('input', self.img_copy)
            
            # 原本:k是int类型,占4个字节
            # & 0xFF:k只保留前两个字节,用于chr()操作
            k = cv2.waitKey(1) & 0xFF
            
            # 退出操作
            if chr(k) == 'q' or k == 27:
                break
            
            # 保存涂改之后的图像 -- 重置掩码,不再修复
            elif chr(k) == 'r':
                self.mask = np.zeros(self.img.shape[:2], dtype=np.uint8)
            
            # 对图像进行修复
            elif chr(k) == 'p':
                dst = cv2.inpaint(self.img, self.mask, 5, cv2.INPAINT_TELEA)
                cv2.imshow('output', dst)
                
        cv2.destroyAllWindows()
        cv2.waitKey(1)
                
App().run()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

¥骁勇善战¥

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

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

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

打赏作者

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

抵扣说明:

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

余额充值