前兩篇文章,介紹了如何繼承intel realsense相機和opengl。
這里介紹如何給深度數據和色彩數據一塊顯示到opengl里面。
首先,需要了解深度數據和彩色數據是如何存儲的。先說彩色數據。彩色圖像一般都是RGB,也就是每個像素有三個字節,分別是RGB,如果一個圖像寬度是2,高度也是2。就是一共有4個像素。如下圖。像素順序是從左上到右下,依次排列。
假設第一個像素是紅色,就是(255,0,0),第二個字節是(0,255,0),第三個是(0,0,255),最后一個是(0,0,0)。這個圖像數據全部的內存就是0,255,0,0,0,255,0,0,255,0,0,0。一共2*2*3=12個字節。實際就是按左右到右下的順序,給每個點的rgb值放到一塊。
依次類推,寬width,高height的圖像數據,實際上就是按左上到右下,width*height個RGB數據放到了一起。就是width*height*3個字節。
然后是深度數據,深度數據是16位,就是兩個字節的無符號數,實際上就是按左上到右下的順序,給4個unsigned short值放到了一塊例如 800,1000,200,30。每個unsigned short兩個字節,共2*2*2=8個字節。
依次類推,寬是width,高是height的深度數據,就是按左上到右下,width*height個unsigned short放到一起,共width*height*2個字節。
有了上面的前題,在Camera類里面增加兩個指針,分別存儲彩色圖像數據,和深度數據,增加兩個整數,用于存儲圖像寬度和高度,以及增加兩個QImage,分別是彩色和深度圖像。
第一步修改Camera類,每次抓取到圖像,給深度數據拷貝到自己準備的緩存區。同時創建兩個用于顯示的深度圖和彩色圖。
修改后的Camera類:
#ifndef CAMERA_H
#define CAMERA_H#include <QObject>
#include <QThread>
#include <librealsense2/rsutil.h>
#include <librealsense2/rs.hpp>
#include <QImage>
class Camera :public QThread
{Q_OBJECT
public:Camera();static Camera* getInstance();//單例模式void startCapture(); //開始采集void stopCapture(); //結束采集void run() override; //線程函數unsigned short * depthData;//深度數據緩存區,需要初始化才能使用unsigned char* colorData;;//存儲顏色數據緩存區,需要初始化才能使用int width; //寬度int height; //高度QImage colorImage; //色彩圖片QImage depthImage; //深度圖片
private slots:void doCam(int code,QString msg); //用于接收線程信息,并彈出消息框
signals:void onCameraEvent(int code,QString msg); //線程內部發送消息void onFrame(); //線程內部發送圖像采集成功信號
private:bool isCapture; //是否采集中rs2::pipeline pipe; //采集采集對象
};#endif // CAMERA_H
#include "camera.h"
#include <QDebug>
#include <QException>
#include <common.h>
#include <GL/glu.h>
Camera::Camera()
{width=height=0;//沒有獲取到數據之前,是0colorData=NULL;//沒有數據之前是空depthData=NULL;//沒有數據之前是空connect(this,&Camera::onCameraEvent,this,&Camera::doCam);//鏈接線程內部的信號
}
/*** @brief Camera::getInstance 單例模式* @return*/
Camera* Camera::getInstance(){static Camera* ins=NULL;//static代碼,唯一對象if(ins==NULL)ins=new Camera();//創建一個實力return ins;
}void Camera::startCapture(){loading("打開攝像頭",0);//顯示打開攝像頭彈窗,一直顯示,直到成功或是失敗this->start();//開始子線程
}
/*** @brief Camera::stopCapture 停止采集*/
void Camera::stopCapture(){isCapture=false;//退出子線程pipe.stop();//停止相機
}
/*** @brief Camera::doCam 接收線程的消息,并彈窗顯示* @param code 消息編碼,0表示成功* @param msg 消息內容*/
void Camera::doCam(int code,QString msg){if(code==0)success(msg);else error(msg);
}
/*** @brief Camera::run 線程函數,用于打開相機和采集圖像*/
void Camera::run(){try {pipe.start(); //嘗試打開相機}catch (const std::exception& e){emit onCameraEvent(1, e.what() ); //如果沒有成功,發送給主線程一個消息return;//返回,不繼續了}emit onCameraEvent(0,"相機打開成功"); //如果打開成功,也發送一個rs2::colorizer color_map;//用于將深度數據,轉換為圖像數據色彩數據的map,isCapture=true;//循環控制變量while(isCapture){try {rs2::frameset data= pipe.wait_for_frames(); //獲取一幀數據 rs2::frame colo=data.get_color_frame(); //獲取彩色圖像rs2::frame dept=data.get_depth_frame(); //獲取深度數據,并轉換為可見圖if(width==0 || height==0){ //如果沒有初始化width = dept.as<rs2::video_frame>().get_width(); //獲取圖像寬度height= dept.as<rs2::video_frame>().get_height(); //獲取圖像高度depthData=(unsigned short*)malloc(2*width*height); //內存初始化,深度數據是16位,就是兩個字節。總字節數量就是寬度*高度*2colorData=(unsigned char*)malloc(3*width*height); //彩色內存初始化,彩色數據RGB,就是3個字節,總字節就是寬度*高度*3}if(depthData!=NULL){memcpy(depthData,dept.get_data(),width*height*2); //拷貝深度圖像數據depthImage= QImage((uchar*)dept.apply_filter(color_map).get_data(),width,height,QImage::Format_RGB888); //深度數據創建一個圖片}if(colorData!=NULL){memcpy(colorData,colo.get_data(),width*height*3); //拷貝彩色圖像數據colorImage=QImage((uchar*)colo.get_data(),width,height,QImage::Format_BGR888);//彩色數據創建一個圖片} emit onFrame(); //發送消息,數據準備好了}catch (const std::exception& e) {emit onCameraEvent(2,e.what());}}
}
第二步:在GLwidget類里面繪制三維數據。
具體的原理是,監聽Camera類的onFrame事件,每次監聽到,就重新繪制。繪制的時候,循環像素的行和列,根據行x列y計算當前像素的下下標i=x+y*width。根據下標,去深度數據里找到對應的深度。去色彩數據尋找對應的RGB值。然后在gl繪制一個點就行了。核心代碼:
Camera* cam=Camera::getInstance();//獲取相機示例int disx=cam->width/2;//x方向偏移,到中心點int disy=cam->height/2;//y方向偏移,到中心點int disz=1000;//這個是隨便寫的glBegin(GL_POINTS);//開始繪制點for(int y=0;y<cam->height;y++){//循環行for(int x=0;x<cam->width;x++){//循環列int i=x+y*cam->width;//計算當前像素的下標,int z=cam->depthData[i];//深度if(z!=0){glColor3f(cam->colorData[i*3]/255.0,cam->colorData[i*3+1]/255.0,cam->colorData[i*3+2]/255.0); //rgb是0-255,opengl用0-1表示glVertex3f(x-disx,disy-y,disz-z);//繪制一個點}}}glEnd();
完整GLWidget類代碼:
#ifndef GLWIGET_H
#define GLWIGET_H#include <QObject>
#include <QGLWidget>
#include <GL/gl.h>
#include <GL/glu.h>
#include <QMouseEvent>
#include <QWheelEvent>
class GLWidget : public QGLWidget
{Q_OBJECT
public:GLWidget();static GLWidget* getInstance(); //單例模式
protected:void resizeGL(int w, int h) override; //窗口大小改變的時候,gl重新初始化void initializeGL() override; //初始化glvoid paintGL() override; //核心繪制void mousePressEvent(QMouseEvent *event) override;void mouseMoveEvent(QMouseEvent *event) override;void mouseReleaseEvent(QMouseEvent *event) override;void wheelEvent(QWheelEvent *event) override;
private slots:void onFrame();//響應數據采集到事件
private:double r;//相機距離中心的的半徑double degZ;//在水平面的角度double degY;//在垂直面的角度double camx,camy,camz;//相機位置void refresh();//重新繪制QPoint old;//鼠標原始位置bool isPressed;//是否按下double rate;//用于換算弧度的比例
};#endif // GLWIGET_H
#include "glwidget.h"
#include <qmath.h>
#include <QDebug>
#include <camera.h>
GLWidget::GLWidget()
{r=1000;//默認距離degY=degZ=0;camz=r*qCos(degZ);camx=r*qSin(degZ);camy=r*qSin(degY);connect(Camera::getInstance(),&Camera::onFrame,this,&GLWidget::onFrame);//鏈接相機采集到數據事件
}/*** @brief GLWidget::getInstance 單例模式* @return*/
GLWidget* GLWidget::getInstance(){GLWidget* ins=NULL;if(ins==NULL)ins=new GLWidget();return ins;
}
/*** @brief GLWidget::initializeGL 初始化opengl。可以步寫*/
void GLWidget::initializeGL(){}
/*** @brief GLWidget::resizeGL 窗口大小改變* @param w* @param h*/
void GLWidget::resizeGL(int w, int h){glViewport(0,0,w,h); //重新適應窗口大小glMatrixMode (GL_PROJECTION);glLoadIdentity ();gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1, 2000.0); //設置相機投影參數glMatrixMode(GL_MODELVIEW);glLoadIdentity();}
/*** @brief GLWidget::paintGL 繪制核心方法*/
void GLWidget::paintGL(){glClearColor(0,0,0,0); //背景色glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);//清除上一次的緩存glLoadIdentity(); //加載單位矩陣gluLookAt(camx,camy,camz, 0,0,0, 0.0,1.0,0); //設置相機Camera* cam=Camera::getInstance();//獲取相機示例int disx=cam->width/2;//x方向偏移,到中心點int disy=cam->height/2;//y方向偏移,到中心點int disz=1000;//這個是隨便寫的glBegin(GL_POINTS);//開始繪制點for(int y=0;y<cam->height;y++){//循環行for(int x=0;x<cam->width;x++){//循環列int i=x+y*cam->width;//計算當前像素的下標,int z=cam->depthData[i];//深度if(z!=0){glColor3f(cam->colorData[i*3]/255.0,cam->colorData[i*3+1]/255.0,cam->colorData[i*3+2]/255.0); //rgb是0-255,opengl用0-1表示glVertex3f(x-disx,disy-y,disz-z);//繪制一個點}}}glEnd();}
/*** @brief GLWidget::onFrame 如果有數據,就重新繪制一下*/
void GLWidget::onFrame(){this->refresh();
}
/*** @brief GLWidget::refresh 重新計算相機位置*/
void GLWidget::refresh(){camz=r*qCos(degZ);camx=r*qSin(degZ);camy=r*qSin(degY);this->update();
}void GLWidget::wheelEvent(QWheelEvent *event){qDebug()<<r<<event->delta();r+=event->delta()/20;if(r<20)r=20;if(r>1800)r=1800;refresh();
}
void GLWidget::mouseMoveEvent(QMouseEvent *event){if(isPressed){QPoint p=event->pos();degZ+=(p.x()-old.x())/5.0;degY+=(p.y()-old.y())/5.0;old=p;refresh();}
}void GLWidget::mousePressEvent(QMouseEvent *event){if(event->button()==Qt::LeftButton){isPressed=true;old=event->pos();}
}
void GLWidget::mouseReleaseEvent(QMouseEvent *event){if(event->button()==Qt::LeftButton){isPressed=false;}
}
運行效果:
?PS:common文件里面有一個用于彈出提示的工具,仿的andoroid系統toast效果,實現tip,success,error,loading(等待)等效果。
完整代碼見文章附件。