C++调用Python和numpy第三方库计算MFCC音频特征实现封装发布

news2024/9/24 19:17:37

文章目录

  • 项目简介
  • 环境准备
  • 执行步骤
    • 1.新建python虚拟环境
    • 2.虚拟环境运行下python代码
    • 3.迁移虚拟环境
    • 4.编写Cmakelists.txt
    • 5.编写C++代码
    • 6.编译项目
    • 7.测试

项目简介

深度学习程序的边缘部署以性能绝佳的C++为主(⊙﹏⊙),但遇到项目开发周期短,则以功能优先,一些复杂的算法和处理用C++写怕不是得写到天荒地老,于是C++调用python以及第三方库的C端接口这样的方案就应运而生,牺牲一小部分性能,换来功能的完成,连准确性也顺便验证了(注意如果开发人员水平不够(ㄒoㄒ),用C++造轮子的性能还不如python)

本项目首先开发了一个python的类用于预处理wav音频文件来提取MFCC特征,得益于python_speech_features库其实几行代码就能解决,但为了后续的学习借鉴,本次开发较完善点,开发的多个接口对多种数据传递的情况做演示,然后用C++调用这些python接口并取回数据,经测试,每次调用接口会比纯python执行慢不到1毫秒,最终打包后的项目放到无任何开发环境的虚拟机做测试,这其中的波折和踩坑真的只有做过的才懂┭┮﹏┭┮

梅尔频率倒谱系数(MFCC)通过对音频信号的处理和分析,提取出反映语音特征的信息,广泛应用于语音识别、语音合成、说话人识别等领域。可以简单的理解为将一个音频文件转为了矩阵,该矩阵保存了音频特征。

在这里插入图片描述# 程序/数据集下载

点击进入下载地址

本文章只发布于博客园爆米算法,被抄袭后可能排版错乱或下载失效,作者:爆米LiuChen

环境准备

python3.8(虚拟环境或主环境均可)、VS2019(已支持cmake)、什么都没装的win虚拟机(用于测试)
整个项目的文件结构如下

在这里插入图片描述

执行步骤

1.新建python虚拟环境

anaconda的命令是【conda create -n 环境名 python=3.8】,然后pip安装下numpy、scipy,python_speech这几个包

在这里插入图片描述

2.虚拟环境运行下python代码

AudioPreprocess.py代码如下,主要实现了AudioPreprocess这个类,作用是将wav文件先采样成numpy矩阵,然后提取MFCC特征
from python_speech_features import mfcc
import scipy.io.wavfile
from numpy.typing import NDArray
from typing import Tuple
import numpy as np

def yell():
    print('''Congratulations,you import 【AudioPreprocess】 successfully!!!''')


class AudioPreprocess():
    def __init__(self,numcep:int=13,keepSecs:int=8):
        '''
        预处理类
        :param numcep: MFCC特征数(通道数)
        :param keepSecs: 一个wav文件读取后保留的秒数,不够则补0
        '''
        self.numcep = numcep
        self.keepSecs = keepSecs

    def readWave(self,wavePath:str)->Tuple[int,NDArray[np.int16]]:
        '''
        读取一个wave文件
        :param wavePath: wav文件路径
        :return: 采样率,采样
        '''
        samplerate, samples = scipy.io.wavfile.read(wavePath)
        return samplerate, samples

    def samples2MFCC(self,samplerate:int, samples:NDArray[np.int16])->NDArray[np.float32]:
        '''
        一个wav的采样转MFCC特征
        :param samplerate: 采样率
        :param samples: 采样
        :return: MFCC特征 size=(channel,feature)
        '''
        samples = samples if len(samples.shape) <= 1 else samples[:, 0]
        samples = samples[:int(self.keepSecs*samplerate)]
        samples = np.pad(samples, pad_width=(0, int(samplerate * self.keepSecs) - samples.shape[0]), mode='constant',constant_values=(0, 0))
        mfccFeature = mfcc(samples, samplerate=samplerate,numcep=self.numcep)
        mfccFeature = np.transpose(mfccFeature,axes=(1,0))
        return mfccFeature

    def wave2MFCC(self,wavePath:str)->NDArray[np.float64]:
        '''
        wav路径转MFCC
        :param wavePath: wav文件路径
        :return: MFCC特征 size=(channel,feature)
        '''
        samplerate, samples = self.readWave(wavePath)
        mfccFeature = self.samples2MFCC(samplerate, samples)
        return mfccFeature

