JetsonTX2開発者キットのオンボードカメラキャプチャ

JetsonTX2の開発キットについているカメラ。
コレのことです。

![61AVOz5Lh7L._SX425_.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329142/94ea6251-fb72-f000-8667-1436a1c5613a.jpeg)

OpenCVのVideoCaptureから使おうとした人も多いはず…
センサとしてOV5693というものがついています。
使うには、

- GStreamer経由、nvarguscamerasrcを使う方法 (BGR/8bit)
- V4L2経由、直接APIを叩いて使う方法 (Bayer/10bit)

の二通りあります。

# 環境
- Jetson TX2 (Linux nvidia-desktop 4.9.140-tegra)
- OpenCV 4.1.1


# GStreamer経由でキャプチャ

使う分にはGStreamer経由のほうが簡便です。
とりあえずカメラからの絵が見たい場合、端末から以下のコマンドで確認できます。

    $ gst-launch-1.0 nvarguscamerasrc ! autovideosink

ただ、これでは絵が見られるだけで面白くありません。
実際に画像処理のソース画像にするためにOpenCVを利用します。

````cpp
#include <opencv2/opencv.hpp>

int main()
{
    cv::VideoCapture cap("nvarguscamerasrc ! video/x-raw(memory:NVMM),width=1280,height=720,format=NV12,framerate=30/1 ! nvvidconv flip-method=0 ! video/x-raw,format=NV12 ! videoconvert ! video/x-raw,format=BGR ! appsink");
    cv::Mat frame;
    while(1)
    {
        cap >> frame;
        cv::imshow("frame", frame);
        if(cv::waitKey(1) > 0)
        {
            break;
        }
    }
    return 0;
}

````

横に長くなりましたが、基本的にはカメラ入力の形式(NV12)からOpenCV形式(BGR)への変換と、動画サイズやフレームレート指定をしているだけです。

センサーの実力として1280x720@120fpsが出せますが、メモリコピーが追い付いていないのでしょうか、カクツキが発生します。

# V4L2経由でキャプチャ

V4L2経由ではJetsonのISPコアの処理をバイパスして、Bayerデータを直接取得することができます。
デモザイキングやデノイズなんかも自前でしないといけないので、実際に利用するにはハードになる方法だと思います。

V4L2を直接さわりに行きます。長くなりますがソースです。

````jetson_camera.h
#include <memory>
#include <opencv2/opencv.hpp>
#include <linux/videodev2.h>

struct Camera_Buffer
{
    struct v4l2_buffer bufinfo;
    char* data;
    int size;

    cv::Mat get_mat(int width, int height) const
    {
        return { height, width, CV_16UC1, data };
    }
    cv::cuda::GpuMat get_gpumat(int width, int height) const
    {
        return { height, width, CV_16UC1, data };
    }

};

class Jetson_Camera
{
public:
    Jetson_Camera(int width, int height);
    ~Jetson_Camera();

    Camera_Buffer* pop();
    void push(Camera_Buffer* buffer);

private:
    int fd;
    static const int buffer_num = 4;
    static const auto type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    static const auto format = V4L2_PIX_FMT_SBGGR10;       // 10bit bayer

    const int width;
    const int height;

    std::unique_ptr<Camera_Buffer[]> cam_buf;

    void open_device();
    void close_device();
    void init_format();
    void init_buffer();
    void uninit_buffer();
    void play();
    void pause();
};
````

````jeston_camera.cpp
#include <cuda_runtime.h>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include "jetson_camera.h"

// errno == EINTRのとき、ioctlをリトライする関数
int xioctl(int fd, int request, void* arg)
{
    int r;
    do{
        r = ioctl(fd, request, arg);
    } while (r == -1 && errno == EINTR);
    return r;
}

Jetson_Camera::Jetson_Camera(int width_, int height_)
  : width(width_), height(height_)
{
    open_device();
    init_format();
    init_buffer();
    play();
}

Jeston_Camera::~Jetson_Camera()
{
    pause();
    uninit_buffer();
    close_device();
}

Camera_Buffer* Jetson_Camera::pop()
{
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(fd, &fds);
    while(select(fd+1 &fds, NULL, NULL, NULL) < 0);   //画像取得できるまでスリープする

    if(FD_ISSET(fd, &fds))
    {
        struct v4l2_buffer buf = {};
        buf.type = type;
        buf.memory = V4L2_MEMORY_USERPTR;
        xioctl(fd, VIDIOC_DQBUF, &buf);     //書き込みされたバッファを取り出し
        cam_buf[buf.index].bufinfo = buf;
        return &cam_buf[buf.index];
    }
}

void Jetson_Camera::push(Camera_Buffer* buf)
{
    xioctl(fd, VIDIOC_QBUF, &(*buf).bufinfo);
}

void Jetson_Camera::open_device()
{
    fd = open("/dev/video0", O_RDWR);    //ov5693は/dev/video0にある
}

void Jetson_Camera::close_device()
{
    close(fd);
}

void Jetson_Camera::init_format()
{
    struct v4l2_format fmt = {};
    fmt.type = type;
    fmt.fmt.pix.width = width;
    fmt.fmt.pix.height = height;     //実際には自由に設定はできない（はず…）
    fmt.fmt.pix.pixelformat = format;
    fmt.fmt.pix.field = V4L2_FIELD_NONE;     //プログレッシブ

    if(xioctl(fd, VIDIOC_S_FMT, &fmt) < 0)
    {
        std::cerr << "Format Error." << std::endl;
    }
}

void Jetson_Camera::init_buffer()
{
    struct v4l2_requestbuffers req = {};
    req.count = buffer_num;
    req.type = type;
    req.memory = V4L2_MEMORY_USERPTR;      //今回は自分で確保したバッファをカメラに使ってもらう

    if(xioctl(fd, VIDIOC_REQBUFS, &req) < 0)
    {
        std::cerr << "Buffer Req Error." << std::endl;
    }

    cam_buf = std::make_unique<Camera_Buffer[]>(buffer_num);
    for(int i = 0; i < buffer_num; ++i)
    {
        cudaMallocManaged(&cam_buf[i].data, width*height*sizeof(short));   //ユニファイドメモリをバッファに使う
    }

    for(int i = 0; i < buffer_num; ++i)
    {
        struct v4l2_buffer buf = {};
        buf.type = type;
        buf.memory = V4L2_MEMORY_USERPTR;
        buf.index = i;
        buf.m.userptr = (unsigned long)cam_buf[i].data;
        buf.length = width * height * sizeof(short);

         // バッファをキューにいれる
        if(xioctl(fd, VIDIOC_QBUF, &buf) < 0)
        {
             std::cerr << "Queue Buffer Error." << std::endl;
        }
    }
}

void Jetson_Camera::uninit_buffer()
{
    for(int i = 0; i < buffer_num; ++i)
    {
        cudaFree(cam_buf[i].data);
    }
}

void Jetson_Camera::play()
{
    auto t = type;
    xioctl(fd, VIDIOC_STREAMON, &t);
}

void Jetson_Camera::pause()
{
    auto t = type;
    xioctl(fd, VIDIOC_STREAMOFF, &t);
}
````

これを使い、画像処理に持っていきます。
今回はCPU側で単純補間によるデモザイキングにしておきます。

````main.cpp
#include "jetson_camera.h"
#include <opencv2/opencv.hpp>

int main()
{
    Jetson_Camera cam(1280, 720);

    while(1)
    {
        auto t = cam.pop();
        cv::Mat raw10 = t->get_mat();
        cv::Mat raw8;
        cv::Mat bgr;
        raw10.convertTo(raw8, 0.25);
        cv::cvtColor(raw8, bgr, cv::COLOR_BayerBG2BGR);

        /*  なにか処理  */

        cv::imshow("frame", bgr);
        cam.push(t);

        if(cv::waitKey(1) > 0)
        {
            break;
        }
    }
}
````



バッファをuserptrとしてユニファイドメモリを割り当ててやると、デモザイキング・デノイズなどの処理をGPUに落としこみやすくなります。
そのほか、mmapとreadもあります。mmap方式はV4L2公式のサンプルコードにありますので、興味のある方は調べてみてください。

# その他
今回はどちらもセンサの能力をフルに発揮できていません。
1280x720@120fpsを出すにはGPUで処理を完結させる必要があります。
そのためにはV4L2を使用したうえで、OpenGL_CUDA interopを利用して表示まで頑張らなくてはなりません。
なかなかにハードルは高いですね…