OpenCV-4-绘图和函数对象

1 绘图和注释

OpenCV提供了一些列绘制线、矩形、圆形等类似图形的函数,其中大部分都支持设置颜色、线宽、抗锯齿类型和亚像素对齐。在设置颜色时通常使用cv::Scalar实例,尽管我们大多数时候只使用其前三个元素。同样按照惯例,在调用如cv::imread()函数读取图像,imshow()函数渲染彩色图形时,OpenCV内部使用BGR的颜色顺序。另外如果使用宏CV_RGB(r, g, b)生成一个cv::Scalar实例,则其值为{b, g, r, 0}。当然你自己实现的函数可以不用遵循这些规范,只需要知道和OpenCV提供的一些渲染函数交互时注意颜色数据的格式即可。

1.1 线条艺术和多边形填充

绘制包含线条及包含线条的图形时通常可以设置线宽thickness和线条类型lineType,这两个参数都是整型,需要注意的是后者只能接受值48或者cv::LINE_AA。对于如圆等封闭图形,线宽thickness可以设置为cv::FILLED-1,这表示使用线条的颜色填充这个封闭图形。线条类型lineType设置为48或者cv::LINE_AA时分别表示使用4邻域连接、8邻域连接还是使用抗锯齿连接法。这三种线条连接的效果如下图,4邻域连接、8邻域连接选项使用到了Bresenham算法,抗锯齿选项使用到了高斯滤波器。另外宽直线总是会被绘制成圆头的。

OpenCV提供的绘图相关函数如下表,其中线的端点、圆心、矩形的角都被规定为整型数据。但是这些函数支持设置亚像素对其参数shift,该参数表示对整型参数应用二进制位移操作,可以理解为将原始值除以2的shift次方,通过这种方式就可以将线的端点、圆心、矩形的角调整为小数。如设置圆心在点(5, 5),同时将shift参数设置为1,则实际点圆心位于(2.5, 2.5)。

绘图相关函数 描述
cv::circle() 绘制圆形
cv::clipLine() 判断一条线是否在给定的矩形内
cv::ellipse() 绘制椭圆,可以倾斜,或者只包含部分圆弧
cv::ellipse2Poly() 计算近似椭圆的多边形
cv::fillConvexPoly() 绘制封闭的简单的多边形
cv::fillPoly() 绘制封闭的任意多边形
cv::line() 绘制线
cv::rectangle() 绘制矩形
cv::polyLines() 绘制折线
cv::circle()

绘制圆形的函数原型如下。

// img:需要绘制圆形的图像
//           可以理解为一个画布,可以读取一个图片文件作为画布
// center:圆心,单位像素
// radius:半径,单位像素
// color:绘制颜色,RGB格式
// thickness:线宽
// lineType:线型,线条绘制算法,支持4或者8
// shift:亚像素对其方式,影响参数radius和center
void circle(cv::Mat& img, cv::Point center, int radius,
            const cv::Scalar& color,
            int thickness = 1, int lineType = 8, int shift = 0);
cv::clipLine()

判断一条线是否在给定的矩形内的函数原型如下。需要注意的是只有当整个线段都位于指定的矩形外时,下面两个函数才返回false,否则返回true。

// imgRect:需要判断的矩形区域
// pt1:线段起点
// pt2:线段终点
bool clipLine(cv::Rect imgRect, cv::Point& pt1, cv::Point& pt2);

// imaSize:矩形的尺寸,默认矩形左上角点为(0, 0)
bool clipLine(cv::Size imgSize, cv::Point& pt1, cv::Point& pt2);
cv::ellipse()

绘制椭圆函数原型如下。

// img:需要绘制圆形的图像
//           可以理解为一个画布,可以读取一个图片文件作为画布
// center:椭圆中心,单位像素
// axes:其height和width属性分别表示椭圆的长轴和短轴距离
// angle:长轴的倾角,单位为角度,从x轴正方向逆时针计算
// startAngle:椭圆弧的起始角度,单位为角度
// endAngle:椭圆弧的终止角度,单位为角度
// color:绘制颜色,BGR格式
// lineType:线型,只能传4或者8
// thickness、shift见函数cv::circle()对应注释
bool ellipse(cv::Mat& img,
             cv::Point center, cv::Size axes, double angle, 
             double startAngle, double endAngle,
             const cv::Scalar& color, int thickness = 1, int lineType  = 8,
             int shift = 0);

