首页 > 编程笔记 > C++笔记 阅读:2

OpenCV cv::RNG随机数类用法详解(附带实例)

随机数在编程中经常用到,例如在进行初始化的时候需要赋一些随机值。

在 C 和 C++ 中,产生随机数的函数有 rand()、srand() 等。这些函数在 OpenCV 中仍然可以使用。此外,OpenCV 还特地编写了 C++ 的随机数类 cv::RNG,使用起来更加方便。

RNG 类是 OpenCV 中 C++ 的随机数产生器,可以产生一个 64 位的 int 随机数。目前可按均匀分布和高斯分布产生随机数。随机数的产生采用的是 Multiply-With-Carry 算法和 Ziggurat 算法。

在 OpenCV 中,主要通过 RNG 类来生成随机数,默认定义类 RNG 的对象时需要初始化一个种子(默认种子为 0xFFFFFFFF,64 位无符号值),再对种子进行运算,从而生成随机数。如果将种子设定为默认种子,那么每次运算所得的随机数不变,而这通常不能满足程序的需求。我们可以测试一下。

【实例】测试默认种子得到的随机数是否相同。
1) 新建一个控制台工程,工程名是 test。

2) 打开 main.cpp,输入如下代码:
#include <iostream>
#include <opencv2/core.hpp>

int main()
{
    // 创建 RNG 对象,使用默认种子 -1
    cv::RNG rng;

    // 返回 32 位随机数
    int N1 = rng;
    printf("N=0x%x\n", N1);

    return 0;
}

首先定义一个没有参数的 RNG 对象,此时将使用默认种子 -1。然后把对象 rng 直接赋值给整型变量 N1,这是因为 RNG 类重载了 int 类型的转换符:
/** @overload */
operator int();
operator int() 是类型转换运算符,比如:
#include <iostream>

struct A {
    int a;
    A(int i) : a(i) {}          // 构造函数
    operator int() const {      // 类型转换运算符:A → int
        return a;
    }
};

int main() {
    A aa(1);        // 创建对象 aa,a=1
    int i = int(aa); // 显式调用转换运算符
    int j = aa;      // 隐式调用转换运算符,作用同上
    // 作用一样:两种写法都会把 aa 转换成 int 值 1
    return 0;
}
该函数的返回值类型就是函数名,所以不用显式地表示出来。为什么返回值类型就是函数名呢?因为返回值类型是 int,函数名也是 int,不写成 int operator int() const { return value; },返回值类型被省去了。

3) 保存工程并运行,结果如下:

N=0x7c09cf6

多运行几次,可以发现,每次打印的N1值都是相同的。这就说明种子为 -1 的时候,生成的随机数相同。

RNG 常用的成员函数如下表所示。

表:RNG 常用的成员函数
成员函数 描述
RNG(); 构造函数,使用默认种子 -1
RNG(uint64 state); state 参数的构造器可以指定初始状态,类似于 C++ 中 srand 的种子,如果 state=0,就回到前一个默认构造器
int uniform(int a, int b); 返回 [a,b) 范围内均匀分布的随机数,a、b 的数据类型要一致,而且必须是 int、float、double 中的一种,默认是 int
double gaussian(double sigma); 返回一个高斯分布的随机数
void fill(InputOutputArray mat, int distType, InputArray a, InputArray b, bool saturateRange=false); 均匀分布或高斯分布填充矩阵
unsigned next(); 返回下一个 32 位随机整数
RNG::operator ushort(); 强制类型转换获取下一个随机数
RNG::operator double(); 强制类型转换获取下一个随机数

RNG产生一个随机数

RNG 可以产生 3 种随机数,下面一一介绍。

1) 使用种子产生一个32位随机整数

直接利用构造函数 RNG() 和 RNG(uint64 state) 产生一个 32 位随机整数,比如:
RNG rng;          // 创建RNG对象,使用默认种子-1
int N1 = rng;  // 产生32位整数,其实rng既是一个RNG对象,又是一个随机整数
使用默认种子 -1 时,每次生成的随机数相同,计算机产生的随机数都是伪随机数,是根据种子 seed 和特定算法计算出来的。因此,只要种子一定,算法一定,产生的随机数就是相同的。要想产生完全不重复的随机数,可以用系统时间做种子。后面的例子中用时间作为种子来生成不同的随机数。

2) 产生一个均匀分布的随机数

利用成员函数 uniform 函数可以实现产生一个均匀分布的随机数,该函数声明如下:
int  uniform (int a, int b);
该函数返回一个 [a,b) 范围的均匀分布的随机数,a、b 的数据类型要一致,而且必须是 int、float、double 中的一种,默认是 int。

3) 产生一个高斯分布的随机数

利用成员函数 gaussian 可以实现产生一个高斯分布的随机数,该函数声明如下:
double gaussian( double  sigma);
该函数返回一个均值为 0、标准差为 sigma 的高斯分布的随机数。如果要产生均值为 λ、标准差为 sigma 的高斯分布的随机数,可以用 λ+ RNG::gaussian(sigma) 来表示。

【实例】生成不同的随机数。
1) 新建一个控制台工程,工程名是 test。

2) 打开 main.cpp,输入如下代码:
#include <opencv2/opencv.hpp>
using namespace cv;
#include <iostream>
using namespace std;

