深入探讨opencv图像矫正算法实战

作者:唯有自己强大 时间:2022-06-03 16:20:39 

摘要

在机器视觉中,对于图像的处理有时候因为放置的原因导致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图像矫正算法实战

来源:https://www.cnblogs.com/xyf327/p/14791077.html

标签:opencv,图像矫正
0
投稿

猜你喜欢

  • Python运行提示缺少模块问题解决方案

    2023-06-24 02:16:23
  • Web开发技术发展史话

    2011-04-25 19:16:00
  • Python图像处理实现两幅图像合成一幅图像的方法【测试可用】

    2022-11-13 08:36:56
  • Python实现读取大量Excel文件并跨文件批量计算平均值

    2023-10-21 06:53:12
  • Python3.6正式版新特性预览

    2023-11-02 09:27:09
  • MyBatis 如何写配置文件和简单使用

    2024-01-26 08:01:18
  • getAllResponseHeaders获取网页的http头信息代码

    2010-03-31 14:31:00
  • ajax中get和post的说明及使用与区别

    2024-04-29 13:58:17
  • SQL对冗余数据的删除重复记录只保留单条的说明

    2024-01-17 07:24:11
  • Pycharm在创建py文件时,自动添加文件头注释的实例

    2023-05-06 19:21:51
  • Python实现功能完整的个人员管理程序

    2021-03-26 02:31:15
  • Symfony2框架学习笔记之HTTP Cache用法详解

    2024-03-26 23:39:12
  • pytorch 实现在预训练模型的 input上增减通道

    2023-12-02 00:49:33
  • Python列表删除元素del、pop()和remove()的区别小结

    2021-12-02 07:32:41
  • Python操作Redis数据库的超详细教程

    2024-01-13 04:37:30
  • PHP之数组学习

    2024-05-02 17:35:43
  • CSS的另类拼图___减少HTTP请求

    2009-05-28 19:05:00
  • python爬虫入门教程--利用requests构建知乎API(三)

    2022-12-17 14:42:45
  • mysqldump备份还原和mysqldump导入导出语句大全详解

    2024-01-14 07:35:14
  • JS生成一维码(条形码)功能示例

    2024-04-26 17:12:30
  • asp之家 网络编程 m.aspxhome.com