将一个斜着拍摄的文档矫正成正的,如图所示:
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);}el 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.02approxpolydp(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] ,corn系词ers_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文档矫正内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!
本文发布于:2023-04-06 03:57:55,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/03bd912a6f0f07e7d3516612eb53a96d.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:C++ OpenCV实现文档矫正功能.doc
本文 PDF 下载地址:C++ OpenCV实现文档矫正功能.pdf
留言与评论(共有 0 条评论) |