1. 程式人生 > >深度學習AI美顏系列---AI瘦身效果演算法揭祕

深度學習AI美顏系列---AI瘦身效果演算法揭祕

最近一段時間,抖音、微視、美圖紛紛推出了視訊實時瘦身的特效,可以說是火了一把!本文將給大家做個技術揭祕!

商湯基於深度學習研發了整套瘦身SDK,包括了瘦腿,瘦腰,瘦胳膊,瘦頭型等等功能,並給出了酷炫的實時瘦身視訊,驚豔到了眾人!本文將以瘦腰和瘦腿為例,給大家詳細講解一下。

瘦身從演算法角度來講,包含兩個模組:①人體輪廓特徵點檢測模組;②人體變形模組

[人體輪廓特徵點檢測模組]

人體輪廓特徵點檢測模組好比人臉特徵點檢測,需要檢測到代表人體輪廓的一些點位資訊,但是技術上比人臉特徵點更加複雜,因為人體是非剛體,比人臉的變化更多,更復雜;

目前人體輪廓特徵點檢測主要方法就是人體姿態估計;從2015年開始,陸續出現了很多人體姿態估計的論文,這裡舉例如:

DeeperCut: A Deeper, Stronger, and Faster Multi-Person Pose Estimation Model

這篇論文中所提供的深度學習模型與效果圖如下:


這裡我們不詳細介紹人體姿態估計的演算法,主要以講瘦身的流程為主;

上面的姿態點與我們想要的輪廓點還有差別,在掌握了上面的人體姿態估計演算法之後,我們可以使用人體輪廓點的樣本進行訓練,這樣就可以得到如下圖所示的人體輪廓特徵點:

圖片來自網路,為了避免侵權,做了人臉遮擋;

[人體變形模組]

人體變形模組主要是依據人體特徵點,將人體變形,也就是瘦身美型;

變形的演算法有很多,比如MLS移動最小二乘法變形演算法,IWD反距離加權變形演算法,MLSR變形演算法等等,相關連線如下:

MLSR變形演算法實現:點選開啟連結

IWD反距離加權變形演算法實現:點選開啟連結

MLS移動最小二乘變形演算法實現:點選開啟連結,程式碼連結點選開啟連結

本人MLS程式碼如下:

static void setSrcPoints(const vector<PointD> &qsrc, vector<PointD> &newDotL, int* nPoint) {
    *nPoint = qsrc.size();
    newDotL.clear();
    newDotL.reserve(*nPoint);
    for (size_t i = 0; i < qsrc.size(); i++) 
        newDotL.push_back(qsrc[i]);
}

static void setDstPoints(const vector<PointD> &qdst,vector<PointD> &oldDotL, int* nPoint) {
    *nPoint = qdst.size();
    oldDotL.clear();
    oldDotL.reserve(*nPoint);

    for (size_t i = 0; i < qdst.size(); i++) oldDotL.push_back(qdst[i]);
}
static double bilinear_interp(double x, double y, double v11, double v12,
                              double v21, double v22) {
    return (v11 * (1 - y) + v12 * y) * (1 - x) + (v21 * (1 - y) + v22 * y) * x;
}

