python计算机视觉——第十章 OpenCV

目录

10.1 OpenCV的Python接口

 10.2 OpenCV基础知识

10.2.1 读取和写入图像 

 10.2.2 颜色空间

​编辑 10.2.3 显示图像及结果

10.3 处理视频 

10.3.1 视频输入 

10.3.2 将视频读取到NumPy数组中

10.4 跟踪 

10.4.1 光流

10.4.2 Lucas-Kanade算法 

1. 使用跟踪器

2. 使用发生器 

10.5 更多示例 

10.5.1 图像修复 

10.5.2 利用分水岭变换进行分割 

 10.5.3 利用霍夫变换检测直线


 

10.1 OpenCV的Python接口

        OpenCV 是一个C++ 库,它包含了计算机视觉领域的很多模块。除了C++ 和C,Python 作为一种简洁的脚本语言,在C++ 代码基础上的Python 接口得到了越来越广泛的支持。

        可以通过以下方式导入新的cv2 模块:

import cv2

 10.2 OpenCV基础知识

        OpenCV 自带读取、写入图像函数以及矩阵操作和数学库。 

10.2.1 读取和写入图像 

        载入一张图像,打印出图像大小,对图像进行转换并保存为.png 格式: 

import cv2

# 读取图像
im = cv2.imread('project/PCV/ch10/pic/empire.jpg')
h,w = im.shape[:2]
print (h,w)
# 保存图像
cv2.imwrite('project/PCV/ch10/pic/result.png',im)

a8a0add4d3d343f0a84296bcbcdce7d4.png

 7b550bbe15524e2ca3ed8a62b2c430cc.png

 10.2.2 颜色空间

        在OpenCV 中,图像不是按传统的RGB 颜色通道,而是按BGR 顺序(即RGB 的倒序)存储的。读取图像时默认的是BGR,但是还有一些可用的转换函数。颜色空间的转换可以用函数cvColor() 来实现。 

        通过下面的方式将原图像转换成灰度图像: 

# -*- coding: utf-8 -*-
import cv2
from pylab import  *

# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)

# 读取图像
im = imread('jsjsj\ch10\pic\empire.jpg')
im1 = cv2.imread('jsjsj\ch10\pic\empire.jpg')
gray = cv2.cvtColor(im1,cv2.COLOR_BGR2GRAY)
BGR = cv2.cvtColor(gray,cv2.COLOR_GRAY2BGR)
figure()
subplot(131)
title(u'(a)原始图像', fontproperties=font)
axis('off')
imshow(im)
subplot(132)
title(u'(b)COLOR_BGR2GRAY转换后图像', fontproperties=font)
axis('off')
imshow(gray)
subplot(133)
title(u'(c)(b)经过COLOR_GRAY2RGB转换后图像', fontproperties=font)
axis('off')
imshow(BGR)
show()

28513bf8e5014b378c500eec1adec3ac.png 10.2.3 显示图像及结果

         利用OpenCV 绘制功能和窗口管理功能来显示结果。

从文件中读取一幅图像,并创建一个整数图像表示: 

import cv2

# 读取图像
im = cv2.imread('jsjsj\ch10\pic\empire.jpg')
gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
# 计算积分图像
intim = cv2.integral(gray)
# 归一化并保存
intim = (255.0*intim) / intim.max()
cv2.imwrite(r'jsjsj\ch10\pic\result.jpg',intim)

 379c430d6426459ab330dfd419740c5c.png

从一个种子像素进行泛洪填充: 

import cv2
import numpy as np
from pylab import  *
from PIL import Image


# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)

# 读取图像
im = cv2.imread('jsjsj\ch10\pic\empire.jpg')
h,w = im.shape[:2]

# 泛洪填充
diff = (6,6,6)
mask = zeros((h+2,w+2),uint8)
im1 = cv2.floodFill(im,mask,(10,10), (255,255,0),diff,diff)

# 在OpenCV 窗口中显示结果
cv2.imshow('flood fill',im)
cv2.waitKey()

# 保存结果
cv2.imwrite(r'jsjsj\ch10\pic\result.jpg',im)

