人工智能专业毕设选题实战指南:从选题误区到可落地的工程化项目
摘要:很多 AI 方向的毕设死在“选题太飘”。本文用“能跑、能看、能讲”三原则,帮你在 12 周内把一个真实项目交到答辩老师手里。全程不讲玄学,只给能复制的代码和踩坑记录。
一、先别急着“改变世界”,这些坑 80% 的人踩过
误区一:追 SOTA 当目标
把“超越 YOLOv8”写进任务书,结果 8 周过去连 COCO 都下不完。记住,毕设不是发顶会,0→1 比 90→95 更有意义。误区二:数据一口吃成胖子
开口就要“10 万条医学影像”,最后发现医院不给脱敏数据,项目直接 GG。
建议:先用 2000 条公开数据跑通 MVP,再考虑增量。误区三:只训不部
笔记本上 loss 漂亮得很,一上服务器 CUDA 版本冲突,连 env 都建不起来。
实战规则:训练那天就要把推理脚本写成 API,Day1 就部署。误区四:界面=PowerPoint
答辩现场打开 PPT 开始录屏,老师一句“现场跑一下”直接社死。
可演示 > 可截图,把 Gradio/Streamlit 嵌进去,三分钟就能互动。
二、3 个“能落地”的毕设方向对比
| 方向 | 技术深度 | 数据规模 | 算力要求 | 展示亮点 | 技术栈组合 |
|---|---|---|---|---|---|
| 边缘端图像识别(树莓派+摄像头) | 中等:MobileNet v3 量化 | 1k~5k 张自拍数据集 | 1080Ti 训,边缘端推理 | 实时画面画框,延迟 <200 ms | PyTorch→ONNX→ONNXRuntime |
| 小样本对话系统(专业 FAQ) | 中高:BERT+对比学习 | 500 条意图+回答 | RTX 3060 足够 | 3 轮对话不跑偏 | HuggingFace Transformers→FastAPI→React |
| 时序异常检测(机房温度) | 中高:LSTM AutoEncoder | 10 万点 CSV | 笔记本可训 | 实时折线+异常阴影 | PyTorch→Flask→Plotly Dash |
评分维度:老师最关心“完整性”“工作量”“可展示”。边缘端方向自带“实物”,对非算法老师杀伤力最大,下文以它为例做全流程拆解。
三、边缘端图像识别:从 0 到可运行 MVP
3.1 任务定义
“在树莓派 4B 上识别‘是否有人戴口罩’,1080p 画面下 FPS≥8,支持 Web 端实时查看。”
——目标清晰、边界明确,12 周可交付。
3.2 数据闭环(2 天搞定)
- 用手机自拍 1500 张,戴口罩/不戴各半;
- 用 Roboflow 在线标注→导出 YOLO 格式;
- 写脚本自动划分 train/val,生成 data.yaml;
- 上传百度网盘,README 里放永久链接,老师一看就觉得“这孩子做事稳”。
3.3 训练脚本(Clean Code 示范)
以下代码可直接python train.py --data data.yaml --epochs 50开跑,已加关键注释。
# train.py import argparse, yaml, os, torch from yolov5 import train # ultralytics/yolov5 def load_hyp(param_file): """超参统一读 YAML,避免魔法数字散落在代码各处。""" with open(param_file, encoding='utf-8') as f: return yaml.safe_load(f) def main(): parser = argparse.ArgumentParser() parser.add_argument('--data', required=True, help='data.yaml path') parser.add_argument('--epochs', type=int, default=30) parser.add_argument('--weights', default='yolov5m.pt') args = parser.parse_args() hyp = load_hyp('hyp.scratch.yaml') train.run( data=args.data, epochs=args.epochs, weights=args.weights, imgsz=640, batch_size=16, device='0', # 单卡 hyp=hyp, project='runs/mask_det', name='exp', exist_ok=False ) if __name__ == '__main__': main()技巧:把
yolov5当子模块git submodule add,版本锁定,换电脑一键复现。
3.4 模型转换与量化(体积↓75%)
训练完最佳权重best.pt→ 导出 ONNX:
python export.py --weights runs/mask_det/exp/weights/best.pt --include onnx --dynamic再量化到 INT8(树莓派 CPU 有加速):
# quantize.py import onnx from onnxruntime.quantization import quantize_dynamic, QuantType model_fp32 = 'best.onnx' model_int8 = 'best_int8.onnx' quantize_dynamic(model_fp32, model_int8, weight_type=QuantType.QInt8)3.5 边缘端推理服务(FastAPI+Jinja2)
目录结构:
mask_det/ ├─ main.py # FastAPI ├─ detector.py # 推理核心 ├─ templates/ │ └─ index.html # 简单网页 └─ static/ └─ jsmpeg.min.js # 低延迟 websocket 视频流关键片段:
# detector.py import cv2, onnxruntime as ort, numpy as np, albumentations as A class MaskDetector: def __init__(self, model_path='best_int8.onnx', conf_thres=0.5): self.session = ort.InferenceSession(model_path, providers=['CPUExecutionProvider']) self.conf_thres = conf_thres # 统一前处理 self.transform = A.Compose([A.Resize(640, 640), A.Normalize(mean=0.0, std=255.0), A.pytorch.ToTensorV2()]) def __call__(self, image): # image: BGR numpy input_blob = self.transform(image=image)['image'].unsqueeze(0).numpy() outputs = self.session.run(None, {'images': input_blob})[0] # 后处理:NMS+conf filter boxes = self.nms(outputs) return boxes# main.py from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from detector import MaskDetector import cv2, asyncio, uvicorn app = FastAPI() detector = MaskDetector() @app.get("/", response_class=HTMLResponse) async def index(request: Request): return templates.TemplateResponse("index.html", {"request": request}) @app.websocket("/ws") async def ws_video(websocket: WebSocket): await websocket.accept() cap = cv2.VideoCapture(0) # 树莓派摄像头 try: while True: ret, frame = cap.read() if not ret: break boxes = detector(frame) # 画框 for *xyxy, conf, cls in boxes: cv2.rectangle(frame, (int(xyxy[0]), int(xyxy[1])), (int(xyxy[2]), int(xyxy[3])), (0,255,0), 2) _, jpeg = cv2.imencode('.jpg', frame) await websocket.send_bytes(jpeg.tobytes()) await asyncio.sleep(0.03) # ~30 FPS except WebSocketDisconnect: cap.release() if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000)启动后浏览器访问
http://<树莓派IP>:8000即可看到实时画框,手机也能点开,答辩现场效果拉满。
四、生产级问题:老师不会问,但你得准备好
冷启动延迟
首次推理 ONNXRuntime 要建缓存,画面会卡 1~2 s。解决:服务启动后先对全黑图跑一次,把缓存“预热”掉。并发资源竞争
默认 ONNX 线程数=CPU 核数,树莓派 4B 只有 4 核,多 websocket 会互相阻塞。
方案:- 在
InferenceSession里设sess_options.intra_op_num_threads=2,留 2 核给系统; - 用
asyncio.Queue把解码和推理拆成两个协程,避免同步等待。
- 在
结果幂等性
同一张图多次推理输出框坐标抖动?后处理 NMS 阈值固定+seed 固定即可;写单测断言“同一帧两次结果 IoU>0.95”。日志与可观测
记录每次推理耗时、返回框数,用 Prometheus+Grafana 画面板,老师看到监控图会觉得你像“干过运维”。
五、避坑指南(血泪版)
- 数据合规:自拍记得打码背景,避免拍到他人肖像;用公开数据集要遵循 License,README 里贴链接+声明。
- 算力不足:实验室显卡排队?把图片尺寸 640→416,batch 降到 8,epoch 先跑 20 个再慢慢加。
- 答辩稳定性:
- 带两块树莓派,一主一备;
- 提前录好 30 秒 demo 视频,万一现场网络罢工直接放 MP4;
- 代码放 GitHub 私有库,答辩前三天把 Release 打标签,老师要看版本差异随时 checkout。
- 论文别写“我提出了新算法”,改成“我完成了完整工程化落地”,老师读起来不头疼。
六、结语:先跑起来,再谈理想
边缘端口罩检测只是模板,把代码里detector换成你自己的模型,就能秒变“火焰识别”“猫脸识别”。毕设不是科研,是秀“你能把 AI 用到地上”。
先按本文搭出 MVP,第 4 周就能给导师演示,剩下的 8 周慢慢打磨界面、写论文、补实验,节奏稳得一匹。
动手吧,祝你 12 周后带着可运行的树莓派,笑着走出答辩教室。