Python机器视觉第三阶段:缺陷检测算法(传统方法+深度学习入门)

在完成了OpenCV基础操作的学习后,我们正式进入缺陷检测算法的学习。这是机器视觉项目的核心,也是从“会用工具”到“解决实际问题”的关键一步。

本阶段内容分为两大块:

  1. 传统方法:阈值分割、差分、模板匹配、特征工程(LBP/HOG)
  2. 深度学习方法:分类网络(ResNet)、目标检测(YOLO)、分割网络(UNet)

本文所有代码都可以直接复制运行,注释非常详细。建议在学习过程中结合参考文档边学边练。


一、传统缺陷检测方法

传统方法不依赖深度学习,通过图像处理和特征工程实现缺陷检测,适合规则性强的场景(如印刷品检测、尺寸测量)。

1. 阈值分割(Thresholding)

阈值分割是图像分割中最基础的方法,通过设定像素值阈值将图像分为前景和背景。

1.1 全局阈值
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取灰度图
img = cv2.imread('defect_sample.jpg', cv2.IMREAD_GRAYSCALE)

# 全局阈值分割
ret, thresh_binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
ret, thresh_binary_inv = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
ret, thresh_trunc = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
ret, thresh_tozero = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)

# 显示结果
plt.figure(figsize=(15,10))
plt.subplot(2,3,1), plt.imshow(img, cmap='gray'), plt.title('Original')
plt.subplot(2,3,2), plt.imshow(thresh_binary, cmap='gray'), plt.title('Binary')
plt.subplot(2,3,3), plt.imshow(thresh_binary_inv, cmap='gray'), plt.title('Binary Inv')
plt.subplot(2,3,4), plt.imshow(thresh_trunc, cmap='gray'), plt.title('Trunc')
plt.subplot(2,3,5), plt.imshow(thresh_tozero, cmap='gray'), plt.title('Tozero')
plt.show()
1.2 自适应阈值(解决光照不均)
import cv2
import matplotlib.pyplot as plt

img = cv2.imread('uneven_light.jpg', cv2.IMREAD_GRAYSCALE)

# 自适应阈值
# ADAPTIVE_THRESH_MEAN_C:邻域均值减去C
# ADAPTIVE_THRESH_GAUSSIAN_C:邻域加权均值减去C
thresh_mean = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C,
                                   cv2.THRESH_BINARY, 11, 2)
thresh_gaussian = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                       cv2.THRESH_BINARY, 11, 2)

plt.figure(figsize=(12,4))
plt.subplot(1,3,1), plt.imshow(img, cmap='gray'), plt.title('Original')
plt.subplot(1,3,2), plt.imshow(thresh_mean, cmap='gray'), plt.title('Mean Adaptive')
plt.subplot(1,3,3), plt.imshow(thresh_gaussian, cmap='gray'), plt.title('Gaussian Adaptive')
plt.show()
1.3 Otsu大津法(自动确定最佳阈值)
import cv2
import matplotlib.pyplot as plt

img = cv2.imread('defect_sample.jpg', cv2.IMREAD_GRAYSCALE)

# Otsu自动计算阈值
ret, thresh_otsu = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print(f"Otsu自动计算的阈值: {ret}")

plt.figure(figsize=(12,4))
plt.subplot(1,2,1), plt.imshow(img, cmap='gray'), plt.title('Original')
plt.subplot(1,2,2), plt.imshow(thresh_otsu, cmap='gray'), plt.title(f'Otsu (thresh={ret:.1f})')
plt.show()

2. 图像差分(Image Difference)

差分法通过比较待测图像与标准模板图像的差异来检测缺陷,适用于固定场景的缺陷检测。

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取标准模板和待测图像
template = cv2.imread('template.jpg', cv2.IMREAD_GRAYSCALE)
test_img = cv2.imread('test_with_defect.jpg', cv2.IMREAD_GRAYSCALE)

# 确保图像大小一致
if template.shape != test_img.shape:
    test_img = cv2.resize(test_img, (template.shape[1], template.shape[0]))

# 计算绝对差分
diff = cv2.absdiff(template, test_img)

# 阈值化得到缺陷区域
_, diff_thresh = cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY)

# 形态学操作去除噪声
kernel = np.ones((3,3), np.uint8)
diff_clean = cv2.morphologyEx(diff_thresh, cv2.MORPH_OPEN, kernel, iterations=2)

# 显示结果
plt.figure(figsize=(15,5))
plt.subplot(1,4,1), plt.imshow(template, cmap='gray'), plt.title('Template')
plt.subplot(1,4,2), plt.imshow(test_img, cmap='gray'), plt.title('Test Image')
plt.subplot(1,4,3), plt.imshow(diff, cmap='gray'), plt.title('Difference')
plt.subplot(1,4,4), plt.imshow(diff_clean, cmap='gray'), plt.title('Defect Regions')
plt.show()

