openCV文档图像校正之巧用cvMinAreaRect2()函数

openCV文档图像校正之巧用cvMinAreaRect2()函数

996.icu LICENSE

  • 问题
  • 解决方案
  • 原理探究:什么是cvMinAreaRect2()函数
  • 总结

阅读之前注意:

本文阅读建议用时:45min
本文阅读结构如下表:

项目下属项目测试用例数量
问题0
解决方案1
原理探究:什么是cvMinAreaRect2()函数1
总结0

问题

对于倾斜的文档图像,我们首先需要找出倾斜角,之后旋转校正即可。
比如把下图
原图
校正为下图
这里写图片描述

解决方案

之前我有写过系列文档图像倾斜校正的Matlab程序,思路可以借鉴。但是现在我提出来的解决方法是另外一种思路:当我们把文字经过闭运算、膨胀、腐蚀等处理后得到连成大概像一个矩形的长条。类似下图:
这里写图片描述
那么我们可以找到最长的长条。之后如果我们能够计算出长条的外接矩形的话,外接矩形的倾斜角即是图像的倾斜角!事实上,openCV给我们提供了非常方便的函数来寻找这样的矩形,使用cvMinAreaRect2()函数即可得到这个长条的外接矩形,而外接矩形的倾斜角能够直接查询到。
以下是具体代码(注意看注释):

//主要应用了cvMinAreaRect2()函数 得到线的倾斜角
#include <iostream>  
#include <opencv2/core/core.hpp>  
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2/opencv.hpp>//如果要包含所有库

using namespace cv;

//图片旋转操作 
void imrotate(Mat& src, Mat& dst, double angle)
{
	cv::Point2f center((float)src.cols / 2, (float)src.rows / 2);
	cv::Mat rot = cv::getRotationMatrix2D(center, angle, 1);
	cv::Rect bbox = cv::RotatedRect(center, src.size(), angle).boundingRect();

	rot.at<double>(0, 2) += bbox.width / 2.0 - center.x;
	rot.at<double>(1, 2) += bbox.height / 2.0 - center.y;
	cv::warpAffine(src, dst, rot, bbox.size());
}

void main()
{
	int i = 0, j = 0;
	Mat img = imread("26.bmp", 0);//读取图像img。0表示转换为灰度图像读入
	Mat saveImg = imread("26.bmp", 0);
	//imshow("savwImg", saveImg);
	if (img.empty())
	{
		printf("当前文件目录下没有那张图片\n");
		system("pause"); exit(0);
	}
	int row = img.rows;
	int col = img.cols;
	imshow("原图", img);

	//图像预处理部分--------------------------------------------------------
	threshold(img, img, 0, 255, CV_THRESH_OTSU);//最大类间方差法进行二值化处理
	img = ~img;//把img取反,变为黑色背景,白色字,便于形态学处理

	morphologyEx(img, img, MORPH_CLOSE, Mat(18, 18, CV_8U), Point(-1, -1), 1);//形态学闭操作
	dilate(img, img, Mat(12, 12, CV_8U), Point(-1, -1), 1);//膨胀
	erode(img, img, Mat(18, 18, CV_8U), Point(-1, -1), 1);//腐蚀

	Size sz;
	pyrDown(img, img, sz, BORDER_DEFAULT);//缩小图片
	pyrDown(img, img, sz, BORDER_DEFAULT);//缩小图片
	imwrite("27.png", img);

	//获取倾斜角------------------------------------------------------
	IplImage imgTmp = img;//为了方便程序运行,这里采用老版的图像数据类型
	IplImage  *src = cvCloneImage(&imgTmp);//图像数据的深拷贝
	cvShowImage("预处理后的图像", src);

	CvMemStorage* storage = cvCreateMemStorage(0);
	CvSeq* contour = NULL;//轮廓
	CvSeq* maxLenContour = NULL;//最长长条的轮廓
	int count = cvFindContours(src, storage, &contour, \
		sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);//找出各个连通域
	CvBox2D box;//外接矩形
	CvBox2D maxLenBox;//最长长条的外接矩形
	float boxLen = 0.;//矩形的长或者宽
	for (; contour != 0; contour = contour->h_next)//找到最长的长条
	{
		box = cvMinAreaRect2(contour, 0);//调用cvMinAreaRect2()函数返回外接矩形给box
		if (boxLen < box.size.width)
		{
			boxLen = box.size.width;
			maxLenContour = contour;
			maxLenBox = box;
		}
		if (boxLen < box.size.height)
		{
			boxLen = box.size.height;
			maxLenContour = contour;
			maxLenBox = box;
		}
	}
	printf("maxBoxWidth is %f\n", maxLenBox.size.width);
	printf("maxBoxHeight is %f\n", maxLenBox.size.height);
	printf("angle is %f\n", maxLenBox.angle);

	//旋转校正-------------------------------------------------------
	Mat newImg;

	if (maxLenBox.size.width >= maxLenBox.size.height)
	{
		imrotate(saveImg, newImg, maxLenBox.angle);
		printf("rotate angle is %f\n", maxLenBox.angle);
	}
	else
	{
		imrotate(saveImg, newImg, 90 + maxLenBox.angle);
		printf("rotate angle is %f\n", 90 + maxLenBox.angle);
	}
	pyrDown(newImg, newImg, sz, BORDER_DEFAULT);//缩小图片
	pyrDown(newImg, newImg, sz, BORDER_DEFAULT);//缩小图片
	namedWindow("校正图");
	imshow("校正图", newImg);
	waitKey(100000);//等待100秒后窗口关闭
}