// 通过指定边界框的方式绘制椭圆
// rect:椭圆边界框,决定了椭圆的大小和方向
bool ellipse(cv::Mat& img, const cv::RotatedRect& rect, 
             const cv::Scalar& color, int thickness = 1, int lineType  = 8,
             int shift = 0);

这两种绘制椭圆的方法如下图,左图是使用上面的第一个函数的示意图,而右图则是使用上面的第二个函数的示意图。

cv::ellipse2Poly()

函数cv::ellipse()内部会调用到函数cv::ellipse2Poly()计算一个近似椭圆弧的多边形,并保存该多边形的顶点,此外你也可以直接调用该函数。通过指定和函数cv::ellipse()中含义相似的centeraxesanglestartAngleendAngle参数后,再指定没两个相邻顶点的的角度参数后,该函数会讲一个近似表示该圆弧的多边形顶点写入到向量参数pts中。函数原型如下。

// center、axes、angle、startAngle、endAngle含义同函数cv::ellipse()
// delta:两个相邻顶点之间的角度
// pts:保存近似椭圆多边形的顶点向量
void ellipse2Poly(cv::Point center, cv::Size axes, double angle,
                  double startAngle, double endAngle, int delta,
                  vector<cv::Point>& pts);
cv::fillConvexPoly()

该函数较于接下来即将讲到的绘制封闭多边形的函数而言使用了更简单的算法,但是它不能处理有相交边的多边形。参数pts保存的顶点会作为序列将两点连接成一条线,同时也会将该序列点第一个点和最后一个点相连接形成一个封闭的多边形。其函数原型如下。

// pts:组成封闭多边形的顶点
// npts:顶点个数
// img、shift含义同函数cv::circle()
// color:绘制颜色,BGR格式
// lineType:线型,只能传4或者8
void fillConvexPoly(cv::Mat& img, const cv::Point* pts, int npts,
                    const cv::Scalar& color, int lineType = 8, int shift = 0);
cv::fillPoly()

绘制任意多边形的函数原型如下,和函数cv::fillConvexPoly()不同,该函数能够处理有相交边的多边形。另外该函数绘制的是一个封闭的多边形,参数pts中的第一个和最后一个顶点将会被连接。

// img、shift含义同函数cv::circle()
// pts:c语言风格二维数组,保存所有顶点,pts[i][j]表示第i个轮廓的第j个顶点
//         其尺寸=ncontours✖️npts
// npts:c语言风格数组,puts[i]表示第i个轮廓的顶点个数
// ncontours:多边形的轮廓数
// color:绘制颜色,BGR格式
// lineType:线型,只能传入4或8
// offset:每个顶点的像素偏移量
void fillPoly(cv::Mat& img,
              const cv::Point* pts, int npts, int ncontours,
              const cv::Scalar& color, int lineType = 8,
              int shift = 0, cv::Point offset = Point());
cv::line()

绘制直线的函数原型如下,超出图像范围的部分将会被裁减。

// img、shift含义同函数cv::circle()
// pt1:直线起点
// pt2:直线终点
// color:绘制颜色,BGR格式
// lineType:线型,只能传入4或8
void line(cv::Mat& img, cv::Point pt1, cv::Point pt2,
          const cv::Scalar& color, int lineType = 8, int shift = 0);
cv::rectangle()

绘制矩形的函数原型如下。

// img、shift含义同函数cv::circle()
// pt1:矩形的左上角顶点
// pt2:矩形的右下角顶点
// color:绘制颜色,BGR格式
// lineType:线型,只能传入4或8
void rectangle(cv::Mat& img, cv::Point pt1, cv::Point pt2,
               const cv::Scalar& color, int lineType = 8, int shift = 0);