3. 模板匹配(Template Matching)

模板匹配在图像中搜索与模板最相似的区域,常用于定位已知图案或检测缺失/偏移。

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取原图和模板
img = cv2.imread('circuit_board.jpg')
template = cv2.imread('component_template.jpg', 0)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
w, h = template.shape[::-1]

# 六种匹配方法
methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR',
           'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']

plt.figure(figsize=(15,10))
for i, method_name in enumerate(methods):
    img_copy = img.copy()
    method = eval(method_name)
    
    # 模板匹配
    res = cv2.matchTemplate(gray, template, method)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
    
    # 根据方法确定最佳匹配位置
    if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
        top_left = min_loc
    else:
        top_left = max_loc
    bottom_right = (top_left[0] + w, top_left[1] + h)
    
    # 画矩形框
    cv2.rectangle(img_copy, top_left, bottom_right, (0,0,255), 2)
    
    plt.subplot(2,3,i+1)
    plt.imshow(cv2.cvtColor(img_copy, cv2.COLOR_BGR2RGB))
    plt.title(f'{method_name[7:]}')
    plt.axis('off')
plt.tight_layout()
plt.show()

4. 特征工程:LBP与HOG

4.1 LBP(局部二值模式)

LBP是一种纹理描述算子,对光照变化鲁棒,常用于纹理分类和缺陷检测。

import cv2
import numpy as np
import matplotlib.pyplot as plt
from skimage import feature

def compute_lbp(image, radius=1, n_points=8):
    """计算LBP特征图"""
    lbp = feature.local_binary_pattern(image, n_points, radius, method='uniform')
    return lbp

# 读取灰度图
img = cv2.imread('texture_sample.jpg', cv2.IMREAD_GRAYSCALE)

# 计算LBP
lbp_uniform = compute_lbp(img, radius=1, n_points=8)

plt.figure(figsize=(12,4))
plt.subplot(1,3,1), plt.imshow(img, cmap='gray'), plt.title('Original')
plt.subplot(1,3,2), plt.imshow(lbp_uniform, cmap='jet'), plt.title('LBP (Uniform)')
plt.subplot(1,3,3), plt.hist(lbp_uniform.ravel(), bins=20, range=(0, 10))
plt.title('LBP Histogram')
plt.show()
4.2 HOG(方向梯度直方图)

HOG通过计算图像局部区域的梯度方向直方图来描述物体形状,常用于行人检测、物体识别。

import cv2
import matplotlib.pyplot as plt
from skimage.feature import hog
from skimage import exposure

# 读取图像
img = cv2.imread('object_sample.jpg', cv2.IMREAD_GRAYSCALE)

# 计算HOG特征和可视化图像
features, hog_image = hog(img, orientations=9, pixels_per_cell=(8,8),
                          cells_per_block=(2,2), visualize=True, 
                          block_norm='L2-Hys')

# 增强HOG图像对比度
hog_image_rescaled = exposure.rescale_intensity(hog_image, in_range=(0, 10))

plt.figure(figsize=(15,5))
plt.subplot(1,3,1), plt.imshow(img, cmap='gray'), plt.title('Original')
plt.subplot(1,3,2), plt.imshow(hog_image_rescaled, cmap='gray'), plt.title('HOG Visualization')
plt.subplot(1,3,3), plt.plot(features[:500])  # 显示前500个特征
plt.title('HOG Features (first 500)')
plt.show()

print(f"HOG特征维度: {len(features)}")

5. 使用SVM分类缺陷(传统方法进阶)

结合HOG特征和SVM分类器,实现简单的缺陷分类。

import cv2
import numpy as np
from skimage.feature import hog
from sklearn import svm
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
import os

# 假设数据集结构:
# dataset/
#   normal/    # 正常样本
#   defect/    # 缺陷样本

def extract_hog_features(img_path):
    """提取单张图像的HOG特征"""
    img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
    img = cv2.resize(img, (128, 128))  # 统一大小
    features = hog(img, orientations=9, pixels_per_cell=(8,8),
                   cells_per_block=(2,2), block_norm='L2-Hys')
    return features

# 准备数据
def load_dataset(data_dir):
    features = []
    labels = []
    
    # 加载正常样本(标签0)
    normal_dir = os.path.join(data_dir, 'normal')
    for fname in os.listdir(normal_dir)[:50]:  # 取前50个样本
        if fname.endswith(('.jpg', '.png')):
            fpath = os.path.join(normal_dir, fname)
            features.append(extract_hog_features(fpath))
            labels.append(0)
    
    # 加载缺陷样本(标签1)
    defect_dir = os.path.join(data_dir, 'defect')
    for fname in os.listdir(defect_dir)[:50]:
        if fname.endswith(('.jpg', '.png')):
            fpath = os.path.join(defect_dir, fname)
            features.append(extract_hog_features(fpath))
            labels.append(1)
    
    return np.array(features), np.array(labels)