5aeea3062a8e4a1292d920493177b9a0.png 

        对图像应用泛洪填充并在 OpenCV 窗口中显示结果。waitKey() 函数一直处于暂停状态,直到有按键按下,此时窗口才会自动关闭。这里的 floodfill() 函数获取(灰度或彩色)图像、一个掩膜(非零像素区域表明该区域不会被填充)、一 个种子像素以及新的颜色值来代替下限和上限阈值差的泛洪像素。泛洪填充以种子像素为起始,只要能在阈值的差异范围内添加新的像素,泛洪填充就会持续扩展。 不同的阈值差异由元组 (R,G,B) 给出。 

SURF 特征的提取,SURF 特征是SIFT 特征的一个更快特征提取版 

 需要OpenCV-contrib库,包含一些来自社区的功能,包括SURF算法。

        pip install opencv-contrib-python

        然后导入cv2.xfeatures2d

import cv2
from numpy import uint8, ones


# 读取图像
im = cv2.imread(r'jsjsj\ch10\pic\fisherman.jpg')

# 下采样
im_lowres = cv2.pyrDown(im)

# 变换成灰度图像
gray = cv2.cvtColor(im_lowres,cv2.COLOR_RGB2GRAY)

# 检测特征点
s = cv2.xfeatures2d.SIFT_create()
mask = uint8(ones(gray.shape))
keypoints = s.detect(gray,mask)

# 显示结果及特征点
vis = cv2.cvtColor(gray,cv2.COLOR_GRAY2BGR)
for k in keypoints[::10]:
    cv2.circle(vis,(int(k.pt[0]),int(k.pt[1])),2,(0,255,0),-1)
    cv2.circle(vis,(int(k.pt[0]),int(k.pt[1])),int(k.size),(0,255,0),2)
cv2.imshow('local descriptors',vis)
cv2.waitKey()

3518c3ad3d8f444390b467f2cf8709bb.png 

        读取图像后,如果没有给定新尺寸,则用 pyrDown() 进行下采样,创建一个尺寸为原 图像大小一半的新图像,然后将该图像转换为灰度图像,并传递到一个 SURF 关键点检测对象;掩膜决定了在哪些区域应用关键点检测器。在画出检测结果时,将灰度图像转换成彩色图像,并用绿色通道画出检测到的关键点。在每到第十个关键点时循环一次,并在中心画一个圆环,每一个圆环显示出关键点的尺度(大小)。绘图函数 circle() 获取一幅图像、图像坐标(仅整数坐标)元组、半径、绘图的颜色元组以及线条粗细(-1 是实线圆环)。上图显示了提取出来的 SURF 特征。 

10.3 处理视频 

        单纯使用 Python 来处理视频有些困难,因为需要考虑速度、编解码器、摄像机、操作系统和文件格式。目前还没有针对 Python 的视频库,使用 OpenCV 的 Python 接口是唯一不错的选择。 

10.3.1 视频输入 

        OpenCV 能够很好地支持从摄像头读取视频。下面给出了一个捕获视频帧并在OpenCV 窗口中显示这些视频帧的完整例子: 

import cv2

# 设置视频捕获
cap = cv2.VideoCapture(0)
while True:
    ret,im = cap.read()
    cv2.imshow('video test',im)
    key = cv2.waitKey(10)
    if key == 27:
        break
    if key == ord(' '):
        cv2.imwrite('vid_result.jpg',im)

d3d1bc249a1744ae8f19495b6a0bbabe.png

        捕获对象VideoCapture 从摄像头或文件捕获视频。我们通过一个整数进行初始化,该整数为视频设备的id;如果仅有一个摄像头与计算机相连接,那么该摄像头的id为0。read() 方法解码并返回下一视频帧,第一个变量ret 是一个判断视频帧是否成功读入的标志,第二个变量则是实际读入的图像数组。函数waitKey() 等待用户按键:如果按下的是Esc 键(ASCII 码是27)键,则退出应用;如果按下的是空格键,就保存该视频帧。

        拓展上面的例子,将摄像头捕获的数据作为输入,并在OpenCV 窗口中实时显示经模糊的(彩色)图像:

import cv2

# 设置视频捕获
cap = cv2.VideoCapture(0)