// r:需要绘制的矩形
void rectangle(cv::Mat& img, cv::Rect r, 
               const cv::Scalar& color, int lineType = 8, int shift = 0);
cv::polyLines()

绘制任意多边形的函数原型如下,它可以处理边相交的多边形。

// img、shift含义同函数cv::circle()
// pts:c语言风格二维数组,保存所有顶点,pts[i][j]表示第i个轮廓的第j个顶点
//         其尺寸=ncontours✖️npts
// npts:c语言风格数组,puts[i]表示第i个轮廓的顶点个数
// ncontours:多边形的轮廓数
// isClosed:多边形是否封闭,即是否将pts的第一个和最后一个顶点相连
// color:绘制颜色,BGR格式
// lineType:线型,只能传入4或8
void polyLines(cv::Mat& img,
               const cv::Point* pts, int npts, int ncontours, bool isClosed,
               const cv::Scalar& color, int lineType = 8, int shift = 0);
cv::LineIterator

线迭代器实例cv::LineIterator可以从线序列中提取每个像素,其构造函数如下。线迭代器是OpenCV中的第一个函数对象(functor)的例子,在下篇文章中将会介绍更多类似的例子。

// img:被绘制的图像,可以从图片文件读取
// pt1:线段起点
// pt2:线段终点
// lineType:线型,只能传入4或8
// leftToRight:像素提取方向是否从左至右
LineIterator::LineIterator(cv::Mat& img, cv::Point pt1, cv::Point pt2,
                           int lineType = 8, bool leftToRight = false);

线迭代器初始化成功后,其整型成员属性cv::LineIterator::count将会记录这条线上的像素个数。重载的运算符🌟会返回一个uchar🌟的指针,它指向了当前像素数据的地址,这里可以简单将线迭代器理解为指向指针的一个指针变量。通过该运算符访问的当前像素从线段的一端开始,通过重载后的运算符++逐像素向线的另一端移动。实际的遍历过程是通过前面提到的Bresenham算法完成的。

使用该函数获取到的像素可能是多种通道,也可能是任意位深度的,但是返回的像素数据地址都是uchar🌟的指针,在使用时需要将其强转为正确的数据类型。例如在访问由3通道32位浮点型数据表示的图像时,如果线迭代器变量为iter,则获取像素数据的方式应该是(Vec3f🌟)🌟iter

1.2 文字和字体

绘图的另外一种方式就是绘制文字,下表列出了OpenCV提供的文字绘制相关函数。

文字绘制相关函数 描述
cv::putText() 在图上绘制指定的文字
cv::getTextSize() 获取字符串的宽和高
cv::putText()

文字绘制函数原型如下。

// img:文字绘制的图片目标,可以从图片文件读取或者直接创建
// tex:需要绘制的文字,通常使用cv::format创建
// origin:文字框的定位顶点
// fontFace:文字字体,如cv::FONT_HERSHEY_PLAIN,具体取值见下文
// fontScale:文字缩放比例,一个乘数,和字体指定的基础尺寸相乘得到文字真实大小
// color:文字颜色,RGB格式
// thickness:线条厚度
// lineType:线型,只能传入4或8
// bottomLeftOrigin:定位顶点位置
//                       true表示定位顶点origin为文本框左下角顶点
//                       false表示定位顶点origin为文本框左上角顶点
void cv::putText(cv::Mat& img, const string& text,
                 cv::Point origin, int fontFace, double fontScale,
                 cv::Scalar color, int thickness = 1, int lineType = 8,
                 bool bottomLeftOrigin = false);

参数fontFace可选子字体如下表。

参数fontFace取值 描述
cv::FONT_HERSHEY_SIMPLEX 标准大小无衬线字体 Normal size sans-serif
cv::FONT_HERSHEY_PLAIN 小号无衬线字体 Small size sans-serif
cv::FONT_HERSHEY_DUPLEX Normal 普通大小无衬线字体,比cv::FONT_HERSHEY_​SIM⁠PLEX更复杂
cv::FONT_HERSHEY_COMPLEX 普通大小无衬线字体,比cv::FONT_HERSHEY_DUPLEX更复杂
cv::FONT_HERSHEY_TRIPLEX 普通大小无衬线字体,比cv::FONT_HERSHEY_COMPLEX更复杂
cv::FONT_HERSHEY_COMPLEX_SMALL 小号版本的cv::FONT_HERSHEY_COMPLEX
cv::FONT_HERSHEY_SCRIPT_SIMPLEX 手写体
cv::FONT_HERSHEY_SCRIPT_COMPLEX 比cv::FONT_HERSHEY_SCRIPT_SIMPLEX更复杂的变体

