You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Nuitrack的Kinect与OpenGL坐标空间匹配及渲染问题

问题描述

我已经获取了Kinect的屏幕空间坐标,现在需要解决两个核心需求:

  1. 将Kinect屏幕空间坐标转换为OpenGL相机的世界空间坐标,用于和其他绘制对象做碰撞检测
  2. 将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, &params ); 
    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, &params ); 
    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世界空间的转换,需要以下步骤:

  1. 从Kinect SDK获取深度相机的内参(fx, fy, cx, cy)和外参(Kinect相对于OpenGL世界的位置/姿态)
  2. 用内参生成投影矩阵,用外参生成视图矩阵
  3. 使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 05:26:23