OpenCV实战:解锁图像处理、特征提取与实时视频分析的密码

OpenCV:计算机视觉的魔法宝盒

在当今数字化时代,计算机视觉技术正以前所未有的速度改变着我们的生活。从智能手机中的人脸识别解锁,到自动驾驶汽车的环境感知,再到工业生产中的质量检测,计算机视觉无处不在。而 OpenCV,作为计算机视觉领域的开源瑰宝,为无数开发者提供了实现创新的强大工具。

OpenCV(Open Source Computer Vision Library)是一个基于 BSD 许可(开源)发行的跨平台计算机视觉库,它轻量级而且高效,由一系列 C 函数和少量 C++ 类构成,同时提供了 Python、Ruby、MATLAB 等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法 。其功能涵盖了从基础的图像处理操作,如滤波、边缘检测、图像变换等,到高级的特征提取、目标检测、图像分割、立体视觉、运动分析等多个领域。

OpenCV 的应用领域极为广泛,在安防监控中,它可以实现实时的目标检测与跟踪,帮助安保人员及时发现异常情况;在智能交通系统里,能够进行车牌识别、车辆流量统计以及驾驶行为分析,为交通管理提供数据支持;在医疗影像处理方面,协助医生对 X 光、CT 等图像进行分析,辅助疾病诊断;在机器人领域,赋予机器人视觉感知能力,使其能够在复杂环境中自主导航、识别物体并完成任务 。可以说,OpenCV 就像是一把万能钥匙,打开了计算机视觉应用的大门,让开发者能够轻松地将创意转化为实际的项目。

环境准备:踏上魔法之旅前的筹备

在开始使用 OpenCV 进行项目开发前,我们需要先搭建好开发环境,准备好所需的工具和库。主要的安装步骤如下:

  1. 安装 Python:OpenCV 提供了 Python 接口,使得开发更加便捷。你可以从Python 官网下载最新版本的 Python。安装过程中,记得勾选 “Add Python to PATH” 选项,以便在命令行中能够直接使用 Python 命令。
  1. 安装 OpenCV 库:Python 环境下,推荐使用pip包管理器来安装 OpenCV 库。在命令行中输入以下命令即可完成安装:
 

pip install opencv-python

如果你还需要使用 OpenCV 的扩展模块,可以安装opencv - contrib - python包:

 

pip install opencv - contrib - python

  1. 选择开发工具:你可以选择自己熟悉的集成开发环境(IDE),如 PyCharm、Visual Studio Code 等。以 PyCharm 为例,创建新项目后,在项目设置中配置好 Python 解释器,确保能够正确导入 OpenCV 库。在 Visual Studio Code 中,安装 Python 插件后,同样需要配置好 Python 解释器路径 。

完成上述步骤后,你的开发环境就搭建好了,可以开始编写 OpenCV 项目代码了。

图像处理:魔法的基础篇章

图像处理是计算机视觉的基石,它涵盖了从图像的读取、显示到各种复杂的操作和变换,为后续的特征提取、目标检测等任务奠定了基础。在这一部分,我们将深入探索 OpenCV 在图像处理方面的强大功能,通过实际代码和案例,掌握图像操作的核心技巧。

(一)图像读取与显示

在 OpenCV 中,读取和显示图像是最基本的操作。使用cv2.imread()函数可以读取图像,该函数接受图像的路径作为参数,并返回一个表示图像的 NumPy 数组。cv2.imshow()函数用于显示图像,它接受两个参数:窗口名称和图像数组。以下是一个简单的示例:

 

import cv2

# 读取图像

image = cv2.imread('example.jpg')

# 显示图像

cv2.imshow('Image', image)

cv2.waitKey(0) # 等待按键,0表示无限期等待

cv2.destroyAllWindows() # 关闭所有窗口

在上述代码中,cv2.imread('example.jpg')读取了名为example.jpg的图像,并将其存储在image变量中。cv2.imshow('Image', image)在名为Image的窗口中显示该图像。cv2.waitKey(0)会使程序暂停,直到用户按下任意键,cv2.destroyAllWindows()则用于关闭所有 OpenCV 窗口 。

(二)图像基本操作

1. 灰度转换

灰度图像是指每个像素只有一个亮度值的图像,相比于彩色图像,它的数据量更小,处理起来也更加高效。在许多图像处理任务中,如边缘检测、特征提取等,通常需要先将彩色图像转换为灰度图像。

灰度转换的原理是根据人眼对不同颜色的敏感度,将彩色图像中的 RGB 三个通道的颜色值按照一定的权重进行加权求和,得到一个灰度值。常用的公式为:Gray = 0.299 * R + 0.587 * G + 0.114 * B。在 OpenCV 中,可以使用cv2.cvtColor()函数进行灰度转换,示例代码如下:

 

import cv2

# 读取彩色图像

color_image = cv2.imread('example.jpg')

# 转换为灰度图像

gray_image = cv2.cvtColor(color_image, cv2.COLOR_BGR2GRAY)

cv2.imshow('Color Image', color_image)

cv2.imshow('Gray Image', gray_image)

cv2.waitKey(0)

cv2.destroyAllWindows()

在这段代码中,cv2.cvtColor(color_image, cv2.COLOR_BGR2GRAY)将彩色图像color_image转换为灰度图像gray_image,cv2.COLOR_BGR2GRAY表示颜色空间的转换模式,即将 BGR 颜色空间转换为灰度空间 。

2. 图像缩放

图像缩放是指调整图像的大小,使其适应不同的应用场景。例如,在将图像输入到深度学习模型中时,通常需要将图像缩放到固定的尺寸;在显示图像时,也可能需要根据显示区域的大小对图像进行缩放。

在 OpenCV 中,使用cv2.resize()函数进行图像缩放,该函数可以指定输出图像的大小,也可以指定缩放因子。示例代码如下:

 

import cv2

# 读取图像

image = cv2.imread('example.jpg')

# 指定输出大小进行缩放

