Qwen3.5-27B镜像扩展实践:集成Whisper语音转文字+图文联合分析链路

1. 引言:从单一对话到多模态分析

想象一下,你正在处理一个客户会议的视频录像。你需要先手动将语音转成文字,再根据文字内容去分析视频中出现的图表和PPT截图,最后整理出一份会议纪要。这个过程繁琐、耗时,而且容易出错。

现在,有了Qwen3.5-27B这个强大的多模态模型,我们已经可以轻松实现图片理解。但如果能让它“听懂”声音,再把听到的和看到的结合起来分析,岂不是更强大?这正是我们今天要做的:为Qwen3.5-27B镜像集成Whisper语音识别能力,打造一个从“听”到“看”再到“理解”的完整智能分析链路。

本文将带你一步步实现这个扩展。你不需要是深度学习专家,只要会基本的命令行操作,就能跟着教程完成。我们将从环境准备开始,到代码集成,最后实现一个能同时处理语音、图片和文本的联合分析应用。

2. 环境准备与依赖安装

在开始集成之前,我们需要确保基础环境已经就绪。假设你已经按照官方手册部署好了Qwen3.5-27B镜像,并且服务正在7860端口正常运行。

2.1 检查现有环境

首先,进入Qwen3.5-27B的运行环境:

# 激活conda环境
conda activate qwen3527

# 检查Python版本和关键包
python --version
pip list | grep -E "torch|transformers|fastapi"

你应该能看到类似以下的输出,确认torch、transformers和fastapi等核心包已安装:

Python 3.10.12
torch                         2.1.0+cu121
transformers                  4.36.0
fastapi                       0.104.1

2.2 安装Whisper及相关依赖

Whisper是OpenAI开源的语音识别模型,支持多语言,识别准确率高。我们来安装它:

# 安装Whisper核心包
pip install openai-whisper

# 安装音频处理相关依赖
pip install ffmpeg-python
pip install soundfile
pip install librosa

# 安装用于HTTP文件上传处理的依赖
pip install python-multipart

安装注意事项

  • Whisper模型会自动下载,默认是base模型(约74M),如果需要更高精度,可以后续手动下载smallmediumlarge模型
  • ffmpeg-python是处理音频文件的关键,确保系统已安装ffmpeg:apt-get update && apt-get install -y ffmpeg(如果是在容器内)

2.3 验证安装

创建一个简单的测试脚本,确认Whisper能正常工作:

# test_whisper.py
import whisper
import torch

print(f"PyTorch版本: {torch.__version__}")
print(f"CUDA可用: {torch.cuda.is_available()}")
print(f"CUDA版本: {torch.version.cuda}")

# 加载模型(首次运行会下载模型)
model = whisper.load_model("base")
print("Whisper模型加载成功!")

# 测试一段示例音频(需要先准备一个test.wav文件)
# result = model.transcribe("test.wav")
# print(f"识别结果: {result['text']}")

运行测试:

python test_whisper.py

如果看到"Whisper模型加载成功!",说明环境准备就绪。

3. 扩展API接口设计

现在我们要在现有的Qwen3.5-27B服务基础上,增加语音处理和联合分析的能力。原有的API结构保持不变,我们只是新增几个端点。

3.1 设计新的API端点

我们在原有的FastAPI应用中添加以下新接口:

  1. 语音转文字接口 (/transcribe):接收音频文件,返回识别文字
  2. 语音+图片联合分析接口 (/analyze_audio_image):同时接收音频和图片,进行综合分析
  3. 多模态对话接口 (/multimodal_chat):支持文本、图片、语音的混合输入

3.2 修改现有应用结构

首先,查看现有的应用目录结构:

cd /opt/qwen3527-27b
ls -la

通常你会看到类似这样的结构:

app.py          # 主应用文件
requirements.txt # 依赖文件
models/         # 模型相关代码
static/         # 静态文件
templates/      # 模板文件

我们在app.py同级目录创建一个新的模块来处理语音功能:

# 创建语音处理模块
touch audio_processor.py
touch multimodal_analyzer.py

4. 实现语音处理模块

4.1 创建音频处理器

打开audio_processor.py,实现核心的语音识别功能:

# audio_processor.py
import whisper
import torch
import tempfile
import os
from typing import Optional, Dict, Any
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class AudioProcessor:
    def __init__(self, model_size: str = "base", device: Optional[str] = None):
        """
        初始化语音处理器
        
        Args:
            model_size: Whisper模型大小,可选["tiny", "base", "small", "medium", "large"]
            device: 运行设备,"cuda"或"cpu",默认自动选择
        """
        self.model_size = model_size
        
        # 自动选择设备
        if device is None:
            self.device = "cuda" if torch.cuda.is_available() else "cpu"
        else:
            self.device = device
            
        logger.info(f"加载Whisper模型 {model_size},使用设备: {self.device}")
        
        # 加载模型
        self.model = whisper.load_model(model_size, device=self.device)
        
        # 支持的语言代码映射
        self.language_codes = {
            "中文": "zh",
            "英语": "en",
            "日语": "ja",
            "韩语": "ko",
            "法语": "fr",
            "德语": "de",
            "西班牙语": "es",
            "自动检测": None
        }
    
    def transcribe_audio(
        self, 
        audio_path: str, 
        language: Optional[str] = None,
        task: str = "transcribe"
    ) -> Dict[str, Any]:
        """
        转录音频文件
        
        Args:
            audio_path: 音频文件路径
            language: 语言代码,如"zh"、"en",None表示自动检测
            task: 任务类型,"transcribe"(转录)或"translate"(翻译成英语)
            
        Returns:
            包含转录结果的字典
        """
        try:
            logger.info(f"开始转录音频: {audio_path}")
            
            # 执行转录
            result = self.model.transcribe(
                audio_path,
                language=language,
                task=task,
                fp16=(self.device == "cuda")  # GPU上使用半精度加速
            )
            
            # 整理结果
            transcription = {
                "text": result["text"].strip(),
                "language": result.get("language", "unknown"),
                "segments": result.get("segments", []),
                "success": True,
                "error": None
            }
            
            logger.info(f"转录完成,语言: {transcription['language']}, 字数: {len(transcription['text'])}")
            return transcription
            
        except Exception as e:
            logger.error(f"转录失败: {str(e)}")
            return {
                "text": "",
                "language": "unknown",
                "segments": [],
                "success": False,
                "error": str(e)
            }
    
    def transcribe_bytes(
        self, 
        audio_bytes: bytes, 
        file_extension: str = "wav",
        **kwargs
    ) -> Dict[str, Any]:
        """
        直接转录音频字节数据
        
        Args:
            audio_bytes: 音频字节数据
            file_extension: 文件扩展名,用于临时文件
            
        Returns:
            转录结果
        """
        # 创建临时文件
        with tempfile.NamedTemporaryFile(suffix=f".{file_extension}", delete=False) as tmp_file:
            tmp_file.write(audio_bytes)
            tmp_path = tmp_file.name
        
        try:
            result = self.transcribe_audio(tmp_path, **kwargs)
        finally:
            # 清理临时文件
            if os.path.exists(tmp_path):
                os.unlink(tmp_path)
        
        return result
    
    def get_supported_languages(self) -> Dict[str, str]:
        """获取支持的语言列表"""
        return self.language_codes.copy()

# 全局处理器实例
_audio_processor = None

def get_audio_processor(model_size: str = "base") -> AudioProcessor:
    """获取或创建音频处理器实例(单例模式)"""
    global _audio_processor
    if _audio_processor is None:
        _audio_processor = AudioProcessor(model_size=model_size)
    return _audio_processor

4.2 创建多模态分析器

接下来,在multimodal_analyzer.py中实现联合分析逻辑:

# multimodal_analyzer.py
import base64
import io
from typing import Dict, Any, Optional
import logging
from PIL import Image

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class MultimodalAnalyzer:
    def __init__(self, qwen_client):
        """
        初始化多模态分析器
        
        Args:
            qwen_client: Qwen3.5-27B的客户端,用于调用图片理解和文本生成
        """
        self.qwen_client = qwen_client
        logger.info("多模态分析器初始化完成")
    
    def analyze_audio_and_image(
        self,
        audio_text: str,
        image_data: bytes,
        prompt_template: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        联合分析音频转录文本和图片
        
        Args:
            audio_text: 音频转录的文本
            image_data: 图片的字节数据
            prompt_template: 自定义提示词模板
            
        Returns:
            分析结果
        """
        try:
            # 默认提示词模板
            if prompt_template is None:
                prompt_template = """请综合分析以下内容:

音频转录文本:
{audio_text}

以及对应的图片内容。

请完成以下任务:
1. 总结音频的主要内容
2. 描述图片中的关键信息
3. 分析音频内容和图片内容之间的关联
4. 如果有不一致或矛盾的地方,请指出
5. 提供整体的理解和见解

请用清晰、有条理的方式回答。"""
            
            # 构建完整的提示词
            prompt = prompt_template.format(audio_text=audio_text)
            
            logger.info(f"开始联合分析,音频文本长度: {len(audio_text)},图片大小: {len(image_data)} bytes")
            
            # 调用Qwen3.5的图片理解接口
            # 这里需要根据实际的Qwen客户端接口进行调整
            result = self.qwen_client.generate_with_image(
                prompt=prompt,
                image_data=image_data,
                max_new_tokens=512
            )
            
            return {
                "success": True,
                "analysis": result.get("text", ""),
                "audio_summary": self._summarize_audio(audio_text),
                "error": None
            }
            
        except Exception as e:
            logger.error(f"联合分析失败: {str(e)}")
            return {
                "success": False,
                "analysis": "",
                "audio_summary": "",
                "error": str(e)
            }
    
    def _summarize_audio(self, audio_text: str, max_length: int = 200) -> str:
        """简单总结音频文本"""
        if len(audio_text) <= max_length:
            return audio_text
        
        # 简单的总结逻辑:取开头和结尾
        words = audio_text.split()
        if len(words) > 20:
            summary = " ".join(words[:10]) + " ... " + " ".join(words[-10:])
        else:
            summary = audio_text[:max_length] + "..."
        
        return summary
    
    def process_multimodal_chat(
        self,
        messages: list,
        image_data: Optional[bytes] = None,
        audio_data: Optional[bytes] = None,
        audio_language: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        处理多模态聊天(文本+图片+语音)
        
        Args:
            messages: 聊天消息历史
            image_data: 图片数据
            audio_data: 音频数据
            audio_language: 音频语言
            
        Returns:
            聊天回复
        """
        try:
            # 如果有音频数据,先转成文字
            audio_text = ""
            if audio_data:
                from audio_processor import get_audio_processor
                processor = get_audio_processor()
                transcription = processor.transcribe_bytes(
                    audio_data, 
                    language=audio_language
                )
                
                if transcription["success"]:
                    audio_text = transcription["text"]
                    # 将转录文本添加到消息中
                    messages.append({
                        "role": "user",
                        "content": f"[语音转录] {audio_text}"
                    })
            
            # 构建最终的消息
            final_messages = messages.copy()
            
            # 调用Qwen3.5的聊天接口
            # 这里需要根据实际的Qwen客户端接口进行调整
            if image_data:
                # 如果有图片,使用图片理解接口
                response = self.qwen_client.chat_with_image(
                    messages=final_messages,
                    image_data=image_data
                )
            else:
                # 纯文本聊天
                response = self.qwen_client.chat(
                    messages=final_messages
                )
            
            return {
                "success": True,
                "response": response,
                "audio_transcription": audio_text if audio_data else "",
                "error": None
            }
            
        except Exception as e:
            logger.error(f"多模态聊天失败: {str(e)}")
            return {
                "success": False,
                "response": "",
                "audio_transcription": "",
                "error": str(e)
            }

5. 集成到现有FastAPI应用

现在我们需要修改主应用文件,将新的功能集成进去。

5.1 修改app.py

打开现有的app.py,添加新的路由和功能:

# 在现有导入的基础上添加
import os
import tempfile
from fastapi import FastAPI, File, UploadFile, Form, HTTPException
from fastapi.responses import JSONResponse, StreamingResponse
import json
from typing import Optional, List
import base64

# 导入我们新写的模块
from audio_processor import get_audio_processor
from multimodal_analyzer import MultimodalAnalyzer

# 初始化应用(假设原有应用已经初始化)
app = FastAPI(title="Qwen3.5-27B with Whisper Extension")

# 初始化处理器
audio_processor = get_audio_processor(model_size="base")

# 假设这是原有的Qwen客户端
# 这里需要根据你的实际实现来调整
class QwenClient:
    def __init__(self):
        # 这里应该是你原有的Qwen客户端初始化代码
        pass
    
    def generate_with_image(self, prompt: str, image_data: bytes, max_new_tokens: int = 256):
        # 这里应该是调用原有图片理解接口的代码
        # 返回格式:{"text": "生成的文本"}
        pass
    
    def chat(self, messages: List[dict], max_new_tokens: int = 256):
        # 这里应该是调用原有聊天接口的代码
        pass
    
    def chat_with_image(self, messages: List[dict], image_data: bytes, max_new_tokens: int = 256):
        # 这里应该是调用带图片的聊天接口的代码
        pass

# 创建客户端和分析器
qwen_client = QwenClient()
multimodal_analyzer = MultimodalAnalyzer(qwen_client)

# ========== 新增的API端点 ==========

@app.post("/transcribe")
async def transcribe_audio(
    audio_file: UploadFile = File(...),
    language: Optional[str] = Form(None),
    task: str = Form("transcribe")
):
    """
    语音转文字接口
    - audio_file: 音频文件(支持wav, mp3, m4a等格式)
    - language: 语言代码,如"zh"、"en",不传则自动检测
    - task: "transcribe"(转录)或"translate"(翻译成英文)
    """
    try:
        # 读取音频文件
        audio_bytes = await audio_file.read()
        
        # 获取文件扩展名
        file_ext = audio_file.filename.split('.')[-1] if '.' in audio_file.filename else "wav"
        
        # 转录音频
        result = audio_processor.transcribe_bytes(
            audio_bytes=audio_bytes,
            file_extension=file_ext,
            language=language,
            task=task
        )
        
        if not result["success"]:
            raise HTTPException(status_code=500, detail=f"转录失败: {result['error']}")
        
        return JSONResponse(content=result)
        
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"处理失败: {str(e)}")

