图像的旋转、切边操作

一.问题描述
平常扫描到的文件等等,图像周围可能会有背景等多余的东西,需要进行切边处理,去掉边缘的空白,这样才使得扫描到的文件更为真实。
若采用人工操作成本与时间较高。
希望程序自动实现、高效、准确。

实现思路:通过边缘检测+轮廓检测或者直线检测最大外接矩形实现。

二.(若图像无旋转角度时)
程序思路:
将图片转为灰度图,用Canny算子进行图像的边缘检测;
寻找出图像的所有轮廓;
绘制出图像最大的轮廓(即图片中最大的框),这里需要设置一个阈值,以便筛选出最大的轮廓(我设置为原图长宽的0.75倍);
图像的最大轮廓绘制出来后,定义bbox为最大轮廓的最小外接矩形,用boundingRect()函数;
最后,srcImage(bbox)将剪切好了的图像显示出来。
(通俗理解为,先找出图像的最大外框,将外框内部的内容提取出来)
运行环境:VS2019+Opencv4.1.1

#include <iostream>
#include <opencv2/opencv.hpp>
#include <math.h>

using namespace cv;
using namespace std;

#define WINDOW_NAME1 "轮廓图"
#define WINDOW_NAME2 "效果图"

int threshold_value = 50;  //滑动条开始位置
int max_level = 255;  //滑动条的最大量程
void FindROI(int, void*);
Mat srcImage, gray_srcImage, dstImage;

int main()
{
	srcImage = imread("E:\\pictures\\39.png");
 	if (!srcImage.data)
 	{
 		cout << "图像读取错误,请检查路径!" << endl;
  		return -1;
 	}
 	namedWindow("原图像", WINDOW_AUTOSIZE);
 	imshow("原图像", srcImage);
 	namedWindow(WINDOW_NAME1, WINDOW_AUTOSIZE);
 	namedWindow(WINDOW_NAME2, WINDOW_AUTOSIZE);

	//创建滑动条
 	createTrackbar("阈值:", WINDOW_NAME1, &threshold_value, max_level, FindROI);
 	FindROI(threshold_value, 0);

	waitKey(0);
 	return 0;
}

//要切边的话,要进行边缘发现,首先进行二值化,设置一个滑动条来看效果
void FindROI(int, void*)
{
	cvtColor(srcImage, gray_srcImage, COLOR_BGR2GRAY);
 	Mat canny_output;  //Canny算子
 	//用Canny进行图像的边缘检测
 	Canny(gray_srcImage, canny_output, threshold_value, threshold_value * 2, 3, false);

	//寻找出图像的轮廓
 	vector<vector<Point>> contours;
 	vector<Vec4i> hierarchy;
 	findContours(canny_output, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));

	//上一步操作寻找到了图像的轮廓,这一步我们把轮廓绘制出来
 	//因为是寻找了图像的所有轮廓,而我们只需要最大的轮廓,这里设定一个范围:原图长宽的0.75倍
 	int min_weight = srcImage.cols * 0.75;
 	int min_height = srcImage.rows * 0.75;
 	RNG rng(12345);
 	Mat drawImage = Mat::zeros(srcImage.size(), CV_8UC3);  //定义一张图用来在上面画旋转矩形
 	Rect bbox;

	//size_t是表示长度(尺寸)的类型,用于保存一些长度信息, 比如数组的长度、字符串的长度等。
 	for (size_t t = 0; t < contours.size(); t++)
 	{
 		//RotatedRect是一个存储旋转矩形的类,用来存储最小外包矩形函数minAreaRect( )和椭圆拟合函数fitEllipse( )返回的结果。
  		//之所以定位旋转矩形,是为了防止原图中的图像有偏移
  		RotatedRect minRect = minAreaRect(contours[t]);
  		float degree = abs(minRect.angle);
  		cout << "当前的角度:" << degree << endl;
  		if (minRect.size.width > min_weight&& minRect.size.height > min_height&& minRect.size.width < (srcImage.cols - 5))
  		{
  			//保证绘制的旋转矩形轮廓:大于设置的阈值原图长宽的0.75倍,同时小于原图的宽度-5个像素
   			Point2f pts[4];  //定义要绘制矩形的四个点
   			minRect.points(pts);
   			bbox = minRect.boundingRect();//boundingRect函数是用来计算轮廓的最小外接矩形
   			Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
   			for (int i = 0; i < 4; i++)
   			{
   				//矩形是4根线,(i+1)%4,保证最后一根线与第一根线连接起来
    				line(drawImage, pts[i], pts[(i + 1) % 4], color, 2, 8, 0);
   			}
  		}
 	}
 	imshow(WINDOW_NAME1, drawImage);

	if (bbox.width > 0 && bbox.height > 0)
	{
		Mat roiImage = srcImage(bbox);
  		imshow(WINDOW_NAME2, roiImage);
	}
}

