近年来,随着计算机、网络以及图像处理、传输技术的飞速发展,摄像头在工业控制领域的应用也越来越广泛了,目前市面上的摄像头可以分为两类,一种是符合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接口设置视频图像的采集窗口、采集的点阵大小和格式;
②申请若干视频采集的帧缓冲区,并将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取/处理视频数据;
③将申请到的帧缓冲区在视频采集输入队列排队,并启动视频采集;
④驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;
⑤停止视频采集。
可以参考下图:
可以看到每一个步骤都是通过ioctl这个接口去设置一些参数来实现的, 启动视频采集后,驱动程序开始采集数据,并把采集的数据放入视频采集输入队列的第一个帧缓冲区,当一帧数据采集完成,也就是第一个帧缓冲区存满数据以后,驱动程序将这一个缓冲区移至视频采集输出队列,等待应用程序取出。驱动程序接下来继续采集下一帧数据,并放入第二个帧缓冲区,同样帧缓冲区存满数据后,被放入视频采集输出队列。
应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区,处理帧缓冲区中的视频数据,如存储或压缩。如果需要连续采集,应用程序需要将处理完数据的帧缓冲区重新放入视频采集输入队列,如图所示。
接下来结合程序来具体看一看通过v4l2接口来操作摄像头的一些重要的步骤:
打开设备文件:
int fd;
fd=open('/dev/video0',o_rdwr);
获取设备的基本信息,包括驱动版本号,设备支持操作等:
struct v4l2_capability cap;
ret=ioctl(fd,vidioc_querycap,&cap);
if(ret>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(i1length))
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程序和摄像头操作的结合,详细的代码请参考例程。
例程的效果如下图所示:
分子数据存储技术 可将数据长期存储
宏晶微电子MS2100E特性 /AV&amp;Svideo转USB芯片
经典的弯管模具设计图解 滑块两次抽芯步骤
手机涨价背后的无奈,下代苹果华为手机64G起步
MLX90640红外热成像传感器测温模块开发笔记(二)
英创信息技术Linux工控主板摄像头应用简介
对于嵌入式来说它需要USB-C技术吗
LG正式发布首款5摄旗舰智能手机LG V40 ThinQ
eps应急电源与ups区别
耐高温热缩套管的分类介绍
两款3A的电源模块TPS82085、MUN3CAD03-JE原位替代
aecq100标准是什么
控创推出支持Intel Xeon 5600处理器的CG210
为什么说5G技术是新基建的首要任务
基于32位单片机的感应灯解决方案
三极管温度与Vbe的关系
人工智能正将数据中心推向极限
2020的区块链有怎样的现实看法
阿里、蚂蚁金服布局共享单车领域,ofo敲定新一轮10亿美金融资
深入研读了25年来的AI研究论文,结果表明深度学习的时代即将结束