关于 QT + OpenGL 操作点云(Point Cloud)缩放、平移、旋转

版权声明:听说这里让写版权声明~~~ https://blog.csdn.net/f_zyj/article/details/81705674

近期在折腾点云可视化的一些东西, m e n t o r 让我参考 p c l _ v i e w e r 的功能自己用 Q T + O p e n G L 去实现,虽然 p c l 提供了现成的功能,但是 m e n t o r 执意让我自己去写,一方面是因为自己写可以写更灵活的功能,另一方面就是锻炼我的能力吧~~~

首先,需要将点云里的数据加载出来,按照我个人的理解,点云里的数据就是一堆点的信息, 存放在类似数组或者向量中,具体是什么没有深入研究……然后第一步是对这些点进行了一个上色的操作,读取了一个色卡,然后根据点云中点的 z 轴信息进行上色,这个也是为了看着更清晰,毕竟在之后要进行一些缩放旋转操作,全部都是一个颜色,看着可能并没有那么强的立体感?这是我个人的理解。

这是上色后的样子,未上色的样子自己跑一下 p c l _ v i e w e r 就知道了。

这里写图片描述

接着做的是放大的功能,做这个功能是要理解几个重要的矩形(其他两个功能同样要用到这个),也可以说是需要理解 O p e n G L 的坐标系统。

这里写图片描述

这个图来自于网上很知名的一个 O p e n G L 教程—— L e a r n O p e n G L   C N

不过一开始我并没有理解太明白,以为必须自己用数学函数去写矩阵进行变化,但是始终没有显示效果,后来才发现,只需要用到人家提供的函数—— g l S c a l e d ( x , y , z ) ,不过这里需要注意的是,用这个函数进行操作是在当前矩阵上进行变换,当前矩阵是一个状态机,假如说此时矩阵的功能是在 x y z 轴分别放大 15 倍,那么再执行一次 g l S c a l e d ( 2 , 2 , 2 ) 时,就是在这个基础上再放大两倍,一共是三十倍,而不是最开始的两倍,这里如果想要实现放大最开始的两倍,可以 g l L o a d I d e n t i t y ( ) 一下将状态机重置为单位阵,不过也可以将原来的状态机保存到栈内,改变放大倍数时,弹出栈内的状态机覆盖现在的再进行变换。

这里我就不需要那么麻烦了,因为需要做的是根据鼠标滑轮事件进行放大缩小,并不需要在最开始的数据上操作,只要对现在的状态进行放大 1.1 倍或者缩小 0.9 倍即可。嗯,这里鼠标事件也得了解一下,不知道为啥,我直接将 g l S c a l e d ( x , y , z ) 函数放在鼠标事件响应的函数中时,没有实现功能,所以我直接在这个里面记录一个 z o o m 来标记状态机下次的变换参数,默认为 1 ,放大时设置为 1.1 ,缩小时置为 0.9

void Widget::wheelEvent(QWheelEvent *event)
{
    qDebug() << "wheelEvent";

    if (event->delta() > 0)
    {
        zoom = 1.1;
    }
    else
    {
        zoom = 0.9;
    }

    update();
}

用鼠标事件控制 z o o m ,最后在一个 v o i d   W i d g e t :: c h a n g e V i e w p o i n t ( ) 函数内统一进行状态机的变换,包括平移旋转。

接着做平移旋转,这个和缩放实现原理相似,鼠标左键拖动时进行平移,右键拖动时进行旋转。

void Widget::mouseMoveEvent(QMouseEvent *event)
{
    qDebug() << "mouseMoveEvent";

    if (last_globalPos == QPoint(-1, -1))
    {
//        qDebug() << "last_globalPos";

        last_globalPos = event->globalPos();
    }

    if (event->buttons() == Qt::LeftButton)
    {
        move = event->globalPos() - last_globalPos;
    }
    else
    {
        angle_x = (event->globalX() - last_globalPos.x());
        angle_y = -(event->globalY() - last_globalPos.y());

//        qDebug() << angleX << angleY;
    }
    last_globalPos = event->globalPos();

    update();
}

这里我用 l a s t _ g l o b a l P o s 记录上一次响应时鼠标的位置,默认为 Q P o i n t ( 1 , 1 ) ,所以又用了鼠标释放的事件来进行重置一些关键信息。

void Widget::mouseReleaseEvent(QMouseEvent *event)
{
    qDebug() << "mouseReleaseEvent";

    zoom = 1;
    move = QPoint(0, 0);
    last_globalPos = QPoint(-1, -1);
    angle_x = angle_y = 0;

    update();
}