# while True:
#     ret,im = cap.read()
#     cv2.imshow('video test',im)
#     key = cv2.waitKey(10)
#     if key == 27:
#         break
#     if key == ord(' '):
#         cv2.imwrite('vid_result.jpg',im)


# 获取视频帧,应用高斯平滑,显示结果
while True:
    ret,im = cap.read()
    blur = cv2.GaussianBlur(im,(0,0),5)
    cv2.imshow('camera blur',blur)
    if cv2.waitKey(10) == 27:
        break

        每一视频帧都会被传递给GaussianBlur() 函数,该函数会用高斯滤波器对传入的该帧图像进行滤波。这里,我们传递的是彩色图像,所以Gaussian Blur() 函数会录入对彩色图像的每一个通道单独进行模糊。该函数需要为高斯函数设定滤波器尺寸(保存在元组中)及标准差;

        以同样的方式从文件读取视频,不过我们调用VideoCapture() 获取视频时是以文件名作为输入的:

capture = cv2.VideoCapture('filename')

10.3.2 将视频读取到NumPy数组中

        使用OpenCV 可以从一个文件读取视频帧,并将其转换成NumPy 数组。下面是一个从摄像头捕获视频并将视频帧存储在一个NumPy 数组中的例子: 

import cv2
from numpy import array


# 设置视频捕获
cap = cv2.VideoCapture(0)

frames = []
# 获取帧,存储到数组中
while True:
    ret,im = cap.read()
    cv2.imshow('video',im)
    frames.append(im)
    if cv2.waitKey(10) == 27:
        break   
frames = array(frames)

# 检查尺寸
print (im.shape)
print (frames.shape)

        上述代码将每一视频帧数组添加到列表末,直到捕获结束。最终得到的数组会有帧数、帧高、帧宽及颜色通道数(3 个),打印出的结果如下: 

 83bd2b7a120643dbba16b3ab578c1258.png

        这里共记录了253帧。类似上面将视频数据存储在数组中对于视频处理是非常有帮助
的,比如计算帧间差异以及跟踪。 

10.4 跟踪 

        跟踪是在图像序列或视频里对其中的目标进行跟踪的过程。 

10.4.1 光流

        光流是目标、场景或摄像机在连续两帧图像间运动时造成的目标的运动。它是图像在平移过程中的二维矢量场。作为一种经典并深入研究了的方法,它在诸如视频压缩、运动估计、目标跟踪和图像分割等计算机视觉中得到了广泛的应用。 

光流法主要依赖于三个假设。 

(1) 亮度恒定:图像中目标的像素强度在连续帧之间不会发生变化。
(2) 时间规律:相邻帧之间的时间足够短,以至于在考虑运行变化时可以忽略它们之
间的差异。该假设用于导出下面的核心方程。
(3) 空间一致性:相邻像素具有相似的运动。

很多情况下这些假设并不成立,但是对于相邻帧间的小运动以及短时间跳跃,它还是一个非常好的模型。假设一个目标像素在t时刻亮度为\(I(x,y,t)\),在t+δt时刻运动[δx,δy]后与t时刻具有相同的亮度,即\(I(x,y,t){=}I(x{+}\delta x, y{+}\delta y, t{+}\delta t)\)。 对该约束用泰勒公式进行一阶展开并关于t求偏导可以得到光流方程:

\(\triangledown I^Tv=-I_t\)

        v=[u,v]是运动矢量,\(I_{i}\)是时间偏导。对于图像中那些单个的点,该方程是线性方程组。由于v包含两个未知变量,所以该方程是不可解的。通过强制加入空间一致性约束,则有可能获得该方程的解。 

利用calcOpticalFlowFarneback() 在视频中寻找运动矢量的例子:

import cv2
import numpy as np
from pylab import  *
from PIL import Image

def draw_flow(im,flow,step=16):
    """ 在间隔分开的像素采样点处绘制光流 """
    h,w = im.shape[:2]
    y,x = mgrid[step/2:h:step,step/2:w:step].reshape(2,-1).astype(int)
    fx, fy = flow[y, x].T
    # 创建线的终点
    lines = vstack([x,y,x+fx,y+fy]).T.reshape(-1,2,2)
    lines = int32(lines)
    # 创建图像并绘制
    vis = cv2.cvtColor(im, cv2.COLOR_GRAY2BGR)
    for (x1, y1), (x2, y2) in lines:
      cv2.line(vis, (x1, y1), (x2, y2), (0, 255, 0), 1)
      cv2.circle(vis, (x1, y1), 1, (0, 255, 0), -1)
    return vis
  #设置视频捕获
