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

news/2024/5/17 11:45:26

模型下载地址:

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.mrgr.cn/p/30127511

相关文章

Linux下SPI设备驱动实验:使用内核提供的读写SPI设备中的数据的函数

一. 简介 前面文章的学习&#xff0c;已经实现了 读写SPI设备中数据的功能。文章如下&#xff1a; Linux下SPI设备驱动实验&#xff1a;验证读写SPI设备中数据的函数功能-CSDN博客 本文来使用内核提供的读写SPI设备中的数据的API函数&#xff0c;来实现读写SPI设备中数据。 …

4*5的矩阵(C语言)

一、N-S流程图&#xff1b; 二、运行结果&#xff1b; 三、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>int main() {//初始化变量值&#xff1b;int i 0;int j 0;int result 0;//嵌套循环输出&#xff1b;for (i 1; i < 4; i){//列…

【unity】【C#】游戏音乐播放和发布

今天我们来认识一下有关 unity 音乐的一些知识 我们先创建 AudioClips 文件夹&#xff0c;这个文件夹通常就是 unity 中存放音乐的文件夹&#xff0c;然后拖进音乐文件进去 这里为大家提供了两个音乐&#xff0c;有需要可以自取 百度网盘&#xff1a;https://pan.baidu.com/s…

Hotcoin4月16日上新热门资产:头部RWA技术提供方Centrifuge(CFG)

Hotcoin持续为全球600万用户发掘优质潜力资产&#xff0c;热门币种交易上热币。一文快速了解今日上新资产:Centrifuge(CFG) 推荐指数 8.2 交易对 CFG/USDT 交易时间 4月16日 19:00 资产赛道 RWA 项目简介 Centrifuge是一个去中心化资产融资协议&#xff0c;专注于释放现实世界资…

使用Python工具库SnowNLP对评论数据标注(二)

这一次用pandas处理csv文件 comments.csv import pandas as pd from snownlp import SnowNLPdf pd.read_csv("C:\\Users\\zhour\\Documents\\comments.csv")#{a: [1, 2, 3], b: [4, 5, 6], c: [7, 8, 9]}是个字典 emotions[] for txt in df[sentence]:s SnowNLP(…

二刷大数据(三)- Flink1.17

目录 Flink概念与SparkStreaming区别分层API 工作流程部署模式**Local Mode****Standalone Mode****YARN Mode****Kubernetes Mode****Application Mode** 运行架构stand alone 核心概念算子链任务槽 窗口窗口**窗口的目的与作用****时间窗口&#xff08;Time Windows&#xff…

uniapp中scroll-view初始化的时候 无法横向滚动到某个为止

项目需求 实现日历&#xff08;13天&#xff09;默认高亮第六天 并定位到第六 左边右边各六天&#xff08;可以滑动&#xff09; 直接上代码 <template><scroll-view class"scroll-X":show-scrollbar"true" :scroll-x"scrollable":…

C++的线程

#include<iostream> #include<thread> #include<unistd.h> using namespace std; void myrun() {while(true){cout<<"I am a thread"<<endl;sleep(1);} } int main() {thread t(myrun);t.join();return 0; } 如果不添加-lpthread就会报…

2024年华中杯数学建模竞赛ABC题思路分析

简单分析一下各个题目可能需要用到的方法和模型&#xff0c;完整代码和成品论文见文末 A题 太阳能路灯光伏板的朝向设计问题: 1. 球面几何、天文学相关知识,如赤纬角、太阳高度角、时角等概念和公式 2. 太阳辐射模型,根据太阳能辐射强度、大气衰减系数等计算地表太阳辐射强度…

Day09 React———— 第九天

ReactRoter 一个路径 path 对应一个组件 component 当我们在浏览器中访问一个 path 的时候&#xff0c;path 对应的组件会在页面中进行渲染 基础用法 import { createBrowserRouter, RouterProvider } from "react-router-dom"; const router createBrowserRoute…