# 请替换为你的数据集路径
# X, y = load_dataset('./dataset')
# 这里用随机数据演示
X = np.random.randn(100, 1764)  # 模拟100个样本,每个1764维特征
y = np.random.randint(0, 2, 100)

# 划分训练测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 训练SVM分类器
clf = svm.SVC(kernel='rbf', C=1.0, gamma='scale')
clf.fit(X_train, y_train)

# 预测评估
y_pred = clf.predict(X_test)
print(classification_report(y_test, y_pred, target_names=['Normal', 'Defect']))

二、深度学习方法入门

传统方法对复杂缺陷(如形状变化、纹理异常)效果有限,深度学习方法通过自动学习特征,能处理更复杂的检测任务。

1. 分类网络:ResNet(判断图像是否包含缺陷)

import torch
import torch.nn as nn
import torchvision.transforms as transforms
from torchvision import models
from PIL import Image
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 加载预训练的ResNet18模型
model = models.resnet18(pretrained=True)

# 修改最后一层为二分类(正常/缺陷)
num_ftrs = model.fc.in_features
model.fc = nn.Linear(num_ftrs, 2)

# 设置为评估模式
model.eval()

# 如果支持GPU,将模型移到GPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = model.to(device)

# 图像预处理
transform = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], 
                         std=[0.229, 0.224, 0.225])
])

def predict_defect(image_path):
    """预测图像是否包含缺陷"""
    # 读取并预处理图像
    image = Image.open(image_path).convert('RGB')
    input_tensor = transform(image).unsqueeze(0).to(device)
    
    # 推理
    with torch.no_grad():
        outputs = model(input_tensor)
        _, predicted = torch.max(outputs, 1)
        probabilities = torch.nn.functional.softmax(outputs, dim=1)
    
    # 结果解释
    class_names = ['Normal', 'Defect']
    pred_class = class_names[predicted.item()]
    confidence = probabilities[0][predicted.item()].item()
    
    return pred_class, confidence

# 测试
result, conf = predict_defect('test_sample.jpg')
print(f"预测结果: {result}, 置信度: {conf:.4f}")

2. 目标检测:YOLOv8(定位缺陷位置)

YOLOv8是目前最流行的目标检测框架之一,可以同时检测多个缺陷并给出位置。

2.1 安装YOLOv8
pip install ultralytics
2.2 使用预训练模型进行推理
import cv2
from ultralytics import YOLO
import matplotlib.pyplot as plt

# 加载预训练模型(可选择yolov8n.pt, yolov8s.pt, yolov8m.pt等)
model = YOLO('yolov8n.pt')  # 先用COCO预训练模型演示

# 加载自定义训练的缺陷检测模型
# model = YOLO('best.pt')  # 训练好的缺陷检测模型

# 进行推理
results = model('test_image.jpg')

# 显示结果
img = cv2.imread('test_image.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 绘制检测结果
for r in results:
    boxes = r.boxes
    for box in boxes:
        x1, y1, x2, y2 = box.xyxy[0].tolist()
        conf = box.conf[0].item()
        cls = int(box.cls[0].item())
        
        # 画矩形框
        cv2.rectangle(img_rgb, (int(x1), int(y1)), (int(x2), int(y2)), (255,0,0), 2)
        cv2.putText(img_rgb, f'Defect {conf:.2f}', (int(x1), int(y1)-5),
                   cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,0,0), 2)

plt.figure(figsize=(12,8))
plt.imshow(img_rgb)
plt.axis('off')
plt.title('YOLOv8 Defect Detection')
plt.show()
2.3 训练自定义缺陷检测模型(步骤概述)
from ultralytics import YOLO

# 1. 准备数据集(YOLO格式)
#    - 每张图像对应一个txt标注文件
#    - 格式:class_id x_center y_center width height
#    - 所有坐标归一化到0-1

# 2. 创建数据集配置文件 defect.yaml
"""
train: ./dataset/images/train
val: ./dataset/images/val

nc: 3  # 缺陷类别数
names: ['scratch', 'dirt', 'crack']  # 类别名称
"""

# 3. 加载预训练模型并训练
model = YOLO('yolov8n.pt')  # 加载预训练权重
results = model.train(
    data='defect.yaml',
    epochs=50,
    imgsz=640,
    batch=16,
    name='defect_detection'
)

# 4. 验证模型
metrics = model.val()
print(f"mAP50: {metrics.box.map50}")

# 5. 导出模型
model.export(format='onnx')  # 导出为ONNX格式

