【opencv】dnn示例-speech_recognition.cpp 使用DNN模块结合音频信号处理技术实现的英文语音识别...

模型下载地址:

https://drive.google.com/drive/folders/1wLtxyao4ItAg8tt4Sb63zt6qXzhcQoR6

终端输出:(audio6.mp3 、audio10.mp3)

[ERROR:0@0.002] global cap_ffmpeg_impl.hpp:1112 open VIDEOIO/FFMPEG: unsupported parameters in .open(), see logger INFO channel for details. Bailout
an american instead of going in a leisure hour to dance merrilyat some place of public resort as the fellows of his calling continue to do throughout the greater part of europe shutshimself up at home to drink
she opened the door softly there sat missus wilson in the old 
rocking chair with one sick death like boy lying on her knee 
crying without let or pause but softly gently as fearing to 
disturb the troubled gasping child while behind her old alicelet her fast dropping tears fall down on the dead body of the other twin which she was laying out on a board placed on a sort of sofa settee in the corner of the room

源码解析:

#include <opencv2/core.hpp> // 包含OpenCV的核心功能头文件
#include <opencv2/videoio.hpp> // 包含OpenCV用于视频IO操作的功能头文件
#include <opencv2/highgui.hpp> // 包含OpenCV用于GUI操作和读/写图片的功能头文件
#include <opencv2/imgproc.hpp> // 包含OpenCV图片处理的功能头文件
#include <opencv2/dnn.hpp> // 包含OpenCV深度神经网络(DNN)模块的功能头文件
#include <iostream> // 包含标准输入输出流头文件
#include <vector> // 包含标准模板库中的动态数组(vector)相关的头文件
#include <string> // 包含C++字符串相关的头文件
#include <unordered_map> // 包含标准模板库中的哈希表相关的头文件
#include <cmath> // 包含数学函数相关的头文件
#include <random> // 包含随机数生成器相关的头文件
#include <numeric> // 包含数值算法相关的头文件
using namespace cv; // 使用命名空间cv,减少代码中的cv::前缀
using namespace std; // 使用命名空间std,减少代码中的std::前缀// 下面是关于FilterbankFeatures类的定义
// 这个类用于初始化声音处理参数,根据Jasper架构的默认值进行初始化。详情可参考论文:https://arxiv.org/abs/1904.03288
class FilterbankFeatures {private:int sample_rate = 16000; // 采样率double window_size = 0.02; // 窗口大小(以秒为单位)double window_stride = 0.01; // 窗口滑动距离(以秒为单位)int win_length = static_cast<int>(sample_rate * window_size); // 窗口长度(采样点数)int hop_length = static_cast<int>(sample_rate * window_stride); // 帧移(采样点数)int n_fft = 512; // 短时傅里叶变换窗口大小// 以下是计算滤波器组参数int n_filt = 64; // 滤波器个数double lowfreq = 0.; // 最低频率double highfreq = sample_rate / 2; // 最高频率,由奈奎斯特频率所限制public:// Mel滤波器组的准备工作double hz_to_mel(double frequencies)
{// 将频率从赫兹转换为梅尔频率尺度// 填充线性刻度部分double f_min = 0.0; // 最小频率double f_sp = 200.0 / 3; // 频率到梅尔尺度的线性转换系数double mels = (frequencies - f_min) / f_sp; // 线性转换结果// 填充对数刻度部分double min_log_hz = 1000.0; // 对数尺度部分的起始赫兹值double min_log_mel = (min_log_hz - f_min) / f_sp; // 起始赫兹值对应的梅尔值double logstep = std::log(6.4) / 27.0; // 对数尺度区间的步长if (frequencies >= min_log_hz){// 如果频率值在对数尺度区间,则进行对数尺度的转换mels = min_log_mel + std::log(frequencies / min_log_hz) / logstep;}return mels; // 返回转换后的梅尔值}vector<double> mel_to_hz(vector<double>& mels){// 将梅尔尺度转换回赫兹尺度// 填充线性刻度部分double f_min = 0.0; // 最小频率double f_sp = 200.0 / 3; // 梅尔尺度到频率的线性转换系数vector<double> freqs; // 存储转换结果的频率向量for (size_t i = 0; i < mels.size(); i++){// 对于每个梅尔值,转换回对应的频率值,并添加到向量中freqs.push_back(f_min + f_sp * mels[i]);}// 处理非线性刻度部分double min_log_hz = 1000.0; // 对数尺度部分的起始赫兹值double min_log_mel = (min_log_hz - f_min) / f_sp; // 起始赫兹值对应的梅尔值double logstep = std::log(6.4) / 27.0; // 对数尺度区间的步长for(size_t i = 0; i < mels.size(); i++){// 对梅尔值在对数尺度区间的部分进行赫兹尺度的转换if (mels[i] >= min_log_mel){freqs[i] = min_log_hz * exp(logstep * (mels[i] - min_log_mel));}}return freqs; // 返回所有转换后的频率值}vector<double> mel_frequencies(int n_mels, double fmin, double fmax){// 计算两个频率之间n个梅尔频率值double min_mel = hz_to_mel(fmin); // 将最小频率转换为梅尔尺度double max_mel = hz_to_mel(fmax); // 将最大频率转换为梅尔尺度vector<double> mels; // 存储梅尔尺度值的向量double step = (max_mel - min_mel) / (n_mels - 1); // 梅尔尺度的步长for(double i = min_mel; i < max_mel; i += step){// 从最小梅尔尺度开始,按照步长逐步增加,直到最大梅尔尺度mels.push_back(i);}mels.push_back(max_mel); // 包含最大梅尔尺度vector<double> res = mel_to_hz(mels); // 将梅尔尺度转换回赫兹尺度return res; // 返回转换后的频率值}vector<vector<double>> mel(int n_mels, double fmin, double fmax){// 生成梅尔滤波器组矩阵double num = 1 + n_fft / 2; // FFT的一半点数vector<vector<double>> weights(n_mels, vector<double>(static_cast<int>(num), 0.)); // 初始化n_mels行,每行有num个元素的二维向量数组weights// 每个FFT bin的中心频率vector<double> fftfreqs;double step = (sample_rate / 2) / (num - 1); // 每个FFT bin的频率间隔for(double i = 0; i <= sample_rate / 2; i += step){fftfreqs.push_back(i); // 计算并填充fftfreqs向量}// 梅尔带的中心频率 - 在限定范围内均匀分布vector<double> mel_f = mel_frequencies(n_mels + 2, fmin, fmax); // 计算梅尔频率vector<double> fdiff; // 用于存放相邻梅尔频率之间的差值for(size_t i = 1; i < mel_f.size(); ++i){fdiff.push_back(mel_f[i]- mel_f[i - 1]); // 计算差值并添加到fdiff向量}vector<vector<double>> ramps(mel_f.size(), vector<double>(fftfreqs.size()));for (size_t i = 0; i < mel_f.size(); ++i){for (size_t j = 0; j < fftfreqs.size(); ++j){ramps[i][j] = mel_f[i] - fftfreqs[j]; // 计算梅尔频率和FFT频率之间的 "斜率"}}double lower, upper, enorm; // 初始化变量,用于计算滤波器的能量归一化因子for (int i = 0; i < n_mels; ++i){// 使用Slaney式的梅尔滤波器,使每个频道的能量大致保持一致enorm = 2./(mel_f[i + 2] - mel_f[i]);for (int j = 0; j < static_cast<int>(num); ++j){// 为所有的bins计算上下限斜率lower = (-1) * ramps[i][j] / fdiff[i];upper = ramps[i + 2][j] / fdiff[i + 1];// 比较上下限斜率并取较小的一个,然后乘以能量因子enorm,得到权重weights[i][j] = max(0., min(lower, upper)) * enorm;}}return weights; // 返回计算出的梅尔滤波器组矩阵}vector<double> pad_window_center(vector<double>& data, int size){// 将窗口填充至n_fft大小的长度int n = static_cast<int>(data.size()); // 原始数据大小int lpad = static_cast<int>((size - n) / 2); // 左侧填充的长度vector<double> pad_array; // 构建用于填充的数组for(int i = 0; i < lpad; ++i){pad_array.push_back(0.); // 在窗口左侧填充0}for(size_t i = 0; i < data.size(); ++i){pad_array.push_back(data[i]); // 添加原始数据}for(int i = 0; i < lpad; ++i){pad_array.push_back(0.); // 在窗口右侧填充0}return pad_array; // 返回填充后的窗口数据}vector<vector<double>> frame(vector<double>& x){// 将数据数组切割成重叠的帧int n_frames = static_cast<int>(1 + (x.size() - n_fft) / hop_length); // 计算帧的数量vector<vector<double>> new_x(n_fft, vector<double>(n_frames)); // 初始化帧的二维数组for (int i = 0; i < n_fft; ++i){for (int j = 0; j < n_frames; ++j){new_x[i][j] = x[i + j * hop_length]; // 从原始数据抽取每一帧}}return new_x; // 返回分割出的所有帧}vector<double> hanning(){// 实现汉宁窗函数,详情访问:https://en.wikipedia.org/wiki/Window_function#Hann_and_Hamming_windowsvector<double> window_tensor; // 初始化窗函数tensorfor (int j = 1 - win_length; j < win_length; j+=2){// 计算汉宁窗的值,对于窗口内每一个点,根据汉宁窗公式进行计算window_tensor.push_back(1 - (0.5 * (1 - cos(CV_PI * j / (win_length - 1)))));}return window_tensor; // 返回计算出的汉宁窗}vector<vector<double>> stft_power(vector<double>& y){// 短时傅里叶变换(STFT)。STFT通过在短时重叠窗口上计算离散傅里叶变换(DFT)将信号表示在时频域上。// 填充时间序列以使帧居中vector<double> new_y;int num = int(n_fft / 2);// 在序列前后进行对称填充,以保证DFT时窗口能够居中for (int i = 0; i < num; ++i){new_y.push_back(y[num - i]);}for (size_t i = 0; i < y.size(); ++i){new_y.push_back(y[i]);}for (size_t i = y.size() - 2; i >= y.size() - num - 1; --i){new_y.push_back(y[i]);}// 计算窗函数vector<double> window_tensor = hanning();// 将窗函数长度填充至n_fft大小vector<double> fft_window = pad_window_center(window_tensor, n_fft);// 对时间序列进行窗函数处理vector<vector<double>> y_frames = frame(new_y);// 应用窗函数for (size_t i = 0; i < y_frames.size(); ++i){for (size_t j = 0; j < y_frames[0].size(); ++j){y_frames[i][j] *= fft_window[i];}}// 转置帧以进行STFT计算vector<vector<double>> y_frames_transpose(y_frames[0].size(), vector<double>(y_frames.size()));for (size_t i = 0; i < y_frames[0].size(); ++i){for (size_t j = 0; j < y_frames.size(); ++j){y_frames_transpose[i][j] = y_frames[j][i];}}// 执行短时傅里叶变换并获取谱的功率vector<vector<double>> spectrum_power(y_frames_transpose[0].size() / 2 + 1 );for (size_t i = 0; i < y_frames_transpose.size(); ++i){Mat dstMat;dft(y_frames_transpose[i], dstMat, DFT_COMPLEX_OUTPUT);// 只需要频谱的前半部分,因为第二部分是对称的for (int j = 0; j < static_cast<int>(y_frames_transpose[0].size()) / 2 + 1; ++j){double power_re = dstMat.at<double>(2 * j) * dstMat.at<double>(2 * j);double power_im = dstMat.at<double>(2 * j + 1) * dstMat.at<double>(2 * j + 1);spectrum_power[j].push_back(power_re + power_im);}}return spectrum_power;}Mat calculate_features(vector<double>& x)
{// 计算滤波器组特征矩阵// 执行预加重处理std::default_random_engine generator;std::normal_distribution<double> normal_distr(0, 1);double dither = 1e-5;for(size_t i = 0; i < x.size(); ++i){x[i] += dither * static_cast<double>(normal_distr(generator));}double preemph = 0.97;for (size_t i =  x.size() - 1; i > 0; --i){x[i] -= preemph * x[i-1];}// 计算短时傅里叶变换并获取谱的功率auto spectrum_power = stft_power(x);vector<vector<double>> filterbanks = mel(n_filt, lowfreq, highfreq);// 计算滤波器矩阵和谱的功率矩阵的乘积的对数vector<vector<double>> x_stft(filterbanks.size(), vector<double>(spectrum_power[0].size(), 0));for (size_t i = 0; i < filterbanks.size(); ++i){for (size_t j = 0; j < filterbanks[0].size(); ++j){for (size_t k = 0; k < spectrum_power[0].size(); ++k){x_stft[i][k] += filterbanks[i][j] * spectrum_power[j][k];}}for (size_t k = 0; k < spectrum_power[0].size(); ++k){x_stft[i][k] = std::log(x_stft[i][k] + 1e-20);}}// 标准化数据auto elments_num = x_stft[0].size();for(size_t i = 0; i < x_stft.size(); ++i){double x_mean = std::accumulate(x_stft[i].begin(), x_stft[i].end(), 0.) / elments_num; // 计算算术平均值double x_std = 0; // 标准差for(size_t j = 0; j < elments_num; ++j){double subtract = x_stft[i][j] - x_mean;x_std += subtract * subtract;}x_std /= elments_num;x_std = sqrt(x_std) + 1e-10; // 确保x_std不为零for(size_t j = 0; j < elments_num; ++j){x_stft[i][j] = (x_stft[i][j] - x_mean) / x_std; // 计算标准分数}}// 将计算好的特征矩阵转换为OpenCV的Mat类型Mat calculate_features(static_cast<int>(x_stft.size()), static_cast<int>(x_stft[0].size()), CV_32F);for(int i = 0; i < calculate_features.size[0]; ++i){for(int j = 0; j < calculate_features.size[1]; ++j){calculate_features.at<float>(i, j) = static_cast<float>(x_stft[i][j]);}}return calculate_features;}
};class Decoder {// 用于解码jasper模型的输出
private:unordered_map<int, char> labels_map = fillMap(); // 将索引映射到字符的哈希表int blank_id = 28; // 特殊的空白符号标识符public:unordered_map<int, char> fillMap(){// 填充索引到字符的映射vector<char> labels={' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','\''};unordered_map<int, char> map;for(int i = 0; i < static_cast<int>(labels.size()); ++i){map[i] = labels[i]; // 将索引与字符关联}return map;}string decode(Mat& x)
{// 接收jasper模型的输出,并执行CTC解码算法来移除重复和特殊符号,返回预测vector<int> prediction;for(int i = 0; i < x.size[1]; ++i){double maxEl = -1e10;int ind = 0;for(int j = 0; j < x.size[2]; ++j){if (maxEl <= x.at<float>(0, i, j)){maxEl = x.at<float>(0, i, j); // 找到最大概率的标签ind = j; // 记录索引}}prediction.push_back(ind); // 将索引添加到预测列表}// CTC解码过程vector<int> decoded_prediction = {};int previous = blank_id; // 初始化前一个字符索引为blank_idfor(int i = 0; i < static_cast<int>(prediction.size()); ++i){// 移除重复字符和blank_idif ((prediction[i] != previous || previous == blank_id) && prediction[i] != blank_id){decoded_prediction.push_back(prediction[i]); // 将索引添加到解码预测列表}previous = prediction[i]; // 更新前一个字符索引}string hypotheses = {}; // 初始化假设字符串for(size_t i = 0; i < decoded_prediction.size(); ++i){auto it = labels_map.find(decoded_prediction[i]); // 从映射查找字符if (it != labels_map.end())hypotheses.push_back(it->second); // 如果找到,添加到假设字符串}return hypotheses; // 返回解码的结果}};static string predict(Mat& features, dnn::Net net, Decoder decoder)
{// 通过Jasper模型传递特征,并解码输出为英语语音转录// 将2D特征矩阵展开成3Dvector<int> sizes = {1, static_cast<int>(features.size[0]), static_cast<int>(features.size[1])};features = features.reshape(0, sizes);// 进行预测net.setInput(features);Mat output = net.forward(); // 获取网络的输出// 解码输出为语音转录auto prediction = decoder.decode(output);return prediction;
}static int readAudioFile(vector<double>& inputAudio, string file, int audioStream)
{// 读取音频文件并返回采样率VideoCapture cap;int samplingRate = 16000; // 定义采样率vector<int> params {    CAP_PROP_AUDIO_STREAM, audioStream,CAP_PROP_VIDEO_STREAM, -1,CAP_PROP_AUDIO_DATA_DEPTH, CV_32F,CAP_PROP_AUDIO_SAMPLES_PER_SECOND, samplingRate};cap.open(file, CAP_ANY, params); // 打开文件并设置参数if (!cap.isOpened()){cerr << "Error : Can't read audio file: '" << file << "' with audioStream = " << audioStream << endl;return -1; // 如果文件打开失败,返回错误代码-1}const int audioBaseIndex = (int)cap.get(CAP_PROP_AUDIO_BASE_INDEX);vector<double> frameVec;Mat frame;for (;;){if (cap.grab()){cap.retrieve(frame, audioBaseIndex);frameVec = frame; // 从视频捕获对象中取得音频帧inputAudio.insert(inputAudio.end(), frameVec.begin(), frameVec.end()); // 将音频帧加入到audio向量中}else{break; // 如果没有数据,退出循环}}return samplingRate; // 返回采样率
}
static int readAudioMicrophone(vector<double>& inputAudio, int microTime)
{// 从麦克风读取音频数据到指定时长VideoCapture cap;int samplingRate = 16000; // 定义采样率为16kHzvector<int> params {    CAP_PROP_AUDIO_STREAM, 0,CAP_PROP_VIDEO_STREAM, -1,CAP_PROP_AUDIO_DATA_DEPTH, CV_32F,CAP_PROP_AUDIO_SAMPLES_PER_SECOND, samplingRate};cap.open(0, CAP_ANY, params); // 打开麦克风设备if (!cap.isOpened()){cerr << "Error: Can't open microphone" << endl;return -1; // 如果无法打开,返回错误}const int audioBaseIndex = (int)cap.get(CAP_PROP_AUDIO_BASE_INDEX);vector<double> frameVec;Mat frame;if (microTime <= 0){cerr << "Error: Duration of audio chunk must be > 0" << endl;return -1; // 如果指定的录音时长不合法,返回错误}size_t sizeOfData = static_cast<size_t>(microTime * samplingRate);while (inputAudio.size() < sizeOfData){if (cap.grab()){cap.retrieve(frame, audioBaseIndex);frameVec = frame;inputAudio.insert(inputAudio.end(), frameVec.begin(), frameVec.end()); // 读取音频数据到向量}else{cerr << "Error: Grab error" << endl;break; // 如果读取失败,输出错误并跳出循环}}return samplingRate; // 返回采样率
}// 这段代码展示了如何使用FilterbankFeatures类和Decoder类以及DNN模块进行语音识别。
// 首先,它通过读取录音或麦克风来获取声音信号,然后将其转换为数学特征,之后它将这些特征通过Jasper语音识别模型进行处理,最后通过解码器将模型输出的数据解码为文本。
// 在main函数中还包含了错误处理、警告输出以及可视化频谱图的选项。
// 整个程序的执行流程是:
// 1. 定义命令行参数解析器和解析参数;
// 2. 根据命令行参数加载DNN网络模型;
// 3. 读取音频文件或通过麦克风捕获音频;
// 4. 判断音频数据是否有效;
// 5. 使用FilterbankFeatures类实例化的对象来计算特征;
// 6. 可选地显示频谱图;
// 7. 使用DNN模型进行预测;
// 8. 打印预测结果。
int main(int argc, char** argv)
{// 主程序的输入参数定义const String keys ="{help h usage ?     |                          | 运行 Jasper 语音识别模型的脚本 }""{input_file i       | audio6.mp3              | 输入音频文件的路径. 如果没有指定,则使用麦克风输入 }""{audio_duration t   | 15                       | 从麦克风捕获的音频块的持续时间 }""{audio_stream a     | 0                        | CAP_PROP_AUDIO_STREAM 的值 }""{show_spectrogram s | false                    | 是否展示输入音频的频谱图: true / false / 1 / 0 }""{model m            | jasper_reshape.onnx      | Jasper 的 onnx 文件路径. 你可以从链接下载转换后的 onnx 模型 }""{backend b          | dnn::DNN_BACKEND_DEFAULT | 选择计算后端: ""dnn::DNN_BACKEND_DEFAULT, ""dnn::DNN_BACKEND_INFERENCE_ENGINE, ""dnn::DNN_BACKEND_OPENCV }""{target t           | dnn::DNN_TARGET_CPU      | 选择目标设备: ""dnn::DNN_TARGET_CPU, ""dnn::DNN_TARGET_OPENCL, ""dnn::DNN_TARGET_OPENCL_FP16 }";// 命令行参数解析器CommandLineParser parser(argc, argv, keys);if (parser.has("help")){parser.printMessage(); // 如果有帮助选项,则打印帮助信息并退出return 0;}// 加载模型网络dnn::Net net = dnn::readNetFromONNX(parser.get<std::string>("model"));net.setPreferableBackend(parser.get<int>("backend")); // 设置模型计算后端net.setPreferableTarget(parser.get<int>("target")); // 设置模型计算目标// 获取音频vector<double> inputAudio = {};int samplingRate = 0;if (parser.has("input_file")){// 如果指定了输入文件,从该文件读取音频string audio = samples::findFile(parser.get<std::string>("input_file"));samplingRate = readAudioFile(inputAudio, audio, parser.get<int>("audio_stream"));}else{// 否则,从麦克风读取音频samplingRate = readAudioMicrophone(inputAudio, parser.get<int>("audio_duration"));}if ((inputAudio.size() == 0) || samplingRate <= 0){// 如果读取音频时出错,输出错误信息并退出cerr << "Error: problems with audio reading, check input arguments" << endl;return -1;}if (inputAudio.size() / samplingRate < 6){// 如果读取的音频时长不足6秒,进行警告并用0填充至6秒cout << "Warning: For predictable network performance duration of audio must exceed 6 sec."" Audio will be extended with zero samples" << endl;for(int i = static_cast<int>(inputAudio.size()); i < samplingRate * 6; ++i){inputAudio.push_back(0);}}// 计算特征FilterbankFeatures filter;auto calculated_features = filter.calculate_features(inputAudio);// 是否显示频谱图if (parser.get<bool>("show_spectrogram")){// 计算并显示频谱图Mat spectogram;normalize(calculated_features, spectogram, 0, 255, NORM_MINMAX, CV_8U);applyColorMap(spectogram, spectogram, COLORMAP_INFERNO);imshow("spectogram", spectogram); waitKey(0); // 等待用户按键之后退出}// 使用解码器并预测结果Decoder decoder;string prediction = predict(calculated_features, net, decoder);// 输出识别结果cout << prediction << endl;return 0; // 主程序退出
}

该代码是一个使用OpenCV的DNN模块结合音频信号处理技术实现的语音识别的C++程序。这个程序首先定义了声音特征提取的类FilterbankFeatures和Jasper模型的解码器Decoder,在main函数中,程序将加载和运行Jasper模型,并通过麦克风或音频文件获取声音数据,然后对其进行处理以提取特征,最后给出语音识别的预测结果。代码中引入了多个音频信号处理相关的函数,以及语音识别模型运用和结果解析的部分,体现了将深度学习应用于音频分析处理的一种实际方法。

  • mel: 生成梅尔滤波器矩阵,接收梅尔滤波器的数量n_mels和频率范围fmin和fmax,用于后续提取音频特征。

  • pad_window_center: 填充窗函数至n_fft大小的长度,这是为了确保窗函数在进行短时傅里叶变换时具有相同的大小。

  • frame: 将音频信号按照窗口大小n_fft和步长hop_length分割成一系列有重叠的帧。

  • hanning: 这是一个窗函数,用于生成汉宁窗,在进行短时傅里叶变换时应用到每一帧上,以减少边缘效应。

  • stft_power 函数实现了短时傅里叶变换(STFT),通过在短时重叠窗口上计算离散傅里叶变换(DFT),使信号在时频域上表示。它还计算了每个频率组件的功率,作为处理的一部分。

  • calculate_features 函数计算滤波器组特征矩阵,首先对音频信号加噪(抖动)以增加其动态范围,然后进行预加重处理以增强高频部分。接下来,使用STFT计算频谱的功率,然后使用梅尔滤波器组筛选出这些功率特征,并对结果取对数。最后,将特征矩阵标准化并将其转换为OpenCV的Mat类型以便后续处理。

  • Decoder类:从深度学习模型输出中提取文本结果,使用连接时序分类(CTC)解码。

  • predict函数:传递特征通过预训练的神经网络模型(如Jasper),并将输出解码为文本。

  • readAudioFile函数:读取音频文件,将音频流转换为一个可以被模型处理的样本数组。

  • Decoder中的decode函数通过处理CTC模型输出,消除重复和特殊的空字符(通常表示为 "-1"或最后一个索引),生成最终的文本预测结果。

  • predict函数通过网络模型计算特征的值,将输出交给Decoder来产出最终文本结果。

  • readAudioFile函数用于从给定文件中读取音频数据,并将其转换为浮点数值数组,该数组随后可以用于音频特征提取。返回值是音频文件的采样率,这对于后续处理至关重要。

  • readAudioMicrophone 函数用于从麦克风捕获音频数据,直到达到指定时长 microTime。

  • main 函数则是程序的入口点,解析命令行参数以设置参数(默认值如输入文件、是否显示频谱图等),加载预训练的神经网络模型(如 Jasper 模型),读取音频文件或者麦克风录音,确保读取的音频长度,对音频数据进行处理和预测,并输出最终的结果。

  • 当录音时长少于6秒时,程序会输出警告,并将音频数据以0填充至6秒长,以保证模型能正常识别音频内容。

  • 显示频谱图的功能被省略了代码,可以利用Mat和imshow实现。

c552efdc4e31fe7bedb08e11b2ea8cc6.png

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

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

相关文章

华为手机p70即将上市,国内手机市场或迎来新局面?

4月15日&#xff0c;华为官宣手机品牌全新升级&#xff0c;p系列品牌升级为Pura。华为P70系列手机预计将于2024年第一季度末发布&#xff0c;而网友也纷纷表示期待p70在拍照、性能上的全新突破。 网友们对华为P70系列的热情高涨&#xff0c;也印证了国内高端手机市场的潜力巨大…

MySql安装(Linux)

一、清除原来的mysql环境 在前期建议使用root用户来进行操作&#xff0c;使用 su -来切换成root用户&#xff0c;但是如果老是提示认证失败&#xff0c;那么有可能我们的root密码并没有被设置&#xff0c; 我们可以先设置root的密码 sudo passwd root 然后就可以切换了。 …

Mac 利用Homebrew安装JDK

一、安装JDK17 1.安装openjdk17 2.把homebrew安装的openjdk17软链接到系统目录&#xff1a; brew install openjdk17 sudo ln -sfn $(brew --prefix)/opt/openjdk17/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-17.jdk 一、检查是否安装成功 在Termina…

深入解析Apache Hadoop YARN:工作原理与核心组件

什么是YARN&#xff1f; YARN&#xff08;Yet Another Resource Negotiator&#xff09;是Apache Hadoop生态系统中的一个重要组件&#xff0c;用于资源管理和作业调度。它是Hadoop 2.x版本中的一个关键特性&#xff0c;取代了旧版本中的JobTracker和TaskTracker。YARN的设计目…

蚂蚁云科技集团应用研究院院长李亚锋先生受邀为第十三届中国PMO大会演讲嘉宾

全国PMO专业人士年度盛会 蚂蚁云科技集团应用研究院院长李亚锋先生受邀为PMO评论主办的2024第十三届中国PMO大会演讲嘉宾&#xff0c;演讲议题为“探索AI技术对项目管理发展的影响”。大会将于5月25-26日在北京举办&#xff0c;敬请关注&#xff01; 议题简要&#xff1a; 19…

CSS的语法规则——基础选择器

元素&#xff1a; 用法&#xff1a; 标签名&#xff1a;{style的内容} 特点&#xff1a; 全局性&#xff0c;使用后&#xff0c;所有的相同标签都是同一种样式。 举例&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UT…

双目人体姿态匹配 | 基于双目视觉的人体姿态匹配+识别算法实现

项目应用场景 面向双目立体视觉的人体姿态匹配场景&#xff0c;项目采用 Visual Studio C 开发&#xff0c;具有人体姿态匹配 人体姿态识别能力。 项目效果 项目细节 > 具体参见项目 README.md项目获取 https://download.csdn.net/download/weixin_42405819/89113399

怎么找平台?伦敦金交易平台查询

在过去的一个星期里&#xff0c;伦敦金价格再创辉煌&#xff0c;一举突破2400大关&#xff0c;并且将历史新高刷新至2431。虽然随后金价出现较大幅度的下跌&#xff0c;但对多数投资者而言&#xff0c;这不是下跌而是回调。回调之后&#xff0c;伦敦金的涨势将继续。既然有这么…

neo4j使用详解(终章、neo4j的java driver使用模板及工具类——<可用于生产>)

Neo4j系列导航: neo4j安装及简单实践 cypher语法基础 cypher插入语法 cypher插入语法 cypher查询语法 cypher通用语法 cypher函数语法 neo4j索引及调优 neo4j java Driver等更多 1. 简介 本文主要是java使用neo4j driver操作neo4j的模板项目及非常有用的工具类,主要包括: 图…

我三战华东师范大学的同桌真的很牛

标题党一下&#xff0c;其实是我的一个关系很好的高中同桌死磕华东师范大学三年&#xff0c;最终第二名上岸的故事。 其实我们是同一届的&#xff0c;我为了早点走选择了个排名还行的双非院校就走了&#xff0c;而我这个同桌相当有毅力&#xff0c;他能坚持这么久我其实很佩服&…

微信小程序开发一(常用的标签)

去微信公众平台注册账号确定怎么操作去网上找找 网址&#xff1a;微信公众平台 (qq.com)https://mp.weixin.qq.com/https://mp.weixin.qq.com/ 微信开放文档中找到工具栏下载微信开发者工具 网址&#xff1a;微信开发者工具&#xff08;稳定版 Stable Build&#xff09;下载地…

JavaWeb开发02-MYSQL-DDL-DML-DQL-多表设计-多表查询-事务-索引

一、MySQL概述 通过SQL语句可以操作数据库 关系型数据库&#xff1a; 只要是关系型数据库就可以用SQL语句这一统一标准进行操作数据库 1.MYSQL数据模型 客户端通过SQL语句交给了数据库管理系统DBMS&#xff0c;进行相应操作&#xff0c;创建一个一个数据库&#xff0c;体现为一…

如何理解信创产品?成为信创产品?

信创产品是指运用信息技术手段创造的产品&#xff0c;它是数据安全、网络安全的基础&#xff0c;也是新基建的重要组成部分。信创产品包括了IT基础设施如CPU芯片、服务器、存储、交换机、路由器、各种云和相关服务内容&#xff1b;基础软件如数据库、操作系统、中间件&#xff…

JRT在线初始化完善

之前实现的在线初始化留了个尾巴&#xff0c;那就是环境下载页构造zip包的时候没修改JRTBrowser的连接串地址为当前网站&#xff0c;这样就要求网站部署好之后给用户下载之前有人要把服务器的浏览器地址配置好。这样就增加一个运维工作&#xff0c;如果忘了或者不知道的人就会导…

nuxt3使用记录四:加载静态资源时路径的写法研究

在上一篇记录了NUXT进行SSG构建时&#xff0c;不仅会构建纯静态的html文件&#xff0c;也会构建一堆js文件。而如果网页中有加载静态资源&#xff0c;如图片&#xff0c;这时就需要注意了&#xff0c;不能简单的使用官网说的<img src"~/assets/img/nuxt.png" alt&…

Linux进阶---常见符号及其含义、常见报错

一、常见符号及其含义&#xff08;重要&#xff09; 二、 常见报错 • 找不到文件、文件不存在&#xff1a;No such file or directory • 没有权限 &#xff1a;Permission denied • 命令不存在 &#xff1a;command not found 1.找不到文件、文件不存在&#xff1a;No s…

AI智能电销机器人是什么?能给我们带来哪些便利?

科技的飞速发展&#xff0c;让很多“懒人”的幻想变成了现实&#xff0c;越来越多的人工智能产品被发明出来甚至完全替代日常生活中的工作。比如在电销行业&#xff0c;很多企业选择AI智能电销机器人进行外呼。那么你了解多少AI智能电销机器人呢&#xff1f;和小编kelaile520一…

声明v.s.定义

参考&#xff1a;http://t.csdnimg.cn/1xnzA 变量的定义&#xff1a;用于为变量分配存储空间&#xff0c;还可以为变量指定初始值&#xff0c;在程序中&#xff0c;变量有且仅有一个定义。变量的声明&#xff1a;用于向程序表明变量的类型和名字。在程序中&#xff0c;变量可以…

SAP SD学习笔记06 - 受注的据否,受注的理由,简易变更(一括处理)

上文讲了一括处理和Block&#xff08;冻结&#xff09;处理。 SAP SD学习笔记05 - SD中的一括处理&#xff08;集中处理&#xff09;&#xff0c;出荷和请求的冻结&#xff08;替代实现承认功能&#xff09;-CSDN博客 本章继续讲SAP的流程中一些常用的操作。 1&#xff0c;受注…

Flume 入门教程

内容目录 Flume 简介 架构和基本概念 多种架构模式 Flume 安装部署 Flume 简介 Flume 是一个分布式、可靠且高可用的数据收集、聚合和传输系统&#xff0c;主要用于高效地处理大规模日志数据。设计之初&#xff0c;它主要服务于日志管理领域&#xff0c;但其灵活性和可扩展…