news 2026/10/23 13:47:34

基于 PaddleCV 新增推理算子:三类算子体系、数据契约与完整实现指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于 PaddleCV 新增推理算子:三类算子体系、数据契约与完整实现指南
  • 人工智能
  • 深度学习
  • 计算机视觉
  • NLP
  • 语音

【免费下载链接】models

Officially maintained, supported by PaddlePaddle, including CV, NLP, Speech, Rec, TS, big models and so on.

项目地址:https://gitcode.com/gh_mirrors/mo/models
点击查看免费下载

PaddleCV 是 PaddlePaddle 社区维护的一套计算机视觉推理框架(当前仓库位于paddlecv/目录),它以"算子(op)"为基本单元,通过配置驱动的方式将预处理、模型推理、后处理、衔接与输出串接成一条可复用的推理流水线。本文以paddlecv/docs/how_to_add_new_op.md为主线,完整讲解 PaddleCV 的算子分类、统一输入/输出数据格式,以及如何新增模型推理算子、模型衔接算子、模型输出算子并配套单测。读完本文,你将掌握 PaddleCV 的算子注册机制(@register)、三类算子的继承体系与必须实现的方法签名,并能照葫芦画瓢地扩展出自定义算子。

1. 算子体系总览:PaddleCV 的三类 op

PaddleCV 的推理流水线由 op 串联而成,按职责划分为三类,对应paddlecv/ppcv/ops/下的三个子目录:

  • 模型推理算子(paddlecv/ppcv/ops/models/):给定输入,加载模型,完成预处理、推理、后处理,返回输出。例如图像分类 opClassificationOp。
  • 模型衔接算子(paddlecv/ppcv/ops/connector/):给定输入,计算得到输出,一般用于将一个模型的输出处理为另一个模型的输入,例如目标检测/文本检测的抠图(BboxCropOp)、方向矫正模块之后的图像旋转(ClsCorrectionOp)、文本合成(FragmentCompositionOp)等。
  • 模型输出算子(paddlecv/ppcv/ops/output/):负责存储、可视化、输出模型的输出结果,例如分类输出ClasOutput可将结果打印、存图、写 JSON 或直接返回。

三类 op 都继承自统一的基类BaseOp(定义于 paddlecv/ppcv/ops/base.py),该基类规定了两个通用约定:

  • 每个 op 通过配置文件中的Inputs字段声明其依赖的输入 key,BaseOp.__init__中通过self.input_keys = model_cfg["Inputs"]保存;
  • 每个 op 通过类方法get_output_keys()声明输出的 key 列表,输出 key 统一规范为op_name.key的形式,例如ClassificationOp声明["class_ids", "scores", "label_names"],实际输出 key 为cls.class_ids、cls.scores、cls.label_names(见 ModelBaseOp.init中self.output_keys = [self.name + '.' + key for key in keys])。

此外,BaseOp还提供了两个开箱即用的通用方法:filter_input()按input_keys从上一 op 的输出 dict 中筛选本 op 需要的字段,check_output()校验输出是否为Sequence且元素为dict,并逐 key 比对输出键名是否与output_keys一致,从框架层面保证算子间数据契约不被破坏。

2. 单个 op 的统一输入/输出格式:a list of dict

PaddleCV 的输入为图像或者视频。无论哪类 op,系统都会把数据整理为a list of dict的格式:列表中的每个元素都是一个待推理的对象及其携带的中间结果。以图像分类为例,其输入仅包含图像信息:

[ {"image": img1}, {"image": img2}, ]

经过ClassificationOp推理后,输出格式为:

[ {"image": img1, "class_ids": class_id1, "scores": scores1, "label_names": label_names1}, {"image": img2, "class_ids": class_id2, "scores": scores2, "label_names": label_names2}, ]

对于模型衔接算子,输入是前序模型输出 + 原始图像的组合。以BboxCropOp为例,其输入为:

[ {"image": img1, "bbox": bboxes1}, {"image": img2, "bbox": bboxes2}, ]

list of dict契约之所以关键,是因为它同时服务于三类算子:流水线中每个 op 都从上一个 op 输出的 dict 中按Inputs声明取数,再把自己的结果以op_name.key的规范键名写回 dict(ModelBaseOp/ConnectorBaseOp中通过self.output_keys统一加self.name + '.'前缀实现),从而形成一条可组合、可复用的数据链。

