From e908959c31cfc28f420ef88f0d84cdbff8600f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Haian=20Huang=28=E6=B7=B1=E5=BA=A6=E7=9C=B8=29?= <1286304229@qq.com> Date: Thu, 17 Feb 2022 22:33:52 +0800 Subject: [PATCH] [Docs] add visualizer docs (#16) * add visualizer * update * update * update * update * update * fix lint * fix commit * fix commit * fix commit * fix commit * refine * refine * update * update * update * update * update --- docs/zh_cn/tutorials/visualizer.md | 269 +++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 docs/zh_cn/tutorials/visualizer.md diff --git a/docs/zh_cn/tutorials/visualizer.md b/docs/zh_cn/tutorials/visualizer.md new file mode 100644 index 00000000..0bff95c2 --- /dev/null +++ b/docs/zh_cn/tutorials/visualizer.md @@ -0,0 +1,269 @@ +# å¯è§†åŒ– (Visualization) + +## 概述 + +**(1) 总体介ç»** + +å¯è§†åŒ–å¯ä»¥ç»™æ·±åº¦å¦ä¹ 的模型è®ç»ƒå’Œæµ‹è¯•è¿‡ç¨‹æ供直观解释。在 OpenMMLab 算法库ä¸ï¼Œæˆ‘们期望å¯è§†åŒ–功能的设计能满足以下需求: + +- æ供丰富的开箱å³ç”¨å¯è§†åŒ–功能,能够满足大部分计算机视觉å¯è§†åŒ–任务 +- 高扩展性,å¯è§†åŒ–åŠŸèƒ½é€šå¸¸å¤šæ ·åŒ–ï¼Œåº”è¯¥èƒ½å¤Ÿé€šè¿‡ç®€å•æ‰©å±•å®žçŽ°å®šåˆ¶éœ€æ±‚ +- 能够在è®ç»ƒå’Œæµ‹è¯•æµç¨‹çš„ä»»æ„点ä½è¿›è¡Œå¯è§†åŒ– +- OpenMMLab å„个算法库具有统一å¯è§†åŒ–接å£ï¼Œåˆ©äºŽç”¨æˆ·ç†è§£å’Œç»´æŠ¤ + +基于上述需求,OpenMMLab 2.0 引入了绘制对象 Visualizer 和写端对象 Writer 的概念 + +- **Visualizer è´Ÿè´£å•å¼ 图片的绘制功能** + + MMEngine æ供了以 Matplotlib 库为绘制åŽç«¯çš„ `Visualizer` 类,其具备如下功能: + + - æä¾›äº†ä¸€ç³»åˆ—å’Œè§†è§‰ä»»åŠ¡æ— å…³çš„åŸºç¡€æ–¹æ³•ï¼Œä¾‹å¦‚ `draw_bboxes` å’Œ `draw_texts` ç‰ + - 上述å„个基础方法支æŒé“¾å¼è°ƒç”¨ï¼Œæ–¹ä¾¿å åŠ ç»˜åˆ¶æ˜¾ç¤º + - æ供了绘制特å¾å›¾åŠŸèƒ½ + + å„个下游算法库å¯ä»¥ç»§æ‰¿ `Visualizer` 并在 `draw` 接å£å®žçŽ°æ‰€éœ€çš„å¯è§†åŒ–功能,例如 MMDetection ä¸çš„ `DetVisualizer` 继承自 `Visualizer` 并在 `draw` 接å£å®žçŽ°å¯è§†åŒ–检测框ã€å®žä¾‹æŽ©ç å’Œè¯ä¹‰åˆ†å‰²å›¾ç‰åŠŸèƒ½ã€‚Visualizer 类的 UML 关系图如下 + + <div align="center"> + <img src="https://user-images.githubusercontent.com/17425982/154475592-7208a34b-f6cb-4171-b0be-9dbb13306862.png" > + </div> + +- **Writer 负责将å„类数æ®å†™å…¥åˆ°æŒ‡å®šåŽç«¯** + + 为了统一接å£è°ƒç”¨ï¼ŒMMEngine æ供了统一的抽象类 `BaseWriter`,和一些常用的 Writer 如 `LocalWriter` æ¥æ”¯æŒå°†æ•°æ®å†™å…¥æœ¬åœ°ï¼Œ`TensorboardWriter` æ¥æ”¯æŒå°†æ•°æ®å†™å…¥ Tensorboard,`WandbWriter` æ¥æ”¯æŒå°†æ•°æ®å†™å…¥ Wandb。用户也å¯ä»¥è‡ªå®šä¹‰ Writer æ¥å°†æ•°æ®å†™å…¥è‡ªå®šä¹‰åŽç«¯ã€‚写入的数æ®å¯ä»¥æ˜¯å›¾ç‰‡ï¼Œæ¨¡åž‹ç»“æž„å›¾ï¼Œæ ‡é‡å¦‚æ¨¡åž‹ç²¾åº¦æŒ‡æ ‡ç‰ã€‚ + + 考虑到在è®ç»ƒæˆ–者测试过程ä¸åŒæ—¶å˜åœ¨å¤šä¸ª Writer 对象,例如åŒæ—¶æƒ³è¿›è¡Œæœ¬åœ°å’Œè¿œç¨‹ç«¯å†™æ•°æ®ï¼Œä¸ºæ¤è®¾è®¡äº† `ComposedWriter` 负责管ç†æ‰€æœ‰è¿è¡Œä¸å®žä¾‹åŒ–çš„ Writer 对象,其会自动管ç†æ‰€æœ‰ Writer 对象,并é历调用所有 Writer 对象的方法。Writer 类的 UML 关系图如下 + <div align="center"> + <img src="https://user-images.githubusercontent.com/17425982/154474755-080b955b-436b-4cdb-9a49-16a9f231ce81.png" > + </div> + +**(2) Writer å’Œ Visualizer 关系** + +Writer å¯¹è±¡çš„æ ¸å¿ƒåŠŸèƒ½æ˜¯å†™å„类数æ®åˆ°æŒ‡å®šåŽç«¯ä¸ï¼Œä¾‹å¦‚写图片ã€å†™æ¨¡åž‹å›¾ã€å†™è¶…å‚å’Œå†™æ¨¡åž‹ç²¾åº¦æŒ‡æ ‡ç‰ï¼ŒåŽç«¯å¯ä»¥æŒ‡å®šä¸ºæœ¬åœ°å˜å‚¨ã€Wandb å’Œ Tensorboard ç‰ç‰ã€‚在写图片过程ä¸ï¼Œé€šå¸¸å¸Œæœ›èƒ½å¤Ÿå°†é¢„æµ‹ç»“æžœæˆ–è€…æ ‡æ³¨ç»“æžœç»˜åˆ¶åˆ°å›¾ç‰‡ä¸Šï¼Œç„¶åŽå†è¿›è¡Œå†™æ“作,为æ¤åœ¨ Writer 内部维护了 Visualizer 对象,将 Visualizer 作为 Writer 的一个属性。当需è¦åˆ©ç”¨ Visualizer 对象æ¥ç»˜åˆ¶ç»“果到图片上时候,å¯ä»¥é€šè¿‡è°ƒç”¨ Writer çš„ Visualizer 属性对象进行绘制。一个简略的演示代ç 如下 + +```python +# 为了方便ç†è§£ï¼Œæ²¡æœ‰ç»§æ‰¿ BaseWriter +class WandbWriter: + def __init__(self, visualizer=None): + self._visualizer = None + if visualizer: + # 示例é…ç½® visualizer=dict(type='DetVisualizer') + self._visualizer = VISUALIZERS.build(visualizer) + + @property + def visualizer(self): + return self._visualizer + + def add_image(self, name, image, datasample=None, step=0, **kwargs): + if self._visualize: + self._visualize.draw(image, datasample) + # 调用 Writer API 写图片到åŽç«¯ + self.wandb.log({name: self.visualizer.get_image()}, ...) + ... + else: + # 调用 Writer API 汇总并写图片到åŽç«¯ + ... + + def add_scaler(self, name, value, step): + self.wandb.log({name: value}, ...) +``` + +å¯¹äºŽéž `LocalWriter` 或者ä¸éœ€è¦è°ƒç”¨å†™å›¾ç‰‡çš„ `add_image` 接å£éœ€æ±‚场景,visualizer å‚æ•°å¯ä»¥ä¸º None。 + +æ³¨æ„ `Visualizer` 仅仅有å•å›¾ç»˜åˆ¶åŠŸèƒ½ï¼Œå¦‚果想将绘制结果ä¿å˜ï¼Œä¾‹å¦‚ä¿å˜åˆ°æœ¬åœ°ã€Wandb 或者 Tensorboard,å¯ä»¥ä½¿ç”¨ Writer 写端对象。一个推è的写法如下 + +```python +# é…置文件 +writer=dict(type='LocalWriter', save_dir='demo_dir', visualizer=dict(type='DetVisualizer')) +# 实例化和调用 +writer_obj=WRITERS.build(writer) +writer_obj.add_image('demo_image', image, datasample) +``` + +在 Runner ä¸é»˜è®¤çš„实现方å¼ä¹Ÿæ˜¯ç±»ä¼¼ä¸Šè¿°å†™æ³•ï¼Œæˆ‘们也推è用户在模型ä¸å¼€å‘自定义å¯è§†åŒ–功能的时候也采用这ç§æ–¹å¼ã€‚如果用户有必è¦ç›´æŽ¥è°ƒç”¨ visualizer ä¸æŽ¥å£è¿›è¡Œç»˜åˆ¶åŠŸèƒ½ï¼Œåˆ™å¯ä»¥é‡‡ç”¨å¦‚下写法 + +```python +# é…置文件 +writer=dict(type='LocalWriter', save_dir='demo_dir', visualizer=dict(type='DetVisualizer')) +# 实例化和调用 +writer_obj=WRITERS.build(writer) +# 以调用 draw 方法为例 +writer_obj.visualizer.draw(image, datasample) +writer_obj.add_image('demo_image', writer_obj.visualizer.get_image()) +``` + +在使用 Jupyter notebook 或者其他地方ä¸éœ€è¦ writer 的情形下,用户å¯ä»¥è‡ªå·±å®žä¾‹åŒ– visualizer。一个简å•çš„例å如下 + +```python +# 实例化 visualizer +visualizer=dict(type='DetVisualizer') +visualizer = VISUALIZERS.build(visualizer) +visualizer.draw(image, datasample) +``` + + +## 绘制对象 Visualizer + +绘制对象 Visualizer è´Ÿè´£å•å¼ 图片的å„类绘制功能,默认绘制åŽç«¯ä¸º Matplotlib。为了统一 OpenMMLab å„个算法库的å¯è§†åŒ–接å£ï¼ŒMMEngine 定义æ供了基于基础绘制功能的 `Visualizer` 类,下游库å¯ä»¥ç»§æ‰¿ `Visualizer` 并实现 `draw` 接å£å®žçŽ°è‡ªå·±çš„å¯è§†åŒ–需求,例如 MMDetection çš„ [`DetVisualizer`]()。 + +### Visualizer + +`Visualizer` æ供了基础而通用的绘制功能,主è¦æŽ¥å£å¦‚下: + +**(1) ç»˜åˆ¶æ— å…³çš„åŠŸèƒ½æ€§æŽ¥å£** + +- set_image è®¾ç½®åŽŸå§‹å›¾ç‰‡æ•°æ® +- get_image 获å–绘制åŽçš„ Numpy æ ¼å¼å›¾ç‰‡æ•°æ® +- show å¯è§†åŒ– +- register_task 注册绘制函数(其作用在 *自定义 Visualizer* å°èŠ‚æè¿°) + +**(2) 绘制相关接å£** + +- draw ç”¨æˆ·ä½¿ç”¨çš„æŠ½è±¡ç»˜åˆ¶æŽ¥å£ +- draw_featmap 绘制特å¾å›¾ +- draw_bboxes 绘制å•ä¸ªæˆ–者多个边界框 +- draw_texts 绘制å•ä¸ªæˆ–者多个文本框 +- draw_lines 绘制å•ä¸ªæˆ–者多个线段 +- draw_circles 绘制å•ä¸ªæˆ–者多个圆 +- draw_polygons 绘制å•ä¸ªæˆ–者多个多边形 +- draw_binary_masks 绘制å•ä¸ªæˆ–者多个二值掩ç + +**(1) 用例 1 - 链å¼è°ƒç”¨** + +例如用户先绘制边界框,在æ¤åŸºç¡€ä¸Šç»˜åˆ¶æ–‡æœ¬ï¼Œç»˜åˆ¶çº¿æ®µï¼Œåˆ™è°ƒç”¨è¿‡ç¨‹ä¸ºï¼š + +```python +visualizer.set_image(image) +visualizer.draw_bboxes(...).draw_texts(...).draw_lines(...) +``` + +**(2) 用例 2 - å¯è§†åŒ–特å¾å›¾** + +特å¾å›¾å¯è§†åŒ–是一个常è§çš„功能,通过调用 `draw_featmap` å¯ä»¥ç›´æŽ¥å¯è§†åŒ–特å¾å›¾ï¼Œç›®å‰è¯¥å‡½æ•°æ”¯æŒå¦‚下功能: + +- 输入 4 ç»´ BCHW æ ¼å¼çš„ tensorï¼Œé€šé“ C 是 1 或者 3 时候,展开æˆä¸€å¼ 图片显示 +- 输入 4 ç»´ BCHW æ ¼å¼çš„ tensorï¼Œé€šé“ C 大于 3 时候,则支æŒé€‰æ‹©æ¿€æ´»åº¦æœ€é«˜é€šé“,展开æˆä¸€å¼ 图片显示 +- 输入 3 ç»´ CHW æ ¼å¼çš„ tensor,则选择激活度最高的 topk,然åŽæ‹¼æŽ¥æˆä¸€å¼ 图显示 + +```python +# 如果æå‰è®¾ç½®äº†å›¾ç‰‡ï¼Œåˆ™ç‰¹å¾å›¾æˆ–者图片å åŠ æ˜¾ç¤ºï¼Œå¦åˆ™åªæ˜¾ç¤ºç‰¹å¾å›¾ +visualizer.set_image(image) +visualizer.draw_featmap(...) +visualizer.save(...) +``` + +### 自定义 Visualizer + +自定义的 Visualizer ä¸å¤§éƒ¨åˆ†æƒ…况下åªéœ€è¦å®žçŽ° `get_image` å’Œ `draw` 接å£ã€‚`draw` 是最高层的用户调用接å£ï¼Œ`draw` 接å£è´Ÿè´£æ‰€æœ‰ç»˜åˆ¶åŠŸèƒ½ï¼Œä¾‹å¦‚绘制检测框ã€æ£€æµ‹æŽ©ç mask å’Œ 检测è¯ä¹‰åˆ†å‰²å›¾ç‰ç‰ã€‚ä¾æ®ä»»åŠ¡çš„ä¸åŒï¼Œ`draw` 接å£å®žçŽ°çš„å¤æ‚度也ä¸åŒã€‚ + +ä»¥ç›®æ ‡æ£€æµ‹å¯è§†åŒ–需求为例,å¯èƒ½éœ€è¦åŒæ—¶ç»˜åˆ¶è¾¹ç•Œæ¡† bboxã€æŽ©ç mask å’Œè¯ä¹‰åˆ†å‰²å›¾ seg_map,如果如æ¤å¤šåŠŸèƒ½å…¨éƒ¨å†™åˆ° `draw` 方法ä¸ä¼šéš¾ä»¥ç†è§£å’Œç»´æŠ¤ã€‚为了解决该问题,`Visualizer` 基于 OpenMMLab 2.0 抽象数æ®æŽ¥å£è§„范支æŒäº† `register_task` 函数。å‡è®¾ MMDetection ä¸éœ€è¦åŒæ—¶ç»˜åˆ¶é¢„测结果ä¸çš„ instances å’Œ sem_seg,å¯ä»¥åœ¨ MMDetection çš„ `DetVisualizer` ä¸å®žçŽ° `draw_instances` å’Œ `draw_sem_seg` 两个方法,用于绘制预测实例和预测è¯ä¹‰åˆ†å‰²å›¾ï¼Œ 我们希望åªè¦è¾“入数æ®ä¸å˜åœ¨ instances 或 sem_seg 时候,对应的两个绘制函数 `draw_instances` å’Œ `draw_sem_seg` 能够自动被调用,而用户ä¸éœ€è¦æ‰‹åŠ¨è°ƒç”¨ã€‚为了实现上述功能,å¯ä»¥é€šè¿‡åœ¨ `draw_instances` å’Œ `draw_sem_seg` ä¸¤ä¸ªå‡½æ•°åŠ ä¸Š `@Visualizer.register_task` 装饰器。 + +```python +class DetVisualizer(Visualizer): + + def get_image(self): + ... + + def draw(self, data_sample, image=None, show_gt=True, show_pred=True): + if show_gt: + for task in self.task_dict: + task_attr = 'gt_' + task + if task_attr in data_sample: + # DataType.GT 表示当å‰ç»˜åˆ¶æ ‡æ³¨æ•°æ® + self.task_dict[task](self, data_sample[task_attr], DataType.GT) + if show_pred: + for task in self.task_dict: + task_attr = 'pred_' + task + if task_attr in data_sample: + # DataType.PRED 表示当å‰ç»˜åˆ¶é¢„测结果 + self.task_dict[task](self, data_sample[task_attr], DataType.PRED) + + @Visualizer.register_task('instances') + def draw_instance(self, instances, data_type): + ... + + @Visualizer.register_task('sem_seg') + def draw_sem_seg(self, pixel_data, data_type): + ... +``` + +注æ„:是å¦ä½¿ç”¨ `register_task` 装饰器函数ä¸æ˜¯å¿…须的,如果用户自定义 Visualizer,并且 `draw `实现éžå¸¸ç®€å•ï¼Œåˆ™æ— 需考虑 `register_task`。 + +## 写端 Writer + +Visualizer åªæ˜¯å®žçŽ°äº†å•å¼ 图片的å¯è§†åŒ–功能,但是在è®ç»ƒæˆ–者测试过程ä¸ï¼Œå¯¹ä¸€äº›å…³é”®æŒ‡æ ‡æˆ–者模型è®ç»ƒè¶…å‚的记录éžå¸¸é‡è¦ï¼Œæ¤åŠŸèƒ½é€šè¿‡å†™ç«¯ Writer 实现。 + +BaseWriter 定义了对外调用的接å£è§„范,主è¦æŽ¥å£å¦‚下: + +- add_hyperparams 写超å‚,常è§çš„è®ç»ƒè¶…å‚如åˆå§‹å¦ä¹ 率 LRã€æƒé‡è¡°å‡ç³»æ•°å’Œæ‰¹å¤§å°ç‰ç‰ +- add_image 写图片 +- add_scalar å†™æ ‡é‡ +- add_graph 写模型图 +- visualizer 绘制对象,å¯ä»¥ä¸º None +- experiment 写åŽç«¯å¯¹è±¡ï¼Œä¾‹å¦‚ Wandb 对象和 Tensorboard 对象 + +`BaseWriter` 定义了 4 ç§å¸¸è§çš„写数æ®æŽ¥å£ï¼Œè€ƒè™‘到æŸäº›å†™åŽç«¯åŠŸèƒ½éžå¸¸å¼ºå¤§ï¼Œä¾‹å¦‚ Wandbï¼Œå…¶å…·å¤‡å†™è¡¨æ ¼ï¼Œå†™è§†é¢‘ç‰ç‰åŠŸèƒ½ï¼Œé’ˆå¯¹è¿™ç±»éœ€æ±‚用户å¯ä»¥ç›´æŽ¥èŽ·å– experiment 对象,然åŽè°ƒç”¨å†™åŽç«¯å¯¹è±¡æœ¬èº«çš„ API å³å¯ã€‚ + +由于 Visualizer å’Œ Writer 对象是解耦的,用户å¯ä»¥é€šè¿‡é…置文件自由组åˆå„ç§ Visualizer å’Œ Writer,例如 `WandbWriter` 绑定 `Visualizer`,表示图片上绘制结果功能由 `Visualizer` æ供,但是最终图片是写到了 Wandb 端,一个简å•çš„例å如下所示 + +```python +# é…置文件 +writer=dict(type='LocalWriter', save_dir='demo_dir', visualizer=dict(type='DetVisualizer')) +# 实例化和调用 +writer_obj=WRITERS.build(writer) +# 写图片 +writer_obj.add_image('demo_image', image, datasample) +# 写模型精度值 +writer_obj.add_scalar('mAP', 0.9) +``` + +## 组åˆå†™ç«¯ ComposedWriter + +考虑到在è®ç»ƒæˆ–者测试过程ä¸ï¼Œå¯èƒ½éœ€è¦åŒæ—¶è°ƒç”¨å¤šä¸ª Writer,例如想åŒæ—¶å†™åˆ°æœ¬åœ°å’Œ Wandb 端,为æ¤è®¾è®¡äº†å¯¹å¤–çš„ `ComposedWriter` 类,在è®ç»ƒæˆ–è€…æµ‹è¯•è¿‡ç¨‹ä¸ `ComposedWriter` 会ä¾æ¬¡è°ƒç”¨å„个 Writer,主è¦æŽ¥å£å¦‚下: + +- add_hyperparams 写超å‚,常è§çš„è®ç»ƒè¶…å‚如åˆå§‹å¦ä¹ 率 LRã€æƒé‡è¡°å‡ç³»æ•°å’Œæ‰¹å¤§å°ç‰ç‰ +- add_image 写图片 +- add_scalar å†™æ ‡é‡ +- add_graph 写模型图 +- setup_env 设置 work_dir ç‰å¿…备的环境å˜é‡ +- get_writer 获å–æŸä¸ª writer +- `__enter__` 上下文进入函数 +- `__exit__` 上下文推出函数 + +为了让用户å¯ä»¥åœ¨ä»£ç çš„ä»»æ„ä½ç½®è¿›è¡Œæ•°æ®å¯è§†åŒ–,`ComposedWriter` 类实现 `__enter__` å’Œ ` __exit__`方法,并且在 `Runner` ä¸ä½¿ä¸Šä¸‹æ–‡ç”Ÿæ•ˆï¼Œä»Žè€Œåœ¨è¯¥ä¸Šä¸‹æ–‡ä½œç”¨åŸŸå†…,用户å¯ä»¥é€šè¿‡ `get_writers` å·¥å…·å‡½æ•°èŽ·å– `ComposedWriter` 类实例,从而调用该类的å„ç§å¯è§†åŒ–和写方法。一个简å•ç²—略的实现和用例如下 + +```python +writer=WRITERS.build(cfg.writer) + +# å‡è®¾åœ¨ epoch è®ç»ƒè¿‡ç¨‹ä¸ +with ComposedWriter(writer): + while self.epoch < self._max_epochs: + for i, flow in enumerate(workflow): + ... +``` + +```python +# é…置文件写法 +writer = dict(type='WandbWriter', init_kwargs=dict(project='demo'), + visualizer= dict(type='DetLocalVisualizer', show=False)) + +# 在上下文作用域生效的任æ„ä½ç½® +composed_writer=get_writers() +composed_writer.add_image('vis_image',image, datasample, iter=iter) +composed_writer.add_scalar('mAP', val, iter=iter) +``` + +如果å˜åœ¨å¤šä¸ª writer 对象,则é…置文件å—段 writer 为列表,如下所示 + +```python +# é…置文件写法 +writer = [dict(type='LocalWriter', save_dir='demo_dir', visualizer=dict(type='DetVisualizer')), + dict(type='WandbWriter', init_kwargs=dict(project='demo'))] + +# 在上下文作用域生效的任æ„ä½ç½® +composed_writer=get_writers() +# 内部会ä¾æ¬¡è°ƒç”¨ LocalWriter å’Œ WandbWriter çš„ add_image 方法进行写图片到本地和 Wandb 远端 +composed_writer.add_image('vis_image', image, datasample, iter=iter) +composed_writer.add_scalar('mAP', val, iter=iter) +``` + +在è®ç»ƒå’Œæµ‹è¯•è¿‡ç¨‹ä¸ï¼Œç”¨æˆ·å¯ä»¥åœ¨ä¸Šä¸‹æ–‡ç”Ÿæ•ˆçš„代ç ä»»æ„ä½ç½®é€šè¿‡è°ƒç”¨ `get_writers()` 获得 ComposedWriter 对象,然åŽé€šè¿‡è¯¥å¯¹è±¡æŽ¥å£å¯ä»¥è¿›è¡Œç»˜åˆ¶æˆ–者写æ“作,内部会ä¾æ¬¡è°ƒç”¨é…置文件ä¸å®šä¹‰çš„所有 writer 的相应接å£ã€‚ -- GitLab