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;
}


関連記事



2011年1月28日金曜日

V4L2のサンプルコードで遊んでみる

http://yasu-2.blogspot.com/2010/04/v4l2.html
サンプルコードの使い方がのっている。
$ gcc v4l2_example.c
$ ./a.out -d /dev/video0

内容:

"$ gcc v4l2_example.c"
- Yet Another Diary: V4L2のサンプルコードで遊んでみるGoogle サイドウィキで表示

LinuxでRAMディスクを使う

Linuxはハードディスクを持たないシステムでも動くように設計されていているので、
標準でRAMディスクが使えるように設定されているようである。

動的に割り当てられているRAMディスクは
/dev/shm

ここである。
もし自分で設定するならば、
# mount -t tmpfs -o size=128m tmpfs /dev/shm

もしくは、

# mke2fs /dev/ramdisk
# mkdir /mnt/ramdisk
# mount -t ext2 /dev/ramdisk /mnt/ramdisk

で設定できて、確認する場合は下記コマンドでできる。
df -h

参考URL:http://itpro.nikkeibp.co.jp/article/Keyword/20070330/267007/

2011年1月27日木曜日

mini2440で使うSDL設定

あー、見事にどつぼに嵌った。。
netsurf、libts、sdlの順にソース追いかけていったらタッチスクリーンを使う設定になってなかっただけであった。。

export TSLIB_TSDEVICE=/dev/input/event1
export SDL_MOUSEDEV=$TSLIB_TSDEVICE
export SDL_MOUSEDRV=TSLIB

SDLのマウスドライバオープン処理で上記パラメータをgetenvで見てるので
設定してあげないとタッチスクリーンのドライバを読み込んでくれないようだ。
Devの部分だけ注意してあとはそのままやればいけるはず。

パラメータの設定例を探してみたら見事同じ道ではまった人がいたようだった。
参考URL:http://www.friendlyarm.net/forum/topic/932

設定例:
/etc/profileを編集
mknod /dev/input/touchscreen0 c 13 65
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_CONFFILE=/etc/ts.conf
export TSLIB_PLUGINDIR=/usr/lib/ts0
export TSLIB_FBDEVICE=/dev/fb0
export TSLIB_CONSOLEDEVICE=none
#export TSLIB_TSEVENTTYPE=INPUT
#export TSLIB_TSDEVICE=/dev/input/event1
export TSLIB_TSDEVICE=/dev/input/touchscreen0
export SDL_MOUSEDEV=$TSLIB_TSDEVICE
export SDL_MOUSEDRV=TSLIB

補足:
SDLライブラリ経由でTouchpanelを使用するときは、
SDLライブラリ自身がTouchPanel有効になっていないと使えません。
確実に使用可能なSDLライブラリを用意する為にソースコードからコンパイルしたほうがよい。
configureでlibtsが有効にならない時はlibts-devが入ってないので、入れておくこと。
apt-get install libts-dev

・Netsurf27を使う時は
ホームディレクトリに /.netsurf/Cookies
を作って、
framebuffer/gui.c
で初期GUIパラメータや画面サイズ、URLなどオリジナル設定を入れておく。

参照URL
---
http://ivis-mynikki.blogspot.com/2010/12/blog-post_19.html

netsurfでタッチパネル対応パッチをつくる

フレームバッファ用ブラウザのnetsurfを使っているのだが、
mini2440のタッチパネルとは相性が悪く90度回転した座標を取得してしまって
左上が原点(0,0)が通常だとすると左したが原点(0,0)になる感じ。

これは困ったと思ってソースを見ていたら
フレームバッファ周りはSDLライブラリを使用していて、
libnsfbライブラリが担当しているようだ。
そこでlibtsから直接座標を取得したものに置き換えるパッチを
surface/sdl.cに埋め込んだ。

次の段階はSDLライブラリの方で修正する方法を見つけること。
libtsを使ってると思うので、キャリブレーションデータを反映させれるように修正すれば
動くんじゃないかなと、推測してる。
もし直接操作してたら、SDL経由はイベントだけ拾って
XY座標はlibts経由という方法のままでいく。

Linuxで印刷機能

libcupsys2を入れる。

内容:

"linuxでプリンタを使う cups"
- Linux愛好者の独り言 linuxでプリンタを使う cupsGoogle サイドウィキで表示

C言語を使ったプリンタ出力

2011年1月24日月曜日

tar.bz2ファイル解凍のやり方

2コマンドで行う。

bunzip2 [ファイル名].tar.bz2
tar xvf [ファイル名].tar

2011年1月19日水曜日

ソフトウェアIC化考案(3)

3.モジュール構造を考える(2)

たたき台のソースが出来たので貼り付けておく。

