700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > open cv平面对象检测及翘曲图像校正-基于FLANN的特征检测和透视变换

open cv平面对象检测及翘曲图像校正-基于FLANN的特征检测和透视变换

时间:2021-10-24 12:35:33

相关推荐

open cv平面对象检测及翘曲图像校正-基于FLANN的特征检测和透视变换

接着上篇FLANN特征匹配,从上篇可以知道,如果特征匹配时全部是用线进行匹配,匹配效果并不能达到一目了然的效果

那么,可不可以把匹配到的结果用矩形或圆表示出来呢 当然可以,这就是平面对象识别

关于基于透视变换的平面对象识别

主要用到两个新的API

cv::findHomography()– 发现两个平面的透视变换,生成透视变换矩阵

cv::perspectiveTransform()– 进行透视变换

关于透视变换

透视变换(Perspective Transformation)可以对图片进行校正也可以通过透视变换进行图像的平面识别

透视变换(Perspective Transformation)是指利用透视中心、像点、目标点三点共线的条件,

按透视旋转定律使承影面(投影面)绕迹线(透视轴)旋转某一角度,破坏原有的投影光线束,

仍能保持承影面上投影几何图形不变的变换

单应矩阵透视变换进行图像矫正,最少需要四个对应点对

关于基于透视变换的图像校正

透视变换对畸变图像的校正需要取得畸变图像的一组4个点的坐标,和目标图像的一组4个点的坐标,通过两组坐标点可以计算出透视变换的变换矩阵,之后对整个原始图像执行变换矩阵的变换,就可以实现图像校正

图像校正API

cv::warpPerspective对翘曲图像进行校正显示

代码演示

#include <opencv2/opencv.hpp>#include <opencv2/highgui/highgui_c.h>#include <opencv2/xfeatures2d.hpp>#include <iostream>#include <math.h>using namespace cv;using namespace std;using namespace cv::xfeatures2d;int Osize = 3;Mat SenceIMG, ObjIMG, dst;void FLANNdetector(int, void*);void WarpPerspective(int, void*);int main(){ObjIMG = imread("D:/实验台/机器视觉/测试图片/自由女神小.jpg", IMREAD_GRAYSCALE);//训练图像SenceIMG = imread("D:/实验台/机器视觉/测试图片/自由女神像.jpg", IMREAD_GRAYSCALE);//查询图像if (SenceIMG.empty() || ObjIMG.empty())//如果SenceIMG这个数据库属性为空{cout << "无法打开" << endl;return -1;}imshow("训练图像", SenceIMG);imshow("查询图像", ObjIMG);namedWindow("透视变换画出目标轮廓的效果", CV_WINDOW_AUTOSIZE);createTrackbar("最大允许重投影错误阈值", "透视变换画出目标轮廓的效果", &Osize, 150, FLANNdetector);FLANNdetector(0, 0);WarpPerspective(0, 0);waitKey(0);return 0;}

FLANN特征检测