if __name__ == "__main__":
    import time
    path = "test.wav"
    audioPreprocess = AudioPreprocess()
    samplerate, samples = audioPreprocess.readWave(path)
    t1 = time.time()
    for i in range(100):
        mfccFeature = audioPreprocess.wave2MFCC(path)
    t2 = time.time()
    print((t2-t1)*1000)

3.迁移虚拟环境

可以将整个虚拟环境都转移到项目中,这样最稳,但文件也最多,我是主要复制了下面几个文件和文件夹,并删除了Lib/site-packages里一些用不到的库,结果还是得要250多M,numpy和scipy这俩库太大了...其实可以尝试一个个的删除,只要留下的文件能支撑你的项目就行,但我这边就懒得这么做了

在这里插入图片描述

4.编写Cmakelists.txt

因为需要调用python解释器,并且也用到了numpy的C接口,所以要额外编写下这俩的配置,需要的文件都在我们的虚拟环境中
cmake_minimum_required (VERSION 3.8)
project ("AudioPrepocess")
SET(CMAKE_BUILD_TYPE "Release")#Debug或Release模式
set(CMAKE_CXX_STANDARD 11)
add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")

#项目文件路径配置
set(CMAKE_BINARY_DIR "${CMAKE_SOURCE_DIR}/build")#项目源码构建路径
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/bin")#存放可执行软件的目录;
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib")#默认存放项目生成的静态库的文件夹位置;
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib")#默认存放项目生成的动态库的文件夹位置;
include_directories(include)#头文件目录
aux_source_directory(source SRC_FILES)#源文件目录的所有文件
 