static double calcArea(const vector<PointD> &V) {
    PointD lt, rb;
    lt.x = lt.y = 1e10;
    rb.x = rb.y = -1e10;
    for (vector<PointD >::const_iterator i = V.begin(); i != V.end();
         i++) {
        if (i->x < lt.x) lt.x = i->x;
        if (i->x > rb.x) rb.x = i->x;
        if (i->y < lt.y) lt.y = i->y;
        if (i->y > rb.y) rb.y = i->y;
    }
    return (rb.x - lt.x) * (rb.y - lt.y);
}
static void calcDelta_rigid(int srcW, int srcH, int tarW, int tarH, double alpha, int gridSize, int nPoint, int preScale, double *rDx, double *rDy, vector<PointD> &oldDotL, vector<PointD> &newDotL)
{
    int i, j, k;
    PointD swq, qstar, newP, tmpP;
    double sw;

    double ratio;

    if (preScale) {
        ratio = sqrt(calcArea(newDotL) / calcArea(oldDotL));
        for (i = 0; i < nPoint; i++) {
			newDotL[i].x *= 1 / ratio;
			newDotL[i].y *= 1 / ratio;
		}
    }
    double *w = new double[nPoint];

    if (nPoint < 2) {
        //rDx.setTo(0);
        //rDy.setTo(0);
        return;
    }
    PointD swp, pstar, curV, curVJ, Pi, PiJ, Qi;
    double miu_r;

    for (i = 0;; i += gridSize) {
        if (i >= tarW && i < tarW + gridSize - 1)
            i = tarW - 1;
        else if (i >= tarW)
            break;
        for (j = 0;; j += gridSize) {
            if (j >= tarH && j < tarH + gridSize - 1)
                j = tarH - 1;
            else if (j >= tarH)
                break;
            sw = 0;
            swp.x = swp.y = 0;
            swq.x = swq.y = 0;
            newP.x = newP.y = 0;
            curV.x = i;
            curV.y = j;
            for (k = 0; k < nPoint; k++) {
                if ((i == oldDotL[k].x) && j == oldDotL[k].y) break;
                if (alpha == 1)
                    w[k] = 1 / ((i - oldDotL[k].x) * (i - oldDotL[k].x) +
                                (j - oldDotL[k].y) * (j - oldDotL[k].y));
                else
                    w[k] = pow((i - oldDotL[k].x) * (i - oldDotL[k].x) +
                                   (j - oldDotL[k].y) * (j - oldDotL[k].y),
                               -alpha);
                sw = sw + w[k];
                swp.x = swp.x + w[k] * oldDotL[k].x;
				swp.y = swp.y + w[k] * oldDotL[k].y;
                swq.x = swq.x + w[k] * newDotL[k].x;
                swq.y = swq.y + w[k] * newDotL[k].y;
            }
            if (k == nPoint) {
				pstar.x = (1 / sw) * swp.x;
				pstar.y = (1 / sw) * swp.y;
                qstar.x = 1 / sw * swq.x;
				qstar.y = 1 / sw * swq.y;
                // Calc miu_r
                double s1 = 0, s2 = 0;
                for (k = 0; k < nPoint; k++) {
                    if (i == oldDotL[k].x && j == oldDotL[k].y) continue;
					Pi.x = oldDotL[k].x - pstar.x;
					Pi.y = oldDotL[k].y - pstar.y;
                    PiJ.x = -Pi.y, PiJ.y = Pi.x;
					Qi.x = newDotL[k].x - qstar.x;
					Qi.y = newDotL[k].y - qstar.y;
					s1 += w[k] * (Qi.x*Pi.x+Qi.y*Pi.y);
                    s2 += w[k] * (Qi.x*PiJ.x+Qi.y*PiJ.y);
                }
                miu_r = sqrt(s1 * s1 + s2 * s2);
				curV.x -= pstar.x;
				curV.y -= pstar.y;

                curVJ.x = -curV.y, curVJ.y = curV.x;

                for (k = 0; k < nPoint; k++) {
                    if (i == oldDotL[k].x && j == oldDotL[k].y) continue;
					 Pi.x = oldDotL[k].x - pstar.x;
					  Pi.y = oldDotL[k].y - pstar.y;
                    PiJ.x = -Pi.y, PiJ.y = Pi.x;
					 tmpP.x = (Pi.x*curV.x+Pi.y*curV.y)* newDotL[k].x -
                             (PiJ.x*curV.x+PiJ.y*curV.y)* newDotL[k].y;
                    tmpP.y = -(Pi.x*curVJ.x+Pi.y*curVJ.y) * newDotL[k].x +
                             (PiJ.x*curVJ.x+PiJ.y*curVJ.y) * newDotL[k].y;
                    tmpP.x *= w[k] / miu_r;
					tmpP.y *= w[k] / miu_r;
                    newP.x += tmpP.x;
					newP.y += tmpP.y;
                }
				newP.x += qstar.x;
				newP.y += qstar.y;
            } else {
                newP = newDotL[k];
            }

            if (preScale) {
                rDx[j * tarW + i] = newP.x * ratio - i;
                rDy[j * tarW + i] = newP.y * ratio - j;
            } else {
				rDx[j * tarW + i] = newP.x - i;
				rDy[j * tarW + i] = newP.y - j;
            }
        }
    }
    delete[] w;

    if (preScale!=0) {
        for (i = 0; i < nPoint; i++){
			newDotL[i].x *= ratio;
			newDotL[i].y *= ratio;
		}
    }
}
static void calcDelta_Similarity(int srcW, int srcH, int tarW, int tarH, double alpha, int gridSize, int nPoint, int preScale, double *rDx, double *rDy, vector<PointD> &oldDotL, vector<PointD> &newDotL)
{
    int i, j, k;

    PointD swq, qstar, newP, tmpP;
    double sw;
	
    double ratio;

    if (preScale) {
        ratio = sqrt(calcArea(newDotL) / calcArea(oldDotL));
        for (i = 0; i < nPoint; i++) {
			newDotL[i].x *= 1 / ratio;
			newDotL[i].y *= 1 / ratio;
		}
    }
    double *w = new double[nPoint];

    if (nPoint < 2) {
        return;
    }

    PointD swp, pstar, curV, curVJ, Pi, PiJ;
    double miu_s;

    for (i = 0;; i += gridSize) {
        if (i >= tarW && i < tarW + gridSize - 1)
            i = tarW - 1;
        else if (i >= tarW)
            break;
        for (j = 0;; j += gridSize) {
            if (j >= tarH && j < tarH + gridSize - 1)
                j = tarH - 1;
            else if (j >= tarH)
                break;
            sw = 0;
            swp.x = swp.y = 0;
            swq.x = swq.y = 0;
            newP.x = newP.y = 0;
            curV.x = i;
            curV.y = j;
            for (k = 0; k < nPoint; k++) {
                if ((i == oldDotL[k].x) && j == oldDotL[k].y) break;
                w[k] = 1 / ((i - oldDotL[k].x) * (i - oldDotL[k].x) +
                            (j - oldDotL[k].y) * (j - oldDotL[k].y));
                sw = sw + w[k];
                swp.x = swp.x + w[k] * oldDotL[k].x;
				swp.y = swp.y + w[k] * oldDotL[k].y;
                swq.x = swq.x + w[k] * newDotL[k].x;
				swq.y = swq.y + w[k] * newDotL[k].y;
            }
            if (k == nPoint) {
                pstar.x = (1 / sw) * swp.x;
				pstar.y = (1 / sw) * swp.y;
                qstar.x = 1 / sw * swq.x;
				qstar.y = 1 / sw * swq.y;
                // Calc miu_s
                miu_s = 0;
                for (k = 0; k < nPoint; k++) {
                    if (i == oldDotL[k].x && j == oldDotL[k].y) continue;

                    Pi.x = oldDotL[k].x - pstar.x;
					Pi.y = oldDotL[k].y - pstar.y;
                    miu_s += w[k] * (Pi.x*Pi.x+Pi.y*Pi.y);
                }

                curV.x -= pstar.x;
				curV.y -= pstar.y;
                curVJ.x = -curV.y, curVJ.y = curV.x;

                for (k = 0; k < nPoint; k++) {
                    if (i == oldDotL[k].x && j == oldDotL[k].y) continue;

                    Pi.x = oldDotL[k].x - pstar.x;
					Pi.y = oldDotL[k].y - pstar.y;
                    PiJ.x = -Pi.y, PiJ.y = Pi.x;

					tmpP.x = (Pi.x*curV.x+Pi.y*curV.y) * newDotL[k].x -
                             (PiJ.x*curV.x+PiJ.y*curV.y) * newDotL[k].y;
					tmpP.y = -(Pi.x*curVJ.x+Pi.y*curVJ.y) * newDotL[k].x +
                             (PiJ.x*curVJ.x+PiJ.y*curVJ.y) * newDotL[k].y;
                    tmpP.x *= w[k] / miu_s;
					tmpP.y *= w[k] / miu_s;
                    newP.x += tmpP.x;
					newP.y += tmpP.y;
                }
                newP.x += qstar.x;
				newP.y += qstar.y;
            } else {
                newP = newDotL[k];
            }

            rDx[j * tarW + i] = newP.x - i;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
            rDy[j * tarW + i] = newP.y - j;
        }
    }

    delete[] w;
	    if (preScale!=0) {
        for (i = 0; i < nPoint; i++){
			newDotL[i].x *= ratio;
			newDotL[i].y *= ratio;
		}
    }
}
static int GetNewImg(unsigned char* oriImg, int width, int height, int stride, unsigned char* tarImg, int tarW, int tarH, int tarStride, int gridSize, double* rDx, double* rDy, double transRatio)
{
    int i, j;
    double di, dj;
    double nx, ny;
    int nxi, nyi, nxi1, nyi1;
    double deltaX, deltaY;
    double w, h;
    int ni, nj;
	int pos, posa, posb, posc, posd;
    for (i = 0; i < tarH; i += gridSize)
        for (j = 0; j < tarW; j += gridSize) {
            ni = i + gridSize, nj = j + gridSize;
            w = h = gridSize;
            if (ni >= tarH) ni = tarH - 1, h = ni - i + 1;
            if (nj >= tarW) nj = tarW - 1, w = nj - j + 1;
            for (di = 0; di < h; di++)
                for (dj = 0; dj < w; dj++) {
                    deltaX =
                        bilinear_interp(di / h, dj / w, rDx[i * tarW + j], rDx[i * tarW + nj],
                                        rDx[ni * tarW + j], rDx[ni * tarW + nj]);
                    deltaY =
                        bilinear_interp(di / h, dj / w, rDy[i * tarW + j], rDy[i * tarW + nj],
                                        rDy[ni * tarW + j], rDy[ni * tarW + nj]);
                    nx = j + dj + deltaX * transRatio;
                    ny = i + di + deltaY * transRatio;
                    if (nx > width - 1) nx = width - 1;
                    if (ny > height - 1) ny = height - 1;
                    if (nx < 0) nx = 0;
                    if (ny < 0) ny = 0;
                    nxi = int(nx);
                    nyi = int(ny);
                    nxi1 = ceil(nx);
                    nyi1 = ceil(ny);
					pos = (int)(i + di) * tarStride + ((int)(j + dj) << 2);
					posa = nyi * stride + (nxi << 2);
					posb = nyi * stride + (nxi1 << 2);
					posc = nyi1 * stride + (nxi << 2);
					posd = nyi1 * stride + (nxi1 << 2);
					tarImg[pos]     = (unsigned char)bilinear_interp(ny - nyi, nx - nxi, oriImg[posa],    oriImg[posb],   oriImg[posc],   oriImg[posd]);
					tarImg[pos + 1] = (unsigned char)bilinear_interp(ny - nyi, nx - nxi, oriImg[posa + 1],oriImg[posb + 1], oriImg[posc + 1], oriImg[posd + 1]);
					tarImg[pos + 2] = (unsigned char)bilinear_interp(ny - nyi, nx - nxi, oriImg[posa + 2],oriImg[posb + 2], oriImg[posc + 2], oriImg[posd + 2]);
					tarImg[pos + 3] = (unsigned char)bilinear_interp(ny - nyi, nx - nxi, oriImg[posa + 3],oriImg[posb + 3], oriImg[posc + 3], oriImg[posd + 3]);
                }
        }
		return 0;
};

