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 ...
随机推荐
- PTA 两个有序链表序列的合并
6-5 两个有序链表序列的合并 (15 分) 本题要求实现一个函数,将两个链表表示的递增整数序列合并为一个非递减的整数序列. 函数接口定义: List Merge( List L1, List L ...
- 攻防世界 csaw2013reversing2 CSAW CTF 2014
运行程序 flag显示乱码 IDA打开查看程序逻辑 1 int __cdecl __noreturn main(int argc, const char **argv, const char **en ...
- 用jar命令打包war远程部署
最近在看jboss的相关漏洞,用jmx-console进行war远程部署的时候碰到一个jsp转war的问题,研究了半天,记录一下免得搞忘了. 一开始网上是说的直接把jsp文件压缩成zip,再把后缀名改 ...
- Android 之 TableLayout 布局详解
TableLayout简介 •简介 Tablelayout 类以行和列的形式对控件进行管理,每一行为一个 TableRow 对象,或一个 View 控件. 当为 TableRow 对象时,可在 Tab ...
- Starting Tomcat v9.0 Server at localhost' has encountered a problem
•问题描述 在通过 Eclipse 打开 Tomcat 时报错: •解决方案 找到 Tomcat 的安装位置,打开 tomcat\bin 目录,找到 shutdown.bat,手动关闭 tomcat: ...
- python3使用cv2对图像进行基本操作
技术背景 在机器视觉等领域,最基本的图像处理处理操作,可以通过opencv这个库来实现.opencv提供了python的接口,所需安装的库为opencv-python,但是在库的导入的时候一般用的是i ...
- Java基础回顾_第一部分
Java基础回顾 基本数据类型 数值类型 什么是字节? 位(bit):是计算机中数据的最小单位 字节(byte):是计算机中数据处理的基本单位,习惯上用大写字母B来表示 1 B = 8 bit 字符: ...
- JS基础学习第五天
作用域 作用域简单来说就是一个变量的作用范围.在JS中作用域分成两种: 1.全局作用域 直接在script标签中编写的代码都运行在全局作用域中全局作用域在打开页面时创建,在页面关闭时销毁.全局作用域中 ...
- 一个诡异的MySQL查询超时问题,居然隐藏着存在了两年的BUG
这一周线上碰到一个诡异的BUG. 线上有个定时任务,这个任务需要查询一个表几天范围内的一些数据做一些处理,每隔十分钟执行一次,直至成功. 通过日志发现,从凌晨5:26分开始到5:56任务执行了三次,三 ...
- 生产环境中的redis是怎么部署的?
redis cluster,10台机器,5台机器部署了redis主实例,另外5台机器部署了redis的从实例,每个主实例挂了一个从实例,5个节点对外提供读写服务,每个节点的读写高峰qps可能可以达到每 ...