Compare commits
11 Commits
body_lengt
...
main
Author | SHA1 | Date | |
---|---|---|---|
2fc37ad2dd | |||
1b0fb383c6 | |||
a2108cc7c9 | |||
5856721002 | |||
d9d0cd4517 | |||
4cdc6e0380 | |||
32056be80a | |||
e17e13efc8 | |||
ced5396be4 | |||
b207f961ec | |||
5052782d94 |
64
config.yml
64
config.yml
@ -1,57 +1,15 @@
|
||||
measurements:
|
||||
- name: arm_length
|
||||
landmarks:
|
||||
- 11
|
||||
- 13
|
||||
- 15
|
||||
|
||||
- name: leg_length
|
||||
landmarks:
|
||||
- 23
|
||||
- 25
|
||||
- 27
|
||||
|
||||
- name: shoulder_length
|
||||
landmarks:
|
||||
- 11
|
||||
- 12
|
||||
|
||||
- name: neck_to_hip_length
|
||||
- left_shoulder
|
||||
- right_shoulder
|
||||
- name: arm_length
|
||||
landmarks:
|
||||
- 11
|
||||
- 23
|
||||
|
||||
#0 - nose
|
||||
#1 - left eye (inner)
|
||||
#2 - left eye
|
||||
#3 - left eye (outer)
|
||||
#4 - right eye (inner)
|
||||
#5 - right eye
|
||||
#6 - right eye (outer)
|
||||
#7 - left ear
|
||||
#8 - right ear
|
||||
#9 - mouth (left)
|
||||
#10 - mouth (right)
|
||||
#11 - left shoulder
|
||||
#12 - right shoulder
|
||||
#13 - left elbow
|
||||
#14 - right elbow
|
||||
#15 - left wrist
|
||||
#16 - right wrist
|
||||
#17 - left pinky
|
||||
#18 - right pinky
|
||||
#19 - left index
|
||||
#20 - right index
|
||||
#21 - left thumb
|
||||
#22 - right thumb
|
||||
#23 - left hip
|
||||
#24 - right hip
|
||||
#25 - left knee
|
||||
#26 - right knee
|
||||
#27 - left ankle
|
||||
#28 - right ankle
|
||||
#29 - left heel
|
||||
#30 - right heel
|
||||
#31 - left foot index
|
||||
#32 - right foot index
|
||||
|
||||
- left_shoulder
|
||||
- left_elbow
|
||||
- left_wrist
|
||||
- name: leg_length
|
||||
landmarks:
|
||||
- left_hip
|
||||
- left_knee
|
||||
- left_ankle
|
||||
|
BIN
landmarks.bin
Executable file
BIN
landmarks.bin
Executable file
Binary file not shown.
289
landmarks.js
Normal file
289
landmarks.js
Normal file
@ -0,0 +1,289 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { ArgumentParser } = require("argparse");
|
||||
const cv = require("@techstark/opencv-js");
|
||||
const yaml = require("js-yaml");
|
||||
const { Pose, POSE_LANDMARKS } = require("@mediapipe/pose");
|
||||
|
||||
const logging = console;
|
||||
const warnings = console;
|
||||
|
||||
class Landmarker {
|
||||
static resizedHeight = 256;
|
||||
static resizedWidth = 256;
|
||||
|
||||
constructor() {
|
||||
this.args = this.parseArgs();
|
||||
this.measurements = this.loadLandmarks();
|
||||
if (!this.args.frontImage) {
|
||||
throw new Error("Front image needs to be passed");
|
||||
}
|
||||
if (!this.args.sideImage) {
|
||||
throw new Error("Side image needs to be passed");
|
||||
}
|
||||
|
||||
this.frontImage = cv.imread(this.args.frontImage);
|
||||
this.sideImage = cv.imread(this.args.sideImage);
|
||||
|
||||
this.frontImageResized = cv.resize(
|
||||
this.frontImage,
|
||||
new cv.Size(Landmarker.resizedWidth, Landmarker.resizedHeight),
|
||||
);
|
||||
this.sideImageResized = cv.resize(
|
||||
this.sideImage,
|
||||
new cv.Size(Landmarker.resizedWidth, Landmarker.resizedHeight),
|
||||
);
|
||||
|
||||
this.distances = {};
|
||||
|
||||
this.personHeight = this.args.personHeight;
|
||||
this.pixelHeight = this.args.pixelHeight;
|
||||
|
||||
this.pose = new Pose({
|
||||
locateFile: (file) => {
|
||||
return `https://cdn.jsdelivr.net/npm/@mediapipe/pose/${file}`;
|
||||
},
|
||||
});
|
||||
|
||||
this.landmarksIndices = [
|
||||
POSE_LANDMARKS.LEFT_SHOULDER,
|
||||
POSE_LANDMARKS.RIGHT_SHOULDER,
|
||||
POSE_LANDMARKS.LEFT_ELBOW,
|
||||
POSE_LANDMARKS.RIGHT_ELBOW,
|
||||
POSE_LANDMARKS.LEFT_WRIST,
|
||||
POSE_LANDMARKS.RIGHT_WRIST,
|
||||
POSE_LANDMARKS.LEFT_HIP,
|
||||
POSE_LANDMARKS.RIGHT_HIP,
|
||||
POSE_LANDMARKS.LEFT_KNEE,
|
||||
POSE_LANDMARKS.RIGHT_KNEE,
|
||||
POSE_LANDMARKS.LEFT_ANKLE,
|
||||
POSE_LANDMARKS.RIGHT_ANKLE,
|
||||
];
|
||||
}
|
||||
|
||||
loadLandmarks() {
|
||||
const file = fs.readFileSync(this.args.yamlFile, "utf8");
|
||||
const landmarksData = yaml.load(file);
|
||||
const measurements = {};
|
||||
for (const measurement of landmarksData.measurements) {
|
||||
measurements[measurement.name] = measurement.landmarks;
|
||||
}
|
||||
return measurements;
|
||||
}
|
||||
|
||||
parseArgs() {
|
||||
const parser = new ArgumentParser({
|
||||
description: "Process images and calculate measurements",
|
||||
});
|
||||
parser.add_argument("--front", {
|
||||
dest: "frontImage",
|
||||
required: true,
|
||||
help: "Path to the front image",
|
||||
});
|
||||
parser.add_argument("--side", {
|
||||
dest: "sideImage",
|
||||
required: true,
|
||||
help: "Path to the side image",
|
||||
});
|
||||
parser.add_argument("--poseDetectionConfidence", {
|
||||
dest: "poseDetectionConfidence",
|
||||
default: 0.5,
|
||||
type: "float",
|
||||
help: "Confidence score for pose detection",
|
||||
});
|
||||
parser.add_argument("--poseTrackingConfidence", {
|
||||
dest: "poseTrackingConfidence",
|
||||
default: 0.5,
|
||||
type: "float",
|
||||
help: "Confidence score for pose tracking",
|
||||
});
|
||||
parser.add_argument("--personHeight", {
|
||||
dest: "personHeight",
|
||||
required: true,
|
||||
type: "int",
|
||||
help: "Person height in cm",
|
||||
});
|
||||
parser.add_argument("--pixelHeight", {
|
||||
dest: "pixelHeight",
|
||||
type: "int",
|
||||
help: "Pixel height of person",
|
||||
});
|
||||
parser.add_argument("--measurement", {
|
||||
dest: "measurement",
|
||||
nargs: "+",
|
||||
type: "str",
|
||||
help: "Type of measurement",
|
||||
});
|
||||
parser.add_argument("--yamlFile", {
|
||||
dest: "yamlFile",
|
||||
required: true,
|
||||
help: "Path to the YAML file containing landmarks",
|
||||
});
|
||||
return parser.parse_args();
|
||||
}
|
||||
|
||||
async run() {
|
||||
await this.pose.initialize();
|
||||
const { frontResults, sideResults } = await this.processImages();
|
||||
|
||||
this.getCenterTopPoint(sideResults);
|
||||
|
||||
const table = [];
|
||||
if (this.args.measurement) {
|
||||
for (const m of this.args.measurement) {
|
||||
if (!this.measurements[m]) {
|
||||
throw new Error("Incorrect input (input not present in config.yml)");
|
||||
} else {
|
||||
const distance = this.calculateDistanceBetweenLandmarks(
|
||||
frontResults,
|
||||
m,
|
||||
);
|
||||
table.push([m, distance]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const m in this.measurements) {
|
||||
const distance = this.calculateDistanceBetweenLandmarks(
|
||||
frontResults,
|
||||
m,
|
||||
);
|
||||
table.push([m, distance]);
|
||||
}
|
||||
}
|
||||
|
||||
console.table(table);
|
||||
|
||||
this.pose.close();
|
||||
}
|
||||
|
||||
async processImages() {
|
||||
const frontResults = await this.pose.estimatePoses(this.frontImageResized);
|
||||
const sideResults = await this.pose.estimatePoses(this.sideImageResized);
|
||||
|
||||
this.sideImageKeypoints = this.sideImageResized.clone();
|
||||
this.frontImageKeypoints = this.frontImageResized.clone();
|
||||
|
||||
if (frontResults[0].landmarks) {
|
||||
this.drawLandmarks(
|
||||
this.frontImageKeypoints,
|
||||
frontResults[0].landmarks,
|
||||
this.landmarksIndices,
|
||||
);
|
||||
}
|
||||
if (sideResults[0].landmarks) {
|
||||
this.drawLandmarks(
|
||||
this.sideImageKeypoints,
|
||||
sideResults[0].landmarks,
|
||||
this.landmarksIndices,
|
||||
);
|
||||
}
|
||||
return {
|
||||
frontResults: frontResults[0],
|
||||
sideResults: sideResults[0],
|
||||
};
|
||||
}
|
||||
|
||||
pixelToMetricRatio() {
|
||||
const pixelToMetricRatio = this.personHeight / this.pixelHeight;
|
||||
logging.debug("pixelToMetricRatio %s", pixelToMetricRatio);
|
||||
return pixelToMetricRatio;
|
||||
}
|
||||
|
||||
drawLandmarks(image, landmarks, indices) {
|
||||
for (const idx of indices) {
|
||||
const landmark = landmarks[idx];
|
||||
const h = image.rows;
|
||||
const w = image.cols;
|
||||
const cx = Math.round(landmark.x * w);
|
||||
const cy = Math.round(landmark.y * h);
|
||||
this.circle(image, cx, cy);
|
||||
}
|
||||
}
|
||||
|
||||
circle(image, cx, cy) {
|
||||
cv.circle(image, new cv.Point(cx, cy), 2, new cv.Scalar(255, 0, 0), -1);
|
||||
}
|
||||
|
||||
calculateDistanceBetweenLandmarks(frontResults, measurementName) {
|
||||
if (!frontResults.landmarks) {
|
||||
return;
|
||||
}
|
||||
|
||||
const landmarks = frontResults.landmarks;
|
||||
const landmarkNames = this.measurements[measurementName];
|
||||
|
||||
let totalDistance = 0;
|
||||
for (let i = 0; i < landmarkNames.length - 1; i++) {
|
||||
const current = landmarks[landmarkNames[i]];
|
||||
const next = landmarks[landmarkNames[i + 1]];
|
||||
const pixelDistance = this.euclideanDistance(
|
||||
current.x * Landmarker.resizedWidth,
|
||||
current.y * Landmarker.resizedHeight,
|
||||
next.x * Landmarker.resizedWidth,
|
||||
next.y * Landmarker.resizedHeight,
|
||||
);
|
||||
const realDistance = pixelDistance * this.pixelToMetricRatio();
|
||||
totalDistance += realDistance;
|
||||
}
|
||||
return totalDistance;
|
||||
}
|
||||
|
||||
euclideanDistance(x1, y1, x2, y2) {
|
||||
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
|
||||
}
|
||||
|
||||
getCenterTopPoint(sideResults) {
|
||||
const grayImage = cv.cvtColor(this.sideImageKeypoints, cv.COLOR_BGR2GRAY);
|
||||
const blurredImage = cv.GaussianBlur(grayImage, new cv.Size(5, 5), 0);
|
||||
const roi = blurredImage.roi(
|
||||
new cv.Rect(
|
||||
0,
|
||||
0,
|
||||
this.sideImageResized.cols,
|
||||
Math.floor(this.sideImageResized.rows / 2),
|
||||
),
|
||||
);
|
||||
this.edges = cv.Canny(roi, 50, 150);
|
||||
const contours = this.edges.findContours(
|
||||
cv.RETR_EXTERNAL,
|
||||
cv.CHAIN_APPROX_SIMPLE,
|
||||
);
|
||||
|
||||
let xt, yt;
|
||||
this.topmostPoint = null;
|
||||
|
||||
for (const contour of contours) {
|
||||
const [xt, yt] = contour.minEnclosingCircle();
|
||||
if (this.topmostPoint === null || yt < this.topmostPoint[1]) {
|
||||
this.topmostPoint = [xt, yt];
|
||||
}
|
||||
}
|
||||
|
||||
const { x, y } = sideResults.landmarks[POSE_LANDMARKS.NOSE];
|
||||
const centerPoint = [
|
||||
x * Landmarker.resizedWidth,
|
||||
y * Landmarker.resizedHeight,
|
||||
];
|
||||
this.pixelHeight = Math.abs(centerPoint[1] - this.topmostPoint[1]);
|
||||
|
||||
cv.circle(
|
||||
this.sideImageKeypoints,
|
||||
new cv.Point(centerPoint[0], centerPoint[1]),
|
||||
2,
|
||||
new cv.Scalar(255, 0, 0),
|
||||
-1,
|
||||
);
|
||||
cv.circle(
|
||||
this.sideImageKeypoints,
|
||||
new cv.Point(this.topmostPoint[0], this.topmostPoint[1]),
|
||||
2,
|
||||
new cv.Scalar(255, 0, 0),
|
||||
-1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const landmarker = new Landmarker();
|
||||
landmarker.run().catch((error) => {
|
||||
console.error(error);
|
||||
});
|
390
landmarks.py
390
landmarks.py
@ -1,75 +1,126 @@
|
||||
import warnings
|
||||
import logging
|
||||
import os
|
||||
import warnings
|
||||
import sys
|
||||
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
|
||||
from tabulate import tabulate
|
||||
import math
|
||||
import argparse
|
||||
import cv2
|
||||
from mediapipe.python.solutions import pose
|
||||
import logging
|
||||
|
||||
warnings.filterwarnings("ignore",
|
||||
category=UserWarning,
|
||||
module="google.protobuf")
|
||||
|
||||
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
|
||||
from mediapipe.python.solutions import (
|
||||
pose,
|
||||
)
|
||||
import yaml
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
warnings.filterwarnings(
|
||||
"ignore",
|
||||
category=UserWarning,
|
||||
module="google.protobuf",
|
||||
)
|
||||
|
||||
LANDMARK_NAME_TO_INDEX = {
|
||||
"nose": 0,
|
||||
"left_eye_inner": 1,
|
||||
"left_eye": 2,
|
||||
"left_eye_outer": 3,
|
||||
"right_eye_inner": 4,
|
||||
"right_eye": 5,
|
||||
"right_eye_outer": 6,
|
||||
"left_ear": 7,
|
||||
"right_ear": 8,
|
||||
"mouth_left": 9,
|
||||
"mouth_right": 10,
|
||||
"left_shoulder": 11,
|
||||
"right_shoulder": 12,
|
||||
"left_elbow": 13,
|
||||
"right_elbow": 14,
|
||||
"left_wrist": 15,
|
||||
"right_wrist": 16,
|
||||
"left_pinky": 17,
|
||||
"right_pinky": 18,
|
||||
"left_index": 19,
|
||||
"right_index": 20,
|
||||
"left_thumb": 21,
|
||||
"right_thumb": 22,
|
||||
"left_hip": 23,
|
||||
"right_hip": 24,
|
||||
"left_knee": 25,
|
||||
"right_knee": 26,
|
||||
"left_ankle": 27,
|
||||
"right_ankle": 28,
|
||||
"left_heel": 29,
|
||||
"right_heel": 30,
|
||||
"left_foot_index": 31,
|
||||
"right_foot_index": 32,
|
||||
}
|
||||
|
||||
class Landmarker:
|
||||
|
||||
resized_height = 256
|
||||
resized_width = 300
|
||||
resized_width = 256
|
||||
|
||||
def __init__(self) -> None:
|
||||
args = self.parse_args()
|
||||
if args.front_image == None:
|
||||
self.args = self.parse_args()
|
||||
self.measurements = self.load_landmarks()
|
||||
if self.args.front_image is None:
|
||||
raise Exception("front image needs to be passed")
|
||||
if args.side_image == None:
|
||||
if self.args.side_image is None:
|
||||
raise Exception("side image needs to be passed")
|
||||
|
||||
self.front_image = cv2.imread(args.front_image)
|
||||
self.side_image = cv2.imread(args.side_image)
|
||||
self.front_image = cv2.imread(self.args.front_image)
|
||||
self.side_image = cv2.imread(self.args.side_image)
|
||||
|
||||
self.front_image_resized = cv2.resize(
|
||||
self.front_image, (self.resized_height, self.resized_width))
|
||||
self.side_image_resized = cv2.resize(
|
||||
self.side_image, (self.resized_height, self.resized_width))
|
||||
self.front_image_resized = cv2.resize(self.front_image, (self.resized_height, self.resized_width))
|
||||
self.side_image_resized = cv2.resize(self.side_image, (self.resized_height, self.resized_width))
|
||||
|
||||
self.distances = {}
|
||||
|
||||
self.person_height = args.person_height
|
||||
self.pixel_height = args.pixel_height
|
||||
self.person_height = self.args.person_height
|
||||
self.pixel_height = self.args.pixel_height
|
||||
|
||||
self.pose = pose.Pose(
|
||||
static_image_mode=True,
|
||||
min_detection_confidence=args.pose_detection_confidence,
|
||||
min_tracking_confidence=args.pose_tracking_confidence,
|
||||
min_detection_confidence=self.args.pose_detection_confidence,
|
||||
min_tracking_confidence=self.args.pose_tracking_confidence,
|
||||
)
|
||||
self.landmarks_to_calculate = []
|
||||
|
||||
self.landmarks_indices = [
|
||||
pose.PoseLandmark.LEFT_SHOULDER.value,
|
||||
pose.PoseLandmark.RIGHT_SHOULDER.value,
|
||||
pose.PoseLandmark.LEFT_ELBOW.value,
|
||||
pose.PoseLandmark.RIGHT_ELBOW.value,
|
||||
pose.PoseLandmark.LEFT_WRIST.value,
|
||||
pose.PoseLandmark.RIGHT_WRIST.value,
|
||||
pose.PoseLandmark.LEFT_HIP.value,
|
||||
pose.PoseLandmark.RIGHT_HIP.value,
|
||||
pose.PoseLandmark.LEFT_KNEE.value,
|
||||
pose.PoseLandmark.RIGHT_KNEE.value,
|
||||
pose.PoseLandmark.LEFT_ANKLE.value,
|
||||
pose.PoseLandmark.RIGHT_ANKLE.value,
|
||||
LANDMARK_NAME_TO_INDEX["left_shoulder"],
|
||||
LANDMARK_NAME_TO_INDEX["right_shoulder"],
|
||||
LANDMARK_NAME_TO_INDEX["left_elbow"],
|
||||
LANDMARK_NAME_TO_INDEX["right_elbow"],
|
||||
LANDMARK_NAME_TO_INDEX["left_wrist"],
|
||||
LANDMARK_NAME_TO_INDEX["right_wrist"],
|
||||
LANDMARK_NAME_TO_INDEX["left_hip"],
|
||||
LANDMARK_NAME_TO_INDEX["right_hip"],
|
||||
LANDMARK_NAME_TO_INDEX["left_knee"],
|
||||
LANDMARK_NAME_TO_INDEX["right_knee"],
|
||||
LANDMARK_NAME_TO_INDEX["left_ankle"],
|
||||
LANDMARK_NAME_TO_INDEX["right_ankle"],
|
||||
]
|
||||
|
||||
def load_landmarks(self):
|
||||
with open(self.args.yaml_file, "r") as file:
|
||||
landmarks_data = yaml.safe_load(file)
|
||||
measurements = {}
|
||||
for measurement in landmarks_data["measurements"]:
|
||||
measurements[measurement["name"]] = [LANDMARK_NAME_TO_INDEX[l] for l in measurement["landmarks"]]
|
||||
return measurements
|
||||
|
||||
def parse_args(self):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--front",
|
||||
dest="front_image",
|
||||
type=str,
|
||||
help="Front image")
|
||||
parser.add_argument("--side",
|
||||
dest="side_image",
|
||||
type=str,
|
||||
help="Side image")
|
||||
parser.add_argument(
|
||||
"--front",
|
||||
dest="front_image",
|
||||
type=str,
|
||||
help="Front image",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--side",
|
||||
dest="side_image",
|
||||
type=str,
|
||||
help="Side image",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pose_detection_confidence",
|
||||
dest="pose_detection_confidence",
|
||||
@ -86,43 +137,74 @@ class Landmarker:
|
||||
)
|
||||
parser.add_argument(
|
||||
"--person_height",
|
||||
# default=153,
|
||||
dest="person_height",
|
||||
type=int,
|
||||
help="person height of person",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pixel_height",
|
||||
# default=216,
|
||||
dest="pixel_height",
|
||||
type=int,
|
||||
help="pixel height of person",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--measurement",
|
||||
dest="measurement",
|
||||
nargs="+",
|
||||
type=str,
|
||||
help="Type of measurement",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--yaml_file",
|
||||
dest="yaml_file",
|
||||
type=str,
|
||||
help="Path to the YAML file containing landmarks",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
def run(self):
|
||||
|
||||
logging.warning("person's height: %s", self.person_height)
|
||||
|
||||
logging.warning("person's pixel height: %s", self.pixel_height)
|
||||
|
||||
front_results, side_results = self.process_images()
|
||||
front_results, _ = self.process_images()
|
||||
|
||||
self.get_center_top_point(front_results)
|
||||
|
||||
self.calculate_distance_betn_landmarks(front_results)
|
||||
table = []
|
||||
if self.args.measurement:
|
||||
for m in self.args.measurement:
|
||||
if m not in self.measurements:
|
||||
raise Exception("Incorrect input (input not present in config.yml)")
|
||||
else:
|
||||
distance = self.calculate_distance_betn_landmarks(front_results, m)
|
||||
table.append([m, distance])
|
||||
else:
|
||||
for m in self.measurements:
|
||||
distance = self.calculate_distance_betn_landmarks(front_results, m)
|
||||
table.append([m, distance])
|
||||
|
||||
self.output()
|
||||
|
||||
self.display_images()
|
||||
output = tabulate(
|
||||
table,
|
||||
headers=[
|
||||
"measurement",
|
||||
"Distance (cm)",
|
||||
],
|
||||
tablefmt="plain",
|
||||
)
|
||||
print(output)
|
||||
|
||||
self.pose.close()
|
||||
|
||||
def process_images(self):
|
||||
front_results = self.pose.process(
|
||||
cv2.cvtColor(self.front_image_resized, cv2.COLOR_BGR2RGB))
|
||||
cv2.cvtColor(
|
||||
self.front_image_resized,
|
||||
cv2.COLOR_BGR2RGB,
|
||||
)
|
||||
)
|
||||
side_results = self.pose.process(
|
||||
cv2.cvtColor(self.side_image_resized, cv2.COLOR_BGR2RGB))
|
||||
cv2.cvtColor(
|
||||
self.side_image_resized,
|
||||
cv2.COLOR_BGR2RGB,
|
||||
)
|
||||
)
|
||||
|
||||
self.side_image_keypoints = self.side_image_resized.copy()
|
||||
self.front_image_keypoints = self.front_image_resized.copy()
|
||||
@ -139,12 +221,18 @@ class Landmarker:
|
||||
side_results.pose_landmarks, # type: ignore# type: ignore
|
||||
self.landmarks_indices,
|
||||
)
|
||||
return front_results, side_results
|
||||
return (
|
||||
front_results,
|
||||
side_results,
|
||||
)
|
||||
|
||||
def pixel_to_metric_ratio(self):
|
||||
self.pixel_height = self.pixel_distance * 2
|
||||
pixel_to_metric_ratio = self.person_height / self.pixel_height
|
||||
logging.warning("pixel_to_metric_ratio %s", pixel_to_metric_ratio)
|
||||
logging.debug(
|
||||
"pixel_to_metric_ratio %s",
|
||||
pixel_to_metric_ratio,
|
||||
)
|
||||
return pixel_to_metric_ratio
|
||||
|
||||
def draw_landmarks(self, image, landmarks, indices):
|
||||
@ -155,118 +243,85 @@ class Landmarker:
|
||||
self.circle(image, cx, cy)
|
||||
|
||||
def circle(self, image, cx, cy):
|
||||
return cv2.circle(image, (cx, cy), 2, (255, 0, 0), -1)
|
||||
return cv2.circle(
|
||||
image,
|
||||
(cx, cy),
|
||||
2,
|
||||
(255, 0, 0),
|
||||
-1,
|
||||
)
|
||||
|
||||
def output(self):
|
||||
table = []
|
||||
for landmark, distance in self.distances.items():
|
||||
table.append([landmark.replace("_", " "), distance])
|
||||
output = tabulate(table,
|
||||
headers=["measurement", "value"],
|
||||
tablefmt="grid")
|
||||
print(output)
|
||||
|
||||
def calculate_distance_betn_landmarks(self, front_results, landmarks=[]):
|
||||
def calculate_distance_betn_landmarks(
|
||||
self,
|
||||
front_results,
|
||||
measurement_name,
|
||||
):
|
||||
if not front_results.pose_landmarks:
|
||||
return
|
||||
|
||||
landmarks = front_results.pose_landmarks.landmark
|
||||
leg_landmarks = [
|
||||
pose.PoseLandmark.LEFT_HIP,
|
||||
pose.PoseLandmark.LEFT_KNEE,
|
||||
pose.PoseLandmark.LEFT_ANKLE,
|
||||
]
|
||||
hand_landmarks = [
|
||||
pose.PoseLandmark.LEFT_SHOULDER,
|
||||
pose.PoseLandmark.LEFT_ELBOW,
|
||||
pose.PoseLandmark.LEFT_WRIST,
|
||||
]
|
||||
self.landmarks_to_calculate = leg_landmarks + hand_landmarks
|
||||
# self.landmarks_to_calculate = [
|
||||
# pose.PoseLandmark.LEFT_SHOULDER,
|
||||
# pose.PoseLandmark.LEFT_ELBOW,
|
||||
# pose.PoseLandmark.LEFT_WRIST,
|
||||
# ]
|
||||
landmark_names = self.measurements[measurement_name]
|
||||
|
||||
table = []
|
||||
for idx, l in enumerate(self.landmarks_to_calculate):
|
||||
if idx < len(self.landmarks_to_calculate) - 1:
|
||||
_current = landmarks[l.value]
|
||||
_nextl = self.landmarks_to_calculate[idx + 1]
|
||||
_next = landmarks[_nextl.value]
|
||||
pixel_distance = self.euclidean_distance(
|
||||
_current.x * self.resized_width,
|
||||
_current.y * self.resized_height,
|
||||
_next.x * self.resized_width,
|
||||
_next.y * self.resized_height)
|
||||
real_distance = pixel_distance * self.pixel_to_metric_ratio()
|
||||
table.append([l.name, _nextl.name, real_distance])
|
||||
|
||||
output = tabulate(
|
||||
table,
|
||||
headers=["Landmark 1", "Landmark 2", "Distance (cm)"],
|
||||
tablefmt="grid")
|
||||
print(output)
|
||||
|
||||
|
||||
# for l in self.landmarks_to_calculate:
|
||||
# real_distance = 0
|
||||
# for idx, l in enumerate(self.landmarks_to_calculate):
|
||||
# if idx < len(self.landmarks_to_calculate) - 1:
|
||||
# _current = landmarks[l.value]
|
||||
# _nextl = self.landmarks_to_calculate[idx + 1]
|
||||
# _next = landmarks[_nextl.value]
|
||||
# pixel_distance = self.euclidean_distance(
|
||||
# _current.x * self.resized_width,
|
||||
# _current.y * self.resized_height,
|
||||
# _next.x * self.resized_width,
|
||||
# _next.y * self.resized_height,
|
||||
# )
|
||||
# real_distance += pixel_distance * self.pixel_to_metric_ratio(
|
||||
# )
|
||||
# print(real_distance)
|
||||
# self.distances[l.name] = real_distance
|
||||
#
|
||||
total_distance = 0
|
||||
for idx in range(len(landmark_names) - 1):
|
||||
_current = landmarks[landmark_names[idx]]
|
||||
_next = landmarks[landmark_names[idx + 1]]
|
||||
pixel_distance = self.euclidean_distance(
|
||||
_current.x * self.resized_width,
|
||||
_current.y * self.resized_height,
|
||||
_next.x * self.resized_width,
|
||||
_next.y * self.resized_height,
|
||||
)
|
||||
real_distance = pixel_distance * self.pixel_to_metric_ratio()
|
||||
total_distance += real_distance
|
||||
return total_distance
|
||||
|
||||
def euclidean_distance(self, x1, y1, x2, y2):
|
||||
distance = math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
|
||||
distance = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
||||
return distance
|
||||
|
||||
def destroy(self):
|
||||
cv2.destroyAllWindows()
|
||||
|
||||
def display_images(self):
|
||||
cv2.imshow("front_image_keypoints", self.front_image_keypoints)
|
||||
cv2.imshow("side_image_keypoints", self.side_image_keypoints)
|
||||
cv2.imshow("edges", self.edges)
|
||||
cv2.waitKey(0)
|
||||
|
||||
def get_center_top_point(self, side_results):
|
||||
gray_image = cv2.cvtColor(self.side_image_keypoints,
|
||||
cv2.COLOR_BGR2GRAY)
|
||||
gray_image = cv2.cvtColor(
|
||||
self.side_image_keypoints,
|
||||
cv2.COLOR_BGR2GRAY,
|
||||
)
|
||||
blurred_image = cv2.GaussianBlur(gray_image, (5, 5), 0)
|
||||
roi = blurred_image[0:int(self.side_image_resized.shape[0] / 2), :]
|
||||
self.edges = cv2.Canny(roi, 50, 150)
|
||||
contours, _ = cv2.findContours(self.edges, cv2.RETR_EXTERNAL,
|
||||
cv2.CHAIN_APPROX_SIMPLE)
|
||||
roi = blurred_image[
|
||||
0 : int(self.side_image_resized.shape[0] / 2),
|
||||
:,
|
||||
]
|
||||
edges = cv2.Canny(roi, 50, 150)
|
||||
contours, _ = cv2.findContours(
|
||||
edges,
|
||||
cv2.RETR_EXTERNAL,
|
||||
cv2.CHAIN_APPROX_SIMPLE,
|
||||
)
|
||||
xt, yt = None, None
|
||||
self.topmost_point = None
|
||||
topmost_point = None
|
||||
|
||||
if contours:
|
||||
largest_contour = max(contours, key=cv2.contourArea)
|
||||
self.topmost_point = tuple(
|
||||
largest_contour[largest_contour[:, :, 1].argmin()][0])
|
||||
xt, yt = self.topmost_point
|
||||
self.circle(self.side_image_keypoints, xt, yt)
|
||||
largest_contour = max(
|
||||
contours,
|
||||
key=cv2.contourArea,
|
||||
)
|
||||
topmost_point = tuple(largest_contour[largest_contour[:, :, 1].argmin()][0])
|
||||
xt, yt = topmost_point
|
||||
|
||||
cv2.circle(
|
||||
self.side_image_keypoints,
|
||||
(xt, yt),
|
||||
2,
|
||||
(255, 255, 0),
|
||||
-1,
|
||||
)
|
||||
|
||||
logging.warning("xt: %s", xt)
|
||||
logging.warning("yt: %s", yt)
|
||||
xc, yc = None, None
|
||||
landmarks = side_results.pose_landmarks.landmark
|
||||
|
||||
if side_results.pose_landmarks:
|
||||
left_hip = landmarks[pose.PoseLandmark.LEFT_HIP.value]
|
||||
right_hip = landmarks[pose.PoseLandmark.RIGHT_HIP.value]
|
||||
left_hip = landmarks[LANDMARK_NAME_TO_INDEX["left_hip"]]
|
||||
right_hip = landmarks[LANDMARK_NAME_TO_INDEX["right_hip"]]
|
||||
center_point = (
|
||||
(left_hip.x + right_hip.x) / 2,
|
||||
(left_hip.y + right_hip.y) / 2,
|
||||
@ -276,23 +331,26 @@ class Landmarker:
|
||||
int(center_point[1] * self.side_image_resized.shape[0]),
|
||||
)
|
||||
xc, yc = center_point
|
||||
logging.warning("xc: %s", xc)
|
||||
logging.warning("yc: %s", yc)
|
||||
self.circle(self.side_image_keypoints, xc, yc)
|
||||
self.circle(
|
||||
self.side_image_keypoints,
|
||||
xc,
|
||||
yc,
|
||||
)
|
||||
|
||||
self.pixel_distance = self.euclidean_distance(xc, yc, xt, yt)
|
||||
logging.warning("top_center_pixel_distance: %s",
|
||||
self.pixel_distance)
|
||||
logging.debug(
|
||||
"top_center_pixel_distance: %s",
|
||||
self.pixel_distance,
|
||||
)
|
||||
self.pixel_height = self.pixel_distance * 2
|
||||
logging.warning("pxl height: %s ", self.pixel_height)
|
||||
self.distance = (self.euclidean_distance(xc, yc, xt, yt) *
|
||||
self.pixel_to_metric_ratio())
|
||||
logging.debug(
|
||||
"pixel height: %s ",
|
||||
self.pixel_height,
|
||||
)
|
||||
self.distance = self.euclidean_distance(xc, yc, xt, yt) * self.pixel_to_metric_ratio()
|
||||
|
||||
return self.distance
|
||||
|
||||
l = Landmarker()
|
||||
try:
|
||||
l.run()
|
||||
except:
|
||||
print("error")
|
||||
finally:
|
||||
l.destroy()
|
||||
if __name__ == "__main__":
|
||||
landmarker = Landmarker()
|
||||
landmarker.run()
|
||||
|
3
pyproject.toml
Normal file
3
pyproject.toml
Normal file
@ -0,0 +1,3 @@
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
|
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
||||
mediapipe==0.10.13
|
||||
tabulate==0.9.0
|
||||
opencv-python-headless==4.10.0.84
|
||||
pyyaml==6.0.1
|
Loading…
x
Reference in New Issue
Block a user