我自己做过不少图像相关的自动化项目,但真正让我觉得“这东西能直接拿去用”的,还是这套商品标签自动生成系统。它解决的痛点非常具体:电商运营每天要处理几百上千张商品图,人工看图画标签费时费力,还容易漏。用Python加AI图像分析,把“看图、理解、写标签”这件事自动化,确实能省下大量重复劳动。这篇就把我这套系统的完整思路和代码实现拆开揉碎了讲。
1. 整体设计与方案选型
1.1 核心需求拆解
先搞清楚这套系统要干什么。输入是一张商品图,输出是一组结构化的标签文本,比如“短袖”“圆领”“纯棉”“夏季新款”。听起来简单,但涉及三个核心环节:
- 图像分析:识别商品的主体类别、颜色、纹理、形状等视觉属性。这一步是整个系统的感知层。
- 语义生成:把识别出的视觉特征转化为人类可读、符合电商场景的标签词。比如识别到“织物表面有毛圈结构”,要能生成“毛圈面料”这种消费者能看懂的词。
- 标签结构化管理:生成的结果不能是一团散乱的词,最好是有优先级、有分类的标签组,方便后续直接导入商品管理系统。
这个拆解非常重要。很多人一上来就想着用一个大模型把图扔进去直接输出标签,效果往往不稳定,因为大模型做图像细粒度属性识别时,尤其对颜色、材质、款式的描述,经常会出现“看起来很对,实际不对”的幻觉。我的方案是:小模型做精确属性识别,大模型做语义归纳整合。两边各干各擅长的,效果和稳定性都会好很多。
1.2 为什么选择API为主、本地模型为辅的混合架构
技术选型上,我试过全本地部署,也试过全API调用,最后稳定用的是混合方案。
全本地部署的痛点是成本。一个能跑图像识别的模型(比如YOLO类或ResNet类),加上一个能生成语义标签的语言模型,对GPU显存的要求至少得16G往上。如果只是给自己用,这成本划不来。而且本地跑大模型的维护成本很高,依赖库更新、模型版本管理、推理速度调优,每一项都在消耗时间。
全API调用的痛点则在于控制力和成本。商品图往往涉及细分类目,通用API的识别结果达不到电商场景的颗粒度要求,而且量大之后,按张计费的费用并不低。更麻烦的是,很多API对图片尺寸、格式有严格限制,商品图各种奇葩格式都能遇到,全扔给API很容易报错。
所以我最后定的架构是:
- 本地用轻量级的图像处理库(OpenCV、Pillow)做预处理和基础特征提取,这一步不花钱。
- 视觉识别部分使用一个支持细粒度识别的通用视觉模型API,获取商品主体、颜色、品类等硬属性。
- 标签语义化部分使用语言模型API,把识别结果整理成电商风格的标签组。
这套架构的好处是:视觉识别交给专业的视觉模型,语义整合交给语言模型,本地只做最擅长的图像预处理和结果后处理,各环节都能用最合适的工具,成本也可控。
1.3 应用场景与适用范围
这套系统适合谁用?我梳理下来主要有三类场景:
- 电商运营者或独立站长:商品上架时需要批量生成标签,尤其是服装、鞋帽、家居百货这类属性多的类目,效率提升非常明显。
- 数据标注和内容管理团队:需要给图片库批量打标,方便后续搜索和推荐系统使用。
- 开发者做二次开发:这套系统的分层架构可以很方便地替换其中某个环节,比如把视觉模型换成自己微调的模型,或者把标签输出格式改成JSON直接对接ERP系统。
需要说明的是,这套方案不适合工业级全自动化。它更适合“人审+机器批量处理”的半自动场景。识别结果偶尔会有偏差,所以我在设计时保留了人工审核的出口,系统输出的标签组会生成一个可编辑的中间文件,审核没问题再批量导入上架。
2. 核心模块拆解与原理分析
2.1 商品主图识别模块的实现思路
商品主图识别的核心任务是从一张图中找出“哪个区域是商品主体”,然后判断“这个主体是什么品类”。这两个问题对应两个经典视觉任务:目标检测和图像分类。
我采用的方案是先用一个目标检测模型框出商品主体区域,再把裁剪出的主体区域交给分类模型做品类判断。为什么要两步而不是直接一个模型搞定?因为电商商品图的背景通常会干扰识别结果——比如一张白底图上有大片阴影,分类模型可能把阴影区域也当成特征的一部分,导致品类误判。先检测再裁剪,把背景干扰减到最小,识别精度能明显提升。
裁剪这一步的位置很讲究。直接按检测框裁,边缘经常会裁掉商品的一部分,尤其是长条形商品(比如连衣裙、围巾)。我的做法是:检测框向外扩10%的边距再裁,同时保证不超过原图边界。这个细节对后续颜色识别和材质判断的影响很大。
品类识别模型这块,我直接用了一个在开放图像数据集上预训练过的视觉模型API,支持几百个常见商品类目的识别。实测下来,服装、鞋、包、数码配件、家居日用这些主流类目准确率都还可以,冷门类目会差一些,所以系统里我留了一个人工设置类目的接口——如果API返回的可信度低于阈值,就把类目标记为待确认,由人工指定。
2.2 颜色与视觉属性提取的细节处理
颜色识别是商品标签里最常见,但也是最容易翻车的环节。问题在于:同一个颜色,在不同的光照、不同的屏幕下看,色差很大。用传统的RGB均值方法去判断颜色,经常会得到“看起来是红色,但实际像素均值偏粉”这种尴尬结果。
我的做法分三步:
- 先把图像从RGB空间转到HSV空间。HSV把色相、饱和度、明度分开,色相值对光照变化没那么敏感,比RGB更适合做颜色分类。
- 对主体区域的像素做聚类,找出占比最高的主色调。
- 把主色调映射到预设的商业颜色名称,比如“酒红”“藏青”“卡其”这种电商常用颜色词,而不是“深红”“蓝黑”这种模糊词。
颜色映射表这一步很关键。我沉淀了一套按色相区间划分的颜色词典,覆盖了电商高频出现的40多个颜色词。色相在0-10度且饱和度较高时归为“正红”,色相在210-230度时归为“藏青”而不是“深蓝”。这种粒度的人工规则,视觉模型API很难直接给出,必须自己做一层映射。
当然,程序判断颜色永远做不到和人类完全一致,尤其是莫兰迪色系这种饱和度变化微妙的颜色。所以我保留了人工修正入口,这个后面会讲。
2.3 基于语言模型的标签语义生成
视觉识别模块拿到的是结构化数据:品类、颜色、主体裁剪图。接下来要生成的是“短袖T恤”“圆领”“纯棉”“休闲百搭”这种标签。
这里有两种做法:
第一种是把视觉识别结果放到预设的模板里,比如“品类+颜色+属性词”,这种做法简单可控,但生成的标签很呆板,同一个类目下所有商品生成的标签相似度太高,缺乏区别度。
第二种是把视觉识别结果喂给语言模型,让它基于这些信息生成一组风格统一、符合电商语境的标签。这种做法生成的标签自然得多,但语言模型可能“自由发挥”,产生视觉模块没识别出来的属性,比如模型会说“这件衣服采用立体剪裁”,但视觉模块根本没分析过剪裁。
我的方案是一个折中:给语言模型设定严格的输出约束——只允许基于给定的结构化信息生成标签,不允许添加任何视觉模块未提供的属性;标签数量控制在5到8个;必须包含品类词和颜色词;风格要符合“商品标签”而不是“种草文案”。
这里最重要的一点是提示词的约束设计。我测试过加了约束和没加约束的生成结果,差别非常明显。不加约束时,模型经常把标签写得像小红书种草笔记,比如“穿上它你就是街头最靓的仔”,这种标签上架是会被运营打回来的。加了约束之后,输出的标签就规整多了:“短袖T恤”“藏青色”“纯棉面料”“圆领设计”“休闲风格”“夏季穿搭”。
2.4 标签结果的结构化与优先级排列
生成了标签之后,还要解决一个问题:标签之间是有优先级和层级关系的。比如“中国风”这个标签的权重,显然应该比“适合作睡衣”更高,前者是风格属性,后者是场景联想。
所以我在生成结果后做了一步后处理:给每个标签打上属性类型标记,分成核心属性(品类、颜色)、材质属性(面料、纹理)、风格属性(休闲、通勤、运动)、场景属性(夏季、室内、日常穿搭)。然后按照“核心属性 > 材质属性 > 风格属性 > 场景属性”的顺序输出。
这一步还有一个实际好处——对接不同的电商平台时,标签的字段要求不一样。有的平台要求主标签和副标签分开,有的平台要求标签里不能含重复语义。结构化之后,我可以很方便地按平台要求重组这些标签字段,不用重新跑一遍模型。
3. 完整实操过程与关键代码
3.1 环境准备与依赖安装
我用的环境是Python 3.10,Windows 11,本地没有GPU,纯CPU跑图像预处理和后处理,视觉识别和语言生成全部走API。这套方案对电脑性能要求不高,8G内存的普通笔记本就能跑。
依赖库这块,基础的三件套就够了:
pip install opencv-python pillow requests另外我还用了pandas做标签结果的Execl输出,用tqdm显示批量处理的进度条,这两个看个人习惯,非必需。
需要注意的一点是opencv-python的版本,4.x以上对Python 3.10支持得比较好,如果安装的时候碰到依赖冲突,建议用虚拟环境装,避免把系统Python搞乱。我自己是在项目目录下建了venv来隔离环境,有次偷懒直接在全局环境里装,结果把另一个项目的numpy版本搞崩了,折腾了半天。
3.2 视觉识别API的调用与返回处理
视觉识别部分我使用的是第三方视觉理解服务的API,这类服务现在有很多选择,你可以根据自己情况选一家。关键点是确认它支持细粒度属性识别,而不是只能给出一个宽泛的类别。
请求的代码结构大概是这样:
import requests import base64 import json def recognize_product(image_path): with open(image_path, "rb") as f: img_base64 = base64.b64encode(f.read()).decode("utf-8") payload = { "image": img_base64, "task": "product_attribute_recognition", "attributes": ["category", "color", "material", "style"] } resp = requests.post( "https://your-api-endpoint/recognize", json=payload, headers={"Authorization": "Bearer YOUR_API_KEY"}, timeout=30 ) result = resp.json() if result.get("code") == 0: return result["data"] else: raise Exception(f"识别失败: {result.get('message')}")这里有几个实操细节值得注意。
一是图像base64编码时,如果原图特别大(比如单反拍的5M原图),编码后传给API既慢又容易超时,最好先做压缩。我一般是先用OpenCV把图缩到最长边不超过1024像素,JPG质量压到85再编码,速度和识别效果都有提升。
二是timeout一定要设置。我之前没设timeout,有次批量处理200张图,有个图让API卡住了,整个脚本挂起一小时,后来强制kill才恢复。设了30秒超时之后,偶尔有失败的直接跳过记日志,不会拖垮整个批次。
三是返回结果的容错。不同API服务返回的字段结构不一样,有的是"product_type",有的是"category",有的是"main_category"。最好在代码里做一个字段映射的适配层,统一转换成自己的内部结构,这样以后换API服务商不用改后面的逻辑。
3.3 颜色识别的核心实现
颜色识别这块,我用的是HSV空间聚类加颜色词典映射的方案。核心代码如下:
import cv2 import numpy as np from collections import Counter def extract_dominant_color(image): # 转换到HSV空间 hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) hsv = hsv.reshape(-1, 3) # 过滤掉饱和度极低的像素(接近黑白灰) filtered = hsv[hsv[:, 1] > 40] if len(filtered) == 0: return "未知" # 对色相做量化到10度一个区间 quantized = (filtered[:, 0] // 10).astype(int) counter = Counter(quantized) top_hue_bin = counter.most_common(3) # 映射到颜色名称 color_names = [] for bin_idx, count in top_hue_bin: hue_center = bin_idx * 10 + 5 color_name = hue_to_color_name(hue_center) if color_name not in color_names: color_names.append(color_name) return color_names[0]hue_to_color_name函数就是前面说的颜色词典映射。这个函数是纯手工整理的规则,我贴一部分核心的色相区间:
def hue_to_color_name(hue): if hue < 10: return "正红" elif hue < 25: return "橙红" elif hue < 40: return "橙色" elif hue < 55: return "卡其" elif hue < 75: return "黄绿" elif hue < 160: return "绿色" elif hue < 200: return "青色" elif hue < 230: return "藏青" elif hue < 260: return "蓝色" # ... 省略其他有几个实际踩过的坑。
第一个是纯白、纯黑、灰色商品的处理。它们的饱和度很低,按上面的过滤逻辑会被直接滤掉,导致颜色识别为“未知”。我的解决办法是:先计算原图的灰度均值,如果灰度均值大于200就判为“白色”,小于30判为“黑色”,中间区域再走HSV逻辑。实际效果比单纯依赖HSV好很多。
第二个是背景色干扰。虽然前面做了主体检测裁剪,但裁剪边缘还是可能带一点背景色,这时候直接做聚类,背景色如果占比很大就会干扰结果。我的方案是:在裁剪图上以中心为原点做个高斯加权,越靠近中心的像素权重越大,因为商品主体通常位于中心区域,背景边缘的干扰像素权重被压低。
3.4 语言模型生成标签的提示词设计
语言模型这块,我用的提示词经过了多轮迭代。最初那版太简单,只说了“请根据这些信息生成商品标签”,结果输出的东西五花八门。后来我总结出一个结构化的提示词模板:
def build_tag_prompt(product_info): prompt = f"""你是一位电商商品标签专家。请根据以下识别到的商品信息生成商品标签。 商品信息: - 品类: {product_info['category']} - 颜色: {product_info['color']} - 材质: {product_info.get('material', '未知')} - 风格: {product_info.get('style', '未知')} - 其他属性: {product_info.get('extra_attrs', [])} 要求: 1. 只允许基于上述信息生成标签,禁止编造未提供的信息。 2. 生成5到8个标签,用中文逗号分隔。 3. 第一个标签必须是品类,第二个标签必须是颜色。 4. 标签应该是电商常见的关键词,不要写句子,不要包含任何营销性文案。 5. 输出格式:直接输出标签列表,不要有任何解释。""" return prompt这个提示词的效果,我对比过不同版本的差异,第2条和第4条约束最能提升输出质量。第2条约束数量,防止标签太少不够用,太多则没有重点;第4条约束风格,直接杜绝了“种草文案化”的倾向。
另外我加了一个后处理校验:把模型返回的标签做一次过滤,去掉长度超过8个字的标签(正常电商标签一般不超过这个长度),去掉和品类/颜色重复的标签,然后按前面的优先级规则排序。这一步保证最终输出的标签即使模型部分抽风了,也不会太难用。
3.5 批量处理流程与文件输出
批量处理是这套系统真正提效的地方。我把整个流程封装成了generate_tags_for_images这个函数,输入一个图片目录,输出一个Excel文件:
import os import pandas as pd from tqdm import tqdm def generate_tags_for_images(image_dir, output_path): image_files = [f for f in os.listdir(image_dir) if f.lower().endswith(('.jpg', '.jpeg', '.png', '.webp'))] results = [] for img_file in tqdm(image_files, desc="处理中"): img_path = os.path.join(image_dir, img_file) try: # 第一步:预处理与压缩 image = preprocess_image(img_path) # 第二步:商品主体检测与裁剪 product_region = detect_and_crop_product(image) # 第三步:视觉属性识别 product_info = recognize_product(product_region) # 第四步:颜色识别 product_info['color'] = extract_dominant_color(product_region) # 第五步:语言模型生成标签 tags = generate_tags_with_llm(product_info) results.append({ "图片名": img_file, "原始路径": img_path, "识别类目": product_info['category'], "识别颜色": product_info['color'], "生成标签": tags, "状态": "成功" }) except Exception as e: results.append({ "图片名": img_file, "原始路径": img_path, "识别类目": "N/A", "识别颜色": "N/A", "生成标签": "N/A", "状态": f"失败:{str(e)}" }) df = pd.DataFrame(results) df.to_excel(output_path, index=False) print(f"处理完成,共 {len(results)} 张图片,结果已保存至 {output_path}")这里有个设计细节值得说:即使某个图片处理失败,我也把它记入结果表,而不是直接跳过。这样能在一个批次处理完之后,快速筛选出失败的那几行,针对性地排查是图片问题还是API问题,而不用自己另外维护日志文件。
预处理函数preprocess_image里,我同时处理了图片旋转修正和压缩。EXIF信息带了旋转角的图片,如果不修正,后续的检测和裁剪都会出问题,这个我之前吃过亏,后来固定加了一步旋转修正。
3.6 人工审核与结果修正流程
前面说过这套系统适合半自动场景,所以结果输出的时候,我特意设计了一个人工审核的中间步骤。
Excel生成之后,运营同学可以打开看一遍,重点检查识别颜色那列和生成标签那列。如果有明显错误的,直接在Excel里改。改完之后,系统里提供了一个读取审核后Excel重新生成上架文件的函数,只处理被修改过的行,用修正后的标签重新生成最终的结构化数据。
有人可能会问,既然都要人工审核,为什么不干脆全手动?我的经验是:审核100张图只需要5分钟,但人工从头打标签100张图至少要40分钟。系统把工作从“创造”变成了“校验”,这个效率提升还是很明显的。
另一个经验是审核的颗粒度要控制好。不要想着每一条标签都去核对,重点看三个最容易出错的字段:品类、颜色、品牌/材质。这三个对了,其他风格和场景标签即使不太准确,也不会造成严重的上架事故。
4. 常见问题与排查技巧实录
4.1 API返回结果为空或超时
这是批量处理时最常遇到的问题。我的排查套路是三步走:
首先看是不是网络问题。用一段测试代码单独跑一张图,确认API是可用的,如果单独跑就超时,那大概率是API服务商那边的问题,换个时间段再试。
其次看是不是图片格式问题。API对图片格式支持有限,webp格式尤其容易出问题,最好统一转成JPG再传。我遇到过有个图片后缀名是jpg,但实际是webp格式的,编码之后传给API,那边直接报错。后来我加了格式检测,读文件头判断真实格式,再决定是否转码。
最后看图片大小。单张图超过10M或者像素特别大的,压缩之后再传。我一般把最长边压到1024像素,这是识别效果和传输效率的平衡点。
4.2 颜色识别明显偏色
颜色识别偏差是必然存在的,但偏差大不大是另一回事。我遇到的最典型情况是灯光偏暖的室内拍摄图,白色物体拍出来是偏黄的,颜色识别返回了“米白”或者“卡其”。这类问题靠算法很难完全解决,我的处理方式是:在颜色识别前加一个白平衡修正步骤,用灰度世界算法或者简单的白点校正,让白色尽量还原成白色。
def auto_white_balance(image): # 用灰度世界假设做简单的白平衡修正 result = cv2.cvtColor(image, cv2.COLOR_BGR2LAB) avg_a = np.mean(result[:, :, 1]) avg_b = np.mean(result[:, :, 2]) result[:, :, 1] = result[:, :, 1] - ((avg_a - 128) * 0.5) result[:, :, 2] = result[:, :, 2] - ((avg_b - 128) * 0.5) result = cv2.cvtColor(result, cv2.COLOR_LAB2BGR) return result不过也要提醒一句,白平衡修正不是银弹,过度修正反而会让本来就偏色的商品图失真。我目前的做法是只在识别颜色时用白平衡修正后的图,商品主体检测仍然用原图,毕竟检测模型是在各类光照条件下训练出来的,对偏色不敏感。
4.3 语言模型生成不相关内容
前面提到了通过提示词约束来防止模型自由发挥,但有时候模型还是会输出视觉模块没识别到的信息。比如识别结果是“连衣裙”,模型会生成“雪纺”这类材质标签,但视觉模块根本没有识别材质。
我的处理方案是在生成结果后加一道“信息溯源校验”——把模型返回的每个标签和输入的属性集合做一次匹配。如果某个标签在输入属性里找不到任何依据,就自动丢弃。这个校验是用关键词匹配实现的,比如标签里出现“雪纺”“棉”“麻”“涤纶”这些材质词,但输入属性里材质是“未知”,就删掉这个标签。
代码逻辑很简单:
def validate_tags(tags, product_info): valid_tags = [] material_keywords = ["棉", "麻", "雪纺", "涤纶", "真丝", "羊毛", "聚酯纤维"] for tag in tags: if any(kw in tag for kw in material_keywords): if product_info.get("material") == "未知": continue valid_tags.append(tag) return valid_tags这个方法不算完美,但能拦住绝大多数“凭空捏造”的标签。想做得更细的话,可以用实体识别模型来做,但我觉得在商品标签这个场景下,关键词匹配已经够用了,没必要为了这点提升增加系统复杂度。
4.4 批量处理效率瓶颈与优化
我最初跑批量脚本时,是逐张图片同步处理的:识别完一张,才处理下一张。遇到一张要10秒的图片时,100张图基本就是15到20分钟。后来我用了ThreadPoolExecutor做并发处理,并发数设为5到6,100张图能压到4到5分钟。
from concurrent.futures import ThreadPoolExecutor, as_completed def process_multithreaded(image_files, max_workers=5): results = [] with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = {executor.submit(process_single_image, f): f for f in image_files} for future in tqdm(as_completed(futures), total=len(futures)): try: result = future.result() results.append(result) except Exception as e: results.append({ "图片名": futures[future], "状态": f"失败:{str(e)}" }) return results并发数不是越大越好,我试过10个并发,结果有一半的请求超时了。原因是API服务端有速率限制,并发太高会触发限流。稳妥的做法是:先小批测试找到当前API的并发上限,然后留出20%的余量作为正式运行的并发数。
5. 系统的局限性与后续扩展方向
任何系统都有边界,这套标签生成系统的边界也很清晰。
第一,识别准确率依赖API的能力。我用过的视觉识别API,在主流商品类目上准确率还不错,但在一些细分类目上会出现识别不稳定的情况,比如“衬衫”和“卫衣”的边界、不同款式的“外套”区分。这个问题短期很难完全解决,缓解办法是设置人工阈值——API返回的可信度低于80%时,标记为待人工确认,不要让低可信度的结果直接进入标签生成流程。
第二,标签的质量上限由算法决定,和运营的专业能力还是有差距。有经验的运营打标签时,会考虑到搜索流量、用户搜索习惯、平台推荐机制,这些是算法无法理解的。所以我一直强调系统的定位是“辅助”而不是“取代”,最终上架前的审核环节不能省。
后续扩展方向,我觉得有两个思路比较靠谱。
一个是引入更细粒度的视觉模型,比如针对服装类目做专用的属性识别模型,把领型、袖长、版型这些深入的属性也识别出来。这样生成标签的信息维度会更丰富,标签的差异化会更强。
另一个是建立标签效果反馈闭环。把生成的标签和商品的实际点击、转化数据关联起来,定期分析哪些标签带来的流量多、哪些标签消费者根本不关心,用数据反哺标签生成的权重和筛选逻辑。这个闭环做起来之后,标签系统的价值就不只是省人工了,还能帮助运营优化商品信息策略。
我个人的看法是,这类“AI+业务场景”的系统,最重要的不是模型选得多先进,而是把流程拆得足够细,让每个环节都可控、可替换、可优化。这套商品标签生成系统最大的收获,不是代码本身,而是这套“识别-生成-校验-审核”的流程框架,它可以平移复制到很多类似的内容自动化场景中去。