100行代码搞定智能视频摘要!VideoMAEv2-Base实战指南

100行代码搞定智能视频摘要!VideoMAEv2-Base实战指南

【免费下载链接】VideoMAEv2-Base 【免费下载链接】VideoMAEv2-Base 项目地址: https://ai.gitcode.com/hf_mirrors/OpenGVLab/VideoMAEv2-Base

你还在为冗长视频的精华提取烦恼吗?还在为复杂的视频理解模型望而却步吗?本文将带你用仅仅100行代码,基于VideoMAEv2-Base模型构建一个高效的智能视频摘要生成器,让你轻松从海量视频中提取关键信息。

读完本文,你将能够:

  • 理解VideoMAEv2-Base模型的核心原理和架构
  • 掌握使用预训练模型进行视频特征提取的方法
  • 实现视频关键帧检测和摘要生成的完整流程
  • 优化视频处理性能,提升摘要生成效率

一、VideoMAEv2-Base模型深度解析

1.1 模型概述

VideoMAEv2-Base是由OpenGVLab开发的视频理解模型,基于掩码自编码器(Masked Autoencoder)架构,在大规模无标签视频数据集上进行预训练。该模型能够有效提取视频中的时空特征,为视频分类、动作识别、视频摘要等任务提供强大的特征支持。

mermaid

1.2 核心参数配置

根据配置文件分析,VideoMAEv2-Base模型的核心参数如下:

参数数值描述
img_size224输入视频帧的尺寸
patch_size16空间方向上的 patch 大小
tubelet_size2时间方向上的 tubelet 大小
in_chans3输入图像的通道数
embed_dim768嵌入维度
depth12Transformer 层数
num_heads12注意力头数
mlp_ratio4MLP 隐藏层维度比例
num_frames16输入视频的帧数
use_mean_poolingtrue是否使用均值池化
cos_attnfalse是否使用余弦注意力

1.3 模型架构特点

VideoMAEv2-Base采用了多种先进技术,使其在视频理解任务中表现出色:

  1. Tubelet Embedding:将视频序列分成时空立方体(tubelet),通过3D卷积将每个tubelet映射为嵌入向量。

  2. 位置编码:采用固定的正弦余弦位置编码,为模型提供时空位置信息。

  3. 多头自注意力:通过多个注意力头并行捕捉视频中的不同时空模式。

  4. 残差连接:每个Transformer块中使用残差连接,缓解深层网络训练困难问题。

  5. 均值池化:使用所有patch的均值进行特征聚合,提高特征鲁棒性。

二、环境准备与项目搭建

2.1 开发环境配置

要构建视频摘要生成器,我们需要以下依赖库:

# 克隆项目仓库
git clone https://gitcode.com/hf_mirrors/OpenGVLab/VideoMAEv2-Base
cd VideoMAEv2-Base

# 创建并激活虚拟环境
conda create -n videomae python=3.8 -y
conda activate videomae

# 安装依赖
pip install torch torchvision transformers opencv-python numpy scikit-learn matplotlib tqdm

2.2 项目结构设计

为了使项目结构清晰,便于维护和扩展,我们采用以下目录结构:

VideoMAEv2-Base/
├── README.md               # 项目说明文档
├── config.json             # 模型配置文件
├── model.safetensors       # 预训练模型权重
├── modeling_config.py      # 模型配置类
├── modeling_videomaev2.py  # 模型定义
├── preprocessor_config.json # 预处理配置
├── video_summarizer.py     # 视频摘要生成器主程序
├── utils/                  # 工具函数目录
│   ├── video_utils.py      # 视频处理工具
│   ├── feature_utils.py    # 特征处理工具
│   └── visualization.py    # 可视化工具
└── examples/               # 示例视频和结果
    ├── input_video.mp4     # 示例输入视频
    └── summary_result/     # 摘要结果

三、视频特征提取核心实现

3.1 视频预处理

在将视频输入模型之前,需要进行一系列预处理操作,包括帧提取、大小调整、归一化等。

import cv2
import numpy as np
import torch
from transformers import VideoMAEImageProcessor