运行结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
三.(若图像有旋转角度时)
程序思路:
先将有旋转角度的图片通过上述的方法,绘制出轮廓,从而获得旋转的角度,然后先把图像旋转至正的;
然后继续执行图像无旋转角度的操作即可。

#include <iostream>
#include <opencv2/opencv.hpp>
#include <math.h>

using namespace cv;
using namespace std;

#define WINDOW_NAME1 "原图【1】"
#define WINDOW_NAME2 "轮廓图【2】(图像有旋转时)"
#define WINDOW_NAME3 "旋转后的图【3】"
#define WINDOW_NAME4 "轮廓图【4】(图像矫正后)"
#define WINDOW_NAME5 "效果图【5】"

int threshold_value_1 = 50;  //Check_rotate()函数中滑动条开始位置
int max_level_1 = 255;  //Check_rotate()函数中滑动条的最大量程
int threshold_value_2 = 50;  //FindROI()函数中滑动条开始位置
int max_level_2 = 255;  //FindROI()函数中滑动条的最大量程

void Check_rotate(int, void*);  //检查图片是否有旋转
void FindROI(int, void*);

Mat srcImage;  //源图像
Mat gray_srcImage1; //Check_rotate()函数中的灰度图
Mat gray_srcImage2; //FindROI()函数中的灰度图
Mat rotate_dstImage; //旋转矫正后的图像

int main()
{
	srcImage = imread("E:\\pictures\\40.png");
 	if (!srcImage.data)
 	{
 		cout << "图像读取错误,请检查路径!" << endl;
  		return -1;
 	}
 	namedWindow(WINDOW_NAME1, WINDOW_AUTOSIZE);
 	imshow(WINDOW_NAME1, srcImage);
 	namedWindow(WINDOW_NAME2, WINDOW_AUTOSIZE);
 	namedWindow(WINDOW_NAME3, WINDOW_AUTOSIZE);
 	namedWindow(WINDOW_NAME4, WINDOW_AUTOSIZE);
 	namedWindow(WINDOW_NAME5, WINDOW_AUTOSIZE);

	//创建滑动条
 	createTrackbar("阈值:", WINDOW_NAME2, &threshold_value_1, max_level_1, Check_rotate);
 	Check_rotate(threshold_value_1, 0);
 	createTrackbar("阈值:", WINDOW_NAME4, &threshold_value_2, max_level_2, FindROI);
 	FindROI(threshold_value_2, 0);

	waitKey(0);
 	return 0;
}

void Check_rotate(int, void*)
{
	cvtColor(srcImage, gray_srcImage1, COLOR_BGR2GRAY);
 	Mat canny_output1;  //Canny算子
 	//用Canny进行图像的边缘检测
 	Canny(gray_srcImage1, canny_output1, threshold_value_1, threshold_value_1 * 2, 3, false);

	//寻找出图像的轮廓
 	vector<vector<Point>> contours;
 	vector<Vec4i> hierarchy;
 	findContours(canny_output1, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));

	//寻找轮廓
 	Mat drawImage1 = Mat::zeros(srcImage.size(), CV_8UC3);
 	float max_width = 0;
 	float max_height = 0;
 	double degree = 0;
 	for (int t = 0; t < contours.size(); t++)
 	{
 		//RotatedRect是一个存储旋转矩形的类,用来存储最小外包矩形函数minAreaRect( )和椭圆拟合函数fitEllipse( )返回的结果。
  		//之所以定位旋转矩形,是为了防止原图中的图像有偏移
  		RotatedRect minRect = minAreaRect(contours[t]);
  		degree = abs(minRect.angle);
  		if (degree > 0)
  		{
  			max_width = max(max_width, minRect.size.width);
   			max_height = max(max_height, minRect.size.height);
  		}
 	}

	//绘制轮廓
 	RNG rng(12345); //随机数产生器
 	for (int t = 0; t < contours.size(); t++)
 	{
 		RotatedRect minRect = minAreaRect(contours[t]);
  		if (max_width == minRect.size.width && max_height == minRect.size.height)
  		{
  			degree = abs(minRect.angle);
   			Point2f pts[4];
   			minRect.points(pts);
   			Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
   			for (int i = 0; i < 4; i++)
   			{
   				//矩形是4根线,(i+1)%4,保证最后一根线与第一根线连接起来
    				line(drawImage1, pts[i], pts[(i + 1) % 4], color, 2, 8, 0);
   			}
  		}
 	}
 	cout << "max contours width:" << max_width << endl;
 	cout << "max contours height:" << max_height << endl;
 	cout << "max contours angle:" << degree << endl;
 	imshow(WINDOW_NAME2, drawImage1);

	//前面的操作已经找到了旋转图像的最大轮廓
 	//下面进行对图像的旋转操作
 	Point2f center(srcImage.cols / 2, srcImage.rows / 2);//首先找到图像的中心
 	Mat rotm = getRotationMatrix2D(center, -degree, 1.0);//第二个参数以实际情况为准
 	//计算二维旋转变换矩阵:getRotationMatrix2D()函数
 	//getRotationMatrix2D(Point2f center, double angle, double scale)
 	//第一个参数,Point2f类型的center,表示源图像的旋转中心
 	//第二个参数,double类型的angle,旋转角度。角度为正值表示向逆时针旋转(坐标原点是左上角)
 	//第三个参数,double类型的scale,缩放系数
 	warpAffine(srcImage, rotate_dstImage, rotm, srcImage.size(), INTER_LINEAR, 0, Scalar(255, 255, 255));
 	//进行仿射变换:warpAffine()函数
 	//第一个参数:输入图像,即源图像,Mat类型
 	//第二个参数:输出图像,与输入图像有一样的尺寸和类型
 	//第三个参数,2*3的变换矩阵
 	//第四个参数,表示输出图像的尺寸,Size类型
 	//第五个参数,插值方式的标识符,默认值INTER_LINEAR(线性插值)
 	//第六个参数,边界像素模式
 	//第七个参数,边界颜色,指旋转之后,不是原图像区域的,我们用白色去填充
 	imshow(WINDOW_NAME3, rotate_dstImage);
}