resized_image1 = cv2.resize(image, (300, 300))

# 指定缩放因子进行缩放

resized_image2 = cv2.resize(image, None, fx=0.5, fy=0.5)

cv2.imshow('Original Image', image)

cv2.imshow('Resized Image 1', resized_image1)

cv2.imshow('Resized Image 2', resized_image2)

cv2.waitKey(0)

cv2.destroyAllWindows()

在上述代码中,cv2.resize(image, (300, 300))将图像缩放到 300x300 的大小;cv2.resize(image, None, fx=0.5, fy=0.5)则将图像在 x 和 y 方向上都缩小为原来的 0.5 倍 。cv2.resize()函数还有一个interpolation参数,用于指定插值方法,不同的插值方法在缩放图像时的效果和计算效率有所不同,常见的插值方法有cv2.INTER_LINEAR(双线性插值,默认方法,适用于一般缩放)、cv2.INTER_NEAREST(最近邻插值,速度快,但图像质量可能较差)、cv2.INTER_CUBIC(双三次插值,适合放大操作,图像质量较高,但计算成本较高)等 。

3. 图像旋转

图像旋转是指将图像绕某个点按照一定的角度进行旋转。在 OpenCV 中,可以使用cv2.getRotationMatrix2D()函数获取旋转矩阵,然后使用cv2.warpAffine()函数对图像进行旋转。示例代码如下:

 

import cv2

import numpy as np

# 读取图像

image = cv2.imread('example.jpg')

# 获取图像的高度和宽度

height, width = image.shape[:2]

# 计算旋转中心

center = (width / 2, height / 2)

# 获取旋转矩阵,逆时针旋转45度,缩放因子为1.0

M = cv2.getRotationMatrix2D(center, 45, 1.0)

# 进行旋转

rotated_image = cv2.warpAffine(image, M, (width, height))

cv2.imshow('Original Image', image)

cv2.imshow('Rotated Image', rotated_image)

cv2.waitKey(0)

cv2.destroyAllWindows()

在这段代码中,cv2.getRotationMatrix2D(center, 45, 1.0)获取了一个绕图像中心逆时针旋转 45 度,缩放因子为 1.0 的旋转矩阵M。cv2.warpAffine(image, M, (width, height))使用该旋转矩阵对图像进行仿射变换,实现图像旋转,其中(width, height)指定了输出图像的大小 。

4. 图像裁剪

图像裁剪是指从图像中提取出感兴趣的区域(ROI,Region of Interest)。在 OpenCV 中,可以通过对图像数组进行切片操作来实现图像裁剪。示例代码如下:

 

import cv2

# 读取图像

image = cv2.imread('example.jpg')

# 定义裁剪区域的左上角和右下角坐标

x1, y1 = 100, 100

x2, y2 = 300, 300

# 裁剪图像

cropped_image = image[y1:y2, x1:x2]

cv2.imshow('Original Image', image)

cv2.imshow('Cropped Image', cropped_image)

cv2.waitKey(0)

cv2.destroyAllWindows()

在上述代码中,image[y1:y2, x1:x2]从图像中裁剪出了以(x1, y1)为左上角坐标,(x2, y2)为右下角坐标的区域,得到了裁剪后的图像cropped_image 。通过图像裁剪,可以聚焦于图像中的特定部分,减少后续处理的数据量,提高处理效率,例如在车牌识别中,可以先裁剪出车牌所在的区域,再进行字符识别等操作 。

(三)图像增强与滤波

1. 直方图均衡化

直方图均衡化是一种用于提升图像对比度的技术,它通过重新分配图像中像素的灰度值,使得图像的灰度直方图更加均匀地分布在整个灰度范围内,从而增强图像的细节和清晰度。其原理是根据图像的灰度直方图,计算出一个映射函数,将原始图像的灰度值映射到一个新的灰度值,使得新的灰度值分布更加均匀。

在 OpenCV 中,可以使用cv2.equalizeHist()函数对灰度图像进行直方图均衡化。示例代码如下:

 

import cv2

import numpy as np

from matplotlib import pyplot as plt

# 读取灰度图像

image = cv2.imread('low_contrast.jpg', 0)

# 进行直方图均衡化

equ = cv2.equalizeHist(image)

# 计算原始图像和均衡化后图像的直方图

hist_original, bins_original = np.histogram(image.flatten(), 256, [0, 256])

hist_equalized, bins_equalized = np.histogram(equ.flatten(), 256, [0, 256])

# 显示原始图像和均衡化后的图像

plt.subplot(2, 2, 1), plt.imshow(image, cmap='gray')

plt.title('Original Image'), plt.xticks([]), plt.yticks([])

plt.subplot(2, 2, 2), plt.imshow(equ, cmap='gray')

plt.title('Equalized Image'), plt.xticks([]), plt.yticks([])

# 绘制原始图像和均衡化后图像的直方图

plt.subplot(2, 2, 3), plt.plot(hist_original, color='r')

plt.title('Original Histogram'), plt.xlim([0, 256])

plt.subplot(2, 2, 4), plt.plot(hist_equalized, color='b')

plt.title('Equalized Histogram'), plt.xlim([0, 256])

plt.show()

在这段代码中,cv2.equalizeHist(image)对灰度图像image进行直方图均衡化,得到均衡化后的图像equ。通过np.histogram()函数计算原始图像和均衡化后图像的直方图,并使用matplotlib库进行绘制,以便直观地对比直方图的变化 。从结果中可以看出,经过直方图均衡化后,图像的对比度明显增强,原本较暗和较亮的区域细节更加清晰,直方图也更加均匀地分布在整个灰度范围内 。

2. 高斯模糊

高斯模糊是一种常用的图像滤波方法,它通过对图像中的每个像素点及其邻域内的像素点进行加权平均,来减少图像中的噪声。其原理是基于高斯分布,离中心像素越近的像素点权重越高,离中心像素越远的像素点权重越低 。在 OpenCV 中,可以使用cv2.GaussianBlur()函数实现高斯模糊。示例代码如下:

 

