ARTICLE DETAIL

资讯详情

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

中国软件杯参赛复盘:从工业视觉缺陷检测到完整系统落地经验

中国软件杯参赛复盘:从工业视觉缺陷检测到完整系统落地经验 第十届中国软件杯我们三个人的小队从初赛一路折腾到总决赛最后拿到了全国二等奖。说实话这个奖算不上特别亮眼但回头看那三个多月从选题、搭框架、调模型、写文档到现场答辩每一步都踩过坑也补过窟窿。这篇就当是给自己留的一份参赛复盘同时也想把一些可能在官方文档里根本找不到的经验写出来给准备参加软件杯、或者想从零做一个“算法系统”项目的同学一些参照。先说说中国软件杯是个什么性质的比赛。它是国内面向高校学生的软件类竞赛最大特点就是赛题大多来自企业的真实场景不是那种在实验室里自我封闭的题目。比赛要求参赛团队做的不只是一个算法也不只是一个网页而是一个能跑、能演示、能说明白业务逻辑的完整系统。这意味着整个备赛的过程和做一个小型产品非常接近需求分析、架构设计、前后端开发、算法落地、文档整理、答辩演示一个都跑不掉。所以这篇复盘不只是讲算法也会把系统开发和现场答辩的部分一起写全。1. 赛前准备为什么把宝押在工业视觉上说实话软件杯的赛题列表刚出来那天我们三个人花了大半个晚上在会议室里把题目从头到尾读了一遍。几十道题有的偏向大数据分析有的偏向物联网应用有的干脆就是一套企业管理系统开发。而我们的判断标准很简单技术上我们有积累、场景上能讲出故事、工作量上三个月能做完三个条件缺一个就换题。1.1 读懂赛题背后真正想要的东西我们最后选的是工业视觉方向题目名称大致是“基于深度学习的工件表面缺陷检测”要求做一个能对工件表面图片进行缺陷识别、结果可视化、并支持历史数据统计的系统。赛题表面上是“目标检测”但仔细读评分标准就会发现问题没那么简单评分不仅看模型的准确率还会看系统的完整性、交互便利性、部署可行性甚至文档的规范程度。这里就引出一个非常重要的参赛策略不要把“比赛”当成“写算法作业”。很多队伍花了95%的时间去刷mAP指标最后系统只有一个命令行demo答辩时根本没法演示评委也很难相信这个方案能落地。我们在赛前就定了一个原则算法要做系统也要做宁可精度从98%降到95%也要保证整个流程能顺滑地跑通。1.2 赛题拆解从一句话变成一张需求清单拿到题目后我们做了第一件很重要的事写需求清单。把赛题文字拆成功能点和非功能点。功能点大概有几类图像上传与检测、缺陷类别与位置展示、检测历史保存、统计数据可视化、用户登录与权限控制。非功能点包括响应速度、并发能力、部署方式、模型可迭代性等。拆完需求以后项目的边界就清楚了。很多团队失败在边界不清什么都想做最后什么都没打磨好。比如此时一个队友想加“实时视频流检测”另一个队友想做“手机端小程序”我们讨论后都砍掉了。理由是赛题没有明确要求视频流小程序则需要额外走一轮开发适配两个月内做出来的效果一定粗糙。比赛比的不是功能数量而是完整度和可靠性。1.3 团队分工与时间预算我们队就三人我负责整体架构、后端和算法服务小马负责算法模型训练和数据处理小周负责前端页面和交互。三个人都没有大厂实习经验就是普通在校生但我认为这种配置有一个好处每个人对“完整交付”都有体感不会出现互相甩锅的断层。时间预算上初赛周期大概有几个月我们把时间分成三段前期约40%做数据、模型和核心算法确保能跑通一条最小路径中期约35%做系统化把前后端、数据库、部署脚本补齐后期约25%专门打磨文档、演示视频和答辩PPT。事实证明这种时间分配是合理的因为算法永远没有“最优”一说如果一直陷在调参里系统部分就会被压缩到没法交付。2. 技术选型与整体设计稳定要比炫酷更重要很多第一次参赛的同学容易走进一个误区哪个技术新、哪个框架听起来厉害就用哪个。但比赛开发周期短、容错低技术选型的核心逻辑应该是“团队最熟的方案能满足功能要求的最简组合”。我们在这道题上所做的所有选型基本都遵循这个逻辑。2.1 检测算法为什么选YOLOv5工业表面缺陷检测本质上就是目标检测任务。当时摆在我们面前的主要选项有Faster R-CNN、YOLO系列和基于Transformer的检测模型。Faster R-CNN精度高但推理慢Transformer类模型效果上限高但训练成本大、对数据量要求高而YOLOv5在平衡精度和速度上是当时最稳的选择。有一个点值得细说软件杯的评分不是只看你模型最高能刷到多少分。决赛现场演示时系统要实时响应用户上传的图片如果一张图要算好几十秒评委体验会很差。YOLOv5这种单阶段的模型在同样精度下推理速度明显更快普通CPU也能勉强跑放在GPU上更是秒级响应。考虑到决赛现场提供的机器不一定有高性能显卡选一个“在较差条件下也能work”的方案远比追求那零点几个点的mAP更重要。2.2 系统架构前后端分离加算法服务系统层面的设计我们采用的是目前比较主流的前后端分离架构前端用Vue3加Element Plus做页面后端用FastAPI提供REST接口算法模型封装成一个独立的服务数据存MySQL。整体的数据流大概是这样的浏览器上传图片后端保存图片并把任务交给算法服务算法服务运行模型推理把检测结果类别、坐标、置信度返回给后端后端将结果写入数据库前端再从接口拉到结果并可视化。这套架构的好处是模块边界清晰。实际开发时算法组和前端组可以并行开工只要事先约定好接口格式就行。我们当时用了一周时间把接口文档敲定之后三个人基本没互相等过。从后端的角度看FastAPI写起来非常快自带的Pydantic还能做参数校验省了很多传参错误。前端则不需要关心模型具体怎么推理只要拿JSON渲染就行。2.3 被我们主动放弃的技术方向这里想专门说说“不做”的选择。我们讨论过要不要用K8s做容器编排讨论过要不要引入Redis做缓存也讨论过要不要用消息队列来实现异步任务。最后全砍了。原因不是这些技术不好而是当时的项目规模不需要它们。图片上传、检测、结果落库这个流程是典型的同步请求用不上消息队列部署也只需要一台服务器用Docker Compose起三个容器就够强行上K8s只会让团队陷入环境配置的泥潭。比赛和真实项目有一点很像并不是技术越复杂越好而是“复杂度要和问题匹配”。把简单方案做好比把复杂方案做砸要强得多。我们当时也差点被“技术先进性”迷惑后来指导老师提了一句“评委更关心你的系统能不能稳定跑完演示”我们才真正下定决心做减法。3. 核心功能落地从数据集到可演示的系统这一步是整个项目最花时间的部分也是我们收获最大的阶段。我会尽量把每个环节的关键决策和具体操作都写出来方便后面参赛的同学直接参考。3.1 数据集开源打底、自采补充模型要检测的缺陷类型赛题描述里写得比较泛我们结合实际场景整理出了几类常见缺陷划痕、裂纹、麻点、夹杂等。第一版训练数据用的是开源缺陷数据集比如NEU-DET热轧带钢表面缺陷数据集里面有六类带钢表面缺陷图像质量比较接近真实的工业采集场景。不过直接用开源数据有一个问题它和赛题里的“工件表面”并不完全一致迁移到实际检测目标时性能会下降。解决办法是补充自采数据。我们找学院借了几块样品工件用一台普通微距相机在不同光线条件下拍了几百张图片再通过裁剪、旋转、加噪声等方式扩充到近千张。虽然数量不大但能让模型见识到更多“不像开源数据集那样规整”的真实瑕疵形态。数据标注用的是LabelImg把每张图里的缺陷框成矩形框。标注这件事非常枯燥但质量直接决定模型上限花了两天多时间我和小马轮流标每条框都会反复确认避免出现漏标和错标。3.2 模型训练参数、指标和调优方法训练部分我们用的是YOLOv5官方仓库代码在自己改了一些数据加载逻辑之后基本就是标准流程。这里列一组当时的核心配置输入图片大小640×640batch size在8到16之间浮动受显存限制训练轮数100轮优化器SGD初始学习率0.01使用余弦退火调度。训练过程中我们重点观察两个曲线一个是训练集和验证集的loss曲线另一个是mAP0.5和mAP0.5:0.95指标。在初版训练中我们发现一个典型问题训练loss一直在降但验证集的mAP涨到一定程度就停滞了。排查后怀疑是数据增强强度太大导致模型在增强后的分布上学偏了。于是把Mosaic增强概率从1.0降到了0.5同时减少了HSV色彩增强的幅度情况明显改善。另一个问题是小目标小尺寸缺陷经常漏检怎么处理这部分在后面“踩坑实录”里详细展开。这里想强调的是一点经验遇到指标不涨先检查数据和增强不要一上来就动网络结构。算法推理服务这部分代码逻辑其实很简洁核心就是把训练好的权重文件加载进模型然后对外提供检测接口。以下是一个极简示例# 算法服务核心YOLOv5 推理封装 import cv2 import torch # 加载训练好的权重文件 model torch.hub.load(ultralytics/yolov5, custom, pathweights/best.pt, force_reloadTrue) model.conf 0.35 # 置信度阈值 model.iou 0.45 # NMS 的 IoU 阈值 def detect(image_path: str): img cv2.imread(image_path) results model(img) df results.pandas().xyxy[0] # 返回 xyxy 坐标、置信度、类别 detections [] for _, row in df.iterrows(): detections.append({ class: row[name], confidence: round(float(row[confidence]), 4), bbox: [int(row[xmin]), int(row[ymin]), int(row[xmax]), int(row[ymax])] }) return detections这段代码虽然短但已经是整个算法服务的主体。生产版本里我们还加了模型加载缓存、异常图片处理和日志记录避免偶发因素影响稳定运行。3.3 后端接口与前端展示后端的核心接口有三个上传图片并检测、查询历史记录、获取统计报表。上传接口接收文件后保存到临时目录调用上面的detect函数得到检测结果后连同原始图片地址一起存入数据库。查询接口很简单分页返回历史记录。统计接口则是按日期和缺陷类别做聚合返回给前端画图表。前端页面主要分三个区块检测工作台、历史记录、数据看板。检测工作台是核心左边上传图片或拖拽图片右边实时显示模型画完框的结果每个框上标着缺陷类别和置信度。历史记录页是一个表格可以点开任意一条查看当时检测的原图和结果。数据看板用了ECharts画了各类缺陷的占比饼图和每天检测量的折线图。这些页面单个看都不难但组合起来就构成了一个“看起来像个产品”的完整系统在答辩时非常加分。4. 踩坑实录五件让人血压升高的事下面这部分是从真实开发日记里挑出来的问题含金量我觉得比较高。每个问题都按照“现象—原因—解法”的结构来写方便后来者直接对照排查。4.1 标注格式不统一loss直接炸掉现象是模型训着训着loss突然飙到几十然后一直不降。排查了很久才发现开源数据集的标注是VOC格式的XML文件而YOLO训练需要TXT格式的归一化坐标转换脚本本身没有报错但是我们漏了有一条数据没有对应的标注文件加载时被默认当成了背景图导致那个batch的loss异常。原因很简单数据流水线里没有做“标注完整性校验”。解决办法是写了一个脚本遍历所有图片检查每张图是否有对应的TXT文件并顺手验证TXT里的类别ID是否在合法范围内。这里建议参赛队伍一定要在数据处理阶段加上这种自动化校验靠人眼扫描几千个文件根本不可行。4.2 小缺陷目标总是漏检我们最初用默认anchor策略训练发现小尺寸的划痕和麻点经常检不出来。为了排查我们把测试图片里漏检的目标单独导出来统计了尺寸分布发现这些缺陷的面积占比普遍过小有些甚至只有20×20像素。YOLO模型在小目标上的表现本身就比大目标弱这是检测模型的一个通病。我们针对性地做了三件事一是把输入尺寸从640提高到960显存允许的情况下二是开启多尺度训练让模型在多种分辨率下都能学习特征三是用k-means在自有数据集上重新聚类了anchor尺寸而不是直接用COCO预训练的默认值。做了这三步之后小目标召回了明显提升但在最终线上方案里我们依然保留了一个“图像分块”的后处理选项对图片做切片每个切片独立推理再合并结果进一步降低漏检。4.3 只有一块8G显存的显卡训练像挤牙膏我们的算力条件比较一般实验室只有一块8G显存的GPUbatch size稍微开大一点就OOM。后来用了两个办法一是开启混合精度训练显存占用直接降了大概三成二是使用梯度累积把batch size为4的梯度累积4步模拟出一个batch size为16的训练效果。这两个小技巧让我们的训练稳定了很多。这里有一个需要记住的教训刚开始训练时不要追求大batch先把整个训练流程跑通并确保能够正常输出权重文件再慢慢加大batch和epoch。我们第一周反复在环境问题和OOM里折腾一度连模型权重都没保存出来后来把“先跑通再调优”刻在了脑门上进度才走上正轨。4.4 前端统计图和数据库里的数字对不上系统联调阶段小周发现数据看板上的缺陷数量和历史记录页的列表对不上同一个日期条件查出来的数量不一致。查问题发现后端统计接口里用了DateTime类型做分组前端传过来的日期参数却是字符串没有做时区转换导致部分记录被归到了前一天或后一天。问题本身不复杂但联调时很隐蔽。后来我们做了一件事前后端所有时间字段统一使用ISO 8601字符串格式并带时区偏移量。同时在接口文档里明确写了每个字段的类型、格式和示例值。从那以后类似问题就很少出现了。接口文档的力量真的不能低估尤其是三个人并行开发的时候。4.5 答辩前夜最佳模型被新训练覆盖了距离提交作品还有两天时小马想再试一次调高训练轮数看能不能涨点精度。结果那次调优实验因为学习率设置不好最终指标反而不如之前。等他准备回滚时发现best.pt已经被覆盖了而旧的权重文件没有备份。那一刻我们三个人都沉默了很久。后来我们建立了一个最简单的版本管理习惯每次训练结果都保存到一个以“时间关键指标”命名的目录里比如best_0701_0909_mAP0956.pt训练脚本自动完成这些命名不再使用固定的best.pt。注意模型文件也是项目资产必须像代码一样重视版本管理。本地和云端服务器各保留一份副本这个习惯建议从第一天就开始执行不要等出事了再后悔。类似的问题其实还有很多下面用表格简要做个梳理方便大家随时对照。问题现象主要原因解决思路训练loss突增标注文件缺失或格式不统一增加数据校验脚本统一为YOLO格式小缺陷漏检anchor与数据不匹配输入尺寸小重聚类anchor增大输入尺寸多尺度训练显存不足batch size过大资源受限混合精度训练梯度累积前端与后端统计不一致时间格式与字段不统一约定ISO 8601格式完善接口文档模型文件被覆盖固定名称保存缺少备份按时间与指标命名多重备份5. 总决赛阶段的准备与现场应对作品提交之后很快就收到了决赛通知。这轮备赛和之前很不一样因为研发阶段基本结束重点变成了“怎么把项目讲好、演好、答好”。我们花了不少时间准备决赛材料并且在现场遇到了几件计划外的事都值得说一下。5.1 PPT怎么讲才像真的做过一个项目决赛评审时间有限PPT一定不要做成流水账。我们最后采用的顺序是先说业务背景和痛点再讲总体架构和关键模块设计然后放一段真实运行演示最后简洁说明创新点和后续优化方向。每一页PPT只讲一个核心结论。准备PPT时的体会是评委里既有学术专家也有企业专家所以内容既要讲清楚技术方案也要说清楚业务价值。我们的检测系统本身技术深度不算特别高但我们在介绍时特别强调“系统能够降低人工质检的重复性劳动”还给出了一个替代人工的效率估算就是“一张图片的检测响应时间约几百毫秒质检员人工看一张图需要好几秒”。这种落在业务上的表达比单纯念网络结构更容易让人记住。5.2 现场演示永远准备两条路决赛现场最让人紧张的其实是环境差异。我们之前一直假设现场有一台装了完整环境的电脑结果发现演示机器上的显卡驱动版本很老模型加载时会触发一个依赖报错。还好我们在提交材料前录好了完整的演示视频立刻切换到视频演示同时用笔记本上的本地环境做了补充讲解整个过程没有冷场。后来复盘建议所有团队把“演示”看成项目的一部分第一必须在项目的干净环境里完整录一段演示视频视频要走完所有核心功能第二准备一个便携的离线运行环境比如把Docker镜像整体打包好第三现场演示前先测试一下机器至少确认浏览器能开、后端能起、数据库能连。演示过程越稳评委对项目的信任度就越高。5.3 评委提问环节的几个高频问题我们在答辩时遇到的高频问题大概有这几类数据集来源和标注方式、模型选型理由、系统在真实产线上的可行性、以及数据安全和隐私问题。关于前两个问题直接照实回答就可以。真正容易翻车的是“真实产线可行性”因为评委其实是在考察你有没有想过落地问题。我们的回应思路是承认当前算法在实际复杂光线和复杂背景下仍有局限然后说明系统提前预留了“模型替换接口”当该类场景积累更多数据后可以在不修改整体系统的情况下更新模型。同时强调我们用Docker封装了全部环境可以比较方便地部署到产线边缘设备上。这样表达出的是一种“我知道差距并且知道怎么补”的状态评委基本都会认可。6. 一些实在的参赛经验分享写完上面的复盘最后再聊几句个人体会。这个部分是写给下一届软件杯参赛者的没有太多理论全部来自亲身体验。第一比赛最稀缺的资源不是技术而是时间和注意力。三人团队看起来人多但三个人各有各的课和杂事真正能同时扑在项目上的时间远比想象中少。我的建议是每周固定两个集中开发时段其他时间通过文档异步推进千万不要靠“想起来再说”来协作。第二文档和代码同样重要。软件杯的评分虽然主要看作品但文档、PPT、演示视频都会影响评委对团队的印象尤其是技术文档最好在开发过程中随手写不要最后两天赶工。第三遇到问题先看日志。我们不少次以为很难的bug最后就是一套日志、几行print就能定位到根因真正难的是去读日志之前的拖延心理。第四比赛结果其实只是很小的一部分更重要的是你能在几个月里体验一遍“把想法变成可用系统”的全过程这个经历放到面试和项目经验里都非常值钱。如果要说给出一条最有用的建议那就是从第一天开始就按“最终交付”的标准做每一个模块不要把“调好再集成”“文档最后再写”挂在嘴边。比赛和真实软件开发一样所有欠下的债到最后都是要还的。
返回列表