Drawing Things

Something that frequently occurs is the need to draw some kind of picture or to draw something on top of an image obtained from somewhere else. Toward this end, OpenCV provides a menagerie of functions that will allow us to make lines, squares, circles, and the like.

The simplest of these routines just draws a line by the Bresenham algorithm [Bresenham65]:

void  cvLine(
  CvArr*   array,
  CvPoint  pt1,
  CvPoint  pt2,
  CvScalar color,
  int      thickness    = 1,
  int      connectivity = 8
);

The first argument to cvLine() is the usual CvArr*, which in this context typically means an IplImage* image pointer. The next two arguments are CvPoints. As a quick reminder, CvPoint is a simple structure containing only the integer members x and y. We can create a CvPoint "on the fly" with the routine cvPoint(int x, int y), which conveniently packs the two integers into a CvPoint structure for us.

The next argument, color, is of type CvScalar. CvScalars are also structures, which (you may recall) are defined as follows:

typdef struct {
  double val[4];
} CvScalar;

As you can see, this structure is just a collection of four doubles. In this case, the first three represent the red, green, and blue channels; the fourth is not used (it can be used for an alpha channel when appropriate). One typically makes use of the handy macro CV_RGB(r, g, b). This macro takes three numbers and packs them up into a CvScalar.

The next two arguments are optional. The thickness is the thickness of the line (in pixels), and connectivity sets the anti-aliasing mode. The default is "8 connected", which will give a nice, smooth, anti-aliased line. You can also set this to a "4 connected" line; diagonals will be blocky and chunky, but they will be drawn a lot faster.

At least as handy as cvLine() is cvRectangle(). It is probably unnecessary to tell you that cvRectangle() draws a rectangle. It has the same arguments as cvLine() except that there is no connectivity argument. This is because the resulting rectangles are always oriented with their sides parallel to the x- and y-axes. With cvRectangle(), we simply give two points for the opposite corners and OpenCV will draw a rectangle.

void  cvRectangle(
  CvArr*   array,
  CvPoint  pt1,
  CvPoint  pt2,
  CvScalar color,
  int      thickness = 1
);

Similarly straightforward is the method for drawing circles, which pretty much has the same arguments.

void  cvCircle (
  CvArr*   array,
  CvPoint  center,
  int      radius,
  CvScalar color,
  int      thickness    = 1,
  int      connectivity = 8
);

For circles, rectangles, and all of the other closed shapes to come, the thickness argument can also be set to CV_FILL, which is just an alias for −1; the result is that the drawn figure will be filled in the same color as the edges.

Only slightly more complicated than cvCircle() is the routine for drawing generalized ellipses:

void cvEllipse(
  CvArr*   img,
  CvPoint  center,
  CvSize   axes,
  double   angle,
  double   start_angle,
  double   end_angle,
  CvScalar color,
  int      thickness = 1,
  int      line_type = 8
);

In this case, the major new ingredient is the axes argument, which is of type CvSize. The structure CvSize is very much like CvPoint and CvScalar; it is a simple structure, in this case containing only the members width and height. Like CvPoint and CvScalar, there is a convenient helper function cvSize(int height, int width) that will return a CvSize structure when we need one. In this case, the height and width arguments represent the length of the ellipse's major and minor axes.

The angle is the angle (in degrees) of the major axis, which is measured counterclockwise from horizontal (i.e., from the x-axis). Similarly the start_angle and end_angle indicate (also in degrees) the angle for the arc to start and for it to finish. Thus, for a complete ellipse you must set these values to 0 and 360, respectively.

An alternate way to specify the drawing of an ellipse is to use a bounding box:

void cvEllipseBox(
  CvArr*   img,
  CvBox2D  box,
  CvScalar color,
  int      thickness = 1,
  int      line_type = 8,
  int      shift     = 0
);

Here again we see another of OpenCV's helper structures, CvBox2D:

typdef struct {
  CvPoint2D32f center;
  CvSize2D32f  size;
  float        angle;
} CvBox2D;

CvPoint2D32f is the floating-point analogue of CvPoint, and CvSize2D32f is the floating-point analog of CvSize. These, along with the tilt angle, effectively specify the bounding box for the ellipse.

