当前位置:   article > 正文

从0到1,反距离加权IDW(Inverse Distance Weighted Interpolation) 插值变形算法_idwinterpolation

idwinterpolation

论文

Image Warping with Scattered Image Warping with Scattered Data Interpolation

局部变形算法:液化,膨胀

全局变形算法:IDW,MLS,特征线变形

算法思路

算法优缺点

优点:实现简单,cpu实现,gpu实现都友好

缺点:速度与点的个数,图片长,宽,这3个指标成正比,点个数越多,速度越慢,图片越大速度越慢。如果点太少,形变会不平滑。

应用场景

大脸,瘦脸,大眼,等任何形变场景

基本实现

好处,更容易结合公式看清原理,缺点,速度很慢。

  1. class IDW(object):
  2. def __init__(self,):
  3. pass
  4. def IDW(self, srcImg, input_points, output_points):
  5. start = time.time()
  6. height, width, _ = srcImg.shape
  7. keypoints_num = len(input_points)
  8. u = 2
  9. UX = np.expand_dims(np.vstack([np.arange(width).astype(np.float32).reshape(1, -1)] * height), axis = 2)
  10. UY = np.expand_dims(np.hstack([np.arange(height).astype(np.float32).reshape(-1, 1)] * width), axis = 2)
  11. input_points_X = np.ones((height,width,keypoints_num), np.float32) * input_points[:,0]
  12. input_points_Y = np.ones((height,width,keypoints_num), np.float32) * input_points[:,1]
  13. output_points_X = np.ones((height,width,keypoints_num), np.float32) * output_points[:,0]
  14. output_points_Y = np.ones((height,width,keypoints_num), np.float32) * output_points[:,1]
  15. v_tmp = np.power( (UX - input_points_X)*(UX - input_points_X) + (UY - input_points_Y)*(UY - input_points_Y), u)
  16. for k in range(keypoints_num):
  17. v_tmp[input_points[k,1],input_points[k,0],:] = 1
  18. v = 1/v_tmp
  19. ww = np.expand_dims(1/np.sum(v,axis = 2), axis =2)
  20. UX = np.squeeze( (np.sum(v*(output_points_X + UX - input_points_X), axis = 2, keepdims=True)) * ww).astype(np.float32)
  21. UY = np.squeeze( (np.sum(v*(output_points_Y + UY - input_points_Y), axis = 2, keepdims=True)) * ww).astype(np.float32)
  22. copyImg = cv2.remap(srcImg, UX, UY, interpolation=cv2.INTER_LINEAR)
  23. end = time.time()
  24. print("IDW time cost:{} s".format(end - start))
  25. return copyImg

numba向量化加速实现

好处,基于向量化实现,速度更快,缺点,没有足够快 

  1. from numba import jit, vectorize, int64, float64, njit, prange
  2. def IDW(self, srcImg, input_points, output_points):
  3. @vectorize([float64(float64,float64, float64, float64, int64)], target='parallel')
  4. def l2_power_numba(UX, input_points_X, UY, input_points_Y, u):
  5. tmp_x = UX - input_points_X
  6. tmp_y = UY - input_points_Y
  7. ux_sum = tmp_x * tmp_x
  8. uy_sum = tmp_y * tmp_y
  9. if u==1:
  10. return ux_sum + uy_sum
  11. else:
  12. return np.power(ux_sum + uy_sum, u)
  13. @vectorize([float64(float64)], target='parallel')
  14. def div_numba(v_tmp):
  15. return 1/v_tmp
  16. start = time.time()
  17. height, width, _ = srcImg.shape
  18. keypoints_num = len(input_points)
  19. u = 2
  20. UX = np.expand_dims(np.vstack([np.arange(width).astype(np.float32).reshape(1, -1)] * height), axis = 2)
  21. UY = np.expand_dims(np.hstack([np.arange(height).astype(np.float32).reshape(-1, 1)] * width), axis = 2)
  22. input_points_X = np.ones((1,1,keypoints_num), np.float32)
  23. input_points_X = input_points[:,0]
  24. input_points_Y = np.ones((1,1,keypoints_num), np.float32)
  25. input_points_Y = input_points[:,1]
  26. output_points_X = np.ones((1,1,keypoints_num), np.float32)
  27. output_points_X = output_points[:,0]
  28. output_points_Y = np.ones((1,1,keypoints_num), np.float32)
  29. output_points_Y = output_points[:,1]
  30. v_tmp = l2_power_numba(UX, input_points_X, UY, input_points_Y, u)
  31. for k in range(keypoints_num):
  32. v_tmp[input_points[k,1],input_points[k,0],:] = v_tmp[input_points[k,1]-1,input_points[k,0]-1,:]
  33. v = div_numba(v_tmp)
  34. ww = np.expand_dims(1/np.sum(v,axis = 2), axis =2)
  35. UX = np.squeeze( UX + np.sum(v * (output_points_X- input_points_X), axis=2, keepdims=True) * ww).astype(np.float32)
  36. UY = np.squeeze( UY + np.sum(v * (output_points_Y- input_points_Y), axis=2, keepdims=True) * ww).astype(np.float32)
  37. copyImg = cv2.remap(srcImg, UX, UY, interpolation=cv2.INTER_LINEAR)
  38. end = time.time()
  39. print("IDW FAST time cost:{} s".format(end - start))
  40. return copyImg

cpu最佳加速实现

