标注多边形区域工具

全篇共 3459 字。按500字/分钟阅读,预计用时 6.9 分钟。此篇想法总访问 54 次,今日访问 1 次。

我打算用Airglass.js重新实现canvas版的LabelImg图像标注切割功能,作为Airglass.js的第一个最佳实践。实现图像的标注和切割,关键不是切割图像,而是如何标注多边形区域,获取标注点信息。为此我为Airglass新增了创建多边形的Polygon类。

拖拽多边形

计算鼠在多边形内标按下后每次拖拽的偏移量

和矩形或圆形不同的是,多边形的中心点坐标位置和多边形的边界是通过计算得来的。用户不断的为多边形增加新的控制点,因此需要每次在新增控制点后重新计算多边形的中心点坐标位置和边界框。同样,当用户拖拽多边形时也需要重新计算。

多边形边界框是指将多边形各控制点刚好包裹起来的矩形范围。控制点中最小横坐标和纵坐标,即是边界框矩形的左上角那一点的坐标位置;控制点中最大横坐标和中坐标,即是边界框矩形右下角那一点的坐标位置。多边形的中心点坐标直接根据多边形边界框计算得出。

击中检测

鼠标位置击中canvas画布多边形区域

击中检测用辅助完成多边形拖拽和控制点的拖拽。因为拖拽的前提是要先知道拖拽谁。击中检测能够获取到鼠标或手指与屏幕接触时选中的物体。

对于上图中两个多边形重叠的情况,可以想像一下,手指或鼠标与屏幕交互时,就像一束激光从手指穿透层层玻璃,所有被击穿的多边形即是检测到的物体。

玻璃平铺开来

平铺的两块玻璃演示创建和编辑多边形控制点

我在开发笔记中提到过Airglass有玻璃的概念,为此我创建了Glass类。凡是继承了Glass类的都可以称作是玻璃。渲染器类Renderer继承了Glass类,而每个渲染器总要绑定一个画布。所以,每一个canvas画布都可以看作是一块玻璃。现实世界里的玻璃特性多少都可以在Airglass中找到合适的对应。

上图是将玻璃平铺开,演示创建和编辑多边形控制点。左侧的玻璃渲染多边形;右侧的玻璃渲染控制点。我在开发笔记中提到过,Airglass.js中有事件订阅的概念。左侧玻璃中的所有多边形受右侧玻璃中控制点的影响改变这形状。因为左侧玻璃订阅了右侧玻璃中拖拽控制点和创建多边形的事件。

玻璃层叠起来

层叠起来看到创建和编辑多边形控制点

上图是两块继承Glass类的渲染器层叠起来,协作创建和编辑多边形的控制点的演示。顶层的渲染多边形控制点;下层的渲染多边形。

现实世界中存在透明与不透明的玻璃。在Airglass中,能创建透明背景和有色背景两种canvas画布。想象一下我有三块透明的玻璃一些颜料🎨和画笔🖌️,一块画白云☁️蓝天,一块画山⛰️,一块画蝴蝶🦋。把这三块玻璃按白云蓝天、山、蝴蝶的顺序叠放在一起,就是一副蓝天白云下一只蝴蝶在山间飞舞的画面。

Airglass.js绘制这样一幅画也会用到三个canvas画布,相同的排列顺序叠放。最下层的画布不断渲染移动中的云。中间画布只需在初始化时渲染一次山。最上层不断渲染飞舞的蝴蝶。将需要频繁渲染的画布与不常需要渲染的画布分开,减少不必要的渲染开支。

我用airglass.js初步完成了第一个最佳实践:创建和编辑多边形标注区域。做Demo的同时完善Airglass.js。我给渲染器类新增了击中检测实例方法,还增加了链式调用提升开发效率。同时新增多边形Polygon类,它是继矩形圆形后Airglass.js出现的第3个基本形状。接下来是对Demo无止境优化的漫漫长路。

画笑脸

Airglass.js最佳实践多边形标注工具绘制笑脸

从上面的演示可以看到,我做了优化的地方是:只显示正在编辑或选中状态的多边形上的控制点。控制点是用来帮助用户编辑多边形的辅助元素,所以控制点应该只在编辑多边形时出现。

我在上一篇提到这一最佳实践用到了两个Glass,顶层渲染控制点,第二层渲染多边形。当击中某个多边形时,清空顶层渲染器以及场景中现有的控制点,并将击中的多边形的全部控制点加入到顶层渲染器绑定的场景中。

暂停与继续编辑

airglass.js多边形标注工具支持暂停继续编辑多边形

Airglass.js实现的这一个多边形标注功能的最佳实践,因为多边形是从无到有创建出来的,所以存在创建多边形的中间状态。

首先,我规定实例化一个Polygon类必须要提供至少三个点的实例,即组成一个面片至少需要三个点。其次,用户理论上可以为多边形创建无限多个控制点,控制点越多则多边形标注的区域就越精细,当用户在编辑多边形的过程中点击了第一个控制点时则多边形闭合,标志着完成了该多边形标注区域的绘制。

