除了基本类型和大型容器之外,还有一系列辅助对象对于控制各种算法(如终止条件)或对容器执行各种操作(如“range”或“切片”)非常重要。还有一个非常重要的对象,智能指针对象cv::Ptr
。
cv::TermCriteria
类
许多算法需要一个停止条件。通常,停止条件要么是有限迭代次数(称为COUNT或MAX_ITER),要么是某种错误参数,基本上就是说,“如果误差都这么小了,可以退出了”(称为EPS
-epsilon
的缩写,代表一个很小的数字)。在许多情况下,最好同时拥有这两种功能,这样,即便算法永远不能“误差足够小”,它仍然会在某个时刻退出。
cv::TermCriteria
对象封装了一个或两个停止标准,以便可以方便地将它们传递给OpenCV算法函数。它们有三个成员变量-type
、maxCount
和epsilon
-可以直接设置(它们是公共的),或者更多情况下,只是由构造函数以TermCri teria( int type, int maxCount, double epsilon )
的形式设置。变量类型设置为cv::TermCriteria::COUNT
或cv::TermCriteria::EPS
。也可以逻辑或将两者连在一起(即“|
”)。cv::TermCriteria::COUNT
和cv::TermCriteria::MAX_ITER
是一样的。如果终止条件包括cv::TermCriteria::COUNT
,算法在maxCount
次迭代之后终止。如果终止标准包括cv::TermCrite ria::EPS
,那么就是在告诉算法在与算法的收敛性相关联的某个度量低于EPS
之后终止。
cv::Range
类
cv::Range
类用于指定连续的整数序列。cv::Range
对象有两个元素:start
和end
,通常使用构造函数cv::Range( int start, int end )
设置。范围包括它们的起始值,但不包括它们的终止值,因此cv::Range rng( 0, 4 )
包括值0、1、2和3,但不包括4。使用size()
可以得到元素数量。在前面的示例中,rng.size()
将等于4。还有一个成员empty()
用于测试范围是否没有元素。最后,静态方法Range::all()
返回一个特殊的变量,表示“整个序列”或“整个范围”,就像Matlab中的“:
”或Python中的“…”。OpenCV 中所有Range的方法和函数都支持这个特殊的Range:: all()
。
比如
void my_function(..., const Range& r, ....)
{
if(r == Range::all()) {
// process all the data
}
else {
// process [r.start, r.end)
}
}
cv::Ptr
模板和垃圾回收机制
C++中一个非常有用的对象类型是“智能”指针。该指针允许我们创建对某个对象的引用,然后将其传递。你可以创建更多对该对象的引用。当引用超出作用域时,智能指针的引用计数会递减。一旦所有的引用(指针的实例)都消失了,内存将被自动清理(释放)。
这就是智能指针的运作方式。首先,为要“包装”的类对象定义指针模板的实例。我们可以使用类似于cv::Ptr<cv::Matx33f> p(new cv::Matx33f);
或cv::Ptr<cv::Matx33f> p = cv::makePtr<cv::Matx33f>();
的调用来完成此操作。模板对象的构造函数接受指向要指向的对象的指针。这样做之后,就有了智能指针p
,它是一种类似指针的对象,可以像普通指针一样传递和使用(即,它支持operator *()
和操作符operator ->()
等)。一旦有了p
,就可以创建相同类型的其他对象,而无需向它们传递指向新对象的指针。例如,您可以创建Ptr<Mat33f> q
,当你将p
的值赋给q
时,智能指针的“智能”操作开始发挥作用。就像通常的指针一样,仍然只有一个实际的cv::Mat33f
对象是p
和q
都指向的。不同之处在于p
和q
都知道它们各自是两个指针中的一个。如果p
消失(例如,在作用域之外),q
就知道它是对原始矩阵对象的唯一剩余引用。如果q
随后消失,并且它的析构函数被调用(隐式地),q
将知道这是剩下的最后一个引用,并且它应该释放原始Mat33f
对象的内存。
cv::Ptr<>
模板类在其接口中支持几个与智能指针的引用计数功能相关的函数。具体地说,函数addref()
和release()
递增和递减指针的内部引用计数器。使用这些函数很危险,但如果您需要自己微观管理引用计数器,则可以使用这些函数。
还有一个名为empty()
的函数,您可以使用它来确定智能指针是否指向已释放的对象。如果对对象调用release()
一次或多次,就可能会发生这种情况。在这种情况下,仍然会有几个个智能指针,但是指向的对象可能已经被销毁了。empty()
还有另一个应用,用于确定智能指针对象内部的内部对象指针是否由于其他原因碰巧为NULL
。例如,如果您通过调用一个一开始可能返回NULL
的函数(cvLoadImage()
、fopen()
等)来分配智能指针,则可能会发生这种情况。
cv::Ptr<>
的最后一个成员函数是delete_obj()
。这是一个在引用计数为零时自动调用的函数。我们可以在实例化cv::Ptr<>
的情况下重载它,该cv::Ptr<>
指向一个类,该类需要一些特定操作才能清理它所指向的类。
比如文件操作:
template<> inline void cv::Ptr<FILE>::delete_obj() {
fclose(obj);
}
然后,继续使用该指针打开文件,对其执行任何操作,然后指针超出作用域时(此时文件句柄将自动关闭)
cv::Exception
类和异常处理
OpenCV定义了自己的异常类型cv::Exception
,它派生自STL异常类std::exception
。实际上,这个异常类型没有什么特别之处,只是它在cv::
命名空间中,因此可以与也从std::exception
派生的其他对象区分开来。
cv::Exception
具有成员code
、err
、func
、file
和line
,它们(分别)是数值错误、指示产生异常的错误的性质的字符串、发生错误的函数的名称、发生错误的文件以及指示该文件中发生错误的行的整数。err
、func
和file
都是STL字符串。
有几个用于自定义异常的内置宏。CV_Error( errorcode, description )
将生成并抛出具有文本描述的异常。CV_Error_( errorcode, printf_fmt_str, [printf-args] )
的作用相同,但允许你用类似printf
的格式字符串和参数替换固定的描述。最后,CV_Assert( condition )
和CV_DbgAssert( condition )
都是测试条件,并在条件不满足时抛出异常。但是,OpenCV之后的版本仅允许在调试版本中运行这些宏。这些宏是抛出异常的首选方法,因为它们会自动为你处理字段func
、file
和line
。
cv::DataType<>
模板
从字面意思来看就是特别定义的数据类型
template<typename _Tp> class DataType
{
typedef _Tp value_type;
typedef value_type work_type;
typedef value_type channel_type;
typedef value_type vec_type;
enum {
generic_type = 1,
depth = -1,
channels = 1,
fmt = 0,
type = CV_MAKETYPE(depth, channels)
};
};
首先,我们可以看到cv::DataType<>
是一个模板,并且实例化到一个名为_tp
的类。然后,它有四个typedef
语句,这些语句在编译时从cv::DataType<>
实例化对象中提取cv::DataType<>
的类型以及一些其他相关类型。在模板定义中,这些都是相同的,我们看来自core.hpp的cv::DataType<>
的两个示例。第一个是float
:
template<> class DataType<float>
{
public:
typedef float value_type;
typedef value_type work_type;
typedef value_type channel_type;
typedef value_type vec_type;
enum {
generic_type = 0,
depth = DataDepth<channel_type>::value,
channels = 1,
fmt = DataDepth<channel_type>::fmt,
type = CV_MAKETYPE(depth, channels)
};
};
在本例中,value_type
是float
,并且work_type
、channel_type
和vec_type
都是相同的。在下一个示例中,我们将更清楚地看到这些是用来做什么的。对于枚举,第一个变量GENERIC_TYPE
被设置为0,因为对于core.hpp中定义的所有类型,它都是零。depth
变量是OpenCV使用的数据类型标识符。例如,cv::DataDepth<float>::value
解析为常量CV_32F
。入口通道是1,因为float
只有一个数字;变量fmt
提供格式的单字符表示形式。在本例中,cv::DataDepth<float>::fmt
解析为f
。最后一项是type
,这是类似于depth
的表示形式,但包括通道数(在本例中为1)。CV_MAKETYPE(CV_32F,1)
解析为CV_32FC1
。
另外一个例子cv::Rect<>
template<typename _Tp> class DataType<Rect_<_Tp> >
{
public:
typedef Rect_<_Tp> value_type;
typedef Rect_<typename DataType<_Tp>::work_type> work_type;
typedef _Tp channel_type;
typedef Vec<channel_type, channels> vec_type;
enum {
generic_type = 0,
depth = DataDepth<channel_type>::value,
channels = 4,
fmt = ((channels-1)<<8) + DataDepth<channel_type>::fmt,
type = CV_MAKETYPE(depth, channels)
};
};
这是一个复杂得多的例子。首先,注意cv::Rect
本身没有出现。因为cv::Rect
实际上是模板的typedef,该模板称为cv::Rect_
。因此该模板可以特化为cv::DataType<Rect>
或例如cv::DataType< Rect_<float>>
。对于这个例子,回想一下所有元素都是整数,所以,模板参数_tp
的所有实例都解析为int
。
我们可以看到,value_type
就是cv::DataType<>
正在描述的东西的编译时名称(即Rect
)。然而,work_type
被定义为cv::DataType<int>
。work_type
告诉我们cv::DataType<>
是由哪种变量组成的。通道类型也是int
。这意味着,如果我们希望将此变量表示为多通道对象,则应将其表示为一定数量的int
对象。最后,channel_type
告诉我们如何将此cv::DataType<>
表示为多通道对象一样,vec_type
告诉我们如何将其表示为cv::Vec<>
类型的对象。cv::DataType<Rect>::vec_type
将解析为cv::Vec<int,4>
。
继续讨论运行常量enum
:generic_type
还是0,depth
为CV_32S
,channels
为4(因为实际上有四个值,与vec_type
实例化为大小为4的cv::Vec<>
的原因相同),fmt
解析为0x3069
(因为i
是0x69
),而type
解析为CV_32SC4
。
cv::InputArray
和 cv::OutputArray
许多OpenCV函数以数组作为参数,返回数组作为返回值,但在OpenCV中,数组的种类很多。我们已经看到,除了大型数组类型(cv::Mat
和cv::SparseMat
)之外,OpenCV还支持一些小数组类型(cv::Scalar
、cv::Vec
、cv::Matx
)和STL的std::vector<>
。为了避免接口变得过于复杂(和重复),OpenCV定义了cv::InputArray
和cv::InputArray
类型。实际上,与库支持的许多数组形式相比,这些类型表示“上述任何类型”。甚至还有一个cv::InputOutputArray
。
cv::InputArray
和cv::InputArray
之间的主要区别在于,前者被假定为常量(即只读)。通常会在库例程的定义中看到这两种类型,而不会自己使用它们,但是在介绍库函数时,它们的存在意味着我们可以使用任何数组类型而不会出错。
与cv::InputArray
相关的是返回cv::InputArray
的特殊函数cv::noArray()
。返回的对象可以传递给任何需要cv::InputArray
的输入,以指示此输入未被使用。某些函数还具有可选的输出数组,当您不需要相应的输出时,可以在其中传递cv::noArray()
。