opencv实战——图像矫正算法深入探讨
摘要
在机器视觉中,对于图像的处理有时候因为放置的原因导致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);


注意:由于原图像背景是白色,因此二值化时候要用 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);
这里需要注意的是:
1️⃣RotatedRect 类的矩形返回的是矩形的中心坐标,倾斜角度。
Rect类的矩形返回的是矩形的左上角坐标,宽,高。 因此要获取RotatedRect 类的矩形的宽,高就要用:
//获取外接矩形宽高
float width = rect.size.width;
float height = rect.size.height;
2️⃣获取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中,使用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);


(二) 霍夫直线检测(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;
}

核心代码分析:
由于需要求解直线的倾斜角度,因此这里使用了HoughLines函数,返回的是直线的步长和弧度(极坐标系下)
通过极坐标系下的步长和弧度,可以转换到直接坐标系下的两点坐标,然后显示。(原理如图)

(三)根据倾斜角度,进行放射变换(逆时针旋转矫正)
//旋转中心为图像中心
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)
刚进入实验室导师就交给我一个任务,就是让我设计算法给图像进行矫正.哎呀,我不太会图像这块啊,不过还是接下来了,硬着头皮开干吧! 那什么是图像的矫正呢?举个例子就好明白了. 我的好朋友小明给我拍了这几张 ...
- OpenCV实战:人脸关键点检测(FaceMark)
Summary:利用OpenCV中的LBF算法进行人脸关键点检测(Facial Landmark Detection) Author: Amusi Date: 2018-03-20 ...
- OpenCV实现KNN算法
原文 OpenCV实现KNN算法 K Nearest Neighbors 这个算法首先贮藏所有的训练样本,然后通过分析(包括选举,计算加权和等方式)一个新样本周围K个最近邻以给出该样本的相应值.这种方 ...
- java 在centos6.5+eclipse环境下调用opencv实现sift算法
java 在centos6.5+eclipse环境下调用opencv实现sift算法,代码如下: import org.opencv.core.Core; import org.opencv.core ...
- OPENCV下SIFT算法使用方法笔记
这几天继续在看Lowe大神的SIFT神作,看的眼花手脚抽筋.也是醉了!!!!实在看不下去,来点干货.我们知道opencv下自带SIFT特征检测以及MATCH匹配的库,这些库完全可以让我们进行傻瓜似的操 ...
- 用OpenCV实现Photoshop算法(三): 曲线调整
http://blog.csdn.net/c80486/article/details/52499919 系列文章: 用OpenCV实现Photoshop算法(一): 图像旋转 用OpenCV实现Ph ...
- OpenCV中Camshitf算法学习(补充)
结合OpenCV中Camshitf算法学习,做一些简单的补充,包括: 实现全自动跟随的一种方法 参考opencv中的相关demo,可以截取目标物体的图片,由此预先计算出其色彩投影图,用于实际的目标跟随 ...
- 基于OpenCV的KNN算法实现手写数字识别
基于OpenCV的KNN算法实现手写数字识别 一.数据预处理 # 导入所需模块 import cv2 import numpy as np import matplotlib.pyplot as pl ...
随机推荐
- SptingAOP
用Spring AOP(面向切面编程)编写简单转账功能实例: 代码结构图 1.准备数据库存储数据(在MySQL中编写) 1 # 删除spring_aop数据库 2 drop database if e ...
- 添加ASP.NET文件夹(3)
注意:这里添加的是ASP.NET文件夹,而不是普通网站下项目创建的文件夹 ASP.NET文件夹主要有7个 1.Bin文件夹包含程序所需的所有已编译程序集 2.App_Code文件夹包含页所使用的类的源 ...
- BUAA_2020_OO_UNIT2_REVIEW
OO第二单元总结 1. 设计策略 总的来说,三次作业没有大的重构,都是使用了多线程进行电梯调度,输入线程和运行线程分离,主要的不同在于三次电梯调度器线程的数量有所不同,第一次为一个,第二次为n个,第三 ...
- 并发编程(共享模型之管程wait notify)
本文主要讲解wait/notify的正确使用姿势.park/unpark.join()的原理.模式之生产者-消费者模式(异步).保护性暂停模式(同步).线程状态转换的流程.死锁和活锁以及如何检查死锁等 ...
- 11. Grub 介绍
Grub 全称:Grand Unified Bootloader grub引导也分为两个阶段stage1阶段和stage2阶段(有些较新的grub又定义了stage1.5阶段). 一般配置文件:/bo ...
- (十四)docker exec 详解
1. 作用 在运行的容器中执行命令 2. 语法 docker exec [OPTIONS] CONTAINER COMMAND [ARG...] OPTIONS说明: -d :分离模式: 在后台运行 ...
- Day05_22_实例化对象的JVM内存分析
创建对象的 JVM 内存分析 *new 运算符的作用是创建对象,在JVM堆内存中开辟新的内存空间 *方法区内存:在类加载的时候,class字节码文件被加载到该内存空间当中 *栈内存(局部变量):方法代 ...
- Day09_46_Set集合_SortedSet03
SortedSet03 让SortedSet集合完成比较,还有另外一种方法,那就是单独编写一个比较器. java.util.comparator 在TreeSet集合创建的时候可以在集合中传入一个比较 ...
- aws eks上部署 ingress-nginx 加NLB
转载自https://kubernetes.github.io/ingress-nginx/deploy/#aws In AWS we use a Network load balancer (NLB ...
- Java设计模式(一):设计模式概述、UML图、设计原则
1 设计模式概述 1.1 软件设计模式的产生背景 "设计模式"最初并不是出现在软件设计中,而是被用于建筑领域的设计中. 1977年美国著名建筑大师.加利福尼亚大学伯克利分校环境结构 ...