QT6.0以上版本实现实时图像传输

2024-06-04 03:20

本文主要是介绍QT6.0以上版本实现实时图像传输,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

    • 服务端
      • 开启摄像头,捕获存储图片
      • TCP图像传输
      • 延时函数
    • 客户端
      • 建立连接
      • 接收数据和处理
      • 缓冲区接收的一些想法

QT借助tcp实现图像传输,达到类似实时监控的目的。
QT到6.0以上后貌似原来的5.0的一些图像的捕获的函数都无法使用了,网上好像也没有人给出相关实现代码,查看qt文档找到的方法。
分为两个部分,一个为客户端,负责数据的接收和展示,服务端负责数据的发送。

服务端

1、开启摄像头,捕获图片到本地
2、开启服务,传输图片

开启摄像头,捕获存储图片

//头文件定义的类私有变量     
QList<QCameraDevice> cameras;
QCamera* camera;//摄像头设备
QImageCapture* imageCapture;
//头文件定义的类私有变量 
void MainWindow::camera_getimg()
{cameras = QMediaDevices::videoInputs();//获取可用摄像头设备列表for (const QCameraDevice &cameraDevice : cameras){qDebug() << cameraDevice.description();//摄像头的设备信息ui->history->append(cameraDevice.description()+" init success.");//          ui->Camerlist->addItem(cameraDevice.description());}QMediaCaptureSession *captureSession = new QMediaCaptureSession;camera = new QCamera(cameras.at(0));//cameras.at(0)是默认摄像头,也可以通过其他方式选择摄像头qDebug() << camera->cameraDevice().description();//摄像头的设备信息,名字//ui->history->append(camera->cameraDevice().description()+" using...");captureSession->setCamera(camera);//use the first one//    captureSession->setVideoOutput(show);//show ui 有专门用来设置显示ui的imageCapture = new QImageCapture(this);imageCapture->setFileFormat(QImageCapture::JPEG);captureSession->setImageCapture(imageCapture);imageCapture->setQuality(QImageCapture::NormalQuality);//质量选择imageCapture->setResolution(240,180);//设置图像尺寸imageCapture->captureToFile("D:/qt_rec.jpg");//捕获一次图像并存储的路径   camera->start();//启动摄像头
}

由于是实时获取图像并传输到客户端,客户端接收然后展示,需要配置定时器,过一段时间就捕获一次图片并发送,相关代码在tcp中实现。

TCP图像传输

//开启服务unsigned short port  = ui->port->text().toUShort();//获取portQString  ip_t = ui->ip->text();//获取ipbool sta = my_s->listen(QHostAddress(ip_t),port);//创建服务qDebug()<<my_s->errorString();if(sta){ser_sta = true;ui->history->append("server open success.");ui->start_bt->setText("关闭服务");// disable the button of start}else{ui->history->append(my_s->errorString());}

发送处理

