虚拟数字人——NeRF实现实时对话数字人

news2024/11/26 4:20:09

前言 

1.这是一个能实时对话的虚拟数字人demo,使用的是NeRF(Neural Radiance Fields)训练方式可以看看我前面的博客。

2.文本转语音是用了VITS语音合成,项目git:https://github.com/jaywalnut310/vits .

3.语言模型是用了新开源的ChatGLM2-6B,当前的项目暂时没有加上这个接口。GitHub - THUDM/ChatGLM2-6B: ChatGLM2-6B: An Open Bilingual Chat LLM | 开源双语对话语言模型 )

4.声音克隆用的是PaddleSpeech,这个语音克隆训练起来很快,使用的数据集也相对少一些,当前的项目暂时没有加上语音克隆。

GitHub - PaddlePaddle/PaddleSpeech: Easy-to-use Speech Toolkit including Self-Supervised Learning model, SOTA/Streaming ASR with punctuation, Streaming TTS with text frontend, Speaker Verification System, End-to-End Speech Translation and Keyword Spotting. Won NAACL2022 Best Demo Award.Easy-to-use Speech Toolkit including Self-Supervised Learning model, SOTA/Streaming ASR with punctuation, Streaming TTS with text frontend, Speaker Verification System, End-to-End Speech Translation and Keyword Spotting. Won NAACL2022 Best Demo Award. - GitHub - PaddlePaddle/PaddleSpeech: Easy-to-use Speech Toolkit including Self-Supervised Learning model, SOTA/Streaming ASR with punctuation, Streaming TTS with text frontend, Speaker Verification System, End-to-End Speech Translation and Keyword Spotting. Won NAACL2022 Best Demo Award.https://github.com/PaddlePaddle/PaddleSpeech

5.当现实现的效果:

实时对话数字人

语音合成

1.VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)是一种结合变分推理(variational inference)、标准化流(normalizing flows)和对抗训练的高表现力语音合成模型。VITS通过隐变量而非频谱串联起来语音合成中的声学模型和声码器,在隐变量上进行随机建模并利用随机时长预测器,提高了合成语音的多样性,输入同样的文本,能够合成不同声调和韵律的语音。

2.声学模型是声音合成系统的重要组成部分:

 它使用预先训练好的语音编码器 (vocoder声码器) 将文本转化为语音。

3.VITS 的工作流程如下:

  • 将文本输入 VITS 系统,系统会将文本转化为发音规则。
  • 将发音规则输入预先训练好的语音编码器 (vocoder),vocoder 会根据发音规则生成语音信号的特征表示。
  • 将语音信号的特征表示输入预先训练好的语音合成模型,语音合成模型会根据特征表示生成合成语音。
  • VITS 的优点是生成的语音质量较高,能够生成流畅的语音。但是,VITS 的缺点是需要大量的训练语料来训练 vocoder 和语音合成模型,同时需要较复杂的训练流程。

4.把项目git下来后,我们试试用VITS做个语音合成,这里使用gradio来辅助创建个demo。

import os
from datetime import datetime
current_path = os.path.dirname(os.path.abspath(__file__))
os.environ["PATH"] = os.path.join(current_path, "ffmpeg/bin/") + ";" + os.environ["PATH"]
import torch
import commons
import utils
import re
from models import SynthesizerTrn
from text import text_to_sequence_with_hps as text_to_sequence
from scipy.io.wavfile import write
from pydub import AudioSegment
import gradio as gr

dir = "data/video/results/"

device = torch.device("cpu")  # cpu  mps
hps = utils.get_hparams_from_file("{}/configs/finetune_speaker.json".format(current_path))
hps.data.text_cleaners[0] = 'my_infer_ce_cleaners'
hps.data.n_speakers = 2
symbols = hps.symbols
net_g = SynthesizerTrn(
    len(symbols),
    hps.data.filter_length // 2 + 1,
    hps.train.segment_size // hps.data.hop_length,
    n_speakers=hps.data.n_speakers,
    **hps.model).to(device)
_ = net_g.eval()
#    G_latest  G_trilingual  G_930000  G_953000 G_984000 G_990000 G_1004000 G_1021000
# _ = utils.load_checkpoint("C:/code/vrh/models/G_1/G_1.pth", net_g, None)
_ = utils.load_checkpoint("C:/code/vrh/models/G_1/G_1.pth", net_g, None)


