OpenCV实战(二)——文件扫描件的切边

       这是来源于一个真实的案例,扫描仪扫描到的法律文件,需要切除白边,去掉边缘空白,这样看上去才更真实。如果采用人工操作,其成本和时间花费都太高了。我们希望通过一个程序,既准确又高效地完成任务。

      其实本案例不需要扩展模块当中的特征提取就可以实现,运用基本的图像处理的知识,我们来尝试。

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

     首先我们拿到一种倾斜的照片,我们在进行切边之前需要校正图片,即对图像进行旋转操作,程序如下:

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


using namespace cv;
using namespace std;

Mat src_img, gray_img, dst_img;
int threshold_value = 100;      // 定义阈值,全局变量
int max_level = 255;          // 定义最大阈值,全局变量
const string output_win = "Contours Result";
const string roi_win = "Final Result";
void FindROI(int, void*);           //声明函数,用于找到兴趣区域
void Check_Skew();        //声明函数,用于纠正倾斜  


int main( ) 
{

	src_img = imread("456.png");
	if (src_img.empty())
	{
		printf("could not load the image...\n");
		return -1;
	}
	namedWindow("原图", CV_WINDOW_AUTOSIZE);
	imshow("原图", src_img);
	Check_Skew();         //纠正倾斜
	
	/*
	src_img = imread("2018.4.25.jpg");
	if (src_img.empty())
	{
		printf("could not load the image...\n");
		return -1;
	}
	namedWindow(output_win, CV_WINDOW_AUTOSIZE);
	// 接下来提取兴趣区域
	createTrackbar("Threshold:",output_win, &threshold_value, max_level, FindROI);
    FindROI(0, 0);
	*/
	waitKey(0);
	return 0;
}

void Check_Skew()
{
	Mat canny_output;          
	cvtColor(src_img, gray_img, COLOR_BGR2GRAY);         //将原图转化为灰度图
	Canny(gray_img, canny_output, threshold_value, threshold_value * 2, 3, false);      // canny边缘检测

	vector<vector<Point>> contours;
	vector<Vec4i> hireachy;
	findContours(canny_output, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));    // 找到所有轮廓
	Mat drawImg = Mat::zeros(src_img.size(), CV_8UC3);   
	float max_width = 0;       // 定义最大宽度
	float max_height = 0;      // 定义最大高度
	double degree = 0;         // 定义旋转角度
	for (auto t = 0; t < contours.size(); ++t)            // 遍历每一个轮廓   
	{ 
		RotatedRect minRect = minAreaRect(contours[t]);        // 找到每一个轮廓的最小外包旋转矩形,RotatedRect里面包含了中心坐标、尺寸以及旋转角度等信息   
		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 (auto t = 0; t < contours.size();++t) 
	{
		RotatedRect minRect = minAreaRect(contours[t]);    
		if (max_width == minRect.size.width && max_height == minRect.size.height)
		{
			degree = 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)
			{
				line(drawImg, pts[i], pts[(i + 1) % 4], color, 2, 8, 0);
			}
		}
	}
	
	imshow("找到的矩形轮廓", drawImg);
	Point2f center(src_img.cols / 2, src_img.rows / 2);
	Mat rotm = getRotationMatrix2D(center, degree, 1.0);    //获取仿射变换矩阵
	Mat dst;
	warpAffine(src_img, dst, rotm, src_img.size(), INTER_LINEAR, 0, Scalar(255, 255, 255));    // 进行图像旋转操作
	imwrite("123.png", dst);      //将校正后的图像保存下来
	imshow("Correct Image", dst);

}

运行程序,我们可以看到:




再得到正立摆放的图像之后,我们就可以对其进行切变操作了,代码如下:

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


using namespace cv;
using namespace std;

Mat src_img, gray_img, dst_img;
int threshold_value = 100;      // 定义阈值,全局变量
int max_level = 255;          // 定义最大阈值,全局变量
const string output_win = "Contours Result";
const string roi_win = "Final Result";
void FindROI(int, void*);           //声明函数,用于找到兴趣区域
void Check_Skew();        //声明函数,用于纠正倾斜  