#调用python的设置
set(PYTHON_DIR "${CMAKE_SOURCE_DIR}/python38/env")
include_directories("${PYTHON_DIR}/include")#头文件目录
link_libraries("${PYTHON_DIR}/libs/python38.lib")
#调用numpy的设置
include_directories("${CMAKE_SOURCE_DIR}/python38/env/Lib/site-packages/numpy/core/include/numpy")#头文件目录
link_libraries("${CMAKE_SOURCE_DIR}/python38/env/Lib/site-packages/numpy/core/lib/npymath.lib")
#移动一些python的依赖
file(COPY "${CMAKE_SOURCE_DIR}/python38" DESTINATION "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
file(RENAME "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/python38/env/python38.dll" "${CMAKE_SOURCE_DIR}/bin/python38.dll")

add_executable(${PROJECT_NAME} main.cpp ${SRC_FILES} "source/AudioPreprocess.cpp")#构建可执行文件

5.编写C++代码

include/AudioPreprocess.h如下,声明一个对应python的AudioPreprocess类,成员函数也一致(可以不用这么对应,单纯写个函数去调执行py脚本里的AudioPreprocess类接口就行)反正最后是调用python代码,要不要对应不重要,但这个博客主要是演示的全面一点,注释也写得全一点
#include <chrono>
#include <vector>
#include "Python.h"
#include "arrayobject.h"

long long getCurrentTimeMS();//获得当前时间戳 单位毫秒

int* initNumpy();//初始化numpy会有返回值 不能直接放在类的构造函数中,所以拿个形式函数包裹下

//包裹readWave的返回值
struct ResReadWave {
	int sampleRate;
	PyArrayObject* samples;
};

//调用python进行音频预处理类 可选择是否标准化数据 但需要传入标准化文件路径 
class AudioPreprocess
{
public:
	/// @brief 初始化python 初始化模块和导入的python类
	/// @param scalerPath 标准化文件路径
	/// @param numcep MFCC特征数(通道数)
	/// @param keepSecs 一个wav文件读取后保留的秒数,不够则补0
	AudioPreprocess(int numcep=13, int keepSecs=8);
	
	/// @brief 读取wav文件,返回采样率和采样
	/// @param wavePath 
	ResReadWave readWave(char* wavePath);

	/// @brief 采样转MFCC特征 返回MFCC特征
	/// @param samplerates 
	/// @param samples 
	/// @return MFCC特征 二维数组
	PyArrayObject *samples2MFCC(int samplerates, PyArrayObject* samples);

	/// @brief 读取wav文件,返回MFCC特征
	/// @param wavePath 
	/// @return MFCC特征 二维数组
	PyArrayObject* wave2MFCC(char* wavePath);

	~AudioPreprocess();

private:
	PyObject* pyModule;
	PyObject* pyFunc;
	PyObject* pyArgs;
	PyObject* pyClass;
	PyObject* pyClassObj;
	//python预处理类中对应的函数、参数、返回值
	PyObject* pyFuncReadWave;
	PyObject* pyArgsReadWave;
	PyObject* pyResReadWave;

	PyObject* pyFuncSamples2MFCC;
	PyObject* pyArgsSamples2MFCC;
	PyObject* pyResSamples2MFCC;

	PyObject* pyFuncWave2MFCC;
	PyObject* pyArgsWave2MFCC;
	PyObject* pyResWave2MFCC;

	int numcep;
	int keepSecs;
};
source/AudioPreprocess.cpp如下,实现C++和python互传一些基本类型以及numpy这种稍微复杂点的矩阵,注意python初始化的执行顺序,还有最好手动释放那些python对象,还有注意numpy的数据精度类型,不对齐是不会报错的

可以看出C++其实实例化了一个python解释器,然后在解释器里执行python代码,等于在python外套了一层,因此不管怎样都不可能比python还快,这种方式适合需要实现复杂算法且开发时间短的场景,毕竟谁愿意去看MFCC的公式呢...
#include "AudioPreprocess.h"


int* initNumpy() {
    import_array();
}

long long getCurrentTimeMS() {
    auto now = std::chrono::system_clock::now(); // 获取当前时间点
    auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now); // 转换为毫秒
    auto epoch = now_ms.time_since_epoch(); // 计算自纪元以来的毫秒数
    return epoch.count(); // 返回毫秒数
}

AudioPreprocess::AudioPreprocess(int numcep, int keepSecs):numcep(numcep), keepSecs(keepSecs){
    //初始化python解释器
    Py_SetPythonHome(L"python38/env");
    Py_Initialize();
    initNumpy();//初始化numpy,必须紧跟在python解释器初始化后面
    PyRun_SimpleString("import sys;sys.path.append('./python38')");
    this->pyModule = PyImport_ImportModule("AudioPreprocess");
    this->pyFunc = PyObject_GetAttrString(this->pyModule, "yell");//yell这个函数的作用只是确认导入成功 顺便示范下怎么调用python函数
    PyEval_CallObject(this->pyFunc, nullptr);
    //实例化python的音频处理类
    this->pyClass = PyObject_GetAttrString(this->pyModule, "AudioPreprocess");//获取AudioPreprocess这个类
    this->pyArgs = Py_BuildValue("(i,i)", numcep, keepSecs);
    this->pyClassObj = PyEval_CallObject(this->pyClass,this->pyArgs);
    //初始化指针对应python的音频处理类成员函数、参数、返回值
    this->pyFuncReadWave = PyObject_GetAttrString(this->pyClassObj, "readWave");
    this->pyArgsReadWave = PyTuple_New(1);
    this->pyResReadWave = PyTuple_New(2);

    this->pyFuncSamples2MFCC = PyObject_GetAttrString(this->pyClassObj, "samples2MFCC");
    this->pyArgsSamples2MFCC = PyTuple_New(2);
    this->pyResSamples2MFCC = PyTuple_New(1);

    this->pyFuncWave2MFCC = PyObject_GetAttrString(this->pyClassObj, "wave2MFCC");
    this->pyArgsWave2MFCC = PyTuple_New(1);
    this->pyResWave2MFCC = PyTuple_New(1);
}

