OpencvSharp基础学习5 | 图像变换(ROI截取)

一、本章学习以下几个算子

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

四、总结

1.案例里,创造的裁剪矩形是变换前的最小外接矩形的长宽和中心,把角度归0。因为我们使用ROI本身去变换,最小矩形通过变换后,就是我们裁剪的矩形,当然需要判断一下长短边,这里省略了,关于最小矩形长短边判断后面再总结。
2.在工业视觉中,模板匹配去做初定位,然后绘制ROI去找直线或者圆等应用,其图像变换是在模板中心和模板角度发生,所以绘制的ROI也要通过同样的变换,才能裁剪正常。
3.案例里,是把目标纠正,然后去裁剪。工业视觉中,通常是不肯正的,所以需要用到案例里的4、5步,以上第二点提到,ROI通过同样的变换后,圈中了目标区域,但是当前ROI很可能带有角度,部分裁剪。所以要掩膜把当前ROI复制出来,然后再一次变换为不带角度裁剪,或者直接扩大裁剪区域。

注:文章部分函数解析参考网上资料!如有侵权,连续删除!
转载本文需要标明出处!
谷子彭:1062484747@163.com

  • 1
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值