深入探讨opencv图像矫正算法实战


Posted in Python onMay 21, 2021

摘要

在机器视觉中,对于图像的处理有时候因为放置的原因导致ROI区域倾斜,这个时候我们会想办法把它纠正为正确的角度视角来,方便下一步的布局分析与文字识别,这个时候通过透视变换就可以取得比较好的裁剪效果。

本次实战,对于图像的矫正使用了两种矫正思路:

  • 针对边缘比较明显的图像,使用基于轮廓提取的矫正算法。
  • 针对边缘不明显,但是排列整齐的文本图像,使用了基于霍夫直线探测的矫正算法。

基于轮廓提取的矫正算法

整体思路:

  • 图片灰度化,二值化
  • 检测轮廓,并筛选出目标轮廓(通过横纵比或面积去除干扰轮廓)
  • 获取目标轮廓的最小外接矩形
  • 获取最小外接矩形的四顶点,并定义矫正图像后的四顶点
  • 透视变换(四点变换)

opencv实现(分解步骤):

(一)图片灰度化,二值化(开运算,消除噪点)

Mat src = imread("D:/opencv练习图片/图片矫正.png");
    imshow("原图片", src);
    // 二值图像
    Mat gray, binary;
    cvtColor(src, gray, COLOR_BGR2GRAY);
    threshold(gray, binary, 0, 255, THRESH_BINARY_INV| THRESH_OTSU);
    imshow("二值化", binary);
    // 定义结构元素
    Mat se = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
    morphologyEx(binary, binary, MORPH_OPEN, se);
    imshow("开运算", binary);

深入探讨opencv图像矫正算法实战

深入探讨opencv图像矫正算法实战

注意:由于原图像背景是白色,因此二值化时候要用THRESH_BINARY_INV

(二)提取轮廓,筛选轮廓

