QT使用V4L2摄像头采集数据

news2024/9/30 4:56:14

前言

        之前我们已经实现了摄像头用V4L2框架采集一张图片,现在就是实现用摄像头采集视频流(本质一张图片就是一帧,很多张图片就是很多帧,拼起来就是一个视频)。

        本部分需要大家有一点QT相关的知识,整体框架还是非常简单的,和昨天采集一个图片没什么区别,这里就只讲如何采集视频流,至于后面功能的完善就大家自己发挥。

       Ubuntu版本的采集,相对比较简单,因为不涉及到交叉编译器等相关知识,在编译过程也比较顺利,稍微注意一点细节就好了。比较难的是嵌入式平台的部署。但本质也都和Ubuntu版本一样,只是用的编译器和链接的库文件有所不同。

Ubuntu下采集

QT代码的编写

#ifndef MAINWINDOW_H
#define MAINWINDOW_H


#include <QTimer>
#include <QMainWindow>

//这些是用到的一些头文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <sys/mman.h>



#define video_width 320
#define video_height 240


QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    QTimer *timer;    //这个是个定时器
    unsigned char *userbuf[4]; //保存地址映射后的地址值
    int userbuf_length[4];//长度
    int fd;        //摄像头文件的id
    int start;    //开始标志位

    int v4l2_open();    //摄像头打开操作
    int v4l2_close();    //摄像头关闭操作

public slots:
    void video_show();    //这是一个槽,由定时器触发,定时器触发一次采集一张图片显示
};
#endif // MAINWINDOW_H

        以上便是QT的.h代码

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "yuv_to_jpeg.h"//这个是用来yuv转jpg的一个C库后面会讲到



MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    timer = new QTimer();
    start=0;
    connect(timer,SIGNAL(timeout()),this,SLOT(video_show()));//连接一个信号槽,定时器超时发出信号,执行槽函数采集一张图片。
    if(0==v4l2_open())//打开摄像头,可以理解为摄像头初始化,如果初始化成功开始标志置1,定时器开始以10ms为周期计时。即10ms采集一张图片。
    {
        start=1;
        timer->start(10);
    }
}

MainWindow::~MainWindow()
{
    v4l2_close();
    delete ui;
}

int MainWindow:: v4l2_open()//和之前一篇文章讲到的框架大同小异,不再赘述。
{
    //打开摄像头
    fd = open("/dev/video0",O_RDWR);
    if(fd<0)
    {
        perror("打开失败");
        return -1;
    }
    //获取摄像头参数
    struct v4l2_capability capability;
    if(ioctl(fd,VIDIOC_QUERYCAP,&capability))
    {
        if((capability.capabilities&V4L2_CAP_VIDEO_CAPTURE)==0)
        {
            perror("不支持视频采集");
            ::close(fd);
            return -2;
        }
        if((capability.capabilities&V4L2_MEMORY_MMAP)==0)
        {
            perror("不支持mmap内存映射");
            ::close(fd);
            return -3;
        }
    }
    //设置摄像头参数
    struct v4l2_format format;
    format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    format.fmt.pix.width = video_width;
    format.fmt.pix.height =video_height;
    format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    if(ioctl(fd,VIDIOC_S_FMT,&format)<0)
    {
        perror("设置失败");
        ::close(fd);
        return -4;
    }
    //申请内存空间
    struct v4l2_requestbuffers requestbuffers;
    requestbuffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    requestbuffers.count = 4;
    requestbuffers.memory = V4L2_MEMORY_MMAP;
    if(ioctl(fd,VIDIOC_REQBUFS,&requestbuffers)==0)
    {
        for(unsigned int i=0;i<requestbuffers.count;i++)
        {
            struct v4l2_buffer buffer;
            buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buffer.index = i;
            buffer.memory = V4L2_MEMORY_MMAP;
            if(ioctl(fd,VIDIOC_QBUF,&buffer)==0)
            {
                userbuf[i] = (unsigned char*)mmap(NULL,buffer.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,buffer.m.offset);
                userbuf_length[i]=buffer.length;
            }
        }
    }
    else
    {
        perror("申请内存失败");
        ::close(fd);
        return -5;
    }
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if(ioctl(fd,VIDIOC_STREAMON,&type)>0)
    {
        perror("打开视频流失败!");
        return -6;
    }

    return 0;
}

