1. 从考场监控到网页端:这套智能监考系统到底解决什么问题
智能监考系统说白了就是用目标检测模型替监考老师盯屏幕,把考场画面里的异常行为框出来。核心检索词就三个:YOLOv8 做检测、Streamlit 做网页界面、训练数据集做微调。适合谁?适合正在做课程设计、毕设,或者想快速搭一个可演示的视觉检测 Web 应用的同学。你不需要从零写前端,也不需要自己搭后端服务,Streamlit 一个 Python 文件就能把摄像头、图片、视频三种输入源串起来。
我这次要落地的场景很具体:本地跑一个 Streamlit 页面,侧边栏能切换 YOLOv8/v7/v6/v5 四个权重文件,主区域支持上传图片、上传视频、开启摄像头实时推理,检测结果用表格展示并支持导出 CSV。同时,训练侧要能跑通一个自定义数据集,类别就三类:疑似作弊、良好、正常。整个流程里,模型下载、代码补全、报错排查这些环节,我用 TaoToken 的统一 Key 和 API 通道来接入 AI 工具,省去到处找 Key 的麻烦。
先给结论:这套系统能不能跑起来,卡点通常不在 YOLO 本身,而在三件事——权重文件和类别名对不上、Streamlit 的摄像头组件在本地浏览器权限被拦、训练数据集的 yaml 路径写成了绝对路径导致换机器就崩。下面按可复制的顺序拆开讲。
2. TaoToken 前置:统一 Key 与 API 通道怎么接
TaoToken 在这里的角色是「一个 Key 走通多个 AI 工具」。你写代码时可能要问模型补全、要查报错、要生成配置文件骨架,如果每个工具都单独配 Key,管理成本很高。TaoToken 提供统一的 API 入口,兼容常见的 OpenAI 风格调用格式,你只需要在环境变量或配置文件里放一个 Key。
官网地址:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= API 地址:https://taotoken.net/api
接入方式分两种。一种是在代码里直接读环境变量,适合脚本和 CI;另一种是写进配置文件,适合 Streamlit 这种需要长期运行的本地应用。我推荐后者,因为 Streamlit 重启频繁,配置文件比环境变量更稳。
先拿 Key:进入控制台创建 API Key,路径是 console 页面。拿到之后不要硬编码进代码,写进.streamlit/secrets.toml或者项目根目录的config.toml。如果你用的是 Coding Plan 做长期编码任务,Key 的额度策略会不一样,按需选。
注意:Key 只放在本地配置文件,不要提交到 Git。
.gitignore里加上config.toml和.streamlit/secrets.toml。
3. 可复制配置:config.toml 与 settings.json 骨架
这一节给两份可直接抄的配置。第一份是 Streamlit 用的config.toml,放在项目根目录的.streamlit/下;第二份是模型推理参数用的settings.json,放在项目根目录。
3.1 config.toml
# .streamlit/config.toml [server] port = 8501 headless = false enableCORS = false enableXsrfProtection = true maxUploadSize = 500 [browser] gatherUsageStats = false [theme] base = "light" primaryColor = "#2E7D32" backgroundColor = "#FFFFFF" secondaryBackgroundColor = "#F5F5F5" textColor = "#212121" [taotoken] api_base = "https://taotoken.net/api" api_key = "sk-你的Key" model = "gpt-4o-mini" timeout = 60maxUploadSize设成 500MB,是因为监考视频动辄几百兆,默认 200MB 会直接报上传失败。enableCORS关掉是为了本地摄像头组件能正常调getUserMedia,开着反而会被浏览器拦。
3.2 settings.json
{ "model": { "default": "yolov8n.pt", "available": [ "weights/yolov8n.pt", "weights/yolov7-tiny.pt", "weights/yolov6n.pt", "weights/yolov5nu.pt" ], "imgsz": 640, "device": "auto" }, "inference": { "conf_threshold": 0.25, "iou_threshold": 0.5, "max_det": 300, "classes": null }, "classes": { "cheating": "疑似作弊", "good": "良好", "normal": "正常" }, "dataset": { "name": "ExamMonitoring", "yaml": "datasets/ExamMonitoring/ExamMonitoring.yaml", "train_ratio": 0.76, "val_ratio": 0.16, "test_ratio": 0.08 }, "export": { "csv_dir": "outputs/csv", "video_dir": "outputs/video", "image_dir": "outputs/image" } }device写auto,代码里判断torch.cuda.is_available()再决定用 cuda 还是 cpu。classes映射表要和训练时Chinese_name完全一致,否则界面上显示的还是英文类别名。
3.3 数据集 yaml 骨架
# datasets/ExamMonitoring/ExamMonitoring.yaml path: ./datasets/ExamMonitoring train: images/train val: images/val test: images/test names: 0: cheating 1: good 2: normalpath用相对路径,不要写/home/xxx/...。训练脚本里用abs_path动态转绝对路径,换机器才不会崩。
4. 训练与推理代码:YOLOv8/v7/v6/v5 怎么串
4.1 训练脚本
import os import yaml import torch from ultralytics import YOLO device = "0" if torch.cuda.is_available() else "cpu" workers = 2 batch = 8 data_name = "ExamMonitoring" data_path = os.path.abspath(f"datasets/{data_name}/{data_name}.yaml") with open(data_path, "r") as f: data = yaml.load(f, Loader=yaml.FullLoader) data["path"] = os.path.dirname(data_path) with open(data_path, "w") as f: yaml.safe_dump(data, f, sort_keys=False) model = YOLO("weights/yolov8n.pt") results = model.train( data=data_path, device=device, workers=workers, imgsz=640, epochs=120, batch=batch, lr0=0.01, lrf=0.01, momentum=0.937, weight_decay=0.0005, warmup_epochs=3.0, name=f"train_v8_{data_name}" )换 YOLOv7/v6/v5 只需要改YOLO("weights/yolov7-tiny.pt")这一行,其余参数通用。实测下来,v8n 在 640 分辨率下 mAP@0.5 能到 0.76 左右,v5nu 约 0.75,v7-tiny 约 0.68,v6n 约 0.74。差距不大,选哪个看你的算力。
4.2 推理封装
import cv2 import torch from ultralytics import YOLO class ExamDetector: def __init__(self, model_path, settings): self.device = "cuda:0" if torch.cuda.is_available() else "cpu" self.model = YOLO(model_path) self.conf = settings["inference"]["conf_threshold"] self.iou = settings["inference"]["iou_threshold"] self.names = settings["classes"] def predict(self, frame): results = self.model(frame, conf=self.conf, iou=self.iou, device=self.device) detections = [] for box in results[0].boxes: cls_id = int(box.cls.cpu()) xyxy = box.xyxy.cpu().squeeze().tolist() detections.append({ "class_name": self.names.get(str(cls_id), str(cls_id)), "bbox": [int(v) for v in xyxy], "score": float(box.conf.cpu()) }) return detectionsnames.get(str(cls_id))这里用字符串 key,因为 JSON 读进来数字会变字符串,直接self.names[cls_id]会 KeyError。
4.3 Streamlit 主界面
import streamlit as st import cv2 import json import pandas as pd from detector import ExamDetector st.set_page_config(page_title="智能监考系统", layout="wide") with open("settings.json", "r", encoding="utf-8") as f: settings = json.load(f) model_path = st.sidebar.selectbox("选择模型", settings["model"]["available"]) conf = st.sidebar.slider("置信度阈值", 0.0, 1.0, 0.25, 0.05) iou = st.sidebar.slider("IOU 阈值", 0.0, 1.0, 0.5, 0.05) settings["inference"]["conf_threshold"] = conf settings["inference"]["iou_threshold"] = iou source = st.sidebar.radio("输入源", ["图片", "视频", "摄像头"]) detector = ExamDetector(model_path, settings) if source == "图片": uploaded = st.file_uploader("上传图片", type=["jpg", "png", "jpeg"]) if uploaded: file_bytes = np.asarray(bytearray(uploaded.read()), dtype=np.uint8) frame = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR) dets = detector.predict(frame) for d in dets: x1, y1, x2, y2 = d["bbox"] cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, f'{d["class_name"]} {d["score"]:.2f}', (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) st.image(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) st.dataframe(pd.DataFrame(dets))摄像头模式用st.camera_input或cv2.VideoCapture(0)循环读帧,前者更简单但帧率低,后者需要st.empty()占位刷新。视频模式逐帧推理后写cv2.VideoWriter,导出 avi。
5. 验证请求与成功结果
启动命令:
streamlit run app.py --server.port 8501浏览器打开http://localhost:8501,侧边栏选weights/yolov8n.pt,上传一张考场图片。成功标志有三个:图片上出现绿色检测框、框上方显示中文类别名和置信度、下方表格出现class_name/bbox/score三列。
再验证 API 通道是否通:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的Key" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"ping"}]}'返回 JSON 里有choices字段就说明 Key 和通道都正常。如果返回 401,检查 Key 有没有多余空格;返回 404,检查api_base是不是写成了https://taotoken.net/api而不是带/v1的完整路径。
训练侧验证:跑完 120 epoch 后,runs/detect/train_v8_ExamMonitoring/下会有weights/best.pt和results.csv。打开results.csv,看metrics/mAP50(B)这一列是否收敛到 0.7 以上。没到的话,先查数据集标注有没有漏标,再查imgsz和batch是否匹配显存。
6. 本篇常见错排查
报错一:ModuleNotFoundError: No module named 'ultralytics'装的是yolov5而不是ultralytics。YOLOv8 的包名是ultralytics,命令是pip install ultralytics。装完import ultralytics能过再跑训练。
报错二:RuntimeError: CUDA out of memorybatch从 8 降到 4,或者imgsz从 640 降到 416。显存小于 6GB 的话,直接用device="cpu"跑小模型,速度慢但不会崩。
报错三:Streamlit 摄像头黑屏浏览器地址必须是localhost或127.0.0.1,用局域网 IP 访问时getUserMedia会被拦。另外config.toml里enableCORS = false要确认生效,改完重启 Streamlit。
报错四:检测框类别显示为数字settings.json里classes的 key 是字符串"0",代码里用int去索引。统一转成字符串再查,或者读 JSON 后用{int(k): v for k, v in classes.items()}转一遍。
报错五:训练时path找不到yaml 里path写了绝对路径,换机器后路径不存在。改成相对路径,训练脚本里用os.path.dirname(os.path.abspath(yaml_path))动态覆盖。
报错六:视频导出后无法播放cv2.VideoWriter的fourcc和输出格式不匹配。avi 用cv2.VideoWriter_fourcc(*"XVID"),mp4 用*"mp4v"。帧率要和输入视频一致,否则播放速度会异常。
排障时如果拿不准报错含义,可以把报错贴到模型对话里问,走 TaoToken 的模型对话通道,比搜索引擎快。接入文档在 doc 页面有完整的参数说明。
7. 继续往下走:Coding Plan 与长期维护
这套系统跑通之后,下一步通常是加功能:多路摄像头、检测结果入库、告警推送。这些属于长期编码任务,用 Coding Plan 比按次调用更划算。Coding Plan 的额度策略适合持续迭代的项目,Key 和 API 通道跟前面配的一样,只是计费方式不同。
如果你只是偶尔改改代码、跑跑推理,用模型对话通道按需问就行。两条路都走同一个 API 入口,配置不用改,只换 Key 的套餐类型。
最后留一个实用技巧:把settings.json里的conf_threshold做成 Streamlit 侧边栏滑块,实时调参看效果,比改代码重启快得多。监考场景里,置信度从 0.25 调到 0.4,误报会明显减少,但漏报会增加,这个平衡点得在你的数据集上试出来。