Skip to content

使用 OpenCV 的随机数生成器与文本绘制

在本教程中,你将学到:

  • 如何使用随机数生成器类(cv::RNG),以及如何从均匀分布中获取一个随机数;
  • 如何使用函数 cv::putText 在 OpenCV 窗口中显示文本。
  • 在上一篇教程(基本几何图形绘制)中,我们绘制了各种几何图形,并给出了坐标(以 cv::Point 的形式)、颜色、粗细等输入参数。你可能已经注意到,我们为这些参数给出的都是具体的值。
  • 在本教程中,我们打算为绘制参数使用随机值。同时,我们打算用大量的几何图形填充图像。由于我们将以随机方式初始化它们,这个过程将是自动的,并通过循环来完成。
  • 这段代码位于你的 OpenCV samples 文件夹中。你也可以从 这里 获取。
/**
* @file Drawing_2.cpp
* @brief Simple sample code
*/
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <stdio.h>
using namespace cv;
/// Global Variables
const int NUMBER = 100;
const int DELAY = 5;
const int window_width = 900;
const int window_height = 600;
int x_1 = -window_width/2;
int x_2 = window_width*3/2;
int y_1 = -window_width/2;
int y_2 = window_width*3/2;
/// Function headers
static Scalar randomColor( RNG& rng );
int Drawing_Random_Lines( Mat image, char* window_name, RNG rng );
int Drawing_Random_Rectangles( Mat image, char* window_name, RNG rng );
int Drawing_Random_Ellipses( Mat image, char* window_name, RNG rng );
int Drawing_Random_Polylines( Mat image, char* window_name, RNG rng );
int Drawing_Random_Filled_Polygons( Mat image, char* window_name, RNG rng );
int Drawing_Random_Circles( Mat image, char* window_name, RNG rng );
int Displaying_Random_Text( Mat image, char* window_name, RNG rng );
int Displaying_Big_End( Mat image, char* window_name, RNG rng );
/**
* @function main
*/
int main( void )
{
int c;
/// Start creating a window
char window_name[] = "Drawing_2 Tutorial";
/// Also create a random object (RNG)
RNG rng( 0xFFFFFFFF );
/// Initialize a matrix filled with zeros
Mat image = Mat::zeros( window_height, window_width, CV_8UC3 );
/// Show it in a window during DELAY ms
imshow( window_name, image );
waitKey( DELAY );
/// Now, let's draw some lines
c = Drawing_Random_Lines(image, window_name, rng);
if( c != 0 ) return 0;
/// Go on drawing, this time nice rectangles
c = Drawing_Random_Rectangles(image, window_name, rng);
if( c != 0 ) return 0;
/// Draw some ellipses
c = Drawing_Random_Ellipses( image, window_name, rng );
if( c != 0 ) return 0;
/// Now some polylines
c = Drawing_Random_Polylines( image, window_name, rng );
if( c != 0 ) return 0;
/// Draw filled polygons
c = Drawing_Random_Filled_Polygons( image, window_name, rng );
if( c != 0 ) return 0;
/// Draw circles
c = Drawing_Random_Circles( image, window_name, rng );
if( c != 0 ) return 0;
/// Display text in random positions
c = Displaying_Random_Text( image, window_name, rng );
if( c != 0 ) return 0;
/// Displaying the big end!
c = Displaying_Big_End( image, window_name, rng );
if( c != 0 ) return 0;
waitKey(0);
return 0;
}
/// Function definitions
/**
* @function randomColor
* @brief Produces a random color given a random object
*/
static Scalar randomColor( RNG& rng )
{
int icolor = (unsigned) rng;
return Scalar( icolor&255, (icolor>>8)&255, (icolor>>16)&255 );
}
/**
* @function Drawing_Random_Lines
*/
int Drawing_Random_Lines( Mat image, char* window_name, RNG rng )
{
Point pt1, pt2;
for( int i = 0; i < NUMBER; i++ )
{
pt1.x = rng.uniform( x_1, x_2 );
pt1.y = rng.uniform( y_1, y_2 );
pt2.x = rng.uniform( x_1, x_2 );
pt2.y = rng.uniform( y_1, y_2 );
line( image, pt1, pt2, randomColor(rng), rng.uniform(1, 10), 8 );
imshow( window_name, image );
if( waitKey( DELAY ) >= 0 )
{ return -1; }
}
return 0;
}
/**
* @function Drawing_Rectangles
*/
int Drawing_Random_Rectangles( Mat image, char* window_name, RNG rng )
{
Point pt1, pt2;
int lineType = 8;
int thickness = rng.uniform( -3, 10 );
for( int i = 0; i < NUMBER; i++ )
{
pt1.x = rng.uniform( x_1, x_2 );
pt1.y = rng.uniform( y_1, y_2 );
pt2.x = rng.uniform( x_1, x_2 );
pt2.y = rng.uniform( y_1, y_2 );
rectangle( image, pt1, pt2, randomColor(rng), MAX( thickness, -1 ), lineType );
imshow( window_name, image );
if( waitKey( DELAY ) >= 0 )
{ return -1; }
}
return 0;
}
/**
* @function Drawing_Random_Ellipses
*/
int Drawing_Random_Ellipses( Mat image, char* window_name, RNG rng )
{
int lineType = 8;
for ( int i = 0; i < NUMBER; i++ )
{
Point center;
center.x = rng.uniform(x_1, x_2);
center.y = rng.uniform(y_1, y_2);
Size axes;
axes.width = rng.uniform(0, 200);
axes.height = rng.uniform(0, 200);
double angle = rng.uniform(0, 180);
ellipse( image, center, axes, angle, angle - 100, angle + 200,
randomColor(rng), rng.uniform(-1,9), lineType );
imshow( window_name, image );
if( waitKey(DELAY) >= 0 )
{ return -1; }
}
return 0;
}
/**
* @function Drawing_Random_Polylines
*/
int Drawing_Random_Polylines( Mat image, char* window_name, RNG rng )
{
int lineType = 8;
for ( int i = 0; i< NUMBER; i++ )
{
Point pt[2][3];
pt[0][0].x = rng.uniform(x_1, x_2);
pt[0][0].y = rng.uniform(y_1, y_2);
pt[0][1].x = rng.uniform(x_1, x_2);
pt[0][1].y = rng.uniform(y_1, y_2);
pt[0][2].x = rng.uniform(x_1, x_2);
pt[0][2].y = rng.uniform(y_1, y_2);
pt[1][0].x = rng.uniform(x_1, x_2);
pt[1][0].y = rng.uniform(y_1, y_2);
pt[1][1].x = rng.uniform(x_1, x_2);
pt[1][1].y = rng.uniform(y_1, y_2);
pt[1][2].x = rng.uniform(x_1, x_2);
pt[1][2].y = rng.uniform(y_1, y_2);
const Point* ppt[2] = {pt[0], pt[1]};
int npt[] = {3, 3};
polylines(image, ppt, npt, 2, true, randomColor(rng), rng.uniform(1,10), lineType);
imshow( window_name, image );
if( waitKey(DELAY) >= 0 )
{ return -1; }
}
return 0;
}
/**
* @function Drawing_Random_Filled_Polygons
*/
int Drawing_Random_Filled_Polygons( Mat image, char* window_name, RNG rng )
{
int lineType = 8;
for ( int i = 0; i < NUMBER; i++ )
{
Point pt[2][3];
pt[0][0].x = rng.uniform(x_1, x_2);
pt[0][0].y = rng.uniform(y_1, y_2);
pt[0][1].x = rng.uniform(x_1, x_2);
pt[0][1].y = rng.uniform(y_1, y_2);
pt[0][2].x = rng.uniform(x_1, x_2);
pt[0][2].y = rng.uniform(y_1, y_2);
pt[1][0].x = rng.uniform(x_1, x_2);
pt[1][0].y = rng.uniform(y_1, y_2);
pt[1][1].x = rng.uniform(x_1, x_2);
pt[1][1].y = rng.uniform(y_1, y_2);
pt[1][2].x = rng.uniform(x_1, x_2);
pt[1][2].y = rng.uniform(y_1, y_2);
const Point* ppt[2] = {pt[0], pt[1]};
int npt[] = {3, 3};
fillPoly( image, ppt, npt, 2, randomColor(rng), lineType );
imshow( window_name, image );
if( waitKey(DELAY) >= 0 )
{ return -1; }
}
return 0;
}
/**
* @function Drawing_Random_Circles
*/
int Drawing_Random_Circles( Mat image, char* window_name, RNG rng )
{
int lineType = 8;
for (int i = 0; i < NUMBER; i++)
{
Point center;
center.x = rng.uniform(x_1, x_2);
center.y = rng.uniform(y_1, y_2);
circle( image, center, rng.uniform(0, 300), randomColor(rng),
rng.uniform(-1, 9), lineType );
imshow( window_name, image );
if( waitKey(DELAY) >= 0 )
{ return -1; }
}
return 0;
}
/**
* @function Displaying_Random_Text
*/
int Displaying_Random_Text( Mat image, char* window_name, RNG rng )
{
int lineType = 8;
for ( int i = 1; i < NUMBER; i++ )
{
Point org;
org.x = rng.uniform(x_1, x_2);
org.y = rng.uniform(y_1, y_2);
putText( image, "Testing text rendering", org, rng.uniform(0,8),
rng.uniform(0,100)*0.05+0.1, randomColor(rng), rng.uniform(1, 10), lineType);
imshow( window_name, image );
if( waitKey(DELAY) >= 0 )
{ return -1; }
}
return 0;
}
/**
* @function Displaying_Big_End
*/
int Displaying_Big_End( Mat image, char* window_name, RNG )
{
Size textsize = getTextSize("OpenCV forever!", FONT_HERSHEY_COMPLEX, 3, 5, 0);
Point org((window_width - textsize.width)/2, (window_height - textsize.height)/2);
int lineType = 8;
Mat image2;
for( int i = 0; i < 255; i += 2 )
{
image2 = image - Scalar::all(i);
putText( image2, "OpenCV forever!", org, FONT_HERSHEY_COMPLEX, 3,
Scalar(i, i, 255), 5, lineType );
imshow( window_name, image2 );
if( waitKey(DELAY) >= 0 )
{ return -1; }
}
return 0;
}
  1. 我们先来看一下 main 函数。可以看到,我们做的第一件事就是创建一个随机数生成器对象(RNG):

    RNG rng( 0xFFFFFFFF );

    RNG 实现了一个随机数生成器。在这个例子中,rng 是一个用值 0xFFFFFFFF 初始化的 RNG 对象。

  2. 然后我们创建一个初始化为零的矩阵(这意味着它会显示为黑色),并指定它的高度、宽度和类型:

    /// Initialize a matrix filled with zeros
    Mat image = Mat::zeros( window_height, window_width, CV_8UC3 );
    /// Show it in a window during DELAY ms
    imshow( window_name, image );
  3. 接着我们开始绘制各种疯狂的东西。浏览代码后你会发现,它主要分为 8 个部分,每个部分定义为一个函数:

    /// Now, let's draw some lines
    c = Drawing_Random_Lines(image, window_name, rng);
    if( c != 0 ) return 0;
    /// Go on drawing, this time nice rectangles
    c = Drawing_Random_Rectangles(image, window_name, rng);
    if( c != 0 ) return 0;
    /// Draw some ellipses
    c = Drawing_Random_Ellipses( image, window_name, rng );
    if( c != 0 ) return 0;
    /// Now some polylines
    c = Drawing_Random_Polylines( image, window_name, rng );
    if( c != 0 ) return 0;
    /// Draw filled polygons
    c = Drawing_Random_Filled_Polygons( image, window_name, rng );
    if( c != 0 ) return 0;
    /// Draw circles
    c = Drawing_Random_Circles( image, window_name, rng );
    if( c != 0 ) return 0;
    /// Display text in random positions
    c = Displaying_Random_Text( image, window_name, rng );
    if( c != 0 ) return 0;
    /// Displaying the big end!
    c = Displaying_Big_End( image, window_name, rng );

    所有这些函数都遵循相同的模式,所以我们只分析其中几个,因为同样的说明适用于所有函数。

  4. 来看一下函数 Drawing_Random_Lines:

    int Drawing_Random_Lines( Mat image, char* window_name, RNG rng )
    {
    int lineType = 8;
    Point pt1, pt2;
    for( int i = 0; i < NUMBER; i++ )
    {
    pt1.x = rng.uniform( x_1, x_2 );
    pt1.y = rng.uniform( y_1, y_2 );
    pt2.x = rng.uniform( x_1, x_2 );
    pt2.y = rng.uniform( y_1, y_2 );
    line( image, pt1, pt2, randomColor(rng), rng.uniform(1, 10), 8 );
    imshow( window_name, image );
    if( waitKey( DELAY ) >= 0 )
    { return -1; }
    }
    return 0;
    }

    我们可以观察到以下几点:

    • for 循环会重复 NUMBER 次。由于函数 cv::line 位于这个循环内,这意味着将生成 NUMBER 条直线。

    • 直线的两个端点由 pt1 和 pt2 给出。对于 pt1,我们可以看到:

      pt1.x = rng.uniform( x_1, x_2 );
      pt1.y = rng.uniform( y_1, y_2 );
      • 我们知道 rng 是一个随机数生成器对象。在上面的代码中我们调用了 rng.uniform(a,b)。这会生成一个在 a 和 b 之间均匀分布的随机数(包含 a,不包含 b)。

      • 由上面的说明可以推断,端点 pt1 和 pt2 将是随机值,因此直线的位置会相当难以预测,从而产生漂亮的视觉效果(见下文的结果一节)。

      • 另一个值得注意的地方是,在 cv::line 的参数中,我们为 color 输入传入了:

        randomColor(rng)

        来看一下这个函数的实现:

        static Scalar randomColor( RNG& rng )
        {
        int icolor = (unsigned) rng;
        return Scalar( icolor&255, (icolor>>8)&255, (icolor>>16)&255 );
        }

        可以看到,返回值是一个带有 3 个随机初始化值的 Scalar,它们分别被用作直线颜色的 R、G 和 B 参数。因此,直线的颜色也将是随机的!

  5. 上面的说明同样适用于其他生成圆、椭圆、多边形等的函数。center(中心)和 vertices(顶点)等参数也是随机生成的。

  6. 在结束之前,我们还应该看一下函数 Display_Random_Text 和 Displaying_Big_End,因为它们都有一些有趣的特性。

  7. Display_Random_Text:

    int Displaying_Random_Text( Mat image, char* window_name, RNG rng )
    {
    int lineType = 8;
    for ( int i = 1; i < NUMBER; i++ )
    {
    Point org;
    org.x = rng.uniform(x_1, x_2);
    org.y = rng.uniform(y_1, y_2);
    putText( image, "Testing text rendering", org, rng.uniform(0,8),
    rng.uniform(0,100)*0.05+0.1, randomColor(rng), rng.uniform(1, 10), lineType);
    imshow( window_name, image );
    if( waitKey(DELAY) >= 0 )
    { return -1; }
    }
    return 0;
    }

    一切看起来都很熟悉,除了这个表达式:

    putText( image, "Testing text rendering", org, rng.uniform(0,8),
    rng.uniform(0,100)*0.05+0.1, randomColor(rng), rng.uniform(1, 10), lineType);

    那么,函数 cv::putText 是做什么的?在我们的例子中:

    • 在 image 中绘制文本 “Testing text rendering”;
    • 文本的左下角位于 Point org;
    • 字体类型是范围 [0,8)[0, 8) 内的一个随机整数值;
    • 字体的缩放比例由表达式 rng.uniform(0, 100)x0.05 + 0.1 给出(意味着其范围是 [0.1,5.1)[0.1, 5.1));
    • 文本颜色是随机的(由 randomColor(rng) 给出);
    • 文本粗细在 1 到 10 之间,由 rng.uniform(1,10) 指定。

    最终,我们会得到(与其他绘制函数类似地)叠加在图像上的 NUMBER 个随机位置的文本。

  8. Displaying_Big_End:

    int Displaying_Big_End( Mat image, char* window_name, RNG rng )
    {
    Size textsize = getTextSize("OpenCV forever!", FONT_HERSHEY_COMPLEX, 3, 5, 0);
    Point org((window_width - textsize.width)/2, (window_height - textsize.height)/2);
    int lineType = 8;
    Mat image2;
    for( int i = 0; i < 255; i += 2 )
    {
    image2 = image - Scalar::all(i);
    putText( image2, "OpenCV forever!", org, FONT_HERSHEY_COMPLEX, 3,
    Scalar(i, i, 255), 5, lineType );
    imshow( window_name, image2 );
    if( waitKey(DELAY) >= 0 )
    { return -1; }
    }
    return 0;
    }

    除了函数 getTextSize(它获取参数文本的大小)之外,我们还可以观察到 for 循环内部的新操作:

    image2 = image - Scalar::all(i)

