2011年1月31日月曜日

V4L2を使ってカメラ画像を取得する

V4L2で画像取得してOpenCVで加工する

Webカメラ画像を取得する

LinuxでWebカメラ画像を取得する方法は色々あります。その中で低レベルAPIのV4L2を使った方法でWebカメラから画像を取得してみます。

私が持っているWEBカメラはUVC対応していますが、V4L2上で扱えるフォーマットを調べてみると、

 画像フォーマット:YUYV, 320x240(4fps)

このフォーマットでしかサポートされていない事が判明しました。

このような低スペックの場合、OpenCVライブラリ経由でカメラデバイスの初期化に失敗します。
OpenCVはオープンソースなので、自分でOpenCVライブラリに修正パッチを当てて使えるようにする方法もありますが、今回はV4L2サンプルコードを基にカメラ制御プログラムを作りました。

実装

V4L2のサンプルコードのままだと画像保存が行えないので、
V4L2経由で取得したストリームデータをYUV形式→RGB変換して
OpenCVライブラリのIplImageフォーマットに合わせてデータを入れて画像を保存しました。
これでV4L2のAPIを使って画像を取得して、その後にOpenCVで画像加工が可能になります。

以下、サンプルプログラムの説明。
  1. デバイスオープン(/dev/video0)
  2. V4L2 APIを使ってキャプチャー情報を取得。
  3. ストリームON/OFFでキャプチャデータ取り込み許可を制御。
  4. MMAP経由でデータを取得する。
  5. 取得したデータ形式(YUYV)をBGR形式に変換して、OpenCVを使ってPNG形式で保存。
    (OpenCV内臓のYUV→BGR変換処理はうまく行かなかった)

実際動いたコード公開。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>          /* low-level i/o */
#include <errno.h>
#include <malloc.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <cv.h>
#include <highgui.h>

#define CLEAR(x) memset (&(x), 0, sizeof (x))

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

const static char * dev_name    = "/dev/video0";
static int          fd          = -1;
#define N_IMAGES                (2)
static IplImage *               images[N_IMAGES];
#define V4L_WIDTH               (320)
#define V4L_HEIGHT              (240)

static void errno_exit(const char *s){
    fprintf (stderr, "%s error %d, %s\n", s, errno, strerror (errno));
    exit (EXIT_FAILURE);
}

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

static void Conv_YUYV2BGR(IplImage *src_img){
    unsigned char *yuyv = src_img->imageDataOrigin;
    unsigned char *bgr = src_img->imageData;
    int z = 0;
    int x;
    int yline;

    for (yline = 0; yline < src_img->height; yline++){
        for (x = 0; x < src_img->width; x++){
            int r, g, b;
            int y, u, v;

            if (!z)
              y = yuyv[0] << 8;
            else
              y = yuyv[2] << 8;
            u = yuyv[1] - 128;
            v = yuyv[3] - 128;

            r = (y + (359 * v)) >> 8;
            g = (y - (88 * u) - (183 * v)) >> 8;
            b = (y + (454 * u)) >> 8;

            *(bgr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);
            *(bgr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
            *(bgr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);

            if (z++) {
                z = 0;
                yuyv += 4;
            }
        }
    }
}


static void process_image(IplImage *yuv_img){
    //IplImage *frm_img = cvCreateImage(cvSize(V4L_WIDTH, V4L_HEIGHT), IPL_DEPTH_8U, 3);
    //cvCvtColor(yuv_img, frm_img, CV_YCrCb2BGR);
    Conv_YUYV2BGR(yuv_img);
    cvSaveImage("/var/www/fcgi/media/test.png", yuv_img, NULL);
    fputc ('.', stdout);
    fflush (stdout);
}

static int read_frame(void) {
    struct v4l2_buffer buf;
    unsigned int i;

    CLEAR (buf);
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;

    if (-1 == xioctl (fd, VIDIOC_DQBUF, &buf)) {
        if(EAGAIN == errno) return 0;
        errno_exit ("VIDIOC_DQBUF");
    }
    process_image (images[buf.index]);

    if (-1 == xioctl (fd, VIDIOC_QBUF, &buf)) errno_exit ("VIDIOC_QBUF");

    return 1;
}

static void get_info(){
    struct v4l2_fmtdesc fmtdesc;
    int i = 0;

    for(i = 0; i < 10; i++){
        fmtdesc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        fmtdesc.index = i;
        if (0 > ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) < 0) break;
        printf("format %d : pixelfotmat:%x desc:%s, ", i, fmtdesc.pixelformat, fmtdesc.description);
        if (fmtdesc.pixelformat == V4L2_PIX_FMT_RGB24) printf("RGB24\n");
        if (fmtdesc.pixelformat == V4L2_PIX_FMT_BGR24) printf("BGR24\n");
        if (fmtdesc.pixelformat == V4L2_PIX_FMT_YUYV)  printf("YUYV\n");
    }
    
}


