Loading

OpenCV For Android实现特征点匹配(Features2d.drawMatches)

基于OpenCV的AndroidSDK实现的特征点匹配案例,其中SDK的版本为4.5.5

SDK下载

OpenCV的SDK这里下载

matchTemplate方案的试错

自己一个项目需要使用图像匹配,其实就是从一个大截图中截出来一个小按钮,然后去识别这个按钮的位置然后做点击。一开始使用的是模板匹配,也就是Imgproc._matchTemplate()_方法,但使用后发现该方法对图片的分辨率要求过高(其实还有旋转、角度等限制),即时是从大图中截图出来的小图,也是需要分辨率对得上才能匹配成功(也就是说不同分辨率的手机截图出来的文件不一定能匹配成功)。

决定使用drawMatches

后来找了好久的资料,终于在OpenCV的官网文档找到一个基于特征点匹配的方法(Features2d._drawMatches()_),不受限分辨率、角度、旋转等限制(虽然我这个项目不涉及到角度和旋转)。
要注意的是这个方法可以使用好几种不同的算法(BRISK、ORB、KAZE、AKAZE、MSER等),这里我最终选用的是BRISK,关于这几种算法的优劣可以百度,我是参考了链接

代码实现

// image2Bitmap:截图出来的Bitmap对象
// templateBitmap:要匹配的按钮的Bitmap对象
// src:截图的Mat对象
// template:要匹配的按钮的Mat对象

