Qt QWebSocket实战:从零构建实时双向通信服务端与客户端
1. 项目概述为什么选择QWebSocket在开发需要实时双向通信的桌面应用时我们常常面临选择。传统的HTTP轮询效率低下长轮询又不够优雅。而WebSocket协议作为HTML5标准的一部分为我们提供了全双工、低延迟的通信能力。对于Qt开发者而言QWebSocket模块的出现让我们能够以非常Qt的方式在服务端和客户端轻松集成WebSocket功能无需引入复杂的第三方库。这个项目就是一次基于QWebSocket的完整实践。我将带你从零开始构建一个可运行的服务端和一个客户端并附上可以直接复制粘贴使用的详细代码。无论你是想为你的Qt应用添加一个实时聊天模块还是需要实现一个轻量级的实时数据监控面板这套代码都能提供一个坚实的起点。我们将重点关注连接建立、消息收发、错误处理以及连接生命周期管理这些核心环节这些都是实际项目中必然会遇到的。2. 环境准备与项目配置在开始敲代码之前确保你的开发环境已经就绪。你需要一个安装了Qt的开发环境版本建议在Qt 5.3或更高因为QWebSocket模块是在Qt 5.3中引入的。Qt Creator是一个不错的选择。2.1 在.pro文件中引入模块这是第一步也是最关键的一步。无论你的项目是新建的还是已有的都需要在.pro项目文件中添加WebSockets模块的依赖。打开你的.pro文件添加如下一行QT core gui websockets如果你的项目是纯控制台应用没有GUI那么gui可以不加但core和websockets是必须的。保存.pro文件后Qt Creator会重新解析项目这时你就可以在代码中#include QWebSocket和#include QWebSocketServer了。注意有时候添加模块后代码中#include仍然报错这可能是因为Qt Creator的代码模型没有及时更新。尝试执行“构建”-“执行qmake”或者直接关闭再重新打开项目通常可以解决。2.2 服务端与客户端的设计思路在动手之前我们先理清思路。一个典型的WebSocket应用包含两个角色服务端 (QWebSocketServer)它像一个电话总机监听特定的网络端口等待客户端的连接请求。一旦有客户端拨号进来发起连接它就创建一个专属的QWebSocket对象来代表这个具体的连接负责与此客户端进行后续的所有通信。客户端 (QWebSocket)它像一部电话主动向服务端的地址和端口发起连接。连接成功后它就可以通过这个QWebSocket对象向服务端发送消息并接收服务端的回复或推送。我们的目标是构建一个简单的“回声”服务端和一个测试客户端。服务端会将收到的任何文本消息原样发回给发送它的客户端这能最直观地验证双向通信是否畅通。3. 服务端实现详解服务端是整个通信的核心我们创建一个继承自QObject的类来实现它这样能方便地使用Qt的信号槽机制。3.1 创建WebSocket服务器首先我们需要实例化一个QWebSocketServer对象。它的构造函数需要三个参数服务器名称可以任意字符串用于标识服务器。运行模式通常是QWebSocketServer::NonSecureMode非安全模式即ws://或SslMode安全模式即wss://。我们这里先用NonSecureMode。父对象用于Qt对象树的内存管理。// websocketserver.h #ifndef WEBSOCKETSERVER_H #define WEBSOCKETSERVER_H #include QObject #include QWebSocketServer #include QWebSocket #include QList class WebSocketServer : public QObject { Q_OBJECT public: explicit WebSocketServer(quint16 port, QObject *parent nullptr); ~WebSocketServer(); private slots: void onNewConnection(); void onTextMessageReceived(const QString message); void onSocketDisconnected(); private: QWebSocketServer *m_pWebSocketServer; QListQWebSocket * m_clients; // 用于管理所有连接的客户端 }; #endif // WEBSOCKETSERVER_H// websocketserver.cpp #include “websocketserver.h” #include QDebug WebSocketServer::WebSocketServer(quint16 port, QObject *parent) : QObject(parent) , m_pWebSocketServer(new QWebSocketServer(QStringLiteral(“Echo Server”), QWebSocketServer::NonSecureMode, this)) { // 尝试监听指定端口 if (m_pWebSocketServer-listen(QHostAddress::Any, port)) { qDebug() “Echo Server listening on port” port; // 连接新连接信号 connect(m_pWebSocketServer, QWebSocketServer::newConnection, this, WebSocketServer::onNewConnection); } else { qDebug() “Failed to start server on port” port “:” m_pWebSocketServer-errorString(); } }这里有几个关键点QHostAddress::Any表示服务器将监听所有可用的网络接口如本地回环127.0.0.1和局域网IP。如果你只想本地测试可以换成QHostAddress::LocalHost。listen()方法的返回值很重要必须检查。失败的原因可能是端口已被占用或者没有权限如Linux/Mac下监听1024以下端口需要root权限。3.2 处理新客户端连接当有新的客户端连接时QWebSocketServer会发出newConnection()信号。我们在对应的槽函数中需要获取代表这个新连接的QWebSocket对象。void WebSocketServer::onNewConnection() { // 获取等待连接的客户端套接字 QWebSocket *pSocket m_pWebSocketServer-nextPendingConnection(); if (!pSocket) { return; } qDebug() “Client connected from:” pSocket-peerAddress().toString() “:” pSocket-peerPort(); // 将新客户端加入管理列表 m_clients.append(pSocket); // 连接该客户端套接字的相关信号 connect(pSocket, QWebSocket::textMessageReceived, this, WebSocketServer::onTextMessageReceived); connect(pSocket, QWebSocket::disconnected, this, WebSocketServer::onSocketDisconnected); // 可选向新连接的客户端发送欢迎消息 pSocket-sendTextMessage(QStringLiteral(“Welcome to Echo Server!”)); }nextPendingConnection()这个方法至关重要它从服务器的待处理连接队列中取出下一个连接并返回一个QWebSocket对象。这个对象就是后续与该特定客户端通信的句柄。连接管理我们将每个新的QWebSocket指针存入一个QList中。这在广播消息向所有客户端发送消息或进行连接统计时非常有用。但务必注意内存管理确保在客户端断开时将其从列表中移除并删除。信号连接我们连接了两个关键信号textMessageReceived(const QString )当客户端发送文本消息时触发。disconnected()当客户端连接断开时触发。3.3 处理消息与连接断开消息处理和连接清理是服务端稳定运行的关键。void WebSocketServer::onTextMessageReceived(const QString message) { // 获取发送消息的客户端对象 QWebSocket *pSender qobject_castQWebSocket *(sender()); if (!pSender) { return; } qDebug() “Message from client [” pSender-peerAddress().toString() “]:” message; // 实现“回声”功能将消息发回给发送者 pSender-sendTextMessage(QStringLiteral(“Echo: %1”).arg(message)); // 示例广播消息给所有客户端非回声模式 // for (QWebSocket *client : qAsConst(m_clients)) { // if (client ! pSender client-isValid()) { // 不发给发送者自己 // client-sendTextMessage(QString(“Broadcast: %1”).arg(message)); // } // } } void WebSocketServer::onSocketDisconnected() { QWebSocket *pClient qobject_castQWebSocket *(sender()); qDebug() “Client disconnected:” (pClient ? pClient-peerAddress().toString() : “Unknown”); if (pClient) { // 从管理列表中移除 m_clients.removeAll(pClient); // 断开所有信号连接 pClient-deleteLater(); // 安排对象在事件循环中安全删除 } }sender()函数在槽函数中sender()返回发出信号的对象的指针。我们通过qobject_cast将其转换为QWebSocket*以确定是哪个客户端发送了消息或断开了连接。这是一个非常Qt风格的用法。消息发送使用sendTextMessage()发送文本消息。QWebSocket也支持发送二进制数据sendBinaryMessage。连接清理在disconnected槽中必须将客户端从管理列表中移除并调用deleteLater()来安全删除对象。直接delete pClient在特定时序下可能导致程序崩溃deleteLater是更安全的做法。实操心得在实际项目中m_clients列表的访问如在广播循环中可能会遇到多线程问题。如果你的服务端逻辑复杂或连接数极高需要考虑使用QMutex等机制保护共享数据或者将网络事件分发到单独的线程中处理。对于中小规模应用在主线程即对象所在线程中处理通常是安全的因为所有信号槽调用默认在同一线程。4. 客户端实现详解客户端相对更简单它的主要任务是连接服务器并处理收发消息。4.1 建立WebSocket连接我们同样创建一个类来封装客户端功能。// websocketclient.h #ifndef WEBSOCKETCLIENT_H #define WEBSOCKETCLIENT_H #include QObject #include QWebSocket class WebSocketClient : public QObject { Q_OBJECT public: explicit WebSocketClient(const QUrl url, QObject *parent nullptr); void sendMessage(const QString message); signals: void connected(); void messageReceived(const QString message); void disconnected(); private slots: void onConnected(); void onTextMessageReceived(const QString message); void onDisconnected(); private: QWebSocket m_webSocket; QUrl m_url; }; #endif // WEBSOCKETCLIENT_H// websocketclient.cpp #include “websocketclient.h” #include QDebug WebSocketClient::WebSocketClient(const QUrl url, QObject *parent) : QObject(parent) , m_url(url) { // 连接WebSocket对象的信号 connect(m_webSocket, QWebSocket::connected, this, WebSocketClient::onConnected); connect(m_webSocket, QWebSocket::textMessageReceived, this, WebSocketClient::onTextMessageReceived); connect(m_webSocket, QWebSocket::disconnected, this, WebSocketClient::onDisconnected); // 发起连接 qDebug() “Connecting to server...” url.toString(); m_webSocket.open(url); }连接URL格式为ws://host:port或wss://host:port加密。例如连接本地服务端QUrl(“ws://127.0.0.1:12345”)。自动重连上面的代码只在构造函数中连接一次。一个健壮的客户端应该实现断线重连逻辑可以在onDisconnected槽中启动一个QTimer延时后再次调用open()。4.2 处理连接事件与收发消息客户端的槽函数实现与服务端对应。void WebSocketClient::onConnected() { qDebug() “Connected to server!”; emit connected(); // 通知外部连接成功 // 连接成功后可以立即发送一条测试消息 // sendMessage(“Hello Server!”); } void WebSocketClient::onTextMessageReceived(const QString message) { qDebug() “Message from server:” message; emit messageReceived(message); // 将消息转发给UI或其他业务模块 } void WebSocketClient::onDisconnected() { qDebug() “Disconnected from server.”; emit disconnected(); } void WebSocketClient::sendMessage(const QString message) { if (m_webSocket.state() QAbstractSocket::ConnectedState) { m_webSocket.sendTextMessage(message); qDebug() “Sent message:” message; } else { qDebug() “Cannot send message, socket is not connected.”; } }状态检查在sendMessage中我们检查了socket的状态。这是一个好习惯避免在连接不可用时尝试发送数据。信号转发我们定义了几个信号connected,messageReceived,disconnected。这样设计可以将网络层与UI层或业务逻辑层解耦。例如你的主窗口可以连接这些信号来更新界面状态。5. 主程序与测试运行有了服务端和客户端类我们需要一个main函数来启动它们。5.1 服务端主程序// server_main.cpp #include QCoreApplication #include “websocketserver.h” int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // 在12345端口启动服务端 WebSocketServer server(12345); return a.exec(); }这是一个控制台应用使用QCoreApplication。编译运行后你会看到“Echo Server listening on port 12345”的输出。5.2 客户端主程序带简单UI为了直观测试我们给客户端加一个最简单的命令行交互界面。// client_main.cpp #include QCoreApplication #include QTextStream #include QTimer #include “websocketclient.h” int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QTextStream cin(stdin); QTextStream cout(stdout); WebSocketClient client(QUrl(“ws://127.0.0.1:12345”)); QObject::connect(client, WebSocketClient::connected, [cout](){ cout “*** Connected to server. Type your message (type ‘exit’ to quit):” endl; cout.flush(); }); QObject::connect(client, WebSocketClient::messageReceived, [cout](const QString msg){ cout “ ” msg endl; cout.flush(); }); QObject::connect(client, WebSocketClient::disconnected, [a](){ qDebug() “Connection closed. Exiting.”; a.quit(); }); // 简单的事件循环读取命令行输入 QTimer::singleShot(0, [client, cin, cout, a](){ QString line; while (true) { cout “ “; cout.flush(); line cin.readLine().trimmed(); if (line.isEmpty()) { continue; } if (line.compare(“exit”, Qt::CaseInsensitive) 0) { qDebug() “Exiting...”; a.quit(); break; } client.sendMessage(line); } }); return a.exec(); }这个客户端会连接本地服务端连接成功后你可以在命令行输入任何文本客户端会将其发送给服务端并立即收到服务端的“回声”回复显示在屏幕上。5.3 编译与运行分别创建两个Qt项目或在一个项目的两个子目录中正确配置.pro文件QT core websockets对于服务端和命令行客户端可能就够了如果客户端有GUI则需要加gui和widgets。先编译运行服务端程序确保它成功监听端口。再编译运行客户端程序。观察两者的控制台输出并在客户端输入消息进行测试。6. 进阶话题与性能调优一个基础的“回声”服务器跑起来后我们可以考虑更多实际场景下的问题。6.1 心跳机制与连接保活在网络不稳定的环境下TCP连接可能“半死不活”即系统未及时感知断开。为了检测这种死连接需要实现心跳机制。服务端实现思路为每个连接的QWebSocket关联一个QTimer。定时如每30秒向客户端发送一个特定的“心跳”消息如字符串“PING”。客户端收到后需立即回复“PONG”。服务端在另一个定时器里检查如果某个连接在超时时间内如60秒未收到任何消息包括PONG和业务消息则判定其失效主动断开连接。客户端实现思路类似可以主动向服务端发送心跳并期待回复。6.2 二进制数据传输除了文本QWebSocket也完美支持二进制数据适用于传输图片、文件或自定义协议包。// 发送二进制数据 QByteArray data ...; // 你的图片或文件数据 pWebSocket-sendBinaryMessage(data); // 接收二进制数据 connect(pWebSocket, QWebSocket::binaryMessageReceived, this, [](const QByteArray message){ // 处理二进制消息 });注意事项传输大文件时不宜一次性调用sendBinaryMessage这可能导致内存压力和网络阻塞。应考虑分片传输定义自己的协议头包含文件信息、分片序号等。6.3 多线程与连接管理当需要处理成千上万的并发连接时单线程模型会成为瓶颈。Qt提供了几种方案每连接一线程传统但资源消耗大不推荐用于高并发。线程池使用QThreadPool和QRunnable将收到消息后的业务逻辑处理任务丢到线程池中网络IO仍在主线程。这是比较常见的折中方案。基于QAbstractSocket的非阻塞IO与多线程QWebSocket底层基于QTcpSocket你可以深入底层使用QThread创建多个工作线程每个线程运行一个QEventLoop并使用moveToThread将一批socket移到工作线程中进行读写。这需要更精细的控制。对于绝大多数应用主线程处理网络IO配合线程池处理业务逻辑已经足够。关键在于不要在textMessageReceived或binaryMessageReceived的槽函数中执行耗时操作如复杂的数据库查询、图像处理应尽快返回将任务派发出去。6.4 错误处理与日志完善的错误处理是服务稳定的基石。连接QWebSocket::error信号处理网络错误。检查sendTextMessage返回值虽然它是异步的但可以检查state()。记录日志使用qDebug(),qWarning(),qCritical()等分级记录日志。生产环境应考虑使用更专业的日志库并记录连接、断开、消息流量等关键信息便于故障排查。7. 常见问题排查与调试技巧在实际开发中你肯定会遇到各种问题。这里记录一些典型场景和排查思路。问题1服务端启动失败提示“Address already in use”原因端口被占用。解决换一个端口号。在命令行Linux/Mac用lsof -i :端口号Windows用netstat -ano | findstr :端口号查找占用进程并结束它。确保你的程序没有重复启动。问题2客户端连接失败提示“Connection refused”原因服务端没有运行。服务端监听的IP地址和客户端连接的地址不匹配例如服务端监听127.0.0.1客户端连接192.168.1.x。防火墙阻止了连接。解决确认服务端程序已运行并成功监听。服务端使用QHostAddress::Any监听所有IP。客户端检查连接URL是否正确。临时关闭防火墙或添加规则放行对应端口。问题3能连接但收不到消息或消息延迟原因代码逻辑错误信号槽没有正确连接。事件循环阻塞在槽函数中执行了耗时操作导致Qt事件循环被卡住无法及时处理网络事件。排查在connected(),textMessageReceived()等槽函数开始处添加qDebug()输出确认信号是否触发。检查是否在主线程UI线程中执行了sleep()或长时间循环。任何可能阻塞主线程的操作都应移到工作线程。问题4内存泄漏连接数增多后内存持续增长原因QWebSocket对象没有正确删除。检查确保在disconnected()信号对应的槽中调用了deleteLater()。确保从管理列表如m_clients中移除了该对象指针。可以使用Qt Creator的调试模式或工具如heob、valgrind检测内存泄漏。调试技巧使用qDebug()进行跟踪在关键步骤连接、断开、收/发消息打印信息这是最直接的调试手段。利用Qt Creator的调试器可以设置断点查看QWebSocket对象的状态state()、错误error()等属性。网络抓包工具对于复杂的协议问题使用Wireshark等工具抓取localhost或本机网卡上的WebSocket流量可以清晰看到握手过程和数据帧是排查网络层问题的终极武器。这套基于QWebSocket的服务端和客户端实现代码结构清晰功能完整可以直接作为你项目中的通信模块使用。从简单的回声测试到复杂的实时数据交互其核心框架是相通的。理解并处理好连接管理、消息分发、错误处理和资源释放就能构建出稳定可靠的实时通信功能。

