C++ OpenCV实现文档矫正功能

目录
  • 需求
  • 思路
  • 代码
  • 效果

需求

将一个斜着拍摄的文档矫正成正的,如图所示:

思路

1.读取原始图像,若图像太大可以先进行缩放处理,并获取原始图像的宽和高

2.对图像进行预处理得到边缘,依次进行灰度处理、高斯模糊、边缘检测、膨胀、腐蚀。

3.找到最大的轮廓,并提取角点

  • 进行降噪处理:检测轮廓面积,只保留大于阈值面积的轮廓
  • 计算每个轮廓的周长,使用DP算法计算出轮廓点的个数,规则为周长*0.02
  • 找到图像中面积最大的,且角点为4的轮廓

4.将找到的四个角点排列成一个固定的顺序,排列后的顺序为:左上角-右上角-左下角-右下角

  • 将每个点的xy坐标值相加(x+y),左上角的点的坐标和应该是最小的,右下角的点的坐标和应该是最大的
  • 将每个点的xy坐标值相减(x-y),左下角的点的坐标差应该是最小的,右上角的点的坐标差应该是最大的
  • 重新排列四个角点

5.进行透视变换

  • 根据变换前及变换后的四个角点,创建变换矩阵
  • 根据变换矩阵对图像进行透视变换

6.若透视变换后有一些毛边,按需要进行裁剪,裁剪后重新调整比例

  • 创建一个矩形用来裁剪,并设定四周裁剪5像素
  • 裁剪后重新调整图像宽高

7.显示变换后图像

代码

代码中均有详细注释,请仔细阅读

#include <iostream>
#include<opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace cv;
using namespace std;

// 一些定义
Mat image_origin,     // 原始图像
	image_gray,       // 灰度处理后的图像
	image_blur,       // 高斯模糊处理后的图像
	image_canny,      // 边缘检测后的图像
	image_dilate,     // 膨胀后的图像
	image_erode,      // 腐蚀后的图像
	image_preprocess, // 预处理后的图像
	image_trans,      // 透视变换后的图像
	image_crop;	      // 裁剪后的图像

vector<Point> origin_points,  // 重新排列前的角点
			  reorder_points; // 重新排列后的角点

int origin_width = 0, origin_height = 0;

/*
 * 函数功能:预处理,依次进行灰度处理、高斯模糊、边缘检测、膨胀、腐蚀。
 * 输入:图像,是否显示(0-不显示 1-显示每一步处理后的图像 2-只显示最终图像)
 * */
Mat PreProcess(const Mat& image, int display)
{
	// 灰度处理
	cvtColor(image, image_gray, COLOR_BGR2GRAY);

	// 高斯模糊
	GaussianBlur(image_gray, image_blur, Size(3, 3), 3, 0);

	// 边缘检测(边缘检测前对图像进行一次高斯模糊)
	Canny(image_blur, image_canny, 50, 150);

	// 膨胀和腐蚀(有时进行边缘检测的时候,没有被完全填充,或者无法正确检测,可以用膨胀和腐蚀)
	// 创建一个用于膨胀和腐蚀的内核,后面的数字越大膨胀的越多(数字要用奇数)
	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
	// 膨胀
	dilate(image_canny, image_dilate, kernel);
	// 腐蚀
	//erode(image_dilate, image_erode, kernel);

	// 显示预处理效果
	if(display == 1)
	{
		imshow("灰度处理后的图像", image_gray);
		imshow("高斯模糊后的图像", image_blur);
		imshow("边缘检测后的图像", image_canny);
		imshow("膨胀后的图像", image_dilate);
//		imshow("腐蚀后的图像", image_erode);
	}
	else if(display == 2)
	{
		imshow("预处理后的图像", image_dilate);
	}

	return image_dilate;
}

/*
 * 函数功能:找到面积最大的轮廓
 * 输入:源图像
 * 输出:最大轮廓的四个角点数组
 * */
