首页 > 编程语言 >OpenCV实战案例——校正+切边[C++]

OpenCV实战案例——校正+切边[C++]

时间:2024-06-11 10:35:11浏览次数:19  
标签:vvContours Mat 代码 rng C++ uniform OpenCV 切边 255

0.前言
本文以实战案例为背景,讲述如何使用计算机图形学知识完成需求,实现最终效果。本文包含实战案例素材以及过程代码讲解,方便读者理解。
1.案例需求
某公司打算开发一款用于提取学生作业本的程序,学生用手机拍摄自己的作业上传到程序,程序进行处理最终提取出作业本区域方便老师批改。
下图(图1-1)为某学生提交的作业本俯拍图片。

该公司希望该程序将图片裁剪校正使其达到方便教师批改的大小。最终效果图如下(图1-2)所示。

2.处理思想

  • 由于环境因素,学生上传的图片可能存在较多的噪点,不利于计算机处理,故可以采取高斯模糊进行降噪处理,方便后续提取特征。
  • 为了更方便提取图像特征,应将图像灰度化、二值化,使其尽可能显示出图片边缘纹理特征,对于处理期间的噪声点可以采用形态学操作消除。
  • 通过需求结果的特点,可以使用轮廓查找findContours()函数查找二值图中的轮廓,根据大小、型状等外显特征进行过滤。
  • 考虑到学生上传的图片可能存在倾斜情况,而普通的矩形查找(boundingRect函数)难以胜任该工作,所以应选择能够衡量角度特征的矩形查找函数,故本文选取minAreaRect()函数查找最小外接矩形。
  • 获取矩形大小、角度等特征后,可以通过仿射变换校正图片,ROI(感兴趣区域)提取获取最终结果。

3.代码实现

点击查看代码
//读取图像
Mat mSrc = imread(path1, ImreadModes::IMREAD_COLOR);
imshow("源图像", mSrc);
上述代码读取了图片加载到内存中并显示,显示结果如下图(图3-1)所示。

点击查看代码
//高斯模糊
Mat mGaussian;
GaussianBlur(mSrc, mGaussian, Size(3, 3), 1);

//转灰度
Mat mGray;
cvtColor(mGaussian, mGray, ColorConversionCodes::COLOR_BGR2GRAY);

//二值化
Mat mBin;
threshold(mGray, mBin, 244, 255, ThresholdTypes::THRESH_TOZERO);

//膨胀
Mat mDilateKernal = getStructuringElement(MorphShapes::MORPH_RECT, Size(3, 3));
dilate(mBin, mBin, mDilateKernal, Point(-1, -1));
imshow("二值图", mBin);
上述代码将源图像高斯模糊去噪,然后转为灰度图像,再通过阈值将灰度图像转变成二值图像,最后通过膨胀操作处理细节,使图像中特征不明显区域特征加强,方便提取轮廓。getStructuringElement函数用于获取核矩阵,常见的核矩阵有十字型、矩形、圆形等。最终显示结果如下图(图3-2)所示。

通过图3-2可以明显看到学生的作业本轮廓,但由于背景干扰,仍出现大量的噪点无法去除。

点击查看代码
//查找轮廓
vector<vector<Point>> vvContours;
vector<Vec4i> vHierarchy;
findContours(mBin, vvContours, vHierarchy, RetrievalModes::RETR_EXTERNAL, ContourApproximationModes::CHAIN_APPROX_SIMPLE);

//绘制轮廓
RNG rng(0);
Mat mContoursImg=mSrc.clone();
for (int a = 0; a < vvContours.size(); ++a)
{
	drawContours(mContoursImg, vvContours, a, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), 1, LineTypes::LINE_AA);
}
imshow("轮廓", mContoursImg);

上述代码使用findContours()函数查找出了所有的轮廓并绘制,每个轮廓的颜色随机生成,其中变量vHierarchy并没有使用到,显示结果如下图(图3-3)所示。