def add_laug_tag(text):
    '''
    添加语言标签
    '''
    pattern = r'([\u4e00-\u9fa5,。!?;:、——……()]+|[a-zA-Z,.:()]+|\d+)'
    segments = re.findall(pattern, text)
    for i in range(len(segments)):
        segment = segments[i]
        if re.match(r'^[\u4e00-\u9fa5,。!?;:、——……()]+$', segment):
            segments[i] = "[ZH]{}[ZH]".format(segment)
        elif re.match(r'^[a-zA-Z,.:()]+$', segment):
            segments[i] = "[EN]{}[EN]".format(segment)
        elif re.match(r'^\d+$', segment):
            segments[i] = "[ZH]{}[ZH]".format(segment)  # 数字视为中文
        else:
            segments[i] = "[JA]{}[JA]".format(segment)  # 日文

    return ''.join(segments)


def get_text(text, hps):
    text_cleaners = ['my_infer_ce_cleaners']
    text_norm = text_to_sequence(text, hps.symbols, text_cleaners)
    if hps.data.add_blank:
        text_norm = commons.intersperse(text_norm, 0)
    text_norm = torch.LongTensor(text_norm)
    return text_norm


def infer_one_audio(text, speaker_id=94, length_scale=1):
    '''
        input_type: 1输入自带语言标签  2中文  3中英混合
        length_scale: 语速,越小语速越快
    '''
    with torch.no_grad():
        stn_tst = get_text(text, hps)
        x_tst = stn_tst.to(device).unsqueeze(0)
        x_tst_lengths = torch.LongTensor([stn_tst.size(0)]).to(device)
        sid = torch.LongTensor([speaker_id]).to(device)  # speaker id
        audio = \
            net_g.infer(x_tst, x_tst_lengths, sid=sid, noise_scale=.667, noise_scale_w=0.8, length_scale=length_scale)[
                0][0, 0].data.cpu().float().numpy()
        return audio
    return None


def infer_one_wav(text, speaker_id, length_scale, wav_name):
    '''
        input_type: 1输入自带语言标签  2中文  3中英混合
        length_scale: 语速,越小语速越快
    '''
    audio = infer_one_audio(text, speaker_id, length_scale)
    write(wav_name, hps.data.sampling_rate, audio)
    print('task done!')

def add_slience(wav_path, slience_len=100):
    silence = AudioSegment.silent(duration=slience_len)
    wav_audio = AudioSegment.from_wav(wav_path)
    wav_audio = wav_audio + silence
    wav_audio.export(wav_path, format="wav")
    pass


# if __name__ == '__main__':
#     infer_one_wav(
#         '觉得本教程对你有帮助的话,记得一键三连哦!',
#         speaker_id=0,
#         length_scale=1.2)

def vits(text):
    now = datetime.now()
    timestamp = datetime.timestamp(now)
    file_name = str(timestamp%20).split('.')[0]
    audio_path = dir + file_name + ".wav"
    infer_one_wav(text,0,1.2,audio_path) #语音合成
    return audio_path
    
inputs = gr.Text()
outputs = gr.Audio(label="Output")

demo = gr.Interface(fn=vits, inputs=inputs, outputs=outputs)

demo.launch()

 合成视频

1.RAD-NeRF是可以对视频中所出现的说话者进行实时的人像合成。它是一种基于神经网络从一组二维图像重建三维体景。

RAD-NERF网络概述 

RAD-NeRF使用一个网络来预测正在可视化的相机视点的所有像素颜色和密度,当镜头围绕主题旋转时,想要显示的所有视点都是这样做的,这是非常需要计算力的,因为在每次学习预测图像中每个坐标的多个参数。此外,在这种情况下,这不仅仅是一个NeRF产生一个3D场景。还必须匹配音频输入,使嘴唇、嘴巴、眼睛和动作与人说的话相匹配。

网络不再预测所有像素的密度和颜色与特定帧的音频相匹配,而是使用两个独立的新压缩空间,称为网格空间,或基于网格的NeRF。再将坐标转换成较小的3D网格空间,将音频转换成较小的2D网格空间,然后将其发送到渲染头部。这意味着网络永远不会将音频数据与空间数据合并在一起,这将以指数方式增加大小,为每个坐标添加二维输入。因此,减少音频特征的大小,同时保持音频和空间特征的分离,将使这种方法更加有效。

