面部变形+基于特征的图像变形+field warping-Siggraph 1992

    xiaoxiao2021-03-25  50

    参考资料

    国立台湾大学-Image Morphing-Homework图像处理(十)基于特征线的图像变形-Siggraph 1992Face Morphing on Animation Producing-ntu-csie2D Image Morphing Algorithms

    基于特征的图像变形算法,来源于1992年的一篇论文。它是分别在两幅图像中分别选择相应的特征对(特征线段对),然后根据原始像素与每个特征的相对位置,映射原始像素,再取加权平均得出最终的像素值。

    如下图是特征线段对为2个的情况:

    特征线段可以是交互产生的,也可以是自动检测的。

    图像变形采用的是双线性插值的反向映射算法。

    具体的算法推演,博客开头列出的资料1和2已经讲得很明白了,而且给出了算法实现的伪代码,以及基于OpenCV1的C代码。

    这篇博客所做的工作是将上述C代码,完全翻译为C++代码。模块解耦清晰,没有一大堆烦人的全局变量,易于理解。

    改写后的代码基于OpenCV2。运行程序的使用方法与原程序同,见参考资料2。

    废话不多说了,贴代码。

    代码实现

    主函数文件 Main.cpp

    #include<string.h> #include"Morpher.h" int main() { Morpher morpher; morpher.main(); return EXIT_SUCCESS; }

    头文件 Morpher.h,包含几个类的定义。

    #include <stdio.h> #include <stdlib.h> #include <iostream> #include <vector> #include <string> #include <math.h> #include <opencv2/opencv.hpp> #include <opencv2/highgui/highgui.hpp> using namespace std; using namespace cv; class Line { public: //起点、终点、中点、线长、角度 Point2f P=Point2f(0.f,0.f); Point2f Q = Point2f(0.f, 0.f); Point2f M = Point2f(0.f, 0.f); double len{0}; float degree{0}; //参数a,b,p double a=1.0; double b=2.0; double p=1.0; public: Line() :P(Point2f(0.f, 0.f)), Q(Point2f(0.f, 0.f)){}; //由P,Q坐标得到中点,线长,角度信息 void PQtoMLD(); void MLDtoPQ(); void show(); double Getu(Point2f X); double Getv(Point2f X); Point2f Get_Point(double u, double v); double Get_Weight(Point2f X); }; class LinePair { public: Line leftLine; Line rightLine; vector<Line> warpLine; public: //生成中间过渡的线段集合 void genWarpLine(int frame_count); }; class Image { public: //过渡帧序号 int frame_index; Mat left_image; Mat right_image; Mat new_image; public: Image(int frame_index,string leftImageName,string rightImageName); //双线性插值 Vec3b bilinear(Mat image, double X, double Y); //生成过渡图像帧 void Warp(int frame_count, string new_image_name, vector<LinePair> pairs); }; class Morpher { public: vector<LinePair> pairs; LinePair curLinePair; //计数,交互式画线要用到 int counter = 0; //设置动画的过渡帧,比如1为50%,3为25%,50%,75% int frame_count = 1; Mat leftImage; Mat rightImage; Mat leftImageTmp; Mat rightImageTmp; //显示相关。画线的颜色、线宽、偏移 Scalar color = Scalar(0, 255, 0); int thickness = 2; int shift = 0; //按键值,用于控制 int key; string first_image_name; string second_image_name; string new_image_name; public: void show_pairs(); //捕获左图像上的鼠标动作 static void on_mousel(int event, int x, int y, int flag, void* param); //捕获右图像上的鼠标动作 static void on_mouser(int event, int x, int y, int flag, void* param); //运行morph void runWarp(); void main(); };

    实现文件 Morpher.cpp,类函数的具体实现。

    #include"Morpher.h" void Line::PQtoMLD() { M.x = (P.x + Q.x) / 2; M.y = (P.y + Q.y) / 2; float tmpx = Q.x - P.x; float tmpy = Q.y - P.y; len = sqrt(tmpx*tmpx + tmpy*tmpy); degree = atan2(tmpy, tmpx); return; } void Line::MLDtoPQ() { float tmpx = 0.5*len*cos(degree); float tmpy = 0.5*len*sin(degree); Point2f tmpP; Point2f tmpQ; tmpP.x = M.x - tmpx; tmpP.y = M.y - tmpy; tmpQ.x = M.x + tmpx; tmpQ.y = M.y + tmpy; P = tmpP; Q = tmpQ; return; } void Line::show() { printf("P(%lf,%lf) Q(%lf,%lf) M(%lf,%lf)\n \tlen=%lf degree=%f\n", P.x, P.y, Q.x, Q.y, M.x, M.y, len, degree); return; } double Line::Getu(Point2f X) { double X_P_x = X.x - P.x; double X_P_y = X.y - P.y; double Q_P_x = Q.x - P.x; double Q_P_y = Q.y - P.y; double u = ((X_P_x * Q_P_x) + (X_P_y * Q_P_y)) / (len * len); return u; } double Line::Getv(Point2f X) { double X_P_x = X.x - P.x; double X_P_y = X.y - P.y; double Q_P_x = Q.x - P.x; double Q_P_y = Q.y - P.y; double Perp_Q_P_x = Q_P_y; double Perp_Q_P_y = -Q_P_x; double v = ((X_P_x * Perp_Q_P_x) + (X_P_y * Perp_Q_P_y)) / len; return v; } Point2f Line::Get_Point(double u, double v) { double Q_P_x = Q.x - P.x; double Q_P_y = Q.y - P.y; double Perp_Q_P_x = Q_P_y; double Perp_Q_P_y = -Q_P_x; double Point_x = P.x + u * (Q.x - P.x) + ((v * Perp_Q_P_x) / len); double Point_y = P.y + u * (Q.y - P.y) + ((v * Perp_Q_P_y) / len); Point2f X; X.x = Point_x; X.y = Point_y; return X; } double Line::Get_Weight(Point2f X) { double d = 0.0; double u = Getu(X); if (u > 1.0) d = sqrt((X.x - Q.x) * (X.x - Q.x) + (X.y - Q.y) * (X.y - Q.y)); else if (u < 0) d = sqrt((X.x - P.x) * (X.x - P.x) + (X.y - P.y) * (X.y - P.y)); else d = abs(Getv(X)); double weight = pow(pow(len, p) / (a + d), b); return weight; } void LinePair::genWarpLine(int frame_count) { while (leftLine.degree - rightLine.degree>3.14159265) rightLine.degree = rightLine.degree + 3.14159265; while (rightLine.degree - leftLine.degree>3.14159265) leftLine.degree = leftLine.degree + 3.14159265; for (int i = 0; i<frame_count; i++) { double ratio = (double)(i + 1) / (frame_count + 1); Line curLine; curLine.M.x = (1 - ratio)*leftLine.M.x + ratio*rightLine.M.x; curLine.M.y = (1 - ratio)*leftLine.M.y + ratio*rightLine.M.y; curLine.len = (1 - ratio)*leftLine.len + ratio*rightLine.len; curLine.degree = (1 - ratio)*leftLine.degree + ratio*rightLine.degree; curLine.MLDtoPQ(); warpLine.push_back(curLine); } return; } Image::Image(int index, string leftImageName, string rightImageName) { frame_index = index; left_image = imread(leftImageName); right_image = imread(rightImageName); Size ImageSize = Size(left_image.cols, left_image.rows); new_image.create(ImageSize, CV_8UC3); } Vec3b Image::bilinear(Mat image, double X, double Y) { int width = image.cols; int height = image.rows; int x_floor = (int)X; int y_floor = (int)Y; int x_ceil = x_floor + 1; int y_ceil = y_floor + 1; double a = X - x_floor; double b = Y - y_floor; if (x_ceil >= width - 1) x_ceil = width - 1; if (y_ceil >= height - 1) y_ceil = height - 1; Vec3b output_scalar; Vec3b leftdown = image.at<Vec3b>(y_floor, x_floor); Vec3b lefttop = image.at<Vec3b>(y_ceil, x_floor); Vec3b rightdown = image.at<Vec3b>(y_floor, x_ceil); Vec3b righttop = image.at<Vec3b>(y_ceil, x_ceil); for (int i = 0; i < 3; i++) { output_scalar.val[i] = (1 - a)*(1 - b)*leftdown.val[i] + a*(1 - b)*rightdown.val[i] + a*b*righttop.val[i] + (1 - a)*b*lefttop.val[i]; } return output_scalar; } void Image::Warp(int frame_count, string new_image_name, vector<LinePair> pairs) { double ratio = (double)(frame_index + 1) / (frame_count + 1); Mat ori_leftImage, ori_rightImage; ori_leftImage = left_image; ori_rightImage = right_image; int width = new_image.cols; int height = new_image.rows; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { Point2f dst_point; dst_point.x = x; dst_point.y = y; double leftXSum_x = 0.0; double leftXSum_y = 0.0; double leftWeightSum = 0.0; double rightXSum_x = 0.0; double rightXSum_y = 0.0; double rightWeightSum = 0.0; for (int i = 0; i < pairs.size(); i++){ Line src_line = pairs[i].leftLine; Line dst_line = pairs[i].warpLine[frame_index]; double new_u = dst_line.Getu(dst_point); double new_v = dst_line.Getv(dst_point); Point2f src_point = src_line.Get_Point(new_u, new_v); double src_weight = dst_line.Get_Weight(dst_point); leftXSum_x = leftXSum_x + (double)src_point.x * src_weight; leftXSum_y = leftXSum_y + (double)src_point.y * src_weight; leftWeightSum = leftWeightSum + src_weight; src_line = pairs[i].rightLine; new_u = dst_line.Getu(dst_point); new_v = dst_line.Getv(dst_point); src_point = src_line.Get_Point(new_u, new_v); src_weight = dst_line.Get_Weight(dst_point); rightXSum_x = rightXSum_x + (double)src_point.x * src_weight; rightXSum_y = rightXSum_y + (double)src_point.y * src_weight; rightWeightSum = rightWeightSum + src_weight; } double left_src_x = leftXSum_x / leftWeightSum; double left_src_y = leftXSum_y / leftWeightSum; double right_src_x = rightXSum_x / rightWeightSum; double right_src_y = rightXSum_y / rightWeightSum; if (left_src_x<0) left_src_x = 0; if (left_src_y<0) left_src_y = 0; if (left_src_x >= width) left_src_x = width - 1; if (left_src_y >= height) left_src_y = height - 1; if (right_src_x<0) right_src_x = 0; if (right_src_y<0) right_src_y = 0; if (right_src_x >= width) right_src_x = width - 1; if (right_src_y >= height) right_src_y = height - 1; Vec3b left_scalar = bilinear(ori_leftImage, left_src_x, left_src_y); Vec3b right_scalar = bilinear(ori_rightImage, right_src_x, right_src_y); Vec3b new_scalar; new_scalar.val[0] = (1 - ratio)*left_scalar.val[0] + ratio*right_scalar.val[0]; new_scalar.val[1] = (1 - ratio)*left_scalar.val[1] + ratio*right_scalar.val[1]; new_scalar.val[2] = (1 - ratio)*left_scalar.val[2] + ratio*right_scalar.val[2]; new_image.at<Vec3b>(y, x) = new_scalar; } } char win_name[16]; char img_name[50]; sprintf(img_name, "%s_%d.jpg", new_image_name.c_str(), frame_index); imshow(img_name, new_image); imwrite(img_name,new_image); return; } void Morpher::runWarp() { for (int i = 0; i<frame_count; i++) { Image curImage = Image(i, first_image_name, second_image_name); printf("warping %d...\n", i); curImage.Warp(frame_count,new_image_name, pairs); } } void Morpher::show_pairs() { int len = pairs.size(); printf("pairs size=%d\n", len); for (int i = 0; i<len; i++) { printf("leftLine:"); pairs[i].leftLine.show(); printf("rightLine:"); pairs[i].rightLine.show(); printf("\n"); } } void Morpher::on_mousel(int event, int x, int y, int flag, void* param) { Morpher* pm = (Morpher*)param; int& counter=pm->counter; LinePair& curLinePair = pm->curLinePair; Mat& leftImage = pm->leftImage; Mat& leftImageTmp = pm->leftImageTmp; Scalar& color = pm->color; int& thickness = pm->thickness; int& shift = pm->shift; if (counter % 2 == 0 && pm->counter > 0) { curLinePair.warpLine.clear(); if (event == CV_EVENT_LBUTTONDOWN || event == CV_EVENT_RBUTTONDOWN){ printf("Left image( %d, %d) ", x, y); printf("The Event is : %d ", event); printf("The flags is : %d ", flag); printf("The param is : %d\n", param); curLinePair.leftLine.P.x = x; curLinePair.leftLine.P.y = y; cout << "P:" << curLinePair.leftLine.P.x << '\t' << curLinePair.leftLine.P.y << endl; } if (event == CV_EVENT_LBUTTONUP || event == CV_EVENT_RBUTTONUP){ printf("Left image( %d, %d) ", x, y); printf("The Event is : %d ", event); printf("The flags is : %d ", flag); printf("The param is : %d\n", param); curLinePair.leftLine.Q.x = x; curLinePair.leftLine.Q.y = y; curLinePair.leftLine.PQtoMLD(); cout <<"P:"<< curLinePair.leftLine.P.x <<'\t'<< curLinePair.leftLine.P.y << endl; cout << "Q:" << curLinePair.leftLine.Q.x << '\t' << curLinePair.leftLine.Q.y << endl; line(leftImage, curLinePair.leftLine.P, curLinePair.leftLine.Q, color, thickness, CV_AA, shift); leftImageTmp = leftImage.clone(); counter--; } if (flag == CV_EVENT_FLAG_LBUTTON || flag == CV_EVENT_FLAG_RBUTTON){ curLinePair.leftLine.Q.x = x; curLinePair.leftLine.Q.y = y; leftImage.release(); leftImage = leftImageTmp.clone(); line(leftImage, curLinePair.leftLine.P, curLinePair.leftLine.Q, color, thickness, CV_AA, shift); imshow("left", leftImage); } } else { //cout << "you have selected the wrong image" << endl; } } void Morpher::on_mouser(int event, int x, int y, int flag, void* param) { Morpher* pm = (Morpher*)param; int& counter = pm->counter; LinePair& curLinePair = pm->curLinePair; Mat& rightImage = pm->rightImage; Mat& rightImageTmp = pm->rightImageTmp; int& frame_count = pm->frame_count; vector<LinePair>& pairs = pm->pairs; Scalar& color = pm->color; int& thickness = pm->thickness; int& shift = pm->shift; if (counter % 2 == 1 && counter > 0) { if (event == CV_EVENT_LBUTTONDOWN || event == CV_EVENT_RBUTTONDOWN){ printf("right image( %d, %d) ", x, y); printf("The Event is : %d ", event); printf("The flags is : %d ", flag); printf("The param is : %d\n", param); curLinePair.rightLine.P.x = x; curLinePair.rightLine.P.y = y; } if (event == CV_EVENT_LBUTTONUP || event == CV_EVENT_RBUTTONUP){ printf("right image( %d, %d) ", x, y); printf("The Event is : %d ", event); printf("The flags is : %d ", flag); printf("The param is : %d\n", param); curLinePair.rightLine.Q.x = x; curLinePair.rightLine.Q.y = y; curLinePair.rightLine.PQtoMLD(); line(rightImage, curLinePair.rightLine.P, curLinePair.rightLine.Q, color, thickness, CV_AA, shift); rightImageTmp = rightImage.clone(); counter--; curLinePair.genWarpLine(frame_count); pairs.push_back(curLinePair); printf("\n"); pm->show_pairs(); counter = 0; } if (flag == CV_EVENT_FLAG_LBUTTON || flag == CV_EVENT_FLAG_RBUTTON){ curLinePair.rightLine.Q.x = x; curLinePair.rightLine.Q.y = y; rightImage.release(); rightImage = rightImageTmp.clone(); line(rightImage, curLinePair.rightLine.P, curLinePair.rightLine.Q, color, thickness, CV_AA, shift); imshow("right", rightImage); } } else { //cout << "you have selected the wrong image" << endl; } } void Morpher::main() { first_image_name = "test_img/1.jpg"; second_image_name = "test_img/2.jpg"; new_image_name = "morph"; //设置动画的过渡帧数,比如1为50%,3为25%,50%,75% frame_count = 3; leftImage = imread(first_image_name); rightImage = imread(second_image_name); leftImageTmp = leftImage.clone(); rightImageTmp = rightImage.clone(); namedWindow("left", 1); moveWindow("left", 10, 10); namedWindow("right", 1); moveWindow("right", 300, 10); setMouseCallback("left", on_mousel, this); setMouseCallback("right", on_mouser, this); imshow("left", leftImage); imshow("right", rightImage); while (true) { key = waitKey(0); if (key == 'c') counter =counter + 2; else if (key == 'w') runWarp(); else if (key == 'q') break; } imwrite("left_marked.jpg",leftImage); imwrite("right_marked.jpg", rightImage); return; }

    运行方法及效果

    在Morpher类成员函数main()的实现里,指定两幅输入图像的路径字符串。指定输出图像的名称字符串。指定变形过渡的帧数。这里设为3。

    first_image_name = "test_img/1.jpg"; second_image_name = "test_img/2.jpg"; new_image_name = "morph"; //设置动画的过渡帧数,比如1为50%,3为25%,50%,75% frame_count = 3;

    按下“C”键交互式的在两幅图像上画出对应特征线。每选取一对,按一次“C”键。

    按下“W”键,开始warping…

    0%:

    25%:

    50%:

    75%:

    100%:

    其它:

    转载请注明原文地址: https://ju.6miu.com/read-39300.html

    最新回复(0)