【全志T113-S3_100ask】通过v4l2采集图像
文章目录
- 【全志T113-S3_100ask】通过v4l2采集图像
- 前言
- 一、设置摄像头采集格式
- 二、申请内核缓冲区队列
- 三、将内核的缓冲区队列映射到用户空间
- 四、开始采集
- 五、停止采集
- 六、释放映射和关闭设备
- 七、执行文件
- 八、通过tftp将图片传至PC上
- 总结
前言
在前一篇文章我已经介绍了v4l2
这个linux关于视频设备的内核驱动以及如何获取我们的usb摄像头所支持的格式
,若没有看过前一篇文章的可以阅读前一篇文章进行了解:
USB摄像头——v4l2打开设备、获取设备支持的格式【以t113-s3为例】 今天这篇文章要讲述的是如何通过v4l2采集一帧图像。
一、设置摄像头采集格式
首先,需要将vmft结构体的type设置为摄像头采集模式:
//设置vfmt.type
struct v4l2_format vfmt;
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//摄像头采集
或许有读者会疑问:如何查看这些标志位、结构体呢?可以通过查看/usr/include/linux/videodev2.h文件获取信息
:
vim /usr/include/linux/videodev2.h
文件打开之后内容如下:
如果没有配置过vim的话是不会显示行号的,看起来可能不太方便,可以在编辑模式下输入":set nu"或": set number"
来显示行号,也可以在/etc/vimrc配置文件最后加上"set number"
。
要搜索你想要的只需要在编辑模式下按下"/"加你要搜索的字符串
便可以了,如我们现在搜索v4l2_format:
可以看到,可以搜索到,按下快捷键ctrl+g
键,可以跳转到下一个符合条件的位置。
接着设置拍摄的宽、高:
//设置宽高(宽高有要求,不是说可以随便设置,宽和高的倍数固定)
vfmt.fmt.pix.width = 1280;
vfmt.fmt.pix.height = 720;
需要注意的是,宽、高并不是可以随意设置的,宽和高的倍数是固定的,比如640×480、320×240等等。
最后设置采集格式:
//设置采集格式
// vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
在上篇文章中有提到,我的摄像头支持YUYV和Motion JPEG这两种格式,所以在设置采集格式的时候,我可以设置为YUYV,也可以设置为MJPEG。不过考虑到方便性,便先将pixelformat设置为V4L2_PIX_FMT_MJPEG了。
最后判断一下格式是否设置错误
int ret = ioctl(fd,VIDIOC_S_FMT,&vfmt);
if(ret < 0)
{
perror("格式设置错误!");
}
// else
// {
// printf("设置成功!\n");
// }
if(vfmt.fmt.pix.width == 1280 && vfmt.fmt.pix.height == 720 && vfmt.fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG)
{
printf("设置成功!\n");
}else
{
printf("设置失败!\n");
}
刚开始我判断格式设置成功的条件是ret大于0,此时打印设置成功。后来了解到如果外接摄像头不支持你所设置的格式,会自动设置为其默认的参数
,在这种情况下可能会导致没设置成功,却打印了“设置成功”。所以最后采用了if判断设置之后的宽、高、采集格式是否和我们所给的相同,如果相同才返回“设置成功”。
二、申请内核缓冲区队列
申请内核缓冲区队列需要用到的是 VIDIOC_REQBUFS
,其对应的结构体为 v4l2_requestbuffers
。
//申请内核缓冲区队列
struct v4l2_requestbuffers reqbuffer;
reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//设置采集格式
//申请4个缓冲区
reqbuffer.count = 4;
//指定映射方式
reqbuffer.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd,VIDIOC_REQBUFS,&reqbuffer);
if(ret < 0)
{
perror("申请队列空间失败!");
}
三、将内核的缓冲区队列映射到用户空间
代码如下:
//将内核的缓冲区队列映射到用户空间
unsigned char *mptr[4];//保存映射后用户空间首地址
struct v4l2_buffer mapbuffer;
mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//指定type
for(int i = 0; i < 4; i++)//查询出缓冲区
{
mapbuffer.index = i;
ret = ioctl(fd,VIDIOC_QUERYBUF,&mapbuffer);//从内核空间中查询一个空间做映射
if(ret < 0)
{
perror("查询内核空间失败");
}
mptr[i] =(unsigned char *)mmap(NULL,mapbuffer.length,PROT_READ | PROT_WRITE,MAP_SHARED,fd,mapbuffer.m.offset);
size[i]=mapbuffer.length;
//使用完毕,“放回去”
ret = ioctl(fd,VIDIOC_QBUF,&mapbuffer);
if(ret < 0)
{
perror("放回失败!");
}
}
四、开始采集
代码如下:
//开始采集
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_STREAMON, &type);
if(ret < 0)
{
perror("开启失败");
}
//从队列中提取一帧数据
struct v4l2_buffer readbuffer;
readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);
if(ret < 0)
{
perror("提取数据失败");
}
FILE *file=fopen("test.jpg", "w+");
fwrite(mptr[readbuffer.index], readbuffer.length, 1, file);//写数据
fclose(file);//关闭文件
//通知内核已经使用完毕
ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
if(ret < 0)
{
perror("放回队列失败");
}
五、停止采集
代码如下:
//停止采集
ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
六、释放映射和关闭设备
代码如下:
//释放映射
unsigned int size[4];
for(int i=0; i<4; i++)
{
munmap(mptr[i], size[i]);
}
//关闭设备
close(fd);
七、执行文件
上面已经完成了采集图片的功能了,将文件编译后传至开发板上运行,其结果如下:
可以看到,原来在目录下是没有test.jpg文件的,运行完之后出现了test.jpg文件,这就是摄像头采集到的图片。
八、通过tftp将图片传至PC上
在开发板上没办法直接查看图片的,所以我们通过tftp将文件传输至电脑上打开查看。
首先,在MobaXterm1_CHS1这款工具上是集成了tftp服务器功能的,点击左上角“服务器”(如果不是中文版的就点击左上角第二个选项),在弹出的窗口中点击tftp服务器的设置,如图所示:
在设置中选择你电脑与开发板“交互”的文件夹,然后点击“好的”完成设置,如图所示:
配置完成之后记得点击启动按钮:
启动之后还需要确保开发板与电脑连接的是同一个局域网
,t113-s3开发板连接wifi的方法是运行/etc/wlan-connect.sh,其格式如下:
/etc/wlan-connect.sh WIFI名字 WIFI密码 1
连接了同一个WiFi之后,便可以通过tftp从开发板将图片传过来(也可以从PC端传文件去开发板,我的执行文件就是这么传去开发板的)。
将图片文件传输至PC端的命令:
tftp -r ./文件名 -p 电脑IP
完成传输后,在文件夹中便会出现我们刚刚拍摄的图片:
打开图片查看:
拍摄图片成功(因为我的摄像头没有自动对焦,所以图片较糊)。
总结
下面给出完整的代码,希望能够给文章点个赞+收藏
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <string.h>
#include <sys/mman.h>
int main()
{
//打开设备
int fd = open("/dev/video0",O_RDWR);
if(fd < 0)
{
perror("打开设备失败!");
return -1;
}
// //获取摄像头支持的格式
// //ioctl(文件描述符,命令,与命令对应结构体)
// struct v4l2_fmtdesc v4fmt;
// v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
// int i = 0;
// while(1)
// {
// v4fmt.index = i++;
// int ret = ioctl(fd,VIDIOC_ENUM_FMT,&v4fmt);
// if(ret < 0)
// {
// perror("获取失败");
// break;
// }
// printf("index=%d\n",v4fmt.index);
// printf("flags=%d\n",v4fmt.flags);
// printf("description=%s\n",v4fmt.description);
// unsigned char *p =(unsigned char *) &v4fmt.pixelformat;
// printf("pixelformat=%c%c%c%c\n",p[0],p[1],p[2],p[3]);
// printf("reserved=%d\n",v4fmt.reserved[0]);
// }
//设置vfmt.type
struct v4l2_format vfmt;
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//摄像头采集
//设置宽高(宽高有要求,不是说可以随便设置,宽和高的倍数固定)
vfmt.fmt.pix.width = 1280;
vfmt.fmt.pix.height = 720;
//设置采集格式
// vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
int ret = ioctl(fd,VIDIOC_S_FMT,&vfmt);
if(ret < 0)
{
perror("格式设置错误!");
}
// else
// {
// printf("设置成功!\n");
// }
// memset(&vfmt,0,sizeof(vfmt)); //memset是一个初始化函数,作用是将某一块内存中的全部设置为指定的值
// vfmt.type =V4L2_BUF_TYPE_VIDEO_CAPTURE; //初始化之后需要告知采集格式,否则会报错
// ret = ioctl(fd,VIDIOC_G_FMT,&vfmt);
// if(ret < 0)
// {
// perror("获取格式失败!");
// }
if(vfmt.fmt.pix.width == 1280 && vfmt.fmt.pix.height == 720 && vfmt.fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG)
{
printf("设置成功!\n");
}else
{
printf("设置失败!\n");
}
//申请内核缓冲区队列
struct v4l2_requestbuffers reqbuffer;
reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//设置采集格式
//申请4个缓冲区
reqbuffer.count = 4;
//指定映射方式
reqbuffer.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd,VIDIOC_REQBUFS,&reqbuffer);
if(ret < 0)
{
perror("申请队列空间失败!");
}
//将内核的缓冲区队列映射到用户空间
unsigned char *mptr[4];//保存映射后用户空间首地址
unsigned int size[4];
struct v4l2_buffer mapbuffer;
mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//指定type
for(int i = 0; i < 4; i++)//查询出缓冲区
{
mapbuffer.index = i;
ret = ioctl(fd,VIDIOC_QUERYBUF,&mapbuffer);//从内核空间中查询一个空间做映射
if(ret < 0)
{
perror("查询内核空间失败");
}
mptr[i] =(unsigned char *)mmap(NULL,mapbuffer.length,PROT_READ | PROT_WRITE,MAP_SHARED,fd,mapbuffer.m.offset);
size[i]=mapbuffer.length;
//使用完毕,“放回去”
ret = ioctl(fd,VIDIOC_QBUF,&mapbuffer);
if(ret < 0)
{
perror("放回失败!");
}
}
//开始采集
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_STREAMON, &type);
if(ret < 0)
{
perror("开启失败");
}
//从队列中提取一帧数据
struct v4l2_buffer readbuffer;
readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);
if(ret < 0)
{
perror("提取数据失败");
}
FILE *file=fopen("test.jpg", "w+");
fwrite(mptr[readbuffer.index], readbuffer.length, 1, file);//写数据
fclose(file);//关闭文件
//通知内核已经使用完毕
ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
if(ret < 0)
{
perror("放回队列失败");
}
//停止采集
ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
//释放映射
for(int i=0; i<4; i++)
{
munmap(mptr[i], size[i]);
}
//关闭设备
close(fd);
return 0;
}
可能有些读者的摄像头并不支持MJPEG格式,采集到的图片需要解码才能查看,这个等下期再讲。