上个月试运行总结会开完,我们几个人留在会议室里对着监控大屏继续排查问题。屏幕上外业组的数据传输进度条又卡在了同一个位置——数据入库任务在87%左右反复重试,随后提示“文件校验失败”。这不是第一次了,几乎每次大里程巡检回来都要折腾两个小时以上才能把数据整理干净。而另一块屏幕上,AI辅助识别平台显示的渗漏水疑似病害标记密密麻麻,负责复核的同事苦笑着说:“照这个误检率,我们一天什么活都不用干,光看图就够呛。”
这篇记录想把这轮地铁病害检测系统软件改进的前后过程完整写下来。主要涉及两个问题:一是海量外业数据“进不来”,入库速度慢、中途失败率高;二是检测识别结果“不准”,非病害误检多到影响正常复核节奏。我会把排查思路、具体改动、实测数据和踩过的坑都摆出来,给同样在做基础设施智能检测系统或类似数据管线处理项目的人一个参考。
1. 这次改的是什么:卡在入库和误检两个老问题上
1.1 系统在做什么,改进前是什么状态
先交代一下系统背景。我们这套地铁病害检测系统,采集端集成在轨道巡检车上,通过探地雷达、激光轮廓扫描和高速工业相机阵列,对隧道衬砌结构做连续采集。软件平台负责把采集回来的原始数据处理成结构化的病害台账,核心流程包括数据预处理、病害目标识别、里程定位校准、结果复核和报告输出。
理论上,车跑完一个区间,数据回来之后放到服务器上,平台自动完成入库、抽帧、模型推理、结果可视化,复核人员只需要重点关注AI标记出来的疑似病害位置。但实际情况远没有那么顺利。
试运行阶段暴露的问题集中在两个环节:数据入库太慢太脆弱,以及模型推理结果里非病害目标占比太高。这两个问题不是孤立的——入库环节卡住,后续的模型分析、报告生成全都得等;误检率太高,复核人员需要逐条判断“这个到底是不是病害”,工作量反而比不用AI辅助时更大。系统看起来功能齐全,实际用起来却处处添堵。
1.2 试运行期间的三个具体症状
第一个症状大家都感受得到:数据入库任务动不动就失败。一次完整巡检产生的原始数据量在几十GB级别,文件数量非常多。老的数据接入模块采取单线程顺序入库,整套处理链路跑下来要将近两个小时,而且任何一个文件的校验异常都会导致整批次回滚。
第二个症状是误检率高到不切实际。渗漏水识别是隧道病害检测里最核心也最复杂的一项。试运行期间模型在部分区段上标记出来的疑似渗漏水区域,有相当一部分是潮湿污渍、照明灯具反射、电缆支架水迹甚至管片接缝阴影被误判成了渗漏水。复核人员需要逐一截图、比对原始图像、确认是否真的存在渗漏水特征。
第三个症状相对隐蔽,但对日常使用影响很大:病害报告生成基本靠人工拼凑。系统虽然能导出检测结果,但报告里的病害类型统计、里程分布、图片排版、文字描述都散落在不同模块里,需要专人花半天时间整理,而且经常出现里程信息和图片对不上的情况。
2. 排查链路:从任务进度条卡住到误检样本分布
2.1 数据入库慢的真凶:文件校验与海量小文件
我先从数据入库问题入手。最初怀疑是磁盘写入速度不够,但看了监控发现磁盘IO一直没跑满,CPU和内存负载也正常。于是把处理日志打开,逐段统计每个步骤的耗时,问题很快就浮现出来了。
真正的瓶颈在文件校验环节。老模块对每一个原始数据文件都做全量MD5校验,校验通过后才写入数据库。MD5本身是安全的,但它需要把文件完整读一遍计算摘要,对于以GB为单位的大文件来说,这个开销非常可观。而巡检数据里大量的是按里程切分的图像切片文件,单个不大但数量动辄数万,每个文件走一遍完整校验再入库,时间全耗在这上面了。
顺着数据文件组织方式往下看,又发现一个更基础的问题:采集端的存储策略把单帧图像和定位数据切得非常碎,每行驶0.5米就生成一批文件。这种粒度对数据采集和传输的容错有好处,但对数据处理平台来说,几万个零散文件意味着几万次打开、读取、关闭操作,效率非常低。
把这两个因素叠加起来,入库慢就很合理了——单线程顺序处理,每个文件全量校验,文件数量又异常庞大,整个链路自然卡得动弹不得。排查到这里,改进方向已经比较清晰了:要么减少校验开销,要么减少文件操作次数,或者两者同时做。
2.2 误检高发的源头:背景多样性不足与空间上下文缺失
误检问题比入库问题复杂一些,因为涉及模型训练和推理策略。我先把试运行期间的误检样本全部导出,按场景做了分类统计,结果非常有意思。
占比最大的一类误检和目标本身没太大关系,而是模型把隧道里的规则性装置当成了病害——照明灯具的高光反射、风机支架、电缆挂钩、消防管道,这些都有明显的几何形状,但在某些角度和光照条件下,它们呈现出来的纹理特征和渗漏水区域的边缘纹理非常接近。
第二类误检来自环境干扰。隧道内壁的污渍、列车运行留下的痕迹、残留泥浆的局部区域,由于颜色和渗漏水的水痕印记接近,很容易被模型激活。管片接缝处也经常出现误报,因为接缝本身的阴影和开裂早期特征在某些分辨率下并不容易区分。
第三类问题最值得反思:模型只用了局部图像块做判断,完全没有用到空间上下文信息。隧道内的病害分布有很强的规律性——渗漏水常见于拱顶和侧壁的施工缝附近,而灯具反射位置相对固定,管片接缝则呈现周期性分布。这些信息对人工判断来说几乎是下意识就会参考的,但模型的输入里完全没有体现。
综合来看,误检率高的根因不是模型结构不够强,而是训练数据的背景多样性覆盖不足,推理阶段又缺乏空间上下文作为判断依据。单纯换更大的模型或者加更多标注数据,当然也能改善,但投入大、周期长。在软件层面,更务实的路线是增加后处理滤波规则,同时把置信度输出做成分层管理。
3. 落地改动:并行入库、结果滤波与报告模板化
3.1 数据接入层的存储与校验策略调整
针对数据入库问题,我重新整理了处理流程,本质上做了三处调整:校验策略分级、文件汇总打包、入库任务分段并行。
首先是校验策略。原来的全量MD5校验保留,但只用于小体量、高价值的核心索引文件。对于大批量的图像和点云数据块,改为“抽样校验+CRC32快速校验”的组合方式:每个批次的头部和尾部文件做完整MD5校验,中间文件做CRC32快速校验。CRC32计算速度远快于MD5,虽然安全性弱一些,但对原始采集数据来说,检测传输损坏已经够用。
然后是文件汇总打包。采集端不做改动,但在软件平台的数据接入模块里增加了一个聚合步骤:先把一段时间内的零散文件按照区段组织成批次文件,再做解析入库。入库程序面对的不再是几万个单文件,而是几十个批次包,文件打开和关闭的次数大幅减少,实测下来系统开销明显下降。
最后是并行化。老模块的入库逻辑是严格单线程,一次只能处理一个文件批次。我改成了任务队列加多工作线程的结构,按物理磁盘分区控制同时写入的并发数,避免多线程争用导致IO反而变慢。经过几次调参,最终固定为4路并行写入。
改造后的核心流程示意如下,以Python风格描述:
def ingest_batch(route_id, batch_files): # 批次内抽样文件做强校验,其余做快速校验 quick_check(batch_files) for chunk in split(batch_files, chunk_size=256): submit_to_worker_pool(parse_and_store, chunk) def parse_and_store(route_id, chunk): # 工作线程内按部就班完成字段解析、坐标映射、写库 for raw in chunk: record = parse(raw) mapping = georeference(record.frame_id) write_to_store(record, mapping)这段伪代码对应的是改进后跑通的主干线。实际实现里还要考虑失败重试和断点续跑,否则一个批次中途报错,前面处理完的数据就白做了。断点续跑我采用了入库状态表的方式,每条数据记录都标记未处理、处理中、已完成、已失败四种状态,重跑时自动跳过已完成记录。
3.2 检测结果后处理与置信度分层
数据入库问题解决后,剩余算力就能腾出来处理检测误检问题了。在不动模型的前提下,我做了两件事:后处理滤波规则和置信度分层管理。
后处理滤波的核心思路是引入空间上下文。我们手头本来就有隧道断面的结构参数和里程定位数据,灯具、管片接缝、消防设备这些固定设施的位置可以通过历史数据建立空间分布图。推理阶段,模型输出目标框之后,系统会叠加两张辅助图:固定设施分布图和结构接缝分布图。目标框的中心点落在已知灯具或消防设施位置的一定范围内,并且形状和颜色特征与设施匹配,就标记为“疑似固定设施干扰”,进入低置信度队列。
针对管片接缝阴影造成的误检,比较有效的规则是周期性纹理抑制。隧道管片接缝的位置在里程维度上具有周期性,系统对高置信度病害目标框做了接缝匹配,若目标框的纵向位置与管片接缝预测位置的距离小于设定阈值,就降级处理。
置信度分层是另一个重要改动。以前模型输出只有一个阈值,0.5以上都显示给复核人员。现在我把输出分成三档:0.9以上直接进入“高置信度病害台账”,这类目标通常特征非常明显,复核只需确认一次;0.7到0.9之间进入“候选复核队列”,这部分占比最大,需要人工仔细判别;0.7以下不再展示,直接丢弃。
这个分档策略的好处是让复核人员的精力集中在最有争议的中间地带。高置信度的部分基本不会看漏,低置信度的部分大多是误检,真正需要人工判断的只有中间那部分。阈值具体设多少,后面我会讲到,这个参数并不是越大越好。
3.3 报告生成模块重构
报告模块原来几乎是纯手工活,这次一并做了模板化重构。参考规则很简单:把报告拆成“固定骨架”和“动态字段”两部分。固定骨架是报告的整体结构,包括封面、检测范围说明、病害统计汇总表、分里程段病害明细、典型病害附图等部分。动态字段则从结构化病害台账里自动提取。
病害台账经过前面置信度分层和人工复核后,已经标记了明确的复核状态。报告生成模块直接读取通过复核的病害记录,按里程排序,自动生成文字描述、统计表、图片索引和坐标定位信息。导出的格式支持Excel和PDF两种,Excel给外业复查用,PDF给上级汇报用。
为了避免“里程信息和图片对不上”的老问题,报告模块里增加了交叉校验逻辑。每条病害记录都包含三个关键字段:图像帧号、定位里程、病害类型。生成报告时,系统会自动比对图像帧号对应的原始定位数据和病害台账里的里程字段,偏差超过阈值的记录会被拦截,不会进入最终的输出文件。
4. 改进后的实测效果:用时缩短、误检下降、报告提速
4.1 入库耗时的前后对比
改进上线后,我们立刻用一批历史巡检数据做了回归测试,数据量约35GB、文件数约2万4千个。旧流程从任务启动到全部入库完成,耗时稳定在130到150分钟之间,偶尔因为个别文件校验失败需要人工干预,还会拖到三个小时以上。
新流程同样的数据量,实测耗时约35分钟,整个过程没有人工介入。提速接近4倍,主要贡献来自三方面:快速校验替换全量MD5节省了约30%的时间,文件打包聚合减少了海量小文件的IO开销节省了约35%的时间,4路并行写入在IO充足的情况下带来了约2.5倍的吞吐提升。三个因素叠加后的总效果符合预期。
后来针对大里程任务又做了一轮压测。单次入库的数据量从35GB提升到60GB,耗时约55分钟,整体线性增长趋势稳定,没有出现在某个量级上突然恶化的情况。入库失败率也从每批次平均1到2次降到了接近零,抽样校验机制没有出现漏判受理的数据损坏问题。
4.2 误检率与漏检率的变化
误检指标我用了两个维度来衡量:每公里疑似病害数量,以及误检在全部疑似病害中的占比。改进前,渗漏水识别的每公里疑似数量高得惊人,大概每公里40到60个,其中大部分复核后判定为非病害。
后处理滤波和置信度分层上线后,每公里疑似数量降到了10个以内,降幅超过80%。更重要的是,被保留下来的人工复核目标里,真实病害的占比明显提升。实测复核效率大概提高了3到4倍,复核人员处理一个区间数据的时间从原来的半天缩短到了1小时左右。
漏检率的变化值得单独说。后处理滤波规则如果设置得太激进,误检率会下降,但真病害也可能被一起过滤掉。我们对比了过滤前后的模型原始输出,确保被规则抑制的目标里没有混入标记过的真实病害样本。首批上线版本里,真实病害的保留率保持在98%以上,剩余不到2%的偏离属于低置信度的早期渗漏水迹象,这类目标本来就会进入人工复核流程,不会直接丢失。
4.3 报告生成的效率提升
报告模块完成模板化重构后,效果是最直观的。以前整理一个区间检测报告,需要一个人花半天甚至一整天,中间还要电话反复确认线路编号、起止里程、病害数量这些基础信息。现在报告模块从已复核的病害台账出发,点一下按钮,20分钟左右自动生成全套报告文件。
交叉校验逻辑也起到了实际作用。在历史数据上跑生成测试时,确实拦截了几条里程和图片帧号不匹配的病害记录,纠正了之前手工整理时容易遗漏的低级错误。这些记录如果不拦截,发到外业复查人员手里,会直接导致复查人员去错误的隧道位置找病害。
5. 过程中踩过的坑:坐标偏移、阈值平衡与灰度发布
5.1 定位信息延迟引发的坐标偏移
改进过程中最典型的坑是坐标偏移。后处理滤波里有一个环节需要把病害目标框和固定设施分布图做空间叠加,第一次联调的时候发现叠加结果整体存在大约0.8米的系统性偏移,所有目标框都偏向行进方向的后方。
排查过程有点曲折。最开始怀疑是坐标系定义不一致,检查后发现两种数据的坐标系完全相同。后来把原始图像帧的采集时间和定位数据的采集时间放在一起比对,发现定位信息并不是实时写入每个图像帧的,而是按固定周期批量写入,帧号和时间戳之间存在稳定延迟。
找到原因后,处理逻辑里增加了一个时间戳校正步骤,根据定位数据的写入周期换算延迟量,再对每个目标框的里程值做补偿。这个偏移如果不处理,滤波规则本身没问题,叠加的设施位置也会整体偏位,导致过滤规则作用在错误的位置上,轻则过滤效果不佳,重则把真实病害错杀。
5.2 阈值改动的连锁反应
置信度分档的阈值参数,我一开始把高置信度线设得偏高,0.95以上才能直接进病害台账。试运行了一周后发现,很多复核确认为真实渗漏水的样本,模型置信度集中在0.85到0.95之间,0.95以上的样本量很少,导致大量真实病害被分流到了人工复核队列,复核工作量并没有像预期那样显著下降。
回头我又检查了分布数据,把阈值从0.95回退到0.90,并同步调整了中间档的上下界。调整后,约四成真实病害直接进入高置信度台账,复核工作量进一步下降,而漏检率没有出现上升。
这次调整让我总结出一个规律:置信度阈值不能拍脑袋定,要在真实数据分布上去看。模型的校准程度、训练数据的均衡性、目标类别的典型性都会影响置信度分布形态,同一个阈值在这条线上好用,换一条线可能就失衡了。
5.3 灰度发布的价值
最后一个想说的是灰度发布。系统改进上线前,我一直坚持先在一个指定区间做灰度验证,验证通过后再逐步推开。这个区间选在有代表性的管片结构、照明条件和渗漏水分布特点的路段。
灰度期间发现了一个实验室环境完全复现不出来的问题:正式环境的隧道照明亮度分布和测试环境差异很大,导致后处理滤波里一个根据图像亮度判断的辅助条件频繁误触发。这个条件最初是为了区分“照明灯具直接照射区域”和“普通隧道壁面区域”的,结果在实际环境里,黄昏时段采集的数据整体亮度偏低,滤波条件误把大量正常区域归入了“疑似干扰区”,压制了部分有效检测结果。
这个bug在灰度阶段被及时捕获,调整后推全量推送时,没有复现同类问题。灰度发布多花了一周时间,但避免了全量上线后大规模误检的返工成本,这笔投入非常划算。
6. 这套改法能复用到哪些场景
6.1 路桥与地下管廊检测系统的参考价值
这次的改动虽然是在地铁病害检测系统里做的,但数据管线和识别策略的调整思路,对其他基础设施检测系统同样适用。
公路路面检测系统同样面临海量图像数据入库压力,巡检车跑几百公里,数据量可能比地铁区间还大。文件打包聚合、抽样校验、并行写入这套组合拳可以直接平移,只需根据相机帧率和存储格式微调参数。病害识别方面,路面裂缝检测和路面渗水检测都有类似的高误检问题——接缝阴影、轮胎痕迹、修补痕迹经常被误识别,空间上下文和后处理规则同样能发挥作用。
地下管廊检测的场景更接近地铁隧道,采集方式、病害类型、环境干扰源都有很多相似之处。尤其是置信度分层管理的思路,在复核人员有限的条件下价值更加突出。管廊检测数据通常由少数技术人员在较短时间内完成复核,把人工精力集中在最需要判断的中间置信度区间,能显著提升有限人力的使用效率。
6.2 工业质检中“检出率-误检率”平衡的启示
如果你做的是工业质检类项目,这次置信度分层和阈值调整的过程也有参考价值。工业质检里同样存在“漏检率”和“误检率”这对矛盾指标,而且往往误检率高了,现场工人就不信任这个系统,宁可全部人工看一遍。
我们最终采用的方案并非简单地把阈值调高来压低误检,而是把输出拆成不同置信度档位,配合人工复核流程。这种做法在工业场景中同样可行:高置信度缺陷自动拦截,低置信度目标忽略,中间档生成待确认列表,由质检员快速确认。目标不是把误检率压到零,而是让系统输出贴合现有的人员分工和流程节奏。
另外,工业质检里的固定干扰源问题也和隧道内装置误检非常相似。如果产线设备的位置相对固定,完全可以利用设备坐标或工位坐标建立空间干扰图,对识别结果做后处理过滤。这种思路比单纯调模型参数更可控,也更容易上线后维护和解释。
6.3 数据管线设计上的几点通用经验
最后总结几条数据管线设计的通用经验,都是我在这轮改进中真实体会到的东西。
文件组织方式对下游处理影响极大。采集端如果为了容错而把数据文件切得非常碎,平台端就必须设计对应的聚合机制,否则入库效率永远上不去。最理想的方式是采集端和平台端共同约定文件打包策略,在容错和处理效率之间找到平衡点。
校验策略要按数据价值分级。重要的核心文件做强校验,海量原始数据做快速校验,整体安全性和处理效率都能兼顾。这不只是性能优化,更是存储和计算资源合理分配的思路。
涉及位置信息的系统,都要对时间戳和坐标之间的延迟保持敏感。只要数据链路里存在“先采集后定位”的情况,就必须做延迟补偿。这个坑我在很多项目里都见过,每次表现形式不同,但根因几乎一致:采集时间和定位时间不是同一时刻,处理时却默认它们一致。
系统的改进不是一次性的。置信度阈值、滤波规则参数、并行度设置这些都需要在实际运行中持续观察和微调。每个区间、每个季节、每种光照条件下的数据分布都会有差异,一个参数组合跑一段时间后,很可能因为环境变化而需要重新校准。这套系统上线后,我们仍会在月度复盘中统计各项指标,根据情况决定是否需要调整参数。软件改进不是把轮子造完就结束,真正的工作量在日常的观察、统计和持续迭代里。