Opencv 图像识别Android实战(识别扑克牌 7.筛选候选区)

变懒了

人一懒下来就很难勤快,再加上疫情,默然回首发现这个小小的Demo文章居然还没写完,也发现有小伙伴留言,再加上最近研究Opencv 4.2 准备继续写下去。
上一节我们提到了函数 Imgproc类的

    public static void findContours(Mat image, List contours, Mat hierarchy, int mode, int method)

函数,这个函数就是从图像中提取联通区域,简单的说它的作用就是把一些可能是一个整体的区域给连接起来,返回给调用者。无论是在牛逼的深度学习网络还是在简单的two-stage 传统的机器学习算法中,都是非常重要的步骤,这一步如果完美那么图像识别就成功80%了。

举一个例子

假如你要在一幅图片中识别一辆汽车:你传入一张含有汽车图片,通过调用findContours 得到的联通区域会有很多,可能有轮子,可能有车灯,引擎盖子,当然也可能有整车的区域(这种是理想情况),通过这种方式得到的的无数碎片区域,其中可能有你想要的区域也可能没有,比如你通过此方式只得到了引擎盖子的区域,但这种区域并不能准确的预测这是一辆车,你更不能很好的预测这辆车的准确位置,原因就是通过这种方式你找到的联通区域并不完美,最完美的区域就是整车的外接矩形框,当然这在实际处理的时候90%是办不到的,或者说你要完善这种算法才行,真遇到这种情况你需要借助深度学习算法框架比如YOLO,通过提供大量的数据集让算法自动去适应学习,找到完美的整车外接矩形框,当然这是另一个话题 了。

回到这个例子

很庆幸,我们能得到完美的矩形框。其中最大的原因就是这里的应用场景非常特殊,首先扑克的花色和数字是分开的,他们的外接矩形并不会连接到一起(扑克的白色背景起到了分割的作用)。其次是每一次摄像头捕捉到的图像相差不大(角度,亮度相差不大,字符特征都是固定的几种),而且我们还可以在软件上对客户进行引导,引导客户把相机的红色矩形框对准扑克牌,这能大量减少计算量,提高速度,且减小识别干扰。我们目前讨论的场景是非常简单的,但也仅限于识别这种单一的场景,如果要识别更加复杂的场景就需要根据实际情况修改算法,甚至使用DNN算法框架。我的一贯看法是如果能使用传统机器学习算法的两步法来解决问题的,尽量不要用DNN框架,DNN框架虽然能完美的解决很多问题,但是操作困难,硬件昂贵,而且应用场景收到了极大的限制,绝大多数情况下是不能在手机上流畅运行的,而且移动设备上通常装不了AI芯片,能装算力也非常有限。而且要让DNN框架工作起来,也需要大量的准备工作(采集样本),编码工作也并不会少,简单的说,杀鸡莫用牛刀。

上面说的这两段就是一个简单的知识铺垫:

我们现在来研究findContours函数,他的最有用的计算结果放在了contours参数中,这个参数是一个MatOfPoint 类型的List。一个MatOfPoint包含了很多点,这些点连起来才算是一个联通的区域。

 List contours = new ArrayList<>();
 Imgproc.findContours(dst, contours, new Mat(dst.rows(), dst.cols(), dst.type()), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);//CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE

还有非常重要的一点就是 findContours 函数的参数选型,最重要的就是这个mode参数的选型,最好选择Imgproc.RETR_LIST,他能最大程度上返回的联通区域数量,其他的参数各位可以自行查看,这里不详细说,我们需要从尽可能多的联通区域(此时可以说是候选区了)找到可能是扑克牌的区域,候选区越多,我们就可能选得越准确。而不会漏掉一些本来是目标的区域。

在接下来文字当中将会结合具体代码, 讨论具体的筛选问题:

第一步

通过boundingRect 函数将联通区域转换成矩形区域,这样方便于我们计算这块区域的高度,宽度,以及比例,面积等特征信息。针对我们当前的应用场景,太小的区域和太大的区域都不能要,因为他们并不是我们要找的区域,太小的区域,可能是一些小缝隙,太大的区域可能是大块的色区。高宽比例太小或者太大,也不是我们要找的区域,因为扑克牌的色号和数字总体趋近正方形,而且一般来说高度大于宽度,我们可以适当放大这一限制,比如高度是宽度的1-3倍都可以作为候选区,并且保存到磁盘上,进行第二步操作。


image.png

第二步

人工筛选,所谓人工智能,没有人的参与是不可能的,把第一步选出来候选区保存下来的文件进行人工分类,比如我们这里要识别数字和色号共18种情况(0,1,2,3,4,5,6,8,9,方块,黑桃,红桃,梅花,A,J,K,Q)那么我们需要建立19个文件夹如下


image.png

这里为什么会建立一个叫X的文件夹呢?
我们得到的候选区文件,之所有叫候选区,就是因为他们不是100%的准确,我们把准确的部分进行分类,不是我们想要的区域(不是这18种的任何一种)单独放到X文件夹,
比如0文件夹里面的图都是非常像0的候选区域小图(我们可以把已经分类好的候选区图像称为样本)

image.png

而X文件夹里什么形状都有,他并不是我们想要的18种的任何一种


image.png

这是因为我们在处理候选区时,我们并不能完全过滤非候选区,因为有些非候选区体现出来的特征和候选区非常像。不能简单的删除了之,在后面的算法还需要用到。先简单的说:在识别过程中我们也是先拿到候选区,再把每一个候选区的特征和已知的分类进行对比,它和谁的特征比较像我们就把它归为那一类,比如我们发现一个候选图和0文件夹中的某一个样本很像,我们就认为它是0,如果和黑桃文件夹里面的一个样本很像,我们就认为这个候选区是黑桃(这是机器学习算法中最简单的算法KNN基本思想,只是这里我们用的是极端情况就是取和候选区最接近的一个样本)。说白了就是针对每一个候选区都和所有的样本进行一次特征(后面再讲)比对,找出特征距离最小的那个样本,这个样本属于哪个类,这个候选区我们也认为它属于这个类。

到此为止X文件夹的作用也就很明白了,如果候选区的特征和X文件夹里面的某个样本特征非常接近(特征距离最小),那么这个样本就属于X,这个候选区不是我们18种当中的一种,我们不用管他。在很多场合我们称X文件夹中的这种样本为负样本,而其他的18中文件夹里面的样本称之为正样本。如果没有负样板,那么有可能出现这样一种情况:根据一些算法的计算结果(比如KNN),针对一个候选区,他本身不属于18种当中的一个,但是结果又需要取一个最相似的结果,这样就会取到18种当中的某一类,这个相似度会很低,所以结果本身就是错误或者极度不准确的(这样做是不科学的,除非再加一个相似度阀值,当样本中和候选区相似度最大的那个样本(想速度最大)相似度达度大于阀值,才作为结果,否则本次比对没有结果)。

一句话:有了负样本,增加了识别结果的准确性。

今天先写到这里.....

你可能感兴趣的:(Opencv 图像识别Android实战(识别扑克牌 7.筛选候选区))