int main()
{
    RNG rng((unsigned)time(NULL));
    int N1 = rng;                       // 产生一个随机整数
    cout << hex << "0x" << N1 << endl;

    /*---- 产生均匀分布的随机数 uniform ----*/
    // 总是得到 double 类型的数据 0.000000,因为会调用 uniform(int,int),只会取整数,所以只产生 0
    double a = rng.uniform(0, 1);

    // 产生 [0,1) 范围内均匀分布的 double 类型数据
    double b = rng.uniform((double)0, (double)1);

    // 产生 [0,1) 范围内均匀分布的 float 类型数据,注意被自动转换为 double 了
    double c = rng.uniform(0.f, 1.f);

    // 产生 [0,1) 范围内均匀分布的 double 类型数据
    double d = rng.uniform(0., 1.);

    /*---- 高斯分布的随机数 gaussian ----*/
    // 产生符合均值为 0、标准差为 2 的高斯分布的随机数
    double g = rng.gaussian(2);

    cout << "a=" << a
         << ",b=" << b
         << ",c=" << c
         << ",d=" << d
         << ",g=" << g << endl;

    waitKey(0);
    return 0;
}
在上述代码中,我们先使用时间种子产生不同的随机数,再通过函数产生均匀分布的随机数,最后由函数 gaussian 产生高斯分布的随机数。

3) 保存工程并运行,结果如下:

0xb71ac1a6
a=0,b=0.106065,c=0.65523,d=0.420879,g=3.31347

RNG返回下一个随机数

前面一次只能返回一个随机数,实际上系统已经生成一个随机数组。如果我们要连续获得随机数,没有必要重新定义一个 RNG 对象,只需要取出随机数组的下一个随机数即可,所使用的成员函数是 next。该函数声明如下:
unsigned next();

或者通过强制类型转换 RNG:: operator type(); 来返回下一个指定类型的随机数,例如:
int N2a = rng.operator uchar();   // 返回下一个无符号字符数
int N2b = rng.operator schar();   // 返回下一个有符号字符数
int N2c = rng.operator ushort();  // 返回下一个无符号短型数
int N2e = rng.operator int();     // 返回下一个整型数

【实例】返回下一个随机数
1) 新建一个控制台工程,工程名是 test。

2) 打开 main.cpp,并输入如下代码:
#include <opencv2/opencv.hpp>
using namespace cv;
#include <iostream>
using namespace std;

int main()
{
    RNG rng(time(NULL));

    int N  = rng.next();            // 返回下一个随机整数,即 rng.next();
    int Ni = rng.operator()();      // 和 rng.next() 等价
    int Nj = rng.operator()(100);   // 返回 [0,100) 范围内的随机数

    // 返回下一个指定类型的随机数
    unsigned char uch = rng.operator uchar();  // 返回下一个无符号字符数
    char          ch  = rng.operator schar();  // 返回下一个有符号字符数
    int           n   = rng.operator int();    // 返回下一个整型数
    float         f   = rng.operator float();  // 返回下一个浮点数
    double        df  = rng.operator double(); // 返回下一个 double 型数

    cout << "N=" << N << ",Ni=" << Ni << ",Nj=" << Nj << endl;
    printf("uch=0x%x,ch=0x%x,n=0x%x,f=%f,df=%.21f\n",
           uch, ch, n, f, df);

    waitKey(0);
    return 0;
}
在上述代码中,分别返回了不同类型的下一个随机数,有字符型、整型、浮点数型等。

3) 保存工程并运行,结果如下:

N=609380418,Ni=1164844515,Nj=3
uch=0xe7,ch=0x57,n=0xf93d60e3,f=0.769613,df=0.31

RNG用随机数填充矩阵

成员函数 fill() 可以用随机数来填充矩阵,这样矩阵Mat中的每个像素值都是一个随机数,这在某些场合非常有用。该函数声明如下:
void fill(InputOutputArray mat, int distType, InputArray a, InputArray b, bool saturateRange=false);
各个参数的含义是:
【实例】随机数填充矩阵。
1) 新建一个控制台工程,工程名是 test。

2) 打开 main.cpp,并输入如下代码:
#include <opencv2/opencv.hpp>
using namespace cv;
#include <iostream>
using namespace std;

#define FALSE 0
#define TRUE  1

int main()
{
    RNG rng(time(NULL));

    // 产生 [1,1000) 均匀分布的 int 随机数填充 fillM
    Mat_<int> fillM(3, 3);
    rng.fill(fillM, RNG::UNIFORM, 1, 1000);
    cout << "fillM = " << fillM << endl << endl;

    // 产生 [0,255) 范围内的 uint8 随机数,saturateRange = TRUE
    Mat fillM1(3, 3, CV_8U);
    rng.fill(fillM1, RNG::UNIFORM, 1, 1000, TRUE);
    cout << "fillM1 = " << fillM1 << endl << endl;

    // 产生 [0,255) 范围内的 uint8 随机数,saturateRange = FALSE(不截断)
    Mat fillM2(3, 3, CV_8U);
    rng.fill(fillM2, RNG::UNIFORM, 1, 1000, FALSE);
    cout << "fillM2 = " << fillM2 << endl << endl;

    // 产生均值为 1、标准差为 3 的随机 double 数填进 fillN
    Mat_<double> fillN(3, 3);
    rng.fill(fillN, RNG::NORMAL, 1, 3);
    cout << "fillN = " << fillN << endl << endl;

    return 0;
}
在上述代码中,fillM1 产生的数据都在 [0,255) 内;fillM2 产生的数据虽然也在同样的范围内,但是由于用了截断操作,因此很多数据都是 255。

3) 保存工程并运行,结果如下:

fillM = [348, 818, 736;
         692,  43, 268;
         212,  45, 464]

fillM1 = [245, 184, 244;
          83, 183, 254;
          243,  33,  83]

fillM2 = [33, 255, 255;
          255, 255, 255;
          255, 255, 255]

fillN = [ 7.780793190002441,  7.622338294982910, -0.262398749589920;
         -1.837115883827209,  2.265364825725555, -2.916623473167419;
          6.712408900260925, -3.534832954406738, -1.893191218376160]

相关文章