OpenCV Mat元素类型及其大小

42

我对OpenCV的Mat元素类型感到困惑。以下内容来自文档:

There is a limited fixed set of primitive data types the library can operate on.
That is, array elements should have one of the following types:

8-bit unsigned integer (uchar) 
8-bit signed integer (schar)
16-bit unsigned integer (ushort)
16-bit signed integer (short)
32-bit signed integer (int)
32-bit floating-point number (float)
64-bit floating-point number (double)
...

For these basic types, the following enumeration is applied:
enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 };

众所周知,C++标准并未定义基本类型的字节大小,那么他们是如何使用这些假设的呢?如果以CV_32S为例,我应该期望什么类型,是int32_t还是int?

2
在openCV中,类型非常容易理解。CV_numbitsType。因此,CV_16S是一个有符号的16位整数。 - Ander Biguri
那么为什么文档中使用这样的名称(int,float等),而int可以是64位而不是32位呢? - lizarisk
2
我并不真正了解底层结构,但当您使用类型为CV_32S创建一个矩阵时: Mat myMat(10,10,CV_32S); 您可以通过以下方式访问它: myMat.at<int>(1,1) = 1; - sfotiadis
有没有一种类型的矩阵,可以使用“long”类型来访问矩阵元素? - Ruchir
1
@Ruchir 你应该能够定义一个cv::Mat_<long>。但我怀疑你是否能够对其应用任何OpenCV函数。 - Antonio
显示剩余2条评论
6个回答

9

Miki的回答开始, 在OpenCV 3中,定义已经转移到modules/core/include/opencv2/core/traits.hpp,在这里您可以找到:

/** @brief A helper class for cv::DataType

The class is specialized for each fundamental numerical data type supported by OpenCV. It provides
DataDepth<T>::value constant.
*/
template<typename _Tp> class DataDepth
{
public:
    enum
    {
        value = DataType<_Tp>::depth,
        fmt   = DataType<_Tp>::fmt
    };
};



template<int _depth> class TypeDepth
{
    enum { depth = CV_USRTYPE1 };
    typedef void value_type;
};

template<> class TypeDepth<CV_8U>
{
    enum { depth = CV_8U };
    typedef uchar value_type;
};

template<> class TypeDepth<CV_8S>
{
    enum { depth = CV_8S };
    typedef schar value_type;
};

template<> class TypeDepth<CV_16U>
{
    enum { depth = CV_16U };
    typedef ushort value_type;
};

template<> class TypeDepth<CV_16S>
{
    enum { depth = CV_16S };
    typedef short value_type;
};

template<> class TypeDepth<CV_32S>
{
    enum { depth = CV_32S };
    typedef int value_type;
};

template<> class TypeDepth<CV_32F>
{
    enum { depth = CV_32F };
    typedef float value_type;
};

template<> class TypeDepth<CV_64F>
{
    enum { depth = CV_64F };
    typedef double value_type;
};

在大多数情况下/编译器中,您应该使用 C++ 精确数据类型而不会出现问题。对于单字节数据类型(CV_8U -> uint8_tCV_8U -> int8_t),在 C++ 中有明确定义,因此不会出现问题,详见Stack Overflow 上的解释。同样的道理适用于float(32位)和 double(64位)。然而,如果要完全确定使用正确的数据类型(例如使用 at<> 方法时),则应使用如下方法:
typedef TypeDepth<CV_WHATEVER_YOU_USED_TO_CREATE_YOUR_MAT>::value_type access_type;
myMat.at<access_type>(y,x) = 0;

作为一个旁注,我很惊讶他们决定采用如此模糊的方法,而不是简单地使用精确的数据类型。
因此,关于您最后一个问题:
“让我们说,CV_32S应该期望哪种类型?”
我认为在OpenCV 3中,最精确的答案是:
TypeDepth<CV_32S>::value_type

3
非常感谢您的回答。在大多数情况下,OpenCV不遵循C++标准。虽然它是一款非常出色的计算机视觉库,但我不喜欢用C++处理它,因为它总是破坏我的代码,让我写出让我感到羞愧的代码。 - Humam Helfawi
1
你如何访问value_type?你的例子在OpenCV 3.0中对我无效,因为它是一个成员而不是公共的。 - user3712853
如果我们从数据中获取CV_WHATEVER_YOU_USED_TO_CREATE_YOUR_MAT,但它不是一个常量值,那该怎么办? - Milan Jain
@安东尼奥:但在这种情况下,整个处理过程只能放在switch语句中,这可能非常低效。 - Milan Jain
@MilanJain 你必须确保检查只进行一次或很少进行,这意味着它不能在循环内部,条件必须放在外面,并且循环代码必须针对每种数据类型重复。理想情况下,您应该将循环分离为一个函数模板化,以便您可以尝试包含代码重复。函数特性在这种情况下也可能有用,具体取决于基于数据类型的操作需要多么专业化。 - Antonio
显示剩余3条评论

