首页 > 代码库 > OpenCV学习笔记[5]FLANN特征匹配

OpenCV学习笔记[5]FLANN特征匹配

OpenCV学习笔记:FLANN特征匹配


        本次给出FLANN特征匹配的Java实现。

[简介]

        特征匹配记录下目标图像与待匹配图像的特征点(KeyPoint),并根据特征点集合构造特征量(descriptor),对这个特征量进行比较、筛选,最终得到一个匹配点的映射集合。我们也可以根据这个集合的大小来衡量两幅图片的匹配程度。

        特征匹配与模板匹配不同,由于是计算特征点集合的相关度,转置操作对匹配影响不大,但它容易受到失真、缩放的影响。

[特征匹配]

FeatureMatching.java:

import org.opencv.core.Mat;
import org.opencv.core.MatOfDMatch;
import org.opencv.core.MatOfKeyPoint;
import org.opencv.features2d.DescriptorExtractor;
import org.opencv.features2d.DescriptorMatcher;
import org.opencv.features2d.FeatureDetector;
import org.opencv.highgui.Highgui;

import com.thrblock.opencv.fm.view.ConsoleView;
import com.thrblock.opencv.fm.view.MatchingView;

public class FeatureMatching {
	private Mat src;
	private MatOfKeyPoint srcKeyPoints;
	private Mat srcDes;
	
	private FeatureDetector detector;
	private DescriptorExtractor extractor;
	private DescriptorMatcher matcher;

	private MatchingView view;
	public FeatureMatching(MatchingView view) {
		this.view = view;
		srcKeyPoints = new MatOfKeyPoint();
		srcDes = new Mat();
		detector = FeatureDetector.create(FeatureDetector.SURF);
		extractor = DescriptorExtractor.create(DescriptorExtractor.SURF);
		matcher = DescriptorMatcher.create(DescriptorMatcher.FLANNBASED);
	}

	public int doMaping(String dstPath) {
		view.setDstPic(dstPath);
		// 读入待测图像
		Mat dst = Highgui.imread(dstPath);
		System.out.println("DST W:"+dst.cols()+" H:" + dst.rows());
		// 待测图像的关键点
		MatOfKeyPoint dstKeyPoints = new MatOfKeyPoint();
		detector.detect(dst, dstKeyPoints);
		// 待测图像的特征矩阵
		Mat dstDes = new Mat();
		extractor.compute(dst, dstKeyPoints, dstDes);
		// 与原图匹配
		
		MatOfDMatch matches = new MatOfDMatch();
		matcher.match(dstDes, srcDes, matches);
		//将结果输入到视图 并得到“匹配度”
		return view.showView(matches, srcKeyPoints, dstKeyPoints);
	}

