深入UGUI源码:性能优化与自定义组件实战指南
1. 项目概述为什么我们要深入UGUI源码做Unity开发尤其是做手游或者对性能有要求的项目UGUI几乎是绕不开的坎。你可能已经熟练地用Canvas、Image、Text、ScrollView搭出了各种酷炫的界面也大概知道“合批”、“重建”这些词对性能至关重要。但当你遇到界面卡顿、Draw Call异常飙升或者想实现一个特殊交互效果却发现UGUI原生组件不支持时那种无力感是不是特别强烈网上搜到的解决方案要么是“玄学调参”要么是“用这个插件”知其然不知其所以然。这就是我决定花时间啃下UGUI源码的原因。这不仅仅是为了回答面试官那句“UGUI的渲染流程是怎样的”更是为了在实战中当问题出现时你能像外科医生一样精准定位病灶而不是像个赤脚医生一样乱试偏方。通过分析源码你将彻底理解性能瓶颈的根源为什么滚动列表快速滑动时会卡为什么看似简单的界面Draw Call却降不下来自定义组件的底气如何从底层扩展一个满足特殊需求的UI组件而不是在现有组件上打丑陋的补丁。问题排查的效率遇到UI显示异常、点击失效、渲染错乱时能快速形成排查思路直击要害。接下来的内容我会带你从宏观设计到微观实现结合大量实战中踩过的坑和优化技巧把UGUI的核心机制掰开揉碎了讲清楚。这不是一篇简单的API文档翻译而是一位老司机带你走的“源码级”UGUI实战之路。2. UGUI核心架构与渲染管线拆解UGUI的架构设计清晰地分离了逻辑更新和渲染提交两个阶段。理解这个分离是理解一切的基础。2.1 核心类关系与职责划分我们可以把UGUI的核心类看作一个协作团队Canvas团队经理。它持有最终的CanvasRenderer列表并负责在合适的时机如摄像机渲染前命令整个团队开始工作Canvas.willRenderCanvases事件。它也是网格合批的决策中心。Graphic所有可渲染UI元素如Image,Text的基类。它是“设计师”负责生成自己的网格数据顶点、三角形、UV、颜色。但它只设计不施工。CanvasRenderer施工队。每个Graphic都对应一个CanvasRenderer。Graphic把设计好的网格数据交给它它负责持有这些数据并在接到Canvas的命令后把数据提交给Unity的底层图形接口如OpenGL ES, Direct3D。MaskableGraphic继承了Graphic增加了与遮罩Mask,RectMask2D协作的能力。Image和Text都继承自它。ICanvasElement接口。定义了UI元素需要“重建”时应具备的行为主要是Rebuild方法。Graphic和LayoutGroup都实现了它。它们的关系简单概括为Canvas驱动 -Graphic生成数据 - 存入对应的CanvasRenderer-Canvas统一提交所有CanvasRenderer的数据进行渲染。2.2 渲染流程详解从顶点到屏幕渲染一帧UI的完整流程可以分解为以下步骤步骤一脏标记Marking as Dirty这是流程的触发器。当UI元素的属性发生改变需要重新生成网格或重新布局时它会将自己标记为“脏”。几何脏Geometry Dirty当Graphic的rectTransform、color、material、sprite等影响最终网格形状和外观的属性改变时触发。调用Graphic.SetVerticesDirty()和Graphic.SetMaterialDirty()。布局脏Layout Dirty当UI元素的尺寸或其在布局组中的位置可能需要改变时触发例如Text的文字内容变化。这会向上冒泡通知父级的LayoutGroup如HorizontalLayoutGroup。步骤二重建Rebuild这是核心计算阶段。Unity在特定的更新循环中检查并处理所有“脏”的元素。布局重建CanvasUpdateRegistry.PerformUpdate在Canvas.willRenderCanvases事件中首先进行布局重建。这会遍历所有布局脏的元素从叶子节点向根节点Canvas进行重新布局计算确保所有RectTransform的尺寸和位置是正确的。这是ContentSizeFitter等组件生效的地方。几何重建布局完成后进行几何重建。遍历所有几何脏的Graphic元素调用其Rebuild方法。Graphic.Rebuild会调用OnPopulateMesh方法对于Text是Text.OnPopulateMesh或TextGenerator来生成文本网格。步骤三网格填充与合批判断在OnPopulateMesh中组件将计算出的顶点、UV、颜色等数据填充到一个VertexHelper对象中。VertexHelper是一个临时的顶点数据容器。 随后Graphic会将这些数据更新到它所附的CanvasRenderer中通过CanvasRenderer.SetMesh。 在这个过程中合批Batching的判断悄然发生。Unity会根据CanvasRenderer的材质Material和纹理Texture来判定哪些UI可以合并到一个Draw Call中。如果两个CanvasRenderer使用相同的材质和纹理且渲染顺序相邻、深度测试等状态一致它们就有可能被合批。步骤四渲染提交最后Canvas将所有CanvasRenderer中存储的网格数据按照正确的渲染顺序由Canvas的sorting order、Render Mode和元素在Hierarchy中的顺序决定提交给Unity的渲染管线。这一步对于开发者来说是黑盒由Unity底层图形API完成。关键心得很多性能问题就出在步骤一和步骤二。频繁设置UI属性如每帧改变颜色、位置会导致频繁的“标记为脏”和“重建”造成CPU性能瓶颈。而步骤三中的合批失败则是导致Draw Call过高的元凶通常是因为材质或纹理不同。3. 性能杀手深度剖析重建与合批理解了流程我们就能精准定位性能问题。下面用两个实战中最头疼的场景来分析。3.1 重建Rebuild的触发条件与优化实战重建是CPU开销的主要来源。除了上面提到的属性改变还有一些隐蔽的触发点隐蔽触发点Canvas组件启用/禁用这会强制其下所有UI元素重建。改变父节点UI元素的parent改变时会触发布局和几何重建。SpriteAtlas加载如果Image的sprite来自一个尚未加载的图集当图集加载完成时会触发该Image重建。实战优化策略分离动态与静态Canvas将频繁变化的UI如血条、计时器、飘字放在一个或多个独立的Canvas上与静态背景UI分开。因为重建是以Canvas为单位的分离可以最小化重建范围。避免每帧调用SetActive显示/隐藏UI优先考虑调整CanvasGroup.alpha从1到0并设置CanvasGroup.blocksRaycasts false而不是直接SetActive(false)。后者会触发整个Canvas下所有元素的禁用和启用流程开销更大。对频繁更新的文本使用缓存对于每秒更新多次的计时器如“00:13”不要直接拼接字符串赋值给Text.text。可以创建一个char[]数组来缓存数字字符只更新变化的位最后一次性构建字符串。或者对于固定格式的数字考虑使用多个Image组件显示数字图集通过切换sprite来更新这通常比文本重建更快。谨慎使用ContentSizeFitter和LayoutGroup它们非常方便但代价是任何子元素尺寸变化都会导致向上冒泡的布局计算。在复杂的滚动列表项中如果尺寸固定应手动设置RectTransform避免使用这些自动布局组件。3.2 合批Batching原理与突破Draw Call限制合批是降低Draw Call、提升GPU效率的关键。UGUI的合批主要是静态合批基于材质和纹理。合批失败的常见原因材质不同即使纹理相同如果材质实例不同例如一个Image加了材质球另一个没加就无法合批。纹理不同这是最常见的原因。每个不同的Sprite即使来自同一个图集但不同Sprite在渲染时被视为不同纹理。层级打断两个使用相同材质和纹理的UI中间插入了一个使用不同材质/纹理的UI会打断合批。渲染顺序由Hierarchy顺序和Canvas的sort order决定。重叠与深度测试复杂的重叠关系有时会影响合批逻辑。使用Mask组件Mask组件会为子元素生成新的材质实例几乎必然导致合批中断。应优先使用RectMask2D它是在Shader中通过Stencil Test实现裁剪不创建新材质对合批更友好。实战优化策略纹理图集化Atlas这是最重要的手段。使用Unity的Sprite Atlas功能或第三方工具如TexturePacker将大量小图打包成一张大图。确保UI元素使用的Sprite都来自同一张图集这是合批的基础。统一材质尽可能让UI元素使用默认的UI/Default材质不要轻易附加自定义材质球。如果必须使用自定义Shader确保所有需要合批的UI共享同一个材质实例。精心规划Hierarchy顺序调整UI元素在Hierarchy中的顺序让使用相同材质/纹理的物体尽量连续排列避免被其他物体打断。可以写编辑器工具在打包前自动排序。利用Canvas的Additional Shader Channels如果你的自定义UI Shader需要额外的顶点数据如UV2、顶点法线需要在这里声明否则合批可能出错。动态字体合批Text组件使用动态字体时每个字符其实是从字体纹理Font Texture中裁剪出来的。同一个Font文件的Text组件只要渲染状态一致是可以合批的。但要注意字体纹理的分辨率和“字符集”避免运行时动态添加字符导致纹理重建。踩坑记录我们项目曾有一个复杂的角色属性面板Draw Call始终在50以上。排查后发现几十个图标虽然来自同一个图集但因为Hierarchy顺序被一些分割线使用不同材质和带外发光效果的技能图标附加了特效材质完全打乱。通过重组Hierarchy将普通图标集中放置并将特效图标剥离到另一个子Canvas最终将Draw Call降到了15以内。4. 从源码到实战自定义高性能UI组件读源码的终极目的是为了创造。我们以两个实战案例看看如何借鉴UGUI源码的思想打造自己的组件。4.1 案例一实现一个虚拟化滚动列表标准ScrollRect在列表项很多时会实例化所有项造成巨大的内存和重建开销。虚拟化列表只创建可视区域内的项复用它们来显示不同数据。核心设计思路借鉴自Graphic与CanvasRenderer的分离数据与视图分离维护一个数据列表。视图ListItem只是一个用于显示的容器。视图池像CanvasRenderer池一样我们维护一个可复用的ListItem对象池。滚动时重建监听ScrollRect的onValueChanged事件。根据滚动位置和项的高度计算出当前可视区域的起始索引和结束索引。按需更新从对象池中取出或创建对应数量的ListItem根据计算出的数据索引调用一个Setup(data)方法更新其显示内容。移出可视区域的ListItem回收到对象池。关键代码片段与避坑指南// 伪代码展示核心逻辑 public class VirtualizedScrollRect : ScrollRect { public RectTransform itemPrefab; public int dataCount; private ListItemData _allData; private QueueRectTransform _pool new QueueRectTransform(); private ListRectTransform _activeItems new ListRectTransform(); private float _itemHeight; private int _firstVisibleIndex 0; protected override void Start() { base.Start(); content.sizeDelta new Vector2(content.sizeDelta.x, dataCount * _itemHeight); // 设置Content总高度 onValueChanged.AddListener(OnScrollValueChanged); RefreshVisibleItems(); } private void OnScrollValueChanged(Vector2 normalizedPos) { // 根据垂直滚动位置计算新的_firstVisibleIndex int newIndex Mathf.FloorToInt((1 - normalizedPos.y) * (dataCount - visibleItemCount)); if (newIndex ! _firstVisibleIndex) { _firstVisibleIndex newIndex; RefreshVisibleItems(); // 更新可见项 } } private void RefreshVisibleItems() { // 1. 将滚出视口的项回池 // 2. 计算需要的新项 // 3. 从池中取或创建新项并调用Setup(data) // 4. 更新这些项的位置 } }避坑指南项高度不均如果列表项高度不固定计算会复杂很多。需要预先计算或缓存每一项的累计高度使用二分查找来确定起始索引。快速滚动白屏如果Setup方法开销很大快速滚动时可能来不及更新。可以考虑分帧更新或者在滚动惯性结束后再统一更新。回收池管理回收时务必重置ListItem的状态避免显示旧数据。4.2 案例二扩展Image组件实现圆角与渐变UGUI的Image组件功能有限。我们通过继承MaskableGraphic重写OnPopulateMesh方法可以自定义几何形状。实现圆角矩形Image的核心继承自MaskableGraphic这样能保留遮罩支持。重写OnPopulateMesh不再调用基类方法而是自己计算顶点。顶点计算将矩形分割成中心矩形和四个圆角。每个圆角用多个三角形扇形来模拟。通过VertexHelper添加顶点位置、UV、颜色。属性暴露定义float radius等属性并在属性设置器里调用SetVerticesDirty()以触发重建。实现渐变如从左到右的色变的核心在计算每个顶点位置时根据其X坐标在矩形宽度中的比例从0到1对预设的起始颜色和结束颜色进行插值Color.Lerp得到该顶点的颜色值然后传给VertexHelper.AddVert。注意事项性能圆角分割的精细度顶点数直接影响性能。在移动端需要权衡效果和性能。合批自定义的Graphic只要使用相同的材质通常是UI/Default并且纹理相同依然可以参与合批。但如果你在自定义Shader中引入了新的属性则需要确保材质实例相同。射线检测Graphic默认的射线检测Raycast是基于其矩形区域的。对于圆角Image你可能需要重写IsRaycastLocationValid方法通过计算点击位置是否在圆角矩形内来提供精确的点击检测。5. 高频问题排查与调试技巧实录即使理解了原理实战中还是会遇到各种光怪陆离的问题。这里分享一个排查清单和调试方法。5.1 UGUI常见问题速查表问题现象可能原因排查方向与解决方案UI点击无响应1.Raycast Target未勾选。2. 被上层UI如图像、透明Panel遮挡。3.CanvasGroup的Blocks Raycasts为false。4. 自定义Graphic未正确重写IsRaycastLocationValid。1. 检查组件复选框。2. 检查Hierarchy顺序及RectTransform覆盖区域。3. 检查父节点CanvasGroup设置。4. 在Scene视图开启GameObject/UI/Debug下的Show Raycast可视化。UI显示异常、紫屏1. 材质丢失或Shader错误。2. 图集Sprite Atlas未打包或未在构建中包含。3. 自定义Shader编译错误或属性未正确设置。1. 检查MeshRenderer或CanvasRenderer的Material字段。2. 检查Sprite Atlas的Include in Build设置或检查AssetBundle依赖。3. 查看Console错误日志检查Shader代码。Draw Call异常高1. 合批被打断见3.2节。2. 使用了多个Canvas且未合理分离。3. 大量UI使用了Mask组件。1. 使用Frame Debugger工具逐帧分析Draw Call查看合批中断点。2. 合并静态UI到最少Canvas。3. 用RectMask2D替代Mask。滚动列表卡顿1. 列表项过多重建开销大。2. 列表项内含复杂布局或子UI。3. 使用了ContentSizeFitter等动态布局。1. 实现虚拟化列表见4.1节。2. 简化列表项结构合并纹理。3. 避免在滚动项内使用动态布局预计算尺寸。文字渲染模糊1. 动态字体纹理分辨率不足。2. Canvas的Render Mode为Screen Space - Camera时Canvas Scaler设置不当。3. 设备分辨率与参考分辨率不匹配。1. 增大Font的Font Size和Character集或使用位图字体。2. 调整Canvas Scaler的Match值或使用Scale With Screen Size模式。3. 检查Canvas Scaler的Reference Resolution和Screen Match Mode。5.2 必备调试工具与技巧Frame Debugger (Window Analysis Frame Debugger)这是性能排查的神器。开启后你可以暂停游戏逐一看清每一帧的每一个Draw Call是如何产生的是什么Shader渲染了哪些物体。它能直观地告诉你合批在哪里被打断。Unity Profiler (Window Analysis Profiler)重点关注CPU Usage模块下的Canvas.SendWillRenderCanvases和Canvas.BuildBatch。前者代表重建开销后者代表合批计算开销。如果它们耗时很高就需要按照前面章节的方法进行优化。Editor UI Debugging在Scene视图通过GameObject/UI/Debug菜单可以开启Show Raycast显示可点击区域和Show Mask Graphic Bounds显示遮罩边界对于排查交互和显示问题非常直观。自定义Debug绘制在自定义组件的OnPopulateMesh或Update中可以使用Debug.DrawLine等Gizmos方法在Scene视图绘制出你计算的顶点位置、边界框等对于验证算法逻辑是否正确极其有用。啃源码的过程就像一次探险开始时可能满是荆棘但每理解一个模块就像点亮了一盏灯脚下的路就越发清晰。UGUI的源码并不完美但它提供了一个稳定而强大的框架。通过这次深入分析希望你能获得的不仅是对UGUI运行机制的理解更是一种“源码级”的解决问题思维方式。下次当UI再出现诡异问题时你大可以自信地打开Frame Debugger和Profiler顺着数据流的线索直捣黄龙。

