<p>应优先用Eigen的SVD而非特征值分解做PCA降维,重建公式为X_recon = U.leftCols(k) (U.leftCols(k).transpose() X_centered) + mean_vector,易错点包括漏加均值、矩阵乘法顺序错误、误用V或S;OpenCV的cv::PCA默认行样本且自动中心化,需注意轴向与接口差异。</p>

用 Eigen 库做 PCA 降维,别手写协方差矩阵
直接手算协方差矩阵(C = (X - μ)(X - μ)ᵀ / (n-1))在 C++ 中极易出错:维度容易颠倒、均值广播没对齐、内存越界。Eigen 提供了 RowMajor 和 ColPivHouseholderQR 等稳定接口,应优先使用 SVD 路径而非特征值分解——尤其当样本数 Eigen::JacobiSVD 更鲁棒。
关键点:
-
X必须是MatrixXd,每列是一个样本(不是每行),否则svd.matrixU()的物理意义会反掉 - 均值中心化必须按列减(即对每个特征维度减去其均值),用
X.colwise() - X.rowwise().mean()是错的;正确写法是X.rowwise() - X.colwise().mean() - 不要调
computeEigenvalues(),它默认对n×n协方差矩阵操作,而你真正需要的是svd.singularValues()和svd.matrixU()的前 k 列
选择主成分数量 k:看 singularValues() 衰减,不是硬设
设原始数据为 X(n×m,n 特征维、m 样本数),SVD 后得到 U(n×n)、S(n×m 对角)、V(m×m)。前 k 个奇异值平方除以总能量,即累计贡献率:sum(S(0:k-1).array().square()) / S.array().square().sum()。常见误区:
- 把
k=50当成“经验数值”直接写死——实际 ORL 人脸数据用 40~60 主成分就能达 95% 以上重构精度,但工业传感器数据可能需 200+ - 误用
U.block(0,0,n,k)做投影,却忘了U是左奇异向量,对应原始特征空间;正确投影是U.leftCols(k).transpose() * X_centered - 忽略数值稳定性:小奇异值(如
)参与计算会导致 <code>NaN,应在取前 k 列前先检查S(k-1) > 1e-10
重构原始数据时,别漏乘 U.leftCols(k) 的转置
降维只是中间步骤,很多场景(如异常检测、去噪)需要从低维重建回近似高维表示。公式是:X_recon = U.leftCols(k) * (U.leftCols(k).transpose() * X_centered) + mean_vector。最容易错的三处:
立即学习“C++免费学习笔记(深入)”;
- 忘记加回均值
mean_vector,导致重建结果整体偏移 - 误写成
U.leftCols(k).transpose() * U.leftCols(k) * X_centered——这是正交投影,但矩阵乘法顺序错误,U不是方阵时会编译失败 - 用
V或S参与重建:只有U定义了新基,V描述样本间关系,不用于特征空间重建
OpenCV 的 PCA 类能用,但要注意构造方式
OpenCV 的 cv::PCA 封装较友好,但默认行为和教科书不一致:它的 operator() 输入是 Mat 每行一个样本(CV_32F),且自动执行均值中心化。若你已有中心化数据,传入前得手动关掉:cv::PCA pca(data, cv::Mat(), CV_PCA_DATA_AS_ROW, k)。注意:
-
CV_PCA_DATA_AS_ROW和CV_PCA_DATA_AS_COL是宏,不是枚举,C++20 下需显式转int -
pca.project(data)返回的是Mat每行一个降维后样本,和 Eigen 默认列样本不兼容,混用前务必 reshape - 它不暴露奇异值,无法计算累计方差贡献率,调试时建议仍用 Eigen 验证
Eigen 的 SVDBase 接口抽象度低,但控制力强;OpenCV 的 PCA 类省事,但黑盒深。真实项目里,常先用 Eigen 快速验证 k 值和重构误差,再切到 OpenCV 部署——因为它的 project() 和 backProject() 在多线程下更轻量。记住:所有 PCA 实现都依赖数据是否已中心化、样本/特征轴朝向、以及奇异值截断阈值,这三个点对不上,结果就全偏。


















