前言
最近公司项目比较空,花了点时间写了个人脸识别的app,可以查看你的性别、年龄、颜值、情绪等信息,利用的是 Face++ 的人脸识别API。本项目采用了 MVP 的架构,使用了 Retrofit、RxJava、Dagger、EventBus 等框架进行开发和解耦,利用 MaterialDesign 进行UI上的布局设计。
主要的功能就是拍照,然后将照片传至 Face++ 服务器,进行人脸识别,获取返回的信息,对信息进行处理。将人脸在照片上标出,并将信息展示出来。
话不多说,先来看一下 app 的效果(吴彦祖还是帅啊,哈哈)。
下面文章主要介绍的是本项目的开发过程和碰到的坑。
过程
项目的整个流程很简单无非就是三步,拍照片,传照片获取数据,然后对数据进行处理展示。
拍照获取照片
拍照需要获取系统权限,我封装了一个方法,来判断App是否有拍照相关的权限,如果没有就去动态请求权限,并返回 false,如果有就返回 true。
public static boolean checkAndRequestPermission(Context context, int requestCode) {\n if (context.checkSelfPermission( Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED\n || context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED\n || context.checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {\n ((Activity) context).requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, requestCode);\n return false;\n }else {\n return true;\n }\n }\n
获取到拍照权限后就可以拍照了,但是拍照得到的照片我们需要通过 FileProvider 获取。FileProvider 相关的内容就不作介绍了,Android 7.0 之后都得用这个。
<provider\n android:name="android.support.v4.content.FileProvider"\n android:authorities="com.chaochaowu.facedetect.provider"\n android:exported="false"\n android:grantUriPermissions="true">\n <meta-data\n android:name="android.support.FILE_PROVIDER_PATHS"\n android:resource="@xml/file_paths" />\n </provider>\n
拍照之后从文件中读取照片,我们可以得到一个 BitMap 对象。这里就有一个很大的坑,如果手机是三星的话,照片从文件里读出来,最后得到的照片会被旋转 90°!!!,这个贼坑啊,调了我好久,以为是自己手机的故障,后来网上查了一下,也请教了一下前辈,原来三星的手机都有这个问题,所以说我们要对文件中取出来的照片进行一下处理。
/**\n * 读取图片的旋转的角度\n *\n * @param path 图片绝对路径\n * @return 图片的旋转角度\n */\n public static int getBitmapDegree(String path) {\n int degree = 0;\n try {\n // 从指定路径下读取图片,并获取其EXIF信息\n ExifInterface exifInterface = new ExifInterface(path);\n // 获取图片的旋转信息\n int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,\n ExifInterface.ORIENTATION_NORMAL);\n switch (orientation) {\n case ExifInterface.ORIENTATION_ROTATE_90:\n degree = 90;\n break;\n case ExifInterface.ORIENTATION_ROTATE_180:\n degree = 180;\n break;\n case ExifInterface.ORIENTATION_ROTATE_270:\n degree = 270;\n break;\n default:\n degree = 0;\n break;\n }\n } catch (IOException e) {\n e.printStackTrace();\n }\n return degree;\n }\n /**\n * 将图片按照某个角度进行旋转\n *\n * @param bm 需要旋转的图片\n * @param degree 旋转角度\n * @return 旋转后的图片\n */\n public static Bitmap rotateBitmapByDegree(Bitmap bm, int degree) {\n Bitmap returnBm = null;\n // 根据旋转角度,生成旋转矩阵\n Matrix matrix = new Matrix();\n matrix.postRotate(degree);\n try {\n // 将原始图片按照旋转矩阵进行旋转,并得到新的图片\n returnBm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);\n } catch (OutOfMemoryError | Exception e) {\n e.printStackTrace();\n }\n if (returnBm == null) {\n returnBm = bm;\n }\n if (bm != returnBm) {\n bm.recycle();\n }\n return returnBm;\n }\n
封装了两个方法,依次调用可以解决三星手机照片的问题。两个方法主要的工作就是,得到取出来的照片被旋转的角度,然后再将角度旋转回去,就可以得到原来的照片。因为并不是所有的手机在获取照片时,照片都会被旋转,所以得先判断一下照片有没有被旋转,再决定是否需要将它旋转调整。
行,这样最后就获得到了正确的 BitMap 照片,可以进行下一步了。
传照片获取数据
传照片获取数据,主要是运用了 Retrofit 和 RxJava 的封装。请求的参数可以参考 Face++ 的官方文档。
/**\n * retrofit 面部识别请求的网络服务\n * @author chaochaowu\n */\npublic interface FaceppService {\n /**\n * @param apikey\n * @param apiSecret\n * @param imageBase64\n * @param returnLandmark\n * @param returnAttributes\n * @return\n */\n @POST("facepp/v3/detect")\n @FormUrlEncoded\n Observable<FaceppBean> getFaceInfo(@Field("api_key") String apikey,\n @Field("api_secret") String apiSecret,\n @Field("image_base64") String imageBase64,\n @Field("return_landmark") int returnLandmark,\n @Field("return_attributes") String returnAttributes);\n}\n
照片需要进行 base64 转码后上传至服务器,封装了一个照片base64转码方法。
public static String base64(Bitmap bitmap){\n ByteArrayOutputStream baos = new ByteArrayOutputStream();\n bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);\n byte[] bytes = baos.toByteArray();\n return Base64.encodeToString(bytes, Base64.DEFAULT);\n }\n
处理完成之后就可以进行网络请求获取数据。
@Override\n public void getDetectResultFromServer(final Bitmap photo) {\n String s = Utils.base64(photo);\n faceppService.getFaceInfo(BuildConfig.API_KEY, BuildConfig.API_SECRET, s, 1, "gender,age,smiling,emotion,beauty")\n .subscribeOn(Schedulers.io())\n .observeOn(AndroidSchedulers.mainThread())\n .subscribe(new Observer<FaceppBean>() {\n @Override\n public void onSubscribe(Disposable d) {\n mView.showProgress();\n }\n @Override\n public void onNext(FaceppBean faceppBean) {\n handleDetectResult(photo,faceppBean);\n }\n @Override\n public void onError(Throwable e) {\n mView.hideProgress();\n }\n @Override\n public void onComplete() {\n mView.hideProgress();\n }\n });\n }\n
Face++ 服务器会对我们上传的照片进行处理,分析照片中的人脸信息,并以 json 形式返回,返回的数据将被放入我们定义的bean类中。
/**\n * 面部识别结果的bean\n * @author chaochaowu\n */\npublic class FaceppBean {\n /**\n * image_id : Dd2xUw9S/7yjr0oDHHSL/Q==\n * request_id : 1470472868,dacf2ff1-ea45-4842-9c07-6e8418cea78b\n * time_used : 752\n * faces : [{"landmark":{"mouth_upper_lip_left_contour2":{"y":185,"x":146},"contour_chin":{"y":231,"x":137},"right_eye_pupil":{"y":146,"x":205},"mouth_upper_lip_bottom":{"y":195,"x":159}},"attributes":{"gender":{"value":"Female"},"age":{"value":21},"glass":{"value":"None"},"headpose":{"yaw_angle":-26.625063,"pitch_angle":12.921974,"roll_angle":22.814377},"smile":{"threshold":30.1,"value":2.566890001296997}},"face_rectangle":{"width":140,"top":89,"left":104,"height":141},"face_token":"ed319e807e039ae669a4d1af0922a0c8"}]\n */\n private String image_id;\n private String request_id;\n private int time_used;\n private List<FacesBean> faces;\n ...显示部分内容\n
bean 类中有人脸识别得到的 性别、年龄、颜值、情绪等信息,还有每张人脸在照片中的坐标位置。接下来的工作就是对这些数据进行处理。
获取信息后的数据处理
数据的处理主要就两件事,一个是将数据以文字的形式展现,这个很简单,就不介绍了,还有一个就是将人脸在照片中标示出来,这个需要对 BitMap 进行处理,利用数据中人脸在照片中的坐标位置,我们用方框将人脸标识出来。
private Bitmap markFacesInThePhoto(Bitmap bitmap, List<FaceppBean.FacesBean> faces) {\n Bitmap tempBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);\n Canvas canvas = new Canvas(tempBitmap);\n Paint paint = new Paint();\n paint.setColor(Color.RED);\n paint.setStyle(Paint.Style.STROKE);\n paint.setStrokeWidth(10);\n for (FaceppBean.FacesBean face : faces) {\n FaceppBean.FacesBean.FaceRectangleBean faceRectangle = face.getFace_rectangle();\n int top = faceRectangle.getTop();\n int left = faceRectangle.getLeft();\n int height = faceRectangle.getHeight();\n int width = faceRectangle.getWidth();\n canvas.drawRect(left, top, left + width, top + height, paint);\n }\n return tempBitmap;\n }\n
封装了一个方法,运用 Canvas 在照片上进行绘制,因为照片中的人脸可能不止一个,所以用for循环遍历。获取人脸在照片中的坐标,利用人脸左上角的坐标以及人脸的宽高,在照片中绘制一个方框将人脸标出。
剩余信息我这边采用 RecyclerView 来展示。左右滑动可以查看每张人脸的信息。RecyclerView 的 item 上展示的是简要信息,可以点击 item 进入详情页面查看面部识别的详细信息。RecyclerView 以及详情界面的实现就不作介绍了,很基本的操作。我这边也就只使用了 SharedElement 让界面切换看起来舒服一点。
其他就没什么操作了,还可以看一下我的项目架构。由于用了各种框架进行解耦,所以代码文件数量变多了,但是单个文件中的代码会变少一点,清晰易读一点,这也是解耦的目的,也方便之后的维护。
最后
写完这个APP后,我一直在思考一个问题,APP给吴彦祖的颜值打分80多,那100分的颜值会是怎样?
好啦,文章写到这里就结束了,如果你觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。
希望读到这的您能转发分享和关注一下我,以后还会更新技术干货,谢谢您的支持!
转发+点赞+关注,第一时间获取最新知识点 私信【资料】还能获得免费资料哦
Android架构师之路很漫长,一起共勉吧!
版权声明:CosMeDna所有作品(图文、音视频)均由用户自行上传分享,仅供网友学习交流。若您的权利被侵害,请联系删除!
本文链接://www.cosmedna.com/article/258354343.html