相关新闻

Python文本润色接口开发:从数据清洗到NLP优化

Python文本润色接口开发:从数据清洗到NLP优化

1. 项目背景与需求分析最近在开发一个内容爬取系统时,遇到了一个典型问题:从不同网站抓取的文本内容质量参差不齐,有的包含大量无意义字符,有的语句不通顺,还有的夹杂着广告信息。这种原始数据直接存入数据库不仅影响后…

2026/10/17 14:30:11 阅读更多 →
AI Agent核心架构解析与主流框架实战指南

AI Agent核心架构解析与主流框架实战指南

1. 从工具到伙伴:AI Agent的范式革命 如果你最近关注AI领域,会发现一个词的热度正在急剧攀升,甚至盖过了年初大火的“RAG”(检索增强生成),那就是“AI Agent”。它不再是实验室里的概念,而是正在…

2026/10/18 3:23:52 阅读更多 →
3步解锁弹幕魔法:让视频互动从XML到ASS一键蜕变

3步解锁弹幕魔法:让视频互动从XML到ASS一键蜕变

3步解锁弹幕魔法:让视频互动从XML到ASS一键蜕变 【免费下载链接】DanmakuFactory 支持特殊弹幕的xml转ass格式转换工具 项目地址: https://gitcode.com/gh_mirrors/da/DanmakuFactory 还在为视频弹幕格式不兼容而头疼吗?DanmakuFactory是你的专业…

