微立顶科技

新闻资讯

创新 服务 价值

  虚拟女友初步调研总结2024-0212(转载)

发布日期:2024/2/19 18:58:15      浏览量:

虚拟女友初步调研总结2024-0212(转载)


转自:https://zhuanlan.zhihu.com/p/682012849


整个AI生成领域的变化,已经非常恐怖了,呈现出一副勃勃生机,万物竞发的状态。

  • 文本生成和对话领域,有Gemini-PRO,GPT4为代表的大语言模型,相当于有了大脑;
  • 语音识别领域,有openai的whisper、阿里的funasr为代表的(Speech Recontion)语音识别模型,有了听觉。
  • 视觉识别这块,有Gemini-PRO的免费api可以白嫖,可以直接处理文本+图片信息,可以说是 开天眼了。
  • 再然后就是前段时间看到花儿不哭大佬的新作品:GPT-sovits,这东西可以只用一分钟的音频,做few-shot训练,然后就能直接拿到参考音频的音色,毫不夸张的说,只需要一分钟的录音,就能偷掉别人的嗓子!霍金来了都得点赞。
  • 最后是数字人视频生成,这个对我来说是一个全新的领域,我也只是浅尝了一下,快速做一个记录,和大家一起学习。我主要使用的是Linly-Talker
  • demo视频:【赛博鹰酱给您拜年了】 赛博鹰酱给您拜年了_哔哩哔哩_bilibili

趁着过年这段时间比较浮躁,快速体验了一下这个功能,希望为后面的智能小车,以及最终的虚拟女友,做一个技术铺垫。

由于文本生成和视觉识别都是调用api,所以接下来主要介绍语音识别、声音复刻和数字人生成这三个部分。

1. 语音识别:whisper or FunASR?


openai的whisper

后面使用的是openai 开源的whisper,网上大家都说它是最强语音识别模型。whisper有三种调用方式,做一下总结:

  1. api调用:对于临时使用、长文本、不用即时对话的场景,用免费的3.5api,就可以直接调用,国内延迟比较高,差不多得3-18秒,才能完成一次识别,非常不稳定,不适合实时对话。另外中文识别效果一言难尽,得字正腔圆的普通话才行,甚至于有时候还会生成繁体字。不知道氪金用户,或者用azure的api会不会好些。
  2. 官网开源的whisper:官网的whisper本地部署,这个要求你得有至少8Gb的显存,我自己用的4060显卡,勉强可以用medium,推理速度比较稳定,大概是2秒左右,识别2秒的音频。缺点是一样的,中文识别效果不是很好,错字、标点乱打、繁体。
  3. fast-whisper:这个项目我得贴一下链接:github.com/SYSTRAN/fast, 它大大降低了对显存的要求!

另外,需要提一句的是,large-v2的识别效果一般会比v3更好,所以如果大家要是用whisper的格式,推荐使用fast-whisper:large-v2。

1.1 关于语音识别的自动分段:

在连续对话的逻辑中,不得不提的是一个技术是"语音活性检测 (Voice activity detection,VAD)",即当一个正在录音的循环中,怎么判断有人声,怎么判断,这段话说完了,开始进入对话的逻辑?

好消息是fast whisper仓库自带vad检测模块,因此,只需要做一个集成封装就好。我的主要工作集中在:

  1. 采集音频信号
  2. 判断是否有人声,调用vad模块,可以直接实时判断当前音频是否有人声。
  3. 判断是否是一个完整的对话,这里的思路比较抽象
  1. 提取完整输入音频之后,保存到本地,然后调用本地的whisper模型,进行语音识别,获取文本;
  2. 将文本+Prompt,输入给GPT3.5,拿到对话文本;
  3. 将对话文本输入给GPT3.5的tts模块,拿到回答音频文件;
  4. 播放回复音频。

这里贴一下我的调用示例代码,也算是开源了,如果有帮助,恳请帮忙给帖子点个赞吧:




# 需要在fast-whipsper工作目录中运行。

import pyaudio
import wave
import time
import tenacity
import openai
import random
import re
from openai import OpenAI
import openai
import numpy as np
from faster_whisper import WhisperModel
import pyaudio  # 导入PyAudio库,用于音频录制和播放
import pygame
from faster_whisper.vad import get_vad_model
chatpaper_keys = """
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
    """
chatpaper_keys = chatpaper_keys.strip()
chatpaper_keys_list = re.findall(r’sk-\w+’, chatpaper_keys)


def play_mp3(file_path):
    pygame.mixer.init()
    pygame.mixer.music.load(file_path)
    pygame.mixer.music.play()
    while pygame.mixer.music.get_busy():  # Wait for music to finish playing
        pygame.time.Clock().tick(10)
    # 停止混音器
    pygame.mixer.music.stop()
    # 卸载当前加载的音乐流,释放资源
    pygame.mixer.music.unload()
    # 退出混音器
    pygame.mixer.quit()