// 寻找最大轮廓
    vector<vector<Point>> contours;
    findContours(binary, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
    int index = -1;
    int max = 0;
    for (size_t i = 0; i < contours.size(); i++) 
    {
        double area = contourArea(contours[i]);
        if (area > max) 
        {
            max = area;
            index = i;
        }
    }

(三)求取最小外接矩形以及四顶点坐标,并定义变换后的四顶点坐标

// 寻找最小外接矩形
    RotatedRect rect = minAreaRect(contours[index]);    
    Point2f srcpoint[4];//存放变换前四顶点
    Point2f dstpoint[4];//存放变换后四顶点
    rect.points(srcpoint);//获取最小外接矩形四顶点坐标
    //显示顶点
    for (size_t i = 0; i < 4; i++)
    {
        circle(src, srcpoint[i], 5, Scalar(0, 0, 255),-1);//-1表示填充
    }
    imshow("顶点坐标", src);
    //获取外接矩形宽高
    float width = rect.size.width;
    float height = rect.size.height;
    //定义矫正后四顶点
    dstpoint[0]= Point2f(0, height);
    dstpoint[1] = Point2f(0, 0);
    dstpoint[2] = Point2f(width, 0);
    dstpoint[3] = Point2f(width, height);

? 这里需要注意的是:

RotatedRect 类的矩形返回的是矩形的中心坐标,倾斜角度。

Rect类的矩形返回的是矩形的左上角坐标,宽,高。因此要获取RotatedRect 类的矩形的宽,高就要用:

//获取外接矩形宽高
    float width = rect.size.width;
    float height = rect.size.height;

获取RotatedRect 类四顶点坐标的顺序依次是:左下-左上-右上-右下(可通过显示顶点依次查看)

对应矫正后的四顶点就是:(0,height)-(0,0)-(width,0)-(width,height)

(四)透视变换

// 透视变换
    Mat M = getPerspectiveTransform(srcpoint, dstpoint);
    Mat result = Mat::zeros(Size(width, height), CV_8UC3);
    warpPerspective(src, result, M, result.size());
    imshow("矫正结果", result);

深入探讨opencv图像矫正算法实战

深入探讨opencv图像矫正算法实战

基于霍夫直线探测的矫正算法

对于文本图像(如图),它没有明显的轮廓边缘去求四顶点。但是经过深入分析,可以发现:文本的每一行文字都是呈一条直线,而且这些直线都是平行的!

深入探讨opencv图像矫正算法实战

利用这个特征就可以实现基于霍夫直线探测的矫正算法:

用霍夫线变换探测出图像中的所有直线计算出每条直线的倾斜角,求他们的平均值根据倾斜角旋转矫正

?先来看看什么是霍夫变换:

霍夫变换在检测各种形状的的技术中非常流行,如果你要检测的形状可以用数学表达式写出,你就可以是使用霍夫变换检测它。

霍夫变换的直线检测简单来说就是在空间坐标系和映射到另外一个参数空间,将空间坐标系中的每一个点映射到另外一个参数空间中的线,通过该参数空间中所有线的交叉次数得到实际空间坐标系中的直线。

在OpenCV中,使用Hough变换的直线检测在函数HoughLines和HoughLinesP中实现。

HoughLines函数(标准霍夫变换)

从平面坐标转换到霍夫空间,最终输出是找到直线的极坐标(r,θ)

HoughLines(
InputArray src,        // 输入图像,必须CV_8U的二值图像(常用canny处理后的二值图像)
OutputArray lines,     // 输出的极坐标来表示直线
double rho,            // 步长(常为1)
double theta,          //角度,(一般是CV_PI/180)
int threshold,         // 阈值,只有获得足够交点的极坐标点才被看成是直线
double min_theta=0,   // 表示角度扫描范围 0 ~180之间, 默认即可
double max_theta=CV_PI) 
// 一般情况是有经验的开发者使用,需要自己反变换到平面空间

HoughLinesP函数(霍夫变换直线概率)

从平面坐标转换到霍夫空间,最终输出是找到直线的起点和终点(直角坐标系)

HoughLinesP(
InputArray src, // 输入图像,必须CV_8U的二值图像
OutputArray lines, // 输出找到直线的两点
double rho, // 步长(半径,常设为1)
double theta, //角度,一般取值CV_PI/180
Int threshold, // 阈值,累计次数必须达到的值,一般为150
double minLineLength=0,// 最小直线长度,一般为50
double maxLineGap=0)// 最大间隔,一般为10

opencv实现(分解步骤):

(一)图片灰度化,Canny边缘提取

Mat src, src_edge, src_gray,src_rotate;
    double angle;
    src = imread("D:/opencv练习图片/文本矫正.png");
    imshow("文本图片", src);
    cvtColor(src, src_gray, COLOR_RGB2GRAY);
    Canny(src_gray, src_edge, 50, 200, 3);
    imshow("canny", src_edge);

深入探讨opencv图像矫正算法实战

深入探讨opencv图像矫正算法实战

(二) 霍夫直线检测(HoughLines函数)并显示

//通过霍夫变换检测直线
    vector<Vec2f> plines;
    //第5个参数就是阈值,阈值越大,检测精度越高
    HoughLines(src_edge, plines, 1, CV_PI / 180, 200, 0, 0);
    cout << plines.size() << endl;
    //由于图像不同,阈值不好设定,因为阈值设定过高导致无法检测直线,阈值过低直线太多,速度很慢
    //所以根据阈值由大到小设置了三个阈值,如果经过大量试验后,可以固定一个适合的阈值。
    
    float sum = 0;
    //依次画出每条线段
    for (size_t i = 0; i < plines.size(); i++)
    {
        float rho = plines[i][0];
        float theta = plines[i][1];
        Point pt1, pt2;
        double a = cos(theta), b = sin(theta);
        double x0 = a * rho, y0 = b * rho;
        pt1.x = cvRound(x0 + 1000 * (-b));//cvRound四舍五入
        pt1.y = cvRound(y0 + 1000 * (a));
        pt2.x = cvRound(x0 - 1000 * (-b));
        pt2.y = cvRound(y0 - 1000 * (a));
        sum += theta;
        line(src_gray, pt1, pt2, Scalar(55, 100, 195), 1, LINE_AA);//Scalar函数用于调节线段颜色         
        imshow("直线探测效果图", src_gray);
        float average = sum / plines.size(); //对所有角度求平均,这样做旋转效果会更好
        angle = DegreeTrans(average) - 90;
    }

深入探讨opencv图像矫正算法实战

?核心代码分析:

由于需要求解直线的倾斜角度,因此这里使用了HoughLines函数,返回的是直线的步长和弧度(极坐标系下)

通过极坐标系下的步长和弧度,可以转换到直接坐标系下的两点坐标,然后显示。(原理如图)

深入探讨opencv图像矫正算法实战

(三)根据倾斜角度,进行放射变换(逆时针旋转矫正)

//旋转中心为图像中心    
    Point2f center;
    center.x = float(src.cols / 2.0);
    center.y = float(src.rows / 2.0);
    int length = 0;
    length = sqrt(src.cols*src.cols + src.rows*src.rows);
    Mat M = getRotationMatrix2D(center, angle, 1);
    warpAffine(src, src_rotate, M, Size(length, length), 1, 0, Scalar(255, 255, 255));//仿射变换,背景色填充为白色  
    imshow("矫正后", src_rotate);

深入探讨opencv图像矫正算法实战

到此这篇关于深入探讨opencv图像矫正算法实战的文章就介绍到这了,更多相关opencv图像矫正内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Python自动重试HTTP连接装饰器
Apr 28 Python
Python中read()、readline()和readlines()三者间的区别和用法
Jul 30 Python
python使用筛选法计算小于给定数字的所有素数
Mar 19 Python
python 拷贝特定后缀名文件,并保留原始目录结构的实例
Apr 27 Python
详解Python连接MySQL数据库的多种方式
Apr 16 Python
python 正则表达式贪婪模式与非贪婪模式原理、用法实例分析
Oct 14 Python
Python numpy.zero() 初始化矩阵实例
Nov 27 Python
Jupyter Notebook 实现正常显示中文和负号
Apr 24 Python
python如何从键盘获取输入实例
Jun 18 Python
python中not、and和or的优先级与详细用法介绍
Nov 03 Python
matplotlib制作雷达图报错ValueError的实现
Jan 05 Python
如何判断pytorch是否支持GPU加速
Jun 01 Python
python正则表达式re.search()的基本使用教程
pandas:get_dummies()与pd.factorize()的用法及区别说明
python spilt()分隔字符串的实现示例
教你用python实现一个无界面的小型图书管理系统
一篇文章带你搞懂Python类的相关知识
Python深度学习之Pytorch初步使用
我对PyTorch dataloader里的shuffle=True的理解
You might like
fleaphp下不确定的多条件查询的巧妙解决方法
2008/09/11 PHP
深入PHP操作MongoDB的技术总结
2013/06/02 PHP
zf框架的校验器使用使用示例(自定义校验器和校验器链)
2014/03/13 PHP
PHP开发Apache服务器配置
2015/07/15 PHP
WordPress中注册菜单与调用菜单的方法详解
2015/12/18 PHP
PHP随机生成中文段落示例【测试网站内容时使用】
2020/04/26 PHP
JObj预览一个JS的框架
2008/03/13 Javascript
10个基于Jquery的幻灯片插件教程
2010/10/29 Javascript
用javascript关闭本窗口技巧小结
2014/09/05 Javascript
javascript中的正则表达式使用指南
2015/03/01 Javascript
JavaScript将字符串转换成字符编码列表的方法
2015/03/19 Javascript
bootstrapValidator bootstrap-select验证不可用的解决办法
2017/01/11 Javascript
Angularjs 实现动态添加控件功能
2017/05/25 Javascript
js禁止表单重复提交
2017/08/29 Javascript
Vue 2.5.2下axios + express 本地请求404的解决方法
2018/02/21 Javascript
详解ES6中的三种异步解决方案
2018/06/28 Javascript
Puppeteer环境搭建的详细步骤
2018/09/21 Javascript
在Vue环境下利用worker运行interval计时器的步骤
2019/08/01 Javascript
Element InputNumber计数器的使用方法
2020/07/27 Javascript
JavaScript仿京东轮播图效果
2021/02/25 Javascript
windows系统中python使用rar命令压缩多个文件夹示例
2014/05/06 Python
Python实现把xml或xsl转换为html格式
2015/04/08 Python
python实现从尾到头打印单链表操作示例
2020/02/22 Python
Python ckeditor富文本编辑器代码实例解析
2020/06/22 Python
使用keras时input_shape的维度表示问题说明
2020/06/29 Python
Pyecharts 中Geo函数常用参数的用法说明
2021/02/01 Python
css3中背景尺寸background-size详解
2014/09/02 HTML / CSS
五年后的职业生涯规划
2014/03/04 职场文书
护理专业毕业生自荐信
2014/06/15 职场文书
领导干部整治奢华浪费之风思想汇报
2014/10/07 职场文书
KTV员工管理制度
2015/08/06 职场文书
《曹冲称象》教学反思
2016/02/20 职场文书
详解Apache SkyWalking 告警配置指南
2021/04/22 Servers
Redis5之后版本的高可用集群搭建的实现
2021/04/27 Redis
Java并发编程必备之Future机制
2021/06/30 Java/Android
MySQL中一条SQL查询语句是如何执行的
2022/04/08 MySQL