上一篇文章,讲的是如何在图像中获取到一个矩形的ROI,并赋值给一个单独的Mat变量进行后续处理。除了这种最基础的用法以外,还会出现更高级一些的应用,比如把ROI拷贝到另外一张图片、特殊形状的ROI怎么提取出来等等,别着急,咱们慢慢讲明白。
也是干货满满的一篇文章。
1、小图复制到大图上,第一种方法。
目标:将 可乐.jpg,大小为250*250,复制到 珠穆朗玛峰.jpg,大小为1440*900 上。
我们定义
- smallMat = 可乐.jpg
- bigMat = 珠穆朗玛峰.jpg
执行如下代码:
Mat dstMat = bigMat.Clone();
Mat roi = new Mat(dstMat, new Rectangle(0, 0, smallMat.Cols, smallMat.Rows)); // 浅拷贝,当roi发生变化,dstMat也会变化
smallMat.CopyTo(roi); // 将smallMat的数据区拷贝到roi,而且dstMat也会变化
CvInvoke.PutText(roi, "ROI", new System.Drawing.Point(10, 30), FontFace.HersheyComplex, 0.75, new Bgr(0, 0, 0).MCvScalar, 2, LineType.EightConnected, false);
CvInvoke.Imshow("Final Mat, " + dstMat.Size.ToString(), dstMat);
效果如下:
其实我们是借助了public Mat(Mat mat, Rectangle roi) 这个函数,定义了一个Mat变量,叫roi,注意了,这种方式是浅拷贝,有什么特性,想一想????然后把smallMat全部通过CopyTo()函数拷贝到roi上,这样dstMat就会在左上角P(0,0)的位置开始,显示实际的smallMat。这是一种浅拷贝的方式,使用时要特别注意。
2、小图复制到大图上,第二种方法。
还可以利用Image元素内置的ROI方法,官方对其描述如下:
可以看出,ROI方法就是一个矩形。还是以 可乐.jpg 和 珠穆朗玛峰.jpg 为例,执行如下代码就可以得到和第一种方法相同的结果:
Mat dstMat = new Mat();
Image<Bgr, int> smallImage = smallMat.ToImage<Bgr, int>();
Image<Bgr, int> bigImage = bigMat.ToImage<Bgr, int>();
bigImage.ROI = new Rectangle(0, 0, smallMat.Width, smallMat.Height);
smallImage.CopyTo(bigImage);
bigImage.ROI = System.Drawing.Rectangle.Empty;
dstMat = bigImage.Mat;
dstMat.ConvertTo(dstMat, DepthType.Cv8U);
CvInvoke.Imshow("Final Mat, " + dstMat.Size.ToString(), dstMat);
步骤说明:
- 定义两个Image变量,代替两个Mat变量进行下面的计算。
- 代表珠穆朗玛峰的Image变量,也就是bigImage,设定ROI区域,就是左上角起始位置P(0,0),并且是 可乐.jpg 的宽度和高度。
- 可乐.jpg利用 CopyTo()函数 复制到 珠穆朗玛峰.jpg。注意了,因为珠穆朗玛峰.jpg设定了ROI,所以可以这样执行。否则两个尺寸不通的图片 ,不能直接CopyTo。
- 清空bigImage的ROI,必须要有这一步,否则的结果是什么样,读者可以试试。
- Image元素转Mat元素并显示。
3、小图复制到大图的指定位置。
上面的两种方法,都是把小图放在了大图的 P(0,0)位置,假如我想把小图放在P(200,50)的位置上,该怎么办呢?其实很简单,第一种方法的代码:
Mat roi = new Mat(dstMat, new Rectangle(0, 0, smallMat.Cols, smallMat.Rows));
改成:
Mat roi = new Mat(dstMat, new Rectangle(200, 50, smallMat.Cols, smallMat.Rows));
或者第二种方法的代码:
bigImage.ROI = new Rectangle(0, 0, smallMat.Width, smallMat.Height);
改成:
bigImage.ROI = new Rectangle(200, 50, smallMat.Width, smallMat.Height);
就是在定义ROI时,改变位置,效果如下:
4、通过mask掩码融合图像
上面的几种方法,都像是把小图硬生生的贴在大图上。注意一下,小图 可乐.jpg,背景颜色是白色的,如果我只想把可乐瓶子复制到大图,而白色背景不复制,那怎么办呢???
这种就不是简单的拷贝粘贴了,好像有个更专业的叫法:融合。注意了,下面介绍的只适合白色背景图片融入到大图。这回以 黑猪.png ,大小为216*143,和 珠穆朗玛峰.jpg 举例。
先看代码:
Mat dstMat = bigMat.Clone();
Mat roi = new Mat(dstMat, new Rectangle(400, 100, smallMat.Cols, smallMat.Rows)); // 浅拷贝,当roi发生变化,dstMat也会变化
// 定义掩码
Mat maskMat = new Mat();
CvInvoke.CvtColor(smallMat, maskMat, ColorConversion.Bgr2Gray); // 彩色转灰度图
CvInvoke.Threshold(maskMat, maskMat, 200, 255, ThresholdType.BinaryInv); // 二值法处理
CvInvoke.Imshow("Threshold small image", maskMat);
smallMat.CopyTo(roi, maskMat); // 将smallMat的数据区拷贝到roi,而且dstMat也会变化
CvInvoke.PutText(roi, "ROI", new System.Drawing.Point(10, 30), FontFace.HersheyComplex, 0.75, new Bgr(0, 0, 0).MCvScalar, 2, LineType.EightConnected, false);
CvInvoke.Imshow("Final Mat, " + dstMat.Size.ToString(), dstMat);
就是在使用CopyTo()函数的时候,增加了一个掩码图。这个掩码图是这样的:
这个掩码图是什么,其实就是一个灰度图,更准确的说是一个二值化图像。白色背景变成了黑色,而小猪部分作为前景,是白色的。掩码图必须是和 smallMat大小一致,而且是单通道、Cv8U深度的图像。这时候再执行 smallMat.CopyTo(roi, maskMat); 就会把smallMat中白色去掉,只留下前景的小猪。代码执行效果如下:
这才是真正的母猪飞上天的效果。那如果用可乐.jpg 和 珠穆朗玛峰.jpg,效果是这样的:
掩码的作用就是:如果掩码图像的值是0,则CopyTo不起作用。如果掩码图像的值大于0,才将输入图像CopyTo到输出的图像中去。进一步思考,掩码图其实就是将不规则的ROI复制到大图上。
5、特殊形状的ROI复制
利用掩码的特征,我们可以将特殊形状的ROI复制到大图上,这回以lena.jpg为例。我们建立一个lenaMask.jpg,尺寸大小和lena.jpg一样,背景为黑色,看上去是这样的:
其实就是有五个ROI,三角形,左向箭头、五角星、右向箭头、心形。利用这个掩码图进行复制,效果是这样的:
看到了吧,只有掩码图中非0的部分,才会复制到 珠穆朗玛峰.jpg 上,神奇不???
这一部分内容有些难以理解,需要读者多写代码进行练习。前期先用我提供的代码,然后多更改参数,多试验几张不同的照片,就能很快理解了。
原创不易,请勿抄袭。共同进步,相互学习。