getMat 、 InputArray 、OutputArray、 CV_Assert 、 create、_tchar、wchar_t、char、typename、类的静态数据成员、Mat

(1)
/**
If you want to design your own function or a class method that can operate of arrays of multiple types,you can use InputArray ( or OutArray) for the respective parameters.
Inside a function you should use _InputArray::getMat() method to construct a matrix header for the array(without coping data).
**/
getMat()是一种获取矩阵的Mat的常用方法,不用额外的复制矩阵的数据。

看过OpenCV源代码的朋友,肯定都知道很多函数的接口都是InputArray或者OutputArray型的,这个接口类还是很强大的,今个就来说说它们的那些事。

InputArray这个接口类可以是Mat、Mat_<T>、Mat_<T, m, n>、vector<T>、vector<vector<T>>、vector<Mat>。也就意味着当你看refman或者源代码时,如果看见函数的参数类型是InputArray型时,把上诉几种类型作为参数都是可以的。

有时候InputArray输入的矩阵是个空参数,你只需要用cv::noArray()作为参数即可,或者很多代码里都用cv::Mat()作为空参。

这个类只能作为函数的形参参数使用,不要试图声明一个InputArray类型的变量

如果在你自己编写的函数中形参也想用InputArray,可以传递多类型的参数,在函数的内部可以使用_InputArray::getMat()函数将传入的参数转换为Mat的结构,方便你函数内的操作;必要的时候,可能还需要_InputArray::kind()用来区分Mat结构或者vector<>结构,但通常是不需要的。例如:

[cpp]  view plain  copy
  1. void myAffineTransform(InputArray _src, OutputArray _dst, InputArray _m)  
  2. {  
  3.   
  4.     Mat src = _src.getMat(), m = _m.getMat();  
  5.     CV_Assert( src.type() == CV_32FC2 && m.type() == CV_32F && m.size() == Size(3, 2) );  
  6.     _dst.create(src.size(), src.type());  
  7.     Mat dst = _dst.getMat();  
  8.     forint i = 0; i < src.rows; i++ )  
  9.         forint j = 0; j < src.cols; j++ )  
  10.         {  
  11.             Point2f pt = src.at<Point2f>(i, j);  
  12.             dst.at<Point2f>(i, j) = Point2f(m.at<float>(0, 0) * pt.x +  m.at<float>(0, 1) *   pt.y + m.at<float>(0, 2);  
  13.         }  
  14. }  

至于有的源代码里使用 InputArrayOfArrays 作为形参,不用慌张,其实它和InputArray是一样一样一样的。

OutputArray是InputArray的派生类。使用时需要注意的问题和InputArray一样。和InputArray不同的是,需要注意在使用_OutputArray::getMat()之前一定要调用_OutputArray::create()为矩阵分配空间。可以用_OutputArray::needed()来检测输出的矩阵是否需要被计算。有时候传进去的参不是空就不需要计算

还有就是OutputArrayOfArrays、InputOutputArray、InputOutputArrayOfArrays都是OutputArray的别名而已

Mat src = _src.getMat(), map1 = _map1.getMat(), map2 = _map2.getMat();

    CV_Assert( map1.size().area() > 0 );
    CV_Assert( !map2.data || (map2.size() == map1.size()));

    _dst.create( map1.size(), src.type() );
    Mat dst = _dst.getMat();

(2)

目前 学习opencv查看源代码时发现CV_Assert(src.rows ++ 3 && src.cols ==3),其实CV_Assert()函数与C++标准库中的assert()函数功能基本相同。

CV_Assert()作用:CV_Assert()若括号中的表达式值为false,则返回一个错误信息。

ssert 宏的原型定义在<assert.h>中, 其作用是如果它的条件返回错误, 则终止程序 执行,原型定义:
#include <assert.h> 
void assert( int expression );

assert 的作用是现计算表达式 expression ,如果其值为假(即为 0),那么它先向 stderr 打印一条出错信息,然后通过调用 abort 来终止程序运行。请看下面的程序清单:

[cpp]  view plain  copy
  1. #include <stdio.h>   
  2. #include <assert.h>   
  3. #include <stdlib.h>   
  4. int main( void ) {  
  5.  FILE *fp;  
  6. fp = fopen( "test.txt""w" );//以可写的方式打开一个文件,如果不存在就创建一个同 名文件   
  7. assert( fp );   
  8. fclose( fp ); //所以这里不会出错  
  9.   
  10. fp = fopen( "noexitfile.txt""r" );//以只读的方式打开一个文件,如果不存在就打 开文件失败   
  11. assert( fp );  
  12.  fclose( fp );   
  13. return 0; } //所以这里出错 //程序永远都执行不到这里来  
