4、经典缺陷特征工程:纹理特征提取(LBP/GLCM)、形状特征描述(Hu矩/面积/周长)、颜色特征分析
好,咱们进入正题。缺陷检测里,特征工程是承上启下的关键一步。你想想看,图像预处理做完了,模型要学什么?总不能把几百万个像素直接扔给分类器吧?那太傻了。我们需要把图像里的信息,提炼成几个有意义的数字——这就是特征。
我个人习惯把特征分成三大类:纹理、形状、颜色。这三板斧用好了,大部分缺陷都能搞定。今天我就把压箱底的经验掏出来,跟你聊聊怎么用。
4.1 纹理特征提取:LBP与GLCM
纹理是什么?说白了就是图像局部的粗糙度、方向性、规律性。比如布料的编织纹路、金属表面的拉丝、木材的年轮。缺陷往往破坏了这种规律,所以纹理特征特别管用。
4.1.1 LBP(局部二值模式)
LBP是我个人最常用的纹理特征之一。它的核心思想很简单:比较中心像素和周围邻居的大小关系。
基本原理:
- 取一个3x3的窗口
- 中心像素作为阈值
- 周围8个像素跟它比,大于等于的标1,小于的标0
- 得到一个8位的二进制数,转成十进制就是LBP值
嗯,这里要注意:原始的LBP对旋转很敏感。我在项目中遇到过,同一个纹理旋转了45度,LBP值就完全不一样了。所以后来我基本都用旋转不变LBP和均匀模式LBP。
import cv2
import numpy as np
from skimage.feature import local_binary_pattern
# 读取灰度图
img = cv2.imread('fabric_defect.jpg', 0)
# 计算LBP特征
# radius=1, n_points=8, method='uniform'
radius = 1
n_points = 8 * radius
lbp = local_binary_pattern(img, n_points, radius, method='uniform')
# 统计直方图作为特征向量
hist, _ = np.histogram(lbp.ravel(), bins=np.arange(0, 59), range=(0, 58))
hist = hist.astype('float32')
hist /= (hist.sum() + 1e-6) # 归一化
print(f'LBP特征向量维度: {len(hist)}') # 输出: 59维
4.1.2 GLCM(灰度共生矩阵)
GLCM是另一种经典纹理特征。它统计的是图像中特定距离和方向上,两个像素灰度值同时出现的概率。
我曾经在金属表面划痕检测中吃过亏。一开始用LBP,划痕和背景的纹理差异不大,效果一般。后来换成GLCM,提取了对比度和相关性特征,划痕一下就凸显出来了。
常用的GLCM统计量:
| 特征名 | 物理含义 | 缺陷场景举例 |
|---|---|---|
| 对比度(Contrast) | 纹理的清晰度和沟纹深浅 | 划痕越深,对比度越大 |
| 相关性(Correlation) | 纹理的方向一致性 | 拉丝纹理被破坏,相关性下降 |
| 能量(Energy) | 纹理的均匀性和规则性 | 污渍区域能量值偏低 |
| 同质性(Homogeneity) | 纹理局部变化的剧烈程度 | 气泡边缘同质性低 |
from skimage.feature import graycomatrix, graycoprops
# 先量化灰度级,一般用16或32级
img_quant = (img / 16).astype(np.uint8)
# 计算GLCM:距离=1,方向=0°, 45°, 90°, 135°
glcm = graycomatrix(img_quant, distances=[1],
angles=[0, np.pi/4, np.pi/2, 3*np.pi/4],
levels=16, symmetric=True, normed=True)
# 提取特征
contrast = graycoprops(glcm, 'contrast').ravel()
correlation = graycoprops(glcm, 'correlation').ravel()
energy = graycoprops(glcm, 'energy').ravel()
homogeneity = graycoprops(glcm, 'homogeneity').ravel()
# 拼接成特征向量
features = np.hstack([contrast, correlation, energy, homogeneity])
print(f'GLCM特征向量维度: {len(features)}') # 输出: 16维
4.2 形状特征描述:Hu矩、面积、周长
形状特征主要用来描述缺陷的几何属性。比如这个缺陷是圆的还是长的?面积有多大?边界有多复杂?
4.2.1 面积与周长
这两个是最基础的特征,但千万别小看它们。我做过一个PCB焊点检测项目,虚焊和正常焊点的纹理、颜色几乎一样,唯一的区别就是面积——虚焊的焊点面积小了15%。
- 面积:缺陷区域的像素总数。注意要乘以像素当量(mm/像素)才能得到真实物理面积。
- 周长:缺陷边界的像素长度。对于不规则形状,可以用链码法精确计算。
- 圆形度:4π × 面积 / 周长²。值越接近1,形状越圆。气泡的圆形度通常在0.8以上,划痕则在0.3以下。
4.2.2 Hu矩
Hu矩是形状特征的「老江湖」了。它有7个不变矩,对平移、旋转、缩放都不敏感。说白了,同一个缺陷不管怎么转、怎么放大缩小,Hu矩的值基本不变。
我记得有一次做瓶盖缺陷检测,瓶盖在传送带上的位置和角度每次都不同。用面积和周长根本没法统一阈值,但用了Hu矩之后,不同位置的同一类缺陷特征值非常稳定。
import cv2
# 假设binary是二值图像,缺陷区域为白色(255)
binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)[1]
# 计算轮廓
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
# 面积
area = cv2.contourArea(cnt)
# 周长
perimeter = cv2.arcLength(cnt, True)
# Hu矩
moments = cv2.moments(cnt)
hu_moments = cv2.HuMoments(moments)
# 注意:Hu矩的值范围很大,要做对数变换
hu_moments = -np.sign(hu_moments) * np.log10(np.abs(hu_moments) + 1e-10)
print(f'面积: {area:.2f}, 周长: {perimeter:.2f}')
print(f'Hu矩: {hu_moments.flatten()}')
4.3 颜色特征分析
颜色特征在缺陷检测中经常被忽视,但其实非常有用。尤其是对于色差、污渍、氧化、发黄这类缺陷,纹理和形状可能变化不大,但颜色信息一抓一个准。
4.3.1 颜色空间的选择
我建议不要直接用RGB。RGB的三个通道相关性太强,光照一变,三个值一起变,很不稳定。
我个人最常用的是HSV和Lab颜色空间:
- HSV:H(色调)对光照不敏感,适合检测色相变化。比如塑料件发黄,H值会偏移。
- Lab:L(亮度)和a、b(颜色对立通道)分离得更好。a通道对红绿色差敏感,b通道对黄蓝色差敏感。
4.3.2 颜色特征提取方法
常用的颜色特征有三种:
- 颜色直方图:统计每个通道的像素分布。简单但有效。注意要归一化,消除面积影响。
- 颜色矩:用均值、标准差、偏度来描述颜色分布。计算量小,适合实时系统。
- 颜色连通域:提取特定颜色范围的区域,再结合形状特征。比如检测红色污渍,先提取H通道在0-10°和160-180°的区域。
import cv2
import numpy as np
# 转换到HSV空间
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 提取H通道直方图
hist_h = cv2.calcHist([hsv], [0], None, [180], [0, 180])
hist_h = cv2.normalize(hist_h, hist_h).flatten()
# 颜色矩:一阶矩(均值)、二阶矩(标准差)、三阶矩(偏度)
mean, stddev = cv2.meanStdDev(hsv)
mean = mean.flatten()
stddev = stddev.flatten()
# 三阶矩需要手动计算
skewness = []
for i in range(3):
channel = hsv[:,:,i].astype(np.float32)
skew = np.mean(((channel - mean[i]) / (stddev[i] + 1e-6)) ** 3)
skewness.append(skew)
color_features = np.hstack([mean, stddev, skewness])
print(f'颜色特征向量维度: {len(color_features)}') # 输出: 9维
4.4 特征融合与实战建议
好了,三种特征都讲完了。你可能会问:到底用哪种?
我的建议是:别单选,要融合。单一特征总有盲区。纹理特征对划痕敏感,但对色差不敏感;颜色特征对污渍敏感,但对形状不敏感。把它们组合起来,才能覆盖更多缺陷类型。
我常用的融合策略:
- 串联融合:直接把LBP直方图、GLCM统计量、Hu矩、颜色矩拼成一个长向量。简单粗暴,适合传统机器学习(SVM、随机森林)。
- 并行融合:每种特征单独训练一个分类器,最后投票决定。适合特征差异很大的场景。
- 降维融合:先用PCA或LDA降维,再融合。我一般保留90%的方差信息,能有效避免维度灾难。
嗯,特征工程这部分就聊到这儿。下一章我们聊聊怎么用这些特征训练分类器,以及如何评估模型效果。到时候我会分享一个我踩过的坑——特征归一化没做好,模型直接废了。敬请期待。