上表的所有选项都可以使用逻辑与运算符和cv::FONT_HERSHEY_ITALIC组合从而渲染斜体文字。每个字体都有一个基础的大小,当函数cv::putText()中参数fontScale的值不等于1时,最终绘制的文字大小等于字体的基本大小和这个缩放系数的乘积。下图依次展示了上表每种字体的示例。

cv::getTextSize()

该函数返回在指定策略下被绘制的文字最终显示的大小,其原型如下。文本基线值的时文本排列时的基准线,如由字符a和b构成的文本,其基线就是紧贴字符底部的一条直线,而对于由y和g构成的文本,文字是挂在基线上的,即有部分内容是位于基线下方。

// text、origin、fontFace、fontScale、thickness含义合函数cv::putText()一致
// baseLine:文本基线相对于文本最低点的y坐标
cv::Size cv::getTextSize(const string& text, cv::Point origin,
                         int fontFace, double fontScale,
                         int thickness, int* baseLine);

2 函数对象

随着OpenCV库的发展,渐渐出现了很多封装复杂功能的对象,这些功能不是单一函数能够完成的,但是将它们实现为一组函数又会使得OpenCV的整个函数库变得太杂乱。因此 OpenCV引入了函数对象(fuction objects),它是实现这些复杂功能的封装。可以使用该函数提供服务时必要数据和配置创建一个函数对象,该对象内部会维护自己的状态信息,然后在需要使用这个服务时通过其成员函数或者将函数对象自身作为函数调用,后者通常是通过重载运算符()来实现的。

2.1 主成分分析

主成分分析(Principal component analysis)是一种分析多维样本分布并从中提取包含最多信息量维度子集的方法。主成分分析计算出的维度不一定和分布最初指定的基础维度相同,实际上它的一个最重要的作用就是能够根据维度的重要性重新排序得到新的基础维度,可能你已经看出来这里已经涉及到机器学习的概念了,在后面的内容中将会详细介绍。这些基向量可以被证明是整个分布的协方差矩阵的特征向量,相应的特征值就是对应维度的分布范围。主成分分析函数对象cv::PCA的工作原理如下图。

在a图中,使用正态分布逼近原始数据,原始数据中的某个样本会被投影到由这个正态分布的协方差矩阵特征向量定义的空间中。对a图中的样本调用函数cv::PCA::project()在被投影到b图空间中的相应位置后,再通过KLT投影到由最有用的特征向量子集定义的降维空间中,即c图中的白色方块。该样本还可以通过调用函数cv::PCA::backProject()投影回其原始空间中,即a图中的黑色方块。

给定一个样本集,PCA对象能够计算并且记忆一个新的基,这个基和该分布的协方差矩阵相关,它最大的好处就是和大的特征值对应的基向量能够携带分布中样本的主要信息。因此,在不损失太多精确度的情况下,我们可以丢弃包含少量信息的维度,这种降维方式被称为KLT(Karhunen-Loeve Transform)。一旦你载入了一个样本分布,主要的成分就将被计算,这个信息可以用来做很多事情,如对新向量应用KLT变换。这种降PCA功能实现为一个函数对象的方法方式,使得它能够记忆分布的必要信息,并在需要的时候提供变换向量的“服务”。

cv::PCA::PCA()

类PCA的构造函数如下,其中默认构造函数PCA::PCA()仅创建一个实例并初始化一个空结构。而第二个构造函数除了执行默认的构造函数外,还使用提供的参数调用接下来即将讲到的被重载的运算符()

PCA::PCA();