ResReadWave AudioPreprocess::readWave(char* wavePath) {
    //传入路径
    PyTuple_SetItem(this->pyArgsReadWave,0,Py_BuildValue("s",wavePath));
    this->pyResReadWave = PyEval_CallObject(this->pyFuncReadWave, this->pyArgsReadWave);
    //返回值1 采样率
    int sampleRate;
    PyArg_Parse(PyTuple_GetItem(this->pyResReadWave, 0),"i", &sampleRate);
    //返回值2 采样 numpy int16一维数组
    PyArrayObject*  samples = (PyArrayObject*)PyArray_FROM_OTF(PyTuple_GetItem(this->pyResReadWave, 1), NPY_INT16, NPY_IN_ARRAY);
    ResReadWave result = {sampleRate,samples};
    //打印下值,验证准确性 python输出的值为58
    npy_intp indices[1] = {0};  // [0]的位置
    int16_t value = *(int16_t*)PyArray_GetPtr(result.samples, indices);
    printf("python输出数组[0,0]    :58\nC++&python输出数组[0,0]:%d\n\n",value);
    return result;
}

PyArrayObject* AudioPreprocess::samples2MFCC(int sampleRate, PyArrayObject* samples) {
    //传入 采样率 采样二维数组
    PyTuple_SetItem(this->pyArgsSamples2MFCC, 0, Py_BuildValue("i", sampleRate));
    PyTuple_SetItem(this->pyArgsSamples2MFCC, 1, (PyObject*)samples);
    this->pyResSamples2MFCC = PyEval_CallObject(this->pyFuncSamples2MFCC, this->pyArgsSamples2MFCC);
    //返回值 采样二维数组
    PyArrayObject* mfccFeature = (PyArrayObject*)PyArray_FROM_OTF(this->pyResSamples2MFCC, NPY_FLOAT64, NPY_IN_ARRAY);
    //打印下值,验证准确性 python输出的值为11.31785676885986
    npy_intp indices[2] = {0,0};  // [0,0]的位置
    double_t value = *(double_t*)PyArray_GetPtr(mfccFeature, indices);
    printf("python输出数组[0,0]    :11.31785676885986\nC++&python输出数组[0,0]:%.14f\n",value);
    return mfccFeature;
}

PyArrayObject* AudioPreprocess::wave2MFCC(char* wavePath) {
    //传入路径
    PyTuple_SetItem(this->pyArgsWave2MFCC, 0, Py_BuildValue("s", wavePath));
    this->pyResWave2MFCC = PyEval_CallObject(this->pyFuncWave2MFCC, this->pyArgsWave2MFCC);
    //返回值 采样二维数组
    PyArrayObject* mfccFeature = (PyArrayObject*)PyArray_FROM_OTF(this->pyResWave2MFCC, NPY_FLOAT64, NPY_IN_ARRAY);
    return mfccFeature;
}

