前言
前面写过一篇obj格式解析的博客,但是这篇文章中可视化的工作是参考PRNet
的源码进行的,后来细细思考了一下,有点问题,具体看下面。
问题来源
在PRNet
源码的render.py
中有个函数render_texture
,是作者用于将uv展开图重新映射回3D模型中,具体流程可以看出是:
-
找到当前三角形的uv坐标和3D坐标
-
将三个顶点的uv图颜色取平均,作为当前面片的颜色
tri_tex = (colors[:, triangles[0,:]] + colors[:,triangles[1,:]] + colors[:, triangles[2,:]])/3.
-
将三个顶点的3D深度值取平均,作为当前面片的深度值:
tri_depth = (vertices[2, triangles[0,:]] + vertices[2,triangles[1,:]] + vertices[2, triangles[2,:]])/3.
-
按像素着色,若当前像素在3D三角面中,且深度值大于当前像素点记录的深度值,则更新深度值和此3D点的像素,反之不更新不记录。
然而,最近用meshlab
看低模人体模型的时候发现一个细节,如下图
那么问题显而易见了,眼睛这里的三角面的颜色根本不可能是通过三个顶点的平均色产生的。
那么可能的解决方法就是,将uv里面的三角面片仿射变换到3D图像中的三角面片。
修正效果
为了验证上述的仿射变换思想是否可行,直接手撕一波,顺便把上一篇博客没有关注的深度值也加进去,整一个完整的代码出来。
读取OBJ信息的代码就不说了,就是一行行遍历,通过第一个字段判断是顶点还是法线还是面片等的信息。
直接进入核心实现:
首先需要通过所有顶点的前两个维度判断当前渲染图的大小:
render_width = int(np.ceil(np.max(vertices[...,0])))
render_height = int(np.ceil(np.max(vertices[...,1])))
最终的渲染图的每个面片必须需要深度信息指引是否渲染,深度值大的覆盖小的:
render_img = np.zeros((render_height,render_width,3),dtype=np.uint8)
depth = np.zeros((render_height,render_width),dtype=np.float32)
depth = depth-9999
为了将uv中的三角面片变换到渲染图中,必须先分别把两个面片取出来
# get uv texture map triangle
triangle_uv = np.float32([[vertex_tex[texcoords[i][0]][0]*height,(1-vertex_tex[texcoords[i][0]][1])*width],
[vertex_tex[texcoords[i][1]][0]*height,(1-vertex_tex[texcoords[i][1]][1])*width],
[vertex_tex[texcoords[i][2]][0]*height,(1-vertex_tex[texcoords[i][2]][1])*width]])
#get corresponding triangle in 3D face model
triangle_3d = np.float32([[vertices[triangles[i][0]][0],vertices[triangles[i][0]][1]],
[vertices[triangles[i][1]][0],vertices[triangles[i][1]][1]],
[vertices[triangles[i][2]][0],vertices[triangles[i][2]][1]]])
接下来进行仿射变换:
# get affine transform matrixwarp_mat = cv2.getAffineTransform(triangle_uv,triangle_3d)dst = cv2.warpAffine(uv_map,warp_mat,(height,width))
因为是按照面片着色,所以必须获取当前面片的mask:
# get draw mask
mask = np.zeros((height,width,3),dtype=np.uint8)
cv2.drawContours(mask,[triangle_3d[np.newaxis,...].astype(np.int)],-1,(255,255,255),-1)
当前面片的深度信息
# judge depth
mask_idx = np.argwhere(mask[...,0]==255)
curr_depth = (vertices[triangles[i][0]][2]+vertices[triangles[i][1]][2]+vertices[triangles[i][2]][2])/3
最开始想的是用render_img = cv2.copyTo(dst,mask,render_img)
去按照面片把整个面片复制过去,但是想来可能有面片叠加的情况,所以还是按照PRNet
作者思想,逐像素复制。注意根据深度信息去判断是否覆盖当前像素即可:
for idx in range(mask_idx.shape[0]):x = mask_idx[idx,0]y = mask_idx[idx,1]if(curr_depth>=depth[x,y]):render_img[x,y] = dst[x,y]depth[x,y] = curr_depth
对比一下meshlab和映射三角面的结果
可以发现中图和右图在颜色上有差距,主要原因在于Meshlab
是一款展示3D模型的软件,它内置了灯光,依据3D模型的法线产生了阴影,因此立体感会更强了,而我写的可视化并没有加入法线信息,仅仅是对展开的uv纹理图进行3D映射,所以照片既视感更强。
后记
主要还是对之前忽视的细节做个记录。
完整的python
脚本实现放在微信公众号的简介中描述的github中,有兴趣可以去找找,同时文章也同步到微信公众号中,有疑问或者兴趣欢迎公众号私信。