Browse Source

add text analyze.

lyc8503 4 years ago
parent
commit
3b999977eb
1 changed files with 345 additions and 6 deletions
  1. 345 6
      backend_refactor/service/photo/StandingPhotoAnalyzer.py

+ 345 - 6
backend_refactor/service/photo/StandingPhotoAnalyzer.py

@@ -1,3 +1,6 @@
+import math
+from typing import Callable
+
 from service.photo import BasePhotoAnalyzer
 
 import cv2
@@ -15,17 +18,349 @@ from service.photo.src1.torch_openpose import torch_openpose
 import service.photo.src1 as src1
 
 
+def angle(v1, v2):
+    dx1 = v1[2] - v1[0]
+    dy1 = v1[3] - v1[1]
+    dx2 = v2[2] - v2[0]
+    dy2 = v2[3] - v2[1]
+    angle1 = math.atan2(dy1, dx1)
+    angle1 = round(angle1 * 180.0 / math.pi, 2)
+    angle2 = math.atan2(dy2, dx2)
+    angle2 = round(angle2 * 180.0 / math.pi, 2)
+    if angle1 * angle2 >= 0:
+        included_angle = abs(angle1 - angle2)
+    else:
+        included_angle = abs(angle1) + abs(angle2)
+        if included_angle > 180:
+            included_angle = 360 - included_angle
+    return included_angle
+
+
+def angle_for_xo(v1, v2):
+    dx1 = v1[2] - v1[0]
+    dy1 = v1[3] - v1[1]
+    dx2 = v2[2] - v2[0]
+    dy2 = v2[3] - v2[1]
+    angle1 = math.atan2(dy1, dx1)
+    angle1 = round(angle1 * 180.0 / math.pi, 2)
+    angle2 = math.atan2(dy2, dx2)
+    angle2 = round(angle2 * 180.0 / math.pi, 2)
+    return angle1 - angle2
+
+
+def cal_head_extra_stress(angle):
+    HEAD_STRESS_ARGUMENT_0 = 11
+    HEAD_STRESS_ARGUMENT_1 = 1.02777777777769
+    HEAD_STRESS_ARGUMENT_2 = 0.00981481481482367
+    HEAD_STRESS_ARGUMENT_3 = -0.000567901234568158
+    HEAD_STRESS_ARGUMENT_4 = 0.00000576131687243021
+    return (HEAD_STRESS_ARGUMENT_1 * angle + HEAD_STRESS_ARGUMENT_2 * pow(angle, 2) \
+            + HEAD_STRESS_ARGUMENT_3 * pow(angle, 3) + HEAD_STRESS_ARGUMENT_4 * pow(angle, 4)) / HEAD_STRESS_ARGUMENT_0
+
+
+def analyse_npy_front(npy_front, height):
+    levels = {
+        # (level, threshold)
+        "head_angle": [
+            (0, 4), (1, 10), (2, 20)
+        ],
+        "shoulder_angle": [
+            (0, 2.8), (1, 8), (2, 15)
+        ],
+        "shoulder_offset": [
+            (0, 3), (1, 8), (2, 15)
+        ],
+        "hip_angle": [
+            (0, 2.19), (1, 8), (2, 15)
+        ],
+        "hip_offset": [
+            (0, 2.19), (1, 8), (2, 15)
+        ],
+        "leg_angle": 4
+    }
+
+    result = []
+    data = np.load(npy_front)[0]
+
+    result.append("------------------")
+    result.append("正面分析内容如下:")
+
+    # h-eye - h-elbow
+    h_pixel_eye2elbow = abs(data[15][1] - data[3][1])
+
+    h_real_eye2elbow = float(height) * 0.333
+
+    # body mid x
+    ankle_mid_x = (data[14][0] + data[11][0]) / 2
+
+    # for head
+    ear_r_x = data[17][0]
+    ear_r_y = data[17][1]
+    ear_l_x = data[18][0]
+    ear_l_y = data[18][1]
+
+    head_angle = angle([ear_r_x, ear_r_y, ear_l_x, ear_l_y], [0, 0, 1, 0])
+    head_front_level = 0
+    for level in levels["head_angle"]:
+        head_front_level = level[0]
+        if head_angle <= level[1]:
+            break
+
+    head_direction = None
+    if ear_l_y < ear_l_y:
+        head_direction = "左"
+    elif ear_l_y > ear_r_y:
+        head_direction = "右"
+    if head_front_level > 0 and head_direction:
+        result.append("头部分析:头部{}部高,向右倾斜,角度为{}度".format(head_direction, round(head_angle, 2)))
+    else:
+        result.append("头部分析:头部保持较好平衡")
+    result.append(head_front_level)
+
+    # for shoulder
+    shoulder_r_x = data[2][0]
+    shoulder_r_y = data[2][1]
+    shoulder_l_x = data[5][0]
+    shoulder_l_y = data[5][1]
+    shoulder_mid_x = (shoulder_l_x + shoulder_r_x) / 2
+    neck_x = data[1][0] - 2
+    shoulder_offset = (shoulder_mid_x - neck_x) * h_real_eye2elbow / h_pixel_eye2elbow
+    shoulder_angle = angle([shoulder_r_x, shoulder_r_y, shoulder_l_x, shoulder_l_y], [0, 0, 1, 0])
+    shoulder_angle = min(shoulder_angle, abs(shoulder_angle - 3))
+    shoulder_angle_level = 0
+    shoulder_offset_level = 0
+    for level in levels["shoulder_angle"]:
+        shoulder_angle_level = level[0]
+        if shoulder_angle <= level[1]:
+            break
+    for level in levels["shoulder_offset"]:
+        shoulder_offset_level = level[0]
+        if shoulder_offset <= level[1]:
+            break
+    if shoulder_offset_level == 0:
+        shoulder_level = shoulder_angle_level
+    elif shoulder_angle_level == 0:
+        shoulder_level = shoulder_offset_level
+    else:
+        shoulder_level = round(0.6 * shoulder_angle_level + 0.4 * shoulder_offset_level)
+
+    result.append("肩部分析:")
+    shoulder_offset_direction = None
+    if shoulder_offset > 0:
+        shoulder_offset_direction = "左"
+    elif shoulder_offset < 0:
+        shoulder_offset_direction = "右"
+    shoulder_angle_direction = None
+    if shoulder_l_y < shoulder_r_y:
+        shoulder_angle_direction = "右"
+    elif shoulder_l_y > shoulder_r_y:
+        shoulder_angle_direction = "左"
+    if shoulder_offset_direction:
+        result.append("肩部向{}横移,距离为{}cm".format(shoulder_offset_direction, round(abs(shoulder_offset), 2)))
+    else:
+        result.append("肩部中心位居中轴,位置良好")
+
+    if shoulder_angle_direction:
+        result.append("肩部向{}倾斜角度为{}度".format(shoulder_angle_direction, round(shoulder_angle, 2)))
+    else:
+        result.append("肩部无左右倾斜")
+    result.append(shoulder_level)
+
+    # for hip
+    hip_r_x = data[9][0]
+    hip_r_y = data[9][1]
+    hip_l_x = data[12][0]
+    hip_l_y = data[12][1]
+    hip_mid_x = (hip_r_x + hip_l_x) / 2
+    hip_offset = (hip_mid_x - ankle_mid_x) * h_real_eye2elbow / h_pixel_eye2elbow
+    hd_hip = abs(data[12][1] - data[9][1])
+    d_hip = math.sqrt((hip_l_x - hip_r_x) ** 2 + (hip_l_y - hip_r_y) ** 2)
+    rate_hip = hd_hip / d_hip
+    hip_angle = angle([hip_r_x, hip_r_y, hip_l_x, hip_l_y], [0, 0, 1, 0])
+    hip_angle_level = 0
+    hip_offset_level = 0
+    for level in levels["hip_angle"]:
+        hip_angle_level = level[0]
+        if hip_angle <= level[1]:
+            break
+    for level in levels["hip_offset"]:
+        hip_offset_level = level[0]
+        if hip_offset <= level[1]:
+            break
+    result.append("髋部分析:")
+    hip_offset_direction = None
+    if hip_offset > 0:
+        hip_offset_direction = "左"
+    elif hip_offset < 0:
+        hip_offset_direction = "右"
+    hip_angle_direction = None
+    if hip_l_y < hip_r_y:
+        hip_angle_direction = "右"
+    elif hip_l_y > hip_r_y:
+        hip_angle_direction = "左"
+    if hip_offset_direction:
+        result.append("髋部向{}横移,距离为{}cm".format(hip_offset_direction, round(abs(hip_offset), 2)))
+    else:
+        result.append("髋部中心位居中轴,位置良好")
+
+    if hip_angle_direction:
+        result.append("髋部向{}倾斜角度为{}度".format(hip_angle_direction, round(hip_angle, 2)))
+    else:
+        result.append("髋部角度保持较好平衡")
+    hip_level = round(0.6 * hip_offset_level + 0.4 * hip_angle_level)
+    result.append(hip_level)
+
+    result.append("腿型分析:")
+
+    # for knee distance
+    knee_r_x = data[10][0]
+    knee_r_y = data[10][1]
+    knee_l_x = data[13][0]
+    knee_l_y = data[13][1]
+
+    ankle_r_x = data[11][0]
+    ankle_r_y = data[11][1]
+    ankle_l_x = data[14][0]
+    ankle_l_y = data[14][1]
+
+    xo_l_angle = angle_for_xo([hip_l_x, hip_l_y, knee_l_x, knee_l_y], [hip_l_x, hip_l_y, ankle_l_x, ankle_l_y])
+    xo_r_angle = angle_for_xo([hip_r_x, hip_r_y, knee_r_x, knee_r_y], [hip_r_x, hip_r_y, ankle_r_x, ankle_r_y])
+    xo_angle = max(abs(xo_l_angle), abs(xo_r_angle))
+    if xo_angle > levels["leg_angle"]:
+        xo_direction = "X" if xo_l_angle < 0 and xo_l_angle + xo_r_angle < 0 else "O"
+        result.append("腿型可能为{}型腿".format(xo_direction))
+    else:
+        result.append("腿型基本正常,非X、O型腿")
+    return result
+
+
+def analyse_npy_side(npy_side, height):
+    levels = {
+        "head_forward_angle": [
+            (0, 4.57), (1, 8), (2, 15)
+        ],
+        "up_forward_angle": [
+            (0, 5), (1, 10), (2, 15)
+        ],
+        "hip_forward_offset": [
+            (0, 3), (1, 5), (2, 8)
+        ],
+        "knee_forward_angle": [
+            (0, 3.86), (1, 8), (2, 13)
+        ]
+    }
+
+    result = []
+    data = np.load(npy_side)[0]
+    h_pixel_eye2elbow = abs(data[15][1] - data[3][1])
+
+    h_real_eye2elbow = float(height) * 0.333
+    result.append(" ")
+    result.append("------------------")
+    result.append("侧面分析内容如下:")
+
+    # for head
+    ear_r_x = data[17][0]
+    ear_r_y = data[17][1]
+    shoulder_r_x = data[2][0]
+    shoulder_r_y = data[2][1]
+    head_forward_angle = angle([shoulder_r_x, shoulder_r_y, ear_r_x, ear_r_y, ], [0, 0, 0, -1])
+    head_forward_level = 0
+    head_extra_stress = round(cal_head_extra_stress(head_forward_angle), 2) if 0 < head_forward_angle < 90 else 0
+    for level in levels["head_forward_angle"]:
+        head_forward_level = level[0]
+        if head_forward_angle <= level[1]:
+            break
+    head_forward_direction = None
+    if ear_r_x > shoulder_r_x:
+        head_forward_direction = "前倾"
+    elif ear_r_x < shoulder_r_x:
+        head_forward_direction = "后仰"
+    if head_forward_direction:
+        result.append("头部{}角度为{}度,颈椎额外承受压力为头部重量{}倍".format(head_forward_direction,
+                                                                               round(head_forward_angle, 2),
+                                                                               round(head_extra_stress, 2)))
+    else:
+        result.append("无头部前倾问题")
+    result.append(head_forward_level)
+
+    # upper part of body
+    neck_x = data[1][0]
+    neck_y = data[1][1]
+
+    ankle_r_x = data[11][0]
+    ankle_r_y = data[11][1]
+    up_angle = round(angle([ankle_r_x, ankle_r_y, neck_x, neck_y], [0, 0, 0, -1]), 2)
+    up_angle = min(up_angle, abs(up_angle - 3))
+    up_level = 0
+    for level in levels["up_forward_angle"]:
+        up_level = level[0]
+        if up_angle <= level[1]:
+            break
+    up_forward_direction = "后仰" if neck_x < ankle_r_x else "前倾"
+    up_state = "身体{}{}度".format(up_forward_direction, round(up_angle, 2))
+    result.append("站姿分析:{}".format(up_state))
+    result.append(up_level)
+
+    # for hip
+    hip_risk_level = 0
+    hip_r_x = data[9][0] - 2
+    hip_r_y = data[9][1]
+    knee_r_x = data[10][0]
+    knee_r_y = data[10][1]
+    hip_offset = abs((hip_r_x - ankle_r_x)) * h_real_eye2elbow / h_pixel_eye2elbow
+    for level in levels["hip_forward_offset"]:
+        hip_risk_level = level[0]
+        if hip_offset <= level[1]:
+            break
+    hip_forward_direction = None
+    if hip_r_x > ankle_r_x:
+        hip_forward_direction = "前移"
+    elif hip_r_x < ankle_r_x:
+        hip_forward_direction = "后移"
+    if hip_forward_direction:
+        hip_state = "髋关节{}{}cm".format(hip_forward_direction, round(hip_offset, 2))
+    else:
+        hip_state = "髋关节正常"
+
+    result.append("髋部分析:{}".format(hip_state))
+
+    result.append(hip_risk_level)
+
+    # for knee
+    knee_angle_r = 180 - angle([hip_r_x, hip_r_y, knee_r_x, knee_r_y], [knee_r_x, knee_r_y, ankle_r_x, ankle_r_y])
+    if knee_angle_r > 90:
+        knee_angle_r = 180 - knee_angle_r
+    knee_angle_r -= 3
+    knee_forward_risk_level = 0
+    for level in levels["knee_forward_angle"]:
+        knee_forward_risk_level = level[0]
+        if knee_angle_r <= level[1]:
+            break
+
+    if knee_forward_risk_level == 0:
+        result.append("膝盖角度分析:膝盖无明显超伸")
+    else:
+        result.append("膝盖角度分析:下肢膝盖超伸角度为{}度".format(round(knee_angle_r, 2)))
+    result.append(knee_forward_risk_level)
+    return result
+
+
 class StandingPhotoAnalyzer(BasePhotoAnalyzer):
 
     def __init__(self, *args):
         super().__init__()
