当前位置: 首页 > news >正文

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。这也再次证明了前面设想的主次缓冲区的想法。至少存在这么一个机制。

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

相关文章:

  • k8s系列-Listen: listen tcp :53: bind: permission denied
  • git拉去代码报错“Failed to connect to 127.0.0.1 port 31181: Connection refused“
  • 宝塔面板搭建thinkphp(fastadmin)项目注意事项
  • DKTCDR:Domain-Oriented Knowledge Transfer for Cross-Domain Recommendation
  • LeetCode:环形链表II
  • Python | 平均绩点
  • Diffle-Hellman Key Exchange密钥交换
  • java面试题及答案2024,java2024最新面试题及答案(之一)
  • 【面试题】Node.js高频面试题
  • Android handler 一次通关
  • Go Modules 使用
  • 使用system verilog进行流水灯和VGA打印字符
  • CentOS 7基础操作01_安装CentOS 7操作系统
  • 【C语言】动态内存管理
  • 外星人Alienware m16R1 原厂Windows11系统 oem系统
  • 4个实用的微服务测试策略
  • CSS进阶篇--用CSS开启硬件加速来提高网站性能
  • HTTP那些事
  • js写一个简单的选项卡
  • Python爬虫--- 1.3 BS4库的解析器
  • rabbitmq延迟消息示例
  • select2 取值 遍历 设置默认值
  • SOFAMosn配置模型
  • swift基础之_对象 实例方法 对象方法。
  • Vue2 SSR 的优化之旅
  • vuex 学习笔记 01
  • 对象引论
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 构建工具 - 收藏集 - 掘金
  • 后端_MYSQL
  • 买一台 iPhone X,还是创建一家未来的独角兽?
  • 七牛云 DV OV EV SSL 证书上线,限时折扣低至 6.75 折!
  • 前端面试题总结
  • 使用阿里云发布分布式网站,开发时候应该注意什么?
  • 腾讯大梁:DevOps最后一棒,有效构建海量运营的持续反馈能力
  • 腾讯视频格式如何转换成mp4 将下载的qlv文件转换成mp4的方法
  • 我的业余项目总结
  • 吴恩达Deep Learning课程练习题参考答案——R语言版
  • ionic异常记录
  • 阿里云API、SDK和CLI应用实践方案
  • 阿里云IoT边缘计算助力企业零改造实现远程运维 ...
  • ​​​​​​​开发面试“八股文”:助力还是阻力?
  • ​比特币大跌的 2 个原因
  • ​什么是bug?bug的源头在哪里?
  • ######## golang各章节终篇索引 ########
  • #pragma once
  • (1)安装hadoop之虚拟机准备(配置IP与主机名)
  • (9)YOLO-Pose:使用对象关键点相似性损失增强多人姿态估计的增强版YOLO
  • (C语言)球球大作战
  • (pojstep1.1.1)poj 1298(直叙式模拟)
  • (pytorch进阶之路)扩散概率模型
  • (附源码)apringboot计算机专业大学生就业指南 毕业设计061355
  • (附源码)ssm高校运动会管理系统 毕业设计 020419
  • (附源码)计算机毕业设计SSM疫情社区管理系统
  • (几何:六边形面积)编写程序,提示用户输入六边形的边长,然后显示它的面积。