cap = cv2.VideoCapture(0)
ret,im = cap.read()
prev_gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
while True:
      # 获取灰度图像
      ret,im = cap.read()
      gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
      # 计算流
      flow = cv2.calcOpticalFlowFarneback(prev_gray,gray,None,0.5,3,15,3,5,1.2,0)
      prev_gray = gray
      # 画出流矢量
      cv2.imshow('Optical flow',draw_flow(gray,flow))
      if cv2.waitKey(10) == 27:
        break

1135d6fa6f854a41b726184fa40c05c0.png 

        利用摄像头捕获图像,并对每个连续图像对进行光流估计。由calcOpticalFlowFarneback() 返回的运动光流矢量保存在双通道图像变量flow 中。除了需要获取需要获取前一帧和当前帧,该函数还需要一系列参数 。

        辅助函数draw_flow() 会在图像均匀间隔的点处绘制光流矢量,它利用OpenCV 的绘图函数line() 和circle(),并用变量step 控制流样本的间距。

10.4.2 Lucas-Kanade算法 

         跟踪最基本的形式是跟随感兴趣点,比如角点。一次流行的算法是Lucas-Kanade 跟踪算法,它利用了稀疏光流算法。

        Lucas-Kanade 跟踪算法可以应用于任何一种特征,不过通常使用一些角点,角点是结构张量(Harris 矩阵)中有两个较大特征值的那些点,且更小的特征值要大于某个阈值。

        如果基于每一个像素考虑,该光流方程组是欠定方程,即每个方程中含很多未知变量。利用相邻像素有相同运动这一假设,对于n 个相邻像素,可以将这些方程写成如下系统方程:

\(\begin{bmatrix}\nabla I^T(\mathbf{x}_1)\\\nabla I^T(\mathbf{x}_2)\\\vdots\\\nabla I^T(\mathbf{x}_n)\end{bmatrix}\boldsymbol{v}=\begin{bmatrix}I_x(\mathbf{x}_1)&I_y(\mathbf{x}_1)\\I_x(\mathbf{x}_2)&I_y(\mathbf{x}_2)\\\vdots&\vdots\\I_x(\mathbf{x}_n)&I_y(\mathbf{x}_n)\end{bmatrix}\begin{bmatrix}\boldsymbol{u}\\\boldsymbol{v}\end{bmatrix}=-\begin{bmatrix}I_t(\mathbf{x}_1)\\I_t(\mathbf{x}_2)\\\vdots\\I_t(\mathbf{x}_n)\end{bmatrix}\) 

        该系统方程的优势是,现在方程的数目多于未知变量,并且可以用最小二乘法解出该系统方程。对于周围像素的贡献可以进行加权处理,使越远的像素影响越小;高斯权重是一种最常见的选择。将上面的矩阵变换成方程的结构张量形式,可以得出以下关系:

\(\overline{\boldsymbol{M}}_I\boldsymbol{v}=-\begin{bmatrix}I_t(\mathbf{x}_1)\\I_t(\mathbf{x}_2)\\\vdots\\I_t(\mathbf{x}_n)\end{bmatrix}, \text{或简记为} A\boldsymbol{v}=\boldsymbol{b}\) 

该超定方程组可以用最小二乘法求解,得出运动矢量: 

\(v=(A^TA)^{-1}A^Tb\) 

        v只有在\(A^{T}A\) 可逆时才是可解的。这就是 Lucas-Kanade 跟踪算法运行矢量怎样计算出来的全过程。

        标准的 Lucas-Kanade 跟踪适用于小位移。为了能够处理较大位移,需要采用分层的方法。在该情形下,光流可以通过对图像由粗到精采样计算得到。这就是 OpenCV 函数 calcOpticalFlowPyrLK() 要做的事。

        这些 Lucas-Kanade 函数包含在 OpenCV 中,利用这些函数建立一个 Python 跟踪类。创建名为 lktrack.py 的文件,向其添加下面的类和构造函数:

import cv2
from numpy import array, float32


# 一些常数及默认参数
lk_params = dict(winSize=(15,15),maxLevel=2,
    criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT,10,0.03))

subpix_params = dict(zeroZone=(-1,-1),winSize=(10,10),
    criteria = (cv2.TERM_CRITERIA_COUNT | cv2.TERM_CRITERIA_EPS,20,0.03))

feature_params = dict(maxCorners=500,qualityLevel=0.01,minDistance=10)


class LKTracker(object):
    """ 用金字塔光流Lucas-Kanade 跟踪类"""
    def __init__(self,imnames):
        """ 使用图像名称列表初始化"""
        self.imnames = imnames
        self.features = []
        self.tracks = []
        self.current_frame = 0


    def detect_points(self):
        """ 利用子像素精确度在当前帧中检测“利于跟踪的好的特征”( 角点) """
        # 载入图像并创建灰度图像
        self.image = cv2.imread(self.imnames[self.current_frame])
        self.gray = cv2.cvtColor(self.image,cv2.COLOR_BGR2GRAY)
        # 搜索好的特征点
        features = cv2.goodFeaturesToTrack(self.gray, **feature_params)
        # 提炼角点位置
        cv2.cornerSubPix(self.gray,features, **subpix_params)
        self.features = features
        self.tracks = [[p] for p in features.reshape((-1,2))]
        self.prev_gray = self.gray


    def track_points(self):
        """ 跟踪检测到的特征"""
        if self.features != []:
            self.step() # 移到下一帧

            # 载入图像并创建灰度图像
            self.image = cv2.imread(self.imnames[self.current_frame])
            self.gray = cv2.cvtColor(self.image,cv2.COLOR_BGR2GRAY)

            #reshape() 操作,以适应输入格式
            tmp = float32(self.features).reshape(-1, 1, 2)

            # 计算光流
            features,status,track_error = cv2.calcOpticalFlowPyrLK(self.prev_gray,
                self.gray,tmp,None,**lk_params)
            
            # 去除丢失的点
            self.features = [p for (st,p) in zip(status,features) if st]

            # 从丢失的点清楚跟踪轨迹
            features = array(features).reshape((-1,2))
            for i,f in enumerate(features):
                self.tracks[i].append(f)
            ndx = [i for (i,st) in enumerate(status) if not st]
            ndx.reverse()# 从后面移除
            for i in ndx:
                self.tracks.pop(i)
                
            self.prev_gray = self.gray


    def step(self,framenbr=None):
        """ 移到下一帧。如果没有给定参数,直接移到下一帧"""
        if framenbr is None:
            self.current_frame = (self.current_frame + 1) % len(self.imnames)
        else:
            self.current_frame = framenbr % len(self.imnames)


    def draw(self):
        """ 用OpenCV 自带的画图函数画出当前图像及跟踪点,按任意键关闭窗口"""
        # 用绿色圆圈画出跟踪点
        for point in self.features:
            cv2.circle(self.image,(int(point[0][0]),int(point[0][1])),3,(0,255,0),-1)
            
        cv2.imshow('LKtrack',self.image)
        cv2.waitKey()

        用一个文件名列表对跟踪对象进行初始化,变量features 和tracks 分别保存角点和对这些角点进行跟踪的位置,同时,我们也利用一个变量对当前帧进行跟踪。我们定义了三个字典变量用于特征提取、跟踪、和亚像素特征点的提炼。

        在开始检测角点时,我们需要载入实际图像,并转换成灰度图像,提取“利用跟踪的好的特征”点。OpenCV 函数goodFeaturesToTrack() 方法可以完成这一主要工作。cornerSubPix() 提炼角点位置,并保存在成员变量features 和tracks中。需要注意的是,运行该函数会清除跟踪历史。

        track_points() 方法, 对其进行跟踪。首先我们需要获得下一帧图像,然后应用OpenCV 函数calcOpticalFlowPyrLK() 找出这些点运动到哪里了,最后清除这些包含跟踪点的列表。

        辅助函数step(),用于移动到下一视频帧,该方法会跳转到一个给定的视频帧,如果没有给定参数则直接跳转到下一帧。

        添加draw() 方法到LKTracker 类:用OpenCV 窗口和绘图函数画出最终的跟踪结果。

