语音识别智能垃圾分类系统:Django项目实战与匹配算法解析
发布时间:2026/9/29 18:34:13来源:尧图网络
简介基于语音识别的智能垃圾分类系统是一套以 Python Django MySQL 构建的完整可运行实战项目主要面向计算机相关专业的毕业生以及正在积累 Web 项目经验的中级学习者。前台实现系统信息展示、语音上传垃圾分类、个人资料查看与密码修改、退出登录等模块后台则覆盖垃圾分类管理、用户信息维护、网站首页系统信息配置基本形成一套前后台联动的业务闭环。资料包共 305 个文件约 9.4MB以 Python 源码、HTML 页面模板、CSS 样式、JavaScript 交互脚本为主其中 SQL 脚本用于快速初始化数据库wav 示例音频服务于语音分类演示另附操作演示视频与说明文档整体目录分层清楚定位到具体模块即可展开阅读和部署。源码经亲测可用附带演示视频便于快速了解项目全貌适合作为毕业设计、课程设计或实训课题的完整参考目前已有 388 人学习下载。1. 基于语音识别的智能垃圾分类系统为什么值得做一个Django项目实战第一次听到“语音识别垃圾分类”这个题目时大部分人会下意识地把难点押在语音识别上真正动手才发现识别接口几分钟就调通了反倒是“识别出来的文字怎么对到正确的垃圾类别”这层映射把人卡了好几天。用户对着手机说“帮我扔一下苹果核”识别引擎可能给你返回“帮我扔一下平果核”这个词条在库里根本查不到。一个完整的基于语音识别的智能垃圾分类系统本质是把录音采集、语音识别、文本纠错、分类匹配和Django业务系统串起来最后还要能演示、能解释、能应付答辩。这篇文章就顺着最可靠的一条路径把每一步怎么做、参数怎么设、坑在哪讲清楚适合正在做Django项目实战的新手也适合准备课程设计和毕业设计的人照着复现。2. 架构与数据模型一条语音从上传到落库走过的路2.1 语音识别垃圾分类系统的三层结构这一类Django项目常见的整体架构是三段式浏览器端负责录音Django服务端负责接收音频、调用识别引擎、执行分类逻辑数据库负责存储垃圾词条和每一次识别记录。Django在整个系统里的职责边界要划清楚它不是识别引擎也不该在视图里写死匹配规则而是做一个可靠的“中转站”和数据管理平台。实际操作时我一般会把项目拆成两个appwaste_classify管垃圾词条和分类记录voice_api管语音上传和识别调度。这样做的直接好处是以后你想换识别引擎或者单独重写匹配算法不需要动Django的业务模型替换一个service层就够了。这也是判断这类源码写得好不好的第一个标准业务层和识别逻辑有没有解耦。选型上Django框架对新手最友好的地方在于ORM和admin后台。词条库、分类记录、用户纠错反馈这些在原生SQL里要写一堆联表查询在Django里只需要定义好模型后台管理和数据统计都自动有了。语音识别部分选什么方案我会在第3章详细对比这里先说结论优先选“本地可跑、断网也不影响核心演示”的方案线上识别接口做备用否则现场演示时网络一抖整个系统就跟着翻车。2.2 用django创建app并定义核心模型新建一个Django工程并创建两个app这是整个项目的地基django-admin startproject waste_project cd waste_project python manage.py startapp waste_classify python manage.py startapp voice_api创建完以后把两个app注册到settings.py的INSTALLED_APPS里顺便把media路径配好。语音识别的音频文件不能直接扔内存里后面会变成一条条文件记录所以MEDIA_ROOT和MEDIA_URL必须在开发早期就配好# settings.py MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / media核心模型放在waste_classify/models.py里。第一个模型是垃圾词条表第二个是识别记录表# waste_classify/models.py from django.db import models class GarbageItem(models.Model): CATEGORY_CHOICES [ (recyclable, 可回收物), (kitchen, 厨余垃圾), (hazardous, 有害垃圾), (other, 其他垃圾), ] name models.CharField(垃圾名称, max_length50, uniqueTrue) keywords models.TextField(匹配关键词, blankTrue) category models.CharField(所属类别, max_length20, choicesCATEGORY_CHOICES) hot models.PositiveIntegerField(命中次数, default0) def __str__(self): return f{self.name}/{self.get_category_display()} class RecognitionRecord(models.Model): audio_file models.FileField(音频文件, upload_toaudio/) raw_text models.CharField(识别文本, max_length200) matched_item models.ForeignKey( GarbageItem, nullTrue, blankTrue, on_deletemodels.SET_NULL ) result_category models.CharField(分类结果, max_length20) correct models.BooleanField(用户是否确认正确, defaultFalse) created_at models.DateTimeField(auto_now_addTrue) class Meta: ordering [-created_at]这两个模型是整套系统的数据地基。keywords字段用逗号或空格分隔同义词比如“旧电池”这一条可以存“电池 干电池 五号电池”匹配时按词拆分。用TextField而不是单独建一张同义词表是为了让新手项目保持简单词条量在几百条以内时完全够用查询也快。hot字段用来记录命中次数后面做“最长命中优先”和词条权重排序时就用得上。correct字段则用于人工纠错闭环用户点击“确认/纠错”后系统可以把正确结果反馈回词条库。2.3 视图层接收音频并把识别结果写入Django ORM视图是整个Django后台的入口。前端把录音文件通过POST请求传到/api/audio/upload/视图要做四件事校验文件、保存临时音频、调用识别、落库返回。# voice_api/views.py import os from django.conf import settings from django.http import JsonResponse from django.views.decorators.http import require_POST from waste_classify.models import GarbageItem, RecognitionRecord from .services.recognizer import recognize_audio, temp_wav_path require_POST def upload_audio(request): audio request.FILES.get(audio) if not audio: return JsonResponse({code: 400, msg: 缺少audio字段}, status400) # 保存原始音频到media/tmp raw_path os.path.join(settings.MEDIA_ROOT, tmp, audio.name) with open(raw_path, wb) as f: for chunk in audio.chunks(): f.write(chunk) # 统一转成16kHz WAV再送识别 wav_path temp_wav_path(raw_path) from .services.recognizer import convert_to_wav convert_to_wav(raw_path, wav_path) text recognize_audio(wav_path) category, item classify_text(text) record RecognitionRecord.objects.create( audio_filetmp/ os.path.basename(wav_path), raw_texttext, matched_itemitem, result_categorycategory, ) return JsonResponse({ code: 200, text: text, category: category, record_id: record.id, })这段代码里有几个关键决定。第一用require_POST限制请求方法语音上传必须走POST避免有人通过GET触发录音识别也能少一类CSRF攻击面。第二文件落盘要分块写audio.chunks()不会一次性把大文件读进内存识别失败时也方便排查原始音频。第三classify_text和recognize_audio都放service层不在视图里写一大坨逻辑这样后面单元测试可以直接调service函数不需要起Django服务。Django执行查询在这个项目里用得很频繁。管理后台要看“今天识别了多少条”、“哪种垃圾被问得最多”用ORM的聚合查询几行就够from django.db.models import Count from waste_classify.models import RecognitionRecord stats RecognitionRecord.objects.values(result_category).annotate(totalCount(id))这段代码返回的是按垃圾类别分组后的计数列表。values(result_category)先按类别分组annotate(totalCount(id))给每组算条数。相比遍历所有记录再用Python数这种方式在数据量大时效率差距明显也符合Django官方推荐的查询写法。2.4 admin后台给词条库一个可视化管理入口配套的admin注册代码放在waste_classify/admin.py里这是Django项目实战里性价比最高的一段代码几乎不用写任何前端页面from django.contrib import admin from .models import GarbageItem, RecognitionRecord admin.register(GarbageItem) class GarbageItemAdmin(admin.ModelAdmin): list_display [name, category, hot] search_fields [name, keywords] admin.register(RecognitionRecord) class RecognitionRecordAdmin(admin.ModelAdmin): list_display [raw_text, result_category, correct, created_at] list_filter [result_category]注册之后/admin后台直接就能增删改查垃圾词条、查看每一条识别记录。演示的时候后台一打开词条库和识别记录一目了然这种“能看见数据”的效果比写十几个页面更能让答辩老师认可项目的完整性。3. 语音识别接入把用户嘴里的垃圾名称稳定变成文本3.1 识别引擎选型本地Whisper、SpeechRecognition与云接口的取舍语音识别方案选型是这个项目里最容易反复折腾的一步。常见的做法有三类调用云端语音识别API、用Python的SpeechRecognition库、本地部署Whisper或类似离线模型。三者的差别很直接方案中文短句效果网络依赖部署成本适合场景云API好必须联网低注册即可正式演示网络稳定SpeechRecognition较好依赖Google服务最低pip安装开发联调本地Whisper好不依赖较高模型体积大离线演示/交付课程设计和毕业设计场景我最推荐的是“SpeechRecognition 本地离线备用”的组合。SpeechRecognition库的使用成本极低识别中文短句够用缺点是它底层调用的是在线服务断网就废所以正式演示前一定要把WiFi断开试一次确认兜底方案能顶上去。本地Whisper模型效果好但第一次加载慢服务器内存小的云主机容易崩溃这个坑我在第5章单独讲。3.2 封装统一的识别服务层无论用哪种引擎在service层都要留一个统一入口这样业务代码不用跟着引擎换# voice_api/services/recognizer.py import speech_recognition as sr def recognize_audio(audio_path: str, language: str zh-CN, timeout: int 8) - str: recognizer sr.Recognizer() with sr.AudioFile(audio_path) as source: audio_data recognizer.record(source) try: text recognizer.recognize_google(audio_data, languagelanguage) except sr.UnknownValueError: return except sr.RequestError: raise RuntimeError(识别服务不可用请检查网络) return text.strip()这个接口有两个参数要重点说明。languagezh-CN指定中文普通话识别英文垃圾名称或地方口音时会自动切换不用改代码。timeout8表示单条音频最多等待8秒超过就放弃录音只有3秒识别却花了8秒基本就是网络问题或者格式有问题没必要让请求一直挂在那里。UnknownValueError捕获的是“能识别但听不清”的情况这时候直接返回空字符串让后面的分类逻辑走兜底流程而不是把异常抛给前端。3.3 音频格式与采样率MediaRecorder的webm坑前端录音用浏览器原生API看起来简单坑藏在格式里。我见过太多新手用MediaRecorder录完直接上传后端识别结果永远是空字符串原因都是音频格式不是WAV。script async function startRecording() { const stream await navigator.mediaDevices.getUserMedia({ audio: true }); const recorder new MediaRecorder(stream); const chunks []; recorder.ondataavailable e chunks.push(e.data); recorder.onstop () { const blob new Blob(chunks, { type: audio/webm }); submitAudio(blob); }; recorder.start(); setTimeout(() recorder.stop(), 3000); } function submitAudio(blob) { const form new FormData(); form.append(audio, blob, voice.webm); fetch(/api/audio/upload/, { method: POST, headers: { X-CSRFToken: getCookie(csrftoken) }, body: form }); } /script这段录音代码本身没有错问题在于MediaRecorder默认输出的audio/webm格式很多识别库不认。所以后端的第一个动作必须是转码。用pydub统一转成16kHz单声道WAV这是语音识别的标准输入格式# voice_api/services/recognizer.py from pydub import AudioSegment def convert_to_wav(src: str, dst: str): audio AudioSegment.from_file(src) audio audio.set_frame_rate(16000).set_channels(1) audio.export(dst, formatwav)set_frame_rate(16000)把采样率降到16kHzset_channels(1)转成单声道。这两个参数对识别准确率影响很大48kHz立体声直接送识别器虽然不会报错但很多特征提取逻辑是按16kHz设计的效果会打折扣。pydub底层依赖ffmpeg开发环境没装ffmpeg的话第一次运行会直接抛异常。3.4 同步识别与WebSocket实时推送的取舍Django视图默认是同步执行的。音频上传后要等识别引擎把语音转成文本、分类逻辑跑完接口才返回结果。这对演示项目完全够用用户点一下“开始识别”1到3秒后看到结果体验可以接受。但如果你想让前端实时显示“正在识别中→识别成功→正在分类”的进度同步接口就力不从心了。常见的做法是引入Django Channels用WebSocket实现后端有数据就主动推给前端。那样的话前端先通过WebSocket建立连接后端收到音频后在识别完成和分类完成两个节点分别推送消息。这个进阶方向对新手来说控制难度不小第6章我会给一个更务实的替代方案先暂时不要为了实时推送把项目复杂度拉高。4. 智能分类核心从文本到“可回收物”的匹配算法与纠错闭环4.1 垃圾词条库设计四分类与关键词拆分分类模块是整个系统最容易被低估的部分。垃圾分类标准各地略有差异但课程设计里默认按四分类可回收物、厨余垃圾、有害垃圾、其他垃圾。词条库要设计的不仅仅是“垃圾名称”还要考虑口语里怎么说它。初始化词条时除了name一定要维护keywords字段把可能的叫法都塞进去# waste_classify/management/commands/seed_garbage.py from django.core.management.base import BaseCommand from waste_classify.models import GarbageItem class Command(BaseCommand): help 初始化垃圾词条 def handle(self, *args, **kwargs): items [ {name: 废纸盒, keywords: 纸盒 包装箱 纸箱, category: recyclable}, {name: 旧电池, keywords: 电池 干电池 五号电池, category: hazardous}, {name: 苹果核, keywords: 苹果核 苹果 果核 平果, category: kitchen}, {name: 矿泉水瓶, keywords: 矿泉水瓶 塑料瓶 饮料瓶, category: recyclable}, ] for data in items: GarbageItem.objects.update_or_create(namedata[name], defaultsdata) self.stdout.write(词条初始化完成)注意“苹果核”的keywords里我写了“平果”——这是故意加上去的。语音识别经常把“苹”识别成“平”这种同音字错误在真实使用里极其常见。与其指望识别算法提升不如直接在词条库里把常见误识别文本收编这是效率最高、最不容易翻车的做法。需要用Django自定义命令初始化数据所以这段代码放在management/commands目录下运行python manage.py seed_garbage就能把词条灌入数据库。4.2 核心匹配算法清洗、去引导词、最长命中优先拿到识别文本以后第一步是清洗第二步是去引导词第三步才是匹配。用户往往不会只报一个垃圾名称而是说“帮我扔一下苹果核”“这个矿泉水瓶怎么丢”这些口语前缀如果不处理包含匹配就会失败。# voice_api/services/classifier.py import re def clean_text(text: str) - str: text re.sub(r[啊呀哦嗯呃\s。,.!?], , text) for prefix in [帮我扔一下, 帮我扔, 扔一下, 这个, 请帮我]: text text.replace(prefix, ) return text.strip() def classify_text(text: str): text clean_text(text) if not text: return other, None candidates [] for item in GarbageItem.objects.all(): for kw in item.keywords.split(): if kw in text: candidates.append((len(kw), item.hot, item)) if not candidates: return other, None _, _, item max(candidates, keylambda x: (x[0], x[1])) return item.category, item这段代码的核心是“最长命中优先”。为什么要这么做因为用户说“矿泉水瓶”词条库里同时有“水瓶”和“矿泉水瓶”如果按遍历顺序先命中“水瓶”就会把可回收物分错。max函数按(关键词长度, 命中次数)排序优先选更具体的词条再选平时命中多的词条在数据量几百条时这个策略简单有效。keywords按空格拆分的写法有个前提同一个词条的关键词彼此不能太像否则会自己和自己竞争。比如某条keywords写了“苹果 苹果核”用户说“苹果核”结果命中两个词长度不同但都指向同一类别不影响结果但如果写“电池 旧电池”而“旧电池”是另一个类别的名称就会出问题需要在建词条时用第5章的方法去自测。4.3 模糊纠错识别文本错了一个字怎么办“米猴桃”、“平果核”这类语音识别错误靠精确包含匹配救不回来。如果词条库几千条可以用jieba分词配合编辑距离但短文本场景里difflib自带的SequenceMatcher足够解决大部分问题。import difflib def fuzzy_match(text: str, candidates, cutoff: float 0.7): best_score 0 best_item None short_text text[:6] for item in candidates: for kw in item.keywords.split(): score difflib.SequenceMatcher(None, short_text, kw).ratio() if score best_score: best_score, best_item score, item return best_item if best_score cutoff else Nonecutoff0.7是经验值。低于0.6会把“苹果”和“平果”以外的无关词也捞进来高于0.8又兜不住同音字错误。实际使用中text[:6]这个截断很重要因为SequenceMatcher是按整段文本算相似度的用户说“帮我扔一下苹果核”如果前缀没清干净整句比对会降低相似度所以模糊匹配前必须先走一遍清洗。模糊匹配的调用顺序放在精确匹配之后先尝试包含匹配没有结果再走模糊匹配最后才落兜底“其他垃圾”。这个顺序避免了精确命中后被模糊结果覆盖的问题。4.4 人工纠错闭环让系统越用越准系统识别得再准总会有边界案例。垃圾分类系统要落地必须给用户一个“纠错/确认”的入口。前端在返回结果时同时返回record_id用户看到“苹果核→厨余垃圾”后可以点“正确”或“我看不是”。# waste_classify/views.py from django.http import JsonResponse from django.views.decorators.http import require_POST from .models import RecognitionRecord, GarbageItem require_POST def set_correct(request, record_id): record RecognitionRecord.objects.get(idrecord_id) record.correct True record.save() if record.matched_item: record.matched_item.hot 1 record.matched_item.save(update_fields[hot]) return JsonResponse({code: 200})这段代码只做两件事标记这条识别记录被确认过同时把对应词条的hot加1。hot除了用于排序还给管理者一个判断依据哪些垃圾是本地居民真正高频询问的可以优先扩充同义词。Django执行查询的save(update_fields[hot])参数要留意它只更新hot一个字段避免对整个大对象的其他字段做无效写操作。5. 实战避坑语音识别垃圾分类系统的5个高频翻车点5.1 上传音频后识别结果永远为空字符串现象接口返回200前端能收到响应但text字段是空的分类结果永远是“其他垃圾”。原因前端MediaRecorder默认输出的webm/opus格式SpeechRecognition的AudioFile读不了。音频文件即使能读采样率和声道不匹配也会导致识别器解出空白。解决在后端统一走pydub转码设置set_frame_rate(16000)和set_channels(1)。如果已经转码还是空把音频文件拉下来用播放器听一遍往往能发现录音本身就有问题或者麦克风权限没给。5.2 本地识别正常部署到云服务器后接口超时现象本地开发环境录音识别3秒出结果部署到云服务器后前端等10秒还没响应最后超时。原因云服务器没有装ffmpegpydub转码时抛异常或者识别服务需要访问外网但云主机出口受限。另一类原因是服务器内存小加载语音识别模型时被系统杀掉。解决先检查运行日志报错信息里有“ffmpeg not found”就在服务器上执行apt-get install ffmpeg。如果识别调用慢把同步调用改成异步任务队列或者干脆切换到本地Whisper这种离线模型。5.3 “旧电池”被分到“可回收物”现象词条库里明确有“旧电池”且类别是“有害垃圾”但测试时返回可回收物。原因关键词表里存在“电池”和某个可回收物词条共用关键词遍历时先命中了可回收物的词条。根源是匹配策略用了“先到先得”而不是“最长命中优先”。解决按照第4章给出的max(candidates, keylambda x: (x[0], x[1]))逻辑优先选择关键词更长的词条。修完算法后把“电池”“旧电池”“充电电池”这类包含关系词条专门写进测试用例。5.4 多个请求并发时服务器直接崩溃现象演示时旁边有人同时点识别服务器CPU飙到100%随后进程被杀页面全部502。原因每个请求都加载了一遍语音识别模型或创建了独立的识别引擎实例内存被瞬间吃满。解决把模型做成单例进程内只保留一份。常见做法是用模块级变量缓存_model None def get_model(): global _model if _model is None: _model load_model() return _model也可以在视图层加一个简单的并发锁同一时刻只处理一个识别请求其他请求排队等待。对演示项目来说排队比并发更容易控场。5.5 fetch上传一直报CSRF验证失败403 Forbidden现象用网页原生fetch上传FormData后端一直返回403但用Postman直接发请求又是正常的。原因Postman不携带Cookie和CSRF token反而绕过了验证浏览器fetch默认不带X-CSRFToken请求头Django的CSRF中间件把请求拦下了。解决请求头里带上从Cookie解析出的csrftoken值。Django在渲染页面时会写入这个Cookie前端用document.cookie正则提取即可不要把csrf_exempt装饰器加到视图上那是给第三方接口用的不是给自家前端用的。6. 交付前最后一步自测脚本和演示视频准备动手做演示视频之前务必先写一个冒烟测试脚本。这个脚本把最常见的测试音频逐条提交到本地服务打印识别文本和分类结果作用是换词条、改算法时能快速回归而不是每次都打开界面手工点。我第一次做类似项目时把验证脚本留到最后写结果改一个关键词就手工点一次浪费了大量时间这个习惯后来彻底改了。# scripts/smoke_test.py import requests BASE_URL http://127.0.0.1:8000/api/audio/upload/ cases [苹果核, 矿泉水瓶, 旧电池, 纸箱] for name in cases: with open(fassets/{name}.wav, rb) as f: resp requests.post(BASE_URL, files{audio: f}) data resp.json() print(f{name:8s} - 识别: {data[text]:12s} 分类: {data[category]})正式演示前把这几个测试音频文件准备好现场直接用文件放音比让用户对着麦克风喊要稳得多。录制演示视频时我的习惯是固定三条流程先打开admin后台展示词条库再录一条“苹果核”展示厨余垃圾识别最后回到后台展示识别记录列表。这样既展示了Django完整的项目能力又不会因为现场环境噪音翻车。每一个换过识别引擎或者改过匹配规则的项目我都会重新跑一遍冒烟测试再录视频。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网