0.配置编译环境
- 复制【正点原子】STM32MP157开发板(A盘)-基础资料\05、开发工具\01、交叉编译器st-example-image-qtwayland-openstlinux-weston-stm32mp1-x86_64-toolchain-3.1-snapshot.sh到虚拟机
- chmod添加可执行文件,./st*运行,选择安装目录在/opt/st/stm32mp1/qt_crossCompile/中
编译
- source /opt/st/stm32mp1/qt_crossCompile/environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi设置环境变量
- 进入pro文件所在目录命令:/opt/st/stm32mp1/qt_crossCompile/sysroots/x86_64-ostl_sdk-linux/usr/bin/qmake生成makefile,这里面的编译路径才是stm32mp1对应的
- make -j 8开始编译生成可执行文件复制到开发板运行
15.led控制
项目简介:设置一个按钮,点击即可控制 LED 状态反转(点亮或者熄灭 LED)。项目看来很起来很简单,实际上有些需要注意的地方,我们在改变 LED 的状态时,需要先去读取 LED的状态,防止外界(外面应用程序)将 LED 的状态改变了。否则我们反转操作将不成立。在
C++里一般使用 get()和 set()方法来获取和设置。我们的 LED 程序里也有这种方法。所以需要写好一个让人看得懂的程序是有“方法”的。不能将程序功能写在一堆,最好是分开写,留有接口。让后面的人看懂!
例 01_led,控制 LED
- windows.h
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName 01_led
* @brief mainwindow.h
* @author Deng Zhimao
* @email [email protected]
* @net www.openedv.com
* @date 2021-03-08
*******************************************************************/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QPushButton>
#include <QFile>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* 按钮 */
QPushButton *pushButton;
/* 文件 */
QFile file;
/* 设置lED的状态 */
void setLedState();
/* 获取lED的状态 */
bool getLedState();
private slots:
void pushButtonClicked();
};
#endif // MAINWINDOW_H
- windows.cpp
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName 01_led
* @brief mainwindow.cpp
* @author Deng Zhimao
* @email [email protected]
* @net www.openedv.com
* @date 2021-03-08
*******************************************************************/
#include "mainwindow.h"
#include <QDebug>
#include <QGuiApplication>
#include <QScreen>
#include <QRect>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 获取屏幕的分辨率,Qt官方建议使用这
* 种方法获取屏幕分辨率,防上多屏设备导致对应不上
* 注意,这是获取整个桌面系统的分辨率
*/
QList <QScreen *> list_screen = QGuiApplication::screens();
/* 如果是ARM平台,直接设置大小为屏幕的大小 */
#if __arm__
/* 重设大小 */
this->resize(list_screen.at(0)->geometry().width(),
list_screen.at(0)->geometry().height());
/* 默认是出厂系统的LED心跳的触发方式,想要控制LED,
* 需要改变LED的触发方式,改为none,即无 */
system("echo none > /sys/class/leds/sys-led/trigger");
#else
/* 否则则设置主窗体大小为800x480 */
this->resize(800, 480);
#endif
pushButton = new QPushButton(this);
/* 居中显示 */
pushButton->setMinimumSize(200, 50);
pushButton->setGeometry((this->width() - pushButton->width()) /2 ,
(this->height() - pushButton->height()) /2,
pushButton->width(),
pushButton->height()
);
/* 开发板的LED控制接口 */
file.setFileName("/sys/devices/platform/leds/leds/sys-led/brightness");
if (!file.exists())
/* 设置按钮的初始化文本 */
pushButton->setText("未获取到LED设备!");
/* 获取LED的状态 */
getLedState();
/* 信号槽连接 */
connect(pushButton, SIGNAL(clicked()),
this, SLOT(pushButtonClicked()));
}
MainWindow::~MainWindow()
{
}
void MainWindow::setLedState()
{
/* 在设置LED状态时先读取 */
bool state = getLedState();
/* 如果文件不存在,则返回 */
if (!file.exists())
return;
if(!file.open(QIODevice::ReadWrite))
qDebug()<<file.errorString();
QByteArray buf[2] = {
"0", "1"};
/* 写0或1 */
if (state)
file.write(buf[0]);
else
file.write(buf[1]);
/* 关闭文件 */
file.close();
/*重新获取LED的状态 */
getLedState();
}
bool MainWindow::getLedState()
{
/* 如果文件不存在,则返回 */
if (!file.exists())
return false;
if(!file.open(QIODevice::ReadWrite))
qDebug()<<file.errorString();
QTextStream in(&file);
/* 读取文件所有数据 */
QString buf = in.readLine();
/* 打印出读出的值 */
qDebug()<<"buf: "<<buf<<endl;
file.close();
if (buf == "1") {
pushButton->setText("LED点亮");
return true;
} else {
pushButton->setText("LED熄灭");
return false;
}
}
void MainWindow::pushButtonClicked()
{
/* 设置LED的状态 */
setLedState();
}
- 运行,下面为 Ubuntu 上仿真界面的效果,由于 Ubuntu 不是“开发板”,所以在读取 LED 设备时会读取失败。实际在板上运行图略。交叉编译程序到正点原子 STM32MP157 开发板上运行即可控制 LED 的状态。
16.控制beep
想要控制这个蜂鸣器(BEEP),首先正点原子的出厂内核已经默认将这个 LED 注册成了 gpio-leds 类型设备。所以实例与上一小节 LED 实例是一样的。项目简介:设置一个按钮,点击即可控制 BEEP 状态反转(打开蜂鸣器或者关闭蜂鸣器)。
例 02_beep,控制 BEEP
- windows.h
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName 02_beep
* @brief mainwindow.h
* @author Deng Zhimao
* @email [email protected]
* @net www.openedv.com
* @date 2021-03-11
*******************************************************************/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QPushButton>
#include <QFile>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* 按钮 */
QPushButton *pushButton;
/* 文件 */
QFile file;
/* 设置BEEP的状态 */
void setBeepState();
/* 获取BEEP的状态 */
bool getBeepState();
private slots:
/* 槽函数 */
void pushButtonClicked();
};
#endif // MAINWINDOW_H
- windows.cpp
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName 02_beep
* @brief mainwindow.cpp
* @author Deng Zhimao
* @email [email protected]
* @net www.openedv.com
* @date 2021-03-11
*******************************************************************/
#include "mainwindow.h"
#include <QDebug>
#include <QGuiApplication>
#include <QScreen>
#include <QRect>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 获取屏幕的分辨率,Qt官方建议使用这
* 种方法获取屏幕分辨率,防上多屏设备导致对应不上
* 注意,这是获取整个桌面系统的分辨率
*/
QList <QScreen *> list_screen = QGuiApplication::screens();
/* 如果是ARM平台,直接设置大小为屏幕的大小 */
#if __arm__
/* 重设大小 */
this->resize(list_screen.at(0)->geometry().width(),
list_screen.at(0)->geometry().height());
#else
/* 否则则设置主窗体大小为800x480 */
this->resize(800, 480);
#endif
pushButton = new QPushButton(this);
/* 居中显示 */
pushButton->setMinimumSize(200, 50);
pushButton->setGeometry((this->width() - pushButton->width()) /2 ,
(this->height() - pushButton->height()) /2,
pushButton->width(),
pushButton->height()
);
/* 开发板的蜂鸣器控制接口 */
file.setFileName("/sys/devices/platform/leds/leds/beep/brightness");
if (!file.exists())
/* 设置按钮的初始化文本 */
pushButton->setText("未获取到BEEP设备!");
/* 获取BEEP的状态 */
getBeepState();
/* 信号槽连接 */
connect(pushButton, SIGNAL(clicked()),
this, SLOT(pushButtonClicked()));
}
MainWindow::~MainWindow()
{
}
void MainWindow::setBeepState()
{
/* 在设置BEEP状态时先读取 */
bool state = getBeepState();
/* 如果文件不存在,则返回 */
if (!file.exists())
return;
if(!file.open(QIODevice::ReadWrite))
qDebug()<<file.errorString();
QByteArray buf[2] = {
"0", "1"};
if (state)
file.write(buf[0]);
else
file.write(buf[1]);
file.close();
getBeepState();
}
bool MainWindow::getBeepState()
{
/* 如果文件不存在,则返回 */
if (!file.exists())
return false;
if(!file.open(QIODevice::ReadWrite))
qDebug()<<file.errorString();
QTextStream in(&file);
/* 读取文件所有数据 */
QString buf = in.readLine();
/* 打印出读出的值 */
qDebug()<<"buf: "<<buf<<endl;
file.close();
if (buf == "1") {
pushButton->setText("BEEP开");
return true;
} else {
pushButton->setText("BEEP关");
return false;
}
}
void MainWindow::pushButtonClicked()
{
/* 设置蜂鸣器的状态 */
setBeepState();
}
- 运行,下面为 Ubuntu 上仿真界面的效果,由于 Ubuntu 不是“开发板”,所以在读取 BEEP 设备时会读取失败。实际在板上运行图略。交叉编译程序到正点原子 STM32MP157 开发板上运行即可控制蜂鸣器的状态。
17.串口series port
在正点原子的 STM32MP157 开发板的出厂系统里,默认已经配置了三路串口可用。一路是调试串口 UART4(对应系统里的节点/dev/ttySTM0),一路是 UART3(对应系统里的节点/dev/ttySTM1),另一路是 UART5(对应系统里的节点/dev/ ttySTM2),由于 UART4 已经作为调试串口被使用。所以我们只能对 UART5/UART3 编程
例 03_serialport,Qt 串口编程(难度:一般)。在 03_serialport.pro 里,我们需要使用串口,需要在 pro 项目文件中添加串口模块的支持QT += core gui serialport
- windows.h
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName 03_serialport
* @brief mainwindow.h
* @author Deng Zhimao
* @email [email protected]
* @net www.openedv.com
* @date 2021-03-12
*******************************************************************/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QPushButton>
#include <QTextBrowser>
#include <QTextEdit>
#include <QVBoxLayout>
#include <QLabel>
#include <QComboBox>
#include <QGridLayout>
#include <QMessageBox>
#include <QDebug>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* 串口对象 */
QSerialPort *serialPort;
/* 用作接收数据 */
QTextBrowser *textBrowser;
/* 用作发送数据 */
QTextEdit *textEdit;
/* 按钮 */
QPushButton *pushButton[2];
/* 下拉选择盒子 */
QComboBox *comboBox[5];
/* 标签 */
QLabel *label[5];
/* 垂直布局 */
QVBoxLayout *vboxLayout;
/* 网络布局 */
QGridLayout *gridLayout;
/* 主布局 */
QWidget *mainWidget;
/* 设置功能区域 */
QWidget *funcWidget;
/* 布局初始化 */
void layoutInit();
/* 扫描系统可用串口 */
void scanSerialPort();
/* 波特率项初始化 */
void baudRateItemInit();
/* 数据位项初始化 */
void dataBitsItemInit();
/* 检验位项初始化 */
void parityItemInit();
/* 停止位项初始化 */
void stopBitsItemInit();
private slots:
void sendPushButtonClicked();
void openSerialPortPushButtonClicked();
void serialPortReadyRead();
};
#endif // MAINWINDOW_H
- windows.cpp
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName 03_serialport
* @brief mainwindow.cpp
* @author Deng Zhimao
* @email [email protected]
* @net www.openedv.com
* @date 2021-03-12
*******************************************************************/
#include "mainwindow.h"
#include <QDebug>
#include <QGuiApplication>
#include <QScreen>
#include <QRect>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 布局初始化 */
layoutInit();
/* 扫描系统的串口 */
scanSerialPort();
/* 波特率项初始化 */
baudRateItemInit();
/* 数据位项初始化 */
dataBitsItemInit();
/* 检验位项初始化 */
parityItemInit();
/* 停止位项初始化 */
stopBitsItemInit();
}
void MainWindow::layoutInit()
{
/* 获取屏幕的分辨率,Qt官方建议使用这
* 种方法获取屏幕分辨率,防上多屏设备导致对应不上
* 注意,这是获取整个桌面系统的分辨率
*/
QList <QScreen *> list_screen = QGuiApplication::screens();
/* 如果是ARM平台,直接设置大小为屏幕的大小 */
#if __arm__
/* 重设大小 */
this->resize(list_screen.at(0)->geometry().width(),
list_screen.at(0)->geometry().height());
#else
/* 否则则设置主窗体大小为800x480 */
this->resize(800, 480);
#endif
/* 初始化 */
serialPort = new QSerialPort(this);
textBrowser = new QTextBrowser();
textEdit = new QTextEdit();
vboxLayout = new QVBoxLayout();
funcWidget = new QWidget();
mainWidget = new QWidget();
gridLayout = new QGridLayout();
/* QList链表,字符串类型 */
QList <QString> list1;
list1<<"串口号:"<<"波特率:"<<"数据位:"<<"检验位:"<<"停止位:";
for (int i = 0; i < 5; i++) {
label[i] = new QLabel(list1[i]);
/* 设置最小宽度与高度 */
label[i]->setMinimumSize(80, 30);
/* 自动调整label的大小 */
label[i]->setSizePolicy(
QSizePolicy::Expanding,
QSizePolicy::Expanding
);
/* 将label[i]添加至网格的坐标(0, i) */
gridLayout->addWidget(label[i], 0, i);
}
for (int i = 0; i < 5; i++) {
comboBox[i] = new QComboBox();
comboBox[i]->setMinimumSize(80, 30);
/* 自动调整label的大小 */
comboBox[i]->setSizePolicy(
QSizePolicy::Expanding,
QSizePolicy::Expanding
);
/* 将comboBox[i]添加至网格的坐标(1, i) */
gridLayout->addWidget(comboBox[i], 1, i);
}
/* QList链表,字符串类型 */
QList <QString> list2;
list2<<"发送"<<"打开串口";
for (int i = 0; i < 2; i++) {
pushButton[i] = new QPushButton(list2[i]);
pushButton[i]->setMinimumSize(80, 30);
/* 自动调整label的大小 */
pushButton[i]->setSizePolicy(
QSizePolicy::Expanding,
QSizePolicy::Expanding
);
/* 将pushButton[0]添加至网格的坐标(i, 5) */
gridLayout->addWidget(pushButton[i], i, 5);
}
pushButton[0]->setEnabled(false);
/* 布局 */
vboxLayout->addWidget(textBrowser);
vboxLayout->addWidget(textEdit);
funcWidget->setLayout(gridLayout);
vboxLayout->addWidget(funcWidget);
mainWidget->setLayout(vboxLayout);
this->setCentralWidget(mainWidget);
/* 占位文本 */
textBrowser->setPlaceholderText("接收到的消息");
textEdit->setText("www.openedv.com");
/* 信号槽连接 */
connect(pushButton[0], SIGNAL(clicked()),
this, SLOT(sendPushButtonClicked()));
connect(pushButton[1], SIGNAL(clicked()),
this, SLOT(openSerialPortPushButtonClicked()));
connect(serialPort, SIGNAL(readyRead()),
this, SLOT(serialPortReadyRead()));
}
void MainWindow::scanSerialPort()
{
/* 查找可用串口 */
foreach (const QSerialPortInfo &info,
QSerialPortInfo::availablePorts()) {
comboBox[0]->addItem(info.portName());
}
}
void MainWindow::baudRateItemInit()
{
/* QList链表,字符串类型 */
QList <QString> list;
list<<"1200"<<"2400"<<"4800"<<"9600"
<<"19200"<<"38400"<<"57600"
<<"115200"<<"230400"<<"460800"
<<"921600";
for (int i = 0; i < 11; i++) {
comboBox[1]->addItem(list[i]);
}
comboBox[1]->setCurrentIndex(7);
}
void MainWindow::dataBitsItemInit()
{
/* QList链表,字符串类型 */
QList <QString> list;
list<<"5"<<"6"<<"7"<<"8";
for (int i = 0; i < 4; i++) {
comboBox[2]->addItem(list[i]);
}
comboBox[2]->setCurrentIndex(3);
}
void MainWindow::parityItemInit()
{
/* QList链表,字符串类型 */
QList <QString> list;
list<<"None"<<"Even"<<"Odd"<<"Space"<<"Mark";
for (int i = 0; i < 5; i++) {
comboBox[3]->addItem(list[i]);
}
comboBox[3]->setCurrentIndex(0);
}
void MainWindow::stopBitsItemInit()
{
/* QList链表,字符串类型 */
QList <QString> list;
list<<"1"<<"2";
for (int i = 0; i < 2; i++) {
comboBox[4]->addItem(list[i]);
}
comboBox[4]->setCurrentIndex(0);
}
void MainWindow::sendPushButtonClicked()
{
/* 获取textEdit数据,转换成utf8格式的字节流 */
QByteArray data = textEdit->toPlainText().toUtf8();
serialPort->write(data);
}
void MainWindow::openSerialPortPushButtonClicked()
{
if (pushButton[1]->text() == "打开串口") {
/* 设置串口名 */
serialPort->setPortName(comboBox[0]->currentText());
/* 设置波特率 */
serialPort->setBaudRate(comboBox[1]->currentText().toInt());
/* 设置数据位数 */
switch (comboBox[2]->currentText().toInt()) {
case 5:
serialPort->setDataBits(QSerialPort::Data5);
break;
case 6:
serialPort->setDataBits(QSerialPort::Data6);
break;
case 7:
serialPort->setDataBits(QSerialPort::Data7);
break;
case 8:
serialPort->setDataBits(QSerialPort::Data8);
break;
default: break;
}
/* 设置奇偶校验 */
switch (comboBox[3]->currentIndex()) {
case 0:
serialPort->setParity(QSerialPort::NoParity);
break;
case 1:
serialPort->setParity(QSerialPort::EvenParity);
break;
case 2:
serialPort->setParity(QSerialPort::OddParity);
break;
case 3:
serialPort->setParity(QSerialPort::SpaceParity);
break;
case 4:
serialPort->setParity(QSerialPort::MarkParity);
break;
default: break;
}
/* 设置停止位 */
switch (comboBox[4]->currentText().toInt()) {
case 1:
serialPort->setStopBits(QSerialPort::OneStop);
break;
case 2:
serialPort->setStopBits(QSerialPort::TwoStop);
break;
default: break;
}
/* 设置流控制 */
serialPort->setFlowControl(QSerialPort::NoFlowControl);
if (!serialPort->open(QIODevice::ReadWrite))
QMessageBox::about(NULL, "错误",
"串口无法打开!可能串口已经被占用!");
else {
for (int i = 0; i < 5; i++)
comboBox[i]->setEnabled(false);
pushButton[1]->setText("关闭串口");
pushButton[0]->setEnabled(true);
}
} else {
serialPort->close();
for (int i = 0; i < 5; i++)
comboBox[i]->setEnabled(true);
pushButton[1]->setText("打开串口");
pushButton[0]->setEnabled(false);
}
}
void MainWindow::serialPortReadyRead()
{
/* 接收缓冲区中读取数据 */
QByteArray buf = serialPort->readAll();
textBrowser->insertPlainText(QString(buf));
}
MainWindow::~MainWindow()
{
}
- 运行,下面为 Ubuntu 上仿真界面的效果,请将程序交叉编译后到 STM32MP157 开发板运行,用串口线连接开发板的 UART3/UART5 到电脑串口,在电脑用正点原子的 XCOM 上位机软件(或者本程序亦可当上位机软件),设置相同的串口参数,在 LCD 屏幕上选择串口号为ttySTM1/ttySTM2(注意 ttySTM0 已经作为调试串口被使用了!),点击打开串口就可以进行消息收发了。默认参数为波特率为 115200,数据位为 8,校验为 None,停止位为 1,流控为关闭。
k可使用usb转rs232线连接测试
19.Camera
- 下面主要介绍 Qt + OpenCV 调用摄像头,要想在 Ubuntu 上使用 OpenCV,那么我们的 Ubuntu 上必须有 OpenCV 的库,直接用出厂系统提供的交叉编译工具链,里面已经提供有 OpenCV。在 Ubuntu 上安装 OpenCV 只是方便我们测试界面,编写的程序也可以在Ubuntu 上运行。
- 首先我们需要在项目 pro 文件添加 OpenCV 库的支持及头文件路径。05_opencv_camera.pro 文件如下,添加以下内容,这里主要是判断交叉编译器的类型,然后链接到不同的头文件路径与库。
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
TARGET_ARCH = $${
QT_ARCH}
contains(TARGET_ARCH, arm){
CONFIG += link_pkgconfig
PKGCONFIG += opencv4
INCLUDEPATH += /opt/st/stm32mp1/3.1-snapshot/sysroots/cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi/usr/include
} else {
#库文件路径
LIBS += -L/usr/local/lib \
-lopencv_core \
-lopencv_highgui \
-lopencv_imgproc \
-lopencv_videoio \
-lopencv_imgcodecs
#头文件路径
INCLUDEPATH += /usr/local/lib/include
}
SOURCES += \
camera.cpp \
main.cpp \
mainwindow.cpp
HEADERS += \
camera.h \
mainwindow.h
# Default rules for deployment.
qnx: target.path = /tmp/$${
TARGET}/bin
else: unix:!android: target.path = /opt/$${
TARGET}/bin
!isEmpty(target.path): INSTALLS += target
第 18 行,获取编译器的类型。
第 19 行,判断交叉编译器的类型是否为 arm。
第 22 行,arm 对应 opencv 的头文件路径,可以不写,编译不会报错,但是我们想查看对应的头文件,就不得不包括这个路径了,否则跳转不过去!
第 24~33 行,添加库的支持。-L 后面指的是库文件路径,-l 后面的是相关库参数(l 是大字母“L”的小写字母“l”,不是一),如果不会写库的名称,可以直接写成 LIBS += /usr/local/lib/libopencv_* ,全部链接即可。
19.2 camera类
- camera.h
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName 05_opencv_camera
* @brief camera.h
* @author Deng Zhimao
* @email [email protected]
* @net www.openedv.com
* @date 2021-03-17
*******************************************************************/
#ifndef CAMERA_H
#define CAMERA_H
#include <QImage>
#include <QTimer>
/* 使用命名空间cv下的VideoCapture与Mat类 */
namespace cv {
class VideoCapture;
class Mat;
}
class Camera : public QObject
{
Q_OBJECT
public:
explicit Camera(QObject *parent = nullptr);
~Camera();
signals:
/* 声明信号,用于传递有图片信号时显示图像 */
void readyImage(const QImage&);
public slots:
/* 用于开启定时器 */
bool cameraProcess(bool);
/* 选择摄像头 */
void selectCameraDevice(int);
private slots:
/* 定时器时间到处理函数,发送图像数据信号 */
void timerTimeOut();
private:
/* 声明OpenCV的cv命名空间下的VideoCapture对象 */
cv::VideoCapture * capture;
/* 定时器,定时处理获取图像 */
QTimer * timer;
/* 图像转换处理函数 */
QImage matToQImage(const cv::Mat&);
};
#endif // CAMERA_H
- camera.cpp
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName 05_opencv_camera
* @brief camera.cpp
* @author Deng Zhimao
* @email [email protected]
* @net www.openedv.com
* @date 2021-03-17
*******************************************************************/
#include "camera.h"
/* opencv4 */
#if __arm__
#include "opencv4/opencv2/core/core.hpp"
#include "opencv4/opencv2/highgui/highgui.hpp"
/* CV_CAP_PROP_FRAME_WIDTH这样的宏在此头文件里 */
#include "opencv4/opencv2/videoio/legacy/constants_c.h"
#else
/* Ubuntu18上使用的opencv3,头文件路径 */
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#endif
#include <QImage>
#include <QDebug>
Camera::Camera(QObject *parent) :
QObject(parent)
{
/* 实例化 */
capture = new cv::VideoCapture();
timer = new QTimer(this);
/* 信号槽连接 */
connect(timer, SIGNAL(timeout()), this, SLOT(timerTimeOut()));
}
Camera::~Camera()
{
delete capture;
capture = NULL;
}
void Camera::selectCameraDevice(int index)
{
/* 如果有其他摄像头打开了,先释放 */
if (capture->isOpened()) {
capture->release();
}
/* 打开摄像头设备 */
capture->open(index);
/* 设置采集摄像头的分辨率的宽度 */
capture ->set(CV_CAP_PROP_FRAME_WIDTH, 640);
/* 设置采集摄像头的分辨率的高度 */
capture ->set(CV_CAP_PROP_FRAME_HEIGHT, 480);
/* 设置亮度 */
capture ->set(CV_CAP_PROP_BRIGHTNESS, 1);
/* 设置曝光 */
capture ->set(CV_CAP_PROP_EXPOSURE, -7);
/* 设置对比度 */
capture ->set(CV_CAP_PROP_CONTRAST, 60);
}
bool Camera::cameraProcess(bool bl)
{
if (bl) {
/* 为什么是33?1000/33约等于30帧,也就是一秒最多显示30帧 */
timer->start(33);
} else {
timer->stop();
}
/* 返回摄像头的状态 */
return capture->isOpened();
}
void Camera::timerTimeOut()
{
/* 如果摄像头没有打开,停止定时器,返回 */
if (!capture->isOpened()) {
timer->stop();
return;
}
static cv::Mat frame;
*capture >> frame;
if (frame.cols)
/* 发送图片信号 */
emit readyImage(matToQImage(frame));
}
QImage Camera::matToQImage(const cv::Mat &img)
{
/* USB摄像头和OV5640等都是RGB三通道,不考虑单/四通道摄像头 */
if(img.type() == CV_8UC3) {
/* 得到图像的的首地址 */
const uchar *pimg = (const uchar*)img.data;
/* 以img构造图片 */
QImage qImage(pimg, img.cols, img.rows, img.step,
QImage::Format_RGB888);
/* 在不改变实际图像数据的条件下,交换红蓝通道 */
return qImage.rgbSwapped();
}
/* 返回QImage */
return QImage();
}
19.3 mainwindows
- 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
*******************************************************************/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QComboBox>
#include <QPushButton>
#include <QVBoxLayout>
#include <QLabel>
#include <QScrollArea>
#include <QDebug>
class Camera;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* 主容器,Widget也可以当作一种容器 */
QWidget *mainWidget;
/* 滚动区域,方便开发高分辨率 */
QScrollArea *scrollArea;
/* 将采集到的图像使用Widget显示 */
QLabel *displayLabel;
/* 界面右侧区域布局 */
QHBoxLayout *hboxLayout;
/* 界面右侧区域布局 */
QVBoxLayout *vboxLayout;
/* 界面右侧区域容器 */
QWidget *rightWidget;
/* 界面右侧区域显示拍照的图片 */
QLabel *photoLabel;
/* 界面右侧区域摄像头设备下拉选择框 */
QComboBox *comboBox;
/* 两个按钮,一个为拍照按钮,另一个是开启摄像头按钮 */
QPushButton *pushButton[2];
/* 拍照保存的照片 */
QImage saveImage;
/* 摄像头设备 */
Camera *camera;
/* 布局初始化 */
void layoutInit();
/* 扫描是否存在摄像头 */
void scanCameraDevice();
private slots:
/* 显示图像 */
void showImage(const QImage&);
/* 设置按钮文本 */
void setButtonText(bool);
/* 保存照片到本地 */
void saveImageToLocal();
};
#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
*******************************************************************/
#include "mainwindow.h"
#include <QGuiApplication>
#include <QScreen>
#include <QFile>
#include <QPixmap>
#include <QBuffer>
#include "camera.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 布局初始化 */
layoutInit();
/* 扫描摄像头 */
scanCameraDevice();
}
MainWindow::~MainWindow()
{
}
void MainWindow::layoutInit()
{
/* 获取屏幕的分辨率,Qt官方建议使用这
* 种方法获取屏幕分辨率,防上多屏设备导致对应不上
* 注意,这是获取整个桌面系统的分辨率
*/
QList <QScreen *> list_screen = QGuiApplication::screens();
/* 如果是ARM平台,直接设置大小为屏幕的大小 */
#if __arm__
/* 重设大小 */
this->resize(list_screen.at(0)->geometry().width(),
list_screen.at(0)->geometry().height());
#else
/* 否则则设置主窗体大小为800x480 */
this->resize(800, 480);
#endif
/* 实例化与布局,常规操作 */
mainWidget = new QWidget();
photoLabel = new QLabel();
rightWidget = new QWidget();
comboBox = new QComboBox();
pushButton[0] = new QPushButton();
pushButton[1] = new QPushButton();
scrollArea = new QScrollArea();
displayLabel = new QLabel(scrollArea);
vboxLayout = new QVBoxLayout();
hboxLayout = new QHBoxLayout();
vboxLayout->addWidget(photoLabel);
vboxLayout->addWidget(comboBox);
vboxLayout->addWidget(pushButton[0]);
vboxLayout->addWidget(pushButton[1]);
rightWidget->setLayout(vboxLayout);
hboxLayout->addWidget(scrollArea);
hboxLayout->addWidget(rightWidget);
mainWidget->setLayout(hboxLayout);
this->setCentralWidget(mainWidget);
pushButton[0]->setMaximumHeight(40);
pushButton[0]->setMaximumWidth(200);
pushButton[1]->setMaximumHeight(40);
pushButton[1]->setMaximumWidth(200);
comboBox->setMaximumHeight(40);
comboBox->setMaximumWidth(200);
photoLabel->setMaximumSize(100, 75);
scrollArea->setMinimumWidth(this->width()
- comboBox->width());
/* 显示图像最大画面为xx */
displayLabel->setMinimumWidth(scrollArea->width() * 0.75);
displayLabel->setMinimumHeight(scrollArea->height() * 0.75);
scrollArea->setWidget(displayLabel);
/* 居中显示 */
scrollArea->setAlignment(Qt::AlignCenter);
/* 自动拉伸 */
photoLabel->setScaledContents(true);
displayLabel->setScaledContents(true);
/* 设置一些属性 */
pushButton[0]->setText("拍照");
pushButton[0]->setEnabled(false);
pushButton[1]->setText("开始");
pushButton[1]->setCheckable(true);
/* 摄像头 */
camera = new Camera(this);
/* 信号连接槽 */
connect(camera, SIGNAL(readyImage(QImage)),
this, SLOT(showImage(QImage)));
connect(pushButton[1], SIGNAL(clicked(bool)),
camera, SLOT(cameraProcess(bool)));
connect(pushButton[1], SIGNAL(clicked(bool)),
this, SLOT(setButtonText(bool)));
connect(pushButton[0], SIGNAL(clicked()),
this, SLOT(saveImageToLocal()));
}
void MainWindow::scanCameraDevice()
{
/* 如果是Windows系统,一般是摄像头0 */
#if win32
comboBox->addItem("windows摄像头0");
connect(comboBox,
SIGNAL(currentIndexChanged(int)),
camera, SLOT(selectCameraDevice(int)));
#else
/* QFile文件指向/dev/video0 */
QFile file("/dev/video0");
/* 如果文件存在 */
if (file.exists())
/* 开发板ov5640摄像头是0 */
comboBox->addItem("video0");
else {
displayLabel->setText("无摄像头设备");
return;
}
file.setFileName("/dev/video1");
if (file.exists()) {
comboBox->addItem("video1");
/* 开发板USB摄像头设备是1 */
comboBox->setCurrentIndex(1);
}
file.setFileName("/dev/video2");
if (file.exists())
comboBox->addItem("video2");
#if !__arm__
/* ubuntu的USB摄像头一般是0 */
comboBox->setCurrentIndex(0);
#endif
connect(comboBox,
SIGNAL(currentIndexChanged(int)),
camera, SLOT(selectCameraDevice(int)));
#endif
}
void MainWindow::showImage(const QImage &image)
{
/* 显示图像 */
displayLabel->setPixmap(QPixmap::fromImage(image));
saveImage = image;
/* 判断图像是否为空,空则设置拍照按钮不可用 */
if (!saveImage.isNull())
pushButton[0]->setEnabled(true);
else
pushButton[0]->setEnabled(false);
}
void MainWindow::setButtonText(bool bl)
{
if (bl) {
/* 设置摄像头设备 */
camera->selectCameraDevice(comboBox->currentIndex());
pushButton[1]->setText("关闭");
} else {
/* 若关闭了摄像头则禁用拍照按钮 */
pushButton[0]->setEnabled(false);
pushButton[1]->setText("开始");
}
}
void MainWindow::saveImageToLocal()
{
/* 判断图像是否为空 */
if (!saveImage.isNull()) {
QString fileName =
QCoreApplication::applicationDirPath() + "/test.png";
qDebug()<<"正在保存"<<fileName<<"图片,请稍候..."<<endl;
/* save(arg1,arg2,arg3)重载函数,arg1代表路径文件名,
* arg2保存的类型,arg3代表保存的质量等级 */
saveImage.save(fileName, "PNG", 1);
/* 设置拍照的图像为显示在photoLabel上 */
photoLabel->setPixmap(QPixmap::fromImage(QImage(fileName)));
qDebug()<<"保存完成!"<<endl;
}
}
第 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,右上角显示保存照片的缩略图,再次点击拍照则会替换已经保存过的照片。若想要保存多个照片可自行设计。若在正点原子 STM32MP157 开发板上运行此程序,先插上 ov5640 摄像头,确保摄像头能用,video0 就是 ov5640 的摄像头,流畅度不错。如果是 USB 摄像头,请确认 USB 摄像头节点video1,然后点击“开始”即可。总结,OpenCV 为我们提供了很多图像处理的 api,这个例子只是简单的获取摄像头的数据显示,处理图像的 api 还没有使用到。比如设置亮度,曝光,对比度等等,请参考“opencv4/opencv2/videoio/legacy/constants_c.h”头文件里的宏定义。
可以使用下面的方法设置。
capture ->set(CV_CAP_PROP_BRIGHTNESS, 1); // 设置亮度
capture ->set(CV_CAP_PROP_EXPOSURE, -7); // 设置曝光
capture ->set(CV_CAP_PROP_CONTRAST, 60); // 设置对比度
21.USER-KEY
- 在正点原子的 STM32MP157 开发板,STM32MP157 开发板上有个用户按键KEY0。首先正点原子的出厂内核已经默认将这个按键注册成了 gpio-keys 类型设备,键值为 114 也就是对应 Qt 的 Key_VolumeDown 键值。也就是说我们可以直接当这个按键是我们普通键盘的音量减键使用,我们在本例中使用 Key_Down(键盘方向键↓)在 Windows/Ubuntu 上测试,在开发板上还是使用 KEY0 按键测试。在开发板监测这个 KEY0 有很多方法。比如使用 C 语言开一个线程监测这个按键,或者按本例重写键盘事件来监测 KEY0 按键按下或者松开。
- mainwindow.h
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName 07_key
* @brief mainwindow.h
* @author Deng Zhimao
* @email [email protected]
* @net www.openedv.com
* @date 2021-04-19
*******************************************************************/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QKeyEvent>
#include <QLabel>
#include <QDebug>
#include <QEvent>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* 标签文本 */
QLabel *label;
/* 重写按键事件,声明需要重写的按键事件类型。分别是按下事件和松开事件。通过重写这两
个事件可以监测到键盘或 KEY0 按下的状态。 */
void keyPressEvent(QKeyEvent *event);
void keyReleaseEvent(QKeyEvent *event);
};
#endif // MAINWINDOW_H
- mainwindow.cpp
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName 07_key
* @brief mainwindow.cpp
* @author Deng Zhimao
* @email [email protected]
* @net www.openedv.com
* @date 2021-04-19
*******************************************************************/
#include "mainwindow.h"
#include <QGuiApplication>
#include <QScreen>
#include <QRect>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 获取屏幕的分辨率,Qt官方建议使用这种方法获取屏幕分辨率,防上多屏设备导致对应不上
* 注意,这是获取整个桌面系统的分辨率*/
QList <QScreen *> list_screen = QGuiApplication::screens();
/* 如果是ARM平台,直接设置大小为屏幕的大小 */
#if __arm__
/* 重设大小 */
this->resize(list_screen.at(0)->geometry().width(),
list_screen.at(0)->geometry().height());
#else
this->setGeometry(0, 0, 800, 480);/* 否则则设置主窗体大小为800x480 */
#endif
label = new QLabel(this);/* 标签实例化 */
#if __arm__
label->setText("VolumeDown松开状态");/* 设置默认文本 */
#else
label->setText("Down按键松开");
#endif
label->setAlignment(Qt::AlignCenter);/* 设置对齐方式 */
setCentralWidget(label);/* 居中显示 */
}
MainWindow::~MainWindow()
{
}
void MainWindow::keyPressEvent(QKeyEvent *event)
{
#if __arm__
if(event->key() == Qt::Key_VolumeDown) {
/* 判断按下的按键,也就是板子KEY0按键 */
label->setText("VolumeDown按键按下"); /* 设置label的文本 */
}
#else
if(event->key() == Qt::Key_Down) {
/* 判断按下的按键,也就是"↓"方向键 */
label->setText("Down按键按下"); /* 设置label的文本 */
}
#endif
QWidget::keyPressEvent(event);/* 保存默认事件 */
}
/*重写按下事件和松开事件,通过判断 event->key()等哪个按键,就可以知道是
哪个按键按下或者松开了。并设置了标签文本的属性*/
void MainWindow::keyReleaseEvent(QKeyEvent *event)
{
#if __arm__
if(event->key() == Qt::Key_VolumeDown) {
/* 判断松开的按键,也就是板子KEY0按键 */
label->setText("VolumeDown按键松开");/* 设置label的文本 */
}
#else
if(event->key() == Qt::Key_Down) {
/* 判断按下的按键,也就是"↓"方向键 */
label->setText("Down按键松开");/* 设置label的文本 */
}
#endif
QWidget::keyReleaseEvent(event);/* 保存默认事件 */
}
- 运行
22.IIC设备ap3216c
- 在正点原子 STM32MP157 出厂系统里,已经编写了 AP3216C 的驱动,并注册成了杂项设备,可以在/sys/class/misc 下找到 ap3216c 节点。我们直接用 Qt 通过访问节点文件的方式来获取 AP3216C 的传感器数据。读取数据流程解释:数据由驱动层传到 Linux 应用层,Qt 应用程序从应用层读取传感器数据。
- 使用到 pri 文件,pri 文件的语法和 pro 文件相同,通常它是由 pro 文件改写得到的,该类型文件类似于 C++中的头文件,可以在 pro 文件中使用 include 将其包含进来,相当于文件引入,当一个项目文件非常多时,或有些项目文件需要重复使用,为了方便管理就可以使用此方法在pro文件末尾添加一行:include(headview/headview.pri)
28.视频监控项目
常见的视频监控和视频直播就是使用 RTMP 和 RTSP 流媒体协议等。
RTSP (Real-Time Stream Protocol)由 Real Networks 和 Netscape 共同提出的,基于文本
的多媒体播放控制协议。RTSP 定义流格式,流数据经由 RTP 传输;RTSP 实时效果非常好,适
合视频聊天,视频监控等方向。
RTMP(Real Time Message Protocol) 由 Adobe 公司提出,用来解决多媒体数据传输流的
多路复用(Multiplexing)和分包(packetizing)的问题,优势在于低延迟,稳定性高,支持所
有摄像头格式,浏览器加载 flash 插件就可以直接播放。
RTSP 和 RTMP 的区别:
RTSP 虽然实时性最好,但是实现复杂,适合视频聊天和视频监控;RTMP 强在浏览器支持
好,加载 flash 插件后就能直接播放,所以非常火,相反在浏览器里播放 rtsp 就很困难了。
说了上面那么多,是为了让大家了解当下比较火的流媒体协议。一般这种协议需要搭配服
务器如 Nginx 服务器和 ffmpeg 工具来使用。当然像 ffmpeg 这种强大的工具我们是写不出来的,
这种强大的工具已经发展的很好,专门是做音视频处理方案的。有兴趣的可以了解下。在我们
STM32MP157 的出厂系统就支持 RTMP+FFMPEG+NGINX 推流了。这就需要一个客户端和一
个服务端。毫无疑问,STM32MP157 充当服务器,客户端用现成的软件 VLC 播放器可以播放。
如果我们不想用这种方式来做视频监控,有没有简单的一些方案在 Qt 里能使用的呢。答案是有
的。看下面的方案。
在这本 Qt 教程里,前面第十一章,我们已经学习过网络编程。既然学习过网络编程,使用
UDP 或者 TCP 传输,使用 Qt 封装好 TCP/IP 协议 Socket 抽象层接口来通信。那么我们也可以
使用它们来发送图像数据啊。这样,服务器和客户端我们完全可以使用 Qt 来写了。实际上 RTSP
协议和 RTMP 协议都使用 TCP/IP 协议,在传输层使用了 UDP 或 TCP,所以说 RTSP 和 RTMP
协议是更高的一层协议。
本章需要使用正点原子的 STM32MP157 开发板及正点原子 OV5640 摄像头。注:本次不对
USB摄像头做适配。原因是USB摄像头采集的是YUYV数据,转RGB数据则会消耗大量CPU,
会卡顿很多,延时性大。推荐使用正点原子的 OV5640 摄像头,支持 RGB 格式采集,Qt 显示
也是 RGB 格式的,所以采集的数据直接给屏幕显示即可,相对 YUYV 摄像头,消耗 CPU 更少,
显示更流畅
28.2 视频监控之服务端
源码路径为 04/05_video_surveillance/video_server
1. capture_thread.h
这个是摄像头捕获线程的头文件。摄像头采集数据,我们开启一个线程来获取
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 2021-2030. All rights reserved.
* @projectName video_server
* @brief capture_thread.h
* @author Deng Zhimao
* @email [email protected]
* @link www.openedv.com
* @date 2021-11-19
*******************************************************************/
#ifndef CAPTURE_THREAD_H
#define CAPTURE_THREAD_H
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#ifdef linux
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <linux/input.h>
#endif
#include <QThread>
#include <QDebug>
#include <QPushButton>
#include <QImage>
#include <QByteArray>
#include <QBuffer>
#include <QTime>
#include <QUdpSocket>
#define VIDEO_DEV "/dev/video1"//rk3568开发板中usb摄像头是video9
#define FB_DEV "/dev/fb0"
#define VIDEO_BUFFER_COUNT 3
struct buffer_info {
void *start;
unsigned int length;
};
class CaptureThread : public QThread//继承于QThread,重写run函数即可
{
Q_OBJECT/* 用到信号槽即需要此宏定义 */
signals:
/* 准备图片 */
void imageReady(QImage);
void sendImage(QImage);
private:
/* 线程开启flag */
bool startFlag = false;
/* 开启广播flag */
bool startBroadcast = false;
/* 本地显示flag */
bool startLocalDisplay = false;
void run() override;
public:
CaptureThread(QObject *parent = nullptr) {
Q_UNUSED(parent);
}
public slots:
/* 设置线程 */
void setThreadStart(bool start) {
startFlag = start;
if (start) {
if (!this->isRunning())
this->start();//QThread类,用start()函数启动,会运行run()函数
} else {
this->quit();
}
}
/* 设置广播 */
void setBroadcast(bool start) {
startBroadcast = start;
}
/* 设置本地显示 */
void setLocalDisplay(bool start) {
startLocalDisplay = start;
}
};
#endif // CAPTURE_THREAD_H
2. capture_thread.cpp
摄像头线程的主程序。
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 2021-2030. All rights reserved.
* @projectName video_server
* @brief capture_thread.cpp
* @author Deng Zhimao
* @email [email protected]
* @link www.openedv.com
* @date 2021-11-19
*******************************************************************/
#include "capture_thread.h"
unsigned short yuyv_to_rgb565(unsigned short y, unsigned short u,unsigned short v) {
int16_t r, g, b;
// 将Y, U, V从8位无符号转换为带符号的16位值
int16_t y1 = y;
int16_t u1 = u - 128;
int16_t v1 = v - 128;
r = y1 + 1.042*(v1);
g = y1- 0.34414*(u1) - 0.71414*v1;
b = y1+ 1.772*u1;
// 将r, g, b值限制在0-255的范围内
r = r < 0 ? 0 : (r > 255 ? 255 : r);
g = g < 0 ? 0 : (g > 255 ? 255 : g);
b = b < 0 ? 0 : (b > 255 ? 255 : b);
// 将r, g, b值转换为RGB565格式
return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
}
void CaptureThread::run()
{
/* 下面的代码请参考正点原子C应用编程V4L2章节,摄像头编程,这里不作解释 */
#ifdef linux
#ifndef __arm__
return;
#endif
static int count=0;
int video_fd = -1;
struct v4l2_format fmt;
struct v4l2_requestbuffers req_bufs;
static struct v4l2_buffer buf;
int n_buf;
struct buffer_info bufs_info[VIDEO_BUFFER_COUNT];
enum v4l2_buf_type type;
video_fd = open(VIDEO_DEV, O_RDWR);
if (0 > video_fd) {
printf("ERROR: failed to open video device %s\n", VIDEO_DEV);
return ;
}
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 640;
fmt.fmt.pix.height = 480;
/*fmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB565;*/
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//tao change to yuyv
if (0 > ioctl(video_fd, VIDIOC_S_FMT, &fmt)) {
printf("ERROR: failed to VIDIOC_S_FMT\n");
close(video_fd);
return ;
}
/* 申请帧缓冲 */
req_bufs.count = VIDEO_BUFFER_COUNT;//帧缓冲的数量
req_bufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req_bufs.memory = V4L2_MEMORY_MMAP;
if (0 > ioctl(video_fd, VIDIOC_REQBUFS, &req_bufs)) {
printf("ERROR: failed to VIDIOC_REQBUFS\n");
return ;
}
/* 建立内存映射 */
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
for (n_buf = 0; n_buf < VIDEO_BUFFER_COUNT; n_buf++) {
buf.index = n_buf;
if (0 > ioctl(video_fd, VIDIOC_QUERYBUF, &buf)) {
printf("ERROR: failed to VIDIOC_QUERYBUF\n");
return ;
}
bufs_info[n_buf].length = buf.length;
bufs_info[n_buf].start = mmap(NULL, buf.length,
PROT_READ | PROT_WRITE, MAP_SHARED,
video_fd, buf.m.offset);
if (MAP_FAILED == bufs_info[n_buf].start) {
printf("ERROR: failed to mmap video buffer, size 0x%x\n", buf.length);
return ;
}
}
/* 入队 */
for (n_buf = 0; n_buf < VIDEO_BUFFER_COUNT; n_buf++) {
buf.index = n_buf;
if (0 > ioctl(video_fd, VIDIOC_QBUF, &buf)) {
printf("ERROR: failed to VIDIOC_QBUF\n");
return ;
}
}
/* 打开摄像头、摄像头开始采集数据 */
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (0 > ioctl(video_fd, VIDIOC_STREAMON, &type)) {
printf("ERROR: failed to VIDIOC_STREAMON\n");
return ;
}
while (startFlag ) {
for (n_buf = 0; n_buf < VIDEO_BUFFER_COUNT; n_buf++) {
buf.index = n_buf;
if (0 > ioctl(video_fd, VIDIOC_DQBUF, &buf)) {
//出队
printf("ERROR: failed to VIDIOC_DQBUF\n");
return;
}
int isBroadcast= startBroadcast && (count++>=4);
if(startLocalDisplay || isBroadcast){
//原地转换为RGB565,由于是usb摄像头,所以需要YUYV格式转换为RGB565
unsigned short *start;
long int i= 0L;
for (start=(unsigned short *)bufs_info[n_buf].start;i < fmt.fmt.pix.width*fmt.fmt.pix.height; i+=2) {
unsigned short y0 = start[i] & 0x00ff;
unsigned short u = start[i] >> 8;
unsigned short y1 = start[i + 1] & 0x00ff;
unsigned short v = start[i + 1] >> 8;
start[i] = yuyv_to_rgb565(y0, u, v); // 第一个像素
start[i+1] = yuyv_to_rgb565(y1, u, v); // 第二个像素
}
QImage qImage((unsigned char*) bufs_info[n_buf].start, fmt.fmt.pix.width, fmt.fmt.pix.height, QImage::Format_RGB16);
//转换为RGB565
//QImage qImage((unsigned char*)bufs_info[n_buf].start, fmt.fmt.pix.width, fmt.fmt.pix.height, QImage::Format_RGB16);
/* 是否开启本地显示,开启本地显示可能会导致开启广播卡顿,它们互相制约 */
if (startLocalDisplay)
emit imageReady(qImage);
/* 是否开启广播,开启广播会导致本地显示卡顿,它们互相制约 */
if (isBroadcast) {
//每采样4次才广播一次,否则延迟太大,mp157处理不过来
count=0;
/* udp套接字 */
QUdpSocket udpSocket;
/* QByteArray类型 */
QByteArray byte;
/* 建立一个用于IO读写的缓冲区 */
QBuffer buff(&byte);
/* image转为byte的类型,再存入buff */
qImage.save(&buff, "JPEG", -1);
/* 转换为base64Byte类型 */
QByteArray base64Byte = byte.toBase64();
/* 由udpSocket以广播的形式传输数据,端口号为8888 */
udpSocket.writeDatagram(base64Byte.data(), base64Byte.size(), QHostAddress::Broadcast, 8888);
//QHostAddress peerAddr("192.168.137.1");//由udpSocket以单播的形式传输数据,端口号为10002
//udpSocket.writeDatagram(base64Byte.data(), base64Byte.size(), peerAddr, 10002);
}
}
if (0 > ioctl(video_fd, VIDIOC_QBUF, &buf)) {
// 数据处理完之后、再入队、往复
printf("ERROR: failed to VIDIOC_QBUF\n");
return;
}
}
}
msleep(800);//at lease 650
for (int i = 0; i < VIDEO_BUFFER_COUNT; i++) {
munmap(bufs_info[i].start, buf.length);
}
close(video_fd);
#endif
}
3. mainwindow.h
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 2021-2030. All rights reserved.
* @projectName video_server
* @brief mainwindow.h
* @author Deng Zhimao
* @email [email protected]
* @link www.openedv.com
* @date 2021-11-19
*******************************************************************/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLabel>
#include <QImage>
#include <QPushButton>
#include <QHBoxLayout>
#include <QCheckBox>
#include "capture_thread.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* 用于显示捕获到的图像 */
QLabel *videoLabel;
/* 摄像头线程 */
CaptureThread *captureThread;
/* 开始捕获图像按钮 */
QPushButton *startCaptureButton;
/* 用于开启本地图像显示 */
QCheckBox *checkBox1;
/* 用于开启网络广播 */
QCheckBox *checkBox2;
/* 重写大小事件 */
void resizeEvent(QResizeEvent *event) override;
private slots:
/* 显示图像 */
void showImage(QImage);
/* 开始采集按钮被点击 */
void startCaptureButtonClicked(bool);
};
#endif // MAINWINDOW_H
4. mainwindow.cpp
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 2021-2030. All rights reserved.
* @projectName video_server
* @brief mainwindow.cpp
* @author Deng Zhimao
* @email [email protected]
* @link www.openedv.com
* @date 2021-11-19
*******************************************************************/
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->setGeometry(0, 0, 800, 480);
videoLabel = new QLabel(this);
videoLabel->setText("未获取到图像数据或未开启本地显示");
videoLabel->setStyleSheet("QWidget {color: white;}");
videoLabel->setAlignment(Qt::AlignCenter);
videoLabel->resize(640, 480);
checkBox1 = new QCheckBox(this);
checkBox2 = new QCheckBox(this);
checkBox1->resize(120, 50);
checkBox2->resize(120, 50);
checkBox1->setText("本地显示");
checkBox2->setText("开启广播");
checkBox1->setStyleSheet("QCheckBox {color: yellow;}"
"QCheckBox:indicator {width: 40; height: 40;}");
checkBox2->setStyleSheet("QCheckBox {color: yellow;}"
"QCheckBox:indicator {width: 40; height: 40}");
/* 按钮 */
startCaptureButton = new QPushButton(this);
startCaptureButton->setCheckable(true);
startCaptureButton->setText("开始采集摄像头数据");
/* 设置背景颜色为黑色 */
QColor color = QColor(Qt::black);
QPalette p;
p.setColor(QPalette::Window, color);
this->setPalette(p);
/* 样式表 */
startCaptureButton->setStyleSheet("QPushButton {background-color: white; border-radius: 30}"
"QPushButton:pressed {background-color: red;}");
captureThread = new CaptureThread(this);
connect(startCaptureButton, SIGNAL(clicked(bool)), captureThread, SLOT(setThreadStart(bool)));
connect(startCaptureButton, SIGNAL(clicked(bool)), this, SLOT(startCaptureButtonClicked(bool)));
connect(captureThread, SIGNAL(imageReady(QImage)), this, SLOT(showImage(QImage)));
connect(checkBox1, SIGNAL(clicked(bool)), captureThread, SLOT(setLocalDisplay(bool)));
connect(checkBox2, SIGNAL(clicked(bool)), captureThread, SLOT(setBroadcast(bool)));
}
MainWindow::~MainWindow()
{
}
void MainWindow::showImage(QImage image)
{
videoLabel->setPixmap(QPixmap::fromImage(image));
}
void MainWindow::resizeEvent(QResizeEvent *event)
{
Q_UNUSED(event)
startCaptureButton->move((this->width() - 200) / 2, this->height() - 80);
startCaptureButton->resize(200, 60);
videoLabel->move((this->width() - 640) / 2, (this->height() - 480) / 2);
checkBox1->move(this->width() - 120, this->height() / 2 - 50);
checkBox2->move(this->width() - 120, this->height() / 2 + 25);
}
void MainWindow::startCaptureButtonClicked(bool start)
{
if (start)
startCaptureButton->setText("停止采集摄像头数据");
else
startCaptureButton->setText("开始采集摄像头数据");
}
这里如果是开发板采集,pc显示,记得使用route add default gw 192.168.137.1添加默认网关,这样才会从这个接口发送广播包

- windows.h
- windows.cpp
- 运行