摘要

在机器视觉中,对于图像的处理有时候因为放置的原因导致ROI区域倾斜,这个时候我们会想办法把它纠正为正确的角度视角来,方便下一步的布局分析与文字识别,这个时候通过透视变换就可以取得比较好的裁剪效果。

本次实战,对于图像的矫正使用了两种矫正思路:

  • 针对边缘比较明显的图像,使用基于轮廓提取的矫正算法。
  • 针对边缘不明显,但是排列整齐的文本图像,使用了基于霍夫直线探测的矫正算法。

基于轮廓提取的矫正算法

整体思路:

  1. 图片灰度化,二值化
  2. 检测轮廓,并筛选出目标轮廓(通过横纵比或面积去除干扰轮廓)
  3. 获取目标轮廓的最小外接矩形
  4. 获取最小外接矩形的四顶点,并定义矫正图像后的四顶点
  5. 透视变换(四点变换)

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);


 基于霍夫直线探测的矫正算法

对于文本图像(如图),它没有明显的轮廓边缘去求四顶点。但是经过深入分析,可以发现:文本的每一行文字都是呈一条直线,而且这些直线都是平行的!

利用这个特征就可以实现基于霍夫直线探测的矫正算法:

  1. 用霍夫线变换探测出图像中的所有直线
  2. 计算出每条直线的倾斜角,求他们的平均值
  3. 根据倾斜角旋转矫正

先来看看什么是霍夫变换:

霍夫变换在检测各种形状的的技术中非常流行,如果你要检测的形状可以用数学表达式写出,你就可以是使用霍夫变换检测它。

霍夫变换的直线检测简单来说就是在空间坐标系和映射到另外一个参数空间,将空间坐标系中的每一个点映射到另外一个参数空间中的线,通过该参数空间中所有线的交叉次数得到实际空间坐标系中的直线。

在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实战——图像矫正算法深入探讨的更多相关文章

  1. OpenCV探索之路(十六):图像矫正技术深入探讨

    刚进入实验室导师就交给我一个任务,就是让我设计算法给图像进行矫正.哎呀,我不太会图像这块啊,不过还是接下来了,硬着头皮开干吧! 那什么是图像的矫正呢?举个例子就好明白了. 我的好朋友小明给我拍了这几张 ...

  2. 图像矫正技术深入探讨(opencv)

    刚进入实验室导师就交给我一个任务,就是让我设计算法给图像进行矫正.哎呀,我不太会图像这块啊,不过还是接下来了,硬着头皮开干吧! 那什么是图像的矫正呢?举个例子就好明白了. 我的好朋友小明给我拍了这几张 ...

  3. OpenCV实战:人脸关键点检测(FaceMark)

    Summary:利用OpenCV中的LBF算法进行人脸关键点检测(Facial Landmark Detection) Author:    Amusi Date:       2018-03-20 ...

  4. OpenCV实现KNN算法

    原文 OpenCV实现KNN算法 K Nearest Neighbors 这个算法首先贮藏所有的训练样本,然后通过分析(包括选举,计算加权和等方式)一个新样本周围K个最近邻以给出该样本的相应值.这种方 ...

  5. java 在centos6.5+eclipse环境下调用opencv实现sift算法

    java 在centos6.5+eclipse环境下调用opencv实现sift算法,代码如下: import org.opencv.core.Core; import org.opencv.core ...

  6. OPENCV下SIFT算法使用方法笔记

    这几天继续在看Lowe大神的SIFT神作,看的眼花手脚抽筋.也是醉了!!!!实在看不下去,来点干货.我们知道opencv下自带SIFT特征检测以及MATCH匹配的库,这些库完全可以让我们进行傻瓜似的操 ...

  7. 用OpenCV实现Photoshop算法(三): 曲线调整

    http://blog.csdn.net/c80486/article/details/52499919 系列文章: 用OpenCV实现Photoshop算法(一): 图像旋转 用OpenCV实现Ph ...

  8. OpenCV中Camshitf算法学习(补充)

    结合OpenCV中Camshitf算法学习,做一些简单的补充,包括: 实现全自动跟随的一种方法 参考opencv中的相关demo,可以截取目标物体的图片,由此预先计算出其色彩投影图,用于实际的目标跟随 ...

  9. 基于OpenCV的KNN算法实现手写数字识别

    基于OpenCV的KNN算法实现手写数字识别 一.数据预处理 # 导入所需模块 import cv2 import numpy as np import matplotlib.pyplot as pl ...

随机推荐

  1. Android学习之活动的最佳实践

    •问题的起源 先来模拟一个场景:打开一个 App,最先映入眼帘的是主活动(MainActivity),在该活动中给用户提供了一个 Button, 用户点击该 Button 实现由 MainActivi ...

  2. Kubernetes 常见问题总结

    Kubernetes 常见问题总结 如何删除不一致状态下的 rc,deployment,service 在某些情况下,经常发现 kubectl 进程挂起现象,然后在 get 时候发现删了一半,而另外的 ...

  3. [Fundamental of Power Electronics]-PART I-3.稳态等效电路建模,损耗和效率-3.2 考虑电感铜损

    3.2 考虑电感铜损 可以拓展图3.3的直流变压器模型,来对变换器的其他属性进行建模.通过添加电阻可以模拟如功率损耗的非理想因素.在后面的章节,我们将通过在等效电路中添加电感和电容来模拟变换器动态. ...

  4. BUAA_OS lab2 难点梳理

    BUAA_OS lab2 难点梳理 实验重点 所列出的实验重点为笔者在进行lab2过程中认为需要深刻理解的部分. 进行内存访问的流程 熟悉mips内存映射布局,即理解mmu.h内图 二级页表的理解和实 ...

  5. msf记录

    生成backdoor msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.187.133 LPORT=6666 -f exe >/ ...

  6. 全网最清楚的:MySQL的insert buffer和change buffer 串讲

    目录 一.前言 二.问题引入 2.1.聚簇索引 2.2.普通索引 三.change buffer存在的意义 四.再看change buffer 五.change buffer 的限制 六.change ...

  7. Ubuntu 20.04 简述环境配置&美化

    不敢说是最好的,基本上是最全面的了~ 修改系统软件源 一开始是国外的源比较慢,建议换成国内的源,常用的有清华源.阿里源等. 清华源地址 Ubuntu 的软件源配置文件是 /etc/apt/source ...

  8. 实现服务端和客户端的实时双向数据传输-WebSocket简单了解

    WebSocket 前段时间项目中遇到了消息推送的问题,当时采用客户端轮询,每隔 5s 请求一次数据.由于轮询的效率低,非常浪费资源.后面准备把轮询调整为使用 WebSocket 来建立连接,实现推送 ...

  9. 图解高性能网络架构:Reactor 和 Proactor

    小林,来了. 这次就来图解 Reactor 和 Proactor 这两个高性能网络模式. 别小看这两个东西,特别是 Reactor 模式,市面上常见的开源软件很多都采用了这个方案,比如 Redis.N ...

  10. C#开发医学影像胶片打印系统(一):万能花式布局的实现思路

    本篇文章将介绍开发医学影像胶片打印系统(printscu模式)遇到不规则排版时的一种思路, 一般来讲,医院打印胶片时都是整张胶片打印,但有时需要将多个病人或一个病人的多个检查打印在同一张胶片上, 这时 ...