import cv2

# 读取图像

image = cv2.imread('noisy_image.jpg')

# 进行高斯模糊,核大小为(5, 5),标准差为0

blurred_image = cv2.GaussianBlur(image, (5, 5), 0)

cv2.imshow('Original Image', image)

cv2.imshow('Blurred Image', blurred_image)

cv2.waitKey(0)

cv2.destroyAllWindows()

在上述代码中,cv2.GaussianBlur(image, (5, 5), 0)对图像image进行高斯模糊,其中(5, 5)是高斯核的大小,必须是奇数,0 表示根据核大小自动计算标准差 。经过高斯模糊后,图像中的噪声得到了有效抑制,变得更加平滑,但同时也会使图像的细节有一定程度的模糊 。高斯模糊在很多场景中都有应用,比如在进行边缘检测前,通常会先对图像进行高斯模糊,以减少噪声对边缘检测结果的干扰 。

3. 中值滤波

中值滤波也是一种去除图像噪声的方法,特别是对于椒盐噪声有很好的效果。它的原理是将图像中每个像素点的灰度值替换为其邻域内像素灰度值的中值 。在 OpenCV 中,使用cv2.medianBlur()函数进行中值滤波。示例代码如下:

 

import cv2

# 读取带有椒盐噪声的图像

image = cv2.imread('salt_and_pepper_noise.jpg')

# 进行中值滤波,核大小为5

median_blurred_image = cv2.medianBlur(image, 5)

cv2.imshow('Original Image', image)

cv2.imshow('Median Blurred Image', median_blurred_image)

cv2.waitKey(0)

cv2.destroyAllWindows()

在这段代码中,cv2.medianBlur(image, 5)对图像image进行中值滤波,核大小为 5。中值滤波能够有效地去除椒盐噪声,同时较好地保留图像的边缘和细节信息 。与高斯模糊不同,中值滤波对于脉冲噪声(椒盐噪声)的去除效果更为显著,而高斯模糊更适用于去除高斯噪声等连续分布的噪声 。在实际应用中,需要根据图像噪声的类型和特点选择合适的滤波方法 。

(四)边缘检测与轮廓提取

1. Canny 边缘检测

Canny 边缘检测算法是一种经典的边缘检测算法,它能够检测出图像中物体的边缘。该算法的主要步骤包括:使用高斯滤波器对图像进行平滑处理,以减少噪声的影响;计算图像中每个像素的梯度强度和方向;应用非极大值抑制,去除可能的虚假边缘;使用双阈值检测来确定真正的边缘和潜在的边缘 。

在 OpenCV 中,可以使用cv2.Canny()函数进行 Canny 边缘检测。示例代码如下:

 

import cv2

# 读取灰度图像

image = cv2.imread('building.jpg', 0)

# 进行Canny边缘检测,低阈值为50,高阈值为150

edges = cv2.Canny(image, 50, 150)

cv2.imshow('Original Image', image)

cv2.imshow('Edges', edges)

cv2.waitKey(0)

cv2.destroyAllWindows()

在上述代码中,cv2.Canny(image, 50, 150)对灰度图像image进行 Canny 边缘检测,其中 50 是低阈值,150 是高阈值 。低阈值用于确定潜在的边缘,高阈值用于确定真正的边缘 。如果一个像素点的梯度强度大于高阈值,则被认为是边缘点;如果小于低阈值,则被忽略;如果在低阈值和高阈值之间,则只有当它与一个高于高阈值的像素点相连时,才被认为是边缘点 。通过调整低阈值和高阈值的值,可以控制检测到的边缘数量和质量 。较低的阈值会检测出更多的边缘,但也可能会引入更多的噪声;较高的阈值会检测出更准确的边缘,但可能会丢失一些细节 。

2. 轮廓提取与绘制

轮廓是指图像中物体边界的曲线。在 OpenCV 中,可以使用cv2.findContours()函数提取图像的轮廓,使用cv2.drawContours()函数绘制轮廓 。轮廓提取在很多领域都有应用,比如在物体识别中,可以通过提取物体的轮廓来识别物体的形状;在图像分割中,可以根据轮廓将图像分割成不同的区域 。示例代码如下:

 

import cv2

import numpy as np

# 读取图像

image = cv2.imread('coins.jpg')

# 转换为灰度图像

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 进行二值化处理

ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

# 提取轮廓

contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 绘制轮廓,-1表示绘制所有轮廓,颜色为绿色,线条厚度为2

image_with_contours = cv2.drawContours(image.copy(), contours, -1, (0, 255, 0), 2)

cv2.imshow('Original Image', image)

cv2.imshow('Image with Contours', image_with_contours)

cv2.waitKey(0)

cv2.destroyAllWindows()

在这段代码中,首先将彩色图像转换为灰度图像,然后通过阈值处理将灰度图像转换为二值图像。cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)从二值图像中提取轮廓,cv2.RETR_TREE表示轮廓检索模式,它会检索所有的轮廓,并重构嵌套轮廓的整个层次结构;cv2.CHAIN_APPROX_SIMPLE是轮廓逼近方法,它会压缩水平、垂直和斜的部分,只保留它们的终点部分,从而减少轮廓点的数量 。cv2.drawContours(image.copy(), contours, -1, (0, 255, 0), 2)在原始图像的副本上绘制提取到的轮廓,其中-1表示绘制所有轮廓,(0, 255, 0)表示轮廓的颜色为绿色,2表示线条厚度为 2 。通过绘制轮廓,可以清晰地看到图像中物体的边界,方便后续的分析和处理 。

特征提取:挖掘图像的独特标识

(一)特征提取简介

特征提取是计算机视觉领域中的关键技术,它旨在从图像或视频数据中提取出能够代表图像内容的关键信息,这些信息被称为特征。特征就像是图像的 “指纹”,具有唯一性和代表性,即使图像在尺度、旋转、光照等条件发生变化时,这些特征依然能够保持相对稳定 。通过提取图像的特征,可以将复杂的图像数据转化为简洁的特征向量,大大降低数据处理的复杂度,提高后续分析和处理的效率。