-        assert len(self.args) == 2, "Invalid arguments: expect 2 photo uuids."
-        self.args = args
+        assert len(self.args) == 3, "Invalid arguments: expect 2 photo uuids and height."
+        self.args = args[:2]
+        self.height = args[2]
 
-    def analyze(self, callback: function) -> None:
+    def analyze(self, callback: Callable) -> None:
         callback("RUNNING")
         try:
-            for i in self.args:
+            photos = []
+            npys = []
+
+            for index, i in enumerate(self.args):
                 test_image = UPLOAD_DIR + '{}.jpg'.format(i)
 
                 ori_img = cv2.imread(test_image)  # B,G,R order
@@ -33,13 +368,17 @@ class StandingPhotoAnalyzer(BasePhotoAnalyzer):
                 tp = torch_openpose('body_25')
                 poses = tp(ori_img)
 
-                np.save(UPLOAD_DIR + '{}.npy'.format(i), poses)
+                if index == 0:
+                    npys.append(analyse_npy_front(poses, self.height))
+                else:
+                    npys.append(analyse_npy_side(poses, self.height))
 
                 canvas = copy.deepcopy(ori_img)
                 canvas = src1.util.draw_bodypose(canvas, poses, 'body_25')
 
                 cv2.imwrite(UPLOAD_DIR + '{}-result.jpg'.format(i), canvas)
-                callback("FINISH", result={"photos": {}, "text": {}})
+
+            callback("FINISHED", result={"photos": {"front": photos[0], "right": photos[1]}, "text": {"front": npys[0], "right": npys[1]}})
         except Exception as e:
             callback("ERROR", error=e)
             raise e