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

2021SC@SDUSC

目录

  • 前言
  • 一、项目环境
  • 二、代码分析
    • 1.安装openCV
    • 2.算法思路
    • 3.算法实现
      • 1)canny描边
      • 2)寻找最大轮廓
  • 三、总结


前言

上几篇文章已经分析完了飞花令的项目,从这一篇开始分析camera的项目。开发人员的设计中,以手机相机为依托的camera项目不仅需要有基础的拍照功能,并且还需要对拍摄的照⽚进⾏⼀系列处理,包括但不仅限于图⽚抗扭曲,曝光度,聚焦等。具体可以整理为以下功能:
1. 相机预览功能
2. 拍照的偏好设置,如闪光灯,聚焦,曝光补偿
3. 相机可随设备旋转,拍摄横屏和竖屏的照⽚
4. 拍照后保存在⼿机的pictures⽂件夹
5. 可以预览拍摄的照⽚
6. 图⽚抗扭曲处理
这篇将分析相机的图片抗扭曲算法。

一、项目环境

android studio版本 4.1.2

sdk版本 Compile SDK version:30

Build Tools Version 30.0.3

gradle版本 6.8.3

二、代码分析

1.安装openCV

OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉和机器学习软件库,可以运行在Linux、Windows、Android和Mac OS操作系统上。 它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。
发现在OpenCV官网下载的安装包适用于Linux/Mac,因此需要使用CMake工具来进行编译安装,但是只适配于xcode6版本,所以采用homebrew来安装
命令:brew install opencv
创新实训项目分析——第十篇_第1张图片
为项目配置openCV:
创新实训项目分析——第十篇_第2张图片
这两个函数,以便opencv能正确启动


    if (!OpenCVLoader.initDebug()) {
        Log.d("OpenCV", "Internal OpenCV library not found. Using OpenCV Manager for initialization");
        OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_0_0, this, mLoaderCallback);
    } else {
        Log.d("OpenCV", "OpenCV library found inside package. Using it!");
        //OpenCV库加载并初始化成功后的回调函数
       mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
    }
}
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
    @Override
    public void onManagerConnected(int status) {
        switch (status) {
            case LoaderCallbackInterface.SUCCESS:
            {
                Log.i("OpenCV", "OpenCV loaded successfully");
            } break;
            default:
            {
                super.onManagerConnected(status);
            } break;
        }
    }
};

2.算法思路

开发人员的算法思路是,对于发生畸变的图:

1.采⽤寻找轮廓的⽅法,⽤approxPolyDP函数,对图像轮廓点进⾏多边形拟合
2.把图像的四个顶点处的点归类,划分出四个区域{左上,右上,右下,左下},利⽤opencv的寻找轮廓,得到最⼤轮廓,然后⽣成最⼩外接矩形,确定四个顶点的⼤致位置。设置⼀个阀值,与上图中的点集合求距离,⼤于阀值的舍弃,⼩于的保留。
3.所有的点集都落到了四个区域,利⽤矩形中,对⻆线距离最⼤,确定四个顶点的位置
创新实训项目分析——第十篇_第3张图片

4.根据输⼊和输出点获得图像透视变换的矩阵
5.透视变换。透视变换(Perspective Transformation)是指利用透视中心、像点、目标点三点共线的条件,按透视旋转定律使承影面(透视面)绕迹线(透视轴)旋转某一角度,破坏原有的投影光线束,仍能保持承影面上投影几何图形不变的变换。

3.算法实现

1)canny描边

利用canny算法进行边缘检测:
代码:

/**
* canny算法,边缘检测
*
* @param src
* @return
*/
public static Mat canny(Mat src) {
Mat mat = src.clone();
Imgproc.Canny(src, mat, 60, 200);
return mat; }

Mat类 (Matrix的缩写) 是OpenCV用于处理图像而引入的一个封装类。
Mat类可以分为两个部分:矩阵头和指向像素数据的矩阵指针。矩阵头 包括数字图像的矩阵尺寸、存储方法、存储地址和引用次数等,矩阵头的大小是一个常数,不会随着图像的大小而改变,但是保存图像像素数据的矩阵则会随着图像的大小而改变,OpenCV使用了引用次数,当进行图像复制和传递时,不再复制整个Mat数据,而只是复制矩阵头和指向像素矩阵的指针,并且修改引用次数。
这里可以明白,Mat实际上就是一个描述且指向图像像素数据的图像说明类,为什么这里使用src的clone()方法,而不使用赋值或者copy?

