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是空!!!"); }
图片资源和效果
作者:冲锋的麦克
出处:https://www.cnblogs.com/zhangwenju/p/17043961.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库