AudioPreprocess::~AudioPreprocess() {
    Py_CLEAR(pyModule);
    Py_CLEAR(pyFunc);
    Py_CLEAR(pyArgs);
    Py_CLEAR(pyClass);
    Py_CLEAR(pyClassObj);
    Py_CLEAR(pyFuncReadWave);
    Py_CLEAR(pyArgsReadWave);
    Py_CLEAR(pyResReadWave);
    Py_CLEAR(pyFuncSamples2MFCC);
    Py_CLEAR(pyArgsSamples2MFCC);
    Py_CLEAR(pyResSamples2MFCC);
    Py_CLEAR(pyFuncWave2MFCC);
    Py_CLEAR(pyArgsWave2MFCC);
    Py_CLEAR(pyResWave2MFCC);
    Py_Finalize();
}
main.cpp如下,验证下上文实现的方法,并于python做下对比验证,精度不一致问题是深度学习大忌,还有看看性能损失有多少,顺便做一下多线程实验,python内部的GIL锁会导致C++多线程崩溃,必须手动给python加锁
#include <iostream>
#include "AudioPreprocess.h"
#include <thread>
#include <mutex>

AudioPreprocess AP(13, 8);//初始化音频处理类 理论上只需要简单实现wave2MFCC函数,但我对应python的类都实现了,就当练习

void wave2MFCC_thread(char* wavePath) {
	
	PyGILState_STATE state = PyGILState_Ensure();
	AP.wave2MFCC("./python38/test.wav");
	PyGILState_Release(state);
	
}

void main() {
	ResReadWave resReadWave;//存储采样率和采用
	PyArrayObject* mfccFeature;//存储MFCC特征
	
	//resReadWave.samples只能在类内访问 不明原因 可能是因为python解释器在那个类中初始化的,可以想办法在类内转成C++ vector数组再访问
	resReadWave = AP.readWave("./python38/test.wav");
	mfccFeature = AP.samples2MFCC(resReadWave.sampleRate, resReadWave.samples);
	mfccFeature = AP.wave2MFCC("./python38/test.wav");
	//运行100次,计算时间 ,对比纯python的时间
	long long t1 = getCurrentTimeMS();
	for (int i = 1; i <= 100; ++i) {
		mfccFeature = AP.wave2MFCC("./python38/test.wav");
	}
	long long t2 = getCurrentTimeMS();
	printf("\npython运行100次函数时间    :930 ms\nC++&python运行100次函数时间:%d ms\n",t2-t1);

	//多线程实验 如果没处理好 C++多线程会使python解释器崩溃
	printf("\n多线程实验");
	printf("\n多线程初始化:%d", PyEval_ThreadsInitialized());
	printf("\n全局解释器锁GIL:%d\n", PyGILState_Check());
	//PyEval_InitThreads();//开启多线程支持 3.8这个版本已经不需要手动调用这行代码来开启多线程支持
	Py_BEGIN_ALLOW_THREADS;//暂时释放全局解释器锁GIL
	char* wavePath = "./python38/test.wav";
	std::thread t1(wave2MFCC_thread, wavePath);
	std::thread t2(wave2MFCC_thread, wavePath);

	t1.join();
	t2.join();
	Py_END_ALLOW_THREADS;//重新获取全局解释器锁
						 
	//Python的对象最好都自己手动销毁
	Py_CLEAR(resReadWave.samples);
	Py_CLEAR(mfccFeature);
	system("pause");
}

6.编译项目

如果有安装上文的文件结构放置,那cmake会将可执行文件和虚拟环境以及测试文件放入bin目录下,并将虚拟环境的python38.dll移动到exe文件同目录,但附件中不会有bin目录,bin是编译后生成的很占空间,如下图

在这里插入图片描述

7.测试

将bin目录扔到虚拟机,模拟一个没有开发环境的客户端,运行exe文件,可以看到运行结果验证和对比,执行100次函数延迟了200ms,算得出做1次调用会比python慢2ms,不过这个可以接受

在这里插入图片描述

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

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

相关文章

五、Centos7-安装Jenkins--吃灰去吧

克隆了一个base的虚拟机&#xff0c;用来安装Jenkins 2023年11月&#xff0c;Jenkins不支持centos7了。我们只是学习用&#xff0c;先看看吧。 &#xff08; 另一个人用别的操作系统安装的jenkins&#xff0c;可以参考 版权声明&#xff1a;本文为博主原创文章&#xff0c;…

