
随着人脸识别技术的普及,人脸数据的隐私问题也得到越来越多关注,针对隐私保护的研究也陆续出现。目前大致有下面几个方向
本文主要讲第 3 点,讲讲怎么使用移动端人脸关键点算法实现人脸匿名功能。这种方法对设备要求低,代码简单易懂,修改后就可直接落地。
下图就是最终想实现的功能

人脸关键点检测是人脸相关算法中的关键一环,它是人脸识别、表情分析、3D 人脸重建,表情驱动 3D 动画等一系列人脸相关问题的前提。

我们将使用 TengineKit 来实现人脸匿名功能
免费移动端实时人脸 212 关键点 SDK 。是一个易于集成的人脸检测和人脸关键点 SDK 。它可以在各种手机上以非常低的延迟运行。
https://github.com/OAID/TengineKit

Project 中的 build.gradle 添加
repositories { ... mavenCentral() ... } allprojects { repositories { ... mavenCentral() ... } } 主 Module 中的 build.gradle 添加
dependencies { ... implementation 'com.tengine.android:tenginekit:1.0.3' ... } <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.FLASHLIGHT" /> <uses-feature android:name = "android.hardware.camera" android:required="true"/> <uses-feature android:name = "android.hardware.camera.autofocus" /> 为 App 创建自定义摄像头界面的步骤如下:
我们先 new 一个 TextureView.SurfaceTextureListener,在里面完成 camera 的初始配置,当 TextureView 可用的时候,onSurfaceTextureAvailable 中的代码将被调用
private final TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(final SurfaceTexture texture, final int width, final int height) { int index = getCameraId(); camera = Camera.open(index); try { Camera.Parameters parameters = camera.getParameters(); List<String> focusModes = parameters.getSupportedFocusModes(); if (focusModes != null && focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); } List<Camera.Size> cameraSizes = parameters.getSupportedPreviewSizes(); Size[] sizes = new Size[cameraSizes.size()]; int i = 0; for (Camera.Size size : cameraSizes) { sizes[i++] = new Size(size.width, size.height); } Size previewSize = CameraConnectionFragment.chooseOptimalSize(sizes, desiredSize.getWidth(), desiredSize.getHeight()); parameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight()); camera.setDisplayOrientation(90); camera.setParameters(parameters); camera.setPreviewTexture(texture); } catch (IOException exception) { camera.release(); } camera.setPreviewCallbackWithBuffer(imageListener); Camera.Size s = camera.getParameters().getPreviewSize(); camera.addCallbackBuffer(new byte[ImageUtils.getYUVByteSize(s.height, s.width)]); textureView.setAspectRatio(s.height, s.width); camera.startPreview(); } @Override public void onSurfaceTextureSizeChanged(final SurfaceTexture texture, final int width, final int height) { } @Override public boolean onSurfaceTextureDestroyed(final SurfaceTexture texture) { return true; } @Override public void onSurfaceTextureUpdated(final SurfaceTexture texture) { } }; 此处将 textureView 和 camera 联系起来
textureView.setSurfaceTextureListener(surfaceTextureListener); 当 camera 启动预览,textureView 得到真实的 size 后。我们得到了 camera 的输出视频流的宽高和预览 textureView,将其保存起来,后续有用到。
textureView.setRealSizeListener(new AutoFitTextureView.RealSizeListener() { @Override public void onRealSizeMeasure(int w, int h) { if(!isReady){ isReady = true; Camera.Size s = camera.getParameters().getPreviewSize(); cameraReadyListener.onCameraReady( s.width, s.height,w, h ); } } }); 首先我们先初始化 TengineKit:
com.tenginekit.Face.init(getBaseContext(), AndroidConfig.create() .setCameraMode() .openFunc(AndroidConfig.Func.Detect) .openFunc(AndroidConfig.Func.Landmark) .setInputImageFormat(AndroidConfig.ImageFormat.YUV_NV21) .setInputImageSize(previewWidth, previewHeight) .setOutputImageSize(outputWidth, outputHeight) ); com.tenginekit.Face.Camera.switchCamera(false); 处理数据
int degree = CameraEngine.getInstance().getCameraOrientation(sensorEventUtil.orientation); com.tenginekit.Face.Camera.setRotation(degree - 90, false, outputWidth, outputHeight); com.tenginekit.Face.FaceDetect faceDetect = Face.detect(data); faceLandmarks = null; if(faceDetect.getFaceCount() > 0){ faceLandmarks = faceDetect.landmark2d(); } 这里使用 Android 的 bitmap 来实现功能,这种做法比较粗糙,性能差,但是简单易懂,如果读者有兴趣可以使用 OpenGLES 来实现此功能。
if(testBitmap != null){ testBitmap.recycle(); } testBitmap = Face.Image.convertCameraYUVData( data, previewWidth, previewHeight, outputWidth, outputHeight, - 90, true); for(Bitmap bitmap : testFaceBitmaps){ bitmap.recycle(); } testFaceBitmaps.clear(); if(testBitmap != null && faceDetect.getFaceCount() > 0){ if(faceLandmarks != null){ for (int i = 0; i < faceLandmarks.size(); i++) { Bitmap face = BitmapUtils.getDstArea(testBitmap, faceLandmarks.get(i).getBoundingBox()); face = BitmapUtils.blurByGauss(face, 50); testFaceBitmaps.add(face); } } } runInBackground(new Runnable() { @Override public void run() { trackingOverlay.postInvalidate(); } }); trackingOverlay 为定制的 view,将 canvas 暴露出来用于画 bitmap
trackingOverlay.addCallback(new OverlayView.DrawCallback() { @Override public void drawCallback(final Canvas canvas) { if(testBitmap != null){ canvas.drawBitmap(testBitmap, 0,0, circlePaint); } if(faceLandmarks != null){ for (int i = 0; i < faceLandmarks.size(); i++) { Rect r = faceLandmarks.get(i).getBoundingBox(); canvas.drawRect(r, circlePaint); canvas.drawBitmap(testFaceBitmaps.get(i), r.left, r.top, circlePaint); } } } });


https://github.com/OAID/TengineKit
https://github.com/jiangzhongbo/TengineKit_Demo_Identity_Protection
1 Jirajine 2020-07-17 12:20:55 +08:00 via Android 那么如何确保原始人脸数据不被这个 sdk 偷走呢。 |
2 aabbcc112233 2020-07-17 12:45:29 +08:00 还是上次那个小姐姐吗?感觉颜值降低了 1 分。另外她的微信号你还没发给我 |
4 exmorning OP @aabbcc112233 不是给了抖音号可以发私信嘛,微信号得自己要啊 |
5 meilande 2020-07-17 17:01:37 +08:00 用 bitmap 性能不是很好,正在商用得用 OpenGL 写 |
6 exmorning P @meilande 如果想实现下 OpenGL,可以参考 https://github.com/CainKernel/CainCamera |
7 2kCS5c0b0ITXE5k2 2020-07-17 18:03:26 +08:00 对于某些视频很实用啊 |
8 CoderGeek 2020-07-17 18:37:49 +08:00 再整点换脸 QvQ |
11 JackCui 2020-07-17 21:05:53 +08:00 跟 landmark 有什么关系?不就是检测个人脸 bbox,然后加个模糊吗? |
15 wangxiaoaer 2020-07-22 12:31:00 +08:00 via iPhone 拦截了摄像头的视频流然后识别,模糊? |
16 exmorning OP @wangxiaoaer 中间加一步人脸关键点 |
17 wangxiaoaer 2020-07-22 14:21:33 +08:00 @exmorning #16 正常情况 Android 允许摄像头被拦截? 支付宝、微信等应用都是自己直接调用摄像头还管用吗? 另外你第一张图(最终想实现的功能)的视频源不是用户自己的设备,而是商家的设备,这种情况除了自己带个面具外我想不出来任何匿名的可能性。 |
18 exmorning OP @wangxiaoaer 这是一个例子,阐述 SDK 落地的一种可能性 |
19 xmoiduts 2020-07-22 15:05:43 +08:00 via Android 最近也在做抹脸内容作为毕设的一部分,迫于场景内人脸 /人头挺小的,决定选用方案:hog+svm 扫头 /(塑料级) cnn 二次判定 /tracker 跟踪-在一段时间内弥补漏检。 反正挺慢的,希望有硬件加速…… |
20 exmorning OP |
21 imn1 2020-07-22 15:14:32 +08:00 |
22 luckyrayyy 2020-07-22 15:16:48 +08:00 小姐姐我爱了 |
23 LiuJiang 2020-07-22 15:22:38 +08:00 前天做了个变脸小程序,蛮有意思的,依靠腾讯云人脸五官识别(每个月 1 万次额度)或 face-api,完成的。 |
24 LiuJiang 2020-07-22 15:24:17 +08:00 |
26 yongzhao106 2020-07-23 20:12:52 +08:00 哎呦 不错喔 |
27 yongzhao106 2020-07-23 20:17:38 +08:00 B 站也有一个相似的人脸识别视频也挺火的:深入解析 Android 人工智能-人脸检测追踪技术 |
28 Pay4Dealer 2020-07-23 20:28:12 +08:00 nice |
29 locoz 2020-07-23 20:30:00 +08:00 via Android @wangxiaoaer #17 很简单,结合 xposed 之类的 hook 框架实现对系统 API 的劫持,在摄像头 API 返回前就做处理,微信支付宝再牛逼也没用。 |
31 Liyiw 2020-07-24 14:25:40 +08:00 这个用目标检测检测出人脸矩形就可以做了吧,为什么要用到关键点? |