基本構造はこんな感じにビジネスロジックだけ書き入れるスタイルにしておいて、
外部モジュールのプロパティアクセスはシングルトンでGetInstance経由でポインタ取得する。
プログラムの構造化は、ビジネスロジックの下位モジュールを別で作成してドライバなり扱えるようにする。
上位ロジックはイベント間通信でやり取りする。
とりあえずこれでシリアル通信でバーコードリーダとやり取り出来るプログラムを作って
どのレベルまでできるか確認しておきたい。

/*
ソース説明
objbase.h   ... オブジェクト基底モジュール
objects.h   ... ツールで自動生成するオブジェクトヘッダファイル
event.def   ... ツールで自動生成するイベント一覧
main.c    ... メインループ

sample_obj1.c  ... サンプル用オブジェクト実体

sample_obj1.def  ... サンプル用オブジェクトヘッダファイル
sample_obj2.def  ... サンプル用オブジェクトヘッダファイル

使い方
まず
オブジェクト作成コマンドを実行。 
コマンドイメージ)オブジェクト生成して、イベントを追加する。
./manage.py create_object sample_obj1
./manage.py add_event sample_obj1 evt1_1 evt1_2

*/

//**********************************************************************
// objbase.h
//**********************************************************************
#ifndef __OBJBASE_H__
#define __OBJBASE_H__
//オブジェクト構造体定義
typedef unsigned int uint32;
typedef signed int sint32;


typedef void (*event)(int* sid);
typedef struct {
    uint32 req_flag;          // 要求した側が操作できるフラグ
    uint32 act_flag;          // メインループ側が操作できるフラグ
    uint32 sid[32];           // イベント別シーケンス番号
    event* events;            // イベントリスト
    void* property;           // 変数ポインタ(プロパティ)
} object;


//シーケンス関係
#define dSEQ_END  (-1)
#define dSEQ_START  (0)

//イベント関係
#define SetEvent(id) objects[id >> 16].req_flag |= (1 << (id & 0x1f));
#define ClrEvent(id) objects[id >> 16].req_flag &= (0xffffffff ^ (1 << (id & 0x1f)));
#define isEventStart(id) (0 != (objects[id >> 16].act_flag & (1 << (id & 0x1f))))
#define isEventFin(id)   (0 == (objects[id >> 16].act_flag & (1 << (id & 0x1f))))

//オブジェクト関係
#define get_instance(id) (objects[id >> 16].property);


#endif//__OBJBASE_H__






//**********************************************************************
// sample_obj1.c
//**********************************************************************
#include "objbase.h"
#include "obj_test.h"

void evt1_1(int* );
void evt1_2(int* );
#include "sample_obj1.def"
#include "event.def"

void smaple_obj1_init()
{
 sample_obj1_property = 0;
}

//シーケンス1
void evt1_1(int* sid){
 switch(sid){
  case 0:
   SetEvent(EVT_OBJ1_TEST2);
   *sid++;
   break;
  case 1:
   if(isEventStart(EVT_OBJ1_TEST2)){
    ClrEvent(EVT_OBJ1_TEST2);
    *sid++;
   }
   break;
  case 2:
   if(isEventFin(EVT_OBJ1_TEST2)){
    *sid++;
   }
   break;
  case 3:
   int* _inctance = (int*)get_instance(EVT_OBJ1_INST);
   if(_instance == 1) *sid = dSEQ_END;
   break;
 }
}

//シーケンス2
void evt1_2(int* sid){
 *sid++;
 if(10req_flag & chk_flag)
#define ACT_FLG (obj->act_flag & chk_flag)



int main(){
    object* obj;
    event* evt;
    int* sid;
    int chk_flag;

    init_object();                   // 全体初期化

    for(;;){                         // メインループ
        chkEvent();
        for(obj = objects; NULL != obj; obj++){
            for(evt=obj->events, sid=obj->sid, chk_flag=0x1; NULL!=evt; evt++, sid++, chk_flag<<=1){
                if(0 != ACT_FLAG){
                    if(0 <= obj->sid){
                        evt(obj->property, sid);              //2.イベント処理実行
                    }else if (0 == REQ_FLG){
                        obj->act_flag ^= chk_flag;            //3.イベント処理終了
                    }
                }else if(0 != REQ_FLG){
                    obj->sid = 0;
                    obj->act_flg |= chk_flag;                 //1.イベント処理開始
                    evt(obj->property, sid);
                }
            }
        }
    }
    
    final_object();                             //全体開放

 return -1;
}

ソフトウェアIC化考案(1)

1.決められた定石について考える。

そもそも自分のスタート地点が組み込み屋なので、「ソフトウェアIC化」という考え自体が
ハードウェア寄りな気質がある。

1-1.現状について

Wikipediaによるとソフトウェアコンポーネントというのは、
http://ja.wikipedia.org/wiki/%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88

抜粋)
ソフトウェアコンポーネントを効果的に再利用可能にするには多大な労力と注意が必要である。特に以下のような点が重要である:

* 完全に文書化すること
* テストをしっかり行うこと
* 特に各種入力値を入力したときの動作をしっかり確認すること
* 分かり易い(あるいは利用し易い)エラーメッセージを返すこと
* 想定していない状況で使われる可能性があることを念頭において開発すること