但是,如果使用包含较少信息的压缩空间,结果如何会才能更好呢?在NeRF中添加一些可控制的特征,如眨眼控制,与以前的方法相比,模型将学习更真实的眼睛行为。这对能还原更加真实的人尤其重要。

 RAD-NeRF所做的第二个改进(模型概述中的绿色矩形)是使用相同的方法用另一个 NERF 建模躯干,而不是试图用用于头部的相同 NERF 建模躯干,这将需要更少的参数和不同的需求,因为这里的目标是动画移动的头部而不是整个身体。由于躯干在这些情况下是相当静态的,他们使用一个更简单和更有效的基于 NERF 的模块,只在2D 中工作,直接在图像空间中工作,而不是像平时通常使用 NERF 那样使用摄像机光线来产生许多不同的角度,这对躯干来说是不需要的。然后,重新组合头部与躯干,以产生最后的视频。

头像调整演示操作

2.当模型训练完之后,只需要data目录下的transforms_train.json文件和微调身体的后的模型文件就可以开始写推理代码了。步骤如下:

  • 输入语音或文字(这里为了方便演示,只写了文字输入的接口)
  • 获取输入的信息,调LLM(大型语文模型)来回答 (该项目当前还没有引入LLM,只写了几句固定的回答,之后有时间会把LLM与本地知识库加上)。
  • 对获取的回答进行语音合成,并生成用于驱动视频的.npy文件。
  • 使用.npy与transforms_train.json里面的数据合成视频,输出。
import gradio as gr
import base64
import time
import json
import gevent
from gevent import pywsgi
from geventwebsocket.handler import WebSocketHandler
from tools import audio_pre_process, video_pre_process, generate_video, audio_process
import os
import re
import numpy as np
import threading
import websocket
from pydub import AudioSegment
from moviepy.editor import VideoFileClip, AudioFileClip, concatenate_videoclips
import cv2
import pygame
from datetime import datetime

import os

dir = "data/video/results/"


audio_pre_process()
video_pre_process()
current_path = os.path.dirname(os.path.abspath(__file__))
os.environ["PATH"] = os.path.join(current_path, "ffmpeg/bin/") + ";" + os.environ["PATH"]
import torch
import commons
import utils
import re
from models import SynthesizerTrn
from text import text_to_sequence_with_hps as text_to_sequence
from scipy.io.wavfile import write

device = torch.device("cpu")  # cpu  mps
hps = utils.get_hparams_from_file("{}/configs/finetune_speaker.json".format(current_path))
hps.data.text_cleaners[0] = 'my_infer_ce_cleaners'
hps.data.n_speakers = 2
symbols = hps.symbols
net_g = SynthesizerTrn(
    len(symbols),
    hps.data.filter_length // 2 + 1,
    hps.train.segment_size // hps.data.hop_length,
    n_speakers=hps.data.n_speakers,
    **hps.model).to(device)
_ = net_g.eval()
#    G_latest  G_trilingual  G_930000  G_953000 G_984000 G_990000 G_1004000 G_1021000
_ = utils.load_checkpoint("C:/code/vrh/models/G_1/G_1.pth", net_g, None)


def add_laug_tag(text):
    '''
    添加语言标签
    '''
    pattern = r'([\u4e00-\u9fa5,。!?;:、——……()]+|[a-zA-Z,.:()]+|\d+)'
    segments = re.findall(pattern, text)
    for i in range(len(segments)):
        segment = segments[i]
        if re.match(r'^[\u4e00-\u9fa5,。!?;:、——……()]+$', segment):
            segments[i] = "[ZH]{}[ZH]".format(segment)
        elif re.match(r'^[a-zA-Z,.:()]+$', segment):
            segments[i] = "[EN]{}[EN]".format(segment)
        elif re.match(r'^\d+$', segment):
            segments[i] = "[ZH]{}[ZH]".format(segment)  # 数字视为中文
        else:
            segments[i] = "[JA]{}[JA]".format(segment)  # 日文

    return ''.join(segments)


def get_text(text, hps):
    text_cleaners = ['my_infer_ce_cleaners']
    text_norm = text_to_sequence(text, hps.symbols, text_cleaners)
    if hps.data.add_blank:
        text_norm = commons.intersperse(text_norm, 0)
    text_norm = torch.LongTensor(text_norm)
    return text_norm


