news 2026/9/29 18:34:05

Django实战:构建语音识别智能垃圾分类系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Django实战:构建语音识别智能垃圾分类系统

简介:这是一份基于Django与语音识别技术的智能垃圾分类系统项目源码,适用于计算机毕业设计、课程设计及Python项目实战练习,也可供对语音交互和Web开发感兴趣的开发者学习参考。压缩包仅9.4MB,共305个文件,主要由31个Python源文件、14个HTML模板、22个CSS样式、35个JavaScript脚本,以及GIF演示图、PNG图标、字体资源、音频示例、说明文档和演示视频组成。目前已有388人浏览学习,项目功能形成前后台闭环:前台支持系统信息展示、语音上传垃圾分类、用户注册登录、资料修改与退出;后台支持垃圾分类管理、用户信息维护和系统首页配置。源码亲测可用,附带SQL数据库文件,部署后可快速体验完整流程;目录结构清晰,便于继续扩展识别算法、优化界面或改造为其他分类应用,是理解Django全栈开发与语音识别集成方式的优质参考。

1. 从“鸡蛋壳是什么垃圾”到语音分类:这个 Django 项目到底在做什么

你在小区垃圾桶前站过十秒钟,掏出手机搜“粽叶是什么垃圾”吗?智能垃圾分类系统的核心诉求就是这样:用最短的操作路径,让用户说出垃圾名称,系统立刻回一个可回收、厨余、有害还是其他。这个标题里的 Django 项目,做的就是这件事——用户按住录音说“鸡蛋壳”,后端把音频转成文字,再通过词库匹配出“厨余垃圾”,把结果返回页面。它的技术主干是 Django + 语音识别(ASR)+ 分类匹配,适合三种人:想找一个不那么“学生管理系统”的 Django 实战选题的人,课程设计或毕业设计需要完整源码加演示链路的人,以及想搞明白“语音识别结果怎么接到 Web 后端里”的开发者。这套东西不需要你训练语音模型,用现成识别引擎加一份词库就能跑起来,难点不在算法,而在把录音、转码、识别、匹配这条链路接顺。

2. 先立骨架:Django 项目结构、数据模型与语音识别方案选型

2.1 三个语音识别方案怎么选:在线 API、本地 Vosk、免训练规则表

做语音识别接入之前,先要决定识别引擎。常见做法是三条路,各有各的坑。

第一类是百度、讯飞这类短语音识别 API。准确率高,中文效果好,但需要注册应用拿 key,有 QPS 限制,免费额度用完就得付费。演示时一旦网络抖动,接口超时,页面就卡住。适合毕设答辩这种“必须一次成功”的场合,前提是你接受把密钥放在配置里。

第二类是 Vosk 这类本地离线识别引擎。下载一个中文小模型,解码在本地完成,不依赖网络,响应稳定。代价是模型小,识别准确率明显不如大厂的云端引擎,同音字错误很常见,“报纸”能给你识别成“抱子”,“鸡蛋壳”识别成“鸡但可”。所以用 Vosk 的话,后面匹配层必须做容错,不能指望 ASR 给你绝对准确的文本。

第三类是纯规则表,也就是不接语音识别,前端用下拉框选垃圾名称,后端直接匹配类别。这条路学习成本最低,但没碰到音频处理,语音识别这个技术点就丢了,答辩时容易被问穿。

我给这个项目的建议是:主通道用 Vosk 本地识别,同时预留一个 API 识别的配置开关。Vosk 保证演示不依赖外网,API 留作效果不佳时一键切换。代码上做一个函数封装,两个实现互相替换,不牵扯业务逻辑。

2.2 django-admin 创建项目与 app:目录划分

先建虚拟环境,装依赖,创建项目和 app。以下是完整命令序列:

# 创建并激活虚拟环境,Python 版本建议 3.9 以上 python -m venv venv source venv/bin/activate # Windows 下是 venv\Scripts\activate # 安装核心依赖 pip install django djangorestframework vosk pypinyin django-cors-headers # 创建项目,项目名直接叫 intelligent_garbage django-admin startproject intelligent_garbage . # 创建业务 app,名字叫 voice_garbage,便于一看就懂 python manage.py startapp voice_garbage # 验证项目能启动 python manage.py runserver