static void mainloop(void){
    unsigned int count;
    for(count = 100; 0<count; count--){
        for (;;) {
            fd_set fds;
            struct timeval tv;
            int r;

            FD_ZERO (&fds);
            FD_SET (fd, &fds);

            /* Timeout. */
            tv.tv_sec = 2;
            tv.tv_usec = 0;

            r = select (fd + 1, &fds, NULL, NULL, &tv);

            if (-1 == r) {
                if (EINTR == errno) continue;
                errno_exit ("select");
            }

            if (0 == r) errno_exit("select timeout\n");

            if (read_frame ()) break;
        }
    }
}

static void open_device(){
    struct v4l2_capability cap;
    struct v4l2_format fmt;
    unsigned int i;
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    fd = open (dev_name, O_RDWR | O_NONBLOCK, 0);
    if (-1 == fd) errno_exit("Cannot open device");
    if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) errno_exit("no V4L2 device");
    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) errno_exit("can't dev");
    if (!(cap.capabilities & V4L2_CAP_STREAMING)) errno_exit("err stream IO");

    get_info(); //debug

    CLEAR (fmt);
    fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width       = V4L_WIDTH; 
    fmt.fmt.pix.height      = V4L_HEIGHT;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    fmt.fmt.pix.field       = V4L2_FIELD_ANY;
    if (-1 == xioctl (fd, VIDIOC_S_FMT, &fmt)) errno_exit("VIDIOC_S_FMT");
    if((fmt.fmt.pix.width != V4L_WIDTH) || (fmt.fmt.pix.height != V4L_HEIGHT)) errno_exit("unavailable size 320x240");

    //init_mmap
    struct v4l2_requestbuffers req;
    CLEAR (req);
    req.count  = 2;
    req.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    if (-1 == xioctl (fd, VIDIOC_REQBUFS, &req)) errno_exit("err mem mapping");
    if (2 > req.count) errno_exit("Insufficient buffer memory on %s\n");

    for (i = 0; i < N_IMAGES; i++) {
        images[i] = cvCreateImage(cvSize(V4L_WIDTH, V4L_HEIGHT), IPL_DEPTH_8U, 3);

        struct v4l2_buffer buf;
        CLEAR (buf);
        buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index  = i;
        if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf)) errno_exit("QUERYBUF");
        printf("size:%d:%d\n", images[i]->imageSize, buf.length);
        //images[i]->imageStep = V4L_WIDTH * 2;
        images[i]->imageSize = buf.length;
        images[i]->imageDataOrigin =
            mmap (NULL /* start anywhere */,
                  buf.length,
                  PROT_READ | PROT_WRITE /* required */,
                  MAP_SHARED /* recommended */,
                  fd, buf.m.offset);

        if (MAP_FAILED == images[i]->imageData) errno_exit ("mmap");
        if (-1 == xioctl (fd, VIDIOC_QBUF, &buf)) errno_exit ("VIDIOC_QBUF");
    }

    if (-1 == xioctl (fd, VIDIOC_STREAMON, &type)) errno_exit ("STREAMON");
}

static void close_device(void){
    unsigned int i;
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if (-1 == xioctl (fd, VIDIOC_STREAMOFF, &type)) errno_exit ("STREAMOFF");

    for (i = 0; i < N_IMAGES; i++) {
        munmap (images[i]->imageData, images[i]->imageSize);
        cvReleaseImage(&images[i]);
    }
    close (fd);
}

int main(){
    open_device();
    mainloop();
    close_device();

    exit(EXIT_SUCCESS);
    return 0;
}


関連記事



コメントを投稿

Androider