1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html
4)对正点原子Linux感兴趣的同学可以加群讨论:935446741
此章节例程适用于Ubuntu和正点原子I.MX6U开发板,不适用于Windows(需要自行修改才能适用Windows,Windows上的应用不在我们讨论范围)!
正点原子I.MX6U开发板底板上有一路“CSI”摄像头接口。支持正点原子的OV5640、OV2640和OV7725(不带FIFO)。同时有USB接口,可以接USB免驱摄像头。例程兼容USB摄像头与正点原子的OV5640、OV2640和OV7725摄像头。
出厂系统请更新到正点原子I.MX6U最新的出厂系统,在驱动层正点原子对OV5640、OV2640和OV7725摄像头维护、优化或者添加支持。
Qt里也有一个QCamera类。没错,确实可以使用这个QCamera类来开发摄像头。但是这个类在正点原子的I.MX6U开发板4.1.15内核版本上不能使用OV5640、OV2640和OV7725,可以使用USB免驱摄像头。因为OV5640、OV2640和OV7725的驱动默认是读取YUYV格式数据,而QCamera里读取的数据是RGB格式数据,它们可能数据对不上就无法使用了!但是也不建议修改驱动的方法来使用QCamera,防止QCamera在某些方法上与驱动层不对应,导致使用报错。
实际上我们使用V4l2编程就可以对摄像头进行编程,效果会比在Qt显示流畅,因为V4l2的数据直接可以显示在fb0(也就是屏上)。而经过Qt还需要在控件上处理,所以效率会慢一些!在正点原子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指令进入此文件夹。
tar xf opencv-3.4.1.tar.gz
cd opencv-3.4.1
安装cmake,用于生成编译OpenCV所需要的文件。
sudo apt-get install cmake
新建一个build目录,并进入,用于编译生成的文件。
mkdir build
cd build
执行cmake配置编译。注意下面的指令“…”不要漏了!这里表示上一层目录。cmake会从上一层目录下找配置项,并配置到当前目录。
cmake ..
在配置的过程中cmake会下载一些库,如 ippicv_2017u3_lnx_intel64_general_20170822.tgz,需要一段时间,请等待,如果不能下载成功请重复尝试。
cmake配置成功如下图。
执行make开始编译。输入下面的指令。
make -j 16 // 以实际分配给虚拟机的核心数为准,最佳为分配给虚拟机核心数据的2倍。编者的虚拟机最大分配了16个核心,编者个人的电脑并不快,就是核心多,所以编译就快。编译完成耗时约5分钟。不要只输入make,否则将编译很久!需要加参数 -j n,n请根据个人虚拟机的实际情况。
执行下面的指令安装,安装到系统目录,需要加sudo权限。
sudo make install
安装完成如下。可以看到库被安装到/usr/local/lib下,头文件被安装在/usr/local/include下。
我们只需要知道安装的库路径和头文件路径即可在Qt里调用Ubuntu安装的OpenCV。头文件作用来编写程序,库路径用来运行程序时调用。我们只要在Qt的pro项目文件里指定这两个路径即可。
请根据【正点原子】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。如果您在19.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
第18行,获取编译器的类型。
第19行,判断交叉编译器的类型是否为arm。
第22行,arm对应opencv的头文件路径,可以不写,编译不会报错,但是我们想查看对应的头文件,就不得不包括这个路径了,否则跳转不过去!
第24~32行,添加库的支持。-L后面指的是库文件路径,-l后面的是相关库参数(l是大字母“L”的小写字母“l”,不是一),如果不会写库的名称,可以参考Ubuntu的OpenCV安装路径下的/usr/local/lib/pkgconfig/opencv.pc文件。
camera.h文件,此文件声明了一个Camera类,其内容如下,比较简单。
9 #ifndef CAMERA_H
10 #define CAMERA_H
11
12 #include <QImage>
13 #include <QTimer>
14 /* 使用命名空间cv下的VideoCapture与Mat类 */
15 namespace cv {
16 class VideoCapture;
17 class Mat;
18 }
19
20 class Camera : public QObject
21 {
22 Q_OBJECT
23 public:
24 explicit Camera(QObject *parent = nullptr);
25 ~Camera();
26
27 signals:
28 /* 声明信号,用于传递有图片信号时显示图像 */
29 void readyImage(const QImage&);
30
31 public slots:
32 /* 用于开启定时器 */
33 bool cameraProcess(bool);
34
35 /* 选择摄像头 */
36 void selectCameraDevice(int);
37
38 private slots:
39 /* 定时器时间到处理函数,发送图像数据信号 */
40 void timerTimeOut();
41
42 private:
43 /* 声明OpenCV的cv命名空间下的VideoCapture对象 */
44 cv::VideoCapture * capture;
45
46 /* 定时器 */
47 QTimer * timer;
48
49 /* 图像转换处理函数 */
50 QImage matToQImage(const cv::Mat&);
51 };
52
53 #endif // CAMERA_H
54
camera.cpp类的定义如下。
9 #include "camera.h"
10 #include "opencv2/core/core.hpp"
11 #include "opencv2/highgui/highgui.hpp"
12 #include <QImage>
13 #include <QDebug>
14
15 Camera::Camera(QObject *parent) :
16 QObject(parent)
17 {
18 /* 实例化 */
19 capture = new cv::VideoCapture();
20 timer = new QTimer(this);
21
22 /* 信号槽连接 */
23 connect(timer, SIGNAL(timeout()), this, SLOT(timerTimeOut()));
24 }
25
26 Camera::~Camera()
27 {
28 delete capture;
29 capture = NULL;
30 }
31
32 void Camera::selectCameraDevice(int index)
33 {
34 /* 如果有其他摄像头打开了,先释放 */
35 if (capture->isOpened()) {
36 capture->release();
37 }
38
39 /* 打开摄像头设备 */
40 capture->open(index);
41 }
42
43 bool Camera::cameraProcess(bool bl)
44 {
45 if (bl) {
46 /* 为什么是33?1000/33约等于30帧,也就是一秒最多显示30帧 */
47 timer->start(33);
48 } else {
49 timer->stop();
50 }
51 /* 返回摄像头的状态 */
52 return capture->isOpened();
53 }
54
55 void Camera::timerTimeOut()
56 {
57 /* 如果摄像头没有打开,停止定时器,返回 */
58 if (!capture->isOpened()) {
59 timer->stop();
60 return;
61 }
62
63 static cv::Mat frame;
64 *capture >> frame;
65 if (frame.cols)
66 /* 发送图片信号 */
67 emit readyImage(matToQImage(frame));
68 }
69
70 QImage Camera::matToQImage(const cv::Mat &img)
71 {
72 /* USB摄像头和OV5640等都是RGB三通道,不考虑单/四通道摄像头 */
73 if(img.type() == CV_8UC3) {
74 /* 得到图像的的首地址 */
75 const uchar *pimg = (const uchar*)img.data;
76
77 /* 以img构造图片 */
78 QImage qImage(pimg, img.cols, img.rows, img.step,
79 QImage::Format_RGB888);
80
81 /* 在不改变实际图像数据的条件下,交换红蓝通道 */
82 return qImage.rgbSwapped();
83 }
84
85 /* 返回QImage */
86 return QImage();
87 }
mainwindow.h头文件代码如下。
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName 05_opencv_camera
* @brief mainwindow.h
* @author Deng Zhimao
* @email [email protected]
* @net www.openedv.com
* @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
mainwindow.cpp源文件代码如下。
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName 05_opencv_camera
* @brief mainwindow.cpp
* @author Deng Zhimao
* @email [email protected]
* @net www.openedv.com
* @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
第111~154行,判断linux下的设备/dev/video*。细心的同学发现,这个程序是Linux下用的。当然Windows也是可以使用OpenCV的,需要自己修改pro文件链接到Windows的OpenCV库-L需要修改为-LD,Windows下的库文件是dll类型,此外不考虑macOS系统,具体情况编者没得实验。
选择合适的摄像头设备,(注意如果在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);
但是正点原子6ULL开发板上的OV5640/OV2640/OV7725(不带FIFO款)摄像头就不可以直接使用些方法设置采集分辨率了,因为驱动里设置分辨率的方法与标准的V4L2设置分辨有些差异。也可以直接使用正点原子I.MX6U里的摄像头采集分辨率设置软件camera_settings直接设置,注意在处理图像中的matToQImage函数需要以确认的分辨率进行转换使用了。
总结,想要在Qt中使用OpenCV,那么我们的开发板文件系统里或者Ubuntu系统必须要有OpenCV的库。对于某些非通用的USB摄像头来说,因为驱动层限制不能直接使用Qt自带的QCamera类。