2026/10/17 16:52:07 阅读更多 →

最新新闻

风电无人机智能巡检平台架构解析:从AI缺陷识别到三维重建与运维闭环

风电无人机智能巡检平台架构解析:从AI缺陷识别到三维重建与运维闭环

1. 巡检平台的整体定位:无人机加AI到底解决了什么问题风电行业发展到今天,有一个绕不开的痛点:风机越来越高,叶片越来越长,塔筒动辄上百米,巡检的难度和成本一直在上涨。用望远镜看、用爬塔机器人、甚至人工…

2026/10/18 7:51:08 阅读更多 →
自研轻量渲染引擎rea:数据驱动到精准DOM更新的实践总结

自研轻量渲染引擎rea:数据驱动到精准DOM更新的实践总结

折腾了两周半,我终于把内部代号叫rea的轻量渲染引擎跑通了。说是引擎,其实没有想象中那么玄乎,它解决的核心问题只有一句话:如何把一段状态数据,变成用户能看到的页面,并且在状态变化时只更新该变化的部分。…

2026/10/18 7:51:08 阅读更多 →
Django+Vue考研分数线预测与院校推荐系统:毕设技术方案全解析

Django+Vue考研分数线预测与院校推荐系统:毕设技术方案全解析

每年毕设季我都要帮忙看几十个题目前往不同方向的技术方案,见得最多的就是两类:一类是纯管理系统,功能单一、算法含量低,答辩时很容易被评委一句话问住;另一类是纯粹的网页展示,看起来花哨但没有实际业务闭…