// data:原始样本数据,由行向量或者列向量组成的二维数组
// mean:均值矩阵,其尺寸为1✖️n或者n✖️1,表示在对应维度的样本均值// flags:原始样本数据结构标志
//             cv::PCA_DATA_AS_ROW表示其由列向量组成
//             cv::PCA_DATA_AS_COL表示其由行向量组成
//             每个向量表示一个样本在各个维度的值
// maxComponents:主成分分析保留的最大维度,0表示保留所有维度
PCA::PCA(cv::InputArray data, cv::InputArray mean,
         int flags, int maxComponents = 0);
cv::PCA::operator()()

PCA实例重载的运算符()及其参数如下,该运算符会根据特征向量建立内部样本的分布模型。每次调用该重载的运算符都会重新计算其内部的特征向量和特征值,也就是说如果你需要重新分析一组新的样本,可以重用PCA对象再调用该重载运算符,无需再重新实例化一个新的对象。

// 参数含义和类PCA的构造函数中一致
PCA::operator()(cv::InputArray data, cv::InputArray mean,
                int flags, int maxComponents = 0);
cv::PCA::project()

在基于特征向量的分布模型建立后,就可以执行如将一些向量集投影到基于主成分分析的新的基向量上的KLT变换等操作。执行KLT变换等两个函数原型如下,其中将投影结果返回到函数的一个优势是能够直接在矩阵表达式中使用。

// 返回值:投影结果,二维矩阵,其向量组成方式及数量应该和这个PCA对象载入原始样本分布时的数据结构
// 相同,但是其维度应当和载入原始样本分布时传入的参数maxComponents相同
// vec:需要投影的向量集,二维矩阵
//      由列向量或者行向量组成,其组成方式和每个向量的维度应该和这个PCA对象载入原始样本分布时的
//      数据结构相同
cv::Mat PCA::project(cv::InputArray vec) const;

// result:投影结果,是一个二维矩阵
void PCA::project(cv::InputArray vec, cv::OutputArray result) const;
cv::PCA::backProject()

该函数是函数cv::PCA::project()的逆运算,他们对输入和输出矩阵的向量组成方式,向量维度及数量要求类似。

// 返回值:样本在原始分布空间的取值,二维矩阵,其向量组成方式、数量及维度应该和这个PCA对象载入
//       原始样本分布时的数据结构相同
// vec:经过函数cv::PCA::project()投影到主成分空间中的样本集合,二维矩阵
//      由列向量或者行向量组成,其组成方式和每个向量的维度应该和这个PCA对象载入原始样本分布
//      时的数据结构相同,其维度应当和载入原始样本分布时传入的参数maxComponents相同
cv::Mat PCA::backProject(cv::InputArray vec} const;

void PCA::backProject(cv::InputArray vec, cv::OutputArray result) const;

需要注意的是如果在载入原始数据分布时设定不保留所有的维度,则对于原始样本向量x,经过KLT投影得到向量x1,再将x1逆投影得到x2,则x和x2是有差异的,当然这种差异会很小,即使在载入原始数据分布时丢弃了很多维度,这也是使用PCA的意义所在。

2.2 奇异值分解

奇异值分解(Singular Value Decomposition)函数对象本质上是一个处理非方阵的、病态的(不适定的)或者在解决欠定线性系统中遇到的不良矩阵的工具。在数学上奇异值分解是对尺寸为m✖️n矩阵A的分解。其分解形式如下。

其中W为m✖️n的对角矩阵,U和V是m✖️m和n✖️n的单位矩阵。这里对角矩阵只行和列下标不相等的元素值都为0。

cv::SVD()

类SVD也包含两个构造函数,其中默认构造函数不包含任何参数,仅仅创建了SVD对象,初始化了一个空的结构。第二个构造函数在默认构造函数的基础上还使用传入的参数调用了接下来即将讲到的重载运算符(),它们函数原型如下。参数flags表示奇异值分解策略,其取值可以是cv::SVD::MODIFY_Acv::SVD::NO::UVcv::SVD::FULL_UV,其中后两个值是互斥的,但是它们都能通过逻辑与符号和第一个值组合。cv::SVD::MODIFY_A表示在计算过程中可以修改矩阵A的值,从而加速计算并节约内存,在处理输入矩阵尺寸非常大时,这个选项很有用。cv::SVD::NO::UV表示不用显示的计算出矩阵UVtcv::SVD::FULL_UV则表示不仅要计算出这两个矩阵的值,而且它们还应该表示为全尺寸的正交方阵。

