爲了確定指尖位置,首先將手的3D點雲進行PCA,再在主方向上找最前點。
1、PCA原理:
PCA (主成分分析)算法提供了一種壓縮數據的方式。我們也可以將 PCA 視爲學習數據表示的無監督學習算法。 PCA 學習一種比原始輸入維數更低的表示。它也學習了一種元素之間彼此沒有線性相關的表示。這是學習表示中元素統計獨立標準的第一步。要實現完全獨立性,表示學習算法也必須去掉變量間的非線性關係。
PCA 將輸入 x 投影表示成 z,學習數據的正交線性變換,這種表示其實對應着數據的第一個主要成分。因此,我們可以用 PCA 作爲保留數據儘可能多信息的降維方法(再次就最小重構誤差平方而言)。
PCA 學習一種線性投影,使最大方差的方向和新空間的軸對齊。(左) 原始數據包含了x的樣本。在這個空間中,方差的方向與軸的方向並不是對齊的。(右) 變換過的數據 z = W 在軸 z 1 的方向上有最大的變化。第二大變化方差的方向沿着軸 z 2 。
主成分元素特點:彼此無關。將數據 x 投影到 z 時,得到的數據表示的協方差矩陣是對角的(證明略),立刻可得 z 中的元素是彼此無關的。
2、PCA實現
使用了Eigen矩陣相關函數和深度圖與世界座標轉化函數(見上上節),代碼如下:
//depth爲輸入三維深度圖,silhouette爲二維掩碼,選取該x,y範圍的深度圖
//hand_dir爲返回主成分向量,已轉化到世界座標系下,第一主成分爲hand_dir.x(),第二主成分爲hand_dir.y(),第三主成分爲hand_dir.z()
Eigen::Vector3f CircleActionImpl::CalcHandDirFromPCA(const cv::Mat &depth, const cv::Mat silhouette, Eigen::Vector3f &hand_dir) {
const unsigned short *dep_ptr;
const unsigned char *sil_ptr;
int width = depth.cols;
int height = depth.rows;
static std::vector<Eigen::Vector3f> points_pca;
points_pca.clear();
Eigen::Vector3f hand_centre;
hand_centre.setZero();
for (int i = 0; i < height; i++) {
dep_ptr = depth.ptr<unsigned short>(i);
sil_ptr = silhouette.ptr<unsigned char>(i);
for (int j = 0; j < width; j++) {
if (*(sil_ptr++) == 255) {
points_pca.push_back(Depth2World(j, i, *(dep_ptr + j)));
hand_centre += points_pca.back();
}
}
}
if (points_pca.size() == 0) return Eigen::Vector3f::Zero();
hand_centre /= points_pca.size();
Eigen::Map<Eigen::Matrix3Nf> points_mat(points_pca[0].data(), 3, points_pca.size());
Eigen::Matrix3f cov = points_mat * points_mat.adjoint();
Eigen::SelfAdjointEigenSolver<Eigen::Matrix3f> eig(cov);
hand_dir = eig.eigenvectors().col(1);
if (hand_dir[0] < 0)
hand_dir = -hand_dir;
return hand_centre;
}
得到的手的主方向實驗結果正確,正負不確定,假設手指衝攝像頭方向,則在主方向上找最前點可得指尖座標。