使用 assert()的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开 销。在调试结束后,可以通过在包含#include <assert.h>的语句之前插入 #define NDEBUG 来禁用 assert 调用,示例代码如下:

#include <stdio.h> #define NDEBUG #include <assert.h>

(3)

//! allocates new matrix data unless the matrix already has specified size and type.
    // previous data is unreferenced if needed.
    void create(int rows, int cols, int type);
    void create(Size size, int type);
    void create(int ndims, const int* sizes, int type);


inline Mat Mat::clone() const
{
    Mat m;
    copyTo(m);
    return m;
}
//! returns deep copy of the matrix, i.e. the data is copied
    Mat clone() const;
    //! copies the matrix content to "m".
    // It calls m.create(this->size(), this->type()).
    void copyTo( OutputArray m ) const;
    //! copies those matrix elements to "m" that are marked with non-zero mask elements.
    void copyTo( OutputArray m, InputArray mask ) const;
    //! converts matrix to another datatype with optional scalng. See cvConvertScale.
    void convertTo( OutputArray m, int rtype, double alpha=1, double beta=0 ) const;
()

(4)

为了搞清这些函数,就必须理请几种字符类型的写法。char就不用说了,先说一些wchar_t。wchar_t是Unicode字符的数据类型,它实际定义在<string.h>里:
 typedef unsigned short wchar_t;
不能使用类似strcpy这样的ANSI C字符串函数来处理wchar_t字符串,必须使用wcs前缀的函数,例如wcscpy。为了让编译器识别Unicode字符串,必须以在前面加一个“L”,例如:
 wchar_t *szTest=L"This is a Unicode string.";

 下面在看看TCHAR。如果你希望同时为ANSI和Unicode编译的源代码,那就要include TChar.h。TCHAR是定义在其中的一个宏,它视你是否定义了_UNICODE宏而定义成char或者wchar_t。如果你使用了TCHAR,那么就不应该使用ANSI的strXXX函数或者Unicode的wcsXXX函数了,而必须使用TChar.h中定义的_tcsXXX函数。另外,为了解决刚才提到带“L”的问题,TChar.h中定义了一个宏:“_TEXT”。

 以strcpy函数为例子,总结一下:
 .如果你想使用ANSI字符串,那么请使用这一套写法:
 char szString[100];
 strcpy(szString,"test");
 .如果你想使用Unicode字符串,那么请使用这一套:
 wchar_t szString[100];
 wcscpyszString,L"test");
 .如果你想通过定义_UNICODE宏,而编译ANSI或者Unicode字符串代码:
 TCHAR szString[100];
 _tcscpy(szString,_TEXT("test"));

参考资料:Windows核心编程,Jeffrey Richter


(5)

#ifdef   UNICODE     
          typedef   wchar_t   TCHAR;    

#else     
          typedef   unsigned   char   TCHAR;    

#endif     
typedef   unsigned   char   CHAR;     
typedef   unsigned   wchar_t   WCHAR;   

//由此可以看出,CHAR实施上就是unsigned char,WCHAR为宽字符,而TCHAR根据是否支持unicode而不同。

//在程序使用sizeof(TCAHR),当默认设置时,这个值是1;当定义UNICODE宏时,这个值是2。

//转换函数:
//—————————————————————————–
// Name: DXUtil_ConvertAnsiStringToWide()
// Desc: This is a UNICODE conversion utility to convert a CHAR string into a
//       WCHAR string. cchDestChar defaults -1 which means it 
//       assumes strDest is large enough to store strSource
//—————————————————————————–
VOID DXUtil_ConvertAnsiStringToWide( WCHAR* wstrDestination, const CHAR* strSource, 
                                     int cchDestChar )
{
    if( wstrDestination==NULL || strSource==NULL )
        return;

    if( cchDestChar == -1 )
        cchDestChar = strlen(strSource)+1;

    MultiByteToWideChar( CP_ACP, 0, strSource, -1, 
                         wstrDestination, cchDestChar-1 );

    wstrDestination[cchDestChar-1] = 0;
}

