JetsonTX2の開発キットについているカメラ。
コレのことです。
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を直接さわりに行きます。長くなりますがソースです。
#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();
};
#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側で単純補間によるデモザイキングにしておきます。
#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を利用して表示まで頑張らなくてはなりません。
なかなかにハードルは高いですね…