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
↑这样更直观一些,可以看出大部分映射关系是正确的。
对图像识别领域的一些概念进行了了解,包括特征点与特征量这样的叙述手段,但问题还是很多,例如特征点的计算依据(轮廓?拐点?)等,我希望在以后的学习中找到答案。
原文地址:http://blog.csdn.net/shuzhe66/article/details/40824883