
简介这是一篇2019年发表于《计算机与现代化》的学术论文由中国石油大学华东作者撰写面向港口智能监控、船舶管理和交通管理等场景针对船舶轮廓复杂、船牌位置不固定、船牌文字类型多样且个数不确定等挑战提出了基于全卷积神经网络的船舶检测与船牌识别系统。压缩包内仅1个PDF文件约4.12MB包含论文全文、图表及参考文献方便系统研读。所提方案分为三部分用船舶检测网络SDNet完成船舶定位用船牌文本检测网络PDNet提取船牌内容再用在线自适应分类器结合AIS信息提高船牌识别精度文末还给出了实际部署运行的高精度验证结果。整个方案在思路设计、网络结构、实验评估上都有完整呈现可作为深度学习、计算机视觉与数据建模方向研究者的参考案例。目前已有221人学习适合关注目标检测与港口智能识别的读者下载学习。1. 港口监控场景下全卷积神经网络如何同时搞定船舶检测和船牌识别做港口智能监控的同行应该都有同感船舶检测不难难的是把船号和船对上。船牌位置不固定有的船干脆把船号刷在船体上文本排列横七竖八加上水面反光、背景干扰传统车牌识别那套固定位置、固定字符数的思路完全搬不过来。这篇论文给出的 SDR-FCN 系统把问题拆成了三步——SDNet 检测船舶、PDNet 检测船牌文本、OA-Classifier 识别船号而且用 AIS 系统做在线数据回流把识别闭环补上了。无论你是要做港口安防、内河航道管理还是想找一份完整的「目标检测 文本检测 分类识别」落地方案这套设计都值得拆开看一遍尤其是它怎么绕开汉字文本检测的坑。2. SDR-FCN 三级流水线为什么把检测拆成三个模型而不是一步到位2.1 三个模型的分工与数据流SDR-FCN 不是一个端到端的单模型而是三个模型串成的流水线。拆开来看每个模型只干一件事数据流很清晰视频帧先送进 SDNet输出船舶外接框框出来的船舶区域再交给 PDNet 做文本检测找出船牌或船号文本区域最后把文本区域裁出来送进 OA-Classifier 分类器输出船号。模型输入输出职责SDNet整帧图像船舶边界框定位船舶位置PDNet船舶区域图像船牌文本边界框定位船牌/船号文本OA-Classifier船牌文本图像船号分类结果识别具体船号这个拆法的好处在于每个模型的输入都被前一级约束住了。PDNet 不需要在全图范围找文本只需要在船舶框内部找OA-Classifier 不需要理解图像内容只需要对固定尺寸的文本区域做分类。对工程部署来说每一级都可以独立替换、独立调参出问题也好定位。我一般会建议做类似项目的团队优先照这个结构搭而不是一上来就追求一个「万能模型」。2.2 九维标注向量一条标注同时约束船和牌论文里最实用的一个设计是它的标注格式。每条数据用九维向量 (X, Y, H, W, x, y, w, h, b) 记录前四维是船舶框中间四维是船牌文本框最后一维是船号。这意味着标注一次同时喂给 SDNet 和 PDNet 两个模型用。# 一条样本的标注结构船舶框 船牌框 船号 sample { ship_box: { X: 320, # 船舶框左上角 x 坐标 Y: 180, # 船舶框左上角 y 坐标 W: 420, # 船舶框宽度 H: 260 # 船舶框高度 }, plate_box: { x: 350, # 船牌文本区域左上角 x 坐标 y: 220, # 船牌文本区域左上角 y 坐标 w: 150, # 船牌文本区域宽度 h: 40 # 船牌文本区域高度 }, boat_id: 鲁苏A12345 # 船号没有船牌时填 0 }这段结构暴露了一个关键细节船牌框是包含在船舶框内部的而且是定位在文字区域上不是定位在一块「牌」上。这正好解决了「有的船没有船牌、直接把船号刷在船体上」的情况——只要检测到文本区域就把它当作船牌处理。后面 PDNet 之所以用文本检测的思路而不是传统的车牌定位思路根源就在这个标注设计上。你做数据标注的时候务必要让标注工具支持这种嵌套框结构不要拆成两套单独标注否则训练时两个模型的坐标参照系会对不上。2.3 SDNet 损失函数置信度与坐标分开回归SDNet 继承了 YOLOv1 的网格思想把图像划分成 6×6 的网格单元每个单元负责预测一个目标框。但它的损失函数和 YOLOv1 稍有区别论文把损失拆成了预测器损失和框回归损失两部分。# 损失函数核心逻辑 # S 6网格总数为 S*S 36 # C_i: 第 i 个网格预测器输出的置信度 # C*_i: 该网格内预测框与真实框的 IOU # 预测器损失约束置信度向 IOU 看齐 L_predictor sum((C_i - C_star_i) ** 2 for i in range(S * S)) # 坐标损失只对包含目标的网格计算 # (x_c, y_c) 预测中心坐标(w_c, h_c) 预测宽高 # 带 * 的表示真实值 L_box sum( (x_c - x_c_star) ** 2 (y_c - y_c_star) ** 2 (w_c - w_c_star) ** 2 (h_c - h_c_star) ** 2 for c in range(S * S) if obj_exists[c] ) # 最终损失直接相加 L L_predictor L_box这里有个容易被忽略的点预测器损失用的是「置信度与 IOU 的差值平方」意思是每个网格不仅要判断里面有没有目标还要让预测的置信度尽量接近实际框的 IOU。坐标损失则是标准的最小二乘直接回归中心点和宽高。训练时要注意只有那些真正包含了目标中心的网格才参与坐标损失计算其他网格只贡献置信度损失。如果发现训练早期 loss 降不下去先检查是不是把空网格也拉进坐标回归了。2.4 全卷积的代价与收益丢掉全连接层带来了什么SDNet 和 YOLOv1 最大的区别是砍掉了全连接层。YOLOv1 用全连接层做回归计算参数量很大SDNet 改用全卷积结构配合 6×6 网格来做预测。论文给的数据是 YOLOv1 在测试集上 150 fpsSDNet 跑到 194 fps精度上 SDNet 的召回率 0.89 略低于 YOLOv1 的 0.92但准确率反而从 0.97 升到了 0.98。这个取舍背后是有场景判断的。论文里说得明白船舶目标尺寸大、很少密集出现干扰因素少所以用简单的网格候选框就够用不需要 RPN 那种复杂的区域建议网络。全卷积网络可以接受任意尺寸输入而且在船舶这种「目标大、背景干净」的场景里轻量化的收益大于精度损失。如果你的场景换成了密集小目标比如检测港口里密密麻麻的小渔船照搬这个设计会翻车后面避坑章节我会展开。3. PDNet 文本检测EAST 在汉字上的修复方案与文本融合实现3.1 EAST 的翻车现场汉字行被拆成碎片PDNet 是在 EAST 算法基础上改的。EAST 在 ICDAR 2015 英文场景文本数据集上表现不错论文给出的准确率是 0.7820检测 720p 图像能达到 13.2 fps。但用到汉字上出了问题汉字彼此独立EAST 会把一整行船牌文本拆成好几个单独的文本框语义信息就丢了。这个现象在工程里非常常见做中文场景文本检测的人应该都踩过。英文单词之间有空格分隔模型天然能学到单词边界汉字是连续排列的每个字之间没有显式分隔符EAST 这类按像素预测文本区域再组框的算法很容易在字间空隙处断开把一个船号拆成三四个碎片。我在实际项目中见过更夸张的七个字被拆成五段后处理根本拼不回来。3.2 文本融合的判定公式与阈值PDNet 的解决方案是在 EAST 的输出后面加一个文本融合步骤把相邻的文本框按距离合并。论文给出了具体的判定公式这个可以直接抄。def should_merge(box1, box2): box 格式: (x_center, y_center, w, h) x1, y1, w1, h1 box1 x2, y2, w2, h2 box2 # 水平方向归一化距离中心距平方 / 宽度和平方 Xd (x1 - x2) ** 2 / (w1 w2) ** 2 # 垂直方向归一化距离中心距平方 / 高度和平方 Yd (y1 - y2) ** 2 / (h1 h2) ** 2 # 论文给出的阈值水平距离小于 0.7垂直距离小于 0.3 return Xd 0.7 and Yd 0.3这段代码逻辑很清楚两个文本框中心点越近、框越宽Xd 越小越容易被判定为相邻。阈值 0.7 和 0.3 是论文在自建数据集上调出来的水平方向比垂直方向宽松因为船牌文本是水平排列的允许同一行内多个字框合并但不同行的框垂直距离要足够近才算相邻。合并策略是每次合并距离最近的两个框重复到没有相邻框为止。实际复现时我建议先用自己的数据跑一遍这两项阈值分布的直方图再决定是否微调不要直接套 0.7 和 0.3——不同分辨率下文本间距差异很大我遇到过把两行船号并成一个框的情况就是垂直阈值放太松了。3.3 训练配置TensorFlow、学习率与迭代次数论文用 TensorFlow 实现 PDNet 的训练数据集是 7000 张自建船舶图像5000 张训练、2000 张测试。训练参数写得比较清楚学习率从 0.001 开始迭代 85000 次等 loss 不再下降时逐步降到 0.000001。这个「按 loss 收敛情况阶梯降学习率」的思路比固定学习率硬跑要稳得多。# TensorFlow 训练配置参考基于论文参数整理 initial_lr 0.001 final_lr 1e-6 epochs 85000 # 学习率按阶段衰减 def lr_schedule(step): if step 30000: return initial_lr elif step 60000: return initial_lr / 10 # 0.0001 elif step 80000: return initial_lr / 100 # 0.00001 else: return final_lr # 0.000001训练环境是单张 NVIDIA GeForce GTX TITAN XCPU 是 Intel i7 2.40 GHz 六核这配置放到现在不算高说明这个方案对硬件要求并不离谱。测试结果方面PDNet 在 2000 张测试集上召回率 0.77、准确率 0.92对比 EAST 的 0.75 和 0.80准确率提升很明显召回率只是略好。这也符合预期——文本融合主要解决的是「把拆碎的框并起来」和「少误检背景区域」但真正漏检的模糊文本它救不回来。如果复现时发现准确率上去了但召回率不理想问题大概率在 EAST 主干本身对模糊小字的检测能力而不是融合后处理。4. OA-Classifier把识别问题转成分类问题再让 AIS 在线更新4.1 为什么是分类进出港船舶集合有限船牌识别这个任务如果走 OCR 路线得先检测字符再识别字符最后拼成船号流程长、对字符切分要求高。论文绕开了这个思路进出港口的船舶数量是有限的一个港口的船队在短期内基本是可枚举的那就干脆把识别问题转成分类问题——每个船号是一个类别用分类器判断船牌图像属于哪一类。# 船号类别映射示例 # 假设当前港口数据库中有 500 条已知船号 ship_id_classes { 鲁S12345: 0, 鲁S12346: 1, # ... 其他船号 未知: 499 # 增加一个未知类别兜底 }这个思路执行起来有个前提船牌数据库得先覆盖该港口的大部分常驻船舶否则陌生船来了会被强行分到某个已知类别里。论文的解决办法是用 AIS 系统做增量扩展新船进港后通过 AIS 拿到真实船号补进数据库重训分类器。分类网络用的是 ResNetResNet 做图像分类已经是标准操作论文没给具体的 ResNet 层数我一般会先用 ResNet18 起步类别数多了再换成 ResNet50不要一上来就上大网络。4.2 AIS 的滞后性如何被校验组件吸收AIS船舶自动识别系统能提供船位、船速、船号这些信息但论文明确指出了它的两个问题滞后性不是所有船都装了 AIS 设备。所以 AIS 不能作为实时识别结果的直接判据只能当更新接口。这个定位非常关键工程上如果直接拿 AIS 数据去纠正识别结果会因为滞后性把对的改成错的。# OA-Classifier 在线更新流程 def oa_classifier_update(ship_frame, ocr_result, timestamp): # 1. 检测到船舶时记录当前时间戳和港口位置 ts timestamp port_id current_port_id # 2. 向 AIS 接口查询该时间戳、该港口附近的船舶信息 ais_ships ais_query(port_id, ts) # ais_ships: [{ship_id, position, timestamp}, ...] # 3. 找出与当前船舶位置匹配的 AIS 记录 matched find_nearest(ais_ships, ship_position(ship_frame)) if matched is None: return ocr_result # AIS 没数据先用 OCR 结果 # 4. 比对 AIS 船号与分类器识别结果 if matched.ship_id ! ocr_result: # 以 AIS 为准构造新训练样本 plate_crop crop_plate(ship_frame) db.add(plate_crop, matched.ship_id) # 采样完成后离线重训分类器 retrain_schedule() return matched.ship_id if matched.ship_id else ocr_result这段流程把 AIS 的滞后性消化得很干净AIS 数据不参与实时判断只在事后用于校验和积累训练样本。识别结果和 AIS 不一致时以 AIS 为准把船牌截图和船号存进数据库攒一批后离线重训分类器。这样即使 AIS 有滞后只要时间戳和位置对得上样本标签就是可信的。我在类似项目里会额外加一道护栏AIS 返回的船位和当前检测框中心点距离超过某个阈值就直接丢弃防止把附近航道里另一条船的信息归到本条船上。4.3 ResNet 分类器与重训练闭环整个 OA-Classifier 的闭环是分类器识别 → 结果校验 → 不一致时用 AIS 数据生成新样本 → 定期重训 → 分类器覆盖新船号。论文提到「每隔一段时间用船牌文本数据库对 OA-Classifier 分类器进行重训练」这个周期在论文里没有硬性规定实际部署时我一般会在新增样本累计到某个数量后触发重训比如攒够 200 张新样本就训一轮而不是定时定点这样可以避免训练资源浪费。这个闭环设计最值得借鉴的一点是它把「模型识别错了」这件事变成了「数据集变厚」的契机。识别错了说明这个船号不在分类器覆盖范围内正好借 AIS 把样本补进来。跑得越久覆盖面越全精度越高。做长期运行的监控系统这个机制比手动收集数据高效得多。5. 部署在港口的避坑指南五个现场常见问题与排查思路5.1 AIS 反馈与识别结果对不上现象OA-Classifier 识别出的船号和 AIS 返回的船号不一致有时候 AIS 返回的是上一艘船的信息。原因AIS 数据本身存在滞后性而且港口附近船舶密集时AIS 查询结果可能包含不止一条船时间戳和位置匹配不够精确就会错位。解决不要直接用 AIS 结果覆盖识别结果。按论文的做法AIS 只作为比对参考和样本来源比对时用「时间戳 港口位置 检测框中心点距离」三重约束筛选 AIS 记录距离阈值根据画面像素和实际距离换算好再定。我通常会把 AIS 匹配不上或置信度不高的识别结果单独标记为「待人工复核」而不是让系统自动给一个答案。5.2 汉字文本行被拆成多个检测框现象PDNet 输出的船牌区域是一堆小碎框一个船号被切成三四段后续分类器根本没法用。原因EAST 类算法对汉字这种无空格分隔的文本天然不友好字间空隙处容易断裂。只加文本融合还不够融合阈值不匹配分辨率也会导致融合失败。解决先复现论文的融合逻辑Xd 0.7、Yd 0.3跑一批数据统计 Xd 和 Yd 的分布再按统计结果调整阈值。另外要注意文本融合只是后处理如果 EAST 主干输出的框本身漏了字后处理救不回来要回头查训练数据和学习率。5.3 6x6 网格在密集场景漏检现象SDNet 在船舶稀疏的港口主航道检测效果不错但一到锚地或渔港多条船挨在一起漏检率明显上升。原因SDNet 的候选框策略是 6×6 网格加每个网格预测一个目标一个网格只能负责一个目标目标密集时框的质量必然下降。这是论文明确承认的限制船舶场景目标稀疏才够用。解决如果你的场景不是稀疏目标不要照搬 SDNet。要么把网格调密比如 12×12要么换 SSD 或 YOLOv5 这类多尺度检测器。网格密度直接影响召回率上限调网格比调 loss 更立竿见影。5.4 loss 不降或收敛过慢现象训练 SDNet 或 PDNet 时 loss 一直在高位震荡降不到论文描述的水平。原因最常见的有三种学习率初始化不合理、坐标损失把空网格也算进去了、数据标注的框不齐。论文说的是学习率从 0.001 开始迭代 85000 次但不同分辨率、不同 batchsize 下这个值要重新试。解决先按论文参数跑一遍确认 loss 曲线形状如果不降优先检查代码里坐标损失是否只对含目标的网格生效再检查标注框是否有长宽为 0 或越界的脏数据。数据问题比参数问题在项目里出现得更多。5.5 船牌不在船号直接写在船体上现象部分船舶根本没有独立的船牌船号刷在船头或船尾反光、锈蚀导致文本不清晰PDNet 检测不到或检测不全。原因这是中国内河和沿海船舶的常见情况船牌检测本质上变成了场景文本检测文本背景是金属船体而不是规整的牌照底板。解决论文的思路是「有文本的区域就是船牌」标注时把船号文本区域直接当作船牌框。工程上还要做一步预处理——对船体区域做对比度增强和去反光否则暗色船体上的深色船号很容易漏检。建议在采集原始图像时就把相机角度压低压平减少倾斜带来的文本形变。6. 从这篇论文迁移到自己的项目验证口径与替换骨干网络的实操思路6.1 先用 IOU 0.7 这个口径验证论文里评估检测质量的标尺是「预测框与真实框的 IOU 0.7 视为正确」这个阈值直接决定了召回率和准确率的数值口径。你现在是随便设了个 IOU 阈值比如 0.5真实性能数字在对比和汇报时可能会被质疑。建议写一个统一的评估脚本按 IOU 0.7 计算 TP、FP、FN输出准确率和召回率def compute_metrics(pred_boxes, gt_boxes, iou_threshold0.7): tp fp fn 0 for pred in pred_boxes: # 找到与当前预测框 IOU 最大的真实框 best_iou max(iou(pred, gt) for gt in gt_boxes) if best_iou iou_threshold: tp 1 else: fp 1 # 简化版假设一个真实框只匹配一个预测框 fn len(gt_boxes) - tp recall tp / (tp fn) if (tp fn) 0 else 0 precision tp / (tp fp) if (tp fp) 0 else 0 return recall, precision这个脚本对应论文中的公式做模型对比或版本迭代时统一用它避免一个项目里多种口径互相打架。有了稳定口径之后再去调参数才谈得上「提升」。6.2 用 YOLO 系列替换 SDNet 的边界SDNet 是 2019 年的轻量化设计现在直接照搬可能不是最优选择。如果项目允许 GPU 资源更充裕换成 YOLOv5s 或 YOLOv8s 做船舶检测通常能拿到更好的小目标召回率。但要记住论文的核心约束条件船牌检测是依赖船舶框的「串行」结构的SDNet 换成 YOLO 之后如果速度太慢导致整体帧率骤降就要考虑把两个检测合并成一个模型或者用目标追踪来降低检测频率、复用历史检测结果。6.3 自制数据集时最值得投入的环节论文的 7000 张图是硬凑出来的最值钱的部分是数据增强和标注质量这直接决定你的模型在阴雨天、逆光、夜间灯光下能不能扛住。我的习惯是把增强流程跑在两套数据上标注好的训练集和一批完全未标注的现场原始视频让增强后的效果跟现场真实环境尽量靠拢。标注时要注意嵌套框的关系船牌框必须落在船舶框内部否则后处理阶段会冒出「船牌在船外」这种幻觉框。从那以后我每次搭类似的检测流程都会先确认两个问题真实场景里目标是稀疏还是密集、文本是独立字符还是整行再决定要不要照搬这三级结构。这个习惯帮我少踩了很多坑希望帮到你。本文还有配套的精品资源点击获取