4
core.hpp 文件中,您可以找到以下内容:
/*!
  A helper class for cv::DataType

  The class is specialized for each fundamental numerical data type supported by OpenCV.
  It provides DataDepth<T>::value constant.
*/
template<typename _Tp> class DataDepth {};

template<> class DataDepth<bool> { public: enum { value = CV_8U, fmt=(int)'u' }; };
template<> class DataDepth<uchar> { public: enum { value = CV_8U, fmt=(int)'u' }; };
template<> class DataDepth<schar> { public: enum { value = CV_8S, fmt=(int)'c' }; };
template<> class DataDepth<char> { public: enum { value = CV_8S, fmt=(int)'c' }; };
template<> class DataDepth<ushort> { public: enum { value = CV_16U, fmt=(int)'w' }; };
template<> class DataDepth<short> { public: enum { value = CV_16S, fmt=(int)'s' }; };
template<> class DataDepth<int> { public: enum { value = CV_32S, fmt=(int)'i' }; };
// this is temporary solution to support 32-bit unsigned integers
template<> class DataDepth<unsigned> { public: enum { value = CV_32S, fmt=(int)'i' }; };
template<> class DataDepth<float> { public: enum { value = CV_32F, fmt=(int)'f' }; };
template<> class DataDepth<double> { public: enum { value = CV_64F, fmt=(int)'d' }; };
template<typename _Tp> class DataDepth<_Tp*> { public: enum { value = CV_USRTYPE1, fmt=(int)'r' }; };

你可以看到,CV_32S是表示类型int的值,而不是int32_t

2

虽然C++没有定义元素的大小,但这个问题是假设的:对于运行OpenCV的系统,这些大小是已知的。鉴于此:

cv::Mat m(32,32,CV_32SC1, cv:Scalar(0));
std::cout << "size of the element in bytes: " << m.depth() << std::endl;
std::cout << "or " << m.step.p[ m.dims-1 ]/m.channels() << std::endl;

那么你如何确定它是int类型?

尝试调用:

int pxVal = m.at<int>(0,0);

将会

CV_DbgAssert( elemSize()==sizeof(int) );

左手边是通过cv::Mat::flags定义的,例如此示例中,预定义深度为CV_32SC1

CV_DbgAssert( m.depth() == sizeof(int) )

或者

CV_DbgAssert( 4 == sizeof(int) )

如果你成功了,那么你只需要关注字节序问题。这个问题在生成cvconfig.h文件时(由CMake完成)已经被检查过了。

简而言之,按照头文件中给定的类型来使用就可以了。


1
m.depth() 不是以字节为单位的,而是返回类型的 EV 枚举值,例如 CV_8U。 - karsten
1
请使用elemSize1()。请注意,elemSize() == elemSize1() * channels()。 - karsten

0

我在OpenCV的代码中发现了几个与CV_8UC1、CV_32SC1等相关的#define。为了使枚举工作,OpenCV添加了额外的代码将纯数字一起转换为参数(即,CV_8UC1、CV_16UC2等都由它们各自的数字表示),并在CvMat的定义中分离深度和通道(我猜Mat在其定义中可能也有类似的代码)。然后,它使用create()来为矩阵分配空间。由于create()是内联的,我只能猜测它类似于malloc()或其他东西。
由于源代码从2.4.9到3.0.0变化很大,所以我需要稍后发布更多证据。请允许我一点时间找到更多信息并编辑我的答案。


0

-3
简而言之,您提供的表格是正确的。 如果要直接访问像素,则将其强制转换为右侧的指定符号,例如CV_32S是带符号的32位。 S始终表示带符号整数(signed char、signed short、signed int) F始终表示浮点数(float、double) U始终表示无符号整数。
枚举仅在创建或转换Mat时使用。这是告诉mat所需类型的一种方式,据我所知,它是在未使用模板之前的C前身。
我仅使用C功能,并且为了创建图像,传递以下内容将是错误的:
cvCreateImage(mySize,char, nChannels);

相反,我传递以下内容:

cvCreateImage(mySize, IPL_DEPTH_8U, nChannels);

在此,IPL_DEPTH_8U 是函数使用的标志。函数本身具有一个开关类型的语句来检查该标志。标志的实际值通常是无意义的,因为它通常由条件语句控制,而不是代数语句。


1
这与问题无关。问题是关于opencv2的,而答案则涉及opencv1。 - mkuse

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接