RGB+D 视频处理
- 2.5D 数据
- 3D 感知(立体声、结构光、TOF、激光扫描仪,...)
向量、矩阵和标量
* vector (lowercase Gothic alphabets): v, p, m = [m1, m2, ..., mk]', ... * matrix (uppercase Gothic alphabets): R, A, ... * scalar (Italic alphabets: s, t, X, Y, Z, ...
索引
v(1) // the 1st element of the vector v R(1,2) // the 1st row and 2nd col of the matrix R A(:,1) // the 1st vector _ from A = [v1, v2, ..., vN]
一些保留符号
hp: a homogeneous representation of a 2D point, hp = [u, v, 1]' hx: a homogeneous representation of a 3D point, hx = [X, Y, Z, 1]' p: a 2D point, p = [u, v]' x: a 3D point, x = [X, Y, Z]' H: (3x3) planar homography matrix P: (3x4) projective projection matrix
坐标变换
o<W>: the origin of the world frame o<C>: the origin of the camera frame o<O>: the origin of the object frame (e.g., x<C> represents a 3D point in the camera frame) R: (3x3) rotation matrix t: (3x1) translation vector T<n, m>: transformation from m to n coordinate system (e.g., o<C> = T<C, W> o<W>)
- 除非我们指定,否则我们使用(第一个)相机坐标系作为世界参考系。
- 除了以上符号外,我们将遵循 MATLAB 的符号。
当您取一个 3D 点,,该点的每个值 x(j) 都是相对于特定坐标系表示的(例如,传感器坐标系)。点 X 也可以在不同的坐标系中表示(例如,房间的一个角),。为了区分两组值,我们可以写出符号以及相应的坐标系: 和 ,其中 是相对于传感器坐标系的点,而 是相对于角落坐标系的相同点。两个坐标系之间存在一个刚性运动,该运动由一个 (3×3) 旋转矩阵 和一个 (3×1) 平移向量 表达:。在齐次坐标系中,刚性运动可以写成。请注意,我们放置了源坐标系(角落)和目标系统(传感器)的符号。一系列变换表示为,其中.
校准
[edit | edit source]为什么要校准?
总体流程
a) RGB camera ---- RGB image --(1)-- Rectified RGB image --(3)-- Registered RGB image (w.r.t. IR image)
b) IR camera ---- IR image --(2)-- Rectified IR image
c) projector --(internal calibration + IR image)-- Depth image --(4)-- Rectified Depth image --(5)-- 3D points
- 问题 1: (1), (2), (4) - 径向畸变
- 方法
http://www.ros.org/wiki/kinect_calibration/technical
http://nicolas.burrus.name/index.php/Research/KinectCalibration
- 问题 2: (3) - RGB 图像到 IR 图像的配准
- 方法:平面单应性
- 问题 3: (5) - 3D 重建
- 方法:三角测量
- 全部合一
http://www.ee.oulu.fi/~dherrera/kinect/
几何基元和不确定性
[edit | edit source]- 当 x 和 s*x 代表相同的几何实体时,实体 x 是齐次的。
- 点:欧几里得空间中的二维点 xx = [x1, x2]' 在二维投影空间中表示为 x = [x1, x2, 1]'。
- 线:二维直线表示为 l = [a b c]'。
- 直线和直线上一点的点积为 0:x' * l == 0。
- 二维欧几里得空间中的直线方程为 a*x + b*y + c == 0。我们可以将其改写为 [a b c] * [x y 1]' == 0,这就是点积。因此,在投影空间中,dot(x,l) == x' * l == 0。
- 两条直线的叉积给出交点:x = cross(l,m)。
- 两条直线(l 和 m)的交点是一个点。交点 x 位于直线 l 上:dot(x,l) = 0。此外,点 x 位于直线 m 上:dot(x,m) = 0。因此,l 和 m 的叉积给出交点 x。
- 两点的叉积给出直线:l = cross(x, y)。
- 两点:|| x - y ||
- 两条直线:|| l x m ||
- 两条线段
- 三维直线:两点 X、Y 构成一条直线。
- 普吕克矩阵定义为 U(4x4) = X Y' - Y X'。从 U 中,直线定义为 L := [ U(4,1) U(4,2) U(4,3) U(2,3) U(3,1) U(1,2) ]'。
- 直线 L 由 [方向向量 U1 | 法向量 U2] 部分组成。U1 代表向量 Y - X。U2 代表 X 和 Y 的叉积。显然,dot(U1,U2) = 0。
- 两条直线的交点是一个点。如果两条直线 L、M 相交,dot(L,M) = 0。
- 两平面的交点形成一条直线。
- 经过平面 K 的直线 L 形成一个点。
- ...
- 三维坐标变换
- 对齐(参见:ICP)
- TP:三维空间的投影变换(15dof),TP = [TA t; v' s]
- TA:三维空间的仿射变换(12dof),TP = [TA t; 0' 1]
- TS:三维空间的相似变换(7dof),TP = [s*R t; 0' 1]
- TE:三维空间的欧几里得变换(6dof),TP = [R t; 0' 1]
- 点的 uncertainty?
- 直线的 uncertainty?
- 方向的 uncertainty?
- 平面的 uncertainty?
假设你用量角器测量三角形的三个角。从第一次试验中,你得到了值 [30.5, 60.0, 90.1] 度。这些值并不完美,因为它们的总和不等于 180 度!因此,你决定测量 10 次并将它们取平均值。但是,你仍然不确定平均值的总和是否会等于 180 度。
图 XXX 显示了三角形中三个角之间的关系。
- 方法
1) Define a manifold M (e.g. Figure XXX) 2) Measure a set of values p = [a(i), b(i), c(i)] for N times 3) Compute the mean m and the covariance matrix S 4) Project p and S onto the manifold M: q, H
output: angles <-- q variance <-- f(H)
当你在 N 个传感器的外部标定中观察到多个刚体变换时,你可以使用“对偶四元数”来计算“平均”刚体变换。
- 算法
* input: {R(i), t(i)} % a set of R, t * output: R*, t* % optimal R, t Procedure: * convert all R(i), t(i) to DQ(i) % Dual Quaternions * find the mean DQ* from {DQ(i)} * convert DQ* to R*, t*
"类似于单位长度四元数可以表示三维空间中的旋转,单位长度对偶四元数可以表示三维空间中的刚体运动。这个事实被用于理论运动学以及在三维计算机图形学、机器人技术和计算机视觉中的应用。"维基百科
http://stat.fsu.edu/~anuj/CVPR_Tutorial/ShortCourse.htm 形状分析和活动识别的微分几何方法
点配准问题 (1) 查找点到点的对应关系,(2) 估计它们之间的变换。以前的方法可以分为三类:ICP(迭代最近点)、软分配方法和概率方法。
ICP 可能会陷入局部最小值,并且对初始化和接受匹配的阈值敏感。因此,最近点策略被连续优化框架内的软分配取代。但是,在存在异常值的情况下,收敛性不能得到保证。点到点的分配问题可以被转化为估计(高斯)混合模型的参数。--> EM-ICP
- ICP、EM-ICP
- 张量
- 初始化
- ECMPR
- EM-ICP
由于我们同时拥有 RGB 和深度图,因此可以通过简单的 3D 特征匹配来完成两个视图的配准。给定两组对应的 3D 特征描述符,X = [x1, x2, ..., xn] 和 Y = [y1, y2, ..., yn],X 和 Y 之间的刚体运动,Y = T X,可以直接计算。在存在异常值的情况下,鲁棒估计方法(如 RANSAC)可以提供精确的结果。
- 伪代码:计算刚性运动
* input: X, Y * output: R, t Solution = []; For i=1:N randomly select 5 corresponding points from X, Y: s1 = {X(j), X(k), X(l)} and s2 = {Y(j), Y(k), Y(l)} estimate T' = (R', t') using s1, s2 compute Y^ = T' X store the number of inliers, m(i), to Solution: S(i) = (R'(i), t'(i), m(i)) Endfor Answer = arg max(i) (S(i).m(i))
- 参见 HK 分割
- 存在噪声深度图时的曲率估计
- 在深度图上使用 2D 图像特征
我们可以在 2.5D 深度图上使用 2D 图像特征(例如,SIFT、ORB、SURF、LBP)。关键区别之一是图像的尺度。由于我们知道范围图中的绝对尺度,因此可以将搜索空间限制为仅检测物理上有意义的特征。
- 最大值、自旋图像
- 使用 LKT 在深度视频中跟踪特征?
- 深度传感器应经过校准。
- 我们对 2D 特征的位置和深度值的误差进行建模。
- 如果我们有一个已知的相机矩阵 K,最小化图像空间中的重新投影误差可以提供一个准确的估计。
- SIM(表面穿透度量)[Queirolo 2010]
- RGB 值和深度信息可以融合。
- 人脸检测
- 人脸识别(OpenNI2 中间件)[1]
- 人脸建模(OpenNI2 应用程序)[2]
- 面部特征检测:眼睛、鼻尖、嘴巴、耳朵
- 3D 人脸姿态跟踪
- 微软发布的人脸跟踪 SDK Kinect Windows[3]
- 姿态估计和跟踪
- 来自 ICT@USC 的 FAAST(骨骼跟踪)
- Styku 人体建模
- 虚拟试衣间
- 魔镜
- 从 2.5D
- 从 3D
[物体识别 - M. Bennamoun 和 G.J. Mamic] 中的特征匹配方法
1. 假设和检验
2. 匹配关系结构
3. 姿态聚类
4. 几何哈希
5. 解释树
6. 配准和距离变换
- 几何哈希
- RANSAC、StaRSaC 等。
向量和矩阵操作、伪逆、秩、特征值分解、奇异值分解、最小二乘法、齐次系统
梯度下降法、牛顿法、列文伯格-马夸特法 (http://www.ics.forth.gr/~lourakis/levmar/)、单纯形法
协方差矩阵、贝叶斯概率
- 卡尔曼滤波、扩展卡尔曼滤波
- 粒子滤波
表面切线、法线、面积、三角形网格、沃罗诺伊图、表面曲率、法线和主曲率
- OpenNI 和 Prime Sensor
http://www.openni.org/Downloads/OpenNIModules.aspx,选择与您的操作系统匹配的版本。
尝试此操作:http://structure.io/openni
您需要
- OpenNI 二进制文件
- 适用于 Prime Sensor 模块的 OpenNI 兼容硬件二进制文件稳定版本
下载并安装二进制文件后,您现在可以使用 OpenNI 库与 OpenCV 结合编写精彩的代码。
- 在您的工作环境(Visual Studio)中进行配置
在项目的“属性”中,您需要添加
- DIRECTORY_OF_OPENNI\Include 到您的“附加包含目录”
- DIRECTORY_OF_OPENNI\Lib 到您的“附加库目录”
- 在链接器部分,在输入节点下,选择附加依赖项并添加 OpenNI2.lib。(应该在 DIRECTORY_OF_OPENNI\Lib\OpenNI2.lib 中)
- 确保您将附加包含目录和库目录添加到您的发布和调试配置中。根据您的 OpenNI2 版本选择 win32 或 x64。
- 您的代码文件应包含 OpenNI.h
供您参考,请查看 http://www.openni.org/openni-programmers-guide/
简单的 Openni
[edit | edit source]https://code.google.com/p/simple-openni/wiki/Installation#Windows
如何从深度传感器读取单个 3D 点?(OpenNI 2)
[edit | edit source]成功安装 OpenNI2 后,您应该能够运行 DIRECTORY_OF_OPENNI\Samples/Bin 中的示例程序。
以下代码是获取单个点的深度信息的关键代码(对于初始化,您可以参考“SimpleViewer”示例代码)
openni::VideoFrameRef depthFrame;
const openni::DepthPixel* pDepth = (const openni::DepthPixel*) depthFrame.getData;
int width = depthFrame.getWidth();
int height = depthFrame.getHeight();
for (int i = 0; i < height; ++i)
for (int j = 0; j < width; ++j, ++pDepth)
if(i == height/2 && j == width/2 && *pDepth != 0 )//The if sentence depends on which point you want, this is the center point for example
{} //assign the value to a 3D point structure
在 OpenCV 图像中显示来自 OpenNI 输入的深度图 (C++) (OpenNI 1)
[edit | edit source]/* Display depth map */
// Matthias Hernandez: 07/27/2011 - University of Southern California
// [email protected]
// display the depthMap from openNI input in the openCV image ‘out’
void displayDepthMap(IplImage *out, const XnDepthPixel* pDepthMap, XnDepthPixel max_depth, XnDepthPixel min_depth) {
uchar *data = (uchar *)out->imageData;
int step = out->widthStep;
int channels = out->nChannels;
float normalize = (float)(max_depth-min_depth)/255.0f;
int index=0;
for (int i=0; i<MAX_I; i++) {
if (pDepthMap[i] < min_depth || pDepthMap[i] > max_depth) {
for (int k=0; k<channels; k++)
data[index++] = 0;
} else {
if (normalize != 0) {
for (int k=0; k<channels; k++)
data[index++] = (int)(255-(float)(pDepthMap[i]-min_depth)/normalize);
}
else
for (int k=0; k<channels; k++)
data[index++] = 255;
}
}
}
显示来自 OpenNI 输入的 RGB 图像 (C) (OpenNI 1)
[edit | edit source]/* Display image map */
// Matthias Hernandez: 07/27/2011 - University of Southern California
// [email protected]
// display the image map from openNI input in the openCV image ‘out’
void displayImageMap(IplImage *out, const XnUInt8* pImageMap) {
uchar *data = (uchar *)out->imageData;
int step = out->widthStep;
int channels = out->nChannels;
for (int i=0; i<MAX3_HR; i+=3) {
data[i+2] = pImageMap[i];
data[i+1] = pImageMap[i+1];
data[i] = pImageMap[i+2];
}
}
从投影到真实世界的转换 (C) (OpenNI 2)
[edit | edit source]#define XtoZ 1.114880018171494f
#define YtoZ 0.836160013628620f
#define MIN_DEPTH 450
#define MAX_DEPTH 800
void convertP2RW(float *pDepth, float *pReal, int x, int y, int w, int h) {
int max_i = w * h;
int i1 = (y * w + x),
i2 = i1 + max_i,
i3 = i2 + max_i;
float Z = pDepth[i1];
if (Z > MIN_DEPTH && Z < MAX_DEPTH){
float X_rw = ( (float)x /(float)w -0.5f)*Z*XtoZ;
float Y_rw = (0.5f-(float)y / (float)h)*Z*YtoZ;
pReal[i1] = X_rw;
pReal[i2] = Y_rw;
pReal[i3] = Z;
} else {
pReal[i1] = 0.0f;
pReal[i2] = 0.0f;
pReal[i3] = 0.0f;
}
}
// Of Course, the inverse function is
void convertRW2P(float *pReal, float *pDepth,int x, int y, int w, int h) {
int max_i = w * h;
int i1 = (y * w + x),
i2 = i1 + max_i,
i3 = i2 + max_i;
float xR = pReal[i1];
float yR = pReal[i2];
float zR = pReal[i3];
int ixi = (xR/zR/XtoZ + 0.5f)*(float)w + 0.5f; // x
int iyi = (-yR/zR/YtoZ + 0.5f)*(float)h + 0.5f; // y
pDepth[i1] = zR;
}
校准的理念(使用绝对圆锥的图像)
[edit | edit source]x = P (H inv(H)) X H: a projective transformation
无穷远平面和绝对圆锥
PAI:在 H 下固定
AC:在 H 下固定
ADQ:在 H 下固定(单个方程)
Q = H Q H’ PAI is the null-vector of ADQ (Q a = 0)
DIAC(绝对圆锥的双重图像)= ADQ 的图像(参见 IAC(绝对圆锥的图像))
W* = K K'(K:校准矩阵)通过 Cholesky 因式分解。
如何从深度相机读取红外流
[edit | edit source]http://stomatobot.com/primesense-3dsensor-ir-stream/?goback=.gde_2642596_member_242724626
参考资料
[edit | edit source]- [Queirolo 2010] C. C. Queirolo,Luciano Silva,Olga R. P. Bellon,M. P. Segund,使用表面互穿测度进行 3D 人脸识别:对 FRGC 数据库的比较评估,使用模拟退火和表面互穿测度进行 3D 人脸识别。IEEE 模式分析与机器智能汇刊 32(2): 206-219 (2010)。
- [CVonline] CVonline:不断发展、分布式、非专有、在线的计算机视觉纲要
- [Foerstner04] 不确定性和射影几何
- 计算微分几何 dmg.tuwien.ac.at
- 计算几何算法库 [4]
深度传感器
[edit | edit source]PrimeSense www.primesense.com
Kinect - Xbox.com www.xbox.com/KINECT
Xtion PRO LIVE http://www.asus.com/Multimedia/Motion_Sensor/Xtion_PRO_LIVE/
softkinetic http://www.softkinetic.com/
velodynelidar.com http://velodynelidar.com/lidar/lidar.aspx
Leap 简介 http://www.youtube.com/watch?v=_d6KuiuteIA
Mesa Imaging http://mesa-imaging.ch/
工具
[edit | edit source]http://www.danielgm.net/cc/ CloudCompare - 3D 点云和网格处理软件
http://www.cs.unc.edu/~isenburg/lastools/ LAStools:用于快速转换、过滤、查看、网格化和压缩 LiDAR 的屡获殊荣的软件
问答
[edit | edit source]- {在此处提出您的问题...}