原理探究:什么是cvMinAreaRect2()函数

以下借鉴说明:函数cvMinAreaRect2()可以返回一个包围轮廓最小的长方形,这个长方形可以是倾斜的;请看下图8-7中的坦克,该函数的参数和cvBoundingRect()的相似。opencv的数据类型CvBox2D就是用来表述这样的长方形状的。
这里写图片描述

但是上图还有一定的错误,我们知道:在openCV中的默认坐标系是这样的,左上角是坐标原点,从原点往右是X轴正半轴,从原点往下是Y轴正半轴。而函数cvMinAreaRect2()返回的数据类型CvBox2D,其定义中的宽是图片顺时针转先碰到X轴的那条边,因此宽可能比长要大,而定义中的角度则是宽与X轴的夹角,因此角度永远是负值。
按照这样的规则,图8-7就是错误的,除了可以形象表示“长方形可以是倾斜的”就没有对的了。。。
我们知道,一张图片,要么是左倾斜(大于或小于45度),要么是右倾斜(大于或小于45度),因此我们测试以下的四副图片
这里写图片描述这里写图片描述这里写图片描述这里写图片描述
得到的角度分别是-77.9度、-13.8度、-13.7度、-58.7度。

具体参考以下代码(测试图片可以从这里获取):

#include <iostream>  
#include <opencv2/core/core.hpp>  
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2/opencv.hpp>//如果要包含所有库

using namespace cv;

//图片旋转操作 
void imrotate(Mat& src, Mat& dst, double angle)
{
	cv::Point2f center((float)src.cols / 2, (float)src.rows / 2);
	cv::Mat rot = cv::getRotationMatrix2D(center, angle, 1);
	cv::Rect bbox = cv::RotatedRect(center, src.size(), angle).boundingRect();

	rot.at<double>(0, 2) += bbox.width / 2.0 - center.x;
	rot.at<double>(1, 2) += bbox.height / 2.0 - center.y;
	cv::warpAffine(src, dst, rot, bbox.size());
}

void main()
{
	Mat img = imread("rhh.bmp", 0);
	Mat saveImg = imread("rhh.bmp", 0);
	if (img.empty())
	{
		printf("当前文件目录下没有那张图片\n");
		system("pause"); exit(0);
	}
	threshold(img, img, 0, 255, CV_THRESH_OTSU);//最大类间方差法
	imshow("原图", img);

	img = ~img;

	IplImage imgTmp = img;
	IplImage  *src = cvCloneImage(&imgTmp);
	//cvShowImage("Source", src);
	CvMemStorage* storage = cvCreateMemStorage(0);
	CvSeq* contour = NULL;//轮廓
	CvSeq* maxLenContour = NULL;//最长长条的轮廓
	int count = cvFindContours(src, storage, &contour, \
		sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);//找出各个连通域
	CvBox2D box;//外接矩形
	CvBox2D maxLenBox;//最长长条的外接矩形
	float boxLen = 0.;//矩形的长或者宽
	for (; contour != 0; contour = contour->h_next)//找到最长的长条
	{
		box = cvMinAreaRect2(contour, 0);//调用cvMinAreaRect2()函数返回外接矩形给box
		if (boxLen < box.size.width)
		{
			boxLen = box.size.width;
			maxLenContour = contour;
			maxLenBox = box;
		}
		if (boxLen < box.size.height)
		{
			boxLen = box.size.height;
			maxLenContour = contour;
			maxLenBox = box;
		}
	}
	printf("maxBoxWidth is %f\n", maxLenBox.size.width);
	printf("maxBoxHeight is %f\n", maxLenBox.size.height);
	printf("angle is %f\n", maxLenBox.angle);
	
	Mat newImg;
	if (maxLenBox.size.width >= maxLenBox.size.height)
	{
		imrotate(saveImg, newImg, maxLenBox.angle);
		printf("rotate angle is %f\n", maxLenBox.angle);
	}	
	else
	{
		imrotate(saveImg, newImg, 90 + maxLenBox.angle);
		printf("rotate angle is %f\n", 90 + maxLenBox.angle);
	}	
	namedWindow("校正图");
	imshow("校正图", newImg);
	waitKey(100000);
}

总结

本篇博客的核心是:弄清楚cvMinAreaRect2()函数返回的数据类型CvBox2D中的参数的定义。

如果本文对你有帮助,不如请我一罐可乐吧 🍼

在这里插入图片描述

友情链接1:cvBox2D和RotatedRect中返回的角度angle详解
友情链接2:opencv轮廓及点在轮廓内判断

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值