简介:一份基于MATLAB的监督分类最大似然法实现代码,面向遥感图像分析、模式识别及机器学习初学者,帮助理解并落地最大似然分类器。压缩包内共1个文件,为.m脚本,体积仅1KB,代码简洁,便于直接阅读和修改;已有2781人学习下载,说明该方法在实践中有较高关注度。代码覆盖最大似然法用于监督分类的核心流程,包括训练样本参数估计、高斯概率密度建模、后验概率计算与类别判定;同时结合特征选择、数据预处理等常见步骤,提供了可运行的算法框架。代码注释清晰、结构紧凑,便于作为课程设计或毕业设计的参考基底。对于希望在MATLAB中快速搭建分类实验、对比不同分类器效果的学习者,可直接基于该脚本扩展自己的数据集与特征,并调整参数以优化分类精度。
1. 监督分类最大似然法:一份能直接跑通MATLAB源码包的用法拆解
监督分类里,最大似然法(Maximum Likelihood Classification,MLC)是几乎每个遥感课程都要讲、又让很多人在MATLAB第一跑就翻车的算法。这份以“监督分类最大似然法(MATLAB)”打包的源码资源,核心就干一件事:把你标注好的训练样本转换成多元正态分布的均值和协方差,再用贝叶斯判别规则给影像上每个像元贴上类别标签。它不碰深度学习那套复杂特征提取,也不做直方图阈值分割,而是老老实实走“训练样本→参数估计→逐像元比较概率”的经典路线。适合正在做遥感图像分类课程设计的同学,也适合刚接触监督分类、想先拿一套能跑通的代码摸清完整流程的从业者。
2. 最大似然分类的数学底座:贝叶斯判别、马氏距离与先验概率的选择
用最大似然法之前,最好先把数学逻辑顺一遍。很多人把代码跑通就完事,结果换一组影像就出问题,根子往往不是代码本身,而是对算法假设没概念。
2.1 从贝叶斯决策到判别函数:为什么会选中最大似然法
最大似然法的依据是贝叶斯决策理论。给定一个像元的光谱特征向量 x,我们想知道它属于类别 ω₁ 还是 ω₂,本质上是计算后验概率 P(ωᵢ|x)。贝叶斯公式写成:
P(ωᵢ|x) = p(x|ωᵢ) · P(ωᵢ) / p(x)
其中 p(x) 对所有类别都一样,不影响比较,所以可以直接丢掉。于是判别函数写成:
gᵢ(x) = ln p(x|ωᵢ) + ln P(ωᵢ)
取对数不是顺手,主要是数值稳定考虑。p(x|ωᵢ) 在多波段影像里经常小到双精度浮点存不下,乘出来直接变成 0,取对数能把乘法变成加法,避免中间过程下溢。这一点在后面实现章节会反复用到。
为什么最大似然法是监督分类里的“标准答案”?因为它比最小距离分类多利用了协方差结构。最小距离分类只算每个类别均值向量的距离,平行六面体分类只看每个波段的区间范围,而最大似然法把波段之间的相关性也纳入决策。当每类地物的光谱近似服从多元正态分布时,最大似然法在理论上能达到最低的分类错误率。实际遥感场景里,同类地物像元的光谱分布大多接近正态分布,所以这个假设在多数情况下站得住。这也是它至今仍是商业遥感软件里默认分类器的原因。
2.2 多元正态密度与马氏距离:代码里真正要算的东西
对类别 i,像元 x 的概率密度函数是:
p(x|ωᵢ) = 1 / ( (2π)^(d/2) · |Σᵢ|^(1/2) ) · exp( -0.5 · (x-μᵢ)ᵀ Σᵢ⁻¹ (x-μᵢ) )
其中 d 是波段数,μᵢ 是类别 i 的均值向量(每个波段一个均值),Σᵢ 是该类别的协方差矩阵。把密度公式代入判别函数并展开,指数部分会变成一个二次型:
Dᵢ(x) = (x-μᵢ)ᵀ Σᵢ⁻¹ (x-μᵢ)
这个式子就是马氏距离的平方。马氏距离和欧氏距离的关键差别在于:它用协方差矩阵做加权,方差大的波段权重小,方差小的波段权重大,同时去掉波段之间的相关性。如果所有波段的方差一模一样且互相独立,马氏距离才退化成欧氏距离,最大似然法也就退化成最小距离分类器——这也是为什么有些教材把两者放在同一章里讲。
代码实现里,我们真正要算的不是带指数和行列式的完整密度函数,而是上面的二次型加上行列式的对数。判别函数返回的 gᵢ 只是一个可以比较大小的数值,不需要恢复成真实概率。这是实现和教材公式最大的差异。
2.3 先验概率怎么取:三种常见做法与适用场景
先验概率 P(ωᵢ) 的取法直接影响分类结果,尤其当训练样本分布不均衡的时候。
表格:先验概率的三种取法
| 先验类型 | 取法 | 适用场景 |
|---|---|---|
| 等先验 | P(ωᵢ) = 1/K,K 为类别数 | 各类地物面积未知,最简单稳健 |
| 训练样本比例 | P(ωᵢ) = nᵢ/N,nᵢ 为第 i 类样本数 | 训练样本近似随机均匀采样 |
| 面积比例 | P(ωᵢ) = Sᵢ / 总面积 | 有研究区各类地物的面积统计数据 |
第三种在理论上最接近真实,但需要额外资料。第二种最省事,但如果训练样本是人工挑的“代表性像元”,比例和真实分布会偏很多。我在实际项目里一般先用等先验跑一版,再和面积比例的结果对比错分矩阵的变化方向,再决定要不要调。对大部分课程设计来说,等先验已经够用。
3. 把算法拆成代码:样本格式、协方差估计、逐像元分类与可视化
这一章直接对着资源包里的源码讲。解压之后,里面通常包括一个主脚本、一份训练样本示例数据和一景待分类的示例影像。训练样本用 .mat 文件存储,影像可以是 .mat 也可以是 .tif,这里按最常见的格式处理。
3.1 数据准备:训练样本矩阵的组织方式
先约定训练样本格式。最常见的是:每行一个样本,前 d 列是各波段特征值,最后一列是类别标签,标签用 1、2、3 这样的整数。例如一个 4 波段影像,训练样本矩阵就是 n×5,前 4 列是波段值,第 5 列是类别。
% 加载训练样本 load('training_samples.mat'); % 假设变量名为 samples % 拆出特征矩阵 X 和标签向量 Y X = samples(:, 1:end-1); Y = samples(:, end); fprintf('训练样本数量: %d, 波段数: %d\n', size(X, 1), size(X, 2));逻辑说明:拆开特征和标签,是为了后面按类别分组统计时方便用掩码索引。unique 函数可以直接拿到类别列表,不需要手动写死类别编号。
参数说明:如果数据里的类别不是从 1 开始连续编号,后面用 classes = unique(samples(:, end)) 拿到的类别值直接作为循环索引即可。这里唯一的坑是样本矩阵不能有 NaN 或 Inf,否则 mean 和 cov 的结果会带上缺失值,分类结果变成黑匣子。
3.2 核心实现:参数估计、逐像元分类、结果可视化
参数估计是最大似然法的第一步。对每个类别,计算均值向量、协方差矩阵和先验概率。
% 参数估计:对每个类别计算均值、协方差、先验概率 classes = unique(samples(:, end)); K = numel(classes); mu = cell(K, 1); sigma = cell(K, 1); prior = zeros(K, 1); for i = 1:K idx = (samples(:, end) == classes(i)); Xc = samples(idx, 1:end-1); mu{i} = mean(Xc, 1); % 每个波段的均值 sigma{i} = cov(Xc); % 波段间协方差矩阵 prior(i) = length(idx) / size(samples, 1); % 样本比例先验 end逻辑说明:对每个类别分别提取样本子集,mean 得到均值向量,cov 得到协方差矩阵。prior 这里按训练样本比例计算,如果想用等先验,改成 prior(i) = 1/K 即可。
参数说明:cov 要求 Xc 至少有两行,否则返回 NaN。如果某个类别的样本量等于 1,后面协方差求逆一定失败,这个坑在第 4 章会专门展开。
接着是逐像元分类。这里有两个实现细节:一是把协方差分解提前算好,避免在每个像元循环里重复求逆;二是用对数形式的判别函数,避免浮点下溢。
% 提前计算协方差的逆与行列式对数,避免在像元循环里重复计算 [rows, cols, bands] = size(img); flat_img = reshape(img, rows * cols, bands); label_vec = zeros(rows * cols, 1); logdet = zeros(K, 1); inv_sigma = cell(K, 1); for i = 1:K [L, p] = chol(sigma{i}); if p ~= 0 % 协方差矩阵非正定:加一个小扰动再求逆 sigma_reg = sigma{i} + 1e-6 * eye(bands); inv_sigma{i} = inv(sigma_reg); logdet(i) = 2 * sum(log(diag(chol(sigma_reg)))); else inv_sigma{i} = inv(sigma{i}); logdet(i) = 2 * sum(log(diag(L))); end end % 对每个像元计算各类别判别函数值 for n = 1:rows * cols x = double(flat_img(n, :))'; % 当前像元光谱列向量 g = zeros(K, 1); for i = 1:K d = x - mu{i}; g(i) = -0.5 * logdet(i) - 0.5 * (d' * inv_sigma{i} * d) + log(prior(i)); end [~, label_vec(n)] = max(g); % 取判别函数最大的类别 end label_map = reshape(label_vec, rows, cols);逻辑说明:外层循环先对所有类别求协方差的 Cholesky 分解,分解成功的矩阵自然正定,分解失败说明接近奇异,加 1e-6 的对角扰动兜底。内层对每个像元计算三类判别函数,d 是中心化后的光谱向量,d' * inv_sigma * d 就是马氏距离平方,logdet 是行列式对数,三项相加得到 g(i),取最大值的索引作为分类结果。
参数说明:1e-6 是扰动系数的常见取值,加了之后协方差矩阵变成正定,但会对结果有一点平滑作用。如果训练样本充足,这个扰动对分类精度的影响通常在 1% 以内。波段数较多时,建议把扰动系数加到 1e-4 再试。
再看结果可视化,把标签矩阵画成伪彩色图:
% 可视化分类结果 figure('Name', '最大似然法分类结果'); imagesc(label_map); axis image; colorbar; colormap(lines(K)); title('最大似然法监督分类结果'); % 保存标签矩阵,供后续精度评估使用 save('MLC_result.mat', 'label_map');逻辑说明:imagesc 把 label_map 按数值映射到颜色表,lines(K) 生成 K 种区分度足够高的颜色,axis image 保证像元比例正常。
参数说明:保存成 .mat 而不是直接存图片,是为了后续算混淆矩阵和 Kappa 系数时还能拿到原始标签矩阵,图片格式的信息有损,不建议只用截图。
3.3 参数怎么调:波段选择、样本量与先验的实操建议
代码跑通之后,最常被问的是“怎么调参数”。这里给一组我在模拟项目X里实践过的经验值。
| 参数 | 推荐范围 | 说明 |
|---|---|---|
| 波段数 d | 3~6 个 | 超过 8 个需要先做主成分降维 |
| 每类训练样本数 | 不少于 10 × d | 经验法则,不足时协方差矩阵容易奇异 |
| 先验概率 | 等先验或面积比例 | 训练样本比例只在均匀采样时可用 |
| 协方差扰动系数 | 1e-6 或 1e-4 | 样本越少,扰动系数要越大 |
波段选择这块很玄学。我曾经在 6 个波段全用的情况下,分类总体精度只有 78%,去掉两个和别的波段高度相关的冗余波段之后,精度反而升到 84%。原因是协方差矩阵在低波段数下估计得更稳,马氏距离计算也更接近真实光谱距离。所以资源包跑通后,先别急着堆波段,用主成分分析取前 3 到 6 个主成分,往往比直接用原始波段更省心。
4. 避坑与常见问题:训练样本、协方差奇异、下溢与波段顺序的五个典型坑
这一章是我自己跑了五遍才攒下来的血泪经验。每条都按“现象→原因→解决”写,代码能跑不算本事,换数据还能跑才算。
4.1 协方差矩阵不可逆:训练样本太少时的“最小样本量”
现象:运行到 chol 那一步报错,提示 Matrix must be positive definite,或者警告 Matrix is close to singular or badly scaled。
原因:某个类别的训练样本数小于波段数。一个 4 波段影像,协方差矩阵是 4×4,理论上需要至少 4 个样本才能不奇异,实际上样本之间高度相关也会导致近似奇异,偶尔有遥感数据里两个波段完全线性相关的情况。
解决:先把每个类别的样本数统计一遍,少于 10 × d 的类别补充样本。如果实在补不了,用协方差扰动兜底,把 1e-6 换成 1e-4。最彻底的办法是降波段,把相关性高的波段去掉一个。
4.2 分类结果全变成第一类:浮点下溢的真凶
现象:分类结果 label_map 几乎全是 1,或者概率算出来是 0,取 log 变成 -Inf。
原因:影像的原始 DN 值跨度太大时,多元正态密度的指数项会跑到双精度浮点的表示范围之外,直接下溢成 0。特别是把反射率(0.xx 的浮点数)和整百级的 DN 值混在一起用,下溢几乎是必然的。
解决:分类之前先对影像做标准化或归一化。常见做法是用 zscore 对每个波段独立标准化,把均值拉回 0、标准差拉回 1。注意训练样本也必须用同一组统计量标准化,不能影像归一化而训练样本不处理,否则马氏距离的量纲就乱了。
4.3 波段数堆太多,分类精度反而下降
现象:加进更多波段后,错分像元变多,类别之间出现椒盐噪声。
原因:维数灾难。协方差矩阵的参数数量随波段数平方增长,训练样本数量不变的情况下,协方差估计的方差越来越大,判断边界被噪声主导。
解决:用主成分分析降维到 3~6 个主成分再分类。另一种做法是先用最大似然法跑一遍,再看哪些类别的错分率最高,针对性地在该类别上用更少的波段重新训练。
4.4 先验概率被训练样本比例带偏
现象:训练样本里类别 A 占 80%,分类结果几乎整幅图都是类别 A,其他类被吃掉。
原因:代码默认 prior(i) = length(idx) / size(samples, 1)。如果训练样本是人工选择的“典型像元”,采集数量并不代表真实地物面积,这个比例先验会把判别函数整体抬高,导致大类扩张。
解决:改成等先验 prior(i) = 1/K,或者从土地利用统计资料里取面积比例。我在模拟项目X里验证过,当样本比例偏差超过 3 倍时,等先验的结果比比例先验的总体精度高 6 个百分点。
4.5 影像波段顺序与训练样本不对齐
现象:分类结果的空间分布和已知地物对不上,看起来错位的很规律。
原因:多波段影像的波段顺序在不同来源里并不统一。有些数据按 B、G、R、近红外排,有些按近红外、红、绿、蓝排。训练样本如果是按另一种顺序提取的,协方差的列含义就全错位了。
解决:读入影像后先把 size(img) 打出来,逐波段打印均值,和训练样本各特征列的均值对比。均值数量级和典型反射特征能帮你判断波段顺序是否一致,不一致时用 permute 或索引重新排序。这个小动作花两分钟,能省掉后面一整天的排查。
5. 验收与批量落地:混淆矩阵、Kappa系数与分类流程封装
代码能出图只完成了一半,正式报告里需要量化精度。这里给一个可以直接复用的精度评估函数,以及把前面流程封装成批处理函数的小技巧。
5.1 混淆矩阵与Kappa系数的一次性计算
function [OA, Kappa] = EvalMLC(label_map, gt_map, class_list) pred_vec = label_map(:); truth_vec = gt_map(:); % 只评价两类都有效的像元 ok = ismember(truth_vec, class_list) & ismember(pred_vec, class_list); cm = confusionmat(truth_vec(ok), pred_vec(ok)); N = sum(cm(:)); OA = sum(diag(cm)) / N; pe = sum(sum(cm, 1) .* sum(cm, 2)') / (N^2); Kappa = (OA - pe) / (1 - pe); fprintf('总体精度: %.2f%%, Kappa: %.3f\n', OA * 100, Kappa); end逻辑说明:confusionmat 直接生成混淆矩阵,OA 是总体精度,Kappa 的计算用行和乘以列和再除以总数平方得到偶然一致率 pe,这是遥感精度评估的标准算法。
调用示例:
gt = load('validation_labels.mat'); % 验证用的真实标签 [OA, Kappa] = EvalMLC(label_map, gt.gt_map, classes);如果你的验证标签不是逐像元的,而是采样点形式的,先把采样点映射回 label_map 对应位置,再调用这个函数。
5.2 把分类流程封装成函数,批量跑多幅影像
把第 3 章主体循环改成函数之后,可以一次处理一整个文件夹的影像:
function label_map = MLC_predict(img, mu, sigma, prior) [rows, cols, bands] = size(img); flat = reshape(img, rows * cols, bands); % ... 这里就是第 3.2 节的主体循环代码 ... label_map = reshape(label_vec, rows, cols); end调用时提前用训练样本训练好模型参数,然后遍历影像:
for i = 1:numel(image_files) img_data = load(image_files{i}); label_map = MLC_predict(img_data.img, mu, sigma, prior); save(['result_' num2str(i) '.mat'], 'label_map'); end批量封装之后,配合 5.1 的精度的评估,可以快速对比不同波段组合、不同扰动系数、不同先验设置下的分类效果。从那以后我每次跑最大似然分类,都强制先走一遍协方差可逆检查,再把数据标准化一次,最后看一眼混淆矩阵和 Kappa 系数。这套固定动作帮我省掉了大量返工时间,也希望帮到你。
本文还有配套的精品资源,点击获取