在实际应用中,特征提取有着广泛的用途。在图像识别中,通过提取图像的特征并与已知物体的特征库进行比对,能够快速准确地识别出图像中的物体;在目标检测任务里,特征提取可以帮助算法定位图像中感兴趣的目标,并确定其位置和类别;在图像匹配和图像拼接中,利用特征提取可以找到不同图像之间的对应关系,从而实现图像的对齐和拼接 。例如,在安防监控系统中,通过对监控视频中的人物特征进行提取和分析,可以实现人员的身份识别和行为分析,为安全防范提供有力支持 。

(二)SIFT 特征提取

1. 原理介绍

SIFT(Scale-Invariant Feature Transform,尺度不变特征变换)算法是一种经典的特征提取算法,由 David Lowe 在 1999 年提出,并在 2004 年进一步完善 。该算法具有尺度不变性、旋转不变性和光照不变性等优点,能够在不同尺度和旋转角度的图像中提取到稳定的特征点 。

SIFT 算法主要包括以下几个步骤:

  • 尺度空间极值检测:为了实现尺度不变性,SIFT 算法首先构建图像的尺度空间。尺度空间是通过对原始图像与不同尺度的高斯核进行卷积得到的,不同尺度的高斯核可以模拟人眼在不同距离下观察物体的效果 。在尺度空间中,通过高斯差分(DoG,Difference of Gaussian)算子来检测潜在的关键点。DoG 算子是通过对相邻尺度的高斯图像相减得到的,它能够突出图像中的边缘和角点等特征 。在 DoG 图像中,每个像素点都要与它周围邻域的 26 个点(同一尺度下的 8 个邻域点以及上下相邻尺度的各 9 个点)进行比较,如果该像素点是局部极值点(极大值或极小值),则它被认为是一个潜在的关键点 。
  • 关键点定位:检测到的潜在关键点可能包含噪声和边缘点等不稳定因素,需要进一步精确关键点的位置,并去除低对比度和边缘响应的点 。SIFT 算法通过拟合三维二次函数来精确关键点的位置和尺度,同时根据关键点的对比度和主曲率来判断其稳定性,去除不稳定的关键点 。
  • 关键点方向确定:为了使 SIFT 特征具有旋转不变性,需要为每个关键点分配一个或多个方向 。算法通过计算关键点邻域内的梯度方向直方图来确定关键点的主方向和辅方向。以关键点为中心,在其邻域内计算每个像素的梯度幅值和方向,然后将这些梯度方向统计到一个方向直方图中,直方图的峰值方向即为关键点的主方向,幅值大于峰值 80% 的方向作为辅方向 。
  • 关键点描述符生成:最后,为每个关键点生成一个 128 维的描述符 。以关键点为中心,将其邻域划分为 16 个 4x4 的子区域,在每个子区域内计算 8 个方向的梯度直方图,这样每个子区域就可以用一个 8 维的向量来表示,16 个子区域共生成 128 维的向量,作为该关键点的描述符 。描述符的计算过程中还进行了一些归一化和抗干扰处理,以增强其对光照和噪声变化的鲁棒性 。
2. 代码实现

在 OpenCV 中,可以使用cv2.xfeatures2d.SIFT_create()函数来创建 SIFT 对象,并使用该对象的detectAndCompute()方法来检测关键点并计算描述符 。示例代码如下:

 

import cv2

import numpy as np

from matplotlib import pyplot as plt

# 读取灰度图像

image = cv2.imread('example.jpg', cv2.IMREAD_GRAYSCALE)

# 创建SIFT对象

sift = cv2.xfeatures2d.SIFT_create()

# 检测关键点并计算描述符

keypoints, descriptors = sift.detectAndCompute(image, None)

# 在图像上绘制关键点

