英创信息技术Linux工控主板摄像头应用简介

描述

近年来,随着计算机、网络以及图像处理、传输技术的飞速发展,摄像头在工业控制领域的应用也越来越广泛了,目前市面上的摄像头可以分为两类,一种是符合UVC规范的摄像头,比如罗技的摄像头就是UVC摄像头。另一种是non-UVC摄像头,即不符合UVC规范。UVC全称为:USB video class (USB视频类)在Linux-2.6.4及以上的版本都已经集成了UCV设备的驱动,而non-UVC摄像头如果要使用,就需要硬件厂商提供专用的驱动。比如中星微的摄像头就是non-UVC设备,需要专用的驱动。

1、Linux内核配置

本文以英创嵌入式板卡EM335x 为例来介绍对于USB摄像头的支持,EM335x内核版本为Linux-3.12.10,USB摄像头选用中星微的ZC301摄像头,该摄像头以其高性价比得以广泛应用,同时在Linux内核中已经包括了对于ZC3XX系列摄像头的驱动支持。

内核配置如下:
<*> Multimedia support --->
[*] Cameras/video grabbers support
[*] Media USB Adapters --->
<*> USB Video Class (UVC)
[*] UVC input events device support
<*> GSPCA based webcams --->
ZC3XX USB Camera Driver

编译成功后,即可得到zc3xx系列USB摄像头驱动文件:gspca_zc3xx.ko。

在EM335x板卡上,该文件放置在根文件系统/lib/modules/3.12.10/目录下。应用时只需调用以下命令,即可完成对于USB摄像头的驱动加载。

insmod /lib/modules/3.12.10/gspca_zc3xx.ko

驱动加载成功后,会自动生成设备节点:“/dev/video0',应用程序可以操作该设备节点对摄像头进行图像的采集和控制。因为中星微的摄像头为non-UVC设备,所以需要再加专用的gspca_zc3xx.ko,如果是其他的UVC摄像头,内核中已经集成了驱动,插上后就可以识别出来,不用再加载其他驱动。

2、Qt摄像头应用程序简介

UVC和non-UVC摄像头都是用了V4L2驱动提供的API来操作摄像头。Video for Linux two简称V4L2,是V4L的改进版。V4L2是Linux操作系统下用于采集图片、视频和音频数据的API接口,配合适当的视频采集设备和相应的驱动程序,可以实现图片、视频、音频等的采集。在视频监控系统和嵌入式多媒体终端中都有广泛的应用。V4L2支持两种方式来采集图像:内存映射方式(mmap)和直接读取方式(read)。在这里我们使用内存映射的方式来进行视频采集。应用程序通过V4L2接口采集视频数据可以分为五个步骤:

①打开视频设备文件,进行视频采集的参数初始化,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式;
②申请若干视频采集的帧缓冲区,并将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取/处理视频数据;
③将申请到的帧缓冲区在视频采集输入队列排队,并启动视频采集;
④驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;
⑤停止视频采集。

可以参考下图:

Linux

可以看到每一个步骤都是通过ioctl这个接口去设置一些参数来实现的, 启动视频采集后,驱动程序开始采集数据,并把采集的数据放入视频采集输入队列的第一个帧缓冲区,当一帧数据采集完成,也就是第一个帧缓冲区存满数据以后,驱动程序将这一个缓冲区移至视频采集输出队列,等待应用程序取出。驱动程序接下来继续采集下一帧数据,并放入第二个帧缓冲区,同样帧缓冲区存满数据后,被放入视频采集输出队列。

应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区,处理帧缓冲区中的视频数据,如存储或压缩。如果需要连续采集,应用程序需要将处理完数据的帧缓冲区重新放入视频采集输入队列,如图所示。

Linux

接下来结合程序来具体看一看通过V4L2接口来操作摄像头的一些重要的步骤:

打开设备文件:
int fd;
fd=open('/dev/video0',O_RDWR);

获取设备的基本信息,包括驱动版本号,设备支持操作等:
struct v4l2_capability cap;
ret=ioctl(fd,VIDIOC_QUERYCAP,&cap);
if(ret<0)
{
printf('failture VIDIOC_QUERYCAP ');
return -1;
}
printf('DriverName:%s Card Name:%s Bus info:%s DriverVersion:%u.%u.%u ',cap.driver,cap.card,cap.bus_info,(cap.version>>16)&0xFF,(cap.version>>8)&0xFF,cap.version&0xFF);

显示所支持的格式:
memset(&fmtdesc, 0, sizeof(fmtdesc));
fmtdesc.index = 0;
// 数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
{
printf('/t%d.%s/n',fmtdesc.index+1,fmtdesc.description);
fmtdesc.index++;
}

设置视频的制式和帧格式,制式包括PAL,NTSC,帧的格式个包括宽度和高度等:
struct v4l2_format fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
fmt.fmt.pix.width = 640; // 宽,必须是16的倍数
fmt.fmt.pix.height = 480; // 高,必须是16的倍数
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG; // 视频数据存储类型//V4L2_PIX_FMT_YUYV;//V4L2_PIX_FMT_YVU420;//V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
// 设置当前驱动的频捕获格式
ret = ioctl (fd, VIDIOC_S_FMT, &fmt);
if(ret<0)
{
printf('failture VIDIOC_S_FMT ');
return -1;
}

