在实际桩基工程项目的现场记录里,“桩型有派,纹理带帅,青龙八道筋”这类说法经常出现在质检人员的口头描述中。它其实概括了三类关键信息:桩型类别、桩身表面纹理,以及钢筋笼上规律布置的八道纵向主筋。把这三类信息从现场照片中快速识别出来,几乎是所有桩基信息化系统的基础能力。本文围绕一个名为“青龙八道筋留图识别”的最小图像识别项目,讲解如何用 PyTorch 实现多任务模型,把现场拍摄的桩体照片自动转换成结构化结果,并提供一套可复用的训练、验证和部署方法。
这篇文章不是只讲理论,而是直接面向一个可运行的小系统。你会先理解三个识别目标分别是什么,再看到数据标注格式、模型结构、训练代码、推理接口和常见报错排查方式。无论你负责桩基质检系统,还是需要做工业图像分类项目,都可以把这套思路移植到自己的场景里。下面所有代码都用于说明实现路径,实际项目落地时需要根据你的数据集、目录结构和部署环境做调整。
1. 先搞清楚项目里的三个识别目标
1.1 桩型识别:从规格上先分类
“桩型有派”中的“派”,在工程系统里可以理解为桩的规格类型。常见的预制桩有 PHC、PC、PTC 等类型,它们的混凝土强度等级、配筋方式和适用场景各不相同。现场人员拍照记录时,桩身外观、端板形式和标识文字都可能作为判断依据。
从图像识别角度,桩型是一个典型的多分类任务。模型输入一张桩体照片,输出“PHC”“PC”“PTC”中的一个标签。相比目标检测,它不需要框出桩体位置,只要求图片主体是桩身或端板区域,因此可以使用分类网络快速落地。
这里需要注意,桩型分类不是多标签问题。一根桩的桩型应当是确定的,模型输出的概率向量中只会取概率最高的那一类。后续代码里会使用CrossEntropyLoss处理这个分支。
1.2 表面纹理:判断桩身工艺特征
“纹理带帅”中的“纹理”,指的是桩身表面的工艺特征。常见的纹理形态有光面、刻痕、抗渗纹等。纹理是否规则、清晰,能反映混凝土成型质量、脱模效果和模具状态。比如带有均匀防滑块或刻痕的管桩,往往是为了提高与承台或土体的摩擦力。
纹理识别的难点在于类间差异小、光照变化大。现场照片会在不同角度、不同光线条件下拍摄,同一根桩的纹理可能看起来差异很大。因此数据增强在纹理分支上非常重要,代码里会加入随机旋转、翻转、颜色抖动和轻微模糊,帮助模型抓住纹理本身的规律,而不是记住照片的拍摄环境。
纹理分支仍然采用多分类。如果你需要把“有无纹理”作为二分类问题,也可以把标签简化成texture: smooth和texture: ribbed两组,代码结构不变。
1.3 青龙八道筋:钢筋配置的关键检测项
“青龙八道筋”是本文项目命名的核心来源。在不少桩基设计图中,钢筋笼的主筋会按 6、8、10 根等数量均匀布置,8 根是一种常见配置。现场施工人员为了快速描述这种配置,常把它叫成“八道筋”。“青龙”在这里更多是形容沿桩身规律布置的视觉效果,并不是某种神秘符号。
从检测任务看,八道筋的识别目标有两个方向。一种是判断钢筋笼是否采用了八道纵向主筋,输出一个二分类标签;另一种是识别主筋数量,输出 0 到 10 的数量值。为了降低标注和训练难度,本文先采用二分类方案:标签eight_bar取 1 表示当前桩身钢筋笼符合八道筋配置,取 0 表示不是。
如果你希望模型直接输出主筋数量,可以把最后一个分类头改成回归头,输出一个浮点数,再用MSELoss训练。实际项目中返回数量比返回“是不是”更有用,但对数据集质量要求也更高。
1.4 为什么用“留图”方式落地
标题里的“结缘留图”,按工程系统的理解,就是用户把现场照片留下来并上传到系统,由后台模型返回识别结果。这种交互方式很适合移动端和现场质检流程:检测人员拍摄照片,系统自动识别桩型、纹理和钢筋配置,把结果写入台账。
相比手动填写记录,“留图”有三个明显好处:
- 照片本身就是原始凭证,后续出现争议时可以回溯。
- 识别结果可以自动落到 Excel 或数据库中,减少二次录入。
- 积累大量图片后,模型可以不断迭代,识别准确率会随数据量提升。
所以“留图”不只是上传功能,更是数据闭环的一部分。下一节会围绕这个场景设计技术方案。
2. 技术选型:多任务分类比三个独立模型更合适
2.1 任务是单标签还是多标签
一个图片需要同时输出三个结果:桩型、纹理、八道筋。如果做成三个独立模型,每个模型只负责一个属性,开发和维护成本都会增加。但观察任务结构可以发现,三个属性都来自同一张桩体图片,共享底层视觉特征,比如轮廓、色块、纹理方向。因此更适合使用多任务模型,即一个骨干网络提取特征,再接多个分类头,分别输出不同预测结果。
这样做的好处是:
- 训练和推理都只需要加载一个模型。
- 共享特征提取层可以减少参数量。
- 三个任务之间的底层特征可以互相促进,尤其是样本量不足时。
需要注意,多任务不是万能的。如果三个任务的数据差异很大,或者某个任务会干扰另一个任务,就需要给不同分支设置损失权重,甚至在反向传播时冻结部分层。
2.2 骨干网络对比
骨干网络负责从图像中提取通用特征。针对桩基图像识别,网络不需要特别大,因为类别数量少,图像内容相对固定。下面表格列出三个可选方案。
| 模型 | 参数量 | 推理速度 | 精度特点 | 适用场景 |
|---|---|---|---|---|
| ResNet18 | 约 11.2M | 快 | 结构简单、稳定,CPU 可运行 | 学习环境、初步原型 |
| MobileNetV3-Small | 约 2.5M | 很快 | 轻量,适合边缘设备 | 手机端、嵌入式设备 |
| EfficientNetV2-S | 约 21M | 中等 | 精读高,数据充足时效果好 | 服务器端、数据集较大 |
实际项目不要盲目追求大模型。如果训练数据只有几百张,ResNet18 或者 MobileNetV3 足够。等到数据量到几千张、类别分布稳定之后,再切换更大模型才有收益。
2.3 项目环境依赖
本文代码基于 PyTorch。建议使用 Python 3.9 或更高版本,主要依赖如下:
pip install torch torchvision opencv-python pillow pandas flask onnxruntime环境说明:
torch和torchvision用于模型定义和训练。opencv-python用于图像读取和简单预处理。pandas用于读取 CSV 标注文件。pillow负责图片对象处理。flask用于搭建上传识别接口。onnxruntime用于部署阶段的模型推理。
如果你的机器没有 GPU,仍然可以跑通本文代码,只是训练速度会慢一些。建议把图片尺寸控制在 224x224,CPU 训练也能接受。
2.4 项目目录结构
下面是一个推荐的目录结构,避免后续代码路径混乱:
pile_recognition/ ├── data/ │ ├── images/ │ │ ├── 001.jpg │ │ ├── 002.jpg │ │ └── ... │ └── annotations.csv ├── train.py ├── predict.py ├── app.py ├── models/ └── weights/data/images存放现场图片。data/annotations.csv存放标注结果。train.py负责训练。predict.py负责单张图片推理。app.py负责上传接口。models存放模型定义文件或导入代码。weights存放训练好的权重文件。
实际项目建议把这个目录纳入 Git 管理,但权重文件和图片数据不要直接提交到仓库,避免仓库体积过大。
3. 数据标注与预处理
3.1 标注 CSV 设计
桩基图像识别项目里,标注是最消耗时间、最容易出错的部分。每张图片需要标注三个字段:桩型、纹理、八道筋。推荐用 CSV 保存,方便人工检查和后续追加数据。
image_id,image_path,pile_type,texture,eight_bar 001,data/images/001.jpg,PHC,ribbed,1 002,data/images/002.jpg,PC,smooth,0 003,data/images/003.jpg,PTC,wire,1字段说明:
| 字段 | 含义 | 示例值 |
|---|---|---|
| image_id | 图片唯一标识 | 001 |
| image_path | 图片相对路径 | data/images/001.jpg |
| pile_type | 桩型类别 | PHC、PC、PTC |
| texture | 纹理类别 | smooth、ribbed、wire |
| eight_bar | 是否为八道筋配置 | 0 或 1 |
标注时要注意,eight_bar是数值 0/1,不是字符串。pile_type和texture使用统一的英文字符串,避免中文标签在跨平台时出现编码问题。
3.2 数据加载器实现
数据加载器的核心作用是把图片路径和标签转换成模型需要的张量。代码如下:
import os import torch from torch.utils.data import Dataset from PIL import Image class PileDataset(Dataset): def __init__(self, df, img_dir, transform=None): self.df = df self.img_dir = img_dir self.transform = transform self.type_map = {"PHC": 0, "PC": 1, "PTC": 2} self.texture_map = {"smooth": 0, "ribbed": 1, "wire": 2} def __len__(self): return len(self.df) def __getitem__(self, idx): row = self.df.iloc[idx] img_path = os.path.join(self.img_dir, row["image_path"]) image = Image.open(img_path).convert("RGB") if self.transform: image = self.transform(image) type_label = torch.tensor(self.type_map[row["pile_type"]], dtype=torch.long) texture_label = torch.tensor(self.texture_map[row["texture"]], dtype=torch.long) eight_label = torch.tensor(float(row["eight_bar"]), dtype=torch.float32) return image, type_label, texture_label, eight_label注意几个关键点:
- 图片统一转换成 RGB,避免灰度图导致输入通道不一致。
eight_label使用float32,配合后面的BCEWithLogitsLoss。pile_type和texture如果 CSV 中出现未知值,会在self.type_map[row["pile_type"]]处抛KeyError,这正是我们想要的效果,能尽早发现标注错误。
3.3 数据增强策略
数据增强能显著提高模型泛化能力,尤其是在现场图片数量有限时。训练集和验证集要使用不同的 transform。训练集可以适度增强,验证集只做缩放和归一化。
from torchvision import transforms train_transform = transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomResizedCrop((224, 224), scale=(0.8, 1.0)), transforms.RandomHorizontalFlip(p=0.5), transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) valid_transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])这里RandomResizedCrop会随机裁剪图片,模拟不同拍摄距离下的局部特征。ColorJitter模拟现场不同光照条件。对于纹理识别,颜色抖动尤其重要,因为阴影和曝光会明显改变纹理观感。
不要对验证集加入随机增强,否则验证指标会受随机因素干扰,无法真实反映模型效果。
4. 多任务模型与训练
4.1 模型结构
模型采用“共享骨干 + 三个分类头”的结构。骨干网络使用 ResNet18,去掉最后的全连接层后,把特征分别送入三个输出头。
import torch.nn as nn import torchvision.models as models class PileMultiTask(nn.Module): def __init__(self, num_types=3, num_textures=3): super().__init__() self.backbone = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) in_features = self.backbone.fc.in_features self.backbone.fc = nn.Identity() self.type_head = nn.Linear(in_features, num_types) self.texture_head = nn.Linear(in_features, num_textures) self.eight_head = nn.Linear(in_features, 1) def forward(self, x): feat = self.backbone(x) type_logits = self.type_head(feat) texture_logits = self.texture_head(feat) eight_logits = self.eight_head(feat) return type_logits, texture_logits, eight_logits这段代码的核心是self.backbone.fc = nn.Identity()。如果不做这一步,ResNet18 会直接输出 1000 类 ImageNet 预测结果,后面的分类头就接收不到正确维度的特征。使用Identity后,特征向量维度是 512。
eight_head输出维度是 1,没有经过 Sigmoid,因为训练时使用BCEWithLogitsLoss会更稳定。
4.2 损失函数为什么这样组合
三个任务使用不同损失函数:
- 桩型分支使用
CrossEntropyLoss,因为它是多分类。 - 纹理分支同样使用
CrossEntropyLoss。 - 八道筋分支使用
BCEWithLogitsLoss,因为它是二分类,且模型输出的是 logit。
总损失是三者之和:
loss = loss_type + loss_texture + loss_eight如果发现某个任务精度明显偏低,可以给对应损失乘上权重。例如:
loss = 1.0 * loss_type + 1.2 * loss_texture + 0.8 * loss_eight权重越大,模型越优先优化该任务。实际使用时,建议先让三个任务权重都为 1.0 跑一轮,观察哪个分支收敛慢再调整。
4.3 训练代码
训练脚本按最简方式实现,包含一个 epoch 的训练和验证逻辑。
import torch import torch.nn as nn from torch.utils.data import DataLoader def train_one_epoch(model, loader, optimizer, device): model.train() total_loss = 0.0 for images, type_labels, texture_labels, eight_labels in loader: images = images.to(device) type_labels = type_labels.to(device) texture_labels = texture_labels.to(device) eight_labels = eight_labels.to(device).unsqueeze(1) optimizer.zero_grad() type_logits, texture_logits, eight_logits = model(images) loss_type = nn.CrossEntropyLoss()(type_logits, type_labels) loss_texture = nn.CrossEntropyLoss()(texture_logits, texture_labels) loss_eight = nn.BCEWithLogitsLoss()(eight_logits, eight_labels) loss = loss_type + loss_texture + loss_eight loss.backward() optimizer.step() total_loss += loss.item() return total_loss / len(loader)训练主循环里需要保存验证集上表现最好的权重,而不是最后几轮的权重:
best_f1 = 0.0 for epoch in range(epochs): train_loss = train_one_epoch(...) val_metrics = validate(model, val_loader, device) if val_metrics["f1"] > best_f1: best_f1 = val_metrics["f1"] torch.save(model.state_dict(), "weights/best_model.pt")保存最佳权重的逻辑对工业项目尤其重要。训练后期模型可能过拟合,最后几个 epoch 的权重并不一定是最优的。
4.4 关键训练参数表
训练参数的设置会直接影响收敛效果。下面是常见配置和解释。
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 图片尺寸 | 224x224 | 兼顾精度和训练速度,ResNet 标准输入 |
| batch_size | 16 或 32 | 显存小用 16,CPU 训练建议 8 |
| epoch | 30 到 50 | 数据少时不要过大,关注验证曲线 |
| 学习率 | 1e-4 到 3e-4 | 使用 ImageNet 预训练权重时不宜过大 |
| 优化器 | Adam | 简单稳定,适合多任务损失 |
| 学习率策略 | CosineAnnealingLR | 后期下降平缓,精度更稳定 |
如果训练曲线震荡严重,可以先降低学习率。如果验证集误差不下降,可以考虑增加数据量或增强强度,而不是继续堆 epoch。
5. 推理验证与模型导出
5.1 单张图片推理
训练完成后,需要写一个推理脚本,输入图片路径,输出结构化结果。核心代码:
import torch from PIL import Image from torchvision import transforms id_to_type = {0: "PHC", 1: "PC", 2: "PTC"} id_to_texture = {0: "smooth", 1: "ribbed", 2: "wire"} def predict_single(model, image_path, device): model.eval() image = Image.open(image_path).convert("RGB") tensor = valid_transform(image).unsqueeze(0).to(device) with torch.no_grad(): type_logits, texture_logits, eight_logits = model(tensor) type_id = torch.argmax(type_logits, dim=1).item() texture_id = torch.argmax(texture_logits, dim=1).item() eight_prob = torch.sigmoid(eight_logits).item() return { "pile_type": id_to_type[type_id], "texture": id_to_texture[texture_id], "eight_bar_prob": round(eight_prob, 4), "eight_bar": 1 if eight_prob > 0.5 else 0 }这里必须调用model.eval(),把 dropout 和 batch normalization 切换到评估模式,否则同一张图片多次推理结果可能不同。
5.2 输出结果解读
对于一张测试图片,输出可能如下:
{ "pile_type": "PHC", "texture": "ribbed", "eight_bar_prob": 0.9732, "eight_bar": 1 }含义是:模型认为这张桩体照片的桩型为 PHC,纹理为刻痕,八道筋置信度为 0.9732,因此判定为八道筋配置。
eight_bar_prob越接近 0 或 1,说明模型越有把握。如果结果接近 0.5,建议在系统里标记为“低置信度,需要人工复核”。
5.3 评估指标
只打印准确率还不够。工业场景中更应该关注每个类别的精确率、召回率和 F1 值。可以单独写一个评估函数,统计混淆矩阵。
from sklearn.metrics import classification_report # 假设 all_type_preds 和 all_type_labels 分别存了批次预测结果 print(classification_report(all_type_labels, all_type_preds, target_names=["PHC", "PC", "PTC"]))对于八道筋任务,因为正负样本可能不均衡,建议同时观察recall和precision。如果只关心准确率,模型很可能把所有图片都判成“不是八道筋”,准确率也不低,但没有实际价值。
5.4 导出 ONNX
训练好的 PyTorch 模型可以用 ONNX 导出,方便在服务端或边缘设备上部署。
dummy_input = torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, "weights/pile_model.onnx", input_names=["input"], output_names=["type_logits", "texture_logits", "eight_logits"], dynamic_axes={ "input": {0: "batch"}, "type_logits": {0: "batch"}, "texture_logits": {0: "batch"}, "eight_logits": {0: "batch"} } )dynamic_axes允许推理时批量大小不固定。生产环境可以用onnxruntime加载这个模型,避免每个服务都安装 PyTorch,显著降低部署体积和内存占用。
6. 实现“留图上传”识别接口
6.1 Flask 接口
为了让“结缘留图”真正可用,需要提供一个 HTTP 上传接口。使用 Flask 实现很直接:
import os import time import tempfile from flask import Flask, request, jsonify app = Flask(__name__) @app.route("/predict", methods=["POST"]) def predict(): f = request.files.get("image") if f is None or f.filename == "": return jsonify({"code": 400, "message": "missing image"}), 400 suffix = os.path.splitext(f.filename)[1].lower() if suffix not in {".jpg", ".jpeg", ".png"}: return jsonify({"code": 400, "message": "unsupported image type"}), 400 tmp_path = os.path.join( tempfile.gettempdir(), f"pile_{int(time.time() * 1000)}{suffix}" ) f.save(tmp_path) try: result = predict_single(model, tmp_path, device) return jsonify({"code": 0, "data": result}) except Exception as e: return jsonify({"code": 500, "message": str(e)}), 500 finally: os.remove(tmp_path) if __name__ == "__main__": model = PileMultiTask() model.load_state_dict(torch.load("weights/best_model.pt", map_location="cpu")) model.eval() device = torch.device("cpu") app.run(host="0.0.0.0", port=5000)这里需要注意临时文件的清理。无论推理成功还是失败,finally都会删除临时图片,避免服务器磁盘被占满。
6.2 使用 curl 验证
Flask 服务启动后,可以在另一个终端发送请求:
curl -X POST http://127.0.0.1:5000/predict \ -F "image=@data/images/001.jpg"正常的响应如下:
{ "code": 0, "data": { "pile_type": "PHC", "texture": "ribbed", "eight_bar_prob": 0.9732, "eight_bar": 1 } }如果缺少文件字段,会收到:
{ "code": 400, "message": "missing image" }先通过 curl 验证接口,再去写前端页面,能省去很多联调成本。
6.3 接口返回设计
接口返回建议统一包裹code字段,而不是直接返回数据或抛异常。前端判断逻辑会简单很多:
code = 0表示成功,数据放在data中。code = 400表示参数错误。code = 500表示服务端异常。
不要把模型预测的原始 logits 直接返回给前端,前端只需要可读的标签和置信度。如果需要追溯,可以在服务端记录一份完整日志,但接口层保持精简。
6.4 前端上传页面的关注点
如果后续要写前端页面,需要注意以下几点:
- 上传前在浏览器端校验图片格式和大小。
- 使用
FormData提交文件,字段名要和后端request.files.get("image")保持一致。 - 显示结果时把低置信度样本标黄,提示人工复核。
- 上传接口要设置超时时间,防止大图导致服务阻塞。
前端页面不是本文重点,但接口设计必须为前端留出余地,尤其是错误码和字段类型要保持稳定,否则前端每兼容一次接口改动都会很痛苦。
7. 常见问题排查
7.1 图片上传报 404 或 400
如果调用接口时出现 404,先确认 Flask 服务是否启动、路由路径是否匹配。如果出现 400,通常是缺少image字段或文件名为空。
检查方式:
curl -X POST http://127.0.0.1:5000/predict -F "image=@data/images/001.jpg" -v加上-v可以看到 HTTP 请求和响应详情。只要不是参数问题,基本都能定位到是路径不对还是文件字段不对。
7.2 训练损失为 NaN
训练时 loss 变成 NaN,常见原因有:
- 学习率过大,梯度爆炸。
- 归一化参数错误,导致输入张量包含异常值。
- 标签越界,例如
pile_type取值不在 0 到 2 之间。 - 使用了不稳定的损失组合。
解决思路是先调小学习率,比如从3e-4降到1e-5。再打印训练集的均值、方差,确认归一化后的图像像素范围正常。最后检查标注 CSV 中是否有多余空格,导致 label 映射失败。
7.3 所有图片被预测成同一个类别
最典型的原因是样本不均衡。比如 90% 的图片桩型标注为 PHC,模型只要全部输出 PHC,准确率也能到 90%,但纹理和八道筋任务会失效。
应对方法:
- 增加少数类样本,或对少数类做更强的数据增强。
- 在
DataLoader中使用WeightedRandomSampler,提高少数类采样概率。 - 给少数类分支设置更高损失权重。
不要一开始就换复杂模型,先确认训练集类别分布是否合理。
7.4 CPU 推理太慢
如果模型在 CPU 上单张图片推理超过几百毫秒,可以从以下几个方面优化:
- 图片输入尺寸从 224x224 降到 192x192 或 160x160。
- 使用 MobileNetV3 替换 ResNet18。
- 导出 ONNX,用
onnxruntime推理。 - 如果 CPU 支持 AVX 指令,安装带 AVX 优化的 PyTorch 或 ONNX Runtime 版本。
对于现场管理系统,单张图片推理速度在 1 秒内通常可以接受。如果并发量高,还要考虑加 GPU 或限制队列长度。
7.5 换数据集后维度不匹配
把代码用到自己的数据上时,最容易遇到“维度不匹配”错误。常见原因是类别数量对不上:
- 桩型从 3 类变成 4 类,但
PileMultiTask仍使用num_types=3。 - 纹理从 3 类变成 2 类,但
num_textures=3。 - 目标从二分类改成回归,但
eight_head输出维度仍是 1。
每次改数据集,都需要同步检查模型初始化参数和id_to_type、id_to_texture映射表。建议把类别数量和标签映射写成一个配置文件,避免代码里硬编码。
8. 生产环境落地建议
8.1 学习环境与生产环境差异
学习环境里,训练脚本和推理脚本可以放在同一台机器上,路径也可以写死。生产环境则需要补齐很多边界:
| 对比项 | 学习环境 | 生产环境 |
|---|---|---|
| 配置文件 | 硬编码在代码中 | 环境变量或配置中心 |
| 模型加载 | 每次启动新建模型 | 预加载并复用实体 |
| 日志 | 控制台打印 | 结构化日志 + 监控告警 |
| 异常处理 | 直接打印堆栈 | 统一错误码和链路追踪 |
| 数据备份 | 不强制 | 标注数据和模型权重定期备份 |
| 回滚 | 不关注 | 保留多个模型版本,支持快速回退 |
不要直接把训练脚本搬到生产环境。训练代码关注的是指标,部署代码关注的是稳定性、并发和可观测性。
8.2 模型版本管理与回滚
模型文件应该像代码一样做版本管理。推荐命名格式:
pile_model_v1_20250101.pt pile_model_v2_20250601.pt每次发布模型时,记录以下信息:
- 训练数据量和类别分布。
- 验证集准确率、召回率、F1 值。
- 模型结构和权重文件路径。
- 发布时间和负责人。
如果新模型出现明显误判,旧模型权重还在,可以快速切换回滚。
8.3 数据标注质量检查
数据标注决定了模型上限。建议在正式训练前检查标注一致性:
- 随机抽 50 张图,让两个标注人员分别标注。
- 比较两人标注结果,计算一致率。
- 一致率低于 90% 时,先修订标注规范,不要急着训练。
对于桩基图片,容易产生歧义的是纹理类别。建议在标注规范里附上每种纹理的示例图和文字描述,减少主观判断差异。
8.4 发布前检查清单
在把系统发布到测试环境或生产环境前,可以按下面的清单检查:
| 检查项 | 检查内容 |
|---|---|
| 权重文件 | 是否存在,是否对应最新模型 |
| 类别映射 | id_to_type和id_to_texture是否与训练时一致 |
| 图片预处理 | 推理时是否使用了和训练相同的 transform |
| 接口字段 | 返回字段名是否稳定,是否有中英文混用 |
| 异常路径 | 缺少文件、图片损坏、超时是否有兜底返回 |
| 日志记录 | 是否记录了图片名称、推理结果、置信度和耗时 |
| 权限控制 | 上传接口是否需要登录鉴权,是否限制文件类型和大小 |
| 备份方案 | 模型权重和 CSV 标注是否已备份 |
每项都值得落到操作手册里,而不是靠记忆。
8.5 后续扩展方向
这个项目把桩型、纹理、八道筋三个识别任务做成了多任务分类。后续可以扩展的方向包括:
- 从二分类八道筋改成主筋数量回归,直接输出 6、8、10 等数值。
- 把桩身区域检测和属性识别串联,形成“检测 + 识别”两阶段流程。
- 记录每张图片的拍摄时间和 GPS,建立桩基质量档案。
- 对低置信度结果自动进入人工复核队列,持续补充难样本。
实际工程中,真正决定系统价值的并不是模型结构,而是数据规范、部署稳定性和人工协作机制。先把最小可用系统跑通,再逐步积累数据,模型的精度和可信度才会稳步提升。