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 ...
随机推荐
- [SpringCloud教程]2. 版本选型和项目搭建
Spring Cloud Alibaba 版本选型 建议先选择Spring Cloud Alibaba的大版本,方便兼容 选择 Spring Cloud Alibaba 大版本 访问链接,找到标题&q ...
- GAMES101作业2
作业任务: 填写并调用函数 rasterize_triangle(const Triangle& t). 即实现光栅化 该函数的内部工作流程如下: 创建三角形的 2 维 bounding bo ...
- [Python]import使用的疑难杂症与包管理
概念:模块与包 模块module:一般是以.py为后缀的文件,也包括.pyo..pyc..pyd..so和.dll后缀的文件,模块内定义了函数.类以及变量 包package:包是含有若干个模块的文件夹 ...
- Qt中检查槽函数connect是否正确
环境:VS2017+Qt插件 一般情况下VS+QT环境下运行的QT程序输出信息需要在调试模式的输出栏可以看到,由于太多信息所以导致查看不方便(当然也可以在筛选选项中筛选信息). 有更方便查看输出信息的 ...
- Spring 学习笔记(四):Spring AOP
1 概述 本文主要讲述了AOP的基本概念以及在Spring中AOP的几种实现方式. 2 AOP AOP,即Aspect-Oriented Programming,面向切面编程,与OOP相辅相成.类似的 ...
- (十二)docker --privileged
1. privileged参数作用 --privileged Give extended privileges to this container 大约在0.6版,privileged被引入docke ...
- MySQL的半同步是什么?
前言 年后在进行腾讯二面的时候,写完算法的后问的第一个问题就是,MySQL的半同步是什么?我当时直接懵了,我以为是问的MySQL的两阶段提交的问题呢?结果确认了一下后不是两阶段提交,然后面试官看我连问 ...
- Day05_17_面向对象编程
Java面向对象 面向对象编程 和 面向过程编程 举个例子: 用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭. 面向过程编程: 主要关注事物因果关系的实现,面向过程就是 ...
- go 的方法集和接口断言
Go 的方法集与接口断言 方法集 引子 首先来看一段代码: package main import "fmt" func main() { var v IpmHelloByValu ...
- deep freeze standard v8.x
62S93Y4Q-6C6E4EQQ-7MKMZA9F-RK5H6CSZ-AB7W3BMMPJ6GGQDCPT5EPVWGJP3EEQ5GAV2TCQD7-F8J933CA-6AEHN9ZG-2FW3M ...