connect(my_s,&QTcpServer::newConnection,this,[=](){// 自定义匿名的槽函数,用于获取连接的套接字对象m_tcp = my_s->nextPendingConnection();//m_status->setPixmap(QPixmap(":/img/status_1.png").scaled(20,20));//scaled 一个缩放函数,等比例//检测是否可以接收数据,也是信号量ui->history->append("a new client connected");connect(m_tcp,&QTcpSocket::readyRead,this,[=](){qDebug() << "cnt_sta: "<<cnt_sta;//read and show the tcp client's data.QByteArray data = m_tcp->readAll();//   m_tcp->write(data);ui->history->append("client: " + data);});connect(m_tcp,&QTcpSocket::disconnected,this,[=](){//disconnected ,set icon.m_status->setPixmap(QPixmap(":/img/status_0.png").scaled(20,20));//scaled 一个缩放函数,等比例m_tcp->close();
//            deletem_tcp->deleteLater();//释放对象,其实最后m_s释放的时候,他也会释放,这里手动释放,也可以使用delete。});//img//设置100ms 的定时器触发信号
//        uchar cout = 0;QTimer *timer = new QTimer(this);connect(timer, &QTimer::timeout, this, [=](){imageCapture->captureToFile("D:/qt_rec.jpg");//捕获并存储一帧图像cout++;QFile file("D:/qt_rec.jpg");QByteArray data;bool a= file.open(QIODevice::ReadOnly);if(a){data=file.readAll();file.close();}qint64 ssize= data.size();qDebug() <<"jpg size :"<<ssize;QString str_len = "img"+QString::number(ssize)+'\n';m_tcp->write(str_len.toUtf8());//发送图像大小信息//延时,防止过快的发送信号到达客户端,客户端一次性读取了长度信息和图像数据Delay_MSec(10);;//延时10msqint32  len=0;if(m_tcp->isValid())//发送图像数据  下面循环可以直接用len = m_tcp->write(data);替代while(ssize){len = m_tcp->write(data);qDebug() << len;ssize -= len;if(ssize<=0) break;}if(cout==3){cout = 0;//做另外的事情}});timer->start(100);//100ms一次});

延时函数

不阻塞延时

void MainWindow::Delay_MSec(unsigned int msec)
{QEventLoop loop;//定义一个新的事件循环QTimer::singleShot(msec, &loop, SLOT(quit()));//创建一个单次定时器,msec毫秒后执行槽函数,槽函数为循环的退出函数loop.exec();//事件循环开始执行,程序会卡在这里,直到定时时间到,循环退出
}

客户端

接收图片,存储,或者直接展示。
先存储后展示。

建立连接

    unsigned short port  = ui->port->text().toUShort();QString  ip_t = ui->ip->text();m_tcp->connectToHost(QHostAddress(ip_t),port);qDebug()<<"启动连接";qDebug()<<m_tcp->errorString();

接收数据和处理

bool tcp_sig =false;//0表示第一次读,1表示为接下来接收图片connect(m_tcp,&QTcpSocket::connected,this,[=](){qDebug()<<"连接成功";// 自定义匿名的槽函数,用于获取连接的套接字对象//检测是否可以接收数据,也是信号量});connect(m_tcp,&QTcpSocket::errorOccurred,this,[=](){qDebug()<<"连接错误";qDebug()<<m_tcp->error();//输出的错误信息更完整});connect(m_tcp,&QTcpSocket::readyRead,this,[=](){//read and show the tcp client's data.//qint32 len,allsize=0,nowsize = 0;if(tcp_sig==0){QByteArray rdata = m_tcp->readLine(1024);
//            qDebug()<<"recive some data len of"<< rdata.size();
//            qDebug()<< rdata;
//第一次读取区分数据类型,是图片还是其他数据if(rdata.startsWith("img")){tcp_sig = 1;//是图片,修改标志rdata.erase(rdata.cbegin(),rdata.cbegin()+3);//去除前向标志rdata.removeLast();//去除回车allsize = rdata.toUInt();qDebug()<< "rec imgsize = "<<allsize;}else if(rdata.startsWith("data")){//QString 其他数据处理rdata.erase(rdata.cbegin(),rdata.cbegin()+4);qDebug()<<" png err";}}else if(tcp_sig == 1){//读取图片Delay_MSec(10);//延时一定需要,根据具体情况进行设定大小,这个延时的目的是等待缓冲区接收完发送的数据while(1){QByteArray img_data = m_tcp->readAll();len  = img_data.size();qDebug()<<"img len size" << img_data.size() ;nowsize +=  len;qDebug()<<nowsize;
//                imgarr.append(img_data);if(nowsize>=allsize){QFile file("G:/qt_img/22.jpg");file.open(QIODevice::WriteOnly| QIODevice::Truncate);//QIODevice::Truncate这个必须要len  = file.write(img_data);
//                    img_data.clear();nowsize = 0;qDebug()<< "img loacal size = " <<len ;file.close();QImage image("G:/qt_img/22.jpg");if(image.isNull()){qDebug()<<"test and png err";}else{QPixmap pic=QPixmap::fromImage(image);ui->img_view->setPixmap(pic.scaled(ui->img_view->size(),Qt::KeepAspectRatio,Qt::SmoothTransformation));//有更高效的方法,此外还可以不需要先存储,直接将接收的数据转为图片进行显示}}if(len==0) break;}//QByteArray da = m_tcp->readAll();//本来是想出现错误,读取掉错乱数据,经过推测和测试没有作用tcp_sig = 0;nowsize = 0;}else{QByteArray da = m_tcp->readAll();//读取掉错乱数据}});

图片的接收和展示还可以进行简化,这是一个实现方式。整体上比较简单吧

其实传输过程中的主要问题就是图片接收的时候,有时候会出现失帧,和读取不全,图片只展示一半的问题,显然是数据没有读取完整,或者发送不完整。想通过图片大小的方式来确保读取完整的图片。
通过调试显示,发送是可以直接完全一次性发送的
接收有时候会出问题,一下多一下少。
而且通过循环的方式来不断读取缓冲区直到那么大小的数据的想法是错误的。这应该和QTTCP缓冲区接收的实现方式有问题,涉及到信号量readyRead,目前来看这个信号量是在服务端发送出数据,客户端缓冲区会接收数据,然后就会发送出这个信号量,但是他是接收到数据就发送了,也就是说不保证缓冲区已经完全接收完数据了。你此时通过信号量触发进行读取缓冲区接收的数据可能是不完整的,如果图片过大,而且存在网络延迟的情况下。这也是在接收的函数中要进行延时的原因,这个才算是解决问题的关键,非常重要

缓冲区接收的一些想法

此外,关于缓冲区接收,他似乎分为多个通道,看到文档中有指定通道进行读取数据, 但是我没有深入研究,不知道是怎么使用和实现的。
目前测出的情况是,当接收到readyRead,并使用read之类的读取函数时,在这个读取的槽函数中,你所读取的缓冲区似乎就是固定的了,相当于是某个时刻固定的。比如传来50k的数据,主缓冲区接收到了20k,此时发送出了readyRead信号,并触发了槽函数,这个过程假设又接收了10k数据(并行),但是还有20k数据没有接收(也有可能这个缓冲区是动态变化的,一开始20k,发现不够用,要动态扩展所以耗时更多),此时调用read之类的函数,相当于给你一个临时变量固定的一个带有30k数据的次缓冲区让你读取,当你将这个缓冲区读取完的时候,你就读不到数据了,应为次缓冲区数据已经被读取完了,如果此时你设立一个循环,如果读取的数据没有达到50k,就继续读,但是所读取的都是次缓冲区,这个是已经空了的,那么你就会进入死循环,一直无法退出。本人有幸进入过,很是掉头发。
主缓冲区和次缓冲区的存在首先是我自己猜想的,另外帮助理解是主缓冲区相当于定义的全局变量a,而次缓冲区可以理解为某个函数定义的和全局变量名字相同的局部变量a,而在这个函数中,访问变量a时是访问局部变量a,而不是全局变量a。主缓冲区就是全局变量a,次缓冲区是局部变量a。
所以说数据过大,或者延时高的情况,readyRead触发的读取槽函数时,需要先进行延时的处理,因为你一定调用了read函数,不管是read()、readAll()等,只要调用了read之类的函数,就会固定化次缓冲区的内容。为了确保接收完全,需要进行延迟。当然一次readyRead的触发,也意味着一次数据的传输到来。
观察过测试输出结果,如果你一开始读取30k,并调用了read函数,也就是主缓冲区还剩下20k数据,此时,又发送来10k数据,触发readyRead,在槽函数中调用readAll,这样可以读取到30k的数据,原来的20k加上新传输的10k。这也再次证明了前面设想的主次缓冲区的想法。至少存在这么一个机制。

以上是经验之谈,都是测试出来的,没有去查其具体的机制,如有什么错误,欢迎大佬指证,大家要是有什么其他问题,也欢迎评论区留言讨论。

这篇关于QT6.0以上版本实现实时图像传输的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1028916

相关文章

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

ONLYOFFICE 8.1 版本桌面编辑器测评

在现代办公环境中,办公软件的重要性不言而喻。从文档处理到电子表格分析,再到演示文稿制作,强大且高效的办公软件工具能够极大提升工作效率。ONLYOFFICE 作为一个功能全面且开源的办公软件套件,一直以来都受到广大用户的关注与喜爱。而其最新发布的 ONLYOFFICE 8.1 版本桌面编辑器,更是带来了诸多改进和新特性。本文将详细评测 ONLYOFFICE 8.1 版本桌面编辑器,探讨其在功能、用户

17.用300行代码手写初体验Spring V1.0版本

1.1.课程目标 1、了解看源码最有效的方式,先猜测后验证,不要一开始就去调试代码。 2、浓缩就是精华,用 300行最简洁的代码 提炼Spring的基本设计思想。 3、掌握Spring框架的基本脉络。 1.2.内容定位 1、 具有1年以上的SpringMVC使用经验。 2、 希望深入了解Spring源码的人群,对 Spring有一个整体的宏观感受。 3、 全程手写实现SpringM

通过SSH隧道实现通过远程服务器上外网

搭建隧道 autossh -M 0 -f -D 1080 -C -N user1@remotehost##验证隧道是否生效,查看1080端口是否启动netstat -tuln | grep 1080## 测试ssh 隧道是否生效curl -x socks5h://127.0.0.1:1080 -I http://www.github.com 将autossh 设置为服务,隧道开机启动

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测 目录 时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测基本介绍程序设计参考资料 基本介绍 MATLAB实现LSTM时间序列未来多步预测-递归预测。LSTM是一种含有LSTM区块(blocks)或其他的一种类神经网络,文献或其他资料中LSTM区块可能被描述成智能网络单元,因为

vue项目集成CanvasEditor实现Word在线编辑器

CanvasEditor实现Word在线编辑器 官网文档:https://hufe.club/canvas-editor-docs/guide/schema.html 源码地址:https://github.com/Hufe921/canvas-editor 前提声明: 由于CanvasEditor目前不支持vue、react 等框架开箱即用版,所以需要我们去Git下载源码,拿到其中两个主

android一键分享功能部分实现

为什么叫做部分实现呢,其实是我只实现一部分的分享。如新浪微博,那还有没去实现的是微信分享。还有一部分奇怪的问题:我QQ分享跟QQ空间的分享功能,我都没配置key那些都是原本集成就有的key也可以实现分享,谁清楚的麻烦详解下。 实现分享功能我们可以去www.mob.com这个网站集成。免费的,而且还有短信验证功能。等这分享研究完后就研究下短信验证功能。 开始实现步骤(新浪分享,以下是本人自己实现

基于Springboot + vue 的抗疫物质管理系统的设计与实现

目录 📚 前言 📑摘要 📑系统流程 📚 系统架构设计 📚 数据库设计 📚 系统功能的具体实现    💬 系统登录注册 系统登录 登录界面   用户添加  💬 抗疫列表展示模块     区域信息管理 添加物资详情 抗疫物资列表展示 抗疫物资申请 抗疫物资审核 ✒️ 源码实现 💖 源码获取 😁 联系方式 📚 前言 📑博客主页:

探索蓝牙协议的奥秘:用ESP32实现高质量蓝牙音频传输

蓝牙(Bluetooth)是一种短距离无线通信技术,广泛应用于各种电子设备之间的数据传输。自1994年由爱立信公司首次提出以来,蓝牙技术已经经历了多个版本的更新和改进。本文将详细介绍蓝牙协议,并通过一个具体的项目——使用ESP32实现蓝牙音频传输,来展示蓝牙协议的实际应用及其优点。 蓝牙协议概述 蓝牙协议栈 蓝牙协议栈是蓝牙技术的核心,定义了蓝牙设备之间如何进行通信。蓝牙协议

Visual Studio中,MSBUild版本问题

假如项目规定了MSBUild版本,那么在安装完Visual Studio后,假如带的MSBUild版本与项目要求的版本不符合要求,那么可以把需要的MSBUild添加到系统中,然后即可使用。步骤如下:            假如项目需要使用V12的MSBUild,而安装的Visual Studio带的MSBUild版本为V14。 ①到MSDN下载V12 MSBUild包,把V12包解压到目录(