	public void setSource(String srcPath) {
		view.setSrcPic(srcPath);
		// 读取图像 写入矩阵
		src = http://www.mamicode.com/Highgui.imread(srcPath);>

MatchingView.java 该接口用来计算并输出结果

import org.opencv.core.MatOfDMatch;
import org.opencv.core.MatOfKeyPoint;


public interface MatchingView {
	public void setDstPic(String dstPath);
	public void setSrcPic(String picPath);
	public int showView(MatOfDMatch matches,MatOfKeyPoint srcKP,MatOfKeyPoint dstKP);
}
ConsoleView.java:实现了视图接口,将结果打印在控制台中,如果实在没有什么拿手的图形环境的话就只能看文字了。
import java.util.LinkedList;
import java.util.List;

import org.opencv.core.MatOfDMatch;
import org.opencv.core.MatOfKeyPoint;
import org.opencv.features2d.DMatch;

public class ConsoleView implements MatchingView{

	@Override
	public int showView(MatOfDMatch matches,MatOfKeyPoint srcKP,MatOfKeyPoint dstKP) {
		System.out.println(matches.rows() + " Match Point(s)");

		double maxDist = Double.MIN_VALUE;
		double minDist = Double.MAX_VALUE;
		
		DMatch[] mats = matches.toArray();
		for(int i = 0;i < mats.length;i++){
			double dist = mats[i].distance;
			if (dist < minDist) {
				minDist = dist;
			}
			if (dist > maxDist) {
				maxDist = dist;
			}
		}
		System.out.println("Min Distance:" + minDist);
		System.out.println("Max Distance:" + maxDist);
		//将“好”的关键点记录,即距离小于3倍最小距离,同时给定一个阈值(0.2f),这样不至于在毫不相干的图像上分析,可依据实际情况调整
		List<DMatch> goodMatch = new LinkedList<>();
		
		for (int i = 0; i < mats.length; i++) {
			double dist = mats[i].distance;
			if(dist < 3*minDist&&dist < 0.2f){
				goodMatch.add(mats[i]);
			}
		}
		System.out.println(goodMatch.size() + " GoodMatch Found");
		int i = 0;
		for(DMatch ma:goodMatch){
			System.out.println("GoodMatch" + "["+i+"]:" + ma.queryIdx + " TO: " + ma.trainIdx);
			i++;
		}
		return i;
	}

	@Override
	public void setDstPic(String dstPath) {}

	@Override
	public void setSrcPic(String picPath) {}
}
GEivView.java:使用自己的游戏引擎绘制图形界面输出结果,如果之前有部署过GEiv系统的话可以尝试(可参照博客内相关文章)。
import geivcore.R;
import geivcore.UESI;
import geivcore.engineSys.texturecontroller.TextureController;
import geivcore.enginedata.canonical.CANExPos;
import geivcore.enginedata.obj.Obj;

import java.awt.Color;
import java.util.LinkedList;
import java.util.List;

import org.opencv.core.MatOfDMatch;
import org.opencv.core.MatOfKeyPoint;
import org.opencv.core.Point;
import org.opencv.features2d.DMatch;
import org.opencv.features2d.KeyPoint;

import com.thrblock.util.RandomSet;

public class GEivView implements MatchingView{
	UESI UES;
	Obj srcPicObj,dstPicObj;
	public GEivView(){
		UES = new R();
		
		srcPicObj = UES.creatObj(UESI.BGIndex);
		srcPicObj.addGLImage(0, 0,TextureController.SYSTEM_DEBUG_TEXTURE);
		
		dstPicObj = UES.creatObj(UESI.BGIndex);
		dstPicObj.addGLImage(0, 0,TextureController.SYSTEM_DEBUG_TEXTURE);
	}
	@Override
	public int showView(MatOfDMatch matches, MatOfKeyPoint srcKP,
			MatOfKeyPoint dstKP) {
		System.out.println(matches.rows() + " Match Point(s)");

		double maxDist = Double.MIN_VALUE;
		double minDist = Double.MAX_VALUE;
		
		DMatch[] mats = matches.toArray();
		for(int i = 0;i < mats.length;i++){
			double dist = mats[i].distance;
			if (dist < minDist) {
				minDist = dist;
			}
			if (dist > maxDist) {
				maxDist = dist;
			}
		}
		System.out.println("Min Distance:" + minDist);
		System.out.println("Max Distance:" + maxDist);
		//将“好”的关键点记录,即距离小于3倍最小距离,可依据实际情况调整
		List<DMatch> goodMatch = new LinkedList<>();
		
		for (int i = 0; i < mats.length; i++) {
			double dist = mats[i].distance;
			if(dist < 3*minDist&&dist < 0.2f){
				goodMatch.add(mats[i]);
			}
		}
		System.out.println(goodMatch.size() + " GoodMatch Found");
		
		DMatch[] goodmats = goodMatch.toArray(new DMatch[]{});
		KeyPoint[] srcKPs = srcKP.toArray();//train
		KeyPoint[] dstKPs = dstKP.toArray();//query
		
		for(int i = 0;i < goodmats.length;i++){
			Point crtD = dstKPs[goodmats[i].queryIdx].pt;
			Point crtS = srcKPs[goodmats[i].trainIdx].pt;
			showMap(dstPicObj.getDx() + crtD.x,dstPicObj.getDy() + crtD.y,srcPicObj.getDx() + crtS.x,srcPicObj.getDy() + crtS.y);
			System.out.println("MAP :("+(int)crtD.x+","+(int)crtD.y+") --->("+(int)crtS.x+","+(int)crtS.y+")");
		}
		return goodmats.length;
	}
	@Override
	public void setDstPic(String dstPath) {
		dstPicObj.setPath(dstPath,true);
		dstPicObj.setPosition(CANExPos.POS_X_LEFT,50.0f);
		dstPicObj.setPosition(CANExPos.POS_Y_CENTER);
		
		dstPicObj.show();
	}
	@Override
	public void setSrcPic(String picPath) {
		srcPicObj.setPath(picPath,true);
		srcPicObj.setPosition(CANExPos.POS_X_RIGHT,50.0f);
		srcPicObj.setPosition(CANExPos.POS_Y_CENTER);

		srcPicObj.show();
	}
	private void showMap(double x,double y,double x1,double y1){
		Color dstColor = RandomSet.getRandomColor();
		
		Obj oval = UES.creatObj(UESI.UIIndex);
		oval.addGLOval("FFFFFF",0,0,5,5,12);
		oval.setColor(dstColor);
		oval.setCentralX((float)x1);
		oval.setCentralY((float)y1);
		oval.show();
		
		oval = UES.creatObj(UESI.UIIndex);
		oval.addGLOval("FFFFFF",0,0,5,5,12);
		oval.setColor(dstColor);
		oval.setCentralX((float)x);
		oval.setCentralY((float)y);
		oval.show();
		
		Obj line = UES.creatObj(UESI.UIIndex);
		line.addGLLine("FFFFFF",(float)x,(float)y,(float)x1,(float)y1);
		line.setLineWidth(2.0f);
		line.setColor(dstColor);
		line.setAlph(0.5f);
		line.show();
	}
}

[测试用例]

原图还是我们之前用的“贝壳”


匹配图,它来自原图转置后截取的一部分:


[测试结果]

[控制台视图]

输出内容:

SRC W:358 H:300
DST W:156 H:85
84 Match Point(s)
Min Distance:0.06136654317378998//所谓关键点的“距离”指的是两个关键点间的匹配程度,越小越匹配
Max Distance:0.4693795144557953
25 GoodMatch Found//共发现25个“好”的匹配点
GoodMatch[0]:0 TO: 6//这里的0->6意思是关键点集合中的映射关系,即KeyPoint[0]->KeyPoint[6]
GoodMatch[1]:1 TO: 23
GoodMatch[2]:3 TO: 30
GoodMatch[3]:4 TO: 20
GoodMatch[4]:5 TO: 23
GoodMatch[5]:6 TO: 27
GoodMatch[6]:7 TO: 19
GoodMatch[7]:9 TO: 73
GoodMatch[8]:10 TO: 65
GoodMatch[9]:12 TO: 96
GoodMatch[10]:14 TO: 38
GoodMatch[11]:15 TO: 97
GoodMatch[12]:19 TO: 162
GoodMatch[13]:20 TO: 175
GoodMatch[14]:22 TO: 164
GoodMatch[15]:29 TO: 247
GoodMatch[16]:31 TO: 283
GoodMatch[17]:33 TO: 155
GoodMatch[18]:36 TO: 261
GoodMatch[19]:39 TO: 218
GoodMatch[20]:45 TO: 717
GoodMatch[21]:48 TO: 487
GoodMatch[22]:60 TO: 150
GoodMatch[23]:68 TO: 91
GoodMatch[24]:77 TO: 1036

[GEiv视图]


↑这样更直观一些,可以看出大部分映射关系是正确的。

[总结]

        对图像识别领域的一些概念进行了了解,包括特征点与特征量这样的叙述手段,但问题还是很多,例如特征点的计算依据(轮廓?拐点?)等,我希望在以后的学习中找到答案。

OpenCV学习笔记[5]FLANN特征匹配