startproject后面有个点,表示在当前目录生成 manage.py 和配置包,不要漏。app 命名为voice_garbage而不是garbage,是为了把“语音”这个入口语义留在名字里。pypinyin是后面做拼音模糊匹配用的,django-cors-headers解决前端跨端口访问的问题,这两个依赖后面都会用到。

创建完 app 后,在settings.py的INSTALLED_APPS里加上voice_garbage和corsheaders,这是新手最容易漏的一步。漏了之后,后面python manage.py makemigrations会提示没有检测到模型变更,或者请求时报 500,都是因为 app 没注册。

2.3 数据模型:垃圾词库表、识别记录表

垃圾分类系统的核心数据模型就是词库表。每一条记录是一个标准垃圾名称,加上类别、别名、积分字段:

# voice_garbage/models.py from django.db import models class GarbageItem(models.Model): """垃圾词库表:标准名称 + 类别 + 别名""" name = models.CharField(max_length=50, unique=True, verbose_name="标准名称") category = models.CharField(max_length=20, verbose_name="垃圾类别") # 可回收/厨余/有害/其他 aliases = models.CharField(max_length=200, blank=True, verbose_name="别名") points = models.IntegerField(default=0, verbose_name="积分") created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") class Meta: db_table = "garbage_item" indexes = [models.Index(fields=["name"])] def __str__(self): return f"{self.name}({self.category})" class RecognizeRecord(models.Model): """识别记录表:记录每次语音识别的文本与分类结果""" text = models.CharField(max_length=100, verbose_name="识别文本") result = models.CharField(max_length=20, verbose_name="分类结果") created_at = models.DateTimeField(auto_now_add=True, verbose_name="识别时间") class Meta: db_table = "recognize_record" ordering = ["-created_at"]

aliases字段是匹配策略的关键。Vosk 识别出的文本可能不是词库里的标准名称,比如用户说“蛋壳”,库里的标准名是“鸡蛋壳”,靠别名表就能命中。recognize_record表用来做演示数据展示,答辩时可以打开后台,给评委看今天识别了多少条、分类准确率如何。

写完模型后执行python manage.py makemigrations voice_garbage和python manage.py migrate。如果提示表已存在,就手动清理数据库再迁移,不要加--fake蒙混过去,后面遇到数据对不上会很难排查。

2.4 词库数据从哪来:写一个 CSV 导入脚本

词库表是空的不行,得灌数据。手工一条一条在 admin 里加太慢,常规做法是准备一份 CSV 然后写 management command 批量导入:

# voice_garbage/management/commands/import_garbage.py import csv from django.core.management.base import BaseCommand from voice_garbage.models import GarbageItem class Command(BaseCommand): help = "从 CSV 导入垃圾词库,CSV 列: name,category,aliases" def add_arguments(self, parser): parser.add_argument("csv_path", type=str) def handle(self, *args, **options): path = options["csv_path"] count = 0 with open(path, encoding="utf-8-sig") as f: reader = csv.DictReader(f) for row in reader: GarbageItem.objects.update_or_create( name=row["name"].strip(), defaults={ "category": row["category"].strip(), "aliases": row.get("aliases", "").strip(), }, ) count += 1 self.stdout.write(self.style.SUCCESS(f"词库导入完成,共处理 {count} 条"))

调用方式:

python manage.py import_garbage garbage_data.csv

CSV 编码用utf-8-sig而不是utf-8,是因为 Excel 导出的 CSV 自带 BOM 头,用utf-8读第一列会多一个不可见字符导致匹配失败。update_or_create保证重复执行脚本不会产生重复记录,词库扩充时直接覆盖运行即可。这个脚本建议从一开始就写,后面调试分类时你会不断往 CSV 里加词条重新导入,比手动一条条敲快得多。

3. 把语音变成文字:MediaRecorder 录音、Vosk 识别与转码处理

