图形编程基础:从点线面到光栅化算法的核心原理与实践
1. 从“点”开始理解图形绘制的基石聊到图形编程无论是做游戏、做数据可视化还是搞UI设计总绕不开一个最基础、也最核心的环节基本图形绘制。你可能觉得画个点、画条线这有什么好讲的直接用库里的函数不就行了但恰恰是这些最基础的函数构成了屏幕上一切复杂视觉效果的基石。我见过不少开发者能熟练调用高级的3D渲染API却对一条线段是如何从数学坐标变成屏幕上一个个发光的像素这个过程一知半解。这就像盖楼不打地基初期可能看不出问题一旦遇到性能瓶颈、渲染异常或者需要实现一些特殊效果时就会感到无从下手。所谓“基本图形绘制函数”指的就是那些能够直接在画布Canvas、图像缓冲区Framebuffer或任何绘图上下文Graphics Context上生成最原始几何图元的函数。这些图元主要包括点Point、直线段Line Segment、三角形Triangle、矩形Rectangle、圆形/椭圆Circle/Ellipse以及由它们衍生出的多边形Polygon。我们今天不聊那些花里胡哨的着色器、光照模型就回归本质拆解一下这些“基本功”到底是怎么工作的以及在实践中你会遇到哪些坑怎么避开它们。无论你用的是HTML5 Canvas的fillRect是OpenGL的glDrawArrays还是Python matplotlib的plot底层思想都是相通的。理解这些函数绝不仅仅是为了“会用”。它关乎效率你知道画一个实心圆是用多个三角形拼接快还是用光栅化算法逐像素填充快它关乎精度为什么你画的1像素宽的线有时候看起来模糊发虚它更关乎灵活性当现有函数无法满足你“画一条带渐变颜色的虚线”这种需求时你是束手无策还是能自己动手基于最基础的画点函数来实现它接下来我们就从最微小的“点”开始一步步构建起对图形绘制世界的完整认知。2. 核心图元拆解算法、实现与视觉陷阱图形绘制的核心是将数学描述的几何图形转化为帧缓冲区中具体的像素颜色值的过程这个过程称为“光栅化”Rasterization。每个基本图元都有其特定的光栅化算法而绘制函数就是这些算法的封装。理解它们你才能知其然并知其所以然。2.1 点Point并非想象中那么简单画一个点听起来是最简单的操作给定坐标(x, y)把对应的像素涂上颜色。但在计算机图形学中“点”是有大小的。在大多数API中你可以设置点的大小例如OpenGL的glPointSize。那么当一个点的大小大于1像素时它该如何绘制是以坐标为中心的正方形还是圆形不同的图形库可能有不同的默认行为。算法本质单像素点就是直接写入帧缓冲区的对应坐标。对于大于1像素的点通常是以给定坐标为中心绘制一个边长为size的正方形区域。更高级的实现可能会支持圆形点或自定义点精灵Point Sprite。实操中的坑坐标系统这是第一个大坑。你的绘图上下文使用的是什么坐标系是数学中常见的原点在中心、Y轴向上的笛卡尔坐标系还是屏幕像素中原点在左上角、Y轴向下的坐标系比如在HTML5 Canvas和许多2D图形API中默认是后者。如果你用数学思维直接代入坐标图形会上下颠倒。整数坐标与像素对齐当你指定一个点为(10.5, 20.3)时它落在像素之间。如何处理是取整、四舍五入还是进行亚像素渲染涉及抗锯齿对于单像素点直接取整到最近像素坐标是最常见的。一个关键技巧如果你想绘制清晰的、不模糊的1像素宽边框或线条请确保你的坐标是整数坐标在Canvas 2D中如果你在(10.5, 0)画一条到(10.5, 100)的竖线由于线落在两个像素之间浏览器会为左右两个像素各填充一半颜色导致模糊。将x坐标调整为10或11线条就会清晰锐利。2.2 直线段Line SegmentBresenham算法的智慧画一条从(x0, y0)到(x1, y1)的直线你不可能真的去画“无限细”的数学直线。你需要决定哪些像素被点亮来最佳地逼近这条直线。这就是经典的“Bresenham直线算法”要解决的问题。它只使用整数加减法和位操作效率极高是硬件光栅化的基础思想之一。算法核心思想以斜率在0到1之间为例算法通过一个误差项e来决定下一个像素是选右边的点还是右上方的点。它避免了浮点数运算和取整操作。计算差值dx x1 - x0,dy y1 - y0。初始化误差项e 2 * dy - dx。从起点(x0, y0)开始循环直到x到达x1。在当前位置(x, y)画点。如果e 0则y增加1同时e减去2 * dx。无论上一步如何x都增加1e加上2 * dy。为什么是这个逻辑误差项e本质上衡量的是当前候选像素点右方点到理想直线的垂直距离的2倍放大了dx倍以避免浮点数。e 0意味着理想直线更靠近右上方的像素所以y需要增加。实操要点与进阶问题线宽绘制宽度大于1像素的直线怎么办最简单的方法是“移动平行线法”计算出直线的单位法向量然后向两侧各偏移width/2得到两条平行线再填充这两条线之间的区域。但这在端点处处理不当会产生缺口。更健壮的方法是使用“线帽”Line Cap和“线连接”Line Join样式这通常是高级绘图API内置的功能如Canvas的lineCap和lineJoin。抗锯齿反走样Bresenham算法画出的直线有“锯齿”。抗锯齿的核心思想是不再简单地“点亮”或“不点亮”一个像素而是根据像素被直线覆盖的面积比例来设置像素的透明度Alpha值。例如Wu氏抗锯齿算法就是一种著名的、效果较好的直线抗锯齿算法。在实际开发中我们通常直接开启API的全局抗锯齿选项如OpenGL的GL_MULTISAMPLE Canvas的imageSmoothingEnabled而非自己实现。2.3 矩形Rectangle与三角形Triangle填充算法的关键矩形和三角形是构成复杂图形包括多边形和圆形近似的基本单元。它们的绘制分为两个步骤光栅化边界和内部填充。矩形由于其边与坐标轴平行光栅化特别简单高效。通常由两个对角顶点定义。填充就是一个简单的二重循环for y in range(top, bottom): for x in range(left, right): set_pixel(x, y, color)这就是为什么在几乎所有图形库中绘制填充矩形fillRect都是极其快速的操作。三角形三角形是3D渲染中最重要的图元因为任意多边形都可以分解为三角形且三角形一定是平面且凸的。填充三角形的主流算法是扫描线填充算法。扫描线填充算法步骤顶点排序按y坐标对三个顶点进行排序得到v_top,v_mid,v_bot。分割三角形用一条水平线穿过v_mid将三角形分割成上下两个平底或平顶三角形。处理每个子三角形对于每个子三角形计算其左右两条边与当前扫描线y值的交点x_left和x_right。填充扫描线在x_left和x_right之间为每个像素点着色。这里有一个至关重要的细节插值。我们不仅仅要填充颜色在3D渲染中还需要为每个像素插值深度值Z-Buffer、纹理坐标、法线等属性。必须使用重心坐标Barycentric Coordinates进行透视校正插值否则在非正面观察时纹理和着色会出现严重失真。这是图形学入门时的一个关键知识点。简单来说在屏幕空间进行线性插值是不正确的需要在投影前的3D空间或者使用倒数深度进行插值结果再投影回来。2.4 圆形Circle与椭圆Ellipse中点画圆法及其扩展如何高效地画一个圆同样有经典的中点画圆算法它也是Bresenham算法思想的一种应用利用圆的八分对称性只需计算八分之一圆弧的点就能通过对称得到整个圆。中点画圆算法思想第一象限八分之一圆弧 假设圆心在(0,0)半径R。我们从点(0, R)开始在斜率绝对值小于1的区域即从圆顶到45度角这部分进行决策。初始化决策参数d 1 - R。在(x, y)处画点及其七个对称点。如果d 0则下一个点选(x1, y)且d d 2*x 3。否则下一个点选(x1, y-1)且d d 2*(x - y) 5。椭圆的绘制中点椭圆算法是圆形算法的扩展但更复杂因为椭圆在四个象限的对称性不同且斜率变化点需要计算。在实际编程中除非有极致的性能要求否则更常见的做法是多边形逼近法用足够多的、在椭圆上均匀分布的点连接成一个多边形来绘制椭圆边框。填充椭圆则可以用扫描线算法但需要计算每条扫描线与椭圆边界的交点解一个二次方程。利用高级API像Canvas的ellipse()方法或矢量图形库它们已经内置了高度优化的椭圆绘制功能直接调用即可。我的经验是在软件渲染自己写像素的学习阶段实现中点画圆算法是很好的练习。但在实际项目开发中99%的情况你应该使用硬件或库提供的原生椭圆绘制函数它们经过高度优化并且支持抗锯齿、描边、填充等各种样式。3. 超越基础样式、性能与常见问题实战掌握了基本图元的光栅化原理我们来看看如何用它们组合出有用的样式以及在实际应用中如何规避性能陷阱。3.1 描边Stroke与填充Fill样式设定的艺术基本绘制函数通常支持两种模式描边只画边界和填充填充内部区域。实现它们有不同的挑战填充如上所述对于简单凸多边形如三角形、矩形扫描线算法高效直接。对于复杂多边形凹多边形、带洞多边形需要更通用的算法如扫描线填充算法的扩展它需要维护一个“活性边表”Active Edge Table, AET来管理所有与当前扫描线相交的边。描边这本质上就是画一系列连续的、有宽度的直线段多边形的边。难点在于边角连接处的处理即“线连接”Line Join样式斜接Miter延长两条外边界直到它们相交。当角度很小时延长部分会非常长因此通常有一个“斜接限制”Miter Limit超过此限制则回退为**斜角Bevel**连接。圆角Round在连接处画一个圆弧。斜角Bevel用一个三角形直接填充缺口。在Canvas 2D API中你可以通过context.lineWidth、context.lineCap用于线端点、context.lineJoin和context.miterLimit来轻松控制这些样式。了解其背后的原理有助于你在没有现成API的环境下例如在自定义的Shader中自己实现它们。3.2 性能优化关键批量绘制与状态切换当你需要绘制成千上万个基本图形时比如一片森林的树木、数据图表的散点直接循环调用成千上万次drawRect或drawCircle函数是性能灾难。主要的开销在于绘制调用Draw Call本身的开销和图形状态切换。优化策略批量绘制Batching将多个相同或相似样式颜色、线宽等的图形合并为一次绘制调用。例如对于许多矩形可以将其顶点数据预先存储在一个大的顶点数组Vertex Array中然后一次性提交给GPU绘制。在Canvas 2D中虽然API是立即模式的但也可以通过构建路径Path来批量操作。例如先beginPath()然后连续调用多个rect()或arc()方法定义路径最后用一次stroke()或fill()来绘制所有图形这比每个图形都单独beginPath()和stroke()要高效得多。避免频繁的状态变更设置颜色、线宽、填充样式等操作在OpenGL中称为状态设置是有成本的。应尽可能将相同状态的图形集中在一起绘制。一个经典的错误模式是在循环内部交替设置颜色和画图正确的做法是遍历所有需要红色图形的画完再切换为蓝色画所有蓝色图形。细节层次LOD与视锥裁剪对于远离相机或特别小的图形可以使用更简化的版本如用方块代替树木甚至直接跳过绘制。在绘制前进行简单的边界框检测如果图形完全不在可视范围内视锥体裁剪则直接跳过避免不必要的计算。3.3 高频问题与调试技巧即使理解了原理在实际编码时还是会遇到一些令人困惑的问题。这里分享几个我踩过的坑和解决方法问题一“我画的1像素线为什么是模糊的/灰色的”原因如前所述坐标没有对齐到像素网格导致了抗锯齿混合。在Canvas中如果你在(10.5, 0)画线线落在10和11像素之间两边各画一半透明度看起来就是模糊的灰色。解决在绘制1像素宽、不需要抗锯齿的清晰线条时对坐标进行Math.floor()或0.5操作确保其位于像素中心。另一个更通用的方法是在绘制前设置context.translate(0.5, 0.5)将整个坐标系偏移半个像素。这个技巧在绘制像素艺术或需要锐利UI边框时非常有用。问题二“填充复杂多边形时为什么有些区域没填上或填错了”原因可能是多边形定义有问题比如顶点顺序不一致有的边顺时针有的逆时针导致填充算法判断内外出错。或者是多边形自相交。解决确保多边形的顶点顺序是统一的全部顺时针或全部逆时针。对于从外部数据源加载的多边形使用一个健壮的几何库如ClipperLib、CGAL进行“规范化”处理确保其是简单的、非自相交的。问题三“我自己实现的三角形填充在远处纹理拉伸严重怎么回事”原因这就是没有进行透视校正插值的典型症状。你在屏幕空间对纹理坐标进行了线性插值但在透视投影下这并不正确。解决你需要使用重心坐标进行插值。具体来说对于任意需要插值的属性a如纹理坐标u,v颜色等在顶点处预先计算好a/w和1/w其中w是齐次坐标的w分量通常就是顶点在观察空间中的深度值的某种映射。在像素处先插值得到(a/w)_interp和(1/w)_interp然后最终的属性值a_pixel (a/w)_interp / (1/w)_interp。这个过程由现代GPU的硬件光栅化器自动完成但如果你在软件渲染器中自己实现就必须手动处理。4. 从理论到创造构建你自己的绘制函数库理解了上述所有内容后你就不再是图形API的被动使用者了。你可以尝试构建一个简易的、跨平台的2D图形绘制库这能极大地加深你的理解。设计这样一个库你需要考虑以下几个层面4.1 定义抽象接口首先定义一套不依赖于任何后端如Canvas, OpenGL, Vulkan的抽象绘制接口。例如class Renderer { public: virtual void setColor(Color c) 0; virtual void setLineWidth(float w) 0; virtual void drawPoint(float x, float y) 0; virtual void drawLine(float x1, float y1, float x2, float y2) 0; virtual void drawTriangle(float x1, float y1, float x2, float y2, float x3, float y3, bool filled) 0; virtual void drawRect(float x, float y, float w, float h, bool filled) 0; virtual void drawCircle(float centerX, float centerY, float radius, bool filled) 0; // ... 更多函数 };4.2 实现一个软件渲染后端这是最锻炼人的部分。你可以基于一个内存中的像素缓冲区一个uint32_t数组每个元素表示ARGB颜色来实现上述接口。drawPoint: 直接计算数组索引并赋值。drawLine: 实现Bresenham算法。考虑线宽和线帽初级实现可以忽略或只实现方形线帽。drawTriangle/drawRect(filled): 实现扫描线填充算法。你需要一个span buffer来存储每条扫描线的起始和结束x坐标。drawCircle(filled): 可以用中点画圆算法得到边界点然后结合扫描线填充。更简单的方法是对于圆内的每个像素判断其到圆心的距离是否小于半径。但这效率较低仅适用于小圆或学习目的。在实现过程中你会遇到无数细节问题如何高效地混合新颜色和背景色Alpha混合如何裁剪图形到视图边界如何实现基本的2D变换平移、旋转、缩放每一个问题都会迫使你深入思考图形学的基础。当你看到自己用纯CPU代码绘制的第一个平滑渐变的三角形时那种成就感是调用API无法比拟的。4.3 性能考量与优化你的第一个软件渲染器可能很慢。接下来可以考虑优化使用SIMD指令如SSE, AVX一次性处理多个像素。多线程渲染将屏幕分割成多个水平带每个线程负责渲染一部分。将算法从“每像素判断”改为“每图元生成”例如填充三角形时先计算出三角形覆盖的扫描线范围然后只在这个范围内循环而不是遍历整个屏幕缓冲区。最终你可以为这个抽象接口再实现一个OpenGL或Vulkan后端利用GPU的并行计算能力进行硬件加速。这时你会发现之前软件渲染的经验让你对GPU在做什么有了清晰的认识你能更好地理解顶点着色器、片段着色器以及它们之间的光栅化阶段。回过头看基本图形绘制函数就像编程中的“Hello World”看似简单却蕴含着计算机图形学最核心的坐标变换、光栅化、插值和混合思想。无论是为了调试复杂的3D渲染问题还是为了在资源受限的环境下如嵌入式系统、某些游戏引擎的自定义UI实现图形功能亦或是单纯为了满足求知欲深入理解这一层都会让你在图形编程的道路上走得更稳、更远。下次当你调用fillRect时或许你会会心一笑知道在那一行简单的代码背后正进行着一场从连续数学到离散像素的精彩转换。

相关新闻

Spring Boot+Redis+Kafka构建高并发电商系统实战

Spring Boot+Redis+Kafka构建高并发电商系统实战

1. 项目概述"Java大厂面试实战:Spring BootRedisKafka电商高并发场景深度解析"这个标题直指当前Java技术栈中最核心的三大技术组合在实际业务场景中的应用。作为一名经历过多次大厂技术面试的Java开发者,我深知这套技术组合在电商、金融、社交…

2026/10/13 14:31:17 阅读更多 →
Mate Engine终极指南:免费开源的桌面虚拟伴侣软件如何改变你的数字生活

Mate Engine终极指南:免费开源的桌面虚拟伴侣软件如何改变你的数字生活

Mate Engine终极指南:免费开源的桌面虚拟伴侣软件如何改变你的数字生活 【免费下载链接】Mate-Engine A free Desktop Mate alternative with a lightweight interface and custom VRM support, though with more features. 项目地址: https://gitcode.com/gh_mir…

2026/10/3 7:25:48 阅读更多 →
如何解锁Wand游戏修改器完整功能:Wand-Enhancer实用技术方案详解

如何解锁Wand游戏修改器完整功能:Wand-Enhancer实用技术方案详解

如何解锁Wand游戏修改器完整功能:Wand-Enhancer实用技术方案详解 【免费下载链接】Wand-Enhancer Advanced UX and interoperability extension for Wand (WeMod) app 项目地址: https://gitcode.com/GitHub_Trending/we/Wand-Enhancer Wand-Enhancer是一个开…

2026/10/2 2:55:17 阅读更多 →

最新新闻

Linux网络排查从入门到实战:协议栈、路由、防火墙与NAT全解析

Linux网络排查从入门到实战:协议栈、路由、防火墙与NAT全解析

Linux下的网络基础,说真的,属于那种你觉得自己会了,但一到生产环境就露怯的主题。干运维这些年,我最大的体会是:网络不通的根因,绝大多数不是命令不够炫,而是没把底层的数据流转、协议分层、路由…

2026/10/13 15:22:58 阅读更多 →
AI写论文全流程合规指南:从选题到查重降重的避坑实操

AI写论文全流程合规指南:从选题到查重降重的避坑实操

这几年一到毕业季,就有大量毕业生来问我同一个问题:AI 写论文到底选哪个工具?论文到底能不能用 AI 写?说实话,AI 辅助写作早就不是什么新鲜事了,但真正让毕业生纠结的不是“用不用”,而是“用了…

2026/10/13 15:22:58 阅读更多 →
Cherry Studio 接入 MCP 文件服务:让大模型真正动手管理文件

Cherry Studio 接入 MCP 文件服务:让大模型真正动手管理文件

说实话,我最近在好几个技术交流群里看到有人在"晒"同一个东西:AI 助手竟然能自己整理下载文件夹、批量修改文件名、甚至按内容把散落的文档归类归档。一问才知道,用的就是Cherry Studio 接上 MCP 文件服务这套组合。我第一次跑通这…

2026/10/13 15:22:58 阅读更多 →
计算机二级C语言题集使用指南:考点地图、刷题方法与避坑要点

计算机二级C语言题集使用指南:考点地图、刷题方法与避坑要点

简介:面向全国计算机等级考试二级C语言考生,这份资源将上机考试常见题型整理成题集并配以题解,涵盖程序填空、程序修改、程序设计三大类典型试题,对应blank、modi、prog等常见命名,便于考生按类突破、专项练习。题解中…

2026/10/13 15:22:58 阅读更多 →
AI芯片软硬件设计关键:如何把8TOPS跑出高利用率

AI芯片软硬件设计关键:如何把8TOPS跑出高利用率

最近几年做AI芯片的项目越来越多,但很多团队做出来的片子,标杆测试很好看,一上真实模型就掉链子。问题往往不在某个硬件模块本身,而是整条链路上软硬件到底怎么切、怎么协同。我参与过一个边缘侧推理芯片的设计,规模不…

2026/10/13 15:22:58 阅读更多 →
OpenClaw `clawbot` 命令别名命名空间:兼容性保障与向现代命令的迁移指南

OpenClaw `clawbot` 命令别名命名空间:兼容性保障与向现代命令的迁移指南

人工智能大模型LLM 网关AI Agent即时通讯 【免费下载链接】openclaw-zero-token OpenClaw: Use All Major AI Models NO API Token! Claude/ChatGPT/Gemini/DeepSeek/Doubao/Grok/Qwen/Manus/Kimi 项目地址: https://gitcode.com/gh_mirrors/op/openclaw-zero-token…

2026/10/13 15:21:56 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 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/12 21:05:09 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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/12 18:11:17 阅读更多 →