创客神器NanoPi
直播中

donatello1996

9年用户 737经验值
擅长:MEMS/传感技术
私信 关注
[经验]

【NanoPi K1 Plus试用体验】移植USB摄像头驱动采集图像并显示到FrameBuffer【动态数据篇】

`    本篇帖子中,关于USB摄像头的驱动参考了以下这篇文章:
https://blog.csdn.net/weed_hz/article/details/8939830
    Linux开发板本身能驱动USB UVC免驱摄像头,可以用于采集静态图像和动态图像。检测Linux开发板能不能驱动手头的USB摄像头,只需要查看/dev目录下的文件即可,先来看看没插上USB摄像头时/dev目录的文件:
52.jpg
可以看到只有一个video0,这个video0是K1+开发板自带的CSI摄像头的驱动,跟USB摄像头驱动无关。再把我的USB摄像头插上开发板,再检查一下/dev目录:
53.jpg
多出了一个video1设备,这个设备就是我们今天的主角,USB UVC免驱摄像头。为什么叫video呢,因为开发板可以拿这个摄像头采集静态或者动态数据,即这个设备为【多媒体视频输入/输出设备】,当然,是当做输入设备来用,摄像头怎么可能做输出设备呢?

要启用Linux系统采集摄像头数据的功能,需要一个名为video for linux 2的软件驱动库,简称v4l2,在大部分的Linux系统中都有,启用时需要添加头文件支持:
54.jpg
然后是定义相关的结构体:
static struct  v4l2_capability cap;
//V4L2设备信息读结构体对象
struct v4l2_fmtdesc fmtdesc;
struct v4l2_format fmt;
//V4L2设置捕获信息读写结构体对象
struct v4l2_streampARM setfps;
//V4L2设置帧数相关读写结构体对象
struct v4l2_requestbuffers req;
//V4L2申请帧缓冲结构体对象
struct v4l2_buffer buf;
//V4L2缓存数据结构体对象
enum v4l2_buf_type type;
//V4L2采集类型枚举量

在使用摄像头之前,必须要做的事情就是检测和设置摄像头的最大分辨率,一般老式UVC摄像头都是640*480,格式都是YUYV42。这里要说明一下,虽然现在提倡使用MJPEG格式的采集流来采集图像数据,这个格式转换方法非常适合系统采集到摄像头数据之后立即保存为JPG图像文件或MPEG视频文件,也就是拍照或者录制视频。但是MJPEG数据结构转标准RGB888缓存数据结构的算法比较复杂,而YUYV42转标准RGB888的算法都有现成的代码可以参考,直接就可以移植到自己的程序里,对于新手理解摄像头数据流来说非常有帮助,因此本帖还是推荐新手使用YUYV42格式采集流采集数据。
直接使用读取摄像头信息的载体v4l2_format结构体对象fmt来读取相关格式信息:
55.JPG
然后就是申请缓存空间、内存块映射等工作了,使用到的方法有malloc、ioctl、mmap等:
int V4l2_Grab()
{
    unsigned int n_buffers;

    buffers =(buffer*)malloc(req.count*sizeof (*buffers));
    if (!buffers)
    {
        printf ("Out of memory
");
        return 0;
    }

    for (n_buffers = 0; n_buffers < req.count; n_buffers++)
    {
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = n_buffers;
        //query buffers
        if (ioctl (fd_video,VIDIOC_QUERYBUF, &buf) == -1)
        {
            printf("query buffer error
");
            return(0);
        }

        buffers[n_buffers].length = buf.length;
        buffers[n_buffers].start = mmap(NULL,buf.length,PROT_READ |PROT_WRITE, MAP_SHARED,
        fd_video, buf.m.offset);
        if (buffers[n_buffers].start == MAP_FAILED)
        {
            printf("buffer map error
");
            return 0;
        }
    }

    for (n_buffers = 0; n_buffers < req.count; n_buffers++)
    {
        buf.index = n_buffers;
        ioctl(fd_video, VIDIOC_QBUF, &buf);
    }

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl (fd_video, VIDIOC_STREAMON, &type);

    ioctl(fd_video, VIDIOC_DQBUF, &buf);

    printf("grab yuyv OK
");
    return 1;
}

再做YUYV42转RGB888的算法代码,以实现将摄像头采集到的数据显示到Frame上面:
int Yuyv_2_RGB888(buffer* input_buffers,unsigned char *output_buffer)
{
    int i,j,r1,g1,b1,r2,g2,b2;
    unsigned char y1,y2,u,v;
    unsigned char *pointer;

    pointer =(unsigned char*)input_buffers[0].start;

    for(i=0;i     {
    for(j=0;j     //每次取4个字节,也就是两个像素点,转换rgb,6个字节,还是两个像素点
    {
    y1 = *( pointer + (i*IMAGEWIDTH/2+j)*4);
    u  = *( pointer + (i*IMAGEWIDTH/2+j)*4 + 1);
    y2 = *( pointer + (i*IMAGEWIDTH/2+j)*4 + 2);
    v  = *( pointer + (i*IMAGEWIDTH/2+j)*4 + 3);

    r1 = y1 + 1.042*(v-128);
    g1 = y1 - 0.34414*(u-128) - 0.71414*(v-128);
    b1 = y1 + 1.772*(u-128);

    r2 = y2 + 1.042*(v-128);
    g2 = y2 - 0.34414*(u-128) - 0.71414*(v-128);
    b2 = y2 + 1.772*(u-128);

    if(r1>255)
    r1 = 255;
    else if(r1<0)
    r1 = 0;

    if(b1>255)
    b1 = 255;
    else if(b1<0)
    b1 = 0;

    if(g1>255)
    g1 = 255;
    else if(g1<0)
    g1 = 0;

    if(r2>255)
    r2 = 255;
    else if(r2<0)
    r2 = 0;

    if(b2>255)
    b2 = 255;
    else if(b2<0)
    b2 = 0;

    if(g2>255)
    g2 = 255;
    else if(g2<0)
    g2 = 0;

    *(output_buffer + (i*IMAGEWIDTH/2+j)*6    ) = (unsigned char)b1;
    *(output_buffer + (i*IMAGEWIDTH/2+j)*6 + 1) = (unsigned char)g1;
    *(output_buffer + (i*IMAGEWIDTH/2+j)*6 + 2) = (unsigned char)r1;
    *(output_buffer + (i*IMAGEWIDTH/2+j)*6 + 3) = (unsigned char)b2;
    *(output_buffer + (i*IMAGEWIDTH/2+j)*6 + 4) = (unsigned char)g2;
    *(output_buffer + (i*IMAGEWIDTH/2+j)*6 + 5) = (unsigned char)r2;
    }
    }
    printf("change to RGB OK
");
    free(input_buffers);
}

做好了之后,有个unsigned char *类型的指针,这个指针指向的用户内存就是存放摄像头数据的缓存了,我们既可以直接输出到FrameBuffer上面:
int LCD_Show_Buffer(char *dev_name,int width,int height,unsigned char *frame_buffer)
{
    int fd_lcd,i,j;
        fd_lcd=open(dev_name,O_RDWR);
        if(fd_lcd==-1)
        {
                printf("open LCD failed!
");
                return -1;
        }
        for(i=0;i         {
            for(j=0;j                 if(i<=height&&j<=width)
                {
                    lcd_buf[i*LCD_WIDTH+j]=
                    frame_buffer[(i*width+j)*3]|
                    frame_buffer[(i*width+j)*3+1]<<8|
                    frame_buffer[(i*width+j)*3+2]<<16;
                }
                    else lcd_buf[i*LCD_WIDTH+j]=0;
        }
        write(fd_lcd,lcd_buf,LCD_WIDTH*LCD_HEIGHT*4);
        close(fd_lcd);
}

也可以保存为JPG文件:
int Encode_Jpeg(char *lpbuf,int width,int height,char *output_filename)
{
    struct jpeg_compress_struct cinfo ;
    struct jpeg_error_mgr jerr ;
    JSAMPROW  row_pointer[1] ;
    int row_stride ;
    char *buf=NULL ;
    int x ;

    FILE *fptr_jpg = fopen (output_filename,"wb");//注意这里为什么用fopen而不用open
    if(fptr_jpg==NULL)
    {
    printf("Encoder:open file failed!/n") ;
     return 0;
    }

    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);
    jpeg_stdio_dest(&cinfo, fptr_jpg);

    cinfo.image_width = width;
    cinfo.image_height = height;
    cinfo.input_components = 3;
    cinfo.in_color_space = JCS_RGB;

    jpeg_set_defaults(&cinfo);


    jpeg_set_quality(&cinfo, 80,1);


    jpeg_start_compress(&cinfo, 1);

    row_stride = width * 3;
    buf=(char*)malloc(row_stride);
    row_pointer[0] =(unsigned char*)buf;
    while (cinfo.next_scanline < height)
    {
     for (x=0;x     {

    buf[x]   = lpbuf[x];
    buf[x+1] = lpbuf[x+1];
    buf[x+2] = lpbuf[x+2];

    }
    jpeg_write_scanlines (&cinfo, row_pointer, 1);
    lpbuf += row_stride;
    }

    jpeg_finish_compress(&cinfo);
    fclose(fptr_jpg);
    jpeg_destroy_compress(&cinfo);
    free(buf);
    printf("save "JPEG"OK
");
    return 0 ;

}

int close_v4l2(void)
{
     if(fd_video!=-1)
     {
         close(fd_video);
         return 1;
     }
     return 0;
}

使用成本低廉像素极渣的UVC摄像头采集一下我简陋但舒服的小房间(PS:等我发工资了,我一定要买一个高清顺畅2000W像素柔光360度拍摄照亮你的美的UVC摄像头):

然后是使用JPG采集即拍照:
56.jpg 57.jpg

` IMG_20180905_092019R.jpg 1.gif

更多回帖

发帖
×
20
完善资料,
赚取积分