opencv 与dlib 结合实现人脸融合

融合流程

  1. 检测面部标记
  2. 旋转、缩放和转换第二张图像,使之与第一张图像相适应
  3. 调整第二张图像的色彩平衡,使之与第一个相匹配
  4. 把第二张图像的特性混合在第一张图像中

实现流程

找到人脸矩阵

使用dlib提取面部标记


用Dlib实现了论文One Millisecond Face Alignment with an Ensemble of Regression Trees中的算法

( http://www.csc.kth.se/~vahidk/papers/KazemiCVPR14.pdf ,作者为Vahid Kazemi 和Josephine Sullivan) 。
算法本身非常复杂,但dlib接口使用起来非常简单

人脸矩阵是由68个像素点组成,根据对应的像素的对应相应的五官
1-16代码脸型
17-21 22-26 分别代表眉毛
27-35代表鼻子
36-41 42-47 代码眼睛
48-68 带表嘴巴
 get_landmarks()函数将一个图像转化成numpy数组,并返回一个68 x2元素矩阵,输入图像的每个特征点对应每行的一个x,y坐标。

特征提取器(predictor)要一个粗糙的边界框作为算法输入,由传统的能返回一个矩形列表的人脸检测器(detector)提供,其每个矩形列表在图像中对应一个脸。
为了构建特征提取器,预训练模型必不可少,相关模型可从dlib sourceforge库下载
( http://sourceforge.net/projects/dclib/files/dlib/v18.10/shape_predictor_68_face_landmarks.dat.bz2 )。

调整脸部图像对齐

原理

 现在我们已经有了两个标记矩阵,每行有一组坐标对应一个特定的面部特征(如第30行给出的鼻子的坐标)。我们现在要搞清楚如何旋转、翻译和规模化第一个向量,

 使它们尽可能适合第二个向量的点。想法是,可以用相同的变换在第一个图像上覆盖第二个图像。

实现步骤

代码分别实现了下面几步:

1.将输入矩阵转换为浮点数。这是之后步骤的必要条件。
2.每一个点集减去它的矩心。一旦为这两个新的点集找到了一个最佳的缩放和旋转方法,这两个矩心c1和c2就可以用来找到完整的解决方案。
3.同样,每一个点集除以它的标准偏差。这消除了问题的组件缩放偏差。
4.使用Singular Value Decomposition计算旋转部分。可以在维基百科上看到关于解决正交普氏问题的细节( https://en.wikipedia.org/wiki/Orthogonal_Procrustes_problem )。

5.利用透视变换矩阵( https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations )返回完整的转化。




源代码

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include


#include
#include
#include
#include
#include
#include
#include
#include
#include


using namespace dlib;
using namespace std;
using namespace cv;


#define LOG_TAG "People_Det-JNI"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)


/*
#ifdef __cplusplus
extern "C" {
#endif
*/


static dlib::shape_predictor msp;
static frontal_face_detector detector;
static dlib::full_object_detection FACE_MARK1,FACE_MARK2;
static Mat wrap_dst, src_img2, mask;


dlib::full_object_detection detectface(dlib::cv_image mat)
{
dlib::full_object_detection mark;
std::vector mRets;
mRets = detector(mat);
if(mRets.size() > 0)
mark = msp(mat, mRets[0]);
return mark;
}


jboolean facesRotate_UseData(JNIEnv* env, jobject thiz,
jintArray face1,jint w1,jint h1,
jintArray face2,jint w2,jint h2)
{
jint *cbuf1;
jint *cbuf2;
cbuf1 = env->GetIntArrayElements(face1, JNI_FALSE);
cbuf2 = env->GetIntArrayElements(face2, JNI_FALSE);
if(cbuf1 == NULL || cbuf1 == NULL){
return JNI_FALSE;
}


Mat src_img(h1, w1, CV_8UC4, (unsigned char*)cbuf1);
Mat src_img1(h2, w2, CV_8UC4, (unsigned char*)cbuf2);
cvtColor(src_img,src_img,CV_RGBA2RGB);
cvtColor(src_img1,src_img1,CV_RGBA2RGB);


if(!src_img.data || !src_img1.data)
LOGD("data error");


dlib::cv_image img(src_img);
dlib::cv_image img1(src_img1);
FACE_MARK1 = detectface(img);
FACE_MARK2 = detectface(img1);


std::vector srcTri;
std::vector dstTri;


for(int i = 0;i < 68;i++) {
srcTri.push_back(cv::Point2f(FACE_MARK1.part(i).x(),FACE_MARK1.part(i).y()));
dstTri.push_back(cv::Point2f(FACE_MARK2.part(i).x(),FACE_MARK2.part(i).y()));
}
cv::Mat warp_mat;
warp_mat = cv::findHomography(srcTri,dstTri,0);
//the faceLine's size is 68,including all face data,such as:
//the index 0 - 16:jaw data
//the index 17 - 68:face data
//the index 17 - 21:right brow data
//the index 22 - 26:left brow data
//the index 27 - 34:nose data
//the index 36 - 41:right eye data
//the index 42 - 47:left eye data
//the index 48 - 60:mouth data


src_img2 = src_img1;
cv::warpPerspective(src_img,wrap_dst,warp_mat,src_img1.size());
dlib::cv_image img3(wrap_dst);
FACE_MARK1 = detectface(img3);
Mat new_img = Mat::zeros(src_img1.size(),CV_8UC3);
std::vector faceLine;
faceLine = render_face_detections(FACE_MARK1);
for(int i = 17 ;i < 61; i++){
cv::Point p1(faceLine[i].p1.x(),faceLine[i].p1.y());
cv::Point p2(faceLine[i].p2.x(),faceLine[i].p2.y());
cv::line(new_img,p1,p2,cv::Scalar(255,255,255),1,cv::LINE_8);
}
imwrite("/data/data/com.example.faceswapdemo/files/newimg.png",new_img);
int blur_amount = faceLine[41].p1.x() - faceLine[47].p1.x();
int blur_mask = blur_amount;
    if (blur_mask % 2 == 0) {
    blur_mask += 1;


    }
    LOGD("blur_mask%d-------",blur_mask);
GaussianBlur(new_img,mask,Size(blur_mask,blur_mask),0,0);


GaussianBlur(mask,mask,Size(blur_mask,blur_mask),0,0);
// FileStorage fs("/data/data/com.example.faceswapdemo/files/face.xml",FileStorage::WRITE);
// fs << "mask" << mask;
blur_amount = blur_amount*3/5;
    if (blur_amount % 2 == 0) {
        blur_amount += 1;
    }
    LOGD("blur_amount%d-------",blur_amount);
    Mat blur0 = Mat(wrap_dst.size(), wrap_dst.type());
    GaussianBlur(wrap_dst, blur0, Size( blur_amount, blur_amount ), 0, 0 );
    Mat blur1= Mat(src_img1.size(), src_img1.type());
    GaussianBlur(src_img1, blur1, Size( blur_amount, blur_amount ), 0, 0 );
    Mat out = Mat::zeros( blur1.size(), blur1.type());
for (int y = 0; y < blur1.rows; y++) {
for (int x = 0; x < blur1.cols; x++) {
for (int c = 0; c < 3; c++) {
if ((blur0.at(y, x)[c]) > 0) {


out.at(y, x)[c] = saturate_cast(
(wrap_dst.at(y, x)[c])
* (blur1.at(y, x)[c])
/ (blur0.at(y, x)[c]));
} else {
out.at(y, x)[c] = saturate_cast(
(wrap_dst.at(y, x)[c])
* (blur1.at(y, x)[c]));
}
}
}
}




    for(int y = 0; y < src_img1.rows; y++ )
        {
            for(int x = 0; x < src_img1.cols; x++ )
            {
            if((mask.at(y,x)[0]) > 0) {
                    for(int c = 0; c < 3; c++ )
                    {
                    src_img1.at(y,x)[c]= (out.at(y,x)[c]);
                    }
            }


            }
        }
//    src_img1 += 128 * (src_img1 <= 1.0);
// for(int i = 0; i < faceLine1.size(); i++) {
// //mark the all face data
// cv::Point p1(faceLine1[i].p1.x(),faceLine[i].p1.y());
// cv::Point p2(faceLine1[i].p2.x(),faceLine[i].p2.y());
// cv::line(src_img,p1,p2,cv::Scalar(0,255,0),1,cv::LINE_8);
// }
//
// for(int i = 0; i < faceLine2.size(); i++) {
// //mark the all face data
// cv::Point p1(faceLine2[i].p1.x(),faceLine[i].p1.y());
// cv::Point p2(faceLine2[i].p2.x(),faceLine[i].p2.y());
// cv::line(src_img1,p1,p2,cv::Scalar(0,255,0),1,cv::LINE_8);
// }
//Size dsize = Size(src_img1.cols,src_img1.rows);
//Mat image2 = Mat(dsize,wrap_dst.type());
//LOGD("img1 w:%d h:%d",src_img1.cols,src_img1.rows);
//resize(wrap_dst, image2,dsize);
//LOGD("dst w:%d h:%d",image2.cols,image2.rows);
imwrite("/data/data/com.example.faceswapdemo/files/data.jpg",src_img1);
env->ReleaseIntArrayElements(face1, cbuf1, 0);
env->ReleaseIntArrayElements(face2, cbuf2, 0);
return JNI_TRUE;
}






jboolean studytrain()
{
dlib::deserialize("/data/data/com.example.faceswapdemo/files/shape_predictor_68_face_landmarks.dat") >> msp;
detector = get_frontal_face_detector();
return JNI_TRUE;
}


jboolean facecheck(JNIEnv* env, jobject thiz,
jintArray face1,jint w1,jint h1,
jintArray face2,jint w2,jint h2)
{


return facesRotate_UseData(env,thiz,face1,w1,h1,face2,w2,h2);
}


jboolean removefaceswap()
{
return JNI_TRUE;
}


void startfaceswap()
{


}


void endfaceswap()
{


}


void facemapping()
{


}


void facefusion()
{


}


void faceswap()
{


}


void setfaceswapcallback()
{


}






/*jintArray facesRotate_UsePath(JNIEnv* env, jobject thiz,jstring path1,jstring path2)
{


LOGD("in det");
frontal_face_detector detector = get_frontal_face_detector();
char * str1;
str1=(char*)env->GetStringUTFChars(path1,NULL);
char * str2;
str2=(char*)env->GetStringUTFChars(path2,NULL);
LOGD("load picture");
Mat src_img = imread(str1,1);
Mat src_img1 = imread(str2,1);
LOGD("%d-----img1-----%d",src_img.cols,src_img.rows);
LOGD("%d-----img1-----%d",src_img1.cols,src_img1.rows);
LOGD("opencv img to dlib img");
if(!src_img.data || !src_img1.data)
LOGD("data error");
dlib::cv_image img(src_img);
dlib::cv_image img1(src_img1);


LOGD("start detector");
    std::vector mRets;
    std::vector mRets1;
    double t = (double)getTickCount();
mRets = detector(img);
mRets1 = detector(img1);
t = ((double)getTickCount()-t)/getTickFrequency();
dlib::shape_predictor msp;
LOGD("detect use %lf s",t);


t = (double)getTickCount();
dlib::deserialize("/data/data/com.example.faceswapdemo/files/shape_predictor_68_face_landmarks.dat") >> msp;
t = ((double)getTickCount()-t)/getTickFrequency();
LOGD("load local mark use %lf s",t);


LOGD("get face mark from the picture");
t = (double)getTickCount();
dlib::full_object_detection shape1,shape2;
if (mRets.size() != 0) {
for (unsigned long j = 0; j < mRets.size(); ++j)
shape1 = msp(img, mRets[j]);
}
if (mRets1.size() != 0) {
for (unsigned long j = 0; j < mRets1.size(); ++j)
shape2 = msp(img1, mRets1[j]);
}
t = ((double)getTickCount()-t)/getTickFrequency();
LOGD("--get face mark use %lf s---",t);


LOGD("--use face mark to get mapping matrix---");
t = (double)getTickCount();
std::vector srcTri;
std::vector dstTri;
for(int i = 0;i < 16;i++) {
srcTri.push_back(cv::Point2f(shape1.part(i).x(),shape1.part(i).y()));
dstTri.push_back(cv::Point2f(shape2.part(i).x(),shape2.part(i).y()));
}
LOGD("--got special points---");
cv::Mat warp_mat;
warp_mat = cv::findHomography(srcTri,dstTri,0);
LOGD("%d---get change matrix-----%d",warp_mat.cols,warp_mat.rows);
uchar* p1;
for(int i = 0;i < 3;i ++) {
   p1 = warp_mat.ptr(i);
   for(int j = 0;j < 3;j ++)
    LOGD("matrix-----%d",(int)p1[j]);
    }
//the faceLine's size is 68,including all face data,such as:
//the index 0 - 16:jaw data
//the index 17 - 68:face data
//the index 17 - 21:right brow data
//the index 22 - 26:left brow data
//the index 27 - 34:nose data
//the index 36 - 41:right eye data
//the index 42 - 47:left eye data
//the index 48 - 60:mouth data


LOGD("use the mapping matrix to change the picture");
Mat wrap_dst;
cv::warpPerspective(src_img,wrap_dst,warp_mat,src_img.size());
t = ((double)getTickCount()-t)/getTickFrequency();
LOGD("change picture use %lf s",t);
int size = wrap_dst.cols * wrap_dst.rows;
jintArray result = env->NewIntArray(size);
uchar* p;
p = wrap_dst.ptr(0);
cv::imwrite("/data/data/com.example.faceswapdemo/files/face_wrap1.png", wrap_dst);
env->SetIntArrayRegion(result, 0, size, (int*)p);
env->ReleaseStringUTFChars(path1, str1);
env->ReleaseStringUTFChars(path2, str2);
return result;
}*/


void testmat(jlong addr1,jlong addr2)
{
Mat& src = *(Mat*)addr1;
Mat& src1 = *(Mat*)addr2;
// Mat src1 = Mat(*((Mat*)addr2));
cvtColor(src1,src1,CV_RGBA2RGB);
cvtColor(src,src,CV_BGR2GRAY);
}




static JNINativeMethod gMethods[] = {
    {"facecheck", "([III[III)Z", (void*)facecheck},
    {"studytrain","()Z",(void*) studytrain},
//    {"faceRotateUsePath", "(Ljava/lang/String;Ljava/lang/String;)[I", (void*)facesRotate_UsePath},
    //    {"testMat","(JJ)V",(void*) testmat},
    // {"faceRotate", "()I", (void*)facesRotate},
};


/*
* 为某一个类注册本地方法
*/
static int registerNativeMethods(JNIEnv* env
        , const char* className
        , JNINativeMethod* gMethods, int numMethods) {
    jclass clazz;
    clazz = env->FindClass(className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }


    return JNI_TRUE;
}


/*
* 为所有类注册本地方法
*/
static int registerNatives(JNIEnv* env) {
    const char* kClassName = "com/example/faceswapdemo/FaceUtils";//指定要注册的类
    return registerNativeMethods(env, kClassName, gMethods,
    sizeof(gMethods)/sizeof(gMethods[0]));
}


jint JNIEXPORT JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
    LOGE("JNI On Load");
    JNIEnv* env = NULL;
    jint result = -1;


    if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("GetEnv failed!");
        return result;
    }
    LOGE("start register!");
    if (!registerNatives(env)) {//注册
    LOGE("register failed!");
        return result;
    }
    return JNI_VERSION_1_4;
}


/*
#ifdef __cplusplus
}
#endif
*/



你可能感兴趣的:(人脸处理,opencv)