int main( ) 
{
	/*
	src_img = imread("456.png");
	if (src_img.empty())
	{
		printf("could not load the image...\n");
		return -1;
	}
	namedWindow("原图", CV_WINDOW_AUTOSIZE);
	imshow("原图", src_img);
	Check_Skew();         //纠正倾斜
	*/
	src_img = imread("123.png");
	if (src_img.empty())
	{
		printf("could not load the image...\n");
		return -1;
	}
	namedWindow(output_win, CV_WINDOW_AUTOSIZE);
	// 接下来提取兴趣区域
	createTrackbar("Threshold:",output_win, &threshold_value, max_level, FindROI);
    FindROI(0, 0);
	
	waitKey(0);
	return 0;
}
/*
void Check_Skew()
{
	Mat canny_output;          
	cvtColor(src_img, gray_img, COLOR_BGR2GRAY);         //将原图转化为灰度图
	Canny(gray_img, canny_output, threshold_value, threshold_value * 2, 3, false);      // canny边缘检测

	vector<vector<Point>> contours;
	vector<Vec4i> hireachy;
	findContours(canny_output, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));    // 找到所有轮廓
	Mat drawImg = Mat::zeros(src_img.size(), CV_8UC3);   
	float max_width = 0;       // 定义最大宽度
	float max_height = 0;      // 定义最大高度
	double degree = 0;         // 定义旋转角度
	for (auto t = 0; t < contours.size(); ++t)            // 遍历每一个轮廓   
	{ 
		RotatedRect minRect = minAreaRect(contours[t]);        // 找到每一个轮廓的最小外包旋转矩形,RotatedRect里面包含了中心坐标、尺寸以及旋转角度等信息   
		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 (auto t = 0; t < contours.size();++t) 
	{
		RotatedRect minRect = minAreaRect(contours[t]);    
		if (max_width == minRect.size.width && max_height == minRect.size.height)
		{
			degree = 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)
			{
				line(drawImg, pts[i], pts[(i + 1) % 4], color, 2, 8, 0);
			}
		}
	}
	
	imshow("找到的矩形轮廓", drawImg);
	Point2f center(src_img.cols / 2, src_img.rows / 2);
	Mat rotm = getRotationMatrix2D(center, degree, 1.0);    //获取仿射变换矩阵
	Mat dst;
	warpAffine(src_img, dst, rotm, src_img.size(), INTER_LINEAR, 0, Scalar(255, 255, 255));    // 进行图像旋转
	imwrite("2018.4.25.jpg", dst);
	imshow("Correct Image", dst);

}
*/

void FindROI(int, void*) 
{
	printf("**************当前阈值:%d******************************\n", threshold_value);         
	cvtColor(src_img, gray_img, COLOR_BGR2GRAY);      //将原图转化为灰度图
	Mat canny_output;
	Canny(gray_img, canny_output, threshold_value, threshold_value * 2, 3, false);                // canny边缘检测
	imshow("canny_output", canny_output);
	vector<vector<Point>> contours;
	vector<Vec4i> hireachy;
	findContours(canny_output, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));    // 调用API,找到轮廓

	// 筛选contours中的轮廓,我们需要最大的那个轮廓
	int min_width = src_img.cols*0.75;          // 矩形的最小宽度	
	int min_height = src_img.rows*0.75;         // 矩形的最小高度
	RNG rng(12345);                            //定义一个随机数产生器,用来产生不同颜色的矩形框
	Mat drawImage = Mat::zeros(src_img.size(), CV_8UC3);
	Rect bbox;
	for (auto t = 0; t < contours.size();++t)        // 遍历每一个轮廓
	{
		RotatedRect minRect = minAreaRect(contours[t]);        // 找到每一个轮廓的最小外包旋转矩形,RotatedRect里面包含了中心坐标、尺寸以及旋转角度等信息
		float degree = abs(minRect.angle);                    // 最小外包旋转矩形的旋转角度
		if (minRect.size.width > min_width && minRect.size.height > min_height && minRect.size.width < (src_img.cols - 5))   //筛选最小外包旋转矩形
		{
			printf("current angle : %f\n", degree);
			Mat vertices;       // 定义一个4行2列的单通道float类型的Mat,用来存储旋转矩形的四个顶点
			boxPoints(minRect, vertices);    // 计算旋转矩形的四个顶点坐标
			bbox = boundingRect(vertices);   //找到输入点集的最小外包直立矩形,返回Rect类型
			cout <<"最小外包矩形:"<< bbox << endl;
			Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));   //产生随机颜色
			for (int i = 0; i < 4; i++)             // 遍历每个旋转矩形的四个顶点坐标
			{
				// 在相邻的顶点之间绘制直线
				Mat p1 = vertices.row(i); // 使用成员函数row(i)和col(j)得到矩阵的第i行或者第j列,返回值仍然是一个单通道的Mat类型
				int j = (i + 1) % 4;
				Mat p2 = vertices.row(j);
				Point p1_point = Point(p1.at<float>(0,0), p1.at<float>(0, 1)); //将Mat类型的顶点坐标转换为Point类型
				Point p2_point = Point(p2.at<float>(0, 0), p2.at<float>(0, 1));
				line(drawImage, p1_point, p2_point, color, 2, 8, 0);    // 根据得到的四个顶点,通过连接四个顶点,将最小旋转矩形绘制出来
			}
		}
	}
	imshow(output_win, drawImage);
	
	if (bbox.width > 0 && bbox.height > 0 )
	{
		Mat roiImg = src_img(bbox);        //从原图中截取兴趣区域
		namedWindow(roi_win, CV_WINDOW_AUTOSIZE);
		imshow(roi_win, roiImg);
	}
	
	return;
}

运行程序,可以看到:



改变阈值,可以看到:


效果还是蛮好的!



    

猜你喜欢

转载自blog.csdn.net/weixin_41695564/article/details/80077706