Finally, we have a set of functions for drawing polygons:

void cvFillPoly(
  CvArr*    img,
  CvPoint** pts,
  int*      npts,
  int       contours,
  CvScalar  color,
  int       line_type = 8
);

void cvFillConvexPoly(
  CvArr*   img,
  CvPoint* pts,
  int      npts,
  CvScalar color,
  int      line_type = 8
);

void cvPolyLine(
  CvArr*    img,
  CvPoint** pts,
  int*      npts,
  int       contours,
  int       is_closed,
  CvScalar  color,
  int       thickness = 1,
  int       line_type = 8
);

All three of these are slight variants on the same idea, with the main difference being how the points are specified.

In cvFillPoly(), the points are provided as an array of CvPoint arrays. This allows cvFillPoly() to draw many polygons in a single call. Similarly npts is an array of point counts, one for each polygon to be drawn. If the is_closed variable is set to true, then an additional segment will be drawn from the last to the first point for each polygon. cvFillPoly() is quite robust and will handle self-intersecting polygons, polygons with holes, and other such complexities. Unfortunately, this means the routine is comparatively slow.

cvFillConvexPoly() works like cvFillPoly() except that it draws only one polygon at a time and can draw only convex polygons.[35] The upside is that cvFillConvexPoly() runs much faster.

The third function, cvPolyLine(), takes the same arguments as cvFillPoly(); however, since only the polygon edges are drawn, self-intersection presents no particular complexity. Hence this function is much faster than cvFillPoly().

One last form of drawing that one may well need is to draw text. Of course, text creates its own set of complexities, but—as always with this sort of thing—OpenCV is more concerned with providing a simple "down and dirty" solution that will work for simple cases than a robust, complex solution (which would be redundant anyway given the capabilities of other libraries).

OpenCV has one main routine, called cvPutText() that just throws some text onto an image. The text indicated by text is printed with its lower-left corner of the text box at origin and in the color indicated by color.

void cvPutText(
  CvArr*        img,
  const char*   text,
  CvPoint       origin,
  const CvFont* font,
  CvScalar      color
);

There is always some little thing that makes our job a bit more complicated than we'd like, and in this case it's the appearance of the pointer to CvFont.

In a nutshell, the way to get a valid CvFont* pointer is to call the function cvInitFont(). This function takes a group of arguments that configure some particular font for use on the screen. Those of you familiar with GUI programming in other environments will find cvInitFont() to be reminiscent of similar devices but with many fewer options.

In order to create a CvFont that we can pass to cvPutText(), we must first declare a CvFont variable; then we can pass it to cvInitFont().

void cvInitFont(
  CvFont* font,
  int     font_face,
  double  hscale,
  double  vscale,
  double  shear     = 0,
  int     thickness = 1,
  int     line_type = 8
);

Observe that this is a little different than how seemingly similar functions, such as cvCreateImage(), work in OpenCV. The call to cvInitFont() initializes an existing CvFont structure (which means that you create the variable and pass cvInitFont() a pointer to the variable you created). This is unlike cvCreateImage(), which creates the structure for you and returns a pointer.

The argument font_face is one of those listed in Table 3-15 (and pictured in Figure 3-6), and it may optionally be combined (by Boolean OR) with CV_FONT_ITALIC.

The eight fonts of drawn with hscale = vscale = 1.0, with the origin of each line separated from the vertical by 30 pixels

Figure 3-6. The eight fonts of Table 3-15 drawn with hscale = vscale = 1.0, with the origin of each line separated from the vertical by 30 pixels

Both hscale and vscale can be set to either 1.0 or 0.5 only. This causes the font to be rendered at full or half height (and width) relative to the basic definition of the particular font.

The shear function creates an italicized slant to the font; if set to 0.0, the font is not slanted. It can be set as large as 1.0, which sets the slope of the characters to approximately 45 degrees.

Both thickness and line_type are the same as defined for all the other drawing functions.



[35] Strictly speaking, this is not quite true; it can actually draw and fill any monotone polygon, which is a slightly larger class of polygons.