int MainWindow::v4l2_close()//关闭操作,可以理解为逆初始化
{
    //停止采集,关闭视频流
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if(ioctl(fd,VIDIOC_STREAMOFF,&type)==0)
    {
        for(int i=0;i<4;i++)
        {
            munmap(userbuf[i],userbuf_length[i]);
        }
        ::close(fd);
        return 0;
    }
    perror("关闭视频流失败");
    return -1;
}

void MainWindow::video_show()
{
    QPixmap pix;
    int jpg_size;
    unsigned char *jpg_p = new unsigned char[240*320*3];//这里申请了内存后面要记得释放,不然会爆
    struct v4l2_buffer buffer;
    buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if(ioctl(fd,VIDIOC_DQBUF,&buffer)==0)
    {
        jpg_size = yuv_to_jpeg(320,240,240*320*3,userbuf[buffer.index],jpg_p,80);//yuv转jpg
        pix.loadFromData(jpg_p,jpg_size);
        pix.scaled(ui->label->width(),ui->label->height(),Qt::KeepAspectRatio);
        ui->label->setPixmap(pix);
    }
    delete [] jpg_p;//释放内存
    jpg_p = nullptr;
    if(ioctl(fd,VIDIOC_QBUF,&buffer)<0)
    {
        perror("返回队列失败1");
    }
}

        以上便是QT的.c代码 

QT添加外部链接库

        由于在本qt工程中,调用到了jpeglib库相关的文件,而qt在编译的时候默认是找不到这个安装的外部库的,所以就需要我们指定一下这个库路径。

        这里去找,我们Ubuntu下安装的jpeglib默认路径是在:/home/usr/local/lib下,选中libjpeg.a文件他自动就会索引上,我们的头文件。

然后下一步到完成即可。其实以上操作的本质就是在qt的.pro文件下添加了这一系列的代码,去索引我们的库文件。

QT添加C语言程序(C库)

        由于C语言和C++很多代码都是共通的,C++也基本向下兼容C语言,所以这里操作比较简单。

把这个.c和.h文件包含进来,添加以下几行声明去兼容C即可。然后我们就可以像正常C语言一样去调用函数了。

总结

完成以上操作以后,我们的QT采集摄像头数据的工程也就完成了,接下来只需要往Ubuntu插入摄像头,然后运行即可。

一个最简单的Ubuntu下摄像头采集就完成了。

IMX6ULL平台下采集

        首先得确保你的开发板能运行QT程序,其次要确保你的开发板已经安装了jepglib,如果符合以上要求再往下看。基于Ubuntu下采集的代码,我们只需要转换以下编译器,编译出一个可以在开发板运行的应用即可,具体流程如下。

创建QT交叉编译器

        正常开发板出厂会提供一个qt的交叉编译器,先把这个安装了。这里我已经安装好了,不同的开发板有不同的安装方法,这里就不赘述了。

        然后便是配置QT的脚本,打开qt安装目录下的.sh文件

        sudo vi /opt/Qt5.12.9/Tools/QtCreator/bin/qtcreator.sh

在第一行插入以下:

        source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi

保存

        接下来是配置qt编译器,在编译器这里添加

        路径就是你之前安装的qt交叉编译器下我的是:

/opt/fsl-imx-x11/4.1.15-2.1.0/sysroots/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-g++

/opt/fsl-imx-x11/4.1.15-2.1.0/sysroots/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-gcc

        g++对应的是C++代码的编译,gcc对应的是c语言代码的编译,这里配置好以后,到配置qmake。

        qmake也在你安装的qt交叉编译器的路径下。配置好以后,到最后配置kits

        指定为你之前新增的g++,gcc,qmake然后保存,现在我们就已经创建好了一个新的qt交叉编译工具,但由于这个交叉编译工具功能非常简陋,这边建议在linux下测试没问题再切换到这个交叉编译器编译上板的app。

编译工程

        打开我们之前写好的摄像头代码,在箭头指的那个套件环境下测试过没问题后,切换到imx6ull的那个套件编译上板应用。

        这里值得注意的是,由于我们引用了一个jpeglib,之前在Ubuntu下跑的摄像头代码选中的这个jpeglib不是用交叉编译器出来的。现在我们要在arm下跑,用前面那个库很明显就不行了。要重新指定一个用交叉编译器编译出来的jpeglib。下图这一段全删了。

重新右键添加库。这次选中的库,就是用交叉编译编译出来的jpeglib了。(如何用交叉编译器编译出一个jpeglinb,并移植到开发板可以看我之前文章)

选择好后,这个地方会生成新的一个引用代码。

