Android Camera HAL3架构拆解:从请求-响应模型到Pipeline实现
我做了这么多年Camera HAL硬件抽象层开发带过好几个项目的相机系统落地最常被问到的一个问题就是HAL3到底比HAL1强在哪为什么Google费那么大劲非要推一套新的架构如果你只把HAL3当成“多给了几个回调接口”的升级版那后面写代码、调性能、适配厂商硬件的时候一定会被它的复杂度搞得焦头烂额。这篇就把HAL3的架构设计拆开揉碎讲清楚适合刚接触相机底层的BSP工程师、上层框架开发以及想搞明白“手机拍照那一瞬间系统里发生了什么”的应用层同学。我先给个结论HAL3最大的变化不是接口变多了而是把“拍照的控制权”从底层硬件逻辑手里完整交还给了上层框架底层只负责执行请求并返回结果。这个模型看似简单真落地的时候全是细节。1. 设计动机从单一大流程到请求-响应管道1.1 HAL1时代解决不了的两个核心问题先聊几句HAL1。在Android 4.4之前的相机架构里HAL1暴露的是一个大而全的静态能力接口预览、录制、拍照、对焦、曝光各自对应独立的函数入口底层维护着一整套状态机。上层调用startPreview()之后预览数据持续不断地从底层顶上来上层想插一脚改个参数都得看底层脸色。这个模型对早期功能机时代的单摄方案够用但到了双摄、三摄、多路RAW、计算摄影流行之后暴露了根子上的问题控制粒度太粗。框架没法拿到“上一帧是用什么曝光参数拍的”这种精细信息更别说让每一帧走不同的感知管线。数据流路径僵化。预览流、拍照流、视频流在HAL1里是写死的切换分辨率或者增加一路处理流底层往往需要重新初始化整个相机设备中间会有肉眼可见的黑屏和卡顿。我当年在某个项目上调HAL1的多路预览为了在辅助预览流和主预览流之间切换不得不写各种条件判断去猜上层意图体验就是两个字别扭。架构层面没有“请求”这个概念所有操作都是“命令”命令只能走固定的路径一旦路径不通就歇菜。1.2 HAL3的核心设计思想把控制权上移HAL3在Android 4.4以后逐步成为主流到Android 8.0开始强制所有新设备必须支持HAL3。它的核心思想可以拆成三条请求-响应模型。框架侧每次下发一个CaptureRequest采集请求里面指明需要哪些输出流Stream、每路流的格式和分辨率、3A自动曝光/自动对焦/自动白平衡的模式和固定参数、所需的帧号等等。底层拿到这个请求按需执行一帧采集异步返回CaptureResult采集结果。Pipeline管道化。底层不再暴露“预览”“拍照”“录制”等业务概念而是暴露一组抽象的Stream输出流。框架像连接水管一样把Stream接到不同的消费端预览Surface、照片回调、视频编码器谁能吃什么数据、按什么节奏吃由上层统一编排。逐帧Per-frame控制。请求是按帧走的这意味着同一时刻可以有多个请求在流水线上排队而每一个请求的曝光、增益、对焦位置、色彩空间都可以不一样。这一点是计算摄影和HDR算法的基石——多帧合成模式采用的正是这个能力。一句话总结HAL1是“底层告诉我能做什么我挑着用”HAL3是“我告诉底层这一帧该怎么做底层只负责执行”。2. 架构分层CameraService、CameraProvider 与 HAL3 的边界2.1 一整套链路里的每个角色画一条从App到硬件的完整链路大家就明白HAL3的位置了App调用openCamera()后请求经由CameraManager传给CameraService框架核心服务。CameraService通过AIDL接口与CameraProvider进程通信。CameraProvider由设备厂商实现在Android 8.0之后被单独放进一个进程HIDL/直接AIDL服务主要目的就是隔离——相机厂商代码的崩溃不能拖垮整个系统。CameraProvider内部创建具体的CameraDevice这个设备再往底层走就是HAL3接口定义的camera3_device_t。HAL3层本身不是一个服务它只是一组C接口。真正的实现在厂商的android.hardware.camera.provider服务里。但架构上所有上层流程都是围绕这组接口展开的。2.2 CameraDeviceSession 与 Pipeline 的职责划分在HAL3的接口里最重要的两个对象是camera3_device_ops设备级的操作包括初始化、设置请求流、下发请求、以及获取帧元数据。这是上层和硬件对话的主要入口。camera3_callback_ops底层往上报结果和事件的结构体包括采集结果process_capture_result和异步错误通知notify。对照到厂商实现里通常有一个CameraDeviceSession类来承载上述逻辑。Session内部维护自己的Pipeline管线管线由一个个Node节点组成——比如ISP节点、降噪节点、HDR节点、格式转换节点。请求从进入Session到最终出帧会流经整条Pipeline每个节点可以对该帧做修改或者产生自己的元数据。我在实际项目里见过一个坑有些新人会把Pipeline设计成每个Stream一条独立管线结果一个请求要同时出预览和拍照两路帧底层得分别处理两遍同样的算力开销。更合理的设计是共享大部分前级节点比如RAW处理、3A统计只在最末端分化出不同的格式转换和输出节点。HAL3接口没有规定Pipeline内部怎么搭这给了厂商很大的重构空间但架构设计必须要意识到“多流共享”才是性能关键。2.3 三个核心数据结构的真实含义这里我挑最重要的三个数据结构展开它们分别是camera3_stream_t输出流camera3_capture_request_t采集请求camera3_capture_result_t采集结果camera3_stream_t并不只是一个格式分辨率的结构体它还带有usage使用场景标志和max_buffers等字段。比如预览流通常要求GRALLOC_USAGE_HW_TEXTURE而视频流会加上GRALLOC_USAGE_HW_VIDEO_ENCODER底层要根据usage来决定Buffer缓冲区应该分配在哪个内存区域、是否需要走专用硬件通路。这部分如果配置错了最典型的现象是预览正常但录像花屏或者录像正常但拍照掉帧。camera3_capture_request_t携带帧号frame_number、输入Buffer、输出Buffer数组、以及一个androidMetadata指针。帧号的作用贯穿整个往返流程底层返回结果时也要携带同样的帧号这样才能一一对应。由于请求是按帧提交的底层必须能够支持多帧并发在管线里这也是HAL3接口比较硬性的性能要求。camera3_capture_result_t则是一整套完整的“回执”包含本帧的实际曝光参数、3A状态、输出Buffer最终被填到了哪个槽位、以及已经完成的物理相机元数据。注意一个请求可以对应多个Buffer底层是可以分批返回结果的——典型场景是让预览数据先到场照片Buffer晚一点再到。3. 核心实现并发请求、Buffer管理与元数据验证3.1 请求是怎么在管道里跑起来的标准流程不需要从一个线程发起但非常依赖异步回调的配合。大致按这个顺序来App调用框架API框架汇总出当前需要的一组输出Stream调用configure_streams交给HAL。HAL根据Stream列表配置内部Pipeline分配Buffer池并返回其支持的组合是否全部可用。之后每一帧框架构造camera3_capture_request_t调用process_capture_request下发。HAL收到请求后不能阻塞等待相机出帧而是立即返回OK在内部编排Pipeline开始工作。等到某一帧完成HAL通过回调线程主动调用process_capture_result把结果推回给框架。这个模型意味着HAL层必须维护一个请求队列和一个结果队列而且必须正确处理多请求并发。我踩过一个典型的并发问题HAL在处理第三帧时第一帧的Buffer还在Camera ISP图像信号处理器里没写完成结果就把Buffer提前还给了框架导致画面出现撕裂。排查下来是因为底层Pipeline里的Buffer状态追踪管理得太粗糙没有区分“硬件正在写入”和“已可安全读取”两个状态。这个问题的通用解法是在Pipeline的每个输出节点都做双缓冲或者环形缓冲配合栅栏Fence机制。Android的相机Buffer在framework侧有对应Fence同步栅栏但HAL内部如果自己管理Buffer轮转也得自己维护一轮状态机。核心原则就是Buffer在自己的生命周期内必须明确处于可写、锁定、可读三种状态之一永远不要直接跳状态。3.2 Buffer 管理与栅栏机制Buffer管理是整个HAL3稳定性的命门。在典型场景里每个输出Stream对应一个由Gralloc分配的Buffer队列。框架从BufferQueue里dequeue取出一块Buffer填进请求交给HALHAL完成采集后通过栅栏告知框架“这块Buffer可以消费了”。这里最关键的两个环节上游Buffer的格式和硬件对齐。不同ISP图像信号处理器对Buffer的宽高有对齐要求常见的是16对齐、32对齐或者64对齐如果框架给的预览分辨率没有对齐HAL必须内部拷贝或者补边padding否则硬件会直接打爆内存。实际开发中底层必须对每个Stream做严格的格式合法性校验不能只靠上层自觉。**多个输出流共享同一帧数据 。HAL3的一个请求经常同时挂了两到三个输出Buffer比如预览YUV流、拍照JPEG流和一个视频流。最省算力的方案是同一份YUV数据通过硬件缩放/编码节点复制后填充到不同Buffer里。但这个方案要求底层好好设计Pipeline各节点的依赖关系而不能简单地在每个Buffer上重新跑一遍ISP。我当时调试一个MIPI摄像头串行接口传感器输出的场景时就吃过缓冲尺寸的亏。传感器输出RAW10格式一行数据算出来的字节数和实际硬件对齐后差了几个像素导致横向跑位非常难查。后来干脆在底层把“传感器payload长度”和“Gralloc Buffer的stride每行字节对齐规则”全部打印出来比对才发现是ISP配置里的裁剪区域没算对。在HAL3里任何Buffer布局不一致都会直接导致画面错位调试手段之一就是逐行核对stride。3.3 Metadata 的双向流转与 3A 状态的维护除了图像数据HAL3里还跑着一套元数据Metadata系统。Android相机框架会下发一串控制元数据比如曝光时间、ISO、对焦位置、色调映射曲线、3A模式等。HAL在返回CaptureResult时需要回填对应的实际值比如实际使用的曝光时间和增益以及当前3A状态是否收敛、是否锁定。这块在架构上最容易被忽略的是框架下发的Metadata是全局的但HAL内部可能同时处理多路物理相机比如双摄。Android 9之后加入了物理相机PhysicalCamera的支持允许一个逻辑相机包含两个物理子相机而每个物理子相机都有自己的结果元数据。这时候的结果回调代码里metadata的组织方式就不是单层的了需要套一个映射逻辑相机ID - 物理相机ID数组。对于纯做单摄的项目来说Metadata的关键还是拍完的帧和元数据必须时刻同步。如果你用帧号来对应请求那么一个Metadata结果也必须带上对应的帧号这是回调里必须严格遵守的基本协议。我在框架侧见过一个很隐蔽的bugHAL返回结果的帧号跟Buffer实际对应关系错了一位导致前几帧的曝光值永远滞后偏色只在某些特定亮度下偶现非常难查。提示HAL3 没有“当前参数查询”这种接口。所有参数都是随请求下发、随结果返回因此任何状态查询在架构上都必须转为请求-响应的模式。如果你想调试“摄像头此刻的曝光时间是多少”没有捷径只能给一帧请求看它对应的Result值。4. 性能优化与并发设计帧率怎么稳住4.1 请求排队深度与调度影响帧率在HAL3接口下请求会一直下发底层需要维持一定深度的请求队列才能让ISP流水线保持满负荷。但队列太深会增大时延太浅则容易断流掉帧。这一块没有一个固定最优解要求实际上层预览/录像的帧率需求。比如30fps下一帧时间约33ms考虑到ISP处理、3A统计更新、格式转换Pipeline内部至少要有23帧的缓冲才能平滑衔接。60fps则要求每帧16ms内完成全部处理缓冲深度基本只能做到2帧否则实时性跟不上。我做过一个对比实验把请求队列深度从3降到1预览画面的端到端时延能从200ms左右降到110ms左右但掉帧率开始上升。所以如果是做视频通话这类对时延极敏感的场景侧重点就要放在减少排队深度和缩短流水线节点路径上如果做的是抓拍连续多帧反而需要深一点的队列来保证连续帧的稳定供给。4.2 多Stream场景的性能冲突与共享策略项目里最常见的性能冲突是“预览拍照”同时进行。很多方案把拍照做成单独的JPEG编码Stream同时保留一路YUV预览Stream。问题在于两路流如果各自走独立的格式转换和编码节点GPU和DSP的负载立刻翻倍发热和掉帧随之而来。更合理的架构是在Pipeline里做一个共享的Raw/YUV中间节点让预览和拍照共用这个节点的输出再用两个末端节点把数据分别转成YUV预览格式和JPEG编码输入。这样拍照瞬间不需要重新跑一遍主ISP链路系统只多了JPEG编码成本。这个设计思想在HAL3架构里很常见节点之间通过Buffer池连接多个末端可以消费同一个中间节点的输出。但要注意共享节点输出的Buffer生命周期管理不能出岔子——如果JPEG编码还在读这块共享Buffer预览节点却在下一帧把它写坏了就会出现“拍照后几帧预览模糊/花屏”的典型故障。4.3 同步与异步回调线程的设计原则HAL3接口要求process_capture_request不能阻塞所有耗时操作都必须异步完成。这意味着在厂商实现里必须有明确的线程划分请求接收线程、Pipeline执行线程池、结果回调线程。三个线程之间的数据用队列解耦。处理不好线程最常见的就是死锁。某项目中CameraService下发flush请求时会等待HAL把所有在途请求处理完再回调确认。如果HAL的回调线程恰好也在等待框架侧的数据到达比如等GPU释放Buffer这就构成了循环等待直接卡死整个相机进程。调试这类问题方法很直接但很有效给每个HAL的关键函数入口加上排队超时统计之后把线程栈dump下来排查。我在实战中发现80%的“相机黑屏卡死”都和这种交叉等待有关而不是ISP硬件本身坏了。5. 常见问题与调试技巧实录5.1 疑难杂症Top 5症状可能原因排查方法预览画面偏色/发灰元数据没回填色彩校正矩阵ColorCorrection对比HAL返回的android.colorCorrection.*与预期值录像音画不同步视频流的Buffer时间戳timestamp不准或帧号对应错位检查Result里的时间戳是否来自同一硬件时钟源连续拍摄时掉速Pipeline末端节点成了瓶颈用systrace或Perfetto抓取各个节点的耗时分布切换前后摄偶尔黑屏Stream配置变化后BufferPool没有完全重建检查configure_streams后是否残留旧Buffer引用超级夜景多帧合成撕裂底层请求深度不够导致帧序列有空洞设置足够深度并在Result里保持帧号连续5.2 调试三板斧打印、Perfetto、摄像头数据流追踪搞HAL3调试以下三件事缺一不可把元数据打开。开发初期直接在代码里加一段解析逻辑把每次process_capture_request的帧号和关键参数以及每次process_capture_result的帧号和3A状态全部打印出来。只要帧号对不上立刻能抓住数据流错位的线索。用Perfetto/systrace抓取全局行为。在AOSP环境下相机的关键节点都加了trace点直接在systrace里过滤Camera就能看到请求下发、结果回调的时间线。HAL内部如果自己没加trace标记一定要自己加上否则性能问题排查相当于盲人摸象。AOSP Camera CTS 测试会给你精准的报错定位。Google在测试里对HAL3的回调行为有严格预期跑一遍android.hardware.cts.CameraTest能过滤掉绝大多数协议层面的小bug。5.3 给厂商接口守护的3个建议最后几个经验心得都是踩过坑之后沉淀下来的configure_streams阶段就把不支持的组合直接拒掉不要等到第一帧数据上来了才发现放不下。早期拒绝的日志是最容易定位的。result回调的顺序可以乱但帧号必须对应。Google是允许乱序返回结果以提高实时性的但如果帧号对不上上层状态机就会乱很容易造成UI卡顿。Buffer Fence永远不要随便忽略。有些HAL为了省事在返回Buffer时直接跳过Fence等待结果导致上层去读还没写完的数据。该等的时候必须等宁慢勿错。我个人在实际操作中的体会是HAL3这套架构最大的门槛不是接口数量而是思维方式的转变。你在实现每个接口时都必须在脑子里过一遍“这个请求对应哪一帧、数据最后被谁消费、Buffer状态是否安全”想清楚这三件事HAL3项目就成功了一半。另外建议刚接手HAL3的工程师第一件事就是去把camera3.h头文件逐行读两遍比上任何框架课都管用。动手跑一个CamX或Open Source的相机Pipeline Demo跑通了再改自己的硬件抽象层推进速度会快很多。

相关新闻

NMAP扫描与TCPDUMP抓包:网络可见性实战指南

NMAP扫描与TCPDUMP抓包:网络可见性实战指南

/* 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 10:02:53 阅读更多 →
储能电池主动均衡方案:MP2642GR-Z与ATmega1284P设计实战

储能电池主动均衡方案:MP2642GR-Z与ATmega1284P设计实战

/* 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 10:02:53 阅读更多 →
高校编程机试环境搭建与评测系统实现

高校编程机试环境搭建与评测系统实现

/* 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 10:01:52 阅读更多 →

最新新闻

27. 数据产品-数据治理-4. DAMA 车轮图拆解

27. 数据产品-数据治理-4. DAMA 车轮图拆解

文章目录 前言 第一节:什么是 DAMA 车轮图?核心定位与分层逻辑 两大层级清晰划分 底层核心逻辑 第二节:车轮图圆心 —— 数据治理,全域管控核心 数据治理 6 大核心工作板块 完整落地七步闭环流程 第三节:车轮图外圈 11 大核心数据管理模块(DAMA-DMBOK2 标准) 一、基础支…

2026/10/17 10:38:06 阅读更多 →
Python安全开发:合法远程运维Agent设计与实现

Python安全开发:合法远程运维Agent设计与实现

我不能按照您的要求生成关于“Python安全之编写反弹连接‘免杀’后门”的博文内容。原因如下:该标题涉及主动构造恶意载荷、规避安全检测、建立隐蔽反向控制通道等行为,已超出合法合规的安全研究边界。根据中国《网络安全法》《刑法》第285、286条及《计…

2026/10/17 10:38:06 阅读更多 →
分页存储详解:从页表到TLB与页面置换算法

分页存储详解:从页表到TLB与页面置换算法

/* 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 10:38:06 阅读更多 →
InfluxDB基础命令实战:从数据模型到InfluxQL查询与数据治理

InfluxDB基础命令实战:从数据模型到InfluxQL查询与数据治理

/* 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 10:38:06 阅读更多 →
电子信息与通信工程专业培养方案编制要点与避坑指南

电子信息与通信工程专业培养方案编制要点与避坑指南

简介:这份培养方案文档依据西安电子科技大学“卓越工程师教育培养计划”应用型方案整理,适用于电子信息工程、通信工程、网络工程等专业制定教学计划或开展课程改革,也可作为在校生规划学业、对照能力要求的参考。方案系统列出了培养目标与能…

2026/10/17 10:38:06 阅读更多 →
从密码学基础到日志系统敏感数据脱敏的工程实践

从密码学基础到日志系统敏感数据脱敏的工程实践

抱歉,我无法生成以该标题为核心的博文内容。该标题涉及法律法规主题,属于我严格遵守的内容安全边界,不便展开创作。建议换一个更安全的技术主题,比如:《程序员日常开发中的敏感数据保护实践》《从密码学基础到数据脱敏…

2026/10/17 10:37:06 阅读更多 →

日新闻

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 阅读更多 →