image_with_keypoints = cv2.drawKeypoints(image, keypoints, None, color=(0, 255, 0), flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

# 显示图像

plt.imshow(cv2.cvtColor(image_with_keypoints, cv2.COLOR_BGR2RGB))

plt.title('SIFT Keypoints')

plt.axis('off')

plt.show()

在上述代码中,cv2.xfeatures2d.SIFT_create()创建了一个 SIFT 对象sift 。sift.detectAndCompute(image, None)在灰度图像image上检测关键点并计算描述符,keypoints是检测到的关键点列表,descriptors是对应的描述符数组 。cv2.drawKeypoints()函数在原始图像上绘制关键点,cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS标志表示绘制的关键点包含更多信息,如关键点的大小和方向 。最后,使用matplotlib库显示绘制了关键点的图像 。

(三)ORB 特征提取

1. 原理介绍

ORB(Oriented FAST and Rotated BRIEF)是一种高效的特征提取算法,它结合了 FAST(Features from Accelerated Segment Test)角点检测算法和 BRIEF(Binary Robust Independent Elementary Features)描述符,并对其进行了改进,使其具有旋转不变性和尺度不变性 。与 SIFT 算法相比,ORB 算法计算速度更快,内存占用更小,更适合实时应用场景 。

ORB 算法的主要步骤如下:

  • FAST 角点检测:FAST 算法是一种快速的角点检测算法,它通过比较像素点与其周围邻域像素的灰度值来判断该点是否为角点 。具体来说,如果一个像素点的灰度值与它周围邻域内的 16 个像素点中的 12 个像素点的灰度值相差超过一定阈值,则该像素点被认为是一个角点 。FAST 算法检测速度快,但不具有方向和尺度信息 。
  • BRIEF 描述符计算:BRIEF 描述符是一种二进制描述符,它通过对关键点邻域内的像素对进行比较来生成一个二进制字符串 。例如,在关键点邻域内随机选择 N 对像素点,比较每对像素点的灰度值,如果第一个像素点的灰度值大于第二个像素点的灰度值,则对应位置的二进制位为 1,否则为 0 。这样就可以生成一个长度为 N 的二进制描述符 。BRIEF 描述符计算简单,匹配速度快,但不具有旋转不变性 。
  • ORB 改进:为了使 ORB 算法具有旋转不变性,ORB 算法为每个 FAST 角点分配一个方向 。它通过计算关键点邻域内的灰度质心来确定关键点的方向,然后根据这个方向对 BRIEF 描述符进行旋转,使其具有旋转不变性 。为了实现尺度不变性,ORB 算法通过构建图像金字塔来模拟不同尺度下的图像,在不同尺度的图像上检测 FAST 角点并计算描述符 。
2. 代码实现

在 OpenCV 中,使用cv2.ORB_create()函数创建 ORB 对象,然后调用其detectAndCompute()方法进行特征提取 。示例代码如下:

 

import cv2

import numpy as np

from matplotlib import pyplot as plt

# 读取灰度图像

image = cv2.imread('example.jpg', cv2.IMREAD_GRAYSCALE)

# 创建ORB对象

orb = cv2.ORB_create()

# 检测关键点并计算描述符

keypoints, descriptors = orb.detectAndCompute(image, None)

# 在图像上绘制关键点

image_with_keypoints = cv2.drawKeypoints(image, keypoints, None, color=(0, 255, 0), flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

# 显示图像

plt.imshow(cv2.cvtColor(image_with_keypoints, cv2.COLOR_BGR2RGB))

plt.title('ORB Keypoints')

plt.axis('off')

plt.show()

上述代码中,cv2.ORB_create()创建了 ORB 对象orb 。orb.detectAndCompute(image, None)在灰度图像image上检测关键点并计算描述符 。cv2.drawKeypoints()用于绘制关键点,最后通过matplotlib显示图像 。通过这段代码,我们可以看到 ORB 算法能够快速地检测出图像中的关键点,并绘制在图像上 。

(四)特征匹配

1. BF 匹配器

BF(Brute-Force)匹配器是一种简单直观的特征匹配方法,它通过计算两个特征描述符之间的距离(如欧氏距离、汉明距离等)来寻找最匹配的特征点对 。对于第一幅图像中的每个特征点,BF 匹配器会在第二幅图像的所有特征点中进行遍历,计算其与每个特征点的描述符距离,距离最小的特征点即为匹配点 。

在 OpenCV 中,使用cv2.BFMatcher()创建 BF 匹配器对象,然后使用其match()方法进行特征匹配 。示例代码如下:

 

import cv2

import numpy as np

from matplotlib import pyplot as plt

# 读取两幅灰度图像

img1 = cv2.imread('image1.jpg', cv2.IMREAD_GRAYSCALE)

img2 = cv2.imread('image2.jpg', cv2.IMREAD_GRAYSCALE)

# 创建ORB对象

orb = cv2.ORB_create()

# 检测关键点并计算描述符

kp1, des1 = orb.detectAndCompute(img1, None)

kp2, des2 = orb.detectAndCompute(img2, None)

# 创建BF匹配器

bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)

# 进行特征匹配

matches = bf.match(des1, des2)

# 根据距离对匹配结果进行排序

matches = sorted(matches, key=lambda x: x.distance)

# 绘制前10个匹配结果

img_matches = cv2.drawMatches(img1, kp1, img2, kp2, matches[:10], None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)

# 显示匹配结果

plt.imshow(cv2.cvtColor(img_matches, cv2.COLOR_BGR2RGB))

plt.title('BF Matcher Matches')

plt.axis('off')

plt.show()

在这段代码中,首先读取两幅灰度图像img1和img2,并使用 ORB 算法提取它们的关键点和描述符 。cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)创建了一个使用汉明距离进行匹配且开启交叉验证的 BF 匹配器 。crossCheck=True表示只有当第一幅图像中的特征点在第二幅图像中有匹配点,且第二幅图像中对应的特征点在第一幅图像中也有匹配点时,才将这对匹配点保留 。bf.match(des1, des2)进行特征匹配,返回匹配结果matches 。sorted(matches, key=lambda x: x.distance)根据匹配点的距离对匹配结果进行排序,距离越小表示匹配越准确 。最后,使用cv2.drawMatches()绘制前 10 个匹配结果,并通过matplotlib显示 。

2. FLANN 匹配器

FLANN(Fast Library for Approximate Nearest Neighbors)匹配器是一种快速的近似最近邻搜索库,它使用 KD 树(K-Dimensional tree)或其他数据结构来加速特征点的匹配过程,特别适用于大规模数据集的匹配 。相比于 BF 匹配器,FLANN 匹配器在处理大量特征点时能够显著提高匹配速度,但其匹配结果可能不是全局最优解,而是近似最优解 。

在 OpenCV 中,使用 FLANN 匹配器需要先定义索引参数和搜索参数,然后创建cv2.FlannBasedMatcher对象进行匹配 。示例代码如下:

 

import cv2

import numpy as np

from matplotlib import pyplot as plt

# 读取两幅灰度图像

img1 = cv2.imread('image1.jpg', cv2.IMREAD_GRAYSCALE)

img2 = cv2.imread('image2.jpg', cv2.IMREAD_GRAYSCALE)

# 创建ORB对象

orb = cv2.ORB_create()

# 检测关键点并计算描述符

kp1, des1 = orb.detectAndCompute(img1, None)

kp2, des2 = orb.detectAndCompute(img2, None)

# 定义FLANN匹配器的索引参数和搜索参数

index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)

search_params = dict(checks=50)

# 创建FLANN匹配器

flann = cv2.FlannBasedMatcher(index_params, search_params)

# 进行特征匹配

matches = flann.knnMatch(des1, des2, k=2)

# 应用比率测试筛选出好的匹配对

good_matches = []

for m, n in matches:

if m.distance < 0.7 * n.distance:

good_matches.append([m])

# 绘制前10个匹配结果

img_matches = cv2.drawMatchesKnn(img1, kp1, img2, kp2, good_matches[:10], None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)