vector<Point> GetMaxContour(const Mat& img_input)
{
	/*
	 * contours是一个双重向量,向量内每个元素保存了一组由连续的Point点构成的点的集合的向量,每一组Point点集就是一个轮廓。有多少轮廓,向量contours就有多少元素。
	 * 相当于创建了这样一个向量{{Point(),Point()},{},{}}
	 * */
	vector<vector<Point>> contours;
	/*
	 * hierarchy向量内每个元素保存了一个包含4个int整型的数组。向量hiararchy内的元素和轮廓向量contours内的元素是一一对应的,向量的容量相同。
	 * hierarchy向量内每一个元素的4个int型变量——hierarchy[i][0] ~ hierarchy[i][3],分别表示第i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号。
	 * 如果当前轮廓没有对应的后一个轮廓、前一个轮廓、父轮廓或内嵌轮廓的话,则hierarchy[i][0] ~ hierarchy[i][3]的相应位被设置为默认值-1。
	 * */
	vector<Vec4i> hierarchy;

	/*
	 * findContours找到轮廓
	 * 第一个参数:单通道图像矩阵,可以是灰度图,但更常用的是二值图像,一般是经过Canny、拉普拉斯等边缘检测算子处理过的二值图像;
	 * 第二个参数:contours (前文介绍过)
	 * 第三个参数:hierarchy(前文介绍过)
	 * 第四个参数:轮廓的检索模式
	 * 		取值一:CV_RETR_EXTERNAL 只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略
	 * 		取值二:CV_RETR_LIST     检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,所以hierarchy向量内所有元素的第3、第4个分量都会被置为-1,具体下文会讲到
	 * 		取值三:CV_RETR_CCOMP    检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层
	 * 		取值四:CV_RETR_TREE     检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。
	 * 第五个参数:轮廓的近似方法
	 * 		取值一:CV_CHAIN_APPROX_NONE   保存物体边界上所有连续的轮廓点到contours向量内
	 * 		取值二:CV_CHAIN_APPROX_SIMPLE 仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留
	 * 		取值三和四:CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
	 * 第六个参数:Point偏移量,所有的轮廓信息相对于原始图像对应点的偏移量,相当于在每一个检测出的轮廓点上加上该偏移量,且Point可以是负值。不填为默认不偏移Point()
	 * */
	findContours(img_input, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
	/*
	 * drawContours绘出轮廓
	 * 第一个参数:指明在哪幅图像上绘制轮廓。image为三通道才能显示轮廓
	 * 第二个参数:contours
	 * 第三个参数:指定绘制哪条轮廓,如果是-1,则绘制其中的所有轮廓
	 * 第四个参数:轮廓线颜色
	 * 第五个参数:轮廓线的宽度,如果是-1(FILLED),则为填充
	 * */
//	// 不全输出,在下文只输出角点
//	drawContours(image, contours, -1, Scalar(255, 0, 255), 2);

	// 定义轮廓,大小与contours相同,但内层向量中只有角点(例如三角形就是3,四边形就是4,圆形可能七八个)
	vector<vector<Point>> corners_contours(contours.size());

	// 定义边界框,大小与contours相同
	vector<Rect> bounding_box(contours.size());

	vector<Point> biggest_contours;
	double max_area = 0;

	for (int i = 0; i < contours.size(); i++)
	{
		// 检测轮廓面积
		double contour_area = contourArea(contours[i]);
//		cout << area << endl;

		// 假设图像中有噪声,需要将其过滤,只保留面积大于1000的轮廓
		if (contour_area > 1000)
		{
			// 计算每个轮廓的周长
			double contour_perimeter = arcLength(contours[i], true);

			// 使用DP算法计算出轮廓点的个数,规则为周长*0.02
			approxPolyDP(contours[i], corners_contours[i], 0.02 * contour_perimeter, true);

			// 找到图像中面积最大的,且角点为4的轮廓
			if (contour_area > max_area && corners_contours[i].size() == 4 ) {

				//drawContours(image_origin, conPoly, i, Scalar(255, 0, 255), 5);
				biggest_contours = { corners_contours[i][0],corners_contours[i][1] ,corners_contours[i][2] ,corners_contours[i][3] };
				max_area = contour_area;
			}

//			// 只绘制角点之间的边框线,Debug用,取消注释可以看到检测出的所有边界框
//			drawContours(image_origin, corners_contours, i, Scalar(255, 0, 255), 2);
//			rectangle(image_origin, bounding_box[i].tl(), bounding_box[i].br(), Scalar(0, 255, 0), 5);
		}
	}

	// 返回最大的轮廓
	return biggest_contours;
}

/*
 * 函数功能:绘制一些点
 * 输入:点集,颜色
 * */
void DrawPoints(vector<Point> points, const Scalar& color)
{
	for (int i = 0; i < points.size(); i++)
	{
		circle(image_origin, points[i], 10, color, FILLED);
		putText(image_origin, to_string(i), points[i], FONT_HERSHEY_PLAIN, 4, color, 4);
	}
}

/*
 * 函数功能:重新排列四个角点的顺序
 * 最终顺序为: 0  1
 * 			  2  3
 * 			  数组中为左上角-右上角-左下角-右下角
 * */
vector<Point> ReorderPoints(vector<Point> points)
{
	vector<Point> newPoints;
	vector<int>  sumPoints, subPoints;

	// OpenCV中左上顶点为(0,0),右为x轴正向,下为y轴正向。
	for (int i = 0; i < 4; i++)
	{
		// 将每个点的xy坐标值相加(x+y),左上角的点的坐标和应该是最小的,右下角的点的坐标和应该是最大的
		sumPoints.push_back(points[i].x + points[i].y);
		// 将每个点的xy坐标值相减(x-y),左下角的点的坐标差应该是最小的,右上角的点的坐标差应该是最大的
		subPoints.push_back(points[i].x - points[i].y);
	}

	// 重新排列
	newPoints.push_back(points[min_element(sumPoints.begin(), sumPoints.end()) - sumPoints.begin()]); // 0 和的最小值
	newPoints.push_back(points[max_element(subPoints.begin(), subPoints.end()) - subPoints.begin()]); // 1 差的最大值
	newPoints.push_back(points[min_element(subPoints.begin(), subPoints.end()) - subPoints.begin()]); // 2 差的最小值
	newPoints.push_back(points[max_element(sumPoints.begin(), sumPoints.end()) - sumPoints.begin()]); // 3 和的最大值

	return newPoints;
}

/*
 * 函数功能:
 * 输入:源图像,四个角点的集合(角点的顺序为,左上角-右上角-左下角-右下角),输出的宽,输出的高
 * 输出:透视变换后的图像
 * */
Mat PerspectiveTrans(const Mat& img, vector<Point> points, float width, float height )
{
	// 前面经过重新排列,四个角点的顺序为:左上角-右上角-左下角-右下角
	Point2f src[4] = { points[0],points[1],points[2],points[3] };
	// 变换后的四个角点
	Point2f dst[4] = { {0.0f,0.0f},{width,0.0f},{0.0f,height},{width,height} };

	// 创建变换矩阵
	Mat matrix = getPerspectiveTransform(src, dst);
	// 透视变换
	warpPerspective(img, image_trans, matrix, Point(width, height));

	return image_trans;
}

int main()
{
	// 1.读取原始图像
	string path = "res/image_origin.jpg";
	image_origin = imread(path);

//	// 若图像太大可以先进行缩放处理
//	resize(image_origin, image_origin, Size(), 0.5, 0.5);

	// 获取原始图像的宽和高
	origin_width  = image_origin.size().width;
	origin_height = image_origin.size().height;

	// 2.对图像进行预处理得到边缘,依次进行灰度处理、高斯模糊、边缘检测、膨胀、腐蚀。
	image_preprocess = PreProcess(image_origin, 0);

	// 3.找到最大的轮廓,并提取角点
	origin_points = GetMaxContour(image_preprocess);
//	DrawPoints(origin_points, Scalar(0, 0, 255)); // 红色
	// 此时发现,角点的顺序不固定,为了后面进行透视变换时与代码中变换后点集的顺序相同,需要将其排列成一个固定的顺序,排列后的顺序为:左上角-右上角-左下角-右下角
	reorder_points = ReorderPoints(origin_points);
//	DrawPoints(reorder_points, Scalar(0, 255, 0)); //绿色

	// 4.透视变换
	image_trans = PerspectiveTrans(image_origin, reorder_points, origin_width, origin_height);

	// 透视变换后有一些毛边,若需要可以进行裁剪
	// 四周裁剪5像素
	int cropVal= 5;
	// 创建一个矩形用来裁剪
	Rect roi(cropVal, cropVal, origin_width - (2 * cropVal), origin_height - (2 * cropVal));
	image_crop = image_trans(roi);
	// 裁剪后重新调整比例
	resize(image_crop, image_crop, Size(origin_width, origin_height));

	// 5.显示并输出变换后图像
	imshow("源图像", image_origin);
	imshow("最终图像", image_crop);

	imwrite("res/image_output.jpg", image_crop);

	waitKey(0);
}

效果

到此这篇关于C++ OpenCV实现文档矫正功能的文章就介绍到这了,更多相关OpenCV文档矫正内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

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

    摘要 在机器视觉中,对于图像的处理有时候因为放置的原因导致ROI区域倾斜,这个时候我们会想办法把它纠正为正确的角度视角来,方便下一步的布局分析与文字识别,这个时候通过透视变换就可以取得比较好的裁剪效果. 本次实战,对于图像的矫正使用了两种矫正思路: 针对边缘比较明显的图像,使用基于轮廓提取的矫正算法. 针对边缘不明显,但是排列整齐的文本图像,使用了基于霍夫直线探测的矫正算法. 基于轮廓提取的矫正算法 整体思路: 图片灰度化,二值化 检测轮廓,并筛选出目标轮廓(通过横纵比或面积去除干扰轮廓) 获取

  • Python Opencv基于透视变换的图像矫正

    本文实例为大家分享了Python Opencv基于透视变换的图像矫正,供大家参考,具体内容如下 一.自动获取图像顶点变换(获取图像轮廓顶点矫正) 图像旋转校正思路如下 1.以灰度图读入2.腐蚀膨胀,闭合等操作3.二值化图像4.获取图像顶点5.透视矫正 #(基于透视的图像矫正) import cv2 import math import numpy as np def Img_Outline(input_dir):     original_img = cv2.imread(input_dir)

  • OpenCV实现透视变换矫正

    本文实例为大家分享了OpenCV实现透视变换矫正的具体代码,供大家参考,具体内容如下 演示结果参考: 功能实现:运行程序,会显示图片的尺寸,按回车键后,依次点击需矫正的图片的左上.右上.左下.右下角,并能显示其坐标,结果弹出矫正后的图片,如图上的PIC2对话框.可以继续选择图片四个点进行实验,按下字符'q'后退出. 代码如下:(注:图中的11.jpg图片自己选取放到该程序目录下.) //使用鼠标在原图像上选取感兴趣区域 #include <opencv2/opencv.hpp> #includ

  • C++ OpenCV实战之图像透视矫正

    目录 前言 一.图像预处理 二.轮廓提取 1.提取最外轮廓 2.提取矩形四个角点 3.将矩形角点排序 三.透视矫正 四.源码 前言 本文将使用OpenCV C++ 进行图像透视矫正. 一.图像预处理 原图如图所示.首先进行图像预处理.将图像进行灰度.滤波.二值化.形态学等操作,目的是为了下面的轮廓提取.在这里我还使用了形态学开.闭操作,目的是使整个二值图像连在一起.大家在做图像预处理时,可以根据图像特征自行处理. Mat gray; cvtColor(src, gray, COLOR_BGR2G

  • C++ OpenCV实现文档矫正功能

    目录 需求 思路 代码 效果 需求 将一个斜着拍摄的文档矫正成正的,如图所示: 思路 1.读取原始图像,若图像太大可以先进行缩放处理,并获取原始图像的宽和高 2.对图像进行预处理得到边缘,依次进行灰度处理.高斯模糊.边缘检测.膨胀.腐蚀. 3.找到最大的轮廓,并提取角点 进行降噪处理:检测轮廓面积,只保留大于阈值面积的轮廓 计算每个轮廓的周长,使用DP算法计算出轮廓点的个数,规则为周长*0.02 找到图像中面积最大的,且角点为4的轮廓 4.将找到的四个角点排列成一个固定的顺序,排列后的顺序为:左

  • opencv实现文档矫正

    本文实例为大家分享了opencv实现文档矫正的具体代码,供大家参考,具体内容如下 原始文档 矫正后文档 思路: 只要获得倾斜文档的倾斜角度,然后通过仿射变化旋转一下就可以实现矫正了,这里获取倾斜角度的方法有两个,下面分别介绍 1.利用霍夫变换,文档内容都是平行的,首先利用利用霍夫变换检测直线,然后将所有直线的平均倾斜角度当做文档的倾斜角度,最后再进行仿射变换就可以了. import cv2 import numpy as np def imshow(img):     cv2.imshow("i

  • python3+PyQt5实现文档打印功能

    本文通过Python3+PyQt5实现<python Qt Gui 快速编程>这本书13章文档打印功能.本文共通过三种方式: 1.使用HTML和QTextDOcument打印文档 2.使用QTextCusor和QTextDocument打印文档 3.使用QPainter打印文档 使用Qpainter打印文档比QTextDocument需要更操心和复杂的计算,但是QPainter确实能够对输出赋予完全控制. #!/usr/bin/env python3 import math import sy

  • Android集成腾讯X5实现文档浏览功能

    Android内部没有控件来直接显示文档,跳转WPS或其他第三方文档App体验性不好,使用腾讯X5内核能很好的解决的这一问题. 一.下载腾讯X5内核 1.前往https://x5.tencent.com/下载Android的内核,新版本的腾讯X5可以直接在bulid.gradle集成 api 'com.tencent.tbs.tbssdk:sdk:43697',如果是在App里集成可以把api换成implementation 2.AndroidStudio导入腾讯X5 a.把下载好的jar包导入

  • Spring Cloud Gateway 整合 knife4j 聚合接口文档功能

    当系统中微服务数量越来越多时,如果任由这些服务散落在各处,那么最终管理每个项目的接口文档将是一件十分麻烦的事情,单是记住所有微服务的接口文档访问地址就是一件苦差事了.当如果能够将所有微服务项目的接口文档都统一汇总在同一个可视化页面,那么将大大减少我们的接口文档管理维护工作,为此,我们可以基于 Spring Cloud Gateway 网关 + nacos + knife4j 对所有微服务项目的接口文档进行聚合,从而实现我们想要的文档管理功能 注:本案例需要 springboot 提前整合 nac

  • 利用Java Apache POI 生成Word文档示例代码

    最近公司做的项目需要实现导出Word文档的功能,网上关于POI生成Word文档的例子很少,找了半天才在官网里找到个Demo,有了Demo一切就好办了. /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See

  • 跟老齐学Python之Python文档

    文档很重要.独孤九剑的剑诀.易筋经的心法.写着辟邪剑谱的袈裟,这些都是文档.连那些大牛人都要这些文档,更何况我们呢?所以,文档是很重要的. 文档,说白了就是用word(这个最多了)等(注意这里的等,把不常用的工具都等掉了,包括我编辑文本时用的vim工具)文本编写工具写成的包含文本内容但不限于文字的文件.有点啰嗦,啰嗦的目的是为了严谨,呵呵.最好还是来一个更让人信服的定义,当然是来自维基百科. 复制代码 代码如下: 软件文档或者源代码文档是指与软件系统及其软件工程过程有关联的文本实体.文档的类型包

  • Java编程中更新XML文档的常用方法

    本文简要的讨论了Java语言编程中更新XML文档的四种常用方法,并且分析这四种方法的优劣.其次,本文还对如何控制Java程序输出的XML文档的格式做了展开论述. JAXP是Java API for XML Processing的英文字头缩写,中文含义是:用于XML文档处理的使用Java语言编写的编程接口.JAXP支持DOM.SAX.XSLT等标准.为了增强JAXP使用上的灵活性,开发者特别为JAXP设计了一个Pluggability Layer,在Pluggability Layer的支持之下,

  • python解析html提取数据,并生成word文档实例解析

    简介 今天试着用ptyhon做了一个抓取网页内容,并生成word文档的功能,功能很简单,做一下记录以备以后用到. 生成word用到了第三方组件python-docx,所以先进行第三方组件的安装.由于windows下安装的python默认不带setuptools这个模块,所以要先安装setuptools这个模块. 安装 1.在python官网上找到 https://bootstrap.pypa.io/ez_setup.py ,把代码保存到本地并执行: python ez_setup.py 2.下载

  • Java如何获取word文档的条目化内容

    在开发Web办公系统或文档系统时,PageOffice组件是众所周知的在线处理微软word/ppt/excel文档的强大工具,它对WORD文档的各种处理在API层面进行了封装,屏蔽了Office VBA接口的复杂性,而又不失VBA的强大功能,在此要分享的正是PageOffice封装的一个很强大的功能:获取word文档的条目化内容.在一个包含了文档处理功能的办公系统里,用户出于各种原因,希望能通过程序自动分析word文档中每个章节的内容也是一种合理的需求,而PageOffice为实现此功能提供的接

随机推荐