前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android CameraX NDK OpenCV(三)-- 人脸贴图替换

Android CameraX NDK OpenCV(三)-- 人脸贴图替换

作者头像
Vaccae
发布2021-01-06 12:04:47
1.2K0
发布2021-01-06 12:04:47
举报
文章被收录于专栏:微卡智享

前言

接上一篇《Android CameraX NDK OpenCV(二)-- 实现Dnn人脸检测》,本篇我们直接在这个基础上做一个小玩意----人脸替换贴图,其实现在相机里很多都有这个功能了,这里就简单的实现一下。

实现效果

上面是Gif动画和视频的效果,代码还是和上面的一样,最后地址还是会放出来。

效果实现

微卡智享

01

加入的布局按钮

按钮在人脸检测的上传更新的Demo中就已经实现了,不过上篇文章没有说,这里简单的说一下,在activity_main.xml中加入了一个TextView和一个FloatingActionButton。

代码语言:javascript
复制
    <TextView        android:id="@+id/tvStatus"        android:layout_width="wrap_content"        android:layout_height="40dp"        android:layout_marginBottom="24dp"        android:textSize="13pt"        android:gravity="center"        android:text="TextView"        android:textColor="@color/design_default_color_secondary"        app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintLeft_toRightOf="@id/btnchange" />
    <com.google.android.material.floatingactionbutton.FloatingActionButton        android:id="@+id/btnchange"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginStart="32dp"        android:layout_marginBottom="24dp"        android:scaleType="fitCenter"        android:src="@drawable/convert"        app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintStart_toStartOf="parent" />

然后是MainActivity中加入这两个组件

定义了一个显示类型,默认0为灰度图,然后定义了一个MutableListof的动态列表,后面再加功能的话,直接在这里修改就可以。

02

点击按钮切换

按钮的事件中写实现方式,上面定义的itype类型,每点击一次就+1然后再除List的集合数取余,这样就实现了点击循环的方式,showtvStatus就是让文本显示出当前的状态。

调用的setTypeId的方式里面,重点说一下,这里用的是mView.postDelayed的方式,后面有50毫秒的延时,主要是如果直接用post在测试的过程中点击有的时候并没有切换,而且存入到的缓存中,当下一次触发post的时候两次都执行,改成postDelayed的方式后解决这个问题。

03

图像分析处理

