
简介面向图像处理初学者与MATLAB使用者的纹理分类算法示例包围绕图像纹理特征提取与分类任务提供基于统计特征、局部二值模式LBP及滤波响应等常见思路的代码实现。资源共5个文件全部为MATLAB脚本.m总大小仅4KB便于直接阅读与修改适合作为课程设计或算法验证的轻量参考。已有465人学习下载。通过Texture.m、wenli.m、wenli2.m等脚本可快速了解从图像读取、灰度判断isrgb.m到纹理特征计算与分类演示的完整流程yin.m则补充了特定纹理分析场景下的处理逻辑。代码结构简洁注释清晰能帮助使用者理解不同纹理描述子的构造方式并在此基础上扩展到SVM等分类器设计。1. 图像纹理提取与纹理分类先把纹理变成向量再谈识别手机相册里的纹理滤镜本质是一层视觉风格转移而标题里的图像纹理提取与纹理分类要解决的是另一件事把纹理变成可量化、可比较、可分类的特征向量。这个差别决定了整条技术路线的走向——先设计特征描述子再用分类器对特征向量做判别。MATLAB 在这条路线上几乎是成本最低的验证环境灰度共生矩阵、LBP、滤波器组和 SVM 都有现成函数新手一两个小时能跑通最小闭环老手也能在统一框架里对比不同描述子的上限。适合织物瑕疵检测、遥感地物分类、医学切片分析等需要按纹理找差异的场景。下面按特征提取、特征工程、分类器选型、排错、验证的顺序展开代码在 R2020a 之后的版本可直接运行用到 Computer Vision Toolbox 和 Statistics Toolbox。2. 可计算的纹理维度GLCM 与 LBP 的 MATLAB 实现和取舍2.1 灰度共生矩阵GLCM用像素对的统计关系描述纹理周期性纹理从视觉上看是在空间上有规律重复的局部模式。直接拿像素矩阵做匹配不具备平移和旋转容忍性换个角度拍同一块布像素矩阵完全不同但相隔一定距离的两点灰度同时出现的频率变化很小。灰度共生矩阵统计的正是这个联合概率给定偏移方向和距离统计灰度对(i,j)出现的次数再归一化成矩阵。周期性越强的纹理在特定方向上会出现明显的重复对角带规律性差的噪声纹理则分布弥散。MATLAB 里用graycomatrix和graycoprops实现I imread(fabric.png); if size(I,3) 3 I rgb2gray(I); end I im2double(I); glcm graycomatrix(I, NumLevels, 16, Offset, [0 1; -1 1; -1 0; -1 -1]); stats graycoprops(glcm, {Contrast,Correlation,Energy,Homogeneity}); contrast mean(stats.Contrast); correlation mean(stats.Correlation); energy mean(stats.Energy); homogeneity mean(stats.Homogeneity); feature [contrast, correlation, energy, homogeneity];Offset是[row col]相对位移上面四行分别对应水平 0°、对角线 45°、垂直 90°、对角线 135° 四个方向。四个方向各生成一个 GLCMgraycoprops返回的每个统计量是 1×4 向量取平均可以消掉主方向敏感度如果纹理有明确方向性比如木纹、拉丝金属把四个方向拆开送入分类器比取平均更有效。NumLevels决定灰度级压缩程度16 是默认值它直接影响特征对亮度噪声的鲁棒性。参数常用取值对特征的影响NumLevels8 / 16 / 32 / 64灰度级越多对比度分辨率越高但对光照噪声更敏感级数超过 32 后多数数据集上准确率不再上升Offset距离1 / 2 / 3距离 1 捕捉细密纹理距离大时对粗纹理更敏感常用做法是同时取多组距离再拼接Offset方向1 个或 4 个方向已知取单方向方向未知取 4 方向均值或保留四方向特征做后续筛选2.2 局部二值模式LBP用邻域编码捕捉微观纹理基元GLCM 擅长周期性纹理但对局部拐角、斑点和边缘这类微观结构区分力弱。LBP 的思路完全不同以当前像素为中心比较周围 P 个邻点与中心像素的亮度关系大于中心记 1否则记 0得到一个 P 位二进制数再统计整幅图像所有像素编码的直方图作为特征。布料上的微小纱线毛羽、陶瓷釉面的气泡、木材导管在 LBP 直方图里都会形成不同的模式分布。MATLAB 的 Computer Vision Toolbox 提供extractLBPFeaturesI imread(fabric.png); if size(I,3) 3 I rgb2gray(I); end I im2double(I); lbp extractLBPFeatures(I, ... Radius, 2, ... NumNeighbors, 16, ... Upright, false, ... Normalization, None);Radius取 1 时只比较 3×3 邻域适合细纹理取 2 或 3 需要做圆形邻域插值能覆盖更粗的纹理基元特征维度也会变化。NumNeighbors是采样点数取 8 时基础编码空间是 256取 16 时是 65536实际特征维度低得多因为默认开启 uniform 和旋转不变。Upright设为 false 会让算法对每个编码做循环移位选最小的二进制值作为代表实现旋转不变如果纹理方向固定比如印刷品字符纹理设成 true 能保留更丰富的方向信息。纹理分类里更常用的默认组合是Radius2, NumNeighbors16, Uprightfalse。2.3 两种描述子的融合特征级联与量纲统一GLCM 偏全局周期性LBP 偏局部微观基元二者互补性很强。我在大多数纹理分类任务里会把两组特征拼接起来用但直接[glcm_feat, lbp_feat]容易出问题——GLCM 的 Contrast 可能是几百的量级LBP 直方图计数可能是上万拼接后距离度量完全被数值大的维度主导。常见的做法是先分别做 L2 归一化再级联glcm_feat [contrast, correlation, energy, homogeneity]; glcm_feat glcm_feat / norm(glcm_feat); lbp_feat extractLBPFeatures(I, ... Radius, 2, NumNeighbors, 16, ... Upright, false, Normalization, None); lbp_feat lbp_feat / norm(lbp_feat); combined [glcm_feat, lbp_feat]; % 级联后交给后续标准化与降维这个组合的优势在于周期性纹理靠 GLCM 的 Correlation 和 Energy 拉开差距无规则表面靠 LBP 直方图区分。代价是特征维度偏高后面需要用 PCA 压缩。级联时还要注意 LBP 特征维度和采样点数的关系——NumNeighbors一旦增大级联后维度可能上千小样本数据集很容易过拟合所以 PCA 在这里不是可选项是必选项。3. 特征工程与分类器选型从特征矩阵到纹理分类结果3.1 归一化与 PCA 降维先标准化再降维才是正确顺序不同描述子的量纲差异很大。GLCM 的对比度可能是几百LBP 直方图计数可能是上万如果不标准化数值大的维度在距离计算和 SVM 核函数里会主导一切。PCA 是线性变换它本身对量纲不免疫必须先做标准化再做 PCA这个顺序不能反。如果先 PCA 再标准化主成分的方向已经被大数值维度带偏后面即使标准化也救不回来。X cell2mat(all_features); % 每行一个样本的特征向量 Y categorical(all_labels); X_norm zscore(X); % 每列零均值、单位标准差 [coeff, score, latent] pca(X_norm); explained cumsum(latent) / sum(latent); k find(explained 0.95, 1); % 取累计解释方差 95% 的主成分数 X_pca score(:, 1:k);zscore按列标准化把每列变成零均值、单位方差。一个容易忽略的坑当某一列在所有样本上取值完全相同标准化后会出现 NaN。纹理特征里如果图像尺寸统一且背景占比过大灰度直方图的某些 bin 可能恒为 0直接触发这个问题。稳妥做法是在标准化前删掉std(X) 0的列。cumsum(latent)/sum(latent)计算累计解释方差占比find(..., 1)找出第一个达到 95% 的位置。95% 只是个起点当样本量小于 50 时把阈值降到 90% 甚至 85% 往往能让分类器更稳因为保留的主成分越少过拟合空间越小。3.2 小样本纹理分类的候选分类器SVM、KNN 与随机森林纹理分类的数据集通常不大每类几十到几百张 ROI 很常见。这个样本量级下分类器选型和样本量强相关。按我的经验三种分类器的取舍如下分类器适用样本量训练开销调参重点适用场景RBF-SVM每类 ≥20低RBF 核尺度KernelScale、惩罚系数BoxConstraint高维特征友好纹理分类默认首选KNN每类 ≥10 即可起步无显式训练K 值、距离度量特征有效性快速验证不均衡样本表现差随机森林每类 ≥30 更稳中树数量、最大深度对特征缩放不敏感小样本容易过拟合先用 RBF-SVM 当基线是纹理分类里最稳妥的做法。原因很简单GLCM 和 LBP 拼接后的特征维度通常几百到上千样本量又不高线性分类器可能欠拟合深度模型会严重过拟合RBF-SVM 正好落在中间。KNN 适合在还没做 PCA 之前快速验证一下特征本身能不能分开不同类别——如果 KNN 准确率都很高说明问题出在分类器如果 KNN 和 SVM 都差大概率要回头调特征提取参数。随机森林在小样本上很容易把噪声学进去除非特征维度已经压到 50 维以内否则我一般不优先用。3.3 完整可运行脚本批量提取特征、训练 SVM 并评估把上面的步骤串成一个可复现的训练脚本假设图片按imgFolder路径存放labelList是和文件名一一对应的类别标签function [acc, k] train_texture_classifier(imgFolder, labelList) files dir(fullfile(imgFolder, *.png)); numSamples numel(files); X zeros(numSamples, 1); % 先占位后面按实际维度扩展 Y categorical(labelList(:)); featAll {}; for i 1:numSamples I imread(fullfile(files(i).folder, files(i).name)); if size(I,3) 3 I rgb2gray(I); end I im2double(I); glcm graycomatrix(I, NumLevels, 32, ... Offset, [0 1; -1 1; -1 0; -1 -1]); s graycoprops(glcm, {Contrast,Correlation,Energy,Homogeneity}); glcmFeat [mean(s.Contrast), mean(s.Correlation), ... mean(s.Energy), mean(s.Homogeneity)]; glcmFeat glcmFeat / norm(glcmFeat); lbpFeat extractLBPFeatures(I, Radius, 2, NumNeighbors, 16, ... Upright, false, Normalization, None); lbpFeat lbpFeat / norm(lbpFeat); featAll{i} [glcmFeat, lbpFeat]; end X cell2mat(featAll); X_norm zscore(X); [~, score, latent] pca(X_norm); k find(cumsum(latent)/sum(latent) 0.95, 1); X_pca score(:, 1:k); mdl fitcsvm(X_pca, Y, KernelFunction, rbf, ... Standardize, false, KFold, 5); acc 1 - kfoldLoss(mdl, LossFun, ClassifError); end这个脚本里fitcsvm的Standardize设为 false因为特征已经在前面的zscore和pca里处理过重复标准化不会带来额外收益。KFold指定内部做 5 折交叉验证kfoldLoss返回的是错误率所以用 1 减。如果图片文件夹里混有其他格式可以扩展dir匹配为{*.png,*.jpg,*.bmp}。图像尺寸不需要统一GLCM 和 LBP 直方图都是全局统计量天然支持任意尺寸但 ROI 尺寸差异过大时统计量波动明显建议在进入特征提取前统一缩放到 128×128 或 256×256。4. 分类效果不佳时的排查方向光照、尺度与样本量4.1 光照不均导致的特征漂移先做预处理再提取特征同样的纹理在暗光下对比度特征明显降低Correlation 和 Homogeneity 也会被压缩。处理光照不均常见做法是直方图均衡化、自适应直方图均衡化和同态滤波三种。纹理分类任务里我优先用adapthisteqCLAHE因为它增强局部对比度不会像全局histeq那样把亮部细节压没I imread(fabric.png); I im2double(rgb2gray(I)); I_eq adapthisteq(I, NumTiles, [8 8], ClipLimit, 0.02); % 预处理后的 I_eq 再进入 graycomatrix 与 extractLBPFeaturesNumTiles决定局部对比度增强的分块数块太小时块与块之间出现网格状边界等于人为引入伪纹理一般 8×8 起步。ClipLimit限制对比度放大倍数过大时阴影边缘会被放大成强纹理反而干扰分类。从 0.01 开始试在验证集上对比 CLIP 参数 0.005/0.01/0.02 三档准确率。对光照方向性很强的场景比如车削金属表面同态滤波比 CLAHE 更合适但 MATLAB 没有内置同态滤波函数需要自己写频域滤波调参成本高通常放到最后再试。4.2 单尺度特征丢失结构信息用图像金字塔做多尺度拼接只用一组Radius2, NumNeighbors16的 LBP对纹理单元较大的材质区分有限。粗纹理要在大尺度下看细纹理要在小尺度下看固定一个尺度等于提前放弃了某些类别信息。最直接的补强是用impyramid对图像降采样每级提取一组特征再拼接I im2double(rgb2gray(imread(wood.png))); levels 2; feat []; for L 0:levels Il I; for j 1:L Il impyramid(Il, reduce); end g graycomatrix(Il, NumLevels, 16, Offset, [0 1; -1 1; -1 0; -1 -1]); s graycoprops(g, {Contrast,Correlation,Energy,Homogeneity}); l extractLBPFeatures(Il, Radius, 2, NumNeighbors, 16, ... Upright, false, Normalization, None); feat [feat, mean(s.Contrast), mean(s.Correlation), ... mean(s.Energy), mean(s.Homogeneity), l]; endimpyramid的reduce操作每次把图像长宽各减半两级金字塔对应原图、1/2 尺度、1/4 尺度三个分辨率。代价是特征维度翻三倍小样本下过拟合风险明显上升。我一般搭配第 3.1 节的 PCA 一起用先拼接、再标准化、再降到 95% 能量否则分类器容易在冗余维度上浪费容量。如果加多尺度后准确率反而下降优先怀疑特征维度过高而不是多尺度思路有问题。4.3 样本量太小导致交叉验证不稳定LOOCV 比 KFold 更可信总样本数低于 50 时5 折交叉验证每折测试集只有 10 个左右样本评估结果波动很大——同样一组特征换个随机种子准确率可能差 10 个百分点。纹理分类小数据集上更稳的评估方法是留一交叉验证LOOCV每轮只留 1 个样本测试训练 n-1 个样本全部样本轮流做一次n size(X_pca, 1); pred strings(n, 1); for i 1:n trIdx true(n, 1); trIdx(i) false; mdl fitcsvm(X_pca(trIdx,:), Y(trIdx), KernelFunction, rbf); pred(i) predict(mdl, X_pca(~trIdx,:)); end accLOO mean(pred Y);LOOCV 的方差小但训练成本是 n 次 SVM 训练样本量到几百时耗时明显。这是可接受的交换——纹理分类更关心特征是否真的可分而不是追求训练速度。汇报结果时同时记录 LOOCV 准确率和 PCA 保留的主成分数读者才能按同样参数复现。验证方式适用样本量评估方差训练成本固定训练/测试划分每类 ≥100大最低5 折交叉验证每类 30~100中中LOOCV每类 30小最高n 次训练5. 验证纹理分类器的可落地性耗时统计与可视化定位特征5.1 用 tic/toc 定位特征提取瓶颈算法在实验里跑通和能在批量任务里跑通是两回事。特征提取是纹理分类流水线里最容易被忽略的耗时环节。先用几行代码统计不同灰度级下 GLCM 的提取耗时比盲目调分类器参数更有效levelsList [8, 16, 32, 64]; timeCost zeros(size(levelsList)); for idx 1:numel(levelsList) tic; for rep 1:10 g graycomatrix(I, NumLevels, levelsList(idx), ... Offset, [0 1; -1 1; -1 0; -1 -1]); s graycoprops(g, {Contrast,Correlation,Energy,Homogeneity}); end timeCost(idx) toc / 10; endNumLevels每翻一倍GLCM 计算量近似线性增长64 级时单张 512×512 图像可能就接近实时上限。关键不是选最快的档位而是找到耗时和精度的拐点——在固定数据集上对比 16/32/64 级的 LOOCV 准确率通常 16 级和 32 级准确率差距很小但耗时差距明显。LBP 的耗时主要由NumNeighbors决定采样点越多插值计算越重但特征维度同步上升这两个参数要一起权衡。5.2 用滑动窗口可视化特征的像素级分布调参调到怀疑特征失效时回到图像上看一眼比只看 acc 数字更直观。用滑动窗口计算每个局部块的 GLCM 对比度生成热力图可以确认特征提取的 ROI 位置和纹理边界是否对齐win 16; contrastMap blockproc(I, [win win], ... (b) graycoprops(graycomatrix(b.data, NumLevels, 16), Contrast).Contrast); figure; subplot(1,2,1); imshow(I); title(原图); subplot(1,2,2); imshow(contrastMap, []); title(滑动窗口对比度);blockproc对每个 16×16 块独立计算 GLCMb.data是当前块的图像数据。块的边界处理比较粗糙但定位特征区域足够。窗口太小比如 4×4 时局部统计量方差极大热力图全是噪点窗口太大比如 64×64空间分辨率又不够。这个可视化也能反过来验证多尺度金字塔的结果纹理单元越大能在小窗口下产生稳定对比度值的窗口尺寸就越大。先把 ROI 尺寸和灰度级固定下来再回头调分类器很多前期瞎调的功夫都能省掉。本文还有配套的精品资源点击获取