1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

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

61AVOz5Lh7L.SX425.jpg

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を利用します。

#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を利用して表示まで頑張らなくてはなりません。
なかなかにハードルは高いですね…

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?