动物识别Web系统:CNN+Flask+MySQL全栈落地实践

📅 发布时间:2026/10/10 8:48:45
动物识别Web系统:CNN+Flask+MySQL全栈落地实践
简介本资源是一套完整的Python毕业设计项目面向计算机专业本科生及AI初学者聚焦于基于卷积神经网络的动物图像识别Web应用开发可支撑课程设计、毕设选题与实战能力提升。项目实现从模型训练到Web部署的全流程适用于动物保护、生态监测等实际场景的技术验证与原型开发。压缩包共436个文件含53个核心Python脚本含CNN训练与Flask/Django后端逻辑、70个JS与30个CSS前端文件支撑响应式网页交互、46个JPG/PNG测试图像及4个SQLite/MySQL数据库文件另有YAML配置、HTML模板与部署相关Shell脚本等整体22.09MB。已有62人学习下载提供完整可运行代码、多层级目录结构含data/model/static/templates清晰划分、BootstrapLayui前端框架集成及NavicatPyCharm环境配置说明开箱即用便于理解AI模型与Web系统协同工作机制。1. 这不是玩具模型一个能跑通、能改、能上线的动物识别Web系统含MySQLFlaskCNN完整链路你手头正赶着毕业设计 deadline导师说“得有模型、有界面、有数据库、能演示”但搜到的所谓“AI动物识别源码”点开全是 Jupyter Notebook 里几行cv2.imread()model.predict()连个登录页都没有——更别说 MySQL 表结构、用户上传逻辑、识别结果存档、错误日志回溯。这份「基于AI的动物识别技术研究源代码python毕业设计完整源码LW」不是 Demo是可交付的最小可行系统MVP它用 ResNet50 微调出 92.3% 验证准确率的动物分类模型封装成 Flask API前端用 Bootstrap Layui 实现响应式上传页识别结果卡片历史记录表格后端用 SQLAlchemy 管理用户、图片元数据、识别日志三张 MySQL 表所有配置项模型路径、数据库连接、上传目录集中写在config.py里连 Navicat 导入.sql文件的截图都给你备好了。适合两类人一是大四学生照着 README 走完 7 步就能答辩演示二是刚转行的 Python 工程师想拆解一个“AI 模型如何真正落地为 Web 服务”的全栈闭环——它不炫技但每行代码都有明确职责每个文件都能解释清楚“为什么放这儿”。2. 从训练到部署CNN 模型不是黑匣子而是可调试的模块化组件这个项目没用 TensorFlow/Keras 高阶 API 封装到底而是用 PyTorch 1.4兼容 Python 3.6.8手写 DataLoader、Model、Trainer 三层结构目的很实在让你能改、能 debug、能换 backbone。模型核心不是魔改网络而是把 ResNet50 的最后两层 FC 替换为带 Dropout 的双层分类头并在训练时冻结前 4 个 stage 的参数——这是小样本动物识别的血泪经验ImageNet 预训练特征足够强强行全量微调反而过拟合。数据增强也做了取舍只保留RandomHorizontalFlip(p0.5)和ColorJitter(brightness0.2, contrast0.2)砍掉了RandomRotation——动物姿态变化有限过度旋转会引入非真实畸变。2.1 模型训练脚本train.py的关键参数与可复现性保障训练入口train.py不是“一键启动”而是强制你声明所有影响结果的超参。这不是矫情是避免答辩时被问“为什么 batch_size32”。以下是必须修改的 4 个参数其他保持默认即可# train.py 关键配置段需按你本地环境修改 BATCH_SIZE 32 # 显存够就调到 64但注意梯度累积步数要同步改 NUM_EPOCHS 50 # 实测 35 轮验证集 loss 就收敛50 是留冗余 LEARNING_RATE 1e-4 # 冻结 backbone 时用 1e-4解冻后建议降到 1e-5 DATA_ROOT /path/to/your/animal_dataset # 必须是标准 ImageFolder 结构/train/cat/xxx.jpg, /val/dog/yyy.jpg提示DATA_ROOT下的目录结构必须严格遵循 PyTorchImageFolder规范否则Dataset初始化直接报FileNotFoundError。常见翻车点是把train文件夹放在animal_dataset外层导致路径错一层。训练过程会自动生成logs/目录里面包含train_loss.csv每 epoch 平均 loss、val_acc.csv验证集 top-1 准确率、best_model.pth最高 val_acc 对应权重。别信last_model.pth——它只是最后一轮权重可能过拟合。我一般会加一行torch.save(model.state_dict(), fmodels/best_epoch_{best_epoch}.pth)到train.py的保存逻辑里方便回溯。2.2 模型导出为 TorchScript为什么不用.h5或.pb项目没用 Keras 的.h5或 TensorFlow 的.pb而是导出为 TorchScript 格式model.pt原因很实际Flask 部署时加载快、无依赖冲突、支持 JIT 优化。导出脚本export_model.py只做三件事加载best_model.pth→ 设置model.eval()→ 用torch.jit.trace()追踪单次前向传播 → 保存为model.pt。关键代码如下# export_model.py import torch from model import AnimalClassifier # 项目自定义模型类 # 1. 加载训练好的权重 model AnimalClassifier(num_classes10) # 假设你有 10 类动物 model.load_state_dict(torch.load(models/best_model.pth)) model.eval() # 必须否则 BatchNorm 层行为异常 # 2. 构造示例输入尺寸必须和训练时一致 example_input torch.randn(1, 3, 224, 224) # batch1, RGB, 224x224 # 3. 追踪并保存 traced_model torch.jit.trace(model, example_input) traced_model.save(models/model.pt) # 输出为 model.ptFlask 直接 load参数说明example_input的 shape 必须和训练时DataLoader的transforms.Resize(224)输出一致。如果训练用的是299x299InceptionV3这里必须同步改成torch.randn(1,3,299,299)否则 Flask 加载后推理会报size mismatch。2.3 Flask API 封装模型不是孤岛而是服务接口app.py是整个 Web 系统的中枢它把model.pt加载进内存暴露/predict接口接收multipart/form-data图片返回 JSON 结果。核心逻辑只有 20 行但每行都踩过坑# app.py 片段 from flask import Flask, request, jsonify, render_template import torch import torchvision.transforms as transforms from PIL import Image import io app Flask(__name__) # 1. 全局加载模型启动时只加载一次避免每次请求都 IO model torch.jit.load(models/model.pt) model.eval() # 2. 定义和训练时完全一致的预处理流水线 preprocess transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) app.route(/predict, methods[POST]) def predict(): if file not in request.files: return jsonify({error: No file uploaded}), 400 file request.files[file] img Image.open(io.BytesIO(file.read())).convert(RGB) # 强制转 RGB防 RGBA 报错 img_tensor preprocess(img).unsqueeze(0) # add batch dim: [1,3,224,224] with torch.no_grad(): # 关键禁用梯度节省显存 output model(img_tensor) # TorchScript 模型直接 call probabilities torch.nn.functional.softmax(output, dim1) top_prob, top_class torch.topk(probabilities, k3) # 返回 top3 置信度 # 3. 类别名映射必须和训练时 labels.txt 顺序一致 with open(labels.txt, r) as f: class_names [line.strip() for line in f.readlines()] result { predictions: [ {class: class_names[idx.item()], confidence: prob.item()} for idx, prob in zip(top_class[0], top_prob[0]) ] } return jsonify(result)逻辑说明unsqueeze(0)是给单张图加 batch 维度因为模型输入要求[N,C,H,W]torch.no_grad()在推理时必须加否则显存暴涨labels.txt必须是纯文本每行一个类别名顺序和训练时Dataset.classes完全一致——这是最常被忽略的“玄学”错误源。3. Web 全栈落地Bootstrap Layui Flask 不是堆砌而是分工明确的协作前端不是炫酷 SPA而是务实的多页应用首页index.html负责上传与实时识别history.html查看历史记录admin.html需登录管理用户和模型版本。所有静态资源CSS/JS都按项目正文列出的文件名存放style.css控制全局布局layer.css是 Layui 的弹窗样式bootstrap-fullcalendar.css实际未使用可能是作者模板残留可安全删除。3.1 前端上传与识别流程Ajax 不是万能的表单才是稳的index.html的上传逻辑没用fetch或axios而是用原生form提交到/predict靠enctypemultipart/form-data保证二进制图片正确传输。关键 HTML 片段如下!-- index.html -- form iduploadForm action/predict methodpost enctypemultipart/form-data div classform-group label forimageFile选择动物图片/label input typefile classform-control-file idimageFile namefile acceptimage/* required /div button typesubmit classbtn btn-primary idpredictBtn span classspinner-border spinner-border-sm d-none rolestatus/span 开始识别 /button /form div idresultContainer classmt-4 d-none h4识别结果/h4 div idpredictionList classlist-group/div /div为什么不用 AjaxPython 3.6.8 的 Flask 0.12.2 对fetch的FormData支持不稳定尤其在 IE/Edge 旧版下易丢文件。用传统 form 提交服务端request.files[file]拿到的就是原始 bytes兼容性 100%。提交后页面跳转问题用event.preventDefault()拦截默认行为再手动fetch——但本项目选择更稳妥的方案form 提交后由 Flaskredirect(url_for(result, ...))返回结果页URL 带?result...参数result.html解析参数渲染结果。这样既规避了跨域又保证了 POST 数据完整性。3.2 Layui 表格渲染历史记录MySQL 查询结果到 DOM 的管道history.html的表格数据来自/api/history接口该接口在app.py中定义为# app.py app.route(/api/history) def get_history(): # 从 MySQL 查询最近 20 条记录 conn get_db_connection() cursor conn.cursor(dictionaryTrue) cursor.execute( SELECT h.id, h.filename, h.prediction, h.confidence, h.created_at, u.username FROM recognition_history h JOIN users u ON h.user_id u.id ORDER BY h.created_at DESC LIMIT 20 ) records cursor.fetchall() conn.close() return jsonify(records)前端用 Layui 的table.render()渲染关键配置如下// history.html layui.use([table, laydate], function(){ var table layui.table; table.render({ elem: #historyTable, url: /api/history, // 自动 GET 请求 page: true, // 开启分页 cols: [[ {field: id, title: ID, width:80}, {field: username, title: 用户, width:120}, {field: filename, title: 图片名, width:180}, {field: prediction, title: 识别结果, width:150}, {field: confidence, title: 置信度, width:120, templet: div{{ (d.confidence * 100).toFixed(2) }}%/div}, {field: created_at, title: 时间, width:180} ]] }); });参数说明templet字段用于格式化数字{{ (d.confidence * 100).toFixed(2) }}%把 0.923 转成92.30%page: true启用 Layui 内置分页但注意/api/history接口本身没做分页查询LIMIT 20 是硬编码所以 Layui 分页只是前端展示真要支持大数据量得改后端 SQL 加OFFSET和LIMIT参数。3.3 MySQL 数据库设计三张表解决身份、资产、行为归因项目数据库共 3 张表全部在database/animal_recognition.sql中定义Navicat 11 可直接执行。设计原则是最小必要字段 外键约束 索引优化表名字段类型说明索引usersidINT PK AI用户主键PRIMARYusernameVARCHAR(50) UNIQUE登录名UNIQUEpassword_hashVARCHAR(128)bcrypt 加密密码—recognition_historyidINT PK AI记录主键PRIMARYuser_idINT FK关联 users.idINDEX (user_id)filenameVARCHAR(255)上传文件名含扩展名—predictionVARCHAR(100)识别出的动物类别名—confidenceFLOAT置信度0~1—created_atDATETIME DEFAULT CURRENT_TIMESTAMP创建时间INDEX (created_at)为什么filename不存路径因为图片物理存储在static/uploads/目录下filename只存相对路径如cat_20230501_123456.jpg避免绝对路径硬编码。created_at加索引是为了history.html按时间倒序查快。4. 避坑指南那些让答辩前夜崩溃的 5 个真实错误这些不是理论假设是我在帮 3 届学生部署时亲手填过的坑每一条都对应一个git commit --amend的深夜。4.1 现象Flask 启动报ModuleNotFoundError: No module named torch原因Python 3.6.8 环境里没装 PyTorch或装了但 CUDA 版本不匹配比如装了torch1.10.0cu113但本机是 CUDA 10.2。项目要求 PyTorch 1.4必须用pip install torch1.4.0 torchvision0.5.0 -f https://download.pytorch.org/whl/torch_stable.html指定链接安装。解决先nvidia-smi看 CUDA 版本再去 PyTorch 官网找对应wheelURL若无 GPU强制装 CPU 版pip install torch1.4.0cpu torchvision0.5.0cpu -f https://download.pytorch.org/whl/torch_stable.html。4.2 现象上传图片后返回{error: No file uploaded}原因HTML 表单input的namefile和 Flaskrequest.files[file]的 key 不一致或enctype属性漏写。更隐蔽的是PyCharm 的 Flask Debug 模式默认DEBUGTrue但某些版本对 multipart 解析有 bug。解决检查index.html中input namefile的 name 属性确认form有enctypemultipart/form-data在app.py顶部加app.config[MAX_CONTENT_LENGTH] 16 * 1024 * 1024限制 16MB防大图 OOM。4.3 现象识别结果全是unknown或background原因labels.txt文件编码不是 UTF-8无 BOMWindows 记事本默认保存为 ANSI或文件末尾有多余空行导致class_names数组长度比模型输出num_classes多 1。解决用 VS Code 打开labels.txt右下角确认编码为UTF-8点击“重新以编码打开”→选UTF-8删掉最后一行空行用print(len(class_names), model.num_classes)在app.py里 debug。4.4 现象MySQL 连接失败报1045 Access denied for user rootlocalhost原因config.py里的DB_PASSWORD写错了或 Navicat 导入.sql文件时没选对数据库应该新建animal_recognition库再导入。更常见的是MySQL 5.7 默认sql_mode包含STRICT_TRANS_TABLES而项目 SQL 里INSERT语句没给NOT NULL字段赋值。解决进 MySQL 执行SELECT sql_mode;若含STRICT_TRANS_TABLES临时关闭SET GLOBAL sql_mode(SELECT REPLACE(sql_mode,STRICT_TRANS_TABLES,));永久修改需改my.cnf。4.5 现象Layui 表格显示Loading但无数据浏览器 Console 报Failed to load resource: the server responded with a status of 404原因/api/history路由在app.py里定义为app.route(/api/history)但前端 JS 请求的是/history少写了/api/前缀或 Flask 没注册该路由函数名拼错、没加app.route装饰器。解决打开浏览器开发者工具 Network 标签页看请求 URL 是什么在app.py里搜索app.route确认路径字符串用curl http://127.0.0.1:5000/api/history测试接口是否返回 JSON。5. 模型可解释性增强不只是 Top-1还要知道“为什么是猫不是狗”毕业答辩时老师常问“模型凭什么认为这是豹子依据在哪” 本项目在app.py的/predict接口中预留了 Grad-CAM 热力图生成逻辑注释掉的代码但真正落地只需 3 步启用热力图开关、修改模型前向传播、前端渲染叠加图。这不是炫技是让黑盒决策变得可追溯。5.1 Grad-CAM 实现用 PyTorch 原生 hook 提取特征图与梯度model.py中的AnimalClassifier类已预留self.features属性指向最后一个卷积层grad_cam.py脚本利用它计算热力图。核心是注册 forward hook 和 backward hook# grad_cam.py import torch import torch.nn.functional as F from PIL import Image import numpy as np class GradCAM: def __init__(self, model, target_layer): self.model model self.target_layer target_layer self.gradients None self.features None # 注册 forward hook 获取特征图 def forward_hook(module, input, output): self.features output # 注册 backward hook 获取梯度 def backward_hook(module, grad_in, grad_out): self.gradients grad_out[0] target_layer.register_forward_hook(forward_hook) target_layer.register_backward_hook(backward_hook) def generate(self, input_img): self.model.zero_grad() output self.model(input_img) # 前向传播 pred_class output.argmax(dim1).item() # 反向传播只对预测类求导 self.model.zero_grad() output[0][pred_class].backward() # 注意output 是 [1, num_classes] # 计算权重全局平均池化梯度 weights torch.mean(self.gradients, dim[0, 2, 3]) # [C] cam torch.zeros(self.features.shape[2:]) # [H, W] # 加权求和特征图通道 for i, w in enumerate(weights): cam w * self.features[0, i] # ReLU 上采样到原图尺寸 cam F.relu(cam) cam cam - torch.min(cam) cam cam / torch.max(cam) return cam.unsqueeze(0).unsqueeze(0) # [1,1,H,W] # 使用示例在 app.py /predict 中调用 # cam_map grad_cam.generate(img_tensor) # 返回热力图 tensor # save_heatmap(cam_map, static/heatmaps/ filename _cam.jpg)参数说明target_layer必须是模型中最后一个卷积层如model.layer4[-1].conv2不能是nn.AdaptiveAvgPool2dinput_img必须是requires_gradTrue的 tensor所以img_tensor preprocess(img).unsqueeze(0).requires_grad_(True)热力图保存后前端用img src{{ url_for(static, filenameheatmaps/ filename _cam.jpg) }}叠加显示。5.2 前端热力图叠加CSS 定位实现“透明红热区”result.html中原始图片和热力图用绝对定位叠加热力图 PNG 设为半透明div classposition-relative stylemax-width: 600px; margin: 0 auto; img src{{ url_for(static, filenameuploads/ filename) }} classimg-fluid altOriginal img src{{ url_for(static, filenameheatmaps/ filename _cam.jpg) }} classposition-absolute top-0 start-0 w-100 h-100 opacity-50 altGrad-CAM Heatmap /div为什么用 opacity-50 而不用 CSS blend-mode因为blend-mode: multiply在旧版浏览器兼容性差而opacity100% 兼容热力图 PNG 本身是红白渐变白色高响应opacity-50让红色区域透出底层图片纹理直观显示模型关注区域。5.3 模型鲁棒性测试用对抗样本检验泛化能力答辩时若被问“模型会不会被干扰图片欺骗”你可以现场演示用adversarial_attack.py生成 FGSM 对抗样本。它不破坏模型只验证其鲁棒性——这才是工程思维。# adversarial_attack.py import torch import torch.nn.functional as F from torch.autograd import Variable def fgsm_attack(image, epsilon, data_grad): # 符号函数取梯度方向 sign_data_grad data_grad.sign() # 扰动 ε * sign(∇xJ(x,y)) perturbed_image image epsilon * sign_data_grad # 截断到 [0,1] 范围 perturbed_image torch.clamp(perturbed_image, 0, 1) return perturbed_image # 在 predict 流程中插入伪代码 # 1. 正常预测得到 output # 2. output.backward() 得到 data_grad # 3. perturbed fgsm_attack(img_tensor, epsilon0.01, data_grad) # 4. 再用 perturbed 输入模型看 top1 是否改变实测结论当epsilon0.011% 像素扰动时本项目模型 top1 准确率下降约 12%说明它对轻微扰动敏感——这恰恰证明了 CNN 学到的是纹理/边缘等局部特征而非全局语义。你可以坦诚说“这提醒我们在野生动物监测场景需配合图像预处理如直方图均衡化提升鲁棒性”比硬吹“100% 抗干扰”更可信。从那以后我每次交付 AI Web 系统都会强制走一遍 Grad-CAM 热力图生成 FGSM 扰动测试哪怕答辩不问也让自己心里有底模型不是魔法是可测量、可解释、可改进的工程产物。希望帮到你。本文还有配套的精品资源点击获取