recommender / body_type_classifier.py
zhenyuxyz's picture
Upload 7 files
54b590a verified
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("胸圍、腰圍、臀圍必須為正數")
# 1) 轉成比例(避免整體放大時結果改變)
total = C + W + H
pC, pW, pH = C / total, W / total, H / total
v = (pC, pW, pH)
# 2) 各體型的「理想比例」(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),
}
# 3) 表格規則的門檻
def gate_rect(C, W, H):
# 胸臀差 ≤ 5%,腰比胸臀小
diff_CH = abs(C - H) / max(C, H)
return diff_CH <= 0.05 and W <= min(C, H)
def gate_tri(C, W, H):
# 三角形(下寬上窄):臀比胸大 > 10%,且腰比胸臀都小
return (H >= C * 1.10) and (W <= min(C, H))
def gate_inv(C, W, H):
# 倒三角(上寬下窄):胸比臀大 > 10%,且腰比胸臀都小
return (C >= H * 1.10) and (W <= min(C, H))
def gate_trap(C, W, H):
# 梯形:胸比臀大 5%~10%,腰比胸臀小
return (C > H * 1.05) and (C <= H * 1.10) and (W <= min(C, H))
def gate_oval(C, W, H):
# 橢圓形:腰比胸臀都大 5% 以上
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
)
# 4) 先找有通過門檻的
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]
# 5) 若完全沒有門檻通過,就純看誰最接近 ideal pattern
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("胸/腰/臀/肩寬 必須為正數")
# ========= 1) 轉成比例(不吃單位) =========
total = C + W + H
pC, pW, pH = C / total, W / total, H / total
v = (pC, pW, pH)
# ========= 2) 各體型的 ideal pattern(比例) =========
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
)
# ========= 3) 定義比例型 gate(門檻) =========
cw_ratio = (C - W) / W # 胸比腰大幾%
hw_ratio = (H - W) / W # 臀比腰大幾%
hip_shoulder_diff = (H - shoulders2) / H # 臀比肩寬x2 大幾%(<0 表肩更寬)
ch_diff_ratio = abs(C - H) / max(C, H) # 胸臀接近程度
w_vs_ch_hip = (W - min(C, H)) / min(C, H) # 腰比胸/臀大多少(判斷蘋果用)
# 沙漏型 gate:胸 & 臀 都比腰大,且胸臀接近
def gate_hourglass(C, W, H, shoulders2):
return (cw_ratio >= 0.25 and hw_ratio >= 0.30 and ch_diff_ratio <= 0.07)
# 蘋果型 gate:腰明顯 > 臀(通常也 ≥ 胸)
def gate_apple(C, W, H, shoulders2):
return (W >= max(C, H)) and ((W - H) / H > 0.03)
# 梨型 gate:臀明顯 > 肩寬x2(原本「> 3cm」,改成約 3%)
def gate_pear(C, W, H, shoulders2):
return hip_shoulder_diff > 0.03
# 倒三角 gate:肩寬x2 明顯 > 臀
def gate_inv_tri(C, W, H, shoulders2):
return hip_shoulder_diff < -0.03
# H 型 gate:肩寬x2 與 臀差距小,胸臀也跟腰差距不算大
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,
}
# ========= 4) 先找「有通過門檻」的體型,裡面選距離最近 =========
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]
# ========= 5) 若完全沒有門檻通過,就純看 ideal pattern 距離 =========
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, # 肩寬 40 → 肩寬x2 = 80
"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, # 胸 90
"waist circumference": 70.0, # 腰 70
"hip circumference": 95.0, # 臀 95
"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)