static void MLSImageWrapping(unsigned char* oriImg,int width, int height, int stride,const vector<PointD > &qsrc, const vector<PointD > &qdst, unsigned char* tarImg, int outW, int outH, int outStride, double transRatio, int preScale, int gridSize, int method)
{
	int srcW = width;
	int srcH = height;
	int tarW = outW;
    int tarH = outH;
    double alpha = 1;
	int nPoint;
	int len = tarH * tarW;
	vector<PointD> oldDotL, newDotL;
	double *rDx = NULL,*rDy = NULL;
    setSrcPoints(qsrc,newDotL,&nPoint);
    setDstPoints(qdst,oldDotL,&nPoint);
    rDx = (double*)malloc(sizeof(double) * len);
    rDy = (double*)malloc(sizeof(double) * len);
    memset(rDx, 0, sizeof(double) * len);
    memset(rDy, 0, sizeof(double) * len);
	if(method!=0)
		calcDelta_Similarity(srcW, srcH, tarW, tarH, alpha, gridSize, nPoint, preScale, rDx, rDy, oldDotL, newDotL);
	else
	    calcDelta_rigid(srcW, srcH, tarW, tarH, alpha, gridSize, nPoint, preScale, rDx, rDy, oldDotL, newDotL);
	GetNewImg(oriImg, srcW, srcH, stride, tarImg, tarW, tarH, outStride, gridSize, rDx, rDy, transRatio);
	if(rDx != NULL)
		free(rDx);
	if(rDy != NULL)
		free(rDy);
};
int f_TMLSImagewarpping(unsigned char* srcData, int width ,int height, int stride, unsigned char* dstData, int outW, int outH, int outStride, int srcPoint[], int dragPoint[], int pointNum, double intensity, int preScale, int gridSize, int method)
{
	int res = 0;
    vector<PointD> qDst;
    vector<PointD> qSrc;
	PointD point = {0};
	int len = 0;
	for(int i = 0; i < pointNum; i++)
	{
		len = (i << 1);
		point.x = srcPoint[len];
		point.y = srcPoint[len + 1];
		qSrc.push_back(point);
		point.x = dragPoint[len];
		point.y = dragPoint[len + 1];
		qDst.push_back(point);
	}
	MLSImageWrapping(srcData, width, height, stride, qSrc, qDst, dstData, outW, outH, outStride, intensity, preScale,gridSize, method);
	return res;
};

本人這裡使用MLS變形來實現瘦身效果:

1,根據人體姿態估計檢測得到原圖人體的特徵點;

2,計算得到瘦腿之後的人體特徵點;

3,計算得到瘦腰之後的人體特徵點;

1-3如下圖所示:


4,使用MLS,將原圖特徵點分別變換到瘦腿特徵點和瘦腰特徵點,進而得到兩個相應的效果圖如下(這裡給出四個效果):





至此,瘦身效果的演算法流程講完,至於實時處理,那是程式碼優化和演算法優化的事情,只要速度夠快,一切都不是問題!

上面只是以瘦腿和瘦腰為例,實際上還可以瘦胳膊,豐胸等等,來真正的達到美顏瘦身!

本人的DEMO介面如下:


本人QQ1358009172

最後給出本人的DEMO:點選開啟連結