def get_tts(client, input_text, output_path="output.mp3"):
    response = client.audio.speech.create(
    model="tts-1",
    voice="nova",
    input=input_text,
    )
    st = time.time()
    response.stream_to_file(output_path)
    print("save audio time:", time.time()-st)

@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, min=4, max=10),
                stop=tenacity.stop_after_attempt(8),
                reraise=True)
def get_response(client, robot_state=’’, user_task=’’):
    openai.api_key = random.choice(chatpaper_keys_list)
    st = time.time()
    # 限制info和prompts的长度
    robot_state = robot_state[:400]
    user_task = str(user_task)[:600]

    messages = [
        {"role": "system", "content": "你是一个对话机器人,你需要根据用户的问题回答."},
        {"role": "assistant", "content": ""},
        {"role": "user", "content": f"""
            根据用户的命令,简洁回答问题。
            robot_state: {robot_state},
            user_task: {user_task}.        
            output format:
            你好主人:xxx(具体回复).
            """},
    ]

    api = "gpt-3.5-turbo"
    response = client.chat.completions.create(
        model=api,
        messages=messages,
        temperature=0.0,
    )
    result = ’’
    for choice in response.choices:
        result += choice.message.content
    return result


class VoiceRecorder:
    def __init__(self, audio_path=’mic_output.wav’):
        # 音频参数
        self.format = pyaudio.paInt16
        self.channels = 1
        self.audio = pyaudio.PyAudio()
        self.frames = []
        self.audio_path = audio_path
        # Run on GPU with FP16
        model_size = "large-v2"
        
        self.model = WhisperModel(model_size, device="cuda", compute_type="float16")
        self.history = []

    def start_recording(self):
        # 打开录音流
        self.stream = self.audio.open(format=self.format, channels=self.channels,
                                      rate=self.rate, input=True,
                                      frames_per_buffer=self.chunk)
        self.frames = []
        print("Recording...")

    def stop_recording(self):
        # 停止录音
        self.stream.stop_stream()
        self.stream.close()
        print("Finished recording.")      

    def play_txt(self,):  
        OPENAI_API_KEY = np.random.choice(chatpaper_keys_list)
        client = OpenAI(api_key=OPENAI_API_KEY)

        # 语音识别:
        st = time.time()
        segments, info = self.model.transcribe(self.audio_path, beam_size=5,
                                               initial_prompt="你是一个对话机器人,你需要根据用户的问题,回答对应的问题.",)

        print("Detected language ’%s’ with probability %f" % (info.language, info.language_probability))

        for segment in segments:
            print("[%.2fs -> %.2fs] %s" % (segment.start, segment.end, segment.text))

        print("time:", time.time()-st)

        st = time.time()
        robot_state = ’默认状态’
        user_task = segment.text
        robot_response = get_response(client, robot_state, user_task)
        print("robot_response: ", robot_response)
        print("robot_response Time taken: ", time.time() - st)

        # 语音合成
        st = time.time()
        output_path = ’output.mp3’
        get_tts(client, robot_response, output_path=output_path)

        print("get_tts Time taken: ", time.time() - st)
        

        # 播放MP3文件
        st = time.time()
        play_mp3(output_path)
        print("Time taken: ", time.time() - st)

    def save_recording(self, file_name):
        # 保存录音到文件
        wave_file = wave.open(file_name, ’wb’)
        wave_file.setnchannels(self.channels)
        wave_file.setsampwidth(self.audio.get_sample_size(self.format))
        wave_file.setframerate(self.rate)
        wave_file.writeframes(b’’.join(self.frames))
        wave_file.close()
        print(f"Recording saved as {file_name}")

    def find_indices(self, speech_list, time_num=2):
        # 初始化索引
        first_index = None
        last_index = None
        lst = [0 if sp < 0.5 else 1 for sp in speech_list]
        # 遍历列表,找到第一个0到1的索引
        for i in range(len(lst) - 1):
            if lst[i] == 0 and lst[i + 1] == 1:
                first_index = i + 1
                break
        
        # 如果没有找到0到1的转变,返回None
        if first_index is None:
            return None

        # 从找到的第一个1开始,遍历列表,找到符合条件的最后一个1到0的转变
        for i in range(first_index, len(lst) - 1):
            if lst[i] == 1 and lst[i + 1] == 0:
                # 检查是否有至少time_num个连续的0
                zero_count = 0
                for j in range(i + 1, len(lst)):
                    if lst[j] == 0:
                        zero_count += 1
                    else:
                        break
                if zero_count >= time_num:
                    last_index = i
                    break # 可以提前结束循环,因为我们只需要最后一个符合条件的索引

        # 如果没有找到符合条件的1到0的转变,或者只有0没有1,返回None
        if last_index is None:
            return None

        # 返回第一个0到1的索引和最后一个符合条件的1到0的索引
        return (first_index, last_index)
                
    def run(self):
        # 开始运行录音程序
        
        temp  = []
        self.audio_rate = 16000
        self.rate = 16000
        self.chunk = 1024
        self.stream = self.audio.open(format=self.format, channels=self.channels,
                                      rate=self.rate, input=True,
                                      frames_per_buffer=self.chunk)
        self.vad_model = get_vad_model()
        self.vad_state = self.vad_model.get_initial_state(batch_size=1)
        self.frames = []
        self.speech_list = []
        time_sum = 0
        time_count = 0
        time_th = 1.0
        print("Recording...")
        while True:
            # 进入无限录音模式:
            st = time.time()            
            # 获取当前帧的数据,chunk=1024
            data = self.stream.read(self.chunk)            
            audio_int16 = np.frombuffer(data, np.int16)
            audio_float32 = np.array(audio_int16).astype(np.float32)/32768.0  #(num_samples,)
            # 利用vad检测是否有人声的概率
            speech_prob, self.vad_state = self.vad_model(audio_float32, self.vad_state, self.audio_rate)
            temp.append(audio_float32)
            # 判断当前帧是否有人声
            is_speech = speech_prob[0][0] > 0.5
            # 把所有的数据和人声判断都存到列表中。
            self.frames.append(data)
            self.speech_list.append(is_speech)            

            time_sum += time.time() - st
            time_count += 1
            time_mean = time_sum / time_count
            time_num = time_th/(time_mean+0.0001)
            # 根据人声判断来送筛选是否有人声,且人声的索引。
            res = self.find_indices(self.speech_list, time_num=time_num)
            if res is not None:
                self.frames = self.frames[res[0]:res[1]]
                file_name = self.audio_path
                # 将人声对应的数据存到本地
                self.save_recording(file_name)
                # 和gpt对话,并且调用tts,最后播放
                self.play_txt()
                self.frames = []
                self.speech_list = []
        # 清理工作
        self.audio.terminate()

print("Press and hold space to record, release to stop and save the audio. Press Esc to exit.")
recorder = VoiceRecorder()
recorder.run()



阿里的Funasr:

中间写了一个插曲,继续介绍第二个语音识别方案:阿里的funasr。

不得不说,我当初小瞧了它。但现在使用下来,funasr的优势还是很明显的:部署简单,模型小,推理速度快,对显存要求低。

最最关键的是,它对中文更友好!除非是方言和特殊的术语,基本上都能识别出来。

另外,它应该还是可以和阿里自家的emotion2vec结合起来,用于音频的情感识别,对后面的声音复刻很有用。

OK,但funasr的使用我就不多介绍了,大家直接看后面的内容就好了。(这个帖子今天晚上要写完,明天不能碰这块了,所以越写越急躁~)

声音复刻-gpt-sovits:

音色复刻,我的了解一样很少,印象中只是看过“AI孙燕姿”和“郭德纲说英文相声”,知道AI现在能做到比较恐怖的程度。但我看完花佬的新项目之后,才被深刻的震撼到,原来只需要花一两个小时的配置,加一分钟的录音,就能把别人的嗓子复刻出来,简直是太恐怖了。

我不知道之前的什么bert-sovits,甚至也不知道GPT-sovits的技术原理,我只是最简单的使用整合包,跑数据、微调模型+推理整活。

我之前玩的时候,官方仓库还有不少bug,现在大家闭眼冲官方仓库的整合包就行,

If you are a Windows user (tested with win>=10) you can install directly via the prezip. Just download the prezip, unzip it and double-click go-webui.bat to start GPT-SoVITS-WebUI.

点击prezip下载就好了,文件比较大,需要点魔法+好的网速。最好是谷歌浏览器下载,这样比较稳定。


数字人仓库:Linly-Talker

GitHub - Kedreamix/Linly-Talker

大家自己玩就好,我不介绍了,没时间了。

现在最大的问题是口型和眼神不太对。

数字人进阶方案:ER-NeRF和geneface++

参考视频:【AI 数字人制作(方案六)-哔哩哔哩】 b23.tv/vOk1IMg

点评:这两个是目前比较完善的开源方案,都是需要对特定个人微调,但好像存在长发乱飘的问题。

另外还有一个wav2lip的方案,值得探索。



  业务实施流程

需求调研 →

团队组建和动员 →

数据初始化 →

调试完善 →

解决方案和选型 →

硬件网络部署 →

系统部署试运行 →

系统正式上线 →

合作协议

系统开发/整合

制作文档和员工培训

售后服务

马上咨询: 如果您有业务方面的问题或者需求,欢迎您咨询!我们带来的不仅仅是技术,还有行业经验积累。
QQ: 39764417/308460098     Phone: 13 9800 1 9844 / 135 6887 9550     联系人:石先生/雷先生