mq可靠性

为了解决阻塞可以采用数据持久化 交换机持久化-可以在配置的时候配置durable 队列持久化-mq在设置时默认就是持久化&#xff0c;spring默认也是持久化 消息持久化&#xff0c;不是默认&#xff0c;需要在发送时对delivery_mode改为2&#xff08;持久&#xff09;&#xff0c;默…

系统架构师(每日一练23)

每日一练 1.软件活动主要包括软件描述、()、软件有效性验证和()&#xff0c;()定义了软件功能及使用限制。答案与解析 问题1 A.软件模型 B.软件需求 C.软件分析 D.软件开发 问题2 A.软件分析 B.软件测试 C.软件演化 D.软件开发 问题3 A.软件分析 B.软件测试 C.软件描述 D.软…

事件监听查看、监听器删除方法

前言 最近在开发过程中遇上了不知在哪加入的点击事件&#xff0c;导致页面跳转发生问题&#xff0c;需要找到该点击事件并将其取消掉。以下就是在完成该目标过程中使用、尝试的方法。 1、事件查看 使用网页开发者工具&#xff08;F12&#xff09;选取想查看的元素找到工具中…

03_React 收集表单数据和 组件生命周期

React 收集表单数据和 组件生命周期 一、收集表单数据1、例子1.1 需求&#xff1a;定义一个包含表单的组件&#xff0c;输入用户名密码后&#xff0c;点击登录提示输入信息 2、理解&#xff1a;包含表单的组件分类2.1 受控组件2.2 非受控组件 二、高阶函数\_函数柯里化1、复习-…

MATLAB 手动实现点云投影滤波器 (76)

点云投影到邻近的精确拟合平面,减少噪声点,此为投影滤波器 MATLAB 手动实现点云投影滤波器(76) 一、投影滤波器简介二、实现步骤二、算法实现1.代码2.效果这里用到的投影方法和平面拟合方法以及生成平面方法都在以往文章有所实现,有兴趣可参考: MATLAB点云处理总目录 一…

Paimon Flink本地读取数据报错

1.idea本地读取paimon 用idea在本地读取paimon的表时需要添加的依赖除了官网提出的和hadoop相关的&#xff0c;paimon-flink之类相关的除外还需要其他额外依赖 import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.tabl…

大模型从入门到精通——词向量及知识库介绍

词向量及知识库介绍 1.词向量 1.1 什么是词向量 词向量是一种将单词表示为实数向量的方式。每个单词通过一个高维向量来表示&#xff0c;向量的每一维都是一个实数&#xff0c;这些向量通常位于一个高维空间中。词向量的目标是将语义相似的单词映射到相邻的向量空间中&#…

【STM32 HAL】多串口printf重定向

【STM32 HAL】多串口printf重定向 前言单串口printf重定向原理实现CubeMX配置Keil5配置 多串口printf重定向 前言 在近期项目中&#xff0c;作者需要 STM32 同时向上位机和手机发送数据&#xff0c;传统的 printf 重定向只能输出到一个串口。本文介绍如何实现 printf 同时输出…

回归预测|基于北方苍鹰优化混合核极限学习机的数据预测Matlab程序NGO-HKELM 多特征输入单输出

回归预测|基于北方苍鹰优化混合核极限学习机的数据预测Matlab程序NGO-HKELM 多特征输入单输出 文章目录 前言回归预测|基于北方苍鹰优化混合核极限学习机的数据预测Matlab程序NGO-HKELM 多特征输入单输出 一、NGO-HKELM 模型1. NGO&#xff08;北方苍鹰优化算法&#xff09;2. …

DRF——pagination分页模块