//要切边的话,要进行边缘检测,首先进行二值化,设置一个滑动条来看效果
void FindROI(int, void*)
{
	cvtColor(rotate_dstImage, gray_srcImage2, COLOR_BGR2GRAY);
 	Mat canny_output2;  //Canny算子
 	//用Canny进行图像的边缘检测
 	Canny(gray_srcImage2, canny_output2, threshold_value_2, threshold_value_2 * 2, 3, false);

	//寻找出图像的轮廓
 	vector<vector<Point>> contours;
 	vector<Vec4i> hierarchy;
 	findContours(canny_output2, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));

	//上一步操作寻找到了图像的轮廓,这一步我们把轮廓绘制出来
 	//因为是寻找了图像的所有轮廓,而我们只需要最大的轮廓,这里设定一个范围:原图长宽的0.5倍
 	int max_width = rotate_dstImage.cols * 0.5;
 	int max_height = rotate_dstImage.rows * 0.5;
 	//这里的0.5要根据具体的情况而定,如果值选的太大,可能会出现找不到轮廓的情况,当时设置的是0.75,绘制不出轮廓。找了三个小时的错误!!!!!哎,难受!!!
 	RNG rng(12345);
 	Mat drawImage2 = Mat::zeros(rotate_dstImage.size(), CV_8UC3);  //定义一张图用来在上面画轮廓
 	Rect bbox;

	//size_t是表示长度(尺寸)的类型,用于保存一些长度信息, 比如数组的长度、字符串的长度等。
 	for (int t = 0; t < contours.size(); t++)
 	{
 		//RotatedRect是一个存储旋转矩形的类,用来存储最小外包矩形函数minAreaRect( )和椭圆拟合函数fitEllipse( )返回的结果。
  		//之所以定位旋转矩形,是为了防止原图中的图像有偏移
  		RotatedRect minRect = minAreaRect(contours[t]);
  		float degree = abs(minRect.angle);
  		cout << "当前的角度:" << degree << endl;
  		if (minRect.size.width > max_width&& minRect.size.height > max_height&& minRect.size.width < (rotate_dstImage.cols - 5))
  		{
  			//保证绘制的旋转矩形轮廓:大于设置的阈值原图长宽的0.75倍,同时小于原图的宽度-5个像素
   			Point2f pts[4];  //定义要绘制矩形的四个点
   			minRect.points(pts);
   			bbox = minRect.boundingRect();//boundingRect函数是用来计算轮廓的最小外接矩形
   			Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
   			for (int i = 0; i < 4; i++)
   			{
   				//矩形是4根线,(i+1)%4,保证最后一根线与第一根线连接起来
    				line(drawImage2, pts[i], pts[(i + 1) % 4], color, 2, 8, 0);
   			}
  		}
 	}
 	imshow(WINDOW_NAME4, drawImage2);

	if (bbox.width > 0 && bbox.height > 0)
	{
		Mat roiImage = rotate_dstImage(bbox);
  		imshow(WINDOW_NAME5, roiImage);
	}
}

运行结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
PS:设置滚动条的目的:通过调整阈值的大小,找到最适合的轮廓,因为对于不同的图片其阈值是不同的。

发布了25 篇原创文章 · 获赞 0 · 访问量 454

猜你喜欢

转载自blog.csdn.net/qq_45445740/article/details/103571482