基于Nuitrack的Kinect与OpenGL坐标空间匹配及渲染问题
问题描述
我已经获取了Kinect的屏幕空间坐标,现在需要解决两个核心需求:
- 将Kinect屏幕空间坐标转换为OpenGL相机的世界空间坐标,用于和其他绘制对象做碰撞检测
- 将OpenGL相机置于Kinect相同位置,查看Kinect采集的点,同时在Kinect坐标空间中渲染对象,但目前设置参数后看不到渲染对象,且当Kinect检测到手部时,原本在原点正常渲染的三角形会停止显示。
已尝试参数与异常现象
我用以下参数生成投影矩阵,但无法看到渲染对象:
- 近裁剪面:
0.01f - 远裁剪面:
1700.0f - 水平FOV:
60.0f - 垂直FOV:
45.0f - 相机位置:
(0.0f, 2.0f, 3.0f) - 相机朝向:
(0.0f, 2.0f, -3.0f)
当Kinect检测到手部时,原本在原点正常渲染的三角形几何图形停止渲染。
相关代码
初始化代码
GameRenderer::GameRenderer() { tracker = new GestureTracker(); tracker->init(); tracker->startGestureDetection(); } bool GameRenderer::Init(int argc, char* argv[]) { GLfloat points[] = { 0.0f, 0.5f, 0.0f, 0.5f, -0.5f, 0.0f, -0.5f, -0.5f, 0.0f }; GLfloat point[] = { 0.0f, 0.0f, 0.0f }; glGenBuffers(1, &m_vbo); glBindBuffer(GL_ARRAY_BUFFER, m_vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_DYNAMIC_DRAW); // VAO初始化 glGenVertexArrays(1, &m_vao); glBindVertexArray(m_vao); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, m_vbo); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL); // 顶点着色器 const char *vertex_shader = "#version 410\n" "in vec3 vp;" "uniform mat4 model_matrix;" "uniform mat4 view_matrix;" "uniform mat4 projection_matrix;" "void main () {" " gl_Position = projection_matrix * view_matrix * model_matrix * vec4(vp, 1.0);" "}"; // 片段着色器 const char *fragment_shader = "#version 410\n" "out vec4 frag_colour;" "void main () {" " frag_colour = vec4(0.0f, 0.5f, 1.0f, 1.0f);" "}"; // 编译顶点着色器 m_vert_shader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(m_vert_shader, 1, &vertex_shader, NULL); glCompileShader(m_vert_shader); int params = -1; glGetShaderiv(m_vert_shader, GL_COMPILE_STATUS, ¶ms ); if (GL_TRUE != params) { ShaderLog(m_vert_shader); return 1; } // 编译片段着色器 m_frag_shader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(m_frag_shader, 1, &fragment_shader, NULL); glCompileShader(m_frag_shader); params = -1; glGetShaderiv(m_frag_shader, GL_COMPILE_STATUS, ¶ms ); if (GL_TRUE != params) { ShaderLog(m_frag_shader); return 1; } // 链接着色器程序 m_shader_program = glCreateProgram(); glAttachShader(m_shader_program, m_frag_shader); glAttachShader(m_shader_program, m_vert_shader); glLinkProgram(m_shader_program); // 初始化模型矩阵 this->m_model_matrix = glm::mat4(1.0f); m_model_matrix = glm::scale(m_model_matrix, glm::vec3(4.0f)); m_model_matrix = glm::translate(m_model_matrix, glm::vec3(0.0f)); this->m_view_matrix = glm::mat4(1.0f); this->m_proj_matrix = glm::mat4(1.0f); // 设置模型矩阵Uniform GLuint model_mat_location = glGetUniformLocation(this->m_shader_program, "model_matrix"); glUseProgram(this->m_shader_program); glUniformMatrix4fv(model_mat_location, 1, GL_FALSE, glm::value_ptr(this->m_model_matrix)); return 1; }
绘制代码
void GameRenderer::Draw(Camera& camera) { auto hands = tracker->getHands(); if(hands.size()) { Point3f coordinates = hands[0].getHandPosition(); glBindBuffer(GL_ARRAY_BUFFER, m_vbo); GLfloat hand_coordinate[3] = { coordinates.x, coordinates.y, coordinates.z }; std::cout << coordinates.x << " " << coordinates.y << " " << coordinates.z << std::endl; // 更新模型矩阵 this->m_model_matrix = glm::mat4(1.0f); m_model_matrix = glm::scale(m_model_matrix, glm::vec3(10.0f)); m_model_matrix = glm::translate(m_model_matrix, glm::vec3(coordinates.x, coordinates.y, coordinates.z)); glUseProgram(this->m_shader_program); GLuint loc = glGetUniformLocation(this->m_shader_program, "model_matrix"); glUniformMatrix4fv(loc, 1, GL_FALSE, glm::value_ptr(this->m_model_matrix)); glUseProgram(0); } // 更新视图和投影矩阵 this->m_view_matrix = camera.GetViewMatrix(); this->m_proj_matrix = glm::perspective(glm::radians(camera.GetFieldOfView()), 60.0f/45.0f, camera.GetNearPlane(), camera.GetFarPlane()); // 设置视图矩阵Uniform GLuint view_mat_location = glGetUniformLocation(this->m_shader_program, "view_matrix"); glUseProgram(this->m_shader_program); glUniformMatrix4fv(view_mat_location, 1, GL_FALSE, glm::value_ptr(this->m_view_matrix)); // 设置投影矩阵Uniform GLuint proj_mat_location = glGetUniformLocation(this->m_shader_program, "projection_matrix"); glUseProgram(this->m_shader_program); glUniformMatrix4fv(proj_mat_location, 1, GL_FALSE, glm::value_ptr(this->m_proj_matrix)); CheckGLError(); // 绘制三角形 glUseProgram(m_shader_program); glBindVertexArray(m_vao); glBindBuffer(GL_ARRAY_BUFFER, m_vbo); glDrawArrays(GL_TRIANGLES, 0, 3); glUseProgram(0); CheckGLError(); }
Kinect手部坐标示例
x y z -75.1628 136.374 650.176 -73.7582 141.239 665.259 -69.9152 148.691 702.163 -67.1218 151.315 723.226 -64.4887 153.013 745.074 -62.3365 153.783 767.393 -62.3365 153.783 767.393 -60.1887 152.979 789.881 -57.9948 150.762 812.395 -55.8624 146.575 834.187 -56.2673 133.543 873.035
解决方案
我们从坐标空间匹配、渲染逻辑错误两个核心方向拆解问题:
1. 先解决坐标系不匹配的核心问题
Kinect和OpenGL的坐标系存在两个关键差异:
- 单位差异:Kinect输出的坐标单位是毫米,OpenGL默认使用米级单位,直接使用会导致物体尺寸/位置异常
- 轴向差异:Kinect的Z轴向前(远离设备),而OpenGL默认Z轴向后(朝向观察者),需要翻转Z轴匹配
2. 修复相机视图矩阵
你当前的相机位置(0,2,3)和朝向(0,2,-3)完全偏离Kinect的视野范围,导致手部坐标(Z≈650mm=0.65m)不在相机视锥体内。正确的做法是将相机置于Kinect的原点,看向Kinect的前方:
// 将相机放在Kinect位置,看向Z轴正方向(Kinect前方),同时翻转Z轴适配OpenGL坐标系 this->m_view_matrix = glm::lookAt( glm::vec3(0, 0, 0), // 相机位置=Kinect原点 glm::vec3(0, 0, 1), // 看向Kinect前方(Z轴正方向) glm::vec3(0, 1, 0) // 上方向与Kinect一致 ); this->m_view_matrix = glm::scale(m_view_matrix, glm::vec3(1, 1, -1)); // 翻转Z轴适配OpenGL
3. 修复投影矩阵
你当前用固定的60/45作为宽高比是错误的,应该使用窗口实际宽高比;另外,用Kinect的实际内参生成投影矩阵比近似FOV更准确:
// 从Kinect SDK获取实际内参(以下为Kinect v2示例值) float depthWidth = 512.0f; float depthHeight = 424.0f; float fx = 365.456f; float fy = 365.456f; float cx = 255.948f; float cy = 205.878f; float n = 0.01f; float f = 1.7f; // 1700mm=1.7m // 用内参生成透视投影矩阵 float left = -cx * n / fx; float right = (depthWidth - cx) * n / fx; float bottom = -cy * n / fy; float top = (depthHeight - cy) * n / fy; this->m_proj_matrix = glm::frustum(left, right, bottom, top, n, f);
4. 修复模型矩阵的运算逻辑
glm的矩阵运算顺序是从右到左,你当前先缩放再平移的顺序会导致缩放基于世界原点而非模型自身,同时要转换单位并翻转Z轴:
if(hands.size()) { Point3f coordinates = hands[0].getHandPosition(); // 转换单位(毫米→米)+ 翻转Z轴适配OpenGL glm::vec3 kinectPos(coordinates.x/1000.0f, coordinates.y/1000.0f, -coordinates.z/1000.0f); this->m_model_matrix = glm::mat4(1.0f); // 正确顺序:先平移到目标位置,再缩放 m_model_matrix = glm::translate(m_model_matrix, kinectPos); m_model_matrix = glm::scale(m_model_matrix, glm::vec3(0.05f)); // 缩放为合适大小(比如5cm的三角形) glUseProgram(this->m_shader_program); GLuint loc = glGetUniformLocation(this->m_shader_program, "model_matrix"); glUniformMatrix4fv(loc, 1, GL_FALSE, glm::value_ptr(this->m_model_matrix)); // 不要在这里关闭shader,保持激活直到绘制完成 }
5. 屏幕空间到世界空间的映射方法
要实现Kinect屏幕坐标到OpenGL世界空间的转换,需要以下步骤:
- 从Kinect SDK获取深度相机的内参(
fx, fy, cx, cy)和外参(Kinect相对于OpenGL世界的位置/姿态) - 用内参生成投影矩阵,用外参生成视图矩阵
- 使用
glm::unProject完成坐标转换:
// screenPos:Kinect屏幕坐标(x,y) + 深度值(毫米转米) glm::vec3 screenPos(x, y, depth/1000.0f); glm::vec4 viewport(0, 0, depthWidth, depthHeight); // 转换为OpenGL世界空间坐标 glm::vec3 worldPos = glm::unProject(screenPos, m_view_matrix, m_proj_matrix, viewport);
6. 其他代码细节修复
- 不要频繁切换shader程序,保持shader激活状态直到绘制完成,避免Uniform设置失效
- 确保Kinect的深度值在近/远裁剪面范围内(0.01m~1.7m)
内容的提问来源于stack exchange,提问作者darth vader
相关产品推荐
相关产品推荐

