在嵌入式系统中,视频采集主要采用两种接口:一种是标准摄像头接口,一种是USB接口(USB1.1)。标准的摄像头接口,接口复杂,但传输速度快,适合高质量视频采集,而USB接口,接口简单,但有性能瓶颈,只能用于低质量的视频采集。mini2440开发板采用的是S3C2440芯片,S3C2440自带了一个OHCI的USB1.1主机接口和一个CMOS摄像头标准接口。所以mini2440开发板的两种视频采集方式都可以,这里主要介绍基于USB接口的视频采集。因为前一段时间编写了主机上基于GTK的USB视频采集程序,现在需要将其移植到开发板上。
根据主机与开发板环境的不同,需要移植的部分主要就是视频显示部分。在主机上视频显示程序是调用GTK的库函数,而在开发板上有众多的UI可以选择,可以基于QT或者基于minigui 做显示界面,但是最简单最直接的方式就是操作frambuffer设备显示,因为这样可以避免GUI函数带来的性能损失,直接看到采集的实际效果,但这种方式只适用于实验程序没有太大的实用价值,我的采集程序程序就是采用了这种方式。USB摄像头采用和主机程序测试一样的摄像头(十几块钱的山寨摄像头),视频输出格式为YUYV,接口为USB2.0接口兼容USB1.1。为了避免线程切换带来的性能损失,在程序中我去掉了显示以及采集线程,主程序采用大循环的结构。下面简单的介绍一下程序:
1 主函数
- int main(int argc, const char* argv[])
- {
- int fp = 0;
- unsigned int i;
-
-
-
- struct camera *cam;
-
- struct timeval tpstart,tpend;
- float timeuse;
-
- unsigned short *pbuf;
-
-
- cam = malloc(sizeof(struct camera));
- if (!cam) {
- printf("malloc camera failure!\n");
- exit(1);
- }
- cam->device_name = "/dev/video0";
- cam->buffers = NULL;
- cam->width = IMAGE_WIDTH;
- cam->height = IMAGE_HEIGHT;
- cam->display_depth = 3;
- cam->rgbbuf = malloc(cam->width * cam->height * cam->display_depth);
-
- if (!cam->rgbbuf) {
- printf("malloc rgbbuf failure!\n");
- exit(1);
- }
- open_camera(cam);
- get_cam_cap(cam);
- get_cam_pic(cam);
- get_cam_win(cam);
- cam->video_win.width = cam->width;
- cam->video_win.height = cam->height;
- set_cam_win(cam);
- close_camera(cam);
- open_camera(cam);
- get_cam_win(cam);
- init_camera(cam);
- start_capturing (cam);
-
- fp = fb_init ("/dev/fb0");
-
- if (fp < 0){
- printf("Error : Can not open framebuffer device\n");
- exit(1);
- }
-
- pbuf = (unsigned short *)fbbuf;
- for (i = 0; i < 320 *240; i++) {
- pbuf[i] = make_pixel(0, 0x0, 0x0, 0xff);
- }
-
- #ifdef DEBUG_GTIME
- gettimeofday(&tpstart,NULL);
- #endif
- for (;;) {
- if (read_frame (cam)) {
- draw_image(pbuf, cam->rgbbuf);
-
- #ifdef DEBUG_GTIME
- gettimeofday(&tpend,NULL);
- timeuse = 1000000 * (tpend.tv_sec - tpstart.tv_sec) + (tpend.tv_usec - tpstart.tv_usec);
- timeuse /= 1000000;
- printf("Used Time:%10f s\n",timeuse);
- gettimeofday(&tpstart,NULL);
- #endif
-
- }
- }
- return 0;
- }
主函数主要初始化摄像头,分配数据结构,为视频采集做准备。然后初始化frambuffer设备,映射帧缓存,为视频显示做准备。主函数调用的v4l2接口函数与主机测试程序几乎一样。与主机测试程序不同的是显示程序draw_image,它用来显示一帧图像。
2 draw_image 函数
- static void draw_image(unsigned short *pbuf, unsigned char *buf)
- {
- unsigned int x,y;
- unsigned int pixel_num;
- if (WINDOW_W <= 240) {
- for (y = WINDOW_Y; y < WINDOW_Y + WINDOW_H; y++) {
- for (x = WINDOW_X; x < WINDOW_X + WINDOW_W; x++) {
- pixel_num = ((y - WINDOW_Y) * IMAGE_WIDTH + x - WINDOW_X) * 3;
- pbuf[y * 240 + x] = make_pixel(0, (unsigned int)buf[pixel_num],
- (unsigned int)buf[pixel_num + 1], (unsigned int)buf[pixel_num + 2]);
- }
- }
- } else {
- for (x = 0; x < WINDOW_W; x++) {
- for (y = 0; y < WINDOW_H; y++) {
- pixel_num = (y * IMAGE_WIDTH + x) * 3;
- pbuf[x * 240 + y] = make_pixel(0, (unsigned int)buf[pixel_num],
- (unsigned int)buf[pixel_num + 1], (unsigned int)buf[pixel_num + 2]);
- }
- }
- }
- }
这个函数作用就是显示一帧图像,因为在程序初始化阶段已经映射了帧缓存fbbuf,所以只需要将一帧图像的数据capy到帧缓存处就可以显示到lcd上了。在程序的开始处定义了一组宏。
- #define IMAGE_WIDTH 320 //采集视频的宽度
- #define IMAGE_HEIGHT 240 //采集视频的高度
- #define WINDOW_W 176 //显示视频的宽度
- #define WINDOW_H 144 //显示视频的高度
- #define WINDOW_X 40 //显示起始横坐标
- #define WINDOW_Y 60 //显示起始纵坐标
这样通过这一组宏就可以调整显示图像的大小与位置,因为mini2440的lcd为240 × 320 的竖屏,而摄像头采集回来的最大图像大小为320 × 240 ,所以这里用了一个技巧。如果显示的图像宽度大于240,那么就将屏幕翻转,这样可以更好的显示。因为在yuv422转rgb的函数中,转换出的RGB格式为RGB24,而mini2440的屏幕为RGB16的,所以需要做一个颜色转换。
- static inline int make_pixel(unsigned int a, unsigned int r, unsigned int g, unsigned int b)
- {
- return (unsigned int)(((r >> 3) << 11) | ((g >> 2) << 5 | (b >> 3)));
- }
这个函数就是将RGB24格式转换成RGB16的格式。
3 性能分析
以上程序可以正确的进行摄像头的视频采集与显示,但是最大只能采集到176 × 144 的低质量图像,如果采集分辨里达到320 * 240 图像会非常卡,有明显的延迟与丢帧现象,这种原因是USB1.1每秒所传的帧数有限造成的。USB1.1最大每秒可传的帧数由图像大小和USB速度共同决定。下面以320×240 YUYV格式的图像为例,计算USB1.1最大每秒可传的帧数。
(1)每帧需要传输的数据量为 320 × 240 × 2 × 8 = 1228800bit = 1.2288Mbit
(2)USB协议规定:USB1.1的最大传输比特率为12M也就是每秒传12Mbit。这只是理论上的数据,实际传输也就10M左右。我们以10Mbps为例
(3)USB协议规定:同步传输不得超过总线的带宽的90%,所以传播速度还得乘以0.9,为9Mbps。
(4)USB传输速度包括了协议相关的位,USB协议规定USB同步传输包,每个包的协议信息为9个字节。每秒帧数还与每个USB帧(1mS)传输的包的数量有关系,这与同步端点的最大数据有关系,我的摄像头同步端点最大数据为8字节。所以每个包的数据与协议数据比就是 8 : 9, 这样一来带宽还得乘以一个 8/17,为4.2353Mbps
(5) 最后算出每秒帧数据就是 4.2353 / 1.2288 = 3.4
以上计算没有考虑SOF包,以及USB位填充,以及其他的因素,粗略的算出,对于320×240的一幅YUYV格式的图像,USB1.1最大每秒传输3.4帧。可谓是非常小了,但这只是理论的值,实际我用gettimeofday测出的只有每秒一帧多,这样的速度不卡才怪。所以最后得出的结论就是:由于USB1.1的速度限制,采用USB1.1做图像采集,在USB摄像头输出格式为未压缩的原始数据(如:RGB,YUV)的前提下只能采集到低分辨率,低质量的画面。基本上不能用于产品,我查了一些资料,USB1.1数据采集的系统,USB摄像头采集出来的数据格式大多是已经压缩过的,如JPEG格式,这样可大大减轻USB传输的负担,提高视频采集的采集质量。但是这样也有弊端,采集回的数据不是原始数据,不方便对数据的二次处理。所以这种压缩输出格式的USB摄像头基本上都是USB1.1的,USB2.0的摄像头输出格式基本上都是未经处理的原始数据,因为2.0的速度已经足够快,理论480Mbps的速度绝对满足需要。
实验代码在我的资源里: http://download.csdn.net/detail/yaozhenguo2006/3925480
源出处: http://blog.csdn.net/yaozhenguo2006/article/details/7074706