一副尺寸为 M × N 的图像可以用一个 M × N 的矩阵来表示,矩阵元素的值表示这个位置上的像素的亮度,一般来说像素值越大表示该点越亮。一般来说,灰度图用 2 维矩阵表示,彩色(多通道)图像用 3 维矩阵(M× N × 3)表示。对于图像显示来说,目前大部分设备都是用无符号 8 位整数(类型为 CV_8U) 表示像素亮度。图像数据在计算机内存中的存储顺序为以图像最左上点(也可能是最左下点)开始, 存储如图所示。
Iij 表示第 i 行 j 列的像素值。如果是多通道图像,比如 RGB 图像,则每个像素用三个字节表示。在 OpenCV 中, RGB 图像的通道顺序为 BGR 。
存储像素值是颜色空间和数据类型。颜色空间是指我们如何组合颜色分量以编码给定的颜色。最简单的一个是灰色,我们可以使用的颜色是黑色和白色。这些组合使我们能够创建许多灰色阴影。
对于丰富多彩的方式,我们有更多的选择方法。他们每个人都将它们分解成三到四个基本组件,我们可以使用这些组合来创建其他组件。最流行的是RGB,主要是因为这也是我们的眼睛如何建立颜色。其基色为红,绿,蓝。为了编码颜色的透明度有时是第四个元素:添加了α(A)。
然而,还有许多其他颜色系统都有自己的优势:
每个建筑组件都有自己的有效域。这导致使用的数据类型。我们如何存储组件定义了我们在其域中的控件。可能的最小数据类型是char,这意味着一个字节或8位。这可能是无符号的(因此可以存储从0到255的值)或带符号(从-127到+127的值)。尽管在三个组件的情况下,这已经提供了1600万个可能的颜色来表示(像在RGB情况下),我们可以通过使用浮点数(4字节= 32位)或双(8字节= 64位)数据来获得更精细的控制每个组件的类型。然而,请记住,增加组件的大小也会增加内存中整个画面的大小。
Mat 类的定义如下所示,关键的属性如下方代码所示:
class CV_EXPORTS Mat
{
public:
//一系列函数
...
/* flag 参数中包含许多关于矩阵的信息,如:
-Mat 的标识
-数据是否连续
-深度
-通道数目
*/
int flags;
//矩阵的维数,取值应该大于或等于 2
int dims;
//矩阵的行数和列数,如果矩阵超过 2 维,这两个变量的值都为-1
int rows, cols;
//指向数据的指针
uchar* data;
//指向引用计数的指针
//如果数据是由用户分配的,则为 NULL
int* refcount;24
//其他成员变量和成员函数
...
};
Mat M(3,2, CV_8UC3, Scalar(0,0,255));
对于二维和多通道图像,我们首先定义它们的大小:行和列数,然后,我们需要指定用于存储元素的数据类型和每个矩阵点的通道数。为此,我们根据以下约定构造了多个定义:
CV_[The number of bits per item][Signed or Unsigned][Type Prefix]C[The channel number]
该代码创建一个行数(高度)为 3,列数(宽度)为 2 的图像,图像元素是 8 位无符号整数类型,且有三个通道。图像的所有像素值被初始化为(0, 0,255)。由于 OpenCV 中默认的颜色顺序为 BGR,因此这是一个全红色的图像。
除了在构造函数中可以创建图像,也可以使用 Mat 类的 create()函数创建图像。如果 create()函数指定的参数与图像之前的参数相同,则不进行实质的内存申请操作;如果参数不同,则减少原始数据内存的索引,并重新申请内存。使用
方法如下面例程所示:
Mat M(2,2, CV_8UC3);//构造函数创建图像
M.create(3,2, CV_8UC2);//释放内存重新创建图像26
需要注意的时,使用 create()函数无法设置图像像素的初始值。
4.3、Matlab 风格的创建对象方法
MATLAB样式初始化器:cv :: Mat :: zeros,cv :: Mat :: ones,cv :: Mat :: eye。指定要使用的大小和数据类型:
Mat Z = Mat::zeros(2,3, CV_8UC1);
cout << "Z = " << endl << " " << Z << endl;
Mat O = Mat::ones(2, 3, CV_32F);
cout << "O = " << endl << " " << O << endl;
Mat E = Mat::eye(2, 3, CV_64F);
cout << "E = " << endl << " " << E << endl;
该代码中,有些 type 参数如 CV_32F 未注明通道数目,这种情况下它表示单通道。
对于单通道图像,其元素类型一般为 8U(即 8 位无符号整数),当然也可以是 16S、 32F 等;这些类型可以直接用 uchar、 short、 float 等 C/C++语言中的基本数据类型表达。
如果多通道图像,如 RGB 彩色图像,需要用三个通道来表示。在这种情况下,如果依然将图像视作一个二维矩阵,那么矩阵的元素不再是基本的数据类型。OpenCV 中有模板类 Vec,可以表示一个向量。 OpenCV 中使用 Vec 类预定义了一些小向量,可以将之用于矩阵元素的表达。
typedef Vec<uchar, 2> Vec2b;
typedef Vec<uchar, 3> Vec3b;
typedef Vec<uchar, 4> Vec4b;
typedef Vec<short, 2> Vec2s;
typedef Vec<short, 3> Vec3s;
typedef Vec<short, 4> Vec4s;
typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
typedef Vec<int, 4> Vec4i;
typedef Vec<float, 2> Vec2f;
typedef Vec<float, 3> Vec3f;
typedef Vec<float, 4> Vec4f;
typedef Vec<float, 6> Vec6f;
typedef Vec<double, 2> Vec2d;
typedef Vec<double, 3> Vec3d;
typedef Vec<double, 4> Vec4d;
typedef Vec<double, 6> Vec6d;
例如 8U 类型的 RGB 彩色图像可以使用 Vec3b, 3 通道 float 类型的矩阵可以使用 Vec3f。
对于 Vec 对象,可以使用[]符号如操作数组般读写其元素,如:
Vec3b color; //用 color 变量描述一种 RGB 颜色
color[0]=255; //B 分量
color[1]=0; //G 分量
color[2]=0; //R 分量
两张图片加、减、叠加等操作
此部分搜集于:https://zhuanlan.zhihu.com/p/196737878
int main(int argc, char** argv)
{
Mat src1 = imread("E:\\1.bmp", 0);//0表示单通道,1表示3通道
Mat src2 = imread("E:\\2.bmp", 0);
Mat absSub;
absdiff(src1, src2, absSub);//两图相减的绝对值
namedWindow("absSub",0);
imshow("absSub", absSub);
Mat roiMat = src1(Range(0,3),Range(0,4));//取图像中的0到3行和0到4列
cout<< roiMat <<endl;
roiMat = roiMat.t();//图像矩阵转置
cout<< roiMat <<endl;
Mat addimg;
add(src1, src2, addimg);//两图相加
namedWindow("addimg",0);
imshow("addimg", addimg);
Mat addNum;
add(src1,100, addNum);//图像加一个数字
namedWindow("addNum",0);
imshow("addNum", addNum);
Mat ddWeightedImg;
cv::addWeighted(src1,0.3,src2,0.7,0, ddWeightedImg);//两图带权重相加,可以用于图像融合
namedWindow("ddWeightedImg",0);
imshow("ddWeightedImg", ddWeightedImg);
Mat subImg;
subtract(src1,src2, subImg);//两图相减
namedWindow("subImg",0);
imshow("subImg", subImg);
Mat subNum;
subtract(100,src1, subNum);//一个数字减去图像
namedWindow("subNum",0);
imshow("subNum", subNum);
Mat divideImg;
cv::divide(src1,src2, divideImg,1.0);//两图相除
namedWindow("divideImg",0);
imshow("divideImg", divideImg);//不能直接显示,因为是浮点数,需要转换
Mat mulImg;
cv::multiply(src1,src2, mulImg,1.0);//两图相乘
namedWindow("mulImg",0);
imshow("mulImg", mulImg); //不能直接显示,因为是浮点数,需要转换
Mat transposeImg;
cv::transpose(src1, transposeImg);//图像转置
namedWindow("transposeImg", 0);
imshow("transposeImg", transposeImg);
Mat tImg;
tImg =src1.t();//图像转置,和上面的转置一样
namedWindow("tImg", 0);
imshow("tImg", tImg);
Mat copyImg;
src1.copyTo(copyImg);//图像复制
namedWindow("copyImg",0);
imshow("copyImg", copyImg);
Mat maxImg;
cv::max(src1,src2, maxImg);//两图中的最大值
namedWindow("maxImg",0);
imshow("maxImg", maxImg);
Mat inRangeImg;
cv::inRange(src1,40,100, inRangeImg);//相当于在一定范围内的值二值化
namedWindow("inRangeImg",0);
imshow("inRangeImg", inRangeImg);
int r = src1.rows;
int c = src2.cols;
Mat r1 = src1.row(10);
Scalar meanValue = cv::mean(src1);//图像均值
cout<<"meanValue=="<< meanValue[0]<<endl;
Mat colRangeImg = src1.colRange(0,10);//取图像中一定列
Mat rowRangeImg = src1.rowRange(0,10); //取图像中一定行
纵向合并图像
Mat colMergeImg =Mat(src1.rows*2,src1.cols,0);
Mat subM = colMergeImg.rowRange(0,src1.rows);
src1.copyTo(subM);
subM = colMergeImg.rowRange(src1.rows, src1.rows*2);
src2.copyTo(subM);
namedWindow("colMergeImg",0);
imshow("colMergeImg", colMergeImg);
///横向合并矩阵
Mat rowMergeImg =Mat(src1.rows,src1.cols*2,0);
subM = rowMergeImg.colRange(0,src1.cols);
src1.copyTo(subM);
subM = rowMergeImg.colRange(src1.cols, src1.cols*2);
src2.copyTo(subM);
namedWindow("rowMergeImg",0);
imshow("rowMergeImg", rowMergeImg);
第二种在纵向合并两个图像的方法,横向合并类似,可以先对图像做转置,然后合并,最后再转置回来
Mat mergeImg2;
mergeImg2.push_back(src1);
mergeImg2.push_back(src2);
namedWindow("mergeImg2", 0);
imshow("mergeImg2", mergeImg2);
double minValue,maxValue;
Point minLoc,maxLoc;
minMaxLoc(src1,&minValue,&maxValue,&minLoc,&maxLoc);//取图像矩阵中的最大最小值和位置
cout<<"minValue=="<<minValue<<endl;
cout<<"maxValue=="<<maxValue<<endl;
cout<<"minLoc"<<minLoc.x<<"--"<<minLoc.y<<endl;
cout<<"maxLoc"<<maxLoc.x<<"--"<<maxLoc.y<<endl;
Mat mean;
Mat stddev;
meanStdDev(src1, mean, stddev);//求图像矩阵的均值和方差
cout << "mean==" << mean << endl;
cout << "stddev==" << stddev << endl;
waitKey(0);
system("pause");
}