这就和我们上学期的课程,oop的浅拷贝、深拷贝相关。正如上面所说,Mat中有一个指向像素数据的的矩阵指针

  • 拷贝
    • “ = ”浅拷贝
      Mat a = imread(“1.jpg”);
      Mat b = a;
      Mat c(a);
      浅拷贝 :不复制像素数据只创建矩阵头,数据共享,即两个指针指向同一个矩阵
    • clone()—— 完全深拷贝
      Mat A = Mat::ones(4,5,CV_32F);
      Mat B = A.clone()
      clone 是完全的深拷贝,在内存中申请新的空间,完全深拷贝后,B复制了A中的矩阵头,并且在新的空间,复制出了一个和A一样的矩阵,矩阵指针指向新的矩阵
    • copyTo() ——深拷贝
      Mat A = Mat::ones(4,5,CV_32F);
      Mat C;
      A.copyTo( C)
      此处的C矩阵大小与A大小不一致,则申请新的内存空间,并完成拷贝,等同于clone()
      Mat D = A.col(1);
      A.col(0).copyTo(D)
      此处D矩阵大小与A.col(0)大小一致,因此不会申请空间,而是直接进行拷贝,相当于把A的第1列赋值给第二列
      copyTo 也是深拷贝,但是否申请新的内存空间,取决于dst矩阵头中的大小信息是否与src一至,若一致则只深拷贝并不申请新的空间,否则先申请空间后再进行拷贝.
            Canny参数解释
            src:输入图像
            mask:输出掩码
            lowThresh:低阈值
            ratio*lowThresh:高阈值
            kernel_size:卷积核大小:奇数
            */
            //Canny输出结果为掩码

Canny是双阈值筛选。通过非极大值抑制后,仍然有很多的可能边缘点,进一步的设置一个双阈值,即低阈值(low),高阈值(high)。灰度变化大于high的,设置为强边缘像素,低于low的,剔除。在low和high之间的设置为弱边缘。进一步判断,如果其领域内有强边缘像素,保留,如果没有,剔除。这样做的目的是只保留强边缘轮廓的话,有些边缘可能不闭合,需要从满足low和high之间的点进行补充,使得边缘尽可能的闭合,从而得到闭合图像
这样,mat中就保存了边缘检测后的图像输出

2)寻找最大轮廓

代码:

/**
* 利⽤函数approxPolyDP来对指定的点集进⾏逼近
*
* @param cannyMat
*/
public static Point[] useApproxPolyDPFindPoints(Mat cannyMat) {
List contours = new ArrayList();

MatOfPoint:2D点对象,通过x,y来确定点的位置

Mat hierarchy = new Mat();
// 寻找轮廓
 Imgproc.findContours(cannyMat, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE,
            new Point(0, 0));
    // 找出匹配到的最大轮廓

Imgproc模块包含了一系列常用的图形处理算法,可以使用Imgproc直接调用这些方法,包含了:
线性滤波
形态学处理
Image Pyramids
Thresholding Operations
边缘检测
检测直线
仿射变换
findContours函数参数解析如下:
findContours( InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point());

InputOutputArray image:单通道图像矩阵,常用的是二值图像,一般是经过Canny、拉普拉斯等边缘检测算子处理过的二值图像;

OutputArrayOfArrays contours:定义为vector contours,是一个双重向量,向量内每个元素保存了一组由连续的Point点构成的点的集合的向量,每一组Point点集就是一个轮廓。
有多少轮廓,向量contours就有多少元素。

OutputArray hierarchy:vector> hierarchy
Vec定义了一个向量内每一个元素包含了4个int型变量的向量。vector>向量内每个元素保存了一个包含4个int整型的数组。hierarchy向量内每一个元素的4个int型变量——hierarchy[i][0] ~hierarchy[i][3],分别表示第i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号。如果当前轮廓没有对应的后一个轮廓、前一个轮廓、父轮廓或内嵌轮廓的话,则hierarchy[i][0] ~hierarchy[i][3]的相应位被设置为默认值-1。

int mode:定义轮廓的检索模式,有以下取值:
CV_RETR_EXTERNAL:只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略
CV_RETR_LIST :检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,即这个检索模式下不存在父轮廓或内嵌轮廓, 所以hierarchy向量内所有元素的第3、第4个分量都会被置为-1
CV_RETR_CCOMP :检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层
CV_RETR_TREE:检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。

int method:定义轮廓的近似方法,有以下取值:
CV_CHAIN_APPROX_NONE: 保存物体边界上所有连续的轮廓点到contours向量内
CV_CHAIN_APPROX_SIMPLE :仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours,拐点与拐点之间直线段上的信息点不予保留
CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS:使用teh-Chinl chain近似算法

Point offset=Point()偏移量,所有的轮廓信息相对于原始图像对应点的偏移量,相当于在每一个检测出的轮廓点上加上该偏移量,Point可以是负值。

开发人员使用到了:
Imgproc.findContours(cannyMat, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE,
new Point(0, 0));
就是对cannyMat(我们之前得到的对图像进行边缘检测后得到的二值图像)进行轮廓寻找,寻找到符合的轮廓输出到contours中,第i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号存储在hierarchy中;
轮廓的检索模式选择只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略,我们可以通过最外围轮廓来调整图像,简化工作量;
轮廓的近似方法选择CV_CHAIN_APPROX_NONE,保存物体边界上所有连续的轮廓点到contours向量内;
轮廓的偏移量设置为(0,0),不改变

三、总结

开始学习openCV相关内容,了解了canny算法,本篇分析了canny边缘检测的代码和寻找最大轮廓的部分代码,对openCV有了一定的了解,在接下来的时间坚持深入学习,下一篇将分析寻找最大轮廓的剩余代码并去获取四个顶点的参照点

你可能感兴趣的:(计算机视觉,图像处理,opencv)