//—————————————————————————–
// Name: DXUtil_ConvertWideStringToAnsi()
// Desc: This is a UNICODE conversion utility to convert a WCHAR string into a
//       CHAR string. cchDestChar defaults -1 which means it 
//       assumes strDest is large enough to store strSource
//—————————————————————————–
VOID DXUtil_ConvertWideStringToAnsi( CHAR* strDestination, const WCHAR* wstrSource, 
                                     int cchDestChar )
{
    if( strDestination==NULL || wstrSource==NULL )
        return;

    if( cchDestChar == -1 )
        cchDestChar = wcslen(wstrSource)+1;

    WideCharToMultiByte( CP_ACP, 0, wstrSource, -1, strDestination, 
                         cchDestChar-1, NULL, NULL );

    strDestination[cchDestChar-1] = 0;
}

//—————————————————————————–
// Name: DXUtil_ConvertGenericStringToAnsi()
// Desc: This is a UNICODE conversion utility to convert a TCHAR string into a
//       CHAR string. cchDestChar defaults -1 which means it 
//       assumes strDest is large enough to store strSource
//—————————————————————————–
VOID DXUtil_ConvertGenericStringToAnsi( CHAR* strDestination, const TCHAR* tstrSource, 
                                        int cchDestChar )
{
    if( strDestination==NULL || tstrSource==NULL )
        return;

#ifdef _UNICODE
    DXUtil_ConvertWideStringToAnsi( strDestination, tstrSource, cchDestChar );
#else
    if( cchDestChar == -1 )
    strcpy( strDestination, tstrSource );
    else
    strncpy( strDestination, tstrSource, cchDestChar );
#endif
}

//—————————————————————————–
// Name: DXUtil_ConvertGenericStringToWide()
// Desc: This is a UNICODE conversion utility to convert a TCHAR string into a
//       WCHAR string. cchDestChar defaults -1 which means it 
//       assumes strDest is large enough to store strSource
//—————————————————————————–
VOID DXUtil_ConvertGenericStringToWide( WCHAR* wstrDestination, const TCHAR* tstrSource, 
                                        int cchDestChar )
{
    if( wstrDestination==NULL || tstrSource==NULL )
        return;

#ifdef _UNICODE
    if( cchDestChar == -1 )
     wcscpy( wstrDestination, tstrSource );
    else
     wcsncpy( wstrDestination, tstrSource, cchDestChar );
#else
    DXUtil_ConvertAnsiStringToWide( wstrDestination, tstrSource, cchDestChar );
#endif
}

//—————————————————————————–
// Name: DXUtil_ConvertAnsiStringToGeneric()
// Desc: This is a UNICODE conversion utility to convert a CHAR string into a
//       TCHAR string. cchDestChar defaults -1 which means it 
//       assumes strDest is large enough to store strSource
//—————————————————————————–
VOID DXUtil_ConvertAnsiStringToGeneric( TCHAR* tstrDestination, const CHAR* strSource, 
                                        int cchDestChar )
{
    if( tstrDestination==NULL || strSource==NULL )
        return;
        
#ifdef _UNICODE
    DXUtil_ConvertAnsiStringToWide( tstrDestination, strSource, cchDestChar );
#else
    if( cchDestChar == -1 )
    strcpy( tstrDestination, strSource );
    else
    strncpy( tstrDestination, strSource, cchDestChar );
#endif
}

//—————————————————————————–
// Name: DXUtil_ConvertAnsiStringToGeneric()
// Desc: This is a UNICODE conversion utility to convert a WCHAR string into a
//       TCHAR string. cchDestChar defaults -1 which means it 
//       assumes strDest is large enough to store strSource
//—————————————————————————–
VOID DXUtil_ConvertWideStringToGeneric( TCHAR* tstrDestination, const WCHAR* wstrSource, 
                                        int cchDestChar )
{
    if( tstrDestination==NULL || wstrSource==NULL )
        return;

#ifdef _UNICODE
    if( cchDestChar == -1 )
     wcscpy( tstrDestination, wstrSource );
    else
     wcsncpy( tstrDestination, wstrSource, cchDestChar );
#else
    DXUtil_ConvertWideStringToAnsi( tstrDestination, wstrSource, cchDestChar );
#endif
}

(6)

typename的常见用法