2026/10/18 7:51:08 阅读更多 →
从模糊词根到技术选型:read、realtime、reasoning、render 的工程化拆解

从模糊词根到技术选型:read、realtime、reasoning、render 的工程化拆解

1. 从“rea”这个模糊词根说起:它到底指向什么第一次看到“rea”这三个字母,我脑子里蹦出来的不是某个具体产品,而是一堆以它开头的词:read、real、reason、react、realtime、reactive……这恰恰是这类极简标题最典型的状态——信…

2026/10/18 7:51:08 阅读更多 →
企业知识库RAG落地:从Demo到生产的架构决策与避坑指南

企业知识库RAG落地:从Demo到生产的架构决策与避坑指南

搞 RAG 最容易被骗的阶段,是 Demo。本地起一个向量库,扔进去几十篇 PDF,对话框里问什么答什么,附带引用还有模有样。那一刻很容易让人以为企业知识库是个三天能交付的活儿。等到真正往生产环境推——几十万文档、十几个业务系统、…

2026/10/18 7:51:08 阅读更多 →
C++游戏开发:从零实现植物大战僵尸核心引擎

C++游戏开发:从零实现植物大战僵尸核心引擎

简介:本资源是一份基于C实现的植物大战僵尸游戏模拟模型,面向C初学者与面向对象编程进阶学习者,聚焦游戏开发核心实践,帮助读者系统掌握类设计、继承多态、状态机管理、碰撞检测及事件驱动等关键技能。压缩包共109个文件&#xff…

2026/10/18 7:50:08 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 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/18 0:55:02 阅读更多 →
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/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练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 阅读更多 →