目录
1. 前言
- 本帖所讲的代码库是我在购买亚博智能的jetson orin super开发套件的时候附赠的代码库,并非官方标准版,但因其Ultralytics官方代码相差不大,对于官方代码的学习仍然具有参考价值。
- 本帖以常见的YOLO V8模型导出为例,其他模型导出同理。
我们先来看看软件动态行为关系图吧(不严谨版本,纯手绘):

其中橙色表示外部库的组件,绿色表示本项目工程内部组件,红色表示非常重要的软件接口。
导出onnx代码相关文件的部分文件结构(脚本文件除外):
c:\Users\Mystic\Desktop\pycharm_pro\
├── cfg/
│ └── __init__.py ←── 包含entrypoint函数
├── engine/
│ ├── model.py ←── 包含Model类和export方法
│ └── exporter.py ←── 包含Exporter类
本文要解决的两个重点问题:
- 运行时命令是如何被解析的?
- onnx格式的模型文件是如何生成的?
2. 命令解析
2.1 命令解析大致过程
以YOLOv8的模型导出为例:
yolo export model=yolov8n.pt format=onnx imgsz=640
当执行上述命令时,系统按以下步骤解析参数:
Entry Point启动:通过Python的entry points机制,调用cfg/init.py中的entrypoint()函数- 参数分离:将命令行参数分解为模式(export)和配置项
- 参数合并:按优先级合并配置:命令行参数 > 方法默认值 > 模型参数 > 全局默认值
- 模型实例化:创建YOLO模型实例并加载权重文件()
2.2 命令解析的底层逻辑
既然知道了参数解析的大致过程,那我们不禁要好奇,为什么这套代码可以解析参数?在整个虚拟环境中,我找到了这个txt文件:
/home/jetson/venvs/ultralytics-env/lib/python3.10/site-packages/ultralytics-8.3.189.dist-info/entry_points.txt
里面有这样一段内容:
[console_scripts]
ultralytics = ultralytics.cfg:entrypoint
yolo = ultralytics.cfg:entrypoint
这不就是说,我们在命令行输入了相关格式的内容,就会跳到相关的路径下去执行代码吗?但这还远远不够,txt文件的内容是如何被解析的呢?起到了什么作用呢?
查阅相关资料,发现当pip安装ultralytics包时,具体分成以下步骤:
第一步:pip解析包的配置文件(setup.py或pyproject.toml)
第二步:pip读取其中的entrypoints配置
第三步:pip安装包文件到 site-packages/ultralytics/
第四步:pip创建 ultralytics-{version}.dist-info/ 目录
第五步:pip在dist-info中生成 entry_points.txt 等元数据文件
第六步:pip根据entry_points生成可执行脚本(如/bin/yolo)
首先,第一个问题,为什么pip会乖乖地创建entry_points.txt文件?
在github中打开项目的pyproject.toml我们会有新的发现:

这就都解释得通了,当用pip命令安装代码包的时候,pip会读pyproject.toml文件,而根据文件的描述,在命令行输入yolo和ultralytics 时,直接跳到对应的文件路径中去执行。
从第四步到第六笔就可以发现,这部分都与我们的entry_points有关系,创建了entry_points相关文件,并据此生成了可执行脚本,然后我们就可以在bin文件夹下找到相关的文件。
在bin文件夹下,确实找到了ultralytics和yolo文件

