- RealSenseTM互动开发实战
- 王曰海 汤振宇 吴新天
- 3594字
- 2025-03-27 02:04:46
2.3 原始数据流获取和处理
这一节将介绍获取和处理原始视频流和音频流的常见任务。
1.获取单个彩色或深度流
SenseManager接口实现I/O设备配置和获取数据流,可以采用过程调用或事件回调方法来使用该接口。
(1)采用过程调用获取彩色图像样本
例2-3说明了如何采用过程调用获取彩色图像样本。
1)使用EnableStream函数选择一个彩色流,再利用Init函数初始化流水线。
2)在循环中使用AcquireFrame函数来等待彩色图像采样到达,然后通过QuerySample进行获取。
3)使用ReleaseFrame函数释放这一帧,并等待下一个样本。
4)利用Close函数进行清理过程。
可以使用CaptureManager接口中的过滤函数(如FilterByDeviceInfo)来自定义数据流选择过程,例如选择一个指定的摄像头;也可使用QueryCaptureManager函数(或者captureManager属性)来获取CaptureManager接口实例。
例2-3 采用过程调用获取彩色图像样本
// 创建PXCMSenseManager实例 PXCMSenseManager sm=PXCMSenseManager.CreateInstance(); // 选择彩色数据流 sm.EnableStream(PXCMCapture.StreamType.STREAM_TYPE_COLOR,640,480); // 初始化流样本 sm.Init(); for (;;) { // 该函数阻塞直到彩色样本到达 if (sm.AcquireFrame(true).isError()) break; // 获取样本 PXCMCapture.Sample sample=sm.QuerySample(); // 对图像sample.color进行操作 ...... // 获取下一个样本 sm.ReleaseFrame(); } // 清理过程 sm.close();
(2)利用事件回调获取深度样本
例2-4展示了如何使用SenseManager接口的事件回调函数来获取60 fps的深度样本,其中主要过程是创建事件处理程序和调用StreamFrame函数。
例2-4 利用SenseManager的事件回调函数获取深度样本
class MyHandler implements PXCMSenseManager.Handler { //注册事件处理程序 public pxcmStatus OnNewSample(int mid, PXCMCapture.Sample sample) { // 对sample.color进行操作 ...... // 返回NO ERROR继续;如有错误则退出循环 return pxcmStatus.PXCM_STATUS_NO_ERROR; } ...... }; // 创建SenseManager实例 PXCMSenseManager sm=PXCMSenseManager.CreateInstance(); // 在320×240×60fps启动深度流 sm.EnableStream(PXCMCapture.StreamType.STREAM_TYPE_DEPTH,320,240,60); // 初始化事件处理器 MyHandler handler=new MyHandler(); sm.Init(handler); // 深度流样本 sm.StreamFrames(true); // 清理过程 sm.close();
2.采集非对齐的彩色和深度样本
例2-5展示了如何采集非对齐的彩色和深度样本。例子中,当采样数据准备好时,SenseManager单独处理某个数据流的每个样本。每个数据流可能以不同的帧率呈现样本。例2-5做了如下工作:
1)使用EnableStream函数选择彩色和深度流,然后使用Init函数初始化处理流水线。
2)在循环中,使用AcquireFrame函数ifall=false来等待样本到达并读取样本。
3)使用QuerySample函数获取样本,并检验彩色和深度样本相应的状态和过程。
4)使用ReleaseFrame函数释放当前帧,并读取下一个样本。
5)使用Close函数清理过程。
例2-5 利用SenseManager捕捉非对齐的彩色和深度数据
// 创建SenseManager实例 PXCMSenseManager sm=PXCMSenseManager.CreateInstance(); // 选择彩色和深度流 sm.EnableStream(PXCMCapture.StreamType.STREAM_TYPE_COLOR,640,480,30); sm.EnableStream(PXCMCapture.StreamType.STREAM_TYPE_DEPTH,320,240,30); // 初始化流样本 sm.Init(); for (;;) { // 该函数阻塞直到有样本到达 if (sm.AcquireFrame(false).isError()) break; // 获取样本 PXCMCapture.Sample sample=sm.QuerySample(); if (sample!=null) { if (sample.color!=null) { // 对彩色样本进行操作 ...... } if (sample.depth!=null) { // 对深度样本进行操作 ...... } } // 获取下一样本 sm.ReleaseFrame(); } // 清理过程 sm.close();
同样可以使用StreamFrame函数和SenseManager事件回调实现获取非对齐彩色和深度样本,如例2-6所示。
例2-6 利用SenseManager事件回调获取彩色和深度样本
class MyHandler implements PXCMSenseManager.Handler { public pxcmStatus OnNewImage(int mid, PXCMCapture.Sample sample) { if (sample.color!=null) { // 对彩色样本进行操作 ...... } if (sample.depth!=null) { // 对深度样本进行操作 ...... } //返回NO ERROR继续;有错误则退出循环 return pxcmStatus.PXCM_STATUS_NO_ERROR; } }; // 创建SenseManager实例 PXCMSenseManager sm=PXCMSenseManager.CreateInstance(); // 选择彩色和深度流 sm.EnableStream(PXCMCapture.StreamType.STREAM_TYPE_COLOR,640,480,30); sm.EnableStream(PXCMCapture.StreamType.STREAM_TYPE_DEPTH,320,240,30); // 初始化处理器 MyHandler handler=new MyHandler(); sm.Init(handler); // 生成样本流 sm.StreamFrames(true); // 清理 sm.close();
3.采集对齐的彩色和深度流
例2-7展示了如何采集对齐的彩色和深度样本。该例子在同步了两种数据帧的到达时间之后立刻处理数据。
例2-7的具体工作如下:
1)使用EnableStream函数选择彩色和深度流,之后利用Init函数初始化处理流水线。
2)在循环中,AcquireFrame函数采用ifall=true选项来等待所有流的样本都准备好。
3)用QuerySample函数获取彩色和深度样本。
4)用ReleaseFrame函数释放帧,并等待读取下一个样本。
5)用Close函数进行清理。
例2-7 采集对齐的彩色和深度样本
// 创建一个 SenseManager 实例 PXCMSenseManager sm=PXCMSenseManager.CreateInstance(); // 选择彩色和深度数据流 sm.EnableStream(PXCMCapture.StreamType.STREAM_TYPE_COLOR,640,480,30); sm.EnableStream(PXCMCapture.StreamType.STREAM_TYPE_DEPTH,320,240,30); // 初始化数据流 sm.Init(); for (;;) { // 这个函数保持阻塞直到样本准备好 if (sm.AcquireFrame(true).isError()) break; // 获取样本 PXCMCapture.Sample sample=sm.QuerySample(); // 在样本上进行处理: sample.color & sample.depth ...... // 准备下一帧处理 sm.ReleaseFrame(); } // 清理 sm.close();
可以使用StreamFrames函数和SenseManager事件回调来采集彩色和深度样本,如例2-8所示。
例2-8 利用SenseManager回调函数获取对齐的彩色和深度样本
class MyHandler implements PXCMSenseManager.Handler { public pxcmStatus OnNewSample(int mid, PXCMCapture.Sample sample) { // 在 sample.color 和 sample.depth样本上处理 ...... //返回NO_ERROR继续;如果出错则报错并放弃 return pxcmStatus.PXCM_STATUS_NO_ERROR; } }; // 创建一个 SenseManager 实例 PXCMSenseManager sm=PXCMSenseManager.CreateInstance(); // 选择彩色和深度数据流 PXCMVideoModule.DataDesc ddesc=new PXCMVideoModule.DataDesc(); ddesc.deviceInfo.streams=EnumSet.of(PXCMCapture.StreamType.STREAM_TYPE_COLOR, PXCMCapture.StreamType.STREAM_TYPE_DEPTH); sm.EnableStreams(ddesc); // 初始化处理器 handler MyHandler handler=new MyHandler(); sm.Init(handler); // 流式处理 sm.StreamFrames(true); // 清理 sm.close();
4.启动强力同步化
SDK支持基于硬件的彩色和深度流同步,可以产生成对的拥有相近时间戳的彩色和深度样本。这种同步环节在很多应用场合非常有用,例如3D背景分割。
可以利用STREAM_OPTION_STRONG_STREAM_SYNC选项来启动强力同步化。选项必须设置对所有需要同步的数据流生效,否则该选项将被忽略。毋庸置疑,这些流需要拥有相同的帧率。例如需要同步彩色和深度流,就可以利用EnableStreams函数对彩色和深度流同时指定这个选项,如例2-9所示。
注 以摄像头F200为例,如果有应用程序请求启动强力同步化,系统将会有明显的停顿并重启摄像头流。因此,只有当确实需要时才使用强力同步化。
例2-9 通过强力同步获取对齐的彩色和深度样本
// 创建一个 SenseManager 实例 PXCMSenseManager sm=PXCMSenseManager.CreateInstance(); // 选择彩色和深度流 PXCMVideoModule.DataDesc ddesc=new PXCMVideoModule.DataDesc(); ddesc.deviceInfo.streams=EnumSet.of(PXCMCapture.StreamType.STREAM_TYPE_COLOR, PXCMCapture.StreamType.STREAM_TYPE_DEPTH); ddesc.streams.color.options=EnumSet.Of(PXCMCapture.Device.StreamOption.STREAM_OPTION_STRONG_STREAM_SYNC); ddesc.streams.depth.options=EnumSet.Of(PXCMCapture.Device.StreamOption.STREAM_OPTION_STRONG_STREAM_SYNC); sm.EnableStreams(ddesc); // 初始化 sm.Init(); for (;;) { // 函数保持阻塞直到采样数据准备好 if (sm.AcquireFrame(true).isError()) break; // 获取样本 PXCMCapture.Sample sample=sm.QuerySample(); // 在 sample.color 和 sample.depth样本上处理 ...... // 获取下一帧数据 sm.ReleaseFrame(); } // 清理 sm.close();
5.采集未矫正数据流
SDK支持R200摄像头采集未矫正的数据流。矫正是修正镜头失真以及对齐的过程。未矫正的数据流是从摄像头传感器得来的原始图像。
SDK利用STREAM_OPTION_UNRECTIFIED选项识别未矫正的数据流配置(参见StreamProle结构)。如果没有指定这个命令选项,数据流默认配置为采用矫正。可以指定STREAM_OPTION_UNRECTIFIED选项获取一个未矫正数据流,如例2-10所示。
注 不是所有的R200算法模块都支持未矫正数据流,只有当需要时才能使用这种方式。
可以通过QueryStreamProjectionParametersEx函数指定STREAM_OPTION_UNRECTIFIED选项来获取未矫正摄像头标定参数。
例2-10 获取未校正彩色样本
void CaptureAlignedColorDepthSamples() { // 创建一个 SenseManager 实例 PXCMSenseManager sm=PXCMSenseManager.CreateInstance(); // 选择彩色和深度流 PXCMVideoModule.DataDesc ddesc=new PXCMVideoModule.DataDesc(); ddesc.deviceInfo.streams=EnumSet.of(PXCMCapture.StreamType.STREAM_TYPE_COLOR); ddesc.streams.color.options=EnumSet.Of(PXCMCapture.Device.StreamOption.STREAM_OPTION_UNRECTIFIED); sm.EnableStreams(ddesc); //初始化 sm.Init(); for (;;) { if (sm.AcquireFrame(true).isError()) break; // 获取样本 PXCMCapture.Sample sample=sm.QuerySample(); // 在 sample.color上进行处理 ...... // 进行下一帧准备 sm.ReleaseFrame(); } // 清理 sm.close(); }
6.记录和回放
可以记录任何流序列到文件,并在之后播放这些流文件。
启动文件记录和回放需要的工作如下:
1)在CaptureManager实例中使用SetFileName函数。
2)在记录模式,提供一个文件名并设定为记录模式。
3)在回放模式,提供一个文件名并设定为非记录模式。
SDK对文件名没有限制,但在记录模式中文件必须是可写的。
注 可以利用QueryCaptureManager函数获取一个CaptureManager实例。
例2-11展示了如何记录和回放已采集的彩色样本。
注 在记录过程中,样本按应用程序所处理后的状态记录到硬盘。如果应用程序采集未对齐的彩色和深度样本,那么在硬盘上的样本就是未对齐的。如果应用程序使样本对齐,那么在硬盘上的样本就是已对齐的。
例2-11 利用SenseManager进行彩色数据流的记录和回放
void RecordORPlayback(string file, boolean record) { // 创建一个 SenseManager 实例 PXCMSenseManager sm=PXCMSenseManager.CreateInstance(); // 设置文件记录和回放 sm.QueryCaptureManager().SetFileName(file,record); // 选择彩色数据流 sm.EnableStream(PXCMCapture.StreamType.STREAM_TYPE_COLOR,640,480,0); // 初始化并记录300帧 sm.Init(); for (int i=0;i<300;i++) { if (sm.AcquireFrame(true).isError()) break; // 获取样本 PXCMCapture.Sample sample=sm.QuerySample(); // 在sample.color上进行处理 ...... // 进行下一帧数据处理准备 sm.ReleaseFrame(); } // 清理 sm.close(); }
7.回放模式
可以按以下配置SDK的文件回放操作:

如果想要精确定位回放中的任意帧,可以选择pause=true和realtime=false。例2-12展示了如何基于帧索引精确获取帧数据。
例2-12 精确帧定位
// 创建SenseManager实例 PXCMSenseManager sm = PXCMSenseManager.CreateInstance(); // 设置回放文件名称 sm.QueryCaptureManager().SetFileName(filename, false); // 启动流和初始化 sm.EnableStream(PXCMCapture.StreamType.STREAM_TYPE_COLOR, 0, 0); sm.Init(); // 设置realtime=true和pause=false sm.QueryCaptureManager().SetRealtime(false); sm.QueryCaptureManager().SetPause(true); // 流循环 for (int i = 0; i < nframes; i+=3) { // 设置在每3帧数据上进行操作 sm.captureManager.SetFrameByIndex(i); sm.FlushFrame(); // 等待帧到达 pxcmStatus sts = sm.AcquireFrame(true); if (sts < pxcmStatus.PXCM_STATUS_NO_ERROR) break; // 获取样本并进行操作,图像在sample.color中 PXCMCapture.Sample sample = sm.QuerySample(); ...... // 等待处理下一帧 sm.ReleaseFrame(); } // 清除 sm.close();
8.文件压缩
在磁盘上记录原始彩色和深度图像数据会给系统增加巨大的负担。举个例子,假设彩色分辨率配置为RGB32×1920×1080×30fps,深度分辨率配置为640×480×30fps,SDK需要大约272MB/s的磁盘I/O带宽来写入数据。这个要求使得大多数旋转磁盘和某些缓慢的SSD无法进行文件记录。
为了解决这个问题,SDK开发了一个试验性的功能,即在数据写入磁盘之前对其进行压缩。这个压缩功能采用H.264(I-frame only,constant QP)进行彩色图像编码和Lempel–Ziv–Oberhumer(LZO)进行深度图像编码。压缩率在彩色图像数据上大约为10:1,在深度图像数据上为2:1。而相比之前例子的高写入带宽要求,磁盘I/O带宽可降低为32MB/s。
文件压缩功能要求系统具有如下配置:Intel Iris Graphics和最新的Intel Iris Graphics驱动。
需要设置以下注册项来控制文件记录功能:
Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\SOFTWARE\Intel\RSSDK\FileRecording] "DisableH264Compression"=dword:0 "H264_QPI"=dword:8 "DisableLZOCompression"=dword:0
SDK默认H.264压缩在彩色图像流中应用,LZO在其他数据流中应用。H.264 QPI(I-frame quantization parameter)取值范围为0(最小压缩)到50(最大压缩)。一个利用H.264压缩的记录文件只能在安装Intel Iris Graphics的系统中播放。
SDK文件的记录和回放功能有以下限制:
1)文件记录可能会影响应用程序运行时间,而组织数据并写入磁盘也是较重的工作负载,因此只有在必要的时候才启用文件记录。
2)计时程序在实时回放模式下计算数据流的准确呈现时间,故对运行时间敏感。因此如果系统在重负载时,文件回放程序可能会跳帧播放。为了保证数据帧的准确回放,应关闭实时模式。
9.访问图像和音频数据
为了和其他运行库实现共享访问或互操作,SDK提供了图像接口提取图像存储。
可以利用AcquireAccess函数来锁定访问图像存储,并在ImageData结构中获取图像存储细节。图像存储的数据平面由data.planes[0-3]指出,其基址(pitch)存储在data.pitches[0-3]。当图像存储访问完成时,需要使用ReleaseAccess函数来解除访问锁定。例2-13展示了如何读取图像缓存。
例2-13 读取图像缓冲
// 图像是一个PXCMImage实例 PXCMImage.ImageData data; image.AcquireAccess(PXCMImage.Access.ACCESS_READ,data); ...... //图像平面由data.planes[0-3]指出,pitch存储在data.pitches[0-3] image.ReleaseAccess(data);
10.创建图像实例
可以利用Session接口的CreateImage函数来创建一个图像实例。应用程序需要保证用来创建图像实例(PXCMImage)的图像缓存具有更长的生命周期。例2-14展示了如何在位图中创建PXCMImage实例。
例2-14 从位图创建图像实例
// 读取位图存入内存 Bitmap bitmap = (Bitmap)Image.FromFile(file); // 图像信息 PXCMImage.ImageInfo iinfo = new PXCMImage.ImageInfo(); iinfo.width = bitmap.Width; iinfo.height = bitmap.Height; iinfo.format = PXCMImage.PixelFormat.PIXEL_FORMAT_RGB32; /* 创建图像 */ PXCMImage image=session.CreateImage(iinfo); /* 复制数据 */ PXCMImage.ImageData idata; image.AcquireAccess(PXCMImage.Access.ACCESS_WRITE, out idata); BitmapData bdata = new BitmapData(); bdata.Scan0 = idata.planes[0]; bdata.Stride = idata.pitches[0]; bdata.PixelFormat = PixelFormat.Format32bppRgb; bdata.Width = bitmap.Width; bdata.Height = bitmap.Height; BitmapData bdata2 = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly | ImageLockMode.UserInputBuffer, PixelFormat.Format32bppRgb, bdata); image.ReleaseAccess(idata); bitmap.UnlockBits(bdata2); ...... // 对图像进行其他操作 image.Dispose();