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 = Highgui.imread(srcPath);
		System.out.println("SRC W:"+src.cols()+" H:" + src.rows());
		// 檢測關鍵點
		detector.detect(src, srcKeyPoints);
		// 根據源圖像、關鍵點產生特徵矩陣數值
		extractor.compute(src, srcKeyPoints, srcDes);
	}

	public static void main(String[] args) {
		System.loadLibrary("opencv_java249");
		FeatureMatching mather = new FeatureMatching(new ConsoleView());
		//FeatureMatching mather = new FeatureMatching(new GEivView());
		mather.setSource("./Data/Lession5/BK.jpg");
		mather.doMaping("./Data/Lession5/BK_part_rr.png");
	}
}

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視圖]


↑這樣更直觀一些,可以看出大部分映射關係是正確的。

[總結]

        對圖像識別領域的一些概念進行了瞭解,包括特徵點與特徵量這樣的敘述手段,但問題還是很多,例如特徵點的計算依據(輪廓?拐點?)等,我希望在以後的學習中找到答案。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章