从源码看,PaddleCV 在入口处就已经把输入包装成这种格式:pipeline.py 的predict_images将每张图包装为{'input.image': img, 'input.fn': path}的 dict 列表再交给Executor.run();视频场景则在 predict_video 中逐帧包装为{'input.image': frame_rgb, 'input.fn': input}。因此自定义 op 时无需关心输入来源,只需遵守 list-of-dict 契约。

3. 新增模型推理算子(Model Op)

模型推理算子整体继承自ModelBaseOp(paddlecv/ppcv/ops/models/base.py),示例可参考图像分类 opClassificationOp(paddlecv/ppcv/ops/models/classification/inference.py)。需要实现的内容如下:

  1. 继承与注册:类必须继承自ModelBaseOp,并使用@register装饰器注册,保证全局唯一(重复注册会抛ValueError,见 paddlecv/ppcv/core/workspace.py 的register实现)。
  2. 实现类方法:
方法输入输出说明
__init__model_cfg、env_cfg无完成配置解析、predictor 构建、preprocessor/postprocessor 构建
preprocess基于input_keys过滤后的模型输入模型预处理结果逐样本执行预处理算子链
postprocess模型推理结果模型后处理结果逐样本执行后处理算子链
__call__该 op 依赖的输入内容该 op 的处理结果组织"解析输入 → 推理 → 合并结果"全流程

从 ModelBaseOp 的源码可以看清模型推理算子的公共骨架:

  • __init__中会通过get_model_path()解析param_path/model_path,将model_cfg中的batch_size(默认 1)写入env_cfg,支持delete_pass配置剔除推理图的某些 pass,然后基于 Paddle Inference 构建PaddlePredictor,最后读取模型的输入名(input_names)并以name.key形式生成output_keys;
  • type()返回'MODEL',标识算子类别;
  • preprocess/postprocess在基类中为raise NotImplementedError,必须由子类实现。

以ClassificationOp为例,它的实现要点包括:

  • get_output_keys()返回["class_ids", "scores", "label_names"];
  • __init__中通过create_operators(model_cfg["PreProcess"], mod)与create_operators(model_cfg["PostProcess"], mod)把配置中的预处理/后处理算子列表实例化(create_operators定义于 paddlecv/ppcv/ops/base.py,支持以字符串或{OpName: params}形式声明算子);
  • infer()中按batch_size分批:先逐样本preprocess后np.concatenate成 batch,再调用self.predictor.run(inputs)推理,最后postprocess;
  • __call__()中先按input_keys解析输入(支持单样本或样本列表两种形态),调用infer后按各样本的子长度把批量结果切分回list of dict返回(paddlecv/ppcv/ops/models/classification/inference.py)。

配套的配置文件示例见 paddlecv/configs/single_op/PP-LCNet.yml,ClassificationOp的完整配置结构为:

MODEL: - ClassificationOp: name: cls param_path: paddlecv://models/PPLCNet_x1_0_infer/inference.pdiparams model_path: paddlecv://models/PPLCNet_x1_0_infer/inference.pdmodel batch_size: 8 PreProcess: - ResizeImage: resize_short: 256 - CropImage: size: 224 - NormalizeImage: scale: 0.00392157 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' channel_num: 3 - ToCHWImage: - ExpandDim: axis: 0 PostProcess: - Topk: topk: 5 class_id_map_file: "paddlecv://dict/classification/imagenet1k_label_list.txt" Inputs: - input.image

配置要点说明:

  • param_path/model_path:Paddle Inference 的inference.pdiparams与inference.pdmodel,支持paddlecv://协议自动下载仓库内模型;
  • batch_size:推理批大小,ModelBaseOp会将其写入env_cfg供推理循环使用;
  • PreProcess/PostProcess:预处理/后处理算子链,按声明顺序依次执行(ClassificationOp的preprocess中outputs = ops(outputs)逐个串联);
  • Inputs:声明该 op 依赖的输入 key,用于从上游输出 dict 中过滤数据。

4. 新增模型衔接算子(Connector Op)

模型衔接算子整体继承自ConnectorBaseOp(paddlecv/ppcv/ops/connector/base.py),示例可参考方向矫正 opClsCorrectionOp(paddlecv/ppcv/ops/connector/op_connector.py)。需要实现的内容如下:

  1. 继承与注册:类必须继承自ConnectorBaseOp,并使用@register装饰器注册。
  2. 实现类方法:
方法输入输出说明
__init__model_cfg、env_cfg(一般为 None)无解析自身配置,校验输入 key
__call__该 op 依赖的输入内容该 op 的处理结果核心计算逻辑,如旋转/抠图/拼接

