ラズパイで画像処理 (V4L2で画像取得編)

ソフトウェア

Gentoo LinuxをインストールしたラズパイとUSBカメラがあるのでこれらをつないで画像処理をしてみたいと思います。

まずラズパイにUSBカメラをつなぎます。demsgコマンドなどで確認するとデバイスを認識したメッセージを見ることができます。正常に認識された場合、自動的にデバイスファイルが作成されるのでそれを使用します。今回は/dev/video0というファイルが作成されました。/dev/video1というファイルも同時に作成されましたがこちらは使用できませんでした。

# dmesg
[  236.021531] usb 1-1.4: new high-speed USB device number 6 using dwc_otg
[  236.111529] usb 1-1.4: New USB device found, idVendor=xxxx, idProduct=xxxx, bcdDevice=00.00
[  236.111551] usb 1-1.4: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[  236.111570] usb 1-1.4: Product: USB Camera  
[  236.111588] usb 1-1.4: Manufacturer: USB Camera  
[  236.152606] uvcvideo: Found UVC 1.00 device USB Camera   (eb1a:7770)
[  236.789731] uvcvideo: Failed to query (GET_INFO) UVC control 6 on unit 1: -32 (exp. 1).
[  236.790295] uvcvideo: Failed to query (GET_INFO) UVC control 8 on unit 1: -32 (exp. 1).
[  236.794656] input: USB Camera   as /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/input/input1
[  236.795319] usbcore: registered new interface driver uvcvideo
[  236.795336] USB Video Class driver (1.1.1)

# ls /dev | grep video
video0
video1

次にカメラから画像を取得します。取得するにはOpenCVなどのライブラリを使用すると非常に簡単に行えますが、今回はライブラリを使用せずLinux標準機能のV4L2を使用したいと思います。
V4L2はVideo for Linux 2の意味でLinuxでビデオに関連するデバイスを扱うことのできるLinuxカーネルの機能です。

V4L2を使用した画像取得のプログラムをC言語で作成します。プログラムするにあたり下記のサイトなどを参考にしました。

詳解V4L2 (video for linux 2)
https://zenn.dev/turing_motors/articles/programming-v4l2

Image Formats — The Linux Kernel documentation
https://www.kernel.org/doc/html/v4.10/media/uapi/v4l/pixfmt.html

これらを参考に実際に作成したプログラムがこちらです。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <linux/videodev2.h>

struct buffer
{
        void *start;
        size_t length;
};

static int xioctl(int fd, int request, void *arg)
{
        int r;
        do {
                r = ioctl(fd, request, arg);
        } while(r == -1 && errno == EINTR);
        return r;
}