# 显示匹配结果

plt.imshow(cv2.cvtColor(img_matches, cv2.COLOR_BGR2RGB))

plt.title('FLANN Matcher Matches')

plt.axis('off')

plt.show()

在上述代码中,index_params定义了索引参数,使用 KD 树作为索引结构,trees=5表示构建 5 棵 KD 树 。search_params定义了搜索参数,checks=50表示在 KD 树中进行 50 次搜索以找到近似最近邻 。flann.knnMatch(des1, des2, k=2)进行 K 近邻匹配,返回每个特征点的 2 个最近邻匹配点 。通过比率测试,筛选出距离比值小于 0.7 的匹配对作为好的匹配对 。最后,使用cv2.drawMatchesKnn()绘制前 10 个好的匹配对,并显示匹配结果 。

实时视频分析:开启动态视觉的魔法

实时视频分析是计算机视觉在动态场景下的应用拓展,它让计算机能够对连续的视频帧进行实时处理和理解,如同赋予机器一双 “动态的眼睛”,在安防监控、自动驾驶、智能交通、视频会议等众多领域都有着广泛且重要的应用。通过实时视频分析,可以实现目标的检测与跟踪、行为分析、事件预警等功能,为人们提供及时、准确的信息,帮助做出决策。接下来,我们将深入探讨如何使用 OpenCV 进行实时视频分析。

(一)实时视频捕获

在 OpenCV 中,使用cv2.VideoCapture类来捕获视频。它既可以从摄像头读取实时视频流,也可以读取本地视频文件。

  1. 从摄像头捕获视频:如果要从摄像头捕获视频,只需创建cv2.VideoCapture对象,并传入参数 0(表示默认摄像头)。示例代码如下:
 

import cv2

# 创建VideoCapture对象,打开默认摄像头

cap = cv2.VideoCapture(0)

# 检查摄像头是否成功打开

if not cap.isOpened():

print("无法打开摄像头")

exit()

while True:

# 读取一帧视频

ret, frame = cap.read()

# 如果读取失败,说明视频结束或摄像头出现问题

if not ret:

print("无法读取视频帧")

break

# 显示视频帧

cv2.imshow('Camera Video', frame)

# 按下'q'键退出循环

if cv2.waitKey(1) & 0xFF == ord('q'):

break

# 释放VideoCapture对象

cap.release()

cv2.destroyAllWindows()

在这段代码中,cap = cv2.VideoCapture(0)打开默认摄像头 。cap.read()读取一帧视频,ret表示是否成功读取,frame是读取到的视频帧 。cv2.imshow('Camera Video', frame)显示视频帧,cv2.waitKey(1) & 0xFF == ord('q')用于检测用户是否按下了 'q' 键,如果按下则退出循环 。最后,使用cap.release()释放摄像头资源,cv2.destroyAllWindows()关闭所有 OpenCV 窗口 。

  1. 读取视频文件:如果要读取本地视频文件,将视频文件的路径作为参数传入cv2.VideoCapture对象即可。示例代码如下:
 

import cv2

# 创建VideoCapture对象,读取视频文件

cap = cv2.VideoCapture('example.mp4')

# 检查视频文件是否成功打开

if not cap.isOpened():

print("无法打开视频文件")

exit()

while True:

# 读取一帧视频

ret, frame = cap.read()

# 如果读取失败,说明视频结束

if not ret:

print("视频结束")

break

# 显示视频帧

cv2.imshow('Video File', frame)

# 按下'q'键退出循环

if cv2.waitKey(25) & 0xFF == ord('q'):

break

# 释放VideoCapture对象

cap.release()

cv2.destroyAllWindows()

上述代码中,cap = cv2.VideoCapture('example.mp4')打开名为example.mp4的视频文件 。cv2.waitKey(25)表示每一帧显示 25 毫秒,这样可以按照视频的正常帧率播放视频 。其他部分与从摄像头捕获视频的代码类似 。

(二)视频帧处理

在捕获到视频帧后,就可以对每一帧进行各种图像处理和分析操作,这些操作与前面图像处理部分介绍的方法类似,只是这里是对连续的视频帧进行实时处理。

例如,对视频帧进行灰度转换、边缘检测、图像滤波等操作。以下是一个对视频帧进行灰度转换和 Canny 边缘检测的示例:

 

import cv2

# 创建VideoCapture对象,读取视频文件

cap = cv2.VideoCapture('example.mp4')

# 检查视频文件是否成功打开

if not cap.isOpened():

print("无法打开视频文件")

exit()

while True:

# 读取一帧视频

ret, frame = cap.read()

# 如果读取失败,说明视频结束

if not ret:

print("视频结束")

break

# 将视频帧转换为灰度图像

gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

# 对灰度图像进行Canny边缘检测

edges = cv2.Canny(gray_frame, 50, 150)

# 显示原始视频帧、灰度视频帧和边缘检测结果

cv2.imshow('Original Frame', frame)

cv2.imshow('Gray Frame', gray_frame)

cv2.imshow('Edges', edges)

# 按下'q'键退出循环

if cv2.waitKey(25) & 0xFF == ord('q'):

break

# 释放VideoCapture对象

cap.release()

cv2.destroyAllWindows()

在这个示例中,cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)将彩色视频帧frame转换为灰度图像gray_frame 。cv2.Canny(gray_frame, 50, 150)对灰度图像进行 Canny 边缘检测,得到边缘图像edges 。然后分别显示原始视频帧、灰度视频帧和边缘检测结果 。通过这样的方式,可以实时地对视频中的每一帧进行图像处理,为后续的目标检测、目标跟踪等任务提供基础 。

(三)目标检测与跟踪

1. Haar 级联分类器

Haar 级联分类器是一种基于 Haar 特征的目标检测方法,它在 OpenCV 中被广泛应用于人脸检测、车辆检测、行人检测等领域。Haar 级联分类器通过一系列的弱分类器组成的级联结构,能够快速地检测出图像中的目标物体。在使用 Haar 级联分类器进行目标检测时,需要先加载已经训练好的分类器模型文件(通常是 XML 格式),这些模型文件包含了目标物体的特征信息。

