当前位置:   article > 正文

EmguCV学习笔记 C# 2.3 Mat类

EmguCV学习笔记 C# 2.3 Mat类

版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。

EmguCV学习笔记目录 Vb.net

EmguCV学习笔记目录 C#

笔者的博客网址:VB.Net-CSDN博客

教程相关说明以及如何获得pdf教程和代码(博客上的教程内容会和pdf教程一致,教程中也会包含所有代码),请移步:EmguCV学习笔记

2.3 Mat

Mat类也是OpenCV中的矩阵类,EmguCV对其进行了封装。Mat类提供了图像数据读取、保存方法,提供矩阵运算方法,如矩阵加减、乘法、转置等,如等。Mat类是EmguCV中处理图像的最常用和最重要的类,需要好好掌握。

Mat类常用属性:

  1. Rows和Cols:分别表示矩阵的行数和列数。可以通过这两个属性来获取矩阵的大小。
  2. Step:表示矩阵每一行的字节数。这个属性可以用于计算矩阵中某个元素的地址。
  3. Data:表示矩阵数据的指针。这个指针指向矩阵数据的第一个元素,可以通过它来访问矩阵中的每个元素。
  4. Depth:这是一个DepthType枚举,表示矩阵中每个元素的深度,即数据类型的位数。例如,CV_8U表示每个元素是8位无符号整数。

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后面的数字表示通道数。

  1. NumberOfChannels:表示矩阵中每个元素的通道数。例如,3通道的CV_8U通常表示每个元素有3个通道,分别表示红、绿、蓝三个颜色通道。
  2. IsContinuous:表示矩阵是否是连续的。如果矩阵的所有元素在内存中是连续存储的,那么这个属性的值为true,否则为false。
  3. Size:表示矩阵的大小,即行数和列数。这个属性返回一个Size类型的对象,可以通过它来获取矩阵的行数和列数。
  4. Width和Height:分别表示矩阵的宽度和高度。可以通过这两个属性来获取矩阵的大小。
  5. ElementSize:表示矩阵中每个元素的大小,即字节数。这个属性等价于Depth属性。

不同通道数、不同深度的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的数据结构

2.3.1 构造函数

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包含以下成员:

  1. Unchanged:不改变深度和通道数,读取原始图像。
  2. GrayScale:将图像转换为单通道灰度图像。
  3. Color:将图像转换为三通道彩色图像。
  4. AnyDepth:读取图像时不限制深度。
  5. AnyColor:读取图像时不限制通道数。
  6. LoadGdal:使用GDAL库读取图像。
  7. ReducedGrayscale2:将图像转换为8位单通道灰度图像。
  8. ReducedColor2:将图像转换为8位三通道彩色图像。
  9. ReducedGrayscale4:将图像转换为4位单通道灰度图像。
  10. ReducedColor4:将图像转换为4位三通道彩色图像。
  11. ReducedGrayscale8:将图像转换为1位单通道灰度图像。
  12. ReducedColor8:将图像转换为1位三通道彩色图像。

【代码位置: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赋值

2.3.2 给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赋值

2.3.3 读取Mat元素的值

1、读取Cv8U单通道且数据连续的Mat的元素的值,在【3.2.1 构造函数】第二部分代码【outputMatdata8U1C】中已经实现。这里不在累述。

2、读取Cv16U三通道且数据连续的Mat的元素的值,

【代码位置:frmChapter2_2_1】Button6_Click

       //获得Cv16u(即shortInt16)、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元素的值输出到数组

2.3.4 修改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元素的值

2.3.5 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

2.3.6 获得子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 生成子矩阵并修改后与原矩阵对比

2.3.7 修改Mat的深度

以下代码使用Mat的ConvertTo方法将Mat的深度从Cv8U修改到Cv16U

【代码位置:frmChapter2_2_1】Button13_Click

       //更改matDepthType

        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 修改深度后的矩阵与原矩阵对比

2.3.8 最大值和最小值

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元素最大最小值

2.3.9 Mat的运算

2.3.9.1 加法

【代码位置: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的加法

2.3.9.2 减法

【代码位置: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的减法

2.3.9.3 乘法

【代码位置: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的乘法

2.3.9.4 除法

【代码位置: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

2.3.9.5 And运算

【代码位置: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运算

2.3.9.6 Or运算

【代码位置: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运算

2.3.9.7 Xor运算

【代码位置: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运算

2.3.9.8 Not运算

【代码位置: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.10 载入并显示图像

在【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这个矩阵。

2.3.11 图像通道的分离与合并

图像通道的分离,使用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];

            //011BGG通道合并成新的图像

            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();

            //011BGG通道组合成新的图像

            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 图像通道的分离与合并之二

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/1001602
推荐阅读
  

闽ICP备14008679号