def infer_one_audio(text, speaker_id=94, length_scale=1):
    '''
        input_type: 1输入自带语言标签  2中文  3中英混合
        length_scale: 语速,越小语速越快
    '''
    with torch.no_grad():
        stn_tst = get_text(text, hps)
        x_tst = stn_tst.to(device).unsqueeze(0)
        x_tst_lengths = torch.LongTensor([stn_tst.size(0)]).to(device)
        sid = torch.LongTensor([speaker_id]).to(device)  # speaker id
        audio = \
            net_g.infer(x_tst, x_tst_lengths, sid=sid, noise_scale=.667, noise_scale_w=0.8, length_scale=length_scale)[
                0][0, 0].data.cpu().float().numpy()
        return audio
    return None


def infer_one_wav(text, speaker_id, length_scale, wav_name):
    '''
        input_type: 1输入自带语言标签  2中文  3中英混合
        length_scale: 语速,越小语速越快
    '''
    audio = infer_one_audio(text, speaker_id, length_scale)
    write(wav_name, hps.data.sampling_rate, audio)
    print('task done!')


def add_slience(wav_path, slience_len=100):
    silence = AudioSegment.silent(duration=slience_len)
    wav_audio = AudioSegment.from_wav(wav_path)
    wav_audio = wav_audio + silence
    wav_audio.export(wav_path, format="wav")
    pass

def play_audio(audio_file):
    pygame.mixer.init()
    pygame.mixer.music.load(audio_file)
    pygame.mixer.music.play()
    while pygame.mixer.music.get_busy():
        pygame.time.Clock().tick(10)
    pygame.mixer.music.stop()

def answer(message, history):
    global dir
    history = history or []
    message = message.lower()
    if message=="你好":
        response = "你好,有什么可以帮到你吗?"

    elif message=="你是谁":
        response = "我是虚拟数字人幻静,你可以叫我小静或者静静。"

    elif message=="你能做什么":
        response = "我可以陪你聊天,回答你的问题,我还可以做很多很多事情!"

    else:
        response = "你的这个问题超出了我的理解范围,等我学习后再来回答你。"

    history.append((message, response))

    save_path = text2video(response,dir)
    
    return history,history,save_path

def text2video(text,dir):
    now = datetime.now()
    timestamp = datetime.timestamp(now)
    file_name = str(timestamp%20).split('.')[0]
    audio_path = dir + file_name + ".wav"
    infer_one_wav(text,0,1.1,audio_path) #语音合成 

    audio_process(audio_path)
    audio_path_eo = dir+file_name+"_eo.npy"

    save_path = generate_video(audio_path_eo, dir, file_name,audio_path)

    return save_path


with gr.Blocks(css="#chatbot{height:300px} .overflow-y-auto{height:500px}") as rxbot: 
    with gr.Row():
        video = gr.Video(label = "数字人",autoplay = True)
        with gr.Column():
            state = gr.State([])
            chatbot = gr.Chatbot(label = "消息记录").style(color_map=("green", "pink"))
            txt = gr.Textbox(show_label=False, placeholder="请输入你的问题").style(container=False)
    txt.submit(fn = answer, inputs = [txt, state], outputs = [chatbot, state,video])
    
rxbot.launch()

运行代码,然后打开http://127.0.0.1:7860/ ,然后输入文字就可得到回答合成的视频。

源码

1.当前的源码包含了语音合成与视频合成两个模型,环境依赖最难装的部分应该是pytorch3d,这个可以参考我之前的博客:

数字人解决方案——基于真人视频的三维重建数字人源码与训练方法_知来者逆的博客-CSDN博客

2.源码在win10,cuda 11.7,cudnn 8.5,python3.10,conda环境下测试运行成功。源码下载地址:

https://download.csdn.net/download/matt45m/88078575

下载源码后,创建conda环境:

cd vrh
#创建虚拟环境
conda create --name vrh python=3.10
activate vrh
#pytorch 要单独对应cuda进行安装,要不然训练时使用不了GPU
conda install pytorch==2.0.0 torchvision==0.15.0 torchaudio==2.0.0 pytorch-cuda=11.7 -c pytorch -c nvidia
conda install -c fvcore -c iopath -c conda-forge fvcore iopath
#安装所需要的依赖
pip install -r requirements.txt

