
有监督+无监督双引擎驱动,误漏判双降,年省成本超400万

大家好,我是AI小怪兽。
深耕计算机视觉与深度学习领域多年,我始终专注于视觉检测前沿技术的探索与突破,长期致力于YOLO系列算法的结构性创新、性能极限优化与工业级落地实践。今天想和大家分享一个我主导的工业缺陷检测项目,聊聊我们如何打通从学术研究到产业应用的“最后一公里”。
本文伪代码如下:
"""
工业缺陷检测双引擎方案
作者:AI小怪兽
技术栈:YOLOv8 (有监督) + PatchCore (无监督) + 模拟光学成像数据增强
功能:1. 仅用良品图像训练无监督模型(快速冷启动)
2. 利用模拟光学成像增强少量缺陷样本,训练YOLO模型
3. 双引擎协同推理,输出最终检测结果
依赖:pip install ultralytics albumentations anomalib opencv-python torch
"""
import cv2
import numpy as np
import albumentations as A
from albumentations.pytorch import ToTensorV2
import torch
from ultralytics import YOLO
from anomalib.models import Patchcore
from anomalib.data import Folder
from anomalib.engine import Engine
from pathlib import Path
# ======================== 配置参数 ========================
GOOD_IMG_DIR = "./datasets/good" # 良品图像目录
DEFECT_DIR = "./datasets/defect" # 缺陷图像目录(按类别分文件夹,如 defect/scratch/)
YOLO_DATA_YAML = "dataset.yaml" # YOLO数据集配置文件
PATCHCORE_CKPT = "patchcore_model.ckpt" # 无监督模型保存路径
YOLO_MODEL_PATH = "yolov8n_custom.pt" # 训练后的YOLO模型路径
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
IMG_SIZE = 640
# ======================== 1. 模拟光学成像数据增强 ========================
def get_optical_augmentation():
"""返回模拟光学成像的增强流水线(仅训练时使用)"""
return A.Compose([
# 光照变化
A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.5),
A.HueSaturationValue(hue_shift_limit=10, sat_shift_limit=20, val_shift_limit=10, p=0.5),
# 镜头畸变(透视变换)
A.Perspective(scale=(0.05, 0.1), p=0.3),
# 材质反光(模拟高光)
A.RandomSunFlare(flare_roi=(0, 0, 1, 0.5), src_radius=100, p=0.3),
# 成像噪声
A.GaussNoise(var_limit=(10.0, 50.0), p=0.4),
A.MotionBlur(blur_limit=5, p=0.3),
# 保证输出尺寸一致
A.Resize(IMG_SIZE, IMG_SIZE),
])
# ======================== 2. 无监督异常检测模型训练(PatchCore) ========================
def train_unsupervised():
"""仅使用良品图像训练无监督模型,实现零缺陷样本冷启动"""
print(">> 开始训练无监督模型(PatchCore)...")
# 数据模块:只读取良品图像
datamodule = Folder(
name="industrial_good",
root="./datasets",
normal_dir="good",
task="segmentation", # 输出像素级异常图
image_size=(IMG_SIZE, IMG_SIZE),
)
model = Patchcore(
backbone="resnet50",
pre_trained=True,
coreset_sampling_ratio=0.1,
)
engine = Engine(device=DEVICE)
engine.fit(datamodule=datamodule, model=model)
engine.save_checkpoint(PATCHCORE_CKPT)
print(f"无监督模型已保存至 {PATCHCORE_CKPT}")
# ======================== 3. YOLOv8 训练(自定义数据集,嵌入模拟光学成像增强) ========================
# 3.1 自定义YOLO数据集类,覆盖数据增强方法
from ultralytics.yolo.data.dataset import YOLODataset
class OpticalAugYOLODataset(YOLODataset):
"""在YOLO数据集基础上增加模拟光学成像增强"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.optical_aug = get_optical_augmentation()
def load_image(self, i, rect_mode=True):
"""加载图像并应用模拟光学成像增强(仅训练阶段)"""
img, (h0, w0), (h, w) = super().load_image(i, rect_mode)
if self.augment:
# 应用我们的光学模拟增强
augmented = self.optical_aug(image=img)
img = augmented["image"]
return img, (h0, w0), (h, w)
def train_yolo():
"""使用少量缺陷样本 + 模拟光学成像增强训练YOLOv8"""
print(">> 开始训练YOLOv8(有监督)...")
# 替换默认的数据集类
from ultralytics.yolo.data import build_dataset
build_dataset.YOLODataset = OpticalAugYOLODataset
model = YOLO("yolov8n.pt")
model.train(
data=YOLO_DATA_YAML,
epochs=100,
imgsz=IMG_SIZE,
batch=16,
device=DEVICE,
augment=True, # 启用增强(会调用我们的custom dataset)
patience=10,
save=True,
project="runs/train",
name="yolo_optical_aug",
)
# 保存最终模型
model.save(YOLO_MODEL_PATH)
print(f"YOLO模型已保存至 {YOLO_MODEL_PATH}")
# ======================== 4. 双引擎协同推理 ========================
class DualEngineInference:
"""加载两个模型,协同进行缺陷检测"""
def __init__(self, yolo_path, patchcore_ckpt):
self.device = DEVICE
self.yolo = YOLO(yolo_path)
# 加载无监督模型
self.anomaly_model = Patchcore.load_from_checkpoint(patchcore_ckpt)
self.anomaly_model.eval()
self.anomaly_model.to(self.device)
def predict(self, image_path):
"""对单张图像进行检测,返回 (缺陷类别, 置信度, 异常区域mask)"""
img = cv2.imread(image_path)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
orig_h, orig_w = img.shape[:2]
# 步骤1:YOLO检测已知缺陷
yolo_results = self.yolo(img_rgb, imgsz=IMG_SIZE, conf=0.25, device=self.device)[0]
yolo_defects = [] # 存储 (bbox, cls_name, conf)
for box in yolo_results.boxes:
cls_id = int(box.cls[0])
conf = float(box.conf[0])
cls_name = self.yolo.names[cls_id]
bbox = box.xyxy[0].cpu().numpy().astype(int)
yolo_defects.append((bbox, cls_name, conf))
# 步骤2:无监督异常检测(像素级)
# 需要将图像调整为模型输入尺寸
input_tensor = self._preprocess_for_anomaly(img_rgb)
with torch.no_grad():
predictions = self.anomaly_model(input_tensor)
anomaly_map = predictions["anomaly_map"].squeeze().cpu().numpy() # (H, W)
# 阈值判断是否有未知异常(可根据实际调整)
unknown_anomaly = np.any(anomaly_map > 0.5) # 0.5为经验阈值
if unknown_anomaly:
# 生成二值mask用于定位
mask = (anomaly_map > 0.5).astype(np.uint8) * 255
# 可以再根据mask寻找轮廓等,这里简化为整体标记
unknown_defect = ("unknown", 1.0, mask)
else:
unknown_defect = None
return {
"yolo_defects": yolo_defects,
"unknown_defect": unknown_defect,
"anomaly_map": anomaly_map
}
def _preprocess_for_anomaly(self, img_rgb):
"""将图像转换为模型输入格式(归一化、tensor等)"""
# 假设无监督模型输入尺寸与训练时一致(这里是IMG_SIZE)
img = cv2.resize(img_rgb, (IMG_SIZE, IMG_SIZE))
img = img.astype(np.float32) / 255.0
img = torch.from_numpy(img).permute(2, 0, 1).unsqueeze(0)
return img.to(self.device)
# ======================== 5. 主流程示例 ========================
if __name__ == "__main__":
# 第一步:训练无监督模型(仅需良品图像,若已存在可跳过)
# train_unsupervised()
# 第二步:训练YOLO模型(需要少量缺陷样本+标注)
# train_yolo()
# 第三步:加载双引擎进行推理演示
detector = DualEngineInference(YOLO_MODEL_PATH, PATCHCORE_CKPT)
test_image = "./test_images/sample.jpg"
result = detector.predict(test_image)
print("YOLO检测到的已知缺陷:")
for bbox, cls, conf in result["yolo_defects"]:
print(f" - {cls}: confidence={conf:.2f}, bbox={bbox}")
if result["unknown_defect"]:
cls, conf, mask = result["unknown_defect"]
print(f"无监督模型发现未知缺陷: {cls}, confidence={conf:.2f}, mask区域已定位")
else:
print("无监督模型未发现异常")
# ======================== 说明 ========================
"""
关键点:
1. 模拟光学成像增强:通过albumentations模拟光照、畸变、反光、噪声,使YOLO模型对“拍不全”问题鲁棒。
2. 无监督模型仅用良品图像训练,快速上线,自动积累未知缺陷样本。
3. 双引擎协同:YOLO负责已知缺陷分类,无监督负责兜底未知缺陷,形成闭环。
4. 通过自定义YOLO数据集类,完美嵌入光学增强,无需修改ultralytics内核。
"""在深入项目之前,先说说工业缺陷检测普遍面临的四个核心痛点:

产线良品率往往在99%以上,缺陷样本可遇不可求。以某电子元器件产线为例,每天产出10万件产品,真正有缺陷的可能不到50件。有些缺陷几个月才出现一次,等收集到足够样本,黄花菜都凉了。这种样本稀缺性,是工业缺陷检测面临的首要挑战。
即便收集到了一些缺陷样本,各类缺陷的数量也极不均衡:
传统分类模型在这种极度不平衡的数据上训练,往往会“无视”样本量少的缺陷类别,导致漏检率居高不下。
工业相机拍摄时,受限于光照条件、拍摄角度、产品摆放位置等因素,同一个缺陷在不同图像中可能呈现完全不同的形态。金属表面的高反光、镜头畸变、运动模糊……这些光学干扰让缺陷“拍不全”甚至“拍不到”,模型难以学到稳定的特征。
这是很多企业容易忽视但实际非常痛的一点。传统AI质检项目从启动到上线,往往需要经历:缺陷样本收集(数月)→ 数据标注(数周)→ 模型训练调优(数周)→ 现场调试(数周)→ 产线验证(数周)。一套流程走下来,少则3-6个月,多则一年以上。等到模型上线,产品可能已经迭代,产线工艺已经变化,之前的缺陷样本又“过期”了。这种长周期严重制约了企业的敏捷响应能力。
针对这四大痛点,我们设计了一套“有监督+无监督”的双引擎方案,核心思路是:用无监督快速冷启动,用有监督持续精进,将导入周期从数月压缩至数周。
有监督部分我们采用YOLO系列算法,但关键在于数据增强的创新和模型结构的深度优化。
在本次项目中,我们重点应用了以下策略:
模拟光学成像数据增强——传统数据增强只是简单旋转、缩放,我们则引入了更贴近工业场景的增强策略:
这套策略让有限的小样本缺陷,能够“生成”出多种光学条件下的变体,模型学到的特征更加鲁棒,大幅提升对“拍不全”问题的适应能力。
同时配合迁移学习和Focal Loss,进一步缓解样本失衡问题,让模型更加关注难分类的缺陷样本。
有监督能解决已知缺陷,但未知缺陷才是真正的“暗雷”。更重要的是,无监督检测成为缩短导入周期的关键突破口——因为它仅需良品图像训练,无需等待缺陷样本积累。
我们引入两个前沿的无监督异常检测算法:
核心优势:
实际部署中,两套引擎各司其职,更重要的是我们重构了项目导入流程:
传统模式(3-6个月): 收集缺陷样本 → 标注 → 训练 → 调优 → 上线
双引擎模式(4-6周):
这套流程的核心优势在于:无监督模型让系统在“零缺陷样本”的情况下就能上线运行,边生产边学习,彻底打破了“先收集缺陷再上线”的传统路径依赖。
在具体运行时:
这套方案既保证了常见缺陷的精准识别,又弥补了对未知缺陷的漏检风险,真正实现了“已知缺陷分得清,未知缺陷抓得住,项目导入跑得快”。
某精密制造企业生产笔记本电脑金属外壳,需检测划痕、变形、凹陷等细微缺陷。传统人工质检痛点突出:
第一阶段:成像系统设计(1周) 设计偏振光调控+多光谱融合方案,彻底消除金属反光干扰,确保缺陷“拍得全、拍得清”。
第二阶段:无监督快速冷启动(1周) 采集10万+良品图像,训练HiAD、BGAD双无监督检测体系。仅用1周时间,系统即上线试运行,开始辅助人工质检并积累缺陷样本。
第三阶段:有监督同步训练(第2-4周) 无监督系统运行过程中,持续收集未知缺陷样本,经快速标注后训练YOLO模型。采用模拟光学成像策略扩充有限缺陷样本,专栏中分享的YOLO优化技巧(包括模块改进、损失函数调优等),训练出高精度的YOLO模型。
第四阶段:双引擎协同上线(第5周) 双模型并行部署,设置人机协同机制:高置信度结果自动判定,低置信度及异常报警送人工复核。从项目启动到双引擎正式上线,仅用5周时间,较传统模式缩短80%。
核心指标对比:
指标 | 优化前(人工) | 优化后(AI双引擎) | 提升幅度 |
|---|---|---|---|
缺陷检出率 | 92.5% | 99.7% | ↑7.2% |
漏检率 | 7.5% | 0.3% | ↓96% |
误判率(过杀) | 5.8% | 1.2% | ↓79% |
单台设备日均检测量 | 2,000件 | 12,000件 | ↑5倍 |
质检人力 | 36人/天 | 6人/天 | 减少83% |
项目导入周期 | 4个月 | 5周 | 缩短80% |
经济收益:
业务价值:
工业缺陷检测的本质是“长尾问题”——20%的常见缺陷加上80%的偶发未知缺陷。单纯依赖有监督学习,永远无法穷尽所有缺陷类型;单纯依赖无监督学习,又难以对已知缺陷精准分类。而传统项目导入的长周期,又让许多企业对AI质检望而却步。
“有监督+无监督”的组合方案,正是破解这一系列困局的关键。 它既保持了YOLO对已知缺陷的精准识别能力,又借助HiAD、BGAD等先进算法构建起未知缺陷的“安全网”,更通过无监督快速冷启动将导入周期从数月压缩至数周,形成一个可持续进化的智能质检系统。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。