一、本章学习以下几个算子
1.MinAreaRect:最小外接矩形
2.CopyTo: 复制图片(掩膜复制法)
3.GetRotationMatrix2D:计算旋转矩阵
4.WarpAffine:图像变换
5.GetRectSubPix:裁剪图像
二、算子介绍
1.MinAreaRect:最小外接矩形
函数解析:
该函数计算并返回指定点集的最小区域边界斜矩形。
函数原型:
RotatedRect minAreaRect(InputArray points)
函数参数:
points:输入信息,可以为包含点的容器(vector)或是Mat。
函数返回值:
RotatedRect类型,返回包覆输入信息的最小斜矩形,参数有最小外接矩形的中心center,(宽度,高度),旋转角度等
使用注意事项:
(1). 旋转角度θ是水平轴(x轴)逆时针旋转,与碰到的矩形的第一条边的夹角。并且这个边的边长是width,另一条边边长是height。也就是说,在这里,width与height不是按照长短来定义的。
(2). 在opencv中,坐标系原点在左上角,相对于x轴,逆时针旋转角度为负,顺时针旋转角度为正。所以,θ∈(-90度,0]。
(3). 获取的4个顶点中顺序为顺时针,第一个点位为y值最小的点。
2.CopyTo: 复制图片
函数解析:
把一张图片的整张或掩膜区域像素拷贝到另一张图片上。
函数原型:
void copyTo( OutputArray m ); //拷贝整张图(被拷贝的图)
void copyTo( OutputArray m, InputArray mask ); //拷贝掩膜图(被拷贝的图和掩膜图作与运算)
函数参数:
m:输出图像,Mat。
mask:输入掩膜图
函数返回值:
无
使用注意事项:
(1). 格式为:被拷贝图像.copyTo(输出图像,掩膜图)。
(2). 被拷贝图像和掩膜图大小必须一致,如果已知掩膜图比被拷贝图像大,可以裁剪掉不需要的部分,反之则新建一个新掩膜图用0像素填充,裁剪ROI区域(大小和老掩膜图一致),然后把老掩膜图拷贝到新掩膜图的ROI中。
(3).ROI拷贝:在原图上截取指定区域的ROI,大小和被拷贝图一致,注意此处截取使用关联截取,C++有两种方式cv:Rect和 cv::Range ,C#则用Mat roi = new Mat(cv2.Rect()),然后再使用copyTo进行整图
3.GetRotationMatrix2D:计算旋转矩阵
函数解析:
仿射变换是一种二维坐标(x, y)到二维坐标(u, v)的线性变换,其数学表达式形式如下:
对应的齐次坐标矩阵表示形式为:
仿射变换保持了二维图形的“平直性”(直线经仿射变换后依然为直线)和“平行性”(直线之间的相对位置关系保持不变,平行线经仿射变换后依然为平行线,且直线上点的位置顺序不会发生变化)。
非共线的三对对应点可以确定一个唯一的仿射变换。
函数原型:
Mat getRotationMatrix2D(Point2f center, double angle, double scale)
函数参数:
Point2f center:表示旋转的中心点
double angle:表示旋转的角度
double scale:图像缩放因子
函数返回值:
Mat类型,存放一个2*3的矩阵(变换参数)
使用注意事项:
无
4.WarpAffine:图像变换
函数解析:
v2.warpAffine()函数主要是利用变换矩阵M对图像进行如旋转、仿射、平移等变换,只需要我们提供一个2*3的变换矩阵M,就可以对图像进行变换。它一般是和cv2.getRotationMatrix2D(旋转和平移)和cv.GetAffineTransform(扭曲仿射变换)两个函数在一起使用,这两个函数是用来获取变换矩阵M,这样就不需要我们自己设置M。
函数原型:
void warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags = INTER_LINEAR, int borderMode = BORDER_CONSTANT, const Scalar& boederValue = Scalar());
函数参数:
src,输入图像,即原图像,填 Mat 类对象那个即可。
dst,输出图像,需要和源图像有一样的类型。
M,2×3 的变换矩阵。因为变换矩阵第三行形式固定,所以忽略。
dsize,输出图像的尺寸。
flags,插值的标识符。默认为 INTER_LINEAR (线性插值)。插值就是根据已知数据点(条件),来预测未知数据点值得方法。在尺寸调整过程中,图像的大小可能发生改变。此时像素与像素之间的关系就不是一一对应关系,因此在尺寸调整过程中,可能会涉及到像素值的插值计算。可选插值方式如下:
INTER_NEAREST(最近邻差值)
INTER_LINEAR(线性插值,默认)
INTER_AREA(区域插值,利用像素区域关系的重采样插值)
INTER_CUBIC(三次样条插值,超过 4×4 像素邻域内的双三次插值)
INTER_LANCZOS4(Lanczos 插值,超过 8×8 像素邻域的 Lanczos 插值)
borderMode,边界扩展类型。默认值为 BORDER_CONSTANT:
borderValue ,只有当 borderMode取值为 BORDER_CONSTANT 时,这个参数才会被使用,边界会被填充成 borderValue 指定的颜色。 默认是BORDER_CONSTANT(即指定常数值填充) ,实质上,边界处理类型,该枚举型还有:
BORDER_CONSTANT
iiiiii|abcdefgh|iiiiiii with some specified i(指定常数填充)
BORDER_REPLICATE
aaaaaa|abcdefgh|hhhhhhh(复制边缘像素填充)
BORDER_REFLECT
fedcba|abcdefgh|hgfedcb(反射复制边界像素)
BORDER_WRAP
cdefgh|abcdefgh|abcdefg
BORDER_REFLECT_101
gfedcb|abcdefgh|gfedcba(对称填充,也就是以最边缘像素为轴)
BORDER_TRANSPARENT
uvwxyz|absdefgh|ijklmno
BORDER_REFLECT101
same as BORDER_REFLECT_101
BORDER_DEFAULT
same as BORDER_REFLECT_101
BORDER_ISOLATED
do not look outside of ROI
函数返回值:
无
使用注意事项:
(1). 使用dsize参数控制输出图像尺寸,如果要无损旋转,disze的大小要比原图大,即:
int heightNew = int(src.cols * fabs(sin(angle * CV_PI / 180.0)) + src.rows * fabs(cos(angle * CV_PI / 180.0)));
int widthNew = int(src.cols * fabs(cos(angle * CV_PI / 180.0)) + src.rows * fabs(sin(angle * CV_PI / 180.0)));
(2). 参数M是通过cv2.getRotationMatrix2D(旋转和平移)或者 cv.GetAffineTransform(扭曲仿射变换)获得的变换矩阵。
5.GetRectSubPix:裁剪图像
函数解析:
从原图像中提取一个感兴趣的矩形区域图像。
函数原型:
void getRectSubPix(InputArray image, Size patchSize, Point2f center, OutputArray dst, int patchType=-1 )
函数参数:
InputArray image:输入图像
Size patchSize:获取感兴趣区域矩形的大小
Point2f center:感兴趣区域矩形在原图像中的位置(即感兴趣区域矩形的中心点坐标)
OutputArray patch:输出的图像
int patchType=-1 :表示输出图像的深度。默认-1 ,深度不变
函数返回值:
无
使用注意事项:
无
三、案例展示
1.结果展示
2.创造原图
Mat binMat; //二值图
Mat srcMat;//原图
private void button1_Click(object sender, EventArgs e)
{
srcMat = new Mat(new OpenCvSharp.Size(400, 400), MatType.CV_8UC1, new Scalar(50));
生成三角形
//RotatedRect rotateRect = new RotatedRect(new Point2f(200,
// 200), new Size2f(200, 200),
// 10);
OpenCvSharp.Point[][] points = new OpenCvSharp.Point[1][];
points[0] = new OpenCvSharp.Point[3] { new OpenCvSharp.Point(220,280), new OpenCvSharp.Point(150,150),new OpenCvSharp.Point(280,220)};
//绘制三角形
Cv2.DrawContours(srcMat, points, 0, new Scalar(255, 255, 255), -1);
pictureBox1.Image = BitmapConverter.ToBitmap(srcMat);
}
2.处理原图:二值化原图(可忽略或者其他图像处理方式,把目标展示出来)
private void button6_Click(object sender, EventArgs e)
{
binMat = new Mat();
Cv2.Threshold(srcMat, binMat, 200, 255, ThresholdTypes.Binary);
pictureBox6.Image = BitmapConverter.ToBitmap(binMat);
}
3.求目标ROI区域(这里用最小外接矩形来求),正常需要加面积过滤来过滤干扰
OpenCvSharp.Point[][] points;
HierarchyIndex[] hierarchyIndices;
RotatedRect rotatedRect;
OpenCvSharp.Point[][] minPoints;
private void button2_Click(object sender, EventArgs e)
{
//查找轮廓
Cv2.FindContours(binMat, out points, out hierarchyIndices, RetrievalModes.External, ContourApproximationModes.ApproxNone);
//求最小外接矩形
Point2f[] minRect;
rotatedRect = Cv2.MinAreaRect(points[0]);
Mat minMat = binMat.Clone();
Point2f[] point2F = rotatedRect.Points();
//顶点浮点数的中心点转成整数
OpenCvSharp.Point[] rectVertices = new OpenCvSharp.Point[4];
for (int i = 0; i < 4; i++)
{
rectVertices[i] = new OpenCvSharp.Point(point2F[i].X, point2F[i].Y);
}
minPoints = new OpenCvSharp.Point[1][];
minPoints[0] = rectVertices;
Cv2.DrawContours(minMat, minPoints, 0, new Scalar(150, 150), 4);
pictureBox2.Image = BitmapConverter.ToBitmap(minMat);
}
4.图像变换前裁剪,和变换后作对比参考(实际应用不需要这步)
private void button9_Click(object sender, EventArgs e)
{
Mat mat = new Mat();
Cv2.GetRectSubPix(binMat, new OpenCvSharp.Size(rotatedRect.Size.Width + 10, rotatedRect.Size.Height + 10), rotatedRect.Center, mat);
MessageBox.Show("变换前ROI宽高和中心是,宽:" + rotatedRect.Size.Width + ",高:" + rotatedRect.Size.Height + ",X:" + rotatedRect.Center.X + ",Y:" + rotatedRect.Center.Y);
pictureBox8.Image = BitmapConverter.ToBitmap(mat);
}
5.把ROI区域用255掩膜,掩膜图用来拷贝
private void button7_Click(object sender, EventArgs e)
{
//在二值图像中圈出轮廓区域(这里是最小外接矩形)并染白
Cv2.DrawContours(binMat, minPoints, -1, Scalar.White, -1);
pictureBox3.Image = BitmapConverter.ToBitmap(binMat);
}
6.在原图上,扣取掩膜区域的图
//把掩膜区域的原图抠到此图上
Mat outRoi;
private void button4_Click(object sender, EventArgs e)
{
outRoi = new Mat(400,400, MatType.CV_8UC1);
outRoi.SetTo(100);
//提取Roi,要把目标画到此处
Mat roi = new Mat(outRoi, new Rect(0, 0, 400, 400));
//将原图通过mask抠图到Roi,这里意思是,将原图srcMat的对应binMat区域中大于1的值抠到新图Roi中,小于1的值ROI图保留原数据
srcMat.CopyTo(roi, binMat);
pictureBox4.Image = BitmapConverter.ToBitmap(outRoi);
}
原则上,5、6都可以省略,这里添加进去是为了测试,把图片ROI区域通过掩膜的方式提取到另一张图上
7.图像变换
//存放ROI发生旋转变换后的图片(画布)
Mat afterRotato;
private void button3_Click(object sender, EventArgs e)
{
//存放ROI发生旋转变换后的图片(画布)
afterRotato = new Mat(srcMat.Size(), MatType.CV_8UC1);
afterRotato.SetTo(0); //设置像素值,不起作用,因为使用了WarpAffine填充边界
//获取最小矩形中心(旋转中心)
Point2f center = rotatedRect.Center;
//获取最小矩形角度(旋转角度)
double angel = rotatedRect.Angle;
//根据中心和角度计算变换矩阵,缩放比率为1
Mat M = Cv2.GetRotationMatrix2D(center, angel, 1);
//得到变换后的图像,控制输入图像大小来改变画布大小,填充边界使用默认0值,这里使用BorderTypes.Replicate(复制边缘像素填充)
Cv2.WarpAffine(outRoi, afterRotato, M, outRoi.Size(), InterpolationFlags.Linear, BorderTypes.Replicate);
pictureBox5.Image = BitmapConverter.ToBitmap(afterRotato);
}
8.变换后裁剪ROI
private void button8_Click(object sender, EventArgs e)
{
Mat mat = new Mat();
Cv2.GetRectSubPix(afterRotato, new OpenCvSharp.Size(rotatedRect.Size.Width+1, rotatedRect.Size.Height+1), rotatedRect.Center, mat);
MessageBox.Show(“变换后ROI宽高和中心是,宽:” + rotatedRect.Size.Width + “,高:” + rotatedRect.Size.Height + “,X:” + rotatedRect.Center.X + “,Y:” + rotatedRect.Center.Y);
pictureBox7.Image = BitmapConverter.ToBitmap(mat);
}文章来源:https://www.toymoban.com/news/detail-480068.html
四、总结
1.案例里,创造的裁剪矩形是变换前的最小外接矩形的长宽和中心,把角度归0。因为我们使用ROI本身去变换,最小矩形通过变换后,就是我们裁剪的矩形,当然需要判断一下长短边,这里省略了,关于最小矩形长短边判断后面再总结。
2.在工业视觉中,模板匹配去做初定位,然后绘制ROI去找直线或者圆等应用,其图像变换是在模板中心和模板角度发生,所以绘制的ROI也要通过同样的变换,才能裁剪正常。
3.案例里,是把目标纠正,然后去裁剪。工业视觉中,通常是不肯正的,所以需要用到案例里的4、5步,以上第二点提到,ROI通过同样的变换后,圈中了目标区域,但是当前ROI很可能带有角度,部分裁剪。所以要掩膜把当前ROI复制出来,然后再一次变换为不带角度裁剪,或者直接扩大裁剪区域。
注:文章部分函数解析参考网上资料!如有侵权,连续删除!
转载本文需要标明出处!
谷子彭:1062484747@163.com文章来源地址https://www.toymoban.com/news/detail-480068.html
到了这里,关于OpencvSharp基础学习5 | 图像变换(ROI截取)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!