windows下安装pytorch3d,这个依赖还是要在刚刚创建的conda环境里面进行安装。

git clone https://github.com/facebookresearch/pytorch3d.git
cd pytorch3d
python setup.py install

如果下载pytorch3d很慢,可以使用这个百度网盘下载:链接:https://pan.baidu.com/s/1z29IgyviQe2KQa6DilnRSA  提取码:dm4q 

如果安装中间报错退出,这里建议安装vs 生成工具。Microsoft C++ 生成工具 - Visual Studio​编辑https://visualstudio.microsoft.com/zh-hans/visual-cpp-build-tools/icon-default.png?t=N6B9https://visualstudio.microsoft.com/zh-hans/visual-cpp-build-tools/3.如果对该项目感兴趣或者在安装的过程中遇到什么错误的的可以加我的企鹅群:487350510,大家一起探讨。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/778553.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Jenkins从配置到实战(一) - 实现C/C++项目自动化编译

前言 本文章主要介绍了,如何去安装和部署Jenkins,并实现自动拉取项目代码,自动化编译流程。 网站 官网中文网站 下载安装 可以下载这个 安装jenkins前先安装java yum search java|grep jdkyum install java-1.8.0-openjdk 安装jenkins j…

NE555 PWM输出

NE555是一种集成电路(IC),通常用于电子电路的各种目的,包括计时器、振荡器等等。 本文介绍搭建NE555电路输出PWM信号,电路如图下: 使用该电路可以输出PWM占空比≥50%波形,仿真波形如下图&#…

20230723在win10的命令行下显示文本文件的内容type

20230723在win10的命令行下显示文本文件的内容type 2023/7/23 20:35 百度搜索:WINDOWS 命令行 打开文本文件 windows命令行读取文件命令-WinFrom控件库|.net开源控件库... 2023年7月14日 linux下,可能会用到cat或都是more命令,windows下可以使用type或more命令 type…

VMware Fusion 14 Tech Preview - 适用于 Arm 的 Windows 11 上的全面 3D 加速

VMware Fusion 14 Tech Preview - 适用于 Arm 的 Windows 11 上的全面 3D 加速 VMware Fusion Tech Preview 2023 请访问原文链接:https://sysin.org/blog/vmware-fusion-14/,查看最新版。原创作品,转载请保留出处。 作者主页:…

求解包含约束的最优化问题:罚函数法

文章目录 外点罚函数法内点罚函数法罚函数法 vs 拉格朗日乘子法 外点罚函数法 针对包含约束条件的最优化问题,此前介绍的拉格朗日乘子法和KKT条件已经提供一种有效的解决方案。但由于我是从智能优化算法入门运筹优化行业的,所以在遇到这类问题时&#x…

day35-Image Carousel(图片轮播图简易版)

50 天学习 50 个项目 - HTMLCSS and JavaScript day35-Image Carousel&#xff08;图片轮播图简易版&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport…

93、简述kafka架构设计

kafka架构设计 Consumer Group&#xff1a;消费者组&#xff0c;消费者组内每个消费者负责消费不同分区的数据&#xff0c;提高消费能力。逻辑上的一个订阅者。Topic: 可以理解为一个队列&#xff0c;Topic 将消息分类&#xff0c;生产者和消费者面向的是同一个 Topic。Partiti…

netty组件详解-中

接着之前的博客netty组件详解-上&#xff0c;我们继续深入到源码层面&#xff0c;来探究netty的各个组件和其设计思想&#xff1a; netty内置的通讯模式 我们在编写netty代码时&#xff0c;经常使用NioServerSocketChannel 作为通讯模式。 例如下面的简单netty客户端示例: pri…

Docker迁移默认的/var/lib/docker目录

安装完Docker后&#xff0c;默认存储路径在/var/lib/docker目录&#xff0c;如果服务器挂载的硬盘不是根目录的话&#xff0c;可能会造成资源不够用。这时候就需要迁移docker默认的目录。 1.停止docker服务 systemctl stop docker 复制 2.创建docker新目录 mkdir -p /data…

airtest-selenium 脚本爬取百度热搜标题

