213 lines
7.3 KiB
Python
213 lines
7.3 KiB
Python
from flask import Flask, render_template, Response, request, jsonify, send_file
|
|
from flask_socketio import SocketIO
|
|
import cv2
|
|
import json
|
|
import mediapipe as mp
|
|
import numpy as np
|
|
from scipy.spatial import procrustes
|
|
import base64
|
|
import os
|
|
|
|
app = Flask(__name__)
|
|
socketio = SocketIO(app)
|
|
|
|
mp_pose = mp.solutions.pose
|
|
mp_drawing = mp.solutions.drawing_utils
|
|
|
|
image_name = "Camel"
|
|
image = None
|
|
|
|
# Extract landmarks from JSON data
|
|
landmarks_from_json = []
|
|
the_landmarks = None
|
|
dataset = {"name": "", "ket": ""}
|
|
similarity = 0
|
|
all_data = []
|
|
|
|
def load_image_and_landmarks(image_name):
|
|
global image, landmarks_from_json, the_landmarks, all_data
|
|
landmarks_from_json = [] # Clear previous landmarks
|
|
# Load JSON data
|
|
with open('data_yoga.json') as f:
|
|
data = json.load(f)
|
|
all_data = data
|
|
|
|
# Load the image and landmarks
|
|
for the_data in data:
|
|
if the_data['name'] == image_name:
|
|
for lm in the_data['landmarks']:
|
|
landmarks_from_json.append([lm['coordinates'][0], lm['coordinates'][1]])
|
|
the_landmarks = the_data['landmarks']
|
|
image = cv2.imread(the_data['image_name'])
|
|
dataset["name"] = the_data['name']
|
|
dataset["ket"] = the_data['ket']
|
|
|
|
# Define the function to calculate the color based on similarity
|
|
def calculate_color(similarity):
|
|
if similarity < 70:
|
|
return (0, 0, 255) # Red
|
|
else:
|
|
normalized_similarity = (similarity - 55) / 45 # Normalize between 0 and 1 for values 71 to 100
|
|
red = int((1 - normalized_similarity) * 255)
|
|
green = int(normalized_similarity * 255)
|
|
return (0, green, red)
|
|
|
|
def resample_landmarks(landmarks, target_length):
|
|
idxs = np.linspace(0, len(landmarks) - 1, target_length).astype(int)
|
|
return [landmarks[i] for i in idxs]
|
|
|
|
def calculate_similarity(landmarks1, landmarks2):
|
|
if not landmarks1 or not landmarks2:
|
|
return 0
|
|
|
|
len1 = len(landmarks1)
|
|
len2 = len(landmarks2)
|
|
|
|
# Resample landmarks to ensure they have the same number of points
|
|
if len1 != len2:
|
|
if len1 < len2:
|
|
landmarks2 = resample_landmarks(landmarks2, len1)
|
|
else:
|
|
landmarks1 = resample_landmarks(landmarks1, len2)
|
|
|
|
# Convert to numpy arrays
|
|
landmarks1 = np.array(landmarks1)
|
|
landmarks2 = np.array(landmarks2)
|
|
|
|
# Normalize landmarks by removing the mean
|
|
norm_landmarks1 = landmarks1 - np.mean(landmarks1, axis=0)
|
|
norm_landmarks2 = landmarks2 - np.mean(landmarks2, axis=0)
|
|
|
|
# Perform Procrustes analysis to align the shapes
|
|
_, mtx1, mtx2 = procrustes(norm_landmarks1, norm_landmarks2)
|
|
|
|
# Calculate the Euclidean distances between corresponding landmarks
|
|
dists = np.linalg.norm(mtx1 - mtx2, axis=1)
|
|
|
|
# Calculate the similarity as the inverse of the average distance
|
|
avg_dist = np.mean(dists)
|
|
similarity = max(0, 1 - avg_dist)
|
|
|
|
# Scale the similarity to a percentage
|
|
similarity_percentage = similarity * 100
|
|
|
|
return similarity_percentage
|
|
|
|
def draw_landmarks(image, landmarks):
|
|
global similarity
|
|
annotated_image = image.copy()
|
|
for landmark in landmarks:
|
|
landmark_x = int(landmark['coordinates'][0] * annotated_image.shape[1])
|
|
landmark_y = int(landmark['coordinates'][1] * annotated_image.shape[0])
|
|
cv2.circle(annotated_image, (landmark_x, landmark_y), 5, (0, 255, 0), -1)
|
|
|
|
connections = mp_pose.POSE_CONNECTIONS
|
|
for connection in connections:
|
|
start_idx = connection[0]
|
|
end_idx = connection[1]
|
|
if 0 <= start_idx < len(landmarks) and 0 <= end_idx < len(landmarks):
|
|
start_landmark = landmarks[start_idx]['coordinates']
|
|
end_landmark = landmarks[end_idx]['coordinates']
|
|
start_x = int(start_landmark[0] * annotated_image.shape[1])
|
|
start_y = int(start_landmark[1] * annotated_image.shape[0])
|
|
end_x = int(end_landmark[0] * annotated_image.shape[1])
|
|
end_y = int(end_landmark[1] * annotated_image.shape[0])
|
|
cv2.line(annotated_image, (start_x, start_y), (end_x, end_y), (0, 255, 0), 2)
|
|
|
|
return annotated_image
|
|
|
|
def generate_frames():
|
|
global similarity
|
|
cap = cv2.VideoCapture(0)
|
|
with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
|
|
while cap.isOpened():
|
|
ret, frame = cap.read()
|
|
if not ret:
|
|
break
|
|
|
|
image_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
|
image_rgb.flags.writeable = False
|
|
results = pose.process(image_rgb)
|
|
image_rgb.flags.writeable = True
|
|
image_bgr = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2BGR)
|
|
the_color = calculate_color(similarity)
|
|
if results.pose_landmarks:
|
|
mp_drawing.draw_landmarks(
|
|
image_bgr,
|
|
results.pose_landmarks,
|
|
mp_pose.POSE_CONNECTIONS,
|
|
mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=4, circle_radius=2),
|
|
mp_drawing.DrawingSpec(color=(the_color), thickness=4, circle_radius=2),
|
|
)
|
|
|
|
landmarks_from_webcam = []
|
|
for lm in results.pose_landmarks.landmark:
|
|
landmarks_from_webcam.append([lm.x, lm.y])
|
|
|
|
similarity = calculate_similarity(landmarks_from_json, landmarks_from_webcam)
|
|
cv2.putText(image_bgr, f'Similarity: {similarity:.2f}%', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
|
|
|
|
ret, buffer = cv2.imencode('.jpg', image_bgr)
|
|
frame = buffer.tobytes()
|
|
yield (b'--frame\r\n'
|
|
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
|
|
|
|
cap.release()
|
|
|
|
@app.route('/')
|
|
def index():
|
|
image_name = request.args.get('image_name', 'Camel')
|
|
previous = None
|
|
next = None
|
|
load_image_and_landmarks(image_name)
|
|
|
|
current_index = 0
|
|
for index, data in enumerate(all_data):
|
|
if data['name'] == image_name:
|
|
current_index = index
|
|
break
|
|
|
|
if current_index == 0:
|
|
previous = all_data[-1]['name']
|
|
else:
|
|
previous = all_data[current_index - 1]['name']
|
|
|
|
if current_index == len(all_data) - 1:
|
|
next = all_data[0]['name']
|
|
else:
|
|
next = all_data[current_index + 1]['name']
|
|
|
|
annotated_image = draw_landmarks(image, the_landmarks)
|
|
_, buffer = cv2.imencode('.jpg', annotated_image)
|
|
img_str = base64.b64encode(buffer).decode('utf-8')
|
|
return render_template('index2.html', img_str=img_str, previous=previous, next=next)
|
|
|
|
@app.route('/video_feed')
|
|
def video_feed():
|
|
return Response(generate_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')
|
|
|
|
@app.route('/getdata', methods=['GET'])
|
|
def getdata():
|
|
return jsonify(all_data)
|
|
|
|
@app.route('/similarity', methods=['GET'])
|
|
def get_similarity():
|
|
global similarity
|
|
return {'similarity': similarity, 'data': dataset}
|
|
|
|
@app.route('/pose_dataset')
|
|
def pose_dataset():
|
|
load_image_and_landmarks("Camel")
|
|
return render_template('pose_dataset.html', data=all_data)
|
|
|
|
@app.route('/show_image')
|
|
def show_image():
|
|
image_path = request.args.get('image_path')
|
|
if image_path and os.path.exists(image_path):
|
|
return send_file(image_path, mimetype='image/jpeg')
|
|
else:
|
|
return "Image not found", 404
|
|
|
|
if __name__ == '__main__':
|
|
socketio.run(app, debug=True)
|