目次
序文
本プロジェクトでは、顔認識の事前学習モデルをベースに、Dlibが提供する機械学習、数値計算、グラフモデルアルゴリズム、画像処理の機能を利用し、写真内の顔を変更する機能の実現を目指しています。
Dlib は、さまざまな機械学習およびコンピューター ビジョン アルゴリズムを提供する強力なオープン ソース ライブラリです。このプロジェクトでは、Dlib で事前にトレーニングされた顔認識モデルを使用して、写真内の顔領域を識別して位置を特定します。
顔認識アルゴリズムを使用すると、2 枚の写真の顔の特徴点と顔の輪郭を取得できます。そして、これらの特徴点と輪郭情報を利用して、顔の位置合わせや登録操作を行うことができます。
登録プロセス中に、ある写真の顔のランドマークと顔の輪郭を別の写真の対応する領域と照合します。2 枚の写真の顔の特徴点を揃えることで、2 枚の写真の顔を交換する効果を実現できます。
このプロジェクトの応用は非常に興味深く、創造的です。エンターテインメント、芸術創作、仮想現実などの分野で活用でき、ユーザーに楽しく想像力豊かな空間を提供します。
このプロジェクトは個人的な学習のみを目的としており、商用または悪質な分野に適用しないでください。
全体的なデザイン
システムの全体構成図とシステムフローチャートを記載します。
システム全体構成図
システムの全体構成を図に示します。
システムフローチャート
システムフローを図に示します。
動作環境
このセクションには、Python 環境と関連ライブラリ パッケージのインストールが含まれます。
Python環境
Python 3.6 以降の構成が必要であり、Anaconda を Windows 環境にダウンロードして、Python に必要な環境の構成を完了します。
関連するライブラリパッケージのインストール
プロジェクトを完了するために必要なライブラリ ファイルには、OpenCV、dlib、numpy、sys、PIL、thikter、matplotlib などがあります。
dlib は、機械学習アルゴリズムと、現実世界の問題を C++ で解決する複雑なソフトウェアを作成するためのツールを含む最新の C++ ツールキットです。
OpenCV は、BSD ライセンス (オープン ソース) に基づいてリリースされたクロスプラットフォーム ライブラリであり、Linux、Windows、Android、および Mac OS オペレーティング システム上で実行できます。これは軽量かつ効率的であり、一連の C 関数と少数の C++ クラスで構成されており、画像処理やコンピューター ビジョンにおける多くの一般的なアルゴリズムを実装するための Python、Ruby、MATLAB などの言語インターフェイスを提供します。
このうち、Dlib ライブラリ ファイルはhttps://pypi.org/project/dlib/19.1.0/#filesからdlib-19.1.0-cp35-cp35m-win_amd64.whl
ダウンロードし、コマンド プロンプトで次のコマンドを使用してインストールする必要があります。
pip install dlib-19.1.0-cp35-cp35m-win_amd64.whl
その他の必要なライブラリ ファイルについては、コマンド プロンプトで pip install を使用するだけです。
モジュールの実装
本プロジェクトは、データ準備、顔マーカー抽出、顔位置調整、画像ブレンディング、色補正、変換機能、インタラクティブインターフェース設計の7つのモジュールで構成されており、各モジュールの機能紹介と関連コードは以下のとおりです。
1. データを準備する
dlib.get_frontal_face_detector()
これは、写真に顔があるかどうかを検出し、顔検出器の長方形のリストを返す顔検出器です。
dlib.shape_predictor(PREDICTOR_PATH) 特徴抽出器は、顔検出器によって提供される境界ボックスをアルゴリズムへの入力として受け取り、顔キーポイント予測器を返します。
公式の事前トレーニング モデルを使用すると、開発者は時間を節約し、Dlib ソースフォージ ライブラリ ( http://sourceforge.net/projects/dclib/files/dlib/v18 .10 ) から 68 個の特徴点で構成される顔特徴抽出ツールをダウンロードして構築できます。 /shape_predictor_68_face_landmarks.dat.bz2
関連するコードは次のとおりです。
PREDICTOR_PATH = "D:/HuanLian/model/shape_predictor_68_face_landmarks.dat"
FACE_POINTS = list(range(17, 68))
MOUTH_POINTS = list(range(48, 61))
RIGHT_BROW_POINTS = list(range(17, 22))
LEFT_BROW_POINTS = list(range(22, 27))
RIGHT_EYE_POINTS = list(range(36, 42))
LEFT_EYE_POINTS = list(range(42, 48))
NOSE_POINTS = list(range(27, 35))
JAW_POINTS = list(range(0, 17))
#划分68个点的每个点意味着什么部位,如第27~35的点(xi,yi)就是鼻子
ALIGN_POINTS = (LEFT_BROW_POINTS + RIGHT_EYE_POINTS + LEFT_EYE_POINTS + RIGHT_BROW_POINTS + NOSE_POINTS + MOUTH_POINTS)
#对齐图片的点,即五官
OVERLAY_POINTS = [LEFT_EYE_POINTS + RIGHT_EYE_POINTS + LEFT_BROW_POINTS + RIGHT_BROW_POINTS,NOSE_POINTS + MOUTH_POINTS,]
detector = dlib.get_frontal_face_detector()
#人脸检测器
predictor = dlib.shape_predictor(PREDICTOR_PATH)
#特征提取器
2. 顔マーカーの抽出
特徴抽出器を取得するための事前トレーニング後、ユーザーは 2 つの画像の顔の特徴点を入力します。この関数は 1 つの画像を numpy 配列に変換し、68*2 要素の行列を返します。入力画像には顔の 68 個の特徴点があり、および各特徴点は、各行の X、Y 座標に対応します。
class TooManyFaces(Exception):#设置检测到太多脸的类
pass
class NoFaces(Exception):#设置没有检测到脸的类
Pass
def get_landmarks(im):#获取人脸特征点,将图像转化成numpy数组,返回68*2元素矩阵
#输入图像的每个特征点对应每行的x、y坐标
rects = detector(im, 1)#每个矩形列表在图像中对应一个脸
#rects表示人脸框的位置信息
if len(rects) > 1: #如果识别的人脸数大于一个,引发TooManyFaces异常
raise TooManyFaces
if len(rects) == 0:#如果图片没人脸,引发NoFaces异常
raise NoFaces
return numpy.matrix([[p.x, p.y] for p in predictor(im, rects[0]).parts()])
#为加快计算,把得到的特征点转换成numpy矩阵
def read_im_and_landmarks(fname):#从计算机中读取用户所选的图片并提取特征点
im = cv2.imread(fname, cv2.IMREAD_COLOR)#opencv读取图片并显示
im = cv2.resize(im, (im.shape[1] * SCALE_FACTOR,
im.shape[0] * SCALE_FACTOR))
s = get_landmarks(im)
return im, s
3. 顔の位置を調整する
プロクラステスの解析方法は、2 つの形状を正規化することです。数学的に言えば、プラッツ解析とは、最小二乗法を使用して形状 A から形状 B へのアフィン変換を求めることです。
これで 2 つのマーカー マトリックスが取得され、各行には特定の顔の特徴に対応する座標のセットが含まれます。次のステップは、最初のベクトルを 2 番目のベクトルの点にできるだけ近づけるように、最初のベクトルを回転、平行移動、スケールする方法を理解することです。2 番目の画像は、同じ変換を使用して 1 番目の画像に重ねることができます。
T、s、R を求めると、式 (1) の結果が最小化されます。
E = min ∑ i = 1 68 ∥ s R ピット + T − qi T ∥ 2 E=\min \sum_{i=1}^{68} \| s Rp_i^t +T-q_i^T \|^2E=分i = 1∑68∥ s R p私た+T−q私T∥2
パラメータの意味は次のとおりです。
s: スケーリング係数。
R: 回転行列 (ベクトルを乗算すると方向が変わるが、大きさは変化せずキラリティーを維持する行列)。
ピットピットp私た: 照合対象の画像から 68 点の座標ベクトルを抽出します。
T: 翻訳ベクトル。
やめてq_i^tq私た:: 参照画像は 68 点の座標ベクトルを抽出します。
関連するコードは次のとおりです。
def transformation_from_points(points1, points2):
#普氏分析(Procrustes analysis)调整脸部,相同变换使两张照片面部特征相对距离尽可能小
#输入的自变量是两张图片特征点矩阵
points1 = points1.astype(numpy.float64) #将输入矩阵转化为浮点数
points2 = points2.astype(numpy.float64)
c1 = numpy.mean(points1, axis=0) #按列求均值,即求样本点在图像中的均值
c2 = numpy.mean(points2, axis=0)
points1 -= c1
#对所有形状的大小进行归一化,将每个样本点减去对应均值,处理消除平移T的影响
points2 -= c2
s1 = numpy.std(points1)#求出每个点的标准差
s2 = numpy.std(points2)
points1 /= s1
points2 /= s2 #每一个点集除以它的标准偏差,这一步处理消除缩放系数s的影响
U, S, Vt = numpy.linalg.svd(points1.T * points2) #可以求解出R
R = (U * Vt).T
return numpy.vstack([numpy.hstack(((s2 / s1) * R, c2.T - (s2 / s1) * R * c1.T)), numpy.matrix([0., 0., 1.])])
重要なのは、最初のピクチャと 2 番目のピクチャができるだけ重なり合うように、最初のピクチャを拡大縮小、回転、平行移動してアフィン変換行列を取得することです。OpenCV の cv2.warpAffine 関数をプラグインして、画像 2 を画像 1 にマッピングします。
4. 画像をブレンドする
マスクを使用してさまざまな領域を表現します。顔に属する領域のピクセル値は 1 で、顔に属さない領域のピクセル値は 0 です。抽出する場合、元の画像にマスクを直接乗算して顔を取得し、残りの領域のピクセル値は 0 になります。元の画像に 1-マスクを乗算すると、顔の領域は 0 になり、残りの領域は確保されます。 。上記の 2 つの結果を加算して、予備的な顔の変更を実現します。
1)凸包
処理中に、画像内のオブジェクトを囲む凸包を見つける必要があります。凸包は多角形に似ており、オブジェクトの最外層を囲む凸集合は、このオブジェクトを囲むことができるすべての凸集合の交差点です。図に示すように、円線で囲まれた凸集合が凸包です。
2) 凸包の実現
OpenCV では、一連の点の凸包は関数 convexHull を通じて取得されます。たとえば、点で構成される輪郭の場合、輪郭の凸包は convexHull 関数で取得できます。
def draw_convex_hull(im, points, color):#绘制凸包
points = cv2.convexHull(points)
#寻找图像的凸包,points就是输入的一组点
cv2.fillConvexPoly(im, points, color=color)
#cv2.fillConvexPoly()函数可以填充凸多边形,由凸包得到的轮廓点作为顶点
3) マスクの実装
def get_face_mask(im, landmarks):#为一张图像和一个标记矩阵生成一个遮罩
#画出了两个凸多边形:一个是眼睛周围的区域,一个是鼻子和嘴部周围的区域
im = numpy.zeros(im.shape[:2], dtype=numpy.float64)
#numpy.zeros返回给定形状和类型的新数组,用0填充
#img.shape[:2] 取彩色图片的高、宽
for group in OVERLAY_POINTS:
#OVERLAY_POINTS 定义为[LEFT_EYE_POINTS + RIGHT_EYE_POINTS + LEFT_BROW_POINTS + RIGHT_BROW_POINTS , NOSE_POINTS + MOUTH_POINTS,]
#分为[眼睛周围、鼻子和嘴]两个区域,是第二张图片中要覆盖第一张图片的点
draw_convex_hull(im,landmarks[group],color=1)
#对图片中68特征点集里是OVERLAY_POINTS的点进行凸包绘制
im = numpy.array([im, im, im]).transpose((1, 2, 0))
im = (cv2.GaussianBlur(im, (FEATHER_AMOUNT, FEATHER_AMOUNT),0)>0)*1.0
#高斯滤波
im = cv2.GaussianBlur(im, (FEATHER_AMOUNT, FEATHER_AMOUNT), 0)
return im
4) 画像の組み合わせ
両方のイメージのマスクが同時に生成されるため、イメージ 2 のマスクをイメージ 1 の座標空間に変換できます。2 つのマスクを 1 つに結合するというアイデアは、画像 2 のプロパティを明らかにしながら、画像 1 が確実にマスクされるようにすることです。
def warp_im(im, M, dshape):#得到了转换矩阵M后,使用它进行映射,OpenCV的cv2.warpAffine函数,将图像2映射到图像1
output_im = numpy.zeros(dshape, dtype=im.dtype)
#返回给定形状和类型的新数组,用0填充
#将图像二映射到图像一
cv2.warpAffine(im, #输入图像
M[:2],#仿射矩阵
(dshape[1], dshape[0]),
dst=output_im,#输出图像
borderMode=cv2.BORDER_TRANSPARENT,#边缘像素模式
flags=cv2.WARP_INVERSE_MAP)#插值方法的组合
return output_im
5. 正しい色
背景照明や 2 つの画像の肌の色などの要因の影響により、画像を直接融合する効果は理想的ではありません。アイデアは、ガウスぼかしを使用して色を補正することです。特定の操作: テンプレートを使用して画像内の各ピクセルをスキャンし、テンプレートによって決定された近傍のピクセルの加重平均グレー値を使用して、テンプレートの中心のピクセルの値を置き換えます。
1) ガウスぼかし
画素エネルギーが高い部分については、加重平均法を用いて画素値を再計算し、エネルギーが低い値となる。画像の場合、高周波部分がローパスフィルターを通過すると、画像全体が低周波となり、画像がぼやけてしまう現象をガウスぼかしといいます。
2) 画像と具体的な実装をインポートする
OpenCV は、グラフィックスでガウス フィルタリングを実行するための GaussianBlur() 関数を提供します。
def correct_colours(im1, im2, landmarks1):
#修正两幅图像之间不同肤色和光线造成的覆盖区域边缘不连续
blur_amount = COLOUR_CORRECT_BLUR_FRAC * numpy.linalg.norm(
numpy.mean(landmarks1[LEFT_EYE_POINTS], axis=0) -
numpy.mean(landmarks1[RIGHT_EYE_POINTS],axis=0))
#numpy.mean求左右眼点集均值,其中axis=0,压缩行,对各列求均值,返回1*n矩阵
#从左眼点集、右眼点集分别得到一个代表左眼和右眼的点,两者相减是左右眼横,纵相对距离
#用numpy.linalg.norm得到矩阵所有元素平方和开根号,勾股定理,得到了两眼之间的距离
#COLOUR_CORRECT_BLUR_FRAC*两眼距离作为高斯内核大小
#内核太小,第一个图像的面部特征将显示在第二个图像中
#内核过大,内核之外区域像素被覆盖并发生变色,COLOUR_CORRECT_BLUR_FRAC设置为0.6
blur_amount = int(blur_amount)
if blur_amount % 2 == 0:
blur_amount += 1 #高斯内核大小不能是偶数
im1_blur = cv2.GaussianBlur(im1, (blur_amount, blur_amount), 0)
#用模板扫描图像中的每一个像素,确定的邻域内像素的加权平均灰度值去替代模板中心像素点的值
#高斯矩阵的长与宽是高斯内核(blur_amount)的大小,标准差取0,返回高斯滤波后的图像
im2_blur = cv2.GaussianBlur(im2, (blur_amount, blur_amount), 0)
im2_blur += (128 * (im2_blur <= 1.0)).astype(im2_blur.dtype)
#防止除零,将高斯滤波后im2元素的数据类型返回,强制类型转换成数据类型
return (im2.astype(numpy.float64) * im1_blur.astype(numpy.float64) /
im2_blur.astype(numpy.float64))
#试图改变图像2的颜色来匹配图像1,通过用im2*im1/im2的高斯模糊
6. 変換機能
上記で定義された関数は変換関数であり、関連するコードは次のとおりです。
def main():
global image1,image2#将两张照片设置为全局变量,方便在各个函数中直接使用
im1, landmarks1 = read_im_and_landmarks(image1)#提取图片1的特征点
im2, landmarks2 = read_im_and_landmarks(image2)#提取图片2的特征点
M = transformation_from_points(landmarks1[ALIGN_POINTS],
landmarks2[ALIGN_POINTS])
#普氏分析(Procrustes analysis)调整脸部,相同变换使两张照片面部特征的相对距离尽可能小
mask = get_face_mask(im2, landmarks2)#为一张图像和一个标记矩阵生成一个遮罩
warped_mask = warp_im(mask, M, im1.shape)
#把图像2遮罩通过仿射矩阵M映射到图像1上
combined_mask = numpy.max([get_face_mask(im1, landmarks1), warped_mask], axis=0)
warped_im2 = warp_im(im2, M, im1.shape)#把图像2映射到遮好的图像1上
warped_corrected_im2 = correct_colours(im1, warped_im2, landmarks1)#校正颜色
output_im = im1 * (1.0 - combined_mask) + warped_corrected_im2 * combined_mask
cv2.imwrite('output.jpg', output_im) #输出换脸后的照片
7. インタラクティブなインターフェースの設計
#从计算机中选择原图1并展示
def show_original1_pic():
global image1
image1 = askopenfilename(title='选择文件') #将选择的图片作为待换脸图片
print(image1)
Img = PIL.Image.open(r'{}'.format(image1))
Img = Img.resize((270,270),PIL.Image.ANTIALIAS)
img_png_original = ImageTk.PhotoImage(Img)
label_Img_original1.config(image=img_png_original)
label_Img_original1.image = img_png_original #保持参考点
cv_orinial1.create_image(5, 5,anchor='nw', image=img_png_original)
#从计算机中选择原图2并展示
def show_original2_pic():
global image2
image2 = askopenfilename(title='选择文件')#将选择的图片作为待换脸图片
print(image2)
Img = PIL.Image.open(r'{}'.format(image2))
Img = Img.resize((270,270),PIL.Image.ANTIALIAS)
img_png_original = ImageTk.PhotoImage(Img)
label_Img_original2.config(image=img_png_original)
label_Img_original2.image = img_png_original #keep a reference
cv_orinial2.create_image(5, 5,anchor='nw', image=img_png_original)
#设置按钮,打开图片1并将其作为待替换图片
Button(root, text = "打开图片1", command = show_original1_pic).place(x=50,y=120)
#设置按钮,打开图片2并将其作为待替换图片
Button(root, text = "打开图片2", command = show_original2_pic).place(x=50,y=200)
#进行换脸
Button(root, text = "换脸!!", command = main).place(x=50,y=280)
#配置界面的一些细节,如边框、线条等
Label(root,text = "图片1",font=10).place(x=280,y=120)
cv_orinial1 = Canvas(root,bg = 'white',width=270,height=270)
cv_orinial1.create_rectangle(8,8,260,260,width=1,outline='red')
cv_orinial1.place(x=180,y=150)
label_Img_original1 = Label(root)
label_Img_original1.place(x=180,y=150)
Label(root,text="图片2",font=10).place(x=600,y=120)
cv_orinial2 = Canvas(root,bg = 'white',width=270,height=270)
cv_orinial2.create_rectangle(8,8,260,260,width=1,outline='red')
cv_orinial2.place(x=500,y=150)
label_Img_original2 = Label(root)
label_Img_original2.place(x=500,y=150)
root.mainloop()
システムテスト
アプリケーション インターフェイスを図に示します。左側にボタンの列があり、最初の 2 つのボタンは画像 1 を開きます。最初のステップは、クリックして閲覧ページに入り、自分でコンピュータ内のファイルを参照することです。第 2 ステップは、顔が変わる写真を選択することです。第 3 ステップは、画面上の 2 つのボックスに選択した写真を表示することです。写真1のすぐ下。同様に、画像 2 を開く機能は画像 1 を開く機能と同じです; 4 番目のステップで、左下の顔変更ボタンをクリックして顔を変更します。顔の変更をクリックすると、現在のファイル ディレクトリに顔が変更された画像が生成されます。
プロジェクトのソースコードのダウンロード
詳細については、ブログ リソースのダウンロード ページをご覧ください。
その他の情報ダウンロード
人工知能関連の学習ルートと知識システムについて学び続けたい場合は、私の他のブログ「重い | 完全な人工知能 AI 学習 - 基本知識学習ルート、すべての資料は料金を支払わずにネットワーク ディスクから直接ダウンロードできます」を参照してください。 「ルーチンへの注意」
このブログでは、Github の有名なオープンソース プラットフォーム、AI テクノロジー プラットフォーム、および関連分野の専門家 (Datawhale、ApacheCN、AI Youdao、Huang Haiguang 博士など) について言及しています。関連資料は約 100G あります。友達全員を助けてください。