OpenGL系列之十七:实现人脸贴纸

目录

效果展示

相关文章

OpenGL系列之一:OpenGL第一个程序
OpenGL系列之二:绘制三角形
OpenGL系列之三:三角形顶点增加颜色
OpenGL系列之四:绘制四边形
OpenGL系列之五:绘制点和线
OpenGL系列之六:绘制立方体
OpenGL系列之七:纹理贴图
OpenGL系列之八:立方体纹理贴图
OpenGL系列之九:glsl着色器语言
OpenGL系列之十:VAO、VBO、EBO的应用
OpenGL系列之十一:Shader图片转场切换动画
OpenGL系列之十二:Shader燃烧动画
OpenGL系列之十三:实现Shader绚丽动画
OpenGL系列之十四:实现相机抖音特效
OpenGL系列之十五:实现美颜相机
OpenGL系列之十六:实现大眼特效

实现步骤

1.找到定位点

这里我们以这篇文章(OpenGL系列之十六:实现大眼特效)的代码为基础,效果展示的是在鼻子上的贴纸,因此我们找到鼻子的定位点,由于fastdeploy人脸检测返回的定位点为5个点,因此我们鼻子的点为第3个点

 //贴图(鼻子)
if(j == 2){
   addNoseImage(imageSrc,(x2-x1),Point(p1x,p1y),u_LeftEyeCenterPos,u_RightEyeCenterPos);
}
2.利用opencv贴图

这里我们使用copyTo函数将贴图增加到,相机图像上,由于opencv读取背景透明的图像显示时背景会变白,因此这里我们通过掩模来实现去掉背景,同时也利用了addWeighted函数可以调整贴图的透明度

//图片混合
bool MixImage(Mat& srcImage, Mat mixImage, Point startPoint)
{
    //检查图片数据
    if (!srcImage.data || !mixImage.data)
    {
        cout << "输入图片 数据错误!" << endl ;
        return false;
    }
    //检查行列是否越界
    int addCols = startPoint.x + mixImage.cols > srcImage.cols ? 0 : mixImage.cols;
    int addRows = startPoint.y + mixImage.rows > srcImage.rows ? 0 : mixImage.rows;
    if (addCols ==0 || addRows ==0 || startPoint.x <=0 || startPoint.y <=0)
    {
        cout << "添加图片超出" << endl;
        return false;
    }

    //ROI 混合区域
    Mat roiImage = srcImage(Rect(startPoint.x, startPoint.y, addCols, addRows));

    Mat mask;
    cvtColor(mixImage,mask,COLOR_BGR2GRAY);
    threshold(mask, mask, 2,255,THRESH_BINARY);
//    mask = 255 - mask; //掩模反色

    //图片类型一致
    if (srcImage.type() == mixImage.type())
    {
        //调整贴图透明度
        cv::Mat i1(cv::Size(mixImage.cols,mixImage.rows),CV_8UC3);
        cv::addWeighted(mixImage,1.0,roiImage,0.0,0.0,i1);

        i1.copyTo(roiImage, mask);
        return true;
    }

    Mat maskImage;
    //原始图片:灰度  贴图:彩色
    if (srcImage.type() == CV_8U && mixImage.type() == CV_8UC3)
    {
        cvtColor(mixImage, maskImage, COLOR_BGR2GRAY);
        maskImage.copyTo(roiImage, maskImage);
        return true;
    }

    //原始图片:彩色  贴图:灰色
    if (srcImage.type() == CV_8UC3 && mixImage.type() == CV_8U)
    {
        cvtColor(mixImage, maskImage, COLOR_GRAY2BGR);
        maskImage.copyTo(roiImage, maskImage);
        return true;
    }

    return false;
}
3.贴图的缩放

由于我们的相机可能靠近人脸可能远离人脸,因此我们的贴图也要随着靠近和原理来相应的调整大小,这里我们以人脸的宽度来作为衡量的标准去进行缩放,缩放我们使用了opencv的resize函数

Mat noseImage = imread("/storage/emulated/0/Android/data/com.itfitness.openglcamera/files/Download/miao.png");

float scale = faceWidth / noseImage.cols;

int scaleLogoImageHeight = (int)(noseImage.rows * scale);

Mat scaleLogoImage((int)faceWidth,scaleLogoImageHeight,CV_8UC3);

resize(noseImage,scaleLogoImage,Size(faceWidth,scaleLogoImageHeight));
4.贴图的角度旋转

由于手机可能会倾斜之类的,会导致贴图不能跟着人脸旋转,所以我们需要根据两个人眼的位置来确定旋转的角度,从而让贴图可以对齐人脸,计算角度的代码如下

float get_angle(float x1, float y1, float x2, float y2)
{
    float x = abs(x1 - x2);
    float y = abs(y1 - y2);
    float z = sqrt(x * x + y * y);
    int angle = round((float) (asin(y / z) / 3.14 * 180));
    return angle;
}

旋转角度,我们配合getRotationMatrix2D和warpAffine两个函数,代码如下

