Bounding box drawing utility in Unity

3 minute read

Published:

I was surpised to find that there is no simple method in Unity to draw a rectangle (or bounding box) on a canvas. This project extends the Texture2D methods in Unity, providing a basic sample which demonstrates how to draw lines between specified 2D coordinates on a canvas and construct a bounding box, complete with label and confidence, around a cat! See the project on my personal Github repo for the implementation.

About the sample

Given the coordinates of a bounding box: x, y, width, height, label and confidence, this sample plots a bounding box onto a scaled canvas of specified size. The xywh format of bounding box is converted to an xyxy format for the plotting util methods. The boundaries of the canvas are checked as follows:

// If value exceeds bounds, set to boundary edge value + 3 pixel buffer (no canvas wrap)
// val = cond1 > cond2 ? val(true) : val(false) 
int x1 = box.X > 0.0f ? (int)box.X : 3;
int y1 = box.Y > 0.0f ? (int)box.Y : 3;
int x2 = (box.Width + x1) > textureSize.x ? (int)(textureSize.x) - 3 : (int)(box.Width + x1);
int y2 = (box.Height + y1) > textureSize.y ? (int)(textureSize.y) - 3 : (int)(box.Height + y1);

To plot the xyxy formatted coordinates on a Texture2D canvas, the connecting lines between the corners are drawn with the Texture2DExtension methods.

The Unity Vector2.Lerp method is used to linearly interpolate between two vectors by a specified increment. For our purposes, we want to incrementally move along a line between two vectors and set the pixels as we go. An initial starting point for the linear interpolation is selected as the inverse of the distance between the two points:

// 1 / Euclidean distance between p2 and p1
float frac = 1 / Mathf.Sqrt(Mathf.Pow(p2.x - p1.x, 2) + Mathf.Pow(p2.y - p1.y, 2));

This value is then used within a while loop. At each loop increment, the interpolation fraction is increased to incrementally change the value of the linear interpolation until the end boundary condition is reached. At each iteration, the pixels within a local region of the selected pixel are set to the desired colour on the Texture2D.

This process of line drawing between points is repeated between each of the four corner points provided to draw a fully connected rectangle.

To create a label with class and confidence values, a prefab is instantiated at the corresponding world location of the top left box corner. To go from pixel to local camera coordinates, the following scaling is used:

// Corners of bounding box
var topLeft = new Vector2(x1, y1);
var bottomRight = new Vector2(x2, y2);

// Canvas is scaled to x = -0.5, 5 and y = -0.5, 0.5.
var xText = ((topLeft.x / textureSize.x) - 0.5f) + 0.01f;
var yText = 0.5f - (1.0f - (topLeft.y / textureSize.y));

The label and colour of the bounding box component are then set. If all went well, the cat should be properly identified with label and confidence, and outlined with a bounding box!

cat