ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MATLAB纹理分类实战:GLCM与Gabor特征融合方法

MATLAB纹理分类实战:GLCM与Gabor特征融合方法 简介面向图像处理与计算机视觉学习者的MATLAB纹理分析小工具包聚焦纹理分类与图像纹理特征提取。基于统计、结构、光谱和模型等常见思路提供可直接运行的.m脚本用于计算灰度共生矩阵、局部二值模式、Gabor响应等特征并配合分类器完成纹理判别适合课程设计、算法复现或入门实践。包内含5个文件全部为MATLAB脚本压缩包仅4KB代码精炼、结构清晰便于快速阅读和修改。目前已有465人下载学习适合希望理解纹理分类相关算法实现细节的读者。通过阅读这些脚本可以直观掌握纹理特征提取的基本流程包括图像预处理、特征计算、结果可视化等模块同时为基于SVM或决策树的纹理分类提供特征输入。资源小巧但覆盖典型知识点能帮助学习者减少自写代码的弯路快速搭建自己的纹理分类实验框架。1. 纹理分类不是玄学是几组可复现的矩阵运算接触过图像检索或医学影像分析的人迟早会遇到纹理分类这个需求。它并不像目标检测那样需要大量标注框也不像语义分割那样依赖像素级掩码而是靠灰度分布、局部方向、频域响应这些底层统计量把图像区分开。MATLAB 里做这件事核心其实落在两个层面一是用graycomatrix、gabor、entropyfilt这类函数把纹理转成特征向量二是把特征交给 SVM 或决策树做分类决策。这个压缩包里的Texture.m、yin.m、wenli.m、wenli2.m走的正是这条路线isrgb.m则是老版本 MATLAB 里判断 RGB 图像的辅助函数。适合谁用正在做图像处理大作业、需要快速出分类效果的工程师以及想对比 GLCM 与 Gabor 特征差异的研究者。这里先给一个反直觉的结论纹理分类的精度瓶颈往往不在分类器而在特征怎么对齐、窗口怎么滑、方向怎么量化这也是本文后面每一步都要解释清楚的原因。2. 灰度共生矩阵纹理特征的“统计底座”2.1 共生矩阵在算什么像素对的联合概率灰度共生矩阵GLCM是纹理分类里最经典的统计特征它回答的问题很具体在某个方向、某个距离上灰度值为 i 的像素和灰度值为 j 的像素同时出现的概率有多大。比如wenli.m里如果对一幅灰度图调用graycomatrix默认会计算 0 度方向、距离 1 的共生关系输出一个 N×N 的矩阵N 是灰度级数。这个矩阵不是直接当特征用的而是从它上面再派生对比度、相关性、能量、熵这几个标量才是送到分类器的特征。MATLAB 里生成 GLCM 的入口很简单I imread(texture_sample.png); if size(I, 3) 3 I rgb2gray(I); end glcm graycomatrix(I, Offset, [0 1; -1 1; -1 0; -1 -1], NumLevels, 8, Symmetric, true); stats graycoprops(glcm, {Contrast, Correlation, Energy, Homogeneity});这段代码里Offset定义了四个方向水平、45 度、垂直、135 度。NumLevels设为 8 意味着把灰度缩放到 8 个级别级别越小计算越快但纹理细节会被磨掉。Symmetric为 true 时矩阵会同时统计 i-j 和 j-i 的共现保证结果与方向正反无关。graycoprops输出的四个统计量中Contrast 描述局部对比强烈程度Energy 反映灰度分布的均匀性Homogeneity 衡量纹理的局部平滑程度。输出stats是一个带字段的结构体数组每个元素对应一个方向的统计值。这里有一个容易被忽略的问题graycomatrix默认输入的图像必须是 2D 灰度图如果直接传 RGB 图会报错。压缩包里的isrgb.m就是为了做这个判断而存在的它的逻辑是检查第三维是否为 3同时验证数据范围。在新版本 MATLAB 中可以用更简洁的size(I, 3) 3替代但保留isrgb.m这种写法兼容旧代码也没有问题。2.2 多方向融合与特征拼接的实际写法单方向的 GLCM 特征只能捕捉一个方向上的纹理规律自然图像里的纹理往往没有绝对的方向性所以常见的做法是把四个方向的统计量合并组成一个特征向量。下面这段代码把四个方向的五个统计量Contrast、Correlation、Energy、Homogeneity加上自己加的 Entropy拼成一行特征offsets [0 1; -1 1; -1 0; -1 -1]; glcm graycomatrix(I, Offset, offsets, NumLevels, 16, Symmetric, true); stats graycoprops(glcm, {Contrast, Correlation, Energy, Homogeneity}); feature []; for k 1:size(offsets, 1) feature [feature, stats.Contrast(k), stats.Correlation(k), ... stats.Energy(k), stats.Homogeneity(k), entropy(glcm(:,:,k))]; end % feature 维度为 1x20graycoprops返回的stats.Contrast是一个长度为方向数的行向量所以循环里用k索引。entropy需要 Image Processing Toolbox它对每个方向的 GLCM 矩阵计算信息熵补充了graycoprops没有直接提供的随机性度量。拼完的特征可以直接喂给分类器但前提是每张图都要用相同的NumLevels和Offset否则特征维度对不上。像Texture.m这类主脚本通常会把上述特征提取封装成一个函数批量跑训练集和测试集。一个容易踩的坑是把graycoprops的参数名写错比如把Homogeneity写成Homogenity函数会直接报错。另一个坑是图像里有NaN或Inf导致 GLCM 统计结果异常。处理方式是先I(isnan(I)) 0再执行graycomatrix。2.3 从 GLCM 到分类不要把特征直接堆给 SVM拿到 20 维特征不代表可以直接训练。GLCM 特征之间的量纲差异很大比如 Contrast 可能是几百Energy 是零点几直接用 SVM 时核函数计算会被大数值特征主导。常见做法是zscore标准化或者用mapminmax。此外如果样本量只有几十张特征维度还继续加高很容易过拟合。一般建议先用pca降维到 510 维再看分类精度变化。wenli2.m这类脚本里如果有分类部分值得去看它是否做了标准化没做的话就自己补上。下面是一个配合 Classification Learner 使用的数据处理方式% 假设 features 是 N x 20 的矩阵labels 是 N x 1 的类别标签 features_norm zscore(features); [coeff, score, latent] pca(features_norm); features_pca score(:, 1:8); % 取前 8 个主成分 % 此时 features_pca 可以直接放入 fitcsvm 或 fitcecoc mdl fitcsvm(features_pca, labels, KernelFunction, rbf, Standardize, true); cvmdl crossval(mdl, KFold, 5); loss kfoldLoss(cvmdl); fprintf(5-fold CV loss: %.4f\n, loss);zscore按列做零均值单位方差标准化SVM 的 RBF 核对尺度敏感所以即使 PCA 之后Standardize设为 true 也是安全的。kfoldLoss输出的 loss 越小说明泛化能力越强。如果分类精度始终上不去回头看 GLCM 的NumLevels——8 和 64 两种设置提取出的特征往往差异非常大建议做一个简单的网格搜索。3. Gabor 滤波把纹理拆成频率和方向3.1 为什么 GLCM 不够用统计特征的盲区GLCM 本质上是二阶统计量它依赖的是像素对的灰度共现关系对周期性纹理比如布纹、栅格能给出不错的区分但对朝向敏感且频率相近的纹理往往力不从心。举个例子两块纹理的灰度共生矩阵统计值可能非常接近但其中一块有明显的 45 度条纹另一块是随机噪声GLCM 的对比度和能量差异很小。Gabor 滤波器能补上这个缺口它是带通滤波器只让某个频率范围、某个方向的纹理成分通过输出响应的均值、方差就构成了对应频率和方向上的纹理强度描述。MATLAB 里创建 Gabor 滤波器组的方式很直接g gabor([5 10 15], [0 45 90 135]); % g 是 3x4 的 gabor 对象数组每个对象包含波长和方向信息第一个参数是波长像素/周期单位是像素值越小对应的空间频率越高抓的是细纹理值越大抓的是粗纹理。第二个参数是方向单位是度。gabor函数会自动构建一组实部和虚部滤波器使用的时候需要把图像转成 single 或 double 类型。3.2 响应图到特征向量的转换流程Gabor 滤波的输出不是一张图而是与输入图像同尺寸的复数响应图。实际使用时对每个响应图计算局部能量幅值的平方的均值和标准差作为纹理特征。yin.m或wenli.m中如果出现imgaborfilt基本就是这个流程I im2double(imread(texture_sample.png)); if size(I, 3) 3 I rgb2gray(I); end g gabor([5 10 15], [0 45 90 135]); [mag, phase] imgaborfilt(I, g); feature []; for k 1:numel(g) energy mag(:,:,k).^2; feature [feature, mean(energy(:)), std(energy(:))]; end % 12 个滤波器 - 24 维特征imgaborfilt输出mag是振幅响应phase是相位响应通常只用mag提特征。对每个通道的振幅平方取均值和标准差等于描述该频率方向通道上的纹理强度与波动。如果纹理有方向性那么某个方向上的响应均值会显著高于其他方向这个差异就能作为分类依据。这里要注意Gabor 滤波器组的参数选择对结果影响很大波长范围应该覆盖目标纹理的周期范围方向要尽量均匀覆盖 0180 度因为 0 度和 180 度对实值图像而言是等价的。gabor默认方向范围是 0180所以传[0 45 90 135]是合理的。滤波后的响应图直接做特征输入维度是滤波器数量的两倍。12 个滤波器就是 24 维特征间存在较强的相关性用 PCA 或relieff做特征选择都会显著提升分类效率。另一种做法是把每个响应图分块统计直方图再接 SVM这种做法计算量大但能保留空间分布信息适合医学影像里组织纹理的精细分类。3.3 Gabor 与 GLCM 特征融合的维度控制实际项目里GLCM 和 Gabor 常常不是二选一而是拼接成组合特征。一个常用的策略是GLCM 提取 20 维统计量Gabor 提取 24 维能量统计拼接后先标准化再 PCA 降维到 1216 维最终交给分类器。这样既保留统计信息又补充频域方向信息分类精度往往比单独使用其中一种高 25 个百分点。glcm_feat extract_glcm_features(I); % 自定义函数返回 1x20 gabor_feat extract_gabor_features(I); % 自定义函数返回 1x24 combined [glcm_feat, gabor_feat]; % 标准化 PCA combined_norm zscore(combined); [~, score, ~] pca(combined_norm); final_feat score(:, 1:15);这里把特征提取封装成函数是工程上推荐的做法训练集和测试集各算各的特征但 PCA 的系数矩阵必须从训练集上拟合测试集要复用同一组系数投影。否则训练和测试特征分布不一致分类精度会虚高换到真实场景立刻露馅。Combine的维度控制要参考样本数经验上特征维度不超过样本数的五分之一否则 SVM 在小样本下很容易出现过拟合。4. 完整流程搭建从图像读取到分类器评估4.1 数据组织与训练集测试集划分纹理分类项目的常见数据组织方式是按文件夹划分类别每个文件夹放同一类纹理图像。使用imageDatastore可以一步到位它会递归扫描子文件夹自动用文件夹名作为标签省去手工维护标签向量的麻烦。下面这段代码展示如何搭建数据流imds imageDatastore(texture_dataset, IncludeSubfolders, true, LabelSource, foldernames); [trainImds, testImds] splitEachLabel(imds, 0.7, randomized);splitEachLabel第二个参数 0.7 表示每个类别取 70% 作为训练集剩余的作为测试集。randomized保证随机打乱防止同类图像连续排列影响交叉验证的公平性。trainImds和testImds是imageDatastore对象可以在后续循环中逐个读取图像。如果原始数据是 MATLAB 的.mat文件里面存的是裁剪好的图像块那就直接load后自己处理标签。本压缩包里的Texture.m如果直接读imread单张图那更适合做单图演示而不是批量训练批量场景建议改成上面这种imageDatastore方式。4.2 批量特征提取的循环结构批量提取特征时最忌讳的是每个样本都写一遍重复代码。正确做法是把特征提取逻辑写成一个函数然后对trainImds和testImds分别调用readimage提取。下面的代码示意核心循环numTrain numel(trainImds.Files); trainFeatures zeros(numTrain, 15); trainLabels trainImds.Labels; for i 1:numTrain I readimage(trainImds, i); if size(I, 3) 3 I rgb2gray(I); end feat extractCombinedFeatures(I); % 内部完成 GLCM Gabor PCA trainFeatures(i, :) feat; endextractCombinedFeatures是自定义函数内部做三件事GLCM 特征、Gabor 特征、拼接后 PCA 投影。注意 PCA 投影矩阵需要在训练特征上拟合一次然后把投影矩阵保存下来给测试集用。readimage每次读取一张图像优点是内存占用低缺点是重复解码速度慢图像总量小的话可以直接imds.readall()一次性读入再在内存里做批处理。4.3 SVM 与随机森林的参数选择特征准备好之后分类器可以用fitcecoc多分类 SVM或fitcensemble随机森林/提升树。纹理分类样本量通常不大SVM 的 RBF 核在特征维度 1020 时表现稳定随机森林则对特征缩放不敏感两者都值得试。下面给出 SVM 参数网格搜索的简写rng(42); svmModel fitcecoc(trainFeatures, trainLabels, ... Learners, templateSVM(KernelFunction, rbf, BoxConstraint, 1, KernelScale, auto), ... Coding, onevsone); predLabels predict(svmModel, testFeatures); acc sum(predLabels testLabels) / numel(testLabels); fprintf(Test accuracy: %.2f%%\n, acc * 100);templateSVM里的BoxConstraint控制误分类惩罚数值越大越容易过拟合KernelScale设auto让 MATLAB 根据训练数据自动估计核宽度。Coding的onevsone适合类别数较少的情况类别超过 10 类时可以考虑onevsall但训练时间会变长。测试精度不是唯一的评估指标如果类别不均衡输出混淆矩阵confusionchart才能看到哪些纹理互相混淆。4.4 常见失败模式与排错对照纹理分类项目跑不通九成问题出在数据预处理或特征提取阶段而不是分类器。这里列一组我实际排查过的现象和对应解法现象可能原因处理方式graycomatrix报错输入是 RGB 或 uint8 且超出范围先转灰度再确认灰度级是否在 0~255 之间Gabor 响应全黑图像未转 double 或滤波器波长远大于图像尺寸im2double减小波长参数训练精度 100% 但测试精度低特征维度太高或 PCA 拟合在全体数据上只在训练集上拟合 PCA测试集复用投影不同图像特征长度不一致某张图预处理后像素级数不同检查是否有imresize将图像统一尺寸SVM 训练极其缓慢特征未标准化核函数迭代不收敛zscore或Standardize设为 truewenli.m、wenli2.m、yin.m之间如果有功能重叠多半是不同版本的特征提取脚本调试时以主脚本Texture.m为入口逐个核对函数调用链。isrgb.m如果报错提示找不到说明当前 MATLAB 路径里少了这个文件把压缩包解压后的目录addpath进去就能解决。5. 进阶用纹理特征做滑动窗口分类与可视化5.1 基于块的滑动窗口纹理映射整张图直接提取一个特征向量只能判断整幅图属于哪一类。但很多实际场景比如遥感图像上的地表覆盖分类、工业产品表面的缺陷检测需要知道每个局部区域的纹理类别。这时用滑动窗口把图像切成重叠或不重叠的小块每块提取一组特征逐块分类最后把标签按空间位置拼回原图就得到一张纹理分类图。这个思路在Texture.m基础上扩展即可核心代码如下blockSize 32; stepSize 16; [rows, cols] size(I); labelMap zeros(floor((rows - blockSize) / stepSize) 1, ... floor((cols - blockSize) / stepSize) 1); idx_row 0; for r 1:stepSize:rows - blockSize 1 idx_row idx_row 1; idx_col 0; for c 1:stepSize:cols - blockSize 1 idx_col idx_col 1; block I(r:r blockSize - 1, c:c blockSize - 1); feat extractCombinedFeatures(block); labelMap(idx_row, idx_col) predict(svmModel, feat); end endblockSize决定局部纹理感知范围过大则分类图边缘模糊过小则每块内像素太少GLCM 统计不稳定。经验值是 32 或 64。stepSize控制重叠率小于blockSize时窗口重叠分类图更平滑但计算量增大。features特征的每一行对应一个块的分类结果空间位置关系由循环里的r、c索引对应。5.2 分类结果叠加显示得到labelMap后用imagesc配合jet或自定义颜色映射可以快速可视化每类纹理的空间分布。如果原始图像是灰度图可以做成半透明叠加figure; imshow(I, []); hold on; h imagesc(imresize(labelMap, [rows cols], nearest)); set(h, AlphaData, 0.4); colormap(jet(numel(classNames)));imresize把低分辨率的分类图放大到原图尺寸用nearest插值避免类别边界被平滑成不存在的中间值。AlphaData设为 0.4 让底图透出来便于同时观察纹理原貌和分类边界。可视化不只是为了演示它能快速暴露一个常见问题如果分类图边界正好落在图像边缘位置说明块尺寸和步长设置合理如果整个图呈现出马赛克状碎斑说明 GLCM 统计对块内细节过于敏感可以增大blockSize或对特征做平滑滤波。5.3 用混淆矩阵定位难分纹理对多类纹理分类里常有几类纹理在视觉上肉眼难分比如不同光滑程度的木纹或者不同织法的布料。混淆矩阵是最直接的诊断工具cm confusionchart(testLabels, predLabels); cm.RowSummary row-normalized;RowSummary设为row-normalized后每一行会额外显示真正例率和假正例率。如果两类纹理的混淆率超过 15%针对它们的特征差异做定向增强比如增加特定方向的 Gabor 滤波器或者降低 GLCM 的NumLevels以让灰度量化更粗放。这类迭代优化速度很快一次特征调整后重新训练分类器即可。对于一个 5 类纹理分类任务通常两三轮特征调参就能把总精度从 85% 拉到 93% 以上。本文还有配套的精品资源点击获取
返回列表