近日在做基于sift特征点的图像配准时遇到匹配失败的情况,失败的原因在于两幅图像分辨率相差有点大,而且这两幅图是不同时间段的同一场景的图片,所以基于sift点的匹配已经找不到匹配点了。然后老师叫我尝试手动选择控制点来支持仿射变换。

很可惜opencv里没有这类似的库,查了下资料,看看有没有现成的手动配准软件,找到了arcgis这款软件可以做手动配准,不过这软件也都太大了吧我要的只是一个简单的功能而已!然后想了想,还是自己写个手动配准工具吧。

首先简单通俗说一下什么是图像配准。先观察一下下面两张图片。

这是两张从不同角度拍的场景,他们有大部分的重合,如果我们需要把这两张图拼接成一幅更大的图,我们需要做第一件事就是对他们进行配准,即对图二进行变换,令图二的物体转换到图一的坐标系,使得像素一一对应,这就是图像配准。

现在图像的配准方法有很多,比如基于特征点的配准,也有基于互信息的配准,都有广泛应用。现在我们使用特征点来配准,关键就在于找出两幅图像尽可能多对应的特征点,来求出变换矩阵,然后将待配准图进行变换。

现在实现一个简易的手动选择控制点的配准工具第一个版本,步骤有:

  1. 搭建交互界面,可以对两幅图自由选点,并把点坐标存储起来
  2. 求出变换矩阵
  3. 利用变换矩阵对待配准图进行仿射变换

根据以上思路,有以下代码

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"#include <cv.h>
#include <cxcore.h>
#include <highgui.h>  #include <iostream>using namespace std;
using namespace cv;vector<Point2f> imagePoints1, imagePoints2;Mat ref_win, src_win;
int pcount = 0;void on_mouse1(int event, int x, int y, int flags, void *ustc) //event鼠标事件代号,x,y鼠标坐标,flags拖拽和键盘操作的代号
{if (event == CV_EVENT_LBUTTONDOWN)//左键按下,读取初始坐标,并在图像上该点处打点{Point  p = Point(x, y);circle(ref_win, p, 1, Scalar(0, 0, 255), -1);imshow("基准图", ref_win);imagePoints1.push_back(p);   //将选中的点存起来cout << "基准图: " << p << endl;pcount++;cout << "ponit num:" << pcount << endl;}
}void on_mouse2(int event, int x, int y, int flags, void *ustc) //event鼠标事件代号,x,y鼠标坐标,flags拖拽和键盘操作的代号
{if (event == CV_EVENT_LBUTTONDOWN)//左键按下,读取初始坐标,并在图像上该点处打点{Point  p = Point(x, y);circle(src_win, p, 1, Scalar(0, 0, 255), -1);imshow("待配准图", src_win);imagePoints2.push_back(p);   //将选中的点存起来cout << "待配准图: " << p << endl;}
}int main()
{Mat ref = imread("ref.png");  //基准图Mat src = imread("src.png");  //待配准图ref_win = ref.clone();src_win = src.clone();namedWindow("待配准图");namedWindow("基准图");imshow("待配准图", src_win);imshow("基准图", ref_win);setMouseCallback("待配准图", on_mouse2);setMouseCallback("基准图", on_mouse1);waitKey();string str;printf("往下执行?\n");cin >> str;//求变换矩阵Mat homo = findHomography(imagePoints2, imagePoints1, CV_RANSAC);Mat imageTransform1;warpPerspective(src, imageTransform1, homo, Size(ref.cols, ref.rows));   //变换imshow("transform", imageTransform1);imshow("基准图打点", ref_win);imshow("待配准图打点", src_win);imshow("变换图", imageTransform1);imwrite("result.jpg", imageTransform1);imwrite("src_p.jpg", src_win);imwrite("ref_p.jpg", ref_win);waitKey();return 0;
}

运行一下,弹出两幅图,一张是基准图,一张待配准图,我们仔细找出两者的匹配点,然后用鼠标左键点击该点,那么这个点的坐标信息就被记录下来了。注意匹配点的顺序必须一一对应,比如用鼠标在基准图点击了一个点,那么我们也必须在待配准图也点击对应的匹配点。