1つのプログラムを作るのに、これだけの労力を払って
「価値はあるのか。」
という問題がある。
Borland社のDelphiで成功したコンポーネント思考の開発手法はあるが、
.Net製品を見ると肥大化しすぎているように思う。
もし準拠して作るならば、
「自分でコンポーネント部品を作ってからメインロジックを組み込む」
この手順を踏まないといけなくなるので、これが1つの制約になって敷居が高くなってるように見える。

1-2.ソフトウェアの定石
プログラム言語とは、「機械語にするための言語」であって、
ソフトウェアは「人間のやっている事を自動化するためのロジック」である。
最近のフレームワークは、ソフトウェアの効率化であって、本来目的以外のロジックも含むデメリットがある。
そこで機械にやらせたいソフトウェア(ロジック)をどうすれば出来るのか、ものすごい基本的な部分を
定石と定義しておく。

まず揺れ動かない部分、スタートアップとメイン。
void main()
{
    x1_init();
    x2_init();

    for(;;){
        //他のモジュール内のメインを動かす
        x1_main();
        x2_main();
        ....
        //アプリケーション終了イベントで抜ける
    }
    x1_finalize();
    x2_finalize();
}

Web系などアプリ層をメインにしている人はフレームワークに任せていて
自分でMain処理をあまり書かないかもしれないが、本来これが基礎になっている。
この部分にやりたい事すべてを入れ込めるかがカギで、
できるだけ1周期内で分岐もせずに、必要な処理以外実行されない構造に出来るかが
ポイントだろう。

ここで、組み込みソフトの場合は電源モードなど動作状態を変える必要があって、
もし動作状態を変える場合は、他のメインループ処理を追加する形になる。

static int PowerMode = 1;

void main()
{
    //全体の初期化
    init();

    //メインループ
    switch(PowerMode)
    {
    case 1:
        x1_init();
        x2_init();

        for(;;){
            //他のモジュール内のメインを動かす
            x1_main();
            x2_main();
            ....
            //アプリケーション終了イベントで抜ける
        }
        x1_finalize();
        x2_finalize();
        break;
    case 2:
        x3_init();
        x4_init();

        for(;;){
            //他のモジュール内のメインを動かす
            x3_main();
            x4_main();
            ....
            //アプリケーション終了イベントで抜ける
        }
        x3_finalize();
        x4_finalize();
        break;
    }
    
    //全体の終了処理
    finalize();
}
ここでインライン化せずに関数に置き換えてしまうと、
コールスタックに戻り先が格納されてしまうので注意。
あえてベタ書きでサンプルコードを記述した。

上記メインループからどうやって一番最適なモジュールが作れるかを次までに考えてみる。


構想メモ:
 ソフトウェアの定石はテンプレートで作成できる構造を狙っている。
それを実現するにはまず、分岐処理の排除、ロジックはなるべく1パスの単純構造になるように設計する点。

ソフトウェアIC化考案(0)

連載の背景)
ソフトウェアの開発手法や概念は今までに沢山の種類が出回っていて、思いついたのだけでも
モジュールの流用やオブジェクト指向、アスペクト思考、構造化手法、フレームワークなど
沢山のテクニックがあって、それぞれ書籍になって売られてる。
これらの手法は使える場面で最も効果が発揮されるように設計されていて、
1つの成功で1つの考えを突き通して手法の1元化などやってしまうとデメリットばかり出てきて
結論として「最初から作り直した方が早かった」、みたいな事になってしまう場合もある。

ISO9001やEMMIなど、開発標準化やレベルなどを示す基準がある。
この基準のハードルをあげれば開発業務の品質が上がるか?という観点で見ると決してそうではない。
なぜならば、開発内容に関する細部の取り決め、厳しい制約についての記述は業務側で決めていくものだからである。
つまり自由な枠組みの中は決められた基準を満たしていれば、表向きの評価は高くする事ができる。
言い換えれば車の免許で、免許持ってるから運転がずば抜けてうまいかといえばそうではなく、
毎日運転してるドライバーからサンデードライバー、ペーパードライバまで幅広くあるわけで、
あればいいというものでもない。
もし改善するならば、人によって10人10色になる部分をどれだけシステマチックにできるかがテーマになると思う。
そこで、この問題に対する解決策はあるのか?というテーマを書いていく予定です。
言い換えて、ソフト開発(職人)としての自分探しの為に連載していきますw

ひさしぶりの更新。

正月明けてからだいぶ経ってしまったけれども、
今年も去年と同じようにぶつかった問題に対して見つけた
解決の糸口を書き連ねていこうと思っています。
見に来てくださった方々に少しでも役に立てるような情報の断片と
リンクを残していきますので、今後ともよろしくお願いします。

とりあえず今年もMini2440とLinuxに関するネタはまだまだ続きますw

Androider