C++ 读取 .oni 文件


发布于

|

分类

学姐给了两个十几 G 的. oni 文件,我需要将里面的 RGB 图像拆开,逐帧保存成. png 文件。

.oni 文件是 OpenNI 导出的数据格式。OpenNI 这个东西之前是 RGBD 相机的 SKD,被 Apple 收购之后就消失了。目前官网也没有了,Github 上的 fork 也不更新了,甚至某些文件最近更改于 17 年前。

怪可惜的。

各种材料都找不到了,只有去 Github 上面猛扒拉东西了。最终找到了 这个 Demo。但是代码太长了,看不懂……

下面依旧是 Log

依赖

Linux 上面各种依赖从来都很头疼。这里我们需要安装 libopenni-dev。当然,我知道有 libopenni2-dev,网上默认下载到的就是 2.0 版本的。但是!!我不知道如何写 CMakelists.txt,就放弃使用新版了。

代码

下面的代码是基于上面找出来的代码修改的。

#include <string>
#include <vector>
#include <chrono>
#include <iostream>
#include <algorithm>
#include <XnCppWrapper.h>
#include <opencv2/opencv.hpp>

#define RETURN_ON_ERROR(status, errmsg) {if (status != XN_STATUS_OK) {std::cerr<<errmsg<<std::endl; return false;}}


int main(int argc, char **argv) {
    if (argc == 1) {
        std::cerr << "参数是 .oni 文件的路径" << std::endl;
        return 1;
    }
    std::string filename(argv[1]);

    xn::Context xContext;
    xn::Player xPlayer;
    xn::ImageGenerator xImageGenerator;
    xn::DepthGenerator xDepthGenerator;

    unsigned int numFrames = 0;

    // 初始化
    RETURN_ON_ERROR(xContext.Init(), "Unable to initialize Context");
    RETURN_ON_ERROR(xContext.OpenFileRecording(filename.c_str(), xPlayer), "Unable to open file" << filename);
    xPlayer.SetRepeat(static_cast<XnBool >(false));
    RETURN_ON_ERROR(xDepthGenerator.Create(xContext), "Unable to create DepthGenerator!");
    RETURN_ON_ERROR(xImageGenerator.Create(xContext), "Unable to create ImageGenerator!");
    xImageGenerator.SetPixelFormat(XN_PIXEL_FORMAT_RGB24);
    RETURN_ON_ERROR(xPlayer.GetNumFrames(xImageGenerator.GetName(), numFrames), "Unable to get number of frames!");
    std::cout << "Number of frames:" << numFrames << std::endl;
    RETURN_ON_ERROR(xContext.StartGeneratingAll(), "Unable to start generating images!");
    const xn::NodeInfo info = xImageGenerator.GetInfo();

    // 两个窗口
    cv::namedWindow("RGB", CV_WINDOW_AUTOSIZE);
    cv::namedWindow("Depth", CV_WINDOW_AUTOSIZE);

    // 两个原始数据
    xn::ImageMetaData xImageMap;
    xn::DepthMetaData xDepthMap;

    // 两个 Mat
    cv::Mat imgRGB;
    cv::Mat imgDepth;

    // 临时变量
    cv::Mat imgRW;
    int index = 0;
    int numPx = 0;
    int xRes = 0, yRes = 0, h = 0, w = 0;
    const XnDepthPixel *pDepth = nullptr;
    const XnRGB24Pixel *xx = nullptr;

    std::vector<cv::Mat> xyd;
    char rgb_filename[20];
    // 逐帧拆开
    for (unsigned long int i = 0; i < numFrames; ++i) {
        if (i > 30000) {
            break;
        }
        std::cout << i << std::endl;
        xImageGenerator.WaitAndUpdateData();
        xDepthGenerator.WaitAndUpdateData();
        // 获取 RGB 图像
        xImageGenerator.GetMetaData(xImageMap);
        h = xImageMap.YRes();
        w = xImageMap.XRes();
        xx = xImageMap.RGB24Data();
        imgRGB = cv::Mat(h, w, CV_8UC3, (void *) xx);
        cvtColor(imgRGB, imgRGB, CV_BGR2RGB);

        // 获取深度图像
        xDepthGenerator.GetMetaData(xDepthMap);
        xRes = xDepthMap.FullXRes();
        yRes = xDepthMap.FullYRes();
        pDepth = xDepthMap.Data();
        numPx = xRes * yRes;
        XnPoint3D ptProj[numPx];
        XnPoint3D ptWorld[numPx];
        for (int y = 0; y < yRes; ++y) {
            for (int x = 0; x < xRes; ++x) {
                index = x + y * xRes;
                ptProj[index].X = (XnFloat) x;
                ptProj[index].Y = (XnFloat) y;
                ptProj[index].Z = pDepth[index];
            }
        }
        xDepthGenerator.ConvertProjectiveToRealWorld((XnUInt32) numPx, ptProj, ptWorld);
        (cv::Mat(yRes, xRes, CV_32FC3, (void *) ptWorld)).copyTo(imgRW);
        cv::split(imgRW, xyd);
        imgDepth = xyd.at(2);
        imgDepth.convertTo(imgDepth, CV_16U);

        // 显示
        cv::imshow("RGB", imgRGB);
        cv::imshow("Depth", imgDepth);
        sprintf(rgb_filename,"JPEGImages/rgb_%ld.png", i);
        cv::imwrite(rgb_filename, imgRGB);
        cv::waitKey(1);
    }

    // 停止
    xContext.StopGeneratingAll();
    xContext.Release();

    return 0;
}

这里需要注意的地方是,xImageGenerator.WaitAndUpdateData();xDepthGenerator.WaitAndUpdateData(); 貌似是用于 “获取下一帧” 的。

另外有个问题:有没有更优雅的方法生成连续的文件名?我并不太想使用 C 的字符串。

CMakeLists.txt

真的,别小看这个 txt,它可是比依赖更烦人的存在。

我的工程名叫 onion

cmake_minimum_required(VERSION 3.6)
project(onion)

# compile with C++11 support
add_definitions("-std=c++0x -Dlinux -D__STANDALONE_ONI_EXTRACTOR")

# OpenCV
find_package(OpenCV REQUIRED)

# OpenNI
set(OPENNI_INCLUDE_DIRS "$ENV{OPEN_NI_INSTALL_PATH}/usr/include/ni")
include_directories(${OPENNI_INCLUDE_DIRS})
find_library(OPEN_NI_LIBRARY NAMES OpenNI PATHS $ENV{OPEN_NI_INSTALL_PATH}/usr/lib)
SET(LIBS ${LIBS} ${OPEN_NI_LIBRARY})

set(SOURCE_FILES main.cpp)

add_executable(onion ${SOURCE_FILES})
target_link_libraries(onion ${LIBS} ${OpenCV_LIBS})

其实可以用更优雅的方式来设定 “使用 C++ 11 标准” 的。或者,我不知道有没有更优雅的方式,像使用 find_package(OpenCV) 一样加载 OpenNI 库。

结束

就到这里了。

写代码的时候,ptProjptWorld 都是 new 出来的,然后一运行,内存就蹭蹭蹭地往上彪。最后 delete 了一下才算消停。这也是第一次写内存泄漏吧。

一个新成就达成~


评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注