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(); | 构造函数,使用默认种子 -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);各个参数的含义是:
- mat 表示输入输出矩阵,最多支持 4 通道,超过 4 通道先用 reshape() 改变结构;
- distType 表示均匀分布和高斯分布,取值为 UNIFORM 或 NORMAL。当 disType 是 UNIFORM 时,a 表示下界(闭区间),b 表示上界(开区间);当 disType 是 NORMAL 时,a 表示均值,b 表示标准差;
- saturateRange 只针对均匀分布有效,当其值为真时,会先把产生随机数的范围变换到数据类型的范围,再产生随机数;如果其值为假,会先产生随机数,再截断到数据类型的有效区间。
【实例】随机数填充矩阵。
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]