Files
2025-01-12 06:15:15 +00:00

304 lines
11 KiB
Python

from fastapi import FastAPI, HTTPException, File, UploadFile
from fastapi.responses import StreamingResponse, JSONResponse
from fastapi.middleware.cors import CORSMiddleware
import cv2
import numpy as np
import json
import uvicorn
from kafka import KafkaProducer, KafkaConsumer
from redis import Redis
import uuid
import os
from datetime import timedelta
import threading
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
app = FastAPI()
mediapipe_app = FastAPI()
app.mount("/mediapipe", mediapipe_app)
# CORS configuration
ALLOWED_ORIGINS = 'https://beta.obscura.work'
app.add_middleware(
CORSMiddleware,
allow_origins=ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Configuration
MODEL_PATH = "/home/zydi/models/face_landmarker.task" # Replace with your model path
KAFKA_BROKER = "222.186.10.253:9092"
KAFKA_TOPIC = "mediapipe"
KAFKA_GROUP_ID = "mediapipe_group"
REDIS_HOST = "222.186.10.253"
REDIS_PORT = 6379
REDIS_PASSWORD = "Obscura@2024"
REDIS_DB = 10
UPLOAD_DIR = "/www/wwwroot/beta.obscura.work/upload_files/upload"
RESULT_DIR = "/www/wwwroot/beta.obscura.work/upload_files/result"
MAX_FILE_AGE = timedelta(hours=1)
# Ensure directories exist
os.makedirs(UPLOAD_DIR, exist_ok=True)
os.makedirs(RESULT_DIR, exist_ok=True)
# Initialize Kafka
producer = KafkaProducer(bootstrap_servers=[KAFKA_BROKER])
consumer = KafkaConsumer(
KAFKA_TOPIC,
bootstrap_servers=[KAFKA_BROKER],
group_id=KAFKA_GROUP_ID,
auto_offset_reset='earliest',
enable_auto_commit=True,
value_deserializer=lambda x: json.loads(x.decode('utf-8'))
)
# Initialize Redis
redis_client = Redis(
host=REDIS_HOST,
port=REDIS_PORT,
password=REDIS_PASSWORD,
db=REDIS_DB
)
class mediapipeEmbedder:
def __init__(self, model_path):
base_options = python.BaseOptions(model_asset_path=model_path)
options = vision.FaceLandmarkerOptions(
base_options=base_options,
output_face_blendshapes=True,
output_facial_transformation_matrixes=True,
num_faces=1
)
self.detector = vision.FaceLandmarker.create_from_options(options)
def get_mediapipe_landmarks(self, image):
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=image)
detection_result = self.detector.detect(mp_image)
if detection_result.face_landmarks:
return np.array([(lm.x, lm.y, lm.z) for lm in detection_result.face_landmarks[0]])
return None
def process_image(self, image_data):
img = cv2.imdecode(np.frombuffer(image_data, np.uint8), cv2.IMREAD_COLOR)
landmarks = self.get_mediapipe_landmarks(img)
if landmarks is not None:
# Calculate a more detailed mediapipe embedding
embedding = self.calculate_detailed_embedding(landmarks)
# Draw landmarks on the image
for lm in landmarks:
cv2.circle(img, (int(lm[0]*img.shape[1]), int(lm[1]*img.shape[0])), 2, (0,255,0), -1)
return {
"embedding": embedding,
"landmarks": landmarks.tolist()
}, img
else:
return None, img
def calculate_detailed_embedding(self, landmarks):
# Calculate various statistical features
mean = np.mean(landmarks, axis=0)
std = np.std(landmarks, axis=0)
median = np.median(landmarks, axis=0)
min_vals = np.min(landmarks, axis=0)
max_vals = np.max(landmarks, axis=0)
# Calculate pairwise distances between key facial landmarks
nose_tip = landmarks[4]
left_eye = landmarks[159]
right_eye = landmarks[386]
left_mouth = landmarks[61]
right_mouth = landmarks[291]
eye_distance = np.linalg.norm(left_eye - right_eye)
mouth_width = np.linalg.norm(left_mouth - right_mouth)
nose_to_mouth = np.linalg.norm(nose_tip - (left_mouth + right_mouth) / 2)
# Calculate face shape features
face_width = np.max(landmarks[:, 0]) - np.min(landmarks[:, 0])
face_height = np.max(landmarks[:, 1]) - np.min(landmarks[:, 1])
face_depth = np.max(landmarks[:, 2]) - np.min(landmarks[:, 2])
# Combine all features into a single embedding
embedding = np.concatenate([
mean, std, median, min_vals, max_vals,
[eye_distance, mouth_width, nose_to_mouth, face_width, face_height, face_depth]
])
return embedding.tolist()
embedder = mediapipeEmbedder(MODEL_PATH)
def process_image(image_data, filename):
try:
results, annotated_img = embedder.process_image(image_data)
if results:
# Save annotated image
annotated_filename = f"mediapipe_{filename}"
annotated_path = os.path.join(RESULT_DIR, annotated_filename)
cv2.imwrite(annotated_path, annotated_img)
return results, annotated_filename
else:
print(f"No face landmarks detected in image: {filename}")
return None, None
except Exception as e:
print(f"Error processing image {filename}: {str(e)}")
import traceback
traceback.print_exc()
return None, None
def process_video(video_data, filename):
try:
temp_video_path = os.path.join(UPLOAD_DIR, f"mediapipe_{filename}")
with open(temp_video_path, 'wb') as temp_video:
temp_video.write(video_data)
cap = cv2.VideoCapture(temp_video_path)
frame_count = 0
results = []
fps = int(cap.get(cv2.CAP_PROP_FPS))
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
annotated_filename = f"mediapipe_{filename}"
output_path = os.path.join(RESULT_DIR, annotated_filename)
out = cv2.VideoWriter(output_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (width, height))
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
if frame_count % fps == 0:
frame_results, annotated_frame = embedder.process_image(cv2.imencode('.jpg', frame)[1].tobytes())
if frame_results:
results.append({"frame": frame_count, "results": frame_results})
else:
annotated_frame = frame
out.write(annotated_frame)
frame_count += 1
cap.release()
out.release()
os.remove(temp_video_path)
return results, annotated_filename
except Exception as e:
print(f"Error processing video: {str(e)}")
return None, None
@mediapipe_app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
content = await file.read()
file_extension = os.path.splitext(file.filename)[1].lower()
new_filename = f"{uuid.uuid4()}{file_extension}"
original_file_path = os.path.join(UPLOAD_DIR, new_filename)
with open(original_file_path, "wb") as f:
f.write(content)
producer.send(KAFKA_TOPIC, json.dumps({
"filename": new_filename,
"file_type": "image" if file_extension in ['.jpg', '.jpeg', '.png'] else "video"
}).encode('utf-8'))
redis_key = f"mediapipe_result:{new_filename}"
redis_client.set(redis_key, json.dumps({"status": "queued"}))
return JSONResponse(content={"message": "File uploaded and queued for processing", "filename": new_filename})
@mediapipe_app.get("/result/{filename}")
async def get_mediapipe_result(filename: str):
redis_key = f"mediapipe_result:{filename}"
result = redis_client.get(redis_key)
if result:
result_data = json.loads(result)
return JSONResponse(content=result_data)
else:
raise HTTPException(status_code=404, detail="Result not found")
@mediapipe_app.get("/annotated/{filename}")
async def get_annotated_file(filename: str):
redis_key = f"mediapipe_result:{filename}"
result = redis_client.get(redis_key)
if result:
result_data = json.loads(result)
if result_data["status"] == "completed":
annotated_filename = result_data["annotated_filename"]
file_path = os.path.join(RESULT_DIR, annotated_filename)
if os.path.exists(file_path):
def iterfile():
with open(file_path, mode="rb") as file_like:
yield from file_like
file_extension = os.path.splitext(annotated_filename)[1].lower()
return StreamingResponse(iterfile(), media_type=f"image/{file_extension[1:]}" if file_extension in ['.jpg', '.jpeg', '.png'] else "video/mp4")
raise HTTPException(status_code=404, detail="Annotated file not found")
def process_task():
for message in consumer:
task = message.value
filename = task['filename']
file_type = task['file_type']
file_path = os.path.join(UPLOAD_DIR, filename)
redis_key = f"mediapipe_result:{filename}"
redis_client.set(redis_key, json.dumps({"status": "processing"}))
try:
if file_type == "image":
with open(file_path, 'rb') as f:
content = f.read()
results, annotated_filename = process_image(content, filename)
else:
with open(file_path, 'rb') as f:
content = f.read()
results, annotated_filename = process_video(content, filename)
if results and annotated_filename:
redis_client.set(redis_key, json.dumps({
"results": results,
"status": "completed",
"annotated_filename": annotated_filename
}))
else:
redis_client.set(redis_key, json.dumps({"status": "failed"}))
except Exception as e:
print(f"Error processing task: {str(e)}")
redis_client.set(redis_key, json.dumps({"status": "failed", "error": str(e)}))
def listen_redis_changes():
pubsub = redis_client.pubsub()
pubsub.psubscribe('__keyspace@3__:mediapipe_result:*')
for message in pubsub.listen():
if message['type'] == 'pmessage':
key = message['channel'].decode('utf-8').split(':')[-1]
operation = message['data'].decode('utf-8')
if operation == 'set':
value = redis_client.get(f"mediapipe_result:{key}")
if value:
result = json.loads(value)
print(f"Status update for {key}: {result['status']}")
if __name__ == "__main__":
threading.Thread(target=process_task, daemon=True).start()
threading.Thread(target=listen_redis_changes, daemon=True).start()
uvicorn.run(app, host="0.0.0.0", port=7006)