Drawing_2_Tutorial_Result_0.jpg Drawing_2_Tutorial_Result_2.jpg Drawing_2_Tutorial_Result_3.jpg Drawing_2_Tutorial_Result_5.jpg Drawing_2_Tutorial_Result_big.jpg

也就是说,**image2** 是 **image** 减去 **Scalar::all(i)** 的结果。实际上,这里发生的事情是:**image2** 的每个像素都等于 **image** 的对应像素减去 **i** 的值(请记住,每个像素我们考虑的是 R、G、B 三个值,所以它们每一个都会受到影响)。
另外请记住,减法操作*总是*在内部执行**饱和**(saturate)操作,这意味着得到的结果将始终位于允许的范围内(在我们的例子中不会出现负值,且介于 0 和 255 之间)。

正如你在代码一节中看到的,程序将依次执行各种绘制函数,产生如下效果:

  1. 首先,屏幕上会出现一组随机的 NUMBER 条直线。
  2. 然后,会出现一组新的图形,这次是矩形。
  3. 接着会出现一些椭圆,每个椭圆的位置、大小、粗细和弧长都是随机的。
  4. 然后,屏幕上会出现带有 3 个线段的折线(polylines),同样是随机配置的。
  5. 随后是填充多边形(在本例中是三角形)。
  6. 最后出现的几何图形:圆!
  7. 接近尾声时,文本 “Testing Text Rendering” 会以各种字体、大小、颜色和位置出现。
  8. 最后是大结局(顺便说一句,它也道出了一个伟大的真理):“OpenCV forever!”。