QT集成IntelRealSense双目摄像头3,3D显示

前两篇文章,介绍了如何继承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(等待)等效果。

完整代码见文章附件。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/64720.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Postman[4] 环境设置

作用&#xff1a;不同的环境可以定义不同的参数&#xff0c;在运行请求时可以根据自己的需求选择需要的环境 1.创建Environment 步骤&#xff1a; Environment-> ->命名->添加环境变量 2.使用Environment 步骤&#xff1a;Collection- >右上角选择需要的环境

【合并区间】

问题 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。 请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。示例 1&#xff1a; 输入&#xff1a;intervals …

SpringBoot_第二天

SpringBoot_第二天 学习目标 Mybatis整合&数据访问 使用SpringBoot开发企业项目时&#xff0c;持久层数据访问是前端页面数据展示的基础&#xff0c;SpringBoot支持市面上常见的关系库产品(Oracle,Mysql,SqlServer,DB2等)对应的相关持久层框架&#xff0c;当然除了对于关系…

SparseViT:基于稀疏编码Transformer的非语义中心、参数高效的图像篡改定位

摘要 https://arxiv.org/pdf/2412.14598 非语义特征或语义无关特征&#xff0c;与图像上下文无关但对图像篡改敏感&#xff0c;被认为是图像篡改定位&#xff08;IML&#xff09;的重要证据。由于无法获得人工标签&#xff0c;现有工作依赖于手工方法提取非语义特征。手工非语…

Redisson 分布式锁获取tryLock和lock的区别

问题 boolean isLock lock.tryLock(10, 30, TimeUnit.SECONDS); boolean isLock lock.lock(30, TimeUnit.SECONDS); boolean isLock lock.lock(); 三者的区别&#xff1f;&#xff1f; 这三个方法都是用于获取 Redisson 分布式锁的&#xff0c;但它们在获取锁的方式和行为…

【git】git生成rsa公钥的方法

git生成rsa公钥的方法 一&#xff0c;简介二&#xff0c;操作方法三&#xff0c;总结 一&#xff0c;简介 在工作的过程中&#xff0c;经常需要生成rsa的密钥&#xff0c;然后提供给别人&#xff0c;然后别人给你开通代码下载权限。本文介绍如何在本地生成rsa的密钥供参考。 …

Zookeeper模式安装Kafka(含常规、容器两种安装方式)

一、#创作灵感# 公司使用Kafka的软件项目较多&#xff0c;故写技术笔记巩固知识要点 二、软件环境 - Kafka 3.9.0 官方下载地址&#xff1a;Kafka 3.9.0 - ZooKeeper 3.9.3 官方下载地址&#xff1a;ZooKeeper 3.9.3 - Docker Desktop 4.37 容器图形化工具 官方下载地址…

7.傅里叶级数练习题

7.傅里叶级数练习题 设函数&#xff1a; f ( x ) { − x , 0 ≤ x ≤ 1 2 , 2 − 2 x , 1 2 < x < 1 , f(x) \begin{cases} -x, & 0 \leq x \leq \frac{1}{2}, \\ 2 - 2x, & \frac{1}{2} < x < 1, \end{cases} f(x){−x,2−2x,​0≤x≤21​,21​<x&…

【高项】信息系统项目管理师(二)项目管理概论

一、PMBOK的发展 项目管理知识体系&#xff08;PMBOK&#xff09;是由美国项目管理协会&#xff08;PMI&#xff09;开发的一套描述项目管理专业范围的知识体系&#xff0c;包含了对项目管理所需的知识、技能和工具的描述。 二、项目基本要素 2.1 项目基础 项目是为提供一项…

C++设计模式:状态模式(自动售货机)

什么是状态模式&#xff1f; 状态模式是一种行为型设计模式&#xff0c;它允许一个对象在其内部状态发生改变时&#xff0c;动态改变其行为。通过将状态相关的逻辑封装到独立的类中&#xff0c;状态模式能够将状态管理与行为解耦&#xff0c;从而让系统更加灵活和可维护。 通…