3.1 前端录音:MediaRecorder 拿 WebM 格式音频

用户的语音从哪里来?最省事的前端方案是MediaRecorder接口。它在浏览器里直接调用麦克风,录完拿到 Blob,不需要额外插件:

<!-- voice_garbage/templates/voice_garbage/index.html 片段 --> <button id="recordBtn">按住说话</button> <input type="hidden" id="audioBase64" /> <script> const recordBtn = document.getElementById('recordBtn'); let mediaRecorder = null; let chunks = []; recordBtn.addEventListener('mousedown', async () => { chunks = []; const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); mediaRecorder = new MediaRecorder(stream); mediaRecorder.ondataavailable = (e) => chunks.push(e.data); mediaRecorder.onstop = () => { const blob = new Blob(chunks, { type: 'audio/webm' }); const reader = new FileReader(); reader.onload = () => { document.getElementById('audioBase64').value = reader.result; }; reader.readAsDataURL(blob); }; mediaRecorder.start(); }); recordBtn.addEventListener('mouseup', () => { mediaRecorder.stop(); }); </script>

FileReader.readAsDataURL会把音频转成 base64 字符串,里面带了data:audio/webm;base64,前缀,后端接收时要把前缀剥掉。这里有个体验细节:按住说话用mousedown开始录音、mouseup停止,比点击两次按钮更自然。移动端触摸事件要额外兼容touchstart/touchend,否则手机浏览器录不了。

MediaRecorder 录出来的格式是 webm,这是坑的起点——后端如果用 pyaudio 直接读,会报“Unknown format”。必须先用 ffmpeg 转成 WAV 再喂给识别引擎。

3.2 Django 视图接收音频:base64 解码与接口设计

前端把音频转成 base64 后,通过 POST 表单传给 Django。视图层这样接:

# voice_garbage/views.py import base64 from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from .recognizer import recognize_audio from .classifier import classify_garbage @csrf_exempt def process_voice(request): """接收音频,完成识别+分类,一次返回结果""" if request.method != "POST": return JsonResponse({"error": "仅支持POST"}, status=405) audio_b64 = request.POST.get("audio_base64", "") if not audio_b64: return JsonResponse({"error": "缺少 audio_base64 参数"}, status=400) # 去掉 Data URL 前缀 if audio_b64.startswith("data:"): audio_b64 = audio_b64.split(",", 1)[1] audio_bytes = base64.b64decode(audio_b64) text = recognize_audio(audio_bytes) if not text: return JsonResponse({"error": "未识别到语音内容"}, status=422) result = classify_garbage(text) return JsonResponse({"text": text, "category": result["category"], "name": result["name"]})

这个接口把识别和分类合在了一起,前端一次 POST 就拿到最终类别。csrf_exempt仅用于本地开发演示,如果项目要部署到公网,必须换掉——要么用 Django 的 CSRF 中间件配合前端带 token,要么在登录态基础上加 JWT。我在项目里见过有人图省事全局关掉 CSRF,结果被人拿来刷接口,日志里全是垃圾请求,这就是偷懒的代价。务实做法是给这个视图单独加@csrf_exempt,其他接口保持默认。

3.3 识别函数:ffmpeg 转码 + Vosk 本地识别

后端拿到的是 webm 二进制,先写临时文件,用 ffmpeg 转成 16kHz 单声道 WAV,再交给 Vosk:

# voice_garbage/recognizer.py import json import subprocess import tempfile import wave from pathlib import Path from django.conf import settings from vosk import Model, KaldiRecognizer # 模型路径用 BASE_DIR 拼接,避免工作目录不一致导致找不到模型 MODEL_PATH = Path(settings.BASE_DIR) / "model" / "vosk-model-small-cn-0.22" def recognize_audio(audio_bytes: bytes) -> str: """接收 webm 音频字节,返回识别文本""" # 写临时文件 with tempfile.NamedTemporaryFile(suffix=".webm", delete=False) as tmp: tmp.write(audio_bytes) webm_path = tmp.name wav_path = webm_path.replace(".webm", ".wav") # ffmpeg 转码:16kHz 采样率、单声道、16bit PCM subprocess.run( [ "ffmpeg", "-y", "-i", webm_path, "-ar", "16000", "-ac", "1", "-c:a", "pcm_s16le", wav_path, ], check=True, capture_output=True, ) # Vosk 解码 model = Model(str(MODEL_PATH)) recognizer = KaldiRecognizer(model, 16000) with wave.open(wav_path, "rb") as wf: while True: data = wf.readframes(4000) if len(data) == 0: break recognizer.AcceptWaveform(data) result = json.loads(recognizer.FinalResult()) return result.get("text", "").strip()

-ar 16000是采样率,语音识别模型基本都按 16kHz 训练,你不转它就只能识别出乱码。-ac 1是单声道,双声道音频会让识别结果重复或丢字。-c:a pcm_s16le显式指定输出编码为 16 位 PCM,避免 ffmpeg 按默认编码输出成其他格式。tempfile.NamedTemporaryFile用完别急着删,Vosk 是流式读取,文件句柄关闭后读不到内容;调试时留着临时文件反而方便检查。

识别启动后如果返回空字符串,先看日志里有没有“Model not found”。这个问题我踩过两次,都是因为Model("model/...")用了相对路径,而 Django 启动目录和命令执行目录不一致。用settings.BASE_DIR拼绝对路径就不会有这种玄学问题。

3.4 在线 API 识别:Vosk 效果差时的备用通道

Vosk 小模型遇到生僻词、方言、环境噪声大时识别结果基本没法看。这时候可以切在线 API。下面是通用的调用模板:

# voice_garbage/recognizer.py 追加 import requests def recognize_by_api(audio_bytes: bytes, api_url: str, api_token: str) -> str: """调云端短语音识别接口,返回文本""" files = {"audio": audio_bytes} headers = {"Authorization": f"Bearer {api_token}"} resp = requests.post(api_url, headers=headers, files=files, timeout=10) resp.raise_for_status() data = resp.json() return data.get("result", {}).get("text", "").strip()

具体请求参数要以你选的 API 文档为准,但结构基本一致:音频二进制、鉴权头、超时控制。调用前先确认音频采样率是否符合接口要求,有的接口不接收 webm,那就得复用前面的 ffmpeg 转码结果再上传。密钥不要硬编码进代码,从settings.py里读环境变量,上线时在服务器环境里配置,代码仓库里不放任何真实 token。我在自己的项目里习惯把USE_API = False写在 settings 里,切换识别通道只需改这个布尔值,不动业务代码。

4. 把文字变成垃圾类别:三层匹配策略、视图整合与后台词库管理

4.1 三层匹配:精确 → 别名 → 拼音模糊

ASR 识别出来的文本不能直接拿去查数据库。用户可能说“鸡蛋壳”也可能说“蛋壳”,Vosk 可能把“报纸”识别成“抱子”。所以分类层要做三级匹配:

# voice_garbage/classifier.py import re from difflib import SequenceMatcher from pypinyin import lazy_pinyin from voice_garbage.models import GarbageItem def classify_garbage(spoken_text: str) -> dict: """把语音识别文本映射为垃圾类别,返回类别和标准名称""" text = re.sub(r"[\s,。,.!?、]", "", spoken_text) if not text: return {"category": "未识别", "name": ""} # 第一层:精确匹配标准名称 item = GarbageItem.objects.filter(name=text).first() if item: return {"category": item.category, "name": item.name} # 第二层:别名匹配 for item in GarbageItem.objects.all(): aliases = [a.strip() for a in item.aliases.split(",") if a.strip()] if text in aliases: return {"category": item.category, "name": item.name} # 第三层:拼音模糊匹配,抵消同音字错误 spoken_py = "".join(lazy_pinyin(text)) best_item = None best_score = 0.0 for item in GarbageItem.objects.all(): name_py = "".join(lazy_pinyin(item.name)) score = SequenceMatcher(None, spoken_py, name_py).ratio() if score > best_score: best_score = score best_item = item if best_item and best_score >= 0.8: return {"category": best_item.category, "name": best_item.name} return {"category": "未识别", "name": text}

