apply_filter.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. import mediapipe as mp
  2. import cv2
  3. import math
  4. import numpy as np
  5. import faceBlendCommon as fbc
  6. import csv
  7. VISUALIZE_FACE_POINTS = False
  8. filters_config = {
  9. 'anonymous':
  10. [{'path': "filters/anonymous.png",
  11. 'anno_path': "filters/anonymous_annotations.csv",
  12. 'morph': True, 'animated': False, 'has_alpha': True}],
  13. 'anime':
  14. [{'path': "filters/anime.png",
  15. 'anno_path': "filters/anime_annotations.csv",
  16. 'morph': True, 'animated': False, 'has_alpha': True}],
  17. 'dog':
  18. [{'path': "filters/dog-ears.png",
  19. 'anno_path': "filters/dog-ears_annotations.csv",
  20. 'morph': False, 'animated': False, 'has_alpha': True},
  21. {'path': "filters/dog-nose.png",
  22. 'anno_path': "filters/dog-nose_annotations.csv",
  23. 'morph': False, 'animated': False, 'has_alpha': True}],
  24. 'cat':
  25. [{'path': "filters/cat-ears.png",
  26. 'anno_path': "filters/cat-ears_annotations.csv",
  27. 'morph': False, 'animated': False, 'has_alpha': True},
  28. {'path': "filters/cat-nose.png",
  29. 'anno_path': "filters/cat-nose_annotations.csv",
  30. 'morph': False, 'animated': False, 'has_alpha': True}],
  31. 'jason-joker':
  32. [{'path': "filters/jason-joker.png",
  33. 'anno_path': "filters/jason-joker_annotations.csv",
  34. 'morph': True, 'animated': False, 'has_alpha': True}],
  35. 'gold-crown':
  36. [{'path': "filters/gold-crown.png",
  37. 'anno_path': "filters/gold-crown_annotations.csv",
  38. 'morph': False, 'animated': False, 'has_alpha': True}],
  39. 'flower-crown':
  40. [{'path': "filters/flower-crown.png",
  41. 'anno_path': "filters/flower-crown_annotations.csv",
  42. 'morph': False, 'animated': False, 'has_alpha': True}],
  43. }
  44. # detect facial landmarks in image
  45. def getLandmarks(img):
  46. mp_face_mesh = mp.solutions.face_mesh
  47. selected_keypoint_indices = [127, 93, 58, 136, 150, 149, 176, 148, 152, 377, 400, 378, 379, 365, 288, 323, 356, 70, 63, 105, 66, 55,
  48. 285, 296, 334, 293, 300, 168, 6, 195, 4, 64, 60, 94, 290, 439, 33, 160, 158, 173, 153, 144, 398, 385,
  49. 387, 466, 373, 380, 61, 40, 39, 0, 269, 270, 291, 321, 405, 17, 181, 91, 78, 81, 13, 311, 306, 402, 14,
  50. 178, 162, 54, 67, 10, 297, 284, 389]
  51. height, width = img.shape[:-1]
  52. with mp_face_mesh.FaceMesh(max_num_faces=1, static_image_mode=True, min_detection_confidence=0.5) as face_mesh:
  53. results = face_mesh.process(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
  54. if not results.multi_face_landmarks:
  55. print('Face not detected!!!')
  56. return 0
  57. for face_landmarks in results.multi_face_landmarks:
  58. values = np.array(face_landmarks.landmark)
  59. face_keypnts = np.zeros((len(values), 2))
  60. for idx,value in enumerate(values):
  61. face_keypnts[idx][0] = value.x
  62. face_keypnts[idx][1] = value.y
  63. # Convert normalized points to image coordinates
  64. face_keypnts = face_keypnts * (width, height)
  65. face_keypnts = face_keypnts.astype('int')
  66. relevant_keypnts = []
  67. for i in selected_keypoint_indices:
  68. relevant_keypnts.append(face_keypnts[i])
  69. return relevant_keypnts
  70. return 0
  71. def load_filter_img(img_path, has_alpha):
  72. # Read the image
  73. img = cv2.imread(img_path, cv2.IMREAD_UNCHANGED)
  74. alpha = None
  75. if has_alpha:
  76. b, g, r, alpha = cv2.split(img)
  77. img = cv2.merge((b, g, r))
  78. return img, alpha
  79. def load_landmarks(annotation_file):
  80. with open(annotation_file) as csv_file:
  81. csv_reader = csv.reader(csv_file, delimiter=",")
  82. points = {}
  83. for i, row in enumerate(csv_reader):
  84. # skip head or empty line if it's there
  85. try:
  86. x, y = int(row[1]), int(row[2])
  87. points[row[0]] = (x, y)
  88. except ValueError:
  89. continue
  90. return points
  91. def find_convex_hull(points):
  92. hull = []
  93. hullIndex = cv2.convexHull(np.array(list(points.values())), clockwise=False, returnPoints=False)
  94. addPoints = [
  95. [48], [49], [50], [51], [52], [53], [54], [55], [56], [57], [58], [59], # Outer lips
  96. [60], [61], [62], [63], [64], [65], [66], [67], # Inner lips
  97. [27], [28], [29], [30], [31], [32], [33], [34], [35], # Nose
  98. [36], [37], [38], [39], [40], [41], [42], [43], [44], [45], [46], [47], # Eyes
  99. [17], [18], [19], [20], [21], [22], [23], [24], [25], [26] # Eyebrows
  100. ]
  101. hullIndex = np.concatenate((hullIndex, addPoints))
  102. for i in range(0, len(hullIndex)):
  103. hull.append(points[str(hullIndex[i][0])])
  104. return hull, hullIndex
  105. def load_filter(filter_name="dog"):
  106. filters = filters_config[filter_name]
  107. multi_filter_runtime = []
  108. for filter in filters:
  109. temp_dict = {}
  110. img1, img1_alpha = load_filter_img(filter['path'], filter['has_alpha'])
  111. temp_dict['img'] = img1
  112. temp_dict['img_a'] = img1_alpha
  113. points = load_landmarks(filter['anno_path'])
  114. temp_dict['points'] = points
  115. if filter['morph']:
  116. # Find convex hull for delaunay triangulation using the landmark points
  117. hull, hullIndex = find_convex_hull(points)
  118. # Find Delaunay triangulation for convex hull points
  119. sizeImg1 = img1.shape
  120. rect = (0, 0, sizeImg1[1], sizeImg1[0])
  121. dt = fbc.calculateDelaunayTriangles(rect, hull)
  122. temp_dict['hull'] = hull
  123. temp_dict['hullIndex'] = hullIndex
  124. temp_dict['dt'] = dt
  125. if len(dt) == 0:
  126. continue
  127. if filter['animated']:
  128. filter_cap = cv2.VideoCapture(filter['path'])
  129. temp_dict['cap'] = filter_cap
  130. multi_filter_runtime.append(temp_dict)
  131. return filters, multi_filter_runtime
  132. # process input from webcam or video file
  133. cap = cv2.VideoCapture(0)
  134. # Some variables
  135. count = 0
  136. isFirstFrame = True
  137. sigma = 50
  138. iter_filter_keys = iter(filters_config.keys())
  139. filters, multi_filter_runtime = load_filter(next(iter_filter_keys))
  140. # The main loop
  141. while True:
  142. ret, frame = cap.read()
  143. if not ret:
  144. break
  145. else:
  146. points2 = getLandmarks(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
  147. # if face is partially detected
  148. if not points2 or (len(points2) != 75):
  149. continue
  150. ################ Optical Flow and Stabilization Code #####################
  151. img2Gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
  152. if isFirstFrame:
  153. points2Prev = np.array(points2, np.float32)
  154. img2GrayPrev = np.copy(img2Gray)
  155. isFirstFrame = False
  156. lk_params = dict(winSize=(101, 101), maxLevel=15,
  157. criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 20, 0.001))
  158. points2Next, st, err = cv2.calcOpticalFlowPyrLK(img2GrayPrev, img2Gray, points2Prev,
  159. np.array(points2, np.float32),
  160. **lk_params)
  161. # Final landmark points are a weighted average of detected landmarks and tracked landmarks
  162. for k in range(0, len(points2)):
  163. d = cv2.norm(np.array(points2[k]) - points2Next[k])
  164. alpha = math.exp(-d * d / sigma)
  165. points2[k] = (1 - alpha) * np.array(points2[k]) + alpha * points2Next[k]
  166. points2[k] = fbc.constrainPoint(points2[k], frame.shape[1], frame.shape[0])
  167. points2[k] = (int(points2[k][0]), int(points2[k][1]))
  168. # Update variables for next pass
  169. points2Prev = np.array(points2, np.float32)
  170. img2GrayPrev = img2Gray
  171. ################ End of Optical Flow and Stabilization Code ###############
  172. if VISUALIZE_FACE_POINTS:
  173. for idx, point in enumerate(points2):
  174. cv2.circle(frame, point, 2, (255, 0, 0), -1)
  175. cv2.putText(frame, str(idx), point, cv2.FONT_HERSHEY_SIMPLEX, .3, (255, 255, 255), 1)
  176. cv2.imshow("landmarks", frame)
  177. for idx, filter in enumerate(filters):
  178. filter_runtime = multi_filter_runtime[idx]
  179. img1 = filter_runtime['img']
  180. points1 = filter_runtime['points']
  181. img1_alpha = filter_runtime['img_a']
  182. if filter['morph']:
  183. hullIndex = filter_runtime['hullIndex']
  184. dt = filter_runtime['dt']
  185. hull1 = filter_runtime['hull']
  186. # create copy of frame
  187. warped_img = np.copy(frame)
  188. # Find convex hull
  189. hull2 = []
  190. for i in range(0, len(hullIndex)):
  191. hull2.append(points2[hullIndex[i][0]])
  192. mask1 = np.zeros((warped_img.shape[0], warped_img.shape[1]), dtype=np.float32)
  193. mask1 = cv2.merge((mask1, mask1, mask1))
  194. img1_alpha_mask = cv2.merge((img1_alpha, img1_alpha, img1_alpha))
  195. # Warp the triangles
  196. for i in range(0, len(dt)):
  197. t1 = []
  198. t2 = []
  199. for j in range(0, 3):
  200. t1.append(hull1[dt[i][j]])
  201. t2.append(hull2[dt[i][j]])
  202. fbc.warpTriangle(img1, warped_img, t1, t2)
  203. fbc.warpTriangle(img1_alpha_mask, mask1, t1, t2)
  204. # Blur the mask before blending
  205. mask1 = cv2.GaussianBlur(mask1, (3, 3), 10)
  206. mask2 = (255.0, 255.0, 255.0) - mask1
  207. # Perform alpha blending of the two images
  208. temp1 = np.multiply(warped_img, (mask1 * (1.0 / 255)))
  209. temp2 = np.multiply(frame, (mask2 * (1.0 / 255)))
  210. output = temp1 + temp2
  211. else:
  212. dst_points = [points2[int(list(points1.keys())[0])], points2[int(list(points1.keys())[1])]]
  213. tform = fbc.similarityTransform(list(points1.values()), dst_points)
  214. # Apply similarity transform to input image
  215. trans_img = cv2.warpAffine(img1, tform, (frame.shape[1], frame.shape[0]))
  216. trans_alpha = cv2.warpAffine(img1_alpha, tform, (frame.shape[1], frame.shape[0]))
  217. mask1 = cv2.merge((trans_alpha, trans_alpha, trans_alpha))
  218. # Blur the mask before blending
  219. mask1 = cv2.GaussianBlur(mask1, (3, 3), 10)
  220. mask2 = (255.0, 255.0, 255.0) - mask1
  221. # Perform alpha blending of the two images
  222. temp1 = np.multiply(trans_img, (mask1 * (1.0 / 255)))
  223. temp2 = np.multiply(frame, (mask2 * (1.0 / 255)))
  224. output = temp1 + temp2
  225. frame = output = np.uint8(output)
  226. cv2.putText(frame, "Press F to change filters", (10, 20), cv2.FONT_HERSHEY_SIMPLEX, .5, (255, 0, 0), 1)
  227. cv2.imshow("Face Filter", output)
  228. keypressed = cv2.waitKey(1) & 0xFF
  229. if keypressed == 27:
  230. break
  231. # Put next filter if 'f' is pressed
  232. elif keypressed == ord('f'):
  233. try:
  234. filters, multi_filter_runtime = load_filter(next(iter_filter_keys))
  235. except:
  236. iter_filter_keys = iter(filters_config.keys())
  237. filters, multi_filter_runtime = load_filter(next(iter_filter_keys))
  238. count += 1
  239. cap.release()
  240. cv2.destroyAllWindows()