与模型推理算子不同,衔接算子不加载模型、没有推理过程,因此ConnectorBaseOp比ModelBaseOp更轻量:它同样在__init__中生成name.key形式的output_keys,type()返回'CONNECTOR',但不需要preprocess/postprocess抽象方法,只需实现__call__。

ClsCorrectionOp是很好的模板,它演示了衔接算子中"配置驱动 + 输入校验 + 纯计算"的典型写法:

  • 通过model_cfg["class_num"](仅支持 2 或 4)决定旋转映射表:2 类时{1: ROTATE_180},4 类时{1: ROTATE_90_COUNTERCLOCKWISE, 2: ROTATE_180, 3: ROTATE_90_CLOCKWISE};
  • 通过model_cfg["threshold"]控制置信度阈值,只有prob >= threshold且类别命中映射表时才调用cv2.rotate旋转图像;
  • check_input_keys()校验Inputs必须恰好包含 3 个 key(image、cls_id、prob);
  • get_output_keys()返回["corr_image"];
  • __call__中逐条读取input_keys对应的字段,兼容"单样本 / 样本列表"两种形态,返回旋转后的图像列表。

同一个文件中的BboxCropOp(paddlecv/ppcv/ops/connector/op_connector.py)则是"检测框抠图"的标准实现:输入image与bbox(N x 4,x1,y1,x2,y2),对每个框做image[ymin:ymax, xmin:xmax, :].copy()切片,输出crop_image,常被用于检测 → 识别(如 OCR)的级联场景。PolyCropOp进一步支持任意四边形(N x 4 x 2 的角点),通过透视变换(cv2.getPerspectiveTransform+cv2.warpPerspective)矫正出规整的文本行图像。这些现成算子可作为自定义衔接算子的直接参照。

5. 新增模型输出算子(Output Op)

模型输出算子整体继承自OutputBaseOp(paddlecv/ppcv/ops/output/base.py),示例可参考分类输出 opClasOutput(paddlecv/ppcv/ops/output/classification.py)。需要实现的内容如下:

  1. 继承与注册:类必须继承自OutputBaseOp,并使用@register装饰器注册。
  2. 实现类方法:
方法输入输出说明
__init__model_cfg、env_cfg(一般为 None)无解析输出目录与保存选项
__call__模型输出返回结果按需打印、存图、写 JSON 或返回结果

从 OutputBaseOp 源码可见其通用行为:type()返回'OUTPUT';__init__中从env_cfg读取并默认以下选项:

  • output_dir(默认'output'):输出目录,不存在时自动创建;
  • save_img(默认 False):是否保存可视化图像;
  • save_res(默认 False):是否把结构化结果保存为 JSON 文件;
  • return_res(默认 False):是否将结果作为返回值回传;
  • print_res(默认 False):是否打印结果。

ClasOutput.__call__完整演示了这些选项的组合用法(paddlecv/ppcv/ops/output/classification.py):逐条解包fn, image, class_ids, scores, label_names,通过 logger 打印每条结果;save_img时把 RGB 图像转回 BGR 并cv2.imwrite到output_dir(文件名取原始文件名);save_res时把结果列表json.dump为clas_output.json;return_res时返回total_res列表,否则返回 None。注意输出算子同样遵循list of dict契约——它读入的是上游 op 输出的 dict 列表。

6. 新增单测:验证 op 的正确性

在新增 op 之后,需要新增基于该 op 的单测,可参考 paddlecv/tests/test_classification.py。该测试的核心逻辑是:

  1. setUp中指定配置文件configs/unittest/test_classification.yml与输入图片demo/ILSVRC2012_val_00020010.jpeg,通过ConfigParser解析出model_cfg与env_cfg;
  2. 用cv2.imread读图并转 RGB,构造两组list of dict输入(一组单张[img]、一组两张[img, img]),用于覆盖"单样本 / 样本列表"两条路径;
  3. 从global_config(即@register的注册表,见 paddlecv/ppcv/core/workspace.py)按 op 名取出类,传入model_cfg[op_name]与env_cfg实例化,直接调用cls_op(inputs)断言能正常完成推理链。

为新增 op 写单测时,只需照此模式替换为自己的 op 名、配置文件与输入数据即可。测试使用的配置文件与单算子配置基本一致(paddlecv/configs/unittest/test_classification.yml),其中ENV段还包含了run_mode(如paddle)、device(如GPU)等运行环境选项,MODEL段则与单算子配置共用同一套 op 结构,便于测试与实跑保持行为一致。

7. 从 op 到流水线:注册、配置与执行链路