第一层直接查name字段,走索引,速度最快。第二层遍历别名表,适合“蛋壳 vs 鸡蛋壳”这种说法差异。第三层是这张表的灵魂——把文本转成拼音再做相似度比较,比如 Vosk 识别出“抱子”,转成拼音baozi,跟“报纸”的baozhi相似度很高,阈值 0.8 时能命中。

阈值 0.8 不是拍脑袋定的。我试过 0.9,Vosk 同音错误稍微多一点就漏了;试过 0.7,会把“香蕉皮”匹配成“香蕉”。0.8 是个折中点。实际项目里你可以在后台看到识别记录,收集一段时间后把误匹配数据攒着,按自己词库规模微调这个值。

这里有人会想到训练一个分类模型,把语音文本丢给朴素贝叶斯或 BERT。对一个演示级项目来说没有这个必要,规则引擎足够应付常见垃圾名称,而且每个分支都能解释清楚,答辩时不会被问倒。想深入语音识别训练模型并推理的教程,那是另一条赛道,别在这个项目里铺太开。

4.2 分类结果保存与积分逻辑

匹配完成之后记录一条历史。修改views.py里的process_voice,加保存逻辑:

# voice_garbage/views.py(在 classify 之后追加) from .models import GarbageItem, RecognizeRecord # 接前面的 classify_garbage 调用 result = classify_garbage(text) RecognizeRecord.objects.create(text=text, result=result["category"]) # 命中标准名称时给用户加分,演示用 if result["category"] != "未识别": item = GarbageItem.objects.filter(name=result["name"]).first() if item: score = item.points else: score = GarbageItem.objects.filter( category=result["category"] ).first().points if GarbageItem.objects.filter(category=result["category"]).exists() else 1 else: score = 0 return JsonResponse({ "text": text, "category": result["category"], "name": result["name"], "points": score, })

积分逻辑很简单:命中的词条有自己定义的积分,没命中但分类成功时取分类下任意词条的积分兜底。代码写到这里时你会发现一件事——垃圾类别的积分最好是只从GarbageItem里取,不要把积分字段写在另一个表里,否则两边数据不一致,后面改词库时会很痛苦。

4.3 前端结果展示:局部刷新不整页跳转

前端拿到 JSON 后只在结果卡片上更新内容,不要让页面刷新。整页刷新会导致录音状态丢失、麦克风权限重新申请,体验很差。

// index.html 里的提交函数 function submitAudio(audioBase64) { const form = new FormData(); form.append('audio_base64', audioBase64); fetch('/voice_garbage/process/', { method: 'POST', body: form }) .then(response => response.json()) .then(data => { const card = document.getElementById('resultCard'); card.style.display = 'block'; document.getElementById('categoryText').innerText = data.category; document.getElementById('nameText').innerText = data.name; document.getElementById('pointsText').innerText = data.points || 0; // 分类成功时给卡片加不同的颜色 const colors = { '可回收': '#4CAF50', '厨余垃圾': '#FF9800', '有害垃圾': '#F44336', '其他垃圾': '#607D8B', '未识别': '#9E9E9E' }; card.style.borderLeftColor = colors[data.category] || '#9E9E9E'; }); }

这个代码块里的视觉反馈是可选的,但建议保留——你给评委演示时,按类别变色能让人一眼看出系统“懂”了。如果前端报跨域错误,就是前面 2.2 里说的django-cors-headers没配好,回到 settings 里检查CORS_ALLOWED_ORIGINS。

4.4 Django admin:后台直接维护词库,运营入口别靠 SQL

词库管理是日常维护最频繁的地方,给 admin 注册好表格,就能在后台里直接增删改查:

# voice_garbage/admin.py from django.contrib import admin from .models import GarbageItem, RecognizeRecord @admin.register(GarbageItem) class GarbageItemAdmin(admin.ModelAdmin): list_display = ("name", "category", "aliases", "points") list_filter = ("category",) search_fields = ("name", "aliases") @admin.register(RecognizeRecord) class RecognizeRecordAdmin(admin.ModelAdmin): list_display = ("text", "result", "created_at") list_filter = ("result",)

list_filter按垃圾类别过滤,方便你查看某个类别下有多少词条;search_fields填name和aliases,这样搜索“蛋壳”也能找到“鸡蛋壳”那条记录。如果觉得 Django 原生后台太素,可以装django-unfold换个现代的 admin 皮肤,纯视觉增强,不改业务代码。我个人的习惯是词库数据主要靠 CSV 批量导入管理,admin 只用来做单条修正,因为批量操作脚本比手工点击可靠得多。

5. Django 语音识别联动避坑:4 个高频翻车点与排查命令

5.1 Model not found:模型路径随着启动目录变化

现象:服务能启动,但一调用识别函数就报Model not found,或者识别结果永远是空的。

原因:Vosk 的Model()接收的是相对路径字符串,而 Django 项目在python manage.py runserver启动时,工作目录可能是你执行命令的目录,不一定是项目根目录。我在项目根目录下能跑通,换个终端从intelligent_garbage/子目录再跑一次,同样的代码就找不到模型了。这就是典型的环境依赖问题,不是代码逻辑错。

解决:不要用相对路径,用settings.BASE_DIR拼绝对路径。同时建议在settings.py里加一个VOSK_MODEL_PATH配置项:

# settings.py import os from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent VOSK_MODEL_PATH = os.environ.get("VOSK_MODEL_PATH", str(BASE_DIR / "model" / "vosk-model-small-cn-0.22"))

然后在recognizer.py里读这个配置。这样部署到服务器时,模型放别处也能通过环境变量指定路径,不用改代码。

5.2 转码失败:webm 只是容器,编码格式才是真坑

现象:前端录音传上来后,后端 ffmpeg 转码报错,或者生成的 wav 文件是 0 字节,识别接口 500。

原因:MediaRecorder 在不同浏览器里产出的 webm 内部编码不同。Chrome 默认用 opus,Safari 可能产出不同的音频轨道参数。ffmpeg 只给输入文件路径时,面对某些 opus 参数组合会解析失败。另外一种情况是 ffmpeg 根本没安装,命令行执行直接报No such file or directory。

解决:给 ffmpeg 加上显式的输出参数,同时第一时间检查 ffmpeg 是否可用:

# 第一步:确认 ffmpeg 装了 ffmpeg -version | head -1 # 第二步:手动用一条命令验证转码 ffmpeg -y -i input.webm -ar 16000 -ac 1 -c:a pcm_s16le output.wav

在代码里还要加异常捕获,ffmpeg 失败时返回一个明确的错误信息而不是让整个接口 500。日志里打出capture_output的内容,能看到 ffmpeg 具体报了什么错——我就是靠这条日志发现是输出路径权限问题,临时目录不可写导致转码失败。

5.3 同音字识别不稳定:Vosk 输出受环境和语速影响大

现象:同一句话“报纸”,上午识别成“报纸”,下午识别成“抱子”;同一个人说快一点结果就变。这是语音识别小模型最折腾人的地方。

原因:Vosk 小模型的语言模型权重是概率性的,同音词的选择依赖上下文约束和声学特征的置信度。单个名词没有上下文,模型天然会摇摆在同音词之间。这不是 bug,是模型能力的边界。

解决:把容错放到分类层,不要试图让 ASR 输出完美文本。拼音模糊匹配派上用场了——3.3 里的classify_garbage第三层就是干这个的。另外可以维护一份词库的拼音索引,启动时预计算好放内存,别每次请求都调lazy_pinyin扫全表,词库多了以后会拖慢响应。我通常在GarbageItem里加一个name_pinyin字段,导入时算好存进去,匹配时只算用户文本的拼音。

5.4 CORS 拦截:接口通了但浏览器不认