向驱动申请帧缓冲,一般不超过五个:
struct v4l2_requestbuffers req;
req.count=1;
req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory=V4L2_MEMORY_MMAP;
// 申请帧缓冲
ret=ioctl(fd,VIDIOC_REQBUFS,&req);
if(ret<0)
{
printf('failture VIDIOC_REQBUFS ');
return -1;
}
if (req.count < 1)
{
printf('Insufficient buffer memory ');
return -1;
}

将申请到的帧缓冲映射到用户空间,这样就能够直接操作帧缓冲了:
buffers =(buffer*)calloc (req.count, sizeof (*buffers));
if (!buffers) {
fprintf (stderr,'Out of memory/n');
exit(EXIT_FAILURE);
}
for (n_buffers = 0; n_buffers < req.count; ++n_buffers)
{
struct v4l2_buffer buf;
memset(&buf,0,sizeof(buf));
buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory =V4L2_MEMORY_MMAP;
buf.index =n_buffers;
// 查询序号为n_buffers 的缓冲区,得到其起始物理地址和大小
if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf))
{
printf('failture VIDIOC_QUERYBUF ');
return -1;
}
buffers[n_buffers].length= buf.length;
// 映射内存
buffers[n_buffers].start=mmap (NULL,buf.length,PROT_READ | PROT_WRITE ,MAP_SHARED,fd, buf.m.offset);
if (MAP_FAILED == buffers[n_buffers].start)
{
printf('failture mmap ');
return -1;
}
}

将申请到的帧缓冲全部入队列,以便存放采集到的数据:
for (i = 0; i< req.count; ++i)
{
struct v4l2_buffer buffer;
buffer.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.memory =V4L2_MEMORY_MMAP;
buffer.index = i;
// 将缓冲帧放入队列尾
ioctl (fd,VIDIOC_QBUF, &buffer);
}

开始视频的采集:
type =V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl (fd,VIDIOC_STREAMON, &type);

取出队列中以取得采集数据的帧缓冲,获得原始采集数据,因为这个摄像头支持的格式为JPG,所以程序中将原始数据保存在新建的一个*.jpg文件中:
struct v4l2_buffer camera_buf;
CLEAR (camera_buf);
camera_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
camera_buf.memory = V4L2_MEMORY_MMAP;
// 取出一个缓冲帧
i1 = ioctl (fd, VIDIOC_DQBUF, &camera_buf);
if(i1<0)
{
printf('failture ');
return -1;
}
fwrite(buffers[camera_buf.index].start, buffers[camera_buf.index].length, 1, file_fd); // 将其写入文件中

将缓冲帧重新入队列尾,这样可以循环采集:
// 将缓冲重新入队列尾
i1=ioctl (fd, VIDIOC_QBUF, &camera_buf);
if(i1<0)
{
printf('failture VIDIOC_QBUF ');
return -1;
}

如果需要关闭摄像头,先停止视屏采集,释放申请的帧缓冲,最后关闭设备节点:
// 停止视频的采集。VIDIOC_STREAMOFF
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == ioctl(fd, VIDIOC_STREAMOFF, &type))
printf('VIDIOC_STREAMOFF');
for (i = 0; i < n_buffers; ++i)
if (-1 == munmap (buffers->start, buffers->length))
printf ('munmap error');
free(buffers);
// 关闭视频设备
close (fd);

所以通过这一套通用的V4L2接口来操作摄像头的工作流程:

打开设备-> 检查和设置设备属性->设置帧格式-> 设置一种输入输出方法(缓冲区管理)-> 循环获取数据-> 关闭设备。通过这几个步骤已经可以操作摄像头来获取数据,下面来看看如何与Qt结合,将前面的代码与Qt界面结合起来。

在Qt中主要就是实现两个功能,一个是通过界面控制摄像头的数据获取,另一个是通过界面显示摄像头所拍摄下来的图片。摄像头的初始化设置,包括格式等参数的设置可以在Qt界面的构造函数中完成。

通过界面来控制摄像头,可以在Qt的界面上做一个按钮,在按钮的单击事件槽中调用摄像头采集数据的部分即可:
void MainWindow::on_init_camera_clicked() // 按钮单击事件
{
for (;;) // 这一段涉及到异步IO
{
fd_set fds;
struct timeval tv;
int r;
FD_ZERO (&fds); // 将指定的文件描述符集清空
FD_SET (fd, &fds); // 在文件描述符集合中增加新的文件描述符
tv.tv_sec = 0;
tv.tv_usec = 500000;
r = select (fd + 1, &fds, NULL, NULL, &tv); // 判断是否可读(即摄像头是否准备好),tv是定时
if (-1 == r)
{
if (EINTR == errno)
continue;
printf ('select err ');
}
if (read_frame ()) // 如果可读,执行read_frame ()函数,并跳出循环
break;
else
{
QMessageBox::information(this, tr('失败'), tr('拍摄图片失败') , QMessageBox::Ok);
}
}
}

关于拍摄图片的显示问题,Qt中提供了很多实现的方法,比如可以在界面中采用一个label来显示,这里采用GraphicsView来显示,主要代码如下:
image=new QImage(pictrue_name);
image->load(pictrue_name);
scene = new QGraphicsScene;
scene->addPixmap(QPixmap::fromImage(*image));
ui->graphicsView->setScene(scene);
ui->graphicsView->setAlignment(Qt::AlignCenter);
ui->graphicsView->show(); // 显示

将摄像头获取的数据写入文件中,再通过GraphicsView显示出来。这样就实现了Qt程序和摄像头操作的结合,详细的代码请参考例程。

例程的效果如下图所示:

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分