QT原生日志持久化方案:可配置、轮转、条件过滤的生产级实现
简介本资源是一套基于Qt框架实现的日志管理完整源码方案面向C桌面应用开发者尤其适合需要在跨平台GUI项目中集成轻量级、可配置日志功能的中级程序员。它解决了Qt原生不提供滚动日志机制的痛点支持按文件大小自动切分、时间戳标记、路径自定义及基础日志级别控制适用于调试监控、用户行为记录与系统稳定性追踪等实际场景。压缩包共6个文件2个cpp核心实现、1个h头文件、1个pro工程配置、1份中文说明文档、1个user开发环境配置总计仅6KB结构精简便于快速集成与二次开发。已有2383人学习下载读者可直接复用savelogaffair类模块掌握Qt QFile/QTextStream流式写入、QFileInfo大小检测、QDir路径管理等关键实践技巧并通过示例工程快速验证日志滚动逻辑与文件命名策略。1. 日志不是“写完就扔”QT项目里最常被低估的可靠性基建很多同学做QT项目时日志就是qDebug() xxx;一贴了事——调试时看着爽一到交付就翻车用户反馈“程序崩了但没留痕迹”自己复现时发现日志全挤在控制台里历史记录无从追溯更糟的是某次批量处理任务中途崩溃连最后一次成功写入哪条数据都找不到。这不是玄学是日志系统根本没设计成生产可用的状态。本方案解决的正是QT C工程中那个被长期轻视却决定项目健壮性的环节可配置、可轮转、可检索、带条件触发的本地日志持久化机制。它不依赖第三方库如spdlog纯QT原生实现适用于嵌入式设备、工业HMI、桌面工具等对部署包体积和运行时依赖敏感的场景。如果你正在做QT毕设、课设或小型商用软件且需要一份能直接集成、答辩时能讲清每行逻辑、上线后真能定位问题的日志模块这套方案就是为你写的。2. 需求拆解与技术选型为什么不用QFile直接追加为什么必须分文件日志看似简单但真实项目里有四个硬性约束直接决定了架构不能“图省事”时间维度隔离运维要求“按天归档”故障发生后需快速定位当日日志而非在GB级单文件里grep空间可控性嵌入式设备SD卡只有512MB日志不能无限增长需支持最大文件大小保留天数双策略条件触发写入不是所有日志都要落盘——仅当level Warning或包含关键词timeout时才写入文件避免调试日志污染生产记录线程安全与性能QT主界面频繁触发日志如传感器采样每秒10次写磁盘不能阻塞UI线程。提示用QFile::append()裸写是新手最常踩的坑。它没有缓冲、无锁保护、无错误重试高并发下极易丢日志或损坏文件。真正的日志模块核心不是“怎么写”而是“怎么不写错”。2.1 QT原生日志机制的局限与绕过路径QT自带qInstallMessageHandler()可接管所有qDebug/qInfo输出但默认handler只打到console或stderr。要落地到文件必须自定义handler。关键点在于不能在handler里直接打开/关闭文件句柄——每次调用都开闭文件I/O开销爆炸。正确做法是预创建QFile对象并保持打开状态配合QTextStream缓冲写入。// ✅ 正确单例管理日志文件流初始化时打开全程复用 class LogFileManager : public QObject { Q_OBJECT public: static LogFileManager* instance() { static LogFileManager inst; return inst; } void openLogFile(const QString path); // 仅在初始化/轮转时调用 void writeLog(const QString msg); // 高频调用仅操作已打开的QTextStream private: QFile m_file; QTextStream m_stream; };2.2 条件过滤引擎的设计不只是level判断“根据条件自动创建日志文件”中的“条件”实际包含三层过滤过滤层级判断依据示例场景实现方式Level过滤QtMsgType枚举值qWarning()强制落盘qDebug()仅在DEBUG模式写在message handler中解析msgType参数内容关键词过滤正则匹配日志字符串捕获所有含ERR_或timeout的行使用QRegularExpression(ERR_\w业务逻辑过滤外部信号/状态变量仅当m_isInCriticalSection true时记录操作链注入QMetaObject::invokeMethod()回调检查关键代码片段message handler核心逻辑void customMessageHandler(QtMsgType type, const QMessageLogContext context, const QString msg) { // Step 1: Level基础过滤生产环境屏蔽Debug if (type QtDebugMsg !qEnvironmentVariableIsSet(QT_LOG_DEBUG)) { return; } // Step 2: 构建结构化日志行时间等级文件行号内容 QByteArray localMsg msg.toLocal8Bit(); QString logLine QString([%1] [%2] %3:%4 - %5) .arg(QDateTime::currentDateTime().toString(yyyy-MM-dd hh:mm:ss.zzz)) .arg(levelToString(type)) .arg(QString(context.file).section(/, -1)) // 只取文件名 .arg(context.line) .arg(localMsg.constData()); // Step 3: 关键词动态过滤支持运行时热更新 static QRegularExpression keywordRegex; static QMutex regexMutex; regexMutex.lock(); if (LogConfig::instance()-keywordsChanged()) { keywordRegex.setPattern(LogConfig::instance()-keywordPattern()); } regexMutex.unlock(); bool shouldLog (type QtWarningMsg) || keywordRegex.match(logLine).hasMatch() || LogConfig::instance()-isForceLog(); if (shouldLog) { LogFileManager::instance()-writeLog(logLine); // 走异步写入队列 } }注意QRegularExpression编译耗时必须缓存编译结果。此处用静态变量双重检查锁DCLP避免重复编译实测正则匹配比QString::contains()慢3倍但灵活性不可替代。2.3 文件轮转策略按日期还是按大小我们全都要单一策略无法覆盖所有场景按日期轮转适合日志量稳定、需合规审计的场景如每天生成app_20240520.log按大小轮转适合突发流量场景如设备启动瞬间涌出2000条初始化日志本方案采用混合策略每日零点强制切新文件 单文件超10MB自动切分。关键设计点在于原子性切换——避免新旧文件同时写入导致数据错乱。void LogFileManager::checkAndRotate() { QFileInfo currentInfo(m_file.fileName()); QDateTime now QDateTime::currentDateTime(); // 条件1日期变更零点触发 bool dateChanged (currentInfo.created().date() ! now.date()); // 条件2文件超限10MB bool sizeExceeded (currentInfo.size() 10 * 1024 * 1024); if (dateChanged || sizeExceeded) { // 1. 关闭当前文件 m_stream.flush(); m_file.close(); // 2. 生成新文件名app_20240520_001.log序号防重名 QString newBaseName QString(app_%1_%2.log) .arg(now.date().toString(yyyyMMdd)) .arg(QString::number(m_rotationIndex, 10).rightJustified(3, 0)); QString newFullPath QDir(LogConfig::instance()-logPath()).filePath(newBaseName); // 3. 原子性重命名旧文件避免写入中断 QString oldPath m_file.fileName(); if (QFile::exists(oldPath)) { QFile::rename(oldPath, oldPath .archived); // 先改名再移动规避Windows锁文件问题 } // 4. 打开新文件确保目录存在 QDir().mkpath(LogConfig::instance()-logPath()); m_file.setFileName(newFullPath); if (m_file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) { m_stream.setDevice(m_file); } } }3. 总体架构设计三层解耦与线程安全模型整个日志模块划分为三个物理层严格遵循“输入-处理-输出”分离原则避免任意一层修改影响其他层层级组件职责线程归属接入层CustomMessageHandler接收QT所有日志源执行初步过滤与格式化任意线程QT消息处理器上下文调度层LogDispatcher单例接收格式化日志按优先级入队触发轮转检查主线程定时器驱动存储层LogFileManager管理文件句柄、缓冲流、轮转逻辑主线程避免多线程操作同一QFile提示QT中QFile非线程安全但QTextStream在QFile已打开状态下多线程写入同一QTextStream会导致内容交错。因此所有写入必须串行化——本方案通过QMetaObject::invokeMethod()将写入请求投递到主线程执行牺牲微小延迟换取绝对安全。3.1 核心类关系与生命周期图[QApplication] │ ▼ [CustomMessageHandler] →信号→ [LogDispatcher] │ ├─ QTimer::singleShot(100ms) → checkAndRotate() └─ queue → invokeMethod(this, doWrite, ...) ↓ [LogFileManager] ← manages → [QFile QTextStream]LogDispatcher不持有QFile仅负责排队和调度降低耦合LogFileManager构造即初始化文件析构时确保flush()和close()防止最后几条日志丢失所有跨线程调用使用Qt::QueuedConnection避免Qt::DirectConnection引发的竞态。3.2 配置中心用QSettings实现零重启热更新日志行为不应硬编码。本方案提供LogConfig单例底层基于QSettingsINI格式支持运行时修改# config.ini [Log] PathC:/MyApp/logs MaxFileSizeMB10 KeepDays7 KeywordPatternERR_\\w|timeout|FATAL EnableDebugLogfalse关键热更新逻辑class LogConfig : public QObject { Q_OBJECT public: static LogConfig* instance() { /* ... */ } // 检查配置是否变更用于正则重编译 bool keywordsChanged() { static QString lastPattern; QString current m_settings.value(Log/KeywordPattern, ).toString(); if (current ! lastPattern) { lastPattern current; return true; } return false; } QString keywordPattern() const { return m_settings.value(Log/KeywordPattern, ).toString(); } signals: void configChanged(); // 可连接到UI实时显示当前策略 private: QSettings m_settings; // 自动读取config.ini无需手动load };注意QSettings在Windows下默认存注册表为保证可移植性显式指定INI文件路径QSettings(config.ini, QSettings::IniFormat)。3.3 异步写入队列用QQueueQTimer模拟轻量级消息总线高频日志如传感器采样若每次writeLog()都同步刷盘UI会明显卡顿。解决方案内存缓冲定时批量落盘。class LogDispatcher : public QObject { Q_OBJECT public: void enqueueLog(const QString line) { QMutexLocker locker(m_queueMutex); m_logQueue.enqueue(line); // 首次入队即启动定时器避免空转 if (!m_timer-isActive()) { m_timer-start(50); // 50ms内攒批平衡延迟与吞吐 } } private slots: void flushBatch() { QListQString batch; { QMutexLocker locker(m_queueMutex); while (!m_logQueue.isEmpty() batch.size() 100) { batch.append(m_logQueue.dequeue()); } if (m_logQueue.isEmpty()) { m_timer-stop(); } } // 批量写入主线程安全 for (const QString line : batch) { LogFileManager::instance()-writeLog(line); } } private: QQueueQString m_logQueue; QMutex m_queueMutex; QTimer* m_timer new QTimer(this); };实测数据1000条日志同步写耗时~1200ms本方案批量写仅~80msSSDCPU占用下降70%。4. 关键实现细节与避坑指南那些让项目延期三天的细节日志模块看似简单但以下5个点是90%的QT初学者必然踩坑的位置按“现象→原因→解决”给出血泪经验4.1 现象日志文件突然变空或最后10秒日志全部丢失原因QTextStream缓冲区未及时刷新程序异常退出如abort()导致内存中日志未写入磁盘。解决在LogFileManager::writeLog()末尾强制m_stream.flush()在LogFileManager析构函数中再次flush()close()更进一步对关键日志如QtFatalMsg使用QFile::flush()绕过QTextStream缓冲。4.2 现象多线程环境下日志内容错乱出现半截中文或乱码原因QTextStream在多线程中共享同一QFile设备写入非原子操作。解决绝对禁止在子线程中直接调用LogFileManager::writeLog()所有日志必须经由LogDispatcher::enqueueLog()进入队列由主线程统一写入若必须子线程直写极少数场景改用QFile::write()并加QMutex全局锁性能损失大不推荐。4.3 现象按日期轮转后旧日志文件被覆盖而不是重命名归档原因QFile::rename()在Windows下对正在使用的文件返回false但开发者未检查返回值直接继续执行。解决每次QFile::rename()后必须检查if (!success) { qWarning() Rename failed: oldPath; }改用两步法先QFile::copy()到新路径再QFile::remove()旧文件兼容性更好生产环境务必添加QFile::permissions()检查避免SD卡只读导致轮转失败。4.4 现象qDebug()输出到控制台正常但文件里全是乱码如??:?? - ??原因QTextStream未设置编码QString转QByteArray时使用系统默认编码Windows为GBK而日志文件用UTF-8打开。解决初始化QTextStream时显式设置编码m_stream.setCodec(UTF-8); // 必须在open()之后、write之前调用 m_file.open(...); m_stream.setDevice(m_file);4.5 现象程序启动后第一次日志写入极慢500ms原因QDir().mkpath()在首次调用时扫描整个路径树尤其当logPath为C:/Program Files/MyApp/logs时UAC虚拟化导致权限检查耗时。解决启动时预创建日志目录在main()中早于日志模块初始化QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) /logs);永远不要用C:/Program Files等受保护路径存日志改用QStandardPaths::AppDataLocation。5. 模块集成与验收标准如何证明你的日志模块真的可靠验收不是“能跑就行”而是要经得起三类压力测试功能验证、边界破坏、长期运行。以下是我在某高校实验室带学生做QT课设时制定的6项硬性验收标准每项都对应可执行的验证步骤验收项验证方法通过标准工具/命令1. 条件过滤生效启动程序执行qDebug()test debug; qWarning()warning test;文件中仅出现warning test行无test debugtail -f app_*.log2. 关键词捕获准确设置KeywordPatternERR_\\d执行qInfo()ERR_404 not found;该行必须出现在日志文件中grep ERR_404 app_*.log3. 按日轮转正确修改系统时间为次日触发checkAndRotate()生成新文件名如app_20240521_001.log旧文件重命名为.archivedls -la logs/4. 大文件自动切分用脚本循环写入10万条日志每条100B最大单文件≤10MB生成app_20240520_001.log,_002.log等du -sh logs/*.log5. UI线程不卡顿在QTimer::singleShot(10, this, []{ /* 写1000条日志 */ });中执行界面动画流畅无掉帧用QElapsedTimer测间隔qDebug() Frame time: timer.elapsed();6. 异常退出保日志kill -9进程重启后检查最后10条日志最后一条日志时间戳距崩溃时间≤50ms缓冲窗口对比journalctl或系统日志时间5.1 答辩演示技巧3分钟讲清架构价值评委最关心“你解决了什么真实问题”。答辩时不要罗列代码用对比法突出设计价值Before传统做法qDebug() Sensor value: value;→ 日志全在控制台用户无法提供现场记录 → 故障复现率30%After本方案qInfo()Sensor value: value;→ 自动归档到logs/app_20240520_001.log→ 用户邮件发送日志文件 → 故障复现率提升至95%必演操作打开config.ini把EnableDebugLogtrue→ 保存点击界面上的“触发调试日志”按钮执行100条qDebugtail -f logs/app_*.log显示新增内容再改回false点击按钮 → 日志文件停止增长。一句话总结“这不是一个日志工具而是一个可配置的现场问题捕获探针。”5.2 部署检查清单给导师/企业客户看交付前请逐项确认避免因环境差异导致线上失效项目检查方式不通过后果日志目录有写入权限QFile::permissions(path)返回QFile::WriteUser程序静默失败无任何错误提示config.ini与可执行文件同目录QCoreApplication::applicationDirPath()/config.ini加载默认配置忽略用户修改QTextStream::setCodec(UTF-8)已调用检查LogFileManager.cpp第XX行中文日志乱码无法阅读qInstallMessageHandler()在main()早期调用位置在QApplication a(argc, argv);之后a.exec()之前部分QT内部日志如插件加载不被捕获LogFileManager析构函数含flush()检查~LogFileManager()实现程序意外退出时最后日志丢失从那以后我每次带学生做QT项目都会在第一天强调日志模块的代码量可能只占5%但它决定了剩下95%功能能否被信任。我会强制他们在main()里加一行qInfo()[LOG] Initialized;然后盯着logs/目录里是否立刻出现带时间戳的文件——这是第一个也是最重要的验收动作。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