相关新闻

参与开源项目的收获与反思:代码写得再好,文档不好没人用

参与开源项目的收获与反思:代码写得再好,文档不好没人用

参与开源项目的收获与反思:代码写得再好,文档不好没人用 一、一份 2000 行的 Pull Request,被 Reviewer 打了回来 PR 描述写了 300 字,代码通过了所有单元测试,性能提升了 12%。但 Reviewer 的第一条评论只有五个字&…

2026/7/27 7:44:32 阅读更多 →
天气丹料体拿货内行话:源头工厂拆解韩系贵妇霜的利润与防坑术

天气丹料体拿货内行话:源头工厂拆解韩系贵妇霜的利润与防坑术

做韩系贵妇霜白牌定制的老板,别上来就问“能不能做某品牌同款”,先算清楚料体成本占比和乳化粒径公差。我在这行十几年,见过太多拿着大牌图片来询价,一听正规料体38元/kg就说贵,转头去拿18元/kg的“矿物油香精”垃圾料…

2026/7/27 7:43:31 阅读更多 →
品牌口红小样进货门道:别被低价包材骗了,工艺底牌全拆解

品牌口红小样进货门道:别被低价包材骗了,工艺底牌全拆解

拿着大牌口红小样图片来问代工价,开口就要一毛五一支,还要求灌装正装同款料体——这种单子我直接劝退,因为做出来就是给自己埋雷。▼ 源头车间质检备案与合作授权说明 ▼小样灌装的精度硬伤:料体成本只是冰山一角很多人以为小样就…

2026/7/27 7:43:31 阅读更多 →

最新新闻

AI动态调整销售目标的技术实现与实战经验

AI动态调整销售目标的技术实现与实战经验

1. 为什么销售目标需要动态调整?在传统销售管理中,目标设定往往采用"去年业绩固定增长率"的简单模式。我在某快消品企业任职时就深有体会:年初制定的季度目标,到第三个月市场突然出现新竞争者,原有目标立刻变…

2026/7/27 7:59:43 阅读更多 →
TMS320C3x DSP软件UART实现:中断驱动全双工串口通信详解

TMS320C3x DSP软件UART实现:中断驱动全双工串口通信详解

1. 项目概述与核心价值在嵌入式开发领域,串行通信是连接设备与外部世界的“血管”。通用异步收发传输器(UART)作为最经典、最普遍的串行通信接口,其重要性不言而喻。然而,并非所有微控制器或数字信号处理器&#xff08…

2026/7/27 7:59:43 阅读更多 →
OpenClaw本地AI代理架构解析与性能优化实践

OpenClaw本地AI代理架构解析与性能优化实践

1. OpenClaw:重新定义本地AI代理的边界当我第一次在M2 Mac mini上成功运行OpenClaw时,这个能自动整理会议纪要并推送到Slack的AI代理彻底改变了我的工作效率认知。作为GitHub史上增长最快的开源项目之一,OpenClaw在66天内星标数从9,000飙升至…

