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