深入探讨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中with及contextlib的用法详解
Jun 08 Python
解决python升级引起的pip执行错误的问题
Jun 12 Python
Python3爬虫学习之爬虫利器Beautiful Soup用法分析
Dec 12 Python
在Django的View中使用asyncio的方法
Jul 12 Python
python3反转字符串的3种方法(小结)
Nov 07 Python
Python TCP通信客户端服务端代码实例
Nov 21 Python
Python实现RabbitMQ6种消息模型的示例代码
Mar 30 Python
python多进程使用函数封装实例
May 02 Python
Python的Django框架实现数据库查询(不返回QuerySet的方法)
May 19 Python
基于Python词云分析政府工作报告关键词
Jun 02 Python
Python Selenium破解滑块验证码最新版(GEETEST95%以上通过率)
Jan 29 Python
深入浅析python3 依赖倒置原则(示例代码)
Jul 09 Python
python正则表达式re.search()的基本使用教程
pandas:get_dummies()与pd.factorize()的用法及区别说明
python spilt()分隔字符串的实现示例
教你用python实现一个无界面的小型图书管理系统
一篇文章带你搞懂Python类的相关知识
Python深度学习之Pytorch初步使用
我对PyTorch dataloader里的shuffle=True的理解
You might like
2020最新CPU的性能排名
2020/04/02 数码科技
利用PHP制作简单的内容采集器的代码
2007/11/28 PHP
用PHP的ob_start() 控制您的浏览器cache
2009/08/03 PHP
查找php配置文件php.ini所在路径的二种方法
2014/05/26 PHP
php发送html格式文本邮件的方法
2015/06/10 PHP
详谈php ip2long 出现负数的原因及解决方法
2017/04/05 PHP
神奇的代码 通杀各种网站-可随意修改复制页面内容
2008/07/17 Javascript
Javascript attachEvent传递参数的办法
2009/12/14 Javascript
jQuery瀑布流插件Wookmark使用实例
2014/04/02 Javascript
21个JavaScript事件(Events)属性汇总
2014/12/02 Javascript
JQuery限制复选框checkbox可选中个数的方法
2015/04/20 Javascript
jQuery使用$.ajax进行即时验证实例详解
2015/12/11 Javascript
jquery  实现轮播图详解及实例代码
2016/10/12 Javascript
Bootstrap路径导航与分页学习使用
2017/02/08 Javascript
Vue.js表单标签中的单选按钮、复选按钮和下拉列表的取值问题
2017/11/22 Javascript
vue实现重置表单信息为空的方法
2018/09/29 Javascript
VUE+Element UI实现简单的表格行内编辑效果的示例的代码
2018/10/31 Javascript
vue项目打包后怎样优雅的解决跨域
2019/05/26 Javascript
Python深入学习之装饰器
2014/08/31 Python
Python中实现结构相似的函数调用方法
2015/03/10 Python
解决新django中的path不能使用正则表达式的问题
2018/12/18 Python
执行Django数据迁移时报 1091错误及解决方法
2019/10/14 Python
使用CSS3来代替JS实现交互
2017/08/10 HTML / CSS
英国第一家领先的在线处方眼镜零售商:Glasses Direct
2018/02/23 全球购物
时尚圣经:The Fashion Bible
2019/03/03 全球购物
程序员跳槽必看面试题总结
2013/06/28 面试题
总经理岗位职责
2013/11/09 职场文书
百日安全活动总结
2014/05/04 职场文书
2014小学生国庆65周年演讲稿
2014/09/21 职场文书
工商局领导班子存在的问题整改措施思想汇报
2014/10/05 职场文书
2014年药剂科工作总结
2014/11/26 职场文书
会计岗位职责
2015/02/03 职场文书
前台接待岗位职责范本
2015/04/03 职场文书
python中%格式表达式实例用法
2021/06/18 Python
win11无法登录onedrive错误代码0x8004def7怎么办 ?
2022/04/05 数码科技
JavaWeb Servlet开发注册页面实例
2022/04/11 Java/Android