利用VS2019和MFC结合OpenCV和RealSense D435i实现视频流显示
Intel RealSense D435i深度相机是Intel RealSense D400系列的一部分,它以其精准的深度感知能力、高帧率视频捕获和丰富的集成传感器而著称。D435i相机配备了一个立体深度传感器,一个高分辨率RGB相机以及一个惯性测量单元(IMU),可以提供精确的运动追踪和姿态估算。D435i的深度传感器可以在0.16米到10米的距离范围内提供准确的深度数据。其RGB相机支持192
简介:本项目介绍如何结合Visual Studio 2019、MFC、OpenCV 3.4.5和Intel RealSense D435i相机来开发一个能够捕获、显示和保存RGB及Depth图像的应用程序。介绍涉及的技术包括C++开发环境搭建、MFC界面设计、OpenCV图像处理,以及使用RealSense SDK 2.0与相机交互。项目旨在提供深度相机视频流的实时处理和显示,以及如何在用户界面中实现视频流的捕获和保存功能。 
1. Visual Studio 2019集成开发环境
Visual Studio 2019是微软公司推出的一款功能强大的集成开发环境(IDE),支持各种编程语言,为开发者提供了一站式的应用开发解决方案。本章将深入探讨如何在Visual Studio 2019中快速启动项目、配置环境以及管理项目资源。
1.1 IDE概览
Visual Studio 2019的用户界面设计更加现代化,整合了先前版本的功能,并引入了“Visual Studio Installer”作为安装和维护工具。开发者可以基于不同的项目需求选择合适的安装选项,包括.NET开发、C++开发、Web开发等。
1.2 项目创建与管理
在Visual Studio 2019中,创建新项目非常便捷。用户可以访问“文件”->“新建”->“项目”,然后根据项目类型选择相应的模板。例如,MFC应用程序的开发可以选择“C++”->“Windows 桌面”->“MFC应用程序”。
1.3 集成开发环境深度配置
为了提升开发效率,开发者可以根据自己的习惯对IDE进行深度配置。这包括设置代码编辑器的快捷键、调整窗口布局、安装扩展插件等。此外,使用“选项”对话框,可以对项目、调试器、版本控制等进行详细设置。
Visual Studio 2019提供的这些功能使得开发者能够更加专注于代码的编写与项目开发,而不是环境的配置,大大提高了工作效率。接下来的章节,我们将深入探讨MFC的图形用户界面编程。
2. MFC图形用户界面编程
2.1 MFC基础概念解析
2.1.1 MFC与Win32 API的关系
MFC(Microsoft Foundation Classes)是一个为简化Windows应用程序开发而提供的一个C++类库。它在底层封装了大量的Win32 API,为开发者提供了一个更为简洁和面向对象的编程方式。尽管MFC封装了很多功能,但其核心仍然依赖于Win32 API。理解MFC和Win32 API的关系对于深入学习MFC编程至关重要。
在早期的Windows开发中,程序员需要直接使用Win32 API,这意味着需要频繁地处理消息循环、窗口过程函数以及各种系统调用。这些操作往往包含大量重复且复杂的代码。MFC通过其封装层抽象了这些底层细节,使得开发者可以使用类和对象的方式来构建应用程序。
例如,一个简单的MFC窗口创建过程不需要程序员去处理WM_CREATE消息或者注册窗口类,只需要通过CFrameWnd类及其派生类就能轻松实现。MFC为常用的窗口类型、控件和消息处理提供了相应的类和方法。
尽管MFC简化了Windows编程,但它并没有完全替代Win32 API。在某些高级或特殊情况下,当MFC提供的功能不能满足需求时,开发者仍然需要直接调用Win32 API。因此,MFC与Win32 API之间的关系可以被视为一种互补关系,MFC在高层提供便利的同时,底层依然需要Win32 API的支持。
2.1.2 MFC应用程序框架结构
MFC应用程序框架是一种基于文档/视图(Document/View)结构的框架模式。这种结构将数据处理与数据显示分离,其中文档(Document)类负责数据的存储和管理,而视图(View)类则负责数据的显示和用户交互。
文档/视图架构的主要组件包括:
CDocument:代表应用程序中的数据,并负责管理数据的加载和保存。CView:提供数据的可视化表示,并处理用户输入。CFrameWnd:是应用程序窗口,可以包含菜单、工具栏和其他视图窗口。
此架构有以下几个关键特性:
- 分离关注点 :文档和视图的分离允许不同的视图显示相同的数据,比如可以同时有文本视图和图形视图显示同一数据文档。
- 重用性 :开发者可以重用同一文档类与不同的视图类配合,从而实现不同形式的数据展示。
- 可扩展性 :通过继承MFC类,开发者可以轻松扩展框架功能,实现特定需求。
整个应用程序框架从主应用程序对象开始,这个对象负责启动应用程序并初始化整个框架,包括创建文档模板、创建应用程序的主窗口,以及处理消息循环。
框架结构的设计使得应用程序易于扩展和维护,同时也让MFC应用程序有着相对统一的用户界面和操作习惯,这对于提升开发效率和应用程序质量都是非常有益的。
2.2 MFC界面设计与事件处理
2.2.1 创建主窗口与子窗口
在MFC应用程序中创建一个主窗口和子窗口是一个核心任务,它涉及到对MFC框架的理解和操作。MFC提供了 CFrameWnd 类用于创建主窗口,而 CMDIFrameWnd 或 CMDIChildWnd 分别用于创建MDI(Multiple Document Interface)的父窗口和子窗口。
主窗口创建流程
创建主窗口一般需要以下步骤:
-
继承
CFrameWnd类: 创建一个新的类并继承自CFrameWnd,并重写OnCreateClient或OnCreateCmdUI方法以添加自定义功能。 -
初始化窗口: 使用
Create函数创建窗口,并通过构造函数传递窗口的样式参数,如窗口标题、大小、位置等。 -
添加菜单和工具栏: 通常,主窗口会有自己的菜单栏和工具栏,可以通过
LoadFrame或CreateMenuBar等函数加载和创建。 -
设置窗口属性: 可以设置窗口的背景色、边框样式等属性。
示例代码段:
class CMyFrame : public CFrameWnd
{
public:
CMyFrame() {
Create(NULL, _T("My Application"));
LoadFrame(IDR_MAINFRAME);
}
};
CMyFrame theAppFrame;
theAppFrame.ShowWindow(SW_SHOW);
theAppFrame.UpdateWindow();
子窗口创建流程
创建子窗口的过程与创建主窗口类似,只不过使用的类是 CMDIChildWnd 或其派生类。MDI应用程序可以容纳多个子窗口,每个窗口都代表一个文档实例。
-
继承
CMDIChildWnd类: 创建一个新的类继承自CMDIChildWnd,并重写需要的方法。 -
注册子窗口类: 使用
AfxRegisterWndClass函数注册一个窗口类,该类将用于创建子窗口。 -
创建子窗口: 在主窗口类中使用
CreateMDIChild函数创建子窗口。
示例代码段:
class CMyChild : public CMDIChildWnd
{
public:
CMyChild() {
Create(NULL, _T("My Child Window"));
}
};
// 在CMDIFrameWnd派生类中创建子窗口
void CMyFrame::OnMdiCreate()
{
new CMyChild;
}
// 在菜单项处理函数中也可以调用创建子窗口的函数
void CMyFrame::OnMenuMdiNew()
{
OnMdiCreate();
}
2.2.2 事件映射与消息处理
MFC应用程序的消息处理机制是基于消息映射的。消息映射是一种机制,它允许程序员将特定的消息与相应的处理函数相映射。当应用程序接收到消息时,MFC会查找映射表,并调用相应的消息处理函数进行处理。
消息映射机制
MFC的消息映射机制通过宏定义实现,主要的宏有 BEGIN_MESSAGE_MAP 、 END_MESSAGE_MAP 和 ON_COMMAND 等。每个窗口类都可以有自己的消息映射,以处理特定的消息。
消息处理函数通常以 On 开头,后跟消息名称。例如,对于一个窗口菜单项点击事件,消息处理函数可能看起来像这样:
void CMyFrame::OnFileExit()
{
PostMessage(WM_CLOSE);
}
事件映射宏
MFC中定义了一系列事件映射宏,它们用于将窗口消息与处理函数进行关联。例如,对于鼠标事件,宏可能如下所示:
BEGIN_MESSAGE_MAP(CMyFrame, CFrameWnd)
ON_WM_CREATE()
ON_COMMAND(ID_FILE_EXIT, &CMyFrame::OnFileExit)
// 其他消息映射
END_MESSAGE_MAP()
当应用程序运行时,MFC运行时系统会自动查找消息映射表,并在消息到达时,根据消息类型调用相应的处理函数。
在MFC中,事件处理不仅仅是对消息的简单响应,它还包括了消息的修改、过滤和传递等更复杂的行为。例如,可以通过 PreTranslateMessage 函数在消息被派送到窗口之前进行预处理。
BOOL CMyFrame::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_KEYDOWN)
{
// 在这里处理按键消息
return TRUE; // 返回TRUE表示消息已被处理
}
return CFrameWnd::PreTranslateMessage(pMsg);
}
消息处理流程
消息处理流程可以概括为以下几个步骤:
-
消息的生成: 当用户与程序交互或系统事件发生时,Windows系统会生成一个消息。
-
消息的排队: Windows消息队列将这些消息进行排队。
-
消息的派送: 窗口消息循环从队列中取出消息,并发送到目标窗口。
-
映射查找与函数调用: MFC查找消息映射表,找到对应的消息处理函数并调用执行。
-
消息的处理: 消息处理函数根据需要处理消息。
-
返回值: 处理函数的返回值决定了消息是否需要进一步处理。
通过以上的步骤,MFC能够有效地将各种Windows消息映射到对应的处理函数中,从而构建出功能丰富的应用程序。
3. OpenCV图像处理算法应用
3.1 OpenCV库的基本使用
3.1.1 OpenCV的数据结构与函数
OpenCV库是用于计算机视觉的开源库,它提供了一系列简单、高效的图像处理函数。在OpenCV中,所有图像数据都使用 cv::Mat 结构存储,它是一个矩阵的类,可以用来存储不同类型的数据。 cv::Mat 可以看做是一个二维数组,它可以包含彩色或灰度图像数据。
cv::Mat 有多种构造函数,可以通过直接数据初始化、从现有矩阵复制或者创建指定大小的矩阵。以下是一个创建 cv::Mat 的简单示例:
cv::Mat image = cv::Mat::zeros(500, 500, CV_8UC1); // 创建一个500x500的单通道灰度图像,初始化为0(黑色)
3.1.2 图像读取与显示
使用OpenCV进行图像处理之前,首先需要能够读取图像。OpenCV的 cv::imread 函数就是用来读取图像的,它有多个重载形式来满足不同的读取需求。
cv::Mat image = cv::imread("path_to_image.jpg", cv::IMREAD_COLOR); // 读取一个彩色图像
读取图像之后,通常需要将其显示出来以便进行分析。OpenCV中, cv::imshow 函数用于显示图像,而 cv::waitKey 函数用于等待用户输入。
cv::imshow("Display window", image); // 显示图像
cv::waitKey(0); // 等待无限时间,直到有按键事件发生
在本小节中,我们介绍了OpenCV的基本数据结构 cv::Mat 以及如何用其进行图像的读取和显示。这些是进行任何图像处理之前必须要掌握的基础知识。
3.2 OpenCV中的图像处理技术
3.2.1 图像滤波与边缘检测
图像滤波是图像处理中减少图像噪声和细节的过程,而边缘检测是识别图像中物体边缘的一种技术。OpenCV提供了广泛的图像滤波和边缘检测函数。
高斯滤波是一种常见的图像平滑处理方法,可以减少图像噪声同时保留边缘信息。以下是使用 cv::GaussianBlur 对图像进行滤波的代码:
cv::Mat filteredImage;
cv::GaussianBlur(image, filteredImage, cv::Size(5, 5), 0);
在边缘检测方面,Sobel算法是一种常用的边缘检测算法,它计算图像亮度的梯度,以下是如何使用Sobel算法检测边缘的示例:
cv::Mat sobelX, sobelY;
cv::Sobel(image, sobelX, CV_64F, 1, 0, 3);
cv::Sobel(image, sobelY, CV_64F, 0, 1, 3);
在这一小节中,我们了解了OpenCV如何实现图像滤波与边缘检测,这两项技术是许多高级图像处理任务的基础。
3.2.2 特征点检测与匹配
特征点检测是在图像中寻找关键点的过程,而特征点匹配则是将两个图像之间的相似特征点进行匹配,这对于图像配准和目标识别等应用至关重要。
OpenCV中的 cv::SIFT 是一种特征点检测算法,尽管由于专利问题,它不是OpenCV官方包的一部分,但可以通过OpenCV contrib模块获取。
std::vector<cv::KeyPoint> keypoints;
cv::SIFT_create sift;
sift->detect(image, keypoints);
特征匹配通常使用 cv::BFMatcher 或者 cv::FlannBasedMatcher ,下面是一个使用BFMatcher进行特征匹配的示例:
cv::BFMatcher matcher;
std::vector<cv::DMatch> matches;
matcher.match(descriptors1, descriptors2, matches);
通过以上对特征点检测与匹配技术的介绍,我们了解了在图像分析过程中识别和比较特征点的重要性。
在本章节的前半部分,我们从基本的OpenCV数据结构与函数介绍开始,到图像读取与显示,再到图像滤波与边缘检测,最后探讨了特征点检测与匹配。这些内容为使用OpenCV进行更复杂的图像处理算法打下了坚实的基础。在后续章节中,我们将进一步深入了解OpenCV所提供的各种图像处理技术,并探讨如何在实际应用中进行应用和优化。
4. Intel RealSense D435i深度相机使用
4.1 RealSense相机硬件概述
4.1.1 D435i相机的技术规格
Intel RealSense D435i深度相机是Intel RealSense D400系列的一部分,它以其精准的深度感知能力、高帧率视频捕获和丰富的集成传感器而著称。D435i相机配备了一个立体深度传感器,一个高分辨率RGB相机以及一个惯性测量单元(IMU),可以提供精确的运动追踪和姿态估算。
D435i的深度传感器可以在0.16米到10米的距离范围内提供准确的深度数据。其RGB相机支持1920x1080分辨率的图像捕获,并具有高帧率,非常适合实时应用。IMU传感器则包含三轴陀螺仪、三轴加速度计以及三轴磁力计,为运动追踪和设备定位提供重要信息。
D435i的另一个显著特点是其轻便的设计和USB 3.0接口,确保了高速数据传输和方便的连接。此外,RealSense D435i也支持Microsoft Windows、Linux和macOS操作系统,使其成为一个跨平台的深度感知解决方案。
4.1.2 相机的安装与配置
安装RealSense D435i相机的第一步是连接相机到计算机。确保USB 3.0端口供电充足,然后将相机的USB连接线连接到计算机。为了获得最佳性能,建议使用主板上的原生USB 3.0端口,避免使用扩展卡。
一旦连接好相机,就需要安装Intel RealSense SDK。可以从Intel的官方网站下载最新的SDK包,并遵循安装向导的指示完成安装。安装完毕后,通常会自动安装RealSense Viewer应用,该应用可以用来测试相机是否正常工作,并进行一些基本的设置。
在配置方面,需要确保计算机的USB端口有足够的带宽,以支持高分辨率视频和深度数据的传输。此外,还需要检查计算机的操作系统是否兼容RealSense SDK,并安装相应的驱动程序。
完成安装后,应通过RealSense Viewer等工具检查相机的校准状态,确保RGB和深度传感器同步工作。如果需要,可以按照用户手册中的说明对相机进行校准,以保证数据的准确性。
4.2 RealSense相机软件开发
4.2.1 RealSense SDK的安装与配置
开发使用RealSense D435i深度相机的应用程序前,首先需要确保系统上安装了最新版本的RealSense SDK。RealSense SDK 2.0为开发者提供了丰富的API接口,支持深度相机的深度感知功能,并可以轻松集成到多种编程环境中,包括C++、C#以及Python等。
安装SDK的过程简单直接。只需从Intel官方网站下载对应操作系统的安装包,运行安装向导并遵循指示即可。安装完成后,还需要检查系统环境变量,确保包含了SDK的路径和库文件,这对于后续程序的编译和链接是必要的。
一旦SDK安装完成,接下来就是开发环境的配置。大多数现代集成开发环境(IDE),如Visual Studio、CLion或者PyCharm,都提供简单的方式来配置SDK。开发者只需选择正确的SDK路径,以及配置项目需要的依赖库,就可以开始编写代码了。
4.2.2 相机参数设置与数据获取
在开发应用程序时,根据应用需求设置相机参数是至关重要的。RealSense SDK允许开发者通过编程方式访问和修改相机参数,包括分辨率、帧率、白平衡、曝光等。这可以确保相机输出的数据质量满足特定应用的需要。
例如,如果需要捕获高分辨率的RGB图像,可以设置相机的RGB流的分辨率为1920x1080,并将帧率设置为30帧每秒。如果应用需要更短的延迟,可以调整深度流的分辨率和帧率以优化性能。
以下是一个简单的示例代码,展示如何使用C++获取RealSense D435i相机的RGB图像流:
#include <librealsense2/rs.hpp> // 引入RealSense C++ SDK核心库
int main(int argc, char * argv[])
{
// 创建管道对象,用于管理相机和数据流
rs2::pipeline pipe;
// 配置管道,配置不同的流和分辨率
pipe.start();
// 使用循环捕获数据帧
while (true)
{
// 从管道获取下一组数据帧
rs2::frameset frames = pipe.wait_for_frames();
// 从帧集中获取RGB帧
rs2::frame color_frame = frames.get_color_frame();
// 检查数据帧是否是有效的
if (!color_frame)
{
continue;
}
// 为了将图像显示在窗口上,需要进行图像格式的转换
// 这里转换为opencv的格式
const int w = color_frame.as<rs2::video_frame>().get_width();
const int h = color_frame.as<rs2::video_frame>().get_height();
// 创建一个空的Mat对象,用于存放转换后的图像数据
cv::Mat color_mat(h, w, CV_8UC3, (void*)color_frame.get_data());
// 使用opencv显示图像
cv::imshow("Color Stream", color_mat);
// 按'q'退出循环
if (cv::waitKey(1) == 'q')
break;
}
return 0;
}
在此代码段中,首先创建了一个 rs2::pipeline 对象来处理数据流。 pipe.start() 启动了数据流, pipe.wait_for_frames() 等待并获取数据帧。通过 frames.get_color_frame() ,我们从帧集中提取出RGB流。使用OpenCV的接口可以将得到的图像帧显示在窗口中。代码执行中需要安装OpenCV库,并将RealSense SDK的C++库链接到项目中。
RealSense SDK提供了灵活的接口,可以根据应用需求配置和获取不同的数据流。在获取数据之后,就可以根据实际应用的需求进行进一步的处理,比如深度感知、物体检测、人脸识别、动作跟踪等高级功能。
4.3 RealSense SDK与MFC应用程序集成
4.3.1 MFC项目中调用SDK方法
为了在基于MFC的应用程序中集成RealSense SDK,开发者需要创建一个MFC项目,并将RealSense SDK的相关库文件和头文件包含到项目中。然后,可以通过编写代码调用SDK提供的API来实现所需的功能。
在MFC项目中调用SDK方法的第一步是确保已经正确安装并配置了RealSense SDK。接下来,在MFC的类中声明和初始化RealSense管道对象。例如:
#include "RealSensePipeline.h" // 假设这是包含RealSense管道实现的头文件
class CMyMFCApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
class CMainDlg : public CDialogEx
{
public:
// 其他成员变量和函数...
// 用于管理RealSense相机的管道对象
rs2::pipeline m_pipeline;
// 其他成员变量和函数...
};
BOOL CMyMFCApp::InitInstance()
{
CMainDlg dlg;
m_pMainWnd = &dlg;
dlg.DoModal();
return FALSE;
}
// CMainDlg 类中的函数,用于初始化和启动RealSense管道
void CMainDlg::OnBnClickedStart()
{
// 开始RealSense管道,获取数据流
m_pipeline.start();
// 其他代码,比如设置数据流的回调等
}
在这个例子中, CMyMFCApp 类继承自 CWinApp ,是MFC应用程序的入口点。 CMainDlg 类继承自 CDialogEx ,用于创建对话框。在 CMainDlg 类中声明了 rs2::pipeline 对象,这允许我们访问SDK的功能。
4.3.2 SDK与MFC界面的交互实现
在MFC应用程序中使用RealSense SDK,重要的一个环节是实现SDK与用户界面之间的交互。例如,用户可能需要通过按钮点击来启动或停止视频流的捕获,或者通过滚动条来调整流的某些参数。
实现这种交互的一个关键点是处理MFC控件的事件,并将它们映射到相应的SDK调用。以下是一个示例,展示了如何在按钮点击事件中启动RealSense视频流捕获:
void CMainDlg::OnBnClickedStart()
{
// 启动RealSense管道
m_pipeline.start();
// 启动一个线程以异步处理视频流数据
AfxBeginThread(RealSenseThreadProc, this);
}
UINT CMainDlg::RealSenseThreadProc(LPVOID lpParam)
{
CMainDlg* pThis = (CMainDlg*)lpParam;
// 捕获循环
while (pThis->m_pipeline.is_running())
{
// 等待下一组数据帧
rs2::frameset frames = pThis->m_pipeline.wait_for_frames();
// 处理数据帧
// ...
}
return 0;
}
在这个例子中, OnBnClickedStart 函数通过按钮点击触发,启动了RealSense视频流的捕获。然后,使用 AfxBeginThread 函数启动一个线程,该线程将异步处理视频流数据。这样可以避免阻塞MFC主线程,从而确保用户界面的流畅响应。
通过这种方式,开发者可以在MFC应用程序中充分利用RealSense SDK提供的功能,构建出既美观又功能强大的交互式应用。在实现过程中,需要特别注意线程安全和实时性能的平衡,确保应用的稳定性和响应性。
4.4 小结
在本章节中,我们深入探讨了如何使用Intel RealSense D435i深度相机,包括它的硬件规格、安装与配置、软件开发以及SDK的集成。RealSense SDK 2.0提供了强大而灵活的API,使得开发者能够轻松集成深度感知功能到应用程序中。
在硬件概述中,我们了解到D435i相机的高精度深度感知能力、高分辨率RGB摄像头和IMU传感器。在软件开发方面,我们介绍了如何安装和配置SDK,以及如何在MFC应用程序中调用SDK方法,并实现与MFC界面的交互。
通过本章节的介绍,开发者应该能够掌握RealSense D435i相机的基本使用方法,并为后续的高级应用打下坚实的基础。随着技术的进步和应用的拓展,RealSense D435i相机无疑将在深度学习、机器视觉等领域发挥重要作用。
5. RealSense SDK 2.0相机交互
在第四章中,我们详细介绍了Intel RealSense D435i深度相机的硬件规格、安装配置以及软件开发基础。接下来,我们将深入探讨RealSense SDK 2.0在相机交互中的应用。
5.1 SDK的API结构解析
RealSense SDK 2.0为开发者提供了丰富的API接口,用于控制相机的多种功能,如流媒体的管理和配置,数据流的同步与异步获取。我们将逐一解析这些核心功能。
5.1.1 流与配置的管理
RealSense SDK 2.0允许开发者管理多个数据流,例如颜色、深度、红外和IMU(惯性测量单元)流。开发者可以通过API设置相机的分辨率、帧率以及其他高级选项,如自动曝光和白平衡。
以下是一个示例代码,展示了如何使用RealSense SDK 2.0来设置相机的深度流分辨率:
#include <librealsense2/rs.hpp> // Include RealSense Cross Platform API
int main() try
{
// Create a Pipeline - this serves as a top-level API for streaming and processing frames
rs2::pipeline pipe;
// Configure and start the pipeline with the default configuration
pipe.start();
// Create a config object
rs2::config cfg;
// Add desired streams to configuration
cfg.enable_stream(RS2_STREAM_DEPTH, 640, 480, RS2_FORMAT_Z16, 30);
// Start the pipeline with the given configuration
pipe.start(cfg);
while (true)
{
// Wait for all configured streams to produce a frame
rs2::frameset frameset = pipe.wait_for_frames();
// Try to get a frame of a depth image
rs2::frame frame = frameset.get_depth_frame();
if (!frame) continue;
// Get the depth data from the frame and convert it, mapping the values to the colour scale
const float* frame_data = reinterpret_cast<const float*>(frame.get_data());
int size = frame.get_width() * frame.get_height();
// Continue processing frame_data...
}
return EXIT_SUCCESS;
}
catch (const rs2::error & e)
{
std::cerr << "RealSense error calling " << e.get_failed_function() << "(" << e.get_failed_args() << "):\n " << e.what() << std::endl;
return EXIT_FAILURE;
}
catch (const std::exception & e)
{
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
在上述代码中,我们首先创建了RealSense管道( rs2::pipeline ),然后创建了一个配置( rs2::config )并指定了深度流的分辨率。通过调用 pipe.start(cfg) ,我们启动了管道并应用了新的配置。在主循环中,我们等待并获取帧,然后处理深度数据。
5.1.2 数据流的同步与异步获取
在实际应用中,数据流的同步和异步获取是处理相机数据的关键。RealSense SDK 2.0提供了多种方式来实现这一目标。数据流同步是指多个流以相同的频率采样,以保证数据的一致性。异步获取则是指在不阻塞主线程的情况下获取数据,这对于实时应用尤为重要。
下面的代码展示了如何使用异步API来获取颜色和深度流数据:
rs2::pipeline pipe;
rs2::pipeline_profile selection = pipe.start();
// Create a colorizer object to convert depth frames to RGB
rs2::colorizer color_map;
rs2::frameset frameset;
rs2::frame color_frame, depth_frame;
pipe.start();
// Use the asynchronous processing block to process frames without blocking
pipe.async_wait_for_frames([color_map](rs2::frameset frames)
{
// Colorize the depth frame to jet colormap
color_frame = color_map.process_frame(frames.get_depth_frame());
// Process the color frame here...
});
在这个例子中,我们使用 pipe.async_wait_for_frames 异步等待帧。在回调函数中,我们使用了 color_map 对象将深度帧颜色化,可以进一步处理颜色化的帧。
5.2 SDK在MFC中的集成
5.2.1 MFC项目中调用SDK方法
在本小节中,我们将介绍如何在MFC项目中集成和调用RealSense SDK 2.0的方法,实现与相机的交互。
首先,需要在MFC项目中包含RealSense SDK 2.0的头文件和库文件。随后,创建RealSense管道对象,并在适当的位置调用相关API。例如,在MFC的文档视图结构中,可以将相机初始化和数据获取放在视图类的初始化函数中。
以下是将RealSense SDK集成到MFC应用程序的示例步骤:
- 添加RealSense SDK到项目中。
- 包含必要的头文件。
- 创建并配置RealSense管道。
- 在MFC的适当位置(例如视图类的OnInitialUpdate)启动和使用管道。
5.2.2 SDK与MFC界面的交互实现
将RealSense SDK集成到MFC应用程序之后,接下来需要实现SDK与MFC界面的交互。
RealSense SDK提供的数据流可以被绑定到MFC控件上,例如显示图像的静态控件。使用回调函数和处理流程,可以在MFC的事件驱动模型中处理SDK事件。
例如,可以创建一个回调函数来处理新的帧数据,并更新MFC界面,如下所示:
// Callback function for new frames
void OnNewFrame(rs2::frame frame)
{
// Convert frame to a format that is convenient for rendering
if (rs2::video_frame color_frame = frame.as<rs2::video_frame>())
{
// Convert the video frame to a format suitable for rendering
cv::Mat color_image = cv::Mat(cv::Size(color_frame.get_width(), color_frame.get_height()), CV_8UC3, (void*)color_frame.get_data());
// Update MFC's static control with the new frame
UpdateImageView(color_image);
}
}
// MFC function to update the image view
void UpdateImageView(const cv::Mat& image)
{
CWnd* pWnd = AfxGetMainWnd()->GetDlgItem(IDC_STATIC_IMAGE);
pWnd->SetWindowText(_T("")); // Clear previous image data
BITMAPINFOHEADER bmi = { sizeof(BITMAPINFOHEADER) };
bmi.biBitCount = 24;
bmi.biPlanes = 1;
bmi.biCompression = BI_RGB;
bmi.biSizeImage = 0;
bmi.biXPelsPerMeter = 0;
bmi.biYPelsPerMeter = 0;
bmi.biWidth = image.cols;
bmi.biHeight = -image.rows;
std::vector<BYTE> bits(image.rows * image.cols * 3);
for (int y = 0; y < image.rows; ++y)
{
for (int x = 0; x < image.cols; ++x)
{
// Iterate over RGB channels
for (int c = 0; c < 3; ++c)
{
bits[(y * image.cols + x) * 3 + c] = image.data[y * image.step + x * 3 + c];
}
}
}
BITMAPINFO bmi_only_headers = { bmi };
HBITMAP hbm = ::CreateDIBBitmap(pWnd->GetDC()->m_hDC, &bmi, CBM_INIT, bits.data(), &bmi_only_headers, DIB_RGB_COLORS);
pWnd->SetBitmap(hbm);
}
在上面的代码中, OnNewFrame 函数会接收一个新的帧并使用OpenCV将其转换为 cv::Mat 。然后, UpdateImageView 函数使用MFC的GDI函数将OpenCV的图像数据转换为位图,并更新到MFC应用程序的一个静态控件上。
这样,我们就完成了RealSense SDK与MFC界面交互的实现。通过这种方式,可以将实时的相机数据展示在MFC应用程序界面上,进一步可以结合MFC控件来实现更丰富的交互功能。
6. OpenCV与MFC应用程序集成
6.1 OpenCV与MFC界面的融合
6.1.1 OpenCV图像显示在MFC窗口
为了在MFC应用程序中集成OpenCV图像显示功能,我们需要在MFC的窗口类中嵌入OpenCV的窗口显示。最常用的方法是创建一个窗口,利用OpenCV的 imshow 函数显示图像,然后通过回调机制将其嵌入到MFC窗口中。
首先,我们必须包含必要的头文件,并初始化MFC和OpenCV的界面系统:
#include "opencv2/opencv.hpp"
#include <afxwin.h>
// 前向声明MFC的窗口类
class CMFCOpenCVWindow;
// 通知Windows我们正在使用Unicode字符集
#ifdef _UNICODE
#define tcout std::wcout
#define tcerr std::wcerr
#else
#define tcout std::cout
#define tcerr std::cerr
#endif
// MFC的窗口类实现
class CMFCOpenCVWindow : public CFrameWnd
{
public:
CMFCOpenCVWindow();
};
// 全局变量
HINSTANCE hInst;
CMFCOpenCVWindow* pMainWnd = nullptr;
然后,我们定义MFC窗口类,并在其中嵌入OpenCV窗口:
// MFC的窗口类实现
CMFCOpenCVWindow::CMFCOpenCVWindow()
{
// 创建一个MFC窗口
Create(NULL, _T("OpenCV与MFC集成示例"));
// 将OpenCV窗口嵌入到MFC窗口中
// 在这里,我们使用Windows消息映射来处理图像显示
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = DefWindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInst;
wc.hIcon = LoadIcon(hInst, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = _T("OpenCVWindowClass");
if (!RegisterClass(&wc))
{
tcerr << _T("RegisterClass failed.") << endl;
return;
}
HWND hWnd = CreateWindow(_T("OpenCVWindowClass"), _T("OpenCV显示窗口"), WS_CHILD | WS_VISIBLE,
0, 0, 640, 480, m_hWnd, NULL, hInst, NULL);
if (!hWnd)
{
tcerr << _T("CreateWindow failed.") << endl;
return;
}
// 获取OpenCV窗口句柄并附加到MFC窗口
cv::namedWindow(_T("OpenCV"), cv::WINDOW_AUTOSIZE);
cv::imshow(_T("OpenCV"), cv::Mat(480, 640, CV_8UC3, cv::Scalar(0, 0, 0)));
int cv_window_id = ::GetDlgCtrlID((HWND)cvGetWindowHandle(_T("OpenCV")));
::SetParent((HWND)cv_window_id, hWnd);
}
// MFC应用程序的入口函数
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
hInst = hInstance;
// 创建应用程序对象
CMFCOpenCVWindow theApp;
// 应用程序的主消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
上述代码中,我们创建了一个名为 CMFCOpenCVWindow 的MFC窗口类,并在其中嵌入了OpenCV的显示窗口。首先,我们注册了一个新的窗口类,并创建了一个子窗口,然后通过 GetDlgCtrlID 和 SetParent 函数将OpenCV窗口的句柄附加到MFC窗口中。这样,我们就可以在MFC应用程序中直接显示OpenCV处理的图像了。
6.1.2 实现OpenCV处理后的图像更新
为了实时更新MFC窗口中显示的OpenCV图像,我们可以使用定时器或者通过事件驱动的方式。下面示例展示了如何通过OpenCV的事件处理和MFC的消息映射机制来实现图像的实时更新:
// 定义一个宏,用于在MFC中发送定时器消息
#define SEND_TIMER(id) SetTimer(id, 33, NULL)
// MFC窗口类中的一个变量,用于跟踪定时器ID
UINT m_nTimerID;
// 在MFC窗口类的消息映射中添加定时器消息的处理函数
BEGIN_MESSAGE_MAP(CMFCOpenCVWindow, CFrameWnd)
// ...
ON_WM_TIMER()
// ...
END_MESSAGE_MAP()
// 定时器消息处理函数
void CMFCOpenCVWindow::OnTimer(UINT_PTR nIDEvent)
{
if (nIDEvent == m_nTimerID)
{
// 使用OpenCV捕获或处理图像
cv::Mat img = CaptureCameraImage(); // 假设这个函数用于捕获或处理图像
// 显示图像
cv::imshow("OpenCV", img);
// 如果需要,更新MFC窗口的客户区
// this->UpdateWindow();
}
CFrameWnd::OnTimer(nIDEvent);
}
// MFC应用程序的入口函数中的一个部分
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
// ...
theApp.m_nTimerID = 1; // 分配一个定时器ID
SEND_TIMER(theApp.m_nTimerID); // 启动定时器
// ...
return (int) msg.wParam;
}
在这个示例中,我们定义了一个 SEND_TIMER 宏,用来发送一个定时器消息,然后在MFC窗口类中定义一个消息映射,用于处理定时器消息。在 OnTimer 函数中,我们可以调用OpenCV的相关函数来获取新的图像数据,并使用 imshow 函数更新图像窗口。定时器可以设置为重复定时器,这样就可以按照固定的时间间隔不断地更新图像。
在MFC应用程序的入口函数中,我们创建一个定时器,并将其ID传递给 SEND_TIMER 宏。这样,每当定时器到期,就会调用 OnTimer 函数来更新图像。
6.2 交互式图像处理功能实现
6.2.1 MFC控件触发OpenCV算法
为了实现通过MFC控件触发OpenCV算法的功能,我们需要将MFC控件的事件(如按钮点击、菜单选择等)与OpenCV处理函数关联起来。下面的例子演示了如何在MFC应用程序中点击一个按钮时,调用OpenCV的图像处理函数:
// 假设有一个按钮控件,ID为IDC_BUTTON_PROCESS_IMAGE
class CMyDialog : public CDialogEx
{
public:
CMyDialog(CWnd* pParent = nullptr);
// 消息映射
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
ON_BN_CLICKED(IDC_BUTTON_PROCESS_IMAGE, &CMyDialog::OnBnClickedButtonProcessImage)
END_MESSAGE_MAP()
};
void CMyDialog::OnBnClickedButtonProcessImage()
{
// 从文件、摄像头或任何其他源获取图像
cv::Mat img = cv::imread("path/to/image.jpg");
// 应用OpenCV算法进行处理
if (!img.empty())
{
// 假设ProcessImage是一个使用OpenCV实现的图像处理函数
cv::Mat processed_img = ProcessImage(img);
// 显示处理后的图像
cv::imshow("Processed Image", processed_img);
cv::waitKey(0); // 等待任意键
}
}
// 在MFC的DoDataExchange中关联控件和变量
void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_BUTTON_PROCESS_IMAGE, m_buttonProcessImage);
}
在这个示例中,我们首先为MFC窗口添加了一个按钮控件,并在对话框类中创建了一个对应的成员变量。我们使用 ON_BN_CLICKED 宏将按钮的点击事件映射到处理函数 OnBnClickedButtonProcessImage 上。在这个函数内部,我们读取图像,调用OpenCV函数 ProcessImage 来处理图像,并显示处理后的结果。
在 DoDataExchange 函数中,我们使用 DDX_Control 宏将MFC控件变量 m_buttonProcessImage 与界面上的按钮控件关联起来,这样我们就可以在对话框类的其他函数中引用这个按钮控件了。
6.2.2 处理结果的界面反馈
当使用OpenCV处理图像并需要将结果反馈到MFC界面时,一种常见的做法是将OpenCV的图像数据转换为MFC可以识别的GDI位图格式。下面的代码展示了如何将OpenCV的 cv::Mat 对象转换为MFC的 CBitmap 对象,并显示在控件上:
// 假设有一个静态控件用于显示图像,控件ID为IDC_STATIC_IMAGE_VIEWER
void CMyDialog::DisplayImage(cv::Mat& image)
{
// 将cv::Mat转换为HBITMAP
HBITMAP hBitmap = MatToHBITMAP(image);
// 创建一个内存DC
CDC* pDC = GetDC();
CDC memDC;
memDC.CreateCompatibleDC(pDC);
// 将HBITMAP选入内存DC中
HBITMAP hOldBitmap = (HBITMAP)memDC.SelectObject(hBitmap);
// 获取图像的尺寸
BITMAP bm;
GetObject(hBitmap, sizeof(BITMAP), &bm);
int nWidth = bm.bmWidth;
int nHeight = bm.bmHeight;
// 创建一个CBitmap对象
CBitmap* pBitmap = CBitmap::FromHandle(hBitmap);
// 创建一个CImage对象并从CBitmap加载图像数据
CImage img;
img.Attach(hBitmap);
img.Load(hBitmap, 0);
// 将CImage复制到静态控件上
CWnd* pWnd = GetDlgItem(IDC_STATIC_IMAGE_VIEWER);
pWnd->SetBitmap(pBitmap->m_hObject);
// 清理资源
memDC.SelectObject(hOldBitmap);
memDC.DeleteDC();
ReleaseDC(pDC);
}
// cv::Mat转换为HBITMAP的函数实现
HBITMAP MatToHBITMAP(const cv::Mat& src)
{
// 创建一个与源图像兼容的CImage对象
CImage image;
image.Create(src.rows, src.cols, src.channels() * 8, 0x000000FF);
// 从cv::Mat复制数据到CImage对象
for(int i = 0; i < src.rows; ++i)
{
memcpy(image.GetBits(i), src.ptr<BYTE>(i), src.cols * src.channels());
}
// 将CImage对象转换为HBITMAP
HBITMAP hBitmap = NULL;
image.GetBitmap(&hBitmap);
return hBitmap;
}
在这个函数中,我们首先将 cv::Mat 对象转换为 HBITMAP ,这是MFC能够识别的位图句柄。然后,我们创建一个与MFC窗口设备上下文兼容的内存设备上下文,并将位图选入这个上下文中。接着,我们从CImage对象中获取图像数据,并通过MFC的 SetBitmap 函数将位图显示在指定的控件上。最后,我们释放相关资源以避免内存泄漏。
通过这种方式,我们可以将OpenCV处理后的图像数据在MFC界面中展示出来,实现了动态的交互式图像处理功能。
7. RGB和Depth视频流捕获与显示
视频流的捕获和实时显示是计算机视觉领域中的重要技术,尤其在深度学习和机器人视觉系统中应用广泛。本章将重点介绍如何使用Intel RealSense SDK 2.0来捕获RGB和Depth视频流,并通过MFC应用程序实现它们的实时显示。
7.1 视频流的捕获技术
7.1.1 使用RealSense SDK捕获RGB视频流
首先,需要创建一个 rs2::pipeline 对象来初始化RealSense管道。这个管道负责捕获和处理视频数据。
#include <iostream>
#include <rs2.hpp>
using namespace rs2;
pipeline pipe;
config cfg;
pipeline_profile selection;
device device;
stream_profile stream_profile;
void configure_pipeline() {
cfg = pipeline::config();
// 配置管道以捕获RGB视频流
cfg.enable_stream(RS2_STREAM_COLOR);
pipe.start(cfg);
}
void capture_frame() {
// 捕获一帧数据
frame_set frames = pipe.wait_for_frames();
// 获取RGB视频流帧
frame color_frame = frames.get_color_frame();
// 其他处理...
}
上述代码展示了如何配置RealSense管道来捕获RGB视频流,并在后续的 capture_frame 函数中等待和获取帧数据。
7.1.2 使用RealSense SDK捕获Depth视频流
捕获Depth视频流的过程与RGB视频流类似,不过需要启用Depth流。
// 在configure_pipeline中添加Depth流配置
cfg.enable_stream(RS2_STREAM_DEPTH);
void capture_depth_frame() {
frame_set frames = pipe.wait_for_frames();
// 获取Depth视频流帧
frame depth_frame = frames.get_depth_frame();
// 其他处理...
}
在以上代码中,我们同样通过管道等待帧数据,并获取Depth视频流的帧。这些帧随后可用于深度数据的计算和处理。
7.2 视频流的实时显示与处理
7.2.1 实现RGB视频流的实时显示
要在MFC应用程序中实现RGB视频流的实时显示,需要将捕获到的帧映射到MFC窗口上。这通常涉及到创建一个与视频流分辨率相匹配的窗口,并利用GDI+或Direct2D进行绘制。
void render_frame(frame f, CWnd* pWnd) {
// 获取窗口的DC
CDC* pDC = pWnd->GetDC();
// 将帧数据绘制到窗口DC
cv::Mat img = frame_to_mat(f);
cv::cvtColor(img, img, cv::COLOR_BGR2RGB); // 将BGR转换为RGB
cv::imshow("RGB Stream", img);
cv::waitKey(1); // 等待1ms以更新显示
pDC->ReleaseDC();
}
在该示例中,使用了OpenCV的 frame_to_mat 函数将RealSense帧转换为OpenCV的 Mat 格式,并利用OpenCV的 imshow 函数显示帧数据。
7.2.2 实现Depth视频流的实时显示
Depth视频流的显示需要对颜色进行映射,以便人类视觉可以识别。深度值通常映射到一个特定的颜色范围内,如热图。
void render_depth_frame(frame f, CWnd* pWnd) {
// 同样获取窗口的DC
CDC* pDC = pWnd->GetDC();
// 将Depth帧转换为Mat对象,并映射为热图颜色
cv::Mat depth_image = frame_to_mat(f);
cv::Mat depth_image_color;
// ... 使用cv::applyColorMap()进行颜色映射 ...
cv::imshow("Depth Stream", depth_image_color);
cv::waitKey(1);
pDC->ReleaseDC();
}
上述代码片段展示了Depth视频流的显示流程,其中包括将帧数据转换为OpenCV的 Mat 格式,并使用颜色映射函数进行深度数据可视化。
7.2.3 视频流同步显示的技术要点
当同时显示RGB和Depth视频流时,需要确保帧的同步性,以便实现精确的时间匹配和数据对齐。
void sync_display_frames(frame color, frame depth, CWnd* pWndColor, CWnd* pWndDepth) {
// 捕获一帧RGB和Depth数据
frame_set frames = pipe.wait_for_frames();
frame color_frame = frames.get_color_frame();
frame depth_frame = frames.get_depth_frame();
// 在两个窗口中分别渲染帧
render_frame(color_frame, pWndColor);
render_depth_frame(depth_frame, pWndDepth);
}
在本节示例中,我们捕获了一组帧,并在两个不同的窗口中分别渲染它们。这种方法可确保RGB和Depth视频流的同步显示。
以上内容介绍了如何使用RealSense SDK 2.0和MFC应用程序捕获和显示RGB以及Depth视频流,并展示了同步显示的技术要点。在下一章节中,我们将探讨如何捕获视频流中的单个帧,并将这些帧保存为图像数据。
简介:本项目介绍如何结合Visual Studio 2019、MFC、OpenCV 3.4.5和Intel RealSense D435i相机来开发一个能够捕获、显示和保存RGB及Depth图像的应用程序。介绍涉及的技术包括C++开发环境搭建、MFC界面设计、OpenCV图像处理,以及使用RealSense SDK 2.0与相机交互。项目旨在提供深度相机视频流的实时处理和显示,以及如何在用户界面中实现视频流的捕获和保存功能。
更多推荐

所有评论(0)