作者:谭妥 | 公众号:谭妥
这是我自己做的一个仿滴滴打车的Android出行项目,主要针对滴滴等出行平台一直饱受质疑的“人车不符”问题,以及当前越发火热的国际化和出海战略,给出行项目增加了下面几个功能:
这几秒的时间里可以在下图的位置添加一些初始化代码,比如网络请求,得到后续Activity的素材,地理位置等等。
通过滑动地图界面上方的主题可以切换不同的项目界面。
注意:乘客的位置信息、当前经纬度、当前街道名字、楼宇名字都是在MainAcitivity做为静态成员变量定义的,原因是在别的Acitivity或者类中,这些变量需要经常使用,直接调用 / 的版本,点击选择司机证件以后调用的是我保存在assets 文件夹里的司机证件照片,也就是下面图片里的
注意:在程序中,想要在运行中读取司机证件照片,要把照片保存在assets 文件夹下面,使用AssetManager 类读取,而不能试图调取drawable 文件夹下面的照片,因为 \res 文件夹下的资源文件都会被编译到apk里面去,并同时赋予资源 id。感兴趣的同学可以看下代码里面的 copyFilesFassets()方法。
这里用我以前在国外读书时候的证件作为例子:
-
首先:要从照片中找到司机证件区域,也就是上证件边缘红色的区域
* 找到图像中的证件区域 * 在RGB色彩空间求取驾驶员证件的图像梯度,之后在此图像上做二值化,从而通过轮廓(contour)发现与面积大小过滤得到证件区域 //得到证件照片的x梯度和y梯度 //注意求梯度的时候我们使用的是Scharr算法,sofia算法容易收到图像细节的干扰 //所谓梯度运算就是对图像中的像素点进行就导数运算,从而得到相邻两个像素点的差异值 by:Tantuo
//openCV中有32位浮点数的CvType用于保存可能是负值的像素数据值 //得到梯度图像以后将其二值化,以便更清晰地找到轮廓边缘 //下面对二值图像进行形态学(morphology excution)的去噪声操作,先得到大小为 3*3像素的结构元素 //然后对结构元素进行 Morph_open开操作。 腐蚀:去除噪声-膨胀:覆盖去除的噪声点 //如果发现某一个
roi兴趣区域的轮廓宽度超过图片的一半,即可以认为这个轮廓是驾驶员证件的轮廓 contour //找到证件轮廓区域就将其拷贝到card图片中 //拷贝完成以后记得释放资源0
第一步先调用Imgproc.Scharr()方法对司机证件的原始照片进行Scharr梯度运算,所谓梯度运算就是对图像中的像素点进行导数运算,从而得到相邻两个像素点的差异值,像素差异大的地方就是图像内轮廓contour,第二步在此图像上做二值化Binarization,调用 Imgproc.morphologyEx()方法,通过轮廓(contour)发现与面积大小过滤得到证件区域。
边缘发现以后调用Imgproc.cvtColor()方法得到下面的证件区域:
2.识别到证件区域以后我们注意到证件左上角有一个比较醒目的矩形,我们用它作为reference识别到照片下方包含数字的号码区域。在程序中这个过程调用下面的 findCardNumBlock(Mat card) 方法。
//inRange函数将hsv彩色图片的根阈值进行过滤,用来过滤掉对识别左上角标志区域帮助不大的颜色 //并且把滤出的图像保存到 binary里面 // Scalar()是具有三个参数的结构体,三个参数代表 hsv的色相,饱和度,亮度值 //以上会得到一个驾驶员证件的二值化图像,但是噪声比较多 //下面对二值话图像进行形态学的开操作(morphology excution),去除小的
5*5大小的结构元素(噪声) //获取证件标志的轮廓(contours) //对于识别出来的矩形区域如果太小(面积小于200像素)则忽略 //找到标志区域以后,以标志区域为基准,证件号码的位置在标志x坐标 *2 左右,宽度大概在 binary.cols() - roi.x - 100像素 //证件号码的高度大概是证件标志(基准)的0.7倍 height*0.7 ;
//如果找到的左上角标志物的轮廓长宽都小于证件的三分之一,则以此标志物作为标准定为号码区域 //如果没有找到就返回null //得到证件号码的区域以后就可以截取下来保存到 textimage
下一步还是形态学操作去噪声。噪声就是二值化图像里面识别出来的一个个小的黑点,形态学的开操作(morphology excution)会把图像中这些小小的黑点用旁边的大区域颜色覆盖掉,目的是为了让处理后的图像更加容易被机器识别。
比如下面的代码调用OpenCV的Imgproc.morphologyEx()方法可以把大小为 5*5的结构元素(噪声)用周边像素弥补掉
//下面对二值话图像进行形态学的开操作(morphology excution),去除小的 5*5大小的结构元素(噪声)
噪声处理以后开始寻找证件区域内的号码区域 Contour做轮廓发现操作:
//对于识别出来的矩形区域如果太小(面积小于200像素)则忽略 //找到标志区域以后,以标志区域为基准,证件号码的位置在标志x坐标 *2 左右,宽度大概在 binary.cols() - roi.x - 100像素 //证件号码的高度大概是证件标志(基准)的0.7倍 height*0.7 ; //如果找到的左上角标志物的轮廓长宽都小于证件的三分之一,则以此标志物作为标准定为号码区域
得到证件号码的区域以后就可以截取下来保存到 textimage
拷贝完成以后记得释放release mat资源
完成以上工作以后可以识别到证件号码区域的矩形轮廓:
识别出了证件中的号码区域,后面就调用 DigitImageProcessor 类对这些数字进行识别,这个过程需要我单独在另外一篇文章介绍,下面仅仅对几个重要方法的功能作介绍:
点击右上角的RFID验证入口以后,会提示乘客使用手机背面像刷公交卡那样感应RFID硬件,比如嵌入芯片的司机证件、固定在车上识别器等。
// 前台分发系统,用于确保检测RFID标签时拥有最高的捕获优先权.
手机读取芯片ID这个功能的代码我单独放到NfcUtils工具类里,在utils 文件夹下。
手机读取到芯片信息,会调用NDK编译成C语言的MD5加密算法so 文件(文章最后会讲),连同当时的地理位置经纬度一起发送给平台服务器(我用的 OkHttp3 ),与数据库中注册司机的信息进行比对,并将验证结果和司机信息发送给乘客:
会一直开放出这个项目的网络接口并持续维护,方便读者测试这个功能。读者只要在验证环节使用手机读取任何一个嵌有RFID加密芯片比如学生证、银行卡、公交卡,程序在发送数据请求之前(下图代码中第二行高亮的部分)都会把读取到的ID信息换成作者本人的,再发送给平台服务器服务器做验证,这样读者测试时使用手机读取任何RFID信息都会接收到从服务器发回来的司机信息。实际项目中把这一行注释掉即可。
服务器端收到乘客发送过来的验证请求以后,会对比平台司机数据库进行核实,并把核实结果和对应司机、车辆信息发回给乘客。
下面就是平台服务器端注册司机的注册信息数据库,我用Navicat 做了部分截图,第一行红色部分就是平台验证的结果,也就是作者本人的信息。
服务器端还会对乘客发送过来的数据进行整理和分析,也可以将“人车不符”数据和位置信息发送给合规部门。
下图是“人车不符”情况发生的地区热力图:
还可以根据乘客的叫车时间,筛选出高峰时段的用车需求热力图,给司机调度部门提供数据支持。
对服务器端的打车数据进行分析,还可以生成非常漂亮的24小时动态热力图、星云图、蝌蚪迁移图,感兴趣的读者可以研究下Python 、Pandas 、MatplotLib,可以快捷地处理服务器端数据,生成可视化图表。
使用NDK调用MD5加密算法
前面提到项目中会把ID号码使用C语言的MD5算法进行加密,关键代码在下图中的cpp 文件夹。.so )和打包的工具,可以把 *.so 动态库打包到apk中。#include "MD5.h"
把头文件导入进来。两者的关系有点像书的目录和内容的关系,目录是对章节和内容进行简单表示,真正的实现实在书里面的。
下面的图可以看到 native-lib 是如何帮助 MD5JniUtils 类的 getMd5 () 方法调用 C语言加密方法的,JNIEXPORT 和 JNICALL 两个宏用来标识函数用途是调用.so 库,就好像 C++可以调用 .dll 动态链接库一样,后面紧跟的是函数名,命名规则很重要:Java_ + 包名 + 调用这个加密算法的Java工具类名 + Java调用方法
,后面的变量参数是Java中String类型对应的JNI jstring类型,下面在方法体中,就可以使用对传入的加密前字符串进行加密的C语言运算了,并把加密完成的 jstring类型结果返回给java层。