代码语言:javascript
复制
   @SuppressLint("UnsafeExperimentalUsageError")    override fun analyze(imgProxy: ImageProxy) {        val image = imgProxy.image        if (image == null) {            imgProxy.close()            return        }

        try {            //将ImageProxy图像转为ByteArray            val buffer = ImageUtils.imageProxyToByteArray(imgProxy)            var bytes: ByteArray? = buffer            var w = image.width            var h = image.height
            //判断如果是竖屏,图像旋转90度            if (mView.width < mView.height) {                //根据宽度和高度将图像旋转90度                bytes = ImageUtils.rotateYUVDegree90(buffer, w, h)                //设置变量当宽和高修改过来                w = image.height                h = image.width            } else {                //用的横屏PAD测试后,发布横屏的要将图像旋转180度                //正常的横屏应该不用处理这个,如果遇到不对,可以屏蔽这一句                bytes = ImageUtils.rotateYUVDegree180(buffer, w, h)            }
            when (mTypeId) {                //0-灰度图                0 -> {                    //调用Jni实现灰度图并返回图像的Pixels                    val grayPixels = jni.grayShow(bytes!!, w, h)                    //将Pixels转换为Bitmap然后画图                    grayPixels?.let {                        val bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)                        bmp.setPixels(it, 0, w, 0, 0, w, h)                        val str = "width:${w}" + " height:${h}"
                        mView.post {                            mView.drawBitmap(bmp)                            mView.drawText(str)                        }                    }                }                //1-人脸检测                1 -> {                    //调用人脸检测返回矩形                    val detectorRects = jni.facedetector(bytes!!, w, h)                    //判断如果检测到                    detectorRects?.let {                        mView.post {                            mView.drawRect(it, w, h)                        }                    }                }                //2-贴图换脸                2 -> {                    //调用人脸检测返回矩形                    val faceRects = jni.facedetector(bytes!!, w, h)                    //判断如果检测到                    faceRects?.let {                        mView.post {                            mView.drawfaceBitmap(it, w, h)                        }                    }                }            }        } catch (e: Exception) {            Log.e("except", e.message.toString())            Snackbar.make(mView, e.message.toString(), Snackbar.LENGTH_SHORT).show()        } finally {            imgProxy.close()        }    }

上面的分析处理中,把原来的if else改为了when的写法,处理的流程比较简单,还是用的人脸检测,返回的矩形,只不过在画矩形时不能再调用原来人脸检测的那个红框了,需要改为指定位置画图片的方式。

04

换脸贴图

代码语言:javascript
复制
    //人脸贴图    private var mFaceBitmap = BitmapFactory.decodeResource(resources, R.drawable.vaccae)    private var mFaceRect = Rect(0, 0, mFaceBitmap.width, mFaceBitmap.height)    private var mFaceRects: List<Rect>? = null

在ViewOverlay中加入了专门为人脸贴图定义的几个变量,mFaceBitmap直接加载的资源里面的png图片,mFaceRect的矩形也是直接获取加载后的mFaceBitmap的矩形大小,定义的这两个主要为了drawBitmap中的函数用到。

代码语言:javascript
复制
    fun drawfaceBitmap(rect: List<Rect>?, w: Int = width, h: Int = height) {        rect?.let {            mFaceRects = rect            mScaleWidth = w.toFloat() / width            mScaleHeight = h.toFloat() / height        }        invalidate()    }            override fun onDraw(canvas: Canvas?) {        super.onDraw(canvas)
        try {            mBmp?.let {                canvas?.drawBitmap(it, x, y, Paint())            }            mRects?.let {                it.forEach { p ->                    p.left = (p.left / mScaleWidth).toInt()                    p.top = (p.top / mScaleHeight).toInt()                    p.right = (p.right / mScaleWidth).toInt()                    p.bottom = (p.bottom / mScaleHeight).toInt()                    canvas?.drawRect(p, paint);                }            }            mText?.let {                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {                    val builder = StaticLayout.Builder.obtain(it, 0, it.length, textpaint, width)                    val myStaticLayout = builder.build()                    canvas?.let { t ->                        t.translate(x, y)                        myStaticLayout.draw(t)                    }                } else {                    canvas?.drawText(it, x, y, textpaint)                }            }
            mFaceRects?.let {                it.forEach { p ->                    p.left = (p.left / mScaleWidth).toInt() - 10                    p.top = (p.top / mScaleHeight).toInt() - 10                    p.right = (p.right / mScaleWidth).toInt() + 10                    p.bottom = (p.bottom / mScaleHeight).toInt() + 10
                    canvas?.drawBitmap(                        mFaceBitmap, mFaceRect, p, Paint()                    )                }            }
        } catch (e: Exception) {            e.message?.let {                Snackbar.make(this, it, Snackbar.LENGTH_SHORT).show()            }        }    }

onDraw事件里针对每个一Rect矩形,我们都在原矩形的基础上再扩大10,所以除了位置偏移后再对每个点做了一个10的固定偏移,最后用drawBitmap画出图像就实现了贴图的效果。

Demo地址

https://github.com/Vaccae/AndroidCameraXNDKOpenCV.git

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微卡智享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Demo地址
相关产品与服务
人脸识别
腾讯云神图·人脸识别(Face Recognition)基于腾讯优图强大的面部分析技术,提供包括人脸检测与分析、比对、搜索、验证、五官定位、活体检测等多种功能,为开发者和企业提供高性能高可用的人脸识别服务。 可应用于在线娱乐、在线身份认证等多种应用场景,充分满足各行业客户的人脸属性识别及用户身份确认等需求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档