class VideoPreprocessor:
    def __init__(self, model_name_or_path="./"):
        """初始化视频预处理工具"""
        self.processor = VideoMAEImageProcessor.from_pretrained(
            model_name_or_path, 
            do_resize=True,
            size=224,
            do_center_crop=True,
            do_normalize=True,
            image_mean=[0.485, 0.456, 0.406],
            image_std=[0.229, 0.224, 0.225]
        )
        
    def extract_frames(self, video_path, max_frames=128):
        """从视频中提取帧"""
        cap = cv2.VideoCapture(video_path)
        frames = []
        frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        fps = cap.get(cv2.CAP_PROP_FPS)
        
        # 均匀采样max_frames帧
        step = max(1, frame_count // max_frames)
        
        for i in range(0, frame_count, step):
            cap.set(cv2.CAP_PROP_POS_FRAMES, i)
            ret, frame = cap.read()
            if not ret:
                break
            # 转换BGR到RGB
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            frames.append(frame)
            if len(frames) >= max_frames:
                break
                
        cap.release()
        return np.array(frames), fps
    
    def preprocess(self, frames):
        """预处理视频帧,准备输入模型"""
        # 使用processor处理帧
        inputs = self.processor(list(frames), return_tensors="pt")
        
        # 调整维度顺序为 [B, C, T, H, W]
        inputs['pixel_values'] = inputs['pixel_values'].permute(0, 2, 1, 3, 4)
        
        return inputs

3.2 特征提取实现

使用VideoMAEv2-Base模型提取视频帧特征:

import torch
from transformers import AutoModel, AutoConfig

class FeatureExtractor:
    def __init__(self, model_name_or_path="./", device=None):
        """初始化特征提取器"""
        # 设置设备
        self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")
        
        # 加载模型配置
        self.config = AutoConfig.from_pretrained(
            model_name_or_path, 
            trust_remote_code=True
        )
        
        # 加载模型
        self.model = AutoModel.from_pretrained(
            model_name_or_path, 
            config=self.config, 
            trust_remote_code=True
        ).to(self.device)
        
        # 设置模型为评估模式
        self.model.eval()
        
    def extract_features(self, inputs, batch_size=4):
        """提取视频帧特征"""
        # 将输入移到设备
        inputs = {k: v.to(self.device) for k, v in inputs.items()}
        
        # 获取视频帧数
        num_frames = inputs['pixel_values'].shape[2]
        features = []
        
        # 批量处理帧,避免内存溢出
        with torch.no_grad():
            for i in range(0, num_frames, self.config.model_config.num_frames):
                end = min(i + self.config.model_config.num_frames, num_frames)
                
                # 如果不足16帧,复制最后一帧补齐
                if end - i < self.config.model_config.num_frames:
                    pad_length = self.config.model_config.num_frames - (end - i)
                    batch = torch.cat([
                        inputs['pixel_values'][:, :, i:end, :, :],
                        inputs['pixel_values'][:, :, end-1:end, :, :].repeat(1, 1, pad_length, 1, 1)
                    ], dim=2)
                else:
                    batch = inputs['pixel_values'][:, :, i:end, :, :]
                
                # 提取特征
                frame_features = self.model.extract_features(batch)
                features.append(frame_features.cpu())
        
        # 拼接所有帧的特征
        features = torch.cat(features, dim=0)
        return features

四、视频关键帧检测算法

4.1 帧相似性计算

为了找到视频中的关键帧,我们需要计算帧之间的相似度,识别视频内容的变化点:

import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

class FrameSimilarityCalculator:
    def __init__(self, method="cosine"):
        """初始化帧相似性计算器"""
        self.method = method
        
    def calculate_similarity(self, features):
        """计算帧特征之间的相似度"""
        if self.method == "cosine":
            # 计算余弦相似度
            similarity = cosine_similarity(features)
        elif self.method == "euclidean":
            # 计算欧氏距离并转换为相似度
            dist = np.sqrt(((features[:, np.newaxis] - features) ** 2).sum(axis=2))
            similarity = 1 / (1 + dist)  # 将距离转换为相似度(0-1)
        else:
            raise ValueError(f"不支持的相似度计算方法: {self.method}")
            
        return similarity
    
    def calculate_difference(self, features):
        """计算连续帧之间的差异度"""
        similarity = self.calculate_similarity(features)
        diffs = []
        
        for i in range(1, len(features)):
            diffs.append(1 - similarity[i-1, i])  # 1-相似度=差异度
            
        return np.array(diffs)

4.2 关键帧选择算法

基于帧间差异度,使用自适应阈值法选择关键帧:

import numpy as np
import matplotlib.pyplot as plt

class KeyframeSelector:
    def __init__(self, threshold_ratio=1.5, min_frames=5, max_frames=30):
        """初始化关键帧选择器"""
        self.threshold_ratio = threshold_ratio
        self.min_frames = min_frames
        self.max_frames = max_frames
        
    def select_keyframes(self, diffs, features):
        """基于帧差异度选择关键帧"""
        # 计算自适应阈值
        mean_diff = np.mean(diffs)
        std_diff = np.std(diffs)
        threshold = mean_diff + self.threshold_ratio * std_diff
        
        # 找到差异度超过阈值的帧
        keyframe_indices = [0]  # 总是保留第一帧
        
        for i, diff in enumerate(diffs):
            if diff > threshold:
                keyframe_indices.append(i+1)  # i+1是当前帧的索引
                
        # 确保关键帧数量在合理范围内
        if len(keyframe_indices) < self.min_frames:
            # 如果关键帧太少,按固定间隔选择
            interval = max(1, len(diffs) // self.min_frames)
            keyframe_indices = list(range(0, len(diffs)+1, interval))
        elif len(keyframe_indices) > self.max_frames:
            # 如果关键帧太多,聚类后选择中心帧
            keyframe_indices = self._cluster_keyframes(keyframe_indices, features)
            
        # 去重并排序
        keyframe_indices = sorted(list(set(keyframe_indices)))
        
        return keyframe_indices
    
    def _cluster_keyframes(self, indices, features, n_clusters=None):
        """使用聚类方法减少关键帧数量"""
        from sklearn.cluster import KMeans
        
        if n_clusters is None:
            n_clusters = min(self.max_frames, len(indices))
            
        # 如果需要聚类的帧少于等于目标数量,直接返回
        if len(indices) <= n_clusters:
            return indices
            
        # 提取关键帧特征
        keyframe_features = features[indices]
        
        # 聚类
        kmeans = KMeans(n_clusters=n_clusters, random_state=42)
        clusters = kmeans.fit_predict(keyframe_features)
        
        # 选择每个聚类中距离中心最近的帧
        selected_indices = []
        for cluster_id in range(n_clusters):
            cluster_indices = np.where(clusters == cluster_id)[0]
            cluster_features = keyframe_features[cluster_indices]
            
            # 计算每个帧到聚类中心的距离
            distances = np.linalg.norm(cluster_features - kmeans.cluster_centers_[cluster_id], axis=1)
            
            # 选择距离最近的帧
            selected_cluster_idx = cluster_indices[np.argmin(distances)]
            selected_indices.append(indices[selected_cluster_idx])
            
        return sorted(selected_indices)
        
    def plot_difference_curve(self, diffs, keyframe_indices, save_path=None):
        """绘制差异度曲线和关键帧位置"""
        plt.figure(figsize=(15, 5))
        plt.plot(diffs, label='Frame Difference')
        
        # 绘制阈值线
        mean_diff = np.mean(diffs)
        std_diff = np.std(diffs)
        threshold = mean_diff + self.threshold_ratio * std_diff
        plt.axhline(y=threshold, color='r', linestyle='--', label='Threshold')
        
        # 标记关键帧位置
        for idx in keyframe_indices[1:]:  # 跳过第一帧
            if idx-1 < len(diffs):  # 确保索引有效
                plt.axvline(x=idx-1, color='g', linestyle=':', alpha=0.5)
        
        plt.title('Frame Difference Curve with Keyframes')
        plt.xlabel('Frame Index')
        plt.ylabel('Difference')
        plt.legend()
        
        if save_path:
            plt.savefig(save_path)
            plt.close()
        else:
            plt.show()

五、完整视频摘要生成器实现

5.1 主程序实现

整合前面的各个组件,实现完整的视频摘要生成器:

import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm

class VideoSummarizer:
    def __init__(self, model_path="./", output_dir="summary_result", 
                 threshold_ratio=1.5, min_frames=5, max_frames=30):
        """初始化视频摘要生成器"""
        # 导入所需组件
        from video_preprocessor import VideoPreprocessor
        from feature_extractor import FeatureExtractor
        from frame_similarity import FrameSimilarityCalculator
        from keyframe_selector import KeyframeSelector
        
        # 创建输出目录
        self.output_dir = output_dir
        os.makedirs(output_dir, exist_ok=True)
        
        # 初始化各个组件
        self.preprocessor = VideoPreprocessor(model_path)
        self.feature_extractor = FeatureExtractor(model_path)
        self.similarity_calculator = FrameSimilarityCalculator()
        self.keyframe_selector = KeyframeSelector(
            threshold_ratio=threshold_ratio,
            min_frames=min_frames,
            max_frames=max_frames
        )
        
    def generate_summary(self, video_path, visualize=True, save_frames=True, save_video=True):
        """生成视频摘要"""
        print(f"Processing video: {video_path}")
        
        # 1. 提取视频帧
        frames, fps = self.preprocessor.extract_frames(video_path)
        print(f"Extracted {len(frames)} frames from video")
        
        # 2. 预处理帧
        inputs = self.preprocessor.preprocess(frames)
        
        # 3. 提取帧特征
        print("Extracting frame features...")
        features = self.feature_extractor.extract_features(inputs)
        features = features.squeeze(0).numpy()  # 转换为 numpy 数组
        
        # 4. 计算帧差异度
        print("Calculating frame differences...")
        diffs = self.similarity_calculator.calculate_difference(features)
        
        # 5. 选择关键帧
        print("Selecting keyframes...")
        keyframe_indices = self.keyframe_selector.select_keyframes(diffs, features)
        print(f"Selected {len(keyframe_indices)} keyframes")
        
        # 6. 可视化差异度曲线
        if visualize:
            self.keyframe_selector.plot_difference_curve(
                diffs, 
                keyframe_indices, 
                save_path=os.path.join(self.output_dir, "difference_curve.png")
            )
        
        # 7. 获取关键帧
        keyframes = [frames[i] for i in keyframe_indices]
        
        # 8. 保存关键帧图片
        if save_frames:
            self._save_keyframes(keyframes, keyframe_indices)
        
        # 9. 生成摘要视频
        summary_video_path = None
        if save_video:
            summary_video_path = self._generate_summary_video(
                keyframes, 
                keyframe_indices, 
                fps, 
                video_path
            )
        
        return {
            "keyframes": keyframes,
            "indices": keyframe_indices,
            "summary_video_path": summary_video_path,
            "features": features
        }
    
    def _save_keyframes(self, keyframes, indices):
        """保存关键帧图片"""
        frames_dir = os.path.join(self.output_dir, "keyframes")
        os.makedirs(frames_dir, exist_ok=True)
        
        for i, (frame, idx) in enumerate(zip(keyframes, indices)):
            # 将RGB转换回BGR用于OpenCV保存
            frame_bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
            frame_path = os.path.join(frames_dir, f"keyframe_{idx:04d}.jpg")
            cv2.imwrite(frame_path, frame_bgr)
        
        print(f"Saved keyframes to {frames_dir}")
    
    def _generate_summary_video(self, keyframes, indices, fps, original_video_path):
        """生成摘要视频"""
        # 获取原视频名称
        video_name = os.path.splitext(os.path.basename(original_video_path))[0]
        summary_video_path = os.path.join(self.output_dir, f"{video_name}_summary.mp4")
        
        # 获取关键帧尺寸
        height, width, _ = keyframes[0].shape
        
        # 创建视频写入器
        fourcc = cv2.VideoWriter_fourcc(*'mp4v')
        out = cv2.VideoWriter(
            summary_video_path, 
            fourcc, 
            fps/2,  # 摘要视频帧率减半
            (width, height)
        )
        
        # 写入关键帧,每个关键帧显示2秒
        for frame, idx in zip(keyframes, indices):
            # 添加帧索引文字
            frame_with_text = frame.copy()
            cv2.putText(
                frame_with_text, 
                f"Frame: {idx}", 
                (10, 30), 
                cv2.FONT_HERSHEY_SIMPLEX, 
                1, 
                (0, 255, 0), 
                2
            )
            
            # 将RGB转换回BGR
            frame_bgr = cv2.cvtColor(frame_with_text, cv2.COLOR_RGB2BGR)
            
            # 每个关键帧写入fps帧(显示1秒)
            for _ in range(int(fps)):
                out.write(frame_bgr)
        
        out.release()
        print(f"Generated summary video: {summary_video_path}")
        
        return summary_video_path

5.2 快速使用接口

为了方便用户快速使用视频摘要生成器,我们提供一个简单的接口:

def main(video_path, output_dir="summary_result", threshold_ratio=1.5):
    """视频摘要生成器主函数"""
    from video_summarizer import VideoSummarizer
    
    # 创建摘要生成器
    summarizer = VideoSummarizer(
        output_dir=output_dir,
        threshold_ratio=threshold_ratio
    )
    
    # 生成视频摘要
    result = summarizer.generate_summary(video_path)
    
    print("\nVideo summary generation complete!")
    print(f"Keyframes saved to: {os.path.join(output_dir, 'keyframes')}")
    if result["summary_video_path"]:
        print(f"Summary video saved to: {result['summary_video_path']}")
    
    return result

if __name__ == "__main__":
    import argparse
    
    # 解析命令行参数
    parser = argparse.ArgumentParser(description='Video Summarizer using VideoMAEv2-Base')
    parser.add_argument('video_path', type=str, help='Path to the input video file')
    parser.add_argument('--output_dir', type=str, default='summary_result', help='Directory to save the results')
    parser.add_argument('--threshold', type=float, default=1.5, help='Threshold ratio for keyframe selection')
    
    args = parser.parse_args()
    
    # 生成视频摘要
    main(args.video_path, args.output_dir, args.threshold)

六、性能优化与参数调优

6.1 性能优化策略

为了提高视频摘要生成器的运行效率,可以采用以下优化策略:

  1. 批量处理:同时处理多个视频帧,充分利用GPU并行计算能力。

  2. 特征缓存:对于同一视频的多次处理,缓存提取的特征,避免重复计算。

  3. 帧采样优化:根据视频内容动态调整采样频率,在内容变化快的部分增加采样密度。

  4. 模型量化:使用模型量化技术,减少模型大小和计算量。

def optimize_model_for_speed(model, device):
    """优化模型以提高推理速度"""
    # 1. 启用FP16精度
    model.half()
    
    # 2. 启用CUDA图优化(如果可用)
    if device.type == 'cuda' and hasattr(torch.cuda, 'CUDAGraph'):
        # 这里需要根据具体输入尺寸创建CUDA图
        pass
        
    # 3. 禁用梯度计算
    model.eval()
    
    return model

6.2 参数调优指南

不同类型的视频可能需要不同的参数设置才能获得最佳摘要效果:

视频类型threshold_ratiomin_framesmax_frames说明
访谈类1.2-1.55-815-20内容变化较慢,关键帧较少
动作电影1.5-2.010-1525-30内容变化快,关键帧较多
教学视频1.3-1.78-1220-25平衡内容覆盖和简洁性
监控视频1.8-2.515-2030-40需要捕捉更多细节变化

七、完整项目部署与使用

7.1 命令行使用

使用命令行接口快速生成视频摘要:

# 基本用法
python video_summarizer.py input_video.mp4

# 指定输出目录
python video_summarizer.py input_video.mp4 --output_dir my_summary

# 调整关键帧选择阈值
python video_summarizer.py input_video.mp4 --threshold 1.8

7.2 集成到应用程序

将视频摘要生成器集成到Python应用程序中:

from video_summarizer import VideoSummarizer

# 创建摘要生成器实例
summarizer = VideoSummarizer(
    output_dir="custom_output",
    threshold_ratio=1.6,
    min_frames=8,
    max_frames=25
)

# 处理多个视频
video_paths = ["video1.mp4", "video2.mp4", "video3.mp4"]
for video_path in video_paths:
    result = summarizer.generate_summary(video_path)
    print(f"Generated summary for {video_path} with {len(result['indices'])} keyframes")

7.3 Web服务部署

使用Flask将视频摘要生成器部署为Web服务:

from flask import Flask, request, jsonify, send_from_directory
import os
import uuid
from video_summarizer import VideoSummarizer

app = Flask(__name__)
UPLOAD_FOLDER = 'uploads'
SUMMARY_FOLDER = 'summaries'

# 创建必要的目录
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(SUMMARY_FOLDER, exist_ok=True)

# 初始化摘要生成器
summarizer = VideoSummarizer(
    output_dir=SUMMARY_FOLDER,
    threshold_ratio=1.5
)

@app.route('/summarize', methods=['POST'])
def summarize_video():
    # 检查是否有视频文件上传
    if 'video' not in request.files:
        return jsonify({"error": "No video file provided"}), 400
        
    video_file = request.files['video']
    
    # 保存上传的视频
    video_id = str(uuid.uuid4())
    video_path = os.path.join(UPLOAD_FOLDER, f"{video_id}.mp4")
    video_file.save(video_path)
    
    # 生成摘要
    result = summarizer.generate_summary(
        video_path,
        output_dir=os.path.join(SUMMARY_FOLDER, video_id)
    )
    
    # 返回结果
    return jsonify({
        "video_id": video_id,
        "num_keyframes": len(result["indices"]),
        "summary_video_path": result["summary_video_path"],
        "keyframes_dir": os.path.join(SUMMARY_FOLDER, video_id, "keyframes")
    })

@app.route('/summaries/<video_id>/keyframes/<filename>')
def get_keyframe(video_id, filename):
    return send_from_directory(
        os.path.join(SUMMARY_FOLDER, video_id, "keyframes"),
        filename
    )

@app.route('/summaries/<video_id>/video')
def get_summary_video(video_id):
    return send_from_directory(
        os.path.join(SUMMARY_FOLDER, video_id),
        f"{os.path.splitext(os.path.basename(request.args.get('original')))[0]}_summary.mp4"
    )

if __name__ == '__main__':
    app.run(debug=True)

八、总结与未来展望

8.1 项目回顾

本文基于VideoMAEv2-Base模型实现了一个高效的智能视频摘要生成器,主要工作包括:

  1. 深入分析了VideoMAEv2-Base模型的架构和核心参数
  2. 实现了视频帧提取和预处理模块
  3. 使用预训练模型提取视频帧特征
  4. 基于帧特征相似度计算实现关键帧检测
  5. 生成视频摘要并可视化结果

该视频摘要生成器具有以下特点:

  • 代码简洁高效,核心功能仅需约100行代码
  • 采用自适应阈值算法,适应不同类型视频
  • 支持关键帧提取和摘要视频生成
  • 可通过参数调整平衡摘要质量和长度

8.2 未来改进方向

未来可以从以下几个方面进一步改进视频摘要生成器:

  1. 多模态融合:结合音频特征,提高摘要质量,特别是对于演讲、音乐会等视频。

  2. 时序优化:考虑关键帧之间的时间关系,生成更连贯的视频摘要。

  3. 用户交互:允许用户调整关键帧选择结果,实现交互式摘要生成。

  4. 语义理解:引入目标检测和动作识别,基于语义内容生成摘要。

mermaid

8.3 使用建议与最佳实践

为了获得最佳的视频摘要效果,建议:

  1. 对于不同类型的视频,调整threshold_ratio参数
  2. 关键帧数量控制在15-25之间,既能概括视频内容,又不会过于冗长
  3. 对于重要视频,建议使用较低的threshold_ratio,获取更详细的摘要
  4. 可以将生成的关键帧按照时间顺序排列,形成视频内容的视觉概览

通过本文介绍的方法,你可以快速构建一个高效的视频摘要生成器,帮助用户从冗长的视频中快速获取关键信息,提高视频内容的利用效率。

如果你觉得这个项目有帮助,请点赞、收藏并关注我们,获取更多AI视频处理的实用教程!下期我们将介绍如何基于视频摘要实现智能视频检索系统,敬请期待!

【免费下载链接】VideoMAEv2-Base 【免费下载链接】VideoMAEv2-Base 项目地址: https://ai.gitcode.com/hf_mirrors/OpenGVLab/VideoMAEv2-Base

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

抵扣说明:

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

余额充值