创新实训项目分析——第十一篇

2021SC@SDUSC


前言

对于图片抗扭曲功能算法的实现,可以划分为以下任务:
1.采⽤寻找轮廓的⽅法,⽤approxPolyDP函数,对图像轮廓点进⾏多边形拟合
2.把图像的四个顶点处的点归类,划分出四个区域{左上,右上,右下,左下},利⽤opencv的寻找轮廓,得到最⼤轮廓,然后⽣成最⼩外接矩形,确定四个顶点的⼤致位置。设置⼀个阀值,与上图中的点集合求距离,⼤于阀值的舍弃,⼩于的保留。
3.所有的点集都落到了四个区域,利⽤矩形中,对⻆线距离最⼤,确定四个顶点的位置
4.根据输⼊和输出点获得图像透视变换的矩阵
5.透视变换。透视变换(Perspective Transformation)是指利用透视中心、像点、目标点三点共线的条件,按透视旋转定律使承影面(透视面)绕迹线(透视轴)旋转某一角度,破坏原有的投影光线束,仍能保持承影面上投影几何图形不变的变换。
这一篇我们将继续分析寻找轮廓方法的代码

一、项目环境

android studio版本 4.1.2

sdk版本 Compile SDK version:30

Build Tools Version 30.0.3

gradle版本 6.8.3

二、代码分析

1. 寻找最大轮廓

double area = Imgproc.boundingRect(contours.get(0)).area();
int index = 0;
// 找出匹配到的最⼤轮廓
for (int i = 0; i < contours.size(); i++) {
double tempArea = Imgproc.boundingRect(contours.get(i)).area();
if (tempArea > area) {
area = tempArea;
index = i;
 }
 }
MatOfPoint2f approxCurve = new MatOfPoint2f();
MatOfPoint2f matOfPoint2f = new MatOfPoint2f(contours.get(index).toArray());
// 原始曲线与近似曲线之间的最⼤距离设置为0.01,true表示是闭合的曲线
Imgproc.approxPolyDP(matOfPoint2f, approxCurve, 0.01, true);
Point[] points = approxCurve.toArray();
return points; }

以此得到contours中存储的轮廓,boundingRect能够得到包覆此轮廓的最小正矩形,依次计算它对应的面积,for循环结束后能得到包裹该轮廓最小正矩形中面积最大的轮廓对应的索引号,并且将该轮廓利用toArray()存放在matOfPoint2f中。
approxPolyDP()函数是opencv中对指定的点集进行多边形逼近的函数,其逼近的精度可通过参数设置。
void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed);
InputArray curve:输入的点集
OutputArray approxCurve:输出的点集,当前点集是能最小包容指定点集的。画出来即是一个多边形
double epsilon:指定的精度,也即是原始曲线与近似曲线之间的最大距离
bool closed:若为true,则说明近似曲线是闭合的,若为false,则断开。
Imgproc.approxPolyDP(matOfPoint2f, approxCurve, 0.01, true):我们可以得到一个符合要求的轮廓相对应的精度值为0.01的闭合的画出来为一个多边形的点集,存储在approxCurve中。

2.寻找最大矩形

除此之外,我们还需要一个寻找最大矩形的方法:

List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Mat hierarchy = new Mat();
    // 寻找轮廓
 Imgproc.findContours(cannyMat, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE,
            new Point(0, 0));
    // 找出匹配到的最大轮廓
 double area = Imgproc.boundingRect(contours.get(0)).area();
    int index = 0;
    // 找出匹配到的最大轮廓
    for (int i = 0; i < contours.size(); i++) {
        double tempArea = Imgproc.boundingRect(contours.get(i)).area();
        if (tempArea > area) {
            area = tempArea;
            index = i;
        }
    }
    MatOfPoint2f matOfPoint2f = new MatOfPoint2f(contours.get(index).toArray());
    RotatedRect rect = Imgproc.minAreaRect(matOfPoint2f);
    return rect;
}

和上述寻找最大轮廓的方法基本相同,但是最后一步的时候,得到最大轮廓对应的索引号和toArray()之后的点集matOfPoint2f之后,利用RotatedRect的minAreaRect()方法,能得计算包围点集的最小旋转矩形,反复符合要求的矩形,至此,我们就得到了最贴近最大轮廓的矩形。

3.获取四个顶点的参照点

思路:

  • 获取四个顶点的参照点,返回Point数组[左上,右上,右下,左下] 把四个点分成两部分,左部分,右部分
  • 左部分:高的为左上,低的为左下(高低是以人的视觉) 右部分同理 找到最左和最右的位置,以它们的两个中间为分界点,
  • 靠左的划分到左部分,靠右的划分到右部分 如果一个区域有三个或更多,哪个比较靠近分界线,划分到少的那个区域
    RotatedRect rect = findMaxRect(cannyMat);
    Point[] referencePoints = new Point[4];
    rect.points(referencePoints);

利用fingMaxRect()得到了最贴近最大轮廓的矩形,point()函数式RotatedRect类的成员函数,points()函数求矩形的4个顶点,我们将该矩形的四个顶点存在referencePoints中。

 double minX = Double.MAX_VALUE;
  double maxX = Double.MIN_VALUE;
  for (int i = 0; i < referencePoints.length; i++) {
      referencePoints[i].x = Math.abs(referencePoints[i].x);
      referencePoints[i].y = Math.abs(referencePoints[i].y);
      minX = referencePoints[i].x < minX ? referencePoints[i].x : minX;
      maxX = referencePoints[i].x > maxX ? referencePoints[i].x : maxX;
  }

Maths.abs()是用来返回绝对值得函数:
在这里插入图片描述
对referencePoints[i]中存储的各个顶点求x,y坐标的绝对值在存储进去。
最小值:Double.MIN_VALUE=4.9E-324
最大值:Double.MAX_VALUE=1.7976931348623157E308
同时对x坐标与Double.MAX_VALUE,Double.MIN_VALUE分别比较,minX存储取绝对值后这四个顶点中x坐标和Double.MIN_VALUE中更小的一个,maxX存储取绝对值后这四个顶点中x坐标和Double.MAX_VALUE中更大的一个。

    double center = (minX + maxX) / 2;
    List<Point> leftPart = new ArrayList<Point>();
    List<Point> rightPart = new ArrayList<Point>();
    // 划分左右两个部分
    
    for (int i = 0; i < referencePoints.length; i++) {
        if (referencePoints[i].x < center) {
            leftPart.add(referencePoints[i]);
        } else if (referencePoints[i].x > center) {
            rightPart.add(referencePoints[i]);
        } else {
            if (leftPart.size() < rightPart.size()) {
                leftPart.add(referencePoints[i]);
            } else {
                rightPart.add(referencePoints[i]);
            }
        }
    }

将minX和maxX取平均值,将矩形一分为二,(minX,center)和(center,maxX),对于每个顶点来划分到leftPart和rightPart两部分中,先判断大小,如果是中间点就依据个数来划分。

    double minDistance = 0;
    int minIndex = 0;
    if (leftPart.size() < rightPart.size()) {
        // 左部分少
        minDistance = rightPart.get(0).x - center;
        minIndex = 0;
        for (int i = 1; i < rightPart.size(); i++) {
            if (rightPart.get(i).x - center < minDistance) {
                minDistance = rightPart.get(i).x - center;
                minIndex = i;
            }
        }
        leftPart.add(rightPart.remove(minIndex));
    } else if (leftPart.size() > rightPart.size()) {
        // 右部分少
        minDistance = center - leftPart.get(0).x;
        minIndex = 0;
        for (int i = 1; i < leftPart.size(); i++) {
            if (center - leftPart.get(0).x < minDistance) {
                minDistance = center - leftPart.get(0).x;
                minIndex = i;
            }
        }
        rightPart.add(leftPart.remove(minIndex));
    }
    

划分结束之后,我们还要判断两边点的个数,如果A部分比B部分点的个数多,我们需要平衡数目,我们设置minDistance和minIndex,分别对应顶点距离center的长度和该顶点的索引号,利用for循环计算出离center最近的顶点距离,minIndex记录该点的索引号,并且从A部分中移除该点,从B部分中增加该点

  if (leftPart.get(0).y < leftPart.get(1).y) {
       referencePoints[0] = leftPart.get(0);//左下
       referencePoints[3] = leftPart.get(1);//左上
   }
   if (rightPart.get(0).y < rightPart.get(1).y) {
       referencePoints[1] = rightPart.get(0);//右下
       referencePoints[2] = rightPart.get(1);//右上
   }

分为左上/左下/右上/右下四部分

三、总结

学习了如何寻找最大轮廓、最大矩形和获取四个顶点的参照点的方法,其中获取四个顶点的过程中对size的再次判断和之后的平衡点的个数的内容让我学习到了很多,以后思考会更全面

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值