• 嵌入式Qt-简易网络监控摄像头


    本编利用Qt实现一个网络摄像头功能,包含一个服务端和一个客户端,服务端用于将USB摄像头转换为一个IP摄像头,当有客户端连接时,将其捕获到的图像通过TCP发送出去;客户端运行在Linux板子上,用于查看摄像头的实时画面。

    1 必备基础知识

    本篇需要编写一个服务器和客户端,关于TCP服务器/客户端的基础知识,可参考这篇:Linux进程间通信详解(七) socket套接字基础

    注意,Qt中对Socket的操作进行了进一步的封装,其基本思想还是一样的。

    下面就来看一下Qt中如何实现TCP Socket通信

    1.1 QTcpSocket与QTcpServer

    • QTcpSocket,在Qt中,Socket被封装成了QTcpSocket,可以用它实现TCP客户端的功能,以及服务端接收到客户端后,对客户端的处理。

    • QTcpServer,对于TCP服务端的功能,可以使用QTcpServer来完成。

    这里整理Qt中TCP Socket的使用方法,配合Qt的信号与槽机制,即可实现服务端/客户端数据的收发处理。

    1.2 QCamera相关

    • QCamer,获取当前系统可用的摄像头 类似获取串口

    • QCamerInfo,获取当前系统可用的摄像头 类似获取串口

    • QCameraViewfinder,取景框类,摄像头的实时画面显示到这个里面

    • QCameraImageCapture,图像录制类,与QCamer 配合使用可进行拍照

    2 Win平台上测试

    首先在Windows平台上用Qt Creator编写服务端和客户端程序,并运行测试。

    2.1 服务器端

    先来看下服务器端的最终效果:

    • 左侧是摄像头的显示界面
    • 可以切换不同的摄像头作为视频源(笔记本自带的摄像头与USB外接的摄像头)
    • 可以切换摄像头的显示分辨率
    • 可以选择开启或关闭摄像头的IP服务

    2.1.1 摄像头画面显示

    Widget::Widget(QWidget *parent) :
        QWidget(parent),
        ui(new Ui::Widget)
    {
        ui->setupUi(this);
    
        QComboBox *pCamType = new QComboBox();
        m_pComboBox = ui->cbBox_resolution;
        pCamType = ui->cbBox_cameras;
        pCamType->clear();
    
        cameraList = QCameraInfo::availableCameras();
        foreach(const QCameraInfo &cameraInfo, cameraList)
        {
            qDebug() << "CameraInfo:" << cameraInfo;
            pCamType->addItem(cameraInfo.description());
        }
    
        m_pCamViewFind = new QCameraViewfinder(this);
        m_pCamViewFind->setGeometry(10, 10, W, H);
        m_pCamViewFind->show();
    
        m_pCam = new QCamera(this);
        m_pCam->setViewfinder(m_pCamViewFind);
        m_pCam->start();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    2.1.2 创建Socket服务

    void Widget::on_btn_IPServer_toggled(bool checked)
    {
        if (checked)
        {
            m_pServer = new QTcpServer(this);
            if (!m_pServer->listen(QHostAddress::Any, 12345))
            {
                QMessageBox::critical(this, "error", "listen port failed");
                exit(0);
            }
            qDebug() << "start IP server";
    
            m_pTimer = new QTimer(this);
            connect(m_pServer, SIGNAL(newConnection()), this, SLOT(new_client()));
            connect(m_pTimer, SIGNAL(timeout()), this, SLOT(timer_slot()));
            m_pTimer->start(100);
    
            ui->btn_IPServer->setText("关闭IP服务");
        }
        else
        {
            qDebug() << "stop IP server";
            m_pServer->close();
            delete m_pServer;
    
            ui->btn_IPServer->setText("开启IP服务");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    2.1.3 读取图像并发送给客户端

    先定义一下图像传送结构体和传送状态:

    enum TransStatus{
        TS_IDLE,       //空闲(图像数据可以更新)
        TS_RUNNING,    //图像数据传输中(还不可以更新图像数据)
        TS_FIRST_DATA, //需要发出图像数据的第一部分
    };
    
    class ImgData {
    public:
        char data[LEN] = {0}; //图像数据
        int  totalLen = 0; //图像大小
        int  hasSentLen = 0; //已发出的数据长度
        TransStatus  stats = TS_IDLE; //工作状态
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    具体的实现过程:

    void Widget::read_data()
    {
        QString str = m_pClient->readAll();
        ImgData *pData = (ImgData*)m_pClient->userData(0);
        QString s("newImage:%1");
    
        if (str == "new_request")
        {
            qDebug() << "read_data, new_request, d->len:" << pData->totalLen << "d->stats:" << pData->stats;
            if ((pData->totalLen > 0) && (pData->stats==TS_IDLE)) //图像大小不为0,表示已更新图像数据了
            {
                pData->stats = TS_RUNNING;
                m_pClient->write(s.arg(pData->totalLen).toUtf8());
                pData->hasSentLen = 0;
            }
            else //图像数据还没有更新
            {
                pData->stats = TS_FIRST_DATA; //在定时器的槽函数里发出"newImage..."
            }
        }
        else if (str == "ack")
        {
            int len_send = P_LEN; //本次需要发送的长度
    
            if (pData->hasSentLen >= pData->totalLen) //如果图像已传输完毕
            {
                qDebug() << "read_data, send done! lenSent:" << pData->hasSentLen << "len" << pData->totalLen;
                return;
            }
    
            // 最后1包数据(不满P_LEN)
            if ((pData->hasSentLen + P_LEN) > pData->totalLen)
            {
                len_send = pData->totalLen - pData->hasSentLen;
            }
    
            qDebug() << "read_data, ack, write len:" << len_send;
    
            // 发送数据
            pData->hasSentLen += m_pClient->write(pData->data + pData->hasSentLen, len_send);
            if (pData->hasSentLen >= pData->totalLen)
            {
                pData->stats = TS_IDLE; //传输完毕后,把状态改为可更新
                pData->totalLen = 0;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    需要注意的是,图像是需要分包传送的,最后一包一般都不是设定的最大长度,需要计算一下最后一包的数据长度。

    2.2 客户端

    先来看下客户端的最终效果:

    • 右侧是摄像头画面的显示框
    • 可以修改要连接的服务端的IP地址
    • 可以选择开启或关闭网络摄像头

    2.2.1 创建Socket连接

    void Widget::on_pushButton_toggled(bool checked)
    {
        if (checked)
        {
            QString ip = ui->lineEdit->text();
            m_pSocket->connectToHost(ip, 12345);
            if (!m_pSocket->waitForConnected(1000))
            {
                QMessageBox::critical(this, "error", "server connection failed");
                return;
            }
    
            ui->pushButton->setText("关闭");
            m_iRecvLen = 0;
            m_pSocket->write("new_request");
            qDebug("on_bnt_connect_clicked, new_request");
        }
        else
        {
            m_pSocket->close();
            ui->pushButton->setText("打开");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    2.3.2 接收服务端的图像

    void Widget::read_data()
    {
        int ret;
        QTime qTime;
        static int i = 0;
    
        ret = m_pSocket->read(m_pData + m_iRecvLen, P_LEN);
        if (0 == strncmp("newImage", m_pData + m_iRecvLen, 8))
        {
            m_iImgLen = atoi(m_pData + m_iRecvLen + 9);
            i++;
        }
        else
        {
            m_iRecvLen += ret;
            if (m_iRecvLen >= m_iImgLen)
            {
                QString timestamp = QString::number(QDateTime::currentMSecsSinceEpoch());
                update();
    
                return;
            }
        }
    
        //图像传输完毕
        m_pSocket->write("ack");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    2.3.3 将图像显示出来

    void Widget::paintEvent(QPaintEvent *event)
    {
        QPixmap map;
    
        if ((m_iRecvLen >= m_iImgLen) && (m_iImgLen > 0))
        {
            map.loadFromData((uchar *)m_pData, m_iImgLen);
            QPainter p(this);
            p.drawPixmap(140, 0, 640, 480, map);
            m_pSocket->write("new_request");
            m_iRecvLen = 0;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3 嵌入式Linux平台上测试

    3.1 交叉编译

    将客户端程序的源代码拷贝到Ubunu中进行交叉编译,具体编译过程可参考之前的文章:

    嵌入式Qt-动手编写并运行自己的第1个ARM-Qt程序

    本篇的实验环境,继续使用的是烧录了野火i.MX6ULL自带的系统固件Linux板子,需要通过SSH的方式将编译的程序再发送到板子中,SSH传输文件的操作可参考上篇文章:

    嵌入式Qt-控制硬件:滑动条控制RGB灯

    3.2 实验演示

    https://www.bilibili.com/video/BV12G4y1a7za
    在这里插入图片描述

    4 总结

    本篇介绍了如何用Qt实现一个网络摄像头功能,通过服务端将USB摄像头转换为一个IP摄像头,Linux板子中的客户端来连接服务器,将摄像头的实时画面显示出来。

  • 相关阅读:
    RDD缓存机制及持久化技术
    为docker-registry仓库添加WebUI
    uni-app之android离线打包
    rexp() R函数,生成服从指数分布的随机数
    接口测试学习笔记(基础知识、Jmeter、Postman)
    k8s集群搭建操作步骤
    Sfdp 超级表单开发平台 V6.0.5 正式发布
    【CNN】浅谈经典神经网络Classic Network
    docker基本管理
    图像处理与计算机视觉--第五章-图像分割-霍夫变换
  • 原文地址:https://blog.csdn.net/hbsyaaa/article/details/126593450