OpenCV实现图像校正功能
一、 需求分析
首先是需求:
1、利用OpenCV里面的仿射变换函数实现对图像进行一些基本的变换,如平移、旋转、缩放
2、学习透视变换原理,对一个矩形进行透视变换,并将变换结果绘制出来。先调用OpenCV函数实现透视变换,自己编写代码实现透视变换。
3、识别一张倾斜拍摄的纸张,找出轮廓,提取出该纸张的位置
4、假设你已通过图像处理的算法找到发生形变的纸张的位置,那么对这个倾斜纸张进行变换,得到纸张的垂直视图,实现文档校准。
然后是分析:
1、首先要调用OpenCV的函数对图像进行平移、旋转、缩放变换,然后要进行仿射变换和透视变换。
2、编程实现仿射变换和透视变换,注意到仿射变换是透视变换的一种,因此只需实现透视变换
3、 实现文档校准:
(1)滤波。考虑到文档中的字(噪点),同时采用均值滤波和闭运算滤波。
(2)边缘提取。利用库函数提取边缘信息
(3)边缘识别。利用经典霍夫变换,获得边界方程,并且计算出文档的四个角的坐标
(4)透视变换。调用库函数,实现文档校准
5、由于前三个需求与最后一个需求的源码放在同一个工程中显得不合适,因此,我将前三个需求的代码和注释放在了工程:作业2_2中,开发环境是win10vs2017,openCV3.43
二、 实现
注意:
以下的函数全部写在标头.h文件中,要在在main中调用标头.h文件中的函数才能完成功能
还有就是图片输入的路径要改好。
1、工程:作业2_2的实现
(1)调用OpenCV内的函数,编写了一个main_transform函数,在主函数调用它,输入图片后,同时将图片缩小、平移、旋转、透视和仿射变换,并且将图片展示和保存下来(实际上后来openCV的仿射、透视我注释掉了,不用它自带的函数了)
都是直接调用函数,没什么好说的。
下面分别是旋转、透视、平移、缩小、仿射的效果图:
(2)手动实现仿射、透视变换函数toushibianhuan和toushibianhuan_gai_fangshebianhuan,并在main_transform中调用他们。
透视变换实现:
注意到仿射变换是透视变换的特殊情况,因此只要实现了透视就可以实现仿射。
透视函数的实现:
首先使用getPerspectiveTransform来获取变换矩阵,然后看透视函数
toushibianhuan函数需要三个输入参数:
- 第一个参数:透视变换输入的图像矩阵,Mat
- 第二个参数:输出图像容器矩阵,Mat
- 第三个参数:变换矩阵,Mat
进入函数后,首先定义出一个位置矩阵position_maxtri用以刻画变换前图像的位置,利用矩阵元素积,乘以变换矩阵后算出变换后的四个角的位置矩阵。
用Max、Min函数计算出图像最高点、最低点,进而算出图像的高和宽
然后,重点来了,定义、更新计算出两个重映射矩阵。Map1是从原图的x—>新图x的映射,Map2是从原图y—>新图y的映射。
/*----------------------------------------------------------------------------------------------------------------- Copyright(C),2018---,HUSTLiu Filename:image_solve.h Author:刘峻源Version:1Date:2018.10.3 ------------------------------------------------------------------------------------------------------------------ Description: 文档矫正项目.cpp的主要函数储存在这里 -------------------------------------------------------------- 函数说明:comMatC用于连接矩阵 toushibianhuan_gai_fangshebianhuan用于仿射变换 toushibianhuan用于仿射变换 main_transform调用函数来处理图像,包括平移、缩小、旋转、仿射变换和透视变换 input_solve用以矫正文档,包括打开图像、滤波、提取边缘、绘制边缘、透视变换矫正文档 -------------------------------------------------------------------------------- Others:NONE FunctionList:comMatC、toushibianhuan、toushibianhuan_gai_fangshebianhuan、input_solve -------------------------------------------------------------------------------- history:NONE -------------------------------------------------------------------------------------*/ /*----------------------------------------------------------------- 标准openCV开头-- 引用头文件和命名空间-- ------------------------------------------------------------------*/ #include#include #include #include #include usingnamespacestd; usingnamespacecv; /*------------------------------------------------------------------------------- Function:comMatC Description:上下连接矩阵,并输出 -------------------------------------------------------------------------------- Calls:Create、copyTo CalledBy:main_transform TableAccessed:NONE TableUpdated:NONE -------------------------------------------------------------------------------- Input: 第一个参数:上面的矩阵,Mat 第二个参数:下面的矩阵,Mat 第三个参数:连接后的输出容器,Mat Output:输出连接后的矩阵 Return:输出矩阵 Others:列数不一致会报错! ---------------------------------------------------------------------------------*/ MatcomMatC(MatMatrix1,MatMatrix2,Mat&MatrixCom) { CV_Assert(Matrix1.cols==Matrix2.cols); MatrixCom.create(Matrix1.rows+Matrix2.rows,Matrix1.cols,Matrix1.type()); Mattemp=MatrixCom.rowRange(0,Matrix1.rows); Matrix1.copyTo(temp); Mattemp1=MatrixCom.rowRange(Matrix1.rows,Matrix1.rows+Matrix2.rows); Matrix2.copyTo(temp1); returnMatrixCom; } /*-------------------------------------------------------------------------------- Function:toushibianhuan Description:实现透视变换功能,将input_image按tp_Transform_maxtri矩阵变换后输出至另一图像容器中 ------------------------------------------------------------------------------- Calls:max、min CalledBy:main_transform TableAccessed:NONE TableUpdated:NONE ---------------------------------------------------------------------------------- Input: 第一个参数:透视变换输入的图像矩阵,Mat 第二个参数:输出图像容器矩阵,Mat 第三个参数:变换矩阵,Mat Output:无返回值。在控制台上打印出原图的位置矩阵、变换后的图像坐标矩阵、变换矩阵 Return:NONE Others:NONE -----------------------------------------------------------------*/ voidtoushibianhuan(Matinput_image,Mat&output,Mattp_Translater_maxtri) { intqiu_max_flag; intj; inti; //定义顶点位置矩阵 Matposition_maxtri(3,4,CV_64FC1,Scalar(1)); position_maxtri.at (0,0)=0; position_maxtri.at (1,0)=0; position_maxtri.at (1,1)=0; position_maxtri.at (0,2)=0; position_maxtri.at (1,2)=input_image.rows; position_maxtri.at (0,3)=input_image.cols; position_maxtri.at (1,3)=input_image.rows; position_maxtri.at (0,1)=input_image.cols; Matnew_corner=tp_Translater_maxtri*position_maxtri; //打印并监视三个矩阵 cout<<"coner_maxtri"< (0,0)/new_corner.at (2,0); doublemin_kuan=new_corner.at (0,0)/new_corner.at (2,0); doublemax_gao=new_corner.at (1,0)/new_corner.at (2,0); doublemin_gao=new_corner.at (1,0)/new_corner.at (2,0); for(qiu_max_flag=1;qiu_max_flag<4;qiu_max_flag++) { max_kuan=max(max_kuan, new_corner.at (0,qiu_max_flag)/new_corner.at (2,qiu_max_flag)); min_kuan=min(min_kuan, new_corner.at (0,qiu_max_flag)/new_corner.at (2,qiu_max_flag)); max_gao=max(max_gao, new_corner.at (1,qiu_max_flag)/new_corner.at (2,qiu_max_flag)); min_gao=min(min_gao, new_corner.at (1,qiu_max_flag)/new_corner.at (2,qiu_max_flag)); } //创建向前映射矩阵map1,map2 output.create(int(max_gao-min_gao),int(max_kuan-min_kuan),input_image.type()); Matmap1(output.size(),CV_32FC1); Matmap2(output.size(),CV_32FC1); Mattp_point(3,1,CV_32FC1,1); Matpoint(3,1,CV_32FC1,1); tp_Translater_maxtri.convertTo(tp_Translater_maxtri,CV_32FC1); MatTranslater_inv=tp_Translater_maxtri.inv(); //核心步骤,将映射阵用矩阵乘法更新出来 for(i=0;i (0)=j+min_kuan; point.at (1)=i+min_gao; tp_point=Translater_inv*point; map1.at (i,j)=tp_point.at (0)/tp_point.at (2); map2.at (i,j)=tp_point.at (1)/tp_point.at (2); } } remap(input_image,output,map1,map2,CV_INTER_LINEAR); } /*-------------------------------------------------------------------------------- Function:toushibianhuan_gai_fangshebianhuan Description:实现仿射变换功能,将input_image按Translater_maxtri矩阵变换后输出至另一图像容器中 ------------------------------------------------------------------------------------ Calls:comMatC、max、min CalledBy:main_transform TableAccessed:NONE TableUpdated:NONE ------------------------------------------------------------------------------------ Input: 第一个参数:透视变换输入的图像矩阵,Mat 第二个参数:输出图像矩阵,Mat 第三个参数:变换矩阵,Mat Output:无返回值。在控制台上打印出原图的位置矩阵、变换后的图像坐标矩阵、变换矩阵 Return:NONE Others:NONE -------------------------------------------------------------------------------*/ voidtoushibianhuan_gai_fangshebianhuan(Matinput_image,Mat&output,MatTranslater_maxtri) { intwidth=0; intheight=0; Mattp_Translater_maxtri; Matposition_maxtri(3,4,CV_64FC1,Scalar(1)); Matone_vector(1,3,CV_64FC1,Scalar(0)); one_vector.at (0,2)=1.; comMatC(Translater_maxtri,one_vector,tp_Translater_maxtri); position_maxtri.at (1,1)=0; position_maxtri.at (0,2)=0; position_maxtri.at (0,0)=0; position_maxtri.at (1,0)=0; position_maxtri.at (0,3)=input_image.cols; position_maxtri.at (1,3)=input_image.rows; position_maxtri.at (0,1)=input_image.cols; position_maxtri.at (1,2)=input_image.rows; Matnew_corner=tp_Translater_maxtri*position_maxtri; cout<<"coner_maxtri"< (0,0)/new_corner.at (2,0); doublemin_kuan=new_corner.at (0,0)/new_corner.at (2,0); doublemax_gao=new_corner.at (1,0)/new_corner.at (2,0); doublemin_gao=new_corner.at (1,0)/new_corner.at (2,0); for(intflag=1;flag<4;flag++) { max_kuan=max(max_kuan,new_corner.at (0,flag)/new_corner.at (2,flag)); min_kuan=min(min_kuan,new_corner.at (0,flag)/new_corner.at (2,flag)); max_gao=max(max_gao,new_corner.at (1,flag)/new_corner.at (2,flag)); min_gao=min(min_gao,new_corner.at (1,flag)/new_corner.at (2,flag)); } output.create(int(max_gao-min_gao),int(max_kuan-min_kuan),input_image.type()); Matmap1(output.size(),CV_32FC1); Matmap2(output.size(),CV_32FC1); Mattp_point(3,1,CV_32FC1,1); Matpoint(3,1,CV_32FC1,1); tp_Translater_maxtri.convertTo(tp_Translater_maxtri,CV_32FC1); MatTranslater_inv=tp_Translater_maxtri.inv(); for(inti=0;i (1)=i+min_gao; point.at (0)=j+min_kuan; tp_point=Translater_inv*point; map1.at (i,j)=tp_point.at (0)/tp_point.at (2); map2.at (i,j)=tp_point.at (1)/tp_point.at (2); } } remap(input_image,output,map1,map2,CV_INTER_LINEAR); } /*------------------------------------------------------------------------------ Function:main_transform Description:实现缩小、平移、旋转的仿射变换功能,加以展示且将图片保存在当前工程目录下 --------------------------------------------------------------------------- Calls:resize、warpAffine、Size、Scalar、getRotationMatrix2D、namedWindow、 toushibianhuan_gai_fangshebianhuan、imshow、imwrite、waitKey、printf、warpPerspective、fangshebianhuan CalledBy:main TableAccessed:NONE TableUpdated:NONE -------------------------------------------------------------------------------- Input: 第一个参数:float类型的旋转角度值(非弧度) 第二个参数:向右平移的像素,int类型 第三个参数:向下平移的像素,int类型 第四个参数:读取图像路径,constchar类型 第五个参数:x方向伸缩比例,float类型 第六个参数:y方向伸缩比例,float类型 Output:仿射变换、透视变换后的图像保存于当前工程目录下,各参数已经设置好,矫正效果不佳 Return:无返回值 Others:NONE ---------------------------------------------------------------------------*/ voidmain_transform(floatangle,intright_translate,intdown_translate, constchar*road_read_image,floatx_tobe,floaty_tobe) { Point2finput_image1[3]={Point2f(50,50),Point2f(200,50),Point2f(50,200)}; Point2fdst1[3]={Point2f(0,100),Point2f(200,50),Point2f(180,300)}; Point2finput_image[4]={Point2f(100,50),Point2f(100,550),Point2f(350,50),Point2f(350,550)}; Point2fdst[4]={Point2f(100,50),Point2f(340,550),Point2f(350,80),Point2f(495,550)}; Matkernel2=getPerspectiveTransform(input_image,dst); Matkernel=getAffineTransform(input_image1,dst1); Matone_vector(1,3,CV_64FC1,Scalar(0)); MatTemp_kernel; one_vector.at (0,2)=1.; comMatC(kernel,one_vector,Temp_kernel); floatall_tobe=x_tobe/2+y_tobe/2; Matold_image=imread(road_read_image); Matnew_min_image; Matnew_translation_image; Matrotate_image; Mattranslater(2,3,CV_32F,Scalar(0)); Matrotater; Matfangshe_image; Mattoushi_image; vector compression_params; resize(old_image,new_min_image,Size(),x_tobe,y_tobe,INTER_CUBIC); translater.at (0,0)=1; translater.at (1,1)=1; translater.at (0,2)=right_translate; translater.at (1,2)=down_translate; warpAffine(new_min_image,new_translation_image,translater, Size(new_min_image.cols*1.5,new_min_image.rows*1.5)); Pointrotate_center=Point(new_translation_image.cols/3,new_translation_image.rows/2); rotater=getRotationMatrix2D(rotate_center,angle,all_tobe); warpAffine(new_translation_image,rotate_image,rotater,Size(), INTER_CUBIC|CV_WARP_FILL_OUTLIERS,BORDER_CONSTANT,Scalar(0)); //warpAffine(new_translation_image,fangshe_image,kernel,Size(new_translation_image.cols*1.5,new_translation_image.rows*1.5)); //这是OpenCV自带的仿射变换......... compression_params.push_back(IMWRITE_PNG_COMPRESSION); toushibianhuan_gai_fangshebianhuan(new_translation_image,fangshe_image,kernel); toushibianhuan(fangshe_image,toushi_image,kernel2); //warpPerspective(fangshe_image,toushi_image,kernel2,Size(new_translation_image.cols,new_translation_image.rows)); //这是openCV的透视变换 compression_params.push_back(9); namedWindow("new_min_image"); imshow("new_min_image",new_min_image); imwrite("task2_1放缩.png",old_image,compression_params); namedWindow("new_translation_image"); imshow("new_translation_image",new_translation_image); boolflags=imwrite("task2_1平移.png",new_translation_image,compression_params); namedWindow("rotate_image"); imshow("rotate_image",rotate_image); imwrite("task2_1旋转.png",rotate_image,compression_params); namedWindow("fangshe_image"); imshow("fangshe_image",fangshe_image); imwrite("task2_1仿射.png",fangshe_image,compression_params); namedWindow("toushi_image"); imshow("toushi_image",toushi_image); imwrite("task2_1透视.png",toushi_image,compression_params); printf("%d",flags); } /*---------------------------------------------------------------------------- Function:getCrossPoint Description:求两直线的交点 ----------------------------------------------------------------------------- Calls:NONE CalledBy:input_solve TableAccessed:NONE TableUpdated:NONE ----------------------------------------------------------------------------- Input: 第一个参数:由两点表示的类型为Vec4i的直线A 第二个参数:由两点表示的类型为Vec4i的直线B Output:Point2f的点 Return:Point2f的点 Others:NONE --------------------------------------------------------------------------------*/ Point2fgetCrossPoint(Vec4iLineA,Vec4iLineB) { doubleka,kb; //求出LineA斜率 ka=(double)(LineA[3]-LineA[1])/(double)(LineA[2]-LineA[0]); //求出LineB斜率 kb=(double)(LineB[3]-LineB[1])/(double)(LineB[2]-LineB[0]); Point2fcrossPoint; crossPoint.x=(ka*LineA[0]-LineA[1]-kb*LineB[0]+LineB[1])/(ka-kb); crossPoint.y=(ka*kb*(LineA[0]-LineB[0])+ka*LineB[1]-kb*LineA[1])/(ka-kb); returncrossPoint; } /*---------------------------------------------------------------------- Function:input_solve Description:用于打开图像、滤波、提取边缘、绘制边缘、透视变换矫正文档的函数。注意,本函数中图像 处理过程中的参数已经调整完毕 ------------------------------------------------------------------------ Calls:imread、resize、morphologyEx、blur、Canny、HoughLines、warpPerspective CalledBy:main TableAccessed:NONE TableUpdated:NONE ------------------------------------------------------------------------- Input: 第一个参数:输入图像的路径 Output:经过文档矫正后的图像 Return:NONE Others:矫正图像保存于当前目录下: "C:/Users/liujinyuan/source/repos/作业2_2/作业2_2/task2_2矫正.png" ---------------------------------------------------------------*/ voidinput_solve(constchar*image_road) { //定义保存图像参数向量 vector compression_params; compression_params.push_back(IMWRITE_PNG_COMPRESSION); compression_params.push_back(9); //获取闭运算滤波的核 Matelement=getStructuringElement(MORPH_RECT,Size(5,5)); Matnew_min_image; Matlast_kernel; //获取灰度图 Matold_image=imread(image_road,0); vector lines; vector coners; vector lines_2pt(10); Pointpt1,pt2,pt3,pt4,pt5,pt6; Matlast_image; Matnew_min_image2; resize(old_image,new_min_image,Size(),0.5,0.5,INTER_CUBIC); resize(old_image,new_min_image2,Size(),0.5,0.5,INTER_CUBIC); //闭运算滤波 morphologyEx(new_min_image,new_min_image,MORPH_CLOSE,element); blur(new_min_image,new_min_image,Size(10,10)); Canny(new_min_image,new_min_image,8.9,9,3); HoughLines(new_min_image,lines,1,CV_PI/180,158,0,0); //利用这个循环,可以绘制霍夫变换获取直线的效果图,但是为了简洁性我暂时删去了创建窗口绘制的代码 for(rsize_ti=0;i /*------------------------------------------------------------------------------------------------------------------------------------- Copyright(C),2018---,HUSTLiu Filename:文档矫正项目.cpp Author:刘峻源Version:1Date:2018.10.3 Description: Part1 根据作业(2)中的任务(1)(2) 做了以下工作: (1)经过仿射变换,图片缩小平移旋转 (2)调用函数进行仿射、透视变换 (3)实现函数来做透视变换、仿射变换 --------------------------------------------------------- Part2 根据作业(2)中的任务(3)(4) 做了以下工作: (1)利用读入灰度图像,并且经过滤波、边缘提取、霍夫变换提取边缘直线、得到 纸张位置(即4个顶点) (2)利用透视变换矫正文档 ---------*----------*---------------*---------------*--------- 具体的任务过程: Part1 调用OpenCV内的函数,编写main_transform函数实现缩小、平移、旋转和仿射变换功能 (实际上后来openCV的仿射、透视我注释掉了,不用它的函数了) 实现仿射、透视变换函数toushibianhuan、toushibianhuan_gai_fangshebianhuan,并在main_transform 中调用 注意:在main中调用标头.h文件中的main_transfom函数实现缩小、平移、旋转和仿射、透视变换!!! ------------------------------------------------------------------ 任务过程: Part2 在input_solve中,利用imread读入灰度图,调用blur、morphologyEx滤波,利用canny提取 边缘,调用HoughLines获取边缘直线,调用getCrossPoint获取直线交点,调用 getPerspectiveTransform获取变换矩阵,调用warpPerspective实现透视变换 注意:编写input_solve函数来实现处理功能,本cpp是在main中调用标头.h中的input_solve函数!!! ------------------------------------------------------------------------------------------ Others:图像输入路径:作业2_2/作业2_2/task2.png 输出图像保存路径:工程文件夹:作业2_2/作业2_2 注意:在其他环境运行时一定要弄好更改读入路径!!!! MayFunctionList:main、main_transform、input_solve ----------------------------------------------------------------------------------------------- History: asfolwing ----------------------------------------------------------------------- 1.2018.10.3 2.by刘峻源 3.description:在工程作业2_1中将comMatC、toushibianhuan、toushibianhuan_gai_fangshebianhuan移入头文件 image_solve.h中 ----------------------------------------------------------------------- 1.2018.10.4 2.by刘峻源 3.description:在工程作业2_2中将main_transfom写入main,去掉main_transform函数的waitKey(0) ----------------------------------------------------------------------- --------------------------------------------------------------------------------*/ /*----------------------------------------------------------------- 标准openCV开头-- --------------------------------------------------------------------- 引用头文件和命名空间-- ------------------------------------------------------------------*/ #include#include #include #include #include"标头.h" usingnamespacestd; usingnamespacecv; intmain() { main_transform(90,0,100,"task2.png",0.5,0.5); input_solve("task2.png"); waitKey(0); } 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。