自定义 op 写好之后,是如何被 PaddleCV 组织成流水线的?从源码可以梳理出如下链路:

  1. 注册:@register将 op 类写入global_config全局注册表(paddlecv/ppcv/core/workspace.py),重复注册同名类会报错;create()按名字实例化 op。
  2. 配置解析:Pipeline.__init__通过ConfigParser解析 yaml,得到model_cfg(MODEL段,op 列表)与env_cfg(ENV段,运行环境),详见 paddlecv/ppcv/engine/pipeline.py。
  3. 执行:Executor(paddlecv/ppcv/core/framework.py)按配置顺序实例化每个 op,把上一 op 的输出 dict 列表作为下一 op 的输入,逐个调用__call__,实现"预处理 → 推理 → 衔接 → 输出"的链式推理。

create_operators(paddlecv/ppcv/ops/base.py)支持两种声明写法:仅写算子名(如- ToCHWImage:,参数为空 dict)或写{OpName: params}的键值对形式;配置结构不合法(非 list、dict 长度不为 1)时会直接断言报错。这些机制共同保证了:新增一个 op,本质上就是"实现一个继承自对应基类的类 + 一个@register装饰器 + 一段 yaml 配置",之后即可被流水线无缝加载。

8. 结语与扩展指引

至此,PaddleCV 新增算子的完整套路已经清晰:先明确 op 的三类归属(模型推理 / 模型衔接 / 模型输出),继承对应的ModelBaseOp/ConnectorBaseOp/OutputBaseOp;再用@register注册保证全局唯一;接着实现__init__与__call__(模型推理算子还需实现preprocess/postprocess),并遵守list of dict的输入输出契约与name.key的输出键规范;最后参考test_classification.py补充单测。流水线、配置文件与注册表三者配合,即可让新 op 立即参与多模型级联推理。

更多细节可进一步阅读:

  • 算子基类与工厂:paddlecv/ppcv/ops/base.py、paddlecv/ppcv/ops/models/base.py、paddlecv/ppcv/ops/connector/base.py、paddlecv/ppcv/ops/output/base.py
  • 注册机制:paddlecv/ppcv/core/workspace.py
  • 流水线执行:paddlecv/ppcv/engine/pipeline.py、paddlecv/ppcv/core/framework.py
  • 配置示例:paddlecv/configs/single_op/PP-LCNet.yml、paddlecv/configs/unittest/test_classification.yml
  • 测试示例:paddlecv/tests/test_classification.py
  • 系统设计说明:paddlecv/docs/system_design.md、paddlecv/docs/config_anno.md
  • 人工智能
  • 深度学习
  • 计算机视觉
  • NLP
  • 语音

【免费下载链接】models

Officially maintained, supported by PaddlePaddle, including CV, NLP, Speech, Rec, TS, big models and so on.

项目地址:https://gitcode.com/gh_mirrors/mo/models
点击查看免费下载

相关推荐

上一篇:Intersection Observer API:终极懒加载和无限滚动实现指南
下一篇:Go并发模式终极指南:10种常见场景完整解决方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 21:04:31

大二计算机规划参考

大二计算机软件学习路径总计划 目标 帮助大二计算机软件学生建立清晰的学习路径,在“开发、人工智能、安全”三大方向中完成探索、选择和深入。 这份计划暂时不展开硬件、嵌入式、电路、单片机等内容,但保留“系统、Linux、内核、安全”等偏底层软件方…

作者头像 李华
网站建设 2026/10/16 0:30:57

【信息科学与工程学】【通信工程】第二百三十九篇 超大规模企业组网设计 第二章节 04 节 互联网边缘与加速 01

第2章 全球广域、云计算与网络基础设施 4.互联网边缘与加速 DNS/GSLB、CDN、边缘计算、DDoS清洗、入站WAF/WAAP SaaS加速:Office/CRM/代码/Salesforce等经SAS 一、总体定位与字数分配 本节放在“第2章 全球广域、云计算与网络基础设施”下,承担“用户/流量进入互联网与云之…

作者头像 李华
网站建设 2026/10/22 5:31:31

SpringBoot接口限流实战:从本地滑动窗口到Redis与Sentinel分布式限流

写SpringBoot接口,最怕什么?不是业务逻辑复杂,也不是数据库慢查询,而是某个接口突然被刷爆,后面所有请求跟着一起死。我自己遇到过最典型的一次:一个内部数据查询接口平时只有几十QPS,结果某个合…

作者头像 李华
网站建设 2026/10/18 0:07:35

葡萄目标检测实操:YOLO/VOC双格式数据集与YOLOv8训练避坑指南

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

作者头像 李华