这两个文件具有相同的内容:
#!/home/jetson/venvs/ultralytics-env/bin/python3
import sys
from ultralytics.cfg import entrypoint
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(entrypoint())
这两个相同的脚本文件是如何生成的呢(两个相同,仅以yolo为例)?
首先,pip内部有一个标准模板:
#!/{python_exe}
import sys
from {
module} import {
function}
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4] # Windows兼容性
sys.exit({
function}())
当解析我们的配置文件pyproject.toml时候,看到上述的配置:
[project.scripts]
yolo = "ultralytics.cfg:entrypoint"
pip会自动替换模板中的变量:
{python_exe}→/home/jetson/venvs/ultralytics-env/bin/python3{module}→ultralytics.cfg{function}→entrypoint
最终生成我们看到的/bin/yolo脚本。
具体的执行流程大致是这样的:
用户输入: yolo export model=yolov8n.pt format=onnx imgsz=640
↓
系统找到: /home/jetson/venvs/ultralytics-env/bin/yolo
↓
执行脚本: #!/home/jetson/venvs/ultralytics-env/bin/python3
↓
运行代码: from ultralytics.cfg import entrypoint
↓
Python导入: cfg/__init__.py 中的 entrypoint 函数
↓
调用函数: sys.exit(entrypoint())
↓
进入代码: entrypoint函数开始处理命令行参数
3. 权重文件加载与预处理
现在我们就是顺利运行到了项目的具体代码(cfg/__init__.py)中,代码总用有1000多行,其实重要的代码并没有太多,很多都是判别输入命令的正确性的。让我们来看看cfg/__init__.py的entrypoint函数是如何一步步解析并执行我们的命令的。
3.1 命令拆分与合法性检查
entrypoint函数中,首先对我们输入的命令进行了拆分,并对其合法性进行了检查,在输入命令不合法的时候,给予适当的提示,这部分代码如下:
args = (debug.split(" ") if debug else ARGV)[1:]
if not args: # no arguments passed
LOGGER.info(CLI_HELP_MSG)
return
special = {
"help": lambda: LOGGER.info(CLI_HELP_MSG),
"checks": checks.collect_system_info,
"version": lambda: LOGGER.info(__version__),
"settings": lambda: handle_yolo_settings(args[1:]),
"cfg": lambda: yaml_print(DEFAULT_CFG_PATH),
"hub": lambda: handle_yolo_hub(args[1:]),
"login": lambda: handle_yolo_hub(args),
"logout": lambda: handle_yolo_hub(args),
"copy-cfg": copy_default_cfg,
"solutions": lambda: handle_yolo_solutions(args[1:]),
}
full_args_dict = {
**DEFAULT_CFG_DICT, **{
k: None for k in TASKS}, **{
k: None for k in MODES}, **special}
# Define common misuses of special commands, i.e. -h, -help, --help
special.update({
k[0]: v for k, v in special.items()}) # singular
special.update({
k[:-1]: v for k, v in special.items() if len(k) > 1 and k.endswith("s")}) # singular
special = {
**special, **{
f"-{
k}": v for k, v in special.items()}, **{
f"--{
k}": v for k, v in special.items()}}
overrides = {
} # basic overrides, i.e. imgsz=320
for a in merge_equals_args(args): # merge spaces around '=' sign
if a.startswith("--"):
LOGGER.warning(f"WARNING ⚠️ argument '{
a}' does not require leading dashes '--', updating to '{
a[2:]}'.")
a = a[2:]
if a.endswith(","):
LOGGER.warning(f"WARNING ⚠️ argument '{
a}' does not require trailing comma ',', updating to '{
a[:-1]}'.")
a = a[:-1]
if "=" in a:
try:
k, v = parse_key_value_pair(a)
if k == "cfg" and v is not None: # custom.yaml passed
LOGGER.info(f"Overriding {
DEFAULT_CFG_PATH} with {
v}")
overrides = {
k: val for k, val in yaml_load(checks.check_yaml(v)).items() if k != "cfg"}
else:
overrides[k] = v
except (NameError, SyntaxError, ValueError, AssertionError) as e:
check_dict_alignment(full_args_dict, {
a: ""}, e)
elif a in TASKS:
overrides["task"] = a
elif a in MODES:
overrides["mode"] = a
elif a.lower() in special:
special[a.lower()]()
return
elif a in DEFAULT_CFG_DICT and isinstance(DEFAULT_CFG_DICT[a], bool):
overrides[a] = True # auto-True for default bool args, i.e. 'yolo show' sets show=True
elif a in DEFAULT_CFG_DICT:
raise SyntaxError(
f"'{
colorstr('red', 'bold', a)}' is a valid YOLO argument but is missing an '=' sign "
f"to set its value, i.e. try '{
a}={
DEFAULT_CFG_DICT[a]}'\n{
CLI_HELP_MSG}"
)
else:
check_dict_alignment(full_args_dict, {
a: ""})
# Check keys
check_dict_alignment(full_args_dict, overrides)
首先是参数拆分:
args = (debug.split(" ") if debug else ARGV)[1:]
输入的命令被args保存起来,变成了这样的一个列表:
args = ['export', 'model=yolov8n.pt', 'format=engine', 'imgsz=640']
仅仅这样还不够,还要做进一步的处理,将args进一步通过=号拆分成键值对,然后将其保存到一个overrides字典中,解析后字典变成了:
overrides = {
"mode": "export", "model": "yolov8n.pt", "format": "onnx", "imgsz": 640}
最后将字典传入函数check_dict_alignment()中,对其进行对其合法性检查:
def check_dict_alignment(base: Dict, custom: Dict, e=None):
custom = _handle_deprecation(custom)
base_keys, custom_keys = (set(x.keys()) for x in (base, custom))
mismatched = [k for k in custom_keys if k not in base_keys]
if mismatched:
from difflib import get_close_matches
string = ""
for x in mismatched:
matches = get_close_matches(x, base_keys) # key list
matches = [f"{
k}={
base[k]}" if base.get(k) is not None else k for k in matches]
match_str = f"Similar arguments are i.e. {
matches}." if matches else ""
string += f"'{
colorstr('red', 'bold', x)}' is not a valid YOLO argument. {
match_str}\n"
raise SyntaxError(string + CLI_HELP_MSG) from e
紧接着,是领取主要任务:
# Mode
mode = overrides.get("mode")
然后加载相关模型:
# Model
model = overrides.pop("model", DEFAULT_CFG.model)
if model is None:
model = "yolo11n.pt"
LOGGER.warning(f"WARNING ⚠️ 'model' argument is missing. Using default 'model={
model}'.")
overrides["model"] = model
stem = Path(model).stem.lower()
if "rtdetr" in stem: # guess architecture
from ultralytics import RTDETR
model = RTDETR(model) # no task argument
elif "fastsam" in stem:

最低0.47元/天 解锁文章
188

被折叠的 条评论
为什么被折叠?



