用Joern做Java代码审计:5分钟导入项目并扫描注入漏洞
做Java安全的人多多少少都被“漏洞扫描”这四个字折腾过。传统商业SAST不是不好但贵、慢、误报率高有时候一份扫描报告发下来研发团队光是人肉筛告警就得筛一天自己写正则扫代码又只能抓到“字符串拼接SQL”“直接调exec”这种最浅层的写法稍微过一层封装、过一层函数调用就彻底抓瞎。我后来换上了Joern才算体会到什么叫“把代码当成一张图去查”——语法树、控制流、数据流全都在同一张图上你可以像写数据库查询一样直接问“哪个方法的参数一路流到了命令执行函数”。这篇实战文章就来聊这件事在环境已经装好的前提下5分钟把一份Java源码导入Joern再用几条完整可复制的查询规则把命令注入、SQL注入、硬编码密钥、危险反序列化这类高频问题先扫一遍。适合被SAST告警淹没的研发同学、刚上手代码审计的新手以及想在CI里加一道快速安全门的团队参考。1. 为什么是Joern——代码属性图和数据库式查询的思路1.1 从AST、CFG到CPG一张图装下所有语义传统静态分析工具盯的是AST抽象语法树只看代码长什么样再进一步会看CFG控制流图看代码按什么顺序跑更深入一点会看PDG程序依赖图看数据在变量之间怎么流动。但这三样东西通常是分开存的分析一个问题时要在不同表示之间反复横跳非常费劲。Joern的思路是直接把这三种图合并成一张代码属性图Code Property GraphCPG。节点就是方法、参数、表达式、字面量这些代码元素边就是调用、跳转、数据依赖、控制依赖这些关系每个节点和边还可以带属性比如方法名、行号、文件名、变量类型。可以这么类比AST是楼里每间房的结构图CFG是楼里的行走路线PDG是门窗之间的依赖关系CPG就是把这些信息全部画在同一张全息地图上。有了这张图你想查什么都很直接。想知道项目里所有调用Runtime.exec的地方查一下“所有名字叫exec的调用节点”就行。想知道某个方法的参数有没有可能被外部输入污染沿着数据依赖边、跨过几十层函数调用往回追就行。这种方式比正则扫描天然高一个维度正则只能匹配文本CPG能匹配“语义”。1.2 和商业SAST、正则扫描的差异我自己用下来的体感三类方案各有利弊我用下面这个表做个对比方案核心思路优势劣势正则/grep匹配文本特征快、零成本完全不理解代码结构过一层封装就失效误报漏报都吓人商业SAST基于AST/数据流分析的闭源引擎开箱即用、报告好看、规则全贵扫描慢规则是个黑盒误报排噪要命Joern将代码转为CPG用图查询语言分析开源、可自定义规则、支持跨函数污点跟踪有学习曲线部分API随版本变化需要自己维护规则脚本Joern最合适的使用场景是需要自定义审计逻辑的人。比如你们团队有一套私有框架外部输入统一从某个注解参数进入商业SAST根本不认识这套东西但用Joern你只需要在这个入口处定义成污点源剩下的交给图查询和数据流引擎。这种自由度是黑盒商业工具给不了的。2. 环境准备5分钟从零到能跑查询2.1 安装Joern与启动基础服务所谓“5分钟搞定”前提是Joern已经装好了。第一次装环境一般要花半小时左右这个我后面会在避坑部分细说。安装方式很简单直接到Joern官方发布页下载对应平台的安装包或者用官方提供的安装脚本一键安装装好后把joern命令所在目录加进PATH就行。启动时有一个非常容易踩的坑Joern底层依赖资源分配项目比较大的时候默认堆内存往往不够用。保险起见启动前先设置一个环境变量export JOERN_OPTS-Xmx8G joern看到joern提示符就说明进入交互式分析环境了。这个提示符下面可以直接写图查询语句也可以执行导入、运行分析任务等操作本质上是一个Scala语言的REPL只是预置了代码属性图相关的API。2.2 把Java项目导入成CPG导入代码是这一步的重点。假设你本地有一份Maven工程的源码代码在./demo-src目录下执行joern importCode(./demo-src, demo)第一个参数是源码路径第二个参数是工程名随便起方便后面检索。Joern会先做语言识别然后解析源码构建完整的代码属性图。对一个几万行的中型项目这一步通常在1到2分钟内完成项目越大耗时越长。如果只是临时做个小Demo把代码里的src目录单独拎出来分析速度会明显更快。导入完成后你可以快速验证一下CPG有没有建好joern cpg.metaData joern cpg.method.size第二条命令返回方法节点的数量数字不为0就说明图不是空的。我习惯在正式开扫之前先看一眼这个数免得路径配错扫了个空项目还傻等半天。2.3 常用体检命令与信息检索CPG建好后一大优势是排查信息几乎不用写复杂逻辑。比如想知道项目里有哪些方法名带“exec”直接joern cpg.method.name(.*exec.*).map(m (m.filename, m.lineNumber, m.fullName)).l后面接.l是把查询结果转换成一个列表方便在终端里看。如果你在REPL里输入cpg.help或cpg.method.help能看到当前版本支持的查询API列表Joern的交互式文档做得不错查API很方便。3. 核心查询规则四类高频Java漏洞拿捏3.1 命令注入与危险调用命令注入的核心问题是用户可控数据被拼进了系统命令。最常见的落点就是Runtime.getRuntime().exec()和ProcessBuilder以及部分历史遗留项目里的ProcessImpl。第一步先把危险入口全部枚举出来joern cpg.method.name(exec|system|popen|start).where(_.fullName(.*(Runtime|ProcessBuilder|Process).*)).callIn.l这里用.callIn拿到所有调用这些方法的位置输出的每一行都包含调用点的文件名、行号和具体代码。只看结果还不行要判断有没有被外部数据影响下一步往往需要看调用处的参数来源。如果看到exec的参数直接来自request.getParameter这种入口这基本就是一条实打实的命令注入链。一个小技巧是优先用callIn而不是直接搜字符串Runtime.getRuntime().exec(...)因为前者拿到的是“调用关系”而不是“文本”后续还能接着往上游追溯数据流这是纯正则做不到的。3.2 SQL注入相关的动态拼接SQL注入的典型特征是执行SQL的API接收了一个字符串而这个字符串是拼接出来的。Java里最常见的API是Statement.executeQuery、PreparedStatement以及各类ORM的查询方法。Joern查询可以这样写joern cpg.method.name(executeQuery|execute|executeUpdate|prepareStatement) .parameter .filter(_.typeFullName.contains(java.lang.String)) .where(_.code(.*\\.*|.*String\\.format.*|.*concat.*|.*replace.*)) .l这条查询的思路是先定位所有SQL执行方法的参数然后只保留String类型再用代码特征过滤出拼接操作。凡是命中“拼接”“String.format”“concat”的都值得点进去看一眼。实际扫描时会有不少误报比如拼接的是常量、编号、日期格式跟SQL注入没什么关系。我的建议是把它当线索而不是结论先看拼接的内容是从哪来的再决定要不要往下追。3.3 硬编码密钥与敏感信息硬编码密钥这类问题商业工具经常报一堆密钥误报Joern的优势在于可以结合上下文过滤。查询思路是找到赋值语句等号左边是包含“密码”“密钥”“Token”这类关键词的变量等号右边是一个字符串字面量joern cpg.assignment .where(_.target.code(.*(?i)(password|passwd|secret|api[_-]?key|token).*)) .filter(_.source.isLiteral) .l为什么要用赋值节点而不是直接找字符串因为直接搜字符串会把配置文件里的示例、测试用例里的假密钥全部扫出来噪音大得没法看。先定变量名、再限定右值是字面量能过滤掉一多半无关项。但这个查询对“从环境变量取密钥失败后硬编码兜底”这类复杂场景无能为力需要结合语义手动看。如果扫出来的点数量太多可以先按文件名排序优先看Controller、Service、Util这类业务代码里的结果。3.4 反序列化入口与危险链路Java反序列化漏洞经典且危害大通常入口是ObjectInputStream.readObject。Joern里先找出所有反序列化调用点joern cpg.method.name(readObject|readUnshared|resolveObject) .where(_.parameter.typeFullName(.*ObjectInputStream.*)) .callIn .l拿到调用点之后如果反序列化的数据来源是网络请求、外部文件或者调用点所在的类实现了Serializable接口且重写了readObject那就要重点看了。不过通常单靠这层查询找不出完整利用链Joern的价值在于可以继续从readObject的调用位置往下追踪看它是否走到了invoke、defineClass等反射或类加载相关的危险方法。这一步可以放到下一章的污点跟踪一起做效果会比单点查询好得多。4. 进阶污点跟踪查询跨函数追数据流4.1 污点跟踪的基本概念和Joern写法把用户输入想象成一瓶墨水代码执行时墨水沿着函数调用、变量赋值一路流过去最后滴到了危险函数上这就是污点跟踪Taint Tracking。Joern内置了数据流引擎你只需要告诉它三件事污点源source、汇聚点sink、以及帮它理解方法调用的语义信息。剩下的路径搜索交给引擎。以一个常见场景为例HTTP请求参数是sourceRuntime.exec是sink中间那段数据流如果打通就是命令注入链。新版Joern里的写法很直观joern def source cpg.method.name(getParameter).parameter joern def sink cpg.method.name(exec).parameter joern run.taintTracking(source, sink)跑完之后Joern会在工作区生成分析报告列出从source到sink的可达路径。路径上每一步都很重要哪些方法做了转换、哪些地方做了校验、哪些地方能绕过去全都能看清。4.2 一个真实的命令注入链路扫描假设有一个模拟项目X其中某个Controller长这样public class DemoController { public void runCmd(HttpServletRequest request) throws IOException { String cmd request.getParameter(cmd); String safeCmd wrap(cmd); Runtime.getRuntime().exec(safeCmd); } }cmd从请求参数进入中间过了一个wrap方法纯文本正则扫描到这里基本就断了因为它不知道wrap内部做了什么。Joern的做法是直接定义source为getParameter的返回值sink定为exec的参数然后跑数据流引擎。导入项目后按上面的命令执行Joern会输出类似这样的路径摘要request.getParameter(cmd)→ 赋值给局部变量cmd→ 传入wrap方法 → 返回值传给exec。如果wrap里没有做危险字符过滤而只是加了引号或者拼接前缀那路径一样会打通。这样查出来的结论质量比单纯看“哪里调用了exec”要可靠得多。我在实际审计中至少有一半的高危漏洞是靠这种跨污点跟踪挖出来的。4.3 把扫出来的路径导出成报告路径信息可以直接在REPL里阅读但没法直接贴给研发组看。Joern的路径对象可以通过.p转换成可读的字符串表示joern run.taintTracking(source, sink) joern val paths sink.reachableBy(source).p.l joern paths.foreach(println)reachableBy是底层的数据流查询API不同版本调用会有些差异但.p转字符串的思路是一致的。输出之后可以用重定向把结果保存成文件joern import java.io._ joern val w new PrintWriter(new File(taint_report.txt)) joern paths.foreach(p w.println(p)) joern w.close()这样产出的报告能直接作为审计底稿也方便在评审会上演示给研发同学看省去了来回截图的麻烦。5. 查询规则的工程化加入日常审计流程5.1 把规则写成脚本跑批交互式REPL适合探索做日常扫描还是得把查询规则沉淀成脚本。Joern支持通过--script参数直接执行Scala脚本不需要每次手动敲命令。比如新建一个cmd_injection.sc// cmd_injection.sc importCode(./demo-src, demo) def source cpg.method.name(getParameter).parameter def sink cpg.method.name(exec).parameter run.taintTracking(source, sink) sink.reachableBy(source).p.l.foreach(println)然后在命令行执行joern --script cmd_injection.sc这样每次扫描用的规则完全固定、可版本管理新同学接手也不用从零学API。脚本里还可以把多条规则的输出统一汇总到一个文件后面接个脚本跑关键字分析就能自动生成初筛清单。5.2 在CI里跑安全扫描与产出规范Joern跑完整项目扫描不是零成本项目一大导入就要几分钟不太适合每次提交都跑。我见过比较务实的做法是在CI里设置一个“每日定时安全扫描”任务拉最新代码后执行固定脚本把扫描结果作为构建产物上传同时自动通知安全负责人。输出格式建议统一成三列文件名、行号、命中代码。这样既可以人工快速排查也能对接缺陷跟踪系统生成工单。我自己一般会在脚本里用.map(n (n.filename, n.lineNumber, n.code))输出元组再用TSV或CSV格式落盘。Joern本身不强制输出格式重点是字段要固定、结果要可排序可去重。5.3 性能调优与噪音控制使用Joern一段时间后你会发现性能瓶颈主要卡在CPG构建阶段而不是查询阶段。几个加速手段只导入src/main/java目录别把test、target、build目录拉进来代码仓库里资源文件多的话单独排除大型项目扫描前先跑一次run.overflowdb.traversal之类的预处理提前创建索引。噪音控制上最有效的一招是把source定义得更精确。不要一把梭把所有getParameter都当source而是限定特定注解、特定类名下。这样查询速度更快结果里无关路径也会少很多。过滤条件宁可先严后松——先筛出少数可疑点人工确认再逐步放宽source范围扩大战果。6. 常见问题与避坑实录6.1 大项目OOM与内存调优Joern对内存的需求比很多人想象中高。一个几十万行的Java项目构建CPG时轻松吃掉几个GB内存。如果看到OutOfMemoryError或进程直接被操作系统杀掉第一反应应该是检查JOERN_OPTS有没有生效export JOERN_OPTS-Xmx16G这里有一点容易被忽略JOERN_OPTS要写在启动joern命令的同一个终端会话里或者是写进启动脚本中否则不会生效。内存实在不够时把依赖包拆出去、只分析业务代码目录是最直接的降载手段。6.2 Joern版本API差异踩坑Joern版本迭代很快查询API偶有变动网上教程里的命令经常在新版本跑不通。比如早期的taintTracking在某些版本里叫run.taintTracking有的版本需要先创建EngineContext再调用reachableBy。我踩过的坑主要集中在两点一是.l和toList在不同版本里行为基本一致但一些旧API会打印deprecated警告二是assignment、parameter这类节点类型的字段名偶尔会调整报错时先用cpg.help查当前版本的字段定义。一个很实用的办法安装Joern后先执行cpg.help、cpg.method.help把当前版本的属性名和API扫一眼再跑网上的示例。遇到报错别硬改多半不是逻辑问题而是API变了。6.3 误报排查与调用链可视化误报是所有静态分析工具的宿命Joern也不例外。排查误报时我习惯沿着污点路径反向读一遍从sink往回走看每一跳的变量名、方法名和行号。如果路径上有个replaceAll把危险字符全过滤了那就是安全的如果只是走了个纯字符串拼接那多半是真漏洞。调用链信息如果太长可以把路径中的行号在IDE里逐个打开配合断点调试确认。Joern的.p输出已经带了可读代码片段但遇到复杂的跨类、跨方法路径还是要去源码里看上下文这一步不能省。宁可慢一点也别把干干净净的代码扔给研发组。6.4 常见报错速查表报错表现常见原因解决办法导入代码后方法数为0路径配错或语言识别失败确认源码目录结构只导入src目录查询结果为空方法名埋点不对或版本API差异先cpg.method.name(.*exec.*).l验证节点执行报缺少隐式参数需要EngineContext按当前版本文档创建上下文对象大量重复路径输出过滤条件过宽收紧source和sink定义加where条件扫描速度突然变慢未建索引或堆太小增大JOERN_OPTS运行预处理索引任务Joern的生态还在快速演进我自己的感受是它不是一个拿过来就能用的黑盒工具更像一把高度可定制的手术刀。刚开始学的时候确实要被Scala DSL和版本差异磨一阵一旦把“把代码看成图”的思路建立起来你会开始嫌弃那些只会跑正则的扫描器。最直接的收获就是以后再有人拿一份报告说“这里可能有SQL注入”你能在几分钟内沿着图上的路径跟到入口和落点给出包含完整调用链的结论。如果你正在搭代码审计能力或者折腾安全CI流程值得花一个下午把Joern装上拿自己手上的项目跑一次体会一下“图查询式安全分析”的思路。