此时再点左下角的锤子图标构建(注意,点绿色播放器按钮是会报错的,因为我们这个套件非常简陋并没有仿真运行功能)。

切换过来后,你原来的工程会有一堆红线错误,但不用管。锤子构建后,显示下图,即编译通过。

此时在产出目录下会有这个文件

把这个文件弄到开发板上(注意:开发板的qt环境要和你编译的qt环境一致)。此时在板子上运行这个app。

可以看到已经跑出来了。

总结

        以上便是Ubuntu下和开发板下的两种QT采集摄像头显示的代码了,这里比较关键的是QT的配置,比较细节,初学可能会踩很多的坑。

补前文采集像素不能过高的问题

        这里的问题原来是在于,imx6ull只支持usb2.0的接口,而我的摄像头是usb3.0的。这就导致在传输480*640这种分辨率比较大的数据时会出这种问题。目前的解决方法似乎只有更换摄像头或者开发板,最近也是在看内核相关的源码,看看能不能找出不更换硬件的解决方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1979949.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CSP 2020 第三题:表达式

牛客网题目 题目内容&#xff1a; 示例1 输入 x1 x2 & x3 | 3 1 0 1 3 1 2 3输出 1 1 0题意&#xff1a; 给出后续表达式&#xff0c;需要计算这个表达式的值&#xff0c;并让某几个变量值取反&#xff0c;再输出新的表达式的值&#xff08;变量改变均为临时的&#xff…

基于Orangepi全志H616学习Python3

目录 一、功能需求 二、Python的安装和环境搭建 三、Python的基础学习 3.1 Python的特点&#xff1a; 3.2 编写并运行第一个Python程序&#xff1a; 3.3 标识符&#xff1a; 3.4 关键字&#xff1a; 3.5 注释&#xff1a; 3.6 行与缩进&#xff1a; 3.7 多行语句&…

虚拟机(CentOS7)安装jenkins

centos7安装jenkins 前提条件&#xff0c;安装jdk与maven 1、JDK17安装 # 进入系统管理员 sudo root # 进入对应文件夹下 cd /usr/local # 下载jdk17 wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.rpm # rpm命令安装下载的jdk17 rpm -ivh jdk-17_li…

从根儿上学习spring 七 之run方法启动第四段(1)

图1 一步一步我们转眼间来到了第四部分&#xff0c;这是spring最核心的部分包含了bean的整个生命周期过程&#xff0c;不过大家不用担心如果内容过长我会分多个小节来说明以防止一篇文章让大家看晕看累难以吸收理解。让我们直接进入正题。 我们先进入图1的refreshContext方法看…

PEX实验

一、kickstart自动安装脚本制作 1.关闭本机dhcp服务 2.安装图形化生成kickstart自动安装脚本的工具 3.配置http服务 下载httpd 启动并挂载 3.启动图形制作工具 system-config-kickstart 4.配置ks.cfg 5.拷贝到/var/www/html/中去 6.浏览器测试 配置dhcp服务 测试 二.搭建pex…

【JVM基础11】——垃圾回收-说一下JVM的分代回收?

目录 1- 引言&#xff1a;分代回收1-1 什么是分代回收&#xff08;What&#xff09;1-2 为什么要用分代回收&#xff1f;&#xff08;Why&#xff09; 2- ⭐核心&#xff1a;分代回收工作机制2-1 工作机制2-2 MinorGC、Mixed GC、FullGC的区别是什么 3- 总结3-1 说一下 JVM 的分…

【Java 第三篇章】注释、数据类型、运算符

一、注释 Java 中的注释有三种方式&#xff1a;单行注释、多行注释、文档注释。 1、单行注释语法 // 这是单行注释2、多行注释 /** 这是多行注释*/3、文档注释 /*** 这是文档注释*/二、数据类型 Java 中有 8 中基本数据类型&#xff0c;分别为&#xff1a;整型&#xff08;b…

数据结构实验报告-排序

桂 林 理 工 大 学 实 验 报 告 一、实验名称 实验8 排序 二、实验内容&#xff1a; 分别采用直接插人排序、希尔排序、冒泡排序、快速排序、简单选择排序、堆排序、归并排序等排序算法对简单的整型数组进行排序,并输出排序结果。 源码&#xff1a;#include <iostre…

分享一个简单线性dp

我们可以o(n^2)&#xff0c;枚举每一个布告&#xff0c;然后从后往前枚举i前面的位置&#xff0c;然后状态转移 void solve() {int n;cin >> n;vector<int> a(n 1);for (int i 1; i < n; i) cin >> a[i];vector<int> f(n 1, 0x3f3f3f3f);f[0] …

