2026年软件学院创新项目实训博客(一)
实训通知下来那天,我们仨在实验室对着选题清单愣了一下午。软件学院的创新项目实训,说难不难,说容易也真不容易——它考验的不是能不能把某个功能写出来,而是能不能在六周时间里把一个想法做成一个能演、能讲、能继续迭代的原型。我们第一轮列出的七八个方向几乎全是“换皮”思路:旧书流转平台、社团活动小程序、宿舍报修工具……写来写去,最后总逃不出增删改查的套路。真正让我们转向的,是一次线上课堂里的小尴尬:老师问“刚才这个概念都听懂了吗”,屏幕上一片沉默,没人点头,也没人摇头。于是我们萌生了一个想法:能不能让课堂反馈变得可视化,让老师一眼看出哪些环节学生是真听进去了,哪些环节其实已经走神。
这篇博客是实训系列的第一篇,我会把从选题、组队、技术选型到第一周踩坑的完整过程写下来,给正在纠结选题的同学一点参考,也给自己留一份复盘。如果你也在准备类似的项目实训,或者对“计算机视觉+教育场景”这个方向感兴趣,可以从这里看到我们真实的决策路径和翻车现场。
1. 选题:从“想做点什么”到“能做完什么”
1.1 为什么“换皮”类选题一开始就出局
最初列选题清单时,我们和大多数组一样,优先想的是“什么需求最真实、最好凑功能”。自习室预约、二手书流转、失物招领、社团活动报名,这些方向需求确实存在,随便一个小组都能在一周内搭出页面。但正因为人人都能做,它们反而撑不起“创新”两个字。
实训答辩时评委常问一句话:“你的系统核心难点在哪?”如果答案只是“我们用了新框架”“我们把页面做得很好看”,项目就会妥妥地被归到课程设计那一档。换皮类项目的另一个隐性问题是时间分布失重:功能实现只需要两周,剩下四周全在调样式、改文案,既没有技术积累,也没有可持续迭代的空间。我们不想六周后拿一个只能演示的“管理系统”出来,所以第一轮清单基本作废。
1.2 从线上课堂的反馈真空里找切入点
真正让我们定下方向的,是那个一直存在但我们习以为常的痛点:课堂上的“反馈黑洞”。线下课老师能靠眼神、集体点头、翻书写字的声音判断节奏,可一旦换了线上或混合式教学,这些信号全断了。老师在屏幕这头讲,学生在屏幕那头沉默,双方都没有一个轻量的工具把学习状态外化出来。
我们把这个场景拆开看,发现技术条件其实已经成熟。摄像头是现成的,计算机视觉对“抬头还是低头”“有没有举手”“是在写字还是盯着屏幕发呆”这类行为的识别精度已经很高,不需要从零训练。难点反而在系统层面:怎么把视频抽帧、行为分类、统计聚合、可视化展示这一整条链路串起来,做成一个能对一节课给出量化报告的小工具。我们给这个方向起了个内部代号叫“课堂反馈雷达”,核心目标很简单:输入一段课堂视频,输出参与度曲线、走神高发片段和互动频次汇总。
这里要专门说一下隐私边界。我们一开始就约定:只处理自己录制、自愿出镜的模拟课堂视频,不做人脸身份识别,不采集真实班级数据,前端展示的永远是统计分布而不是个人影像。这个边界不是嘴上说说,而是直接写进了需求文档——它既是伦理要求,也是项目能否落地的必要条件。
1.3 把大目标收敛成三个能验收的模块
确定方向后,我们做的第一件事不是写代码,而是把这个看似宏大的目标砍成三个边界清晰的模块。
第一个是视频预处理模块。读入视频、按固定时间间隔抽帧、把画面裁到课桌出现的主要区域、做基本的光照归一化。这个模块看起来不起眼,却是后面所有分析的地基,抽帧间隔、裁切范围直接影响识别效果。第二个是行为识别模块。对每一帧输出每个学生的姿态标签(抬头、低头、侧头)、动作标签(书写、举手、静止)和表情状态标签(专注、困惑、游离),然后加一个滑动窗口做多数投票,避免单帧抖动带来忽高忽低的噪声。第三个是统计可视化模块。把标签序列聚合成参与度得分、课堂时间轴热力图、走神高发片段列表,最终在前端用看板形式呈现。
我们还专门列了“不做清单”:不做真实人脸身份识别,不做实时摄像头接入,不做细粒度情感识别。这几个坑一旦踩进去,六周绝对填不完。边界划得越早,后面越不会陷入无底洞式的需求蔓延。
2. 团队分工:三个人怎么把一个“AI+数据可视化”项目拆开
2.1 角色分配按交付物,不按“谁是大佬”
三个人分活看起来简单,实际很容易翻车。常见错误是前端、后端、算法的分工只写在文档里,干了两周才发现有人忙死有人闲死。我们这次采用的办法是:每个人都对应一个独立可演示的交付物。
队友A负责模型侧,交付物是一个能处理指定视频、产出结构化检测结果的命令行工具;队友B负责后端,交付物是一组查询接口和一个统计聚合模块;我负责前端,交付物是能展示检测结果的可视化看板页面。三个人每周都有东西拿出来拼,而不是“等上游做完了我再动”。
这个分工还有一个好处:答辩时每个人都能讲清楚自己做的部分,不会出现“整个系统好像都是谁谁谁一个人写的”这种尴尬。更重要的是,模块边界清晰以后,并行开发成为可能,A在调模型参数的时候,B可以写解析逻辑,我可以画前端页面,谁也不用干等。
2.2 接口契约先于代码:哪怕模型还没训练,也要先定JSON
第一次组会我们没写一行代码,只做了一件事:把模型侧的输出格式定死。这也是三个月前某次课程设计踩过的坑——当时前后端各自写,最后联调才发现字段命名各叫各的,光改字段就花了两天。
这次我们直接把检测结果的JSON格式定成契约:
{ "frame_id": 120, "ts": "00:10:03.200", "students": [ { "id": 1, "pose": "look_up", "action": "writing", "emotion": "focused" }, { "id": 2, "pose": "look_down", "action": "idle", "emotion": "distracted" } ] }约定好之后,A按这个格式输出,B按这个格式解析,我按这个格式渲染。模型没跑通怎么办?用Mock数据顶上。我当天就写了一个假数据生成器,随机造出几百帧检测结果,把前端页面先画起来,把后端接口先测起来。后面A的模型一跑通,只要把真实输出替换成Mock数据源,整条链路立刻就能通。这个决策省下来的时间,远超我们预期。
2.3 六周的预算怎么排
我们做了一张粗排期表,贴在实验室的白板上:
| 周次 | 目标 | 关键交付 |
|---|---|---|
| 第1周 | 选题确定、技术调研、环境搭建 | 接口契约、需求边界文档 |
| 第2周 | 数据准备、模型预研 | 模拟课堂视频、预训练模型验证 |
| 第3-4周 | 核心识别管线跑通 | 视频→结构化检测结果工具 |
| 第5周 | 前后端对接、参与度算法调优 | 可联调的全链路Demo |
| 第6周 | 演示视频、文档、答辩准备 | 项目原型、展示材料 |
排期时我们特意把“模型训练”从主线里拿掉了。这不是逃避技术难点,而是基于现实判断:六周时间根本不够从零训练一个可靠的行为识别模型,也不会有人要求实训项目必须发论文。更务实的做法是使用现成的预训练模型做推理,再结合业务规则修正结果,把重心放在“系统能不能稳定跑完一节课的视频”上。事实证明这个取舍非常关键,它让我们避开了训练环境的一堆麻烦,把精力投在了真正有区分度的地方。
3. 技术选型:为什么我们没有无脑上全家桶
3.1 主链路统一在Python生态,减少沟通成本
技术选型时我们定了一个基本盘:主链路全用Python生态。A的模型侧要用Python,这是没得选的事;B的后端如果用Java或别的语言,光通信格式就能扯半天。统一之后,A的检测脚本可以直接被B的聚合模块复用,我处理数据样本也能直接用同一个环境,省去了跨语言桥接的沟通成本。
后端没有选择重型全家桶,而是用了Python生态里比较轻量的异步API框架。理由很简单:项目规模就那么大,后端只需要提供三四个查询接口,没必要把一套完整工程体系拖进来。数据库选了本地文件型数据库,方便在演示环境部署,也避免评委老师现场问“数据库密码是什么”带来的尴尬。有人可能觉得这些选型太简陋,但实训项目的评判标准是“在限定时间内做出能演示并讲清楚逻辑的东西”,不是生产环境的高可用系统。
3.2 前端坚持轻量单页方案:数据可视化是核心,页面本身能跑就行
前端方面,我们一开始就达成共识:可视化是核心,其他都是次要的。所以没有引入重型UI组件库,也没有做多页面路由的复杂设计,就是一个单页看板,包含总参与度数字、课堂参与度时间轴热力图、走神高发片段列表、学生状态分布几个区域。
图表部分直接用了现成的开源可视化库,没有自己画Canvas。这节省了大量样式调试时间,也保证了图表交互的一致性。我可以直接把后端返回的统计结果丢给图表组件,做到“接口出什么,页面画什么”。对实训项目来说,把页面做得华丽不如把页面做得诚实——它应该直接反映后端数据,而不是靠动画截图撑场面。
看板页只有一个,但我们约定“页面不能写死任何业务数字”,所有数值都必须来自接口。这个约定当时看没什么,后来Mock数据和真实数据切换时才体会到有多省心:前端页面一行代码没改,只是数据源换了,整条链路就通了。
3.3 视觉模型:预训练权重 + 规则修正,而不是从零训练
模型侧的选择可能是整个项目里最重要也最容易跑偏的地方。A最开始也想过“自己训练一个分类器”,被我们劝住了。现实情况是:我们手头没有带标注的课堂数据集,训练时间不够,计算资源也有限。真正可靠的做法是两条腿走路。
第一步,姿态估计直接使用现成的预训练模型提取关键点,然后用角度阈值判断抬头、低头、侧头。比如通过肩膀和耳朵的相对坐标计算头部俯仰角,角度小于某个阈值就认为是低头。这个方案不需要训练,只需要把预训练权重加载进来做推理,A用半天就验证了可行性。第二步,手部动作和表情状态用一个预训练的图像分类模型做基础分类,再针对我们自己的模拟视频做了少量规则修正——比如连续十帧检测到低头,才标记为“游离”,否则视为“正在看书”。
为什么需要规则修正?纯模型的帧级输出噪声很大,可能前一帧是抬头,后一帧因为轻微移动就被判定为低头。我们加了一个滑动窗口多数投票机制:每十帧做一个投票,取出现次数最多的标签作为这个时间段的最终状态。这个机制极其简单,但效果立竿见影,稍后我会在踩坑部分详细说它是怎么在真实数据里救了我们一次的。
4. 第一周踩坑复盘:环境不一致带来的连锁事故
4.1 事故起点:“我电脑上能跑”
第一周的第四天,A发来消息说模型推理脚本已经跑通,输出了一批检测结果。B按A的代码在自己电脑上复现,结果一运行就报错,而且报错信息完全看不懂。我们第一反应是代码写错了,三个人开始一行一行对代码,对了两轮毫无收获。期间B不断尝试装依赖,连装了好几个库,报错反而越来越多。
最后B说了一句我们后来用了无数遍的话:“你电脑上能跑,不代表别人也能跑。”这句话听上去像吐槽,实际是环境不一致的典型症状。A跑通是因为他的全局环境恰好满足新版本库的某种行为,B的机器上库版本偏旧,同名函数拿到的返回值结构完全不同。问题不在代码逻辑,而在依赖环境。
4.2 定位过程:从报错到最小复现再到版本对比
复盘那次排错过程,我们最终沉淀出一套可复用的三步骤。
第一步,看完整报错栈,而不是只看最后一行。B把完整报错信息贴出来后,我们发现出错点在一个库的函数调用上,这个函数的作用是把检测结果整理成字典。第二步,写一个最小复现脚本,只保留出问题的那一两行调用,不掺任何项目代码。这个脚本在A的机器上正常,在B的机器上报错,问题范围立刻缩小到“库版本差异”。第三步,分别在两台机器打印库版本号,对比后发现A用的是新版本,B用的是旧版本。
真正有意思的是,新版本库改变了其中一个方法的返回值类型,导致A写代码时把返回值当成了新版格式来处理,而B的旧版本返回的是另一种格式。数据结构一错,后面所有字段解析全乱套。这个例子特别能说明问题:跨机器协作时,环境差异不是“偶尔会出现”,而是“一定会出现”,只是时间早晚。
4.3 修复与方法固化:依赖锁定加Mock数据救场
修复方案其实很简单:把项目依赖写成一个锁定文件,精确记录每个库的版本号,大家统一用虚拟环境安装,删除全局环境依赖。我们在项目里加了一个requirements文档,内容类似这样:
numpy==1.26.4 opencv-python==4.8.1 fastapi==0.104.1 uvicorn[standard]==0.24.0版本号一旦固定,B那边重新创建虚拟环境、按文档安装,问题立刻消失。但这件事给我们的教训远不止“装个固定版本”这么简单。我们意识到,依赖锁定必须在项目第一天就做,不能等技术问题出现再来补。后续任何人在安装新依赖时,都要同时更新锁定文件,并保证每个成员都在同一套环境里运行。
Mock数据在这次事故里也发挥了关键作用。由于A的脚本暂时不能跨机器跑,B和我直接使用了事先约定好的假数据继续开发。我们拿到的是同样的JSON结构,只是数据是随机生成的。结果就是,A在修环境的两天里,后端和前端进度一点没耽误。这件事让我第一次直观理解了“接口契约”这四个字的分量——它不只是减少联调麻烦,它是在队友遇到问题时,让整条流水线不至于停摆。
5. 第一周复盘:两个关键决策让项目没有走向失控
5.1 灰度验收单:把每块功能写成可勾选的验收标准
第一周结束复盘时,我们做了两个对后续影响很大的决策。第一个决策是建立“灰度验收单”。所谓灰度,就是把每个模块的最终验收拆成一系列客观可测的中间标准,不追求“一次做到完美”,而是让每一步都有明确的完成定义。
模型侧的验收项包括:能连续处理一段五分钟的模拟课堂视频不崩溃,推理速度不低于每秒两帧,检测结果的字段必须与契约完全一致。后端的验收项包括:能正确解析Mock文件,参与度得分的计算结果可复现,接口响应时间在本地环境低于五百毫秒。前端的验收项包括:看板能在三秒内完成首屏渲染,各图表能正确响应数据更新,不出现写死的业务数字。
这张验收单写完后,我们发现最直接的好处是:六周结束时不会互相扯皮。功能达没达标,跑一下验收项就知道。
5.2 每周一次全链路演示,哪怕数据是假的
第二个决策是坚持每周做一次全链路演示。听起来像废话,但实际执行起来很容易被跳过:模型还没跑通、前端还没做完、后端还在改Bug,为什么要浪费时间拼在一起?
我们强制执行四周后,效果非常明显。第一次全链路演示安排在第一周末,当时模型检测还没完全跑通,我们就把Mock数据接进后端,后端返回统计结果,前端渲染成图表。演示过程中立刻发现两个问题:一个是A输出的时间戳格式是毫秒,B解析时当成秒,时间轴完全错乱;另一个是前端拿到的字段大小写不统一,有的地方是student_id,有的地方是studentId。这两个问题如果拖到第五周才联调,至少要花半天排查,但在全链路演示里五分钟就暴露并当场修正了。
我自己的体会是:全链路演示的价值不在于“看起来有个东西在跑”,而在于把隐藏在接口缝隙里的问题提前逼出来。接口缝隙就是项目最大的隐藏风险,越早暴露,代价越小。
5.3 后续系列的内容预告
到这里,第一篇博客差不多该收尾了。按我们目前的节奏,第二篇我会写数据准备和模型识别从零到一的过程,包括我们怎么录制模拟课堂视频、怎么标注、怎么验证预训练模型的效果,以及滑动窗口多数投票在实际数据上到底带来了多少提升。第三篇应该会写前后端联调、参与度算法调优和最终演示的打磨过程。
如果让我对下一届同学说一句最想说的话,我会说:选题的价值看得见,团队的治理方式看不见,但恰恰是看不见的部分决定了六周结束时你站在台上的是惊喜还是事故。定好边界、定好契约、定好验收标准,剩下的就是每周老老实实把系统拼起来跑一遍。项目很扎实的时候,答辩准备其实是最轻松的一环。