video4linux 自动 拍照,基于Video4Linux 的USB 摄像头图像采集实现[2]

一、用mmap(内存映射)方式截取视频

mmap( )系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。两个不同进程A、B共享内存的意思是,R>同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。

采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝

(1)设置picture的属性

(2)初始化video_mbuf,以得到所映射的buffer的信息

ioctl(vd->fd, VIDIOCGMBUF,

&(vd->mbuf))

(3)可以修改video_mmap和帧状态的当前设置

(4)将mmap与video_mbuf绑定

void* mmap ( void * addr ,

size_t len , int prot , int flags , int fd , off_t offset

)

len:映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起

Prot:指定共享内存的访问权限 PROT_READ(可

读), PROT_WRITE (可写),PROT_EXEC (可执行)

Flags:MAP_SHARED MAP_PRIVATE中必选一个,MAP_ FIXED不推荐使用

Addr:共内存享的起始地址,一般设0,表示由系统分配

Mmap( ) 返回值是系统实际分配的起始地址

int v4l_mmap_init(v4l_device

*vd)

{

if

(v4l_get_mbuf(vd) < 0)

return -1;

if

((vd ->map = mmap(0, vd->mbuf.size,

PROT_READ|PROT_WRITE, MAP_SHARED, vd->fd, 0))

< 0)

{

perror("v4 l_mmap_init:mmap");

return -1;

}

return 0;

}

(5)Mmap方式下真正做视频截取的

VIDIOCMCAPTURE

ioctl(vd->fd, VIDIOCMCAPTURE,

&(vd->mmap)) ;

若调用成功,开始一帧的截取,是非阻塞的,是否截取完毕留给VIDIOCSYNC来判断

(6)调用VIDIOCSYNC等待一帧截取结束

if(ioctl(vd->fd, VIDIOCSYNC,

&frame) < 0)

{

perror("v4l_sync:VIDIOCSYNC");

return -1;

}

若成功,表明一帧截取已完成。可以开始做下一次VIDIOCMCAPTURE frame是当前截取的帧的序号。

********关于双缓冲************

video_bmuf bmuf.frames =

2;一帧被处理时可以采集另一帧

int frame;

//当前采集的是哪一帧

int framestat[2];

//帧的状态 没开始采集|等待采集结束

帧的地址由vd->map +

vd->mbuf.offsets[vd->frame]得到。

采集工作结束后调用munmap取消绑定

munmap(vd->map,

vd->mbuf.size)

在实际应用时还可以采用缓冲队列等方式。

二、视频截取的第二种方法:直接读设备

关于缓冲大小,图象等的属性须由使用者事先设置

调用read();

int read

(要访问的文件描述符;指向要读写的信息的指针;应该读写的字符数);

返回值为实际读写的字符数

int len ;

unsigned char

*vd->map=(unsigned char *)

malloc(vd?capability.maxwidth*vd?capability.maxheight );

len =

read(vd?fd,vd?

vd->map,

vd?capability.maxwidth*vd?capability.maxheight*3 );

2.3 编程实例(mouse_capture)

不管是ov511还是zc301的摄像头,它们采集的方式都是相同的,只不过采集到的数据有所差异,ov511的就是rgb的位流,而zc301是jpeg编码的位流。

mouse_capture是根据servfox改编的一个专门从zc301摄像头获取一张jpeg图片,用来测试摄像头是否加载成功的小程序。这样就可以不用cat

/dev/video0>1.jpg来测试摄像头是否正常。cat命令一运行,就源源不断地采集jpeg流。但是采到的图片只能显示第一个jpeg头和jpeg尾之间的数据。mouse_capture仅仅获得一张完整的jpeg。可以从()处下载参考。

现将主要函数的功能介绍如下:

static int GetVideoPict

(struct vdIn *vd);//获取图片属性信息。

static int SetVideoPict

(struct vdIn *vd);//设置图片属性。

static int isSpcaChip (const

char *BridgeName);//测试芯片类型

static int GetStreamId

(const char *BridgeName); //测试输出数据的格式

static int GetDepth (int

format);//获取颜色深度。

void exit_fatal(char

*messages);//错误显示。

int init_videoIn(struct vdIn

*vd,char *device,int width,int height,int format,int

grabmethod);//初始化设备。

