首页 / 资讯中心 / 文章详情

YOLOv3危化品车辆危险货物标志识别与边缘部署

YOLOv3危化品车辆危险货物标志识别与边缘部署 ★ FEATURED ARTICLE
简介面向交通管理、运输安全监控与车辆身份识别场景的技术文献围绕高速公路危化品运输车辆货物类型自动识别问题给出基于YOLOv3的实时目标检测思路。PDF共1个文件约2.41MB内容来自《山西交通科技》2020年第4期适合智能交通、计算机视觉及危化品运输安全方向的学习者与工程人员参考。文中覆盖机器视觉抓拍系统设计、高清监控与红外补光采集、危化品车辆货物类型数据集构建、labelImg标注流程、YOLOv3网格预测与损失函数以及10000张背面图像、23类22174个标注框的训练实验设置和不同光照天气下的识别结果。已有107人学习可用于快速了解危化品车辆标志识别从数据采集到模型验证的完整技术链路并获取论文写作与课题申报时可借鉴的实验组织、指标描述和场景落地思路。1. 需求先立住卡口那几秒决定救援带不带防爆装备危险货物运输车辆跑的是长距离、跨地区线路事故的时间地点几乎无法预判现场处置的第一个分岔口往往不是“派几台车”而是“罐里到底装的是什么”。基于深度学习的危化品车辆货物类型识别技术填的就是这个信息缺口车辆驶过高速公路卡口、抓拍机拍下车尾的瞬间把车尾那块菱形危险货物标志读出来把类别实时回传给监管中心。它适合三类读者参考——正在做目标检测方向毕设或课设的学生、要给交通或园区做车辆管控方案的算法与集成工程师、以及需要把“识别结果”接进应急处置流程的系统设计方。这不是一个跑通就完事的分类 Demo从抓拍硬件、数据集口径、YOLOv3 训练到验收指标每一环都会决定最终能不能上线。2. 技术选型为什么单阶段检测器更适合卡口实时场景先把链路摆清楚再谈模型。很多方案翻车不是因为网络选错而是把“识别”当成了一个孤立的离线任务等到要接入卡口才发现时延、带宽、成像条件全都不配合。这一章把业务链路拆成三段再解释为什么在实时约束下单阶段检测器是更合理的起点。2.1 业务链路拆解抓拍、边缘推理、中心汇聚三段完整链路分三段。第一段是抓拍单元卡口摄像机架在收费广场或主线门架镜头对着车辆尾部因为危险货物标志按规定贴在车尾夜间和逆光靠红外闪光灯补光。第二段是边缘计算单元一台嵌入式智能硬件挂在摄像机旁边本地完成推理只把结构化结果时间、点位、车牌、货物类别、置信度和命中危化品车辆的原图往上送。第三段是中心平台负责汇聚、检索、告警推送和与应急处置流程对接。把推理放在边缘而不是中心是一个被带宽逼出来的决定。4096×2160 的抓拍图单张压缩后仍有 12 MB一条主线卡口全天过车量按万级计算全量回传对专线是灾难。边缘侧只回传结构化字段一天的报文量还不如几十张原图大。这样做的代价是边缘盒子算力有限模型不能太深输入分辨率也不能无脑往上堆这条约束会一路传导到后面的网络选择和输入尺寸设定上。提示链路设计阶段就要把“单帧推理时延预算”写进需求文档通常从车辆触发抓拍到结果入库要求控制在 1 秒以内其中留给模型推理的时间一般不超过 300 毫秒剩下的留给解码、上传和平台写入。时延预算定下来之后模型选型其实已经没多少自由度了。三百毫秒的窗口里要完成图像解码、预处理、前向推理、后处理阈值过滤加非极大值抑制还要留出抖动余量模型本身的前向耗时就得压到两百毫秒以内。这个数字直接排除了参数量大、需要两阶段串行计算的方案也是后面所有取舍的依据。2.2 两阶段与单阶段的取舍精度账要放在时延账后面算目标检测的主流方法分两类。两阶段检测器以 R-CNN 系列为代表先由区域建议网络生成一批候选区域再对每个候选区域提取固定长度的特征向量最后用分类器判定类别并回归边框。它的思路是“先找可能有东西的地方再仔细看”精度上限高但两步串行候选区域越多耗时越长而且每个候选都要过一次特征提取计算量随候选数量线性增长。单阶段检测器把检测直接当成回归问题在特征图上预先铺好锚点密集覆盖整幅图像的所有空间位置一次前向就同时输出位置偏移、置信度和类别概率。代表模型是 YOLO 系列、SSD 和 RetinaNet。它没有独立的区域建议环节速度优势明显代价是正负样本极度不均衡早期版本对小目标和密集目标略吃亏。放到卡口这个具体场景里取舍就很清楚了。目标位置高度结构化——车辆背面基本占据画面中部标志贴在车尾固定高度区间不需要模型在整幅图里大海捞针类别判定依赖的是标志上的图案和文字纹理属于细粒度识别而实时性是硬指标。三条件叠加单阶段是更合适的起点。YOLOv3 在这一类里又多了两个实际好处一是用了三个尺度的特征图做预测对小目标比早期版本友好二是工程生态成熟权重、配置文件、转 ONNX 或 TensorRT 的路径都有大量现成实践边缘盒子上的部署成本低。需要说清局限。YOLOv3 在密集小目标上的表现不如后续版本如果一张车尾同时贴了三四块紧挨着的标志漏检概率会上升骨干网络也偏重边缘设备上想再压时延就得换轻量化骨干或者做量化。这些不是选型错误是需要在方案里提前写明的边界条件。2.3 YOLOv3 的检测原理S×S 网格、B 个先验框与四项损失YOLOv3 把输入图像经过骨干网络和特征金字塔输出三个尺度的特征图比如输入 608×608 时得到 19×19、38×38、76×76 三层。每层把特征图划成 S×S 个网格每个网格分配 B 个先验框通常是 3 个每个先验框输出一组预测中心点偏移、宽高缩放比、置信度以及类别概率。三层加起来模型一次性产出所有候选框再经过置信度过滤和非极大值抑制得到最终结果。训练时的损失由四项相加构成中心点误差、宽高误差、置信度误差、分类误差。这四项不是简单相加就完事每一项的掩码和权重都不一样写代码时最容易在这上面出错。# YOLOv3 四项损失的组装逻辑示意不绑定具体框架 # pred / target 形状一致[N, A, H, W, 5 C] # A 为先验框数量C 为类别数5 表示 tx, ty, tw, th, conf def yolo_loss(pred, target, obj_mask, noobj_mask, cls_target): # 1) 中心点误差只对含目标的网格计算常用二值交叉熵 loss_xy bce(pred[..., 0:2], target[..., 0:2], maskobj_mask) # 2) 宽高误差回归的是相对先验框的缩放比用平方误差更稳 loss_wh mse(pred[..., 2:4], target[..., 2:4], maskobj_mask) # 3) 置信度误差正负样本分开算负样本要降权 loss_conf_pos bce(pred[..., 4], target[..., 4], maskobj_mask) loss_conf_neg bce(pred[..., 4], target[..., 4], masknoobj_mask) * 0.5 loss_conf loss_conf_pos loss_conf_neg # 4) 分类误差多标签二分类因为属性层与细分层标签可能同时命中 loss_cls bce(pred[..., 5:], cls_target, maskobj_mask) return loss_xy loss_wh loss_conf loss_cls几个参数值得单独说明。obj_mask定义的是正样本也就是“哪个先验框负责这个目标”通常按与真实框的 IoU 最大来指派指派错了模型会一直学不会。noobj_mask只作用于置信度项因为负样本只需要教会模型“这里没东西”不需要参与坐标和分类回归。负样本权重压到 0.5 甚至更低是因为一幅图里负样本数量是正样本的几十上百倍不降权的话置信度分支会被负样本主导模型倾向于全部预测为背景。分类项用二值交叉熵而不是 softmax是这个双层标签体系下的必要选择。属性层易燃、腐蚀等粗粒度和细分层具体品名类别可能同时命中同一个标志用 softmax 会强制类别互斥等于人为制造标签冲突。这一点在数据集设计那一章会更明显。3. 数据集构建10000 张车尾图与 23 类标注的落地口径模型结构是公开的真正决定这套方案能不能用的是数据集。这一章把采集、筛选、标注、类别体系、划分五件事按实际操作顺序讲清楚每一步都给出可复现的口径因为标注环节的随意性是这个项目里最大的隐性风险。3.1 抓拍系统与成像参数4096×2160、红外补光与逆光难题抓拍单元用的是 900 万像素级的卡口抓拍摄像机输出 4096×2160 的图片配红外闪光灯做弱光补光抓拍位置选在收费广场出口和主线门架两类点位都是为了拍到相对正面的车尾。选这个分辨率不是堆参数而是因为危险货物标志的菱形牌尺寸有限在画面里往往只占几百像素见方分辨率不够的话标志上的图案和数字根本区分不开。环节关键参数典型取值设计意图成像输出分辨率4096×2160保证车尾标志有足够像素成像补光方式红外闪光灯夜间与弱光下保住纹理抓拍触发方式线圈或视频触发保证车辆处于画面中心区传输回传内容结构化结果 命中原图控制专线带宽占用推理部署位置卡口旁嵌入式设备满足 1 秒级时延预算成像上真正的难点有三个。一是逆光早晚时段太阳低角度直射镜头车尾整体压暗标志变成一团黑影靠曝光策略很难两全。二是夜间红外补光能救回纹理但色彩信息丢失标志的底色红、黄、蓝区分度下降而底色恰恰是判断类别的重要线索。三是雨雾天水膜反光和能见度下降会同时拉低成像质量。这三类样本在采集阶段就得有意识地多留不能等训练完发现夜间指标崩了再回头补数据。3.2 从全量过车图里筛样本三步筛选法卡口抓的是所有车不是危化品车所以第一步是把危化品运输车辆挑出来。可行的做法是先按车辆备案库或专用号段做初筛把命中记录对应的原图拉出来这一步能把数据量从百万级压到万级。第二步是人工过一遍剔掉非车尾角度、严重遮挡、运动模糊、标志被货物遮挡的图保留标志清晰可辨的样本。第三步是按类别和场景做均衡避免某一类车在某个月份扎堆。最终形成的数据集规模是 10000 张危化品运输车辆背面图像全部来自真实道路监控场景覆盖不同照度、不同天气、不同点位。这个量级对 23 类目标检测来说偏紧尤其是稀有类所以后面的增强和阈值策略必须配合上不能指望模型自己学会。注意样本筛选阶段就要按“日期 点位 时段”三个维度做记录不是为了好看而是后面划分数据集时唯一能防止数据泄漏的依据。3.3 标注规范labelimg、xml 字段与边界框口径标注工具用 labelimg输出 PASCAL VOC 格式的 xml和原图同名一个 xml 对应一张图里面记录每个目标的位置和类别。格式本身很简单难的是口径统一到底框到哪里、倾斜多少度还算、遮挡多少算不可标、一块牌上有两个图案算一个框还是两个框。annotation filenamehwy_0412_000317.jpg/filename size width4096/width height2160/height depth3/depth /size object nameflammable_liquid/name !-- 必须与冻结后的类别表逐字一致 -- difficult0/difficult !-- 严重遮挡或极模糊标 1训练时可降权 -- bndbox xmin2871/xminymin1204/ymin xmax3082/xmaxymax1419/ymax /bndbox /object /annotation口径上我一般会定死几条只框标志牌本体不把车体、支架、反光条框进来标志倾斜但整体可辨的照标框取外接矩形遮挡超过一半且关键图案不可辨的标difficult1或者直接不标同一车尾贴多块标志的全部标出不允许只标最清楚的那块。这几条写在标注手册第一页新标注员先标 100 张做交叉复核通过率够了再放量。name字段最容易出问题。多人协作时会出现flammable_liquid、Flammable-Liquid、易燃液体三种写法混用的情况训练脚本按字符串匹配结果就是同一类被拆成三类每类样本数都变少模型学得稀里糊涂。解决办法只有一个类别表先冻结写成一份独立的配置文件标注和训练都从这份文件读脚本里做一次全量校验。3.4 类别体系5 类属性加 18 类分项的双层标签设计类别体系不是拍脑袋定的它跟着危险货物的分类标准走。按主要特性和运输要求危险货物分为 9 个大类往下的细分项很多。直接上几十类会带来两个问题样本不够分类别间视觉差异极小。所以这里做成两层。第一层是危险化学品属性分类5 类粒度粗标注框 8419 个平均每类接近 1700 个样本相对充足模型容易学稳。这一层的价值在应急端救援队伍第一时间要知道的是“能不能用水”“要不要防爆装备”“需不需要防毒面具”粗粒度属性直接对应处置动作。第二层是危险化学品分类18 类标注框 13755 个平均每类七百多实际分布相当不均。这一层服务管理端统计某路段某月运输的货物构成、追溯特定品名的通行记录。层级类别数标注框数平均每类主要用途属性层58419约 1684应急处突、救援决策细分层1813755约 764通行统计、溯源查询合计2322174约 964—两层合计 23 种标注类别、22174 个标注框。平均数是骗人的真实分布里头部类别可能占两成以上尾部类别不足百分之一。这个不均衡是后面所有训练策略的前提也是避坑那一章要重点讲的问题。3.5 划分与统计按日期和点位切不按随机数切很多检测项目在这里栽跟头把 10000 张图随机打乱8:1:1 分给训练、验证、测试跑出来的指标很漂亮一上线掉一大截。原因是同一辆车在同一个卡口会被连续抓拍多张这些图几乎一模一样随机划分会把它们分散到训练集和验证集里等于让模型在验证时“见过原题”。正确的切法按时间或点位切用前几个月的图做训练后一个月做验证再留一个从没出现过的卡口做测试。这样验证集上的指标才能反映模型面对新场景时的真实水平。如果数据量允许还可以再按白天/夜间分层保证每个子集里都有一定比例的弱光样本。import os import xml.etree.ElementTree as ET from collections import Counter def count_boxes(label_dir): 统计每个类别的标注框数量用于发现长尾类别 counter Counter() for fn in os.listdir(label_dir): if not fn.endswith(.xml): continue root ET.parse(os.path.join(label_dir, fn)).getroot() for obj in root.findall(object): counter[obj.find(name).text.strip()] 1 return counter stat count_boxes(./annotations) total sum(stat.values()) for name, n in stat.most_common(): # 占比低于 1% 的类后续要单独做过采样或阈值调整 print(f{name:24}{n:7}{n / total:8.2%})这段脚本跑一遍就能拿到类别分布输出里占比低于 1% 的类要单独列出来。它们不是“顺手标一下”的附属品而是决定验收时能不能过关的关键类别——真出事故的往往就是这些小概率品类。4. 训练与调参300 轮、动量 0.9 背后的配置清单训练环节最忌讳的是“默认配置跑一遍指标不行再瞎调”。这一章把超参、配置文件字段、曲线判读、推理后处理四件事按顺序讲每个参数都说清它管什么、改它会带来什么连锁反应。4.1 训练环境与超参设置动量 0.9、权重衰减 0.0005、300 轮训练在带 GPU 的深度学习服务器上完成。动量设 0.9作用是让参数更新带一点惯性缓解批次噪声带来的震荡权重衰减设 0.0005做 L2 正则防止模型在万级样本上过拟合总训练周期 300 轮在总周期的 70% 和 90% 两个节点把学习率衰减到原来的 0.1 倍。初始学习率一般取 0.001配合阶梯衰减。超参取值说明动量0.9抑制批次间震荡权重衰减0.0005L2 正则防过拟合训练周期300万级样本的常见量级学习率衰减点70%、90%衰减为原来的 0.1 倍初始学习率0.001配合阶梯衰减使用输入尺寸608×608小目标场景下的折中输入尺寸这个参数要单独说。416×416 推理快但经过 32 倍下采样后特征图只剩 13×13画面里只有几十像素的小标志在那个尺度上基本消失608×608 能把小目标多保住一点代价是显存占用和推理耗时大约翻倍。在边缘盒子算力有限的前提下608 是“精度还能看、时延还没爆”的折中点如果硬件更强可以再往上试如果时延超标就得退回去同时补上切图推理之类的补救手段。4.2 配置文件关键字段classes 与 filters 的联动改法检测模型的配置文件里最容易改错的是类别数和卷积核数量。两者是绑定关系改一个不改另一个训练能启动但结果全是错的而且不一定报错属于典型的静默失败。[net] width608 height608 batch16 subdivisions4 # 实际批大小 16/4 4显存不够就调大 subdivisions momentum0.9 decay0.0005 learning_rate0.001 max_batches60000 # 按 300 轮 × 每轮批次数折算 [yolo] mask 6,7,8 classes 23 # 与冻结后的类别表一致 num 9 # 先验框总数三个尺度各用 3 个 [convolutional] size1 stride1 pad1 filters84 # (classes 5) × 3 (23 5) × 3 84 activationlinearfilters的算法是(类别数 5) × 先验框数5 指的是中心点两个、宽高两个、置信度一个。三个尺度的预测层前面各有一个这样的卷积层三处都要改漏改一处就会出现维度不匹配或者输出错位。subdivisions是显存调节阀实际批大小等于batch / subdivisions显存报 OOM 时优先调它而不是直接砍batch因为砍batch会连带影响批归一化的统计稳定性。4.3 训练曲线判读三个阶段和四种异常形态300 轮训练大致分三段。前 20 轮损失快速下降这一段主要在学习“哪里有目标”损失曲线陡是正常的。中期进入平台期损失下降变缓这时候别只盯损失要同时看验证集上的平均精度因为损失还在降但精度不涨往往意味着模型在拟合训练集的标注噪声。70% 和 90% 两个衰减点之后损失会有小台阶式下降这是学习率降低后模型在做精细调整。四种异常形态要认得。第一种是损失突然变 NaN绝大多数是学习率过大或者标注框坐标越界比如 xmin 大于 xmax、坐标超出图像尺寸先用脚本校验一遍标注文件。第二种是训练损失降、验证精度也降典型过拟合减小模型规模、加数据增强、提高权重衰减都能缓解。第三种是损失几乎不降先检查类别数和 filters 是否匹配、类别名是否和标注文件逐字一致这是最常见的静默失败。第四种是某一类始终学不会去看这一类的样本数和标注质量八成是长尾或者标注口径出了问题。提示每 10 轮存一次权重别只留最后一轮。小数据集上最后一轮未必是最好的很多时候最优模型出现在 80% 到 90% 之间没有中间权重就只能重跑。4.4 推理侧阈值与后处理分类别阈值加非极大值抑制训练完只是拿到权重真正决定线上表现的是推理侧的后处理参数。全局统一的置信度阈值在长尾分布下必然出错阈值调高稀有类全漏阈值调低常见类误报爆炸。可行的做法是按类别分别设阈值稀有类下调、常见类上调。# 推理后处理分类别阈值 NMS伪代码与框架无关 PER_CLASS_CONF { flammable_liquid: 0.45, # 头部长尾分布里的头部类阈值可以高一些 oxidizer: 0.25, # 尾部类下调阈值优先保召回 toxic: 0.30, } def postprocess(raw_boxes, iou_thres0.45): kept [] for cls_id, boxes in group_by_class(raw_boxes): thres PER_CLASS_CONF.get(cls_id, 0.40) # 未配置的类走默认值 boxes [b for b in boxes if b.score thres] kept nms(boxes, iou_thres) return kept # iou_thres 取 0.45 是因为同一车尾可能贴两块相邻标志 # 阈值压得太低会把紧挨着的第二块牌当成重复框删掉造成漏检iou_thres这个参数在别的检测任务里通常设到 0.5 甚至 0.6这里反而要往下压一点原因是车尾多标志共存的情况真实存在而且两块牌经常挨得很近。这属于场景特性带来的参数偏移不能照抄通用配置。5. 避坑与排查五类最容易翻车的问题及处置下面五条是这个项目里最容易出问题的地方每一条都按“现象、原因、解决”写遇到对应现象可以直接对照排查。5.1 验证指标虚高随机划分导致的数据泄漏现象是验证集上平均精度能到 0.9 以上一上线掉到 0.5 左右且错误集中在同一批卡口。原因是随机划分把同一辆车在相邻几秒内的连拍图分到了训练集和验证集这些图背景、光照、角度几乎一致模型在验证时等于做原题。解决是改成按日期或点位切分训练用早期数据、验证用后期数据、测试用一个完全没参与训练的卡口同时记录划分清单后续每次迭代沿用同一套切分保证指标可比。5.2 长尾类别学不会稀有类样本占比过低现象是头部几类精度很高尾部类别召回率个位数混淆矩阵里尾部类大量落到背景。原因是标注框分布极度不均平均每类七百多但实际尾部类可能只有几十个框模型在损失里几乎感受不到它们。解决分三层数据层对稀有类做过采样和小角度旋转、亮度扰动增强损失层提高正样本权重或引入焦点损失压一压易分样本的贡献推理层给稀有类单独下调置信度阈值用精度换召回。三管齐下还不行的类别考虑合并到属性层先保住粗粒度可用。5.3 夜间与逆光性能断崖成像分布偏了现象是白天场景召回率正常夜间和清晨掉三成以上误报也明显变多。原因是训练样本的时段分布严重偏白天模型没怎么见过红外补光下的低饱和图像同时标志表面反光在强补光下会糊掉图案。解决是按时段分层抽样强制每个训练批次里都有一定比例的夜间样本增强里加入亮度、对比度、伽马变换和运动模糊模拟不同曝光条件评估时单独出一份夜间子集指标不和白天混在一起算平均值否则问题会被平均掉。5.4 远距离小目标漏检下采样把标志抹没了现象是近处车辆识别正常画面远端或大车遮挡后的车辆整块漏掉日志里连低置信度的框都没有。原因是输入缩放后远处标志在特征图上只剩十几个像素经过多次下采样后响应基本消失。解决有三个方向提高输入尺寸代价是时延做两级检测先定位车辆尾部区域把该区域裁剪放大后再送一次识别或者对原图做切片推理把大图分块送入再合并结果。工程上通常选第二种因为它的算力增量可控而且车辆检测本身可以复用已有的卡口算法。5.5 标注口径漂移同一类被拆成好几个名字现象是训练日志里某一类样本数比预期少很多混淆矩阵出现两个语义相近的类别互相混。原因是多人标注时类别名写法不统一或者框选范围一个人框牌、一个人框牌加支架。解决是先把类别表冻结成独立文件标注工具和训练脚本都从它读写一页标注手册用正例反例图说明边界上线前跑一次全量校验脚本检查类别名合法性、坐标越界、宽高为零的异常框。这套动作做一遍要半天不做的话后面返工是几周。6. 验收与答辩用什么指标证明这套识别真的能用验收这件事最怕拿一个总体平均精度交差。评审只要问一句“夜间怎么样”“尾部类别召回多少”总体指标就撑不住了。我的做法是把验收拆成一张分层指标表每一项都写清口径和门槛答辩时按这张表讲比讲网络结构有说服力得多。验收项指标口径建议门槛整体精度全部类别平均精度IoU 0.5按基线版本对比要求不降属性层召回5 类粗粒度漏检优先高于细分层应急场景以不漏为主细分层召回18 类分项含尾部类别单列尾部类别单独给数不混平均值单帧时延边缘设备端到端含前后处理控制在预算内并留抖动余量分场景表现白天、夜间、雨雾分别统计各场景差距控制在可接受区间连续试运行连续 7 天在线统计误报与漏报误报按点位归因漏报逐条复盘这张表里有两个设计意图值得说明。一是把属性层和细分层的门槛分开属性层服务于应急处置漏检的代价远大于误报所以召回门槛要压得住细分层服务于统计查询可以适当让精度优先。二是要求尾部类别单独给数不接受“平均值达标”这种说法因为平均值会被头部类别拉高恰恰掩盖了最需要关注的稀有品类。答辩的讲述顺序也有讲究我一般按四段走。第一段讲场景和代价为什么要在卡口识别货物类型识别错了会带来什么后果这一分钟决定评委愿不愿意继续听。第二段讲数据怎么来抓拍参数、筛选流程、标注口径、类别体系为什么分两层这一段最能体现工作量也最容易被追问细节提前把标注手册和类别分布图准备好。第三段讲模型和训练为什么选单阶段、四个尺度的输出怎么设计、损失四项各自的掩码和权重、超参怎么定的讲取舍而不是讲公式。第四段讲指标和失败案例把上面那张表放出来主动讲一两个没解决好的场景比如夜间反光导致的误判以及打算怎么补。主动交代失败案例这一步很多人不敢做其实是加分项。全部指标都漂亮的方案反而让人怀疑数据划分有问题能说清“哪一类现在还不稳、原因是什么、下一步怎么改”才像真的做过这个项目。从那以后我每次做检测类项目都强制把所有验证集的划分方式写在方案第一页标注口径和类别表冻结之后才允许开训指标表里必须留一行给最差的那个类别。这三条替我挡掉了好几次上线前的返工。这套从抓拍、数据集、训练到验收的完整链路整理在文末的资源里做毕设或课设时按章节顺序对着改就行希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站