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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/21856.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

KAN(Kolmogorov-Arnold Network)的理解 3

系列文章目录 第一部分 KAN的理解——数学背景 第二部分 KAN的理解——网络结构 第三部分 KAN的实践——第一个例程 文章目录 系列文章目录前言KAN 的第一个例程 get started 前言 这里记录我对于KAN的探索过程&#xff0c;每次会尝试理解解释一部分问题。欢迎大家和我一起讨…

百度/迅雷/夸克,网盘免费加速,已破!

哈喽&#xff0c;各位小伙伴们好&#xff0c;我是给大家带来各类黑科技与前沿资讯的小武。 之前给大家安利了百度网盘及迅雷的加速方法&#xff0c;详细方法及获取参考之前文章&#xff1a; 刚刚&#xff01;度盘、某雷已破&#xff01;速度50M/s&#xff01; 本次主要介绍夸…

Python sorted 用法:深入解析排序函数的奥秘

Python sorted 用法&#xff1a;深入解析排序函数的奥秘 在Python编程中&#xff0c;sorted函数是一个强大的工具&#xff0c;用于对可迭代对象进行排序。然而&#xff0c;它的用法和功能远不止表面看起来那么简单。本文将深入剖析sorted函数的四个方面、五个方面、六个方面和…

simulink基础学习笔记

写在前面 这个笔记是看B站UP 快乐的宇航boy 所出的simulink基础教程系列视频过程中记下来的&#xff0c;写的很粗糙不完整&#xff0c;也不会补。视频教程很细跟着做就行。 lesson1-7节的笔记up有&#xff0c;可以加up的群&#xff0c;里面大佬挺活跃的。 lesson8 for循环 For …

【C++初阶学习】第十二弹——stack和queue的介绍和使用

C语言栈&#xff1a;数据结构——栈(C语言版)-CSDN博客 C语言队列&#xff1a;数据结构——队列&#xff08;C语言版&#xff09;-CSDN博客 前言&#xff1a; 在之前学习C语言的时候&#xff0c;我们已经学习过栈与队列&#xff0c;并学习过如何使用C语言来实现栈与队列&…

Python | 平均绩点

字符串的概念和特点 字符串既可以使用单引号&#xff0c;也可以使用双引号""来创建 可以使用运算符来拼接字符串&#xff0c;并返回字符串拼接后的结果。 first_name "Tom" last_name "Jerry" full_name first_name " " &quo…

OCR图片转Excel表格:没结构化的弊端

随着OCR技术的不断发展&#xff0c;将表格图片转为excel已不再是难题&#xff0c;但是&#xff0c;目前市面上的程序还大多处于仅能将图片表格转为普通的excel格式阶段&#xff0c;而不能将其结构化&#xff0c;这样就会产生许多的弊端&#xff0c;具体弊端如下&#xff1a; &l…

数据容器的通用操作、字符串大小比较 总结完毕!

1.数据容器的通用操作 1&#xff09;五类数据容器是否都支持while循环/for循环 五类数据容器都支持for循环遍历 列表、元组、字符串都支持while循环&#xff0c;集合、字典不支持&#xff08;无法下标索引&#xff09; 尽管遍历的形式不同&#xff0c;但都支持遍历操作 2&a…

办公软件 Office 安装教程(亲测有效)

Office 现已更名为 Microsoft 365。习惯还是称作 Office。 1、Office 套装下载 Windows 的样子 这里下载的是最新版本的 O365ProPlus 安装完成后&#xff0c;点击关闭&#xff08;请先不要打开&#xff09;。 Mac 的样子 这里下载的是Office for Mac 2019&#xff08;更多版…

QNAP的jellyfin无法使用TMDB刮削的问题

解决方法很简单&#xff0c;修改QNAP的HOSTS文件 使用DNS Checker工具查询对应IP&#xff1a; image.tmdb.org api.themoviedb.org www.themoviedb.org 打开hosts文件&#xff0c;添加下列内容&#xff0c;wq保存 [~] # vi /etc/hosts 169.150.249.166 image.tmdb.org 103.2…

速递FineWeb:一个拥有无限潜力的15T Tokens的开源数据集

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则提…

【TB作品】MSP430F149单片机,6通道ADC,串口发送

功能 用6个引脚采集ADC&#xff0c;串口发送到电脑 部分程序 /** Otherwise, the compiler removes it* because it is not used for anything.*/ int kaiguanliang[4]; /* 四个开关量 */unsigned char adok_ifg 1; …

内核宕机自救

【问题】在测试内核级防篡改时&#xff0c;偶尔会遇到内核宕机的问题 【结论】进入紧急救援模式&#xff0c;将服务进程文件的start注释掉&#xff0c;即可 在Linux系统启动时&#xff0c;内核启动顺序选择界面&#xff0c;进入系统欢迎界面按上下左右键进入GRUB界面&#xff…

欧佩克+同意集体性减产延长,油价能否稳住?

KlipC报道&#xff1a;欧佩克组织同意将延长目前部分减产协议至2025年&#xff0c;以支撑油价。主要成员国把2023年11月宣布的日均220万桶的自愿减产措施延长至今年9月底&#xff0c;将在10月份根据市场情况开始缩减自愿减产规模。 高盛分析师表示&#xff0c;“我们认为这次欧…

python常见数据分析函数

apply DataFrame.apply(func, axis0, broadcastFalse, rawFalse, reduceNone, args(), **kwds) 第一个参数是函数 可以在Series或DataFrame上执行一个函数 支持对行、列或单个值进行处理 import numpy as np import pandas as pdf lambda x: x.max()-x.min()df pd.DataFrame(…

高端、大气、很牛B的免费wordpress模板主题

这是一款专为WordPress打造的极简主义风格主题&#xff0c;以白色和黑色为主色调&#xff0c;搭配红色点缀&#xff0c;营造出一种简洁、专业且具有视觉冲击力的效果。 该主题的设计理念是“简单即美”&#xff0c;旨在帮助用户快速搭建一个美观、易用的网站。它提供了丰富的自…

动态sql set标签 , trim标签

set标签 来看例子 set标案解决了逗号问题(当if条件不满足时,逗号无处安放的问题),我认为set标签可以识别这个问题,并自动忽略这个问题 <update id"update">update employee<set><if test"name!null">name#{name},</if><if te…

使用 Python 处理 Excel 表格数据的实用技巧

简介 在日常工作中&#xff0c;处理 Excel 表格数据是一项常见的任务。Python 提供了丰富的库和工具&#xff0c;能够帮助我们高效地处理 Excel 数据。本篇博客将介绍如何使用 openpyxl 库来读取 Excel 表格并进行数据处理&#xff0c;同时展示一个实际案例&#xff0c;说明如…

3079. 求出加密整数的和

给你一个整数数组 nums &#xff0c;数组中的元素都是 正 整数。定义一个加密函数 encrypt &#xff0c;encrypt(x) 将一个整数 x 中 每一个 数位都用 x 中的 最大 数位替换。比方说 encrypt(523) 555 且 encrypt(213) 333 。 请你返回数组中所有元素加密后的 和 。 示例 1&…

HTML基本元素包含HTML表单验证

可将以下代码复制另存为一个HTML文件浏览器打开自己去看看实际使用效果 <!DOCTYPE html> <html> <head> <meta charset"utf-8"><title>测试</title> </head> <body> <h1>很多事</h1> <h1><b&…