好处,基于scipy加速计算距离,公式合并,减少一次乘法,2d矩阵变1d矩阵。

  1. from scipy.spatial.distance import cdist
  2. class IDW(object):
  3. def __init__(self,
  4. original_control_points=None,
  5. deformed_control_points=None,
  6. power=1):
  7. if original_control_points is None:
  8. self.original_control_points = np.array([[0., 0., 0.], [0., 0., 1.],
  9. [0., 1., 0.], [1., 0., 0.],
  10. [0., 1., 1.], [1., 0., 1.],
  11. [1., 1., 0.], [1., 1.,
  12. 1.]])
  13. else:
  14. self.original_control_points = original_control_points
  15. if deformed_control_points is None:
  16. self.deformed_control_points = np.array([[0., 0., 0.], [0., 0., 1.],
  17. [0., 1., 0.], [1., 0., 0.],
  18. [0., 1., 1.], [1., 0., 1.],
  19. [1., 1., 0.], [1., 1.,
  20. 1.]])
  21. else:
  22. self.deformed_control_points = deformed_control_points
  23. self.power = power
  24. def __call__(self, src_pts):
  25. displ = self.deformed_control_points - self.original_control_points
  26. dist = cdist(src_pts, self.original_control_points, metric='sqeuclidean')
  27. dist = dist** self.power
  28. # Weights are set as the reciprocal of the distance if the distance is
  29. # not zero, otherwise 1.0 where distance is zero.
  30. dist[dist == 0.0] = 1
  31. weights = 1. / dist
  32. #weights[dist == 0.0] = 1.0
  33. offset = np.dot(weights, displ) / np.sum(weights, axis=1, keepdims=True)
  34. return src_pts + offset
  35. def IDW_cpu(self, srcImg, input_points, output_points):
  36. start = time.time()
  37. idw = IDW(original_control_points=input_points.astype(np.float64), deformed_control_points=output_points.astype(np.float64), power=3)
  38. h, w = srcImg.shape[:-1]
  39. x = np.empty((h, w), np.float64)
  40. x[:, :] = np.arange(w)
  41. y = np.empty((h, w), np.float64)
  42. y[:, :] = np.arange(h)[:, np.newaxis]
  43. mesh = np.array([x.ravel(), y.ravel()])
  44. mesh = mesh.T
  45. new_mesh = idw(mesh.astype(np.float64))
  46. UX = new_mesh[:,0].reshape(h,w).astype(np.float32)
  47. UY = new_mesh[:,1].reshape(h,w).astype(np.float32)
  48. copyImg = cv2.remap(srcImg, UX, UY, interpolation=cv2.INTER_LINEAR)
  49. end = time.time()
  50. print("IDW time cost:{} s".format(end - start))
  51. return copyImg

pytorch基于cuda加速实现

好处,基于最佳cpu版本实现,基于pytorch实现加速,速度快的一逼。缺点,图片太大,注意显存开销。解决思路,使用pytorch1.8以上版本,进行显存设置。当然也可以考虑tensorflow基于静态图的优化。

  1. torch.cuda.set_per_process_memory_fraction(0.5, 0)
  2. 参数1:fraction 限制的上限比例,如0.5 就是总GPU显存的一半,可以是0~1的任意float大小;
  3. 参数2:device 设备号; 如0 表示GPU卡 0号;
  1. class IDW_TORCH(object):
  2. def __init__(self,
  3. original_control_points=None,
  4. deformed_control_points=None,
  5. power=1):
  6. if torch.cuda.is_available():
  7. device = "cuda"
  8. else:
  9. device = "cpu"
  10. self.original_control_points = original_control_points.to(device)
  11. self.deformed_control_points = deformed_control_points.to(device)
  12. self.power = power
  13. self.device = device
  14. def __call__(self, src_pts):
  15. src_pts = src_pts.to(self.device)
  16. displ = self.deformed_control_points - self.original_control_points
  17. dist = torch.cdist(src_pts, self.original_control_points)
  18. dist = dist ** (self.power*2)
  19. # Weights are set as the reciprocal of the distance if the distance is
  20. # not zero, otherwise 1.0 where distance is zero.
  21. dist[dist == 0.0] = 1
  22. weights = 1. / dist
  23. offset = torch.matmul(weights, displ) / torch.sum(weights, axis=1, keepdims=True)
  24. return src_pts + offset
  25. def IDW_torch(self, srcImg, input_points, output_points):
  26. start = time.time()
  27. idw = IDW_TORCH(original_control_points=torch.from_numpy(input_points.astype(np.float32)),
  28. deformed_control_points=torch.from_numpy(output_points.astype(np.float32)),
  29. power=3)
  30. h, w = srcImg.shape[:-1]
  31. x = torch.empty((h, w))
  32. x[:, :] = torch.arange(w)
  33. y = torch.empty((h, w))
  34. y[:, :] = torch.arange(h)[:, np.newaxis]
  35. #mesh = torch.vstack([x.flatten(), y.flatten()]).T
  36. mesh = torch.stack([x.flatten(), y.flatten()], dim=1)
  37. new_mesh = idw(mesh.float()).cpu().numpy()
  38. UX = new_mesh[:,0].reshape(h,w).astype(np.float32)
  39. UY = new_mesh[:,1].reshape(h,w).astype(np.float32)
  40. self.idwUX = UX
  41. self.idwUY = UY
  42. copyImg = cv2.remap(srcImg, UX, UY, interpolation=cv2.INTER_LINEAR)
  43. end = time.time()
  44. print("IDW TORCH time cost:{} s".format(end - start))
  45. return copyImg

上面所有代码 input_points, output_points,2个矩阵的维度都是(n,2),n表示点的个数。坐标就是n个点的x,y坐标。只需要拿到人脸图片上,变换前的坐标位置,变换后的坐标位置,调用上面的函数就可以。生成图片基于opencv自带函数cv2.remap,更加高效。

大眼瘦脸运行效果

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
  

闽ICP备14008679号