【Pytorch实用教程】循环神经网络中使用dropout需要注意的问题

文章目录 问题解答警告的具体含义解决方案示例代码总结问题 UserWarning: dropout option adds dropout after all but last recurrent layer, so non-zero dropout expects num_layers greater than 1, but got dropout=0.3 and num_layers=1 warnings.warn("dropout op…

数据中台与数据治理服务方案[50页PPT]

本文概述了数据中台与数据治理服务方案的核心要点。数据中台作为政务服务数据化的核心&#xff0c;通过整合各部门业务系统数据&#xff0c;进行建模与加工&#xff0c;以新数据驱动政府管理效率提升与政务服务能力增强。数据治理则聚焦于解决整体架构问题&#xff0c;确保数据…

postgresq-自定义执行计划(custom plan)与generic plan(通用执行计划)

文章目录 之前写过一篇关于 PostgreSQL prepare sql的文章&#xff0c;但当时没有提到generic plan(通用计划)和custom plan(自定义计划)这两个概念。现在将通过举例介绍这两个概念。 创建测试表&#xff1a; postgres# create database demo; CREATE DATABASE postgres# \c d…

dockfile 配置 /etc/apt/source.list.d/debian.list 清华镜像

docker:3.12.7 镜像使用的是 debian 系统&#xff0c;比 ubuntu 更轻量。debian 系统内&#xff0c;apt 镜像源列表位于 /etc/apt/source.list.d/debian.list&#xff08;作为对比&#xff0c;ubuntu 的镜像列表位于 /etc/apt/source.list&#xff0c;二者语法相同&#xff09;…

程序员测试日常小工具

作为一名程序员&#xff0c;或者测试人员&#xff0c;日常工作最常用的工具有哪些&#xff0c;截图&#xff0c;截图漂浮&#xff0c;翻译&#xff0c;日期处理&#xff0c;api调用...&#xff0c; 当你拿到一串报文后&#xff0c;想要json转换时&#xff0c;是不是要打…

【MySQL高级】第1-4章

第1章 存储过程 1.1 什么是存储过程&#xff1f; 存储过程可称为过程化SQL语言&#xff0c;是在普通SQL语句的基础上增加了编程语言的特点&#xff0c;把数据操作语句(DML)和查询语句(DQL)组织在过程化代码中&#xff0c;通过逻辑判断、循环等操作实现复杂计算的程序语言。 换…

深入浅出:AWT事件监听器及其应用

前言 在Java的GUI编程中&#xff0c;事件处理是非常重要的一环。AWT&#xff08;Abstract Window Toolkit&#xff09;框架提供了灵活的事件处理机制&#xff0c;使得开发者能够响应用户的操作&#xff0c;例如点击按钮、键盘输入、鼠标点击等。AWT的事件监听器就是实现这一机…

【Rust自学】8.5. HashMap Pt.1:HashMap的定义、创建、合并与访问

8.5.0. 本章内容 第八章主要讲的是Rust中常见的集合。Rust中提供了很多集合类型的数据结构&#xff0c;这些集合可以包含很多值。但是第八章所讲的集合与数组和元组有所不同。 第八章中的集合是存储在堆内存上而非栈内存上的&#xff0c;这也意味着这些集合的数据大小无需在编…

混合合并两个pdf文件

混合两个pdf 1、在线免费交替和混合奇数和偶数PDF页面2、有什么软件把两个 PDF 交叉合并&#xff1f;3、pdfsam本地合并 如何Google翻译的原文和译文合并&#xff0c;&#xff08;沉浸式翻译效果相对较好&#xff09; 1、在线免费交替和混合奇数和偶数PDF页面 https://deftpd…

Hutool 发送 HTTP 请求的几种常见写法

最简单的 GET 请求&#xff1a; String result HttpUtil.get("https://www.baidu.com");带参数的 GET 请求&#xff1a; // 方法1: 直接拼接URL参数 String result HttpUtil.get("https://www.baidu.com?name张三&age18");// 方法2: 使用 HashMap…