int convertframe(unsigned

char *dst,unsigned char *src, int width,int height, int formatIn,

int size);//把共享缓冲区中的数据放到一个变量中,通知系统已获得一帧。

int v4lGrab (struct vdIn

*vd,char *filename );//从摄像头采集图片。

int close_v4l (struct vdIn

*vd);//关闭摄像头。

int get_jpegsize (unsigned

char *buf, int insize);//获取jpeg图片大小。

三 实例程序

3.1 LCD 实时显示从ov511 上采集的图像

参考HHARM9-EDU/applications/usbcam2lcd。从摄像头获取bmp位流直接显示在framebuffer中。此程序图像的采集采用read的方式,注意由于lcd液晶屏显示的是16bits的RGB图片,所以,ov511输出的图片格式也应该是16bits的RGB图片数据,宏VIDEO_PALETTE_RGB565定义的就是16bits的RGB数据图片。而linux自带的ov511驱动中图像采集是32位的,这样采集到的图片显示在lcd上是雪花点。因此需要修改驱动。在kernet/driver/usb/目录下有ov511芯片的驱动ov511.c,驱动里的ov51x_set_default_params函数是设置芯片默认的输出图片的格式,该函数中的

for (i = 0; i

< OV511_NUMFRAMES; i++)

{

ov511->frame[i].width =

ov511->maxwidth;

ov511->frame[i].height =

ov511->maxheight;

ov511->frame[i].bytes_read = 0;

if

(force_palette)

ov511->frame[i].format =

force_palette;

else

ov511->frame[i].format =

VIDEO_PALETTE_RGB24;

ov511->frame[i].depth =

ov511_get_depth(ov511->frame[i].format);

}

部分语句是主要设置ov511默认输出图片格式的,其中maxwidth和maxheight设置了图片的最大的宽度和高度。Ifelse语句设置了图片的格式,作如下的修改:

for (i = 0; i

< OV511_NUMFRAMES; i++)

{

ov511->frame[i].width =

ov511->maxwidth;

ov511->frame[i].height =

ov511->maxheight;

ov511->frame[i].bytes_read = 0;

ov511->frame[i].format =

VIDEO_PALETTE_RGB565;

ov511->frame[i].depth =

ov511_get_depth(ov511->frame[i].format);

}

如果需要,也可以改变图片的默认输出大小。

3.2 LCD 实时显示从zc301 上采集的图像

编程思想:从摄像头采集到的图片存放在本地文件夹,通过minigui加载jpeg来实现显示。

具体过程:

1.从网上下载jpegsrc-6b的jpeg库,交叉编译。

(1)./configure –enable-static –enable-shared

–prefix=.libs

(2)修改Makefile,将编译器改成交叉编译器。

例如:我改成/opt/host/armv4l/bin/armv4l-unknown-linux-gcc

(3)make 后即在.libs目录中生成for arm的libjpeg.a, libjpeg.la, libjpeg.so, libjpeg.so.62,

libjpeg.so.62.0.0。将这些文件拷贝到系统库文件目录,我的是/usr/lib中。

2.因为看从zc301采集的图片的二进制位流,jpeg头是ff

d8 ff db。而在minigui库文件libminigui的源文件src/mybmp/jpeg.c中,load_jpg和check_jpg的时候测试的头位EXIF和JFIF两种格式的jpeg图片。这两种对应的二进制分别是ff d8 ff e1和ff d8

ff e0。所以我们minigui通过判断认为这是错误的jpeg格式而不加载,故无法显示。实际上通过测试,在源码中去掉这两个判断就能正确加载。

3.交叉编译minigui

(1)

编译库: ./configure --host=arm-unknown-linux

--enable-jpgsupport=yes

--enable-pngsupport=no

--enable-gifsupport=no --disable-lite

--prefix=/HHARM9-EDU/applications/minigui-free/nfsroot

--enable-smdk2410ial=yes

make

make install

(2)编译实例程序时,要加上jpeg库的支持,即在Makefile中加上-ljpeg。此时将在nfsroot生成的库文件和可执行文件移到ramdisk.image.gz相应的目录下。(具体参考华恒的2410开发手册)。

3.Minigui程序的编写

