Files
2026-09-11 01:41:52 +08:00

612 lines
33 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Fashion-CLIP 衣物分类服务
使用 patrickjohncyh/fashion-clip 模型进行零样本分类,
一次性识别衣物的 15 个维度:
大类 / 子类别 / 风格 / 主颜色 / 副颜色 / 厚度 / 版型 / 材质 / 花纹 /
袖型 / 衣长类型 / 领型 / 正式程度(1-5) / 适用场合 / 视觉冲击力(1-5)。
所有维度在一次模型前向中完成(约 210 个提示词拼接为一个 batch,
按组 softmax 归一化),响应字段与后端 clothes 表模型命名对齐。
模型首次运行从 HuggingFace Hub 下载权重并缓存到本地。
默认使用 hf-mirror.com 镜像(国内加速)。
"""
import io
import logging
import os
# !!!必须在 import transformers 之前设置,否则 huggingface_hub 读不到这个环境变量!!!
if "HF_ENDPOINT" not in os.environ:
os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"
import torch
from fastapi import FastAPI, HTTPException, UploadFile
from PIL import Image
from transformers import CLIPModel, CLIPProcessor
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(title="Fashion-CLIP Classification Service")
# ============================================================================
# 分类提示词
# 说明:
# - Fashion-CLIP 是英文训练的,用英文+视觉特征描述效果远好于简单中文标签。
# - 键为中文标签(返回给客户端,与 clothes 表字段取值一致),值为英文提示词。
# - 描述尽量包含剪裁、长度、材质视觉特征与穿着场景,提升零样本区分度。
# ============================================================================
CATEGORY_PROMPTS = {
"上装": "a short top, t-shirt, shirt, blouse or sweater worn on the upper body, garment covering the torso from shoulders to waist or hips",
"下装": "a pair of pants, trousers, jeans, shorts, leggings or a skirt worn on the lower body below the waist",
"连衣裙": "a one-piece dress worn from shoulders down, covering both upper and lower body in a single connected garment",
"外套": "a jacket, coat, blazer or outerwear worn as an outer layer over other clothes, with sleeves and front opening",
"鞋": "a pair of shoes, sneakers, boots, sandals or slippers worn on the feet, a full shoe shape with sole, heel and toe, footwear covering the foot",
"配饰": "a small personal accessory item like a hat, scarf, belt, bag, sunglasses, necklace, earrings, bracelet, wristwatch or other jewelry, not a main clothing garment",
}
# 子类别按大类分组:大类确定后在其对应组内取 top1,避免牛仔裤出现在上装里
SUBCATEGORY_GROUPS = {
"上装": {
"T恤": "a short-sleeve cotton t-shirt with round crew neck, casual basic top, no buttons no collar",
"衬衫": "a button-up shirt with collar and front buttons, long or short sleeves, woven fabric with cuffs",
"卫衣": "a hoodie or sweatshirt, thick fleece cotton pullover with or without hood and kangaroo pocket",
"毛衣": "a thick knitted wool sweater or pullover, chunky warm textured knit top",
"针织衫": "a thin fine-knit top, ribbed lightweight knitwear, fitted casual knit shirt, finer yarn than a chunky sweater",
"背心": "a sleeveless tank top with wide shoulder coverage, sporty or basic vest top",
"Polo衫": "a polo shirt with flat collar, short sleeves and a 2-3 button placket, sporty casual",
"开衫": "a cardigan, open-front knitted sweater-like top with buttons or zipper down the front",
"吊带衫": "a camisole top with thin spaghetti straps, sleeveless lightweight top with narrow shoulder straps",
"打底衫": "a basic fitted long-sleeve layering top, thin turtleneck or crew neck undershirt, plain solid color",
},
"下装": {
"牛仔裤": "a pair of blue denim jeans, casual pants with visible seams, rivets and five pockets",
"休闲裤": "a pair of casual cotton or chino trousers, straight leg comfortable pants, no denim texture",
"运动裤": "a pair of sporty sweatpants or joggers with elastic waistband, relaxed athletic fit",
"短裤": "a pair of shorts ending above the knee, short pants for warm weather",
"半身裙": "a skirt worn from waist down, flared A-line or straight or pleated silhouette",
"阔腿裤": "a pair of wide-leg loose flowing trousers with very wide leg openings",
"西装裤": "a pair of formal tailored dress trousers with center crease, suit pants",
"鲨鱼裤": "a pair of tight shiny yoga leggings, skin-tight stretch pants, sporty smooth surface",
"工装裤": "a pair of cargo pants with large side pockets on the legs, utility workwear style",
"喇叭裤": "a pair of flared pants fitted at thigh and widening from knee to hem, bell-bottom style",
},
"连衣裙": {
"连衣短裙": "a short mini dress above the knee, one-piece garment",
"连衣长裙": "a long maxi dress reaching the ankles, elegant flowing one-piece",
"吊带裙": "a slip dress with thin spaghetti straps, sleeveless one-piece dress",
"衬衫连衣裙": "a shirt dress with button-up front and collar, shirt style one-piece dress",
"针织连衣裙": "a knitted one-piece dress with ribbed sweater texture, body-hugging or flowing knit",
"雪纺连衣裙": "a chiffon dress with sheer lightweight flowing layered fabric, romantic airy look",
"西装连衣裙": "a tailored blazer-style dress with lapels, structured office dress",
},
"外套": {
"夹克": "a short lightweight jacket ending at waist or hip, sporty casual outerwear with zipper",
"西装外套": "a formal blazer or suit jacket with lapels, structured shoulders, button closure",
"风衣": "a long trench coat with belt, double-breasted buttons, knee-length waterproof style",
"羽绒服": "a puffy down jacket with quilted sections, thick warm insulation, winter puffer coat",
"大衣": "a long wool overcoat reaching knee or below, elegant warm winter coat",
"马甲": "a sleeveless vest worn over other clothes, no sleeves, front zipper or buttons",
"牛仔外套": "a denim jacket with metal buttons and chest flap pockets, blue jean fabric, trucker style",
"皮衣": "a leather jacket with glossy or matte leather surface, biker or fashion style",
"棉服": "a quilted cotton padded jacket, lightweight puffer, insulated casual winter jacket",
},
"鞋": {
"运动鞋": "a pair of athletic sneakers with rubber sole, sport running or training shoes",
"皮鞋": "a pair of formal leather dress shoes with polished shiny surface, lace-up or slip-on",
"凉鞋": "a pair of open-toe sandals with straps, exposed toes, summer footwear",
"靴子": "a pair of boots covering ankle or higher, sturdy footwear with high-top shaft",
"帆布鞋": "a pair of canvas shoes with flat rubber sole, casual lace-up sneakers",
"高跟鞋": "a pair of high heel shoes or pumps with elevated heel, formal feminine footwear",
"乐福鞋": "a pair of loafers, slip-on leather shoes without laces, flat casual dress shoes",
"外穿拖鞋": "a pair of outdoor slides or chunky slippers, open-toe flat sandals for casual wear",
"玛丽珍鞋": "a pair of Mary Jane shoes with rounded toe and one or more straps across the instep, feminine flat or slight heel",
"洞洞鞋": "a pair of foam clogs with perforated holes on the upper, casual slip-on crocs style",
"马丁靴": "a pair of combat boots with chunky sole, lace-up leather ankle boots",
},
"配饰": {
"帽子": "a hat or cap worn on the head, baseball cap, beanie, bucket hat or sun hat",
"围巾": "a scarf worn around the neck, long fabric accessory",
"腰带": "a belt worn around the waist, leather strap with metal buckle",
"墨镜": "a pair of sunglasses with dark tinted lenses",
"项链": "a necklace, jewelry chain or pendant worn around the neck",
"手链": "a bracelet or bangle, purely decorative jewelry worn on the wrist, no watch face, no dial, no clock",
"包包": "a handbag, purse, shoulder bag, tote bag or backpack accessory",
"耳饰": "a pair of earrings, jewelry worn on the earlobes",
"丝巾": "a silk scarf or bandana, lightweight decorative fabric neck accessory",
"发带": "a headband or hair tie accessory worn on the head or in the hair",
"戒指": "a ring, small jewelry worn on the finger",
"胸针": "a brooch, decorative pin jewelry attached to clothing",
"手表": "a wristwatch timepiece with a large prominent round or square watch face at the center, a clock dial showing numbers and watch hands or digital display, buttons or crown on the sides, thick strap band, worn on the wrist as a timekeeping device",
},
}
COLOR_PROMPTS = {
"黑色": "a black colored garment, very dark black clothing",
"白色": "a white colored garment, pure white clothing",
"红色": "a red colored garment, bright red clothing",
"蓝色": "a standard blue colored garment, medium blue clothing",
"绿色": "a green colored garment, green clothing",
"黄色": "a yellow colored garment, bright yellow clothing",
"粉色": "a pink colored garment, soft pink clothing",
"紫色": "a purple colored garment, violet purple clothing",
"灰色": "a gray colored garment, grey clothing",
"棕色": "a brown colored garment, brown clothing",
"橙色": "an orange colored garment, orange clothing",
"卡其色": "a khaki colored garment, beige-tan clothing",
"米色": "a cream or beige colored garment, off-white soft beige clothing",
"杏色": "an apricot colored garment, soft peachy beige clothing",
"焦糖色": "a caramel brown colored garment, warm rich toffee brown clothing",
"藏青色": "a navy blue colored garment, very dark deep blue clothing",
"天蓝色": "a sky blue colored garment, light bright clear blue clothing",
"军绿色": "an olive green or army green colored garment, military green clothing",
"酒红色": "a burgundy or wine red colored garment, deep maroon red clothing",
"金色": "a gold colored garment, metallic golden shiny clothing",
"银色": "a silver colored garment, metallic silver shiny clothing",
"玫红色": "a rose red or hot pink magenta colored garment, vivid pinkish red clothing",
"荧光色": "a neon fluorescent colored garment, extremely bright glowing highlighter colors like neon green or neon orange",
"墨绿色": "a dark forest green colored garment, deep dark green clothing",
"宝蓝色": "a royal blue colored garment, saturated vivid bright blue clothing",
"浅蓝色": "a light blue colored garment, pale soft pastel blue clothing",
"粉紫色": "a lavender or lilac colored garment, soft purple-pink pastel clothing",
"驼色": "a camel colored garment, warm tan brown beige clothing",
"砖红色": "a brick red colored garment, muted earthy red-brown clothing",
"浅灰色": "a light gray colored garment, pale soft grey clothing",
}
STYLE_PROMPTS = {
"休闲风": "casual everyday style clothing, relaxed comfortable fashion",
"通勤风": "business formal office style clothing, professional workplace attire",
"运动风": "athletic sporty style clothing, gym and workout fashion",
"街头风": "streetwear urban style clothing, trendy hip-hop influenced fashion",
"优雅风": "elegant sophisticated style clothing, feminine chic fashion",
"甜美风": "cute sweet girly style clothing, lovely feminine fashion with ruffles and pastel colors",
"复古风": "vintage retro style clothing, old-school classic fashion from past decades",
"简约风": "minimalist simple style clothing, clean basic design without decoration",
"商务正装风": "formal business suit style clothing, corporate professional tailored attire",
"轻熟风": "smart casual mature style clothing, polished sophisticated young professional fashion",
"工装风": "utility workwear style clothing, cargo pockets and durable functional fashion",
"学院风": "preppy academic style clothing, college campus style with plaid, knit sweaters, collared shirts",
"甜酷风": "sweet and edgy style clothing, feminine meets punk, cute yet rebellious fashion",
"辣妹风": "sexy hot girl style clothing, bold bodycon and crop top fashion, confident glam look",
"度假风": "resort vacation style clothing, tropical holiday beachwear, relaxed bohemian fashion",
"新中式风": "modern Chinese style clothing, contemporary oriental fashion with traditional Chinese elements",
"极简风": "extreme minimalist style clothing, monochrome neutral colors, no logos no patterns, pure basic design",
"波西米亚风": "bohemian style clothing, flowy fabric, fringe, ethnic embroidery, relaxed free-spirited silhouette",
"朋克风": "punk style clothing, black leather, studs, ripped details, rebellious rock look",
"嘻哈风": "hip-hop style clothing, oversized baggy silhouette, urban street rap culture fashion",
"森系风": "mori girl forest style clothing, natural earthy colors, layered loose linen and cotton, rustic gentle look",
"法式风": "French chic style clothing, effortless elegant parisian look, simple refined feminine fashion",
"美式复古风": "American vintage retro style clothing, classic 90s american casual, denim, varsity, old-school usa look",
"日系风": "Japanese style clothing, clean simple japanese fashion, relaxed natural fabric look",
"韩系风": "Korean style clothing, trendy korean fashion, soft colors, neat youthful casual look",
}
FIT_PROMPTS = {
"宽松": "a loose oversized fit garment, baggy relaxed silhouette clothing",
"修身": "a slim fitted garment, tailored silhouette that follows the body shape",
"阔腿": "a wide leg loose flowing silhouette, palazzo or flare pants style",
"紧身": "a tight skinny fit garment, bodycon stretch clothing, form-fitting second skin",
"常规合身": "a regular standard fit garment, normal classic cut, neither tight nor loose",
"直筒": "a straight leg or straight cut garment, parallel vertical lines from top to bottom, no flare no taper",
}
MATERIAL_PROMPTS = {
"牛仔布": "denim fabric material, rugged blue cotton twill with visible diagonal weave and stitching",
"针织": "knitted fabric material, textured knitwear with visible yarn loops, sweater material",
"皮质": "genuine leather material, smooth shiny or matte animal hide surface with natural grain",
"纯棉": "pure cotton material, soft matte natural cotton fabric, breathable woven cotton textile",
"羊毛": "wool material, fuzzy warm woolen fabric, thick natural animal fiber textile",
"丝绸": "silk material, smooth lustrous shiny satin-like fabric, elegant silky sheen surface",
"雪纺": "chiffon material, sheer lightweight translucent fabric, soft delicate flowy textile",
"麻料": "linen material, textured natural flax fabric with visible weave, breathable rustic look",
"蕾丝": "lace material, delicate openwork patterned fabric, intricate floral mesh textile",
"尼龙": "nylon synthetic material, smooth shiny technical fabric, waterproof windbreaker material",
"灯芯绒": "corduroy material, ribbed velvety fabric with parallel raised cords, textured wales",
"天鹅绒": "velvet material, soft plush fabric with rich deep sheen and smooth nap",
"缎面": "satin material, smooth glossy surface with strong silky shine, fluid drape",
"网纱": "tulle or mesh material, sheer fine net fabric, transparent gauzy texture",
"羊羔绒": "sherpa fleece material, fluffy curly wool-like lining or surface, cozy plush texture",
"PU皮": "faux leather PU material, artificial leather with uniform smooth plastic-like glossy surface",
"磨毛棉": "brushed cotton material, soft slightly fuzzy matte surface, cozy flannel-like texture",
}
PATTERN_PROMPTS = {
"纯色": "a solid color garment with no pattern, plain single-color fabric without any design",
"条纹": "a striped pattern garment with horizontal or vertical parallel lines",
"格纹": "a plaid or checkered pattern garment, crossed horizontal and vertical lines forming squares",
"印花": "a printed pattern garment with graphic floral or artistic designs on fabric",
"刺绣": "an embroidered garment with raised stitched thread decorative patterns",
"波点": "a polka dot pattern garment with small round dots evenly distributed",
"迷彩": "a camouflage pattern garment with irregular green brown patches, military style",
"扎染": "a tie-dye pattern garment with irregular colorful swirled dyed patterns",
"碎花": "a small floral print pattern garment with tiny scattered flower designs",
"字母": "a lettering or slogan print garment with text words or logos",
"千鸟格": "a houndstooth check pattern garment, small broken zigzag checks in two-tone",
"菱格纹": "an argyle pattern garment with diamond shapes, diagonal intersecting lines",
"人字纹": "a herringbone pattern garment with V-shaped zigzag weave, broken twill",
"拼色块": "a color block garment with contrasting solid color panels stitched together in large sections",
"渐变": "an ombre gradient pattern garment with colors fading gradually from one shade to another",
"豹纹": "a leopard print pattern garment with animal spots, brown and black rosette spots",
"蕾丝花纹": "a lace pattern garment with delicate openwork floral mesh design",
"几何图案": "a geometric pattern garment with shapes like triangles, circles and abstract lines arranged in order",
"logo图案": "a logo or brand emblem print garment with large recognizable brand marks or icons",
}
# 厚度(适用:上装/下装/连衣裙/外套/鞋)
THICKNESS_PROMPTS = {
"薄": "a thin lightweight fabric garment, sheer or semi-sheer summer clothing, cool breathable material",
"适中": "a medium weight fabric garment, regular thickness clothing, standard everyday material",
"厚": "a thick heavy fabric garment, warm dense material, substantial winter clothing",
"加厚": "an extra thick heavily padded garment, quilted puffy down insulation, maximum warmth winter wear",
}
# 袖型(适用:上装/连衣裙/外套)
SLEEVE_TYPE_PROMPTS = {
"无袖": "a sleeveless garment with no sleeves, bare shoulders and arms exposed",
"吊带": "a garment with thin spaghetti straps, narrow shoulder straps instead of sleeves",
"短袖": "a short sleeve garment, sleeves ending above the elbow",
"中袖": "a half sleeve or elbow length sleeve garment, sleeves ending at or below the elbow",
"长袖": "a long sleeve garment, sleeves reaching the wrist",
"喇叭袖": "a flared bell sleeve garment, sleeve widening dramatically from elbow to a wide cuff",
"泡泡袖": "a puff sleeve garment, voluminous balloon sleeve gathered at shoulder and cuff",
"落肩袖": "a dropped shoulder sleeve garment, shoulder seam falling below the natural shoulder, relaxed slouchy look",
}
# 衣长类型(适用:上装/下装/连衣裙/外套)
CLOTHES_TYPE_PROMPTS = {
"短款": "a cropped short length garment, ends above or at the waist, showing midriff or waistline",
"常规": "a regular standard length garment, normal classic length, ends around hip or upper thigh",
"中长": "a mid length garment, ends around mid-thigh or knee",
"长款": "a long length garment, ends below the knee or at the calf",
"超长款": "an extra long garment, ankle-length or floor-length, maxi style full length",
}
# 领型(适用:上装/连衣裙/外套)
NECKLINE_PROMPTS = {
"圆领": "a round crew neckline, circular collar hugging the neck",
"V领": "a V-shaped neckline, pointed dip at the front center chest",
"方领": "a square neckline with straight horizontal edge across the chest",
"高领": "a turtleneck high collar, folded fabric covering the whole neck",
"翻领": "a lapel or shirt collar, folded flat collar with pointed tips",
"立领": "a standing band collar, mandarin collar rising vertically around the neck",
"一字领": "an off-shoulder neckline, straight horizontal top edge exposing the shoulders",
"挂脖": "a halter neckline with straps tying behind the neck, open back and shoulders",
"连帽": "a hooded garment with an attached hood around the neckline",
"娃娃领": "a peter pan collar, small flat rounded collar lying on the garment",
}
# 正式程度 1-5(label 分类后映射为数字等级)
FORMALITY_PROMPTS = {
"非常休闲": "very casual clothing, sporty gym wear, beach wear, loungewear, relaxed informal style",
"休闲": "casual clothing, everyday relaxed wear, jeans and t-shirt style",
"轻正式": "smart casual clothing, neat polished everyday style, business casual attire",
"商务正式": "business formal clothing, office suit and tailored professional attire",
"非常正式": "very formal clothing, evening gown, tuxedo, ceremonial dress code attire",
}
# 适用场合(取置信度达标的 top-N 逗号连接)
OCCASION_PROMPTS = {
"日常通勤": "everyday commuting and office wear, practical comfortable work outfit",
"运动健身": "sports and gym workout wear, athletic exercise clothing",
"约会聚会": "date night and social gathering outfit, romantic or fashionable party look",
"正式商务": "formal business meeting attire, professional corporate dress code",
"居家休闲": "home loungewear, cozy at-home relaxing clothing",
"度假旅游": "vacation and travel wear, resort holiday clothing, beach and sightseeing outfit",
"户外活动": "outdoor activity wear, hiking camping adventure functional clothing",
"派对夜店": "party and nightclub outfit, trendy bold nightlife fashion",
"校园上课": "school and campus wear, youthful student daily outfit",
"婚礼宴会": "wedding and banquet attire, elegant formal celebration dress",
}
# 视觉冲击力 1-5(label 分类后映射为数字等级)
VISUAL_IMPACT_PROMPTS = {
"低调": "very low visual impact, plain minimal clothing, muted neutral colors, no decoration",
"平淡": "low visual impact, simple understated clothing, basic design with little attention-grabbing",
"适中": "moderate visual impact, average clothing with normal color and simple design details",
"亮眼": "high visual impact, eye-catching clothing, bright colors or striking design, stands out",
"高调": "very high visual impact, bold dazzling clothing, neon glitter dramatic statement design, impossible to ignore",
}
# ============================================================================
# 批量推理分组:所有维度 + 子类别按大类展开,一次前向全部完成
# ============================================================================
INFERENCE_GROUPS = {
"category": CATEGORY_PROMPTS,
"color": COLOR_PROMPTS,
"style": STYLE_PROMPTS,
"version": FIT_PROMPTS,
"material": MATERIAL_PROMPTS,
"pattern": PATTERN_PROMPTS,
"thickness": THICKNESS_PROMPTS,
"sleeve_type": SLEEVE_TYPE_PROMPTS,
"clothes_type": CLOTHES_TYPE_PROMPTS,
"neckline_type": NECKLINE_PROMPTS,
"formality": FORMALITY_PROMPTS,
"occasion": OCCASION_PROMPTS,
"visual_impact": VISUAL_IMPACT_PROMPTS,
}
for _main, _subs in SUBCATEGORY_GROUPS.items():
INFERENCE_GROUPS[f"subcategory:{_main}"] = _subs
# 副颜色提取阈值:第二颜色置信度达到 min(top1 * 40%, 0.15) 才输出
COLOR_SUB_RATIO_THRESHOLD = 0.40
COLOR_SUB_ABS_THRESHOLD = 0.15
# 适用场合:置信度不低于 top1 的 60%,最多取 3 个
OCCASION_MIN_RATIO = 0.60
OCCASION_MAX_COUNT = 3
# 维度适用性:各维度仅在指定大类下输出,其余品类置空
DIMENSION_APPLICABILITY = {
"thickness": {"上装", "下装", "连衣裙", "外套", "鞋"},
"version": {"上装", "下装", "连衣裙", "外套"},
"sleeve_type": {"上装", "连衣裙", "外套"},
"clothes_type": {"上装", "下装", "连衣裙", "外套"},
"neckline_type": {"上装", "连衣裙", "外套"},
}
# 1-5 等级映射(中文标签 -> 数字等级字符串)
FORMALITY_LEVELS = {
"非常休闲": "1",
"休闲": "2",
"轻正式": "3",
"商务正式": "4",
"非常正式": "5",
}
VISUAL_IMPACT_LEVELS = {
"低调": "1",
"平淡": "2",
"适中": "3",
"亮眼": "4",
"高调": "5",
}
def _classify_all(
model: CLIPModel,
processor: CLIPProcessor,
image: Image.Image,
groups: dict,
device: str,
) -> dict:
"""一次前向完成多组分类。
groups: 组名 -> {中文标签: 英文提示词}
返回: 组名 -> [{"label": 标签, "confidence": 置信度}, ...] 按置信度降序
"""
texts = []
boundaries = []
for prompts in groups.values():
start = len(texts)
texts.extend(prompts.values())
boundaries.append((start, len(texts)))
inputs = processor(
text=texts,
images=image,
return_tensors="pt",
padding=True,
).to(device)
with torch.inference_mode():
outputs = model(**inputs)
logits = outputs.logits_per_image # [1, total_texts]
logits = logits.squeeze(0).cpu()
results = {}
for name, (start, end) in zip(groups.keys(), boundaries):
labels = list(groups[name].keys())
probs = torch.softmax(logits[start:end], dim=-1)
order = torch.argsort(probs, descending=True)
results[name] = [
{"label": labels[int(i)], "confidence": round(float(probs[int(i)]), 4)}
for i in order
]
return results
def _top1(results: list) -> tuple:
"""取组内 top1 的 (标签, 置信度)。"""
return results[0]["label"], results[0]["confidence"]
def _extract_color_sub(color_results: list) -> tuple:
"""从颜色 top-2 提取副颜色,置信度过低则返回空。"""
if len(color_results) < 2:
return "", 0.0
top_conf = color_results[0]["confidence"]
sub = color_results[1]
threshold = min(top_conf * COLOR_SUB_RATIO_THRESHOLD, COLOR_SUB_ABS_THRESHOLD)
if sub["confidence"] >= threshold:
return sub["label"], sub["confidence"]
return "", 0.0
def _pick_occasions(occasion_results: list) -> str:
"""取置信度不低于 top1 一定比例的场合,逗号连接。"""
top_conf = occasion_results[0]["confidence"]
picked = [
r["label"]
for r in occasion_results
if r["confidence"] >= top_conf * OCCASION_MIN_RATIO
][:OCCASION_MAX_COUNT]
if not picked:
picked = [occasion_results[0]["label"]]
return ",".join(picked)
# ---------- 模型加载 ----------
MODEL_NAME = "patrickjohncyh/fashion-clip"
device = "cuda" if torch.cuda.is_available() else "cpu"
# 模型缓存走 HuggingFace 默认目录(~/.cache/huggingface)
logger.info(f"Loading {MODEL_NAME} on {device} ...")
try:
model = CLIPModel.from_pretrained(MODEL_NAME, local_files_only=True)
processor = CLIPProcessor.from_pretrained(MODEL_NAME, local_files_only=True)
logger.info(f"{MODEL_NAME} 命中本地缓存,离线加载完成")
except (OSError, ValueError) as e:
logger.info(f"本地未找到 {MODEL_NAME} 缓存,自动从 HuggingFace 下载 ... ({e})")
model = CLIPModel.from_pretrained(MODEL_NAME)
processor = CLIPProcessor.from_pretrained(MODEL_NAME)
logger.info(f"{MODEL_NAME} 下载完成")
model = model.to(device)
model.eval()
if device == "cuda":
model.half()
logger.info(f"Fashion-CLIP loaded on {device}")
@app.post("/classify")
async def classify(file: UploadFile):
"""对上传的衣物图片进行分类,返回与 clothes 表模型对齐的 15 个维度。
Response:
category_main: 衣物大类(上装/下装/连衣裙/外套/鞋/配饰)
category_sub: 子类别(大类对应组内 top1)
style: 风格
color_main: 主颜色
color_sub: 副颜色(top-2 置信度达标才输出,否则空字符串)
thickness: 厚度(薄/适中/厚/加厚)
version: 版型(宽松/修身/阔腿/紧身/常规合身/直筒)
material: 材质
pattern: 花纹
sleeve_type: 袖型(仅上装/连衣裙/外套,其余置空)
clothes_type: 衣长类型(短款/常规/中长/长款/超长款)
neckline_type: 领型(仅上装/连衣裙/外套,其余置空)
formality: 正式程度 "1"~"5"(附 formality_label 中文标签)
occasions: 适用场合(top-N 逗号连接)
visual_impact: 视觉冲击力 "1"~"5"(附 visual_impact_label 中文标签)
details: 各维度 Top-5 明细
"""
raw = await file.read()
try:
image = Image.open(io.BytesIO(raw))
# 仅含透明通道的图片(PNG 等)合成白底,避免透明通道干扰 CLIP;普通 JPEG 直接转 RGB
if image.mode in ("RGBA", "LA") or (image.mode == "P" and "transparency" in image.info):
image = image.convert("RGBA")
bg = Image.new("RGBA", image.size, (255, 255, 255, 255))
image = Image.alpha_composite(bg, image)
image = image.convert("RGB")
except Exception:
raise HTTPException(status_code=400, detail="invalid image")
# 1. 一次前向完成全部分类
results = _classify_all(model, processor, image, INFERENCE_GROUPS, device)
# 2. 大类
best_main, best_main_conf = _top1(results["category"])
# 3. 子类别:大类对应组内 top1
sub_results = results.get(f"subcategory:{best_main}", [])
if sub_results:
best_sub, best_sub_conf = _top1(sub_results)
else:
best_sub, best_sub_conf = "", 0.0
# 4. 颜色:主色 + 副色(top-2 阈值提取)
color_results = results["color"]
best_color, best_color_conf = _top1(color_results)
best_color_sub, best_color_sub_conf = _extract_color_sub(color_results)
# 5. 风格 / 材质 / 花纹
best_style, best_style_conf = _top1(results["style"])
best_material, best_material_conf = _top1(results["material"])
best_pattern, best_pattern_conf = _top1(results["pattern"])
# 6. 适用性控制:不适用维度置空
def pick_or_empty(dim: str) -> tuple:
if best_main in DIMENSION_APPLICABILITY.get(dim, set()):
return _top1(results[dim])
return "", 0.0
best_thickness, best_thickness_conf = pick_or_empty("thickness")
best_version, best_version_conf = pick_or_empty("version")
best_sleeve, best_sleeve_conf = pick_or_empty("sleeve_type")
best_clothes_type, best_clothes_type_conf = pick_or_empty("clothes_type")
best_neckline, best_neckline_conf = pick_or_empty("neckline_type")
# 7. 正式程度 1-5
formality_label, formality_conf = _top1(results["formality"])
formality = FORMALITY_LEVELS.get(formality_label, "")
# 8. 适用场合 top-N
occasions = _pick_occasions(results["occasion"])
# 9. 视觉冲击力 1-5
impact_label, impact_conf = _top1(results["visual_impact"])
impact = VISUAL_IMPACT_LEVELS.get(impact_label, "")
def detail(name: str) -> list:
return results.get(name, [])[:5]
return {
"category_main": best_main,
"category_main_confidence": best_main_conf,
"category_sub": best_sub,
"category_sub_confidence": best_sub_conf,
"style": best_style,
"style_confidence": best_style_conf,
"color_main": best_color,
"color_main_confidence": best_color_conf,
"color_sub": best_color_sub,
"color_sub_confidence": best_color_sub_conf,
"thickness": best_thickness,
"thickness_confidence": best_thickness_conf,
"version": best_version,
"version_confidence": best_version_conf,
"material": best_material,
"material_confidence": best_material_conf,
"pattern": best_pattern,
"pattern_confidence": best_pattern_conf,
"sleeve_type": best_sleeve,
"sleeve_type_confidence": best_sleeve_conf,
"clothes_type": best_clothes_type,
"clothes_type_confidence": best_clothes_type_conf,
"neckline_type": best_neckline,
"neckline_type_confidence": best_neckline_conf,
"formality": formality,
"formality_label": formality_label,
"formality_confidence": formality_conf,
"occasions": occasions,
"visual_impact": impact,
"visual_impact_label": impact_label,
"visual_impact_confidence": impact_conf,
"details": {
"category_main": detail("category"),
"category_sub": sub_results[:5],
"style": detail("style"),
"color_main": color_results[:5],
"thickness": detail("thickness"),
"version": detail("version"),
"material": detail("material"),
"pattern": detail("pattern"),
"sleeve_type": detail("sleeve_type"),
"clothes_type": detail("clothes_type"),
"neckline_type": detail("neckline_type"),
"formality": detail("formality"),
"occasions": detail("occasion"),
"visual_impact": detail("visual_impact"),
},
}
@app.get("/healthz")
def healthz():
return {"status": "ok"}
if __name__ == "__main__":
# 模型已在模块 import 时于模块级加载,这里直接传 app 对象启动,
# 不要用 "main:app" 字符串 + reload 方式,否则会重复加载模型。
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)