相关新闻

SQL进阶必学:连接、视图、事务、约束与权限管理实战

SQL进阶必学:连接、视图、事务、约束与权限管理实战

其实在大二的数据库课上学到这里的时候,我并没有觉得"中级SQL"有什么特别的,无非是多记几个语法。直到后来真刀真枪写业务查询、维护线上数据,才意识到这一章其实是整个SQL能力体系里最实用的一段——单表查询是基础,但…

2026/10/18 6:56:58 阅读更多 →
数字后端log高效排查指南:综合到签核的完整阶段拆解与实战

数字后端log高效排查指南:综合到签核的完整阶段拆解与实战

带过我的师傅说过一句话,做数字后端的人,一半时间在跑流程,另一半时间在看log。当时我以为是夸张,等自己真正开始盯综合、布局布线、时钟树综合、时序收敛、物理验证这一整条线之后,才发现这句话几乎就是日常工作写照。…

2026/10/18 6:56:58 阅读更多 →
vim 回到上次编辑的位置:用 ctrl+O / ctrl+I 在 normal mode 里跳转编辑历史

vim 回到上次编辑的位置:用 ctrl+O / ctrl+I 在 normal mode 里跳转编辑历史

/* 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 6:55:58 阅读更多 →

最新新闻

Open-Code-Review:基于CLI与git diffs的AI代码评审范式

Open-Code-Review:基于CLI与git diffs的AI代码评审范式

1. “open-code-review”不是新工具,而是一套可落地的开源协作范式最近在几个技术社区里频繁看到“open-code-review”这个词,有人把它当成某个刚发布的 CLI 工具,有人以为是 GitHub 新功能,还有人直接搜“open-code-review 安装命…

2026/10/19 13:02:20 阅读更多 →
AI一键将Word文档转为信息图:NanoBanana实操指南

AI一键将Word文档转为信息图:NanoBanana实操指南

1. 为什么拿Word文档和信息图过不去:核心需求拆解先说个真实的工作场景。上个月某公司市场部拿到一份十几页的产品调研总结,要在一个下午之内做成一张信息图发到公众号上。如果按传统做法,先通读全文,再提取三四条核心数据&#x…

2026/10/19 13:02:20 阅读更多 →
30天教大模型给孩子讲善恶:AI育儿系统全记录

30天教大模型给孩子讲善恶:AI育儿系统全记录

1. 这个标题是怎么来的:一次让人哭笑不得的对话先交代一下背景。我本职工作是做自然语言处理相关的东西,平时跟大模型打交道比较多。家里有个快四岁的娃,从会说话开始就特别喜欢缠着我问"为什么",比如"为什么不能打…

2026/10/19 13:02:20 阅读更多 →
坚果云团队文件权限怎么设?6种权限场景化指南与进阶技巧

坚果云团队文件权限怎么设?6种权限场景化指南与进阶技巧

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

2026/10/19 13:02:20 阅读更多 →
延续前,保密培训档案必须补齐的 3 类记录

延续前,保密培训档案必须补齐的 3 类记录

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

2026/10/19 13:02:20 阅读更多 →
HTTPS加密过程详解:从TLS握手到密钥协商与排障实战

HTTPS加密过程详解:从TLS握手到密钥协商与排障实战

前阵子某公司同事拿了个抓包文件来找我,说某个内部系统的接口访问时好时坏,偶尔卡住十几秒然后报错。我打开抓包软件扫了一眼,发现大量TLS握手中的重传报文,再往下看,原来他压根没把HTTPS加密过程当回事——以为是“握…

2026/10/19 13:01:18 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 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/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 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/19 2:54:50 阅读更多 →
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/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →