基于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>() {@Overridepublic 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();// 承载最终结果的MatMatOfDMatch 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是空!!!");}
图片资源和效果



