最近太忙了,今天抽空更新一篇。
随着OpenCV库的发展,引入新封装的对象变得越来越普遍,这些封装功能过于复杂,无法与单个功能相关联,并且如果作为一组功能实施,会导致库的整体功能空间变得太混乱。
因此,新功能通常由相关的新对象类型来表示,它可以执行任何功能。大多数这些都有一个重载的operator()。他们可以设置需要的任何数据或配置,并且他们被要求通过普通成员函数来执行,或者通过重载operator()来调用。
主成分分析(PCA)。
主成分分析是从多维分布中提取主要成分的过程。得到的是携带最多信息的维度子集。由PCA计算的维度不一定是最初指定分布的基准维度。事实上,主成分分析最重要的方面之一是能够产生一个新的按其重要性进行排序的维度。这些基向量将成为总体分布的协方差矩阵的特征向量,相应的特征值将告诉我们关于该维度中分布的程度。
与特征值对应的基向量携带有关分布中物体的大部分信息。因此,在不损失多少准确性的情况下,可以放弃较少信息量的维度。一旦加载了一个样本分布并计算了主要成分,可能想要用这些信息来做各种事情。
PCA对象有一个默认构造函数PCA(),它只是构建PCA对象并初始化一个空的结构,然后继续将其参数传递给operator()。
PCA的重载运算符构建PCA对象内部的分布。数据可以排列为n×D(n行采样,每个D维)或D×n阵列(n列采样,每个D维)。 flags参数目前仅用于指定数据中数据的排列和均值。可以将标志设置为PCA_DATA_AS_ROW或PCA_DATA_AS_COL,以指数据是n×D并且均值是n×1或者数据是D×n并且平均值是1×n。参数maxComponents指定了PCA应保留的最大维度。
一旦用operator(),你可以开始要求PCA对象为你做一些有用的事情,比如计算某些向量集的投影project()。 project()函数有两种形式;第一个返回包含投影结果的矩阵,第二个将结果写入提供的矩阵。第一种形式的优点是可以在矩阵表达式中使用它。
vec参数包含输入向量。 vec需要具有与第一次分析时传递给PCA的数据数组相同的维数和相同的“方向”。比如,如果数据是列时, vec也应该将数据排列成列。
返回的数组将具有与vec具有相同方向的相同数量的对象,但每个对象的维度都是在PCA对象首次使用operator()配置时传递给maxComponents的内容。
backProject()函数执行project()的反向操作,对输入和输出数组有类似的限制。 vec参数包含输入向量,这个输入向量来自投影空间。当配置PCA对象时,它们将具有与在maxComponents中指定的相同数量的维度,并且与首次分析时传递给PCA的数据数组具有相同的“方向”。例如,如果数据是列,vec也应该把数据排列成列。
返回的数组将具有与vec相同数量的对象,方向相同,但每个对象的维度将是PCA对象首次配置为PCA时给予PCA对象的原始数据的维数。
例1 PCA使用示例,单张图像的PCA投影和反投影
int main(int argc,char** argv){ Mat dst, src; Mat gray = imread("E:/test.png", 0); namedWindow("原图", 0); namedWindow("PCA映射", 0); namedWindow("PCA反映射", 0); int maxComponents = 100; cv::PCA pca(gray, Mat(), cv::PCA::DATA_AS_ROW, maxComponents);//0.95 dst = pca.project(gray); src = pca.backProject(dst); imshow("原图", gray); imshow("PCA映射", dst); imshow("PCA反映射", src); waitKey(0); return 0;}
![c1742ad35fdfead81a3d8ad465444e33.png](https://i-blog.csdnimg.cn/blog_migrate/f43910a6944cc163e03958b295202ea7.jpeg)
例1 结果
例2 PCA使用示例,系列图像的均值,特征值和特征向量
int main(int argc, char** argv){ Mat data; string prefix = "E:/p1/"; Mat src = imread(prefix + "1.jpg", 0); int rows = src.rows; Mat rowMat = src.reshape(1,1); data.push_back(rowMat); src = imread(prefix + "2.jpg", 0); rowMat = src.reshape(1, 1); data.push_back(rowMat); src = imread(prefix + "3.jpg", 0); rowMat = src.reshape(1, 1); data.push_back(rowMat); // PCA算法保留的主成分分量 int num_components = 100; //pca PCA pca(data, Mat(), cv::PCA::DATA_AS_ROW, num_components); Mat mean = pca.mean.clone(); Mat eigenvalues = pca.eigenvalues.clone(); Mat eigenvectors = pca.eigenvectors.clone(); //均值脸 namedWindow("mean", 0); imshow("mean", norm_0_255(mean.reshape(1, rows))); namedWindow("p1_1", 0); namedWindow("p1_2", 0); namedWindow("p1_3", 0); imshow("p1_1", norm_0_255(pca.eigenvectors.row(0)).reshape(1, rows)); imshow("p1_2", norm_0_255(pca.eigenvectors.row(1)).reshape(1, rows)); imshow("p1_3", norm_0_255(pca.eigenvectors.row(2)).reshape(1, rows)); waitKey(0); return 0;}
![eea447e44084373eef27b8a5ca993760.png](https://i-blog.csdnimg.cn/blog_migrate/57eff42c2bd640b534379a97bb7d6599.jpeg)
例2结果
PCA算法在早期用于人脸识别,可以提取人脸的主要特征,但是识别准确率有限。
奇异值分解(SVD)。
SVD类与PCA类似,因为它是同一类型的函数对象。 然而,它的目的是完全不同的。 奇异值分解本质上是一种用于处理病态矩阵的工具。 在数学上,奇异值分解(SVD)是将m×n矩阵进行分解。
其中W是对角矩阵,U和V分别是m×m和n×n矩阵。 当然,矩阵W也是一个m×n的矩阵,所以这里的“对角线”意味着行和列号不相等的任何元素必然是0。
SVD对象有一个默认构造函数SVD(),它只是构建SVD对象并初始化空的结构,然后立即继续将其参数传递给operator()。
运算符operator()将要分解的矩阵传递给SVD对象。矩阵被分解为矩阵U, Vt和一组奇异值。标志可以是MODIFY_A, NO_UV或FULL_UV中的任何一个。后两者是相互排斥的,但可以与第一个结合。 标志MODIFY_A表示在计算时修改矩阵A是可以的。 这加快了计算速度并节省了一些内存。 当输入矩阵已经非常大时更重要。 标志NO_UV告诉SVD不明确地计算矩阵U和Vt,Vt其实是V的转置矩阵,这点与matlab有点不同。而标志FULL_UV表明不仅希望计算U和V,而且被表示为正交矩阵。
如果存在解的话,solveZ()函数将解放在数组中,solveZ()会找到一个解决方案, 如果不存在解,则返回最小的向量。
例3 SVD使用示例
int main(int argc, char** argv){ Mat src = imread("E:/ test.png", 1); namedWindow("src", 1); imshow("src", src); Mat gray; cvtColor(src, gray, COLOR_RGB2GRAY); namedWindow("gray", 1); imshow("gray", gray); Mat U, W, Vt; gray.convertTo(gray, CV_32FC1); SVD::compute(gray, W, U, Vt); Mat w = Mat::zeros(Size(W.rows, W.rows), CV_32FC1); int width = 0.02*W.rows; for (int i = 0; i < width; ++i) w.ptr(i)[i] = W.ptr(i)[0]; Mat result = Mat::zeros(Size(gray.cols, gray.rows), CV_32FC1); result= U*w*Vt; Mat outputImg; result.convertTo(outputImg, CV_8UC1); namedWindow("result", 1); imshow("result", outputImg); waitKey(0); return 0;}
SVD可以用于数据降维,注意看上面的例2中的int width = 0.02*W.rows。最后出来的图像的结果与原图是有区别的。
![18ea4fb7a65b24c5641ee934a864e11a.png](https://i-blog.csdnimg.cn/blog_migrate/771861e11d587274332a19cb03ecc7ab.jpeg)
例3结果
随机数发生器(RNG)。
在对大型系统进行编程时,最好在代码的不同模块中使用单独的随机数字。 这样,删除一个模块不会改变其他模块中的行为。
随机数发生器会根据需要生成均匀分布或高斯分布的随机数。
theRNG()函数返回调用默认随机数生成器。 OpenCV会为执行中的每个线程自动创建一个RNG实例。这是由randu()或randn()等函数隐式访问的同一个随机数生成器。 如果想要一个数字或初始化一个数组,这些函数很方便。 然而,如果需要产生大量随机数,最好可以使用operator T()来获得随机数。
可以使用默认构造函数创建一个RNG对象,也可以通过向它传递一个64位无符号整数,它将用作随机数序列的种子。 如果调用默认构造函数,则生成器将使用标准化值进行初始化。
cv::RNG::operator 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();
operator T()实际上是一组不同的方法,从某些特定类型的RNG返回一个新的随机数。 它们中的每一个都是重载的转换运算符,因此实际上可以将RNG对象转换为所需的任何类型,
当生成整数类型时,它们将在整个值范围内生成。 当生成浮点类型时,它们将始终位于区间[0.0,1.0)的范围内。
uniform()函数允许在区间[a,b)中按照均匀分布生成一个随机数。
gaussian()函数允许根据给定的标准偏差生成一个随机数。
例4 随机数生成示意
int main(int argc, char** argv){ RNG rng = theRNG(); cout << "整数: " << (int)rng << endl; cout << "整数: " << int(rng) << endl; cout << "浮点数: " << (float)rng << endl; cout << "浮点数: " << float(rng) << endl; int c = rng.uniform(0,10); // Return value from a-(b-1) float d = rng.uniform(0.1, 1.0); // Return value in range [a,b) double e = rng.uniform(1.1, 2.0); // Return value in range [a,b) cout << "uniform整数: " << c << endl; cout << "uniform浮点数: " << d << endl; cout << "uniform双精度数: " << e << endl; int f = rng.gaussian(2.0); // Return value from a-(b-1) float g = rng.gaussian(0.707); // Return value in range [a,b) double h = rng.gaussian(5.0); // Return value in range [a,b) cout << "gaussian整数: " << f << endl; cout << "gaussian浮点数: " << g << endl; cout << "gaussian双精度数: " << h << endl; system("pause"); waitKey(0); return 0;}
![9252e50ac7a885c1455fcd1a7e5f181a.png](https://i-blog.csdnimg.cn/blog_migrate/5824786a935e788a4b0c0ee3695d628f.jpeg)
例4结果