编程小技巧,我采取的方法是一刻不停地从摄像头采集到图片存储在/tmp/1.jpg中,在minigui中通过loadbitmap函数来加载图片。而图片加载后不会自动更新,不能自动根据1.jpg的改变自动变化。因此,我在程序中设定一个timer。每隔100ms刷新屏幕,基本上实现实时更新了。而出现另外一个问题,刷新时会以背景色来填充桌面,导致屏幕闪烁严重。故想到采用MSG_ERASEBKGND的方式,用前一张图片做为刷新屏幕时的填充背景图片。这样就保证了lcd上图像的连续性啦。

Minigui程序如下:其中一些自定义的函数跟mouse_capture中的一样,只是变采集单幅到采集多幅。具体您可以自己改一下:)。也可以向我索取源码。

#include

#include

#include

#include

#include

#include

"spcav4l.h"

#define IDTIMER

100

static BITMAP

bmp;

static int

LoadBmpWinProc(HWND hWnd, int message, WPARAM wParam, LPARAM

lParam)

{

HDC hdc;

RECT rc={0,0,240,320};

switch (message)

{

case MSG_CREATE:

SetTimer(hWnd,IDTIMER,100);

return 0;

case MSG_ERASEBKGND:

{

RECT rcTemp;

if(

LoadBitmap(HDC_SCREEN,&bmp,"/tmp/1.jpg"))

{

printf("load wrong!\n");

return -1;

}

GetClientRect(hWnd, &rcTemp);

hdc = BeginPaint (hWnd);

FillBoxWithBitmap (hdc, rcTemp.left, rcTemp.top,

rcTemp.right-rcTemp.left, rcTemp.bottom-rcTemp.top,

&bmp);

EndPaint(hWnd, hdc);

return 0;

}

case MSG_TIMER:

InvalidateRect(hWnd,&rc,TRUE);

return 0;

case MSG_CLOSE:

UnloadBitmap (&bmp);

DestroyMainWindow (hWnd);

PostQuitMessage (hWnd);

return 0;

}

return DefaultMainWinProc(hWnd, message, wParam,

lParam);

}

int MiniGUIMain (int argc,

const char* argv[])

{

MSG Msg;

HWND hMainWnd;

MAINWINCREATE CreateInfo;

char videodevice[] = "/dev/video0";

char jpegfile[] = "/tmp/1.jpg";

int grabmethod = 0;

int format = VIDEO_PALETTE_JPEG;

int width = 240;

int height = 320;

int i;

#ifdef

_LITE_VERSION

SetDesktopRect(0, 0, 1024, 768);

#endif

CreateInfo.dwStyle = WS_VISIBLE | WS_BORDER |

WS_CAPTION;

CreateInfo.dwExStyle = WS_EX_NONE;

CreateInfo.spCaption = "Load and display a

bitmap";

CreateInfo.hMenu = 0;

CreateInfo.hCursor = GetSystemCursor(0);

CreateInfo.hIcon = 0;

CreateInfo.MainWindowProc = LoadBmpWinProc;

CreateInfo.lx = 0;

CreateInfo.ty = 0;

CreateInfo.rx = 240;

CreateInfo.by = 320;

CreateInfo.iBkColor = PIXEL_lightwhite;

CreateInfo.dwAddData = 0;

CreateInfo.hHosting = HWND_D

ESKTOP;

hMainWnd = CreateMainWindow

(&CreateInfo);

if

(hMainWnd == HWND_INVALID)

return -1;

ShowWindow (hMainWnd, SW_SHOWNORMAL);

memset(&videoIn, 0, sizeof (struct

vdIn));

if(init_videoIn(&videoIn, videodevice,

width, height, format,grabmethod) == 0)

{

printf("init is ok!\n");

}

else printf("init is wrong!\n");

while (GetMessage(&Msg,

hMainWnd))

{

TranslateMessage(&Msg);

v4lGrab(&videoIn,

jpegfile);

DispatchMessage(&Msg);

}

close_v4l (&videoIn);

MainWindowThreadCleanup (hMainWnd);

return 0;

}

#ifndef

_LITE_VERSION

#include

#endif

先写到这里吧,呵呵,希望能对您有所帮助。如果您在阅读的过程中发现问题,欢迎和我交流。

2006-7-7 晚

参考文献

1.HHARM2410摄像头调试记录 华恒科技

2.基于video4linux的视频设备编程 Lingzhi_Shi Apr 7 2004

3.《video4linux programming》 Alan

Cox

4.《video streaming 探讨》 陈俊宏

5.《Video4Linux Kernel API Reference 》

6.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值