SVD::SVD();

// A:需要处理的矩阵
// flags:奇异值分解策略
SVD::SVD(cv::InputArray A, int flags = 0);
cv::SVD::operator()()

使用重载运算符载入矩阵后A,会计算出上面讲解奇异值分解公式时引入的矩阵UVt,以及构成对角矩阵W的一组奇异值。

// 参数含义同默认的构造函数一致
SVD::& SVD::operator() (cv::InputArray A, int flags = 0);
cv::SVD::compute()

该函数是计算奇异值分解的另外一种方式,和前面的函数不同的是这种方式会将矩阵WUVt写入到用户指定的矩阵中。

// A:需要处理的矩阵
// W、U、Vt:见奇异值分解等式
// flags:见构造函数中的定义
void SVD::compute(cv::InputArray A, 
                  cv::OutputArray W, cv::OutputArray U, cv::OutputArray Vt,
                  int flags = 0);
cv::SVD::solveZ()

该函数原型如下,给定一个欠定线性系统,函数cv::SVD::solveZ()会尝试寻找一个单位长度解使得A✖️X=0成立,其中X向量为线性系统的解。并将结果写入到矩阵Z中。因为线性系统是奇异的,因此可能存在无穷多个解,也可能无无解。如果存在解,将会将结果写入到矩阵Z中,如果不存在解,则会找到一个使得A✖️X最小的解。

// A:待分解的奇异线性系统矩阵
// z:一个可能的单位长度解
void SVD::solveZ(cv::InputArray A, cv::OutputArray z);
cv::SVD::backSubst()

求解线性系统的另一类函数具有两种形式,第一种形式函数原型如下,需要使用前面已经介绍过的方法先求出矩阵UWVt

// b:线性系统的右侧常数矩阵
// x:线性系统的解
void SVD::backSubst(cv::InputArray b, cv::OutputArray x);

该函数的计算公式如下。

第二种形式的函数原型如下,它需要传入矩阵UWVt

// W:奇异值对角线矩阵
// U:左侧奇异向量
// Vt:右侧奇异向量
// b:线性系统的右侧常数矩阵
// x:线性系统的解
void SVD::backSubst(cv::InputArray W, cv::InputArray U,
                    cv::InputArray Vt, cv::InputArray b,
                    cv::OutputArray x);

该函数的计算公式如下。

这两种函数在求解时如果处理的是超定线性系统,则会使用最小二乘法求出一个最接近的解,如果处理的是恰定方程组则会直接求出线性系统的解。实际上,很少情况会直接使用到函数cv::SVD::backSubst()。因为使用函数cv::solve()并将参数flag设置为cv::DECOMP_SVD能达到同样的目的,而且这种方式更简单。只有在很少的情况下,如果需要使用相同的系数矩阵即左手侧处理不同的线性系统时才需要优先调用 函数cv::SVD::backSubst(),相反如果使用不同的常数矩阵即右手侧处理相同线性系统时最好调用函数cv::solve()

2.3 随机数生成器

随机数生成器(Random Number Generator)函数对象cv::RNG持有生成随机数的伪随机数队列的状态,这样可以更方便的得到多个伪随机数流。在大型系统中,在不同模块使用不同不同的随机数流是一个很好的习惯,这样当某个模块移除后不会改变其他模块内部的随机数流。随机数生成器创建后,可以提供均一分布或者正态分布的随机数。生成均一分布随机数使用了Multiply with Carry(MWC)算法,生成正态分布随机数使用了Ziggurat算法。

cv::theRNG()

该函数返回当前线程的默认的随机数生成器。其原型如下。

cv::RNG& theRNG(void);

