5.6. 绘制形状和文本

一个对图像做出某种判断的算法,往往需要让该判断可见。色块检测器找到了应用所关心的区域;应用希望将该区域绘制到帧上,以便操作员——或运行脚本的开发者——能看到检测到了什么。一个坐标变换把输入位置映射到输出位置;调试它通常意味着在同一幅图像上标出这两个位置。IDE 预览读取它轮询那一刻位于帧缓冲区中的任何内容,因此让算法输出可见的最简单方法,就是把标注写入帧缓冲区本身。Image 类上的绘图系列正是为这项工作准备的工具集。

5.6.1. 绘图基元

每个绘图方法在图像上放置一种特定的标记。这个目录很小,并且紧贴标注实际所需的几何基元:

  • draw_line() —— 两个端点之间的一条直线段。

  • draw_rectangle() —— 一个轴对齐的矩形,空心或填充。

  • draw_circle() —— 围绕中心的一个圆,空心或填充。

  • draw_ellipse() —— 一个可任意旋转的椭圆。

  • draw_cross() —— 在某点处的一个加号,通常用于标记质心或点击目标。

  • draw_arrow() —— 从起点到终点的一个箭头。

  • draw_edges() —— 给定四个角点,绘制任意四边形的四条边;这是勾勒已检测标签或透视扭曲区域轮廓的自然方式。

  • draw_string() —— 使用内置位图字体绘制的文本。

这些方法中的每一个都会就地修改源图像,并返回同一幅图像以便链式调用,遵循先前确立的操作方法惯例。

一个由小面板组成的网格,展示这八个绘图基元各应用一次的效果。每个面板包含一条线、一个矩形、一个圆、一个椭圆、一个十字、一个箭头、一个四边形或一段简短的文本字符串,下方标注了产生它的方法名称。

八个绘图基元,每个面板一个。每个方法绘制一种标记。

5.6.2. 颜色

每个绘图方法都接受一个 color 参数,它决定向每个被绘制的像素写入什么值。该参数采用的形式取决于图像的格式。对于 RGB565 图像,自然的形式是一个 (r, g, b) 元组,每个通道取值范围为 0 —— 255;该模块会在写入前将其打包成 16 位的 RGB565 字。对于灰度图像,自然的形式是单个整数亮度,从 0(黑)到 255(白)。这些方法也接受该格式的原始存储值——对于 RGB565 是一个 16 位打包字,对于灰度是一个 8 位整数——当颜色已在别处计算好并已处于存储形式时,这是高效的形式。

省略 color 参数会绘制白色。该默认值对灰度处理很方便,其中白色是最大值,并且在大多数背景下都清晰可辨。但对于 RGB565 调试叠加层,它几乎总是错误的:绿色或红色通常在摄像头实际看到的那类场景上更易辨认,而显式的颜色能传达意图。

5.6.3. 粗细与填充

大多数几何方法接受两个决定标记如何绘制的标志:

  • thickness=N 设置线宽(以像素为单位)。默认值是 1,对大多数叠加层来说足够;当某个标注必须在繁忙的场景上保持可见,或在流水线的后续阶段进一步修改图像之后仍要可见时,较大的值会很有用。

  • fill=True 将标记从轮廓切换为实心,用所选颜色绘制每一个内部像素。默认值是 False

这些标志不适用于没有内部可填充的基元——线、十字、箭头、四边形——对于这些基元只有 thickness 有意义。

5.6.4. 绘制文本

draw_string() 使用内置的 8×10 像素位图字体绘制字符。xy 是第一个字符单元的左上角,text 是要绘制的字符串,color 遵循与几何方法相同的惯例。该字体涵盖完整的可打印 ASCII 范围且没有字距调整——每个字符占据相同的 8 像素宽单元——这使得输出易于定位。

img.draw_string(10, 10, "blobs: 3", color=(0, 255, 0))

字符串可以包含换行符(\n);每个换行符会把下一个字符移到比上一行低十个像素的新行的开头。scale 参数按一个浮点因子以更大尺寸绘制每个字符,而 x_spacingy_spacing 在每个字符周围添加间距。一小组旋转/镜像/翻转标志可应用于整个字符串或单独应用于每个字符——足以在布局需要时沿某个角度或贴着非水平边缘排布文本。

5.6.5. 清空画布

该系列中的一个方法不绘制任何特定标记。它只是把图像的每个像素重置为零:

  • clear() —— 将每个像素清零,可选地限制在某个 ROI 内或通过掩码限定范围。

当应用每帧都从头合成一个标注时——从黑色画布开始,绘制新的标注,再把结果交给显示——而不是叠加在捕获的帧之上,clear() 就是正确的选择。它也是准备一幅临时图像用作掩码缓冲区的最廉价方式。

一幅新分配的图像在构造时就已经是零,因此 clear() 尤其对在帧间复用的缓冲区才有意义。