现象:前端页面在localhost:8080,Django 在localhost:8000,POST 请求发出去后浏览器报CORS policy: No 'Access-Control-Allow-Origin'。接口本身是通的,curl 能调通,浏览器不行。

原因:浏览器的同源策略拦截了跨域请求。Django 默认不返回 CORS 响应头,所以前端拿不到响应。用 curl 测试没有这个限制,这就是为什么很多人 curl 正常、页面里死活不通。

解决:装django-cors-headers并在 settings 里配置白名单,别用CORS_ALLOW_ALL = True图省事:

# settings.py INSTALLED_APPS = [ "corsheaders", # ... ] MIDDLEWARE = [ "corsheaders.middleware.CorsMiddleware", # ... ] CORS_ALLOWED_ORIGINS = [ "http://localhost:8080", "http://127.0.0.1:8080", ]

CorsMiddleware要放在所有能返回响应的中间件前面,否则 Django 处理完请求返回响应时头已经写死了,加不上去。这个坑我排过半小时,最后是看中间件顺序才发现的。

5.5 排查命令合集:从接口到数据库一条线查下去

调试这类前后端联动的项目,我习惯从外往里查。先确认请求到没到 Django,再确认识别函数有没有输出,最后确认词库里有没有数据:

# 看请求是否到达后端,以及数据库查询被触发了多少条 python manage.py runserver --print-sql # 绕过前端,直接用 curl 传 base64 音频测识别接口 curl -X POST http://127.0.0.1:8000/voice_garbage/process/ \ -F "audio_base64=$(base64 -w 0 test.webm)" # 或直接测分类逻辑,不经过音频 curl -X POST http://127.0.0.1:8000/voice_garbage/process/ \ -d "text=鸡蛋壳" # 检查词库数据 python manage.py shell -c "from voice_garbage.models import GarbageItem; print(list(GarbageItem.objects.values('name','category')))" # 误导入了脏数据时,删除后再重新导入 python manage.py shell -c "from voice_garbage.models import GarbageItem; GarbageItem.objects.filter(category='未知').delete()" python manage.py import_garbage garbage_data.csv

--print-sql是 Django 自带日志,会把每条 ORM 查询打印出来。如果一次请求打出几百条 SQL,说明你的循环里在反复查库——比如 4.1 那个遍历全表的循环,启动时预加载到内存能省掉 99% 的查询。这个习惯对新手特别有用,能让你看到 ORM 到底执行了什么,而不是黑匣子一样跑着。

6. 用测试脚本给语音分类链路体检:从单元测试到真机验证

项目做到这里,功能链路已经通了。但要验证它是不是真稳定,得写自动化测试。Django 自带的TestCase可以直接测分类逻辑,不用起服务:

# voice_garbage/tests/test_pipeline.py from django.test import TestCase from voice_garbage.classifier import classify_garbage from voice_garbage.models import GarbageItem class ClassifierPipelineTest(TestCase): """分类链路自动化测试:覆盖精确/别名/拼音三层匹配""" def setUp(self): GarbageItem.objects.create( name="鸡蛋壳", category="厨余垃圾", aliases="蛋壳,鸡蛋白" ) GarbageItem.objects.create( name="报纸", category="可回收", aliases="" ) def test_exact_match(self): result = classify_garbage("鸡蛋壳") self.assertEqual(result["category"], "厨余垃圾") def test_alias_match(self): result = classify_garbage("蛋壳") self.assertEqual(result["category"], "厨余垃圾") def test_pinyin_fallback(self): # 模拟 Vosk 把“报纸”识别成“抱子”的情形 result = classify_garbage("抱子") self.assertEqual(result["category"], "可回收") def test_pinyin_wrong_threshold(self): # 相似度不够时不应误匹配 result = classify_garbage("香蕉") self.assertNotEqual(result["category"], "可回收")

跑测试:

python manage.py test voice_garbage

这个测试文件的价值在于:你以后往词库里加词条、改匹配逻辑、调整阈值,跑一下就知道有没有把原来的功能弄坏。我习惯在改完classifier.py后立刻跑一遍,再顺手补一个新词的测试用例。语音识别引擎永远是黑匣子,输出不稳定,但我们的匹配层必须有确定性——这正是自动化测试能锁住的底线。

