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

SDK下载

OpenCV的SDK这里下载

matchTemplate方案的试错

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

决定使用drawMatches

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

代码实现

  1. // image2Bitmap:截图出来的Bitmap对象
  2. // templateBitmap:要匹配的按钮的Bitmap对象
  3. // src:截图的Mat对象
  4. // template:要匹配的按钮的Mat对象
  5. if (image2Bitmap != null) {
  6. LogUtils.d("截图完成,准备匹配截图");
  7. src = new Mat();
  8. Utils.bitmapToMat(image2Bitmap, src);
  9. LogUtils.d("获取的截图宽高", image2Bitmap.getWidth(), image2Bitmap.getHeight());
  10. if (templateBitmap == null) {
  11. templateBitmap = ImageUtils.getBitmap(R.drawable.image_open);
  12. }
  13. if (template == null) {
  14. template = new Mat();
  15. Utils.bitmapToMat(templateBitmap, template);
  16. }
  17. MatOfKeyPoint keyPointTemplate = new MatOfKeyPoint();
  18. Mat templateDescriptorMat = new Mat();
  19. MatOfKeyPoint keyPointSrc = new MatOfKeyPoint();
  20. Mat srcDescriptorMat = new Mat();
  21. // ORB me = ORB.create(1000, 1.2f);
  22. // ORB me = ORB.create();
  23. // KAZE me = KAZE.create();
  24. // AKAZE me = AKAZE.create();
  25. BRISK me = BRISK.create();
  26. // MSER me = MSER.create();
  27. me.detect(template, keyPointTemplate);
  28. me.compute(template, keyPointTemplate, templateDescriptorMat);
  29. me.detect(src, keyPointSrc);
  30. me.compute(src, keyPointSrc, srcDescriptorMat);
  31. if (templateDescriptorMat.type() != CvType.CV_32F && srcDescriptorMat.type() != CvType.CV_32F) {
  32. templateDescriptorMat.convertTo(templateDescriptorMat, CvType.CV_32F);
  33. srcDescriptorMat.convertTo(srcDescriptorMat, CvType.CV_32F);
  34. }
  35. MatOfDMatch matches = new MatOfDMatch();
  36. FlannBasedMatcher matcher = FlannBasedMatcher.create();
  37. matcher.match(templateDescriptorMat, srcDescriptorMat, matches);
  38. List<DMatch> matchList = matches.toList();
  39. LogUtils.d("优化前的匹配点数量:", matchList.size());
  40. // 按照distance升序
  41. Collections.sort(matchList, (a, b) -> {
  42. return (int) (a.distance * 1000 - b.distance * 1000);
  43. });
  44. LogUtils.d("排序后的匹配点列表", matchList);
  45. float min = matchList.get(0).distance;
  46. float max = matchList.get(matchList.size() - 1).distance;
  47. List<DMatch> goodMatchList = new ArrayList(matchList);
  48. // 对列表进行筛选,去除distance小于(最大的distance * 0.4)的特征点
  49. CollectionUtils.filter(goodMatchList, new CollectionUtils.Predicate<DMatch>() {
  50. @Override
  51. public boolean evaluate(DMatch item) {
  52. return item.distance < max * 0.4;
  53. }
  54. });
  55. LogUtils.d("优化后的匹配点数量:", goodMatchList.size());
  56. // 如果匹配点小于4个,是无法继续执行的话,不然OpenCV会报错
  57. if (goodMatchList.size() < 4) {
  58. LogUtils.d("匹配点小于4个,跳过本次!");
  59. continue;
  60. }
  61. Mat result = new Mat();// 承载最终结果的Mat
  62. MatOfDMatch matOfDMatch = new MatOfDMatch();
  63. matOfDMatch.fromList(goodMatchList);
  64. // 把匹配图在大图中的特征点关系线画上
  65. Features2d.drawMatches(template, keyPointTemplate, src, keyPointSrc, matOfDMatch, result);
  66. // 以上其实已经能标识出大图中关于匹配图的特征点关系了(用线连接),下面要做的是找出匹配图在大图中的位置
  67. //-- 定位对象
  68. List<Point> obj = new ArrayList<>();
  69. List<Point> scene = new ArrayList<>();
  70. List<KeyPoint> listOfKeypointsObject = keyPointTemplate.toList();
  71. List<KeyPoint> listOfKeypointsScene = keyPointSrc.toList();
  72. for (int i = 0; i < goodMatchList.size(); i++) {
  73. //-- 从良好的匹配中获取关键点
  74. obj.add(listOfKeypointsObject.get(goodMatchList.get(i).queryIdx).pt);
  75. scene.add(listOfKeypointsScene.get(goodMatchList.get(i).trainIdx).pt);
  76. }
  77. MatOfPoint2f objMat = new MatOfPoint2f();
  78. MatOfPoint2f sceneMat = new MatOfPoint2f();
  79. objMat.fromList(obj);
  80. sceneMat.fromList(scene);
  81. double ransacReprojThreshold = 3.0;
  82. Mat H = Calib3d.findHomography(objMat, sceneMat, Calib3d.RANSAC, ransacReprojThreshold);
  83. //-- 从image_1(要“检测”的对象)获取角
  84. Mat objCorners = new Mat(4, 1, CvType.CV_32FC2), sceneCorners = new Mat();
  85. float[] objCornersData = new float[(int) (objCorners.total() * objCorners.channels())];
  86. objCorners.get(0, 0, objCornersData);
  87. objCornersData[0] = 0;
  88. objCornersData[1] = 0;
  89. objCornersData[2] = template.cols();
  90. objCornersData[3] = 0;
  91. objCornersData[4] = template.cols();
  92. objCornersData[5] = template.rows();
  93. objCornersData[6] = 0;
  94. objCornersData[7] = template.rows();
  95. objCorners.put(0, 0, objCornersData);
  96. Core.perspectiveTransform(objCorners, sceneCorners, H);
  97. float[] sceneCornersData = new float[(int) (sceneCorners.total() * sceneCorners.channels())];
  98. sceneCorners.get(0, 0, sceneCornersData);
  99. //-- 在角之间绘制线,也就是我要找的按钮的四个边
  100. Imgproc.line(result, new Point(sceneCornersData[0] + template.cols(), sceneCornersData[1]),
  101. new Point(sceneCornersData[2] + template.cols(), sceneCornersData[3]), new Scalar(255, 0, 0, 255), 4);
  102. Imgproc.line(result, new Point(sceneCornersData[2] + template.cols(), sceneCornersData[3]),
  103. new Point(sceneCornersData[4] + template.cols(), sceneCornersData[5]), new Scalar(255, 0, 0, 255), 4);
  104. Imgproc.line(result, new Point(sceneCornersData[4] + template.cols(), sceneCornersData[5]),
  105. new Point(sceneCornersData[6] + template.cols(), sceneCornersData[7]), new Scalar(255, 0, 0, 255), 4);
  106. Imgproc.line(result, new Point(sceneCornersData[6] + template.cols(), sceneCornersData[7]),
  107. new Point(sceneCornersData[0] + template.cols(), sceneCornersData[1]), new Scalar(255, 0, 0, 255), 4);
  108. Bitmap bitmap = Bitmap.createBitmap(result.width(), result.height(), Bitmap.Config.ARGB_8888);
  109. Utils.matToBitmap(result, bitmap);
  110. // 保存图片到本地
  111. ImageUtils.save2Album(bitmap, Bitmap.CompressFormat.PNG);
  112. } else {
  113. LogUtils.d("截图的Bitmap是空!!!");
  114. }

图片资源和效果

gxt_hand.pngbig_gxt.jpgimage.png