Airglass.js中所有的类创建的实例对象都是可以被开发者扩展属性的。这这一个标注多边形的最佳实践中,开发者可以自定义一个标志多边形是否闭合的真假值存储在多边形对象上。当击中的多边形时,首先检测该多边形是否闭合。如果多边形并未闭合,则继续从上次暂停绘制的控制点继续开始绘制多边形。

第一个控制点在哪里

Airglass.js标注多边形高亮第一个控制点

后来我又发现了需要继续优化的地方。如果你善于观察细节,你会发现在上面“暂停与继续编辑”部分的演示中,我想闭合右边的多边形编程三角形,但是我迟疑了一下,因为我一时想不起来端点处的两个控制点哪个才是多边形的第一个控制点。所以我又绘制了第4个控制点,才找到哪个是第一个出发时的控制点。

我提到已经实现了可以从多边形上次暂停绘制的控制点处继续绘制,但是每一个控制点外观都相同,如果想要直接闭合多边形,甚至搞不清楚两个控制点中哪一个是上次暂停时的控制点,哪一个才是多边形的第一个控制点。虽然我可以根据自己习惯从左上到右下的绘图,猜到左上方的控制点就是多边形的第一个控制点,但这并不是真实业务场景中应有的侥幸。

所以我将开始创建多边形时的第一个控制点的外观变成实心加高亮的样式。当用户回过头来继续编辑未完成的多边形时,看到两个端点控制点,一个实心控制点代表这是绘制多边形的第一个控制点,一个空心控制点代表这是上次暂停时的最后一个控制点。

颜色的维度

Airglass.js标注带颜色多边形区域

同一类别的多边形往往都有相似的特征。除了使用“汽车”、“人”等命名方式将标注的区域归为一类,颜色的维度也是划分归属的绝好帮手。相同的类别使用同一种颜色,不同的类别使用不同种颜色,视觉上更加一目了然。

名称赋予颜色以意义。比如所有“汽车”使用粉色标注,所有“人”使用黄色标注。通过颜色分辨所标注的物体的从属类别,还有一个用途是训练机器识别图像时需要用到遮罩颜色。

使用Airglass.js第一个最佳实践标注了许多多边形标注区域,下一步就是将这些标注数据导出用于下一步工序。以及将标注数据导入继续编辑标注。统一数据导出格式,是实现正确导入导出的关键。

预览识别标注结果

预览识别

预览识别标注结果

多边形的控制点能精细控制标注位置。预览识别时自动计算处多边形的矩形边界框。无论是预览识别结果,亦或是为标注的物体设置标签,Airglass.js的多边形标注工具能满足实际场景的全部需要。

有价值的数据

所有的数据都是围绕用户绘制的多边形来挖掘的。思路是凡是能通过这些数据确定一个多边形的都是有用的数据。确定一个多边形需要所有多边形上的控制点数据,每一个控制点提供位置坐标数据,位置坐标是相对于画布宽高确定的,有了这些控制点的位置坐标数据就能确定所有的所有信息多边形。

前面的文章中提到过多边形有边界框,边界框有位置坐标[x,y]和宽高信息。这些信息都存储在Airglass.js中Polygon类实例化的多边形对象中。这些都是有价值的信息。

导出什么

自定义标注导出格式

导出什么影响导入什么,不论是导入到其他工具中,亦或是导出到该标注工具内。决定导出什么,包括决定导出的数据本身和导出的数据采用何种编码格式,我这里说的编码格式不是存储格式。比如存储一个多边形的一个控制点的位置信息,可以使用对象表示:

{x:10, y:25}

也可以用数组表述:

[10, 25]

这就是我在这里说的数据编码格式。开发者根据具体业务指定自己的数据编码方式即可。如果表示一系列控制点可以如何表示呢,使用对象数据编码方式可以这样:

[
  {x: 3, y:25},
  {x: 23, y:27},
  {x: 36, y:125},
]

使用数组表示一系列控制点可以这样简化写:

[
  [3, 25],
  [23, 27],
  [36, 125]
]

再极简一些可以这样表示,使用一个数组表示所有控制点,每两个元素表示一个控制点的x坐标和y坐标:

[3, 25, 23, 27, 36, 125]

导入什么

导出了什么会影响导出什么。时间复杂度与空间复杂度总是此消彼长。导出时追求极简的数据格式,占用更少的存储空间或传输大小,那么在导入是就需要花更多时间解析。只要明白这一点,标注数据的导出和导入可以做到相对平衡,包括代码复杂度、标注数据占用的存储空间、传输速度等之间的平衡。

不管如何导出,如何导入,数据都不应该失真,都不应该影响视觉上的呈现,也不会影响。

发布日期 » 2019年9月12日 周四
原创声明 » 请勿复制转载,谢谢配合。
Airglass.js核心库
JavaScript核心概念
硬件编程、Arduino
文档翻译计划
微信开发
前端脚手架
运维
可视化
生活自有“道”理
视觉设计、用户体验
陈帅华的微信二维码