@app.post("/analyze_audio_image")
async def analyze_audio_image(
    audio_file: UploadFile = File(...),
    image_file: UploadFile = File(...),
    language: Optional[str] = Form(None),
    custom_prompt: Optional[str] = Form(None)
):
    """
    语音+图片联合分析接口
    - audio_file: 音频文件
    - image_file: 图片文件
    - language: 音频语言
    - custom_prompt: 自定义分析提示词
    """
    try:
        # 读取音频文件并转录
        audio_bytes = await audio_file.read()
        transcription = audio_processor.transcribe_bytes(
            audio_bytes=audio_bytes,
            language=language
        )
        
        if not transcription["success"]:
            raise HTTPException(status_code=500, detail=f"音频转录失败: {transcription['error']}")
        
        # 读取图片文件
        image_bytes = await image_file.read()
        
        # 验证图片格式
        try:
            from PIL import Image
            image = Image.open(io.BytesIO(image_bytes))
            image.verify()  # 验证图片完整性
        except Exception as e:
            raise HTTPException(status_code=400, detail=f"图片格式无效: {str(e)}")
        
        # 联合分析
        analysis_result = multimodal_analyzer.analyze_audio_and_image(
            audio_text=transcription["text"],
            image_data=image_bytes,
            prompt_template=custom_prompt
        )
        
        if not analysis_result["success"]:
            raise HTTPException(status_code=500, detail=f"分析失败: {analysis_result['error']}")
        
        # 返回完整结果
        return JSONResponse(content={
            "success": True,
            "audio_transcription": transcription["text"],
            "audio_language": transcription["language"],
            "analysis": analysis_result["analysis"],
            "audio_summary": analysis_result["audio_summary"],
            "image_format": image_file.content_type,
            "image_size": len(image_bytes)
        })
        
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"处理失败: {str(e)}")

@app.post("/multimodal_chat")
async def multimodal_chat(
    messages: str = Form(...),  # JSON字符串格式的消息历史
    image_file: Optional[UploadFile] = File(None),
    audio_file: Optional[UploadFile] = File(None),
    audio_language: Optional[str] = Form(None),
    max_new_tokens: int = Form(256)
):
    """
    多模态聊天接口(支持文本、图片、语音)
    - messages: 聊天消息历史,JSON格式
    - image_file: 可选,图片文件
    - audio_file: 可选,音频文件
    - audio_language: 音频语言
    - max_new_tokens: 最大生成长度
    """
    try:
        # 解析消息历史
        try:
            message_list = json.loads(messages)
        except json.JSONDecodeError:
            raise HTTPException(status_code=400, detail="消息格式无效,必须是JSON数组")
        
        # 处理音频文件
        audio_data = None
        if audio_file:
            audio_data = await audio_file.read()
        
        # 处理图片文件
        image_data = None
        if image_file:
            image_data = await image_file.read()
            # 验证图片
            try:
                from PIL import Image
                image = Image.open(io.BytesIO(image_data))
                image.verify()
            except Exception as e:
                raise HTTPException(status_code=400, detail=f"图片格式无效: {str(e)}")
        
        # 调用多模态分析器
        chat_result = multimodal_analyzer.process_multimodal_chat(
            messages=message_list,
            image_data=image_data,
            audio_data=audio_data,
            audio_language=audio_language
        )
        
        if not chat_result["success"]:
            raise HTTPException(status_code=500, detail=f"聊天失败: {chat_result['error']}")
        
        return JSONResponse(content=chat_result)
        
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"处理失败: {str(e)}")

@app.get("/supported_languages")
async def get_supported_languages():
    """获取支持的语言列表"""
    languages = audio_processor.get_supported_languages()
    return JSONResponse(content={"languages": languages})

# ========== 原有的API端点保持不变 ==========
# 这里是你原有的 /generate, /generate_with_image, /chat_stream 等端点
# ...

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=7860)

5.2 更新requirements.txt

确保所有依赖都记录在requirements.txt中:

# 原有依赖
torch>=2.0.0
transformers>=4.35.0
fastapi>=0.100.0
uvicorn>=0.24.0
# ... 其他原有依赖

# 新增依赖
openai-whisper>=20231117
ffmpeg-python>=0.2.0
soundfile>=0.12.1
librosa>=0.10.0
python-multipart>=0.0.6
Pillow>=10.0.0

6. 测试与验证

现在让我们测试新集成的功能是否正常工作。

6.1 重启服务

首先重启FastAPI服务:

# 进入服务目录
cd /opt/qwen3527-27b

# 重启服务(根据你的部署方式)
supervisorctl restart qwen3527

# 查看日志确认启动正常
tail -f /root/workspace/qwen3527.log

6.2 测试语音转文字接口

创建一个测试脚本test_audio.py

# test_audio.py
import requests
import json

# 测试音频转录
def test_transcribe():
    url = "http://127.0.0.1:7860/transcribe"
    
    # 准备测试音频文件(需要先准备一个test.wav)
    files = {
        'audio_file': open('test.wav', 'rb')
    }
    
    data = {
        'language': 'zh',
        'task': 'transcribe'
    }
    
    response = requests.post(url, files=files, data=data)
    
    if response.status_code == 200:
        result = response.json()
        print("转录成功!")
        print(f"识别文本: {result['text']}")
        print(f"识别语言: {result['language']}")
    else:
        print(f"请求失败: {response.status_code}")
        print(response.text)

# 测试联合分析
def test_analyze_audio_image():
    url = "http://127.0.0.1:7860/analyze_audio_image"
    
    # 准备测试文件
    files = {
        'audio_file': open('test.wav', 'rb'),
        'image_file': open('test.jpg', 'rb')
    }
    
    data = {
        'language': 'zh'
    }
    
    response = requests.post(url, files=files, data=data)
    
    if response.status_code == 200:
        result = response.json()
        print("联合分析成功!")
        print(f"音频转录: {result['audio_transcription'][:100]}...")
        print(f"分析结果: {result['analysis'][:200]}...")
    else:
        print(f"请求失败: {response.status_code}")
        print(response.text)

if __name__ == "__main__":
    print("测试语音转文字接口...")
    test_transcribe()
    
    print("\n测试联合分析接口...")
    # test_analyze_audio_image()  # 需要准备测试图片

6.3 使用curl命令测试

如果你没有Python环境,也可以用curl测试:

# 测试语音转文字
curl -X POST http://127.0.0.1:7860/transcribe \
  -F "audio_file=@/path/to/audio.wav" \
  -F "language=zh"

# 测试联合分析
curl -X POST http://127.0.0.1:7860/analyze_audio_image \
  -F "audio_file=@/path/to/audio.wav" \
  -F "image_file=@/path/to/image.jpg" \
  -F "language=zh"

# 获取支持的语言列表
curl http://127.0.0.1:7860/supported_languages

6.4 创建简单的Web测试界面

为了方便测试,我们可以创建一个简单的HTML页面:

<!-- test_multimodal.html -->
<!DOCTYPE html>
<html>
<head>
    <title>多模态功能测试</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
        .section { margin-bottom: 30px; padding: 20px; border: 1px solid #ddd; border-radius: 5px; }
        h2 { color: #333; }
        input, textarea, button { margin: 10px 0; padding: 10px; width: 100%; box-sizing: border-box; }
        button { background: #007bff; color: white; border: none; cursor: pointer; }
        button:hover { background: #0056b3; }
        .result { background: #f8f9fa; padding: 15px; margin-top: 10px; border-radius: 5px; white-space: pre-wrap; }
    </style>
</head>
<body>
    <h1>Qwen3.5-27B多模态功能测试</h1>
    
    <div class="section">
        <h2>1. 语音转文字</h2>
        <input type="file" id="audioFile" accept="audio/*">
        <select id="language">
            <option value="">自动检测</option>
            <option value="zh">中文</option>
            <option value="en">英语</option>
        </select>
        <button onclick="transcribe()">开始转录</button>
        <div id="transcribeResult" class="result"></div>
    </div>
    
    <div class="section">
        <h2>2. 语音+图片联合分析</h2>
        <p>音频文件:<input type="file" id="audioFile2" accept="audio/*"></p>
        <p>图片文件:<input type="file" id="imageFile" accept="image/*"></p>
        <button onclick="analyze()">开始分析</button>
        <div id="analyzeResult" class="result"></div>
    </div>
    
    <script>
        const API_BASE = 'http://127.0.0.1:7860';
        
        async function transcribe() {
            const fileInput = document.getElementById('audioFile');
            const language = document.getElementById('language').value;
            const resultDiv = document.getElementById('transcribeResult');
            
            if (!fileInput.files[0]) {
                alert('请选择音频文件');
                return;
            }
            
            resultDiv.textContent = '处理中...';
            
            const formData = new FormData();
            formData.append('audio_file', fileInput.files[0]);
            if (language) {
                formData.append('language', language);
            }
            
            try {
                const response = await fetch(`${API_BASE}/transcribe`, {
                    method: 'POST',
                    body: formData
                });
                
                const result = await response.json();
                resultDiv.textContent = JSON.stringify(result, null, 2);
            } catch (error) {
                resultDiv.textContent = '错误: ' + error.message;
            }
        }
        
        async function analyze() {
            const audioFile = document.getElementById('audioFile2').files[0];
            const imageFile = document.getElementById('imageFile').files[0];
            const resultDiv = document.getElementById('analyzeResult');
            
            if (!audioFile || !imageFile) {
                alert('请选择音频和图片文件');
                return;
            }
            
            resultDiv.textContent = '处理中...';
            
            const formData = new FormData();
            formData.append('audio_file', audioFile);
            formData.append('image_file', imageFile);
            
            try {
                const response = await fetch(`${API_BASE}/analyze_audio_image`, {
                    method: 'POST',
                    body: formData
                });
                
                const result = await response.json();
                resultDiv.textContent = JSON.stringify(result, null, 2);
            } catch (error) {
                resultDiv.textContent = '错误: ' + error.message;
            }
        }
    </script>
</body>
</html>

7. 实际应用场景示例

现在我们的扩展已经完成,来看看它能解决哪些实际问题。

7.1 场景一:会议纪要自动生成

假设你有一个团队会议录像,包含语音和共享的PPT截图:

# meeting_minutes.py
import requests
import os

class MeetingMinutesGenerator:
    def __init__(self, api_url="http://127.0.0.1:7860"):
        self.api_url = api_url
    
    def generate_minutes(self, audio_path, screenshot_paths):
        """生成会议纪要"""
        
        # 1. 转录会议音频
        print("正在转录会议音频...")
        with open(audio_path, 'rb') as f:
            audio_data = f.read()
        
        # 分段处理长音频(实际应用中可能需要分段)
        transcription = self._transcribe_audio(audio_data)
        
        if not transcription:
            print("音频转录失败")
            return None
        
        print(f"音频转录完成,共{len(transcription)}字")
        
        # 2. 分析每张截图
        all_analysis = []
        for i, screenshot_path in enumerate(screenshot_paths):
            print(f"正在分析第{i+1}张截图...")
            
            with open(screenshot_path, 'rb') as f:
                image_data = f.read()
            
            # 联合分析音频和截图
            analysis = self._analyze_audio_image(
                audio_text=transcription,
                image_data=image_data,
                custom_prompt="""这是会议中的一个PPT截图,请结合刚才的会议讨论内容:
1. 描述这张PPT的主要内容
2. 分析讨论内容与PPT的关联
3. 提取关键决策点或行动项"""
            )
            
            if analysis:
                all_analysis.append({
                    "slide": i+1,
                    "analysis": analysis
                })
        
        # 3. 生成完整的会议纪要
        print("正在生成完整会议纪要...")
        minutes = self._generate_summary(transcription, all_analysis)
        
        return {
            "transcription": transcription,
            "slide_analysis": all_analysis,
            "minutes": minutes
        }
    
    def _transcribe_audio(self, audio_data):
        """转录音频"""
        files = {'audio_file': ('audio.wav', audio_data, 'audio/wav')}
        
        try:
            response = requests.post(
                f"{self.api_url}/transcribe",
                files=files,
                data={'language': 'zh'}
            )
            
            if response.status_code == 200:
                result = response.json()
                return result.get('text', '')
        except Exception as e:
            print(f"转录失败: {e}")
        
        return ""
    
    def _analyze_audio_image(self, audio_text, image_data, custom_prompt):
        """联合分析音频和图片"""
        files = {
            'audio_file': ('audio.wav', b'placeholder', 'audio/wav'),
            'image_file': ('image.jpg', image_data, 'image/jpeg')
        }
        
        # 这里需要调整,因为我们的接口需要真实的音频文件
        # 实际应用中可能需要调整接口设计
        return "分析结果示例"
    
    def _generate_summary(self, transcription, slide_analysis):
        """生成总结"""
        # 这里可以调用Qwen的文本生成接口
        return "会议纪要示例"

# 使用示例
if __name__ == "__main__":
    generator = MeetingMinutesGenerator()
    
    # 假设有这些文件
    audio_file = "meeting_audio.wav"
    screenshots = ["slide1.jpg", "slide2.jpg", "slide3.jpg"]
    
    # 检查文件是否存在
    if os.path.exists(audio_file):
        result = generator.generate_minutes(audio_file, screenshots)
        if result:
            print("会议纪要生成完成!")
            print(f"转录文本长度: {len(result['transcription'])}")
            print(f"分析幻灯片数: {len(result['slide_analysis'])}")
            print(f"\n会议纪要:\n{result['minutes']}")

7.2 场景二:教育内容分析

分析教学视频,结合老师的讲解和板书/PPT:

# education_analyzer.py
class EducationContentAnalyzer:
    def analyze_lecture(self, video_audio_path, slide_images):
        """分析教学视频"""
        # 1. 提取音频并转录
        # 2. 分析每张教学幻灯片
        # 3. 生成学习要点总结
        # 4. 创建练习题建议
        pass
    
    def generate_quiz(self, content_analysis):
        """基于内容生成测验题"""
        prompt = f"""基于以下教学内容,生成5道选择题:

{content_analysis}

要求:
1. 题目覆盖核心知识点
2. 每个题目4个选项
3. 标注正确答案
4. 题目难度适中"""
        
        # 调用Qwen生成题目
        return "生成的题目"

7.3 场景三:客服质检分析

分析客服通话录音和操作截图:

# customer_service_analyzer.py
class CustomerServiceAnalyzer:
    def analyze_call(self, call_recording, screen_recording_screenshots):
        """分析客服通话"""
        # 1. 转录通话内容
        # 2. 分析客服操作截图
        # 3. 评估服务质量
        # 4. 识别改进点
        pass
    
    def generate_feedback(self, analysis_result):
        """生成客服反馈报告"""
        prompt = f"""根据以下客服通话分析,生成改进建议:

{analysis_result}

请从以下角度提供建议:
1. 沟通技巧改进
2. 问题解决效率
3. 系统操作规范
4. 客户满意度提升"""
        
        return "反馈建议"

8. 性能优化与部署建议

8.1 性能优化技巧

  1. Whisper模型选择

    • tiny:最快,精度较低,适合实时应用
    • base:平衡速度与精度(默认)
    • small/medium:更高精度,适合离线处理
    • large:最高精度,需要更多资源
  2. 音频预处理优化

def optimize_audio(audio_path):
    """优化音频处理"""
    import whisper
    from whisper.utils import get_writer
    
    # 使用GPU加速
    model = whisper.load_model("base", device="cuda")
    
    # 批量处理多个文件
    # 设置合适的参数
    result = model.transcribe(
        audio_path,
        language="zh",
        fp16=True,  # 使用半精度浮点数
        temperature=0.0,  # 确定性输出
        best_of=5,  # 多次采样取最佳
        beam_size=5  # 束搜索大小
    )
    
    return result
  1. 缓存优化
from functools import lru_cache
import hashlib

@lru_cache(maxsize=100)
def transcribe_cached(audio_hash: str, language: str):
    """带缓存的转录函数"""
    # 先检查缓存
    # 如果缓存命中,直接返回
    # 否则执行转录并缓存结果
    pass

8.2 部署配置建议

  1. 资源分配
# docker-compose.yml 示例
version: '3.8'
services:
  qwen-multimodal:
    image: qwen-whisper-integration
    ports:
      - "7860:7860"
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 2  # 分配2个GPU
              capabilities: [gpu]
    environment:
      - WHISPER_MODEL_SIZE=base
      - MAX_WORKERS=4
      - CUDA_VISIBLE_DEVICES=0,1
  1. 监控配置
# monitoring.py
import psutil
import GPUtil
from prometheus_client import start_http_server, Gauge

# 监控指标
cpu_usage = Gauge('cpu_usage_percent', 'CPU使用率')
memory_usage = Gauge('memory_usage_percent', '内存使用率')
gpu_usage = Gauge('gpu_usage_percent', 'GPU使用率')
request_count = Gauge('request_count', '请求数量')

def monitor_resources():
    """监控系统资源"""
    # CPU使用率
    cpu_usage.set(psutil.cpu_percent())
    
    # 内存使用率
    memory = psutil.virtual_memory()
    memory_usage.set(memory.percent)
    
    # GPU使用率
    try:
        gpus = GPUtil.getGPUs()
        for gpu in gpus:
            gpu_usage.set(gpu.load * 100)
    except:
        pass

8.3 扩展性考虑

  1. 支持更多音频格式
SUPPORTED_AUDIO_FORMATS = {
    '.wav': 'wav',
    '.mp3': 'mp3',
    '.m4a': 'm4a',
    '.flac': 'flac',
    '.ogg': 'ogg'
}

def convert_audio_format(input_path, output_format='wav'):
    """转换音频格式"""
    import subprocess
    # 使用ffmpeg转换
    pass
  1. 流式处理支持
@app.post("/transcribe_stream")
async def transcribe_stream(audio_chunk: bytes = File(...)):
    """流式语音识别"""
    # 处理音频流
    # 实时返回部分结果
    pass
  1. 批量处理接口
@app.post("/batch_transcribe")
async def batch_transcribe(files: List[UploadFile] = File(...)):
    """批量转录"""
    results = []
    for file in files:
        # 并行处理多个文件
        pass
    return results

9. 总结

通过本文的实践,我们成功将Whisper语音识别能力集成到Qwen3.5-27B镜像中,构建了一个强大的多模态分析系统。这个扩展让Qwen3.5不仅能够"看懂"图片,还能"听懂"声音,实现了真正的视听联合分析。

9.1 主要成果回顾

  1. 功能扩展:在原有图片理解基础上,新增了语音转文字能力
  2. API集成:设计了三个新的API端点,保持与原有接口的一致性
  3. 实用场景:展示了会议纪要、教育分析、客服质检等实际应用
  4. 易于部署:提供了完整的代码和配置,开箱即用

9.2 核心价值

这个扩展方案的核心价值在于:

  • 降低使用门槛:用户无需分别部署语音识别和图像理解服务
  • 提升分析深度:联合分析比单独分析能获得更深入的洞察
  • 提高工作效率:自动化处理原本需要人工完成的繁琐任务
  • 灵活可扩展:架构设计支持后续添加更多模态(如视频分析)

9.3 后续优化方向

如果你已经成功部署并运行了这个扩展,可以考虑以下优化方向:

  1. 性能优化:使用更大的Whisper模型提高识别精度,或使用量化版本减少资源占用
  2. 功能增强:添加实时流式识别、支持更多音频格式、增加语音合成能力
  3. 用户体验:开发更友好的Web界面,支持拖拽上传、实时预览等功能
  4. 企业级特性:添加用户认证、访问控制、使用量统计等特性

9.4 开始使用建议

对于想要立即尝试的用户,建议:

  1. 从简单开始:先用小音频文件测试,熟悉接口使用
  2. 逐步扩展:从单一功能开始,逐步尝试联合分析
  3. 关注资源:监控GPU内存使用,根据需求调整模型大小
  4. 结合实际:思考如何将这个能力应用到自己的业务场景中

这个扩展方案展示了AI多模态能力的强大潜力。通过简单的集成,我们就能让大模型具备更全面的感知能力,为各种创新应用打开了大门。无论是内容创作、教育培训,还是企业办公,这种视听结合的分析能力都能带来显著的效率提升。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