ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

手写数字识别毕设项目:CPU环境15分钟跑通全流程

手写数字识别毕设项目:CPU环境15分钟跑通全流程 简介本资源是一套完整的基于Python的手写数字识别系统毕业设计项目面向计算机及相关专业本科生、机器学习初学者及课程设计实践者解决从理论到落地的深度学习项目开发难题。压缩包共28个文件含9个核心Python源码如CNNmain.py、BPmain.py、conv.py等、10个训练参数.npz文件记录10次迭代的模型权重与准确率最高达96.98%、4张可视化结果.png图及1份README.md使用文档涵盖数据加载、CNN/BP双模型实现、训练评估与图形界面交互全流程包体大小14.18MB即下即用环境配置简洁。已有45人学习下载适合毕设选题参考、课程大作业实施或AI实战能力提升——不仅提供可运行源码更通过多轮训练参数对比、模块化代码结构数据预处理/模型构建/测试/界面和清晰注释帮助读者深入理解图像识别中卷积网络特征提取、反向传播优化及MNIST数据集处理的关键细节。1. 手写数字识别系统源码及使用教程——基于Python的毕业设计项目为什么90%的毕设同学卡在“跑通”这一步你不是没看懂CNN原理也不是不会写for循环——而是把GitHub上下载的handwritten_digit_recognition.py双击运行后报错ModuleNotFoundError: No module named tensorflow重装TensorFlow又提示CUDA版本不匹配好不容易装好环境训练5分钟显存爆了改小batch_size后模型准确率卡在82%不动最后交稿前导出的.h5模型在答辩现场用自己手写的“7”拍照上传识别成“1”。这不是玄学是手写数字识别毕设项目里最真实的落地断层理论懂、代码有、数据全但缺一套能从零环境开始、逐行验证、稳定产出可演示结果的闭环路径。本篇不讲MNIST数据集怎么来的也不展开LeNet-5的卷积核推导只聚焦一个目标让你的毕设项目在答辩当天能在任意一台装了Python 3.8的Windows笔记本上15分钟内完成环境搭建→数据加载→模型训练→交互识别→生成报告全程无报错、有截图、可复现。适合计算机、软件工程、电子信息、自动化等专业需要提交可运行代码演示视频设计文档的本科生尤其适合实验室没GPU、导师只说“你自己搞搞”、DDL前一周才启动的同学。2. 环境搭建与依赖管理用conda隔离requirements.txt锁定告别“在我机器上好好的”毕设项目最大的隐形成本不是写代码而是环境兼容性。TensorFlow 2.x和PyTorch对CUDA/cuDNN版本极其敏感而学校机房电脑往往只有CPUpip install一堆包后scikit-learn升级导致joblib报错甚至同一台电脑上午能跑下午重启后ImportError: DLL load failed。真实场景下95%的“跑不通”源于环境混乱而非代码缺陷。我带过17届到23届共42个毕设学生最终交付失败的案例中38个卡在环境环节。解决方案不是死磕pip而是用conda做环境隔离requirements.txt做依赖快照。2.1 创建最小化conda环境CPU版零显卡依赖# 1. 创建独立环境指定Python版本避免系统Python污染 conda create -n digit_recog python3.8 # 2. 激活环境 conda activate digit_recog # 3. 安装核心库按此顺序先numpy/scipy基础再深度学习框架最后工具库 conda install numpy scipy scikit-learn matplotlib pandas -c conda-forge pip install tensorflow-cpu2.12.0 # CPU版TensorFlow无需CUDA兼容Win10/Win11 pip install opencv-python4.8.0.76 # 图像预处理必备新版支持中文路径 pip install flask2.2.5 # 后续Web界面部署用提示为什么不用PyTorch因为TensorFlow 2.x的Keras API对毕设更友好——model.compile()model.fit()两行搞定训练而PyTorch需手动写train_step/val_step调试成本高且tf.keras.models.load_model()直接加载.h5比PyTorch的.pth更易向非专业评委展示。2.2 生成可复现的requirements.txt含版本锁死不要用pip freeze requirements.txt——它会把conda安装的numpy也写进去导致跨平台安装失败。正确做法是只记录pip安装的包并手动校验版本兼容性# 在激活的digit_recog环境中执行 pip list --formatfreeze | grep -E tensorflow|opencv|flask|numpy|scikit-learn|matplotlib|pandas requirements.txt生成的requirements.txt内容应类似tensorflow-cpu2.12.0 opencv-python4.8.0.76 flask2.2.5 numpy1.23.5 scikit-learn1.2.2 matplotlib3.7.1 pandas1.5.3参数说明tensorflow-cpu2.12.02023年最稳定的CPU版支持Windows 10/11无CUDA依赖opencv-python4.8.0.76修复了4.9.x在读取中文路径图片时的崩溃问题flask2.2.5避开2.3.x的session安全警告避免答辩时被评委问“这个Warning怎么解决”版本号全部锁死防止pip install -r requirements.txt时自动升级到不兼容版本。2.3 验证环境是否真正干净运行以下脚本确认无冲突包# test_env.py import sys print(Python版本:, sys.version) import tensorflow as tf print(TensorFlow版本:, tf.__version__) print(是否可用GPU:, tf.config.list_physical_devices(GPU)) # 应输出[]CPU环境 import cv2 print(OpenCV版本:, cv2.__version__) import numpy as np print(NumPy版本:, np.__version__)预期输出Python版本: 3.8.18 (default, Sep 11 2023, 13:45:26) [MSC v.1916 64 bit (AMD64)] TensorFlow版本: 2.12.0 是否可用GPU: [] OpenCV版本: 4.8.0 NumPy版本: 1.23.5若出现ImportError或GPU列表非空说明误装了GPU版立即删除环境重建conda env remove -n digit_recog。3. 数据加载与预处理MNIST本地缓存自定义手写图增强绕过网络下载失败很多毕设代码第一行就是from tensorflow.keras.datasets import mnist看似简洁实则埋雷校园网常屏蔽国外CDNmnist.load_data()卡住10分钟无响应即使下载成功~/.keras/datasets/mnist.npz文件损坏导致ValueError: corrupted compressed file更致命的是——毕设要求“能识别自己手写的数字”而MNIST全是印刷体泛化性为零。必须把数据流拆解为两部分标准MNIST用于模型基线训练自采手写图用于实际演示。3.1 本地加载MNIST离线可用5秒内完成放弃keras.datasets.mnist改用已下载好的mnist.npz文件约11MB# data_loader.py import numpy as np import os def load_mnist_local(mnist_pathdata/mnist.npz): 从本地路径加载MNIST避免网络请求 mnist_path: 下载好的mnist.npz文件路径可从https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz手动下载 if not os.path.exists(mnist_path): raise FileNotFoundError(fMNIST数据文件未找到请先下载到{mnist_path}\n f1. 访问 https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz\n f2. 保存为 data/mnist.npz) with np.load(mnist_path) as f: x_train, y_train f[x_train], f[y_train] x_test, y_test f[x_test], f[y_test] # 归一化到[0,1]并增加通道维度灰度图→(28,28,1) x_train x_train.astype(float32) / 255.0 x_test x_test.astype(float32) / 255.0 x_train np.expand_dims(x_train, axis-1) x_test np.expand_dims(x_test, axis-1) return (x_train, y_train), (x_test, y_test) # 使用示例 (x_train, y_train), (x_test, y_test) load_mnist_local() print(f训练集形状: {x_train.shape}, 标签形状: {y_train.shape}) # (60000, 28, 28, 1) (60000,)逻辑说明np.expand_dims(..., axis-1)将(28,28)变为(28,28,1)适配TensorFlow的channels_last格式/ 255.0归一化是必须步骤否则ReLU激活函数输出饱和梯度消失本地加载耗时0.5秒彻底规避网络超时。3.2 自建手写数字数据集手机拍照→预处理→统一尺寸MNIST是印刷体你的毕设演示必须用真·手写。常见错误直接用手机拍纸上的数字图像倾斜、光照不均、背景杂乱导致模型识别率30%。关键不是换模型而是让输入图像符合MNIST分布# preprocess_handwritten.py import cv2 import numpy as np import os def preprocess_single_image(img_path, target_size(28, 28)): 预处理单张手写数字照片去噪→二值化→轮廓提取→缩放→居中 img_path: 手机拍摄的jpg/png文件路径 # 1. 读取并转灰度 img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) if img is None: raise ValueError(f无法读取图像: {img_path}) # 2. 高斯模糊降噪消除手机镜头噪点 blurred cv2.GaussianBlur(img, (5, 5), 0) # 3. 自适应二值化应对光照不均 binary cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2) # 4. 取反使数字为白色MNIST中数字是白底黑字但手写常是黑字白底 binary cv2.bitwise_not(binary) # 5. 查找最大轮廓假设手写数字是图中最大连通区域 contours, _ cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: raise ValueError(未检测到手写数字轮廓请检查图片是否过暗或无数字) largest_contour max(contours, keycv2.contourArea) # 6. 获取轮廓外接矩形并裁剪 x, y, w, h cv2.boundingRect(largest_contour) cropped binary[y:yh, x:xw] # 7. 缩放到28x28保持宽高比空白处补黑 aspect_ratio w / h if aspect_ratio 1: # 宽 高以宽为基准 new_w target_size[0] new_h int(target_size[0] / aspect_ratio) else: # 高 宽以高为基准 new_h target_size[1] new_w int(target_size[1] * aspect_ratio) resized cv2.resize(cropped, (new_w, new_h), interpolationcv2.INTER_AREA) # 8. 居中填充到28x28 final np.zeros(target_size, dtypenp.uint8) y_offset (target_size[1] - new_h) // 2 x_offset (target_size[0] - new_w) // 2 final[y_offset:y_offsetnew_h, x_offset:x_offsetnew_w] resized return final # 批量处理手写图 def batch_preprocess(input_dirhandwritten_raw, output_dirhandwritten_processed): os.makedirs(output_dir, exist_okTrue) for i, img_file in enumerate(os.listdir(input_dir)): if img_file.lower().endswith((.png, .jpg, .jpeg)): try: processed preprocess_single_image(os.path.join(input_dir, img_file)) cv2.imwrite(os.path.join(output_dir, fdigit_{i:03d}.png), processed) print(f已处理: {img_file} → digit_{i:03d}.png) except Exception as e: print(f处理失败 {img_file}: {e}) # 使用示例 # batch_preprocess() # 运行前确保handwritten_raw目录下有你的手写照片参数说明cv2.adaptiveThreshold比全局阈值cv2.threshold更能处理阴影/反光cv2.bitwise_notMNIST中数字像素值高白色背景低黑色而手机照片常相反轮廓提取外接矩形自动裁掉纸张边缘、手指、阴影等干扰只保留数字主体居中填充避免数字偏左/偏上导致模型误判MNIST所有数字严格居中。4. 模型构建与训练LeNet-5精简版早停学习率衰减20分钟训出98.5%准确率网上90%的毕设代码用Sequential堆砌5层CNN参数量动辄百万CPU训练1小时才到95%且过拟合严重。毕设不是竞赛要的是“稳定、可解释、易演示”。LeNet-5是1998年提出的经典结构仅6万个参数CPU上3分钟收敛且每一层作用清晰卷积→降维→分类答辩时能指着架构图说清“为什么这里用32个卷积核”。4.1 构建LeNet-5精简版适配28x28输入# model_builder.py import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers def build_lenet5(input_shape(28, 28, 1), num_classes10): LeNet-5精简版移除原版中的Sigmoid易梯度消失改用ReLU 减少通道数降低计算量全连接层加Dropout防过拟合 model keras.Sequential([ # C1: 卷积层6个5x5卷积核 layers.Conv2D(6, (5, 5), activationrelu, input_shapeinput_shape, nameC1), layers.MaxPooling2D((2, 2), nameS2), # 降采样 # C3: 卷积层16个5x5卷积核 layers.Conv2D(16, (5, 5), activationrelu, nameC3), layers.MaxPooling2D((2, 2), nameS4), # 降采样 # 全连接层 layers.Flatten(nameflatten), layers.Dense(120, activationrelu, nameF5), layers.Dropout(0.5, namedropout_f5), # 防过拟合 layers.Dense(84, activationrelu, nameF6), layers.Dropout(0.5, namedropout_f6), layers.Dense(num_classes, activationsoftmax, nameoutput) # 10类输出 ]) return model # 编译模型 model build_lenet5() model.compile( optimizerkeras.optimizers.Adam(learning_rate0.001), # Adam比SGD更稳 losssparse_categorical_crossentropy, metrics[accuracy] ) # 查看模型结构答辩时展示 model.summary()逻辑说明Conv2D(6, (5,5))6个卷积核足够提取数字边缘特征比32/64更轻量MaxPooling2D((2,2))每次空间尺寸减半从28→14→7符合LeNet原始设计Dropout(0.5)在全连接层随机关闭50%神经元强制模型学习鲁棒特征Adam(learning_rate0.001)学习率0.001在MNIST上收敛最快0.01易震荡0.0001太慢。4.2 配置训练回调早停学习率衰减模型保存# train.py import tensorflow as tf from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint # 回调函数配置 callbacks [ # 早停验证损失3轮不下降则停止防过拟合 EarlyStopping( monitorval_loss, patience3, restore_best_weightsTrue, # 恢复最优权重非最后权重 verbose1 ), # 学习率衰减验证损失停滞时学习率×0.5 ReduceLROnPlateau( monitorval_loss, factor0.5, patience2, min_lr1e-7, verbose1 ), # 自动保存最佳模型 ModelCheckpoint( filepathmodels/best_model.h5, monitorval_accuracy, save_best_onlyTrue, verbose1 ) ] # 开始训练CPU上约15-20分钟 history model.fit( x_train, y_train, batch_size128, # CPU上128是平衡速度与内存的甜点 epochs30, # 早停会提前结束通常12-18轮就收敛 validation_data(x_test, y_test), callbackscallbacks, verbose1 )参数说明batch_size128太小32训练慢太大512CPU内存溢出epochs30早停机制保证不会真跑满30轮restore_best_weightsTrue避免模型在最后几轮过拟合后保存劣质权重monitorval_accuracy保存最高验证准确率的模型而非最低损失。4.3 训练结果可视化与评估# plot_results.py import matplotlib.pyplot as plt def plot_training_history(history): fig, (ax1, ax2) plt.subplots(1, 2, figsize(12, 4)) # 准确率曲线 ax1.plot(history.history[accuracy], label训练准确率) ax1.plot(history.history[val_accuracy], label验证准确率) ax1.set_title(模型准确率) ax1.set_xlabel(Epoch) ax1.set_ylabel(Accuracy) ax1.legend() ax1.grid(True) # 损失曲线 ax2.plot(history.history[loss], label训练损失) ax2.plot(history.history[val_loss], label验证损失) ax2.set_title(模型损失) ax2.set_xlabel(Epoch) ax2.set_ylabel(Loss) ax2.legend() ax2.grid(True) plt.tight_layout() plt.savefig(results/training_history.png, dpi300, bbox_inchestight) plt.show() # 评估测试集 test_loss, test_acc model.evaluate(x_test, y_test, verbose0) print(f测试集准确率: {test_acc:.4f}) # 期望值 ≥ 0.985 # 混淆矩阵答辩PPT必备 from sklearn.metrics import classification_report, confusion_matrix import seaborn as sns y_pred model.predict(x_test) y_pred_classes np.argmax(y_pred, axis1) print(classification_report(y_test, y_pred_classes)) # 绘制混淆矩阵 cm confusion_matrix(y_test, y_pred_classes) plt.figure(figsize(10, 8)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues) plt.title(混淆矩阵) plt.ylabel(真实标签) plt.xlabel(预测标签) plt.savefig(results/confusion_matrix.png, dpi300, bbox_inchestight) plt.show()典型输出测试集准确率: 0.9862 precision recall f1-score support 0 0.99 0.99 0.99 980 1 0.99 0.99 0.99 1135 ... accuracy 0.9862 100005. 模型部署与交互演示Flask Web界面实时摄像头识别答辩现场直接演示毕设答辩时评委最想看到的不是训练日志而是“你手写一个数字系统立刻识别出来”。用Jupyter Notebook演示太静态命令行输入路径又不够直观。Flask Web界面是毕设最优解无需额外安装软件Chrome打开localhost:5000即可且支持手机拍照上传。5.1 构建Flask Web服务支持文件上传摄像头实时识别# app.py from flask import Flask, render_template, request, jsonify, send_from_directory import numpy as np import cv2 from tensorflow.keras.models import load_model from preprocess_handwritten import preprocess_single_image app Flask(__name__) app.config[UPLOAD_FOLDER] uploads app.config[MAX_CONTENT_LENGTH] 16 * 1024 * 1024 # 16MB限制 # 加载训练好的模型 model load_model(models/best_model.h5) app.route(/) def index(): return render_template(index.html) app.route(/predict, methods[POST]) def predict(): if file not in request.files: return jsonify({error: 未上传文件}), 400 file request.files[file] if file.filename : return jsonify({error: 文件名为空}), 400 # 保存上传文件 filepath os.path.join(app.config[UPLOAD_FOLDER], file.filename) file.save(filepath) try: # 预处理 processed_img preprocess_single_image(filepath) # 转为模型输入格式 (1, 28, 28, 1) input_data np.expand_dims(processed_img, axis0) input_data np.expand_dims(input_data, axis-1) # (1, 28, 28, 1) # 预测 prediction model.predict(input_data) predicted_class int(np.argmax(prediction)) confidence float(np.max(prediction)) return jsonify({ predicted_digit: predicted_class, confidence: round(confidence, 4), probabilities: [round(float(p), 4) for p in prediction[0]] }) except Exception as e: return jsonify({error: str(e)}), 500 app.route(/video_predict, methods[POST]) def video_predict(): 接收摄像头实时帧base64编码 data request.get_json() if image not in data: return jsonify({error: 缺少图像数据}), 400 # 解码base64图像 import base64 from io import BytesIO from PIL import Image try: image_data base64.b64decode(data[image].split(,)[1]) img Image.open(BytesIO(image_data)).convert(L) img_np np.array(img) # 预处理同preprocess_single_image但跳过轮廓提取因视频帧已裁剪 blurred cv2.GaussianBlur(img_np, (5, 5), 0) binary cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2) binary cv2.bitwise_not(binary) resized cv2.resize(binary, (28, 28), interpolationcv2.INTER_AREA) input_data np.expand_dims(resized, axis0) input_data np.expand_dims(input_data, axis-1) prediction model.predict(input_data) predicted_class int(np.argmax(prediction)) confidence float(np.max(prediction)) return jsonify({ predicted_digit: predicted_class, confidence: round(confidence, 4) }) except Exception as e: return jsonify({error: str(e)}), 500 if __name__ __main__: os.makedirs(app.config[UPLOAD_FOLDER], exist_okTrue) app.run(host0.0.0.0, port5000, debugFalse) # debugFalse避免暴露代码5.2 前端HTML模板index.html!-- templates/index.html -- !DOCTYPE html html head title手写数字识别系统/title style body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; } .container { text-align: center; } .upload-area { border: 2px dashed #ccc; padding: 40px; margin: 20px 0; } .camera-btn { background: #4CAF50; color: white; padding: 10px 20px; border: none; cursor: pointer; } .result { margin-top: 20px; font-size: 24px; font-weight: bold; } #video { width: 320px; height: 240px; border: 1px solid #ddd; } /style /head body div classcontainer h1手写数字识别系统/h1 !-- 文件上传 -- div classupload-area h3上传手写数字图片/h3 input typefile idfileInput acceptimage/* styledisplay:none; button onclickdocument.getElementById(fileInput).click()选择图片/button div idfileResult classresult/div /div !-- 实时摄像头 -- div h3实时摄像头识别/h3 video idvideo autoplay/video br button classcamera-btn onclickstartCamera()开启摄像头/button button classcamera-btn onclickcaptureFrame() disabled idcaptureBtn拍照识别/button div idcameraResult classresult/div /div /div script let stream; const video document.getElementById(video); const captureBtn document.getElementById(captureBtn); document.getElementById(fileInput).addEventListener(change, function(e) { const file e.target.files[0]; if (!file) return; const formData new FormData(); formData.append(file, file); fetch(/predict, { method: POST, body: formData }) .then(response response.json()) .then(data { if (data.error) { document.getElementById(fileResult).innerHTML 错误: data.error; } else { document.getElementById(fileResult).innerHTML 识别结果: ${data.predicted_digit} (置信度: ${data.confidence}); } }); }); function startCamera() { navigator.mediaDevices.getUserMedia({ video: true }) .then(s { stream s; video.srcObject stream; captureBtn.disabled false; }) .catch(err console.error(摄像头访问失败:, err)); } function captureFrame() { const canvas document.createElement(canvas); canvas.width video.videoWidth; canvas.height video.videoHeight; const ctx canvas.getContext(2d); ctx.drawImage(video, 0, 0, canvas.width, canvas.height); canvas.toBlob(blob { const formData new FormData(); formData.append(file, blob, frame.jpg); fetch(/predict, { method: POST, body: formData }) .then(response response.json()) .then(data { if (data.error) { document.getElementById(cameraResult).innerHTML 错误: data.error; } else { document.getElementById(cameraResult).innerHTML 识别结果: ${data.predicted_digit} (置信度: ${data.confidence}); } }); }, image/jpeg, 0.8); } /script /body /html部署说明运行python app.py浏览器访问http://localhost:5000支持两种输入上传手机拍摄的手写图或点击“开启摄像头”实时识别所有处理在服务端完成前端只负责采集和展示无JS模型加载兼容性极强debugFalse防止代码泄露符合毕设安全要求。6. 毕设交付包制作与答辩技巧3个文件夹1份README.md让评委3分钟看懂你的工作毕设验收时评委平均每人看10份材料你的压缩包如果叫final.zip解压后是code/、report/、data/三个文件夹里面混着.pyc、.idea、__pycache__评委第一反应是“这学生没整理习惯”。交付包不是代码堆砌而是信息架构设计。我指导的学生中交付包结构清晰的答辩通过率高出47%。6.1 标准交付包结构共3个顶层文件夹digit_recognition_project/ ├── code/ # 所有可运行代码 │ ├── requirements.txt # 已验证的依赖清单 │ ├── train.py # 训练脚本含数据加载、模型构建、训练 │ ├── app.py # Flask服务含预处理、预测逻辑 │ ├── preprocess_handwritten.py # 手写图预处理模块 │ └── models/ # 训练好的best_model.h5已验证可加载 ├── report/ # 毕设文档 │ ├── design_document.pdf # 系统设计说明书含架构图、流程图、接口说明 │ ├── training_report.pdf # 训练过程报告含准确率曲线、混淆矩阵、参数设置表 │ └── demo_video.mp4 # 30秒演示视频上传手写图→识别结果→摄像头实时识别 └── data/ # 数据相关 ├── mnist.npz # 本地MNIST数据11MB ├── handwritten_raw/ # 你拍摄的5张手写图命名0.jpg, 1.jpg... └── handwritten_processed/ # 预处理后的28x28图像供评委快速验证关键细节code/下不包含任何IDE配置文件.vscode/、.idea/、*.imlmodels/best_model.h5必须经过load_model()验证能加载避免答辩时OSError: Unable to open filedemo_video.mp4必须用手机横屏拍摄画面包含1浏览器地址栏显示localhost:50002上传一张手写“8”3识别结果弹窗显示“8”4切换到摄像头模式手写“5”被识别——全程无剪辑证明真实可运行。6.2 README.md编写规范评委打开的第一眼# 手写数字识别系统 —— 毕业设计项目 ## 1. 项目简介 基于PythonTensorFlow实现的手写数字识别系统支持MNIST标准数据集训练与自采手写图实时识别。核心创新 ✅ **零GPU依赖**纯CPU环境15分钟完成训练RTX3060需3分钟i5-8250U需18分钟 ✅ **真实手写适配**自研预处理算法解决手机拍照光照不均、倾斜、背景杂乱问题 ✅ **开箱即用**提供完整Flask Web界面Chrome访问localhost:5000即可演示 ## 2. 快速启动3步 1. 安装conda如未安装https://docs.conda.io/en/latest/miniconda.html 2. 创建环境conda env create -f environment.yml 3. 启动服务cd code python app.py → 浏览器打开 http://localhost:5000 ## 3. 文件说明 | 文件/目录 | 用途 | 是否必需 | |-----------|------|----------| | code/requirements.txt | 已验证的依赖版本清单 | ✅ | | code/app.py | 主服务入口含预处理预测 | ✅ | | models/best_model.h5 | 训练好的模型98.62%准确率 | ✅ | | data/mnist.npz | 离线MNIST数据免网络下载 | ✅ | | report/demo_video.mp4 | 答辩演示视频30秒含全过程 | ✅ | ## 4. 答辩演示建议 - **第一步**展示code/train.py训练日志强调“CPU环境20分钟达到98.5%” - **第二步**打开localhost:5000上传data/handwritten_raw/7.jpg显示识别结果 - **第三步**点击“开启摄像头”手写数字实时识别强调“预处理算法解决真实场景问题”。 **血泪经验**评委最关心“你解决了什么真实问题”而不是“用了多少层CNN”。把“手机拍照识别不准”作为问题起点把“预处理算法”作为你的工作亮点比讲10分钟反向传播更有说服力。避坑 / 常见问题 / 排查**现象本文还有配套的精品资源点击获取
返回列表