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視圖]
↑這樣更直觀一些,可以看出大部分映射關係是正確的。
[總結]
對圖像識別領域的一些概念進行了瞭解,包括特徵點與特徵量這樣的敘述手段,但問題還是很多,例如特徵點的計算依據(輪廓?拐點?)等,我希望在以後的學習中找到答案。