吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 14447|回复: 76
收起左侧

[其他转载] Android图片识别

  [复制链接]
--Eternal-- 发表于 2017-3-11 19:42
本帖最后由 --Eternal-- 于 2017-3-12 11:48 编辑

本来想利用OpenCV的工具实现手机上的图片识别,但随着对OpenCV内容的探索,严格地说,我认为所谓的“识别”,只不过是图片的“匹配”而已。因为计算机在图片的相似度比较上太笨了,不像人眼一看就能判断,它需要依赖大量计算的算法方可“匹配”出相似点。而且!根据相似点匹配,从而判断两张图片的内容是否一样,还需要很大的人为主观干涉。目前研究了一下OpenCV关于图片特征匹配的基本内容,基本上实现的原理大致相同,不同的是算法在计算机上的性能表现。要理解原理的话,可以去搜索“sift算法”,可以找到一篇小姐姐写的博客,该博客内容介绍了sift算法的原理和图像的匹配过程,详细得感人,需要掌握一定的高数和线性代数才能看得懂。数学不好的话,实际上也能大概看明白整个过程是怎么样的。类似与sift算法的图像匹配,在计算机上实现的过程基本是: 加载图片-->找关键点-->计算关键点特征-->匹配关键点特征-->判断或者画线展示(非必须)。其中匹配关键点是需要插入一些人为主观判断的,最后一步只是展示用,所以不一定需要。
好吧,扯了那么多,说是图片识别,连张图片都没有,没图说个j八~~好,先放一张原始的测试图:

有人会怀疑,AndroidJava开发,用java来做图片处理会不会很慢?其实,这里的java部分只是负责接口而已,实际上还是调用了native层的C/C++代码进行处理的,这个在上一篇关于OpenCV在Android Studio上的搭建博客里已经知道了,ctrl+右键去看源码,会发现最终到native修饰的函数就探索不下去了。
Go now~1. 加载图片。低版本加载图片是用Highgui这个类加载的,但高版本的opencv sdk里把读取图片的加载接口移到了Imgcodecs类去了。
[java] view plain copy
  • Mat src=Imgcodecs.imread(imgPath);  
其中参数imgPath为String类型,即图片的路径。又或者使用Utils类加载图片
[java] view plain copy
  • Mat src=Utils.loadResource(context, resId);  
其中参数为Context,和图片id,如R.drawable.xxx。
Mat类是英文Matrix的缩写,即数学上的矩阵,用于保存图片。图片的在计算机上的处理,基本上也是矩阵的数值作各种变换处理。大学里的线性代数真的要学好呐~
2. 找关键点。找关键点的算法有很多,如 fast,  orb,  mser,  gftt,  harris,  simpleblob,  brisk,  akaze, sift 和 surf等
[java] view plain copy
  • MatOfKeyPoint keyPoint=new MatOfKeyPoint();  
  • FeatureDetector fd=FeatureDetector.create(FeatureDetector.BRISK);  
  • fd.detect(src, keyPoint);  
因为sift和surf算法有专利,所以opencv官方并没有集成,所以使用sift算法会报错。github上已有三方集成了。每个算法找关键点不一样,导致在计算机上的性能也会不一样,最直接的表现就是时间。fast算法,risk算法,orb算法  效果比较:左,fast算法找了许多关键点,用时仅仅4ms;中,brisk算法的关键点不多,但却用时1944ms;(这个算法真的很久,我也不知道为啥)右,orb算法关键点比brisk稍微多一点,用时12ms。(测试机cpu很渣~)至于其他的算法我就不一一去比较了,基本上就是时间和精度的矛盾和取舍了,用时间换精度,or用精度换时间。

虽然感觉性能fast貌似比brisk要好,但我觉得brisk找的关键点质量比较好,而fast只是简单地找了一些边缘点而已。而且!并不是说关键点越多越好,因为会影响后面的计算速度,越多,自然而然下一步的特征计算量就越大。
3. 关键点的特征计算。单单靠关键点是没法匹配两张图片的,所以我们还需要计算出关键点的特征,才能进行比较。
[java] view plain copy
  • Mat description=new Mat();  
  • DescriptorExtractor de=DescriptorExtractor.create(DescriptorExtractor.ORB);  
  • de.compute(src, keyPoint, description);  
关键函数compute(),计算其关键点的特征,参数src为原图Mat,keyPoint为上一步计算src关键点的MatOfKeypoit, 当然计算出来的description也是一个Mat,可见基本图形处理都是基于矩阵Matrix的运算。这里的话,opencv提供的算法也有几种:orb, brisk, brief, akaze, freak, surf, sift等其中的surf和sift也是不能使用的。耗时:brisk:1877ms(仍表示怀疑,难到跟手机有关?),  orb:16ms。注意:找关键点和计算特征的算法不能随便乱配,会有报错的情况,测试下面几种情况没问题:都是brisk,都是orb,或者fast配brisk,fast配orb,这个这里就不一一测试了。
4. 匹配特征。匹配主要有两个类一个BFMatcher,一个FlannBasedMatcher,均继承DescriptorMatcher。BFMatcher,即Brute Force,暴力,尽可能多地匹配符合的特征点;FlannBasedMatcher,基于flann算法,这个就不扯了,我也不太懂。但实际使用过程,试过用flannbasedmatcher会报错,大概是跟Mat容器的数据类型有关。搜索一下区别,发现两者对Mat容器的数据类型是有要求的,这里就不多扯了,有时间的话可以慢慢探索,这里就先用着BFMatcher。
[java] view plain copy
  • List<MatOfDMatch> matches=new ArrayList<>();  
  • DescriptorMatcher matcher=DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE);  
  • matcher.radiusMatch(descriptionA, descriptionB, matches, 30f);  
或者直接用子类
[java] view plain copy
  • List<MatOfDMatch> matches=new ArrayList<>();  
  • BFMatcher matcher=BFMatcher.create();  
  • matcher.radiusMatch(descriptionA, descriptionB, matches, 30f);  
对于radiusMatch()这个函数,前两个参数分别是图片A和图片B经过上面几步算出来的关键点特征,均是Mat类型,而第三个参数matches是一个List<MatOfDMatch>类型,即存储了n个MatOFDMatch (n个元素里包含1*1矩阵,即匹配成功;和0*0空矩阵,即匹配不成功) 的列表,也就是输出,第四个参数float,指的是MatOfDMatch中的DMatch元素的属性distance不能超过这个最大“距离”,说简单点,就是超过这个“距离”就不要,也即是过滤器。断点Debug下matches列表的元素:
MatOfDMatch实际上是一个1*1的矩阵,调用toArray()[0],可以直接获得对象DMatch,该对象存储了两个关键点特征的匹配信息,里面有一个很重要的属性distance.
距离属性:DMatch类的属性distance,该distance比不是指物理上的距离,而是指欧氏距离,但处于2维或者3维的时候,你还可以用物理空间或者勾股定理来理解,但超过了3维,那维数就不是能用物理空间来表示的了,更多的是指两个物体之间的相似度,值越小,表示越相似。举个栗子:好比如小姐姐写的图文并茂的博客里,对于sift算法,计算特征值的时候,会取一个半径范围内的所有采样点的关于颜色变化梯度的向量相加,得到8个方向的特征向量,那么这8个特征向量就可以当做8维,也就是说这个关键点有8维。(我随便说说的,便于理解~)回到正题,除了matcher.radiusMach()接口外,还有一个matcher.knnMatch()该接口基于knn算法,没有radiusMatch那样的过滤器,所以有很多不准确的点都被当做是匹配对了。匹配同一张图,然后对radiusMatch()和knnMatch()做个比较:radius: 连线都平行
knn:  连线不完全平行
可以看到,radius有过滤器,可以把一些distance较大的配对点滤掉,即那些不平行的连线的匹配点。上面是两张相同的照片匹配结果,在所有的匹配点List<MatOfDmatch>里,其distance基本都是0。网上有C++的实现方法,基本都是knnMatch后,再根据匹配点的distance进行一次过滤,去掉不太符合的匹配点,基本和radiusMatch类似。
5. 判断或连线(非必须)画图可以方便展示,调用Feature2d接口即可,除了连线,也可以标注关键点。
[java] view plain copy
  • Mat dst=new Mat();  
  • Features2d.drawMatches2(srcA, keyPointA, srcB, keyPointB, matches, dst);  
  • Bitmap bitmap=Bitmap.createBitmap(dst.cols(), dst.rows(), Bitmap.Config.ARGB_4444);  
  • Utils.matToBitmap(dst, bitmap);  
  • ivTest.setImageBitmap(bitmap);  
基本就是创建一个输出用的容器Mat,然后drawMatches,接着创建Bitmap,尺寸要和输出Mat的尺寸对应,即width==cols,height==rows,最后将Mat转Bitmap,输出到ImageView上。
再贴出几个对比图:
拉伸,distance范围2.236068--870.503906, 限制max distance=500:
拉伸,distance范围2.236068--870.503906, 限制max distance=200:

旋转,distance范围9.797959--885.404418, 限制max distance=200:
拉伸并旋转,distance范围29.966648--819.875610, 限制max distance=200:

截取部分,distance范围0.0--1052.601562,限制max distance=200:

从上面几个例子可以看到,匹配特征点有一个范围,表示所有匹配点从准确到粗糙的一个范围,匹配率=1*1矩阵数 / (1*1矩阵数+0*0矩阵数)*100%。然而,影响匹配率最大的因素,就是radiusMatch()接口中的第四个参数,maxDistance,即匹配特征相似度的一个阀值。如果该阀值太高,那么会匹配到许多不符合的关键点;如果太低,虽然精度变高,但匹配率也会很低,对于一些变化比较大的图片,但内容一样,却有可能识别不出来。因此,该阀值的选取,最终还是取决于你个人对识别的精度要求。那么,你觉得匹配率多大才算是同一张图片呢?3个关键点,有2个匹配上了,难道你就敢说是同一张图片了?匹配率只有百分之几,难道你敢说不是同一张?
于是,就回到了刚开始说的,对于图片识别,实际上还是插入了很大的人为主观干涉进去。计算机处理图片速度很快,但却很笨~
下面贴出完整代码:
[java] view plain copy
  • public class MainActivity extends AppCompatActivity {  
  •   
  •     private Button btnTest;  
  •     private ImageView ivTest;  
  •   
  •     static{  
  •         if(!OpenCVLoader.initDebug()){  
  •             Log.d("zz", "init failed");  
  •         }  
  •     }  
  •   
  •     @Override  
  •     protected void onCreate(Bundle savedInstanceState) {  
  •         super.onCreate(savedInstanceState);  
  •         setContentView(R.layout.activity_main);  
  •   
  •         ivTest=(ImageView)findViewById(R.id.iv_test);  
  •   
  •         btnTest=(Button)findViewById(R.id.btn_test);  
  •         btnTest.setOnClickListener(new View.OnClickListener() {  
  •             @Override  
  •             public void onClick(View v) {  
  •                 String imgA="/sdcard/a.jpg";  
  •                 String imgB="/sdcard/d.jpg";  
  •                 if(match(imgA, imgB)){  
  •                     Toast.makeText(MainActivity.this, "Match succeed", Toast.LENGTH_SHORT).show();  
  •                 }  
  •             }  
  •         });  
  •   
  •     }  
  •   
  •     private boolean match(String imgPath1, String imgPath2){  
  •   
  •         //加载图片  
  •         File a=new File(imgPath1);  
  •         File b=new File(imgPath2);  
  •         if(!a.exists() || !b.exists()) return false;  
  •         Mat srcA=Imgcodecs.imread(imgPath1);  
  •         Mat srcB=Imgcodecs.imread(imgPath2);  
  •   
  •         //寻找关键点  
  •         FeatureDetector fd=FeatureDetector.create(FeatureDetector.ORB);  
  •         MatOfKeyPoint keyPointA=new MatOfKeyPoint();  
  •         MatOfKeyPoint keyPointB=new MatOfKeyPoint();  
  •         fd.detect(srcA, keyPointA);  
  •         fd.detect(srcB, keyPointB);  
  •   
  •         //计算关键点的特征  
  •         DescriptorExtractor de=DescriptorExtractor.create(DescriptorExtractor.ORB);  
  •         Mat descriptionA=new Mat();  
  •         Mat descriptionB=new Mat();  
  •         de.compute(srcA, keyPointA, descriptionA);  
  •         de.compute(srcB, keyPointB, descriptionB);  
  •   
  •         //匹配两张图片关键点的特征  
  •         List<MatOfDMatch> matches=new ArrayList<>();  
  •         DescriptorMatcher matcher=DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE);  
  •         matcher.radiusMatch(descriptionA, descriptionB, matches, 200f);  
  •   
  •         //画图显示匹配结果(演示用,非必须,可注释)  
  •         Mat dst=new Mat();  
  •         Features2d.drawMatches2(srcA, keyPointA, srcB, keyPointB, matches, dst);  
  •         Bitmap bitmap=Bitmap.createBitmap(dst.cols(), dst.rows(), Bitmap.Config.ARGB_4444);  
  •         Utils.matToBitmap(dst, bitmap);  
  •         ivTest.setImageBitmap(bitmap);  
  •   
  •         //根据匹配率判断是否匹配(以下处理不科学,无视掉)  
  •         int total=Math.min(keyPointA.rows(), keyPointB.rows());  
  •         int matchedNum=0;  
  •         for(MatOfDMatch match : matches){  
  •             if(match.rows()!=0) matchedNum++;  
  •         }  
  •         float ratio=matchedNum*1.0f/total;  
  •         if(ratio>0.75f) return true;  
  •   
  •         return false;  
  •     }  
  • }  

布局文件很简单,就一个button和一个imageview,不贴了。因为读图片是从sdcard读的,需要加权限
[html] view plain copy
  • <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>  


不由惊叹人类视觉神经处理的伟大和奇妙~

免费评分

参与人数 21吾爱币 +21 热心值 +21 收起 理由
吕鼎 + 1 + 1 我很赞&lt;span id=&quot;transmark&quot; style=&quot;display: none; width:
yhtj77 + 1 + 1 用心讨论,共获提升!
zjjyl + 1 + 1 谢谢@Thanks!
青白Pallor + 1 + 1 用心讨论,共获提升!
lygwindking + 1 + 1 谢谢@Thanks!
i7_720qm + 1 + 1 谢谢@Thanks!
wagxu + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
够钟qwb + 1 + 1 谢谢@Thanks!
天秤男 + 1 + 1 我很赞同!
zhuzaiting + 1 + 1 谢谢@Thanks!
时光机任意门 + 1 + 1 用心讨论,共获提升!
YYL7535 + 1 + 1 谢谢@Thanks!
真爱贤 + 1 + 1 我很赞同!
健健J + 1 热心回复!
dxdeng + 2 + 1 热心回复!
SureFire丶 + 1 + 1 谢谢@Thanks!
Daxx88 + 1 + 1 用心讨论,共获提升!
zfzzqlx + 1 + 1 热心回复!
fujita731 + 1 + 1 谢谢@Thanks!
likang + 1 + 1 谢谢@Thanks!
rzhxw + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

cunzher 发表于 2017-3-11 22:38
csdn转过来的?
友情提示:转载请注明来源
如果楼主您是原作者,也阔以说明一下啊

还有哦,排版不美观~多次用到https://code.csdn.net/assets/ico_fork.svg这个图片~
dszsdyx 发表于 2017-3-13 05:09
图片比对,我个人感觉没有意义,说穿了,如果你是集图爱好者,一般都是收集图站官网的图片,那个有专业的图片比对,如果不是,你比对有什么意义
犹行犹予 发表于 2017-10-2 12:32
吕鼎 发表于 2017-7-31 07:36
这是一个好帖
ddian 发表于 2017-7-22 21:50
抛砖引玉,好东西
小神喏喏 发表于 2017-7-21 17:38
学习学习,感谢楼主
0.438 发表于 2017-7-21 17:23
很强势,看能不能用于C#
wdh 发表于 2017-7-21 17:17
学习了,谢谢楼主
wdh 发表于 2017-7-21 17:15
学习了,谢谢
hydvd 发表于 2017-4-11 09:50
看帖回帖是一种美德,所以我每次都回帖!
我就是爱吃咸菜 发表于 2017-3-16 07:19 来自手机
表示完全没有看懂
农村小青年 发表于 2017-3-15 21:03
感谢楼主分享,学习了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-27 00:48

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表