Linux小相机(利用V4L2接口编写,保存格式为Mat)

这段时间一直在做Linux视觉相关的学习,一直弄不清楚V4L2相关的操作。所以本着学习的态度,制作一个Linux小相机。本次工程是基于正原子的i.IMX6null开发板+USB免驱摄像头+任意显示屏。至于为什么要转为Mat格式呢?因为在后期进行深度学习时,利用OpenCV来处理是非常方便的。

目录

一、编写V4L2相关代码

1、打开摄像头,利用open函数来打开,fd句柄用于判断开启状态。

2、获取摄像头支持的格式,可以查看摄像头支持的格式,用于第三步设置摄像头抓图格式

3、设置摄像头相关格式,相关格式可以在第二部打印出来,我这里设置的是MJPEG格式,大家根据自己摄像头来设置。

4、申请内核缓冲区队列映射到用户空间,也就是向内核申请一个位置用于存放数据

5、把内核的缓冲区映射到用户空间

6、开始采集,VIDIOC_STREAMON(开始采集写数据到队列中)

7、提取数据,并且将数据转为Mat格式。

8、停止采集、释放空间

二、利用封装好的函数进行显示

三、利用按键输入来进行拍照


一、编写V4L2相关代码

编写这一部分的代码需要一定的文件IO基础,能够明白open、ioctl等等基础知识。整体流程就是

1、打开摄像头,利用open函数来打开,fd句柄用于判断开启状态。

    if(argc != 2)
    {
        printf("plese input camrea_dev!\n");
        return -1;
    }
    printf("按下key0进行拍照!\n");
    //1、打开摄像头
    int fd = open(argv[1], O_RDWR);
    if(fd < 0)
    {
        perror("open camrea fail!");
        return -1;
    }

2、获取摄像头支持的格式,可以查看摄像头支持的格式,用于第三步设置摄像头抓图格式

	struct v4l2_fmtdesc  vfmts;
	vfmts.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	int ret  = ioctl(fd, VIDIOC_ENUM_FMT, &vfmts);
	if(ret < 0)
	{
		perror("获取设备支持格式VIDIOC_ENUM_FMT失败");
		return -1;
	}
	printf("index = %d\n", vfmts.index);
	printf("%s\n", vfmts.description);

3、设置摄像头相关格式,相关格式可以在第二部打印出来,我这里设置的是MJPEG格式,大家根据自己摄像头来设置。

    //3、设置摄像头格式
    struct v4l2_format v4format;
    v4format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    v4format.fmt.pix.height = 480;
    v4format.fmt.pix.width = 640;
    v4format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; //根据自己的摄像头支持格式
    int ret = ioctl(fd, VIDIOC_S_FMT ,&v4format);
    if(ret < 0)
    {
        perror("set format fail!");
    }
    printf("set camrea format MJPEG succesful!\n");

4、申请内核缓冲区队列映射到用户空间,也就是向内核申请一个位置用于存放数据

    struct v4l2_requestbuffers requstbuffer; 
    requstbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    requstbuffer.count = 4; //申请4个缓冲区
    requstbuffer.memory = V4L2_MEMORY_MMAP;
	ret = ioctl(fd, VIDIOC_REQBUFS, &requstbuffer);
    if(ret < 0)
    {
        perror("request fail!");
    }

5、把内核的缓冲区映射到用户空间

    //unsigned char *mptr[4]; //保存映射后用户空间的首地址 ,非常重要,以后采集就是在这里,我这里设置为全局变量了;
    unsigned int size[4];
    struct v4l2_buffer mapbuffer;
    mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    int i = 0;
    for(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("back fail!");
        }

    }

6、开始采集,VIDIOC_STREAMON(开始采集写数据到队列中)

    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    ret = ioctl(fd, VIDIOC_STREAMON, &type);

    if(ret < 0)

    {

        perror("start camrea fail");

    }

7、提取数据,并且将数据转为Mat格式。

   //从队列中提取一帧数据

       //struct v4l2_buffer readbuffer;
        readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);
        if(ret < 0)
        {
            perror("read fail");
        }

    /*将mjpg格式数转化成opencv Mat格式*/
        std::vector<uchar> mjpeg_vec((char *)mptr[readbuffer.index], (char *)mptr[readbuffer.index] + readbuffer.bytesused);
        cv::Mat frame = cv::imdecode(mjpeg_vec, cv::IMREAD_COLOR);
        show(frame); //我这里封装了一个显示函数
        //告诉内核我已经使用完毕
        ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
        if(ret < 0 )
        {
            perror("back fail");
        }

