QtCreator学习(二).在stm32mp1中使用

0.配置编译环境

  1. 复制【正点原子】STM32MP157开发板(A盘)-基础资料\05、开发工具\01、交叉编译器st-example-image-qtwayland-openstlinux-weston-stm32mp1-x86_64-toolchain-3.1-snapshot.sh到虚拟机
  2. chmod添加可执行文件,./st*运行,选择安装目录在/opt/st/stm32mp1/qt_crossCompile/中

编译

  1. source /opt/st/stm32mp1/qt_crossCompile/environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi设置环境变量
  2. 进入pro文件所在目录命令:/opt/st/stm32mp1/qt_crossCompile/sysroots/x86_64-ostl_sdk-linux/usr/bin/qmake生成makefile,这里面的编译路径才是stm32mp1对应的
  3. make -j 8开始编译生成可执行文件复制到开发板运行

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

15.led控制

项目简介:设置一个按钮,点击即可控制 LED 状态反转(点亮或者熄灭 LED)。项目看来很起来很简单,实际上有些需要注意的地方,我们在改变 LED 的状态时,需要先去读取 LED的状态,防止外界(外面应用程序)将 LED 的状态改变了。否则我们反转操作将不成立。在
C++里一般使用 get()和 set()方法来获取和设置。我们的 LED 程序里也有这种方法。所以需要写好一个让人看得懂的程序是有“方法”的。不能将程序功能写在一堆,最好是分开写,留有接口。让后面的人看懂!
例 01_led,控制 LED

  1. windows.h
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName   01_led
* @brief         mainwindow.h
* @author        Deng Zhimao
* @email         1252699831@qq.com
* @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

  1. windows.cpp
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName   01_led
* @brief         mainwindow.cpp
* @author        Deng Zhimao
* @email         1252699831@qq.com
* @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();
}
  1. 运行,下面为 Ubuntu 上仿真界面的效果,由于 Ubuntu 不是“开发板”,所以在读取 LED 设备时会读取失败。实际在板上运行图略。交叉编译程序到正点原子 STM32MP157 开发板上运行即可控制 LED 的状态。
    在这里插入图片描述

16.控制beep

想要控制这个蜂鸣器(BEEP),首先正点原子的出厂内核已经默认将这个 LED 注册成了 gpio-leds 类型设备。所以实例与上一小节 LED 实例是一样的。项目简介:设置一个按钮,点击即可控制 BEEP 状态反转(打开蜂鸣器或者关闭蜂鸣器)。
例 02_beep,控制 BEEP

  1. windows.h
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName   02_beep
* @brief         mainwindow.h
* @author        Deng Zhimao
* @email         1252699831@qq.com
* @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

  1. windows.cpp
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName   02_beep
* @brief         mainwindow.cpp
* @author        Deng Zhimao
* @email         1252699831@qq.com
* @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();
}



  1. 运行,下面为 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

  1. windows.h
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName   03_serialport
* @brief         mainwindow.h
* @author        Deng Zhimao
* @email         1252699831@qq.com
* @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

  1. windows.cpp
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName   03_serialport
* @brief         mainwindow.cpp
* @author        Deng Zhimao
* @email         1252699831@qq.com
* @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()
{
}
  1. 运行,下面为 Ubuntu 上仿真界面的效果,请将程序交叉编译后到 STM32MP157 开发板运行,用串口线连接开发板的 UART3/UART5 到电脑串口,在电脑用正点原子的 XCOM 上位机软件(或者本程序亦可当上位机软件),设置相同的串口参数,在 LCD 屏幕上选择串口号为ttySTM1/ttySTM2(注意 ttySTM0 已经作为调试串口被使用了!),点击打开串口就可以进行消息收发了。默认参数为波特率为 115200,数据位为 8,校验为 None,停止位为 1,流控为关闭。
    在这里插入图片描述
    在这里插入图片描述
    k可使用usb转rs232线连接测试
    在这里插入图片描述

19.Camera

  1. 下面主要介绍 Qt + OpenCV 调用摄像头,要想在 Ubuntu 上使用 OpenCV,那么我们的 Ubuntu 上必须有 OpenCV 的库,直接用出厂系统提供的交叉编译工具链,里面已经提供有 OpenCV。在 Ubuntu 上安装 OpenCV 只是方便我们测试界面,编写的程序也可以在Ubuntu 上运行。
  2. 首先我们需要在项目 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类

  1. camera.h
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName   05_opencv_camera
* @brief         camera.h
* @author        Deng Zhimao
* @email         1252699831@qq.com
* @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

  1. camera.cpp
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName   05_opencv_camera
* @brief         camera.cpp
* @author        Deng Zhimao
* @email         1252699831@qq.com
* @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

  1. mainwindow.h
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName   05_opencv_camera
* @brief         mainwindow.h
* @author        Deng Zhimao
* @email         1252699831@qq.com
* @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
  1. mainwindow.cpp
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName   05_opencv_camera
* @brief         mainwindow.cpp
* @author        Deng Zhimao
* @email         1252699831@qq.com
* @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

  1. 在正点原子的 STM32MP157 开发板,STM32MP157 开发板上有个用户按键KEY0。首先正点原子的出厂内核已经默认将这个按键注册成了 gpio-keys 类型设备,键值为 114 也就是对应 Qt 的 Key_VolumeDown 键值。也就是说我们可以直接当这个按键是我们普通键盘的音量减键使用,我们在本例中使用 Key_Down(键盘方向键↓)在 Windows/Ubuntu 上测试,在开发板上还是使用 KEY0 按键测试。在开发板监测这个 KEY0 有很多方法。比如使用 C 语言开一个线程监测这个按键,或者按本例重写键盘事件来监测 KEY0 按键按下或者松开。
  2. mainwindow.h
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName   07_key
* @brief         mainwindow.h
* @author        Deng Zhimao
* @email         1252699831@qq.com
* @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

  1. mainwindow.cpp
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName   07_key
* @brief         mainwindow.cpp
* @author        Deng Zhimao
* @email         1252699831@qq.com
* @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);/* 保存默认事件 */
}
  1. 运行
    在这里插入图片描述

22.IIC设备ap3216c

  1. 在正点原子 STM32MP157 出厂系统里,已经编写了 AP3216C 的驱动,并注册成了杂项设备,可以在/sys/class/misc 下找到 ap3216c 节点。我们直接用 Qt 通过访问节点文件的方式来获取 AP3216C 的传感器数据。读取数据流程解释:数据由驱动层传到 Linux 应用层,Qt 应用程序从应用层读取传感器数据。
  2. 使用到 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         dengzhimao@alientek.com
* @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         dengzhimao@alientek.com
* @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         dengzhimao@alientek.com
* @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         dengzhimao@alientek.com
* @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添加默认网关,这样才会从这个接口发送广播包
在这里插入图片描述

  1. windows.h

  1. windows.cpp

  1. 运行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值