一、 需求分析
首先是需求:
1、利用 OpenCV 里面的仿射变换函 数实现对图像进行一些基本的变换,如平移、旋转、缩放
2、学习透视变换原理,对一个矩形进行透视变换,并将变换结果绘制出来。先调用 OpenCV 函数实现透视变换,自己编写代码实现透视变换。
3、识别一张倾斜拍摄的纸张,找出轮廓,提取出该纸张的位置
4、 假设你已通过图像处理的算法找到发生形变的纸张的位置,那么对这个倾斜 纸张进行变换,得到纸张的垂直视图,实现文档校准。
然后是分析:
1、首先要调用OpenCV的函数对图像进行平移、旋转、缩放变换,然后要进行仿射变换和透视变换。
2、编程实现仿射变换和透视变换,注意到仿射变换是透视变换的一种,因此只需实现透视变换
3、 实现文档校准:
(1)滤波。考虑到文档中的字(噪点),同时采用均值滤波和闭运算滤波。
(2)边缘提取。利用库函数提取边缘信息
(3)边缘识别。利用经典霍夫变换,获得边界方程,并且计算出文档的四个角的坐标
(4)透视变换。调用库函数,实现文档校准
5、由于前三个需求与最后一个需求的源码放在同一个工程中显得不合适,因此,我将前三个需求的代码和注释放在了工程:作业2_2中,开发环境是win10 vs2017,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---, HUST Liu File name: image_solve.h Author: 刘峻源 Version: 1 Date: 2018.10.3 ------------------------------------------------------------------------------------------------------------------ Description: 文档矫正项目.cpp的主要函数储存在这里 ------- ----------- ------------ ------------ ----------- --------- 函数说明:comMatC用于连接矩阵 toushibianhuan_gai_fangshebianhuan用于仿射变换 toushibianhuan用于仿射变换 main_transform 调用函数来处理图像,包括平移、缩小、旋转、仿射变换和透视变换 input_solve 用以矫正文档,包括打开图像、滤波、提取边缘、绘制边缘、透视变换矫正文档 -------------------------------------------------------------------------------- Others:NONE Function List: comMatC、toushibianhuan、toushibianhuan_gai_fangshebianhuan、input_solve -------------------------------------------------------------------------------- history: NONE -------------------------------------------------------------------------------------*/ /*----------------------------------------------------------------- 标准openCV开头 -- 引用头文件和命名空间 -- ------------------------------------------------------------------*/ #include#include #include #include #include using namespace std; using namespace cv; /*------------------------------------------------------------------------------- Function: comMatC Description:上下连接矩阵,并输出 -------------------------------------------------------------------------------- Calls:Create、copyTo Called By: main_transform Table Accessed: NONE Table Updated: NONE -------------------------------------------------------------------------------- Input: 第一个参数:上面的矩阵,Mat 第二个参数:下面的矩阵,Mat 第三个参数:连接后的输出容器,Mat Output:输出连接后的矩阵 Return:输出矩阵 Others:列数不一致会报错! ---------------------------------------------------------------------------------*/ Mat comMatC(Mat Matrix1, Mat Matrix2, Mat &MatrixCom) { CV_Assert(Matrix1.cols == Matrix2.cols); MatrixCom.create(Matrix1.rows + Matrix2.rows, Matrix1.cols, Matrix1.type()); Mat temp = MatrixCom.rowRange(0, Matrix1.rows); Matrix1.copyTo(temp); Mat temp1 = MatrixCom.rowRange(Matrix1.rows, Matrix1.rows + Matrix2.rows); Matrix2.copyTo(temp1); return MatrixCom; } /*-------------------------------------------------------------------------------- Function: toushibianhuan Description:实现透视变换功能 ,将input_image按tp_Transform_maxtri矩阵变换后输出至另一图像容器中 ------------------------------------------------------------------------------- Calls:max、min Called By:main_transform Table Accessed: NONE Table Updated: NONE ---------------------------------------------------------------------------------- Input: 第一个参数:透视变换输入的图像矩阵,Mat 第二个参数:输出图像容器矩阵,Mat 第三个参数:变换矩阵,Mat Output:无返回值。在控制台上打印出原图的位置矩阵、变换后的图像坐标矩阵、变换矩阵 Return:NONE Others:NONE -----------------------------------------------------------------*/ void toushibianhuan(Mat input_image, Mat &output, Mat tp_Translater_maxtri) { int qiu_max_flag; int j; int i; //定义顶点位置矩阵 Mat position_maxtri(3, 4, CV_64FC1, Scalar(1)); position_maxtri.at < double >(0, 0) = 0; position_maxtri.at < double >(1, 0) = 0; position_maxtri.at < double >(1, 1) = 0; position_maxtri.at < double >(0, 2) = 0; position_maxtri.at < double >(1, 2) = input_image.rows; position_maxtri.at < double >(0, 3) = input_image.cols; position_maxtri.at < double >(1, 3) = input_image.rows; position_maxtri.at < double >(0, 1) = input_image.cols; Mat new_corner = tp_Translater_maxtri * position_maxtri; //打印并监视三个矩阵 cout << "coner_maxtri" << new_corner << ";" << endl << endl; cout << "pos_maxtri" << position_maxtri << ";" << endl << endl; cout << "T_maxtri" << tp_Translater_maxtri << ";" << endl << endl; //为了计算图像高度,先初始化最高最低、最左最右点 double max_kuan = new_corner.at < double >(0, 0) / new_corner.at < double >(2, 0); double min_kuan = new_corner.at < double >(0, 0) / new_corner.at < double >(2, 0); double max_gao = new_corner.at < double >(1, 0) / new_corner.at < double >(2, 0); double min_gao = new_corner.at < double >(1, 0) / new_corner.at < double >(2, 0); for (qiu_max_flag = 1; qiu_max_flag < 4; qiu_max_flag++) { max_kuan = max(max_kuan, new_corner.at < double >(0, qiu_max_flag) / new_corner.at < double >(2, qiu_max_flag)); min_kuan = min(min_kuan, new_corner.at < double >(0, qiu_max_flag) / new_corner.at < double >(2, qiu_max_flag)); max_gao = max(max_gao, new_corner.at < double >(1, qiu_max_flag) / new_corner.at < double >(2, qiu_max_flag)); min_gao = min(min_gao, new_corner.at < double >(1, qiu_max_flag) / new_corner.at < double >(2, qiu_max_flag)); } //创建向前映射矩阵 map1, map2 output.create(int(max_gao - min_gao), int(max_kuan - min_kuan), input_image.type()); Mat map1(output.size(), CV_32FC1); Mat map2(output.size(), CV_32FC1); Mat tp_point(3, 1, CV_32FC1, 1); Mat point(3, 1, CV_32FC1, 1); tp_Translater_maxtri.convertTo(tp_Translater_maxtri, CV_32FC1); Mat Translater_inv = tp_Translater_maxtri.inv(); //核心步骤,将映射阵用矩阵乘法更新出来 for (i = 0; i < output.rows; i++) { for (j = 0; j < output.cols; j++) { point.at (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 Called By:main_transform Table Accessed: NONE Table Updated: NONE ------------------------------------------------------------------------------------ Input: 第一个参数:透视变换输入的图像矩阵,Mat 第二个参数:输出图像矩阵,Mat 第三个参数:变换矩阵,Mat Output:无返回值。在控制台上打印出原图的位置矩阵、变换后的图像坐标矩阵、变换矩阵 Return:NONE Others:NONE -------------------------------------------------------------------------------*/ void toushibianhuan_gai_fangshebianhuan(Mat input_image, Mat &output, Mat Translater_maxtri) { int width = 0; int height = 0; Mat tp_Translater_maxtri; Mat position_maxtri(3, 4, CV_64FC1, Scalar(1)); Mat one_vector(1, 3, CV_64FC1, Scalar(0)); one_vector.at (0, 2) = 1.; comMatC(Translater_maxtri, one_vector, tp_Translater_maxtri); position_maxtri.at < double >(1, 1) = 0; position_maxtri.at < double >(0, 2) = 0; position_maxtri.at < double >(0, 0) = 0; position_maxtri.at < double >(1, 0) = 0; position_maxtri.at < double >(0, 3) = input_image.cols; position_maxtri.at < double >(1, 3) = input_image.rows; position_maxtri.at < double >(0, 1) = input_image.cols; position_maxtri.at < double >(1, 2) = input_image.rows; Mat new_corner = tp_Translater_maxtri * position_maxtri; cout << "coner_maxtri" << new_corner << ";" << endl << endl; cout << "pos_maxtri" << position_maxtri << ";" << endl << endl; cout << "T_maxtri" << tp_Translater_maxtri << ";" << endl << endl; double max_kuan = new_corner.at < double >(0, 0) / new_corner.at < double >(2, 0); double min_kuan = new_corner.at < double >(0, 0) / new_corner.at < double >(2, 0); double max_gao = new_corner.at < double >(1, 0) / new_corner.at < double >(2, 0); double min_gao = new_corner.at < double >(1, 0) / new_corner.at < double >(2, 0); for (int flag = 1; flag < 4; flag++) { max_kuan = max(max_kuan, new_corner.at < double >(0, flag) / new_corner.at < double >(2, flag)); min_kuan = min(min_kuan, new_corner.at < double >(0, flag) / new_corner.at < double >(2, flag)); max_gao = max(max_gao, new_corner.at < double >(1, flag) / new_corner.at < double >(2, flag)); min_gao = min(min_gao, new_corner.at < double >(1, flag) / new_corner.at < double >(2, flag)); } output.create(int(max_gao - min_gao), int(max_kuan - min_kuan), input_image.type()); Mat map1(output.size(), CV_32FC1); Mat map2(output.size(), CV_32FC1); Mat tp_point(3, 1, CV_32FC1, 1); Mat point(3, 1, CV_32FC1, 1); tp_Translater_maxtri.convertTo(tp_Translater_maxtri, CV_32FC1); Mat Translater_inv = tp_Translater_maxtri.inv(); for (int i = 0; i < output.rows; i++) { for (int j = 0; j < output.cols; j++) { point.at (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 Called By: main Table Accessed: NONE Table Updated: NONE -------------------------------------------------------------------------------- Input: 第一个参数:float类型的旋转角度值(非弧度) 第二个参数:向右平移的像素,int类型 第三个参数:向下平移的像素,int类型 第四个参数:读取图像路径,const char类型 第五个参数:x方向伸缩比例,float类型 第六个参数:y方向伸缩比例,float类型 Output:仿射变换、透视变换后的图像保存于当前工程目录下,各参数已经设置好,矫正效果不佳 Return:无返回值 Others:NONE ---------------------------------------------------------------------------*/ void main_transform(float angle, int right_translate, int down_translate, const char* road_read_image, float x_tobe, float y_tobe) { Point2f input_image1[3] = { Point2f(50,50),Point2f(200,50),Point2f(50,200) }; Point2f dst1[3] = { Point2f(0,100),Point2f(200,50),Point2f(180,300) }; Point2f input_image[4] = { Point2f(100,50),Point2f(100,550),Point2f(350,50),Point2f(350,550) }; Point2f dst[4] = { Point2f(100,50),Point2f(340,550),Point2f(350,80),Point2f(495,550) }; Mat kernel2 = getPerspectiveTransform(input_image, dst); Mat kernel = getAffineTransform(input_image1, dst1); Mat one_vector(1, 3, CV_64FC1, Scalar(0)); Mat Temp_kernel; one_vector.at (0, 2) = 1.; comMatC(kernel, one_vector, Temp_kernel); float all_tobe = x_tobe / 2 + y_tobe / 2; Mat old_image = imread(road_read_image); Mat new_min_image; Mat new_translation_image; Mat rotate_image; Mat translater(2, 3, CV_32F, Scalar(0)); Mat rotater; Mat fangshe_image; Mat toushi_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)); Point rotate_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); bool flags = 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 Called By: input_solve Table Accessed: NONE Table Updated: NONE ----------------------------------------------------------------------------- Input: 第一个参数:由两点表示的类型为Vec4i的直线A 第二个参数:由两点表示的类型为Vec4i的直线B Output:Point2f的点 Return:Point2f的点 Others:NONE --------------------------------------------------------------------------------*/ Point2f getCrossPoint(Vec4i LineA, Vec4i LineB) { double ka, 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]); Point2f crossPoint; 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); return crossPoint; } /*---------------------------------------------------------------------- Function: input_solve Description:用于打开图像、滤波、提取边缘、绘制边缘、透视变换矫正文档的函数。注意,本函数中图像 处理过程中的参数已经调整完毕 ------------------------------------------------------------------------ Calls: imread、resize、morphologyEx、blur、Canny、HoughLines、warpPerspective Called By: main Table Accessed: NONE Table Updated: NONE ------------------------------------------------------------------------- Input: 第一个参数:输入图像的路径 Output:经过文档矫正后的图像 Return:NONE Others:矫正图像保存于当前目录下: "C:/Users/liujinyuan/source/repos/作业2_2/作业2_2/task2_2矫正.png" ---------------------------------------------------------------*/ void input_solve(const char* image_road) { //定义保存图像参数向量 vector compression_params; compression_params.push_back(IMWRITE_PNG_COMPRESSION); compression_params.push_back(9); //获取闭运算滤波的核 Mat element = getStructuringElement(MORPH_RECT, Size(5, 5)); Mat new_min_image; Mat last_kernel; //获取灰度图 Mat old_image = imread(image_road,0); vector lines; vector coners; vector lines_2pt(10); Point pt1, pt2,pt3,pt4,pt5,pt6; Mat last_image; Mat new_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_t i = 0 ; i < lines.size(); i++) { if (i!=lines.size()-2) { float zhongxinjuli = lines[i][0], theta = lines[i][1]; double cos_theta = cos(theta), sin_theta = sin(theta); double x0 = zhongxinjuli * cos_theta, y0 = zhongxinjuli * sin_theta; pt1.x = cvRound(x0 - 1000 * sin_theta); pt1.y = cvRound(y0 + 1000 * cos_theta); pt2.x = cvRound(x0 + 1000 * sin_theta); pt2.y = cvRound(y0 - 1000 * cos_theta); line(new_min_image, pt1, pt2, Scalar(255, 255, 255), 1, LINE_AA); } } //获取霍夫变换直线的交点 for (rsize_t flag = 0,flag2=0; flag < lines.size(); flag++) { if (flag != lines.size() - 2) { float zx_juli = lines[flag][0], theta2 = lines[flag][1]; double cos_theta2 = cos(theta2), sin_theta2 = sin(theta2); double x1 = zx_juli * cos_theta2, y1 = zx_juli * sin_theta2; lines_2pt[flag2][0]= cvRound(x1 - 1000 * sin_theta2); lines_2pt[flag2][1] = cvRound(y1 +1000 * cos_theta2); lines_2pt[flag2][2] = cvRound(x1 + 1000 * sin_theta2); lines_2pt[flag2][3] = cvRound(y1 - 1000 * cos_theta2); flag2++; } } for(int flag3=0;flag3<4;flag3++) { cout << "line_vector=" <
/*------------------------------------------------------------------------------------------------------------------------------------- Copyright (C),2018---, HUST Liu File name:文档矫正项目.cpp Author: 刘峻源 Version: 1 Date: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 注意:在其他环境运行时一定要弄好更改读入路径!!!! May Function List: main、main_transform、input_solve ----------------------------------------------------------------------------------------------- History: as folwing ----- ------------- ---------------- ------------------ -------------- ----- 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" using namespace std; using namespace cv; int main() { main_transform(90, 0, 100, "task2.png", 0.5, 0.5); input_solve("task2.png"); waitKey(0); }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。