对于typename这个关键字,如果你熟悉C++的模板,一定会知道它有这样一种最常见的用法(代码摘自C++ Primer):

// implement strcmp-like generic compare function
// returns 0 if the values are equal, 1 if v1 is larger, -1 if v1 is smaller
template <typename T>
int compare(const T &v1, const T &v2)
{
    if (v1 < v2) return -1;
    if (v2 < v1) return 1;
    return 0;
}

也许你会想到上面这段代码中的typename换成class也一样可以,不错!那么这里便有了疑问,这两种方式有区别么?查看C++ Primer之后,发现两者完全一样。那么为什么C++要同时支持这两种方式呢?既然class很早就已经有了,为什么还要引入typename这一关键字呢?问的好,这里面有一段鲜为人知的历史(也许只是我不知道:-))。带着这些疑问,我们开始探寻之旅。

typename的来源

对于一些更早接触C++的朋友,你可能知道,在C++标准还未统一时,很多旧的编译器只支持class,因为那时C++并没有typename关键字。记得我在学习C++时就曾在某本C++书籍上看过类似的注意事项,告诉我们如果使用typename时编译器报错的话,那么换成class即可。

一切归结于历史。

Stroustrup在最初起草模板规范时,他曾考虑到为模板的类型参数引入一个新的关键字,但是这样做很可能会破坏已经写好的很多程序(因为class已经使用了很长一段时间)。但是更重要的原因是,在当时看来,class已完全足够胜任模板的这一需求,因此,为了避免引起不必要的麻烦,他选择了妥协,重用已有的class关键字。所以只到ISO C++标准出来之前,想要指定模板的类型参数只有一种方法,那便是使用class。这也解释了为什么很多旧的编译器只支持class

但是对很多人来说,总是不习惯class,因为从其本来存在的目的来说,是为了区别于语言的内置类型,用于声明一个用户自定义类型。那么对于下面这个模板函数的定义(相对于上例,仅将typename换成了class):

template <class T>
int compare(const T &v1, const T &v2)
{
    if (v1 < v2) return -1;
    if (v2 < v1) return 1;
    return 0;
}

从表面上看起来就好像这个模板的参数应该只支持用户自定义类型,所以使用语言内置类型或者指针来调用该模板函数时总会觉得有一丝奇怪(虽然并没有错误):

int v1 = 1, v2 = 2;
int ret = compare(v1, v2);

int *pv1 = NULL, *pv2 = NULL;
ret = compare(pv1, pv2);

令人感到奇怪的原因是,class在类和模板中表现的意义看起来存在一些不一致,前者针对用户自定义类型,而后者包含了语言内置类型和指针。也正因为如此,人们似乎觉得当时没有引入一个新的关键字可能是一个错误。

这是促使标准委员会引入新关键字的一个因素,但其实还有另外一个更加重要的原因,和文章最开始那行代码相关。

一些关键概念

在我们揭开真实原因的面纱之前,先保持一点神秘感,因为为了更好的理解C++标准,有几个重要的概念需要先行介绍一下。

限定名和非限定名

限定名(qualified name),故名思义,是限定了命名空间的名称。看下面这段代码,coutendl就是限定名:

#include <iostream>

int main()  {
    std::cout << "Hello world!" << std::endl;
}

coutendl前面都有std::,它限定了std这个命名空间,因此称其为限定名。

如果在上面这段代码中,前面用using std::cout;或者using namespace std;,然后使用时只用coutendl,它们的前面不再有空间限定std::,所以此时的coutendl就叫做非限定名(unqualified name)。

依赖名和非依赖名

依赖名(dependent name)是指依赖于模板参数的名称,而非依赖名(non-dependent name)则相反,指不依赖于模板参数的名称。看下面这段代码:

template <class T>
class MyClass {
    int i;
    vector<int> vi;
    vector<int>::iterator vitr;

    T t;
    vector<T> vt;
    vector<T>::iterator viter;
};

因为是内置类型,所以类中前三个定义的类型在声明这个模板类时就已知。然而对于接下来的三行定义,只有在模板实例化时才能知道它们的类型,因为它们都依赖于模板参数T。因此,Tvector<T>vector<T>::iterator称为依赖名。前三个定义叫做非依赖名。

更为复杂一点,如果用了typedef T U; U u;,虽然T没再出现,但是U仍然是依赖名。由此可见,不管是直接还是间接,只要依赖于模板参数,该名称就是依赖名。

