工具准备
1.从http://www.structure.io/openni 下载OpenNI-Windows-x64-2.2.0.33.zip
2.打开zip,运行其中的msi文件。安装完毕注销windows并重新登入(安装完毕后,将有两个新的环境变量OPENNI2_INCLUDE64,OPENNI2_LIB64被添加到系统环境变量中)
3.准备好visual studio 2010 或更高版本
创建第一个工程
新建win32控制台应用程序
配置工程依赖项
编写代码
以下代码是对OpenNI2提供的范例进行的归纳,任何与OpenNI2及深度传感器无关的代码都被略去。
启动OpenNI2和设备
以下是常规的读取深度帧的上下文初始化过程,其梗概是:
启动OpenNI2—>打开一个传感器设备—>通过设备创建一个视频流(这个流可以是深度图像流,颜色图像流或其他设备支持的流)
openni::Status rc = openni::STATUS_OK;
rc = openni::OpenNI::initialize();
printf("After initialization:\n%s\n", openni::OpenNI::getExtendedError());
openni::Device device;
rc = device.open(openni::ANY_DEVICE);
if (rc != openni::STATUS_OK)
{
printf("DepthSensor Viewer: Device open failed:\n%s\n", openni::OpenNI::getExtendedError());
openni::OpenNI::shutdown();
return 1;
}
openni::VideoStream depth;
rc = depth.create(device, openni::SENSOR_DEPTH);
auto videomode = new openni::VideoMode();
videomode->setFps(30);
videomode->setPixelFormat(openni::PIXEL_FORMAT_DEPTH_100_UM);
videomode->setResolution(160, 120);
depth.setVideoMode(*videomode);
if (rc == openni::STATUS_OK)
{
rc = depth.start();
if (rc != openni::STATUS_OK)
{
printf("DepthSensor Viewer: Couldn't start depth stream:\n%s\n", openni::OpenNI::getExtendedError());
depth.destroy();
}
}
else
{
printf("DepthSensor Viewer: Couldn't find depth stream:\n%s\n", openni::OpenNI::getExtendedError());
}
// m_streams 将在后面的代码片段中被用到,我们需要一个指针的数组作为参数
m_streams = new VideoStream*[1];
m_streams[0] = depth;
在自定义的上下文中读取深度帧并渲染
你可以将下面的代码放置在渲染线程的循环中,比如通过glut窗口系统的glutDisplayFunc()注册的回调函数中
int changedIndex;
openni::Status rc = openni::OpenNI::waitForAnyStream(m_streams, 2, &changedIndex);
openni::VideoFrameRef depthFrame;
m_streams[changedIndex]->readFrame(&depthFrame);
/// 我们已经拿到了数据,现在可以用你想要的方式进行绘制了, 典型的绘制如下:将数据转为RGB纹理图片
const openni::DepthPixel* pDepthRow = (const openni::DepthPixel*)depthFrame.getData();
openni::RGB888Pixel* pTexRow = m_pTexMap + m_depthFrame.getCropOriginY() * m_nTexMapX;
int rowSize = depthFrame.getStrideInBytes() / sizeof(openni::DepthPixel);
for (int y = 0; y < depthFrame.getHeight(); ++y)
{
const openni::DepthPixel* pDepth = pDepthRow;
openni::RGB888Pixel* pTex = pTexRow + depthFrame.getCropOriginX();
for (int x = 0; x < depthFrame.getWidth(); ++x, ++pDepth, ++pTex)
{
if (*pDepth != 0)
{
char value = 255 - ((float)(*pDepth) / (float)256); ///DepthPixel是两个字节的,需要将其压缩为1个字节;
///这种转换效果也许不是最好的,关于这可以查看OpenNI2的例子SimpleViewer
pTex->r = value;
pTex->g = value;
pTex->b = 0;
}
}
pDepthRow += rowSize;
pTexRow += m_nTexMapX;
}
}
当然,OpenNI2 也提供了将深度值转换为世界坐标系下的3维坐标的API:
void depthPixelToWorld3d(const openni::VideoStream& depthStream, int depthX, int depthY,openni::DepthPixel depthZ, float world3d[3])
{
openni::CoordinateConverter::convertDepthToWorld(depthStream, depthX, depthY,depthZ, &world3d[0],
&world3d[1], &world3d[2]);
}
const openni::DepthPixel* pDepthRow = (const openni::DepthPixel*)depthFrame.getData();
int depthYStart = depthFrame.getCropOriginY();
int depthXStart = depthFrame.getCropOriginX();
int rowSize = depthFrame.getStrideInBytes() / sizeof(openni::DepthPixel);
auto points= new float[depthFrame.getWidth()* depthFrame.getHeight()][3];
int pointCounts = 0;
for (int y = 0; y < depthFrame.getHeight(); ++y)
{
const openni::DepthPixel* pDepth = pDepthRow;
for (int x = 0; x < depthFrame.getWidth(); ++x, ++pDepth)
{
if (*pDepth != 0)
{
depthPixelToWorld3d(depthStream, depthXStart + x,
depthYStart + y, *pDepth, points[pointCounts]);
++pointCounts;
}
}
pDepthRow += rowSize;
}
运行程序
用USB连接StructureSensor,并将OpenNI2/Redist目录下的所有文件和文件夹拷贝到应用程序的同一目录下,运行程序。如果程序不能找到设备,请抽出USB数据线并重新连接。
可能存在的问题
openni::CoordinateConverter::convertDepthToWorld()所输出的世界坐标的XYZ分量的值并不是以毫米为单位
这是OpenNI2社区最近才修复的bug:
https://github.com/occipital/OpenNI2/commit/1fce8edffab43c4a4cf201cff86f415b07a2d37f
要想解决这个问题,只能自己编译OpenNI2。从https://github.com/occipital/OpenNI2下载最新的zip源码。无须下载该页面所指出的其他内容,直接运行源码包中的Visual Studio sln文件,生成其中的OpenNI工程,将生成的OpenNI2.dll拷贝到应用程序所在目录,替换旧的OpenNI2.dll
自己编译完整的OpenNI2安装包
如果需要生成完整的OpenNI2安装包,请使用Visual Studio 2010,并安装
WIX 3.5 http://wix.codeplex.com/releases/view/60102 。运行OpenNI.sln 并生成解决方案下的Install工程