int main(int argc, char *argv[])
{
        int fd = open("/dev/video0", O_RDWR);
        if(fd == -1) {
                printf("error 1\n");
                return -1;
        }

        struct v4l2_capability cap;
        if(xioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) {
                printf("error 2\n");
                return -1;
        }

        if(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
        }

        if(cap.capabilities & V4L2_CAP_STREAMING) {
        }

        struct v4l2_format fmt = {0};
        fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        fmt.fmt.pix.width = 640;
        fmt.fmt.pix.height = 480;
        fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
        fmt.fmt.pix.field = V4L2_FIELD_ANY;

        if(xioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
                printf("error 3\n");
                return -1;
        }

        memset(&fmt, 0, sizeof(fmt));
        fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

        if(xioctl(fd, VIDIOC_G_FMT, &fmt) == -1) {
                printf("error 4\n");
                return -1;
        }

        struct v4l2_requestbuffers req = {0};
        req.count = 3;
        req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        req.memory = V4L2_MEMORY_MMAP;
        if(xioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
                printf("error 5\n");
                return -1;
        }

        if(req.count < 3) {
                printf("error 6\n");
                return -1;
        }

        struct buffer *buffers;
        const int n_buffers = 3;
        buffers = (struct buffer*)calloc(n_buffers, sizeof(struct buffer));
        if(buffers == NULL) {
                printf("error 7\n");
                return -1;
        }
        for(int i = 0; i < req.count; i++) {
                struct v4l2_buffer buff = {0};
                buff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                buff.memory = V4L2_MEMORY_MMAP;
                buff.index = i;
                if(xioctl(fd, VIDIOC_QUERYBUF, &buff) == -1) {
                        printf("error 8\n");
                        return -1;
                }

                buffers[i].length = buff.length;
                buffers[i].start = mmap(NULL, buff.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buff.m.offset);
        }

        for(int i = 0; i < n_buffers; i++) {
                struct v4l2_buffer buf = {0};
                buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                buf.memory = V4L2_MEMORY_MMAP;
                buf.index = i;

                if(xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
                        printf("error 9\n");
                        return -1;
                }
        }

        enum v4l2_buf_type type;
        type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if(xioctl(fd, VIDIOC_STREAMON, &type) == -1) {
                printf("error 10\n");
                return -1;
        }

        struct pollfd fds[1];
        fds[0].fd = fd;
        fds[0].events = POLLIN;
        int p = poll(fds, 1, 5000);
        if(p == -1) {
                printf("error 11\n");
                return -1;
        }

        struct v4l2_buffer buf = {0};
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        if(xioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
                printf("error 12\n");
                return -1;
        }

        int out = open("out.data", O_RDWR | O_CREAT, S_IRWXU | S_IRWXO | S_IRWXG);
        if(out == -1) {
                ;
        }
        write(out, buffers[buf.index].start, buffers[buf.index].length);
        close(out);

        //struct v4l2_buffer buf = {0};
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = 0;
        if(xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
                printf("error 13\n");
                return -1;
        }

        //enum v4l2_buf_type type;
        type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if(xioctl(fd, VIDIOC_STREAMOFF, &type) == -1) {
                printf("error 14\n");
                return -1;
        }

        for(int i = 0; i < n_buffers; i++) {
                munmap(buffers[i].start, buffers[i].length);
        }
        free(buffers);
        close(fd);
}

このプログラムでは640×480のサイズの画像をYUYVフォーマットで取得します。gccを使用して、特にオプションの指定はなしでビルドできるはずです。取得した画像データはそのままファイルに保存します。ここで使用しているYUYVのデータはY1,U1,Y2,V1の順でデータが並んでいます。YUV422と呼ばれることもあります。

YUYVフォーマットのままでは画像ビューワで開いて確認することができないので一般的な画像フォーマットに変換します。例としてjpegフォーマットに変換してみます。他にもPNGフォーマットなどに変換することもできます。
変換にはffmpegやimagemagickを使用する方法があります。これら二通りの方法を下記に示します。ラズパイにはどちらのコマンドもインストールしていないので別のマシンで変換します。ラズパイはsshでログインして操作しているので、scpを利用してファイルを持ってきて変換します。

# scp root@192.168.0.171:/path/to/image/out.data ./
# ffmpeg -f rawvideo -s 640x480 -pix_fmt yuyv422 -i out.data out.jpg
# dd if=out.data conv=swab | convert -sampling-factor 4:2:2 -size 640x480 -depth 8 uyvy:- out.jpg

このコマンドで変換したout.jpgを画像ビューワで開くとカメラから取得した画像を確認できます。

USBカメラで撮影した画像です。ffmpegを使用してjepg画像に変換しました。前回分解したソーラーセンサーライトが写っています。

ここまでで画像を取得することまで成功したので、取得した画像データに対していろいろな画像処理ができるようになりました。実際の画像処理は次回以降にやっていきたいと思います。

V4L2を使う注意点として、カメラへのパラメータのセットで解像度や画像のフォーマットを指定しても設定しても反映されません。書き込みにはエラーなく成功しますがVIDIOC_G_FMTしても反映されていないことが確認できます。

コメント