赞
踩
版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。
笔者的博客网址:VB.Net-CSDN博客
教程相关说明以及如何获得pdf教程和代码(博客上的教程内容会和pdf教程一致,教程中也会包含所有代码),请移步:EmguCV学习笔记
Mat类也是OpenCV中的矩阵类,EmguCV对其进行了封装。Mat类提供了图像数据读取、保存方法,提供矩阵运算方法,如矩阵加减、乘法、转置等,如等。Mat类是EmguCV中处理图像的最常用和最重要的类,需要好好掌握。
Mat类常用属性:
DepthType枚举元素主要有:
元素名称 | 元素值 | 占用位数 | 说明 |
Cv8U | 0 | 8位 | 无符号整数 Byte |
Cv8S | 1 | 8位 | 符号整数 SByte |
Cv16U | 2 | 16位 | 无符号整数 UInt16 |
Cv16S | 3 | 16位 | 符号整数 Int16 |
Cv32S | 4 | 32位 | 无符号整数 Int32 |
Cv32F | 5 | 32位 | 浮点数 Single |
Cv64F | 6 | 64位 | 浮点数 Double |
注意:在OpenCV中使用CV_8UC1这样的类型,表示8位无符号单通道矩阵。C后面的数字表示通道数。
不同通道数、不同深度的Mat对应的数据结构如下面的图示:
图2-27 Rows=4、Cols=3、CV8U、单通道Mat数据的结构
图2-28 Rows=4、Cols=3、CV8U、三通道Mat数据的结构
图2-29 Rows=4、Cols=3、CV32S、三通道Mat的数据结构
1、使用行数、列数、深度、通道数初始化Mat
【代码位置:frmChapter2_2_1】Button1_Click、showMatInfo
//构造函数1:使用行数、列数、深度、通道数 初始化Mat
private void Button1_Click(object sender, EventArgs e)
{
//指定行数4、列数3,无符号整数 Byte,通道数1
Mat m8U1 = new Mat(4, 3, Emgu.CV.CvEnum.DepthType.Cv8U, 1);
//将值设置为255
m8U1.SetTo(new MCvScalar(255));
//输出Mat信息
showMatInfo(m8U1, "m8U1");
TextBox1.Text += "============" + "\r\n";
//指定行数4、列数3,无符号整数 Byte,通道数3
Mat m8U3 = new Mat(4, 3, Emgu.CV.CvEnum.DepthType.Cv8U, 3);
m8U3.SetTo(new MCvScalar(200, 100, 0));
showMatInfo(m8U3, "m8U3");
TextBox1.Text += "============" + "\r\n";
//指定行数4、列数3,无符号整数 Int32,通道数3
Mat m32S3 = new Mat(4, 3,Emgu.CV.CvEnum.DepthType.Cv32S, 3);
m32S3.SetTo(new MCvScalar(200, 100, 0));
showMatInfo(m32S3, "m32S3");
}
//此方法输出Mat的信息
private void showMatInfo(Mat m , string strTypeName)
{
TextBox1.Text += "strTypeName:" + strTypeName + "\r\n";
TextBox1.Text += "Cols:" + m.Cols + "\r\n";
TextBox1.Text += "Rows:" + m.Rows + "\r\n";
TextBox1.Text += "Depth:" + m.Depth + "\r\n";
TextBox1.Text += "ElementSize:" + m.ElementSize + "\r\n";
TextBox1.Text += "Height:" + m.Height + "\r\n";
TextBox1.Text += "Width:" + m.Width + "\r\n";
TextBox1.Text += "NumberOfChannels:" + m.NumberOfChannels + "\r\n";
TextBox1.Text += "Step:" + m.Step + "\r\n";
TextBox1.Text += "Width*Height:" + m.Size.Width + "*" + m.Size.Height + "\r\n";
TextBox1.Text += "IsContinuous:" + m.IsContinuous + "\r\n";
}
显示结果如下:
图2-30 显示Mat信息
2、使用行数、列数、深度、通道数、指向数据的指针、步长初始化Mat
【代码位置:frmChapter2_2_1】Button2_Click、outputMatdata8U1C
//构造函数2:使用行数、列数、深度、通道数、指向数据的指针、步长 初始化Mat
private void Button2_Click(object sender, EventArgs e)
{
//包含数据的数组
byte[] buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
//定义指针并分配内存空间
IntPtr matptr = Marshal.AllocHGlobal(buffer.Length);
//将数组拷贝到指针对应的内存空间
Marshal.Copy(buffer, 0, matptr, buffer.Length);
//指定行数4、列数3,无符号整数 Byte,通道数1,数据,步长3
Mat m = new Mat(4, 3, Emgu.CV.CvEnum.DepthType.Cv8U, 1, matptr, 3);
//显示Mat中元素的值
outputMatdata8U1C(m);
}
//输出矩阵中元素的值
//为简化说明,在此方法中只考虑Cv8U单通道且数据连续的Mat
private void outputMatdata8U1C(Mat m)
{
int colcount = m.Cols;
int rowcount= m.Rows;
//获得数组大小
int bufferSize = m.Step * m.Rows;
//定义数组
byte[] buffer = new byte[bufferSize];
//将Mat的数据指针指向的内存拷贝到数组
System.Runtime.InteropServices.Marshal.Copy(m.DataPointer, buffer, 0, bufferSize);
//循环输出每个元素的值
for(int row = 0;row< rowcount;row++)
{
for(int col=0; col< colcount;col++)
TextBox1.Text += buffer[row * colcount + col] + " ";
TextBox1.Text += "\r\n";
}
}
显示结果如下:
图2-31 输出Mat元素的值
3、使用图片、色彩模式初始化Mat
其中参数ImreadModes(图像读取模式)枚举可以使用来指定不同的图像读取模式,以读取不同类型的图像。ImreadModes包含以下成员:
【代码位置:frmChapter2_2_1】Button3_Click
//构造函数3:使用图片、图像读取模式 初始化Mat
private void Button3_Click(object sender, EventArgs e)
{
//使用彩色模式将数据载入Mat
Mat m1 = new Mat("C:\\learnEmgucv\\lena.jpg", Emgu.CV.CvEnum.ImreadModes.Color);
showMatInfo(m1, "彩色图片");
//使用灰度模式将数据载入Mat
Mat m2 = new Mat("C:\\learnEmgucv\\lena.jpg", Emgu.CV.CvEnum.ImreadModes.Grayscale);
showMatInfo(m2, "灰度图片");
}
显示结果如下:
图2-32 使用图片初始化Mat
4、无参数的构造函数
下面的代码使用Mat无参数的构造函数,同时通过Matrix给Mat赋值,这也是本教程代码中常用的方式。
【代码位置:frmChapter2_2_1】Button4_Click
//构造函数4:无任何参数,同时此代码中还使用Matrix赋值给Mat
private void Button4_Click(object sender, EventArgs e)
{
Mat m = new Mat();
byte[,] inputBytes = new byte[,] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
Matrix<byte> matr = new Matrix<byte>(inputBytes);
//使用Matrix.Mat属性给Mat赋值
m = matr.Mat;
showMatInfo(m, "MatrixToMat");
}
显示结果如下:
图2-33 使用Matrix给Mat赋值
在【3.2.1 构造函数】中其实已经讲述了如何给Mat元素赋值的方法。这里另外还有一个方法,就是将数组拷贝到Mat数据指针处。
【代码位置:frmChapter2_2_1】Button5_Click
//将值拷贝到mat
private void Button5_Click(object sender, EventArgs e)
{
//这里特地少了1个值,方便查看效果
byte[] buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Mat m = new Mat(4, 3, Emgu.CV.CvEnum.DepthType.Cv8U, 1);
//输出mat元素的值,可以看到值是随机的
outputMatdata8U1C(m);
TextBox1.Text += "===============" + "\r\n";
IntPtr matptr = m.DataPointer;
//将数组拷贝到指针处
Marshal.Copy(buffer, 0, matptr, buffer.Length);
//由于差1个值,所以,Mat中最后一个元素的值仍然是之前的
outputMatdata8U1C(m);
}
显示结果如下:
图2-34 使用数组给Mat赋值
1、读取Cv8U单通道且数据连续的Mat的元素的值,在【3.2.1 构造函数】第二部分代码【outputMatdata8U1C】中已经实现。这里不在累述。
2、读取Cv16U三通道且数据连续的Mat的元素的值,
【代码位置:frmChapter2_2_1】Button6_Click
//获得Cv16u(即short或Int16)、3通道且连续的Mat元素的值
private void Button6_Click(object sender, EventArgs e)
{
Mat m = new Mat(4, 3, Emgu.CV.CvEnum.DepthType.Cv16U, 3);
//将所有元素设置为(200, 100, 0)
m.SetTo(new MCvScalar(200, 100, 0));
//计算数组所占用的内存的大小
int bufferlen = m.Step * m.Rows / 2;
//因为用的16U,也就是2个字节,对应Short类型
short[] buffer = new short[bufferlen];
//将Mat的数据指针指向的内存拷贝到数组
System.Runtime.InteropServices.Marshal.Copy(m.DataPointer, buffer, 0, bufferlen);
//简单输出,未考虑行列
foreach (short b in buffer)
TextBox1.Text += b + "\r\n";
}
显示结果如下:
图2-35 输出Cv16U三通道Mat元素的值
3、单通道Mat数据拷贝到数组
以下代码中使用到了Mat的CopyTo方法。
【代码位置:frmChapter2_2_1】Button7_Click
//单通道mat数据拷贝到数组
private void Button7_Click(object sender, EventArgs e)
{
Mat m = new Mat();
Matrix<byte> matr = new Matrix<byte>(new byte[,] { { 0, 220, 0 }, { 30, 140, 199 }, { 80, 220, 7 }, { 65, 42, 190 } });
m = matr.Mat;
byte[] b = new byte[m.Cols * m.Rows];
//使用CopyTo方法将Mat中的元素的值拷贝到数组
m.CopyTo(b);
//输出数组中的值
for (int i = 0; i < b.Length; i++)
TextBox1.Text += b[i] + "\r\n";
}
显示结果如下:
图2-36 单通道Mat元素的值输出到数组
4、三通道Mat数据拷贝到数组
【代码位置:frmChapter2_2_1】Button8_Click
//三通道mat数据拷贝到数组
private void Button8_Click(object sender, EventArgs e)
{
Mat m = new Mat(4, 3, Emgu.CV.CvEnum.DepthType.Cv8U, 3);
m.SetTo(new MCvScalar(200, 100, 0));
byte[] b = new byte[m.Cols * m.Rows * m.NumberOfChannels];
//使用CopyTo方法将Mat中的元素的值拷贝到数组
m.CopyTo(b);
//输出数组中的值
for (int i = 0; i < b.Length; i++)
TextBox1.Text += b[i] + "\r\n";
}
显示结果如下:
图2-37 三通道Mat元素的值输出到数组
【代码位置:frmChapter2_2_1】Button9_Click
//修改Mat中的数值
private void Button9_Click(object sender, EventArgs e)
{
Mat m = new Mat(4, 3, Emgu.CV.CvEnum.DepthType.Cv8U, 1);
m.SetTo(new MCvScalar(255));
TextBox1.Text += "修改前的矩阵:" + "\r\n";
outputMatdata8U1C(m);
//需要修改的行列元素的值
int row;
int col;
//修改行序号0,列序号0位置元素(如果将二维数组拉通排列,序号0的元素)的值
row = 0;
col = 0;
IntPtr ptr1 = m.DataPointer;
ptr1 += row * 3 + col;
byte[] newvalue = new byte[1];
newvalue[0] = 100;
System.Runtime.InteropServices.Marshal.Copy(newvalue, 0, ptr1, 1);
//修改行序号1,列序号1位置元素(如果将二维数组拉通排列,序号4的元素)的值
row = 1;
col = 1;
IntPtr ptr2 = m.DataPointer;
ptr2 += row * 3 + col;
newvalue[0] = 200;
System.Runtime.InteropServices.Marshal.Copy(newvalue, 0, ptr2, 1);
TextBox1.Text += "\r\n" + "修改后的矩阵:" + "\r\n";
outputMatdata8U1C(m);
}
显示结果如下,可以看到(0,0)和(1,1)位置的元素已经被修改:
图2-38 修改Mat元素的值
使用Mat的Clone方法来实现对一个Mat的完全复制。
以下代码测试,说明修改克隆后的Mat对源Mat不产生影响。
【代码位置:frmChapter2_2_1】Button10_Click
//clone,不会影响源Mat
private void Button10_Click(object sender, EventArgs e)
{
Mat m1 = new Mat();
Matrix<byte> matr1 = new Matrix<byte>(new Byte[,]{ { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, { 3, 3, 3 } });
m1 = matr1.Mat;
outputMatdata8U1C(m1);
TextBox1.Text += "===============" + "\r\n";
Mat m2 = new Mat();
m2 = m1.Clone();
outputMatdata8U1C(m2);
TextBox1.Text += "===============" + "\r\n";
//需要修改的行列元素的值
int row;
int col;
//修改行序号0,列序号0位置元素(如果将二维数组拉通排列,序号0的元素)的值
row = 0;
col = 0;
IntPtr ptr1 = m2.DataPointer;
ptr1 += row * 3 + col;
byte[] newvalue = new byte[1];
newvalue[0] = 100;
System.Runtime.InteropServices.Marshal.Copy(newvalue, 0, ptr1, 1);
outputMatdata8U1C(m2);
TextBox1.Text += "===============" + "\r\n";
outputMatdata8U1C(m1);
}
显示结果如下,可以看到(0,0)和(1,1)位置的元素已经被修改:
图2-39 克隆Mat修改后不会影响源Mat
作为对比,请将上述代码中的
m2 = m1.Clone;
修改为
m2 = m1;
【代码位置:frmChapter2_2_1】Button11_Click
//使用=赋值,会影响源Mat
private void Button11_Click(object sender, EventArgs e)
{
……
//修改Button10_Click中此处的代码:
m2 = m1;
……
}
显示结果如下,可以看到(0,0)和(1,1)位置的元素已经被修改:
图2-40 =赋值并修改后会影响源Mat
以下代码使用Mat的构造函数方法之一,源Mat和矩形(Rectangle)作为参数,生成一个子Mat。测试修改子Mat中元素的值,源Mat也会对应改变。
【代码位置:frmChapter2_2_1】Button12_Click、outputMatdata8U1CAfter
outputMatdata8U1Cafter作用是输出数据不连续的Mat的值。
//不连续的子矩阵
private void Button12_Click(object sender, EventArgs e)
{
Mat m1 = new Mat();
Matrix<byte> matr1 = new Matrix<byte>(new Byte[,] { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 }, { 9, 10, 11 } });
m1 = matr1.Mat;
outputMatdata8U1C(m1);
TextBox1.Text += "===============" + "\r\n";
//m2实际和m1指向的内存空间位置相同,m2修改会影响m1
//原始视图 DataPointer,与m1相比较,指针位置多了4
//IsContinuous属性(数据是否连续)为False
//实际值查看在:原始视图-非公共成员-bytes
Mat m2 = new Mat(m1, new Rectangle(1, 1, 2, 2));
TextBox1.Text += "输出连续数据" + "\r\n";
outputMatdata8U1C(m2);
TextBox1.Text += "===============" + "\r\n";
TextBox1.Text += "输出不连续数据" + "\r\n";
outputMatdata8U1CAfter(m2);
TextBox1.Text += "===============" + "\r\n";
//需要修改的行列元素的值
int row;
int col;
//修改行序号0,列序号0位置元素(如果将二维数组拉通排列,序号0的元素)的值
row = 0;
col = 0;
IntPtr ptr1 = m2.DataPointer;
ptr1 += row * 3 + col;
byte[] newvalue = new byte[1];
newvalue[0] = 100;
System.Runtime.InteropServices.Marshal.Copy(newvalue, 0, ptr1, 1);
outputMatdata8U1CAfter(m2);
TextBox1.Text += "===============" + "\r\n";
//再次输出m1,可以看到修改m2中元素的之后,m1对应元素的值也变化了
outputMatdata8U1CAfter(m1);
}
//对outputMatdata8U1C方法进行修改,修改后的方法考虑数据不连续的情况
//为简化说明,这里仍然使用8U单通道Mat
private void outputMatdata8U1CAfter(Mat m)
{
int colcount = m.Cols;
int rowcount = m.Rows;
int matstep = m.Step;
//每次读取数据时候的指针位置
IntPtr pos = IntPtr.Zero;
for (int i = 0; i < rowcount; i++)
{
//检查指针位置,
//如果是第一次循环,即pos初始为0,那么指针位置为mat的指针位置
//否则,应该将指针位置增加step
if (pos == IntPtr.Zero)
pos = m.DataPointer;
else
pos += matstep;
int bufferSize = colcount;
byte[] buffer = new byte[bufferSize];
//每次循环从新的指针位置拷贝数据,拷贝的数量为mat的列数。
System.Runtime.InteropServices.Marshal.Copy(pos, buffer, 0, bufferSize);
for (int col = 0; col < colcount; col++)
TextBox1.Text += buffer[col] + " ";
TextBox1.Text += "\r\n";
}
}
显示结果如下:
图2-40 生成子矩阵并修改后与原矩阵对比
以下代码使用Mat的ConvertTo方法将Mat的深度从Cv8U修改到Cv16U。
【代码位置:frmChapter2_2_1】Button13_Click
//更改mat的DepthType
private void Button13_Click(object sender, EventArgs e)
{
Mat m = new Mat(4, 3, Emgu.CV.CvEnum.DepthType.Cv8U, 3);
m.SetTo(new MCvScalar(200, 100, 0));
//输出Mat信息
showMatInfo(m, "m8U3");
TextBox1.Text += "============" + "\r\n";
Mat mOut = new Mat();
m.ConvertTo(m, Emgu.CV.CvEnum.DepthType.Cv16U);
//输出Mat信息
showMatInfo(m, "m16U3");
TextBox1.Text += "============" + "\r\n";
}
显示结果如下:
图2-41 修改深度后的矩阵与原矩阵对比
Mat使用MinMax方法来获得最大值和最小值及对应坐标。使用方式和Matrix相同。
【代码位置:frmChapter2_2_1】Button14_Click
//最大最小值以及对应坐标位置
private void Button14_Click(object sender, EventArgs e)
{
Mat m = new Mat();
Matrix<byte> matr = new Matrix<byte>(new Byte[,] { { 0, 220, 0 }, { 30, 140, 199 }, { 80, 220, 7 }, { 65, 42, 190 } });
m = matr.Mat;
double[] maxvalues;
double[] minvalues;
Point[] maxposes;
Point[] minposes;
//只能查找每个通道的最大值和最小值,而不是查找每个通道的所有最大值和最小值
//最大值和最小值的位置,也只是返回最先的那个。
m.MinMax(out minvalues, out maxvalues, out minposes, out maxposes);
TextBox1.Text += "最小值:" + minvalues[0] + " 位置:" + minposes[0].X + "," + minposes[0].Y + "\r\n";
TextBox1.Text += "最大值:" + maxvalues[0] + " 位置:" + maxposes[0].X + "," + maxposes[0].Y;
}
显示结果如下:
图2-42 获得Mat元素最大最小值
【代码位置:frmChapter2_2_1】Button15_Click
//加法
private void Button15_Click(object sender, EventArgs e)
{
Mat m1 = new Mat();
Matrix<byte> matr1 = new Matrix<byte>(new Byte[,] { { 0, 220, 0 }, { 30, 140, 225 }, { 80, 200, 7 }, { 65, 42, 190 } });
m1 = matr1.Mat;
Mat m2 = new Mat();
Matrix<byte> matr2 = new Matrix<byte>(new Byte[,] { { 50, 40, 220 }, { 130, 16, 80 }, { 95, 221, 229 }, { 17, 3, 22 } });
m2 = matr2.Mat;
Mat mout = new Mat();
//方法1:
mout = m1 + m2;
//方法2:
//CvInvoke.Add(m1, m2, mout);
outputMatdata8U1C(mout);
}
显示结果如下:
图2-43 Mat的加法
【代码位置:frmChapter2_2_1】Button16_Click
//减法
private void Button16_Click(object sender, EventArgs e)
{
Mat m1 = new Mat();
Matrix<byte> matr1 = new Matrix<byte>(new Byte[,] { { 0, 220, 0 }, { 30, 140, 225 }, { 80, 200, 7 }, { 65, 42, 190 } });
m1 = matr1.Mat;
Mat m2 = new Mat();
Matrix<byte> matr2 = new Matrix<byte>(new Byte[,] { { 50, 40, 220 }, { 130, 16, 80 }, { 95, 221, 229 }, { 17, 3, 22 } });
m2 = matr2.Mat;
Mat mout = new Mat();
//方法1:直接相减,对于小于0的值设置为0
mout = m1 - m2;
outputMatdata8U1C(mout);
TextBox1.Text += "============" + "\r\n";
//方法2:使用 CvInvoke.AbsDiff,与直接相减有所不同,小于0的值将取绝对值
CvInvoke.AbsDiff(m1, m2, mout);
outputMatdata8U1C(mout);
}
使用上述方法,应注意直接相减和使用AbsDiff函数的区别。
显示结果如下:
图2-44 Mat的减法
【代码位置:frmChapter2_2_1】Button17_Click
//乘法
private void Button17_Click(object sender, EventArgs e)
{
Mat m1 = new Mat();
Matrix<byte> matr1 = new Matrix<byte>(new Byte[,] { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 }, { 9, 10, 11 } });
m1 = matr1.Mat;
//矩阵乘以一个数值
Mat mout1 = new Mat();
mout1 = m1 * 2;
outputMatdata8U1C(mout1);
TextBox1.Text += "===============" + "\r\n";
//两个矩阵相乘,大小必须相同
Mat m2 = new Mat();
Matrix<byte> matr2 = new Matrix<byte>(new Byte[,] { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 }, { 9, 10, 11 } });
m2 = matr2.Mat;
//对应元素相乘,不同于一般的矩阵相乘
Mat mout2 = new Mat();
CvInvoke.Multiply(m1, m2, mout2);
outputMatdata8U1C(mout2);
}
显示结果如下:
图2-45 Mat的乘法
【代码位置:frmChapter2_2_1】Button18_Click
//除法
private void Button18_Click(object sender, EventArgs e)
{
Mat m1 = new Mat();
Matrix<byte> matr1 = new Matrix<byte> (new Byte[,] { { 99, 21, 112 }, { 21, 36, 55 }, { 24, 77, 24 }, { 90, 210, 121 } });
m1 = matr1.Mat;
//矩阵除以一个数值
Mat mout1 = new Mat();
mout1 = m1 / 2;
outputMatdata8U1C(mout1);
TextBox1.Text += "===============" + "\r\n";
//两个矩阵相除,大小必须相同
Mat m2 = new Mat();
Matrix<byte> matr2 = new Matrix<byte>(new Byte[,] { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 }, { 9, 10, 11 } });
m2 = matr2.Mat;
//对应元素相除,不同于一般的矩阵相除
Mat mout2 = new Mat(m1.Size, DepthType.Cv8U, 1);
CvInvoke.Divide(m1, m2, mout2) ;
outputMatdata8U1C(mout2);
}
显示结果如下:
图2-46 Mat的除法
注意:如果相除的矩阵元素类型是整型,不论被除数还是除数为0,都将会返回0。如果相除的矩阵元素类型是浮点型,被除数不为0,除数为0,那么将返回∞(无穷大);被除数为0,且除数为0,那么将返回NaN。下面的代码演示了使用0作为被除数时的输出结果,其中outputMatdata64F1C函数输出64位浮点值类型Mat的元素的值。
【代码位置:frmChapter2_2_1】Button19_Click、outputMatdata64F1C
//除法
private void Button19_Click(object sender, EventArgs e)
{
Mat m1 = new Mat();
Matrix<Double> matr1 = new Matrix<Double>(new Double[,] { { 9, 0, 0 }, { 22, 33, 55 }, { 24, 23, 24 }, { 90, 87, 121 } });
m1 = matr1.Mat;
//两个矩阵相除,大小必须相同
Mat m2 = new Mat();
Matrix<Double> matr2 = new Matrix<Double>(new Double[,] { { 0, 0, 2 }, { 3, 4, 5 }, { 6, 7, 8 }, { 9, 10, 11 } });
m2 = matr2.Mat;
//对应元素相除,不同于一般的矩阵相除
Mat mout = new Mat(m1.Size, DepthType.Cv64F, 1);
CvInvoke.Divide(m1, m2, mout);
outputMatdata64F1C(mout);
}
//输出64位浮点值类型Mat的元素的值
private void outputMatdata64F1C(Mat m)
{
int colcount = m.Cols;
int rowcount = m.Rows;
//获得数组大小
int bufferSize = m.Step * m.Rows;
//定义数组
byte[] buffer = new byte[bufferSize];
//将Mat的数据指针指向的内存拷贝到数组
System.Runtime.InteropServices.Marshal.Copy(m.DataPointer, buffer, 0, bufferSize);
//注意Double类型占用8个字节
byte[] bteOutDouble = new byte[8];
for (int i = 0; i < buffer.Length; i += 8)
{
for (int j = 0; j < 8; j++)
bteOutDouble[j] = buffer[i + j];
//这里使用了BitConverter.ToDouble将字节数组转Double
Double outDouble = BitConverter.ToDouble(bteOutDouble, 0);
TextBox1.Text += outDouble.ToString() + "\r\n";
}
}
显示结果如下:
图2-47 Mat浮点类型的除法
可以看到输出的前三个值分别为:∞、NaN、0
【代码位置:frmChapter2_2_1】Button20_Click
//And运算
private void Button20_Click(object sender, EventArgs e)
{
Mat m1 = new Mat();
Matrix<byte> matr1 = new Matrix<byte>(new Byte[,] { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, { 3, 3, 3 } });
m1 = matr1.Mat;
Mat m2 = new Mat();
Matrix<byte> matr2 = new Matrix<byte>(new Byte[,] { { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 } });
m2 = matr2.Mat;
Mat mout = new Mat();
//方法1:
mout = m1 & m2;
//方法2:
//CvInvoke.BitwiseAnd(m1, m2, mout);
outputMatdata8U1C(mout);
}
显示结果如下:
图2-48 Mat的And运算
【代码位置:frmChapter2_2_1】Button21_Click
//Or运算
private void Button21_Click(object sender, EventArgs e)
{
Mat m1 = new Mat();
Matrix<byte> matr1 = new Matrix<byte>(new Byte[,] { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, { 3, 3, 3 } });
m1 = matr1.Mat;
Mat m2= new Mat();
Matrix<byte> matr2 = new Matrix<byte>(new Byte[,] { { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 } });
m2 = matr2.Mat;
Mat mout = new Mat();
//方法1:
mout = m1 | m2;
//方法2:
//CvInvoke.BitwiseOr(m1, m2, mout)
outputMatdata8U1C(mout);
}
显示结果如下:
图2-49 Mat的Or运算
【代码位置:frmChapter2_2_1】Button22_Click
//xor运算
private void Button22_Click(object sender, EventArgs e)
{
Mat m1 = new Mat();
Matrix<byte> matr1 = new Matrix<byte>(new Byte[,] { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, { 3, 3, 3 } });
m1 = matr1.Mat;
Mat m2 = new Mat();
Matrix<byte> matr2 = new Matrix<byte>(new Byte[,] { { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 } });
m2 = matr2.Mat;
Mat mout = new Mat();
//Mat没有Xor方法,直接使用xor是错误的
//mout = m1 Xor m2
CvInvoke.BitwiseXor(m1, m2, mout);
outputMatdata8U1C(mout);
}
显示结果如下:
图2-50 Mat的Xor运算
【代码位置:frmChapter2_2_1】Button23_Click
//not(补码)运算
private void Button23_Click(object sender, EventArgs e)
{
Mat m1 = new Mat();
Matrix<byte> matr1 = new Matrix<byte>(new Byte[,] { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 }, { 9, 10, 11 } });
m1 = matr1.Mat;
Mat mout = new Mat();
//方法1:
mout = ~m1;
//方法2:
//CvInvoke.BitwiseNot(m1, mout);
outputMatdata8U1C(mout);
}
显示结果如下:
图2-51 Mat的Not运算
在【2.3.1 构造函数】中讲述过使用Mat载入图片,但是未进行显示。以下代码将显示Lena的彩色图像和灰度图像,请在窗体上放置2个ImageBox控件。ImageBox控件也是在Vs的【工具箱】|【Emgu.CV】下面。
【代码位置:frmChapter2_2_2】Button1_Click
//载入并显示图片
private void Button1_Click(object sender, EventArgs e)
{
//彩色图片
Mat m1 = new Mat("C:\\learnEmgucv\\lena.jpg", Emgu.CV.CvEnum.ImreadModes.Color);
ImageBox1.Image = m1;
//灰度图片
Mat m2 = new Mat("C:\\learnEmgucv\\lena.jpg", Emgu.CV.CvEnum.ImreadModes.Grayscale);
ImageBox2.Image = m2;
}
显示结果如下:
图2-52 图像显示
可以将图像简单理解为包含了很多像素点的矩阵,也就对应了Mat这个矩阵。
图像通道的分离,使用Mat的Split方法,将多通道图像按照通道分别存入一个Mat数组。
图像通道的合并,使用 CvInvoke的Merge方法,将多个单通道图像合并成一个多通道图像。Merge方法的语法如下:
Public Shared Sub Merge(src As VectorOfMat, dst As Mat)
需要注意的是,要合并的单通道Mat对象必须有相同的行数、列数和深度类型。合并后的多通道Mat对象的通道数等于要合并的单通道Mat对象的数量。
在进行分离和合并的时候,需要使用到VectorOfMat类型。VectorOf~直译为向量,可以简单理解为类似于数组或者集合,用来表示不定长度数组,可以通过索引来获得里面指定的元素。例如:VectorOfMat理解为多个Mat的数组,VectorOfPoint理解为多个点的数组。在EmguCV中还定义有VectorOfRect、VectorOfFloat、VectorOfTriangle2DF等等,特别是还会有VectorOfVectorOf~,表示数组的数组,例如在后面处理图像轮廓时会用到VectorOfVectorOfPoint,表示多个点的数组然后又做了个数组。
VectorOf~通过Push方法向数组中增加元素。VectorOfMat中使用Push,也就是增加通道,但需要注意,如果要显示合并后的图像,图像必须有正确的通道数,如,不能有2通道的图像。
在以下代码中可以看到上述方法和类型的用法。
【代码位置:frmChapter2_2_2】Button2_Click
//Mat按照通道分离,然后合并
private void Button2_Click(object sender, EventArgs e)
{
Mat m = new Mat("C:\\learnEmgucv\\lena.jpg", Emgu.CV.CvEnum.ImreadModes.Color);
Mat[] mDst;
//按通道数分离
mDst = m.Split();
//显示0号通道,即蓝色(B)通道
ImageBox1.Image = mDst[0];
//将0,1,1即BGG通道合并成新的图像
VectorOfMat vm = new VectorOfMat();
vm.Push(mDst[0]);
vm.Push(mDst[1]);
vm.Push(mDst[1]);
Mat mDstMerge = new Mat();
CvInvoke.Merge(vm, mDstMerge);
ImageBox2.Image = mDstMerge;
}
显示结果如下:
图2-53 图像通道的分离与合并
在以下代码中加入了一个自定义的通道,最后把图像显示出来。
【代码位置:frmChapter2_2_2】Button3_Click
//Mat按照通道分离,然后组合,更多的效果
private void Button3_Click(object sender, EventArgs e)
{
Mat m = new Mat("C:\\learnEmgucv\\lena.jpg", Emgu.CV.CvEnum.ImreadModes.Color);
//显示源图像
ImageBox1.Image = m;
Mat[] mDst;
//按通道数分离
mDst = m.Split();
//将0,1,1即BGG通道组合成新的图像
VectorOfMat vm = new VectorOfMat();
vm.Push(mDst[0]);
vm.Push(mDst[1]);
//设置第三个通道,元素的值都为0
Mat m2 = new Mat(m.Size, Emgu.CV.CvEnum.DepthType.Cv8U, 1);
m2.SetTo(new MCvScalar(0));
vm.Push(m2);
Mat mDstMerge = new Mat();
CvInvoke.Merge(vm, mDstMerge);
ImageBox2.Image = mDstMerge;
}
显示结果如下:
图2-54 图像通道的分离与合并之二
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。