以下是使用 Haar 级联分类器进行人脸检测的代码示例:

 

import cv2

# 创建VideoCapture对象,读取视频文件

cap = cv2.VideoCapture('example.mp4')

# 加载人脸检测的Haar级联分类器

face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')

# 检查视频文件是否成功打开

if not cap.isOpened():

print("无法打开视频文件")

exit()

while True:

# 读取一帧视频

ret, frame = cap.read()

# 如果读取失败,说明视频结束

if not ret:

print("视频结束")

break

# 将视频帧转换为灰度图像

gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

# 进行人脸检测,scaleFactor表示每次图像尺寸减小的比例,minNeighbors表示构成检测目标的相邻矩形的最小个数

faces = face_cascade.detectMultiScale(gray_frame, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))

# 在检测到的人脸周围绘制矩形框

for (x, y, w, h) in faces:

cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)

# 显示带有人脸检测结果的视频帧

cv2.imshow('Face Detection', frame)

# 按下'q'键退出循环

if cv2.waitKey(25) & 0xFF == ord('q'):

break

# 释放VideoCapture对象

cap.release()

cv2.destroyAllWindows()

在上述代码中,cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')加载了用于人脸检测的 Haar 级联分类器 。face_cascade.detectMultiScale(gray_frame, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))在灰度视频帧gray_frame上进行人脸检测 。scaleFactor=1.1表示每次图像尺寸减小 10%,minNeighbors=5表示每个候选矩形至少要被检测到 5 次才被认为是真正的人脸,minSize=(30, 30)表示检测到的人脸的最小尺寸为 30x30 像素 。最后,使用cv2.rectangle()函数在检测到的人脸周围绘制矩形框,将结果显示出来 。

2. 基于特征的目标跟踪

基于特征的目标跟踪是通过在视频帧中提取目标物体的特征点,并在后续帧中根据特征点的匹配来跟踪目标物体的运动轨迹。常用的特征点提取算法如 SIFT、ORB 等,在前面特征提取部分已经介绍过。结合特征点匹配算法(如 BF 匹配器、FLANN 匹配器),可以实现目标的跟踪。

以下是一个简单的基于 ORB 特征点匹配的目标跟踪示例,假设我们要跟踪视频中的一个特定物体(这里以一本书为例):

 

import cv2

import numpy as np

# 创建VideoCapture对象,读取视频文件

cap = cv2.VideoCapture('book_video.mp4')

# 创建ORB对象

orb = cv2.ORB_create()

# 读取第一帧视频,并在其中选择要跟踪的目标区域(这里手动选择一个矩形区域作为目标)

ret, frame = cap.read()

if not ret:

print("无法读取视频文件")

exit()

x, y, w, h = cv2.selectROI('Select Target', frame, fromCenter=False, showCrosshair=True)

target_roi = frame[y:y + h, x:x + w]

kp1, des1 = orb.detectAndCompute(target_roi, None)

while True:

# 读取一帧视频

ret, frame = cap.read()

if not ret:

break

# 提取当前帧的ORB特征

kp2, des2 = orb.detectAndCompute(frame, None)

# 使用BF匹配器进行特征匹配

bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)

matches = bf.match(des1, des2)

# 根据匹配结果,计算目标物体在当前帧中的位置

if len(matches) > 0:

src_pts = np.float32([kp1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2)

dst_pts = np.float32([kp2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2)

M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)

if M is not None:

h, w = target_roi.shape[:2]

pts = np.float32([[0, 0], [0, h - 1], [w - 1, h - 1], [w - 1, 0]]).reshape(-1, 1, 2)

dst = cv2.perspectiveTransform(pts, M)

frame = cv2.polylines(frame, [np.int32(dst)], True, (0, 255, 0), 2, cv2.LINE_AA)

# 显示跟踪结果

cv2.imshow('Object Tracking', frame)

# 按下'q'键退出循环

if cv2.waitKey(25) & 0xFF == ord('q'):

break

# 释放VideoCapture对象

cap.release()

cv2.destroyAllWindows()

在这段代码中,首先读取视频的第一帧,并手动选择一个目标区域 。然后提取目标区域的 ORB 特征点和描述符 。在后续的每一帧中,提取当前帧的 ORB 特征,并使用 BF 匹配器与目标区域的特征进行匹配 。根据匹配结果,通过计算单应性矩阵(cv2.findHomography())来确定目标物体在当前帧中的位置,并使用cv2.polylines()函数在当前帧上绘制目标物体的轮廓,实现目标跟踪 。

(四)案例实战:智能安防监控系统

1. 系统需求分析

智能安防监控系统是实时视频分析的典型应用之一,其主要功能需求包括:

  • 运动检测:能够实时检测视频画面中的运动物体,区分背景和前景,当有物体在监控区域内移动时,及时发出警报。
  • 目标识别:对检测到的运动目标进行识别,判断其类别,如人员、车辆、动物等,并记录相关信息,如人员的面部特征、车辆的车牌号码等 。
  • 行为分析:分析目标物体的行为,判断是否存在异常行为,如人员的徘徊、奔跑、打架等行为,车辆的逆行、超速、违规停车等行为,一旦发现异常行为,立即触发警报 。
  • 视频存储与回放:将监控视频进行存储,以便后续查询和分析。同时,支持对存储的视频进行回放,方便用户查看历史监控记录 。
2. 系统设计与实现

下面使用 OpenCV 实现一个简单的智能安防监控系统,主要实现运动检测和目标识别(这里以人脸检测为例)功能。

 

import cv2

import numpy as np

# 创建VideoCapture对象,读取摄像头视频

cap = cv2.VideoCapture(0)

# 加载人脸检测的Haar级联分类器

face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')

# 创建背景减除器

fgbg = cv2.createBackgroundSubtractorMOG2()

while True:

# 读取一帧视频

ret, frame = cap.read()

if not ret:

break

# 进行背景减除,得到前景掩码

fgmask = fgbg.apply(frame)

# 对前景掩码进行形态学操作,去除噪声

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))

fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)

# 查找前景掩码中的轮廓

contours, hierarchy = cv2.findContours(fgmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 遍历轮廓,检测运动物体

for contour in contours:

if cv2.contourArea(contour) > 500: # 过滤掉面积较小的轮廓,认为是噪声

x, y, w, h = cv2.boundingRect(contour)

cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

# 在检测到的运动物体区域内进行人脸检测

roi_gray = cv2.cvtColor(frame[y:y + h, x:x + w], cv2.COLOR_BGR2GRAY)

faces = face_cascade.detectMultiScale(roi_gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))

for (fx, fy, fw, fh) in faces:

cv2.rectangle(frame[y:y + h, x:x + w], (fx, fy), (fx + fw, fy + fh), (255, 0, 0), 2)

# 显示监控画面

cv2.imshow('Security Monitoring', frame)

# 按下'q'键退出循环

if cv2.waitKey(1) & 0xFF == ord('q'):

break

# 释放VideoCapture对象

cap.release()

cv2.destroyAllWindows()

在上述代码中,cv2.createBackgroundSubtractorMOG2()创建了一个背景减除器,用于从视频帧中提取运动物体的前景 。cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)对前景掩码进行形态学开运算,去除噪声 。cv2.findContours(fgmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)查找前景掩码中的轮廓,通过判断轮廓面积过滤掉噪声,找到运动物体 。然后在运动物体区域内进行人脸检测,使用cv2.rectangle()函数绘制运动物体和人脸的矩形框 。

3. 效果展示与优化

运行上述代码,即可实现一个简单的智能安防监控系统,能够实时检测监控画面中的运动物体,并在运动物体区域内进行人脸检测。

然而,这个系统还有很多可以优化的地方:

  • 提高检测精度:可以使用更复杂的目标检测算法,如基于深度学习的目标检测模型(如 YOLO、Faster R-CNN 等),以提高目标识别的准确率和召回率 。
  • 优化性能:对于实时性要求较高的安防监控系统,需要优化代码性能,减少处理时间。可以采用多线程、GPU 加速等技术,提高视频处理速度 。例如,使用 OpenCV 的 CUDA 加速模块,将一些计算密集型的操作(如卷积运算、矩阵运算等)放到 GPU 上执行,以提高处理效率 。
  • 增加行为分析功能:进一步实现行为分析算法,如通过分析目标物体的轨迹、速度、停留时间等信息,判断其行为是否异常,丰富系统的功能 。可以使用卡尔曼滤波等算法对目标物体的运动轨迹进行预测和跟踪,结合机器学习

总结与展望:魔法的延续

OpenCV 作为计算机视觉领域的核心工具,在图像处理、特征提取和实时视频分析等方面展现出了强大的功能和广泛的应用潜力。通过本文的介绍和实践,我们深入了解了 OpenCV 在各个领域的基本原理、常用算法以及实际应用案例。

在图像处理方面,OpenCV 提供了丰富的函数和方法,能够完成从图像的基本读取、显示,到复杂的图像增强、滤波、边缘检测、轮廓提取等一系列操作。这些操作是计算机视觉的基础,为后续的特征提取和分析提供了高质量的图像数据。通过对图像的处理,我们可以改善图像的质量、突出图像中的关键信息,从而满足不同应用场景的需求 。

特征提取是计算机视觉中的关键环节,OpenCV 中的 SIFT、ORB 等算法能够有效地从图像中提取出具有代表性的特征点和描述符,这些特征在图像匹配、目标识别、图像检索等任务中发挥着重要作用。不同的特征提取算法具有各自的特点和优势,开发者可以根据具体的应用需求选择合适的算法 。例如,SIFT 算法具有良好的尺度不变性和旋转不变性,适用于对精度要求较高的场景;ORB 算法则计算速度快、内存占用小,更适合实时性要求较高的应用 。

实时视频分析将计算机视觉技术应用于动态场景,OpenCV 的VideoCapture类使得视频的捕获变得简单高效,结合图像处理和特征提取技术,能够实现目标检测与跟踪、行为分析等功能。在智能安防监控、自动驾驶、视频会议等领域,实时视频分析都有着不可或缺的作用 。通过实时分析视频流,我们可以及时获取关键信息,做出相应的决策,提高系统的智能化水平和安全性 。

展望未来,随着人工智能、大数据、物联网等技术的不断发展,计算机视觉领域将迎来更广阔的发展空间,OpenCV 也将持续演进和创新 。在算法层面,预计会有更高效、更准确的图像处理和特征提取算法出现,以应对日益复杂的应用场景和数据规模 。例如,深度学习与传统计算机视觉算法的融合将成为一个重要趋势,通过将深度学习的强大表示能力与 OpenCV 的传统算法相结合,可以进一步提升目标检测、图像分割等任务的性能 。在应用方面,OpenCV 将在更多领域得到深入应用,如医疗保健、智能家居、工业制造等 。在医疗保健领域,OpenCV 可以用于医学影像分析,辅助医生进行疾病诊断;在智能家居中,实现智能安防监控、智能家电控制等功能;在工业制造中,用于产品质量检测、机器人视觉引导等 。同时,随着硬件技术的不断进步,如 GPU、FPGA 等硬件设备的性能提升,OpenCV 的运行效率也将得到进一步提高,从而支持更复杂、更实时的计算机视觉应用 。

OpenCV 为计算机视觉的发展提供了坚实的基础和强大的动力,希望本文能够帮助读者更好地了解和应用 OpenCV,激发大家在计算机视觉领域的探索和创新热情 。相信在 OpenCV 的助力下,计算机视觉技术将在未来创造更多的可能性,为我们的生活带来更多的便利和惊喜 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

计算机学长

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

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

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

打赏作者

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

抵扣说明:

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

余额充值