深入探讨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请求的方法小结
Jul 08 Python
使用python实现生成用户信息
Mar 20 Python
详谈python read readline readlines的区别
Sep 22 Python
Python读取MRI并显示为灰度图像实例代码
Jan 03 Python
numpy实现合并多维矩阵、list的扩展方法
May 08 Python
Python后台开发Django会话控制的实现
Apr 15 Python
Python获取当前脚本文件夹(Script)的绝对路径方法代码
Aug 27 Python
Python tkinter模版代码实例
Feb 05 Python
ipython jupyter notebook中显示图像和数学公式实例
Apr 15 Python
Python使用shutil模块实现文件拷贝
Jul 31 Python
浅谈anaconda python 版本对应关系
Oct 07 Python
使用Python脚本对GiteePages进行一键部署的使用说明
May 27 Python
python正则表达式re.search()的基本使用教程
pandas:get_dummies()与pd.factorize()的用法及区别说明
python spilt()分隔字符串的实现示例
教你用python实现一个无界面的小型图书管理系统
一篇文章带你搞懂Python类的相关知识
Python深度学习之Pytorch初步使用
我对PyTorch dataloader里的shuffle=True的理解
You might like
Yii分页用法实例详解
2014/12/04 PHP
PHP调用Mailgun发送邮件的方法
2017/05/04 PHP
thinkPHP分页功能实例详解
2017/05/05 PHP
解决Jquery load()加载GB2312页面时出现乱码的两种方案
2013/09/10 Javascript
js读取被点击次数的简单实例(从数据库中读取)
2014/03/07 Javascript
JS中类或对象的定义说明
2014/03/10 Javascript
node.js中的socket.io入门实例
2014/04/26 Javascript
浅谈javascript的call()、apply()、bind()的用法
2016/02/21 Javascript
vue.js 表格分页ajax 异步加载数据
2016/10/18 Javascript
浅析script标签中的defer与async属性
2016/11/30 Javascript
Ionic + Angular.js实现图片轮播的方法示例
2017/05/21 Javascript
Angular开发实践之服务端渲染
2018/03/29 Javascript
浅谈vue首屏加载优化
2018/06/28 Javascript
详解在HTTPS 项目中使用百度地图 API
2019/04/26 Javascript
js图片查看器插件用法示例
2019/06/22 Javascript
解决vue项目,npm run build后,报路径错的问题
2020/08/13 Javascript
OpenLayers加载缩放控件使用方法详解
2020/09/25 Javascript
Python的subprocess模块总结
2014/11/07 Python
使用Python编写类UNIX系统的命令行工具的教程
2015/04/15 Python
用Python将动态GIF图片倒放播放的方法
2016/11/02 Python
Python自定义一个类实现字典dict功能的方法
2019/01/19 Python
python 切换root 执行命令的方法
2019/01/19 Python
python中PS 图像调整算法原理之亮度调整
2019/06/28 Python
python设置环境变量的作用和实例
2019/07/09 Python
python requests库爬取豆瓣电视剧数据并保存到本地详解
2019/08/10 Python
css3中的calc函数浅析
2018/07/10 HTML / CSS
Everything But Water官网:美国泳装品牌
2019/03/17 全球购物
寒假思想汇报
2014/01/10 职场文书
英文导游欢迎词
2014/01/11 职场文书
2014年人事行政工作总结
2014/12/03 职场文书
结婚保证书(卖身契)
2015/02/26 职场文书
活动总结模板大全
2015/05/11 职场文书
七一晚会主持词
2015/06/29 职场文书
《少年闰土》教学反思
2016/02/18 职场文书
2016年社区党支部公开承诺书
2016/03/25 职场文书
2016年全国爱牙日宣传活动总结
2016/04/05 职场文书