OpenCV会默认为每个正在执行的线程创建一个随机数生成器,你也可以通过调用函数cv::randu()或者cv::randn()隐式的使用这个随机数生成器。如果你只是简单的创建一个随机数或者是初始化一个矩阵,使用这些方法更为便捷。但是如果有一个循环需要生成大量的随机数,最好持有一个随机数生成器 。这个函数获取的是默认的随机数生成器,当然你也可以创建一个新的实例,然后通过重载的运算符()获取随机数。

cv::RNG()

随机数生成器的构造函数原型如下。

// 使用默认的随机种子创建随机数生成器,通常为2^32 - 1
cv::RNG::RNG( void );

// state:随机种子,传入0时使用默认的随机种子
cv::RNG::RNG( uint64 state );
cv::RNG::operator T()

重载的类型转换运算符T()是从某个随机数生成器生成指定类型的随机数方法集,其中T表示的是数据类型。另外使用(T)风格的类型转换符也是有效的。这些重载的类型转换运算符如下。

cv::RNG::operator uchar();
cv::RNG::operator schar();
cv::RNG::operator ushort();
cv::RNG::operator short int();
cv::RNG::operator int();
cv::RNG::operator unsigned();
cv::RNG::operator float();
cv::RNG::operator double();

生成了不同类型的随机数代码如下。

cv::RNG rng = cv::theRNG();
cout << "An integer: " << (int)rng << endl;
cout << "Another integer: " << int(rng) << endl;
cout << "A float: " << (float)rng << endl;
cout << "Another float: " << float(rng) << endl;

生成整型随机数时取值空间同整型数的取值空间一致,而在生成浮点型随机数时,取值空间是[0.0,1.0]

cv::RNG::operator()

通过重载的运算符()获取随机数会更加便利,本质上,使用该运算符等同于(unsigned int)rng,此外还支持设置一个参数N,通过rng(N)理由获取取值取决为[0, N-1]的均匀分布随机数。

unsigned int cv::RNG::operator()();
unsigned int cv::RNG::operator()(unsigned int N);
cv::RNG::uniform()

生成指定区间均匀分布随机数的函数原型如下。

// 取值区间为[a, b-1]
int cv::RNG::uniform(int a, int b);
// 取值区间为[a, b)
float  cv::RNG::uniform(float a, float b);
// 取值区间为[a, b)
double cv::RNG::uniform(double a, double b);

C++编译器在处理函数名相同的函数时只考虑参数类型,而不会考虑返回值类型,因此对于代码float x = rng.uniform(0, 1),得到的结果只会是0.0f,因为代码段中参数0和1都被识别为整型数据。如果想要得到浮点型数据,则必须显示告诉编译器,则代码应该写成float x = rng.uniform(0.f, 1.f),如果想要得到双精度数据,则代码应该写成double x = rng.uniform(0., 1.),当然对参数使用显示类型转换也是可以的。

cv::RNG::gaussian()

生成期望为0,标准差为sigma的正态分布随机数的函数原型如下。

// sigma:正态分布的标准差
double cv::RNG::gaussian(double sigma);
cv::RNG::fill()

该函数可以使用指定分布的随机数填充一个最多四通道的矩阵,其原型如下。矩阵a和b的含义取决于使用的分布类型,但是它们的尺寸都是n✖️1或者1✖️n,其中n等于矩阵mat的通道数。在使用随机数填充矩阵是,矩阵的每个元素的每个通道都使用矩阵a和矩阵b中对应通道的特征元素决定。

// mat:需要被填充的矩阵
// distType:随机数分布的类型,正态分布或者均匀分布
// a:对于正态分布,表示的是期望,对于均匀分布表示的是最小值
// b:对于正态分布,表示的是方差,对于均匀分布表示的是最大值
void cv::RNG::fill(InputOutputArray mat, int distType,
                   InputArray a, InputArray b);

3 小结

本章首先有关图形和文字绘制的函数,它们都是应用在矩阵对象cv::Mat上。接着又介绍了函数对象的概念,如负责主成分分析的PCA,负责奇异值矩阵求解线性系统的SVD,负责生成随机值的RNG对象。随着对OpenCV学习的深入,将会遇到更多使用这一概念的现代插件。