效果如下:
手动选择控制点(红点就是我们选中的点)

配准效果

再换个图试试吧

控制点选择

配准效果

这么一个简易手动配准工具1.0算是完成了。但是我们使用时遇到了新的问题,那就是需要两幅图的尺寸太大了,显示器根本没法显示完整个图像!有人会说,把图像缩小再配准不行吗?缩小再配准的话,精度就不能保证了,因为配准时像素级别的。要精确配准,就得用原图。

可惜opencv没有提供浏览大图的工具,那就只能自己再写一写了。

好在可以借助前辈们的经验
http://blog.csdn.net/chenyusiyuan/article/details/6565424

那就在原来代码的基础加点东西,来适应这种“浏览大图的效果”。但是其中需要改动的东西很多,所以1.0的代码几乎全改了。因为前辈的这种浏览大图的效果是拥塞的,只能在一幅图操作完之后才可以操作另一幅图,这个限制对于我们配准操作而言是无法接受的,所以我使用了多线程来操作这个窗口,使得我们可以随意在任何一张图片打点,随时切换。

下面是手动配准工具2.0版本的代码

main.cpp

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/opencv.hpp"
#include <Windows.h>
#include <iostream>
#include "NewWindows.h"using namespace std;
using namespace cv;void CreateWindows(char* s, char* pic);
void CreateWindows2(char* s, char* pic);vector<Point2f> imagePoints1, imagePoints2;  //记录匹配点DWORD WINAPI ThreadFun1(LPVOID pM)
{NewWindow ref_obj("基准", "ref.jpg");ref_obj.CreateWindows();imagePoints1 = ref_obj.imagePoints;return 0;
}DWORD WINAPI ThreadFun2(LPVOID pM)
{NewWindow src_obj("待变换", "src.jpg");src_obj.CreateWindows();imagePoints2 = src_obj.imagePoints;return 0;
}int HandSlectPoint()
{Mat tsrc1 = imread("ref.jpg");  //基准图Mat tsrc2 = imread("src.jpg");while (1){
#if 1imagePoints1.clear();imagePoints2.clear();HANDLE handle1 = CreateThread(NULL, 0, ThreadFun1, NULL, 0, NULL);  //创建线程HANDLE handle2 = CreateThread(NULL, 0, ThreadFun2, NULL, 0, NULL);printf("往下执行?\n");//先拥塞住,点选完再进行计算变换矩阵string s;cin >> s;Mat homo = findHomography(imagePoints2, imagePoints1, CV_RANSAC);Mat imageTransform1;warpPerspective(tsrc2, imageTransform1, homo, Size(tsrc1.cols, tsrc1.rows));imwrite("trans.jpg", imageTransform1); //把配准后结果存起来CloseHandle(handle1);//销毁线程1  CloseHandle(handle2);//销毁线程1  #endifprintf("是否结束?\n");//判断是否结束,如果点选得不好,就再来一次string str;cin >> str;if (str == "yes")break;}return 0;
}int main()
{HandSlectPoint();return 0;
}

NewWindows.cpp