目录 1. 前言 2. 爬取标题的脚本 3. 命令行运行 Web 自动化脚本 1&#xff09;python 环境准备 2&#xff09;chrome 与 chromedriver 版本对应 3&#xff09;命令行运行 1. 前言 airtest-selenium是一个基于Python的UI自动化测试框架&#xff0c;它结合了airtest和sele…

【Redis】缓存问题小记

文章目录 1、缓存模型和思路1.1、缓存更新策略1.2、具体实现思路 2、缓存穿透问题2.1、方案分析2.2、缓存空对象实现思路2.3、小总结 3、缓存雪崩4、缓存击穿4.1、方案分析4.1.1、互斥锁4.1.2、逻辑过期4.1.3、方案对比 4.2、互斥锁实现思路4.3、逻辑过期实现思路 1、缓存模型和…

微服务——统一网关Getway

为什么需要网关&#xff1f; 网关的两种实现: 网关Getway——快速入门 步骤一 网关背身也是一个微服务&#xff0c;需要注册到nacos中去 步骤二 成功运行后 可以通过网关进行请求转发到对应服务。 流程如下&#xff1a; 路由断言工厂 网关路由可以配置的东西有如下。 spri…

RocketMQ分布式事务 -> 最终一致性实现

文章目录 前言事务消息场景代码示例订单服务事务日志表TransactionMQProducerOrderTransactionListener业务实现类调用总结 积分服务积分记录表消费者启动消费者监听器增加积分幂等性消费消费异常 前言 分布式事务的问题常在业务与面试中被提及, 近日摸鱼看到这篇文章, 阐述的…

Web前端开发概述(二)

&#x1f60a;Web前端开发概述&#xff08;二&#xff09; &#x1f47b;前言&#x1fa81;前端开发背景&#x1f50d;当下前端开发要求&#x1f526;Web前端开发技术&#x1f3ad;HTML&#x1f3ad;CSS&#x1f3ad;JavaScript&#x1f3ad;HTML DOM&#x1f3ad;BOM&#x1f…

Spring中AOP的通知类型和执行顺序

Spring中AOP的通知类型&#xff1a; Around&#xff1a;环绕通知&#xff0c;此注解标注的通知方法在目标方法前、后都被执行Before&#xff1a;前置通知&#xff0c;此注解标注的通知方法在目标方法前被执行After &#xff1a;后置通知&#xff0c;此注解标注的通知方法在目标…

Jmeter+Jenkins+Ant自动化持续集成环境搭建

一、安装准备 1.JDK:jdk-8u121-windows-x64 2.jmeter工具&#xff1a;apache-jmeter-2.13 3.ANT工具&#xff1a;apache-ant-1.9.7-bin 4.jenkins工具&#xff1a;jenkins-2.32.2 二、软件安装 1.JDK的安装 >双击JDK安装包&#xff0c;选择安装路径&#xff08;本人是…

论文笔记--ERNIE: Enhanced Language Representation with Informative Entities

论文笔记--ERNIE: Enhanced Language Representation with Informative Entities 1. 文章简介2. 文章概括3 文章重点技术3.1 模型框架3.2 K-Encoder(Knowledgeable Encoder)3.3 预训练任务3.4 微调 4. 文章亮点5. 原文传送门6. References 1. 文章简介 标题&#xff1a;ERNIE:…

每天五分钟计算机视觉:单卷积层的前向传播过程

什么是单卷积层? 一张图片(输入)经过多个卷积核卷积就会得到一个输出,而这多个卷积核的组合就是一个单卷积层。 这些卷积核可能大小是不一样的,但是他们接收同样大小是输入,他们的输出必须是一般大小,所以不同的卷积核需要具备不同的步长和填充值。 单层卷积网络前向传…

springboot+vue开发后台增删改查

效果图 前端代码【User.vue】 <template><div class"data-container"><!--添加 start--><div class"data-header"><el-button round click"addHander" size"large" type"primary"><el-ic…

Cesium态势标绘专题-普通点(标绘+编辑)

标绘专题介绍:态势标绘专题介绍_总要学点什么的博客-CSDN博客 入口文件:Cesium态势标绘专题-入口_总要学点什么的博客-CSDN博客 辅助文件:Cesium态势标绘专题-辅助文件_总要学点什么的博客-CSDN博客 本专题没有废话,只有代码,代码中涉及到的引入文件方法,从上面三个链…