AkashKumarave commited on
Commit
279af2f
·
verified ·
1 Parent(s): f9f00e0

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +166 -0
app.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ from fastapi import FastAPI, UploadFile, File, HTTPException
3
+ from fastapi.responses import Response
4
+ from fastapi.middleware.cors import CORSMiddleware
5
+ import cv2
6
+ import numpy as np
7
+ import face_recognition
8
+ import os
9
+ import shutil
10
+ import logging
11
+
12
+ app = FastAPI()
13
+
14
+ # Enable CORS to allow Framer frontend to connect
15
+ app.add_middleware(
16
+ CORSMiddleware,
17
+ allow_origins=["*"],
18
+ allow_credentials=True,
19
+ allow_methods=["*"],
20
+ allow_headers=["*"],
21
+ )
22
+
23
+ # Configure logging
24
+ logging.basicConfig(level=logging.INFO)
25
+
26
+ def get_face_data(image):
27
+ """Detect face and landmarks in an image."""
28
+ face_locations = face_recognition.face_locations(image)
29
+ if len(face_locations) == 0:
30
+ raise ValueError("No face detected in the image.")
31
+ if len(face_locations) > 1:
32
+ raise ValueError("Multiple faces detected; only one face per image is supported.")
33
+
34
+ face_location = face_locations[0] # (top, right, bottom, left)
35
+ landmarks = face_recognition.face_landmarks(image, face_locations=[face_location])
36
+ if not landmarks:
37
+ raise ValueError("Could not detect face landmarks.")
38
+
39
+ return face_location, landmarks[0]
40
+
41
+ def get_face_size(face_location):
42
+ """Calculate the width and height of the face bounding box."""
43
+ top, right, bottom, left = face_location
44
+ width = right - left
45
+ height = bottom - top
46
+ return width, height
47
+
48
+ def resize_face_image(source_img, target_face_size, source_face_location):
49
+ """Resize the source image to match the target face size."""
50
+ source_width, source_height = get_face_size(source_face_location)
51
+ target_width, target_height = target_face_size
52
+
53
+ # Calculate scaling factor to match the target face size
54
+ scale_x = target_width / source_width
55
+ scale_y = target_height / source_height
56
+ scale = min(scale_x, scale_y) # Use the smaller scale to avoid distortion
57
+
58
+ # Resize the source image
59
+ new_width = int(source_img.shape[1] * scale)
60
+ new_height = int(source_img.shape[0] * scale)
61
+ resized_source = cv2.resize(source_img, (new_width, new_height), interpolation=cv2.INTER_AREA)
62
+
63
+ return resized_source, scale
64
+
65
+ def swap_faces(source_img, target_img):
66
+ """Perform face swapping with size preservation and seamless blending."""
67
+ # Convert images to RGB (face_recognition expects RGB)
68
+ source_rgb = cv2.cvtColor(source_img, cv2.COLOR_BGR2RGB)
69
+ target_rgb = cv2.cvtColor(target_img, cv2.COLOR_BGR2RGB)
70
+
71
+ # Detect faces and landmarks
72
+ source_face_location, source_landmarks = get_face_data(source_rgb)
73
+ target_face_location, target_landmarks = get_face_data(target_rgb)
74
+
75
+ # Calculate face sizes
76
+ target_face_size = get_face_size(target_face_location)
77
+
78
+ # Resize source image to match target face size
79
+ resized_source, scale = resize_face_image(source_img, target_face_size, source_face_location)
80
+
81
+ # Adjust source face location after resizing
82
+ source_top, source_right, source_bottom, source_left = source_face_location
83
+ adjusted_source_location = (
84
+ int(source_top * scale),
85
+ int(source_right * scale),
86
+ int(source_bottom * scale),
87
+ int(source_left * scale)
88
+ )
89
+
90
+ # Extract the source face region
91
+ source_face = resized_source[
92
+ adjusted_source_location[0]:adjusted_source_location[2],
93
+ adjusted_source_location[3]:adjusted_source_location[1]
94
+ ]
95
+
96
+ # Calculate the center of the target face
97
+ target_top, target_right, target_bottom, target_left = target_face_location
98
+ target_center_x = (target_left + target_right) // 2
99
+ target_center_y = (target_top + target_bottom) // 2
100
+
101
+ # Create a mask for the source face
102
+ mask = 255 * np.ones(source_face.shape, source_face.dtype)
103
+
104
+ # Perform seamless cloning
105
+ try:
106
+ result = cv2.seamlessClone(
107
+ source_face,
108
+ target_img,
109
+ mask,
110
+ (target_center_x, target_center_y),
111
+ cv2.NORMAL_CLONE
112
+ )
113
+ except Exception as e:
114
+ logging.error(f"Seamless cloning failed: {str(e)}")
115
+ raise ValueError("Failed to blend the faces seamlessly.")
116
+
117
+ return result
118
+
119
+ @app.post("/swap-face/")
120
+ async def swap_face(
121
+ source_file: UploadFile = File(...),
122
+ target_file: UploadFile = File(...),
123
+ doFaceEnhancer: bool = True
124
+ ):
125
+ try:
126
+ # Save uploaded files temporarily
127
+ source_path = f"temp_source_{source_file.filename}"
128
+ target_path = f"temp_target_{target_file.filename}"
129
+ output_path = "output.jpg"
130
+
131
+ with open(source_path, "wb") as f:
132
+ shutil.copyfileobj(source_file.file, f)
133
+ with open(target_path, "wb") as f:
134
+ shutil.copyfileobj(target_file.file, f)
135
+
136
+ # Read images
137
+ source_img = cv2.imread(source_path)
138
+ target_img = cv2.imread(target_path)
139
+
140
+ if source_img is None or target_img is None:
141
+ raise ValueError("Failed to load one or both images.")
142
+
143
+ # Perform custom face swap
144
+ result_img = swap_faces(source_img, target_img)
145
+
146
+ # Optional: Apply face enhancement (e.g., using a library like GFPGAN)
147
+ # Skipped for simplicity
148
+
149
+ # Save the result
150
+ cv2.imwrite(output_path, result_img)
151
+
152
+ # Read the output image
153
+ with open(output_path, "rb") as f:
154
+ image_data = f.read()
155
+
156
+ # Clean up temporary files
157
+ for path in [source_path, target_path, output_path]:
158
+ if os.path.exists(path):
159
+ os.remove(path)
160
+
161
+ # Return the swapped image
162
+ return Response(content=image_data, media_type="image/jpeg")
163
+
164
+ except Exception as e:
165
+ logging.error(f"Error in face swap: {str(e)}")
166
+ raise HTTPException(status_code=500, detail=f"Face swap failed: {str(e)}")