3DMM-Fitting_Pytorch代码阅读
convert_bfm_data.py
(transfer original BFM09 to our face model)
Scipy是世界上著名的Python開源科學(xué)計(jì)算庫,建立在Numpy之上。它增加的功能包括數(shù)值積分、最優(yōu)化、統(tǒng)計(jì)和一些專用函數(shù)。 SciPy函數(shù)庫在NumPy庫的基礎(chǔ)上增加了眾多的數(shù)學(xué)、科學(xué)以及工程計(jì)算中常用的庫函數(shù)。例如線性代數(shù)、常微分方程數(shù)值求解、信號(hào)處理、圖像處理、稀疏矩陣等等。
BFM模型介紹官網(wǎng)
01_MorphableModel.mat(數(shù)據(jù)主體)
BFM模型由53490個(gè)3D頂點(diǎn)構(gòu)成。也就是其shape/texture的數(shù)據(jù)長(zhǎng)度為160470(53490*3),因?yàn)槠渑帕蟹绞饺缦?#xff1a;
segbin:是segment binary,用熱點(diǎn)法標(biāo)注屬于面部哪一部分。
不同恒等式的面可以由199個(gè)主分量組成線性組合。
為了增加模型的靈活性,我們獨(dú)立處理面部的四個(gè)部分。每個(gè)部分定義在一個(gè)掩碼(每個(gè)頂點(diǎn)索引)中。(對(duì)應(yīng)09_mask)
基本原理
目標(biāo)shape或者texture都可以通過如下式子得到:
obj = average + pc * (coeficient .* pcVariance)
其中系數(shù)(coeficient)是變量,其余均是數(shù)據(jù)庫里的常量,其是一個(gè)199維(對(duì)應(yīng)199個(gè)PC)的向量。
BFM數(shù)據(jù)集的使用可以參照該博客:BFM使用 - 獲取平均臉模型的68個(gè)特征點(diǎn)坐標(biāo)
將臉部模型裁剪對(duì)齊臉部地標(biāo),其中只包含35709個(gè)頂點(diǎn)。
fit.py
face_alignment庫
這篇講解很好
根據(jù)這里的源碼,可以看到get_landmarks_from_image函數(shù)的返回值。
fit.py里面的代碼
#圖片的寬和高的獲取 h,w = orig_img.shape[:2] # 結(jié)合代碼可以看到,代碼這里的返回值為landmark tmp_lms = fa.get_landmarks_from_image(orig_img) #但這里得到是兩個(gè)array,第二個(gè)也沒用過,就暫時(shí)不探究了。 第一個(gè)array的數(shù)據(jù)就是landmark點(diǎn)坐標(biāo),但是其是68*3的一個(gè)而且向量,對(duì)于第三列是什么我也還是不太清楚,前兩列就是橫縱坐標(biāo)了。單獨(dú)運(yùn)行這行代碼時(shí)報(bào)錯(cuò)
lms = fa.get_landmarks_from_image(orig_img)[0] print(lms[:,0])TypeError: list indices must be integers or slices, not tuple
因?yàn)榱斜砜梢源娣挪煌愋偷臄?shù)據(jù),因此列表中每個(gè)元素的大小可以相同,也可以不同,也就不支持一次性讀取一列,即使是對(duì)于標(biāo)準(zhǔn)的二維數(shù)字列表。
用列表解析的方法讀取一列:
Bounding box Regression bbox 邊框回歸
重新設(shè)置了一下邊框的大小
def pad_bbox(bbox, img_wh, padding_ratio=0.2):x1, y1, x2, y2 = bboxwidth = x2 - x1height = y2 - y1size_bb = int(max(width, height) * (1+padding_ratio))center_x, center_y = (x1 + x2) // 2, (y1 + y2) // 2x1 = max(int(center_x - size_bb // 2), 0)y1 = max(int(center_y - size_bb // 2), 0)size_bb = min(img_wh[0] - x1, size_bb)size_bb = min(img_wh[1] - y1, size_bb)return [x1, y1, x1+size_bb, y1+size_bb]- cv2.resize函數(shù)參考這里
參數(shù)說明:
src :需要改變尺寸的圖像
dsize:目標(biāo)圖像大小
dst:目標(biāo)圖像
fx:w方向上的縮放比例
fy:h方向上的縮放比例
interpolation - 插值方法。共有5種:
有三點(diǎn)需要注意:
- 注意,dsize的形狀是(w,h),而opencv讀取出來的圖像的形狀是(h,w)
- 當(dāng)參數(shù)dsize不為0時(shí),dst的大小為dsize;否則,由src的大小以及縮放比例fx和fy來決定;可以看出dsize和(fx,fy)兩者不能同時(shí)為0
- 因?yàn)閐size是沒有默認(rèn)值的,所以必須指定,也即我們使用fx和fy來控制大小的時(shí)候必須設(shè)置dsize=(0,0)
關(guān)于代碼中這一部分的理解,這里出現(xiàn)了[None, …]
lms = lms[:, :2][None, ...] #由(68,2)維的數(shù)據(jù)變成了(1,68,2)的數(shù)據(jù) lms = torch.tensor(lms, dtype=torch.float32).cuda() img_tensor = torch.tensor(cropped_img[None, ...], dtype=torch.float32).cuda()Python中xx[:,None]是分開切片的意思
a[:,None]相當(dāng)于調(diào)用a.getitem(slice(None, None, None), None)
- 省略號(hào)表示根據(jù)對(duì)應(yīng)的ndim展開相應(yīng)數(shù)量的冒號(hào),如對(duì)于ndim=3,以下兩句等價(jià)
a[None,…]相當(dāng)于調(diào)用a.getitem(None, slice(None, None, None)) (代碼中l(wèi)ms是一個(gè)2維的數(shù)組) 這樣一處理就變成了三維的數(shù)組
python切片的介紹
[:,None]
None表示該維不進(jìn)行切片,而是將該維整體作為數(shù)組元素處理。
所以,[:,None]的效果就是將二維數(shù)組按每行分割,最后形成一個(gè)三維數(shù)組
load facemodel階段
很多數(shù)據(jù)集都是mat格式的標(biāo)注信息,使用模塊scipy.io的函數(shù)loadmat和savemat可以實(shí)現(xiàn)Python對(duì)mat數(shù)據(jù)的讀寫。
scipy.io.loadmat(file_name, mdict=None, appendmat=True, **kwargs) scipy.io.savemat(file_name, mdict, appendmat=True, format='5', long_field_names=False, do_compression=False, oned_as='row')計(jì)算機(jī)圖形學(xué)中的基本變換
torch.optim.Adam 方法的使用和參數(shù)的解釋
torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0)參數(shù):
- params (iterable) – 待優(yōu)化參數(shù)的iterable或者是定義了參數(shù)組的dict
- lr (float, 可選) – 學(xué)習(xí)率(默認(rèn):1e-3)
- betas (Tuple[float, float], 可選) – 用于計(jì)算梯度以及梯度平方的運(yùn)行平均值的系數(shù)(默認(rèn):0.9,0.999)
- eps (float, 可選) – 為了增加數(shù)值計(jì)算的穩(wěn)定性而加到分母里的項(xiàng)(默認(rèn):1e-8)
- weight_decay (float, 可選) – 權(quán)重衰減(L2懲罰)(默認(rèn): 0)
optimizer的方法
基本方法:
zero_grad() :清空所管理參數(shù)的梯度
step() :執(zhí)行一步更新
add_param_group():添加參數(shù)組
state_dict() :獲取優(yōu)化器當(dāng)前狀態(tài)信息字典
load_state_dict() :加載狀態(tài)信息字典
pytorch特性:張量梯度不自動(dòng)清零
sum(1) 求數(shù)組每一行的和,等價(jià)于 sum(axis=1)
tqdm模塊——進(jìn)度條配置
Tqdm 是一個(gè)快速,可擴(kuò)展的Python進(jìn)度條,可以在 Python 長(zhǎng)循環(huán)中添加一個(gè)進(jìn)度提示信息,用戶只需要封裝任意的迭代器 tqdm(iterator)。
總之,它是用來顯示進(jìn)度條的,很漂亮,使用很直觀(在循環(huán)體里邊加個(gè)tqdm),而且基本不影響原程序效率
關(guān)于with torch.no_grad():
在使用pytorch時(shí),并不是所有的操作都需要進(jìn)行計(jì)算圖的生成(計(jì)算過程的構(gòu)建,以便梯度反向傳播等操作)。而對(duì)于tensor的計(jì)算操作,默認(rèn)是要進(jìn)行計(jì)算圖的構(gòu)建的,在這種情況下,可以使用 with torch.no_grad():,強(qiáng)制之后的內(nèi)容不進(jìn)行計(jì)算圖構(gòu)建。
model.train() model.eval() with torch.no_grad() 參考這里
with torch.no_grad是指停止自動(dòng)求導(dǎo)
model.train()用于在訓(xùn)練階段
model.eval()用在驗(yàn)證和測(cè)試階段
- 他們的區(qū)別是對(duì)于Dropout和Batch Normlization層的影響。在train模式下,dropout網(wǎng)絡(luò)層會(huì)按照設(shè)定的參數(shù)p設(shè)置保留激活單元的概率(保留概率=p); batchnorm層會(huì)繼續(xù)計(jì)算數(shù)據(jù)的mean和var等參數(shù)并更新。在val模式下,dropout層會(huì)讓所有的激活單元都通過,而batchnorm層會(huì)停止計(jì)算和更新mean和var,直接使用在訓(xùn)練階段已經(jīng)學(xué)出的mean和var值。
models.py
最最重要的一個(gè)模塊了
pytorch3d Renderer模塊
可區(qū)分渲染通過允許2D圖像像素與場(chǎng)景的3D屬性相關(guān)聯(lián)來彌合2D和3D之間的差距。
例如,根據(jù)神經(jīng)網(wǎng)絡(luò)預(yù)測(cè)的3D形狀來渲染一個(gè)圖像,其可能使用參考圖像來計(jì)算2D損失。反轉(zhuǎn)渲染步驟意味著我們可以將像素的2D損失與形狀的3D屬性(如網(wǎng)格頂點(diǎn)的位置)聯(lián)系起來,使3D形狀可以在沒有任何明確的3D監(jiān)督的情況下學(xué)習(xí)。
paddle 填補(bǔ) 一般在深度學(xué)習(xí)中都是將其填補(bǔ)成同一長(zhǎng)度的序列
data loading and transformation / loss function / differentiable rendering
支持3d數(shù)據(jù)的異構(gòu)批處理
第二層有力的支持了異構(gòu)批處理
圖卷積
render模塊整體結(jié)構(gòu)官網(wǎng)
Fragments
The rasterizer returns 4 output tensors in a named tuple.
-
pix_to_face: LongTensor of shape (N, image_size, image_size, faces_per_pixel) specifying the indices of the faces (in the packed faces) which overlap each pixel in the image.
-
zbuf: FloatTensor of shape (N, image_size, image_size, faces_per_pixel) giving the z-coordinates of the nearest faces at each pixel in world coordinates, sorted in ascending z-order.
-
bary_coords: FloatTensor of shape (N, image_size, image_size, faces_per_pixel, 3) giving the barycentric coordinates in NDC units of the nearest faces at each pixel, sorted in ascending z-order.
-
pix_dists: FloatTensor of shape (N, image_size, image_size, faces_per_pixel) giving the signed Euclidean distance (in NDC units) in the x/y plane of each point closest to the pixel.
渲染需要在幾個(gè)不同的坐標(biāo)框架之間進(jìn)行轉(zhuǎn)換:世界空間、視圖/攝像機(jī)空間、NDC空間和屏幕空間。在每一步中,重要的是要知道相機(jī)的位置,+X, +Y, +Z軸是如何對(duì)齊的,以及可能的值范圍。下圖概述了PyTorch3D使用的約定。
NDC全稱:Normalized Device Coordinates。
- Pytorch3D和OpenGL的坐標(biāo)系約定存在差異
get_render函數(shù)
- 源碼在 renderer/camera_utils.py // eye, at and up vectors represent its position.
from cameras import look_at_view_transform
eye, at, up = camera_to_eye_at_up(cam.get_world_to_view_transform())
R, T = look_at_view_transform(eye=eye, at=at, up=up)
只要根據(jù)同一個(gè)R T創(chuàng)造出來的相機(jī),其觀察的視角都是一樣的
- 源碼在renderer/cameras.py
znear: near clipping plane of the view frustrum
zfar: far clipping plane of the view frustrum.
R: Rotation matrix of shape (N, 3, 3)
T: T: Translation matrix of shape (N, 3)
fov: field of view angle of the camera.
#其原來是OpenGLPerspectiveCameras,現(xiàn)在改成FoVPerspective
#FoVPerspectiveCameras是一個(gè)類,它存儲(chǔ)一批參數(shù),通過指定視場(chǎng)來生成一批投影矩陣。
#下面這是初始化一個(gè)類的成員變量
- 源碼在 renderer/lighting.py
- PointLights也是一個(gè)類,這里也是初始化一個(gè)類的成員
ambient_color: RGB color of the ambient component
diffuse_color: RGB color of the diffuse component
specular_color: RGB color of the specular component
location: xyz position of the light.
源碼在 render/mesh/rasterize_meshes.py
- blur_radius:范圍[0,2]內(nèi)的浮動(dòng)距離,用于擴(kuò)展面邊界框以進(jìn)行柵格化。設(shè)置模糊半徑會(huì)使形狀周圍的邊緣模糊,而不是硬邊界。設(shè)置為0表示沒有模糊。
- faces_per_pixel(可選):每個(gè)像素保存的面數(shù),返回z軸上最近的faces_per_pixel點(diǎn)。
- image_size:要光柵化的輸出圖像的像素大小。在非正方形圖像的情況下,可以選擇為(H, W)的元組。
class BlendParams(NamedTuple):
sigma: float = 1e-4
gamma: float = 1e-4
background_color: Union[torch.Tensor, Sequence[float]] = (1.0, 1.0, 1.0)
最后一步,將前面的參數(shù)組合起來,來初始化一個(gè)render by composing a rasterizer and a shader
renderer = MeshRenderer(rasterizer=MeshRasterizer(cameras=cameras,raster_settings=raster_settings),shader=SoftPhongShader(device=device,cameras=cameras,lights=lights,blend_params=blend_params))Split_coeff函數(shù)
將傳過來的系數(shù)分別分開,最后分別返回這些系數(shù):
return id_coeff, ex_coeff, tex_coeff, angles, gamma, translation
Shape_formation函數(shù)
只是用了身份和表情參數(shù)
einsum 愛因斯坦求和約定
Texture_formation函數(shù)
使用了紋理系數(shù)
def Texture_formation(self, tex_coeff):n_b = tex_coeff.size(0)face_texture = torch.einsum('ij,aj->ai', self.texBase, tex_coeff) + self.meantexface_texture = face_texture.view(n_b, -1, 3)return face_textureCompute_norm函數(shù)
def Compute_norm(self, face_shape):face_id = self.tri.long() - 1 #tri是triangle三角形 long() 函數(shù)將數(shù)字或字符串轉(zhuǎn)換為一個(gè)長(zhǎng)整型。point_id = self.point_buf.long() - 1 shape = face_shapev1 = shape[:, face_id[:, 0], :]v2 = shape[:, face_id[:, 1], :]v3 = shape[:, face_id[:, 2], :]e1 = v1 - v2e2 = v2 - v3face_norm = e1.cross(e2) #返回兩個(gè)(數(shù)組)向量的叉積。empty = torch.zeros((face_norm.size(0), 1, 3), dtype=face_norm.dtype, device=face_norm.device)face_norm = torch.cat((face_norm, empty), 1) v_norm = face_norm[:, point_id, :].sum(2) #torch.norm()函數(shù)#inputs的一共N維的話對(duì)就這N個(gè)數(shù)據(jù)求p范數(shù)#p指的是求p范數(shù)的p值,函數(shù)默認(rèn)p=2,那么就是求2范數(shù)#inputs3 = inputs.norm(p=2, dim=1, keepdim=False)v_norm = v_norm / v_norm.norm(dim=2).unsqueeze(2) return v_normCompute_rotation_matrix
#靜態(tài)方法 #輸入angles 輸出rotation rotXYZ = torch.eye(3).view(1, 3, 3).repeat(n_b * 3, 1, 1).view(3, n_b, 3, 3)Rigid_transform_block
輸入:face_shape, rotation, translation
輸出:face_shape_t
將臉部旋轉(zhuǎn)之后平移
Illumination_layer
輸入:face_texture, norm, gamma
輸出:face_color
get_lms
def get_lms(self, face_shape, kp_inds):lms = face_shape[:, kp_inds, :]return lmsProjection_block
輸入:face_shape
輸出:face_projection
pytorch函數(shù)
- bmm函數(shù)
- 計(jì)算兩個(gè)tensor的矩陣乘法,torch.bmm(a,b),tensor a 的size為(b,h,w),tensor b的size為(b,w,h),注意兩個(gè)tensor的維度必須為3. 結(jié)果維度為: (b,h,h)
- permute函數(shù)
- torch.Tensor.permute (Python method, in torch.Tensor)
- permute(dims) 將tensor的維度換位
- clone函數(shù)
- orch 為了提高速度,向量或是矩陣的賦值是指向同一內(nèi)存的,這不同于 Matlab。如果需要保存舊的tensor即需要開辟新的存儲(chǔ)地址而不是引用,可以用 clone() 進(jìn)行深拷貝
- stack函數(shù)
- stack(tensors,dim=0,out=None)
- dim=0時(shí),將tensor在一維上連接
- dim=1時(shí),將每個(gè)tensor的第i行按行連接組成一個(gè)新的2維tensor,再將這些新tensor按照dim=0的方式連接。
- dim=2時(shí),將每個(gè)tensor的第i行轉(zhuǎn)置后按列連接組成一個(gè)新的2維tensor,再將這些新tesnor按照dim=0的方式連接
- tile函數(shù)
- tile函數(shù)位于python模塊 numpy.lib.shape_base中,他的功能是重復(fù)某個(gè)數(shù)組。比如tile(A,n),功能是將數(shù)組A重復(fù)n次,構(gòu)成一個(gè)新的數(shù)組
- clamp函數(shù)
- torch.clamp(input, min, max, out=None) → Tensor
- clamp()函數(shù)的功能將輸入input張量每個(gè)元素的值壓縮到區(qū)間 [min,max],并返回結(jié)果到一個(gè)新張量。
TexturesVertex
源代碼位置:renderer/mesh/textures.py
這里也是初始化一個(gè)類的成員
Meshes
源代碼位置:structures/meshes.py
def __init__(self,verts=None,faces=None,textures=None,*,verts_normals=None,) -> None: mesh = Meshes(face_shape_t, tri.repeat(batch_num, 1, 1), face_color)總結(jié)
以上是生活随笔為你收集整理的3DMM-Fitting_Pytorch代码阅读的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【leetcode刷题笔记】Single
- 下一篇: Hadoop学习笔记—10.Shuffl