v4l2摄像头数据获取并显示到fb上,yuv格式处理

本文介绍了如何在嵌入式Linux系统中使用v4l2驱动采集摄像头数据,处理YUV格式图像并显示到framebuffer上。主要内容包括v4l2驱动的使用、图像格式转换、图像缩放以及显示过程,同时提到了YUV到RGB的转换和内存映射等关键技术。
摘要由CSDN通过智能技术生成

使用v4l2采集摄像头数据,将yuv图像解码成rgb,并显示到fb上

折腾了一个多星期,总算实现了摄像头数据采集并显示到屏幕上,整理一下嵌入式Linux从摄像头获取数据并显示到fb屏幕上的过程。使用尽量少的依赖,只用了v4l2视频设备驱动以及fb驱动,更加深入的理解了计算机底层图像处理的原理。

主要流程

整个处理的主要步骤分为以下:

  1. Linux v4l2驱动采集摄像头数据。
  2. 图像格式转换:由于采集到的图像不是能直接用来显示的RGB格式,需要进行格式转换。
  3. 图像缩放:摄像头采集的图片分辨率与显示屏分辨率不匹配,需要进行图像缩放。
  4. 图像显示:缩放后的数据写到fb的内存映射上。

v4l2驱动采集摄像头数据

v4l2是Linux内置的视频设备驱动,其采集摄像头数据的流程主要分为以下几步:

  1. 打开视频设备,使用open()函数,打开成功返回对应的设备描述符。
//1. open device
char* default_camera = "/dev/video0";
int fd_camera;
    if(argc<2)
        fd_camera = open(default_camera, O_RDWR);
    else
        fd_camera = open(argv[1], O_RDWR);
    if(fd_camera == -1)
    {
        printf("fail to open the camera!\n");
        return -1;
    }
  1. 获取,设置摄像头参数
    相关结构体有:
    v4l2_capability: 摄像头主要信息的结构体,如其名字,记录了摄像头的能力。
    v4l2_fmtdesc: 存储摄像头数据格式信息的结构体,可查看摄像头支持的视频数据格式。
    v4l2_format: 帧数据具体格式信息,如图像宽高等。可通过此结构体对摄像头传输出来的帧数据进行设置( VIDIOC_S_FMT)或者获取( VIDIOC_G_FMT)。这里主要是把输出帧图像数据格式设置为YUYV或MJPEG。

!!!这里有个坑需要注意,设置后一定要再次获取format,因为设置不一定成功。而且图像宽高不能随意设置,我一开始简单的设置为跟屏幕分辨率一致,虽然设置成功了,但是读取时摄像头却没有采集到数据,推测摄像头不支持我设的分辨率,毕竟摄像头里面应该支持不了任意尺度的图像缩放,需要获取后自己用算法进行处理。但是它竟然返回的结果确实设置成功了,导致后面在等待buffer出队的时候一直没有数据,程序一直处于阻塞状态。所以在不了解摄像头的情况下还是谨慎设置。

//2.get camera capture(camera infomation)
    v4l2_capability cap;
    ioctl(fd_camera, VIDIOC_QUERYCAP, &cap);
    describe_cap(cap);
//3.format describe: get cam frame info
    v4l2_fmtdesc fmtdesc;
    fmtdesc.index = 0;
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    printf("support format:\n");
    while(ioctl(fd_camera, VIDIOC_ENUM_FMT, &fmtdesc) != -1)
    {
        cout<<fmtdesc.index<<":"<<fmtdesc.description<<endl;
        fmtdesc.index++;
    }

//4.format: set cam info, YUYV here.
    v4l2_format fmt;
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;

    if(-1 == ioctl(fd_camera, VIDIOC_S_FMT, &fmt))
        perror("fail to set fmt");
    if(-1 == ioctl(fd_camera, VIDIOC_G_FMT, &fmt))
        perror("fail to get fmt");
    cout<<"video format is:"<<(fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV?"YUYV":"UNKNOWN")<<endl;
    cout<<"frame size:"<<fmt.fmt.pix.width<<"x"<<fmt.fmt.pix.height<<endl;

    void describe_cap(v4l2_capability cap)
{
    cout<<"driver:"<<cap.driver<<endl;
    cout<<"name:"<<cap.card<<endl;
    cout<<"bus info: "<<cap.bus_info<<endl;
    cout<<"driver version:"<<(cap.version>>16&0xff)<<"."<<
                             (cap.version>>8&0xff)<<"."<<
                             (cap.version&0xff)<<endl;
    printf("capabilities: %x\n", cap.capabilities);
    printf("is video capture: %s\n", cap.capabilities&V4L2_CAP_VIDEO_CAPTURE? "True":"False");
    printf("support io read: %s\n", cap.capabilities&V4L2_CAP_READWRITE? "True":"False");
    printf("support IO stream: %s\n", cap.capabilities&V4L2_CAP_STREAMING?"True":"False");
}
  1. 申请帧缓冲队列。
    v4l2提供帧缓冲队列缓冲摄像头采集到的帧数据,申请到帧缓冲队列后是操作系统自动管理的,每次要获取数据需要将buffer出队,用完后将buffer入队。涉及到的结构体有:
    v4l2_requestbuffers:用于申请帧缓冲队列;
    v4l2_buffer: 用于操作buffer的结构体;
    整个操作的流程就是:
    (1)申请帧缓冲队列(VIDIOC_REQBUFS)
    (2) 查询帧缓冲队列(VIDIOC_QUERYBUF),字面意思,查询申请的buffer
    (3) 缓冲入队列( VIDIOC_QBUF)初始化时每次QUERYBUF后需要执行一次入列。后续每次访问数据需要先QBUF, 访问后需要入队列VIDIOC_DQBUF
    (4) 内存映射 mmap
//5.get frame to buffer
//5.1 request buffers
    struct v4l2_requestbuffers req;
    req.count = 4;
    req.memory = V4L2_MEMORY_MMAP;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if(ioctl(fd_camera, VIDIOC_REQBUFS, &req)<0)
    {
        perror("fail to request buffer");
    }
    cout<<"frame buffer request done"<<endl;

//5.2 map buffer
    void* frame_map[2];
    v4l2_buffer buf;
    for(int i=0;i<2;i++)
    {
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        if(-1 == ioctl(fd_camera, VIDIOC_QUERYBUF, &buf))
        {
            printf("query buf%d error!\n", i);
            return 1;
        }
        printf("query buf%d done;\t", i);


        //start to map,把缓冲队列映射到frame_map[]指针数组里面。
        frame_map[i] = mmap(0, buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, fd_camera, buf.m.offset);
        if(MAP_FAILED == frame_map[i])
        {
            perror("fram buffer map failed\n");
        }
        printf("map buf%d done;\t", i);

        // queue buffer
        if(-1 == ioctl(fd_camera, VIDIOC_QBUF, &buf))
        {
            printf("queue buffer%d failed!\n", i);
            return 1;
        }
        printf("queue buf%d done!\n", i);
    }

  1. 开启视频流
// stream on
    v4l2_buf_type type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if(-1 == ioctl(fd_camera, VIDIOC_STREAMON, &type))
        perror("stream on error\n");
    cout<<"stream on done\n"<<endl;
  1. 采集图像
    准备工作都做好后,采集图像只要遵循这一个简单的流程就可以了:
    VIDIOC_DQBUF buffer出队列==》用映射好的内存地址获取图像数据==》VIDIOC_QBUF buffer入队列
 while(!con.interrupt)
    {
//6.1 dequeue buffer;
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        int ret = ioctl(fd_camera, VIDIOC_DQBUF, &buf);
        if(ret == -1)
        {
            perror("dequeue fail");
            return 1;
        }


//6.2 pic process
	//图像处理
//6.3 enqueue buffer
        if(-1 == ioctl(fd_camera, VIDIOC_QBUF, &buf))
        {
            perror("enqueue fail");
            return 1;
        }
        usleep(10000);
    }

图像处理与显示

图像处理

摄像头采集的图片是YUV4:2:2的格式,即像素按照以下顺序排列:

Y00 U00, 01 Y01 V00, 01 Y02 U02, U03 Y03 V02, V03
Y10 U10, 11 Y11 V10, 11 Y12 U12, U13 Y13 V12, V13

简单理解起来就是每个像素点都有Y亮度值,而U跟V的色度信号是左右间隔排列。可以把每两个像素看成一组,则每组的两个像素各有各的亮度值,但是分享了U、V色度值。这样做估计是因为人眼对亮度的空间分辨率较为敏感吧。
根据YUV转RGB的公式,可以将图片转为RGB。
由于每一帧都需要进行处理,为了节省解码时间,可以在初始化时将YUV到RGB的映射关系制成一个映射表,生成文件存放起来,这样可以大大提高解码数率。

void init_yuv2bgr()
{
   
//将YUV转RGB映射输出为文件保存,方便以后每次运行。
    FILE* yuv2bgr_table = fopen("yuv2bgr_table.txt", "r");
    if(yuv2bgr_table == NULL)
        yuv2bgr_table = fopen("yuv2bgr_table.txt", "w");
    else
    {
   
        fread(yuv2r, 255*255*255, 1, yuv2bgr_table);
        fread(yuv2g, 255*255*255, 1
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值