【分隔链表】python刷题记录

R3-双指针&#xff08;快慢指针&#xff09; 新建两个链表 一个链表记录<x的值 一个链表记录>x的值 拼接即可 # Definition for singly-linked list. # class ListNode: # def __init__(self, val0, nextNone): # self.val val # self.next ne…

C语言 | Leetcode C语言题解之第322题零钱兑换

题目&#xff1a; 题解&#xff1a; int coinChange(int* coins, int coinsSize, int amount) {int dp[coinsSize1][amount1];for(int i0;i<coinsSize;i){for(int j0;j<amount;j){dp[i][j]INT_MAX-1;//初始化值为INT_MAX-1&#xff0c;避免后续加一导致溢出}}for(int i0;…

Python | Leetcode Python题解之第321题拼接最大数

题目&#xff1a; 题解&#xff1a; class Solution:def maxNumber(self, nums1: List[int], nums2: List[int], k: int) -> List[int]:m, n len(nums1), len(nums2)maxSubsequence [0] * kstart, end max(0, k - n), min(k, m)for i in range(start, end 1):subsequen…

语言无界,沟通无限:2024年好用在线翻译工具推荐

随着技术的发展现在的翻译在线工具从基础词句翻译到复杂的文章翻译都不在话下。为了防止你被五花八门的工具挑花眼&#xff0c;我给你介绍几款我用过的便捷、高效、准确的翻译工具吧。 1.福晰翻译端 链接直通&#xff1a;https://www.foxitsoftware.cn/fanyi/ 这个软件支持…

Google上架:8月份政策改动,未在8月31日前回应做出改动的包体将会有下架的风险

谷歌一直以用户为中心的服务思想,政策一直在变动,未及时变动的包体又下架甚至封号的风险,如有以下情况,请及时检查包体或账号相关问题,希望能给各位开发者带来帮助。 截止提交时间 2024-08-31 支付相关要求变动公布日期改动须知更改要求垃圾应用包体与用户体验公布日期改动…

LinuxC++(9):进程

linux信号 linux信号单指给进程发送的信息。比如killall 就是杀死进程&#xff0c;其实这个描述并不准确&#xff0c;应该是给程序发送一个信号&#xff0c;让程序自我了断&#xff0c;并不是我们亲自动手。 为什么直接杀死进程不好&#xff1f; 因为直接杀死进程&#xff0…

JAVA毕业设计|ssm基于ssm的宠物医院管理系统的设计与实现vue包含文档代码讲解

收藏点赞不迷路 关注作者有好处 文末获取源码 一、系统展示 二、万字文档展示 基于ssm基于ssm的宠物医院管理系统的设计与实现vue 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringSpringMVCMyBatisVue 工具&#xff1a;IDEA/Ecilpse、Navicat、Ma…

SQL注入实例(sqli-labs/less-5)

0、初始页面 1、确定闭合字符 ?id1 and 11 ?id1 and 12 ?id1 ?id1 -- 在进行前两句传参时&#xff0c;页面没有发生任何变化&#xff0c;但是当使用单引号闭合时&#xff0c;报错了。通过报错可以确定闭合符号为单引号。 2、爆库名 ?id1 and updatexml(1,concat(0x7e,(…

腾讯HunyuanDit代码解析

注意&#xff1a;本文仅供自己记录学习过程使用。 训练 全参训练过程 输入图像用VAE编码得到输入的x_start(1,4,128,128)&#xff1b;文本的两个特征&#xff1a;bert的encoder feature(1,77,1024)和T5 的feature(1,256,2048)&#xff0c;和旋转位置编码freqs_cis_img: cos …

4.8.双向循环神经网络

双向循环神经网络 ​ 在序列模型中&#xff0c;我们总是关注之前的信息&#xff0c;并以此来对下一个输出进行预测&#xff0c;但可能未来的信息也很重要&#xff0c;比如文本序列填空&#xff1a; 我___。我___饿了。我___饿了&#xff0c;我可以吃半头猪。 ​ 我们可以分别…

数据安全复合治理与实践

数据安全复合治理与实践 关键要点理论与实践 本文探讨了数据安全复合治理模式的理论与实践&#xff0c;着重强调了在数字经济迅猛发展的背景下&#xff0c;数据安全的重要性以及面对数据安全挑战时所需采取的综合治理策略。首先&#xff0c;文章概述了数据安全治理的必要性&…