文章目录 分页继承APIView类用法1.PageNumberPagination2.LimitOffsetPagination3.CursorPagination 继承GenericAPIView派生类用法1.PageNumberPagination2.LimitOffsetPagination3.CursorPagination 分页 在查看数据列表的API中&#xff0c;如果 数据量 比较大&#xff0c;肯…

RSA非对称性加密02: 加密redis的连接密码(下)

全文目录,一步到位 1.前言简介1.1 专栏传送门1.1.2 上文传送门 2. 使用方式2.1 使用druid自带的RSA加密工具2.1.1 引入druid依赖2.1.2 原yml配置(对比使用)2.1.2 新yml配置 2.2 springboot的redis配置类2.2.1 例如在RedisConfig中2.2.2 设置序列化与反序列化代码示例如下: 2.3 …

初始redis:Zset有序集合

Set作为集合&#xff0c;有两个特点&#xff1a;唯一且无序。 Zset是有序集合&#xff0c;在保证唯一的情况下&#xff0c;是根据什么来排序的呢&#xff1f;排序的规则是什么&#xff1f; Zset中的member引入了一个属性&#xff0c;分数&#xff08;score&#xff09;&#…

写SCI能用上的AI论文写作工具!码住!

01 Quillbot 提供改写、语法检查、抄袭检测、摘要生成、引文生成等功能。它可以在各种喜欢的网站上使用&#xff0c;帮助用户轻松提高写作效率和质量。 02梅子AI论文 1智能AI论文神器-查重率10%左右-参考文献致谢模板查重报告一键打包 无限免费生成千字论文大纲-在线快速生成…

智慧园区可视化:智能管理与高效运营的未来

图扑 GIS 智慧园区可视化系统整合地理信息和数据分析&#xff0c;实现全方位智能管理&#xff0c;优化资源调度&#xff0c;提高运营效率&#xff0c;保障园区安全和用户满意度。

数据结构【链试结构二叉树】

&#x1f31f;个人主页&#xff1a;落叶 目录 ​编辑 实现链式结构⼆叉树 前中后序遍历&#xff1a; 遍历规则 代码实现 前序遍历&#xff1a; 中序遍历&#xff1a; 后序遍历&#xff1a; 图解遍历&#xff1a; 函数递归栈帧图&#xff1a; 结点个数以及高度等 【⼆…

【生日视频制作】教师节中秋节国庆节奔驰大G汽车车身AE模板修改文字软件生成器教程特效素材【AE模板】

奔驰大G汽车身生日视频制作教程AE模板修改字软件生成器素材 怎么如何做的【生日视频制作】教师节中秋节国庆节奔驰大G汽车车身AE模板修改文字软件生成器教程特效素材【AE模板】 生日视频制作步骤&#xff1a; 安装AE软件下载AE模板把AE模板导入AE软件修改图片或文字渲染出视频…

iOS工程:获取手机相册权限,iOS原生系统弹窗, Privacy隐私政策选择,如何添加系统弹出并修改描述文字

【iOS工程】获取手机相册权限&#xff0c;iOS原生系统弹窗, Privacy隐私政策选择&#xff0c;如何添加系统弹出并修改描述文字 设备/引擎&#xff1a;Mac&#xff08;11.6&#xff09;/Mac Mini 开发工具&#xff1a;Xcode&#xff08;15.0.1&#xff09; 开发需求&#xff…

maven Tomcat插件安装 运行web项目 (3)

1.maven工程目录结构 main是用来写我们程序 test是用来测试 java是源程序 resource是配置 maven项目构建命令 2.配置maven 创建Maven工程 这里的项目名称可以随便取 进来后改成自己想要的jdk

天津市2024年成人高考报名须知

&#x1f6a7;天津市2024年成人高考报名须知 &#x1f32b;2024年天津市成人高校招生考试报名。符合天津市报名条件的在职从人员和社会其他人员&#xff0c;可报名参加全国各类成人高等学校招生统一考试。考生须在规定时间完成报名&#xff0c;逾期不再补报。 &#x1f32b;网上…