8、停止采集、释放空间

    //停止采集,不再向队列写数据

    ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
    for(i=0 ; i< 4; i++)
    {
        munmap(mptr[i], size[i]);
    }
    close(fd);

二、利用封装好的函数进行显示

这个函数是我自己封装好的函数,用于显示OpenCV的Mat格式。底层还是用到了Linux通用的架构

struct framebuffer_info {
    uint32_t bits_per_pixel;
    uint32_t xres_virtual;
};

struct framebuffer_info get_framebuffer_info(const char* framebuffer_device_path)
{
    struct framebuffer_info info;
    struct fb_var_screeninfo screen_info;
    int fd = -1;
    fd = open(framebuffer_device_path, O_RDWR);
    if (fd >= 0) {
        if (!ioctl(fd, FBIOGET_VSCREENINFO, &screen_info)) {
            info.xres_virtual = screen_info.xres_virtual;
            info.bits_per_pixel = screen_info.bits_per_pixel;
        }
    }
    return info;
};

void show(const cv::Mat& image) {
    framebuffer_info fb_info = get_framebuffer_info("/dev/fb0");
    std::ofstream ofs("/dev/fb0");
    if (image.depth() != CV_8U) {
        std::cerr << "Not 8 bits per pixel and channel." << std::endl;
        return;
    }

    else if (image.channels() != 3) {
        std::cerr << "Not 3 channels." << std::endl;
        return;
    }
    else{
    cv::Mat transposed_image;
    cv::resize(image, transposed_image, cv::Size(DISPLAY_X, DISPLAY_Y));
    int framebuffer_width = fb_info.xres_virtual;
    int framebuffer_depth = fb_info.bits_per_pixel;
    cv::Size2f image_size = transposed_image.size();
    cv::Mat framebuffer_compat;
    switch (framebuffer_depth) {
        case 16:
           cv::cvtColor(transposed_image, framebuffer_compat, cv::COLOR_BGR2BGR565);
            for (int y = 0; y < image_size.height; y++) {
                ofs.seekp(y * framebuffer_width * 2);
                ofs.write(reinterpret_cast<char*>(framebuffer_compat.ptr(y)), image_size.width * 2);
            }
            break;
        case 32: {
            std::vector<cv::Mat> split_bgr;
            cv::split(transposed_image, split_bgr);
            split_bgr.push_back(cv::Mat(image_size, CV_8UC1, cv::Scalar(255)));
            cv::merge(split_bgr, framebuffer_compat);
            for (int y = 0; y < image_size.height; y++) {
                ofs.seekp(y * framebuffer_width * 4);
                ofs.write(reinterpret_cast<char*>(framebuffer_compat.ptr(y)), image_size.width * 4);
            }
        } break;
        default:
            std::cerr << "Unsupported depth of framebuffer." << std::endl;
            break;
    }   
    }
}

三、利用按键输入来进行拍照

这个底层逻辑还是文件的读写,因为底层驱动都已经写好了。不过这里我使用了多线程进行实现拍照功能,主线程用于显示,这个线程用于获取按键输入和保存图片。

static void *thread_takephoto_control(void *args)
{
             /* 打开文件 */
        if (0 > (fd_key = open("/dev/input/event2", O_RDONLY))) {
            perror("open error");
           exit(-1);

        }
        int i = 0;   
            while(1) 
            {
                char file[100];
                /* 循环读取数据 */

                if (sizeof(struct input_event) != read(fd_key, &in_ev, sizeof(struct input_event))) {
                    perror("read error");
                    exit(-1);
                }
                if (EV_KEY == in_ev.type) { //按键事件
                    switch (in_ev.value) {
                    case 1:
                        /*将mjpg格式数转化成opencv Mat格式*/
                        std::vector<uchar> mjpeg_vec((char *)mptr[readbuffer.index], (char *)mptr[readbuffer.index] + readbuffer.bytesused);
                        frame = cv::imdecode(mjpeg_vec, cv::IMREAD_COLOR);
                        sprintf(file,"%u.jpg",i);
                        cv::imwrite(file, frame);
                        printf("拍照成功!%s已保存至当前目录\n", file);
                        i++;
                        break;;
                    }
                }
            }
            close(fd_key);
}

这里使用了全局变量,mptr[]。也就是前面我们申请的用户空间。

四、实物展示进行拍照

1、在板端进行运行

2、摄像头拍下的照片

3、LCD屏幕上进行显示

需要完整代码可以私信我。

  • 19
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值