if (image2Bitmap != null) {
  LogUtils.d("截图完成,准备匹配截图");
  src = new Mat();
  Utils.bitmapToMat(image2Bitmap, src);
  LogUtils.d("获取的截图宽高", image2Bitmap.getWidth(), image2Bitmap.getHeight());
  if (templateBitmap == null) {
    templateBitmap = ImageUtils.getBitmap(R.drawable.image_open);
  }
  
  if (template == null) {
    template = new Mat();
    Utils.bitmapToMat(templateBitmap, template);
  }

  MatOfKeyPoint keyPointTemplate = new MatOfKeyPoint();
  Mat templateDescriptorMat = new Mat();
  
  MatOfKeyPoint keyPointSrc = new MatOfKeyPoint();
  Mat srcDescriptorMat = new Mat();
  
  //            ORB me = ORB.create(1000, 1.2f);
  //            ORB me = ORB.create();
  //            KAZE me = KAZE.create();
  //            AKAZE me = AKAZE.create();
  BRISK me = BRISK.create();
  //            MSER me = MSER.create();
  
  me.detect(template, keyPointTemplate);
  me.compute(template, keyPointTemplate, templateDescriptorMat);
  
  me.detect(src, keyPointSrc);
  me.compute(src, keyPointSrc, srcDescriptorMat);
  
  if (templateDescriptorMat.type() != CvType.CV_32F && srcDescriptorMat.type() != CvType.CV_32F) {
    templateDescriptorMat.convertTo(templateDescriptorMat, CvType.CV_32F);
    srcDescriptorMat.convertTo(srcDescriptorMat, CvType.CV_32F);
  }
  MatOfDMatch matches = new MatOfDMatch();
  FlannBasedMatcher matcher = FlannBasedMatcher.create();
  matcher.match(templateDescriptorMat, srcDescriptorMat, matches);
  
  List<DMatch> matchList = matches.toList();
  LogUtils.d("优化前的匹配点数量:", matchList.size());
  // 按照distance升序
  Collections.sort(matchList, (a, b) -> {
    return (int) (a.distance * 1000 - b.distance * 1000);
  });
  LogUtils.d("排序后的匹配点列表", matchList);
  float min = matchList.get(0).distance;
  float max = matchList.get(matchList.size() - 1).distance;
  List<DMatch> goodMatchList = new ArrayList(matchList);
  // 对列表进行筛选,去除distance小于(最大的distance * 0.4)的特征点
  CollectionUtils.filter(goodMatchList, new CollectionUtils.Predicate<DMatch>() {
    @Override
    public boolean evaluate(DMatch item) {
      return item.distance < max * 0.4;
    }
  });
  LogUtils.d("优化后的匹配点数量:", goodMatchList.size());
  // 如果匹配点小于4个,是无法继续执行的话,不然OpenCV会报错
  if (goodMatchList.size() < 4) {
    LogUtils.d("匹配点小于4个,跳过本次!");
    continue;
  }
  
  Mat result = new Mat();// 承载最终结果的Mat
  MatOfDMatch matOfDMatch = new MatOfDMatch();
  matOfDMatch.fromList(goodMatchList);
  // 把匹配图在大图中的特征点关系线画上
  Features2d.drawMatches(template, keyPointTemplate, src, keyPointSrc, matOfDMatch, result);
  // 以上其实已经能标识出大图中关于匹配图的特征点关系了(用线连接),下面要做的是找出匹配图在大图中的位置
  
  //-- 定位对象
  List<Point> obj = new ArrayList<>();
  List<Point> scene = new ArrayList<>();
  List<KeyPoint> listOfKeypointsObject = keyPointTemplate.toList();
  List<KeyPoint> listOfKeypointsScene = keyPointSrc.toList();
  for (int i = 0; i < goodMatchList.size(); i++) {
    //-- 从良好的匹配中获取关键点
    obj.add(listOfKeypointsObject.get(goodMatchList.get(i).queryIdx).pt);
    scene.add(listOfKeypointsScene.get(goodMatchList.get(i).trainIdx).pt);
  }
  MatOfPoint2f objMat = new MatOfPoint2f();
  MatOfPoint2f sceneMat = new MatOfPoint2f();
  objMat.fromList(obj);
  sceneMat.fromList(scene);
  double ransacReprojThreshold = 3.0;
  Mat H = Calib3d.findHomography(objMat, sceneMat, Calib3d.RANSAC, ransacReprojThreshold);
  //-- 从image_1(要“检测”的对象)获取角
  Mat objCorners = new Mat(4, 1, CvType.CV_32FC2), sceneCorners = new Mat();
  float[] objCornersData = new float[(int) (objCorners.total() * objCorners.channels())];
  objCorners.get(0, 0, objCornersData);
  objCornersData[0] = 0;
  objCornersData[1] = 0;
  objCornersData[2] = template.cols();
  objCornersData[3] = 0;
  objCornersData[4] = template.cols();
  objCornersData[5] = template.rows();
  objCornersData[6] = 0;
  objCornersData[7] = template.rows();
  objCorners.put(0, 0, objCornersData);
  Core.perspectiveTransform(objCorners, sceneCorners, H);
  float[] sceneCornersData = new float[(int) (sceneCorners.total() * sceneCorners.channels())];
  sceneCorners.get(0, 0, sceneCornersData);
  //-- 在角之间绘制线,也就是我要找的按钮的四个边
  Imgproc.line(result, new Point(sceneCornersData[0] + template.cols(), sceneCornersData[1]),
               new Point(sceneCornersData[2] + template.cols(), sceneCornersData[3]), new Scalar(255, 0, 0, 255), 4);
  Imgproc.line(result, new Point(sceneCornersData[2] + template.cols(), sceneCornersData[3]),
               new Point(sceneCornersData[4] + template.cols(), sceneCornersData[5]), new Scalar(255, 0, 0, 255), 4);
  Imgproc.line(result, new Point(sceneCornersData[4] + template.cols(), sceneCornersData[5]),
               new Point(sceneCornersData[6] + template.cols(), sceneCornersData[7]), new Scalar(255, 0, 0, 255), 4);
  Imgproc.line(result, new Point(sceneCornersData[6] + template.cols(), sceneCornersData[7]),
               new Point(sceneCornersData[0] + template.cols(), sceneCornersData[1]), new Scalar(255, 0, 0, 255), 4);
  
  Bitmap bitmap = Bitmap.createBitmap(result.width(), result.height(), Bitmap.Config.ARGB_8888);
  Utils.matToBitmap(result, bitmap);
  // 保存图片到本地
  ImageUtils.save2Album(bitmap, Bitmap.CompressFormat.PNG);
  
} else {
  LogUtils.d("截图的Bitmap是空!!!");
}

图片资源和效果

gxt_hand.pngbig_gxt.jpgimage.png

posted @ 2023-01-11 15:36  冲锋的麦克  阅读(201)  评论(2编辑  收藏  举报