目标html
咱们有多种方法能够得到从现实世界的数字图像:数码相机、扫描仪、计算机体层摄影或磁共振成像就是其中的几种。在每种状况下咱们(人类)看到了什么是图像。可是,转换图像到咱们的数字设备时咱们的记录是图像的每一个点的数值。python
例如在上图中你能够看到车的镜子只是一个包含全部强度值的像素点矩阵。如今,咱们如何获取和存储像素值可能根据最适合咱们的须要而变化,最终可能减小计算机世界内的全部图像数值矩阵和一些其余的信息的描述基质自己。OpenCV 是一个计算机视觉库,其主要的工做是处理和操做,进一步了解这些信息。所以,你须要学习和开始熟悉它的第一件事是理解OpenCV 是如何存储和处理图像。程序员
Mat算法
OpenCV 自 2001 年出现以来。在那些日子里库是围绕C接口构建的。在那些日子里,他们使用名为IplImage C 的结构在内存中存储图像。这是您将在大多数较旧的教程和教材中看到的那个。使用这个结构的问题是将 C 语言的全部负面效果都摆到了桌面上。最大的问题是手动管理。它是创建在用户来负责处理内存分配和解除分配的假设之上的。当程序规模较小时,这是没有问题的,一旦代码基开始变得愈来愈大它将会愈来愈挣扎着处理全部这一切而不是着眼于实际解决本身的开发目标。express
幸运的是 c + + 出现了,并引入了类的概念,使得为用户开辟另外一条路成为可能:数组
自动内存管理 (或多或少)。好消息是,c + +,若是彻底兼容 C 因此进行更改时没有兼容性问题产生。所以, OpenCV其2.0 版本引入一个新的c + + 接口,经过利用这些优势将为你的工做提供新的方法。某种程度上,在其中您不须要拨弄内存管理让你的代码简洁 (写得更少,实现的更多)。C + + 接口的惟一主要缺点在于,目前许多嵌入式的开发系统支持仅 C.所以,除非您的目标是这一平台,不然就没有理由再使用旧的方法(除非你是个受虐狂程序员和喜欢自讨苦吃)。数据结构
你须要知道的关于Mat的第一件事是你再也不须要手动分配其大小而且当你不须要它的时候你再也不须要手动释放它。虽然这样作仍然是可能的,大多数 OpenCV 函数将手动分配其输出数据。还有一个额外的好处是若是传递一个已存在Mat对象,它已经为矩阵分配所需的空间,这段空间将被重用。也就是说咱们在任什么时候候只使用与咱们执行任务时所必须多的内存同样多的内存。less
Mat本质上是由两个数据部分组成的类: (包含信息有矩阵的大小,用于存储的方法,矩阵存储的地址等) 的矩阵头和一个指针,指向包含了像素值的矩阵(可根据选择用于存储的方法采用任何维度存储数据)。矩阵头部的大小是恒定的。然而,矩阵自己的大小因图像的不一样而不一样,一般是较大的数量级。所以,当你在您的程序中传递图像并在有些时候建立图像副本您须要花费很大的代价生成图像矩阵自己,而不是图像的头部。OpenCV 是图像处理库,它包含大量的图像处理函数。若要解决的计算挑战,最终大部分时间你会使用库中的多个函数。因为这一缘由图像传给库中的函数是一种常见的作法。咱们不该忘记咱们正在谈论每每是计算量至关大的图像处理算法。咱们想要作的最后一件事是经过制做没必要要的可能很大的图像的拷贝进一步下降您的程序的速度。ide
为了解决这一问题 OpenCV 使用引用计数系统。其思想是Mat的每一个对象具备其本身的头,但可能他们经过让他们矩阵指针指向同一地址的两个实例之间共享该矩阵。此外,拷贝运算符将只能复制矩阵头部,也还将复制指向矩阵的指针,但不复制矩阵自己。函数
上文中的全部对象,使用同一个数据矩阵。他们的头不一样,可是其中任何一个对矩阵进行修改,都会将影响全部其余的矩阵。在实践中的不一样对象只是提供相同的底层数据不一样的访问方法,然而,它们的头部是不一样的。真正有趣的部分是您能够建立仅指向完整数据的一小部分的头。例如,要在图像中建立兴趣区域 ( ROI) 只需建立一个新头设置新边界:
Mat D (A, Rect(10, 10, 100, 100) );
Mat E = A(Range:all(), Range(1,3));
如今,你可能会问多个Mat对象使用同一片数据,这片数据何时清理。简短的回答是:由最后一个使用它的对象清理。这里使用引用计数的机制,每当有人复制Mat对象的头,计数器增长。每当一个头被清除,计数器下调。当计数器变为零,矩阵也就被释放了。由于有时会仍然也要复制矩阵的自己,存在着 clone() 或 copyTo() 函数。
Mat F = A.clone();
Mat G;
A.copyTo(G);
如今 modifyingForGwill 不会影响由 theMatheader 指出的矩阵。你要记得从全部的是:
一、输出图像分配 OpenCV 功能是自动 (除非另行指定,不然)。
二、用c + + OpenCV的接口就无需考虑内存释放。
三、赋值运算符和复制构造函数 (构造函数)只复制头。
四、使用clone () 或copyTo () 函数将复制的图像的基础矩阵。
存储方法
这是关于你是如何存储的像素值。您能够选择的颜色空间和使用的数据类型。色彩空间是指咱们如何结合为了代码指定的颜色的颜色份量。最简单的是灰色的规模。在这里咱们所掌握的颜色是黑色和白色。组合的这些让咱们能创造不少的灰度级。
对于彩色的方法,咱们有不少方法可供选择。不过,每一就是将他们拆解成三个或四个基本组成部分,这些部分就会组合给全部其余的方法。最受欢迎的这一个 RGB,主要是由于这也是咱们的眼睛如何创建中咱们的眼睛的颜色。其基准的颜色是红、 绿、 蓝。编写代码的一种颜色的透明度有时第四个元素: 添加 alpha (A)。可是,它们不少颜色系统每一个具备自身的优点:
一、RGB 是最多见的是咱们的眼睛使用相似的事情,咱们显示系统还撰写使用这些颜色。
· 单纯疱疹和合肥分解颜色到他们的色相、 饱和度和亮度值/组件,这是咱们来描述颜色更天然的方式。您使用,例如可驳回的最后一个组件,使你不那么明智的输入图像的光照条件的算法。
二、 YCrCb 使用流行的 JPEG 图像格式。
三、 CIE L *b*a 是均匀颜色空间,它是很是方便的若是您须要测量给定的颜色,以另外一种颜色的距离。
如今,每一个建筑构件都本身有效的域。这会致使使用的数据类型。咱们如何存储组件的定义只是如何精细的控制,咱们已于其域。最小的数据类型多是 char 类型,这意味着一个字节或 8 位。这多是有符号(值-127 到 + 127)或无符号(以即可以存储从 0 到 255 之间的值)。虽然这三个组件的状况下已经给 16 万可能的颜色来表示 (如 RGB 的状况下) 咱们可能经过使用浮点数 (4 字节 = 32 位) 或double(8 字节 = 64 位) 数据类型的每一个组件得到甚至更精细的控制。然而,请记住增长组件的大小也会增长在内存中的整张图片的大小。
显式建立Mat对象
在Load, Modify and Save an Image 教程中,你已经能够看到如何使用readWriteImageVideo: 'imwrite() <imwrite>' 函数将一个矩阵写到一个图像文件中。然而,出于调试目的显示的实际值就方便得多。您能够实现此经过Mat的 <<运算符。不过,请注意这仅适用于二维矩阵。
虽然Mat是一个伟大的图像容器类,它也是通常矩阵类。所以,利用Mat建立和操做多维矩阵是可能的。您能够经过多种方式建立Mat的对象:
一、 Mat()构造函数
Mat M(2,2, CV_8UC3, Scalar(0,0,255));
cout << "M = " << endl << " " << M << endl << endl;
对于二维的和多通道的图像,咱们首先定义它们的大小:按行和列计数。
而后咱们须要指定的数据类型,用于存储元素和每一个矩阵点通道的数量。为此,咱们根据如下的约定能够做出多个定义:
CV_ [每一项的位数] [有符号或无符号] [类型前缀] C [通道数]
例如,CV_8UC3 意味着咱们使用那些长的 8 位无符号的 char 类型和每一个像素都有三个项目的这三个通道的造成。这是预约义的四个通道数字。Scalar 是四个元素短向量。指定此和能够初始化全部矩阵点与自定义的值。可是若是你须要更多您能够建立与上部宏和频道号码放在括号中,您能够看到下面的类型。
使用 C\C++ 数组和经过构造函数来初始化
int sz[3] = {2,2,2};
Mat L(3,sz,CV_8UC(1),Scalar::all(0));
上例为咱们展现了如何建立一个二维以上的矩阵。首先指定其维度数,而后传入一个包含了每一个维度尺寸信息的指针,其余都保持不变。
二、为一个已经存在的IplImage建立一个头:
IplImage* img = cvLoadImage("greatwave.png", 1);
Mat mtx(img); // 转换 IplImage*-> Mat
三、 Create()函数:
M.create(4,4, CV_8UC(2));
cout << "M = "<< endl << " " << M << endl << endl;
你不能经过这个构造来初始化矩阵中的数值。它只会在新的矩阵尺寸与旧的矩阵尺寸不合时从新分配矩阵的数据空间。
MATLAB风格的初始化函数:zeros(), ones(),
:eyes().指定使用的尺寸和数据类型
Mat E = Mat::eye(4, 4, CV_64F);
cout << "E = " << endl << " " << E << endl << endl;
Mat O = Mat::ones(2, 2, CV_32F);
cout << "O = " << endl << " " << O << endl << endl;
Mat Z = Mat::zeros(3,3, CV_8UC1);
cout << "Z = " << endl << " " << Z << endl << endl;
对于小的矩阵来讲你可使用逗号隔开的初始化函数:
Mat C = (Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
cout << "C = " << endl << " " << C << endl << endl;
为一个已有的Mat对象建立一个新的头而后clone()或者copyTo()这个头.
Mat RowClone = C.row(1).clone();
cout << "RowClone = " << endl << " " << RowClone << endl << endl;
打印格式
注意:你能够经过用randu()函数产生的随机值来填充矩阵。你须要给定一个上限和下限来确保随机值在你指望的范围内:
Mat R = Mat(3, 2, CV_8UC3);
cv::randu(R, Scalar::all(0), Scalar::all(255));
randu(cv::Mat &dat ,const cv::Scalar &low,const cv::Scalar &high);
在上一个例子中你能够看到默认的格式选项。尽管如此,OpenCV容许你在符合如下规则的同时格式化你的输出:
默认
cout << "R (default) = " << endl << R << endl << endl;
Python
cout << "R (python) = " << endl << format(R,"python") << endl << endl;
Comma separated values (CSV)
cout << "R (csv) = " << endl << format(R,"csv" ) << endl << endl;
Numpy
cout << "R (numpy) = " << endl << format(R,"numpy" ) << endl << endl;
C
cout << "R (c) = " << endl << format(R,"C" ) << endl << endl;
打印出其它常见数据项
OpenCV 经过<<操做符也为其余经常使用OpenCV数据结构提供打印输出的支持,如:
2D 点
Point2f P(5, 1);
cout << "Point (2D) = " << P << endl << endl;
3D 点
Point3f P3f(2, 6, 7);
cout << "Point (3D) = " << P3f << endl << endl;
std::vector经过 cv::Mat
vector<float> v;
v.push_back( (float)CV_PI); v.push_back(2); v.push_back(3.01f);
cout << "Vector of floats via Mat = " << Mat(v) << endl << endl;
点的std::vector
vector<Point2f> vPoints(20);
for (size_t E = 0; E < vPoints.size(); ++E)
vPoints[E] = Point2f((float)(E*5), (float)(E % 7));
cout << "A vector of 2D Points = " << vPoints << endl << endl;
这里大多数的例程都是在一个小控制台程序里运行。你能够在这里下载或是在cpp示例文件夹下找到。
Mat::~Mat
Mat的析构函数。
C++: Mat::~Mat()
析构函数调用Mat::release()。
Mat::operator =
提供矩阵赋值操做。
C++: Mat& Mat::operator=(const Mat& m)
C++: Mat& Mat::operator=(const MatExpr_Base& expr)
C++: Mat& Mat::operator=(const Scalar& s)
参数:
m – 被赋值的右侧的矩阵。 矩阵的赋值是一个复杂度为O(1) 的操做。 这就意味着没有复制数据段,而且两矩阵将使用同一引用计数器。在给矩阵赋新数据以前先由Mat::release()释放引用。
expr –被赋值的矩阵表达式对象。 做为第一种赋值方式的逆操做第二种形式能够被从新用到具备适当大小和尺寸的已分配空间的矩阵上以适应表达式的结果。矩阵表达式扩展获得的实函数将自动处理这个分配过程。例如:
C=A+B 扩展成add(A, B, C) , andadd() 要小心C从新分配数据的操做。.
s – 标量赋值给每个矩阵元,矩阵的大小和类型将不会改变。有现成的赋值运算符。因为他们各不相同请阅读运算符参数说明。
Mat::operator MatExpr
提供一种Mat-to-MatExpr转换运算符
C++: Mat::operator MatExpr_<Mat, Mat>() const
转换运算符不能显示调用而是由矩阵表达式引擎(Matrix Expression engine)内部调用The cast operator should not be called explicitly. It is used internally by the Matrix Expressions engine.
Mat::row
建立一个指定行数的矩阵头。.
C++: Mat Mat::row(int i) const
参数:
i – 一个0基的行索引.
该方法建立一个具备指定了行数的新矩阵头的矩阵并返回它。这是一个复杂度为O(1) 的操做,无须考虑矩阵的尺寸。新矩阵和原矩阵共享一份基础数据。这是一个典型基本矩阵处理操做的例子, axpy是LU和许多其它算法都使用的一个函数
inline void matrix_axpy(Mat& A, int i, int j, double alpha)
{
A.row(i) += A.row(j)*alpha;
}
Note:在当前实现中,下面的代码不会没法按预期的效果工做:
Mat A ;
...
A.row(i) = A.row(j) ;/ /不起做用
发生这种状况是由于 A.row(i) 造成临时矩阵头进一步分配给另外一个矩阵头。请记住,每一个操做复杂度为O(1),即没有复制任何数据。所以,若是你预期第 j行被复制到第 i行,那么上述赋值不成立。要作到这一点,应该把这种简单的赋值转换到表达式中或使用 Mat::copyTo() 方法:
Mat A ;
...
/ / 可行,但看上去有点目的不明确。
A.row(i) = A.row(j) + 0;
/ / 这是有点儿长,但这是推荐的方法。
A.row(j).copyTo(A.row(i)) ;
Mat::col
建立一个具备指定了矩阵头中列数这个参数的矩阵
C++: Mat Mat::col(int j) const
参数:
j –一个0基(从0开始)的列索引
该方法建立一个具备指定了矩阵头中列数这个参数的新矩阵并做为函数返回值。这是一种复杂度为O(1)的操做,不用考虑矩阵的尺寸大小。新矩阵和原始矩阵共享一份基础数据。参看Mat::row()说明信息。
Mat::rowRange
为指定的行span建立一个新的矩阵头。
C++: Mat Mat::rowRange(int startrow, int endrow) const
C++: Mat Mat::rowRange(const Range& r) const
参数:
startrow – 一个包容性的0基(从0开始)的行span起始索引.。
endrow – 一个0基的独占性的行span.终止索引。
r – Range 结构包含着起始和终止的索引值。该方法给矩阵指定的行span建立了新的头。 与Mat::row() 和 Mat::col()相相似这是一个复杂度为O(1)的操做。
Mat::colRange
为指定的行span建立一个矩阵头。
C++: Mat Mat::colRange(int startcol, int endcol) const
C++: Mat Mat::colRange(const Range& r) const
参数:
startcol – 一个包容性的0基(从0开始)的span列起始索引。
endcol –一个0基的独占性的列span.终止索引。
r –Range 结构包含着起始和终止的索引值。该方法给矩阵指定的列span建立了新的头。 与Mat::row() 和 Mat::col()相相似这是一个复杂度为O(1)的操做。
Mat::diag
提取或建立矩阵对角线。
C++: Mat Mat::diag(int d) const
C++: static Mat Mat::diag(const Mat& matD)
参数:
d – 对角线的索引值,能够是如下的值:
– d=0 是主对角线
– d>0表示下半部的对角线。例如:d=1对角线是紧挨着住对角线并位于矩阵下方。
– d<0表示来自矩阵上半部的对角线。例如:d= 1表示对角线被设置在对角线的上方并紧挨着。
matD – 单列用于造成矩阵对角线的列。
该方法为指定的矩阵建立一个新的头。而后新矩阵被分割为单独的列矩阵。相似于Mat::row() 和Mat::col() ,它是复杂度为O(1)操做。
Mat::clone
建立一个数组及其基础数据的完整副本。
C++: Mat Mat::clone() const
该方法建立了一个完整的数组副本。原始的step[]不会被考虑在内的。所以数组的副本是一占用total()*elemSize()字节的连续阵列。
Mat::copyTo
把矩阵复制到另外一个矩阵中。
C++: void Mat::copyTo(OutputArray m) const
C++: void Mat::copyTo(OutputArray m, InputArray mask) const
参数:
m – 目标矩阵。若是它的尺寸和类型不正确,在操做以前会从新分配。
mask – 操做掩码。它的非零元素表示矩阵中某个要被复制。
该方法把矩阵的复制到另外一个新的矩阵中在复制以前该方法会调用
m.create(this->size(), this->type);
所以,目标矩阵会在必要的状况下从新分配
尽管m.copyTo(m) works ?awlessly,该函数并不处理源矩阵和目标矩阵之间有重叠的部分的状况。当操做掩码指定以及上述的Mat::create从新分配矩阵,新分配的矩阵在数据复制到里面以前全都被初始化为0。
Mat::convertTo
在缩放或不缩放的状况下转换为另外一种数据类型。
C++:
void Mat::convertTo(OutputArray m,int rtype,double alpha=1,double beta=0)const
参数:
m – 目标矩阵。若是它的尺寸和类型不正确,在操做以前会从新分配。
rtype – 要求是目标矩阵的类型,或者在当前通道数与源矩阵通道数相同的状况下的depth。若是rtype 为负,目标矩阵与源矩阵类型相同。
beta – 可选的delta加到缩放值中去。
该方法将源像素值转化为目标类型saturate_cast<> 要放在最后以免溢出
m( x;y) = saturate_cast < rType > ( α*( *this)( x;y) +β)
Mat::assignTo
提供了一个convertTo的功能形式。
C++: void Mat::assignTo(Mat& m, int type=-1 ) const
Parameters
m – 目标阵列。
type – 要求是目标阵列depth或-1(若是阵列的类型和源矩阵类型相同)
这是一个 internally 使用的由 Matrix Expressions引擎调用的方法。
Mat::setTo
将阵列中全部的或部分的元素设置为指定的值。
C++: Mat& Mat::setTo(const Scalar& s, InputArray mask=noArray())
参数:
s – 把标量赋给阵列并转化到阵列的实际类型。
mask – 与 *this尺寸相同的操做掩码。这是Mat::operator=(const Scalar& s)运算符的一个高级变量。
Mat::reshape
在无需复制数据的前提下改变2D矩阵的形状和通道数或其中之一。
C++: Mat Mat::reshape(int cn, int rows=0) const
参数:
cn – 新的通道数。若cn=0,那么通道数就保持不变。
rows –新的行数。 若rows = 0, 那么行数保持不变。
该方法为*this元素建立新的矩阵头。这新的矩阵头尺寸和通道数或其中之一发生改变,在如下的状况任意组合都是有可能的:
ü 新的矩阵没有新增或减小元素。一般,rows*cols*channels()在转换过程当中保持一致。.
ü 无数据的复制。也就是说,这是一个复杂度为 O(1)的操做。一般,若是该操做改变行数或透过其余方式改变元素行索引,那么矩阵一定是连续的。参见Mat::isContinuous()。
例如,有一存储了STL向量的三维点集,你想用3xN的矩阵来完成下面的操做:
std::vector<Point3f> vec;
...
Mat pointMat = Mat(vec). //把向量转化成Mat, 复杂度为O(1)的运算
reshape(1). // 从Nx1的3通道矩阵得出Nx3 的单通道矩阵
//一样是复杂度为O(1)的运算
t(); // 最后转置Nx3 的矩阵
//这个过程要复制全部的元素
Mat::t
转置矩阵。.
C++: MatExpr Mat::t() const
该方法经过矩阵表达式(matrix expression)实现矩阵的转置The method performs matrix transposition by means of matrix expressions. 它并未真正完成了转置但却返回一个临时的能够进一步用在更复杂的矩阵表达式中或赋给一个矩阵的转置矩阵对象:
Mat A1 = A + Mat::eye(A.size(), A.type)*lambda;
Mat C = A1.t()*A1; //计算(A + lambda*I)^t * (A + lamda*I).
Mat::inv
反转矩阵
C++: MatExpr Mat::inv(int method=DECOMP_LU) const
参数:
method – 反转矩阵的方法。有如下几种可能的值:
– DECOMP_LU是 LU 分解必定不能是单数的。
– DECOMP_CHOLESKY 是 Cholesky LLT只适用于对称正矩阵的分解。该类型在处理大的矩阵时的速度是LU的两倍左右。
– DECOMP_SVD是 SVD 分解。若是矩阵是单数或甚至不是2维,函数就会计算伪反转矩阵。
该方法执行矩阵的反转矩阵表达。这意味着该方法返回一个临时矩阵反转对象并可进一步用于更复杂的矩阵表达式的中或分配给一个矩阵。
Mat::mul
执行两个矩阵按元素相乘或这两个矩阵的除法。
C++: MatExpr Mat::mul(InputArray m, double scale=1) const
参数:
m – 与*this具备相同类型和大小的矩阵,或矩阵表达式。
scale – 可选缩放系数。
该方法返回一个用可选的缩放比率编码了每一个元素的数组乘法的临时的对象。 注意:这不是一个对应“*”运算符的简单的矩阵乘法。.
例::
Mat C = A.mul(5/B); // 等价于divide(A, B, C, 5)
Mat::cross
计算3元素向量的一个叉乘积。
C++: Mat Mat::cross(InputArray m) const
参数:
m –另外一个叉乘操做对象。
该方法计算了两个3元素向量的叉乘的积被操做向量必须是3元素浮点型的具备相同形状和尺寸的向量。结果也是一语被操做对象的具备相同形状和大小的浮点型3元素向量。
Mat::dot
计算两向量的点乘。
C++: double Mat::dot(InputArray m) const
参数:
m –另外一个点积操做对象。
方法计算两个矩阵的点积。若是矩阵不单列或单行的向量,用顶部到底部从左到右扫描次序将它们视为 1 D向量。这些向量必须具备相同的大小和类型。若是矩阵有多个通道,从全部通道获得的点积会被加在一块儿。
Mat::zeros
返回指定的大小和类型的零数组。
C++: static MatExpr Mat::zeros(int rows, int cols, int type)
C++: static MatExpr Mat::zeros(Size size, int type)
C++: static MatExpr Mat::zeros(int ndims, const int* sizes, int type)
参数
ndims – 数组的维数。
rows–行数。
cols –列数。
size–替代矩阵大小规格Size(cols, rows)的方法。
sizes– 指定数组的形状的整数数组。
type– 建立的矩阵的类型。
该方法返回一个 Matlab 式的零数组初始值设定项。它能够用于快速造成一个常数数组做为函数参数,做为矩阵的表达式或矩阵初始值设定项的一部分。
Mat A;
A = Mat::zeros (3,3,CV_32F);
在上面的示例中,只要A不是 3 x 3浮点矩阵它就会被分配新的矩阵。不然为现有的
矩阵 A填充零。
Mat::ones
返回一个指定的大小和类型的全为1的数组。
C++: static MatExpr Mat::ones(int rows, int cols, int type)
C++: static MatExpr Mat::ones(Size size, int type)
C++: static MatExpr Mat::ones(int ndims, const int* sizes, int type)
参数:
ndims –数组的维数。
rows –行数。.
cols –列数。
size –替代矩阵大小规格Size(cols, rows)的方法。
sizes –指定数组的形状的整数数组。
type –建立的矩阵的类型。
该方法返回一个 Matlab 样式 1 的数组初始值设定项,相似Mat::zeros()。请注意,这种方法中你可使用任意一个值和Matlab 语法初始化数组以下:
Mat A = Mat::ones (100,100,CV_8U) * 3 ;/ / 使100 x 100 矩阵里充满 3。
上述操做不会造成一个 100 x 100 1 的矩阵,而后乘以 3。相反,它只是记住
缩放因子(在本例中 3)在实际调用矩阵初始值设定项时使用它。