瀏覽代碼

update backend

dongyuanjushi 4 年之前
父節點
當前提交
3027b3ab25

+ 32 - 0
.gitignore

@@ -0,0 +1,32 @@
+.DS_Store
+/frontend/node_modules
+/frontend/dist
+
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# Editor directories and files
+.idea
+/frontend/.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+/backend/video/
+/backend/pose_temp_data/
+/backend/pose_source_images/
+/backend/pose_processed_txt/
+/backend/pose_processed_images/
+/backend/model/
+/backend/capture_image/
+/backend/images/
+/backend/demo.mp4

+ 0 - 72
LICENSE

@@ -1,72 +0,0 @@
-Apache License 
-Version 2.0, January 2004 
-http://www.apache.org/licenses/
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
-"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
-
-"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
-
-"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
-
-"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
-
-"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
-
-"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
-
-"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
-
-"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
-
-"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
-
-"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
-
-2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
-
-3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
-
-4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
-
-(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
-
-(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
-
-(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
-
-(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
-
-You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
-
-5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
-
-6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
-
-8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work.
-
-To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
-
-Copyright [yyyy] [name of copyright owner]
-
-Licensed under the Apache License, Version 2.0 (the "License"); 
-you may not use this file except in compliance with the License. 
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software 
-distributed under the License is distributed on an "AS IS" BASIS, 
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-See the License for the specific language governing permissions and 
-limitations under the License.

+ 15 - 0
backend/Dockerfile

@@ -0,0 +1,15 @@
+FROM python:3.7
+WORKDIR /home/seecoder/Documents/pose-correction/pc-demo
+
+COPY requirements.txt ./
+RUN pip install -r requirements.txt  -i https://pypi.tuna.tsinghua.edu.cn/simple
+RUN pip install PyMySQL
+RUN sed -i 's/ports.ubuntu.com/mirror.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list
+
+RUN apt update
+RUN apt install -y  libgl1-mesa-glx
+COPY . .
+
+#RUN python db.py
+
+CMD ["gunicorn", "flask_api:app", "-c", "./gunicorn.conf.py"]

+ 425 - 0
backend/analyse_by_npy.py

@@ -0,0 +1,425 @@
+import numpy as np
+import cv2 as cv
+import math
+import sys
+from demo_func import run_openpose_for_image_front,run_openpose_for_image_side
+
+sys.path.append('../')
+
+index = sys.argv[1]
+height = sys.argv[2]
+
+print(height)
+
+level_0 = 2
+level_1 = 10
+level_2 = 20
+
+O_ARGUMENT_1 = 0.2
+X_ARGUMENT_1 = 1.1
+
+HEAD_FORWARD_ARGUMENT_0 = 2
+HEAD_FORWARD_ARGUMENT_1 = 8
+HEAD_FORWARD_ARGUMENT_2 = 15
+
+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
+
+SHOULDER_OFFSET_ARGUMENT_0 = 0.5
+SHOULDER_OFFSET_ARGUMENT_1 = 1
+SHOULDER_OFFSET_ARGUMENT_2 = 3
+
+HIP_OFFSET_ARGUMENT_0 = 0.5
+HIP_OFFSET_ARGUMENT_1 = 1
+HIP_OFFSET_ARGUMENT_2 = 3
+
+HIP_OFFSET_ARGUMENT_0 = 0.5
+HIP_OFFSET_ARGUMENT_1 = 1
+HIP_OFFSET_ARGUMENT_2 = 3
+
+
+UP_FORWARD_ARGUMENT_0 = 2
+UP_FORWARD_ARGUMENT_1 = 10
+UP_FORWARD_ARGUMENT_2 = 20
+
+HIP_FORWARD_ARGUMENT_0 = 2
+HIP_FORWARD_ARGUMENT_1 = 5
+HIP_FORWARD_ARGUMENT_2 = 10
+
+KNEE_FORWARD_ARGUMENT_0 = 1
+KNEE_FORWARD_ARGUMENT_1 = 5
+KNEE_FORWARD_ARGUMENT_2 = 10
+
+
+
+
+source_npy_front = "pose_temp_data/{}-0.npy".format(index)
+source_npy_side = "pose_temp_data/{}-1.npy".format(index)
+save_path = "pose_processed_txt/{}.txt".format(index)
+
+def text_save(filename, data):#filename为写入CSV文件的路径,data为要写入数据列表.
+    file = open(filename,'w',encoding='utf-8')
+    for i in range(len(data)):
+        s = str(data[i]).replace('[','').replace(']','')
+        s = s.replace("'",'').replace(',','') +'\n'
+        file.write(s)
+    file.close()
+    print("保存成功")
+
+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)
+  # print(angle1)
+  angle2 = math.atan2(dy2, dx2)
+  angle2 = round(angle2 * 180.0/math.pi,2)
+  # print(angle2)
+  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 cal_head_extra_stress(angle):
+    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):
+
+
+    result = []
+    data = np.load(npy_front)
+
+    print("------------------")
+    result.append("------------------")
+
+    print("正面分析内容如下:")
+    result.append("正面分析内容如下:")
+
+
+
+    # h-eye - h-elbow
+    h_pixel_eye2elbow = abs(data[14][1]-data[3][1])
+    #print("h_pixel{}".format(h_pixel_eye2elbow))
+
+    h_real_eye2elbow = float(height) * 0.333
+    #print("h_real{}".format(h_real_eye2elbow))
+
+    # body mid x
+    ankle_mid_x = (data[13][0] + data[10][0])/2
+
+
+    # for head
+    ear_r_x = data[16][0]
+    ear_r_y = data[16][1]
+    ear_l_x = data[17][0]
+    ear_l_y = data[17][1]
+
+    head_angle = angle([ear_r_x, ear_r_y, ear_l_x, ear_l_y], [0, 0, 1, 0])
+    ear_level = ""
+    if head_angle <= level_0:
+        ear_level = ""
+    elif head_angle <= level_1 and head_angle > level_0 :
+        ear_level = "轻微"
+    elif head_angle > level_1 and head_angle <= level_2 :
+        ear_level = "中等"
+    else:
+        ear_level = "严重"
+
+
+    if ear_l_y < ear_r_y and ear_level != "":
+        print("头部分析:头部左部高,向右倾斜,角度为{}度,程度{}".format(head_angle,ear_level))
+        result.append("头部分析:头部左部高,向右倾斜,角度为{}度,程度{}".format(head_angle,ear_level))
+    elif ear_l_y > ear_r_y and ear_level != "":
+        print("头部分析:头部右部高,向左倾斜,角度为{}度,程度{}".format(head_angle,ear_level))
+        result.append("头部分析:头部右部高,向左倾斜,角度为{}度,程度{}".format(head_angle,ear_level))
+    else:
+        print("头部分析:头部保持完美平衡")
+        result.append("头部分析:头部保持完美平衡")
+
+    # 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
+    offset_x_shoulder = (shoulder_mid_x - ankle_mid_x)*h_real_eye2elbow/h_pixel_eye2elbow
+    d_shoulder = abs(data[5][1] - data[2][1])
+    l_shoulder = math.sqrt((shoulder_l_x - shoulder_r_x) ** 2 + (shoulder_l_y - shoulder_r_y) ** 2)
+    rate_shoulder = d_shoulder/l_shoulder
+    shoulder_angle = angle([shoulder_r_x, shoulder_r_y, shoulder_l_x, shoulder_l_y], [0, 0, 1, 0])
+    shoulder_level = ""
+    shoulder_offset_level = ""
+    if shoulder_angle <= level_0:
+        shoulder_level = ""
+    elif shoulder_angle <= level_1 and shoulder_angle > level_0:
+        shoulder_level = "轻微"
+    elif shoulder_angle > level_1 and shoulder_angle <= level_2 :
+        shoulder_level = "中等"
+    else:
+        shoulder_level = "严重"
+
+    if offset_x_shoulder <= SHOULDER_OFFSET_ARGUMENT_0:
+        shoulder_offset_level = ""
+    elif offset_x_shoulder <= SHOULDER_OFFSET_ARGUMENT_1 and offset_x_shoulder > SHOULDER_OFFSET_ARGUMENT_0:
+        shoulder_offset_level = "轻微"
+    elif offset_x_shoulder > SHOULDER_OFFSET_ARGUMENT_1 and offset_x_shoulder <= SHOULDER_OFFSET_ARGUMENT_2 :
+        shoulder_offset_level = "中等"
+    else:
+        shoulder_offset_level = "严重"
+
+    print("肩部分析:")
+    result.append("肩部分析:")
+    if offset_x_shoulder > 0  and shoulder_offset_level != "":
+        print("肩部向左横移,距离为{}cm,程度{}".format(round(abs(offset_x_shoulder),3),shoulder_offset_level))
+        result.append("肩部向左横移,距离为{}cm,程度{}".format(round(abs(offset_x_shoulder),3),shoulder_offset_level))
+    elif offset_x_shoulder < 0 and shoulder_offset_level != "":
+        print("肩部向右横移,距离为{}cm,程度{}".format(round(abs(offset_x_shoulder),3),shoulder_offset_level))
+        result.append("肩部向右横移,距离为{}cm,程度{}".format(round(abs(offset_x_shoulder),3),shoulder_offset_level))
+    else:
+        print("肩部中心位居中轴,位置良好")
+        result.append("肩部中心位居中轴,位置良好")
+
+    if shoulder_l_y < shoulder_r_y and shoulder_level != "":
+        print("肩部左部高,向右倾斜,角度为{}度,程度{},高度差异与肩长比值为{}%".format(shoulder_angle,shoulder_level,round(rate_shoulder*100,2)))
+        result.append("肩部左部高,向右倾斜,角度为{}度,程度{},高度差异与肩长比值为{}%".format(shoulder_angle,shoulder_level,round(rate_shoulder*100,2)))
+    elif ear_l_y > ear_r_y and shoulder_level != "":
+        print("肩部右部高,向左倾斜,角度为{}度,程度{},高度差异与肩长比值为{}%".format(shoulder_angle,shoulder_level,round(rate_shoulder*100,2)))
+        result.append("肩部右部高,向左倾斜,角度为{}度,程度{},高度差异与肩长比值为{}%".format(shoulder_angle,shoulder_level,round(rate_shoulder*100,2)))
+    else:
+        print("肩部角度保持完美平衡")
+        result.append("肩部角度保持完美平衡")
+
+    # for hip
+    hip_r_x = data[8][0]
+    hip_r_y = data[8][1]
+    hip_l_x = data[11][0]
+    hip_l_y = data[11][1]
+    hip_mid_x = (hip_r_x + hip_l_x)/2
+    offset_x_hip = (hip_mid_x - ankle_mid_x)*h_real_eye2elbow/h_pixel_eye2elbow
+    hd_hip = abs(data[11][1] - data[8][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_level = ""
+    hip_offset_level = ""
+    if hip_angle <= level_0:
+        hip_level = ""
+    elif hip_angle <= level_1 and hip_angle > level_0:
+        hip_level = "轻微"
+    elif hip_angle > level_1 and hip_angle <= level_2 :
+        hip_level = "中等"
+    else:
+        hip_level = "严重"
+
+    if offset_x_hip <= HIP_OFFSET_ARGUMENT_0:
+        hip_offset_level = ""
+    elif offset_x_hip <= HIP_OFFSET_ARGUMENT_1 and offset_x_hip > HIP_OFFSET_ARGUMENT_0:
+        hip_offset_level = "轻微"
+    elif offset_x_hip > HIP_OFFSET_ARGUMENT_1 and offset_x_hip <= HIP_OFFSET_ARGUMENT_2 :
+        hip_offset_level = "中等"
+    else:
+        hip_offset_level = "严重"
+    print("髋部分析:")
+    result.append("髋部分析:")
+
+    if offset_x_hip > 0  and hip_offset_level != "":
+        print("髋部向左横移,距离为{}cm,程度{}".format(round(abs(offset_x_hip),3),hip_offset_level))
+        result.append("髋部向左横移,距离为{}cm,程度{}".format(round(abs(offset_x_hip),3),hip_offset_level))
+    elif offset_x_hip < 0 and hip_offset_level != "":
+        print("髋部向右横移,距离为{}cm,程度{}".format(round(abs(offset_x_hip),3),hip_offset_level))
+        result.append("髋部向右横移,距离为{}cm,程度{}".format(round(abs(offset_x_hip),3),hip_offset_level))
+    else:
+        print("髋部中心位居中轴,位置良好")
+        result.append("髋部中心位居中轴,位置良好")
+
+    if hip_l_y < hip_r_y:
+        print("髋部左部高,向右倾斜,角度为{}度,程度{},高度差异与髋宽比值为{}%".format(hip_angle,hip_level,round(rate_hip*100,2)))
+        result.append("髋部左部高,向右倾斜,角度为{}度,程度{},高度差异与髋宽比值为{}%".format(hip_angle,hip_level,round(rate_hip*100,2)))
+    elif ear_l_y > ear_r_y:
+        print("髋部右部高,向左倾斜,角度为{}度,程度{},高度差异与髋宽比值为{}%".format(hip_angle,hip_level,round(rate_hip*100,2)))
+        result.append("髋部右部高,向左倾斜,角度为{}度,程度{},高度差异与髋宽比值为{}%".format(hip_angle,hip_level,round(rate_hip*100,2)))
+    else:
+        print("髋部角度保持完美平衡")
+        result.append("髋部角度保持完美平衡")
+
+
+    #
+
+    print("腿型分析:")
+    result.append("腿型分析:")
+
+    # for knee distance
+    knee_r_x = data[9][0]
+    knee_r_y = data[9][1]
+    knee_l_x = data[12][0]
+    knee_l_y = data[12][1]
+    d_knee = math.sqrt((knee_l_x - knee_r_x) ** 2 + (knee_l_y - knee_r_y) ** 2)
+    rate_knee = d_knee / d_hip
+    print("双膝间距与髋宽比值为{}%".format(round(rate_knee*100,2)))
+    result.append("双膝间距与髋宽比值为{}%".format(round(rate_knee*100,2)))
+
+
+
+    # XO judgement
+    ankle_r_x = data[10][0]
+    ankle_r_y = data[10][1]
+    ankle_l_x = data[13][0]
+    ankle_l_y = data[13][1]
+    d_ankle = math.sqrt((ankle_l_x - ankle_r_x) ** 2 + (ankle_l_y - ankle_r_y) ** 2)
+    rate_ankle = d_ankle / d_knee
+    print("脚踝间距与双膝间距比值为{}%".format(round(rate_ankle*100,2)))
+    result.append("脚踝间距与双膝间距比值为{}%".format(round(rate_ankle*100,2)))
+
+    if rate_ankle > X_ARGUMENT_1 :
+        print("腿型可能为X型腿")
+        result.append("腿型可能为X型腿")
+    elif rate_knee - rate_ankle > O_ARGUMENT_1 :
+        print("腿型可能为O型腿")
+        result.append("腿型可能为O型腿")
+    else:
+        print("腿型基本正常,非X、O型腿")
+        result.append("腿型基本正常,非X、O型腿")
+
+    return result
+
+def analyse_npy_side(npy_side):
+    result = []
+    data = np.load(npy_side)
+
+    result.append(" ")
+    print("------------------")
+    result.append("------------------")
+    print("侧面分析内容如下:")
+    result.append("侧面分析内容如下:")
+
+
+    # for head
+    ear_r_x = data[len(data)-1][0]
+    ear_r_y = data[len(data)-1][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 = ""
+    head_extra_stress = 0
+
+    if head_forward_angle <90 and head_forward_angle > 0:
+        head_extra_stress = round(cal_head_extra_stress(head_forward_angle),3)
+    if head_forward_angle <= HEAD_FORWARD_ARGUMENT_0:
+        head_forward_level = ""
+    elif head_forward_angle <= HEAD_FORWARD_ARGUMENT_1 and head_forward_angle > HEAD_FORWARD_ARGUMENT_0:
+        head_forward_level = "轻微"
+    elif head_forward_angle > HEAD_FORWARD_ARGUMENT_1 and head_forward_angle <= HEAD_FORWARD_ARGUMENT_2:
+        head_forward_level = "中等"
+    else:
+        head_forward_level = "严重"
+    if ear_r_x > shoulder_r_x and head_forward_level != "":
+        print("颈椎分析:头部前倾,角度为{}度,程度{},颈椎额外承受压力为头部重量{}倍".format(round(head_forward_angle,2),head_forward_level,head_extra_stress))
+        result.append("颈椎分析:头部前倾,角度为{}度,程度{},颈椎额外承受压力为头部重量{}倍".format(round(head_forward_angle,2),head_forward_level,head_extra_stress))
+    elif ear_r_x < shoulder_r_x and head_forward_level != "":
+        print("颈椎分析:头部后仰,角度为{}度,程度{},颈椎额外承受压力为头部重量{}倍".format(round(head_forward_angle,2),head_forward_level,head_extra_stress))
+        result.append("颈椎分析:头部后仰,角度为{}度,程度{},颈椎额外承受压力为头部重量{}倍".format(round(head_forward_angle,2),head_forward_level,head_extra_stress))
+    else:
+        print("无头部前倾问题")
+        result.append("无头部前倾问题")
+
+
+
+    # upper part of body
+    up_risk_level = ""
+    up_state = ""
+    neck_x = data[1][0]
+    neck_y = data[1][1]
+
+    ankle_r_x = data[10][0]
+    ankle_r_y = data[10][1]
+    up_angle =  round(angle([ankle_r_x, ankle_r_y, neck_x, neck_y], [0,0,0,-1]),2)
+    if up_angle >= UP_FORWARD_ARGUMENT_0 and up_angle < UP_FORWARD_ARGUMENT_1:
+        up_risk_level = "轻微"
+    elif up_angle < UP_FORWARD_ARGUMENT_2 and up_angle >= UP_FORWARD_ARGUMENT_1:
+        up_risk_level = "中度"
+    elif up_angle >= UP_FORWARD_ARGUMENT_2:
+        up_risk_level = "严重"
+    else:
+        up_risk_level = ""
+
+    if neck_x < ankle_r_x and up_angle >= HIP_FORWARD_ARGUMENT_0 :
+        up_state = "身体整体后倾,程度{},身体与竖直方向角度为{}度".format(up_risk_level,up_angle)
+    elif neck_x > ankle_r_x and  up_angle >= HIP_FORWARD_ARGUMENT_0:
+        up_state = "身体整体前倾,程度{},身体与竖直方向角度为{}度".format(up_risk_level,up_angle)
+    else:
+        up_state = "身体整体竖直于地面,无明显站姿前后倾斜"
+
+    print("站姿分析:{}".format(up_state))
+    result.append("站姿分析:{}".format(up_state))
+
+    # for hip
+    hip_state = ""
+    hip_risk_level = ""
+    hip_r_x = data[8][0]
+    hip_r_y = data[8][1]
+    knee_r_x = data[9][0]
+    knee_r_y = data[9][1]
+    hip_angle = 180- angle([hip_r_x,hip_r_y,neck_x,neck_y],[hip_r_x,hip_r_y,knee_r_x,knee_r_y])
+    if hip_angle >= 90:
+        hip_angle = 180- hip_angle
+    if  hip_angle >= HIP_FORWARD_ARGUMENT_0 and hip_angle < HIP_FORWARD_ARGUMENT_1 :
+        hip_risk_level = "轻微"
+    elif hip_angle < HIP_FORWARD_ARGUMENT_2 and hip_angle >= HIP_FORWARD_ARGUMENT_1:
+        hip_risk_level = "中度"
+    elif hip_angle >= HIP_FORWARD_ARGUMENT_2:
+        hip_risk_level = "严重"
+    else:
+        hip_risk_level = ""
+    if hip_angle > 90:
+        hip_angle = 180 - hip_angle
+    if knee_r_x > hip_r_x and hip_angle >= HIP_FORWARD_ARGUMENT_0 :
+        hip_state = "髋部后移,程度{},躯干与腿部角度为{}度".format(hip_risk_level,round(180-hip_angle,2))
+    elif knee_r_x < hip_r_x and  hip_angle >= HIP_FORWARD_ARGUMENT_0:
+        hip_state = "髋部前移,程度{},躯干与腿部角度为{}度".format(hip_risk_level,round(180-hip_angle,2))
+    else:
+        hip_state = "无明显髋部前移或后移"
+
+    print("髋部分析:{}".format(hip_state))
+    result.append("髋部分析:{}".format(hip_state))
+
+    # 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_forward_risk_level = ""
+    if knee_angle_r >= KNEE_FORWARD_ARGUMENT_0 and knee_angle_r < KNEE_FORWARD_ARGUMENT_1:
+        knee_forward_risk_level = "轻微"
+    elif knee_angle_r < KNEE_FORWARD_ARGUMENT_2 and knee_angle_r >= KNEE_FORWARD_ARGUMENT_1:
+        knee_forward_risk_level = "中度"
+    elif knee_angle_r >= KNEE_FORWARD_ARGUMENT_2:
+        knee_forward_risk_level = "严重"
+    else:
+        knee_forward_risk_level = ""
+
+    if knee_forward_risk_level == "":
+        print("膝盖角度分析:膝盖无明显超伸")
+        result.append("膝盖角度分析:膝盖无明显超伸")
+    else:
+        print("膝盖角度分析:下肢膝盖超伸角度为{}度,程度{}".format(round(knee_angle_r,2),knee_forward_risk_level))
+        result.append("膝盖角度分析:下肢膝盖超伸角度为{}度,程度{}".format(round(knee_angle_r,2),knee_forward_risk_level))
+
+    return  result
+
+
+run_openpose_for_image_front(index)
+run_openpose_for_image_side(index)
+result_front = analyse_npy_front(source_npy_front)
+result_side = analyse_npy_side(source_npy_side)
+result = result_front + result_side
+text_save(save_path,result)

+ 409 - 0
backend/analyse_func.py

@@ -0,0 +1,409 @@
+# -!- coding: utf-8 -!-
+import numpy as np
+import cv2 as cv
+import math
+import sys
+from demo_func import run_openpose_for_image_front, run_openpose_for_image_side
+
+def text_save(filename, data):
+    file = open(filename, 'w', encoding='utf-8')
+    for i in range(len(data)):
+        s = str(data[i]).replace('[', '').replace(']', '')
+        s = s.replace("'", '').replace(',', '') + '\n'
+        file.write(s)
+    file.close()
+    print("保存成功")
+
+
+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)
+    # print(angle1)
+    angle2 = math.atan2(dy2, dx2)
+    angle2 = round(angle2 * 180.0 / math.pi, 2)
+    # print(angle2)
+    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)
+
+    print("------------------")
+    result.append("------------------")
+
+    print("正面分析内容如下:")
+    result.append("正面分析内容如下:")
+
+    # h-eye - h-elbow
+    h_pixel_eye2elbow = abs(data[14][1] - data[3][1])
+    # print("h_pixel{}".format(h_pixel_eye2elbow))
+
+    h_real_eye2elbow = float(height) * 0.333
+    # print("h_real{}".format(h_real_eye2elbow))
+
+    # body mid x
+    ankle_mid_x = (data[13][0] + data[10][0]) / 2
+
+    # for head
+    ear_r_x = data[16][0]
+    ear_r_y = data[16][1]
+    ear_l_x = data[17][0]
+    ear_l_y = data[17][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:
+        print("头部分析:头部{}部高,向右倾斜,角度为{}度".format(head_direction, round(head_angle,2)))
+        result.append("头部分析:头部{}部高,向右倾斜,角度为{}度".format(head_direction, round(head_angle,2)))
+    else:
+        print("头部分析:头部保持较好平衡")
+        result.append("头部分析:头部保持较好平衡")
+    result.append(head_front_level)
+    print(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("肩部分析:")
+    print("肩部分析:")
+    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:
+        print("肩部向{}横移,距离为{}cm".format(shoulder_offset_direction, round(abs(shoulder_offset), 2)))
+        result.append("肩部向{}横移,距离为{}cm".format(shoulder_offset_direction, round(abs(shoulder_offset), 2)))
+    else:
+        print("肩部中心位居中轴,位置良好")
+        result.append("肩部中心位居中轴,位置良好")
+
+    if shoulder_angle_direction:
+        print("肩部向{}倾斜角度为{}度".format(shoulder_angle_direction, round(shoulder_angle,2)))
+        result.append("肩部向{}倾斜角度为{}度".format(shoulder_angle_direction, round(shoulder_angle,2)))
+    else:
+        print("肩部无左右倾斜")
+        result.append("肩部无左右倾斜")
+    result.append(shoulder_level)
+    print(shoulder_level)
+
+    # for hip
+    hip_r_x = data[8][0]
+    hip_r_y = data[8][1]
+    hip_l_x = data[11][0]
+    hip_l_y = data[11][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[11][1] - data[8][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
+    print("髋部分析:")
+    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:
+        print("髋部向{}横移,距离为{}cm".format(hip_offset_direction, round(abs(hip_offset), 2)))
+        result.append("髋部向{}横移,距离为{}cm".format(hip_offset_direction, round(abs(hip_offset), 2)))
+    else:
+        print("髋部中心位居中轴,位置良好")
+        result.append("髋部中心位居中轴,位置良好")
+
+    if hip_angle_direction:
+        print("髋部向{}倾斜角度为{}度".format(hip_angle_direction, round(hip_angle,2)))
+        result.append("髋部向{}倾斜角度为{}度".format(hip_angle_direction, round(hip_angle,2)))
+    else:
+        print("髋部角度保持较好平衡")
+        result.append("髋部角度保持较好平衡")
+    hip_level = round(0.6 * hip_offset_level + 0.4 * hip_angle_level)
+    print(hip_level)
+    result.append(hip_level)
+
+    #
+
+    print("腿型分析:")
+    result.append("腿型分析:")
+
+    # for knee distance
+    knee_r_x = data[9][0]
+    knee_r_y = data[9][1]
+    knee_l_x = data[12][0]
+    knee_l_y = data[12][1]
+
+    ankle_r_x = data[10][0]
+    ankle_r_y = data[10][1]
+    ankle_l_x = data[13][0]
+    ankle_l_y = data[13][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"
+        print("腿型可能为{}型腿".format(xo_direction))
+        result.append("腿型可能为{}型腿".format(xo_direction))
+    else:
+        print("腿型基本正常,非X、O型腿")
+        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)
+    h_pixel_eye2elbow = abs(data[14][1] - data[3][1])
+    # print("h_pixel{}".format(h_pixel_eye2elbow))
+
+    h_real_eye2elbow = float(height) * 0.333
+    result.append(" ")
+    print("------------------")
+    result.append("------------------")
+    print("侧面分析内容如下:")
+    result.append("侧面分析内容如下:")
+
+    # for head
+    ear_r_x = data[len(data) - 1][0]
+    ear_r_y = data[len(data) - 1][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:
+        print("头部{}角度为{}度,颈椎额外承受压力为头部重量{}倍".format(head_forward_direction, round(head_forward_angle, 2),
+                                                   round(head_extra_stress,2)))
+        result.append("头部{}角度为{}度,颈椎额外承受压力为头部重量{}倍".format(head_forward_direction, round(head_forward_angle, 2),
+                                                           round(head_extra_stress,2)))
+    else:
+        print("无头部前倾问题")
+        result.append("无头部前倾问题")
+    print(head_forward_level)
+    result.append(head_forward_level)
+
+    # upper part of body
+    neck_x = data[1][0]
+    neck_y = data[1][1]
+
+    ankle_r_x = data[10][0]
+    ankle_r_y = data[10][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))
+    print("站姿分析:{}".format(up_state))
+    result.append("站姿分析:{}".format(up_state))
+    print(up_level)
+    result.append(up_level)
+
+    # for hip
+    hip_risk_level = 0
+    hip_r_x = data[8][0] - 2
+    hip_r_y = data[8][1]
+    knee_r_x = data[9][0]
+    knee_r_y = data[9][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 = "髋关节正常"
+
+    print("髋部分析:{}".format(hip_state))
+    result.append("髋部分析:{}".format(hip_state))
+
+    result.append(hip_risk_level)
+    print(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:
+        print("膝盖角度分析:膝盖无明显超伸")
+        result.append("膝盖角度分析:膝盖无明显超伸")
+    else:
+        print("膝盖角度分析:下肢膝盖超伸角度为{}度".format(round(knee_angle_r, 2)))
+        result.append("膝盖角度分析:下肢膝盖超伸角度为{}度".format(round(knee_angle_r, 2)))
+    print(knee_forward_risk_level)
+    result.append(knee_forward_risk_level)
+    return result
+
+
+def do_analysis(index, height):
+    source_npy_front = "pose_temp_data/{}-0.npy".format(index)
+    source_npy_side = "pose_temp_data/{}-1.npy".format(index)
+    save_path = "pose_processed_txt/{}.txt".format(index)
+    run_openpose_for_image_front(index)
+    run_openpose_for_image_side(index)
+    result_front = analyse_npy_front(source_npy_front, height)
+    result_side = analyse_npy_side(source_npy_side, height)
+    result = result_front + result_side
+    text_save(save_path, result)
+
+
+if __name__ == '__main__':
+    heights = [104, 100, 104.5, 124, 107, 120.6, 111, 120, 118, 103.5, 101, 114, 128, 142, 117
+        , 181, 105, 183, 150, 177
+    ]
+    for (i, h) in enumerate(heights):
+        try:
+            print("{}/{}".format(i + 1, len(heights)))
+            do_analysis(i + 1, h)
+        except Exception:
+            print("\nWarning")
+            print("照片{}有关键点未识别出来".format(i + 1))
+            continue

+ 19 - 0
backend/config.py

@@ -0,0 +1,19 @@
+# config.py
+import os
+import gevent.monkey
+gevent.monkey.patch_all()
+
+import multiprocessing
+
+# debug = True
+loglevel = 'debug'
+bind = "0.0.0.0:7001"
+pidfile = "log/gunicorn.pid"
+accesslog = "log/access.log"
+errorlog = "log/debug.log"
+daemon = True
+
+# 启动的进程数
+workers = multiprocessing.cpu_count()
+worker_class = 'gevent'
+x_forwarded_for_header = 'X-FORWARDED-FOR'

+ 88 - 0
backend/db.py

@@ -0,0 +1,88 @@
+#-*- coding: UTF-8 -*-
+import pymysql
+
+db = pymysql.connect(host="localhost",
+                     user="root",
+                     password="root",
+                     port=3306,  # 端口
+                     charset='utf8')
+
+cursor = db.cursor()
+
+def create_db():
+    db = pymysql.connect(host="localhost",
+                         user="root",
+                         password="root",
+                         #port=3306,  # 端口
+                         #charset='utf8')
+                         )
+
+    cursor = db.cursor()
+    # 使用 execute() 方法执行 SQL,如果表存在则删除
+    #cursor.execute("DROP TABLE IF EXISTS pose")
+
+    # 使用预处理语句创建表
+    cursor.execute("create database if not exists pose")
+    db.commit()
+    db.close()
+    cursor.close()
+def create_table():
+    db = pymysql.connect(host="localhost",
+                         user="root",
+                         password="root",
+                         port=3306,  # 端口
+                         charset='utf8',
+                         database="pose")
+
+    cursor = db.cursor()
+
+
+    sql = """CREATE TABLE pose(
+             id  INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+             status INT)
+             """
+    insert_table_sql = """INSERT INTO `pose` (`status`) VALUES ('0')"""
+
+    cursor.execute(sql)
+    db.commit()
+    cursor.execute(insert_table_sql)
+    version = cursor.fetchone()
+    print(version)
+    db.commit()
+    db.close()
+def add_new_data():
+    db = pymysql.connect(host="localhost",
+                         user="root",
+                         password="root",
+                         port=3306,  # 端口
+                         database="pose",
+                         charset='utf8')
+
+    cursor = db.cursor()
+
+    insert_table_sql="""INSERT INTO `pose` (`status`) VALUES ( '0')"""
+    cursor.execute(insert_table_sql)
+    db.commit()
+    db.close()
+    cursor.close()
+    return_id=cursor.lastrowid
+    return return_id
+
+def process(id):
+    db = pymysql.connect(host="localhost",
+                         user="root",
+                         password="123456",
+                         port=3306,  # 端口
+                         database="pose",
+                         charset='utf8')
+    sql = "update pose set status = 1 where id = %d " %id
+    cursor = db.cursor()
+    cursor.execute(sql)
+    db.commit()
+    cursor.close()
+    db.close()
+
+if __name__ == "__main__":
+    create_db()
+    create_table()
+    print(add_new_date())

+ 45 - 0
backend/demo.py

@@ -0,0 +1,45 @@
+import cv2
+import matplotlib.pyplot as plt
+import copy
+import numpy as np
+
+from src import util
+from src.body import Body
+
+body_estimation = Body('model/body_pose_model.pth')
+#hand_estimation = Hand('model/hand_pose_model.pth')
+
+test_image = 'pose_source_images/4-0.png'
+oriImg = cv2.imread(test_image)  # B,G,R order
+candidate, subset = body_estimation(oriImg)
+print(candidate)
+np.save('pose_temp_data/4-0.npy',candidate)
+canvas = copy.deepcopy(oriImg)
+canvas = util.draw_bodypose(canvas, candidate, subset)
+# detect hand
+# hands_list = util.handDetect(candidate, subset, oriImg)
+
+# all_hand_peaks = []
+# for x, y, w, is_left in hands_list:
+#     # cv2.rectangle(canvas, (x, y), (x+w, y+w), (0, 255, 0), 2, lineType=cv2.LINE_AA)
+#     # cv2.putText(canvas, 'left' if is_left else 'right', (x, y), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
+
+#     # if is_left:
+#         # plt.imshow(oriImg[y:y+w, x:x+w, :][:, :, [2, 1, 0]])
+#         # plt.show()
+#     peaks = hand_estimation(oriImg[y:y+w, x:x+w, :])
+#     peaks[:, 0] = np.where(peaks[:, 0]==0, peaks[:, 0], peaks[:, 0]+x)
+#     peaks[:, 1] = np.where(peaks[:, 1]==0, peaks[:, 1], peaks[:, 1]+y)
+#     # else:
+#     #     peaks = hand_estimation(cv2.flip(oriImg[y:y+w, x:x+w, :], 1))
+#     #     peaks[:, 0] = np.where(peaks[:, 0]==0, peaks[:, 0], w-peaks[:, 0]-1+x)
+#     #     peaks[:, 1] = np.where(peaks[:, 1]==0, peaks[:, 1], peaks[:, 1]+y)
+#     #     print(peaks)
+#     all_hand_peaks.append(peaks)
+
+# canvas = util.draw_handpose(canvas, all_hand_peaks)
+
+plt.imshow(canvas[:, :, [2, 1, 0]])
+plt.axis('off')
+plt.savefig('pose_processed_images/4-0-result.jpg')
+plt.show()

+ 42 - 0
backend/demo_camera.py

@@ -0,0 +1,42 @@
+import cv2
+import copy
+import numpy as np
+import torch
+
+from src import util
+from src.body import Body
+from src.hand import Hand
+
+body_estimation = Body('model/body_pose_model.pth')
+hand_estimation = Hand('model/hand_pose_model.pth')
+
+print(f"Torch device: {torch.cuda.get_device_name()}")
+
+cap = cv2.VideoCapture(0)
+cap.set(3, 640)
+cap.set(4, 480)
+while True:
+    ret, oriImg = cap.read()
+    candidate, subset = body_estimation(oriImg)
+    canvas = copy.deepcopy(oriImg)
+    canvas = util.draw_bodypose(canvas, candidate, subset)
+
+    # detect hand
+    hands_list = util.handDetect(candidate, subset, oriImg)
+
+    all_hand_peaks = []
+    for x, y, w, is_left in hands_list:
+        peaks = hand_estimation(oriImg[y:y+w, x:x+w, :])
+        peaks[:, 0] = np.where(peaks[:, 0]==0, peaks[:, 0], peaks[:, 0]+x)
+        peaks[:, 1] = np.where(peaks[:, 1]==0, peaks[:, 1], peaks[:, 1]+y)
+        all_hand_peaks.append(peaks)
+
+    canvas = util.draw_handpose(canvas, all_hand_peaks)
+
+    cv2.imshow('demo', canvas)#一个窗口用以显示原视频
+    if cv2.waitKey(1) & 0xFF == ord('q'):
+        break
+
+cap.release()
+cv2.destroyAllWindows()
+

+ 142 - 0
backend/demo_func.py

@@ -0,0 +1,142 @@
+import cv2
+import matplotlib.pyplot as plt
+import copy
+import numpy as np
+from PIL import Image
+import os
+
+
+from src import util
+from src.body import Body
+from src.infer_video_d2 import infer_image,load_predictor
+
+def run_visual_3d_for_image_front(index):
+    pass
+
+def run_visual_3d_for_image_right(index):
+    pass
+
+def run_openpose_for_image_front(index):
+    PNG_OR_NOT = True
+    # hand_estimation = Hand('model/hand_pose_model.pth')
+    test_image = 'pose_source_images/{}-0.png'.format(index)
+    if os.path.exists(test_image) is False:
+        PNG_OR_NOT = False
+        test_image = 'pose_source_images/{}-0.jpg'.format(index)
+    oriImg = cv2.imread(test_image)  # B,G,R order
+    im = cv2.imread(test_image, cv2.IMREAD_UNCHANGED)
+
+    # --- for openpose ---
+    body_estimation = Body('model/body_pose_model.pth')
+    candidate, subset = body_estimation(oriImg)
+
+    # --- for detectron ---
+    # predictor = load_predictor()
+    # candidate, subset = infer_image(oriImg, predictor)
+    np.save('pose_temp_data/{}-0.npy'.format(index), candidate)
+
+    canvas = copy.deepcopy(oriImg)
+    canvas = util.draw_bodypose(canvas, candidate, subset)
+
+
+    # sssssssssssss
+    if PNG_OR_NOT == True:
+        alpha_value_new = np.reshape(im[:,:,3],(im.shape[0],im.shape[1],1))
+        canvas = np.c_[canvas, alpha_value_new]
+
+    # test = np.c_[im[:,:,0:3],alpha_value_new]
+    # print(test)
+    # print(im)
+
+
+    #cv2.imwrite('pose_processed_images/tmp_transparent_ori.png', oriImg, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
+    cv2.imwrite('pose_processed_images/{}-0-result.png'.format(index), canvas, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
+    #cv2.imwrite('pose_processed_images/tmp_transparent_test.png', test, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
+    #cv2.imwrite('pose_processed_images/tmp_transparent_im.png', im, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
+
+    # plt.imshow(canvas[:, :, [2, 1, 0]])
+    # plt.axis('off')
+    # plt.savefig('pose_processed_images/{}-1-result.png'.format(index))
+    # plt.show()
+
+def run_openpose_for_image_side(index):
+    PNG_OR_NOT = True
+    body_estimation = Body('model/body_pose_model.pth')
+    # hand_estimation = Hand('model/hand_pose_model.pth')
+    test_image = 'pose_source_images/{}-1.png'.format(index)
+    if os.path.exists(test_image) is False:
+        PNG_OR_NOT = False
+        test_image = 'pose_source_images/{}-1.jpg'.format(index)
+    oriImg = cv2.imread(test_image)  # B,G,R order
+    im = cv2.imread(test_image, cv2.IMREAD_UNCHANGED)
+    # --- for openpose ---
+    body_estimation = Body('model/body_pose_model.pth')
+    candidate, subset = body_estimation(oriImg)
+
+    # --- for detectron ---
+    # predictor = load_predictor()
+    # candidate, subset = infer_image(oriImg, predictor)
+    np.save('pose_temp_data/{}-1.npy'.format(index), candidate)
+
+    canvas = copy.deepcopy(oriImg)
+    canvas = util.draw_bodypose(canvas, candidate, subset)
+
+    if PNG_OR_NOT == True:
+        alpha_value_new = np.reshape(im[:,:,3],(im.shape[0],im.shape[1],1))
+        canvas = np.c_[canvas, alpha_value_new]
+    # test = np.c_[im[:,:,0:3],alpha_value_new]
+    # print(test)
+    # print(im)
+
+
+    #cv2.imwrite('pose_processed_images/tmp_transparent_ori.png', oriImg, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
+    cv2.imwrite('pose_processed_images/{}-1-result.png'.format(index), canvas, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
+    #cv2.imwrite('pose_processed_images/tmp_transparent_test.png', test, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
+    #cv2.imwrite('pose_processed_images/tmp_transparent_im.png', im, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
+
+    # plt.imshow(canvas[:, :, [2, 1, 0]])
+    # plt.axis('off')
+    # plt.savefig('pose_processed_images/{}-1-result.png'.format(index))
+    # plt.show()
+
+def run_openpose_for_normal(index):
+    print(index)
+    
+    # 输入index和原来一样,就是序号,你可以更改一下输入或者输出,方便你们处理
+
+    # 判断是否是PNG格式,如果是,增添对alpha通道的处理
+    PNG_OR_NOT = True
+    # 调用的模型路径,无需更改
+    #原图像的位置,需要更改 !!
+    test_image = './capture_image/capture_image{}.png'.format(index)
+    if os.path.exists(test_image) is False:
+        PNG_OR_NOT = False
+        # 原图像的位置,需要更改 !!
+        test_image = './capture_image/capture_image{}.jpg'.format(index)
+    oriImg = cv2.imread(test_image)  # B,G,R order
+    im = cv2.imread(test_image, cv2.IMREAD_UNCHANGED)
+
+    # --- for openpose ---
+    # body_estimation = Body('model/body_pose_model.pth')
+    # candidate, subset = body_estimation(oriImg)
+
+    # --- for detectron ---
+    predictor = load_predictor()
+    candidate, subset = infer_image(oriImg, predictor)
+
+    # 坐标点数值保存路径,需要更改!!
+    np.save('./capture_image/capture_image{}-1.png'.format(index), candidate)
+    print('./capture_image/capture_image{}.png'.format(index))
+
+    # 处理
+    canvas = copy.deepcopy(oriImg)
+    canvas = util.draw_bodypose(canvas, candidate, subset)
+    #if PNG_OR_NOT == True:
+        #alpha_value_new = np.reshape(im[:,:,3],(im.shape[0],im.shape[1],1))
+        #canvas = np.c_[canvas, alpha_value_new]
+
+    # 结果图片的保存路径,需要更改 !!
+    cv2.imwrite('./capture_image/capture_image_result{}.png'.format(index), canvas, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
+
+if __name__ == '__main__':
+    run_openpose_for_normal(1)

+ 134 - 0
backend/demo_video.py

@@ -0,0 +1,134 @@
+import copy
+import numpy as np
+import cv2
+import os
+import json
+
+# video file processing setup
+# from: https://stackoverflow.com/a/61927951
+import argparse
+import subprocess
+from typing import NamedTuple
+
+
+class FFProbeResult(NamedTuple):
+    return_code: int
+    json: str
+    error: str
+
+
+def ffprobe(file_path) -> FFProbeResult:
+    command_array = ["ffprobe",
+                     "-v", "quiet",
+                     "-print_format", "json",
+                     "-show_format",
+                     "-show_streams",
+                     file_path]
+    result = subprocess.run(command_array, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
+    return FFProbeResult(return_code=result.returncode,
+                         json=result.stdout,
+                         error=result.stderr)
+
+
+# openpose setup
+from src import util
+from src.body import Body
+from src.hand import Hand
+
+body_estimation = Body('model/body_pose_model.pth')
+hand_estimation = Hand('model/hand_pose_model.pth')
+
+def process_frame(frame, body=True, hands=True):
+    canvas = copy.deepcopy(frame)
+    if body:
+        candidate, subset = body_estimation(frame)
+        canvas = util.draw_bodypose(canvas, candidate, subset)
+    if hands:
+        hands_list = util.handDetect(candidate, subset, frame)
+        all_hand_peaks = []
+        for x, y, w, is_left in hands_list:
+            peaks = hand_estimation(frame[y:y+w, x:x+w, :])
+            peaks[:, 0] = np.where(peaks[:, 0]==0, peaks[:, 0], peaks[:, 0]+x)
+            peaks[:, 1] = np.where(peaks[:, 1]==0, peaks[:, 1], peaks[:, 1]+y)
+            all_hand_peaks.append(peaks)
+        canvas = util.draw_handpose(canvas, all_hand_peaks)
+    return canvas
+
+# writing video with ffmpeg because cv2 writer failed
+# https://stackoverflow.com/questions/61036822/opencv-videowriter-produces-cant-find-starting-number-error
+import ffmpeg
+
+# open specified video
+parser = argparse.ArgumentParser(
+        description="Process a video annotating poses detected.")
+parser.add_argument('file', type=str, help='Video file location to process.')
+parser.add_argument('--no_hands', action='store_true', help='No hand pose')
+parser.add_argument('--no_body', action='store_true', help='No body pose')
+args = parser.parse_args()
+video_file = args.file
+cap = cv2.VideoCapture(video_file)
+
+# get video file info
+ffprobe_result = ffprobe(args.file)
+info = json.loads(ffprobe_result.json)
+videoinfo = [i for i in info["streams"] if i["codec_type"] == "video"][0]
+input_fps = videoinfo["avg_frame_rate"]
+# input_fps = float(input_fps[0])/float(input_fps[1])
+input_pix_fmt = videoinfo["pix_fmt"]
+input_vcodec = videoinfo["codec_name"]
+
+# define a writer object to write to a movidified file
+postfix = info["format"]["format_name"].split(",")[0]
+output_file = ".".join(video_file.split(".")[:-1])+".processed." + postfix
+
+
+class Writer():
+    def __init__(self, output_file, input_fps, input_framesize, input_pix_fmt,
+                 input_vcodec):
+        if os.path.exists(output_file):
+            os.remove(output_file)
+        self.ff_proc = (
+            ffmpeg
+            .input('pipe:',
+                   format='rawvideo',
+                   pix_fmt="bgr24",
+                   s='%sx%s'%(input_framesize[1],input_framesize[0]),
+                   r=input_fps)
+            .output(output_file, pix_fmt=input_pix_fmt, vcodec=input_vcodec)
+            .overwrite_output()
+            .run_async(pipe_stdin=True)
+        )
+
+    def __call__(self, frame):
+        self.ff_proc.stdin.write(frame.tobytes())
+
+    def close(self):
+        self.ff_proc.stdin.close()
+        self.ff_proc.wait()
+
+
+writer = None
+while(cap.isOpened()):
+    ret, frame = cap.read()
+    if frame is None:
+        break
+
+    posed_frame = process_frame(frame, body=not args.no_body,
+                                       hands=not args.no_hands)
+
+    if writer is None:
+        input_framesize = posed_frame.shape[:2]
+        writer = Writer(output_file, input_fps, input_framesize, input_pix_fmt,
+                        input_vcodec)
+
+    cv2.imshow('frame', posed_frame)
+
+    # write the frame
+    writer(posed_frame)
+
+    if cv2.waitKey(1) & 0xFF == ord('q'):
+        break
+
+cap.release()
+writer.close()
+cv2.destroyAllWindows()

+ 319 - 0
backend/flask_api.py

@@ -0,0 +1,319 @@
+import json
+import os
+
+from flask import Flask, request
+from flask_cors import *
+from PIL import Image
+import base64
+
+from analyse_func import  do_analysis
+
+# str_res=""
+# a dict contains the txt
+dict_total = {}
+li_last = []
+# store dic_data's json format
+bJson = ""
+global_index = [6]
+li_return = []
+img_return = []
+ip_ad="106.15.1.178"
+
+
+import pymysql
+
+db = pymysql.connect(host=ip_ad,
+                     user="root",
+                     password="root",
+                     port=3306,  # 端口
+                     charset='utf8')
+
+cursor = db.cursor()
+
+def create_db():
+    db = pymysql.connect(host=ip_ad,
+                         user="root",
+                         password="root",
+                         #port=3306,  # 端口
+                         #charset='utf8')
+                         )
+
+    cursor = db.cursor()
+    # 使用 execute() 方法执行 SQL,如果表存在则删除
+    #cursor.execute("DROP TABLE IF EXISTS pose")
+
+    # 使用预处理语句创建表
+    cursor.execute("create database if not exists pose")
+    db.commit()
+    db.close()
+    cursor.close()
+def create_table():
+    db = pymysql.connect(host=ip_ad,
+                         user="root",
+                         password="root",
+                         port=3306,  # 端口
+                         charset='utf8',
+                         database="pose")
+
+    cursor = db.cursor()
+
+
+    sql = """CREATE TABLE pose(
+             id  INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+             status INT)
+             """
+    insert_table_sql = """INSERT INTO `pose` (`status`) VALUES ('0')"""
+
+    cursor.execute(sql)
+    db.commit()
+    cursor.execute(insert_table_sql)
+    version = cursor.fetchone()
+    print(version)
+    db.commit()
+    db.close()
+def add_new_data():
+    db = pymysql.connect(host=ip_ad,
+                         user="root",
+                         password="root",
+                         port=3306,  # 端口
+                         database="pose",
+                         charset='utf8')
+
+    cursor = db.cursor()
+
+    insert_table_sql="""INSERT INTO `pose` (`status`) VALUES ( '0')"""
+    cursor.execute(insert_table_sql)
+    db.commit()
+    db.close()
+    cursor.close()
+    return_id=cursor.lastrowid
+    return return_id
+
+def process_db(id):
+    db = pymysql.connect(host=ip_ad,
+                         user="root",
+                         password="root",
+                         port=3306,  # 端口
+                         database="pose",
+                         charset='utf8')
+    sql = "update pose set status = 1 where id = %d " %id
+    cursor = db.cursor()
+    cursor.execute(sql)
+    db.commit()
+    cursor.close()
+    db.close()
+
+def get_status(id):
+    db = pymysql.connect(host=ip_ad,
+                         user="root",
+                         password="root",
+                         port=3306,  # 端口
+                         database="pose",
+                         charset='utf8')
+    sql="select status from pose where id =%d"%id
+    cursor=db.cursor()
+    status=cursor.execute(sql)
+    cursor.close()
+    db.close()
+    if str(status)=="1":
+        return True
+    return False                      
+
+
+# read txt file and change it to the list format
+def read_pose_processes_txt_result():
+    file_dir = "pose_processed_txt"  # the file's path
+    dir_list = os.listdir(file_dir)  # dir_list=["1.txt","2.txt","3.txt","4.txt"]
+    global li_last  # 分析的最后结果
+    global dict_total  # list的字典
+    li_last = []
+    dict_total = {}
+    for cur_file in dir_list:
+        if (cur_file[0:len(cur_file) - 4] != str(global_index[0])):
+            # print(cur_file[0:len(cur_file)-4])
+            # print(global_index[0])
+            continue
+        path = os.path.join(file_dir, cur_file)  # path="pose_processed_txt/1.txt"
+        f = open(path, encoding='utf-8')
+        lines = f.readlines()
+
+        # process logic,{"正面分析":{“头部分析”:{},"肩部分析": {}}
+        dic_data = {}
+        for i in range(len(lines)):
+            if lines[i][0] == '-':
+                continue
+            elif lines[i][0] == "正":
+                dic_data = {"title": "正面分析如下"}
+                li = []
+                dic_1 = {}
+                i = i + 1  # i是头部分析那行
+                dic_1["name"] = "头部分析"
+                dic_1["value"] = lines[i][5:len(lines[i]) - 1]
+                i = i + 1
+                dic_1["level"] = int(lines[i].strip())
+                li.append(dic_1)
+                dic_2 = {}
+                i += 2
+                dic_2["name"] = "肩部分析"
+                num_risk_1 = int(lines[i + 1].strip())
+                dic_2["value"] = lines[i][:-1] + " " + lines[(i + 2)][:-1]
+                num_risk_2 = int(lines[i + 3].strip())
+                dic_2["level"] = int(max(num_risk_1, num_risk_2))
+                li.append(dic_2)
+                i += 5
+                dic_3 = {}
+                dic_3["name"] = "髋部分析"
+                dic_3["value"] = lines[i][:-1] + " " + lines[(i + 2)][:-1]
+                num_risk_1 = int(lines[i + 1].strip())
+                num_risk_2 = int(lines[i + 3].strip())
+                dic_3["level"] = int(max(num_risk_1, num_risk_2))
+                i += 5
+                dic_4 = {"name": "腿型分析", "value": lines[i][:-1] + " " + lines[(i + 1)][:-1] + " " + lines[(i + 2)][:-1]}
+                li.append(dic_3)
+                li.append(dic_4)
+                dic_data["detail"] = li
+                if ("非" in lines[(i + 2)][:-1]):
+                    dic_4["level"] = 0
+                else:
+                    dic_4["level"] = 1
+                li_last.append(dic_data)
+            elif lines[i][0] == "侧":
+                li = []
+                dic_data = {"title": "侧面分析如下"}
+                dic_1 = {}
+                i += 1
+                dic_1["name"] = "颈椎分析"
+                if lines[i][4] != ":":
+                    dic_1["value"] = lines[i][0:len(lines[i]) - 1]
+                else:
+                    dic_1["value"] = lines[i][5:len(lines[i]) - 1]
+                dic_1["level"] = int(lines[i + 1].strip())
+                i += 2
+                dic_2 = {"name": "站姿分析", "value": lines[i][5:len(lines[i]) - 1]}
+                dic_2["level"] = int(lines[i + 1].strip())
+                i += 2
+                dic_3 = {"name": "髋部分析", "value": lines[i][5:len(lines[i]) - 1]}
+                dic_3["level"] = int(lines[i + 1].strip())
+                i += 2
+                dic_4 = {"name": "膝盖分析", "value": lines[i][7:len(lines[i]) - 1]}
+                dic_4["level"] = int(lines[i + 1].strip())
+                li.append(dic_1)
+                li.append(dic_2)
+                li.append(dic_3)
+                li.append(dic_4)
+                dic_data["detail"] = li
+                li_last.append(dic_data)
+            else:
+                i += 1
+        print(li_last)
+
+        dict_total[cur_file] = li_last
+
+        f.close()
+    global li_return
+    li_return = dict_total[(str(global_index[0]) + ".txt")]
+
+
+def run_the_process(index: int = 0, height: float = 110):
+    # sys.path.append("../analyse_func.py")
+    # import analyse_func
+    #print(global_index[0])
+    do_analysis(global_index[0], height)
+
+
+# 把list转化为可以直接返回的json格式
+
+def list_to_json(li):
+    #print(li)
+
+    aJson = json.dumps(li, ensure_ascii=False)
+    print(aJson)
+
+    # dict1={}
+    # dict1["front"]="/home/seecoder/Documents/pose-correction/pc-demo/pose_processed_images/"+str(global_index[0])+"-0-result.jpg"
+    # dict1["right"] = "/home/seecoder/Documents/pose-correction/pc-demo/pose_processed_images/" + \
+    #                str(global_index[0] )+ "-1-result.jpg"
+    # print(dict1)
+    return aJson
+    # print(dic_data)
+
+
+app = Flask(__name__)
+
+
+def save_file(file):
+    base = "pose_source_images/"
+    filename = file.filename
+    index = global_index[0]
+    #global_index[0] = index
+    suffix = ".png"
+    if filename.find("jpg") > 0:
+        suffix = ".jpg"
+    elif filename.find("png") > 0:
+        suffix = ".png"
+    save_name = ""
+    print(str(index)+"index")
+    print(filename+"filename")
+    if "front" in filename:
+        save_name = base + str(index) + str("-0") + suffix
+        print(filename+"filename_front")
+        print(save_name+"savename_front")
+    elif "right" in filename:
+        save_name = base + str(index) + str("-1") + suffix
+        print(filename+"filename_right")
+        print(save_name+"savename_right")
+    else :
+        print("nope")
+    file.save(save_name)
+    print(filename)
+    print(save_name)
+
+
+@app.route('/send_form', methods=['POST'])
+@cross_origin()
+def send_form():
+    text_data = request.form.to_dict()
+    fileList = request.files.to_dict()
+    global global_index
+    global_index[0]=int(add_new_data())
+    for file in fileList.values():
+        save_file(file)
+    height = float(text_data["height"]) if text_data["height"] != "undefined" else None
+    run_the_process(global_index[0], height)
+    process_db(int(global_index[0]))
+    return {
+        "id":global_index[0], 
+        "status": True if height is not None else False
+    }
+
+
+def get_base64(path):
+    with open(path, "rb") as f:
+        base64_data = base64.b64encode(f.read())
+        return base64_data.decode()
+
+
+@app.route('/get_result', methods=['POST'])
+@cross_origin()
+def run_project():
+    id = request.form.to_dict()["id"]
+    print(id)
+    base = "pose_processed_images"
+    global_index[0]=int(id)
+    read_pose_processes_txt_result()
+    print(base + "/" + str(global_index[0]) + "-0-result.png")
+    return {
+        "front": get_base64(base + "/" + str(global_index[0]) + "-0-result.png"),
+        "right": get_base64(base + "/" + str(global_index[0]) + "-1-result.png"),
+        "data": list_to_json(li_return),
+    }
+
+
+
+if __name__ == '__main__':
+    # read_pose_processes_txt_result()
+    # print(list_to_json(li_return))
+
+    #app.run()
+    app.run(host="0.0.0.0", port=8090,debug=True)
+    CORS(app, resouces=r'/*')

+ 10 - 0
backend/gunicorn.conf.py

@@ -0,0 +1,10 @@
+workers = 5
+worker_class = "gevent"
+bind = "0.0.0.0:8090"
+#accesslog="/home/seecoder/Documents/pose-correction/pc-demo/log/gunicorn_access.log"
+#errorlog="/home/seecoder/Documents/pose-correction/pc-demo/log/gunicorn_error.log"
+#pidfile = '/home/seecoder/Documents/pose-correction/pc-demo/run/gunicorn.pid'
+accesslog="/root/openpose/pose-correction/pc-demo/log/gunicorn_access.log"
+errorlog="/root/openpose/pose-correction/pc-demo/log/gunicorn_error.log"
+pidfile = '/root/openpose/pose-correction/pc-demo/run/gunicorn.pid'
+capture_output = True

+ 7 - 0
backend/main.py

@@ -0,0 +1,7 @@
+from flask import Flask
+app = Flask(__name__)
+@app.route("/")
+def hello():
+    return "<h1>Hello There!</h1>"
+if __name__ == "__main__":
+    app.run(host='0.0.0.0')

+ 12 - 0
backend/requirements.txt

@@ -0,0 +1,12 @@
+scikit_image==0.18.3
+scipy==1.7.1
+matplotlib==3.4.3
+tqdm==4.62.2
+numpy==1.19.2
+Flask==2.0.1
+Flask_Cors==3.0.10
+torch==1.7.1
+opencv_python==4.5.3.56
+ffmpeg==1.4
+gevent==21.8.0
+gunicorn==20.1.0

+ 1 - 0
backend/run/gunicorn.pid

@@ -0,0 +1 @@
+16685

+ 0 - 0
backend/src/__init__.py


+ 215 - 0
backend/src/body.py

@@ -0,0 +1,215 @@
+import cv2
+import numpy as np
+import math
+from scipy.ndimage.filters import gaussian_filter
+import matplotlib.pyplot as plt
+import torch
+
+from src import util
+from src.model import bodypose_model
+
+class Body(object):
+    def __init__(self, model_path):
+        self.model = bodypose_model()
+        if torch.cuda.is_available():
+            self.model = self.model.cuda()
+        model_dict = util.transfer(self.model, torch.load(model_path))
+        self.model.load_state_dict(model_dict)
+        self.model.eval()
+
+    def __call__(self, oriImg):
+        # scale_search = [0.5, 1.0, 1.5, 2.0]
+        scale_search = [0.5]
+        boxsize = 368
+        stride = 8
+        padValue = 128
+        thre1 = 0.1
+        thre2 = 0.05
+        multiplier = [x * boxsize / oriImg.shape[0] for x in scale_search]
+        heatmap_avg = np.zeros((oriImg.shape[0], oriImg.shape[1], 19))
+        paf_avg = np.zeros((oriImg.shape[0], oriImg.shape[1], 38))
+
+        for m in range(len(multiplier)):
+            scale = multiplier[m]
+            imageToTest = cv2.resize(oriImg, (0, 0), fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC)
+            imageToTest_padded, pad = util.padRightDownCorner(imageToTest, stride, padValue)
+            im = np.transpose(np.float32(imageToTest_padded[:, :, :, np.newaxis]), (3, 2, 0, 1)) / 256 - 0.5
+            im = np.ascontiguousarray(im)
+
+            data = torch.from_numpy(im).float()
+            if torch.cuda.is_available():
+                data = data.cuda()
+            # data = data.permute([2, 0, 1]).unsqueeze(0).float()
+            with torch.no_grad():
+                Mconv7_stage6_L1, Mconv7_stage6_L2 = self.model(data)
+            Mconv7_stage6_L1 = Mconv7_stage6_L1.cpu().numpy()
+            Mconv7_stage6_L2 = Mconv7_stage6_L2.cpu().numpy()
+
+            # extract outputs, resize, and remove padding
+            # heatmap = np.transpose(np.squeeze(net.blobs[output_blobs.keys()[1]].data), (1, 2, 0))  # output 1 is heatmaps
+            heatmap = np.transpose(np.squeeze(Mconv7_stage6_L2), (1, 2, 0))  # output 1 is heatmaps
+            heatmap = cv2.resize(heatmap, (0, 0), fx=stride, fy=stride, interpolation=cv2.INTER_CUBIC)
+            heatmap = heatmap[:imageToTest_padded.shape[0] - pad[2], :imageToTest_padded.shape[1] - pad[3], :]
+            heatmap = cv2.resize(heatmap, (oriImg.shape[1], oriImg.shape[0]), interpolation=cv2.INTER_CUBIC)
+
+            # paf = np.transpose(np.squeeze(net.blobs[output_blobs.keys()[0]].data), (1, 2, 0))  # output 0 is PAFs
+            paf = np.transpose(np.squeeze(Mconv7_stage6_L1), (1, 2, 0))  # output 0 is PAFs
+            paf = cv2.resize(paf, (0, 0), fx=stride, fy=stride, interpolation=cv2.INTER_CUBIC)
+            paf = paf[:imageToTest_padded.shape[0] - pad[2], :imageToTest_padded.shape[1] - pad[3], :]
+            paf = cv2.resize(paf, (oriImg.shape[1], oriImg.shape[0]), interpolation=cv2.INTER_CUBIC)
+
+            heatmap_avg += heatmap_avg + heatmap / len(multiplier)
+            paf_avg += + paf / len(multiplier)
+
+        all_peaks = []
+        peak_counter = 0
+
+        for part in range(18):
+            map_ori = heatmap_avg[:, :, part]
+            one_heatmap = gaussian_filter(map_ori, sigma=3)
+
+            map_left = np.zeros(one_heatmap.shape)
+            map_left[1:, :] = one_heatmap[:-1, :]
+            map_right = np.zeros(one_heatmap.shape)
+            map_right[:-1, :] = one_heatmap[1:, :]
+            map_up = np.zeros(one_heatmap.shape)
+            map_up[:, 1:] = one_heatmap[:, :-1]
+            map_down = np.zeros(one_heatmap.shape)
+            map_down[:, :-1] = one_heatmap[:, 1:]
+
+            peaks_binary = np.logical_and.reduce(
+                (one_heatmap >= map_left, one_heatmap >= map_right, one_heatmap >= map_up, one_heatmap >= map_down, one_heatmap > thre1))
+            peaks = list(zip(np.nonzero(peaks_binary)[1], np.nonzero(peaks_binary)[0]))  # note reverse
+            peaks_with_score = [x + (map_ori[x[1], x[0]],) for x in peaks]
+            peak_id = range(peak_counter, peak_counter + len(peaks))
+            peaks_with_score_and_id = [peaks_with_score[i] + (peak_id[i],) for i in range(len(peak_id))]
+
+            all_peaks.append(peaks_with_score_and_id)
+            peak_counter += len(peaks)
+
+        # find connection in the specified sequence, center 29 is in the position 15
+        limbSeq = [[2, 3], [2, 6], [3, 4], [4, 5], [6, 7], [7, 8], [2, 9], [9, 10], \
+                   [10, 11], [2, 12], [12, 13], [13, 14], [2, 1], [1, 15], [15, 17], \
+                   [1, 16], [16, 18], [3, 17], [6, 18]]
+        # the middle joints heatmap correpondence
+        mapIdx = [[31, 32], [39, 40], [33, 34], [35, 36], [41, 42], [43, 44], [19, 20], [21, 22], \
+                  [23, 24], [25, 26], [27, 28], [29, 30], [47, 48], [49, 50], [53, 54], [51, 52], \
+                  [55, 56], [37, 38], [45, 46]]
+
+        connection_all = []
+        special_k = []
+        mid_num = 10
+
+        for k in range(len(mapIdx)):
+            score_mid = paf_avg[:, :, [x - 19 for x in mapIdx[k]]]
+            candA = all_peaks[limbSeq[k][0] - 1]
+            candB = all_peaks[limbSeq[k][1] - 1]
+            nA = len(candA)
+            nB = len(candB)
+            indexA, indexB = limbSeq[k]
+            if (nA != 0 and nB != 0):
+                connection_candidate = []
+                for i in range(nA):
+                    for j in range(nB):
+                        vec = np.subtract(candB[j][:2], candA[i][:2])
+                        norm = math.sqrt(vec[0] * vec[0] + vec[1] * vec[1])
+                        norm = max(0.001, norm)
+                        vec = np.divide(vec, norm)
+
+                        startend = list(zip(np.linspace(candA[i][0], candB[j][0], num=mid_num), \
+                                            np.linspace(candA[i][1], candB[j][1], num=mid_num)))
+
+                        vec_x = np.array([score_mid[int(round(startend[I][1])), int(round(startend[I][0])), 0] \
+                                          for I in range(len(startend))])
+                        vec_y = np.array([score_mid[int(round(startend[I][1])), int(round(startend[I][0])), 1] \
+                                          for I in range(len(startend))])
+
+                        score_midpts = np.multiply(vec_x, vec[0]) + np.multiply(vec_y, vec[1])
+                        score_with_dist_prior = sum(score_midpts) / len(score_midpts) + min(
+                            0.5 * oriImg.shape[0] / norm - 1, 0)
+                        criterion1 = len(np.nonzero(score_midpts > thre2)[0]) > 0.8 * len(score_midpts)
+                        criterion2 = score_with_dist_prior > 0
+                        if criterion1 and criterion2:
+                            connection_candidate.append(
+                                [i, j, score_with_dist_prior, score_with_dist_prior + candA[i][2] + candB[j][2]])
+
+                connection_candidate = sorted(connection_candidate, key=lambda x: x[2], reverse=True)
+                connection = np.zeros((0, 5))
+                for c in range(len(connection_candidate)):
+                    i, j, s = connection_candidate[c][0:3]
+                    if (i not in connection[:, 3] and j not in connection[:, 4]):
+                        connection = np.vstack([connection, [candA[i][3], candB[j][3], s, i, j]])
+                        if (len(connection) >= min(nA, nB)):
+                            break
+
+                connection_all.append(connection)
+            else:
+                special_k.append(k)
+                connection_all.append([])
+
+        # last number in each row is the total parts number of that person
+        # the second last number in each row is the score of the overall configuration
+        subset = -1 * np.ones((0, 20))
+        candidate = np.array([item for sublist in all_peaks for item in sublist])
+
+        for k in range(len(mapIdx)):
+            if k not in special_k:
+                partAs = connection_all[k][:, 0]
+                partBs = connection_all[k][:, 1]
+                indexA, indexB = np.array(limbSeq[k]) - 1
+
+                for i in range(len(connection_all[k])):  # = 1:size(temp,1)
+                    found = 0
+                    subset_idx = [-1, -1]
+                    for j in range(len(subset)):  # 1:size(subset,1):
+                        if subset[j][indexA] == partAs[i] or subset[j][indexB] == partBs[i]:
+                            subset_idx[found] = j
+                            found += 1
+
+                    if found == 1:
+                        j = subset_idx[0]
+                        if subset[j][indexB] != partBs[i]:
+                            subset[j][indexB] = partBs[i]
+                            subset[j][-1] += 1
+                            subset[j][-2] += candidate[partBs[i].astype(int), 2] + connection_all[k][i][2]
+                    elif found == 2:  # if found 2 and disjoint, merge them
+                        j1, j2 = subset_idx
+                        membership = ((subset[j1] >= 0).astype(int) + (subset[j2] >= 0).astype(int))[:-2]
+                        if len(np.nonzero(membership == 2)[0]) == 0:  # merge
+                            subset[j1][:-2] += (subset[j2][:-2] + 1)
+                            subset[j1][-2:] += subset[j2][-2:]
+                            subset[j1][-2] += connection_all[k][i][2]
+                            subset = np.delete(subset, j2, 0)
+                        else:  # as like found == 1
+                            subset[j1][indexB] = partBs[i]
+                            subset[j1][-1] += 1
+                            subset[j1][-2] += candidate[partBs[i].astype(int), 2] + connection_all[k][i][2]
+
+                    # if find no partA in the subset, create a new subset
+                    elif not found and k < 17:
+                        row = -1 * np.ones(20)
+                        row[indexA] = partAs[i]
+                        row[indexB] = partBs[i]
+                        row[-1] = 2
+                        row[-2] = sum(candidate[connection_all[k][i, :2].astype(int), 2]) + connection_all[k][i][2]
+                        subset = np.vstack([subset, row])
+        # delete some rows of subset which has few parts occur
+        deleteIdx = []
+        for i in range(len(subset)):
+            if subset[i][-1] < 4 or subset[i][-2] / subset[i][-1] < 0.4:
+                deleteIdx.append(i)
+        subset = np.delete(subset, deleteIdx, axis=0)
+
+        # subset: n*20 array, 0-17 is the index in candidate, 18 is the total score, 19 is the total parts
+        # candidate: x, y, score, id
+        return candidate, subset
+
+if __name__ == "__main__":
+    body_estimation = Body('../model/body_pose_model.pth')
+
+    test_image = '../images/ski.jpg'
+    oriImg = cv2.imread(test_image)  # B,G,R order
+    candidate, subset = body_estimation(oriImg)
+    canvas = util.draw_bodypose(oriImg, candidate, subset)
+    plt.imshow(canvas[:, :, [2, 1, 0]])
+    plt.show()

+ 80 - 0
backend/src/hand.py

@@ -0,0 +1,80 @@
+import cv2
+import numpy as np
+from scipy.ndimage.filters import gaussian_filter
+import torch
+from skimage.measure import label
+
+from src.model import handpose_model
+from src import util
+
+class Hand(object):
+    def __init__(self, model_path):
+        self.model = handpose_model()
+        if torch.cuda.is_available():
+            self.model = self.model.cuda()
+        model_dict = util.transfer(self.model, torch.load(model_path))
+        self.model.load_state_dict(model_dict)
+        self.model.eval()
+
+    def __call__(self, oriImg):
+        scale_search = [0.5, 1.0, 1.5, 2.0]
+        # scale_search = [0.5]
+        boxsize = 368
+        stride = 8
+        padValue = 128
+        thre = 0.05
+        multiplier = [x * boxsize / oriImg.shape[0] for x in scale_search]
+        heatmap_avg = np.zeros((oriImg.shape[0], oriImg.shape[1], 22))
+        # paf_avg = np.zeros((oriImg.shape[0], oriImg.shape[1], 38))
+
+        for m in range(len(multiplier)):
+            scale = multiplier[m]
+            imageToTest = cv2.resize(oriImg, (0, 0), fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC)
+            imageToTest_padded, pad = util.padRightDownCorner(imageToTest, stride, padValue)
+            im = np.transpose(np.float32(imageToTest_padded[:, :, :, np.newaxis]), (3, 2, 0, 1)) / 256 - 0.5
+            im = np.ascontiguousarray(im)
+
+            data = torch.from_numpy(im).float()
+            if torch.cuda.is_available():
+                data = data.cuda()
+            # data = data.permute([2, 0, 1]).unsqueeze(0).float()
+            with torch.no_grad():
+                output = self.model(data).cpu().numpy()
+                # output = self.model(data).numpy()q
+
+            # extract outputs, resize, and remove padding
+            heatmap = np.transpose(np.squeeze(output), (1, 2, 0))  # output 1 is heatmaps
+            heatmap = cv2.resize(heatmap, (0, 0), fx=stride, fy=stride, interpolation=cv2.INTER_CUBIC)
+            heatmap = heatmap[:imageToTest_padded.shape[0] - pad[2], :imageToTest_padded.shape[1] - pad[3], :]
+            heatmap = cv2.resize(heatmap, (oriImg.shape[1], oriImg.shape[0]), interpolation=cv2.INTER_CUBIC)
+
+            heatmap_avg += heatmap / len(multiplier)
+
+        all_peaks = []
+        for part in range(21):
+            map_ori = heatmap_avg[:, :, part]
+            one_heatmap = gaussian_filter(map_ori, sigma=3)
+            binary = np.ascontiguousarray(one_heatmap > thre, dtype=np.uint8)
+            # 全部小于阈值
+            if np.sum(binary) == 0:
+                all_peaks.append([0, 0])
+                continue
+            label_img, label_numbers = label(binary, return_num=True, connectivity=binary.ndim)
+            max_index = np.argmax([np.sum(map_ori[label_img == i]) for i in range(1, label_numbers + 1)]) + 1
+            label_img[label_img != max_index] = 0
+            map_ori[label_img == 0] = 0
+
+            y, x = util.npmax(map_ori)
+            all_peaks.append([x, y])
+        return np.array(all_peaks)
+
+if __name__ == "__main__":
+    hand_estimation = Hand('../model/hand_pose_model.pth')
+
+    # test_image = '../images/hand.jpg'
+    test_image = '../images/hand.jpg'
+    oriImg = cv2.imread(test_image)  # B,G,R order
+    peaks = hand_estimation(oriImg)
+    canvas = util.draw_handpose(oriImg, peaks, True)
+    cv2.imshow('', canvas)
+    cv2.waitKey(0)

+ 992 - 0
backend/src/hand_model_output_size.json

@@ -0,0 +1,992 @@
+{
+    "10":1,
+    "11":1,
+    "12":1,
+    "13":1,
+    "14":1,
+    "15":1,
+    "16":2,
+    "17":2,
+    "18":2,
+    "19":2,
+    "20":2,
+    "21":2,
+    "22":2,
+    "23":2,
+    "24":3,
+    "25":3,
+    "26":3,
+    "27":3,
+    "28":3,
+    "29":3,
+    "30":3,
+    "31":3,
+    "32":4,
+    "33":4,
+    "34":4,
+    "35":4,
+    "36":4,
+    "37":4,
+    "38":4,
+    "39":4,
+    "40":5,
+    "41":5,
+    "42":5,
+    "43":5,
+    "44":5,
+    "45":5,
+    "46":5,
+    "47":5,
+    "48":6,
+    "49":6,
+    "50":6,
+    "51":6,
+    "52":6,
+    "53":6,
+    "54":6,
+    "55":6,
+    "56":7,
+    "57":7,
+    "58":7,
+    "59":7,
+    "60":7,
+    "61":7,
+    "62":7,
+    "63":7,
+    "64":8,
+    "65":8,
+    "66":8,
+    "67":8,
+    "68":8,
+    "69":8,
+    "70":8,
+    "71":8,
+    "72":9,
+    "73":9,
+    "74":9,
+    "75":9,
+    "76":9,
+    "77":9,
+    "78":9,
+    "79":9,
+    "80":10,
+    "81":10,
+    "82":10,
+    "83":10,
+    "84":10,
+    "85":10,
+    "86":10,
+    "87":10,
+    "88":11,
+    "89":11,
+    "90":11,
+    "91":11,
+    "92":11,
+    "93":11,
+    "94":11,
+    "95":11,
+    "96":12,
+    "97":12,
+    "98":12,
+    "99":12,
+    "100":12,
+    "101":12,
+    "102":12,
+    "103":12,
+    "104":13,
+    "105":13,
+    "106":13,
+    "107":13,
+    "108":13,
+    "109":13,
+    "110":13,
+    "111":13,
+    "112":14,
+    "113":14,
+    "114":14,
+    "115":14,
+    "116":14,
+    "117":14,
+    "118":14,
+    "119":14,
+    "120":15,
+    "121":15,
+    "122":15,
+    "123":15,
+    "124":15,
+    "125":15,
+    "126":15,
+    "127":15,
+    "128":16,
+    "129":16,
+    "130":16,
+    "131":16,
+    "132":16,
+    "133":16,
+    "134":16,
+    "135":16,
+    "136":17,
+    "137":17,
+    "138":17,
+    "139":17,
+    "140":17,
+    "141":17,
+    "142":17,
+    "143":17,
+    "144":18,
+    "145":18,
+    "146":18,
+    "147":18,
+    "148":18,
+    "149":18,
+    "150":18,
+    "151":18,
+    "152":19,
+    "153":19,
+    "154":19,
+    "155":19,
+    "156":19,
+    "157":19,
+    "158":19,
+    "159":19,
+    "160":20,
+    "161":20,
+    "162":20,
+    "163":20,
+    "164":20,
+    "165":20,
+    "166":20,
+    "167":20,
+    "168":21,
+    "169":21,
+    "170":21,
+    "171":21,
+    "172":21,
+    "173":21,
+    "174":21,
+    "175":21,
+    "176":22,
+    "177":22,
+    "178":22,
+    "179":22,
+    "180":22,
+    "181":22,
+    "182":22,
+    "183":22,
+    "184":23,
+    "185":23,
+    "186":23,
+    "187":23,
+    "188":23,
+    "189":23,
+    "190":23,
+    "191":23,
+    "192":24,
+    "193":24,
+    "194":24,
+    "195":24,
+    "196":24,
+    "197":24,
+    "198":24,
+    "199":24,
+    "200":25,
+    "201":25,
+    "202":25,
+    "203":25,
+    "204":25,
+    "205":25,
+    "206":25,
+    "207":25,
+    "208":26,
+    "209":26,
+    "210":26,
+    "211":26,
+    "212":26,
+    "213":26,
+    "214":26,
+    "215":26,
+    "216":27,
+    "217":27,
+    "218":27,
+    "219":27,
+    "220":27,
+    "221":27,
+    "222":27,
+    "223":27,
+    "224":28,
+    "225":28,
+    "226":28,
+    "227":28,
+    "228":28,
+    "229":28,
+    "230":28,
+    "231":28,
+    "232":29,
+    "233":29,
+    "234":29,
+    "235":29,
+    "236":29,
+    "237":29,
+    "238":29,
+    "239":29,
+    "240":30,
+    "241":30,
+    "242":30,
+    "243":30,
+    "244":30,
+    "245":30,
+    "246":30,
+    "247":30,
+    "248":31,
+    "249":31,
+    "250":31,
+    "251":31,
+    "252":31,
+    "253":31,
+    "254":31,
+    "255":31,
+    "256":32,
+    "257":32,
+    "258":32,
+    "259":32,
+    "260":32,
+    "261":32,
+    "262":32,
+    "263":32,
+    "264":33,
+    "265":33,
+    "266":33,
+    "267":33,
+    "268":33,
+    "269":33,
+    "270":33,
+    "271":33,
+    "272":34,
+    "273":34,
+    "274":34,
+    "275":34,
+    "276":34,
+    "277":34,
+    "278":34,
+    "279":34,
+    "280":35,
+    "281":35,
+    "282":35,
+    "283":35,
+    "284":35,
+    "285":35,
+    "286":35,
+    "287":35,
+    "288":36,
+    "289":36,
+    "290":36,
+    "291":36,
+    "292":36,
+    "293":36,
+    "294":36,
+    "295":36,
+    "296":37,
+    "297":37,
+    "298":37,
+    "299":37,
+    "300":37,
+    "301":37,
+    "302":37,
+    "303":37,
+    "304":38,
+    "305":38,
+    "306":38,
+    "307":38,
+    "308":38,
+    "309":38,
+    "310":38,
+    "311":38,
+    "312":39,
+    "313":39,
+    "314":39,
+    "315":39,
+    "316":39,
+    "317":39,
+    "318":39,
+    "319":39,
+    "320":40,
+    "321":40,
+    "322":40,
+    "323":40,
+    "324":40,
+    "325":40,
+    "326":40,
+    "327":40,
+    "328":41,
+    "329":41,
+    "330":41,
+    "331":41,
+    "332":41,
+    "333":41,
+    "334":41,
+    "335":41,
+    "336":42,
+    "337":42,
+    "338":42,
+    "339":42,
+    "340":42,
+    "341":42,
+    "342":42,
+    "343":42,
+    "344":43,
+    "345":43,
+    "346":43,
+    "347":43,
+    "348":43,
+    "349":43,
+    "350":43,
+    "351":43,
+    "352":44,
+    "353":44,
+    "354":44,
+    "355":44,
+    "356":44,
+    "357":44,
+    "358":44,
+    "359":44,
+    "360":45,
+    "361":45,
+    "362":45,
+    "363":45,
+    "364":45,
+    "365":45,
+    "366":45,
+    "367":45,
+    "368":46,
+    "369":46,
+    "370":46,
+    "371":46,
+    "372":46,
+    "373":46,
+    "374":46,
+    "375":46,
+    "376":47,
+    "377":47,
+    "378":47,
+    "379":47,
+    "380":47,
+    "381":47,
+    "382":47,
+    "383":47,
+    "384":48,
+    "385":48,
+    "386":48,
+    "387":48,
+    "388":48,
+    "389":48,
+    "390":48,
+    "391":48,
+    "392":49,
+    "393":49,
+    "394":49,
+    "395":49,
+    "396":49,
+    "397":49,
+    "398":49,
+    "399":49,
+    "400":50,
+    "401":50,
+    "402":50,
+    "403":50,
+    "404":50,
+    "405":50,
+    "406":50,
+    "407":50,
+    "408":51,
+    "409":51,
+    "410":51,
+    "411":51,
+    "412":51,
+    "413":51,
+    "414":51,
+    "415":51,
+    "416":52,
+    "417":52,
+    "418":52,
+    "419":52,
+    "420":52,
+    "421":52,
+    "422":52,
+    "423":52,
+    "424":53,
+    "425":53,
+    "426":53,
+    "427":53,
+    "428":53,
+    "429":53,
+    "430":53,
+    "431":53,
+    "432":54,
+    "433":54,
+    "434":54,
+    "435":54,
+    "436":54,
+    "437":54,
+    "438":54,
+    "439":54,
+    "440":55,
+    "441":55,
+    "442":55,
+    "443":55,
+    "444":55,
+    "445":55,
+    "446":55,
+    "447":55,
+    "448":56,
+    "449":56,
+    "450":56,
+    "451":56,
+    "452":56,
+    "453":56,
+    "454":56,
+    "455":56,
+    "456":57,
+    "457":57,
+    "458":57,
+    "459":57,
+    "460":57,
+    "461":57,
+    "462":57,
+    "463":57,
+    "464":58,
+    "465":58,
+    "466":58,
+    "467":58,
+    "468":58,
+    "469":58,
+    "470":58,
+    "471":58,
+    "472":59,
+    "473":59,
+    "474":59,
+    "475":59,
+    "476":59,
+    "477":59,
+    "478":59,
+    "479":59,
+    "480":60,
+    "481":60,
+    "482":60,
+    "483":60,
+    "484":60,
+    "485":60,
+    "486":60,
+    "487":60,
+    "488":61,
+    "489":61,
+    "490":61,
+    "491":61,
+    "492":61,
+    "493":61,
+    "494":61,
+    "495":61,
+    "496":62,
+    "497":62,
+    "498":62,
+    "499":62,
+    "500":62,
+    "501":62,
+    "502":62,
+    "503":62,
+    "504":63,
+    "505":63,
+    "506":63,
+    "507":63,
+    "508":63,
+    "509":63,
+    "510":63,
+    "511":63,
+    "512":64,
+    "513":64,
+    "514":64,
+    "515":64,
+    "516":64,
+    "517":64,
+    "518":64,
+    "519":64,
+    "520":65,
+    "521":65,
+    "522":65,
+    "523":65,
+    "524":65,
+    "525":65,
+    "526":65,
+    "527":65,
+    "528":66,
+    "529":66,
+    "530":66,
+    "531":66,
+    "532":66,
+    "533":66,
+    "534":66,
+    "535":66,
+    "536":67,
+    "537":67,
+    "538":67,
+    "539":67,
+    "540":67,
+    "541":67,
+    "542":67,
+    "543":67,
+    "544":68,
+    "545":68,
+    "546":68,
+    "547":68,
+    "548":68,
+    "549":68,
+    "550":68,
+    "551":68,
+    "552":69,
+    "553":69,
+    "554":69,
+    "555":69,
+    "556":69,
+    "557":69,
+    "558":69,
+    "559":69,
+    "560":70,
+    "561":70,
+    "562":70,
+    "563":70,
+    "564":70,
+    "565":70,
+    "566":70,
+    "567":70,
+    "568":71,
+    "569":71,
+    "570":71,
+    "571":71,
+    "572":71,
+    "573":71,
+    "574":71,
+    "575":71,
+    "576":72,
+    "577":72,
+    "578":72,
+    "579":72,
+    "580":72,
+    "581":72,
+    "582":72,
+    "583":72,
+    "584":73,
+    "585":73,
+    "586":73,
+    "587":73,
+    "588":73,
+    "589":73,
+    "590":73,
+    "591":73,
+    "592":74,
+    "593":74,
+    "594":74,
+    "595":74,
+    "596":74,
+    "597":74,
+    "598":74,
+    "599":74,
+    "600":75,
+    "601":75,
+    "602":75,
+    "603":75,
+    "604":75,
+    "605":75,
+    "606":75,
+    "607":75,
+    "608":76,
+    "609":76,
+    "610":76,
+    "611":76,
+    "612":76,
+    "613":76,
+    "614":76,
+    "615":76,
+    "616":77,
+    "617":77,
+    "618":77,
+    "619":77,
+    "620":77,
+    "621":77,
+    "622":77,
+    "623":77,
+    "624":78,
+    "625":78,
+    "626":78,
+    "627":78,
+    "628":78,
+    "629":78,
+    "630":78,
+    "631":78,
+    "632":79,
+    "633":79,
+    "634":79,
+    "635":79,
+    "636":79,
+    "637":79,
+    "638":79,
+    "639":79,
+    "640":80,
+    "641":80,
+    "642":80,
+    "643":80,
+    "644":80,
+    "645":80,
+    "646":80,
+    "647":80,
+    "648":81,
+    "649":81,
+    "650":81,
+    "651":81,
+    "652":81,
+    "653":81,
+    "654":81,
+    "655":81,
+    "656":82,
+    "657":82,
+    "658":82,
+    "659":82,
+    "660":82,
+    "661":82,
+    "662":82,
+    "663":82,
+    "664":83,
+    "665":83,
+    "666":83,
+    "667":83,
+    "668":83,
+    "669":83,
+    "670":83,
+    "671":83,
+    "672":84,
+    "673":84,
+    "674":84,
+    "675":84,
+    "676":84,
+    "677":84,
+    "678":84,
+    "679":84,
+    "680":85,
+    "681":85,
+    "682":85,
+    "683":85,
+    "684":85,
+    "685":85,
+    "686":85,
+    "687":85,
+    "688":86,
+    "689":86,
+    "690":86,
+    "691":86,
+    "692":86,
+    "693":86,
+    "694":86,
+    "695":86,
+    "696":87,
+    "697":87,
+    "698":87,
+    "699":87,
+    "700":87,
+    "701":87,
+    "702":87,
+    "703":87,
+    "704":88,
+    "705":88,
+    "706":88,
+    "707":88,
+    "708":88,
+    "709":88,
+    "710":88,
+    "711":88,
+    "712":89,
+    "713":89,
+    "714":89,
+    "715":89,
+    "716":89,
+    "717":89,
+    "718":89,
+    "719":89,
+    "720":90,
+    "721":90,
+    "722":90,
+    "723":90,
+    "724":90,
+    "725":90,
+    "726":90,
+    "727":90,
+    "728":91,
+    "729":91,
+    "730":91,
+    "731":91,
+    "732":91,
+    "733":91,
+    "734":91,
+    "735":91,
+    "736":92,
+    "737":92,
+    "738":92,
+    "739":92,
+    "740":92,
+    "741":92,
+    "742":92,
+    "743":92,
+    "744":93,
+    "745":93,
+    "746":93,
+    "747":93,
+    "748":93,
+    "749":93,
+    "750":93,
+    "751":93,
+    "752":94,
+    "753":94,
+    "754":94,
+    "755":94,
+    "756":94,
+    "757":94,
+    "758":94,
+    "759":94,
+    "760":95,
+    "761":95,
+    "762":95,
+    "763":95,
+    "764":95,
+    "765":95,
+    "766":95,
+    "767":95,
+    "768":96,
+    "769":96,
+    "770":96,
+    "771":96,
+    "772":96,
+    "773":96,
+    "774":96,
+    "775":96,
+    "776":97,
+    "777":97,
+    "778":97,
+    "779":97,
+    "780":97,
+    "781":97,
+    "782":97,
+    "783":97,
+    "784":98,
+    "785":98,
+    "786":98,
+    "787":98,
+    "788":98,
+    "789":98,
+    "790":98,
+    "791":98,
+    "792":99,
+    "793":99,
+    "794":99,
+    "795":99,
+    "796":99,
+    "797":99,
+    "798":99,
+    "799":99,
+    "800":100,
+    "801":100,
+    "802":100,
+    "803":100,
+    "804":100,
+    "805":100,
+    "806":100,
+    "807":100,
+    "808":101,
+    "809":101,
+    "810":101,
+    "811":101,
+    "812":101,
+    "813":101,
+    "814":101,
+    "815":101,
+    "816":102,
+    "817":102,
+    "818":102,
+    "819":102,
+    "820":102,
+    "821":102,
+    "822":102,
+    "823":102,
+    "824":103,
+    "825":103,
+    "826":103,
+    "827":103,
+    "828":103,
+    "829":103,
+    "830":103,
+    "831":103,
+    "832":104,
+    "833":104,
+    "834":104,
+    "835":104,
+    "836":104,
+    "837":104,
+    "838":104,
+    "839":104,
+    "840":105,
+    "841":105,
+    "842":105,
+    "843":105,
+    "844":105,
+    "845":105,
+    "846":105,
+    "847":105,
+    "848":106,
+    "849":106,
+    "850":106,
+    "851":106,
+    "852":106,
+    "853":106,
+    "854":106,
+    "855":106,
+    "856":107,
+    "857":107,
+    "858":107,
+    "859":107,
+    "860":107,
+    "861":107,
+    "862":107,
+    "863":107,
+    "864":108,
+    "865":108,
+    "866":108,
+    "867":108,
+    "868":108,
+    "869":108,
+    "870":108,
+    "871":108,
+    "872":109,
+    "873":109,
+    "874":109,
+    "875":109,
+    "876":109,
+    "877":109,
+    "878":109,
+    "879":109,
+    "880":110,
+    "881":110,
+    "882":110,
+    "883":110,
+    "884":110,
+    "885":110,
+    "886":110,
+    "887":110,
+    "888":111,
+    "889":111,
+    "890":111,
+    "891":111,
+    "892":111,
+    "893":111,
+    "894":111,
+    "895":111,
+    "896":112,
+    "897":112,
+    "898":112,
+    "899":112,
+    "900":112,
+    "901":112,
+    "902":112,
+    "903":112,
+    "904":113,
+    "905":113,
+    "906":113,
+    "907":113,
+    "908":113,
+    "909":113,
+    "910":113,
+    "911":113,
+    "912":114,
+    "913":114,
+    "914":114,
+    "915":114,
+    "916":114,
+    "917":114,
+    "918":114,
+    "919":114,
+    "920":115,
+    "921":115,
+    "922":115,
+    "923":115,
+    "924":115,
+    "925":115,
+    "926":115,
+    "927":115,
+    "928":116,
+    "929":116,
+    "930":116,
+    "931":116,
+    "932":116,
+    "933":116,
+    "934":116,
+    "935":116,
+    "936":117,
+    "937":117,
+    "938":117,
+    "939":117,
+    "940":117,
+    "941":117,
+    "942":117,
+    "943":117,
+    "944":118,
+    "945":118,
+    "946":118,
+    "947":118,
+    "948":118,
+    "949":118,
+    "950":118,
+    "951":118,
+    "952":119,
+    "953":119,
+    "954":119,
+    "955":119,
+    "956":119,
+    "957":119,
+    "958":119,
+    "959":119,
+    "960":120,
+    "961":120,
+    "962":120,
+    "963":120,
+    "964":120,
+    "965":120,
+    "966":120,
+    "967":120,
+    "968":121,
+    "969":121,
+    "970":121,
+    "971":121,
+    "972":121,
+    "973":121,
+    "974":121,
+    "975":121,
+    "976":122,
+    "977":122,
+    "978":122,
+    "979":122,
+    "980":122,
+    "981":122,
+    "982":122,
+    "983":122,
+    "984":123,
+    "985":123,
+    "986":123,
+    "987":123,
+    "988":123,
+    "989":123,
+    "990":123,
+    "991":123,
+    "992":124,
+    "993":124,
+    "994":124,
+    "995":124,
+    "996":124,
+    "997":124,
+    "998":124,
+    "999":124
+}

+ 17 - 0
backend/src/hand_model_outputsize.py

@@ -0,0 +1,17 @@
+import torch
+from tqdm import tqdm
+import json
+
+from src.model import handpose_model
+
+model = handpose_model()
+
+size = {}
+for i in tqdm(range(10, 1000)):
+    data = torch.randn(1, 3, i, i)
+    if torch.cuda.is_available():
+        data = data.cuda()
+    size[i] = model(data).size(2)
+
+with open('hand_model_output_size.json') as f:
+    json.dump(size, f)

+ 217 - 0
backend/src/infer_video_d2.py

@@ -0,0 +1,217 @@
+# Copyright (c) 2018-present, Facebook, Inc.
+# All rights reserved.
+#
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+#
+
+"""Perform inference on a single video or all videos with a certain extension
+(e.g., .mp4) in a folder.
+"""
+
+import detectron2
+from detectron2.utils.logger import setup_logger
+from detectron2.config import get_cfg
+from detectron2 import model_zoo
+from detectron2.engine import DefaultPredictor
+
+import subprocess as sp
+import numpy as np
+import time
+import argparse
+import sys
+import os
+import glob
+import copy
+
+
+def parse_args():
+    parser = argparse.ArgumentParser(description='End-to-end inference')
+    parser.add_argument(
+        '--cfg',
+        dest='cfg',
+        help='cfg model file (/path/to/model_config.yaml)',
+        default="COCO-Keypoints/keypoint_rcnn_R_101_FPN_3x.yaml",
+        type=str
+    )
+    parser.add_argument(
+        '--output-dir',
+        dest='output_dir',
+        help='directory for visualization pdfs (default: /tmp/infer_simple)',
+        default='/tmp/infer_simple',
+        type=str
+    )
+    parser.add_argument(
+        '--image-ext',
+        dest='image_ext',
+        help='image file name extension (default: mp4)',
+        default='png',
+        type=str
+    )
+    parser.add_argument(
+        '--im_or_folder', help='image or folder of images', default='input'
+    )
+    # if len(sys.argv) == 1:
+    #     parser.print_help()
+    #     sys.exit(1)
+    return parser.parse_args()
+
+
+def get_resolution(filename):
+    command = ['ffprobe', '-v', 'error', '-select_streams', 'v:0',
+               '-show_entries', 'stream=width,height', '-of', 'csv=p=0', filename]
+    pipe = sp.Popen(command, stdout=sp.PIPE, bufsize=-1)
+    for line in pipe.stdout:
+        w, h = line.decode().strip().split(',')
+        return int(w), int(h)
+
+
+def read_video(filename):
+    w, h = get_resolution(filename)
+
+    command = ['ffmpeg',
+               '-i', filename,
+               '-f', 'image2pipe',
+               '-pix_fmt', 'bgr24',
+               '-vsync', '0',
+               '-vcodec', 'rawvideo', '-']
+
+    pipe = sp.Popen(command, stdout=sp.PIPE, bufsize=-1)
+    while True:
+        data = pipe.stdout.read(w * h * 3)
+        if not data:
+            break
+        yield np.frombuffer(data, dtype='uint8').reshape((h, w, 3))
+
+
+def load_predictor():
+    cfg = get_cfg()
+    args = parse_args()
+    cfg.merge_from_file(model_zoo.get_config_file(args.cfg))
+    cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.7
+    cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url(args.cfg)
+    predictor = DefaultPredictor(cfg)
+    return predictor
+
+
+def main(args):
+    predictor = load_predictor()
+    if os.path.isdir(args.im_or_folder):
+        im_list = glob.iglob(args.im_or_folder + '/*.' + args.image_ext)
+    else:
+        im_list = [args.im_or_folder]
+
+    for video_name in im_list:
+        out_name = os.path.join(
+            args.output_dir, os.path.basename(video_name)
+        )
+        print('Processing {}'.format(video_name))
+
+        boxes = []
+        segments = []
+        keypoints = []
+
+        for frame_i, im in enumerate(read_video(video_name)):
+            t = time.time()
+            outputs = predictor(im)['instances'].to('cpu')
+
+            print('Frame {} processed in {:.3f}s'.format(frame_i, time.time() - t))
+
+            has_bbox = False
+            if outputs.has('pred_boxes'):
+                bbox_tensor = outputs.pred_boxes.tensor.numpy()
+                if len(bbox_tensor) > 0:
+                    has_bbox = True
+                    scores = outputs.scores.numpy()[:, None]
+                    bbox_tensor = np.concatenate((bbox_tensor, scores), axis=1)
+            if has_bbox:
+                kps = outputs.pred_keypoints.numpy()
+                kps_xy = kps[:, :, :2]
+                kps_prob = kps[:, :, 2:3]
+                kps_logit = np.zeros_like(kps_prob)  # Dummy
+                kps = np.concatenate((kps_xy, kps_logit, kps_prob), axis=2)
+                kps = kps.transpose(0, 2, 1)
+            else:
+                kps = []
+                bbox_tensor = []
+
+            # Mimic Detectron1 format
+            cls_boxes = [[], bbox_tensor]
+            cls_keyps = [[], kps]
+
+            boxes.append(cls_boxes)
+            segments.append(None)
+            keypoints.append(cls_keyps)
+
+        # Video resolution
+        metadata = {
+            'w': im.shape[1],
+            'h': im.shape[0],
+        }
+
+        np.savez_compressed(out_name, boxes=boxes, segments=segments, keypoints=keypoints, metadata=metadata)
+
+
+def infer_image(im, predictor):
+    outputs = predictor(im)['instances'].to('cpu')
+
+    # print('Frame {} processed in {:.3f}s'.format(frame_i, time.time() - t))
+
+    has_bbox = False
+    if outputs.has('pred_boxes'):
+        bbox_tensor = outputs.pred_boxes.tensor.numpy()
+        if len(bbox_tensor) > 0:
+            has_bbox = True
+            scores = outputs.scores.numpy()[:, None]
+            bbox_tensor = np.concatenate((bbox_tensor, scores), axis=1)
+    if has_bbox:
+        kps = outputs.pred_keypoints.numpy()
+        kps_xy = kps[:, :, :2]
+        kps_prob = kps[:, :, 2:3]
+        kps_logit = np.zeros_like(kps_prob)  # Dummy
+        kps = np.concatenate((kps_xy, kps_logit, kps_prob), axis=2)
+        kps = kps.transpose(0, 2, 1)
+    else:
+        kps = []
+        bbox_tensor = []
+
+    # Mimic Detectron1 format
+    # cls_boxes = [[], bbox_tensor]
+    # cls_keyps = [[], kps]
+    #
+    # boxes.append(cls_boxes)
+    # segments.append(None)
+    # keypoints.append(cls_keyps)
+    kps = kps.squeeze(0)
+    adjusted_kps = np.zeros((kps.shape[0], kps.shape[1] + 1))
+
+    # adjust detectron to openpose
+    adjusted_kps[:, 0] = kps[:, 0]
+    adjusted_kps[:, 1] = (kps[:, 5] + kps[:, 6]) / 2
+    adjusted_kps[:, 2] = kps[:, 6]
+    adjusted_kps[:, 3] = kps[:, 8]
+    adjusted_kps[:, 4] = kps[:, 10]
+    adjusted_kps[:, 5] = kps[:, 5]
+    adjusted_kps[:, 6] = kps[:, 7]
+    adjusted_kps[:, 7] = kps[:, 9]
+    adjusted_kps[:, 8] = kps[:, 12]
+    adjusted_kps[:, 9] = kps[:, 14]
+    adjusted_kps[:, 10] = kps[:, 16]
+    adjusted_kps[:, 11] = kps[:, 11]
+    adjusted_kps[:, 12] = kps[:, 13]
+    adjusted_kps[:, 13] = kps[:, 15]
+    adjusted_kps[:, 14] = kps[:, 2]
+    adjusted_kps[:, 15] = kps[:, 1]
+    adjusted_kps[:, 16] = kps[:, 4]
+    adjusted_kps[:, 17] = kps[:, 3]
+
+    subset = np.arange(20) / 1.0
+    subset[-1] = 18
+    subset[-2] = np.sum(kps[3,:])
+    return np.transpose(adjusted_kps[:2]), subset
+
+
+if __name__ == '__main__':
+    setup_logger()
+    args = parse_args()
+    main(args)

+ 219 - 0
backend/src/model.py

@@ -0,0 +1,219 @@
+import torch
+from collections import OrderedDict
+
+import torch
+import torch.nn as nn
+
+def make_layers(block, no_relu_layers):
+    layers = []
+    for layer_name, v in block.items():
+        if 'pool' in layer_name:
+            layer = nn.MaxPool2d(kernel_size=v[0], stride=v[1],
+                                    padding=v[2])
+            layers.append((layer_name, layer))
+        else:
+            conv2d = nn.Conv2d(in_channels=v[0], out_channels=v[1],
+                               kernel_size=v[2], stride=v[3],
+                               padding=v[4])
+            layers.append((layer_name, conv2d))
+            if layer_name not in no_relu_layers:
+                layers.append(('relu_'+layer_name, nn.ReLU(inplace=True)))
+
+    return nn.Sequential(OrderedDict(layers))
+
+class bodypose_model(nn.Module):
+    def __init__(self):
+        super(bodypose_model, self).__init__()
+
+        # these layers have no relu layer
+        no_relu_layers = ['conv5_5_CPM_L1', 'conv5_5_CPM_L2', 'Mconv7_stage2_L1',\
+                          'Mconv7_stage2_L2', 'Mconv7_stage3_L1', 'Mconv7_stage3_L2',\
+                          'Mconv7_stage4_L1', 'Mconv7_stage4_L2', 'Mconv7_stage5_L1',\
+                          'Mconv7_stage5_L2', 'Mconv7_stage6_L1', 'Mconv7_stage6_L1']
+        blocks = {}
+        block0 = OrderedDict([
+                      ('conv1_1', [3, 64, 3, 1, 1]),
+                      ('conv1_2', [64, 64, 3, 1, 1]),
+                      ('pool1_stage1', [2, 2, 0]),
+                      ('conv2_1', [64, 128, 3, 1, 1]),
+                      ('conv2_2', [128, 128, 3, 1, 1]),
+                      ('pool2_stage1', [2, 2, 0]),
+                      ('conv3_1', [128, 256, 3, 1, 1]),
+                      ('conv3_2', [256, 256, 3, 1, 1]),
+                      ('conv3_3', [256, 256, 3, 1, 1]),
+                      ('conv3_4', [256, 256, 3, 1, 1]),
+                      ('pool3_stage1', [2, 2, 0]),
+                      ('conv4_1', [256, 512, 3, 1, 1]),
+                      ('conv4_2', [512, 512, 3, 1, 1]),
+                      ('conv4_3_CPM', [512, 256, 3, 1, 1]),
+                      ('conv4_4_CPM', [256, 128, 3, 1, 1])
+                  ])
+
+
+        # Stage 1
+        block1_1 = OrderedDict([
+                        ('conv5_1_CPM_L1', [128, 128, 3, 1, 1]),
+                        ('conv5_2_CPM_L1', [128, 128, 3, 1, 1]),
+                        ('conv5_3_CPM_L1', [128, 128, 3, 1, 1]),
+                        ('conv5_4_CPM_L1', [128, 512, 1, 1, 0]),
+                        ('conv5_5_CPM_L1', [512, 38, 1, 1, 0])
+                    ])
+
+        block1_2 = OrderedDict([
+                        ('conv5_1_CPM_L2', [128, 128, 3, 1, 1]),
+                        ('conv5_2_CPM_L2', [128, 128, 3, 1, 1]),
+                        ('conv5_3_CPM_L2', [128, 128, 3, 1, 1]),
+                        ('conv5_4_CPM_L2', [128, 512, 1, 1, 0]),
+                        ('conv5_5_CPM_L2', [512, 19, 1, 1, 0])
+                    ])
+        blocks['block1_1'] = block1_1
+        blocks['block1_2'] = block1_2
+
+        self.model0 = make_layers(block0, no_relu_layers)
+
+        # Stages 2 - 6
+        for i in range(2, 7):
+            blocks['block%d_1' % i] = OrderedDict([
+                    ('Mconv1_stage%d_L1' % i, [185, 128, 7, 1, 3]),
+                    ('Mconv2_stage%d_L1' % i, [128, 128, 7, 1, 3]),
+                    ('Mconv3_stage%d_L1' % i, [128, 128, 7, 1, 3]),
+                    ('Mconv4_stage%d_L1' % i, [128, 128, 7, 1, 3]),
+                    ('Mconv5_stage%d_L1' % i, [128, 128, 7, 1, 3]),
+                    ('Mconv6_stage%d_L1' % i, [128, 128, 1, 1, 0]),
+                    ('Mconv7_stage%d_L1' % i, [128, 38, 1, 1, 0])
+                ])
+
+            blocks['block%d_2' % i] = OrderedDict([
+                    ('Mconv1_stage%d_L2' % i, [185, 128, 7, 1, 3]),
+                    ('Mconv2_stage%d_L2' % i, [128, 128, 7, 1, 3]),
+                    ('Mconv3_stage%d_L2' % i, [128, 128, 7, 1, 3]),
+                    ('Mconv4_stage%d_L2' % i, [128, 128, 7, 1, 3]),
+                    ('Mconv5_stage%d_L2' % i, [128, 128, 7, 1, 3]),
+                    ('Mconv6_stage%d_L2' % i, [128, 128, 1, 1, 0]),
+                    ('Mconv7_stage%d_L2' % i, [128, 19, 1, 1, 0])
+                ])
+
+        for k in blocks.keys():
+            blocks[k] = make_layers(blocks[k], no_relu_layers)
+
+        self.model1_1 = blocks['block1_1']
+        self.model2_1 = blocks['block2_1']
+        self.model3_1 = blocks['block3_1']
+        self.model4_1 = blocks['block4_1']
+        self.model5_1 = blocks['block5_1']
+        self.model6_1 = blocks['block6_1']
+
+        self.model1_2 = blocks['block1_2']
+        self.model2_2 = blocks['block2_2']
+        self.model3_2 = blocks['block3_2']
+        self.model4_2 = blocks['block4_2']
+        self.model5_2 = blocks['block5_2']
+        self.model6_2 = blocks['block6_2']
+
+
+    def forward(self, x):
+
+        out1 = self.model0(x)
+
+        out1_1 = self.model1_1(out1)
+        out1_2 = self.model1_2(out1)
+        out2 = torch.cat([out1_1, out1_2, out1], 1)
+
+        out2_1 = self.model2_1(out2)
+        out2_2 = self.model2_2(out2)
+        out3 = torch.cat([out2_1, out2_2, out1], 1)
+
+        out3_1 = self.model3_1(out3)
+        out3_2 = self.model3_2(out3)
+        out4 = torch.cat([out3_1, out3_2, out1], 1)
+
+        out4_1 = self.model4_1(out4)
+        out4_2 = self.model4_2(out4)
+        out5 = torch.cat([out4_1, out4_2, out1], 1)
+
+        out5_1 = self.model5_1(out5)
+        out5_2 = self.model5_2(out5)
+        out6 = torch.cat([out5_1, out5_2, out1], 1)
+
+        out6_1 = self.model6_1(out6)
+        out6_2 = self.model6_2(out6)
+
+        return out6_1, out6_2
+
+class handpose_model(nn.Module):
+    def __init__(self):
+        super(handpose_model, self).__init__()
+
+        # these layers have no relu layer
+        no_relu_layers = ['conv6_2_CPM', 'Mconv7_stage2', 'Mconv7_stage3',\
+                          'Mconv7_stage4', 'Mconv7_stage5', 'Mconv7_stage6']
+        # stage 1
+        block1_0 = OrderedDict([
+                ('conv1_1', [3, 64, 3, 1, 1]),
+                ('conv1_2', [64, 64, 3, 1, 1]),
+                ('pool1_stage1', [2, 2, 0]),
+                ('conv2_1', [64, 128, 3, 1, 1]),
+                ('conv2_2', [128, 128, 3, 1, 1]),
+                ('pool2_stage1', [2, 2, 0]),
+                ('conv3_1', [128, 256, 3, 1, 1]),
+                ('conv3_2', [256, 256, 3, 1, 1]),
+                ('conv3_3', [256, 256, 3, 1, 1]),
+                ('conv3_4', [256, 256, 3, 1, 1]),
+                ('pool3_stage1', [2, 2, 0]),
+                ('conv4_1', [256, 512, 3, 1, 1]),
+                ('conv4_2', [512, 512, 3, 1, 1]),
+                ('conv4_3', [512, 512, 3, 1, 1]),
+                ('conv4_4', [512, 512, 3, 1, 1]),
+                ('conv5_1', [512, 512, 3, 1, 1]),
+                ('conv5_2', [512, 512, 3, 1, 1]),
+                ('conv5_3_CPM', [512, 128, 3, 1, 1])
+            ])
+
+        block1_1 = OrderedDict([
+            ('conv6_1_CPM', [128, 512, 1, 1, 0]),
+            ('conv6_2_CPM', [512, 22, 1, 1, 0])
+        ])
+
+        blocks = {}
+        blocks['block1_0'] = block1_0
+        blocks['block1_1'] = block1_1
+
+        # stage 2-6
+        for i in range(2, 7):
+            blocks['block%d' % i] = OrderedDict([
+                    ('Mconv1_stage%d' % i, [150, 128, 7, 1, 3]),
+                    ('Mconv2_stage%d' % i, [128, 128, 7, 1, 3]),
+                    ('Mconv3_stage%d' % i, [128, 128, 7, 1, 3]),
+                    ('Mconv4_stage%d' % i, [128, 128, 7, 1, 3]),
+                    ('Mconv5_stage%d' % i, [128, 128, 7, 1, 3]),
+                    ('Mconv6_stage%d' % i, [128, 128, 1, 1, 0]),
+                    ('Mconv7_stage%d' % i, [128, 22, 1, 1, 0])
+                ])
+
+        for k in blocks.keys():
+            blocks[k] = make_layers(blocks[k], no_relu_layers)
+
+        self.model1_0 = blocks['block1_0']
+        self.model1_1 = blocks['block1_1']
+        self.model2 = blocks['block2']
+        self.model3 = blocks['block3']
+        self.model4 = blocks['block4']
+        self.model5 = blocks['block5']
+        self.model6 = blocks['block6']
+
+    def forward(self, x):
+        out1_0 = self.model1_0(x)
+        out1_1 = self.model1_1(out1_0)
+        concat_stage2 = torch.cat([out1_1, out1_0], 1)
+        out_stage2 = self.model2(concat_stage2)
+        concat_stage3 = torch.cat([out_stage2, out1_0], 1)
+        out_stage3 = self.model3(concat_stage3)
+        concat_stage4 = torch.cat([out_stage3, out1_0], 1)
+        out_stage4 = self.model4(concat_stage4)
+        concat_stage5 = torch.cat([out_stage4, out1_0], 1)
+        out_stage5 = self.model5(concat_stage5)
+        concat_stage6 = torch.cat([out_stage5, out1_0], 1)
+        out_stage6 = self.model6(concat_stage6)
+        return out_stage6
+
+

+ 198 - 0
backend/src/util.py

@@ -0,0 +1,198 @@
+import numpy as np
+import math
+import cv2
+import matplotlib
+from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
+from matplotlib.figure import Figure
+import numpy as np
+import matplotlib.pyplot as plt
+import cv2
+
+
+def padRightDownCorner(img, stride, padValue):
+    h = img.shape[0]
+    w = img.shape[1]
+
+    pad = 4 * [None]
+    pad[0] = 0 # up
+    pad[1] = 0 # left
+    pad[2] = 0 if (h % stride == 0) else stride - (h % stride) # down
+    pad[3] = 0 if (w % stride == 0) else stride - (w % stride) # right
+
+    img_padded = img
+    pad_up = np.tile(img_padded[0:1, :, :]*0 + padValue, (pad[0], 1, 1))
+    img_padded = np.concatenate((pad_up, img_padded), axis=0)
+    pad_left = np.tile(img_padded[:, 0:1, :]*0 + padValue, (1, pad[1], 1))
+    img_padded = np.concatenate((pad_left, img_padded), axis=1)
+    pad_down = np.tile(img_padded[-2:-1, :, :]*0 + padValue, (pad[2], 1, 1))
+    img_padded = np.concatenate((img_padded, pad_down), axis=0)
+    pad_right = np.tile(img_padded[:, -2:-1, :]*0 + padValue, (1, pad[3], 1))
+    img_padded = np.concatenate((img_padded, pad_right), axis=1)
+
+    return img_padded, pad
+
+# transfer caffe model to pytorch which will match the layer name
+def transfer(model, model_weights):
+    transfered_model_weights = {}
+    for weights_name in model.state_dict().keys():
+        transfered_model_weights[weights_name] = model_weights['.'.join(weights_name.split('.')[1:])]
+    return transfered_model_weights
+
+# draw the body keypoint and lims
+def draw_bodypose(canvas, candidate, subset):
+    stickwidth = 4
+    limbSeq = [[2, 3], [2, 6], [3, 4], [4, 5], [6, 7], [7, 8], [2, 9], [9, 10], \
+               [10, 11], [2, 12], [12, 13], [13, 14], [2, 1], [1, 15], [15, 17], \
+               [1, 16], [16, 18], [3, 17], [6, 18]]
+
+    colors = [[255, 0, 0], [255, 85, 0], [255, 170, 0], [255, 255, 0], [170, 255, 0], [85, 255, 0], [0, 255, 0], \
+              [0, 255, 85], [0, 255, 170], [0, 255, 255], [0, 170, 255], [0, 85, 255], [0, 0, 255], [85, 0, 255], \
+              [170, 0, 255], [255, 0, 255], [255, 0, 170], [255, 0, 85]]
+    for i in range(18):
+        for n in range(len(subset)):
+            index = int(subset[n][i])
+            if index == -1:
+                continue
+            x, y = candidate[index][0:2]
+            cv2.circle(canvas, (int(x), int(y)), 4, colors[i], thickness=-1)
+    for i in range(17):
+        for n in range(len(subset)):
+            index = subset[n][np.array(limbSeq[i]) - 1]
+            if -1 in index:
+                continue
+            cur_canvas = canvas.copy()
+            Y = candidate[index.astype(int), 0]
+            X = candidate[index.astype(int), 1]
+            mX = np.mean(X)
+            mY = np.mean(Y)
+            length = ((X[0] - X[1]) ** 2 + (Y[0] - Y[1]) ** 2) ** 0.5
+            angle = math.degrees(math.atan2(X[0] - X[1], Y[0] - Y[1]))
+            polygon = cv2.ellipse2Poly((int(mY), int(mX)), (int(length / 2), stickwidth), int(angle), 0, 360, 1)
+            cv2.fillConvexPoly(cur_canvas, polygon, colors[i])
+            canvas = cv2.addWeighted(canvas, 0.4, cur_canvas, 0.6, 0)
+    # plt.imsave("preview.jpg", canvas[:, :, [2, 1, 0]])
+    # plt.imshow(canvas[:, :, [2, 1, 0]])
+    return canvas
+
+def draw_handpose(canvas, all_hand_peaks, show_number=False):
+    edges = [[0, 1], [1, 2], [2, 3], [3, 4], [0, 5], [5, 6], [6, 7], [7, 8], [0, 9], [9, 10], \
+             [10, 11], [11, 12], [0, 13], [13, 14], [14, 15], [15, 16], [0, 17], [17, 18], [18, 19], [19, 20]]
+    fig = Figure(figsize=plt.figaspect(canvas))
+
+    fig.subplots_adjust(0, 0, 1, 1)
+    fig.subplots_adjust(bottom=0, top=1, left=0, right=1)
+    bg = FigureCanvas(fig)
+    ax = fig.subplots()
+    ax.axis('off')
+    ax.imshow(canvas)
+
+    width, height = ax.figure.get_size_inches() * ax.figure.get_dpi()
+
+    for peaks in all_hand_peaks:
+        for ie, e in enumerate(edges):
+            if np.sum(np.all(peaks[e], axis=1)==0)==0:
+                x1, y1 = peaks[e[0]]
+                x2, y2 = peaks[e[1]]
+                ax.plot([x1, x2], [y1, y2], color=matplotlib.colors.hsv_to_rgb([ie/float(len(edges)), 1.0, 1.0]))
+
+        for i, keyponit in enumerate(peaks):
+            x, y = keyponit
+            ax.plot(x, y, 'r.')
+            if show_number:
+                ax.text(x, y, str(i))
+    bg.draw()
+    canvas = np.fromstring(bg.tostring_rgb(), dtype='uint8').reshape(int(height), int(width), 3)
+    return canvas
+
+# image drawed by opencv is not good.
+def draw_handpose_by_opencv(canvas, peaks, show_number=False):
+    edges = [[0, 1], [1, 2], [2, 3], [3, 4], [0, 5], [5, 6], [6, 7], [7, 8], [0, 9], [9, 10], \
+             [10, 11], [11, 12], [0, 13], [13, 14], [14, 15], [15, 16], [0, 17], [17, 18], [18, 19], [19, 20]]
+    # cv2.rectangle(canvas, (x, y), (x+w, y+w), (0, 255, 0), 2, lineType=cv2.LINE_AA)
+    # cv2.putText(canvas, 'left' if is_left else 'right', (x, y), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
+    for ie, e in enumerate(edges):
+        if np.sum(np.all(peaks[e], axis=1)==0)==0:
+            x1, y1 = peaks[e[0]]
+            x2, y2 = peaks[e[1]]
+            cv2.line(canvas, (x1, y1), (x2, y2), matplotlib.colors.hsv_to_rgb([ie/float(len(edges)), 1.0, 1.0])*255, thickness=2)
+
+    for i, keyponit in enumerate(peaks):
+        x, y = keyponit
+        cv2.circle(canvas, (x, y), 4, (0, 0, 255), thickness=-1)
+        if show_number:
+            cv2.putText(canvas, str(i), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.3, (0, 0, 0), lineType=cv2.LINE_AA)
+    return canvas
+
+# detect hand according to body pose keypoints
+# please refer to https://github.com/CMU-Perceptual-Computing-Lab/openpose/blob/master/src/openpose/hand/handDetector.cpp
+def handDetect(candidate, subset, oriImg):
+    # right hand: wrist 4, elbow 3, shoulder 2
+    # left hand: wrist 7, elbow 6, shoulder 5
+    ratioWristElbow = 0.33
+    detect_result = []
+    image_height, image_width = oriImg.shape[0:2]
+    for person in subset.astype(int):
+        # if any of three not detected
+        has_left = np.sum(person[[5, 6, 7]] == -1) == 0
+        has_right = np.sum(person[[2, 3, 4]] == -1) == 0
+        if not (has_left or has_right):
+            continue
+        hands = []
+        #left hand
+        if has_left:
+            left_shoulder_index, left_elbow_index, left_wrist_index = person[[5, 6, 7]]
+            x1, y1 = candidate[left_shoulder_index][:2]
+            x2, y2 = candidate[left_elbow_index][:2]
+            x3, y3 = candidate[left_wrist_index][:2]
+            hands.append([x1, y1, x2, y2, x3, y3, True])
+        # right hand
+        if has_right:
+            right_shoulder_index, right_elbow_index, right_wrist_index = person[[2, 3, 4]]
+            x1, y1 = candidate[right_shoulder_index][:2]
+            x2, y2 = candidate[right_elbow_index][:2]
+            x3, y3 = candidate[right_wrist_index][:2]
+            hands.append([x1, y1, x2, y2, x3, y3, False])
+
+        for x1, y1, x2, y2, x3, y3, is_left in hands:
+            # pos_hand = pos_wrist + ratio * (pos_wrist - pos_elbox) = (1 + ratio) * pos_wrist - ratio * pos_elbox
+            # handRectangle.x = posePtr[wrist*3] + ratioWristElbow * (posePtr[wrist*3] - posePtr[elbow*3]);
+            # handRectangle.y = posePtr[wrist*3+1] + ratioWristElbow * (posePtr[wrist*3+1] - posePtr[elbow*3+1]);
+            # const auto distanceWristElbow = getDistance(poseKeypoints, person, wrist, elbow);
+            # const auto distanceElbowShoulder = getDistance(poseKeypoints, person, elbow, shoulder);
+            # handRectangle.width = 1.5f * fastMax(distanceWristElbow, 0.9f * distanceElbowShoulder);
+            x = x3 + ratioWristElbow * (x3 - x2)
+            y = y3 + ratioWristElbow * (y3 - y2)
+            distanceWristElbow = math.sqrt((x3 - x2) ** 2 + (y3 - y2) ** 2)
+            distanceElbowShoulder = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
+            width = 1.5 * max(distanceWristElbow, 0.9 * distanceElbowShoulder)
+            # x-y refers to the center --> offset to topLeft point
+            # handRectangle.x -= handRectangle.width / 2.f;
+            # handRectangle.y -= handRectangle.height / 2.f;
+            x -= width / 2
+            y -= width / 2  # width = height
+            # overflow the image
+            if x < 0: x = 0
+            if y < 0: y = 0
+            width1 = width
+            width2 = width
+            if x + width > image_width: width1 = image_width - x
+            if y + width > image_height: width2 = image_height - y
+            width = min(width1, width2)
+            # the max hand box value is 20 pixels
+            if width >= 20:
+                detect_result.append([int(x), int(y), int(width), is_left])
+
+    '''
+    return value: [[x, y, w, True if left hand else False]].
+    width=height since the network require squared input.
+    x, y is the coordinate of top left 
+    '''
+    return detect_result
+
+# get max index of 2d array
+def npmax(array):
+    arrayindex = array.argmax(1)
+    arrayvalue = array.max(1)
+    i = arrayvalue.argmax()
+    j = arrayindex[i]
+    return i, j

+ 177 - 0
backend/supervisor.conf

@@ -0,0 +1,177 @@
+; Sample supervisor config file.
+;
+; For more information on the config file, please see:
+; http://supervisord.org/configuration.html
+;
+; Notes:
+;  - Shell expansion ("~" or "$HOME") is not supported.  Environment
+;    variables can be expanded using this syntax: "%(ENV_HOME)s".
+;  - Quotes around values are not supported, except in the case of
+;    the environment= options as shown below.
+;  - Comments must have a leading space: "a=b ;comment" not "a=b;comment".
+;  - Command will be truncated if it looks like a config file comment, e.g.
+;    "command=bash -c 'foo ; bar'" will truncate to "command=bash -c 'foo ".
+;
+; Warning:
+;  Paths throughout this example file use /tmp because it is available on most
+;  systems.  You will likely need to change these to locations more appropriate
+;  for your system.  Some systems periodically delete older files in /tmp.
+;  Notably, if the socket file defined in the [unix_http_server] section below
+;  is deleted, supervisorctl will be unable to connect to supervisord.
+
+[unix_http_server]
+file=/tmp/supervisor.sock   ; the path to the socket file
+;chmod=0700                 ; socket file mode (default 0700)
+;chown=nobody:nogroup       ; socket file uid:gid owner
+;username=user              ; default is no username (open server)
+;password=123               ; default is no password (open server)
+
+; Security Warning:
+;  The inet HTTP server is not enabled by default.  The inet HTTP server is
+;  enabled by uncommenting the [inet_http_server] section below.  The inet
+;  HTTP server is intended for use within a trusted environment only.  It
+;  should only be bound to localhost or only accessible from within an
+;  isolated, trusted network.  The inet HTTP server does not support any
+;  form of encryption.  The inet HTTP server does not use authentication
+;  by default (see the username= and password= options to add authentication).
+;  Never expose the inet HTTP server to the public internet.
+
+;[inet_http_server]         ; inet (TCP) server disabled by default
+;port=127.0.0.1:9001        ; ip_address:port specifier, *:port for all iface
+;username=user              ; default is no username (open server)
+;password=123               ; default is no password (open server)
+
+[supervisord]
+logfile=/tmp/supervisord.log ; main log file; default $CWD/supervisord.log
+logfile_maxbytes=50MB        ; max main logfile bytes b4 rotation; default 50MB
+logfile_backups=10           ; # of main logfile backups; 0 means none, default 10
+loglevel=info                ; log level; default info; others: debug,warn,trace
+pidfile=/tmp/supervisord.pid ; supervisord pidfile; default supervisord.pid
+nodaemon=false               ; start in foreground if true; default false
+silent=false                 ; no logs to stdout if true; default false
+minfds=1024                  ; min. avail startup file descriptors; default 1024
+minprocs=200                 ; min. avail process descriptors;default 200
+;umask=022                   ; process file creation umask; default 022
+;user=supervisord            ; setuid to this UNIX account at startup; recommended if root
+;identifier=supervisor       ; supervisord identifier, default is 'supervisor'
+;directory=/tmp              ; default is not to cd during start
+;nocleanup=true              ; don't clean up tempfiles at start; default false
+;childlogdir=/tmp            ; 'AUTO' child log dir, default $TEMP
+;environment=KEY="value"     ; key value pairs to add to environment
+;strip_ansi=false            ; strip ansi escape codes in logs; def. false
+
+; The rpcinterface:supervisor section must remain in the config file for
+; RPC (supervisorctl/web interface) to work.  Additional interfaces may be
+; added by defining them in separate [rpcinterface:x] sections.
+
+[rpcinterface:supervisor]
+supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
+
+; The supervisorctl section configures how supervisorctl will connect to
+; supervisord.  configure it match the settings in either the unix_http_server
+; or inet_http_server section.
+
+[supervisorctl]
+serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL  for a unix socket
+;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket
+;username=chris              ; should be same as in [*_http_server] if set
+;password=123                ; should be same as in [*_http_server] if set
+;prompt=mysupervisor         ; cmd line prompt (default "supervisor")
+;history_file=~/.sc_history  ; use readline history if available
+
+; The sample program section below shows all possible program subsection values.
+; Create one or more 'real' program: sections to be able to control them under
+; supervisor.
+
+;[program:theprogramname]
+;command=/bin/cat              ; the program (relative uses PATH, can take args)
+;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
+;numprocs=1                    ; number of processes copies to start (def 1)
+;directory=/tmp                ; directory to cwd to before exec (def no cwd)
+;umask=022                     ; umask for process (default None)
+;priority=999                  ; the relative start priority (default 999)
+;autostart=true                ; start at supervisord start (default: true)
+;startsecs=1                   ; # of secs prog must stay up to be running (def. 1)
+;startretries=3                ; max # of serial start failures when starting (default 3)
+;autorestart=unexpected        ; when to restart if exited after running (def: unexpected)
+;exitcodes=0                   ; 'expected' exit codes used with autorestart (default 0)
+;stopsignal=QUIT               ; signal used to kill process (default TERM)
+;stopwaitsecs=10               ; max num secs to wait b4 SIGKILL (default 10)
+;stopasgroup=false             ; send stop signal to the UNIX process group (default false)
+;killasgroup=false             ; SIGKILL the UNIX process group (def false)
+;user=chrism                   ; setuid to this UNIX account to run the program
+;redirect_stderr=true          ; redirect proc stderr to stdout (default false)
+;stdout_logfile=/a/path        ; stdout log path, NONE for none; default AUTO
+;stdout_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)
+;stdout_logfile_backups=10     ; # of stdout logfile backups (0 means none, default 10)
+;stdout_capture_maxbytes=1MB   ; number of bytes in 'capturemode' (default 0)
+;stdout_events_enabled=false   ; emit events on stdout writes (default false)
+;stdout_syslog=false           ; send stdout to syslog with process name (default false)
+;stderr_logfile=/a/path        ; stderr log path, NONE for none; default AUTO
+;stderr_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)
+;stderr_logfile_backups=10     ; # of stderr logfile backups (0 means none, default 10)
+;stderr_capture_maxbytes=1MB   ; number of bytes in 'capturemode' (default 0)
+;stderr_events_enabled=false   ; emit events on stderr writes (default false)
+;stderr_syslog=false           ; send stderr to syslog with process name (default false)
+;environment=A="1",B="2"       ; process environment additions (def no adds)
+;serverurl=AUTO                ; override serverurl computation (childutils)
+
+; The sample eventlistener section below shows all possible eventlistener
+; subsection values.  Create one or more 'real' eventlistener: sections to be
+; able to handle event notifications sent by supervisord.
+
+;[eventlistener:theeventlistenername]
+;command=/bin/eventlistener    ; the program (relative uses PATH, can take args)
+;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
+;numprocs=1                    ; number of processes copies to start (def 1)
+;events=EVENT                  ; event notif. types to subscribe to (req'd)
+;buffer_size=10                ; event buffer queue size (default 10)
+;directory=/tmp                ; directory to cwd to before exec (def no cwd)
+;umask=022                     ; umask for process (default None)
+;priority=-1                   ; the relative start priority (default -1)
+;autostart=true                ; start at supervisord start (default: true)
+;startsecs=1                   ; # of secs prog must stay up to be running (def. 1)
+;startretries=3                ; max # of serial start failures when starting (default 3)
+;autorestart=unexpected        ; autorestart if exited after running (def: unexpected)
+;exitcodes=0                   ; 'expected' exit codes used with autorestart (default 0)
+;stopsignal=QUIT               ; signal used to kill process (default TERM)
+;stopwaitsecs=10               ; max num secs to wait b4 SIGKILL (default 10)
+;stopasgroup=false             ; send stop signal to the UNIX process group (default false)
+;killasgroup=false             ; SIGKILL the UNIX process group (def false)
+;user=chrism                   ; setuid to this UNIX account to run the program
+;redirect_stderr=false         ; redirect_stderr=true is not allowed for eventlisteners
+;stdout_logfile=/a/path        ; stdout log path, NONE for none; default AUTO
+;stdout_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)
+;stdout_logfile_backups=10     ; # of stdout logfile backups (0 means none, default 10)
+;stdout_events_enabled=false   ; emit events on stdout writes (default false)
+;stdout_syslog=false           ; send stdout to syslog with process name (default false)
+;stderr_logfile=/a/path        ; stderr log path, NONE for none; default AUTO
+;stderr_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)
+;stderr_logfile_backups=10     ; # of stderr logfile backups (0 means none, default 10)
+;stderr_events_enabled=false   ; emit events on stderr writes (default false)
+;stderr_syslog=false           ; send stderr to syslog with process name (default false)
+;environment=A="1",B="2"       ; process environment additions
+;serverurl=AUTO                ; override serverurl computation (childutils)
+
+; The sample group section below shows all possible group values.  Create one
+; or more 'real' group: sections to create "heterogeneous" process groups.
+
+;[group:thegroupname]
+;programs=progname1,progname2  ; each refers to 'x' in [program:x] definitions
+;priority=999                  ; the relative start priority (default 999)
+
+; The [include] section can just contain the "files" setting.  This
+; setting can list multiple files (separated by whitespace or
+; newlines).  It can also contain wildcards.  The filenames are
+; interpreted as relative to this file.  Included files *cannot*
+; include files themselves.
+
+;[include]
+;files = relative/directory/*.ini
+command = /home/seecoder/Documents/pose-correction/pc-demo/gunicorn -w 3 -b 0.0.0.0:5000 flask_api:app
+directory =/home/seecoder/Documents/pose-correction/pc-demo/
+autostart = true
+startsecs = 5
+autorestart = true
+autowaitsecs = 0
+stdout_logfile = /data/logs/usercenter_stout.log

+ 64 - 0
backend/video_to_photo.py

@@ -0,0 +1,64 @@
+import os
+
+import  cv2
+from demo_func import run_openpose_for_normal
+from PIL import Image
+
+
+
+def split_video():
+    video_path = "./video/200467890-1-192.mp4"
+    cap = cv2.VideoCapture(video_path)
+    FPS = cap.get(5)
+    print(FPS)
+    c = 1
+    frameRate = 1
+
+    while (True):
+        ret, frame = cap.read()
+        if ret:
+            if (c % frameRate == 0):
+                cv2.imwrite("./capture_image/capture_image" + str(c) + '.png', frame)
+                
+            c += 1
+            cv2.waitKey(0)
+        else:
+            break
+    cap.release()
+    return c
+    
+def process_photo(c):
+    for i in range(1,c):
+        run_openpose_for_normal(i)
+    
+def gether_video():
+    fps = 30  # 帧率
+    num_frames = len(os.listdir(r'./capture_image'))
+    print(num_frames)
+    img_array = []
+    im=Image.open("./capture_image/capture_image_result1.png")
+
+    #img_width = 720
+    #img_height = 1280
+    k=0
+    for i in range(num_frames + 1):
+        filename = "./capture_image/capture_image_result" + str(i) + ".png"
+        k+=1
+        img = cv2.imread(filename)
+
+        if img is None:
+            continue
+        img_array.append(img)
+    print(k)
+    out = cv2.VideoWriter('demo.mp4', cv2.VideoWriter_fourcc(*"mp4v"), fps, im.size)
+
+    for i in range(len(img_array)):
+        out.write(img_array[i])
+    out.release()
+
+if __name__ == '__main__':
+    c=split_video()
+    process_photo(c)
+    gether_video()
+
+

+ 0 - 23
frontend/.gitignore

@@ -1,23 +0,0 @@
-.DS_Store
-/node_modules
-/dist
-
-
-# local env files
-.env.local
-.env.*.local
-
-# Log files
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-pnpm-debug.log*
-
-# Editor directories and files
-.idea
-.vscode
-*.suo
-*.ntvs*
-*.njsproj
-*.sln
-*.sw?