3. 图像分割:UNet(像素级缺陷分割)

UNet擅长对每个像素进行分类,能精确分割出缺陷的形状。

import torch
import torch.nn as nn
import torchvision.transforms as transforms
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt

# 简单的UNet实现(简化版)
class SimpleUNet(nn.Module):
    def __init__(self, n_classes):
        super(SimpleUNet, self).__init__()
        
        # 编码器
        self.enc1 = self.conv_block(3, 64)
        self.enc2 = self.conv_block(64, 128)
        self.enc3 = self.conv_block(128, 256)
        self.enc4 = self.conv_block(256, 512)
        
        # 池化
        self.pool = nn.MaxPool2d(2)
        
        # 解码器
        self.up4 = self.up_conv(512, 256)
        self.dec4 = self.conv_block(512, 256)
        self.up3 = self.up_conv(256, 128)
        self.dec3 = self.conv_block(256, 128)
        self.up2 = self.up_conv(128, 64)
        self.dec2 = self.conv_block(128, 64)
        
        # 输出层
        self.out = nn.Conv2d(64, n_classes, kernel_size=1)
    
    def conv_block(self, in_ch, out_ch):
        return nn.Sequential(
            nn.Conv2d(in_ch, out_ch, 3, padding=1),
            nn.BatchNorm2d(out_ch),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_ch, out_ch, 3, padding=1),
            nn.BatchNorm2d(out_ch),
            nn.ReLU(inplace=True)
        )
    
    def up_conv(self, in_ch, out_ch):
        return nn.ConvTranspose2d(in_ch, out_ch, 2, stride=2)
    
    def forward(self, x):
        # 编码
        e1 = self.enc1(x)
        e2 = self.enc2(self.pool(e1))
        e3 = self.enc3(self.pool(e2))
        e4 = self.enc4(self.pool(e3))
        
        # 解码
        d4 = self.up4(e4)
        d4 = torch.cat([d4, e3], dim=1)
        d4 = self.dec4(d4)
        
        d3 = self.up3(d4)
        d3 = torch.cat([d3, e2], dim=1)
        d3 = self.dec3(d3)
        
        d2 = self.up2(d3)
        d2 = torch.cat([d2, e1], dim=1)
        d2 = self.dec2(d2)
        
        out = self.out(d2)
        return out

# 使用预训练模型进行推理
def unet_inference(image_path, model_path, device='cpu'):
    # 加载模型
    model = SimpleUNet(n_classes=2)  # 2类:背景和缺陷
    model.load_state_dict(torch.load(model_path, map_location=device))
    model.eval()
    model.to(device)
    
    # 预处理图像
    transform = transforms.Compose([
        transforms.Resize((256, 256)),
        transforms.ToTensor(),
    ])
    
    img = Image.open(image_path).convert('RGB')
    input_tensor = transform(img).unsqueeze(0).to(device)
    
    # 推理
    with torch.no_grad():
        output = model(input_tensor)
        pred = torch.argmax(output, dim=1).squeeze(0).cpu().numpy()
    
    # 可视化结果
    fig, axes = plt.subplots(1, 2, figsize=(12,5))
    axes[0].imshow(img)
    axes[0].set_title('Original Image')
    axes[1].imshow(pred, cmap='hot')
    axes[1].set_title('Defect Segmentation')
    plt.show()
    
    return pred

三、传统方法与深度学习对比总结

维度 传统方法 深度学习方法
数据需求 少量样本,依赖人工特征设计 大量标注数据,自动学习特征
计算资源 CPU即可,速度快 需要GPU,训练时间长
可解释性 好,每一步可解释 差,黑盒模型
泛化能力 对场景变化敏感 对光照、角度变化鲁棒性更好
缺陷类型 规则缺陷(尺寸、位置固定) 复杂缺陷(纹理异常、形状变化)
开发周期 短,快速验证 长,需数据准备和训练

实际项目建议

  1. 先从传统方法快速验证可行性
  2. 若传统方法效果不达标,再引入深度学习
  3. 可结合两者:传统方法粗筛ROI + 深度学习精细分类

📚 第三阶段参考文档链接

传统方法文档

特征工程文档

深度学习文档

综合实战案例


四、下一步学习建议

完成本阶段学习后,你应该能够:

  • 熟练使用阈值分割、差分、模板匹配等传统方法检测规则缺陷
  • 理解LBP、HOG等特征提取原理并能应用
  • 了解ResNet、YOLO、UNet的基本原理和使用方法
  • 能够根据实际问题选择合适的检测方法

下一步:进入第四阶段——综合项目实战,将所学知识整合成一个完整的缺陷检测系统。

如果你在学习过程中有任何问题,欢迎在评论区留言。记得多动手练习,把代码自己敲一遍,才能真正掌握!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