类作用域

在类外部访问类中的名称时,可以使用类作用域操作符,形如MyClass::name的调用通常存在三种:静态数据成员、静态成员函数和嵌套类型:

struct MyClass {
    static int A;
    static int B();
    typedef int C;
}

MyClass::AMyClass::BMyClass::C分别对应着上面三种。

引入typename的真实原因

结束以上三个概念的讨论,让我们接着揭开typename的神秘面纱。

一个例子

在Stroustrup起草了最初的模板规范之后,人们更加无忧无虑的使用了class很长一段时间。可是,随着标准化C++工作的到来,人们发现了模板这样一种定义:

template <class T>
void foo() {
    T::iterator * iter;
    // ...
}

这段代码的目的是什么?多数人第一反应可能是:作者想定义一个指针iter,它指向的类型是包含在类作用域T中的iterator。可能存在这样一个包含iterator类型的结构:

struct ContainsAType {
    struct iterator { /*...*/ };
    // ...
};

然后像这样实例化foo

foo<ContainsAType>();

这样一来,iter那行代码就很明显了,它是一个ContainsAType::iterator类型的指针。到目前为止,咱们猜测的一点不错,一切都看起来很美好。

问题浮现

在类作用域一节中,我们介绍了三种名称,由于MyClass已经是一个完整的定义,因此编译期它的类型就可以确定下来,也就是说MyClass::A这些名称对于编译器来说也是已知的。

可是,如果是像T::iterator这样呢?T是模板中的类型参数,它只有等到模板实例化时才会知道是哪种类型,更不用说内部的iterator。通过前面类作用域一节的介绍,我们可以知道,T::iterator实际上可以是以下三种中的任何一种类型:

  • 静态数据成员
  • 静态成员函数
  • 嵌套类型

前面例子中的ContainsAType::iterator是嵌套类型,完全没有问题。可如果是静态数据成员呢?如果实例化foo模板函数的类型是像这样的:

struct ContainsAnotherType {
    static int iterator;
    // ...
};

然后如此实例化foo的类型参数:

foo<ContainsAnotherType>();

那么,T::iterator * iter;被编译器实例化为ContainsAnotherType::iterator * iter;,这是什么?前面是一个静态成员变量而不是类型,那么这便成了一个乘法表达式,只不过iter在这里没有定义,编译器会报错:

error C2065: ‘iter’ : undeclared identifier

但如果iter是一个全局变量,那么这行代码将完全正确,它是表示计算两数相乘的表达式,返回值被抛弃。

同一行代码能以两种完全不同的方式解释,而且在模板实例化之前,完全没有办法来区分它们,这绝对是滋生各种bug的温床。这时C++标准委员会再也忍不住了,与其到实例化时才能知道到底选择哪种方式来解释以上代码,委员会决定引入一个新的关键字,这就是typename

千呼万唤始出来

我们来看看C++标准

A name used in a template declaration or definition and that is dependent on a template-parameter is assumed not to name a type unless the applicable name lookup finds a type name or the name is qualified by the keyword typename.

对于用于模板定义的依赖于模板参数的名称,只有在实例化的参数中存在这个类型名,或者这个名称前使用了typename关键字来修饰,编译器才会将该名称当成是类型。除了以上这两种情况,绝不会被当成是类型。

因此,如果你想直接告诉编译器T::iterator是类型而不是变量,只需用typename修饰:

template <class T>
void foo() {
    typename T::iterator * iter;
    // ...
}

这样编译器就可以确定T::iterator是一个类型,而不再需要等到实例化时期才能确定,因此消除了前面提到的歧义。

不同编译器对错误情况的处理

但是如果仍然用ContainsAnotherType来实例化foo,前者只有一个叫iterator的静态成员变量,而后者需要的是一个类型,结果会怎样?我在Visual C++ 2010和g++ 4.3.4上分别做了实验,结果如下:

Visual C++ 2010仍然报告了和前面一样的错误:

error C2065: ‘iter’ : undeclared identifier

虽然我们已经用关键字typename告诉了编译器iterator应该是一个类型,但是用一个定义了iterator变量的结构来实例化模板时,编译器却选择忽略了此关键字。出现错误只是由于iter没有定义。

再来看看g++如何处理这种情况,它的错误信息如下:

In function ‘void foo() [with T = ContainsAnotherType]’: instantiated from here error: no type named ‘iterator’ in ‘struct ContainsAnotherType’

g++在ContainsAnotherType中没有找到iterator类型,所以直接报错。它并没有尝试以另外一种方式来解释,由此可见,在这点上,g++更加严格,更遵循C++标准。

使用typename的规则

最后这个规则看起来有些复杂,可以参考MSDN

  • typename在下面情况下禁止使用:
    • 模板定义之外,即typename只能用于模板的定义中
    • 非限定类型,比如前面介绍过的intvector<int>之类
    • 基类列表中,比如template <class T> class C1 : T::InnerType不能在T::InnerType前面加typename
    • 构造函数的初始化列表中
  • 如果类型是依赖于模板参数的限定名,那么在它之前必须加typename(除非是基类列表,或者在类的初始化成员列表中)
  • 其它情况下typename是可选的,也就是说对于一个不是依赖名的限定名,该名称是可选的,例如vector<int> vi;

其它例子

对于不会引起歧义的情况,仍然需要在前面加typename,比如:

template <class T>
void foo() {
    typename T::iterator iter;
    // ...
}

不像前面的T::iterator * iter可能会被当成乘法表达式,这里不会引起歧义,但仍需加typename修饰。

再看下面这种:

template <class T>
void foo() {
    typedef typename T::iterator iterator_type;
    // ...
}

是否和文章刚开始的那行令人头皮发麻的代码有些许相似?没错!现在终于可以解开typename之迷了,看到这里,我相信你也一定可以解释那行代码了,我们再看一眼:

typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;

它是将__type_traits<T>这个模板类中的has_trivial_destructor嵌套类型定义一个叫做trivial_destructor的别名,清晰明了。

再看常见用法

既然typename关键字已经存在,而且它也可以用于最常见的指定模板参数,那么为什么不废除class这一用法呢?答案其实也很明显,因为在最终的标准出来之前,所有已存在的书、文章、教学、代码中都是使用的是class,可以想像,如果标准不再支持class,会出现什么情况。

对于指定模板参数这一用法,虽然classtypename都支持,但就个人而言我还是倾向使用typename多一些,因为我始终过不了class表示用户定义类型这道坎。另外,从语义上来说,typenameclass表达的更为清楚。C++ Primer也建议使用typename:

使用关键字typename代替关键字class指定模板类型形参也许更为直观,毕竟,可以使用内置类型(非类类型)作为实际的类型形参,而且,typename更清楚地指明后面的名字是一个类型名。但是,关键字typename是作为标准C++的组成部分加入到C++中的,因此旧的程序更有可能只用关键字class。

(7)

可以通过以下几个例子更形象的说明这个问题:
//test.cpp

#include <stdio.h>
class A {
public:
static int a; //声明但未定义
};
int main() {
printf("%d", A::a);
return 0;
}

编译以上代码会出现“对‘A::a’未定义的引用”错误。这是因为静态成员变量a未定义,也就是还没有分配内存,显然是不可以访问的。
再看如下例子:
//test.cpp

#include <stdio.h>
class A {
public:
static int a; //声明但未定义
};
int A::a = 3; //定义了静态成员变量,同时初始化。也可以写"int A:a;",即不给初值,同样可以通过编译
int main() {
printf("%d", A::a);
return 0;
}

这样就对了,因为给a分配了内存,所以可以访问静态成员变量a了。
因为类中的静态成员变量仅仅是声明,暂时不需分配内存,所以我们甚至可以这样写代码:
//a.cpp

class B; //这里我们使用前置声明,完全不知道B是什么样子
class A {
public:
static B bb;//声明了一个类型为B的静态成员,在这里编译器并未给bb分配内存。
//因为仅仅是声明bb,所以编译器并不需要知道B是什么样子以及要给其对应的对象分配多大的空间。
//所以使用前置声明"class B"就可以保证编译通过。
};
 
  
类的析构函数什么时候会调用?
 
  
在这个对象的作用域最后..

比如你在main里面声明了一个类A..那么~A()会在main结束时调用..
如果在自定义的函数f()里面声明了一个A 函数f结束的时候就会调用~A()

或者你delete 指向A的指针..
或者显式的调用析构函数

(8)Mat

二. 申请一个全1或者全0矩阵

Mat mat=Mat::ones(2,4,CV_64FC1);
Mat mat=Mat::zeros(2,4,CV_64FC1);
  • 1
  • 2

