704|12

295

帖子

3

资源

纯净的硅(初级)

嵌入式Qt-简易网络监控摄像头 [复制链接]

 


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

 

1 必备基础知识

本篇需要编写一个服务器和客户端

1.1 QTcpSocket与QTcpServer

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

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

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

 

1.2 QCamera相关

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

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

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

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

2 Win平台上测试

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

2.1 服务器端

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

  • 左侧是摄像头的显示界面

  • 可以切换不同的摄像头作为视频源(笔记本自带的摄像头与USB外接的摄像头)

  • 可以切换摄像头的显示分辨率

  • 可以选择开启或关闭摄像头的IP服务

2.png

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();
}

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服务");
    }
}

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; //工作状态
};

具体的实现过程:

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;
        }
    }
}

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

2.2 客户端

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

  • 右侧是摄像头画面的显示框

  • 可以修改要连接的服务端的IP地址

  • 可以选择开启或关闭网络摄像头

3.png

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("打开");
    }
}

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");
}

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;
    }
}

 

3 嵌入式Linux平台上测试

3.1 交叉编译

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

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

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

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

3.2 实验演示


 

4 总结

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

 

此帖出自ARM技术论坛

回复

6994

帖子

0

资源

五彩晶圆(中级)

看来用Qt实现一个网络摄像头功能也挺复杂,谢谢分享


回复

673

帖子

15

资源

版主

Qt不是做页面的吗?

Web页面应该更简单吧

点评

嵌入式当中MPU下的浏览器要求是很高的,通常必须要有操作系统的支持,所以运行成本较高,而嵌入式很多时候是没有操作系统的。  详情 回复 发表于 2022-9-3 15:09

回复

2162

帖子

3

资源

版主

野火的板子什么价,最近想学习一下。

点评

要STM32系列的还是Linux系列的?  详情 回复 发表于 2022-8-30 13:13

回复

1916

帖子

0

资源

版主

看视频好像延迟不大,因为是局域网所以延时小?


回复

1916

帖子

0

资源

版主

lugl4313820 发表于 2022-8-30 08:37 野火的板子什么价,最近想学习一下。

要STM32系列的还是Linux系列的?

点评

linux吧,书已经买了,就是没有配套开发板。  详情 回复 发表于 2022-8-30 15:50

回复

2162

帖子

3

资源

版主

wangerxian 发表于 2022-8-30 13:13 要STM32系列的还是Linux系列的?

linux吧,书已经买了,就是没有配套开发板。


回复

2373

帖子

2

资源

五彩晶圆(初级)

吾妻思萌 发表于 2022-8-30 08:24 Qt不是做页面的吗? Web页面应该更简单吧

嵌入式当中MPU下的浏览器要求是很高的,通常必须要有操作系统的支持,所以运行成本较高,而嵌入式很多时候是没有操作系统的。


回复

1

帖子

0

资源

一粒金砂(初级)

这种对开发板上的功率消耗大吗?如果采用电池供电那种设计是不是扛不住很长时间呢?

点评

功耗的问题暂没有仔细考虑过  详情 回复 发表于 2022-9-5 22:40

回复

295

帖子

3

资源

纯净的硅(初级)

shanyinglee 发表于 2022-9-5 12:21 这种对开发板上的功率消耗大吗?如果采用电池供电那种设计是不是扛不住很长时间呢?

功耗的问题暂没有仔细考虑过


回复

2

帖子

0

资源

一粒金砂(初级)

不错不错啊,激发了我学习的热情。学起来,掌握c++,qt,linux,网络编程,摄像头等知识。


回复

68

帖子

15

资源

一粒金砂(中级)

挺好的,谢谢分享


回复

673

帖子

15

资源

版主

不错不错学习学习


回复
您需要登录后才可以回帖 登录 | 注册

相关帖子
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
    关闭
    站长推荐上一条 1/10 下一条

    About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

    站点相关: 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

    北京市海淀区知春路23号集成电路设计园量子银座1305 电话:(010)82350740 邮编:100191

    电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2022 EEWORLD.com.cn, Inc. All rights reserved
    快速回复 返回顶部 返回列表