#include "NewWindows.h"NewWindow::NewWindow(char* label, char* pic_name)
{this->pic_name = pic_name;this->label = label;
}void NewWindow::mouse_callback(int event, int x, int y, int flags, void* param)
{p = Point(x, y);pp = Point(x + x_offset, y + y_offset);if (needScroll){switch (event){case CV_EVENT_RBUTTONDOWN:mx = x, my = y;dx = 0, dy = 0;// 按下左键时光标定位在水平滚动条区域内  if (x >= rect_bar_horiz.x && x <= rect_bar_horiz.x + rect_bar_horiz.width&& y >= rect_bar_horiz.y && y <= rect_bar_horiz.y + rect_bar_horiz.height){clickHorizBar = true;}// 按下左键时光标定位在垂直滚动条区域内  if (x >= rect_bar_verti.x && x <= rect_bar_verti.x + rect_bar_verti.width&& y >= rect_bar_verti.y && y <= rect_bar_verti.y + rect_bar_verti.height){clickVertiBar = true;}break;case CV_EVENT_MOUSEMOVE:if (clickHorizBar){dx = fabs(x - mx) > 1 ? (int)(x - mx) : 0;dy = 0;}if (clickVertiBar){dx = 0;dy = fabs(y - my) > 1 ? (int)(y - my) : 0;}mx = x, my = y;break;case CV_EVENT_RBUTTONUP:mx = x, my = y;dx = 0, dy = 0;clickHorizBar = false;clickVertiBar = false;break;case CV_EVENT_LBUTTONDOWN://cvShowImage("jizuhn",dst_img);imagePoints.push_back(pp);cout << label <<": "<< pp << endl;//_p1count++;//cout << "zhihuan count:" << _p1count << endl;flag = 1;//dx = 0, dy = 0;break;default:dx = 0, dy = 0;break;}}
}void NewWindow::myShowImageScroll(char* title, IplImage* src_img, int winWidth, int winHeight ) // 显示窗口大小默认为 1400×700
{CvRect  rect_dst,   // 窗口中有效的图像显示区域  rect_src;   // 窗口图像对应于源图像中的区域  int imgWidth = src_img->width,imgHeight = src_img->height,barWidth = 25;  // 滚动条的宽度(像素)  double  scale_w = (double)imgWidth / (double)winWidth,    // 源图像与窗口的宽度比值  scale_h = (double)imgHeight / (double)winHeight;  // 源图像与窗口的高度比值  if (scale_w<1)winWidth = imgWidth + barWidth;if (scale_h<1)winHeight = imgHeight + barWidth;int showWidth = winWidth, showHeight = winHeight; // rect_dst 的宽和高  int src_x = 0, src_y = 0;   // 源图像中 rect_src 的左上角位置  int horizBar_width = 0, horizBar_height = 0,vertiBar_width = 0, vertiBar_height = 0;needScroll = scale_w>1.0 || scale_h>1.0 ? TRUE : FALSE;// 若图像大于设定的窗口大小,则显示滚动条  if (needScroll){IplImage* dst_img = cvCreateImage(cvSize(winWidth, winHeight), src_img->depth, src_img->nChannels);cvZero(dst_img);// 源图像宽度大于窗口宽度,则显示水平滚动条  if (1){showHeight = winHeight - barWidth;horizBar_width = (int)((double)winWidth / scale_w);horizBar_height = winHeight - showHeight;horizBar_x = min(max(0, horizBar_x + dx),winWidth - horizBar_width);rect_bar_horiz = cvRect(horizBar_x,showHeight + 1,horizBar_width,horizBar_height);// 显示水平滚动条  cvRectangleR(dst_img, rect_bar_horiz, cvScalarAll(255), -1);}// 源图像高度大于窗口高度,则显示垂直滚动条  if (scale_h > 1.0){// printf("come!\n");showWidth = winWidth - barWidth;vertiBar_width = winWidth - showWidth;vertiBar_height = (int)((double)winHeight / scale_h);vertiBar_y = min(max(0, vertiBar_y + dy),winHeight - vertiBar_height);//printf("vertiBar_width:%d vertiBar_height:%d\n", vertiBar_width, vertiBar_height);//printf("x:%d y:%d\n", showWidth + 1, vertiBar_y);rect_bar_verti = cvRect(showWidth + 1,vertiBar_y,vertiBar_width,vertiBar_height);// 显示垂直滚动条  //printf("w:%d h:%d\n", dst_img->width, dst_img->height);cvRectangleR(dst_img, rect_bar_verti, cvScalarAll(255), -1);}showWidth = min(showWidth, imgWidth);showHeight = min(showHeight, imgHeight);// 设置窗口显示区的 ROI  rect_dst = cvRect(0, 0, showWidth, showHeight);cvSetImageROI(dst_img, rect_dst);// 设置源图像的 ROI  src_x = (int)((double)horizBar_x*scale_w);src_y = (int)((double)vertiBar_y*scale_h);src_x = min(src_x, imgWidth - showWidth);src_y = min(src_y, imgHeight - showHeight);rect_src = cvRect(src_x, src_y, showWidth, showHeight);x_offset = src_x;y_offset = src_y;cvSetImageROI(src_img, rect_src);if (flag == 1){cvCircle(src_img, p, 3, Scalar(0, 0, 255), -1);flag = 0;}// 将源图像内容复制到窗口显示区  cvCopy(src_img, dst_img);cvResetImageROI(dst_img);cvResetImageROI(src_img);// 显示图像和滚动条  cvShowImage(title, dst_img);cvReleaseImage(&dst_img);}// 源图像小于设定窗口,则直接显示图像,无滚动条  else{cvShowImage(title, src_img);}
}void m_callback(int event, int x, int y, int flags, void* param)
{NewWindow* p_win = (NewWindow*)param;p_win->mouse_callback(event, x, y, flags, NULL);
}void NewWindow::CreateWindows()
{int width = 1200, height = 700;  //显示的图片大小cvNamedWindow(label, 1);cvSetMouseCallback(label, m_callback, this);image = cvLoadImage(pic_name, CV_LOAD_IMAGE_COLOR);while (1){myShowImageScroll(label, image, width, height);//Sleep(100);int KEY = cvWaitKey(10);if ((char)KEY == 27)break;}cvDestroyWindow(label);
}

NewWindows.h

#ifndef __NEW_WINDOWS_H__
#define __NEW_WINDOWS_H__#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc_c.h>
#include <Windows.h>
#include <iostream>
#include <vector>  #define FALSE 0
#define TRUE 1using namespace std;
using namespace cv;class NewWindow
{
public:vector<Point2f> imagePoints;void CreateWindows();void mouse_callback(int event, int x, int y, int flags, void* param);NewWindow(char* label, char* pic_name);private:double mx = 0, my = 0;int dx = 0, dy = 0, horizBar_x = 0, vertiBar_y = 0;bool clickVertiBar = false, clickHorizBar = false, needScroll = false;CvRect rect_bar_horiz, rect_bar_verti;IplImage* image;Point  p;Point pp;int flag = 0;int x_offset;int y_offset;char* pic_name;char* label;void myShowImageScroll(char* title, IplImage* src_img,int winWidth = 1400, int winHeight = 700); // 显示窗口大小默认为 1400×700  };#endif

看看效果吧,现在我们需要对两张2000*2000的图像进行配准,因为我们的显示器无法完全显示整张图片,所以使用了这个带浏览大图的工具来进行配准。可以看到,显示图的右侧和下侧都有滚动条,我们只需按住鼠标右键拖动即可浏览到显示不到的区域,同样地,我们是点击鼠标左键实现选点。

点的坐标一一记录

配准之后,可以看出图像发生了轻微形变,与基准图一对比,发现配准成功。

有几个园友发信息给我,说不知这个程序怎么用,那我在这里总结一下使用步骤:

1.左键选择控制点,右键是用于滚动条的。选择控制点的时候注意在图一选了点后需要在图二也选好对应点,形成控制点对。

2.当控制点对全部选好后按“esc”关闭窗口。要按两次,因为要关闭两个窗口。

3.按键盘任意键开始透视变换。

4.如果你觉得这次变换OK,就按yes退出

OpenCV精进之路(十七):工具——图像配准工具相关推荐

  1. OpenCV精进之路(十六):图像分解和融合技术——图像拼接和图像融合技术

    图像拼接在实际的应用场景很广,比如无人机航拍,遥感图像等等,图像拼接是进一步做图像理解基础步骤,拼接效果的好坏直接影响接下来的工作,所以一个好的图像拼接算法非常重要. 再举一个身边的例子吧,你用你的手 ...

  2. OpenCV精进之路(十九):工具——程序打包发布

    我们经常遇到这样的需求:我们在VS写好的程序,需要在一个没有装opencv甚至没有装vs的电脑下运行,跑出效果.比如,你在你的电脑用opencv+vs2015写出一个程序,然后老师叫你把程序发给他,他 ...

  3. OpenCV精进之路(十五):特征检测和特征匹配方法汇总

    一幅图像中总存在着其独特的像素点,这些点我们可以认为就是这幅图像的特征,成为特征点.计算机视觉领域中的很重要的图像特征匹配就是一特征点为基础而进行的,所以,如何定义和找出一幅图像中的特征点就非常重要. ...

  4. OpenCV精进之路(二十):工具——图像标注小工具

    搞图像深度学习的童鞋一定碰过图像数据标注的东西,当我们训练网络时需要训练集数据,但在网上又没有找到自己想要的数据集,这时候就考虑自己制作自己的数据集了,这时就需要对图像进行标注.图像标注是件很枯燥又很 ...

  5. OpenCV精进之路(八):图像轮廓和图像分割修复——轮廓查询和多边形包围轮廓

    Canny一类的边缘检测算法可以根据像素之间的差异,检测出轮廓边界的像素,但它没有将轮廓作为一个整体.所以要将轮廓提起出来,就必须将这些边缘像素组装成轮廓. OpenCV中有一个很强大的函数,它可以从 ...

  6. OpenCV精进之路(九):图像轮廓和图像分割修复——图像修复技术

    在实际应用中,我们的图像常常会被噪声腐蚀,这些噪声或是镜头上的灰尘或水滴,或是旧照片的划痕,或者是图像遭到人为的涂画(比如马赛克)或者图像的部分本身已经损坏.如果我们想让这些受到破坏的额图片尽可能恢复 ...

  7. OpenCV精进之路(四):图像处理——图片的缩放和图像金字塔

    前言 对图像进行缩放的最简单方法当然是调用resize函数啦! resize函数可以将源图像精确地转化为指定尺寸的目标图像. 要缩小图像,一般推荐使用CV_INETR_AREA来插值:若要放大图像,推 ...

  8. OpenCV精进之路(零):访问图像中像素的三种方法

    访问像素的三种方法 指针访问:最快 迭代器iterator:较慢,非常安全,指针访问可能出现越界问题 动态地址计算:更慢,通过at()实现.适用于访问具体某个第i行,j列的像素,而不适用遍历像素 这里 ...

  9. OpenCV精进之路(十四):图像矫正技术深入探讨

    刚进入实验室导师就交给我一个任务,就是让我设计算法给图像进行矫正.哎呀,我不太会图像这块啊,不过还是接下来了,硬着头皮开干吧! 那什么是图像的矫正呢?举个例子就好明白了. 我的好朋友小明给我拍了这几张 ...

最新文章

  1. 杭电1754--I Hate It(线段树)
  2. 业务系统设计之一:系统菜单设计
  3. linux备份svn仓库脚本,SVN服务备份操作步骤分享
  4. 杭电1260java实现
  5. XGBoost使用教程(与sklearn一起使用)二
  6. 关于异或的一些东西和应用
  7. 二叉树三种递归和非递归遍历 层序遍历
  8. C# XML文件操作类XmlHelper
  9. 二级计算机c语言各题型,计算机二级C语言都有哪些题型?
  10. 3dmm人脸配准/重建:gold standard algorithm
  11. Excel4.0宏病毒查看隐藏宏代码
  12. 把平板、手机作为电脑第二屏幕(Linux系统下)
  13. FAT表、Fat32与exFat与NTFS分区
  14. GridControl GridView 属性
  15. python做用友财务报表_用友财务软件怎样生成财务报表?
  16. 知识点滴 - Git名字的由来
  17. Assembly x64 Intro - XMM ABS
  18. SQL查询无限层级结构的所有下级,所有上级(即所有的子孙曾孙等等)
  19. 分布式内存数据技术为查询提速
  20. java.lang.IllegalStateException: No instances available for 的解决思路

热门文章

  1. 【汇编语言与计算机系统结构笔记20】补充内容:可定制处理器指令集
  2. 【数据结构笔记08】哨兵查找、二分查找、树、儿子-兄弟表示法、二叉树的引子
  3. 如何判断对方列表里是不是好友_微信如何快速查看是否为好友关系
  4. 蓝软服务器文件监控同步系统,蓝软7000ERP通用操作使用教程
  5. magento url rewrite规则
  6. 第一篇 Windows 8 开发Windows Metro style app环境配置
  7. window 服务(三)
  8. LeetCode简单题目(#203 #204 # #205 #206 #217 #219)-6道(序列、数字)
  9. 探讨绝对哲学存在的必要条件
  10. Concrete Mathematics A Foundation for Computer Science