点击查看代码
//查找最小外接矩形
vector<RotatedRect> vRotatedRects;
for (int a = 0; a < vvContours.size(); ++a)
{
	vRotatedRects.emplace_back(minAreaRect(vvContours[a]));
}
对于每一个轮廓都生成一个最小外接矩形并保存在vRotatedRects向量中。
点击查看代码
//过滤不合格的
vector<RotatedRect> vGoodRotatedRects;
for (int a = 0; a < vRotatedRects.size(); ++a)
{
	Size2f sz_rect = vRotatedRects.at(a).size;
	if (sz_rect.width >= 100 && sz_rect.height>=100)
	{
		vGoodRotatedRects.push_back(vRotatedRects.at(a));
	}
}
通过minAreaRect()函数得到了大量的最小外接矩形,但是仅有少数是符合实际情况,所以需要对找到的轮廓进行过滤。上述代码根据轮廓的大小特征进行过滤,过滤掉宽度小于100且高度小于100的矩形,当然,仅设置阈值为100是比较随意的,读者也可以添加其他判断条件过滤,例如宽高比例等。
点击查看代码
//绘制矩形
Mat mRectsImg = mSrc.clone();
Point2f* p1 = new Point2f[4];
vGoodRotatedRects.front().points(p1);
Scalar color(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
for (int a = 0; a < 4; ++a)
{
	line(mRectsImg, p1[a], p1[(a + 1) % 4], color,3);
}
delete p1;
p1 = nullptr;
imshow("最小外接矩形", mRectsImg);
上述代码绘制过滤后的最小外接矩形,其中(a + 1) % 4可以实现线条的首尾相连效果,最终显示效果如下图(图3-4)所示。

通过图3-4可以明显的看到学生上传的作业本轮廓被提出出来了,与最终预期结果又进一步。

点击查看代码
//绘制十字坐标系、方向
Mat mDirectionImg= mRectsImg.clone();
float fAngle = vGoodRotatedRects.front().angle;
Point2f p2fCenter = vGoodRotatedRects.front().center;
cout << "angle:" << fAngle << ",center:" << p2fCenter << endl;
//x轴
line(mDirectionImg, Point(p2fCenter.x - 200, p2fCenter.y), Point(p2fCenter.x + 200, p2fCenter.y), Scalar(0, 0, 255), 2);
//y轴
line(mDirectionImg, Point(p2fCenter.x, p2fCenter.y-200), Point(p2fCenter.x, p2fCenter.y+200), Scalar(0, 0, 255), 2);
imshow("坐标", mDirectionImg);
上述代码以作业本中心点为(0,0)点绘制直角坐标系(红线),绿色标注是笔者后期添加上的,用于后续分析理解,θ值就是控制台输出的angle度数。最终结果如下图(图3-5)所示。 控制台输出结果如下所示:

angle:41.3478,center:[365.829, 209.149]

具体angle的实际含义可以参考笔者的另一篇文章:https://www.cnblogs.com/hello-nullptr/p/18240905

对于图3-5,若将图像中的作业本校正(使黑色签字笔笔尖垂直向下),则需要将整幅图像逆时针旋转angle度即可,本文通过以下代码实现校正。

点击查看代码
//校正
Mat mRotationKernal= getRotationMatrix2D(p2fCenter, fAngle, 1.0);
Mat mCorrectionImg ;
warpAffine(mSrc, mCorrectionImg, mRotationKernal, mSrc.size());
imshow("校正", mCorrectionImg);
通过执行上述代码,输出结果如下图(图3-6)所示。

学生提交的作业图片成功被校正了,接下来仅需提取感兴趣区域(ROI)即可。

点击查看代码
//提取ROI区域
Size sz_rect=vGoodRotatedRects.front().size;
Rect rRoi(p2fCenter.x - (sz_rect.width / 2), p2fCenter.y - (sz_rect.height / 2), sz_rect.width, sz_rect.height);
Mat mRoiImg(mCorrectionImg, rRoi);
imshow("ROI", mRoiImg);
上述代码提取了作业本区域,输出结果如下图(图3-7)所示。

至此结束。

4.完整代码

点击查看代码
	//读取图像
	Mat mSrc = imread(path1, ImreadModes::IMREAD_COLOR);
	imshow("源图像", mSrc);

	//高斯模糊
	Mat mGaussian;
	GaussianBlur(mSrc, mGaussian, Size(3, 3), 1);

	//转灰度
	Mat mGray;
	cvtColor(mGaussian, mGray, ColorConversionCodes::COLOR_BGR2GRAY);

	//二值化
	Mat mBin;
	threshold(mGray, mBin, 244, 255, ThresholdTypes::THRESH_TOZERO);

	//膨胀
	Mat mDilateKernal = getStructuringElement(MorphShapes::MORPH_RECT, Size(3, 3));
	dilate(mBin, mBin, mDilateKernal, Point(-1, -1));
	imshow("二值图", mBin);

	//查找轮廓
	vector<vector<Point>> vvContours;
	vector<Vec4i> vHierarchy;
	findContours(mBin, vvContours, vHierarchy, RetrievalModes::RETR_EXTERNAL, ContourApproximationModes::CHAIN_APPROX_SIMPLE);

	//绘制轮廓
	RNG rng(0);
	Mat mContoursImg=mSrc.clone();
	for (int a = 0; a < vvContours.size(); ++a)
	{
		drawContours(mContoursImg, vvContours, a, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), 1, LineTypes::LINE_AA);
	}
	imshow("轮廓", mContoursImg);

	//查找最小外接矩形
	vector<RotatedRect> vRotatedRects;
	for (int a = 0; a < vvContours.size(); ++a)
	{
		vRotatedRects.emplace_back(minAreaRect(vvContours[a]));
	}

	//过滤不合格的
	vector<RotatedRect> vGoodRotatedRects;
	for (int a = 0; a < vRotatedRects.size(); ++a)
	{
		Size2f sz_rect = vRotatedRects.at(a).size;
		if (sz_rect.width >= 100 && sz_rect.height>=100)
		{
			vGoodRotatedRects.push_back(vRotatedRects.at(a));
		}
	}

	//绘制矩形
	Mat mRectsImg = mSrc.clone();
	Point2f* p1 = new Point2f[4];
	vGoodRotatedRects.front().points(p1);
	Scalar color(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
	for (int a = 0; a < 4; ++a)
	{
		line(mRectsImg, p1[a], p1[(a + 1) % 4], color,3);
	}
	delete p1;
	p1 = nullptr;
	imshow("最小外接矩形", mRectsImg);

	//绘制十字坐标系、方向
	Mat mDirectionImg= mRectsImg.clone();
	float fAngle = vGoodRotatedRects.front().angle;
	Point2f p2fCenter = vGoodRotatedRects.front().center;
	cout << "angle:" << fAngle << ",center:" << p2fCenter << endl;
	//x轴
	line(mDirectionImg, Point(p2fCenter.x - 200, p2fCenter.y), Point(p2fCenter.x + 200, p2fCenter.y), Scalar(0, 0, 255), 2);
	//y轴
	line(mDirectionImg, Point(p2fCenter.x, p2fCenter.y-200), Point(p2fCenter.x, p2fCenter.y+200), Scalar(0, 0, 255), 2);
	imshow("坐标", mDirectionImg);

	//校正
	Mat mRotationKernal= getRotationMatrix2D(p2fCenter, fAngle, 1.0);
	Mat mCorrectionImg ;
	warpAffine(mSrc, mCorrectionImg, mRotationKernal, mSrc.size());
	imshow("校正", mCorrectionImg);

	//提取ROI区域
	Size sz_rect=vGoodRotatedRects.front().size;
	Rect rRoi(p2fCenter.x - (sz_rect.width / 2), p2fCenter.y - (sz_rect.height / 2), sz_rect.width, sz_rect.height);
	Mat mRoiImg(mCorrectionImg, rRoi);
	imshow("ROI", mRoiImg);

标签:vvContours,Mat,代码,rng,C++,uniform,OpenCV,切边,255
From: https://www.cnblogs.com/hello-nullptr/p/18233690

相关文章

  • 华为云短信服务教你用C++实现Smgp协议
    本文分享自华为云社区《华为云短信服务教你用C++实现Smgp协议》,作者:张俭。引言&协议概述中国联合网络通信有限公司短消息网关系统接口协议(SGIP)是中国网通为实现短信业务而制定的一种通信协议,全称叫做ShortMessageGatewayInterfaceProtocol,用于在短消息网关(SMG)和服务提供商(SP......
  • C/C++ 位域注意事项
    C/C++位域注意事项:一、位域定义与布局可以使用无名域位,这样的域位主要用来补齐或调整位置,但不能被直接使用。位域的长度不能大于其类型说明符中指定类型的固有长度。例如,int类型的位域长度不能超过32位,char的位域长度不能超过8位。二、位域的使用与对齐在使用位......
  • LeetCode 算法:缺失的第一个正数c++
    原题链接......
  • C++笔记
    c++一、基础(一)C++初识1.注释//1.单行注释,上方或末尾/*2.多行注释,上方*//* 3.main是一个程序的入口 每个程序都有必须有这么一个函数 有且仅有一个 默认return0,程序状态正常*/// ;是语句结束// ;;;;多个空语句// l+(r-1)/2比(l+r)/2比l+r>>1更安全......
  • c++ stringstream
    转载:https://blog.csdn.net/jllongbell/article/details/79092891v前言:  以前没有接触过stringstream这个类的时候,常用的字符串和数字转换函数就是sscanf和sprintf函数。开始的时候就觉得这两个函数应经很叼了,但是毕竟是属于c的。c++中引入了流的概念,通过流来实现字......
  • C# OpenCvSharp Mat操作-创建Mat-zeros
    在OpenCvSharp中,zeros函数用于创建一个全零的矩阵(Mat对象)。这个函数有多个重载版本,可以根据不同的需求来创建不同形状和类型的全零矩阵。下面我将详细解释每个重载版本,并通过具体的代码示例来说明如何使用它们。......
  • 程序设计与算法(三)C++:第四章poj代码
    课程:北京大学程序设计与算法(三)   MOOCOJ:OpenJudge014:MyString这个题需要写的函数有点多我们来分析一下。charw1[200],w2[100]; while(cin>>w1>>w2){ MyStrings1(w1),s2=s1;//构造函数题目有了,不用写//复制构造函数没有,需要写 MyStrings3......
  • OpenCV RotatedRect类中angle参数解析 C++
    0.前言本文主要探讨RotatedRect类angle的实际含义,为后续学者提供一定的参考。1.官方手册RotatedRect其一构造函数如下图(图1-1)所示。在OpenCV图形坐标系中,水平方向向右为x轴正方向,垂直方向向下为y轴正方向,左上角为(0,0)点。center表示矩形的中心坐标,size中包含了矩形的宽度......
  • DAQmx数据采集---C++版本
    (一)效果展示:(二)采集流程:检索采集设备检索采集通道创建DAQ任务创建采集通道配置采集频率开始采集任务读取采集数据停止采集任务清空采集任务(三)相关接口:该接口可以检测系统已连接的相关采集卡的设备名称paramdata:分配的空间用来存储系统识别到的设备名称......
  • vscode运行C++20,支持模块的实现。
    C++是一个古老的语言,为了跟上时代,一直进行缓慢的演化。在2011年,C++11的发布让这个语言进入21世纪,可以现代化的使用。它有着lambda表达式,auto类型推断。此外使用容器替代低级语言结构,智能指针或其他RAII技术加强了安全编程。我们在编写C++代码应多使用现代化的函数。C++20让编程更......