尝试 AI 人脸关键点算法实现一下 Android 人脸匿名功能 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
exmorning
V2EX    Android

尝试 AI 人脸关键点算法实现一下 Android 人脸匿名功能

  •  1
     
  •   exmorning 2020-07-17 11:51:48 +08:00 16483 次点击
    这是一个创建于 1981 天前的主题,其中的信息可能已经有所发展或是发生改变。

    什么是人脸匿名( Face Anonymization )

    随着人脸识别技术的普及,人脸数据的隐私问题也得到越来越多关注,针对隐私保护的研究也陆续出现。目前大致有下面几个方向

    1. 篡改输入人脸识别系统的图像。
    2. 生成式对抗网络(GAN)来匿名某人的照片或视频。

    本文主要讲第 3 点,讲讲怎么使用移动端人脸关键点算法实现人脸匿名功能。这种方法对设备要求低,代码简单易懂,修改后就可直接落地。

    下图就是最终想实现的功能
    demo1

    什么是人脸关键点算法( Face Landmarks )

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

    face landmarks

    我们将使用 TengineKit 来实现人脸匿名功能

    TengineKit

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

    TengineKit 效果图

    demo1

    实现

    配置 Gradle

    Project 中的 build.gradle 添加

     repositories { ... mavenCentral() ... } allprojects { repositories { ... mavenCentral() ... } } 

    主 Module 中的 build.gradle 添加

     dependencies { ... implementation 'com.tengine.android:tenginekit:1.0.3' ... } 

    配置 manifests

     <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" /> 

    初始化 Android Camera

    为 App 创建自定义摄像头界面的步骤如下:

    1. 检测和访问 Camera
    2. 创建预览 TextureView
    3. 构建预览 TextureView 布局
    4. 将 Camera 和 TextureView 绑定
    5. 启动预览

    我们先 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 ); } } }); 

    处理 Camera 传过来的视频流

    首先我们先初始化 TengineKit:

    1. 选用 camera 处理模式
    2. 打开人脸检测和人脸关键点功能
    3. 设置视频流格式为 YUV_NV21 ( Android camera 默认格式)
    4. 设置输入视频流的宽高,此处为 camera 的预览宽高
    5. 设置输出视频流的宽高,此处为 textrureView 的宽高
    6. 设置输入视频流来自前置摄像头
     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); 

    处理数据

    1. 得到手机旋转角度,将其设置到 TengineKit
    2. 开始检测,当检测到人脸数目大于 0 的时候,调用 faceDetect.landmark2d(),得到人脸关键点链表
     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 来实现此功能。

    1. 将从摄像头中得到的 yuv 数据通过 TengineKit 的图片帮助函数转化为 Bitmap
    2. 通过人脸关键点的外接框,裁剪 bitmap 得到人脸的 bitmap 数组
    3. 将得到的人脸 bitmap 进行高斯模糊
     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); } } } }); 

    效果

    demo demo

    Demo

    demo

    参考

    https://github.com/OAID/TengineKit

    源码

    https://github.com/jiangzhongbo/TengineKit_Demo_Identity_Protection

    知乎

    https://zhuanlan.zhihu.com/p/161038093

    32 条回复    2020-07-24 14:34:46 +08:00
    Jirajine
        1
    Jirajine  
       2020-07-17 12:20:55 +08:00 via Android
    那么如何确保原始人脸数据不被这个 sdk 偷走呢。
    aabbcc112233
        2
    aabbcc112233  
       2020-07-17 12:45:29 +08:00
    还是上次那个小姐姐吗?感觉颜值降低了 1 分。另外她的微信号你还没发给我
    exmorning
        3
    exmorning  
    OP
       2020-07-17 13:07:25 +08:00
    @Jirajine 目前就 auth 了一下,不上传任何数据
    exmorning
        4
    exmorning  
    OP
       2020-07-17 13:12:03 +08:00
    @aabbcc112233 不是给了抖音号可以发私信嘛,微信号得自己要啊
    meilande
        5
    meilande  
       2020-07-17 17:01:37 +08:00
    用 bitmap 性能不是很好,正在商用得用 OpenGL 写
    exmorning
        6
    exmorning  
    P
       2020-07-17 17:26:30 +08:00
    @meilande 如果想实现下 OpenGL,可以参考 https://github.com/CainKernel/CainCamera
    2kCS5c0b0ITXE5k2
        7
    2kCS5c0b0ITXE5k2  
       2020-07-17 18:03:26 +08:00   2
    对于某些视频很实用啊
    CoderGeek
        8
    CoderGeek  
       2020-07-17 18:37:49 +08:00
    再整点换脸 QvQ
    exmorning
        9
    exmorning  
    OP
       2020-07-17 20:53:11 +08:00
    @emeab 你启发了我!
    exmorning
        10
    exmorning  
    OP
       2020-07-17 20:53:45 +08:00
    @CoderGeek 下一篇考虑写一个换脸的 demo
    JackCui
        11
    JackCui  
       2020-07-17 21:05:53 +08:00
    跟 landmark 有什么关系?不就是检测个人脸 bbox,然后加个模糊吗?
    exmorning
        12
    exmorning  
    OP
       2020-07-17 21:13:42 +08:00
    @JackCui 用关键点外接框做的,如果用人脸检测框,打码没有这么稳定
    CoderGeek
        13
    CoderGeek  
       2020-07-18 12:25:30 +08:00
    @exmorning 赞~
    exmorning
        14
    exmorning  
    OP
       2020-07-18 19:59:35 +08:00
    wangxiaoaer
        15
    wangxiaoaer  
       2020-07-22 12:31:00 +08:00 via iPhone
    拦截了摄像头的视频流然后识别,模糊?
    exmorning
        16
    exmorning  
    OP
       2020-07-22 12:36:58 +08:00
    @wangxiaoaer 中间加一步人脸关键点
    wangxiaoaer
        17
    wangxiaoaer  
       2020-07-22 14:21:33 +08:00
    @exmorning #16 正常情况 Android 允许摄像头被拦截? 支付宝、微信等应用都是自己直接调用摄像头还管用吗?

    另外你第一张图(最终想实现的功能)的视频源不是用户自己的设备,而是商家的设备,这种情况除了自己带个面具外我想不出来任何匿名的可能性。
    exmorning
        18
    exmorning  
    OP
       2020-07-22 14:34:55 +08:00
    @wangxiaoaer 这是一个例子,阐述 SDK 落地的一种可能性
    xmoiduts
        19
    xmoiduts  
       2020-07-22 15:05:43 +08:00 via Android
    最近也在做抹脸内容作为毕设的一部分,迫于场景内人脸 /人头挺小的,决定选用方案:hog+svm 扫头 /(塑料级) cnn 二次判定 /tracker 跟踪-在一段时间内弥补漏检。

    反正挺慢的,希望有硬件加速……
    exmorning
        20
    exmorning  
    OP
       2020-07-22 15:09:05 +08:00
    imn1
        21
    imn1  
       2020-07-22 15:14:32 +08:00
    @JackCui #11
    没去看或者说看不懂代码
    如果 sdk 的步骤分得比较细,可以重写后处理(模糊)的模块,变成伪造数据(不一定是整体换脸),这就很多可以玩的功能了
    luckyrayyy
        22
    luckyrayyy  
       2020-07-22 15:16:48 +08:00
    小姐姐我爱了
    LiuJiang
        23
    LiuJiang  
       2020-07-22 15:22:38 +08:00
    前天做了个变脸小程序,蛮有意思的,依靠腾讯云人脸五官识别(每个月 1 万次额度)或 face-api,完成的。
    exmorning
        25
    exmorning  
    OP
       2020-07-22 17:05:47 +08:00
    @imn1 是的,这一步看开发者的想象力了
    yongzhao106
        26
    yongzhao106  
       2020-07-23 20:12:52 +08:00
    哎呦 不错喔
    yongzhao106
        27
    yongzhao106  
       2020-07-23 20:17:38 +08:00
    B 站也有一个相似的人脸识别视频也挺火的:深入解析 Android 人工智能-人脸检测追踪技术
    Pay4Dealer
        28
    Pay4Dealer  
       2020-07-23 20:28:12 +08:00
    nice
    locoz
        29
    locoz  
       2020-07-23 20:30:00 +08:00 via Android
    @wangxiaoaer #17 很简单,结合 xposed 之类的 hook 框架实现对系统 API 的劫持,在摄像头 API 返回前就做处理,微信支付宝再牛逼也没用。
    exmorning
        30
    exmorning  
    OP
       2020-07-23 21:02:20 +08:00
    @locoz 好思路
    Liyiw
        31
    Liyiw  
       2020-07-24 14:25:40 +08:00
    这个用目标检测检测出人脸矩形就可以做了吧,为什么要用到关键点?
    exmorning
        32
    exmorning  
    OP
       2020-07-24 14:34:46 +08:00
    @Liyiw 关键点外接框更稳定
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2489 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 43ms UTC 12:30 PVG 20:30 LAX 04:30 JFK 07:30
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86