多媒体实验全流程:从环境搭建到特征提取与结果评测

多媒体实验全流程:从环境搭建到特征提取与结果评测

简介:面向高校“多媒体技术及应用”课程学生与Authorware初学者的实验报告资源。该报告以“文字的滚动效果”为主题,系统演示了显示、等待、运动、擦除四大图标在交互作品中的协同用法,涵盖背景导入、文字编辑、移动路径调整、擦除特效设置&a…

2026/10/17 13:50:48 阅读更多 →
[SWPUCTF 2023 秋季新生赛]Easy SMC

[SWPUCTF 2023 秋季新生赛]Easy SMC

将下述文件拉到kali命令行执行我的kali是64,所以选linux_server根据IP和端口填远程调试在此处打断点、 按F7进入GetNextNumber函数按U取消定义选中0000555555555527到00005555555557B6,此处有retn按c强制转换为代码按p转换为函数按f5查看伪函数exp impor…

2026/10/17 13:50:48 阅读更多 →
2026大平层装修高定木作品牌哪家好:口碑、优缺点与选购避坑

2026大平层装修高定木作品牌哪家好:口碑、优缺点与选购避坑

在大平层和高端大宅装修中,高定木作不仅是收纳与功能的载体,更是决定空间风格调性与生活品质的核心要素。2026年,国内高定木作市场品牌林立,不同品牌在工艺、材质、风格、价格和服务上差异显著,面对琳琅满目的选择&…

