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的基本原理和使用方法
  • 能够根据实际问题选择合适的检测方法

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

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

更多推荐