void FLANNdetector(int, void*){//copy自FLANN特征检测项目的代码 int minHessian = 400;//特征检测器阈值Ptr<SURF> detector = SURF::create(minHessian);//构建SURF类的特征点检测器vector<KeyPoint>keypointOBJ; // 设置用于存放第一张图特征点信息的 KeyPoint类的集合向量 keypointsvector<KeyPoint>keypointSCENE;//同上 待匹配的第二张图的特征点信息Mat descriptorOBJ, descriptorSCENE; //建立两个描述符集矩阵分别存放图一的查询描述集和图二的训练描述集合//进行对两张需要进行匹配的图像的特征提取和描述符信息的计算detector->detectAndCompute(ObjIMG, Mat(), keypointOBJ, descriptorOBJ);detector->detectAndCompute(SenceIMG, Mat(), keypointSCENE, descriptorSCENE);//todocv :: Feature2D :: detectAndCompute检测特征点并计算其描述子的函数(使用detector这个检测器)基于Flann类型的描述符匹配器FlannBasedMatcher matcher;//todo建立查询训练集中的描述符得出的 匹配项描述符 所需要的动态向量DMatch类数组vector<DMatch>matches;//未进行筛选前的匹配描述符matcher.match(descriptorOBJ, descriptorSCENE, matches);//todovoid cv::DescriptorMatcher::match使用建立好的Flann匹配器matcher进行匹配//匹配相关度最高的特征点过滤不相干的特征点double minDist = 1000;double maxDist = 0;//通过遍历两个匹配的特征向量之间的欧氏距离(距离越近匹配度越高)进行精确特征点的筛选for (int i = 0; i < descriptorOBJ.rows; i++){double dist = matches[i].distance;//读取匹配特征描述符集中的欧式距离if (dist > maxDist){maxDist = dist;//寻找距离最大值}if (dist < minDist){minDist = dist;//寻找距离最小值}}printf("最大欧氏距离为 %f\n", maxDist);printf("最小欧氏距离为 %f\n", minDist);//todo建立存储过滤欧氏距离时大于阈值的匹配描述符的DMatch类向量矩阵goodMatchesvector<DMatch>goodMatches; //被压入此矩阵内的匹配描述符信息将被认为是准确的匹配描述符进行绘制for (int i = 0; i < descriptorOBJ.rows; i++){double dist = matches[i].distance;//读取匹配特征描述符集中的欧式距离if (dist < max(2*minDist, 0.02)){goodMatches.push_back(matches[i]);}}Mat MatchesIMG;drawMatches(ObjIMG, keypointOBJ, SenceIMG, keypointSCENE, goodMatches, MatchesIMG, Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);//todovoid cv::drawMatches 对进行匹配的两张图片水平排列,然后在最佳匹配的点之间绘制直线imshow("FLANN特征匹配结果", MatchesIMG);//imshow("descriptorOBJ-查询描述符集", descriptorOBJ);//imshow("descriptorSCENE-训练描述符集", descriptorSCENE);//?copy自 FLANN特征检测项目代码到此为止

平面对象识别findHomography+perspectiveTransform对最佳匹配keypoint关键点进行几何变换

//建立两组存放透视变换矩阵坐标点的向量 (PT为透视变换PerspectiveTransformation)vector<Point2f>PTOBJ;//存放最佳匹配的 匹配项描述符集中的 训练图像的特征描述符的坐标位置信息(浮点型二维x,y)vector<Point2f>PTSCENE;//存放最佳匹配的 匹配项描述符集中的 查询图像的特征描述符的坐标位置信息(浮点型二维x,y)//todo有了这两组位置信息 就为训练图像和目标图像中的目标 构建了坐标对应关系 //todo遍历最佳匹配 匹配项描述符集 并将训练图像和查询图像的特征描述符中的坐标信息压入透视变换的矩阵坐标向量中(利用这两组值进行投射矩阵的计算)for (size_t i = 0; i < goodMatches.size(); i++){PTOBJ.push_back(keypointOBJ[goodMatches[i].queryIdx].pt); // queryIdx:是测试图像(训练图像)的特征点描述符(descriptorOBJ)的下标,同时也是描述符对应特征点(keypointOBJ)的下标PTSCENE.push_back(keypointSCENE[goodMatches[i].trainIdx].pt); //trainIdx:是样本图像(查询图像)的特征点描述符的下标,同样也是相应的特征点的下标cout << endl;cout << "被压入的第" << i + 1 << "对训练图像的特征描述符在过滤后的匹配项描述符集中的索引为" << goodMatches[i].queryIdx << "此描述符对应的关键点的坐标为" << keypointOBJ[goodMatches[i].queryIdx].pt << endl;cout << "被压入的第" << i + 1 << "对查询图像的特征描述符在过滤后的匹配项描述符集中的索引为" << goodMatches[i].trainIdx << "此描述符对应的关键点的坐标为" << keypointSCENE[goodMatches[i].trainIdx].pt << endl;cout << endl;/*注:在提取特征点描述符坐标时虽然 keypointOBJ.pt 和 keypointSCENE[goodMatches[i].trainIdx].pt的意义一样 都是提取特征描述符坐标的坐标信息 但是一定要写成 keypointSCENE[goodMatches[i].trainIdx].pt 因为goodMatches[i].trainIdx].pt 才是过滤后的匹配度高的特征点!!*/}//获取了训练图像的特征描述符的坐标信息 和 查询图像的特征描述符的坐标信息 利用两个坐标点集就可以生成一个透视变换矩阵// 使用findHomography函数 计算多个二维点对之间的最优投射矩阵 H (3行*3列) 使用最小均方误差 或 RANSAC方法Mat H = findHomography(PTOBJ, PTSCENE, RANSAC,Osize);//todocv::findHomography(单应性矩阵计算) 通过源平面坐标矩阵(PTOBJ)和目标平面坐标矩阵(PTSCENE)之间的坐标对应关系 计算透视变换矩阵H// 1.InputArray srcPoints -- 源平面(训练图像) 中点的坐标矩阵,可以是CV_32FC2类型,也可以是vector<Point2f>类型// 2.InputArray dstPoints -- 目标平面(查询图像) 中点的坐标矩阵,可以是CV_32FC2类型,也可以是vector<Point2f>类型// 3.int method = 0 -- 用于计算单应性矩阵的方法 有以下四个参数 (此值需按情况调节)// 0-利用所有点(常规方法)//RANSAC 基于RANSAC的鲁棒性算法//LMEDS 最小中值鲁棒算法//RHO 基于PROSAC的鲁棒算法5// 4.double ransacReprojThreshold = 3 -- 将点对视为内点的最大允许重投影错误阈值 (仅用于RANSAC和RHO方法) (此值需按情况调节)//原图像的点经过变换后点与目标图像上对应点的误差,超过误差就认为是异常值,该参数通常设置在1到10的范围内// 5.OutputArray mask = noArray() -- 可选输出掩码矩阵,通常由鲁棒算法(RANSAC或LMEDS)设置。 请注意,输入掩码矩阵是不需要设置的 (此值多数情况下不需要使用)// 6.const int maxIters = 2000 -- RANSAC算法的最大迭代次数,默认值和最大值皆为2000 (此值需按情况调节)// 7.const double confidence = 0.995--可信度值,取值范围为0到1 (此值越大越准确)//注 :该函数 根据PTOBJ的顶点数据与PTSCENE的顶点数据,返回由PTOBJ变换到PTSCENE的变换矩阵Himshow("H",H);//显示投射矩阵H的信息vector<Point2f>PTOBJCorners(4);//建立 存储进行透视变换前 训练图像的角点vector<Point2f>PTSceneCorners(4);//建立存储对透视变换前训练图像的角点进行透视变换后 在合成的匹配图像中绘制的矩形的四个角点坐标//设置训练图像的四个角点位置为透视变换时需要输入的训练图像的四个角点信息PTOBJCorners[0] = Point(0, 0); //左上角(第一点)坐标PTOBJCorners[1] = Point(ObjIMG.cols, 0); //右上角(第二点)坐标PTOBJCorners[2] = Point(ObjIMG.cols, ObjIMG.rows); //右下角(第三点)坐标PTOBJCorners[3] = Point(0, ObjIMG.rows); //右下角(第三点)坐标cout << " 透视矩阵变换前 在合成的匹配图像中绘制的标记目标的矩形的四个角点坐标为" << endl << PTSceneCorners << endl;//todo有了训练图像的目标的四个角点的坐标就可以通过变换矩阵H配合perspectiveTransform透视矩阵变换得到查询图像上目标对应训练图像的四个角点的坐标//使用cv2.perspectiveTransform()来找目标,它需要至少4个正确的点来进行透视变换perspectiveTransform(PTOBJCorners, PTSceneCorners, H);//todocv::perspectiveTransform() 利用计算出的透视变换矩阵H 执行矢量的透视矩阵变换(将训练图像的四个角点投影到一个合成图像的对应的四个角点)// 1.InputArray src -- 输入的训练图像的四个角点坐标信息(双通道或三通道浮点数组/图像)// 2.OutputArray dst -- 输出透视变换前训练图像的角点用变换矩阵H进行透视变换后 在合成的匹配图像中计算出的绘制的矩形的四个角点坐标信息// 3.InputArray m -- 3x3或4x4浮点转换矩阵H/*注:透视矩阵变换的工作过程 1.取的需要进行透视变换的训练图像的四个角点坐标(PTOBJCorners)2.用之前生成的用于计算透视转换矩阵H(H为投射矩阵)进行对原角点的透视转换 (H)3.转换完毕的角点为在合成图像上绘制矩形的角点(PTSceneCorners)*/cout << " 透视矩阵变换前 训练图像的四个角点坐标为" << endl << PTOBJCorners << endl;cout << " 透视矩阵变换后 在合成的匹配图像中绘制的标记目标的矩形的四个角点坐标为" << endl << PTSceneCorners << endl;// draw line 因为matchesImg是两张图像的合成,所以若要在matchesImg上显示找到的书本的位置,x坐标需要偏移img1.colsline(MatchesIMG, PTSceneCorners[0] + Point2f(ObjIMG.cols, 0), PTSceneCorners[1] + Point2f(ObjIMG.cols, 0), Scalar(0, 0, 255), 2, 8, 0);line(MatchesIMG, PTSceneCorners[1] + Point2f(ObjIMG.cols, 0), PTSceneCorners[2] + Point2f(ObjIMG.cols, 0), Scalar(0, 0, 255), 2, 8, 0);line(MatchesIMG, PTSceneCorners[2] + Point2f(ObjIMG.cols, 0), PTSceneCorners[3] + Point2f(ObjIMG.cols, 0), Scalar(0, 0, 255), 2, 8, 0);line(MatchesIMG, PTSceneCorners[3] + Point2f(ObjIMG.cols, 0), PTSceneCorners[0] + Point2f(ObjIMG.cols, 0), Scalar(0, 0, 255), 2, 8, 0);//Point2f(SenceIMG.cols, 0)为偏移量dst=SenceIMG;line(dst, PTSceneCorners[0], PTSceneCorners[1], Scalar(0, 0, 255), 2, 8, 0);line(dst, PTSceneCorners[1], PTSceneCorners[2], Scalar(0, 0, 255), 2, 8, 0);line(dst, PTSceneCorners[2], PTSceneCorners[3], Scalar(0, 0, 255), 2, 8, 0);line(dst, PTSceneCorners[3], PTSceneCorners[0], Scalar(0, 0, 255), 2, 8, 0);imshow("透视变换画出目标轮廓的效果", MatchesIMG); // 在合成的matchesImg上显示找到的书本imshow("透视变换画出目标轮廓的效果不使用偏移量绘制", dst); // 在原训练图上显示找到的书本//todo注意!训练图像的大小对匹配结果至关重要 如果训练图像尺寸太小 会导致无法绘制}

效果

实战!利用透视变换矩阵h和和翘曲透视变换对 翘曲图像进行校正

利用findHomography计算投射矩阵+warpPerspective透视变换进行翘曲图像的校正

void WarpPerspective(int, void*){Mat Src = imread("D:/实验台/机器视觉/测试图片/书本1.jpg");//首先要选中翘曲图像中的书本目标的四个角点的坐标值并压入相应的向量矩阵中vector<Point2f> PTSsrc;PTSsrc.push_back(Point2f(65, 62));//书本目标左上角PTSsrc.push_back(Point2f(226, 74));//书本目标右上角PTSsrc.push_back(Point2f(233, 299));//书本目标右下角PTSsrc.push_back(Point2f(26, 286));//书本目标左下角//其次建立大小为(300*400)的校正图像矩阵 并将校正图像的四个角点坐标值依次压入相应的向量矩阵中vector<Point2f> PTSdst;PTSdst.push_back(Point2f(0,0));//校正图像左上角PTSdst.push_back(Point2f(300, 0));//校正图像左上角PTSdst.push_back(Point2f(300, 400));//校正图像左上角PTSdst.push_back(Point2f(0, 400));//校正图像左上角//todo建立翘曲和校正两个图像的各四个角点坐标 是为了对应他们彼此的坐标关系 (有了这个坐标关系就可以计算他们的投射矩阵H 有了投射矩阵就可以进行依照彼此坐标的联系进行透视变换)Mat h = findHomography(PTSsrc, PTSdst);//todocv::findHomography(单应性矩阵计算) 通过源平面(PTOBJ)和目标平面(PTSCENE)之间的坐标对应关系 生成投射矩阵H// 1.InputArray srcPoints -- 源平面(翘曲图像中的目标的四个角点) 的坐标矩阵,可以是CV_32FC2类型,也可以是vector<Point2f>类型// 2.InputArray dstPoints -- 目标平面(建立的校正后存放目标的矩阵图像的四个角点) 的坐标矩阵,可以是CV_32FC2类型,也可以是vector<Point2f>类型//返回值:一个Mat类型的矩阵h,作为接下来投射变换的参数// 3.int method = 0 //3.int method = 0 -- 用于计算单应性矩阵的方法 已给出准确的源平面和目标平面的对应性矩阵信息 直接利用所有点进行计算即可( 0-利用所有点(常规方法))// 4.double ransacReprojThreshold = 3 默认值即可// 5.OutputArray mask = noArray() -- 默认值即可// 6.const int maxIters = 2000 -- 默认值即可// 7.const double confidence = 0.995-默认值即可imshow("透视变换矩阵H",h);//建立透视变换后的校正图像的矩阵Mat ResultIMG;warpPerspective(Src, ResultIMG, h,Size(300,400));//todocv::warpPerspective(翘曲透视变换)对翘曲图像进行透视变换并生成校正图像//1.cv::InputArray src -- 输入图像(将h中的输入图像的书本目标的四个角点的坐标 透视变换到 输出图像的四个角点坐标)//2.cv::OutputArray dst -- 输出图像(把变换矩阵h中的四个目标图像角点的坐标对应输出图像的角点坐标进行透视变换)//3.cv::InputArray M -- 输入的 3x3 变换矩阵h(此矩阵变换了需要校正的目标的四个角点标PTSsrc 和校正后准备好的新角点坐标PTSdst)//4.cv::Size dsize -- 输出图像的大小//5.int flags = cv::INTER_LINEAR -- 输出图像的插值方法(线性插值或者最近邻插值)//6.int borderMode = cv::BORDER_CONSTANT -- 图像边界的处理方式 可选(BORDER_CONSTANT或BORDER_REPLICATE)//7.const cv::Scalar& borderValue = cv::Scalar() -- 边界的颜色设置,一般默认是0imshow("包含书本目标的翘曲图像", Src);imshow("经过选中书本目标并透射变换后的校正图像", ResultIMG);waitKey(0);}

效果

鼠标点击选取角点版本

struct userdata {Mat im;vector<Point2f> points;};void mouseHandler(int event, int x, int y, int flags, void* data_ptr){if (event == EVENT_LBUTTONDOWN){userdata* data = ((userdata*)data_ptr);circle(data->im, Point(x, y), 3, Scalar(0, 0, 255), 5, CV_AA);imshow("Image", data->im);if (data->points.size() < 4){data->points.push_back(Point2f(x, y));}}}void WarpPerspective(int,void*){Mat im_src = imread("D:/实验台/机器视觉/测试图片/书本1.jpg");// Destination image. The aspect ratio of the book is 3/4Size size(300, 400);Mat im_dst = Mat::zeros(size, CV_8UC3);// Create a vector of destination points.vector<Point2f> pts_dst;pts_dst.push_back(Point2f(0, 0));pts_dst.push_back(Point2f(size.width - 1, 0));pts_dst.push_back(Point2f(size.width - 1, size.height - 1));pts_dst.push_back(Point2f(0, size.height - 1));// Set data for mouse eventMat im_temp = im_src.clone();userdata data;data.im = im_temp;cout << "Click on the four corners of the book -- top left first and" << endl<< "bottom left last -- and then hit ENTER" << endl;// Show image and wait for 4 clicks.imshow("Image", im_temp);// Set the callback function for any mouse eventsetMouseCallback("Image", mouseHandler, &data);waitKey(0);// Calculate the homographyMat h = findHomography(data.points, pts_dst);// Warp source image to destinationwarpPerspective(im_src, im_dst, h, size);// Show imageimshow("Image", im_dst);waitKey(0);}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。