测试通过后,再做一次真机验证:手机连到和服务器同一局域网,用浏览器打开页面,按住录音说三句——“鸡蛋壳”“旧报纸”“遥控器”,看三次结果是否都正确。这一步不要用电脑模拟器,电脑麦克风降噪和手机完全不一样,Vosk 在手机上的表现更能暴露识别层的短板。开发到上线这步,我吃过亏的就是直接拿笔记本演示,现场环境嘈杂,识别一连错三次,场面尴尬。所以我现在每做一个语音项目,必先准备一张离线词库的备用方案——用户在页面输入关键词也能触发分类,语音挂了演示不至于整个翻车。

另外,如果之后想把识别结果实时推送而不依赖前端轮询,可以考虑用 Django Channels 接 WebSocket,把每次识别记录推给正在浏览的页面。这个方向属于实时推送优化,核心链路是 Django 侧发消息、前端接 WebSocket 更新卡片,和当前这套分类逻辑完全解耦,可以放二期做。当前版本用fetch单次请求已经足够撑起演示和课程设计要求。

整个项目做完,你可以自豪的一点是:从录音、转码、识别到分类,整条链路每一段你都知道它怎么工作、坏了怎么查。如果以后有人问我智能垃圾分类怎么做,我会先让他把这个链路跑通,再谈 AI 模型和算法优化——链路不稳,模型再聪明也白搭。希望这篇实战笔记帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 18:30:12

Unity复刻英雄联盟:MOBA核心系统从零构建实战

1. 从零构建一个MOBA&#xff1a;为什么我选择用Unity复刻英雄联盟的核心系统 聊到MOBA游戏开发&#xff0c;很多人第一反应是“这玩意儿一个人做不了”。确实&#xff0c;英雄联盟这种体量的产品背后是几百人的团队、数年的迭代和上亿的预算。但如果你把目标从“做一个完整的商…

作者头像 李华
网站建设 2026/9/29 18:29:19

Halcon深度学习从标注到C#上位机部署全流程实战

简介&#xff1a;这份资源面向具备一定C#基础、希望将深度学习落地到机器视觉场景的开发者&#xff0c;围绕Halcon 21.11与VS2019联合开发&#xff0c;完整演示物体识别与图像分割的标注、训练、验证全流程。压缩包共57个文件&#xff0c;约5.39MB&#xff0c;以cs源码、resx与…

作者头像 李华
网站建设 2026/9/29 18:29:17

Jev哑巴模型接入Codex:配置方法、报错排查与正确用法

最近社区里“Jev”这个词出现的频率明显高了起来&#xff0c;而且很多人聊它的时候都带着同一个外号&#xff1a;哑巴模型。我第一次听到这个叫法还挺疑惑&#xff0c;AI模型怎么会是哑巴&#xff1f;后来自己把Jev翻来覆去用了好几轮才明白&#xff0c;大家说的“哑巴”不是指…

作者头像 李华
网站建设 2026/9/29 18:28:58

大模型推理加速工程实践:从TensorRT-LLM到vLLM的端到端优化

1. 项目概述&#xff1a;Model-Optimizer 不是工具名&#xff0c;而是工程范式的代号“Model-Optimizer”这个标题乍看像某个开源工具或商业软件的名称&#xff0c;但结合NVIDIA、TensorRT-LLM、vLLM、PT文件转换、Docker镜像部署等高频热词&#xff0c;它实际指向的是一整套面…

作者头像 李华
网站建设 2026/9/29 18:27:16

OCR遇上大模型:Provider配置与Function Calling机制拆解

我上周刷 GitHub Trending 的时候&#xff0c;看到阿里开源的那个 OCR 项目登顶本周第一&#xff0c;点进去翻了翻源码和文档&#xff0c;发现它跟传统 Tesseract 那套完全不是一个路子——它的核心卖点是把"OCR 识别能力"做成了一个大模型工具链中的一个 function&a…

作者头像 李华