<学习opencv>图像和大型阵列类型
OPenCV /*=========================================================================*/ // 图像和大型阵列类型 /*=========================================================================*/ cv::Mat class:N维密集阵列 该cv::Mat class 可用于任何数组尺寸数量。数据存储在数组中,可以被认为是 “光栅扫描顺序” 的n维模拟。 这意味着在一维数组中,元素是顺序的。在二维数组中,数据被组织成行, 并且每行一个接一个地出现。对于三维阵列,每个平面逐行填充,然后平面一个接一个地打包。 每个矩阵包含一个 表示flags 数组内容的dims元素, 一个表示维数的元素,rows 以及cols表示行数和列数的元素(这些元素无效dims>2 ), 一个data 指向数组数据存储位置的指针,以及一个refcount 引用计数器类似于使用的参考计数器cv::Ptr<> 。 后一个成员允许cv::Mat 表现得非常类似于包含在其中的数据的智能指针data 。存储器布局data 由数组描述step[] 。 包含的数据cv::Mat 不需要是简单的原语。a中数据的每个元素cv::Mat 本身可以是单个数字,也可以是多个数字。在多个数字的情况下,这是什么 库指的是多通道 阵列。实际上,n 维数组和(n -1)维多通道数组实际上是非常相似的对象,但是因为在很多情况下将数组视为向量值 数组是有用的, 图书馆包含对此类结构的特殊规定。1 这种区别的一个原因是内存访问。根据定义, 数组的元素是可以是向量值的部分。 例如,一个数组可能被认为是一个32位浮点数的二维三通道数组; 在这种情况下,数组的元素是三个32位浮点数,大小为12个字节。 当在内存中布局时,数组的行可能不是绝对顺序的; 可能存在小间隙,在下一行之前缓冲每一行。 2n维单通道数组和(n -1)维多通道数组之间的区别在于此填充将始终出现在完整行的末尾(即,元素中的通道始终是顺序的)。 创建一个数组: 您可以简单地创建一个数组 通过实例化一个变量 类型cv::Mat 。 以这种方式创建的数组没有大小,也没有数据类型。 但是,您可以稍后要求它使用诸如的成员函数来分配数据create() 。 create() 作为参数的一种变体是多个行,多个列和一个类型,并将数组配置为表示二维对象。 数组的类型决定了它具有哪种元素。此上下文中的有效类型既指定元素的基本类型,也指定通道数。 所有这些类型在库头限定,并且具有形式CV_ {8U ,16S ,16U ,32S ,32F ,64F } C { 1 ,2 ,3 }。 例如, CV_32FC3 意味着一个32位浮点三通道阵列。 如果您愿意,还可以在首次分配矩阵时指定这些内容。有许多构造函数cv::Mat, 其中一个构造函数采用相同的参数create()(以及一个可选的第四个参数,用于初始化新数组中的所有元素)。 例如: cv :: Mat m; //为3行和10列3通道32位浮点数创建数据区域 m.create(3,10,CV_32FC3); //将第1个通道中的值设置为1.0,将第2个中的值设置为0.0,将第3个中的值设置为1.0 m.setTo(cv :: Scalar(1.0f,0.0f,1.0f)); 相当于: cv::Mat m(3,20,CV_32FC3,cv::Scalar(1.0f,0.0f,1.0f)) ; 了解数组中的数据没有 严格附加到数组对象是至关重要的。 该cv::Mat 对象实际上是数据区域的标题,原则上它是完全独立的。 cv::不复制数据的mat构造函数 —————————————————————————————————————————————————————————————————— 构造函数 | 描述 —————————————————————————————————————————————————————————————————— cv::Mat ; | 默认构造函数 —————————————————————————————————————————————————————————————————— cv::Mat(int rows,int cols,int type) ; | 按类型的二维数组 —————————————————————————————————————————————————————————————————— cv::Mat (int rows, int cols, | 具有初始化值的二维数组 int type, const Scalar&s) ; | —————————————————————————————————————————————————————————————————— cv::Mat(int rows, int cols, | 具有预先存在的数据的二维数组 void* data,size_t step = AUTO_STEP) | —————————————————————————————————————————————————————————————————— cv::Mat(cv::Size sz,int type) ; | 按类型划分的二维数组(Size sz) —————————————————————————————————————————————————————————————————— cv::Mat(cv::Size sz,int type, | 具有初始化值的类型的二维数组(Size sz) const Scalar&s) ; | —————————————————————————————————————————————————————————————————— cv::Mat(cv::Size sz,int type, | 具有预先存在的数据的类型的二维数组 void* data,size_t step = AUTO_STEP); | —————————————————————————————————————————————————————————————————— cv::Mat(int ndims,const int* sizes, | 按类型的多维数组 int type) ; | —————————————————————————————————————————————————————————————————— cv::Mat(int ndims,const int * sizes, | 具有初始化值的多维数组 int type, const Scalar&s) ; | —————————————————————————————————————————————————————————————————— cv::Mat(int ndims, const int *sizes, | 具有预先存在的数据的多维数组 int type,void * data, | size_t step = AUTO_STEP) | —————————————————————————————————————————————————————————————————— 除了默认构造函数之外,它们分为三个基本类别: 那些采用多个行和多个列来创建二维数组的类,使用cv::Size 对象创建二维数组的那些, 以及构造二维数组的那些n维数组,要求您指定维数,并传入指定每个维的大小的整数数组。 此外,其中一些允许您初始化数据,或者通过提供cv::Scalar(在这种情况下,整个数组将初始化为该值), 或者通过提供指向数组可以使用的适当数据块的指针。在后一种情况下,您基本上只是创建现有数据的标头。 (即,不复制数据;数据成员设置为指向data参数指示的数据 )。 cv::从其他cv::Mats复制数据的mat构造函数 —————————————————————————————————————————————————————————————————— 构造函数 | 描述 —————————————————————————————————————————————————————————————————— cv::Mat(const Mat &mat) ; | 复制构造函数 —————————————————————————————————————————————————————————————————— cv::Mat(const Mat &mat, | const cv::Range &rows, | 复制仅复制行和列子集的构造函数 const cv::Range &cols) ; | —————————————————————————————————————————————————————————————————— cv::Mat (const Mat &mat, | | 复制构造函数, const cv::Range &roi) ; | 仅复制由感兴趣区域指定的行和列的子集 —————————————————————————————————————————————————————————————————— cv::Mat(const Mat &mat, | 复制构造函数 const cv::Rect &roi) | 仅复制由感兴趣区域指定的行和列的子集 —————————————————————————————————————————————————————————————————— cv::Mat(const Mat &mat, | 广义感兴趣区域复制构造函数 const cv::Range *range) ; | 使用范围数组从n维数组中进行选择 —————————————————————————————————————————————————————————————————— cv::Mat(const MatExpr &expr) | 复制构造函数,m 使用其他矩阵的代数表达式的结果进行初始化 | —————————————————————————————————————————————————————————————————— 子区域(也称为“感兴趣区域”)构造函数也有三种形式: 一种采用一系列行和一系列列(这仅适用于二维矩阵),一 种使用a cv::Rect 指定一个矩形子区域(也仅适用于二维矩阵),以及最后一个采用范围数组的子区域。 在后一种情况下,指针参数指向的有效范围的数量ranges 必须等于数组的维数mat 。 如果mat是ndim 大于2的多维数组,则必须使用第三个选项。 cv::Mat 模板构造函数 —————————————————————————————————————————————————————————————————— 构造函数 | 描述 —————————————————————————————————————————————————————————————————— cv::Mat(const cv::Vec<T,n> &vec, | 从同一类型构造一个类型T和大小的一维数组 bool copyData = true) | ncv::Vec —————————————————————————————————————————————————————————————————— cv::Mat(const cv::Matx<T,m,n>&vec, | 从相同类型的构造中构造类型T和Sizemx的二维数组 bool copyData = true) | ncv::MatExpr —————————————————————————————————————————————————————————————————— class cv::Mat 还提供了一个数字静态成员函数用于创建某些常用数组(表4-5 )。这些包括诸如zeros() , ones() 和等的函数eye(),它们分别构造一个满零的矩阵,一个满的矩阵或一个单位矩阵。 创建cv::Mat的静态函数 —————————————————————————————————————————————————————————————————— 功能 | 描述 —————————————————————————————————————————————————————————————————— cv::Mat::zeros(rows,cols,type) ; | 创建一个cv::Mat大小为rows*cols,其中类型type(CV_32FC3)等 —————————————————————————————————————————————————————————————————— cv::Mat::ones(rows,cols,type) ; | 创建一个cv::Mat大小rows*cols,其中包含类型type(CV_32FC3等) —————————————————————————————————————————————————————————————————— cv::Mat::eye(rows,cols,type) ; | 创建一个cv::Mat大小rows*cols,这是单位矩阵 —————————————————————————————————————————————————————————————————— ///单独访问数组元素 直接访问的基本手段是(模板)成员函数at<>()。此函数有许多变体,它们为不同维数的数组采用不同的参数。 此函数的工作方式是将at<>()模板专门化为矩阵包含的数据类型,然后使用所需数据的行和列位置来访问该元素。 cv::Mat m = cv::Mat::eye(10,10,32FC1) ; printf( "Element(3,3) is %f\n", m.at<float>(3,3) ; ) 对于多通道阵列,例如: cv::Mat m = cv::Mat::eye(10,10,32FC2) ; printf( "Element(3,3) is (%f,%f)\n" , m.at<cv::Vec2f>(3,3)[0] ; m.at<cv::Vec2f>(3,3)[1] ; ) 请注意,如果要指定模板函数,例如at<>() 在多通道阵列上操作, 最好的方法是使用cv::Vec<> 对象(使用预制别名或模板形式)。 与矢量大小写类似,您可以创建由更复杂类型组成的数组,例如复数: cv::Mat m = cv::Mat::eye(10,10,cv::DataType<cv::Complexf>::type) ; printf( "Element(3,3 is %f + i%f \n", m.at<cv::Complexf>(3,3).RE ; m.at<cv::Complexf>(3,3).IM ; ); at<>()访问器函数的变化 —————————————————————————————————————————————————————————————————— 例 | 描述 —————————————————————————————————————————————————————————————————— M.at<int>(i) ; | i整数数组中的元素M —————————————————————————————————————————————————————————————————— M.at<float>(i,j) ; | (i,j)浮点数组中的元素M —————————————————————————————————————————————————————————————————— M.at<int>(pt) ; | (pt.x, pt.y)整数矩阵中位置出的元素M —————————————————————————————————————————————————————————————————— M.at<float>(i,j,k) ; | (i,j,k)三维浮点数组中位置的元素M —————————————————————————————————————————————————————————————————— M.at<uchar>(idx) | n维位置的元素由无符号字符idx[]数组表示M —————————————————————————————————————————————————————————————————— 要访问二维数组,还可以提取指向数组特定行的C样式指针。这是通过ptr<>模板成员函数 完成的cv::Mat。 至于at<>() ,ptr<>() 是一个类型名实例化的模板函数。 它需要一个整数参数来指示您希望获得指针的行。 该函数返回一个指向构造数组的基本类型的指针(即,如果数组类型是CV_32FC3 ,则返回值将是类型float* )。 因此,给定一个mtx 类型的三通道矩阵float ,该结构mtx.ptr<Vec3f>(3)将返回指向行3中第一个元素的 第一个(浮点)通道的指针mtx 。这通常是访问数组元素最快的方法,因为一旦有了这个指针,你是用正确的数据在那儿。 【注意】 因此有两种方法可以获得指针 矩阵中的数据mtx 。 一种是使用ptr<>() 成员函数。另一种是直接使用成员指针data , 并使用成员数组step[] 来计算地址。后者选项类似于什么一个倾向于在C做的界面, 但一般不再优于接入方法,诸如at<>() ,ptr<>() 以及迭代器。 话虽如此,直接地址计算可能仍然是最有效的,特别是当您处理大于两个维度的数组时。 关于C风格的指针访问,请记住最后一点。如果要访问数组中的所有内容,您可能希望一次迭代一行; 这是因为行可能会或可能不会在阵列中连续打包。但是,成员函数isContinuous() 会告诉您成员是否连续打包。如果它们是,你可以抓住指向第一行的第一个元素的指针并巡航整个数组,好像它是一个巨大的一维数组。 顺序访问的另一种形式是使用内置的迭代器机制cv::Mat。 该机制基于STL容器提供的类似机制,并且或多或少地与其相同。 基本思想是OpenCV提供了一对迭代器模板,const 一个用于非const 数组,一个用于非数组。 这些迭代器分别命名cv::MatIterator<> 和cv::MatConstIterator<> 。 这种cv::Mat 方法begin()和end() 返回对象。 这种迭代方法很方便,因为迭代器足够智能,可以自动处理连续打包和非连续打包情况, 以及处理数组中的任意数量的维度。必须声明每个迭代器并将其指定为构造数组的对象类型。 [例]简答的迭代器示例,用于计算三通道元素三维数组(三维矢量场)中的“最长”元素. int sz[3] = {4,4,4} ; cv::Mat m(3,sz,CV_32FC3) ; //4*4*4的三维数组 cv::randu(m,-1.0f,1.0f) ; float max = 0.0f ; cv::MatConstIterator<cv::Vec3f>it = m.begin() ; while(it != m.end()) { len2 = (*it)[0] * (*it)[0] + (*it)[1] * (it)[1] + (*it)[2] * (*it)[2] ; if (len2 > max) max = len2 ; it++ ; } 在整个数组上执行操作时,通常会使用基于迭代器的访问,或者在多个数组中执行元素操作。 考虑添加两个数组,或将数组从RGB颜色空间转换为HSV颜色空间的情况。 在这种情况下,将在每个像素位置进行相同的精确操作。 N-ary数组迭代器: NAryMatIterator N -ary迭代器不是返回被迭代的数组的单个元素,而是通过返回那些称为平面的数组的块来操作 。 一个plane是输入数组的一部分(通常是一维或二维切片),其中数据保证在存储器中是连续的。 这就是如何处理不连续性:逐个给出连续的块。 对于每个这样的平面,您可以使用数组操作对其进行操作,或者自己对其进行简单的迭代。 [例]多维数组的求和,逐平面完成 —————————————————————————————————————————————————————————————————— const int n_mat_size = 5 ; const int_n_mat_sz[] = {n_mat_size,n_mat_size,n_mat_size} ; cv::Mat n_mat(3,n_mat_sz,CV_32FC1) ; cv::RNG rng ; const cv::Mat * arrays[] = {&n_mat, 0} ; const cv::Mat my_planes[1] ; cv::NAryMatIterator it (arrays, my_planes) ; //在每次迭代中,it.planes [i]将成为当前的平面 //来自'arrays'的第i个数组。 float s = 0.f ; //所有平面的总和 int n = 0 ; //总平面数 for (int p = 0; p < it.nplanes; p++, ++it) { s += cv::sum(it.planes[0])[0] ; n++ ; } 一旦我们创建了N -ary迭代器,我们就可以迭代它。 回想一下,这个迭代是在构成我们给迭代器的数组的平面上。 平面的数量(每个数组相同,因为它们具有相同的几何形状)将始终由it.nplanes 。 所述Ñ 进制迭代器包含一个称为C数组planes ,其保持标头为每个输入阵列中的当前平面。 在我们的例子中,只有一个数组被迭代,所以我们只需要参考it.planes[0] (一个唯一的数组中的当前平面)。 [例]使用N-ary运算符求和两个数组 —————————————————————————————————————————————————————————————————— const int n_mat_size = 5 ; const int n_mat_sz[] = {n_mat_size,n_mat_size,n_mat_size} ; cv::Mat n_mat0(3, n_mat_sz, CV_32FC1) ; cv::Mat n_mat1(3, n_mat_sz, CV_32FC1) ; cv::RNG rng ; rng.fill(n_mat0, cv::RNG::UNIFORM,0.f,1.f) ; rng.fill(n_mat1, cv::RNG::UNIFORM,0.f,1.f) ; const cv::Mat * arrays[] = {&n_mat0, &n_mat1,0} ; cv::Mat my_planes[2] ; cv::NAryMatIterator it(arrays, my_planes) ; float s = 0.f ; //两个数组中所有平面的总和 int n = 0 ; for (int p = 0; p < it.nplanes; p++,++it) { s += cv::sum(it.planes[0])[0] ; s += cv::sum(it.planes[1])[0] ; n++ ; } 调用的C样式数组arrays 被赋予指向两个输入数组的指针,并且在my_planes 数组中提供了两个矩阵。 当需要迭代平面时,在每个步骤中,planes[0] 包含一个平面n_mat0 并planes[1]包含相应的平面n_mat1 。在这个简单的例子中, 我们只将两个平面相加并将它们添加到我们的累加器中。在一个稍微扩展的情况下, 我们可以使用逐元素加法来对这两个平面求和,并将结果放入第三个数组中的相应平面。 /////////// compute dst [*] = pow(src1 [*],src2 [*])////////////// const Mat * arrays [] = {src1,src2,dst,0}; float * ptrs [3]; NAryMatIterator it(arrays,(uchar **)ptrs); for(size_t i = 0; i <it.nplanes; i ++,++ it) { for(size_t j = 0; j <it.size; j ++) { ptrs [2] [j] = std :: pow(ptrs [0] [j],ptrs [1] [j]); } } 按块访问数组元素 —————————————————————————————————————————————————————————————————— 所有这些都是cv::Mat 类的成员函数,并返回调用它们的数组的子部分。 这些方法中最简单的是row() 和col() ,它接受一个整数并返回我们正在调用其成员的数组的指示行或列。 显然,这些只对二维数组有意义; 我们将暂时处理更复杂的案例。 您使用m.row() 或m.col() (对于某些数组m )或我们将要讨论的任何其他函数时,重要的是要理解数据中的数据m 不会复制到新数组中。考虑像这样的表达式m2 = m.row(3) 。此表达式意味着创建一个新的数组头m2 ,并安排其data 指针,step 数组等,以便它将访问行3 中的数据m 。 如果修改了数据m2 ,则将修改数据m 。之后,我们将访问copyTo() 方法,实际上将复制数据。 在OpenCV中处理此方法的主要优点是, 创建访问现有阵列的一部分的新阵列所需的时间不仅非常小,而且与旧阵列或新阵列的大小无关。 与此密切相关row() ,并col() 有rowRange() 和colRange() 。 除了它们将提取具有多个连续行(或列)的数组之外,这些函数与它们更简单的表兄弟基本上完全相同。 您可以通过指定整数开始和结束行(或列),或通过传递cv::Range 指示所需行(或列)的对象, 以两种方式之一调用这两个函数。在双整数方法的情况下, 范围包括起始索引但不包括结束索引(您可能记得cv::Range 使用类似的约定)。 成员函数diag() 与row() or 的作用相同col(),只是从m.diag()引用返回的数组返回矩阵的对角元素。 m.diag() 期望一个整数参数,指示要提取哪个对角线。如果该参数为零,则它将是主对角线。 如果它是正的,它将从主对角线偏移到阵列上半部分的距离。如果它是负数,那么它将来自数组的下半部分。 提取子矩阵的最后一种方法是使用operator() 。 使用此运算符,您可以传递一对范围(一个cv::Range 用于行,一个cv::Range 用于列) 或一个cv::Rect 用于指定所需的区域。这是唯一允许您从更高维数组中提取子体积的访问方法。 在这种情况下,需要指向C样式范围数组的指针,并且该数组必须具有与数组维数一样多的元素。 块访问cv::Mat的方法 —————————————————————————————————————————————————————————————————— 例 | 描述 —————————————————————————————————————————————————————————————————— m.rows(i) ; | 对应于行i的数组m —————————————————————————————————————————————————————————————————— m.col(j) ; | 对应于列j的数组m —————————————————————————————————————————————————————————————————— m.ranRange(i0,i1) | 对应于行阵列i0通过i1-1矩阵的m —————————————————————————————————————————————————————————————————— m.rowRange(cv::Range(i0,i1)) | 对应于行阵列i0通过i1-1矩阵的m —————————————————————————————————————————————————————————————————— m.colRange(j0,j1) | 列相对应的阵列j0通过j1-1矩阵的m —————————————————————————————————————————————————————————————————— m.colRange(cv::Range(j0,j1)) | 列相对应的阵列j0通过j1-1矩阵的m —————————————————————————————————————————————————————————————————— m.diag(d) | 对应于d矩阵的偏移对角线的数组m —————————————————————————————————————————————————————————————————— m(cv::Range(i0,i1),cv::Range(j0,j1)) ; | 对应于矩阵的子矩形的数组m |其中一个角在i0,j0(i1-1,j1-1)处的对角 —————————————————————————————————————————————————————————————————— m(cv::Rect(i0,i1,w,h)) ; | 对应于矩阵的子矩形的数组m,其中一个角 |在i0,j0(i0+w-1,j0+h-1)处的对角 —————————————————————————————————————————————————————————————————— m(范围) ; | 从m对应于字体积中提取的数组,该子体积是给定的范围的交集 | ranges[0]-ranges[ndims-1] ; —————————————————————————————————————————————————————————————————— 矩阵表达式:代数和 cv::Mat —————————————————————————————————————————————————————————————————— 迁移到C ++所启用的功能之一是 重载运算符和创建代数表达式的能力 由矩阵阵列8 和单体组成。 这样做的主要优点是代码清晰度,因为许多操作可以组合成一个更紧凑且通常更有意义的表达式。 在后台,OpenCV数组类的许多重要功能正被用于使这些操作工作。 例如,根据需要自动创建矩阵标头,并根据需要(仅)分配工作空间数据区域。 当不再需要时,数据区域将被无形地自动解除分配。最终将计算结果放在目标数组中operator=() 。 然而,一个重要的区别是这种形式operator=()不是分配a cv::Mat 或a cv::Mat (如它可能出现的那样), 而是分配a cv::MatExpr (表达式本身9 )到a 。这种区别很重要,因为数据总是被复制到结果(左手)数组中。 回想一下 cv::Matm2=m1 是合法的,它意味着略有不同。在后一种情况下,m2 将是对数据的另一个引用m1。 相比之下,m2=m1+m0 意味着不同的东西。因为m1+m0 是矩阵表达式 , 所以将对其进行求值,并在其中指定指向结果的指针m2 。结果将驻留在新分配的数据区域中。 列出了可用的代数运算示例。请注意,除了简单代数之外, 还有比较运算符,用于构造矩阵的运算符(例如cv::Mat::eye() ,我们之前遇到过的运算符), 以及用于计算转置和反转的高级运算。 这里的关键思想是, 您应该能够采用在进行计算机视觉时出现的各种相对重要的矩阵表达式,并以清晰简洁的方式在单行上表达它们。 可用于矩阵表达式的操作 —————————————————————————————————————————————————————————————————— 例 | 描述 —————————————————————————————————————————————————————————————————— m0 + m1, m0 - m1 ; | 矩阵的加法或减法 —————————————————————————————————————————————————————————————————— m0 + s ; | m0 - s ; | 矩阵和单例之间的加法或减法 s - m1 ; | —————————————————————————————————————————————————————————————————— -MO | 矩阵的否定 —————————————————————————————————————————————————————————————————— s * m0; m0 * s ; | 通过单例缩放矩阵 —————————————————————————————————————————————————————————————————— m0.mul(ml) ; | 每元素乘法m0和m1,每个元素的划分m0通过m1 M0 / M1 ; | —————————————————————————————————————————————————————————————————— m0 * m1 ; | 矩阵乘法m0和m1 —————————————————————————————————————————————————————————————————— m0.inv(方法) ; | 矩阵求逆m0 (方法默认值是: DECOMP_LU) —————————————————————————————————————————————————————————————————— m0.t() ; | 矩阵转置m0(无副本完成) —————————————————————————————————————————————————————————————————— M0>M1; M0>=M1; | M0==M1; M0 <= M1; | 每个元素比较,返回uchar带元素0或255的矩阵 M0 < M1 ; | —————————————————————————————————————————————————————————————————— M0&M1; M0 | M1; | M0 ^ M1; 〜M0; | M0和S; S&M0; M0 | S; | 每个元素在两个矩阵或者矩阵和单个元素之间的最大值 S | M0; M0 ^ S; | 和最小值 s^ M0; | —————————————————————————————————————————————————————————————————— min(m0,m1); max(m0,m1); | 每个元素在两个矩阵或矩阵和单个元素之间的最小值 min(m0,s); min(s,m0); | 和最大值。 max(m0,s); max(s,m0); | —————————————————————————————————————————————————————————————————— cv::abs(m0) ; | 每个元素的绝对值 —————————————————————————————————————————————————————————————————— mo.cross(m1); | 矢量十字和点积分 (矢量十字产品仅仅定位为3*1矩阵) m0.dot(m1) ; | —————————————————————————————————————————————————————————————————— cv::Mat::eye(Nr,Nc,type) ; | 返回类型的固定Nc*Nr矩阵的类静态矩阵初始值 cv::Mat::zeros(Nr,Nc,type) ; | 设定项。 cv::Mat::ones(Nr,Nc,type) ; | —————————————————————————————————————————————————————————————————— 饱和度铸造 在OpenCV中,您经常会进行一些操作,这些操作可能会溢出或下溢某些计算目标中的可用值。 当您对涉及减法的无符号类型进行操作时,这种情况尤为常见,但它可以在任何地方发生。 为了解决这个问题,OpenCV依赖于一个名为饱和度转换的构造 。 这意味着OpenCV算法和其他作用于数组的操作将自动检查下溢和溢出; 在这些情况下,库函数将分别用最低或最高可用值替换操作的结果值。请注意,这不是C语言操作通常和本机操作。 OpenCV提供了一些方便的模板 铸造操作员,使您轻松。 它们被实现为一个名为的模板函数cv::saturate_cast<>() ,它允许您指定要将参数强制转换为的类型。 [例]uchar&Vxy = m0.at <uchar>(y,x); Vxy = cv :: saturate_cast <uchar>((Vxy-128)* 2 + 128);} 数组可以做的更多事情 —————————————————————————————————————————————————————————————————— 例 | 描述 —————————————————————————————————————————————————————————————————— m1 = m0.clone() ; | m0复制所有数据元素完整副本; 克隆阵列将是连续的 —————————————————————————————————————————————————————————————————— m0.copyTo(m1) ; | 将m0的内容复制到m1,必要时重新分配m1(相当于m1=m0.clone()) —————————————————————————————————————————————————————————————————— m0.copyTo(m1,mask) ; | 与m0.copyto(m1)相同,只复制数组掩码中指示的条目 —————————————————————————————————————————————————————————————————— m0.convertTo(m1,type,scale,offset) ; | 将m0的元素转换为类型(例如,cv f), 并在按比例缩放(默认为1.0)和添加偏移量(默认为0.0)后写入m1。 —————————————————————————————————————————————————————————————————— m0.assignTo(m1,type) | 仅限内部使用(类似convertTo ) —————————————————————————————————————————————————————————————————— m0.setTo(s,mask) ; | 将所有条目设置m0 为单例值s ; 如果存在mask,则仅设置与非零元素对应的值mask —————————————————————————————————————————————————————————————————— m0.reshape(chan,rows) ; | 改变二维矩阵的有效形状; chan 或者rows 可能为零,这意味着“没有变化”; 数据不会被复制 —————————————————————————————————————————————————————————————————— m0.push_back(s) ; | 扩展m*1矩阵并s在末尾插入单例 —————————————————————————————————————————————————————————————————— m0.push_back(m1) ; | 按行扩展m*并复制到这些行中,必须是 ×nkm1m1kn —————————————————————————————————————————————————————————————————— m0.pop_back(n) ; | n从mx的末尾删除行n(默认值为n is 1) ; —————————————————————————————————————————————————————————————————— m0.locateROI(size,offset) ; | 写的全尺寸m0 来cv::Size size ; 如果m0 是较大矩阵的“视图”,则将起始角的位置写入Point& offset —————————————————————————————————————————————————————————————————— m0.adjustROI(t,b,l,r) ; | 按t 上方b 像素,下方l 像素,左侧r 像素和右侧像素增加视图大小 —————————————————————————————————————————————————————————————————— m0.total() ; | 计算数组元素的总数(不包括通道) —————————————————————————————————————————————————————————————————— m0.isContinuous() ; | true仅仅m0在内存中包含行之间没有空格的情况下返回 —————————————————————————————————————————————————————————————————— m0.elemSize() ; | 返回m0以字节为单位的元素的大小(例如:三通道浮点矩阵将返回12个字节) —————————————————————————————————————————————————————————————————— m0.elemSize1() ; | 返回m0以字节为单位的元素的大小(例如:三通道浮点矩阵将返回12个字节) —————————————————————————————————————————————————————————————————— m0.type() ; | 返回元素的有效类型标识符(例如:CV_32FC3) —————————————————————————————————————————————————————————————————— m0.depth() ; | 返回m0(例如:CV_32F)个别通道的有效类型标识符 —————————————————————————————————————————————————————————————————— m0.channels() ; | 返回元素中的通道数m0 ; —————————————————————————————————————————————————————————————————— m0.size() ; | 返回m0作为cv::size对象的大小 —————————————————————————————————————————————————————————————————— m0.empty() ; | true仅在数组没有元素时返回(即,m0.total==0 或 m0.data==NULL) —————————————————————————————————————————————————————————————————— cv::SparseMat class :稀疏数组 使用cv::SparseMat 该类 当一个数组是与非零条目的数量相比可能非常大。 这种情况经常出现在具有稀疏矩阵的线性代数中,但是当人们希望在更高维数组中表示数据(特别是直方图)时也会出现这种情况,因为在许多实际应用中大多数空间都是空的。稀疏表示仅存储实际存在的数据,因此可以节省大量内存。在实践中,许多稀疏对象太大而无法以密集格式表示。稀疏表示的缺点是使用它们的计算较慢(基于每个元素)。最后一点很重要, 因为使用稀疏矩阵的计算并不是明确地较慢,因为可以预先知道许多操作根本不需要进行。 OpenCV稀疏矩阵cv::SparseMat 类cv::Mat 在大多数方面类似于密集矩阵类。 它的定义类似,支持大多数相同的操作,并且可以包含相同的数据类型。在内部,数据的组织方式完全不同。 虽然cv::Mat 使用与C数据阵列紧密相关的数据数组(其中数据按顺序打包并且地址可直接从元素的索引计算), 但cv::SparseMat 使用哈希表仅存储非零元素。该哈希表是自动维护的, 因此当数组中的(非零)元素数量变得太大而无法进行有效查找时,表会自动增长。 访问稀疏数组元素 稀疏数组和密集数组之间最重要的区别是如何访问元素。 稀疏数组提供四种不同的访问机制: 1. cv::SparseMat::ptr() 2. cv::SparseMat::ref() 3. cv::SparseMat::value() 4. cv::SparseMat::find() 该cv::SparseMat::ptr() 方法有几个 变体,其中最简单的有模板: uchar * cv :: SparseMat :: ptr(int i0,bool createMissing,size_t * hashval = 0); 此特定版本用于访问一维数组。第一个参数i0 是请求元素的索引。 下一个参数,createMissing 表示如果元素尚未存在于数组中,是否应该创建该元素。 当cv::SparseMat::ptr() 被调用时,它会返回一个指向该元素如果元素的数组中已经定义, 但NULL 如果该元素没有定义。但是,如果createMissing 参数是true ,则将创建该元素, 并将有效的非NULL 指针返回给该新元素。要理解最终的论证, hashval 有必要回顾一下基础数据的表示cv::SparseMat 是一个哈希表。 在哈希表中查找对象需要两个步骤:首先,计算哈希键(在这种情况下,从索引), 然后,搜索与该键相关联的列表。通常,该列表将很短(理想情况下只有一个元素), 因此查找中的主要计算成本是散列键的计算。如果已经计算了这个密钥(如下cv::SparseMat::hash() 所述, 将会覆盖),那么可以通过不重新计算来保存时间。在这种情况下cv::SparseMat::ptr() , 如果参数hashval 保留其默认参数NULL ,则将计算散列键。 但是,如果提供了密钥,则将使用它。 还有一些变体cv::SparseMat::ptr() 允许两个或三个索引,以及一个版本, 其第一个参数是一个指向整数数组的指针(即const int* idx ),它需要具有与数组维度相同的条目数被访问。 在所有情况下,该函数都cv::SparseMat::ptr() 返回一个指向无符号字符(即uchar* )的指针, 该字符通常需要重新转换为数组的正确类型。 访问器模板函数SparseMat::ref<>() 用于返回对数组的特定元素的引用。 此函数SparseMat::ptr() 可以采用一个,两个或三个索引,或指向索引数组的指针, 还支持指向要在查找中使用的哈希值的可选指针。因为它是模板函数,所以必须指定被引用的对象的类型。 因此,例如,如果您的数组是类型CV_32F ,那么您可能会这样调用SparseMat::ref<>() : a_sparse_mat.ref <float>(i0,i1)+ = 1.0f; 模板方法 与之cv::SparseMat::value<>() 相同SparseMat::ref<>(), 只是它返回值而不是对值的引用。因此,该方法本身是一个“ const 方法”。 最终的访问器函数cv::SparseMat::find<>()与cv::SparseMat::ref<>()and的工作方式类似 cv::SparseMat::value<>(),但返回指向所请求对象的指针。cv::SparseMat::ptr() 但是,与此指针的模板实例化指定的类型不同,cv::SparseMat::find<>() 因此不需要重新设置。 出于代码清晰的目的,cv::SparseMat::find<>() 优先于cv::SparseMat::ptr() 可能的地方。cv::SparseMat::find<>() 但是,它是一个const 方法并返回一个const 指针,因此这两个并不总是可以互换的。 [例]打印稀疏数组的所有非零元素 —————————————————————————————————————————————————————————————————— // 使用一些非零元素创建一个10*10的稀疏矩阵 int size[] = {10,10} ; cv::SparseMat sm(2,size,CV_32F) ; for (int i = 0; i < 10; i++) { //填充数组 int idx[2] ; idx[0] = size[0] * rand() ; idx[1] = size[1] * rand() ; sm.ref<float>(idx) += 1.0f ; } //打印出非零元素 cv::SparseMatConstIterator_<float> it = sm.begin<float>() ; cv::SparseMatConstIterator_<float> it_end = sm.end<flaot>() ; for ( ; it != it_end; ++it) { const cv::SparseMat::Node * node = it.node() ; printf("(%3d,%3d)%f\n",node->idx[0],node->idx[1],*it) ; } node() 返回指向迭代器指示的稀疏矩阵中的内部数据节点的指针。 返回的对象类型cv::SparseMat::Node具有以下定义: struct Node{ size_t hashval ; size_t next ; int idx[cv::MAX_DIM] ; }; 稀疏数组的独特功能 —————————————————————————————————————————————————————————————————— 例 | 描述 —————————————————————————————————————————————————————————————————— cv::SparseMat sm ; | 无需初始化即可创建稀疏矩阵 —————————————————————————————————————————————————————————————————— cv::SparseMat sm(3,sz,CV_32F) ; | 创建一个三维稀疏矩阵,其尺寸由sz类型数组给出float —————————————————————————————————————————————————————————————————— cv::SparseMat sm(sm0) ; | 创建一个新的稀疏矩阵,它是稀疏矩阵的副本sm0 —————————————————————————————————————————————————————————————————— cv::SparseMat(m0.tryld) ; | 从现有的密集矩阵创建稀疏矩阵m0; 如果bool tryld是true,m0如果密集矩阵是n*1或1*,则转换为一维稀疏矩阵n —————————————————————————————————————————————————————————————————— cv::SparseMat(&old_sparse_mat) ; | 从指指针创建一个新的稀疏矩阵稀疏矩阵的类型CvSparseMat —————————————————————————————————————————————————————————————————— CvSparseMat * old_sm = | Cast运算符创建指稀疏矩阵的指针; (cv :: SparseMat *)sm; | CvSparseMat 创建该对象并将所有数据复制到其中,然后返回其指针 —————————————————————————————————————————————————————————————————— size_t n = sm.nzcount() ; | 返回非零元素的数量sm —————————————————————————————————————————————————————————————————— size_t h = sm.hash(i0) ; | i0 在一维稀疏矩阵中返回元素的哈希值; size_t h = sm.hash(i0,i1) ; | 0 ,i1 在二维稀疏矩阵中 size_t h = sm.hash(i0,i1,i2) ; | i0 ,i1 ,i2 在三维稀疏矩阵; size_t h = sm.hash(idx) ; | 或由整数数组中指示的元件idx 在Ñ 维的稀疏矩阵 —————————————————————————————————————————————————————————————————— sm.ref<float>(i0) = f0 ; | 将值分配 给一维稀疏矩阵中的f0 元素i0; sm.ref<float>(i0,i1) = f0 ; | i0 ,i1 在二维稀疏矩阵中; sm.ref<float>(i0,i1,i2) = f0 ; | i0 ,i1 ,i2 在三维稀疏矩阵; sm.ref<float>(idx) = f0 ; | 或由整数数组中指示的元件idx 在Ñ 维的稀疏矩阵 —————————————————————————————————————————————————————————————————— f0 = sm.value<float>(i0) ; | 在一维稀疏矩阵中f0 为元素赋值i0; f0 = sm.value<float>(i0,i1) ; | i0 ,i1 在二维稀疏矩阵中; f0 = sm.value<float>(i0,i1,i2) ; | i0 ,i1 ,i2 在三维稀疏矩阵; 或由整数数组中指示的元件idx 在Ñ 维的稀疏矩阵 —————————————————————————————————————————————————————————————————— sm.ref<float>(i0) = f0 ; | 将值分配 给一维稀疏矩阵中的f0 元素i0; sm.ref<float>(i0,i1) = f0 ; | i0 ,i1 在二维稀疏矩阵中; sm.ref<float>(i0,i2,i2) = f0 ; | i0 ,i1 ,i2 在三维稀疏矩阵; sm.ref<float>(idx) = f0 ; | 或由整数数组中指示的元件idx 在Ñ 维的稀疏矩阵 —————————————————————————————————————————————————————————————————— f0 = sm.value<float>(i0) ; | 在一维稀疏矩阵中f0 为元素赋值i0; f0 = sm.value<float>(i0,i1) ; | i0 ,i1 在二维稀疏矩阵中; f0 = sm.value<float>(i0,i1,i2) ; | i0 ,i1 ,i2 在三维稀疏矩阵; f0 = sm.value<float>(idx) ; | 或由整数数组中指示的元件idx 在Ñ 维的稀疏矩阵 —————————————————————————————————————————————————————————————————— p0 = sm.find<float>(i0) ; | 分配给 一维稀疏矩阵中p0 的元素的地址i0; p0 = sm.find<float>(i0,i1) ; | i0 ,i1 在二维稀疏矩阵中; p0 = sm.find<float>(i0,i1,i2) ; | i0 ,i1 ,i2 在三维稀疏矩阵; p0 = sum.find<float>(idx) ; | 或由整数数组中指示的元件idx 在Ñ 维的稀疏矩阵 —————————————————————————————————————————————————————————————————— sm.erase(i0,i1,&hashval) ; | 删除 二维稀疏矩阵中的(i0 ,i1)元素; sm.erase(i0,i1,i2,&hashval) ; | 在(i0 ,i1 ,i2 )在三维稀疏矩阵; sm.erase(idx,&hashval) ; | 或元件由整数数组表示idx 在Ñ 维的稀疏矩阵。 如果hashval 不是NULL ,请使用提供的值而不是计算它 —————————————————————————————————————————————————————————————————— cv::SparseMatIterator<float> it = sm.begin<float>() | 创建一个稀疏矩阵迭代器it 并将其指向浮点数组的第一个值sm —————————————————————————————————————————————————————————————————— cv::SparseMatIterator<uchar>it_end = sm.end<uchar>(); | 创建一个稀疏矩阵迭代器it_end 并将其初始化为字节数组中最后一个值之后的值sm —————————————————————————————————————————————————————————————————— 使用模板形式的目的cv::Mat_<>,并cv::SparseMat_<>都让你不必使用他们的成员函数模板的形式。 考虑一个例子,我们有一个矩阵定义: cv::Mat m(10,10,CV_32FC2) ; 对于此矩阵的单个元素访问需要指定矩阵的类型,如下所示: m.at<Vec2f>(i0,i1) = cv::Vec2f(x,y) ; 或者,如果您已经使用template类定义了矩阵m, 那么您可以使用at()而不进行专门化,甚至只使用operator(): cv::Mat_<Vec2f>m(10,10) ; m.at(i0,i1) = cv::Vec2f(x,y) ; //or.. m( i0, i1 ) = cv::Vec2f( x, y ); !!使用这些模板定义会导致代码大量简化 声明矩阵的这两种方式及其相关.at 方法在效率上是等效的。但是,第二种方法被认为更“正确”, 因为它允许编译器在m 传递到需要某种类型矩阵的函数时检测类型不匹配。 如果:cv :: Mat m(10,10,CV_32FC2); 被传递到:void foo((cv :: Mat_ <char> *)myMat); 在运行期间可能会以非显而易见的方式发生故障。如果您改为使用: cv :: Mat_ <Vec2f> m(10,10);在编译时会检测到失败。 模板表单可用于创建在特定类型的数组上运行的模板函数。我们创建了一个小的稀疏矩阵, 然后打印出其非零元素。我们可能会尝试编写一个函数来实现如下: void print_matrix(const cv::SparseMat * sm) { cv::SparseMatConstIterator_<float> it = sm.begin<float>() ; cv::SparseMatConstIterator_<float> it_end = sm.end<float>() ; for (; it != it_end; ++it) { const cv::SparseMat::Node * node = it.node() ; printf("(%3d,%3d)%f\n",node->idx[0],node->idx[1],*it) ; } } [例]打印矩阵的更好方法 —————————————————————————————————————————————————————————————————— template <class T> void print_matrix(const cv::SparseMat_ <T> * sm) { cv::SparseMatConstIterator_<T> it = sm->begin() ; cv::SparseMatConstIterator_<t> it_end = sm->end() ; for (; it != it_end; ++it) { const typename cv::SparseMat_<T>::Node * node = it.node() ; cout << "(" << node->idx[0] << "," << node->idx[1] << ")=" << *it << endl ; } } void calling_function1(void){ cv::SparseMat_<float> sm(ndim, size) ; print_matrix<float>(&sm) ; } void calling_function2(void){ cv::SparseMat sm(ndim, size, CV_32F) ; print_matrix<float>((cv::SparseMat_<float>*) &sm) ; }
本文摘录自<学习opencv>
Talk is cheap. Show me the code