OpenCV 5.0.0
Open Source Computer Vision
読み込み中...
検索中...
見つかりません
🤖 AIによる機械翻訳(非公式) — これは OpenCV 5.0.0 公式リファレンス(英語)を AI (Claude) で自動翻訳したものです。訳に誤りを含む場合があります。正確な情報は 公式英語版(原文) を参照してください。
顔のランドマーク検出を用いた顔の入れ替え

このアプリケーションでは、ある画像内の顔を別の画像内の別の顔と入れ替えることができる。アプリケーションはまず両方の画像から顔を検出し、そのランドマークを求める。次に、最初の画像内の顔を別の画像内の顔と入れ替える。2つの顔を入れ替えるには、画像へのパスを指定してアプリケーションを実行するだけでよい。

// Command to be typed for running the sample
./sample_face_swapping -file=trained_model.dat -face_cascade=lbpcascadefrontalface.xml -image1=/path_to_image/image1.jpg -image2=/path_to_image/image2.jpg

コマンドパラメータの説明

‍* image1 i1 (必須) 入れ替えを適用したい1番目の画像ファイルへのパス。

  • image2 i2 (必須) 顔の入れ替えを適用したい2番目の画像ファイルへのパス。
  • model m (必須) 顔ランドマーク検出のために読み込むモデルを含むファイルへのパス。
  • face_cascade f (必須) 顔検出器として使用したい顔カスケードのxmlファイルへのパス。

コードの理解

このチュートリアルでは、OpenCVを用いた顔の入れ替えのサンプルコードを説明する。直接コードに進む :

c++
CascadeClassifier face_cascade;
bool myDetector( InputArray image, OutputArray ROIs );
bool myDetector( InputArray image, OutputArray ROIs ){
Mat gray;
std::vector<Rect> faces;
if(image.channels()>1){
cvtColor(image.getMat(),gray,COLOR_BGR2GRAY);
}
else{
gray = image.getMat().clone();
}
equalizeHist( gray, gray );
face_cascade.detectMultiScale( gray, faces, 1.1, 3,0, Size(30, 30) );
Mat(faces).copyTo(ROIs);
return true;
}

facemark APIは、顔ランドマーク検出で独自の顔検出器を使用する機能をユーザーに提供する。上記のコードはサンプルの顔検出器を生成する。上記の関数はfacemark API内の関数ポインタに渡される。

c++
Mat img = imread(image);
face_cascade.load(cascade_name);
FacemarkKazemi::Params params;
params.configfile = configfile_name;
Ptr<Facemark> facemark = FacemarkKazemi::create(params);
facemark->setFaceDetector(myDetector);

上記のコードは顔ランドマーク検出クラスのポインタを生成する。モデルの学習中に顔を検出するため、上記で生成した顔検出器を、生成したfacemarkポインタへ関数ポインタとして渡す必要がある。

c++
vector<Rect> faces1,faces2;
vector< vector<Point2f> > shape1,shape2;
float ratio1 = (float)img1.cols/(float)img1.rows;
float ratio2 = (float)img2.cols/(float)img2.rows;
resize(img1,img1,Size(640*ratio1,640*ratio1),0,0,INTER_LINEAR_EXACT);
resize(img2,img2,Size(640*ratio2,640*ratio2),0,0,INTER_LINEAR_EXACT);
Mat img1Warped = img2.clone();
facemark->getFaces(img1,faces1);
facemark->getFaces(img2,faces2);
facemark->fit(img1,faces1,shape1);
facemark->fit(img2,faces2,shape2);

上記のコードは、検出された顔を格納するベクトルと、両方の画像で検出された各顔の形状を格納するベクトルのベクトルを生成する。次に、両方の画像で検出された各顔のランドマークを検出する。小さい画像のほうが処理が容易なため、画像はリサイズされる。画像は実際の縦横比に従ってリサイズされる。

c++
vector<Point2f> boundary_image1;
vector<Point2f> boundary_image2;
vector<int> index;
convexHull(Mat(points2),index, false, false);
for(size_t i = 0; i < index.size(); i++)
{
boundary_image1.push_back(points1[index[i]]);
boundary_image2.push_back(points2[index[i]]);
}

上記のコードは、次に凸包を求めることで、入れ替え対象となる画像内の顔の境界点を見つける。

c++
vector< vector<int> > triangles;
Rect rect(0, 0, img1Warped.cols, img1Warped.rows);
divideIntoTriangles(rect, boundary_image2, triangles);
for(size_t i = 0; i < triangles.size(); i++)
{
vector<Point2f> triangle1, triangle2;
for(int j = 0; j < 3; j++)
{
triangle1.push_back(boundary_image1[triangles[i][j]]);
triangle2.push_back(boundary_image2[triangles[i][j]]);
}
warpTriangle(img1, img1Warped, triangle1, triangle2);
}

ここで、一方の顔をもう一方に重ねてワープする必要があり、そのためにアフィン変換を求める必要がある。OpenCVでアフィン変換を求める関数では、アフィン行列を計算するために3組の点が必要である。また、周囲の領域ではなく顔だけをワープすればよい。そのため、顔を三角形に分割し、各三角形を容易にもう一方の画像へワープできるようにする。

関数 divideIntoTriangles は、検出された顔を三角形に分割する。次に関数 warpTriangle が、一方の画像の各三角形をもう一方の画像へワープして顔を入れ替える。

c++
vector<Point> hull;
for(size_t i = 0; i < boundary_image2.size(); i++)
{
Point pt((int)boundary_image2[i].x,(int)boundary_image2[i].y);
hull.push_back(pt);
}
Mat mask = Mat::zeros(img2.rows, img2.cols, img2.depth());
fillConvexPoly(mask,&hull[0],(int)hull.size(), Scalar(255,255,255));
Rect r = boundingRect(boundary_image2);
Point center = (r.tl() + r.br()) / 2;
Mat output;
img1Warped.convertTo(img1Warped, CV_8UC3);
seamlessClone(img1Warped,img2, mask, center, output, NORMAL_CLONE);
imshow("Face_Swapped", output);

ワープした後でも、結果はどことなく不自然に見える。そこで結果を改善するため、シームレスクローニングを適用して、求める望ましい結果を得る。

結果

次のような顔の入れ替え (face swapping) に使用する2枚の画像を考える:

1枚目の画像

2枚目の画像

入れ替え後の結果