图片处理——NDK实现人脸抠图

        今天是2018年第一天,首先真诚地祝福各位同行朋友元旦快乐,在未来开发之路不断进步,为促进公司发展壮大、为提升人们生活品质以实现自我价值。言归正传,继续探讨Android移动端的图片处理,使用NDK实现人脸抠图。天天P图、美图秀秀们都具备P图技能,让女孩秒变网红脸、明星脸,这技能令人爱不释手,简直是女神打印机。那么到底如何实现这变脸大法呢?经过自己研究,总结出三个步骤:人脸检测—>人脸抠图—>人脸替换。

        1、人脸检测

        利用Android系统自带人脸检测API,只需要创建FaceDetector,传入图片宽度、高度以及检测人脸最大数这三个参数:

faceDetector = new FaceDetector(mFace.getWidth(), mFace.getHeight(), N_MAX);
        然后开始人脸检测,传入图片以及人脸数组:

int nFace = faceDetector.findFaces(mFace, face);
        2、人脸抠图

        根据人脸检测结果,得到人脸的眼坐标与中间坐标,计算出人脸矩形:left、top、right、bottom,最后根据这四个坐标点进行人脸抠图。先看下Java版本的方法:

    public int[] extractFaceByJava(Bitmap bitmap, int left, int top, int right, int bottom){
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        int mWidth = right-left;
        int mHeight = bottom-top;
        int[] pixels = new int[width*height];
        int[] mPixels = new int[mWidth*mHeight];
        //获取bitmap的所有pixel像素点
        bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
        //获取ROI(区域遍历)
        for(int x=left; x<right; x++){
            for(int y=top; y<bottom; y++){
                mPixels[(y-top)*mWidth + (x-left)] = pixels[y*width + x];
            }
        }
        return mPixels;
    }
        下面是NDK版本的方法,供Java层调用:

jintArray
Java_com_frank_image_ImageUtil_extractFace(JNIEnv *env, jobject, jobject bitmap, jint left, jint top, jint right, jint bottom){
    AndroidBitmapInfo bitmapInfo;
    int *pixelColor;
    int x, y;
    int mWidth = right-left;
    int mHeight = bottom-top;
    jint *mPixels = new jint[mWidth*mHeight];

    if (AndroidBitmap_getInfo(env, bitmap, &bitmapInfo) < 0){
        LOGI("AndroidBitmap_getInfo error...");
        return NULL;
    }
    if(AndroidBitmap_lockPixels(env, bitmap, (void **) &pixelColor) < 0){
        LOGI("AndroidBitmap_lockPixels error...");
        return NULL;
    }

    //获取ROI(区域遍历)
    for(y=top; y<bottom; y++){
        for(x=left; x<right; x++){
            mPixels[(y-top)*mWidth + (x-left)] = *(pixelColor + y*bitmapInfo.width + x);
        }
    }
    AndroidBitmap_unlockPixels(env, bitmap);
    LOGI("extractFace has done...");
    jintArray newPixel = env->NewIntArray(mWidth*mHeight);
    env->SetIntArrayRegion(newPixel, 0, mWidth*mHeight, mPixels);
    return newPixel;
}
        另外,在研究openCV时,发现它提供一个封装好的方法,可以很轻松获取ROI区域:

    /**
     *利用openCV的ROI实现人脸抠图(需要导入openCV库)
     */
    public Bitmap extractFaceByOpenCV(Mat src, int left, int top, int right, int bottom){
        //创建ROI矩形区域
        Rect ROI = new Rect(left, top, right-left, bottom-top);
        //根据原图得到ROI区域
        Mat roiMat = new Mat(src, ROI);
        Bitmap bitmap = Bitmap.createBitmap(src.width(), src.height(), Bitmap.Config.ARGB_8888);
        //Mat转成Bitmap
        Utils.matToBitmap(roiMat, bitmap);
        return bitmap;
    }
       聊了这么多,中场暂停休息下,看看人脸抠图效果:
  

        3、人脸替换

        其实人脸替换需要重复第一步,进行人脸检测,然后把目标人脸替换原本人脸。整个过程如下:

    /**
     * 人脸替换
     * @return Bitmap
     */
    public Bitmap doExchangeFace(Bitmap src, Bitmap faceBitmap){
        if(faceBitmap == null){
            return src;
        }
        int nFace = faceDetector.findFaces(src, face);
        if(nFace > 0){
            Face f  = face[0];
            PointF midPoint = new PointF();
            float dis = f.eyesDistance();
            f.getMidPoint(midPoint);
            int dd = (int)(dis);
            int width = dd * 2;
            int height = dd * 2 + compensation;
            faceBitmap = Bitmap.createScaledBitmap(faceBitmap, width, height, true);
            return extractFaceHelper.exchangeFace(src, faceBitmap, (int)(midPoint.x - dd), (int)(midPoint.y - dd),
                    (int)(midPoint.x + dd), (int)(midPoint.y + dd) + compensation);
        }
        return src;
    }
        上面调用的NDK的实现方法:
jobject
Java_com_frank_image_ImageUtil_exchangeFace(JNIEnv *env, jobject, jobject src, jobject face,
                                            jint left, jint top, jint right, jint bottom){
    AndroidBitmapInfo srcInfo, faceInfo;
    int *srcColor, *faceColor;
    int x, y;

    if (AndroidBitmap_getInfo(env, src, &srcInfo) < 0
        || AndroidBitmap_getInfo(env, face, &faceInfo) < 0){
        LOGI("AndroidBitmap_getInfo error...");
        return NULL;
    }
    if(AndroidBitmap_lockPixels(env, src, (void **) &srcColor) < 0
       || AndroidBitmap_lockPixels(env, face, (void **) &faceColor) < 0){
        LOGI("AndroidBitmap_lockPixels error...");
        return NULL;
    }

    for(y=top; y<bottom; y++){
        for(x=left; x<right; x++){
            *(srcColor + y*srcInfo.width + x) = *(faceColor + (y-top)*faceInfo.width + (x-left));
        }
    }
    AndroidBitmap_unlockPixels(env, src);
    AndroidBitmap_unlockPixels(env, face);
    LOGI("extractFace has done...");
    return src;
}

        由此可见,JNI可以获取Bitmap地址,直接操作Bitmap像素数据,节省内存开销,并且效率比Java方法相对高一点。

        

        人脸替换的效果并不是很理想,因为每个人脸大小不一致,需要根据目标人脸进行压缩,而且这里抠图是矩形人脸,没有无缝替换原本人脸。大家别介意,在这里只为分享实现思路。



评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

徐福记456

您的鼓励和肯定是我创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值