| import math
|
|
|
|
|
|
|
|
|
| def classify_male_body_type(body_measurements):
|
| """
|
| 根據胸圍、腰圍、臀圍判斷男生體型:
|
| 回傳其中一個:
|
| "矩形", "三角形", "倒三角", "梯形", "橢圓形"
|
| """
|
|
|
| C = body_measurements["chest circumference"]
|
| W = body_measurements["waist circumference"]
|
| H = body_measurements["hip circumference"]
|
|
|
| if C <= 0 or W <= 0 or H <= 0:
|
| raise ValueError("胸圍、腰圍、臀圍必須為正數")
|
|
|
|
|
| total = C + W + H
|
| pC, pW, pH = C / total, W / total, H / total
|
| v = (pC, pW, pH)
|
|
|
|
|
| ideal_patterns = {
|
| "矩形": (0.34, 0.32, 0.34),
|
| "三角形": (0.32, 0.31, 0.37),
|
| "倒三角": (0.37, 0.31, 0.32),
|
| "梯形": (0.36, 0.31, 0.33),
|
| "橢圓形": (0.32, 0.36, 0.32),
|
| }
|
|
|
|
|
| def gate_rect(C, W, H):
|
|
|
| diff_CH = abs(C - H) / max(C, H)
|
| return diff_CH <= 0.05 and W <= min(C, H)
|
|
|
| def gate_tri(C, W, H):
|
|
|
| return (H >= C * 1.10) and (W <= min(C, H))
|
|
|
| def gate_inv(C, W, H):
|
|
|
| return (C >= H * 1.10) and (W <= min(C, H))
|
|
|
| def gate_trap(C, W, H):
|
|
|
| return (C > H * 1.05) and (C <= H * 1.10) and (W <= min(C, H))
|
|
|
| def gate_oval(C, W, H):
|
|
|
| return (W >= C * 1.05) and (W >= H * 1.05)
|
|
|
| gates = {
|
| "矩形": gate_rect,
|
| "三角形": gate_tri,
|
| "倒三角": gate_inv,
|
| "梯形": gate_trap,
|
| "橢圓形": gate_oval,
|
| }
|
|
|
| def dist(a, b):
|
| return math.sqrt(
|
| (a[0] - b[0]) ** 2 +
|
| (a[1] - b[1]) ** 2 +
|
| (a[2] - b[2]) ** 2
|
| )
|
|
|
|
|
| candidates = []
|
| for name in ideal_patterns.keys():
|
| if gates[name](C, W, H):
|
| d = dist(v, ideal_patterns[name])
|
| candidates.append((d, name))
|
|
|
| if candidates:
|
| candidates.sort()
|
| return candidates[0][1]
|
|
|
|
|
| best_name = None
|
| best_d = float("inf")
|
| for name, ideal_v in ideal_patterns.items():
|
| d = dist(v, ideal_v)
|
| if d < best_d:
|
| best_d = d
|
| best_name = name
|
|
|
| return best_name
|
|
|
|
|
|
|
|
|
|
|
| def classify_female_body_type(body_measurements):
|
| """
|
| 使用【比例】而不是【公分】來判斷女性體型,並引入 ideal_patterns + 距離判斷:
|
| - 沙漏型:胸 & 臀 都明顯 > 腰,且胸臀相近
|
| - 蘋果型:腰明顯 > 臀(且多半 ≥ 胸)
|
| - 梨型:臀明顯 > 肩寬x2
|
| - 倒三角型:肩寬x2 明顯 > 臀
|
| - H 型:胸/腰/臀差距都不大、肩寬x2 與 臀也接近
|
|
|
| 回傳:
|
| "蘋果型", "梨型", "沙漏型", "倒三角型", "H 型"
|
| """
|
|
|
| C = body_measurements["chest circumference"]
|
| W = body_measurements["waist circumference"]
|
| H = body_measurements["hip circumference"]
|
| S = body_measurements["shoulder breadth"]
|
| shoulders2 = S * 2
|
|
|
| if min(C, W, H, shoulders2) <= 0:
|
| raise ValueError("胸/腰/臀/肩寬 必須為正數")
|
|
|
|
|
| total = C + W + H
|
| pC, pW, pH = C / total, W / total, H / total
|
| v = (pC, pW, pH)
|
|
|
|
|
| ideal_patterns = {
|
| "沙漏型": (0.35, 0.30, 0.35),
|
| "蘋果型": (0.32, 0.36, 0.32),
|
| "梨型": (0.32, 0.31, 0.37),
|
| "倒三角型": (0.37, 0.31, 0.32),
|
| "H 型": (0.34, 0.32, 0.34),
|
| }
|
|
|
| def dist(a, b):
|
| return math.sqrt(
|
| (a[0] - b[0]) ** 2 +
|
| (a[1] - b[1]) ** 2 +
|
| (a[2] - b[2]) ** 2
|
| )
|
|
|
|
|
| cw_ratio = (C - W) / W
|
| hw_ratio = (H - W) / W
|
| hip_shoulder_diff = (H - shoulders2) / H
|
|
|
| ch_diff_ratio = abs(C - H) / max(C, H)
|
| w_vs_ch_hip = (W - min(C, H)) / min(C, H)
|
|
|
|
|
| def gate_hourglass(C, W, H, shoulders2):
|
| return (cw_ratio >= 0.25 and hw_ratio >= 0.30 and ch_diff_ratio <= 0.07)
|
|
|
|
|
| def gate_apple(C, W, H, shoulders2):
|
| return (W >= max(C, H)) and ((W - H) / H > 0.03)
|
|
|
|
|
| def gate_pear(C, W, H, shoulders2):
|
| return hip_shoulder_diff > 0.03
|
|
|
|
|
| def gate_inv_tri(C, W, H, shoulders2):
|
| return hip_shoulder_diff < -0.03
|
|
|
|
|
| def gate_h(C, W, H, shoulders2):
|
| return (
|
| abs(hip_shoulder_diff) <= 0.03 and
|
| abs(cw_ratio) <= 0.25 and
|
| abs(hw_ratio) <= 0.25 and
|
| w_vs_ch_hip < 0.10
|
| )
|
|
|
| gates = {
|
| "沙漏型": gate_hourglass,
|
| "蘋果型": gate_apple,
|
| "梨型": gate_pear,
|
| "倒三角型": gate_inv_tri,
|
| "H 型": gate_h,
|
| }
|
|
|
|
|
| candidates = []
|
| for name in ideal_patterns.keys():
|
| if gates[name](C, W, H, shoulders2):
|
| d = dist(v, ideal_patterns[name])
|
| candidates.append((d, name))
|
|
|
| if candidates:
|
| candidates.sort()
|
| return candidates[0][1]
|
|
|
|
|
| best_name = None
|
| best_d = float("inf")
|
| for name, ideal_v in ideal_patterns.items():
|
| d = dist(v, ideal_v)
|
| if d < best_d:
|
| best_d = d
|
| best_name = name
|
|
|
| return best_name
|
|
|
|
|
|
|
|
|
|
|
| if __name__ == "__main__":
|
|
|
| male_body_measurements = {
|
| "height": 196.27,
|
| "shoulder to crotch height": 77.3,
|
| "arm left length": 55.35,
|
| "arm right length": 58.13,
|
| "inside leg height": 81.09,
|
| "shoulder breadth": 62.82,
|
| "arm length (shoulder to elbow)": 36.15,
|
| "crotch height": 83.7,
|
| "Hip circumference max height": 94.81,
|
| "arm length (spine to wrist)": 32.54,
|
| "head circumference": 60.98,
|
| "neck circumference": 33.72,
|
| "chest circumference": 150.78,
|
| "waist circumference": 154.85,
|
| "hip circumference": 158.92,
|
| "wrist right circumference": 25.12,
|
| "bicep right circumference": 39.0,
|
| "forearm right circumference": 35.64,
|
| "thigh left circumference": 72.86,
|
| "calf left circumference": 49.86,
|
| "ankle left circumference": 29.34
|
| }
|
|
|
| male_type = classify_male_body_type(male_body_measurements)
|
| print("男性體型判斷結果:", male_type)
|
|
|
|
|
| female_body_measurements = {
|
| "height": 165.0,
|
| "shoulder to crotch height": 60.0,
|
| "arm left length": 50.0,
|
| "arm right length": 50.0,
|
| "inside leg height": 75.0,
|
| "shoulder breadth": 40.0,
|
| "arm length (shoulder to elbow)": 30.0,
|
| "crotch height": 78.0,
|
| "Hip circumference max height": 95.0,
|
| "arm length (spine to wrist)": 30.0,
|
| "head circumference": 55.0,
|
| "neck circumference": 33.0,
|
| "chest circumference": 90.0,
|
| "waist circumference": 70.0,
|
| "hip circumference": 95.0,
|
| "wrist right circumference": 16.0,
|
| "bicep right circumference": 28.0,
|
| "forearm right circumference": 24.0,
|
| "thigh left circumference": 55.0,
|
| "calf left circumference": 36.0,
|
| "ankle left circumference": 22.0
|
| }
|
|
|
| female_type = classify_female_body_type(female_body_measurements)
|
| print("女性體型判斷結果:", female_type)
|
|
|