2026/10/17 13:50:48 阅读更多 →

最新新闻

STM32F103裸机PWM精准输出包:寄存器级1kHz/75%占空比实测<0.3%误差

STM32F103裸机PWM精准输出包:寄存器级1kHz/75%占空比实测<0.3%误差

简介:本资源是一个基于STM32F1系列的双极性SPWM波形生成与控制工程代码包,面向电力电子、嵌入式开发及电机驱动方向的初学者与进阶工程师,解决逆变器系统中高精度正弦波调制信号生成与滤波频率可调的实际问题。压缩包共142个文件,…

2026/10/17 14:41:25 阅读更多 →
Java数据结构与算法分析:递归、大O与链表习题答案精讲

Java数据结构与算法分析:递归、大O与链表习题答案精讲

简介:《数据结构与算法分析(Java语言描述)》第三版习题答案文档,面向正在学习数据结构与算法、需要核对解题思路的Java开发者及计算机专业学生。内容聚焦教材前几章的经典题目,涵盖递归程序设计、数学归纳法证明、文件…

2026/10/17 14:41:25 阅读更多 →
PyTorch入门实战:从张量与自动求导到CNN图像分类

PyTorch入门实战:从张量与自动求导到CNN图像分类

1. 入门之前,先理解PyTorch的设计思路与核心概念 1.1 为什么大家现在都在用PyTorch PyTorch入门这个话题,我在不同场合聊过很多次。带过的朋友里有刚接触编程的大三学生,也有从其他框架转过来的后端工程师,大家问的第一个问题往往…

2026/10/17 14:41:25 阅读更多 →
Hive动态分区为何必须Shuffle?原理与调优实战

Hive动态分区为何必须Shuffle?原理与调优实战

1. 项目概述:为什么动态分区在 Hive 里既香又烫手?“Hive 优化动态分区实现 开启reduce 需要shuffle”——这个标题不是一句技术口号,而是某天凌晨三点我盯着 YARN UI 上卡在 99% 的 MapReduce 任务时,一边灌着第三杯冷咖啡、一边…

2026/10/17 14:41:25 阅读更多 →
SQL Server 游标循环与 FOR 循环实战:TaoToken 统一 Key 下的调试与验证

SQL Server 游标循环与 FOR 循环实战:TaoToken 统一 Key 下的调试与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 14:41:25 阅读更多 →
具身智能进阶开发:从感知到控制的完整技术链路与工程实践

具身智能进阶开发:从感知到控制的完整技术链路与工程实践

做具身智能这几年,我最大的感受是:这个方向不缺热度,缺的是能把系统跑通的人。很多人一上来就追大模型、追VLA,结果连ROS2的坐标变换都没搞明白,仿真环境里物体抓得稳稳的,一上真机就到处乱撞。我之前在团队…

2026/10/17 14:40:24 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →