简介
sherpa 是 Next-gen Kaldi
项目的部署框架。
使用
VAD
语音活动检测(Voice Activity Detection,简称VAD)是一种技术,用于检测音频信号中是否存在语音或其他声音活动。它在语音处理、语音识别、音频压缩等领域有广泛的应用。
VAD的主要功能
- 语音识别系统:通过VAD,系统可以在检测到语音时启动识别过程,提高效率。
- 音频压缩:在语音通信中,VAD可以帮助压缩算法仅对有效语音信号进行压缩,减少传输数据量。
- 噪声抑制系统:通过检测语音活动,系统可以在静默时段增强噪声抑制效果。
在GO中使用
todo
KWS
关键词唤醒(Keyword Spotting,简称KWS)是一种技术,用于检测音频信号中特定的关键词或短语。它广泛应用于语音助手、智能家居设备、车载系统等领域,通过识别特定关键词来激活设备或执行特定命令。
主要功能
- 关键词检测:识别音频信号中是否包含预定义的关键词或短语。
- 唤醒设备:当检测到关键词时,激活设备或应用程序。
- 提高用户体验:通过语音命令简化操作流程,增强用户体验。
自定义keywords
通过官方的工具sherpa-onnx-cli,可以实现自定义关键字,下面是简单的介绍 原文
# Note: You need to run pip install sherpa-onnx to get the commandline tool: sherpa-onnx-cli
sherpa-onnx-cli text2token --help
Usage: sherpa-onnx-cli text2token [OPTIONS] INPUT OUTPUT
Options:
--text TEXT Path to the input texts. Each line in the texts contains the original phrase, it might also contain some extra items,
for example, the boosting score (startting with :), the triggering threshold
(startting with #, only used in keyword spotting task) and the original phrase (startting with @).
Note: extra items will be kept in the output.
example input 1 (tokens_type = ppinyin):
小爱同学 :2.0 #0.6 @小爱同学
你好问问 :3.5 @你好问问
小艺小艺 #0.6 @小艺小艺
example output 1:
x iǎo ài tóng x ué :2.0 #0.6 @小爱同学
n ǐ h ǎo w èn w èn :3.5 @你好问问
x iǎo y ì x iǎo y ì #0.6 @小艺小艺
example input 2 (tokens_type = bpe):
HELLO WORLD :1.5 #0.4
HI GOOGLE :2.0 #0.8
HEY SIRI #0.35
example output 2:
▁HE LL O ▁WORLD :1.5 #0.4
▁HI ▁GO O G LE :2.0 #0.8
▁HE Y ▁S I RI #0.35
--tokens TEXT The path to tokens.txt.
--tokens-type TEXT The type of modeling units, should be cjkchar, bpe, cjkchar+bpe, fpinyin or ppinyin.
fpinyin means full pinyin, each cjkchar has a pinyin(with tone). ppinyin
means partial pinyin, it splits pinyin into initial and final,
--bpe-model TEXT The path to bpe.model. Only required when tokens-type is bpe or cjkchar+bpe.
--help Show this message and exit.
我这里只是记录下小爱同学 :2.0 #0.6 @小爱同学
这部分。在这个例子里面:数字
是增强得分(boosting score),#数字
是触发阈值(triggering threshold)。
关于关键词识别中增强得分和触发阈值的优化技巧,我从AI那里获取了一些建议:
简单地讲是“增大 keywords_score, 减小 keywords_threshold”
增大 keywords_score
:这会使系统更容易触发关键词识别
减小 keywords_threshold
:降低整体触发门槛
增强得分(Boosting Score)优化技巧
-
优先级分配:
- 为重要指令设置更高的增强得分(如"暂停"、“结束"可设为3.0-4.0)
- 为常用指令设置中等增强得分(如"开始”、“跳过"可设为2.0-2.5)
- 为非关键指令设置较低增强得分(如"确认”、“是"可设为1.0-1.5)
-
长短句区分:
- 短词通常需要更高的增强得分,因为它们更容易被误触发(如单字词"是”、“否"可设为3.0)
- 较长的短语可以使用相对较低的增强得分(如"跳过这个动作"可设为1.5-2.0)
-
音素相似性处理:
- 对于音素相似的关键词,可以为更重要的词设置更高的增强得分
- 例如,“开始"和"结束"音素差异大,但"暂停"和"跳过"部分音素相似,可适当调整
触发阈值(Triggering Threshold)优化技巧
-
重要性区分:
- 紧急指令(如"结束”、“暂停”)可设置较低的触发阈值(如#0.5),使其更容易被触发
- 非紧急指令可设置较高的阈值(如#0.7),避免误触发
-
环境适应:
- 在噪音大的环境中使用时,可增加阈值(如#0.7-0.8)减少误触发
- 在安静环境中,可适当降低阈值(如#0.5-0.6)提高响应速度
-
常用性考虑:
- 常用指令可设置适中的阈值(如#0.6)
- 不常用但重要的指令可设置较低的阈值(如#0.5)确保在需要时能被识别
测试和优化方法
-
渐进式调整:
- 从默认值开始(如增强得分:2.0,阈值#0.6)
- 在真实使用场景中测试并记录误触发和漏触发情况
- 基于测试结果逐步调整参数
-
交叉测试:
- 在不同环境下(安静/嘈杂)进行测试
- 由不同人(不同口音、性别)测试同一组关键词
- 综合调整达到最佳平衡
-
优先级矩阵: 创建类似下面的优先级矩阵来分配参数:
关键词优先级 低噪音环境 中噪音环境 高噪音环境 高优先级 :3.0 #0.5 :3.5 #0.6 :4.0 #0.7 中优先级 :2.0 #0.5 :2.5 #0.6 :3.0 #0.7 低优先级 :1.5 #0.6 :2.0 #0.7 :2.5 #0.8
根据具体应用场景,建议从中等参数开始(如:2.0 #0.6),然后根据实际表现进行微调。最终目标是在减少误触发的同时保证必要指令能被可靠识别。
ASR
自动语音识别(Automatic Speech Recognition,简称ASR)是一种技术,用于将口语转换为文本或命令。它广泛应用于语音助手、语音输入系统、语音控制设备等领域,通过识别语音内容来执行相应的操作。
主要功能
- 语音转文本:将口语转换为对应的文本信息。
- 语音命令识别:识别语音中的命令或指令,并执行相应的操作。
- 提高效率:通过语音输入简化操作流程,提高用户效率。
我们可以借助portaudio来进行实时的语音识别任务。对于ubuntu系统,请使用下面的命令进行安装
apt-get install portaudio19-dev
Arch请使用源码编译安装 https://www.portaudio.com
需要编译为arm64的架构,可以参考下面的步骤:
- 安装相关docker及相关工具
docker
paru -S docker docker-compose docker-buildx
跨平台编译
# 1. 安装 QEMU 模拟支持 docker run --privileged --rm tonistiigi/binfmt --install all # 2. 创建并使用 buildx 构建器 docker buildx create --use --name multiarch-builder
创建dockerfile
FROM docker.hlmirror.com/ubuntu:noble # 设置工作目录 WORKDIR /app # 1. 安装基础工具和依赖 RUN apt-get update && \ apt-get install -y \ wget \ build-essential \ gcc \ pkg-config \ portaudio19-dev \ ca-certificates \ && rm -rf /var/lib/apt/lists/* # 2. 安装 Go (ARM64 版本) ENV GO_VERSION=1.24.2 RUN wget -O go.tgz "https://go.dev/dl/go${GO_VERSION}.linux-arm64.tar.gz" && \ tar -C /usr/local -xzf go.tgz && \ rm go.tgz # 3. 设置 Go 环境变量 ENV PATH="/usr/local/go/bin:${PATH}" # 4. 复制项目代码 COPY . . RUN go env -w GOPROXY='https://goproxy.io,https://goproxy.cn,direct' RUN go mod tidy && go mod download RUN CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -v -o tingAutoServer_linux_arm64 ./cmd/app/ # 6. 收集所有依赖的 .so 文件到 /output 目录 RUN mkdir -p /output && \ cp app /output/ && \ ldd app | awk '/=>/ {print $3}' | xargs -I '{}' cp --parents '{}' /output/ 2>/dev/null || true # 7. 定义输出目录(用于后续从容器中拷贝文件) VOLUME /output
脚本
#!/bin/bash # --- 配置 --- IMAGE_NAME="tiny-builder" # 临时镜像名称 (可以自定义) IMAGE_TAG="latest" # 镜像标签 PLATFORM="linux/arm64" # 目标平台 OUTPUT_DIR="./build_output" # 宿主机上存放输出文件的目录名 CONTAINER_COPY_PATH="/output" # Dockerfile 中存放编译结果的路径 # --- 脚本主体 --- rm -rf "$OUTPUT_DIR" # 确保输出目录存在 mkdir -p "$OUTPUT_DIR" echo "Output directory: $OUTPUT_DIR" echo "--- Building Docker image using buildx: $IMAGE_NAME:$IMAGE_TAG for $PLATFORM ---" # 执行 Docker Buildx 构建并加载到本地 # 如果你的 Docker 不需要 sudo,请去掉下面的 sudo # sudo docker buildx build --platform "$PLATFORM" --load -t "$IMAGE_NAME:$IMAGE_TAG" . docker buildx build --platform "$PLATFORM" --load -t "$IMAGE_NAME:$IMAGE_TAG" . # 检查构建是否成功 if [ $? -ne 0 ]; then echo "Docker buildx build failed." exit 1 fi echo "--- Creating temporary container to copy files ---" TEMP_CONTAINER_NAME="temp_copy_$$" # 如果你的 Docker 不需要 sudo,请去掉下面的 sudo # sudo docker create --name "$TEMP_CONTAINER_NAME" "$IMAGE_NAME:$IMAGE_TAG" docker create --name "$TEMP_CONTAINER_NAME" "$IMAGE_NAME:$IMAGE_TAG" if [ $? -ne 0 ]; then echo "Failed to create temporary container. Was the image loaded correctly?" # 可选:尝试清理可能未加载的镜像 # sudo docker rmi "$IMAGE_NAME:$IMAGE_TAG" docker rmi "$IMAGE_NAME:$IMAGE_TAG" exit 1 fi echo "--- Copying files from container path $CONTAINER_COPY_PATH to host path $OUTPUT_DIR ---" # 执行拷贝,确保源路径末尾有 '/.' 来拷贝目录内容 # 如果你的 Docker 不需要 sudo,请去掉下面的 sudo # sudo docker cp "$TEMP_CONTAINER_NAME:$CONTAINER_COPY_PATH/." "$OUTPUT_DIR/" docker cp "$TEMP_CONTAINER_NAME:$CONTAINER_COPY_PATH/." "$OUTPUT_DIR/" # 检查拷贝是否成功 if [ $? -ne 0 ]; then echo "docker cp failed." # 清理临时容器 # 如果你的 Docker 不需要 sudo,请去掉下面的 sudo # sudo docker rm "$TEMP_CONTAINER_NAME" docker rm "$TEMP_CONTAINER_NAME" # 可选:清理镜像 # sudo docker rmi "$IMAGE_NAME:$IMAGE_TAG" docker rmi "$IMAGE_NAME:$IMAGE_TAG" exit 1 fi echo "--- Cleaning up temporary container ---" # 删除临时容器 # 如果你的 Docker 不需要 sudo,请去掉下面的 sudo # sudo docker rm "$TEMP_CONTAINER_NAME" docker rm "$TEMP_CONTAINER_NAME" # 可选:构建完成后删除临时镜像 echo "--- Cleaning up temporary build image ---" # 如果你的 Docker 不需要 sudo,请去掉下面的 sudo # sudo docker rmi "$IMAGE_NAME:$IMAGE_TAG" docker rmi "$IMAGE_NAME:$IMAGE_TAG" echo "--- Build successful! Files copied to $OUTPUT_DIR ---" ls -l "$OUTPUT_DIR" exit 0
在go语言中使用
import "github.com/gordonklaus/portaudio"
func (t *ASRServer) SetupEngine(ctx context.Context) {
lastText := ""
framesPerBuffer := 1024
err := portaudio.Initialize()
if err != nil {
t.logger.Fatal().Err(err).Msg("cannot init portaudio")
}
defer portaudio.Terminate()
defaultDevice, err := portaudio.DefaultInputDevice()
if err != nil {
t.logger.Fatal().Err(err).Msg("failed to get default input device")
}
portAudioParam := portaudio.StreamParameters{
Input: portaudio.StreamDeviceParameters{
Device: defaultDevice,
Channels: 1,
Latency: defaultDevice.DefaultLowInputLatency,
},
SampleRate: 16000,
FramesPerBuffer: framesPerBuffer,
Flags: portaudio.ClipOff,
}
config := createNewOnlineRecognizerConfig()
recognizer := sherpa.NewOnlineRecognizer(&config)
defer sherpa.DeleteOnlineRecognizer(recognizer)
stream := sherpa.NewOnlineStream(recognizer)
defer sherpa.DeleteOnlineStream(stream)
// 每次采样的时长
samplesPerCall := int32(framesPerBuffer)
samples := make([]float32, samplesPerCall)
s, err := portaudio.OpenStream(portAudioParam, samples)
if err != nil {
t.logger.Fatal().Err(err).Msg("failed to open stream")
}
defer s.Stop()
defer s.Close()
err = s.Start()
if err != nil {
t.logger.Fatal().Err(err).Msg("failed to start stream")
}
t.logger.Info().Msg("service is ready..")
for {
select {
case <-ctx.Done():
break
default:
if t.paused.Load() {
continue
}
err = s.Read()
if err != nil {
t.logger.Error().Err(err).Msg("failed to read")
continue
}
stream.AcceptWaveform(config.FeatConfig.SampleRate, samples)
for recognizer.IsReady(stream) {
recognizer.Decode(stream)
}
text := recognizer.GetResult(stream).Text
if len(text) != 0 && lastText != text {
lastText = strings.ToLower(text)
}
if recognizer.IsEndpoint(stream) {
if len(text) != 0 {
t.logger.Info().Str("text", lastText).Msg("recognized text")
t.subscribers.Range(func(ch, _ any) bool {
if channel, ok := ch.(chan string); ok {
select {
case channel <- text:
case <-time.After(100 * time.Millisecond):
t.logger.Warn().Str("text", text).Msg("failed to broadcast: channel blocked")
}
}
return true
})
}
recognizer.Reset(stream)
}
}
}
}
实时语音识别
模型下载
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/icefall-asr-zipformer-streaming-wenetspeech-20230615.tar.bz2
参数设置
sherpa.OnlineRecognizerConfig{
ModelConfig: sherpa.OnlineModelConfig{
Transducer: sherpa.OnlineTransducerModelConfig{
Encoder: "models/icefall-asr-zipformer-streaming-wenetspeech-20230615/exp/encoder-epoch-12-avg-4-chunk-16-left-128.onnx",
Decoder: "models/icefall-asr-zipformer-streaming-wenetspeech-20230615/exp/decoder-epoch-12-avg-4-chunk-16-left-128.onnx",
Joiner: "models/icefall-asr-zipformer-streaming-wenetspeech-20230615/exp/joiner-epoch-12-avg-4-chunk-16-left-128.onnx",
},
Tokens: "models/icefall-asr-zipformer-streaming-wenetspeech-20230615/data/lang_char/tokens.txt",
NumThreads: 1,
Debug: 0,
ModelType: "zipformer2",
Provider: "cpu",
},
DecodingMethod: "greedy_search",
MaxActivePaths: 4,
EnableEndpoint: 1,
Rule1MinTrailingSilence: 2.4,
Rule2MinTrailingSilence: 1.2,
Rule3MinUtteranceLength: 20,
FeatConfig: sherpa.FeatureConfig{
SampleRate: 16000,
FeatureDim: 80,
},
}
实时语音识别+热词
文档参考 https://k2-fsa.github.io/sherpa/onnx/hotwords/index.html
模型下载
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20.tar.bz2
参数设置
sherpa.OnlineRecognizerConfig{
ModelConfig: sherpa.OnlineModelConfig{
Transducer: sherpa.OnlineTransducerModelConfig{
Encoder: "models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/encoder-epoch-99-avg-1.onnx",
Decoder: "models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/decoder-epoch-99-avg-1.onnx",
Joiner: "models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/joiner-epoch-99-avg-1.onnx",
},
Tokens: "models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/tokens.txt",
NumThreads: 2,
Debug: 0,
Provider: "cpu",
ModelingUnit: "cjkchar",
BpeVocab: "models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/bpe.vocab",
},
HotwordsFile: "models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/hotwords.txt",
HotwordsScore: 2.0,
HotwordsBufSize: 1024,
DecodingMethod: "modified_beam_search",
MaxActivePaths: 4,
EnableEndpoint: 1,
Rule1MinTrailingSilence: 2.4,
Rule2MinTrailingSilence: 1.2,
Rule3MinUtteranceLength: 20,
FeatConfig: sherpa.FeatureConfig{
SampleRate: 16000,
FeatureDim: 80,
},
}
热词文件示例
关闭电视:3.5
打开PC:3.3
热词自定义方法
-
创建热词文件
- 热词文件是一个纯文本文件,每行包含一个热词或热词短语
- 可以为每个热词指定一个可选的增益值(boost值)
-
格式说明
-
基本格式:
热词 [boost值]
-
如果不指定boost值,默认为1.0
-
例如:
咖啡 2.0 奶茶 今天天气真好 3.5
-
-
调用方式
- 在创建识别器时通过参数传入热词文件路径
- 在Python API中使用
hotwords_file
参数 - 在C++中使用
SetHotwordsFile()
方法
热词分数与识别的关联
- boost值的作用
- boost值(增益值)直接影响该热词在解码过程中的概率
- 较高的boost值会增加模型对该词的偏好度
- 取值范围通常为正数,1.0表示不做特殊处理
- 工作原理
- 在解码阶段,系统会为每个可能的词序列计算分数
- 当遇到热词时,其分数会乘以对应的boost值
- 这使得包含热词的句子获得更高的总体分数,从而更容易被选为最终结果
- 权衡考虑
- boost值设置过高可能导致过度匹配,即使发音不太接近也被识别为热词
- boost值设置适中可以在保持准确性的同时提高热词的识别率
- 一般建议从1.0-5.0的范围内尝试,根据实际效果调整
TTS
文本转语音(Text-to-Speech,简称TTS)是一种技术,用于将文本信息转换为合成语音。它广泛应用于语音助手、有声读物、导航系统等领域,通过合成语音来传达信息。
主要功能
- 文本转换:将输入的文本转换为自然语音。
- 语音合成:使用算法生成类似人类的声音。
- 提高可访问性:通过TTS技术,视障人士可以更方便地获取信息。