2019-8
最近小弟准备稍微写下自己的毕业设计的内容(如各位大佬赶时间不想听的话,直接划到下边吧),本人是个毕业于广东广州普通二本的学生,去年考研没上岸(学校没保研资格...,不过复习也有点浪),今年刚毕业,目前正在准备考研,先说说自己的毕业设计的内容,本人选了自己的毕业导师给的课题,无人机图像拼接,实话说,做到最后,也就是进行图像的特征点检测以及匹配拼接融合而已,各位大佬如果发现有什么错误的地方,可以的话,麻烦指出下,谢谢。好了,话不多说,简要说下自己在毕业设计中做的东西,若有对各位大佬有帮助,可以看看。本设计主要采用是用C++(虽然本人在本科阶段专业一直是教我们Java,我自己也是写Java偏多,C++只学了一个学期,有很多写法不规范和不好的地方,还请谅解自行修改....)加上OpenCV,CImg等工具进行开发,GUI界面使用了QT,(不过其实界面设计是找了个别人的界面代码进行修改填充我自己的内容)。开始动工进行毕业设计的时候那时候我,我参考了很多博客的教程,这里列举几个不错的博主文章,有很多图像拼接的案例可以去查找下这些楼主的博文。
这里列下文章的目录,各个模块可以自己找下:
1.准备阶段的建议
2.参考资料分享
3....
4 代码下载地址
5 结束语
准备选择这个做作业或者设计的,可以看看!!!
这里稍微说一下我对这一次作业设计的感觉吧,如果是打算拿来做设计的作业的话,我还是建议去选择SURF算法和ORB算法进行特征点检测,真的比SIFT算法快很多,我也是做到最后没空换算法了,就在对图像进行特征检测的时候对图像进行降采样然后再进行检测来提高速度,精度也有损失不少,如果是SURF算法是SIFT算法的改进,速度快,进度也还可以,ORB也是不错的选择,当然图像拼接也不仅仅只有特征点检测这个思路,这个已经是2004年提出来的技术,并且在07年的时候由完成了全景拼接,现在还有很多很不错的拼接方法,具体可以自己稍微去找找,知乎上也有人分享,这里本人也就不再展开。我的这次设计也不好,后期没时间去做柱面投影(这个对后期的图像融合效果有很大的用处和改进),因此也投影矩阵也做得不好(在使用Ransac算法计算投影矩阵),应该说自己实现的不够好,效率太低了,所以选择去找网上的方法了(数学功底太差,不会改进,正常手工运算方式太差了,参考论文是用了加速版的SURF和ORB)
对了,关于opencv的安装配置,可能是我C++学得不好,CMake那边搞不定,没法编译出开发版本,我打算配置安装,于是我就去淘宝找人安装,当初花了差不多4-5条都搞不定,客服那边也就花了差不多15分钟远程桌面就搞定了,如果想快速开发的话,可以选择这种方式,不过我还是建议有空的各位可以自己试着去安装下。
CImg有个问题,那就是只能处理bmp文件,要想处理jpg格式的图像,需要重新查找插件重新编译下链接进去,本人比较懒,选择QT后借助OpenCV进行图像转换的处理。
个人觉得不错的参考资料,需要的可以看下
关于论文的话,我建议是看下最近的图像拼接论文,浙大的几篇虽然年代久远,但也不错,电子科技大学的几篇也都不错,在对融合方面的几块技术的讲解,也算挺细致的,也有博士的论文,写得很全面,查看理论可以上知网看看,对于SIFT代码的,有多个版本,我看过一个是opencv2的实现,以及Rob Hess的c语言版的SIFT代码讲解,关于Rob Hess的SIFT代码,可以在GitHub上查找下载,需要的可以自行查找。
对于全景拼接的一个大佬,我曾经找到过一个大佬完成对所有代码的编写,是一个全景拼接的程序,功能相当强大,不过我不太会调试,大神貌似出国留学了,这几晚我找找他的博客和代码,对C++和Opencv熟悉的大佬可以自行查找调试下,我调试运行不了,对C++基础太差。https://blog.csdn.net/hc1025808587/article/details/52922308 ,大概是这个博客。
这位是我在浏览博客的时候发现到的一个图像大神,我有很多东西都参考她的博客https://me.csdn.net/wd1603926823。
这个是我参考SIFT算法实现的一个博文,这个大佬算是把Rob Hess的c语言版的SIFT代码稍微修改为C++版的,稍微更新了下,想参考的话可以看他的一些博文https://blog.csdn.net/qq_33000225/article/details/70906106#comments(P.S.实话说,那时候觉得自己完全实现SIFT庞大的代码,自己完全编写完,有点难度,就稍微参考修改下了,而且我以为我能进入复试,没想到今年突然划线到322,难受)
对于图像融合的话,我的缝合线查找是参考这位大佬的博客 https://blog.csdn.net/wd1603926823/article/details/49536691 将matlab写成C++版的,大家也可以自己改下
这个是有介绍利用RANSAC算法对SIFT筛选出特征点进行特征点匹配,主要介绍了OpenCV 里的方法进行匹配的,https://blog.csdn.net/masibuaa/article/details/9145441
这篇博文也有讲解 http://www.360doc.com/content/16/1107/20/28378250_604709641.shtml
对于RANSAC算法的实现,在Rob Hess的代码中,也有实现,主要是依靠bbf算法,借助kd-tree实现,不过我没有模仿成功,这个是一位楼主的讲解博客,有兴趣可以看看 https://blog.csdn.net/ijuliet/article/details/4471311
快速开发的建议:此段落章节是针对想要快速开发使用opencv库函数和急着做作业的的朋友或者大神想快速过一遍的简要开发介绍建议,只是想拼接图像不需要写代码,ps2015cc版本往上走就有拼接图片的功能,自己去找找就好,效果挺ok的(拿舍友去旅行的图片测试过,效果确实很好),没必要去找付费的软件那些,学习的话建议看看文章了解下,希望能有所帮助。
OpenCV里边有SIFT,SURF和ORB库函数可供学习调用,其中,SURF和SIFT商用需要收费,ORB是免费的,
先是特征点检测这一块,这一部分的话我是通过参考了几个博客和Rob Hess的SIFT代码稍微改动了下,不过基本改动不大,我后期再传上GitHub上。参考代码可以看下这个博客的,这位大佬的代码在运行的时候会不断显示上采样和下采样的图像,如果需要观察的话,可以直接运行,若不需要,可以自己选择性将cpp里边的imshow删去
//MySift.h
#ifdef _CH_
#pragma package
#endif
#ifndef _EiC
#include
#include "stdlib.h"
#include "string.h"
#include "malloc.h"
#include "math.h"
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#endif
#ifdef _EiC
#define WIN32
#endif
#define NUMSIZE 2
#define GAUSSKERN 3.5
#define PI 3.14159265358979323846
//Sigma of base image -- See D.L.'s paper.
#define INITSIGMA 0.5
//Sigma of each octave -- See D.L.'s paper.
#define SIGMA sqrt(3)//1.6//
//Number of scales per octave. See D.L.'s paper.
#define SCALESPEROCTAVE 2
#define MAXOCTAVES 4
#define CONTRAST_THRESHOLD 0.02
#define CURVATURE_THRESHOLD 10.0
#define DOUBLE_BASE_IMAGE_SIZE 1
#define peakRelThresh 0.8
#define LEN 128
#define min(a,b) (((a) < (b)) ? (a) : (b))
#define max(a,b) (((a) > (b)) ? (a) : (b))
//特征描述点,网格
#define GridSpacing 4
//Data structure for a float image.
typedef struct ImageSt { /*金字塔每一层*/
float levelsigma;
int levelsigmalength;
float absolute_sigma;
CvMat *Level; //CvMat是OPENCV的矩阵类,其元素可以是图像的象素值
} ImageLevels;
typedef struct ImageSt1 { /*金字塔每一阶梯*/
int row, col; //Dimensions of image.
float subsample;
ImageLevels *Octave;
} ImageOctaves;
//keypoint数据结构,Lists of keypoints are linked by the "next" field.
typedef struct KeypointSt {
float row, col; /* 反馈回原图像大小,特征点的位置 */
float sx, sy; /* 金字塔中特征点的位置 */
int octave, level; /* 金字塔中,特征点所在的阶梯、层次 */
float scale, ori, mag; /* 所在层的尺度sigma,主方向orientation (range [-PI,PI]),以及幅值 */
float *descrip; /* 特征描述字指针:128维或32维等 */
struct KeypointSt *next; /* Pointer to next keypoint in list. */
} *Keypoint;
class MySift {
public:
MySift();
~MySift();
MySift(char* _filename, int _isColor);
void MySiftArray(char* _filename, int _isColor);
CvMat * halfSizeImage(CvMat * im); //缩小图像:下采样
CvMat * doubleSizeImage(CvMat * im); //扩大图像:最近临方法
CvMat * doubleSizeImage2(CvMat * im); //扩大图像:线性插值
float getPixelBI(CvMat * im, float col, float row);//双线性插值函数
void normalizeVec(float* vec, int dim);//向量归一化
CvMat* GaussianKernel2D(float sigma); //得到2维高斯核
void normalizeMat(CvMat* mat); //矩阵归一化
float* GaussianKernel1D(float sigma, int dim); //得到1维高斯核
//在具体像素处宽度方向进行高斯卷积
float ConvolveLocWidth(float* kernel, int dim, CvMat * src, int x, int y);
//在整个图像宽度方向进行1D高斯卷积
void Convolve1DWidth(float* kern, int dim, CvMat * src, CvMat * dst);
//在具体像素处高度方向进行高斯卷积
float ConvolveLocHeight(float* kernel, int dim, CvMat * src, int x, int y);
//在整个图像高度方向进行1D高斯卷积
void Convolve1DHeight(float* kern, int dim, CvMat * src, CvMat * dst);
//用高斯函数模糊图像
int BlurImage(CvMat * src, CvMat * dst, float sigma);
//SIFT算法第一步:图像预处理
CvMat *ScaleInitImage(CvMat * im); //金字塔初始化
//SIFT算法第二步:建立高斯金字塔函数
ImageOctaves* BuildGaussianOctaves(CvMat * image); //建立高斯金字塔
//SIFT算法第三步:特征点位置检测,最后确定特征点的位置
int DetectKeypoint(int numoctaves, ImageOctaves *GaussianPyr);
void DisplayKeypointLocation(IplImage* image, ImageOctaves *GaussianPyr);
//SIFT算法第四步:计算高斯图像的梯度方向和幅值,计算各个特征点的主方向
void ComputeGrad_DirecandMag(int numoctaves, ImageOctaves *GaussianPyr);
int FindClosestRotationBin(int binCount, float angle); //进行方向直方图统计
void AverageWeakBins(double* bins, int binCount); //对方向直方图滤波
//确定真正的主方向
bool InterpolateOrientation(double left, double middle, double right, double *degreeCorrection, double *peakValue);
//确定各个特征点处的主方向函数
void AssignTheMainOrientation(int numoctaves, ImageOctaves *GaussianPyr, ImageOctaves *mag_pyr, ImageOctaves *grad_pyr);
//显示主方向
void DisplayOrientation(IplImage* image, ImageOctaves *GaussianPyr);
//SIFT算法第五步:抽取各个特征点处的特征描述字
void ExtractFeatureDescriptors(int numoctaves, ImageOctaves *GaussianPyr);
//为了显示图象金字塔,而作的图像水平、垂直拼接
CvMat* MosaicHorizen(CvMat* im1, CvMat* im2);
CvMat* MosaicVertical(CvMat* im1, CvMat* im2);
/* 以下为在源代码基础上添加部分 */
void SiftMainProcess();
int getKeyPointsCount(); //获取keypoint总数
Keypoint getFirstKeyDescriptors(); //获取第一个keyDescriptor结点
void saveImgWithKeypoint(char* filename);
private:
char* filename;
int isColor;
int numoctaves;
ImageOctaves *DOGoctaves;
//DOG pyr,DOG算子计算简单,是尺度归一化的LoG算子的近似。
ImageOctaves *mag_thresh;
ImageOctaves *mag_pyr;
ImageOctaves *grad_pyr;
int keypoint_count = 0;
//定义特征点具体变量
Keypoint keypoints = NULL; //用于临时存储特征点的位置等
Keypoint keyDescriptors = NULL; //用于最后的确定特征点以及特征描述字
//声明当前帧IplImage指针
IplImage* src = NULL;
IplImage* image_kp = NULL;
IplImage* image_featDir = NULL;
IplImage* grey_im1 = NULL;
IplImage* mosaic1 = NULL;
IplImage* mosaic2 = NULL;
CvMat* mosaicHorizen1 = NULL;
CvMat* mosaicHorizen2 = NULL;
CvMat* mosaicVertical1 = NULL;
CvMat* image1Mat = NULL;
CvMat* tempMat = NULL;
ImageOctaves *Gaussianpyr;
};
上边是mysift.h的头文件,需要预装OpenCV,才能对图像进行加载,
利用RANSAC算法进行图像特征匹配:
一般来说,可以利用RANSAC算法对特征点进行匹配,有好几种方式来实现,当两幅图像有特征点重合的地方(我个人的理解,可能也不正确),就可以进行拟合计算出变换矩阵H(不过此算法能成功拟合出所需的变换矩阵也是有概率的),当然,曾经看到过一篇论文,其加速思路是选取对两幅待匹配的图像分别选取4个,(加起来八个),通过最小二乘法进行计算,求投影矩阵H(也叫变换矩阵),具体文章可以看下 倪霄龙 的 全景图像拼接关键技术 研究的这篇论文,里边有提到过
由于从上面步骤1得到的结果只是每张图片自身的特征点,即两张图片的特征点之间还没对应关系。因此我们需要先通过上面得到的128维描述子先进行大致的特征点匹配(结果可能包括outliers)。匹配方法不难理解,只需计算两个128维特征描述子的距离差,小于某阈值即可视为相同的特征点。
特征点匹配后,我是直接进行融合操作,但是,但是!!!,这样子的效果是非常差的,而且是很不好的,还需要进行调整,常规的做法是进行柱面投影的对图像进行调整,例如使用Photoshop里边全景拼接功能就是这么做(如果只是想进行图像拼接,可以直接使用Photoshop进行全景拼接,里边不但能对图像进行调整融合,而且还可以对图像还原调整前的图像,同时融合的效果很不错,对缝隙过渡衔接都还行,大公司商业化的软件算法确实挺强的),
收工睡觉,明天继续补充
有什么不好或者需要补充的可以留言说下,本人一般白天没空,打算最近晚上图书馆闭馆后回来上博客写下看看回答,尽力调好坑,
2019-12-24
终于考完试了,感觉这一年好像又白费了,数学花了大半年的时间,整个复习时间的2/3还要多,然后空了好多,哎,难受,总之,趁着这段时间有空,这段时间会陆续补完这个文章。
那么,继续补完吧,假设能自己实现柱面投影在进行图像融合的话,效果将会变得很不错,不过由于那个在编写代码的时候我不大懂这块原理,而且时间也有些赶,加上老师给的图像大概是90度垂直拍摄出来的图像,我就先以平行拍摄作为实验先进行测试。
上边讲到查找出特征点以及进行特征点匹配这一块,我在设计的过程中得到了两张图像的水平和垂直位移(这边我就不再重复画出,需要的话参考这个博客的原理),然后计算是否增加整幅图像的长于宽,在设计的过程中,我以第一幅图像作为基准,其它所有待拼接的图像的融合位置都是相对于第一幅图像进行计算放置。在进行多图像进行拼接的过程中,首先得出两幅图像的水平和垂直位移偏差值,再与基准图像进行对比计算,两两图像进行融合。
融合的方法有很多,直接覆盖也可以,不过会显得有点突兀。通过渐入进出的方式过渡起来会看起来好点。渐入渐出的方式,有点类似于我们看得一些影片转场过渡那种,从当前背景渐渐转变为下一个场景的方式。将待融合的A图像的颜色随着重叠区域渐变为透明,待融合的B图像的颜色从透明渐渐变为原来图像的色彩。使用这种方式,主要用于两幅图像的色差变化不大而且图像经过柱面投影矫正后,效果会更佳。(可能我的解释有点难以理解,看看图示解释或许会好点。)
当然还可以用拉普拉斯融合的方式以及泊松融合的方法(小波变换图像融合也可以,可根据不同的场景可选择不同的融合方式),效果也很不错,对于两幅图像存在光线色差存在些许不同效果还可以。不过呢,在融合之前,若能找出两幅图像的进行融合的重叠区域的最佳缝合线(镶嵌线),这样子的话两幅图像进行融合后图像会减少鬼影的出现以及合成后图像的突兀感。本人是在寻找最佳缝合线后对两幅图像的重叠区域进行拉普拉斯图像融合,感觉看起来比渐入渐出好点。
2020-5-19复试成绩总算上岸了,不过是联合培养的学生,感觉限制还是有点多,那几天都在找导师。
有关代码下载地址:https://github.com/lmpeng12/opencv-stitching
有问题可以联系我,我如果有上线的话偶尔会看看
希望这篇文章能帮助刚开始着手了解下这一块内容的各位朋友,能够让你们快速入手准备,建议对这块不大懂的朋友可以看看下。