1. 使用跟踪器

         将该跟踪类用于真实的场景中。下面的脚本初始化一个跟踪对象,对视频序列进行角点检测、跟踪,并画出跟踪结果:

import lktrack

imnames = [r'jsjsj\ch10\10.4\bt.003.pgm', r'jsjsj\ch10\10.4\bt.002.pgm', r'jsjsj\ch10\10.4\bt.001.pgm', r'jsjsj\ch10\10.4\bt.000.pgm']

# 创建跟踪对象
lkt = lktrack.LKTracker(imnames)

# 在第一帧进行检测,跟踪剩下的帧
lkt.detect_points()
lkt.draw()
for i in range(len(imnames)-1):
    lkt.track_points()
    lkt.draw()

14c77dec27594f7993da4797eb501013.pngdb12a35966bd46039cdb0831f49dfeb9.png17ea68a5d8644387be284d1f3ca4505d.pngcdacaa63b8774a919cc2926ee4dc576f.png

每次画出一帧,并显示当前跟踪到的点,按任意键会转移到序列的下一帧。 

2. 使用发生器 

将下面的方法添加到LKTracker 类: 

    def track(self):
        """ 发生器,用于遍历整个序列"""

        for i in range(len(self.imnames)):
            if self.features == []:
                self.detect_points()
            else:
                self.track_points()
                
        # 创建一份RGB 副本
        f = array(self.features).reshape(-1,2)
        im = cv2.cvtColor(self.image,cv2.COLOR_BGR2RGB)
        yield im,f

        上面的方法创建一个发生器,可以使遍历整个序列并将获得的跟踪点和这些图像以RGB 数组保存,以方便画出跟踪结果。将它用于经典的牛津“dinosaur”序列(也来源于上面提到的多视图数据集),并画出这些点及这些点的跟踪结果,代码如下:

import lktrack
from pylab import *

imnames = [r'jsjsj\ch10\10.4\viff.000.ppm', r'jsjsj\ch10\10.4\viff.001.ppm',
r'jsjsj\ch10\10.4\viff.002.ppm', r'jsjsj\ch10\10.4\viff.003.ppm', r'jsjsj\ch10\10.4\viff.004.ppm']

# 用LKTracker 发生器进行跟踪
lkt = lktrack.LKTracker(imnames)
for im,ft in lkt.track():
    print ('tracking %d features' % len(ft))

# 画出轨迹
figure()
imshow(im)
for p in ft:
    plot(p[0],p[1],'bo')
for t in lkt.tracks:
    plot([p[0] for p in t],[p[1] for p in t])
axis('off')
show()

2188cb4c65c4411f963f7d0e4ce01954.png 

10.5 更多示例 

OpenCV 自带很多关于如何使用Python 接口的有用示例。 

10.5.1 图像修复 

        对图像丢失或损坏的部分进行重建的过程叫做修复,既包括以复原为目的的对图像丢失数据或损坏部分进行恢复的算法,也包括在照片编辑应用程序中去除红眼或物体的算法。 

$ python inpaint.py empire.jpg

运行上面的命令会打开一个交互窗口,在该窗口中你可以画一些需要修复的区域。最终修复的结果会在一个单独的窗口中显示出来

10.5.2 利用分水岭变换进行分割 

        分水岭是一种可以用于分割的图像处理技术。图像可以看成是一幅有很多种子区域“淹没”后形成的拓扑地貌。由于梯度幅值图像在突出的边缘有脊,而且分割通常在这些边缘处停止,所以通常会用到梯度幅值图像。 

$ python watershed.py empire.jpg

 10.5.3 利用霍夫变换检测直线

        霍夫变换是一种用于在图像中寻找各种形状的方法,原理是在参数空间中使用投票机制。霍夫变换常用于在图像中寻找直线结构。在该情况下,可以在二维直线参数空间对相同的直线参数进行投票,将边缘和线段组合在一起。 

$ python houghlines.py empire.jpg

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值