最后都在 v o i d   W i d g e t :: c h a n g e V i e w p o i n t ( ) 里修改状态机,然后通过 v o i d   W i d g e t :: p a i n t G L ( ) 函数进行绘制,不过此时遇见了最大的问题,那就是在平移时,由于视图放大比例不一样,视图的平移和鼠标的平移无法同步,这里就要根据视图的放大比例来进行调参了,这里需要记录目前视图的放大比例,我这里给他乘以了一个 30 z o o m _ a m o u n t 的参数,使其同步了,这里的 30 是反复修改取的一个比较适合的值,当我将视图放大三十倍时,鼠标和视图的平移基本一致,低于三十时,鼠标移动的幅度大于视图,大于三十时,恰好相反,所以最后除以 z o o m _ a m o u n t 视图的放大倍数来调节一下。

当然到这里问题并没有处理完,因为当视图旋转后,在进行平移时,发现视图平移的方向和鼠标平移的方向并不同,后来网上查到说在视图旋转后,相当于坐标系也旋转了,想要实现鼠标平移旋转和视图平移旋转的组合操作的一致,那就需要将平移的向量也进行相应的旋转,视图旋转多少弧度,平移的向量也需要旋转多少弧度。于是我推算了一下公式,对向量进行了改变后,才大功告成。

void Widget::changeViewpoint()
{
    qDebug() << "changeViewpoint";

//    glMatrixMode(GL_MODELVIEW);
//    glRotated(10 / 360, 0.0f, 0.0f, 1.0f);
    glScaled(zoom, zoom, zoom);
    zoom_amount *= zoom;
    zoom = 1;

//    qDebug() << move.x() << -move.y();
    qDebug() << angle_amount << std::cos(angle_amount / 180 * pi);
    GLdouble angle_cos = std::cos(angle_amount / 180 * pi);
    GLdouble angle_sin = std::sin(angle_amount / 180 * pi);
    GLdouble coefficient = move_coefficient / zoom_amount;
    GLdouble delta_x = (move.x() * angle_cos + move.y() * angle_sin) * coefficient;
    GLdouble delta_y = -(-move.x() * angle_sin + move.y() * angle_cos) * coefficient;
    glTranslated(delta_x,
                 delta_y,
                 0.0);
    move = QPoint(0, 0);

    glRotated((angle_x + angle_y) / 2, 0, 0, 1);
    angle_amount -= (angle_x + angle_y) / 2;
    angle_amount += round_angle;
    angle_amount = fmod(angle_amount, round_angle);
//    glRotated(angleY, 0, 1, 0);
    angle_x = angle_y = 0;

    qDebug() << zoom_amount << angle_amount;
}

这里并没有实现 p c l _ v i e w e r 中的旋转方式,这里通过 z 轴旋转视图,但是想要实现更多轴的旋转,感觉处理起来还是挺麻烦的,如果想要效果更加接近于 p c l _ v i e w e r 中的效果,可能不能通过旋转视图,而应该通过改变相机的位置。一开始并没有想到这么多,同组的大佬告诉我让我先实现按照 z 轴旋转,其他的具体需要了再改,然后我就傻乎乎的旋转了视图……如果一开始就尝试改变相机的位置,可能下一步修改代码就没有那么麻烦了。

这是最后通过缩放旋转平移后的效果,看着还挺像回事儿,现在看来这个代码写的十分简单,但是一开始真的无从下手……新手入坑难。

这里写图片描述

最后贴一下 v o i d   W i d g e t :: p a i n t G L ( ) 函数:

void Widget::paintGL()
{
    qDebug() << "paintGL";

    changeViewpoint();

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    if (!pointcloud->points.empty())
    {
        qreal r, g, b, a;
        QColor color;
        float upper = 4, lower = -4, range = upper - lower;
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE);
        glBegin(GL_POINTS);
        for (uint32_t i = 0; i < pointcloud->points.size(); ++i)
        {
            float z = pointcloud->points[i].z;
            if (z < upper && z > lower)
            {
                float percent = (z - lower) / range;
                int index = int(percent * colormap.size());
                color = colormap.at(index);
                if (percent > 0.5)
                    color.setAlphaF(0.3);
            }
            else
                continue;

            color.getRgbF(&r, &g, &b, &a);
            glColor4f(r, g, b, a);
            glVertex3f(pointcloud->points[i].x,
                       pointcloud->points[i].y,
                       pointcloud->points[i].z);
        }

        glEnd();
    }
}

其他的一些细碎的东西就不贴了吧……还是太菜了,这么简单的功能折腾了好几天。

猜你喜欢

转载自blog.csdn.net/f_zyj/article/details/81705674