//贴图旋转
    int angle = get_angle(u_LeftEyeCenterPos.x,u_LeftEyeCenterPos.y,u_RightEyeCenterPos.x,u_RightEyeCenterPos.y);

    //当右眼的y坐标大于左眼的y坐标的时候,反着转
    if(u_RightEyeCenterPos.y > u_LeftEyeCenterPos.y){
        angle = - angle;
    }

    Mat rotationMatrix = getRotationMatrix2D(Point(scaleLogoImage.cols / 2,scaleLogoImage.rows / 2), angle, 1);

    //进行旋转变换
    warpAffine(scaleLogoImage,scaleLogoImage, rotationMatrix, Size(scaleLogoImage.cols, scaleLogoImage.rows));

由于旋转角度时,贴图的宽高会发生变化,并且背景默认会变成黑色,因此,贴图我都做成了背景黑色的正方形了,如下所示



整体的添加贴图的那部分代码如下所示

//图片混合
bool MixImage(Mat& srcImage, Mat mixImage, Point startPoint)
{
    //检查图片数据
    if (!srcImage.data || !mixImage.data)
    {
        cout << "输入图片 数据错误!" << endl ;
        return false;
    }
    //检查行列是否越界
    int addCols = startPoint.x + mixImage.cols > srcImage.cols ? 0 : mixImage.cols;
    int addRows = startPoint.y + mixImage.rows > srcImage.rows ? 0 : mixImage.rows;
    if (addCols ==0 || addRows ==0 || startPoint.x <=0 || startPoint.y <=0)
    {
        cout << "添加图片超出" << endl;
        return false;
    }

    //ROI 混合区域
    Mat roiImage = srcImage(Rect(startPoint.x, startPoint.y, addCols, addRows));

    Mat mask;
    cvtColor(mixImage,mask,COLOR_BGR2GRAY);
    threshold(mask, mask, 2,255,THRESH_BINARY);
//    mask = 255 - mask; //掩模反色

    //图片类型一致
    if (srcImage.type() == mixImage.type())
    {
        //调整贴图透明度
        cv::Mat i1(cv::Size(mixImage.cols,mixImage.rows),CV_8UC3);
        cv::addWeighted(mixImage,1.0,roiImage,0.0,0.0,i1);

        i1.copyTo(roiImage, mask);
        return true;
    }

    Mat maskImage;
    //原始图片:灰度  贴图:彩色
    if (srcImage.type() == CV_8U && mixImage.type() == CV_8UC3)
    {
        cvtColor(mixImage, maskImage, COLOR_BGR2GRAY);
        maskImage.copyTo(roiImage, maskImage);
        return true;
    }

    //原始图片:彩色  贴图:灰色
    if (srcImage.type() == CV_8UC3 && mixImage.type() == CV_8U)
    {
        cvtColor(mixImage, maskImage, COLOR_GRAY2BGR);
        maskImage.copyTo(roiImage, maskImage);
        return true;
    }

    return false;
}

float get_angle(float x1, float y1, float x2, float y2)
{
    float x = abs(x1 - x2);
    float y = abs(y1 - y2);
    float z = sqrt(x * x + y * y);
    int angle = round((float) (asin(y / z) / 3.14 * 180));
    return angle;
}

/**
 * 添加鼻子贴图
 * @param imageSrc
 * @param faceWidth
 * @param nosePoint
 * @param u_LeftEyeCenterPos
 * @param u_RightEyeCenterPos
 */
void addNoseImage(Mat &imageSrc,float faceWidth,Point nosePoint,glm::vec2 u_LeftEyeCenterPos,glm::vec2 u_RightEyeCenterPos) {
    Mat noseImage = imread("/storage/emulated/0/Android/data/com.itfitness.openglcamera/files/Download/miao.png");

    float scale = faceWidth / noseImage.cols;

    int scaleLogoImageHeight = (int)(noseImage.rows * scale);

    Mat scaleLogoImage((int)faceWidth,scaleLogoImageHeight,CV_8UC3);

    resize(noseImage,scaleLogoImage,Size(faceWidth,scaleLogoImageHeight));

    //贴图旋转
    int angle = get_angle(u_LeftEyeCenterPos.x,u_LeftEyeCenterPos.y,u_RightEyeCenterPos.x,u_RightEyeCenterPos.y);

    //当右眼的y坐标大于左眼的y坐标的时候,反着转
    if(u_RightEyeCenterPos.y > u_LeftEyeCenterPos.y){
        angle = - angle;
    }

    Mat rotationMatrix = getRotationMatrix2D(Point(scaleLogoImage.cols / 2,scaleLogoImage.rows / 2), angle, 1);

    //进行旋转变换
    warpAffine(scaleLogoImage,scaleLogoImage, rotationMatrix, Size(scaleLogoImage.cols, scaleLogoImage.rows));


    //2、实现贴图
    MixImage(imageSrc, scaleLogoImage, Point(nosePoint.x - scaleLogoImage.cols / 2, nosePoint.y - scaleLogoImage.rows / 2));
    noseImage.release();
    scaleLogoImage.release();
    rotationMatrix.release();
}

案例源码

https://gitee.com/itfitness/opengl-camera-opencv-addpicture

你可能感兴趣的:(OpenGL系列之十七:实现人脸贴纸)