2025MathorCup大数据竞赛A题思路分析模型代码,基于计算机视觉的集装箱智能破损检测
dataset/├─ train/│ ├─ normal/ # 无残损(标签0)│ │ └─ ...│ └─ damaged/ # 有残损(标签1)│ └─ ...└─ val/ # 验证集,结构同上import os# 加载图片路径和标签# 无残损(标签0)# 有残损(标签1)# 读图片(OpenCV读的是BGR,转RGB)# 应用增强# 转成PyTorch的Tensor格式(通道在前:HWC→
2025MathorCup大数据竞赛A题思路分析模型代码,完整模型论文持续更新见文末名片
咱们今天就盯着赛道A(集装箱破损检测)往细了说,从数据怎么收拾、模型怎么选、代码怎么写,到每个环节要踩的坑,全给你捋明白——全程不沾赛道B的边,就扣着A题的需求来。
首先得明确,A题的核心是“跟图片打交道”,但图片里全是坑:港口背景乱(机器、天空、地面混在一起)、光照一会儿亮一会儿暗、还有反光阴影雨水渍,更头疼的是数据不均衡——没破损的图可能占一大半,有破损的里还分“大锈蚀”“小裂纹”,小裂纹的样本可能少得可怜。所以第一步必须先把数据“捋顺”,不然模型学半天全学歪了,要么把背景当破损,要么漏了小裂纹。
一、先搞定“数据预处理”——这是所有模型的基础,比选模型还重要
咱们先解决A题里提到的“背景复杂、干扰多、数据不平衡”这三个破事,代码用Python+OpenCV+Albumentations(处理图像超好用的库)来写,咱们一步步来:
1. 先给图片“减减肥”——去掉没用的背景
港口的图里,集装箱可能只占中间一部分,周围的机器、天空都是干扰。咱们可以先做“粗定位”:要么用简单的边缘检测(Canny算法)把集装箱的矩形轮廓抠出来,要么如果数据集里有标注(比如标了集装箱的位置),直接按标注裁图。
举个代码例子,用边缘检测抠集装箱:
import cv2
import numpy as np
def crop_container(image):
# 转灰度图,方便边缘检测
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 去噪(高斯模糊),避免把污渍当边缘
blur = cv2.GaussianBlur(gray, (5,5), 0)
# 边缘检测(Canny),调阈值找集装箱的硬边缘
edges = cv2.Canny(blur, 50, 150)
# 找轮廓,只留面积最大的(大概率是集装箱)
contours, _ = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if not contours:
return image # 没找到轮廓就返回原图
max_contour = max(contours, key=cv2.contourArea)
# 给轮廓画个 bounding box
x, y, w, h = cv2.boundingRect(max_contour)
# 裁剪集装箱区域(留10个像素边距,避免裁掉边缘破损)
cropped = image[y-10:y+h+10, x-10:x+w+10]
# 防止裁剪出负坐标(比如集装箱靠 edge)
cropped = np.clip(cropped, 0, 255)
return cropped
这么一弄,图片里就只剩集装箱了,背景干扰少了一大半。
2. 给图片“做美容”——解决光照、反光、阴影
A题里提到的光照变化、反光、阴影,会让同一个破损在不同图里看起来不一样,模型认不出来。咱们用“图像增强”来解决:
光照/反光:调亮度、对比度,用“直方图均衡化”(CLAHE,比普通均衡化效果好);
阴影:用“灰度世界法”或者“Retinex算法”去阴影,这里给个简单的去阴影代码;
污渍:加轻微的高斯模糊或者椒盐噪声(反过来想,训练时让模型见过“脏图”,测试时就不怕污渍了)。
import albumentations as A
# 定义增强 pipeline(训练用,测试不用随机增强)
train_transform = A.Compose([
# 先裁剪集装箱(刚才写的函数)
A.Lambda(image=crop_container),
# 调整大小(统一输入尺寸,比如640x640,方便模型处理)
A.Resize(height=640, width=640),
# 解决光照:随机调亮度、对比度、饱和度
A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.5),
# 解决反光:随机调HUE(色调),模拟不同光线颜色
A.HueSaturationValue(hue_shift_limit=10, sat_shift_limit=15, val_shift_limit=10, p=0.5),
# 去阴影(简单版:用图像均值减阴影区域)
A.Lambda(image=lambda x: remove_shadow(x)),
# 数据扩充(解决数据不平衡):随机翻转、旋转
A.HorizontalFlip(p=0.5),
A.RandomRotate90(p=0.5),
# 加轻微模糊,模拟污渍
A.GaussianBlur(blur_limit=(3, 5), p=0.3),
# 转Tensor(给PyTorch用)
A.ToFloat(max_value=255.0),
])
# 去阴影函数(简单有效版)
def remove_shadow(image):
if len(image.shape) == 3:
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
else:
gray = image
# 用闭运算(先膨胀再腐蚀)找出阴影区域(暗的大块区域)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (20,20))
closed = cv2.morphologyEx(gray, cv2.MORPH_CLOSE, kernel)
# 阴影区域 = 原图灰度 闭运算结果(暗的地方差值大)
shadow_mask = cv2.absdiff(gray, closed)
# 阈值化,把阴影区域标出来
_, shadow_mask = cv2.threshold(shadow_mask, 10, 255, cv2.THRESH_BINARY_INV)
# 用周围非阴影区域的均值填充阴影
mean_val = cv2.mean(gray, mask=cv2.bitwise_not(shadow_mask))[0]
gray[shadow_mask == 255] = mean_val
# 转回去彩色图
if len(image.shape) == 3:
return cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)
return gray
3. 解决“数据不平衡”——别让模型只认“没破损”
A题里说“无残损图片远多于有残损”,还有“裂缝样本少”。咱们用两种办法:
过采样:对“有残损”的图多做几种增强(比如旋转、翻转、调亮度),相当于造更多有破损的样本;
加权损失:训练时给“少样本类别”(比如裂缝)更高的权重,比如没破损的样本权重设1,裂缝设5,让模型更重视少样本。
二、问题1:二分类(有没有残损)——简单但要“稳”
这个任务不用找位置,就判断“有/无”,核心是“别漏检”(漏了破损会出安全事故,比误检严重)。所以模型不用太复杂,用“迁移学习”(站在巨人肩膀上)最划算——拿预训练好的图像分类模型改改就行。
1. 选模型:优先ResNet50/EfficientNetB0
这俩模型成熟、效果好,还不占太多显存。ResNet50抗过拟合能力强,EfficientNetB0更轻量(速度快),选一个就行。咱们用PyTorch来写,因为灵活。
2. 代码核心步骤
(1)定义数据集类(加载图片+标签)
dataset/
├─ train/
│ ├─ normal/ # 无残损(标签0)
│ │ ├─ img1.jpg
│ │ └─ ...
│ └─ damaged/ # 有残损(标签1)
│ ├─ img2.jpg
│ └─ ...
└─ val/ # 验证集,结构同上
代码:
from torch.utils.data import Dataset, DataLoader
import os
class ContainerBinaryDataset(Dataset):
def __init__(self, root_dir, transform=None):
self.root_dir = root_dir
self.transform = transform
# 加载图片路径和标签
self.image_paths = []
self.labels = []
# 无残损(标签0)
normal_dir = os.path.join(root_dir, "normal")
for img_name in os.listdir(normal_dir):
self.image_paths.append(os.path.join(normal_dir, img_name))
self.labels.append(0)
# 有残损(标签1)
damaged_dir = os.path.join(root_dir, "damaged")
for img_name in os.listdir(damaged_dir):
self.image_paths.append(os.path.join(damaged_dir, img_name))
self.labels.append(1)
def __len__(self):
return len(self.image_paths)
def __getitem__(self, idx):
# 读图片(OpenCV读的是BGR,转RGB)
img_path = self.image_paths[idx]
image = cv2.imread(img_path)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
label = self.labels[idx]
# 应用增强
if self.transform:
augmented = self.transform(image=image)
image = augmented["image"]
# 转成PyTorch的Tensor格式(通道在前:HWC→CHW)
image = image.transpose(2, 0, 1) # 原来的HWC(640,640,3)→ CHW(3,640,640)
return image, label
# 加载数据
train_dataset = ContainerBinaryDataset(
root_dir="dataset/train",
transform=train_transform # 刚才定义的训练增强
)
val_dataset = ContainerBinaryDataset(
root_dir="dataset/val",
transform=val_transform # 验证集只裁剪+resize+转Tensor,不随机增强
)
# 数据加载器(batch_size根据显卡调,比如8/16)
train_loader = DataLoader(train_dataset, batch_size=8, shuffle=True, num_workers=4)
val_loader = DataLoader(val_dataset, batch_size=8, shuffle=False, num_workers=4)
(2)定义模型(迁移学习)
import torch
import torch.nn as nn
from torchvision import models
class ContainerBinaryModel(nn.Module):
def __init__(self, pretrained=True):
super().__init__()
# 加载预训练的ResNet50
self.backbone = models.resnet50(pretrained=pretrained)
# 把最后一层全连接层改掉:原来ResNet50输出1000类,咱们输出2类(0/1)
in_features = self.backbone.fc.in_features
self.backbone.fc = nn.Linear(in_features, 2) # 2类:无残损/有残损
def forward(self, x):
return self.backbone(x)
# 初始化模型(用GPU训练,快很多)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = ContainerBinaryModel(pretrained=True).to(device)
(3)训练:重点解决“数据不平衡”
这里用“交叉熵损失+类别权重”,比如无残损样本有1000张,有残损有200张,权重就是1:5。代码里自动算权重:
from torch.optim import AdamW
from torch.optim.lr_scheduler import StepLR
import torch.nn.functional as F
# 1. 算类别权重(解决不平衡)
class_counts = [len(os.listdir("dataset/train/normal")), len(os.listdir("dataset/train/damaged"))]
class_weights = torch.FloatTensor([1/count for count in class_counts]).to(device)
class_weights = class_weights / class_weights.sum() * 2 # 归一化,让权重和为2
criterion = nn.CrossEntropyLoss(weight=class_weights)
# 2. 优化器和学习率调度(AdamW比Adam好,防过拟合)
optimizer = AdamW(model.parameters(), lr=1e-4, weight_decay=1e-5)
scheduler = StepLR(optimizer, step_size=5, gamma=0.5) # 每5轮学习率减半
# 3. 训练循环(简单版,实际要加早停防止过拟合)
num_epochs = 20
best_val_recall = 0.0 # 重点看召回率,别漏检
for epoch in range(num_epochs):
model.train()
train_loss = 0.0
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
# 前向传播
outputs = model(images)
loss = criterion(outputs, labels)
# 反向传播+优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_loss += loss.item() * images.size(0)
train_loss /= len(train_loader.dataset)
# 验证(算召回率、精确率、F1)
model.eval()
val_loss = 0.0
true_positive = 0 # 真阳性(有破损判对)
false_negative = 0 # 假阴性(有破损判错→漏检)
false_positive = 0 # 假阳性(无破损判错→误检)
with torch.no_grad():
for images, labels in val_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
val_loss += loss.item() * images.size(0)
# 算预测结果(取概率最大的类)
preds = torch.argmax(outputs, dim=1)
# 统计指标
true_positive += ((preds == 1) & (labels == 1)).sum().item()
false_negative += ((preds == 0) & (labels == 1)).sum().item()
false_positive += ((preds == 1) & (labels == 0)).sum().item()
val_loss /= len(val_loader.dataset)
# 算关键指标(召回率优先)
recall = true_positive / (true_positive + false_negative + 1e-6) # 加1e-6防除0
precision = true_positive / (true_positive + false_positive + 1e-6)
f1 = 2 * recall * precision / (recall + precision + 1e-6)
# 保存最好的模型(召回率最高的)
if recall > best_val_recall:
best_val_recall = recall
torch.save(model.state_dict(), "best_binary_model.pth")
print(f"Epoch {epoch}: 召回率提升到 {recall:.4f},保存模型!")
print(f"Epoch {epoch} | 训练损失: {train_loss:.4f} | 验证损失: {val_loss:.4f}")
print(f"召回率: {recall:.4f} | 精确率: {precision:.4f} | F1: {f1:.4f}")
scheduler.step() # 调整学习率
(4)评估:别只看准确率!
A题里无残损样本多,准确率会很高,但没意义。比如1000张无残损、200张有残损,模型全判“无残损”,准确率83%,但召回率0%(全漏了),这绝对不行。所以必须看召回率(越高越好,漏检少)、F1分数(平衡召回率和精确率)。
三、问题2:检测+分割(定位+类别)——A题最难,但有现成框架能用
这个任务要“画框标破损位置”+“说类别”+“像素级分割破损区域”,自己搭模型太费劲,直接用成熟的检测分割框架——Detectron2(Facebook出的,好用)或者MMDetection(国内团队出的,文档全)。这里用Detectron2,因为Mask R-CNN在上面实现得很成熟,能同时做检测和分割。
1. 核心思路:用Mask R-CNN解决“多任务”
Mask R-CNN本质是“Faster R-CNN+分割头”:先找破损的框(检测),再在框里做像素级分割(判断每个像素是不是破损)。针对A题的难点:
多尺度破损(大锈蚀、小裂纹):用FPN(特征金字塔网络),把不同尺度的特征融合,小目标用高层特征(看得远)+低层特征(细节多),大目标用高层特征。
类别不平衡(比如裂缝样本少):在训练时给少类别样本更高的“采样权重”,或者在损失函数里加权重。
背景干扰:之前预处理已经裁了集装箱,这里再在模型里加“上下文注意力”(Detectron2里有现成的配置),让模型更关注集装箱区域。
2. 数据准备:按Detectron2格式来
Detectron2需要数据集是COCO格式,或者自定义格式。咱们简单点,用“JSON标注文件”,每个样本标注:
图片路径;
破损的 bounding box(x1,y1,x2,y2,左上角和右下角坐标);
破损类别(比如0=裂纹,1=凹陷,2=穿孔,3=锈蚀);
分割掩码(用RLE格式,或者多边形坐标,标注工具推荐LabelMe,能自动生成)。
假设标注文件是train_annotations.json,格式大概这样:
{
"images": [
{"id": 1, "file_name": "train/img1.jpg", "height": 640, "width": 640},
...
],
"annotations": [
{
"id": 1,
"image_id": 1,
"category_id": 1, # 1=凹陷
"bbox": [100, 200, 50, 40], # x1,y1,w,h(注意Detectron2默认是x1,y1,x2,y2,要转格式)
"segmentation": [[100,200, 150,200, 150,240, 100,240]], # 多边形坐标(矩形为例)
"area": 2000 # bbox面积
},
...
],
"categories": [
{"id": 0, "name": "crack"}, # 裂纹
{"id": 1, "name": "dent"}, # 凹陷
{"id": 2, "name": "hole"}, # 穿孔
{"id": 3, "name": "rust"} # 锈蚀
]
}
3. 代码核心步骤(Detectron2)
(1)安装Detectron2
pip install detectron2 -f https://dl.fbaipublicfiles.com/detectron2/wheels/cu118/torch2.0/index.html
(根据自己的CUDA和PyTorch版本换链接,官网有教程)
(2)定义数据集和配置
from detectron2.config import get_cfg
from detectron2.data import DatasetCatalog, MetadataCatalog
from detectron2.data.datasets import register_coco_instances
from detectron2.engine import DefaultTrainer, DefaultPredictor
from detectron2.model_zoo import model_zoo
from detectron2.evaluation import COCOEvaluator, inference_on_dataset
from detectron2.data import build_detection_test_loader
# 1. 注册数据集(告诉Detectron2数据集在哪)
register_coco_instances(
"container_train", # 数据集名
{},
"train_annotations.json", # 标注文件路径
"dataset/train" # 图片文件夹路径
)
register_coco_instances(
"container_val",
{},
"val_annotations.json",
"dataset/val"
)
# 2. 获取元数据(类别信息)
container_metadata = MetadataCatalog.get("container_train")
num_classes = len(container_metadata.thing_classes) # 4类(裂纹、凹陷、穿孔、锈蚀)
# 3. 配置模型(用预训练的Mask R-CNN)
cfg = get_cfg()
# 加载预训练配置(选COCO数据集上预训练的Mask R-CNN,ResNet50-FPN backbone)
cfg.merge_from_file(model_zoo.get_config_file("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml"))
# 改数据集配置
cfg.DATASETS.TRAIN = ("container_train",)
cfg.DATASETS.TEST = ("container_val",)
cfg.DATALOADER.NUM_WORKERS = 4 # 线程数,根据CPU调
# 4. 改模型配置(重点)
cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml") # 预训练权重
cfg.MODEL.ROI_HEADS.NUM_CLASSES = num_classes # 4类,改这里!
cfg.MODEL.ROI_HEADS.BATCH_SIZE_PER_IMAGE = 128 # 每个图片的ROI数量,调大训练更准(别超显存)
cfg.MODEL.ROI_HEADS.POSITIVE_FRACTION = 0.3 # 正样本比例,不平衡时可以调高点
# 5. 训练配置(防过拟合+解决不平衡)
cfg.SOLVER.IMS_PER_BATCH = 4 # 每个batch的图片数,根据GPU显存调(比如12G显存设4)
cfg.SOLVER.BASE_LR = 1e-4 # 学习率,预训练模型微调用小学习率
cfg.SOLVER.MAX_ITER = 5000 # 训练迭代次数,根据数据量调(比如5000次够了)
cfg.SOLVER.STEPS = (3000, 4000) # 学习率衰减步骤
cfg.SOLVER.GAMMA = 0.5 # 学习率衰减系数
cfg.SOLVER.CLIP_GRADIENTS.ENABLED = True # 梯度裁剪,防梯度爆炸
# 6. 数据增强(Detectron2里的配置,和之前的预处理互补)
cfg.INPUT.RANDOM_FLIP = "horizontal" # 随机水平翻转
cfg.INPUT.MAX_SIZE_TRAIN = 640 # 训练图片最大尺寸
cfg.INPUT.MIN_SIZE_TRAIN = (512, 640, 768) # 多尺度训练,解决多尺度破损!
cfg.INPUT.MIN_SIZE_TRAIN_SAMPLING = "choice" # 随机选训练尺寸
# 7. 损失权重(解决类别不平衡,比如裂纹样本少,权重设高)
cfg.MODEL.ROI_HEADS.LOSS_WEIGHTS = {
"loss_cls": 1.0, # 分类损失权重
"loss_box_reg": 1.0, # 框回归损失权重
"loss_mask": 1.0 # 分割损失权重
}
# 如果裂纹样本少,比如裂纹占比10%,可以把分类损失里的裂纹权重设5:
# 需要自定义损失函数,这里简单点,先按默认来,后续再调
# 8. 输出路径(模型和日志保存在哪)
cfg.OUTPUT_DIR = "mask_rcnn_output"
os.makedirs(cfg.OUTPUT_DIR, exist_ok=True)
(3)训练模型
# 自定义Trainer(加评估)
class MyTrainer(DefaultTrainer):
@classmethod
def build_evaluator(cls, cfg, dataset_name, output_folder=None):
if output_folder is None:
output_folder = os.path.join(cfg.OUTPUT_DIR, "inference")
return COCOEvaluator(dataset_name, output_dir=output_folder)
# 开始训练
trainer = MyTrainer(cfg)
trainer.resume_or_load(resume=False) # 第一次训练设False
trainer.train()
(4)预测(测试集用)
训练完后,用最好的模型做预测,输出到test_result.csv:
# 加载最好的模型(训练时会自动保存best model)
cfg.MODEL.WEIGHTS = os.path.join(cfg.OUTPUT_DIR, "model_final.pth")
cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.5 # 置信度阈值,低于0.5的预测忽略(减少误检)
predictor = DefaultPredictor(cfg)
# 加载测试集图片(假设测试集在test_folder里)
test_folder = "test_images"
test_result = []
for img_name in os.listdir(test_folder):
img_path = os.path.join(test_folder, img_name)
image = cv2.imread(img_path)
# 预处理(和训练时一样:裁剪集装箱+resize)
image = crop_container(image)
image = cv2.resize(image, (640, 640))
# 预测
outputs = predictor(image)
# outputs里有:
# pred_boxes:预测的框(x1,y1,x2,y2)
# pred_classes:预测的类别(0=裂纹,1=凹陷...)
# pred_masks:预测的掩码(像素级)
# scores:置信度
# 整理结果
has_damage = len(outputs["instances"]) > 0 # 有没有残损
if has_damage:
# 取置信度最高的前4个(A题提示:验证集最多取4个严重破损)
instances = outputs["instances"].to("cpu")
sorted_indices = torch.argsort(instances.scores, descending=True)[:4]
for idx in sorted_indices:
box = instances.pred_boxes.tensor[idx].numpy() # x1,y1,x2,y2
cls = instances.pred_classes[idx].item()
cls_name = container_metadata.thing_classes[cls]
score = instances.scores[idx].item()
# 掩码转成字符串(方便存CSV,比如用RLE格式)
mask = instances.pred_masks[idx].numpy()
rle = mask_to_rle(mask) # 自己写个RLE编码函数
test_result.append({
"image_name": img_name,
"has_damage": 1,
"damage_class": cls_name,
"bbox_x1": box[0],
"bbox_y1": box[1],
"bbox_x2": box[2],
"bbox_y2": box[3],
"mask_rle": rle,
"confidence": score
})
else:
test_result.append({
"image_name": img_name,
"has_damage": 0,
"damage_class": "",
"bbox_x1": "",
"bbox_y1": "",
"bbox_x2": "",
"bbox_y2": "",
"mask_rle": "",
"confidence": ""
})
# 保存到CSV
import pandas as pd
df = pd.DataFrame(test_result)
df.to_csv("test_result.csv", index=False)
4. 关键优化点(解决A题难点)
小裂纹检测:调小cfg.MODEL.ANCHOR_GENERATOR.SIZES(锚点大小),比如默认是(32,64,128,256,512),加个16,让模型能捕捉更小的目标;
相似类别区分(比如深凹痕和破损):在训练集里给这些类别加更多标注,或者用“细分类损失”(比如Triplet Loss),让模型学出更细的特征;
分割精度:调大cfg.MODEL.ROI_MASK_HEAD.CONV_DIM(分割头的通道数),从256改成512,让分割更精细。
四、问题3:模型评估——对着A题需求“打分”
别瞎用指标,要紧扣A题的“业务场景”(港口安全、减少事故),每个任务的指标要对应需求:
1. 问题1(二分类)评估
核心指标:召回率(Recall) → 漏检率低,安全第一;
辅助指标:F1分数 → 平衡漏检和误检(误检多了会增加人工复核成本);
其他:ROC-AUC → 看模型整体区分能力。
计算方式就是训练时写的:召回率=真阳性/(真阳性+假阴性),F1=2*(召回率*精确率)/(召回率+精确率)。
2. 问题2(检测+分割)评估
检测部分:mAP(平均精度均值) → 框得准不准、类别判得对不对。用COCO标准的mAP@0.5(IoU≥0.5就算对)和mAP@0.5:0.95(不同IoU阈值下的平均,更严格);
分割部分:mIoU(平均交并比) → 分割得精不精确。IoU=(预测掩码∩真实掩码)/(预测掩码∪真实掩码),mIoU是所有类别的平均;
业务指标:小目标mAP → 专门看小裂纹的检测效果,避免模型只认大锈蚀。
用Detectron2的评估工具自动算:
# 评估检测和分割指标
evaluator = COCOEvaluator("container_val", cfg, False, output_dir=os.path.join(cfg.OUTPUT_DIR, "inference"))
val_loader = build_detection_test_loader(cfg, "container_val")
results = inference_on_dataset(trainer.model, val_loader, evaluator)
# 打印结果(会输出mAP、mIoU等)
print(results)
3. 错误分析(必做!)
A题要求“从不同维度评估”,光甩指标没用,得分析模型哪里不行:
漏检原因:是不是小裂纹没看见?是不是阴影里的破损没检测到?
误检原因:是不是把背景里的螺栓当成裂纹了?是不是污渍当成锈蚀了?
类别混淆:是不是把深凹痕当成破损了?拿出几个错误案例,截图分析,这样评估才完整。
五、最后给学生团队的建议
- 别从零造轮子:问题1用ResNet/EfficientNet,问题2用Mask R-CNN,用现成框架(PyTorch、Detectron2),省时间;
- 数据预处理别偷懒:背景不裁、干扰不除,模型再牛也白搭,这步至少占40%的工作量;
- 优先保证召回率:A题是安全相关,漏检比误检严重,训练时多关注召回率;
- 测试集预留接口:最后24小时给测试集,提前写好测试代码(加载图片→预处理→预测→存CSV),别临时慌。
这么一套下来,A题的三个任务就都覆盖了,思路、模型、代码都能落地,而且紧扣题目里的所有难点,不会跑偏。
更多推荐


所有评论(0)