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=truepause=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();