一.瘦脸算法实现包括三部分: 1.人脸关键点识别。2.计算瘦脸区域点之间的映射。3.双线性插值实现对应坐标颜色的映射。
1.人脸关键点识别(暂时省略):
2.坐标点的映射:
void LocalTranslationWarp_Eye(Mat &img, Mat &dst, int warpX, int warpY, int endX, int endY, float radius)
{
//平移距离
float ddradius = radius * radius;
//计算|m-c|^2
size_t mc = (endX - warpX)*(endX - warpX) + (endY - warpY)*(endY - warpY); //计算两个点距离的平方
//计算 图像的高 宽 通道数量
int height = img.rows;
int width = img.cols;
int chan = img.channels();
auto Abs = [&](float f) { //取绝对值
return f > 0 ? f : -f;
};
for (int j = 0; j < height; j++)
{
for (int i = 0; i < width; i++)
{
// # 计算该点是否在形变圆的范围之内
//# 优化,第一步,直接判断是会在(startX, startY)的矩阵框中
if ((Abs(i - warpX) > radius) && (Abs(j - warpY) > radius))
continue;
float distance = (i - warpX)*(i - warpX) + (j - warpY)*(j - warpY); //取当前点到变换起始点的距离
if (distance < ddradius) //如果处在变换圆的范围内
{
//float rnorm = sqrt(distance) / radius; //当前点与变形圆半径的比值
//float ratio = 1 - (rnorm - 1)*(rnorm - 1)*0.5;
映射原位置
//float UX = warpX + ratio * (i - warpX);
//float UY = warpY + ratio * (j - warpY);
float radio = (ddradius - distance) / (ddradius - distance + mc);
radio = radio * radio;
float UX = i - radio * (endX - warpX);
float UY = j - radio * (endY - warpY);
if (UX < 0 || UY < 0) {
std::cout << "出错了" << std::endl;
}
//根据双线性插值得到UX UY的值
BilinearInsert(img, dst, UX, UY, i, j);
}
}
}
}
3.双线性插值:
void BilinearInsert(Mat &src, Mat &dst, float ux, float uy, int i, int j)
{
auto Abs = [&](float f) {
return f > 0 ? f : -f;
};
int c = src.channels();
if (c == 3)
{
//存储图像得浮点坐标
cv::Point3f uv;
cv::Point3f f1;
cv::Point3f f2;
//取整数
int iu = (int)ux;
int iv = (int)uy;
uv.x = iu + 1;
uv.y = iv + 1;
//上面行插值
f1.x = ((uchar*)(src.data + src.step*iv))[iu * 3] * (1 - Abs(ux - iu)) + \
((uchar*)(src.data + src.step*iv))[(iu + 1) * 3] * (ux - iu);
f1.y = ((uchar*)(src.data + src.step*iv))[iu * 3 + 1] * (1 - Abs(ux - iu)) + \
((uchar*)(src.data + src.step*iv))[(iu + 1) * 3 + 1] * (ux - iu);
f1.z = ((uchar*)(src.data + src.step*iv))[iu * 3 + 2] * (1 - Abs(ux - iu)) + \
((uchar*)(src.data + src.step*iv))[(iu + 1) * 3 + 2] * (ux - iu);
//下面行插值
f2.x = ((uchar*)(src.data + src.step*(iv + 1)))[iu * 3] * (1 - Abs(ux - iu)) + \
((uchar*)(src.data + src.step*(iv + 1)))[(iu + 1) * 3] * (ux - iu);
f2.y = ((uchar*)(src.data + src.step*(iv + 1)))[iu * 3 + 1] * (1 - Abs(ux - iu)) + \
((uchar*)(src.data + src.step*(iv + 1)))[(iu + 1) * 3 + 1] * (ux - iu);
f2.z = ((uchar*)(src.data + src.step*(iv + 1)))[iu * 3 + 2] * (1 - Abs(ux - iu)) + \
((uchar*)(src.data + src.step*(iv + 1)))[(iu + 1) * 3 + 2] * (ux - iu);
//int tempNum = f1.x*(1 - Abs(uv.y - iv)) + f2.x*(Abs(uv.y - iv));
((uchar*)(dst.data + dst.step*j))[i * 3] = f1.x*(1 - Abs(uy - iv)) + f2.x*(Abs(uy - iv)); //三个通道进行赋值
((uchar*)(dst.data + dst.step*j))[i * 3 + 1] = f1.y*(1 - Abs(uy - iv)) + f2.y*(Abs(uy - iv));
((uchar*)(dst.data + dst.step*j))[i * 3 + 2] = f1.z*(1 - Abs(uy - iv)) + f2.z*(Abs(uy - iv));
}
}