|
本帖最后由 正点原子 于 2021-8-11 12:26 编辑
1)实验平台:正点原子i.MX6ULL Linux阿尔法开发板
2) 章节摘自【正点原子】《I.MX6U嵌入式Qt开发指南》
3)购买链接:https://item.taobao.com/item.htm?&id=603672744434
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/arm-linux/zdyz-i.mx6ull.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子Linux技术交流群:1027879335
第十九章 Camera
此小节例程适用于Ubuntu和正点原子I.MX6U开发板,不适用于Windows(需要自行修改才能适用Windows,Windows上的应用不在我们讨论范围)!
19.1 资源简介
正点原子I.MX6U开发板底板上有一路“CSI”摄像头接口。支持正点原子的OV5640、OV2640和OV7725(不带FIFO)。同时有USB接口,可以接USB免驱摄像头。例程兼容USB摄像头与正点原子的OV5640、OV2640和OV7725摄像头。
出厂系统请更新到正点原子I.MX6U最新的出厂系统,在驱动层正点原子有对OV5640、OV2640和OV7725摄像头有维护、优化或者添加支持。
19.2 环境搭建
Qt里也有一个QCamera类。没错,确实可以使用这个QCamera类来开发摄像头。但是这个类在正点原子的I.MX6U开发板上此内核版本上不能使用OV5640、OV2640和OV7725,可以使用USB免驱摄像头。因为OV5640、OV2640和OV7725的驱动默认是YUYV数据,QCamera里读取的数据是RGB格式的数据,可能数据对不上就无法使用了!但是也不建议修改驱动的方法来使用QCamera,防止QCamera在某些方法上与驱动层次不对应,导致使用报错。
实际上我们使用V4l2编程就可以对摄像头进行编程,效果会比在Qt显示流畅,因为V4l2的数据直接可以显示在fb0(也就是屏上)。而经过Qt还需要在控件上处理,所以效率会慢一些!如果我们单单只顾着编程,而不考虑CPU性能的问题,最终结果出人所料!在正点原子I.MX6U开发板上或者说是嵌入式上,这种比较低性能的CPU对于处理图像等大数据还是比较吃力的。所以我们需要去优化,不同的编程方式,对数据的处理不同,写出来的效果也会不同!
下面主要介绍Qt + OpenCV调用摄像头,效果肯定不能与V4l2直接显示在fb0上比。在这个低端的CPU上显示效果还是比较好的,还能接受,流畅度一般。
要想在Ubuntu上使用OpenCV,那么我们的Ubuntu上必须有OpenCV,如果您不想在Ubuntu安装OpenCV,就可以往下面继续走,直接用出厂系统提供的交叉编译工具链,里面已经提供有OpenCV。在Ubuntu上安装OpenCV只是方便我们测试界面,编写的程序也可以在Ubuntu上运行。安装的步骤也比较简单。
正点原子I.MX6U出厂系统的OpenCV版本为3.1.0。也不一定非要与正点原子I.MX6U出厂系统里的OpenCV相同版本,我们只是在Ubuntu上运行OpenCV。其中用到的API在3.1.0版本与3.4.1版本没有什么区别。3.1.0版本的OpenCV与3.4.1版本的OpenCV绝大多数核心API都相同,不必要担心找不到相同的API。
这里编者选择安装到OpenCV版本为3.4.1版本,因为3.1.0版本在配置cmake时下载的第三方库因为网络的原因难下载。导致cmake配置不过去。(PS:如果不是因为编译不过去,编者会选择与开发板相同的版本的OpenCV的)。
进入OpenCV的官网https://opencv.org/releases。下载3.4.1版本的OpenCV,如下图。我们选择Sources(源码)进行下载。
右键选择复制下载链接,用迅雷下载会比较快。下载完成后我们拷贝下载的文件到Ubuntu上进行解压。或者直接在正点原子的I.MX6U的光盘资料路径下找到开发板光盘A-基础资料/1、例程源码/7、第三方库源码/opencv-3.4.1.tar.gz。然后拷贝到Ubuntu下。
如下图我们已经下载好文件,并拷贝下载好的文件到Ubuntu的家目录下。
执行下面的指令进行解压。解压将会得到一个opencv-3.4.1文件夹,我们使用cd指令进入此文件夹。
<div>tar xf opencv-3.4.1.tar.gz</div><div>cd opencv-3.4.1</div>
安装cmake,用于生成编译所需要的文件。
- sudo apt-get install cmake
复制代码
新建一个build目录,并进入,用于编译生成的文件。
执行cmake配置编译。注意下面的指令“..”不要漏了!这里表示上一层目录。cmake会从上一层目录下找配置项,并配置到当前目录。
在配置的过程中cmake会下载一些库,如 ippicv_2017u3_lnx_intel64_general_20170822.tgz,需要一段时间,请等待,如果不能下载成功请重复尝试。
cmake配置成功如下图。
执行make开始编译。输入下面的指令。
- make -j 16 // 以实际分配给虚拟机的核心数为准,最佳为分配给虚拟机核心数据的2倍。编者的虚拟机最大分配了16个核心,编者个人的电脑并不快,就是核心多,所以编译就快。编译完成耗时约5分钟。不要只输入make,否则将编译很久!需要加参数 -j n,n请根据个人虚拟机的实际情况。
复制代码
执行下面的指令安装,安装到系统目录,需要加sudo权限。
安装完成如下。可以看到库被安装到/usr/local/lib下,头文件被安装在/usr/local/include下。
我们只需要知道安装的库路径和头文件路径即可在Qt里调用Ubuntu安装的OpenCV。头文件作用来编写程序,库路径用来运行程序时调用。我们只要在Qt的pro项目文件里指定这两个路径即可。
19.3 应用实例
请根据【正点原子】I.MX6U 出厂系统Qt交叉编译环境搭建V1.x.pdf(x >= 6)的文档搭建好I.MX6U的交叉编译环境。交叉编译工具链里已经有OpenCV,所以我们只要在我们搭建的交叉环境下就可以调用OpenCV的相关API进行编写Qt项目了。
项目简介:Qt加OpenCV打开摄像头采集图像并拍照。
例05_opencv_camera,Qt Camera编程(难度:较难)。项目路径为Qt/3/05_opencv_camera。
用脚本打开Qt Creator,必须按出厂系统Qt交叉编译环境搭建V1.x.pdf(x >= 6)的文档搭建好交叉编译环境,用脚本启动,脚本有相应的环境变量,编译时会用到。
- /opt/Qt5.12.9/Tools/QtCreator/bin/qtcreator.sh &
复制代码
编写程序应使用我们搭建的ATK-I.MX6U套件编写,否则若选择了Desktop Qt 5.12.9 GCC 64bit,如果我们的Ubuntu没有安装OpenCV就会使用不了OpenCV。如果您在14.6.2小节已经安装过OpenCV,那么下面两个套件都可一起选。本次编者两个一起选,因为编者有USB摄像头可以在Ubutnu上使用OpenCV测试,编写的程序交叉编译后在I.MX6U开发板使用USB免驱摄像头或者正点原子OV5640/OV7725(不带FIFO款)/OV2640测试成功!
下面开始编写程序。首先我们需要在项目pro文件添加OpenCV库的支持及头文件路径。
05_opencv_camera.pro文件如下,添加以下内容,这里主要是判断交叉编译器的类型,然后链接到不同的头文件路径与库。
- 1 QT += core gui
- 2
- 3 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
- 4
- 5 CONFIG += c++11
- 6
- 7 # The following define makes your compiler emit warnings if you use
- 8 # any Qt feature that has been marked deprecated (the exact warnings
- 9 # depend on your compiler). Please consult the documentation of the
- 10 # deprecated API in order to know how to port your code away from it.
- 11 DEFINES += QT_DEPRECATED_WARNINGS
- 12
- 13 # You can also make your code fail to compile if it uses deprecated APIs.
- 14 # In order to do so, uncomment the following line.
- 15 # You can also select to disable deprecated APIs only up to a certain version of Qt.
- 16 #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
- 17
- 18 TARGET_ARCH = ${QT_ARCH}
- 19 contains(TARGET_ARCH, arm){
- 20 CONFIG += link_pkgconfig
- 21 PKGCONFIG += opencv
- 22 INCLUDEPATH += /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots/cortexa7hf-neon-poky-linux-gnueabi/usr/include
- 23 } else {
- 24 LIBS += -L/usr/local/lib \
- 25 -lopencv_core \
- 26 -lopencv_highgui \
- 27 -lopencv_imgproc \
- 28 -lopencv_videoio \
- 29 -lopencv_imgcodecs
- 30
- 31 #INCLUDEPATH可写可不写,系统会到找到此路径
- 32 INCLUDEPATH += /usr/local/include
- 33 }
- 34
- 35 SOURCES += \
- 36 camera.cpp \
- 37 main.cpp \
- 38 mainwindow.cpp
- 39
- 40 HEADERS += \
- 41 camera.h \
- 42 mainwindow.h
- 43
- 44 # Default rules for deployment.
- 45 qnx: target.path = /tmp/${TARGET}/bin
- 46 else: unix:!android: target.path = /opt/${TARGET}/bin
- 47 !isEmpty(target.path): INSTALLS += target</font>
复制代码
第18行,获取编译器的类型。
第19行,判断交叉编译器的类型是否为arm。
第22行,arm对应opencv的头文件路径,可以不写,编译不会报错,但是我们想查看对应的头文件,就不得不包括这个路径了,否则跳转不过去!
第24~32行,添加库的支持。-L后面指的是库文件路径,-l后面的是相关库参数(l是大字母“L”的小写字母“l”,不是一),如果不会写库的名称,可以参考Ubuntu的安装路径下的/usr/local/lib/pkgconfig/opencv.pc文件。
camera.h文件,此文件声明了一个Camera类,其内容如下,比较简单。
mainwindow.h头文件代码如下。
- /******************************************************************
- Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
- * @projectName 05_opencv_camera
- * @brief mainwindow.h
- * @author Deng Zhimao
- * @email <a href="mailto:1252699831@qq.com" style="">1252699831@qq.com</a>
- * @net <a href="www.openedv.com" target="_blank" style="">www.openedv.com</a>
- * @date 2021-03-17
- *******************************************************************/
- 1 #ifndef MAINWINDOW_H
- 2 #define MAINWINDOW_H
- 3
- 4 #include <QMainWindow>
- 5 #include <QVBoxLayout>
- 6 #include <QHBoxLayout>
- 7 #include <QComboBox>
- 8 #include <QPushButton>
- 9 #include <QVBoxLayout>
- 10 #include <QLabel>
- 11 #include <QScrollArea>
- 12 #include <QDebug>
- 13
- 14 class Camera;
- 15
- 16 class MainWindow : public QMainWindow
- 17 {
- 18 Q_OBJECT
- 19
- 20 public:
- 21 MainWindow(QWidget *parent = nullptr);
- 22 ~MainWindow();
- 23
- 24 private:
- 25 /* 主容器,Widget也可以当作一种容器 */
- 26 QWidget *mainWidget;
- 27
- 28 /* 滚动区域,方便开发高分辨率 */
- 29 QScrollArea *scrollArea;
- 30
- 31 /* 将采集到的图像使用Widget显示 */
- 32 QLabel *displayLabel;
- 33
- 34 /* 界面右侧区域布局 */
- 35 QHBoxLayout *hboxLayout;
- 36
- 37 /* 界面右侧区域布局 */
- 38 QVBoxLayout *vboxLayout;
- 39
- 40 /* 界面右侧区域容器 */
- 41 QWidget *rightWidget;
- 42
- 43 /* 界面右侧区域显示拍照的图片 */
- 44 QLabel *photoLabel;
- 45
- 46 /* 界面右侧区域摄像头设备下拉选择框 */
- 47 QComboBox *comboBox;
- 48
- 49 /* 两个按钮,一个为拍照按钮,另一个是开启摄像头按钮 */
- 50 QPushButton *pushButton[2];
- 51
- 52 /* 拍照保存的照片 */
- 53 QImage saveImage;
- 54
- 55 /* 摄像头设备 */
- 56 Camera *camera;
- 57
- 58 /* 布局初始化 */
- 59 void layoutInit();
- 60
- 61 /* 扫描是否存在摄像头 */
- 62 void scanCameraDevice();
- 63
- 64 private slots:
- 65 /* 显示图像 */
- 66 void showImage(const QImage&);
- 67
- 68 /* 设置按钮文本 */
- 69 void setButtonText(bool);
- 70
- 71 /* 保存照片到本地 */
- 72 void saveImageToLocal();
- 73 };
- 74 #endif // MAINWINDOW_H</font>
复制代码
mainwindow.cpp源文件代码如下。
- /******************************************************************
- Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
- * @projectName 05_opencv_camera
- * @brief mainwindow.cpp
- * @author Deng Zhimao
- * @email <a href="mailto:1252699831@qq.com" style="">1252699831@qq.com</a>
- * @net <a href="www.openedv.com" target="_blank" style="">www.openedv.com</a>
- * @date 2021-03-17
- *******************************************************************/
- 1 #include "mainwindow.h"
- 2 #include <QGuiApplication>
- 3 #include <QScreen>
- 4 #include <QFile>
- 5 #include <QPixmap>
- 6 #include <QBuffer>
- 7 #include "camera.h"
- 8
- 9 MainWindow::MainWindow(QWidget *parent)
- 10 : QMainWindow(parent)
- 11 {
- 12 /* 布局初始化 */
- 13 layoutInit();
- 14
- 15 /* 扫描摄像头 */
- 16 scanCameraDevice();
- 17 }
- 18
- 19 MainWindow::~MainWindow()
- 20 {
- 21 }
- 22
- 23 void MainWindow::layoutInit()
- 24 {
- 25 /* 获取屏幕的分辨率,Qt官方建议使用这
- 26 * 种方法获取屏幕分辨率,防上多屏设备导致对应不上
- 27 * 注意,这是获取整个桌面系统的分辨率
- 28 */
- 29 QList <QScreen *> list_screen = QGuiApplication::screens();
- 30
- 31 /* 如果是ARM平台,直接设置大小为屏幕的大小 */
- 32 #if __arm__
- 33 /* 重设大小 */
- 34 this->resize(list_screen.at(0)->geometry().width(),
- 35 list_screen.at(0)->geometry().height());
- 36 #else
- 37 /* 否则则设置主窗体大小为800x480 */
- 38 this->resize(800, 480);
- 39 #endif
- 40
- 41 /* 实例化与布局,常规操作 */
- 42 mainWidget = new QWidget();
- 43 photoLabel = new QLabel();
- 44 rightWidget = new QWidget();
- 45 comboBox = new QComboBox();
- 46 pushButton[0] = new QPushButton();
- 47 pushButton[1] = new QPushButton();
- 48 scrollArea = new QScrollArea();
- 49 displayLabel = new QLabel(scrollArea);
- 50 vboxLayout = new QVBoxLayout();
- 51 hboxLayout = new QHBoxLayout();
- 52
- 53 vboxLayout->addWidget(photoLabel);
- 54 vboxLayout->addWidget(comboBox);
- 55 vboxLayout->addWidget(pushButton[0]);
- 56 vboxLayout->addWidget(pushButton[1]);
- 57
- 58 rightWidget->setLayout(vboxLayout);
- 59
- 60 hboxLayout->addWidget(scrollArea);
- 61 hboxLayout->addWidget(rightWidget);
- 62 mainWidget->setLayout(hboxLayout);
- 63
- 64 this->setCentralWidget(mainWidget);
- 65
- 66 pushButton[0]->setMaximumHeight(40);
- 67 pushButton[0]->setMaximumWidth(200);
- 68
- 69 pushButton[1]->setMaximumHeight(40);
- 70 pushButton[1]->setMaximumWidth(200);
- 71
- 72 comboBox->setMaximumHeight(40);
- 73 comboBox->setMaximumWidth(200);
- 74 photoLabel->setMaximumSize(100, 75);
- 75 scrollArea->setMinimumWidth(this->width()
- 76 - comboBox->width());
- 77
- 78 /* 显示图像最大画面为xx */
- 79 displayLabel->setMinimumWidth(scrollArea->width() * 0.75);
- 80 displayLabel->setMinimumHeight(scrollArea->height() * 0.75);
- 81 scrollArea->setWidget(displayLabel);
- 82
- 83 /* 居中显示 */
- 84 scrollArea->setAlignment(Qt::AlignCenter);
- 85
- 86 /* 自动拉伸 */
- 87 photoLabel->setScaledContents(true);
- 88 displayLabel->setScaledContents(true);
- 89
- 90 /* 设置一些属性 */
- 91 pushButton[0]->setText("拍照");
- 92 pushButton[0]->setEnabled(false);
- 93 pushButton[1]->setText("开始");
- 94 pushButton[1]->setCheckable(true);
- 95
- 96 /* 摄像头 */
- 97 camera = new Camera(this);
- 98
- 99 /* 信号连接槽 */
- 100 connect(camera, SIGNAL(readyImage(QImage)),
- 101 this, SLOT(showImage(QImage)));
- 102 connect(pushButton[1], SIGNAL(clicked(bool)),
- 103 camera, SLOT(cameraProcess(bool)));
- 104 connect(pushButton[1], SIGNAL(clicked(bool)),
- 105 this, SLOT(setButtonText(bool)));
- 106 connect(pushButton[0], SIGNAL(clicked()),
- 107 this, SLOT(saveImageToLocal()));
- 108
- 109 }
- 110
- 111 void MainWindow::scanCameraDevice()
- 112 {
- 113 /* 如果是Windows系统,一般是摄像头0 */
- 114 #if win32
- 115 comboBox->addItem("windows摄像头0");
- 116 connect(comboBox,
- 117 SIGNAL(currentIndexChanged(int)),
- 118 camera, SLOT(selectCameraDevice(int)));
- 119 #else
- 120 /* QFile文件指向/dev/video0 */
- 121 QFile file("/dev/video0");
- 122
- 123 /* 如果文件存在 */
- 124 if (file.exists())
- 125 comboBox->addItem("video0");
- 126 else {
- 127 displayLabel->setText("无摄像头设备");
- 128 return;
- 129 }
- 130
- 131 file.setFileName("/dev/video1");
- 132
- 133 if (file.exists()) {
- 134 comboBox->addItem("video1");
- 135 /* 开发板ov5640等设备是1 */
- 136 comboBox->setCurrentIndex(1);
- 137 }
- 138
- 139 file.setFileName("/dev/video2");
- 140
- 141 if (file.exists())
- 142 /* 开发板USB摄像头设备是2 */
- 143 comboBox->addItem("video2");
- 144
- 145 #if !__arm__
- 146 /* ubuntu的USB摄像头一般是0 */
- 147 comboBox->setCurrentIndex(0);
- 148 #endif
- 149
- 150 connect(comboBox,
- 151 SIGNAL(currentIndexChanged(int)),
- 152 camera, SLOT(selectCameraDevice(int)));
- 153 #endif
- 154 }
- 155
- 156 void MainWindow::showImage(const QImage &image)
- 157 {
- 158 /* 显示图像 */
- 159 displayLabel->setPixmap(QPixmap::fromImage(image));
- 160 saveImage = image;
- 161
- 162 /* 判断图像是否为空,空则设置拍照按钮不可用 */
- 163 if (!saveImage.isNull())
- 164 pushButton[0]->setEnabled(true);
- 165 else
- 166 pushButton[0]->setEnabled(false);
- 167 }
- 168
- 169 void MainWindow::setButtonText(bool bl)
- 170 {
- 171 if (bl) {
- 172 /* 设置摄像头设备 */
- 173 camera->selectCameraDevice(comboBox->currentIndex());
- 174 pushButton[1]->setText("关闭");
- 175 } else {
- 176 /* 若关闭了摄像头则禁用拍照按钮 */
- 177 pushButton[0]->setEnabled(false);
- 178 pushButton[1]->setText("开始");
- 179 }
- 180 }
- 181
- 182 void MainWindow::saveImageToLocal()
- 183 {
- 184 /* 判断图像是否为空 */
- 185 if (!saveImage.isNull()) {
- 186 QString fileName =
- 187 QCoreApplication::applicationDirPath() + "/test.png";
- 188 qDebug()<<"正在保存"<<fileName<<"图片,请稍候..."<<endl;
- 189
- 190 /* save(arg1,arg2,arg3)重载函数,arg1代表路径文件名,
- 191 * arg2保存的类型,arg3代表保存的质量等级 */
- 192 saveImage.save(fileName, "PNG", 1);
- 193
- 194 /* 设置拍照的图像为显示在photoLabel上 */
- 195 photoLabel->setPixmap(QPixmap::fromImage(QImage(fileName)));
- 196
- 197 qDebug()<<"保存完成!"<<endl;
- 198 }
- 199 }
- 200</font>
复制代码
第111~154行,判断linux下的设备/dev/video*。细心的同学发现,这个程序是Linux下用的。当然Windows也是可以使用OpenCV的,需要自己修改pro文件链接到Windows的OpenCV库-L需要修改为-LD,Windows下的库文件是dll类型,此外不考虑macOS系统,具体情况编者没得实验。
19.4 程序运行效果
选择合适的摄像头设备,(注意如果在Ubuntu使用USB摄像头,需要设置USB的兼容性为3.0反之2.0,具体需要看不同摄像头设备,点击连接摄像头到虚拟机。可以先使用Ubuntu18.04自带的茄子拍照软件,检测摄像头是否可用)。点击拍照,可以看程序输出的Debug信息,保存照片的路径为当前可执行程序的路径,保存照片名称为test.png,右上角显示保存照片的缩略图,再次点击拍照则会替换已经保存过的照片。若想要保存多个照片可自行设计。
若在正点原子I.MX6U开发板上运行此程序,先插上摄像头,确保摄像头能用,注意不要选择video0,video0是NXP的pxp驱动产生的节点,不是摄像头,否则会报错。I.MX6U开发板是单核A7的CPU,性能有限,所以流畅度一般,还可以。但是在保存照片时会比PC电脑慢好多,不过也不能太勉强这个6ULL芯片了啦,能拍照已经不错了,或者大家可以对保存照片步骤进行优化,开启一个线程进行优化,剩下的交给大家了,大家可以的。
OpenCV在不设置摄像头分辨率时会采用默认分辨率640*480 30fps(绝大多数摄像头都是支持这个分辨率)。USB免驱摄像头可以使用下面的方法来设置分辨率。
- capture->open(1);
- capture ->set(CV_CAP_PROP_FRAME_WIDTH, 320);
- capture ->set(CV_CAP_PROP_FRAME_HEIGHT, 240);</font>
复制代码
但是正点原子6ULL开发板上的OV5640/OV2640/OV7725(不带FIFO款)摄像头就不可以直接使用些方法设置采集分辨率了,因为驱动里设置分辨率的方法与标准的V4L2设置分辨有些差异。也可以直接使用正点原子I.MX6U里的摄像头采集分辨率设置软件camera_settings直接设置,注意在处理图像中的matToQImage函数需要以确认的分辨率进行转换使用了。
总结,想要在Qt中使用OpenCV,那么我们的开发板文件系统里或者Ubuntu系统必须要有OpenCV的库。对于某些非通用的USB摄像头来说,因为驱动层限制不能直接使用Qt自带的QCamera类。
|
阿莫论坛20周年了!感谢大家的支持与爱护!!
该献的血还是要献的。你不献他不献。难道让我去献? --- 出自坛友:lovejp1981
|