2026/7/27 7:59:43 阅读更多 →
深入TM4C129XKCZAD GPIO:从引脚复用到中断与触发配置实战

深入TM4C129XKCZAD GPIO:从引脚复用到中断与触发配置实战

1. 项目概述与GPIO核心价值在嵌入式开发的世界里,无论你是驱动一颗LED,还是与复杂的传感器阵列通信,都绕不开一个最基础、最核心的接口:通用输入/输出,也就是我们常说的GPIO。它就像是微控制器伸向外部世界的“触手”和…

2026/7/27 7:59:43 阅读更多 →
Claude HUD一款好用的Claude Code状态栏插件

Claude HUD一款好用的Claude Code状态栏插件

小伙伴们大家好,我是小溪,见字如面。最近在使用Claude Code CLI时因为无法实时查看上下文窗口信息,导致经常上下文频繁压缩打断开发进度体感非常不好,在逛Github时找到一个不错的项目,这里记录一下配置使用方式。当前使…

2026/7/27 7:59:43 阅读更多 →
AI的技术演进路线

AI的技术演进路线

一、前言:这里来说说ai近几年的技术演进和痛点解决,我会从prompt engineering->context engineering->skill->harness engineering到当下的loop engineering 二、Prompt Engineering(2021 年上半年) 1.Prompt Engineeri…

2026/7/27 7:58:40 阅读更多 →

日新闻

【JAVA毕设源码分享】基于SpringBoot的社区智能垃圾管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

【JAVA毕设源码分享】基于SpringBoot的社区智能垃圾管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/27 0:00:54 阅读更多 →
SPI实战指南:从时钟模式到寄存器配置,解决嵌入式通信难题

SPI实战指南:从时钟模式到寄存器配置,解决嵌入式通信难题

1. 项目概述:从寄存器手册到实战指南 如果你手头有一份类似德州仪器(TI)TMS320x240xA系列DSP的SPI模块技术手册,看着里面密密麻麻的寄存器位定义、时序图和公式,是不是感觉头大?这份资料虽然权威&#xff0…

2026/7/27 0:00:54 阅读更多 →
【JAVA毕设源码分享】基于springboot的水果购物管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

【JAVA毕设源码分享】基于springboot的水果购物管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/27 0:00:54 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/27 4:33:59 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/27 6:31:56 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/27 4:01:12 阅读更多 →

月新闻