如何在Vue3中使用H.265视频EasyPlayer.js流媒体播放器?

H5无插件流媒体播放器EasyPlayer属于一款高效、精炼、稳定且免费的流媒体播放器&#xff0c;可支持多种流媒体协议播放&#xff0c;可支持H.264与H.265编码格式&#xff0c;性能稳定、播放流畅&#xff0c;能支持WebSocket-FLV、HTTP-FLV&#xff0c;HLS&#xff08;m3u8&#…

3D可视化技术:研发基地的科技新篇章

在科技日新月异的今天&#xff0c;我们生活在一个充满无限可能性的时代。而在这个时代中&#xff0c;3D可视化技术正以其独特的魅力&#xff0c;引领着科技领域的新一轮变革。 3D可视化技术通过三维图像的方式&#xff0c;将现实世界或虚拟世界中的物体、场景等以立体、逼真的形…

【嵌入式之中断】

Cortex-M4集成了嵌套式矢量型中断控制器(Nested Vectored Interrupt Controller (NVIC))来实现高效的异常和中断处理。NVIC实现了低延迟的异常和中断处理&#xff0c;以及电源管理控制。它和内核是紧密耦合的。 凡是打断程序顺序执行的事件都称为异常&#xff08;exception&am…

VN6501使用方法(学习笔记)

1:总体简介 VNA6501可以进行CAN Stress测试&#xff0c;可以进行采样点测试。此外VN6501还能够定制&#xff0c;外围测试电路&#xff08;通过软件配置&#xff0c;部分情况是需要连接VN6501的部分接口&#xff09;。 关于外围测试电路&#xff08;添加电容&#xff0c;电阻&…

<计算机网络自顶向下> 可靠数据传输的原理(未完成)

可靠数据传输&#xff08;rdt&#xff1a;Reliable Data Transfer&#xff09;的原理 rdt在应用层&#xff0c;传输层和数据链路层都很重要是网络TOP10问题之一信道的不可靠特点决定了可靠数据传输rdt的复杂性rdt_send: 被上层&#xff08;如应用层&#xff09;调用&#xff0…

机器学习和深度学习--李宏毅(笔记与个人理解)Day11-12

Day11 when gradient is small…… 怎么知道是局部小 还是鞍点&#xff1f; using Math 这里巧妙的说明了hessan矩阵可以决定一个二次函数的凹凸性 也就是 θ \theta θ 是min 还是max&#xff0c;最后那个有些有些 哈 是一个saddle&#xff1b; 然后这里只要看hessan矩阵是不…

基于数据库现有表导出为设计文档

1.查询 SELECTCOLUMN_NAME 字段名,COLUMN_COMMENT 字段描述,COLUMN_TYPE 字段类型,false as 是否为主键 FROMINFORMATION_SCHEMA.COLUMNS wheretable_NAME region -- 表名2.查询结果 3.导出为excel

学习云计算HCIE选择誉天有什么优势?

誉天云计算课程优势实战性强 课程注重实践操作&#xff0c;通过实际案例和实验操作&#xff0c;让学员深入了解云计算的应用场景和实际操作技能。课程内容全面 涵盖所有云计算涉及的IT基础知识、服务器、存储、网络等方面的基础知识&#xff0c;开源操作系统Linux&#xff0c;开…

吴恩达机器学习笔记(1-1到2-1)

吴恩达机器学习笔记(1-1到2-1) https://www.bilibili.com/video/BV164411b7dx?p=1 https://www.bilibili.com/video/BV164411b7dx?p=2 https://www.bilibili.com/video/BV164411b7dx?p=3 https://www.bilibili.com/video/BV164411b7dx?p=4 机器学习-吴恩达 一、初学 1、什…

YAML教程-1-基础入门

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go YAML简介 YAML&#xff08;YAML Aint Markup Language&#xff09;是一种用于数据序列化的人类可读格式。它广泛用于配置文件、数据交换、持续集成/持续部署&#xff08;CI/CD&#xff09;等领域。YAML的设计目标…