三. 矩阵的点乘和叉乘

double a[] = {1, 2, 3, 4, 5, 6, 7, 8};
double b[] = {8, 7, 6, 5 ,4, 3, 2, 1};
Mat Ma = Mat(2, 4, CV_64FC1, a);
Mat Mb = Mat(2, 4, CV_64FC1, b);
Mat Mc = Ma.mul(Mb);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

点乘

Ma=[15263748]Ma=[12345678]  Mb=[84736251]Mb=[87654321]  Mc=[82014181814208]Mc=[81418202018148]  叉乘
    double a[] = {1, 2, 3, 4, 5, 6, 7, 8};
    double b[] = {8, 7, 6, 5 ,4, 3, 2, 1};
    Mat Ma = Mat(2, 4, CV_64FC1, a);
    Mat Mb = Mat(2, 4, CV_64FC1, b);
    Mat Mb_transpose = Ma.t(); // 对Mb取转置
    Mat Mc = Ma * Mb_transpose ;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Ma=[15263748]Ma=[12345678]  MbMb_ transpose=43218765transpose=[48372615]  Mc=[307070174]Mc=[307070174]

三. 求矩阵的行或列的最大值、最小值、平均值、和

cvReduce( const CvArr* src, CvArr* dst, int dim, int op=CV_REDUCE_SUM); 
src 
输入矩阵 
dst 
输出的通过处理输入矩阵的所有行/列而得到的单行/列向量 
dim 
矩阵被简化后的维数索引.0意味着矩阵被处理成一行,1意味着矩阵被处理成为一列 
op 
简化操作的方式,可以有以下几种取值: 
CV_REDUCE_SUM-输出是矩阵的所有行/列的和. 
CV_REDUCE_AVG-输出是矩阵的所有行/列的平均向量. 
CV_REDUCE_MAX-输出是矩阵的所有行/列的最大值. 
CV_REDUCE_MIN-输出是矩阵的所有行/列的最小值. 

i.求每一列的总和

    double a[] = {1, 5, 6, 3, 2, 6, 7, 2};
    Mat dst;
    Mat Ma = Mat(2, 4, CV_64FC1, a);
    reduce(Ma,dst,0,CV_REDUCE_SUM);
  • 1
  • 2
  • 3
  • 4

Ma=[12566732]Ma=[15632672]  dst=[311135]dst=[311135]

ii.求每一列的总和

    double a[] = {1, 5, 6, 3, 2, 6, 7, 2};
    Mat dst;
    Mat Ma = Mat(2, 4, CV_64FC1, a);
    reduce(Ma,dst,0,CV_REDUCE_AVG);
  • 1
  • 2
  • 3
  • 4

Ma=[12566732]Ma=[15632672]  dst=[1.55.56.52.5]dst=[1.55.56.52.5]

iii.求每一列的总和

    double a[] = {1, 5, 6, 3, 2, 6, 7, 2};
    Mat dst;
    Mat Ma = Mat(2, 4, CV_64FC1, a);
    reduce(Ma,dst,0,CV_REDUCE_SUM);
  • 1
  • 2
  • 3
  • 4

Ma=[12566732]Ma=[15632672]  dst=[1517]dst=[1517]

iv.求每一列的总和

    double a[] = {1, 5, 6, 3, 2, 6, 7, 2};
    Mat dst;
    Mat Ma = Mat(2, 4, CV_64FC1, a);
    reduce(Ma,dst,0,CV_REDUCE_AVG);
  • 1
  • 2
  • 3
  • 4

Ma=[12566732]Ma=[15632672]  dst=[3.754.25]dst=[3.754.25]

四. 矩阵求逆


invert(const CvArr* src, CvArr *dst, CV_SVD); 
//CV_LU : 高斯消去法 (LU 分解) 
//CV_SVD : 奇异值分解(SVD) 
//CV_SVD_SYM : 对称矩阵的SVD

    double a[] = {2, 0, 0, 2};
    Mat dst;
    Mat Ma = Mat(2, 2, CV_64FC1, a);
    invert(Ma, dst, CV_SVD);
  • 1
  • 2
  • 3
  • 4

Ma=[2002]Ma=[2002]  dst=[0.5000.5]


猜你喜欢

转载自blog.csdn.net/weixin_41484240/article/details/80595507
今日推荐