|
@@ -0,0 +1,552 @@
|
|
|
|
|
+# 抠图调用流程说明文档
|
|
|
|
|
+
|
|
|
|
|
+> 代码入口:`python/service/base_deal.py` L663-687
|
|
|
|
|
+> 核心实现:`python/service/remove_bg_ali.py`
|
|
|
|
|
+> 上传接口封装:`python/service/image_deal_base_func.py`、`python/api.py`
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 一、抠图模式总览
|
|
|
|
|
+
|
|
|
|
|
+系统配置 `other_configs.cutout_mode`(读取方式:`settings.getSysConfigs("other_configs", "cutout_mode", "普通抠图")`)先被映射为模式字符串 `"1"~"5"`(见 `python/api.py` L498-511),再传入 `base_deal.py` 分发:
|
|
|
|
|
+
|
|
|
|
|
+| 模式值 | 模式名称 | 调用类 | 调用方法 | 后端类型 |
|
|
|
|
|
+|---|---|---|---|---|
|
|
|
|
|
+| `"1"` | 普通抠图 | `RemoveBgALi` | `get_image_cut()` | 阿里云视觉智能 SDK |
|
|
|
|
|
+| `"2"` | 精细化抠图 | `DealCutout`(内部 `DealOneImageBeforehand` + `DealOneImage`) | `run()` | 阿里 SDK 预抠图 + Pixian 第三方接口 二次抠图 |
|
|
|
|
|
+| `"3"` | 旗舰AI抠图 | `RemoveUltraBackground` | `ultra_segment_fast()` | 自建平台 HTTP 接口 |
|
|
|
|
|
+| `"4"` | 旗舰AI抠图-精细 | `RemoveUltraBackground` | `run_ultra_segment()` | 阿里 SDK 预抠图 + 自建平台接口 二次抠图 |
|
|
|
|
|
+| `"5"` | 旗舰AI抠图-透明物品 | `RemoveBgGlasses` | `glasses_segment()` | 自建平台 HTTP 接口 |
|
|
|
|
|
+
|
|
|
|
|
+调用分发代码(`base_deal.py` L663-687):
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+if cutout_mode == "1":
|
|
|
|
|
+ remove_pic_ins = RemoveBgALi()
|
|
|
|
|
+ im = remove_pic_ins.get_image_cut(
|
|
|
|
|
+ file_path=original_image_path,
|
|
|
|
|
+ out_file_path=original_move_bg_image_path,
|
|
|
|
|
+ )
|
|
|
|
|
+if cutout_mode == "3":
|
|
|
|
|
+ remove_pic_ins = RemoveUltraBackground()
|
|
|
|
|
+ im = remove_pic_ins.ultra_segment_fast(
|
|
|
|
|
+ file_path=original_image_path,
|
|
|
|
|
+ out_file_path=original_move_bg_image_path,
|
|
|
|
|
+ )
|
|
|
|
|
+if cutout_mode == "4":
|
|
|
|
|
+ remove_pic_ins = RemoveUltraBackground()
|
|
|
|
|
+ im = remove_pic_ins.run_ultra_segment(
|
|
|
|
|
+ file_path=original_image_path,
|
|
|
|
|
+ out_file_path=original_move_bg_image_path,
|
|
|
|
|
+ )
|
|
|
|
|
+if cutout_mode == "5":
|
|
|
|
|
+ remove_pic_ins = RemoveBgGlasses()
|
|
|
|
|
+ im = remove_pic_ins.glasses_segment(
|
|
|
|
|
+ file_path=original_image_path,
|
|
|
|
|
+ out_file_path=original_move_bg_image_path,
|
|
|
|
|
+ )
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+所有方法签名一致:入参 `file_path`(原图路径,必须是 RGB/JPG,PNG 直接返回 None)、`out_file_path`(抠图结果输出 PNG 路径);返回 PIL `Image` 对象(RGBA),失败返回 `None`。方法均带 `@func_set_timeout(40)` 40 秒超时保护,超时抛 `FunctionTimedOut`。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 二、模式1:普通抠图(阿里云 SDK)
|
|
|
|
|
+
|
|
|
|
|
+### 2.1 依赖安装
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+pip install alibabacloud-imageseg20191230==3.0.1
|
|
|
|
|
+pip install alibabacloud_tea_openapi==0.3.13
|
|
|
|
|
+pip install alibabacloud_tea_util==0.3.13
|
|
|
|
|
+pip install func_timeout==4.3.5
|
|
|
|
|
+pip install pillow==11.1.0
|
|
|
|
|
+pip install opencv-python==4.11.0.86
|
|
|
|
|
+pip install numpy==1.25.2
|
|
|
|
|
+pip install requests==2.32.3
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+(版本来自 `python/requestments.txt`)
|
|
|
|
|
+
|
|
|
|
|
+### 2.2 导入包
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+from alibabacloud_imageseg20191230.client import Client as imageseg20191230Client
|
|
|
|
|
+from alibabacloud_imageseg20191230 import models as imageseg_20191230_models
|
|
|
|
|
+from alibabacloud_imageseg20191230.models import SegmentCommodityAdvanceRequest
|
|
|
|
|
+from alibabacloud_tea_openapi import models as open_api_models
|
|
|
|
|
+from alibabacloud_tea_util.models import RuntimeOptions
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 2.3 客户端初始化(`Segment.create_client`)
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+config = open_api_models.Config(
|
|
|
|
|
+ access_key_id=AccessKeyId, # 硬编码在 remove_bg_ali.py L23
|
|
|
|
|
+ access_key_secret=AccessKeySecret # 硬编码在 remove_bg_ali.py L24
|
|
|
|
|
+)
|
|
|
|
|
+config.endpoint = 'imageseg.cn-shanghai.aliyuncs.com'
|
|
|
|
|
+client = imageseg20191230Client(config)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 2.4 调用流程(`RemoveBgALi.get_image_cut`)
|
|
|
|
|
+
|
|
|
|
|
+1. `Image.open(file_path)` 打开原图,校验 `mode == "RGB"`(PNG 直接返回 None)。
|
|
|
|
|
+2. 长边超过 **2000px** 则等比缩放到 2000,记录 `after_need_resize=True`。
|
|
|
|
|
+3. 图片存进 `BytesIO`(JPEG 格式),构造请求对象:
|
|
|
|
|
+
|
|
|
|
|
+ ```python
|
|
|
|
|
+ request = imageseg_20191230_models.SegmentCommodityAdvanceRequest()
|
|
|
|
|
+ request.image_urlobject = img # 图片字节流(本地文件流 / BytesIO 均可)
|
|
|
|
|
+ runtime = util_models.RuntimeOptions()
|
|
|
|
|
+ response = client.segment_commodity_advance(request, runtime)
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+ **请求参数**:`image_urlobject` — 商品图片字节流(能力名称 SegmentCommodity,对应方法 `segment_commodity_advance`)。
|
|
|
|
|
+
|
|
|
|
|
+4. **返回取值**:
|
|
|
|
|
+
|
|
|
|
|
+ ```python
|
|
|
|
|
+ body = eval(str(response.body))
|
|
|
|
|
+ image_url = body["Data"]["ImageURL"] # 阿里云 OSS 上的抠图结果 PNG 地址
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+5. `requests.get(image_url)` 下载抠图结果,转 PIL 对象。
|
|
|
|
|
+6. 若原图被缩放过:把抠图结果放大回原尺寸 → 取 alpha 通道做 mask → OpenCV 腐蚀(`MORPH_ELLIPSE (10,10)` 选区收缩)→ 用 mask 从**原图**重新抠出透明图,与结果拼接(保证原图分辨率与细节)。
|
|
|
|
|
+7. `out_file_path` 存在时通过 `ImageSaver.save_image(..., quality=100, dpi=(350,350), _format="PNG")` 保存,返回 PIL 对象。
|
|
|
|
|
+
|
|
|
|
|
+> 变体 `get_image_cut_new()`:逻辑相同,但不做还原拼接,直接返回抠图结果裁剪后的 `BytesIO`(供 `/api/upload` 上传用)。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 三、模式2:精细化抠图 Pixian(两段式)
|
|
|
|
|
+
|
|
|
|
|
+实现文件:`python/service/deal_cutout.py`、`python/service/deal_one_image.py`、`python/service/remove_bg_pixian.py`、`python/service/online_request/module_online_data.py`
|
|
|
|
|
+
|
|
|
|
|
+### 3.1 调用入口(`base_deal.py` L649-661、L736-745)
|
|
|
|
|
+
|
|
|
|
|
+模式2 不走单图直调,而是先把当前货号文件夹内所有待抠图组装成列表,再批量执行:
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+# L649-661:组装 cutImageList
|
|
|
|
|
+cutImageList.append({
|
|
|
|
|
+ "file_name": file_name, # 文件名(不含后缀)
|
|
|
|
|
+ "file_e": suffix, # 后缀,.jpg
|
|
|
|
|
+ "file_path": original_image_path, # 原图完整路径
|
|
|
|
|
+ "file": "{}{}".format(file_name, suffix), # 图片文件名,带后缀
|
|
|
|
|
+ "need_cutout": True, # 必须,需要抠图
|
|
|
|
|
+ "out_path": original_move_bg_image_path, # 抠图结果输出路径
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+# L736-745:批量执行并轮询等待
|
|
|
|
|
+if cutout_mode == "2":
|
|
|
|
|
+ dealCutout = DealCutout(windows=None, token=self.token)
|
|
|
|
|
+ dealCutout.need_cutout_images = cutImageList
|
|
|
|
|
+ dealCutout.run()
|
|
|
|
|
+ while True:
|
|
|
|
|
+ time.sleep(0.5)
|
|
|
|
|
+ if dealCutout.state == 3: # 1进行中 2停止 3已结束
|
|
|
|
|
+ if len(dealCutout.resultData) != len(cutImageList):
|
|
|
|
|
+ error_goods_art_no_folder.append(folder_path) # 有图失败 → 整个文件夹记错误
|
|
|
|
|
+ break
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**返回取值**:`dealCutout.state == 3` 表示结束;`dealCutout.resultData` 为成功输出的文件路径列表,其数量与 `cutImageList` 不等即视为有失败项。
|
|
|
|
|
+
|
|
|
|
|
+### 3.2 第一阶段:阿里预抠图预处理(`DealOneImageBeforehand.run`)
|
|
|
|
|
+
|
|
|
|
|
+对每张图(并发上传槽位最多 4 个,`is_upload_pic_num >= 4` 时排队等待):
|
|
|
|
|
+
|
|
|
|
|
+1. 打开原图 → EXIF 方向校正 → 转 RGB。
|
|
|
|
|
+2. 像素 < 1,000,000:直接用原图,`二次抠图是否缩放=False`。
|
|
|
|
|
+3. 否则调用**模式1 的阿里 SDK** `RemoveBgALi.get_image_cut()` 预抠图 → `getbbox()` 定位主体 → 四周**扩边 2.5%** 从原图裁剪。
|
|
|
|
|
+4. 裁剪区像素超过 `settings.MAX_PIXIAN_SIZE`(12,000,000):按 `sqrt(max)/sqrt(xy)*0.9` 缩放,`二次抠图是否缩放=True` 并备份原尺寸 PIL 对象。
|
|
|
|
|
+5. 结果写入共享字典:
|
|
|
|
|
+
|
|
|
|
|
+ ```python
|
|
|
|
|
+ windows.upload_pic_dict[file_path] = {
|
|
|
|
|
+ "url": "", "image_deal_info": image_deal_info,
|
|
|
|
|
+ "flag": True, # 预处理成功标志
|
|
|
|
|
+ "_im": cut_image, # 待二次抠图的 PIL 对象
|
|
|
|
|
+ }
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+### 3.3 第二阶段:Pixian 抠图(`DealOneImage.run`)
|
|
|
|
|
+
|
|
|
|
|
+1. 轮询等待该图的 `upload_pic_dict` 条目就绪(最长 60 秒,超时失败)。
|
|
|
|
|
+2. **获取 Pixian 密钥**:
|
|
|
|
|
+
|
|
|
|
|
+ - 接口:`POST {DOMAIN}/api/ai_image/client/get_key_serect`
|
|
|
|
|
+ - 请求头:`{"Authorization": token}`
|
|
|
|
|
+ - 返回取值:`response_data["data"]["api_info"]["api_key"]` 与 `["api_serect"]`,组成 HTTP Basic Auth 元组 `(api_key, api_serect)`
|
|
|
|
|
+
|
|
|
|
|
+3. **调用 Pixian 抠图接口**(`RemoveBgPiXian.run_by_image_im`):
|
|
|
|
|
+
|
|
|
|
|
+ - 先上传:`upload_image_by_io(im)` → `POST {DOMAIN}/api/upload`(`files={"file": BytesIO}`,无鉴权头,timeout=100),取 `resultData["data"]["url"]`
|
|
|
|
|
+ - 域名替换:URL 中 `ossimg.valimart.net` → `img-cuts.valimart.net`
|
|
|
|
|
+ - 抠图请求(`Segment.get_no_bg_goods_by_url`):
|
|
|
|
|
+
|
|
|
|
|
+ | 项 | 值 |
|
|
|
|
|
+ |---|---|
|
|
|
|
|
+ | 接口 | `POST https://3api.valimart.net/api/v2/remove-background` |
|
|
|
|
|
+ | 鉴权 | HTTP Basic Auth `(api_key, api_serect)`(第3步动态获取) |
|
|
|
|
|
+ | 请求体 | form 表单:`{"image.url": <上传后的图片URL>}` |
|
|
|
|
|
+ | 超时 | 300 秒 |
|
|
|
|
|
+ | 返回 | **成功(200)时响应体直接是抠图后的 PNG 图片字节流**(非 JSON),`Image.open(BytesIO(response.content))` 转 PIL |
|
|
|
|
|
+
|
|
|
|
|
+ - 状态码处理:`200` 成功;`402` Pixian 账号余额不足(重试,第2次失败发钉钉告警 `POST {DOMAIN}/api/ai_image/client/send_message`,body `{"message": text}`);`429` 限流(延迟10秒重试);其他 → 失败。
|
|
|
|
|
+
|
|
|
|
|
+4. **还原拼接**(与模式4一致):若 `二次抠图是否缩放=True` → `picture_resize_to_original()` 放大回裁剪区尺寸 + mask 腐蚀;新建原图尺寸透明画布,把结果粘贴到"抠图扩边后位置"左上角;`save(out_path, dpi=(350,350))`。
|
|
|
|
|
+
|
|
|
|
|
+### 3.5 直传变体(`Segment.get_no_bg_goods2`,备用)
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+requests.post(
|
|
|
|
|
+ 'http://47.76.110.118:27777/api/v2/remove-background',
|
|
|
|
|
+ files={'image.url': img}, # 图片字节流直传,无需先 /api/upload
|
|
|
|
|
+ auth=(k, s), # 硬编码密钥,remove_bg_pixian.py L11-12
|
|
|
|
|
+)
|
|
|
|
|
+# 200 → response.content 为抠图 PNG 字节流
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> 依赖:仅 `requests` + `pillow`,Pixian 为标准 HTTP 接口,无需额外 SDK。密钥硬编码于 `remove_bg_pixian.py`(k/s)与平台下发(get_key_serect),建议移到配置管理。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 四、模式3:旗舰AI抠图(自建平台接口)
|
|
|
|
|
+
|
|
|
|
|
+### 4.1 调用流程(`RemoveUltraBackground.ultra_segment_fast`)
|
|
|
|
|
+
|
|
|
|
|
+1. 打开原图,EXIF 方向校正(`Segment.get_image_orientation`),校验 RGB。
|
|
|
|
|
+2. 长边超过 **2000px** 等比缩放。
|
|
|
|
|
+3. **先上传**:调用 `uploadImage(im)`(见第五节 `/api/upload`)拿到图片 URL。
|
|
|
|
|
+4. **调用抠图接口**(`Segment.get_bo_bg_goods_ultra_background`):
|
|
|
|
|
+
|
|
|
|
|
+ - 接口地址:`{settings.DOMAIN}/api/ai_image/segment_shadow/segment_service`
|
|
|
|
|
+ (`DOMAIN` 定义在 `settings.py` L252:非 dev 环境 `https://dev2.valimart.net`,dev 环境 `https://dev2.pubdata.cn`)
|
|
|
|
|
+ - 请求方式:`POST`,`Content-Type: application/json`
|
|
|
|
|
+ - 请求头:`{"Authorization": "Bearer " + settings.USER_TOKEN}`
|
|
|
|
|
+ - 请求体(JSON):
|
|
|
|
|
+
|
|
|
|
|
+ | 字段 | 类型 | 说明 |
|
|
|
|
|
+ |---|---|---|
|
|
|
|
|
+ | `image_url` | str | `/api/upload` 返回的图片 URL |
|
|
|
|
|
+ | `is_shadow` | bool | 是否生成阴影,默认 `False` |
|
|
|
|
|
+
|
|
|
|
|
+ - 调用代码:
|
|
|
|
|
+
|
|
|
|
|
+ ```python
|
|
|
|
|
+ response = requests.post(api_url, json=data, headers=post_headers)
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+5. **返回取值**:
|
|
|
|
|
+
|
|
|
|
|
+ ```python
|
|
|
|
|
+ resultData = response.json()
|
|
|
|
|
+ data = resultData.get("data") # 为空 → 返回 None(失败)
|
|
|
|
|
+ cutout_image = data.get("cutout_image") # 抠图结果图片 URL;为空 → 返回 None
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+6. `requests.get(cutout_image)` 下载结果转 PIL;若缩放过则执行与模式1相同的"放大回原尺寸 + mask 腐蚀 + 原图重抠"还原处理。
|
|
|
|
|
+7. 保存 PNG(quality=100, dpi=350)并返回 PIL 对象。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 五、模式4:旗舰AI抠图-精细(两段式)
|
|
|
|
|
+
|
|
|
|
|
+`RemoveUltraBackground.run_ultra_segment(file_path, out_file_path)`,流程:
|
|
|
|
|
+
|
|
|
|
|
+1. **阿里预抠图**(`get_image_cut_ali`):
|
|
|
|
|
+ - 原图像素 < 1,000,000:直接用原图,不预处理。
|
|
|
|
|
+ - 否则调用模式1的 `RemoveBgALi.get_image_cut()` 先粗抠一次,用 `getbbox()` 找到主体位置,四周**扩边 2.5%** 裁剪出主体区域;
|
|
|
|
|
+ - 裁剪区域像素超过 `settings.MAX_PIXIAN_SIZE`(12,000,000)时二次缩放(缩放系数 `sqrt(max)/sqrt(xy)*0.9`),记录 `二次抠图是否缩放=True` 并备份原尺寸 PIL 对象。
|
|
|
|
|
+2. **旗舰接口二次抠图**(`get_image_cut_ultra`):把裁剪后的图走第三节同样的 `/api/ai_image/segment_shadow/segment_service` 接口,取 `data.cutout_image` 下载。
|
|
|
|
|
+3. **还原拼接**:
|
|
|
|
|
+ - 若二次缩放过 → `picture_resize_to_original()` 把抠图结果放大回裁剪区尺寸并做 mask 腐蚀还原;
|
|
|
|
|
+ - 新建与原图同尺寸的透明画布 `Image.new("RGBA", 原始图片大小, (0,0,0,0))`,把抠图结果粘贴回"抠图扩边后位置"的左上角坐标;
|
|
|
|
|
+ - `save(out_file_path, dpi=(350,350))` 保存,返回 PIL 对象;任何异常记录日志并返回 None。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 六、模式5:旗舰AI抠图-透明物品(玻璃制品)
|
|
|
|
|
+
|
|
|
|
|
+`RemoveBgGlasses.glasses_segment(file_path, out_file_path)`,流程与模式3几乎一致,仅接口不同:
|
|
|
|
|
+
|
|
|
|
|
+- 接口地址:`{settings.DOMAIN}/api/ai_image/segment_shadow/transparent_segment`
|
|
|
|
|
+- 缩放阈值:长边 **4000px**(旗舰版为 2000px)
|
|
|
|
|
+- 请求体:`{"image_url": <上传URL>, "is_shadow": False}`
|
|
|
|
|
+- 请求头:`{"Authorization": "Bearer " + settings.USER_TOKEN}`
|
|
|
|
|
+- 返回取值:`resultData["data"]["cutout_image"]`(图片 URL)
|
|
|
|
|
+- 后续同样执行 EXIF 校正、下载、mask 腐蚀还原、保存 PNG。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 七、登录鉴权:获取 token 流程
|
|
|
|
|
+
|
|
|
|
|
+所有平台接口(`/api/upload`、旗舰抠图、余额查询等)都依赖登录 token。token 获取分三步:登录 → (按需)选择企业 → 使用 token。
|
|
|
|
|
+
|
|
|
|
|
+### 7.1 第一步:账号登录
|
|
|
|
|
+
|
|
|
|
|
+- 接口:`POST {settings.DOMAIN}/api/auth/login`
|
|
|
|
|
+- 请求头:`Content-Type: application/json;charset=UTF-8`
|
|
|
|
|
+- 请求体(JSON):
|
|
|
|
|
+
|
|
|
|
|
+ | 字段 | 示例 | 说明 |
|
|
|
|
|
+ |---|---|---|
|
|
|
|
|
+ | `site` | `1` | 站点标识 |
|
|
|
|
|
+ | `username` | `"15001208995"` | 手机号账号 |
|
|
|
|
|
+ | `password` | `"753159"` | 密码 |
|
|
|
|
|
+ | `invite_code` | `""` | 邀请码,可空 |
|
|
|
|
|
+ | `type` | `1` | 登录类型 |
|
|
|
|
|
+ | `device` | `"aigc-pc"` | 设备标识 |
|
|
|
|
|
+
|
|
|
|
|
+- 调用示例:
|
|
|
|
|
+
|
|
|
|
|
+ ```bash
|
|
|
|
|
+ curl --url "https://dev2.pubdata.cn/api/auth/login" \
|
|
|
|
|
+ -H "content-type: application/json;charset=UTF-8" \
|
|
|
|
|
+ --data-raw '{"site":1,"username":"15001208995","password":"753159","invite_code":"","type":1,"device":"aigc-pc"}'
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+- 响应示例:
|
|
|
|
|
+
|
|
|
|
|
+ ```json
|
|
|
|
|
+ {
|
|
|
|
|
+ "data": {
|
|
|
|
|
+ "token": "ebc9d472ec392dac1bea3bbd4ef20577316196ae",
|
|
|
|
|
+ "is_set_password": true,
|
|
|
|
|
+ "is_need_select_company": 40006,
|
|
|
|
|
+ "is_real_name": 1
|
|
|
|
|
+ },
|
|
|
|
|
+ "code": 0,
|
|
|
|
|
+ "message": "success"
|
|
|
|
|
+ }
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+- **返回取值**:`data.token` — 后续所有接口的鉴权凭证(`Authorization: Bearer {token}`)。
|
|
|
|
|
+- **关键判断**:`data.is_need_select_company`:
|
|
|
|
|
+ - `== 40006`(未选择组织)→ 必须先执行 7.2 选择企业,token 才能正常使用;
|
|
|
|
|
+ - 其他值 → 跳过 7.2,直接使用 token。
|
|
|
|
|
+
|
|
|
|
|
+### 7.2 第二步(仅当 is_need_select_company == 40006):获取企业列表并选择
|
|
|
|
|
+
|
|
|
|
|
+**① 获取当前账号下的企业列表**
|
|
|
|
|
+
|
|
|
|
|
+- 接口:`GET {settings.DOMAIN}/api/backend/account/get_account_company?site=1`
|
|
|
|
|
+- 请求头:`Authorization: Bearer {token}`(7.1 返回的 token)
|
|
|
|
|
+
|
|
|
|
|
+ ```bash
|
|
|
|
|
+ curl --url "https://dev2.pubdata.cn/api/backend/account/get_account_company?site=1" \
|
|
|
|
|
+ -H "authorization: Bearer 9f1ac7af7ce1b42bd8c5c78e4f059b2ddedbdbd2"
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+- 返回取值:企业列表,从中取用户要进入的**企业 id**。
|
|
|
|
|
+
|
|
|
|
|
+**② 选择企业登录**
|
|
|
|
|
+
|
|
|
|
|
+- 接口:`POST {settings.DOMAIN}/api/auth/select_company`
|
|
|
|
|
+- 请求头:`Authorization: Bearer {token}`
|
|
|
|
|
+- 请求体:`multipart/form-data`,表单字段 `id` = 企业 id
|
|
|
|
|
+
|
|
|
|
|
+ ```bash
|
|
|
|
|
+ curl --location "https://dev2.pubdata.cn/api/auth/select_company" \
|
|
|
|
|
+ -H "Authorization: Bearer {token}" \
|
|
|
|
|
+ --form 'id="企业id"'
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+- 选择成功后,**继续使用 7.1 的 token** 调用其他业务接口。
|
|
|
|
|
+
|
|
|
|
|
+### 7.3 错误码对照表(`App\Helper\ErrorCode`)
|
|
|
|
|
+
|
|
|
|
|
+| 错误码 | 常量 | 含义 |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| 40000 | `COMMON` | 一般错误 |
|
|
|
|
|
+| 40001 | `NO_AUTH` | 总后台未登录 |
|
|
|
|
|
+| 40002 | `USER_DISABLE` | 账号被禁用 |
|
|
|
|
|
+| 40003 | `NO_PERMISSION` | 用户无权限访问 |
|
|
|
|
|
+| 40006 | `NO_SELECT_COMPANY` | **未选择组织**(需走 7.2 选企业) |
|
|
|
|
|
+| 40007 | `NO_COMPANY` | 用户未在任何组织中 |
|
|
|
|
|
+| 40008 | `CURRENT_COMPANY_STOP` | 当前企业已停用 |
|
|
|
|
|
+| 40401 | `AI_NO_AUTH` | AI 无授权权限 |
|
|
|
|
|
+| 400011 | `BODY_NOT_MATCH` | 请求体不匹配 |
|
|
|
|
|
+| 500401 | `SHOP_DISABLE` | 门店已禁用 |
|
|
|
|
|
+
|
|
|
|
|
+### 7.4 token 在项目中的注入位置
|
|
|
|
|
+
|
|
|
|
|
+获取到的 token 赋给 `settings.USER_TOKEN`,供全部抠图/上传接口使用:
|
|
|
|
|
+
|
|
|
|
|
+- `api.py` L1650 / L1692:`settings.USER_TOKEN = hlm_token`
|
|
|
|
|
+- `api.py` L1917:`settings.USER_TOKEN = params.token`(接口入参携带)
|
|
|
|
|
+- `sockets/message_handler.py` L690:`settings.USER_TOKEN = token`(WebSocket 登录消息)
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 八、`/api/upload` 图片上传接口
|
|
|
|
|
+
|
|
|
|
|
+### 8.1 接口定义
|
|
|
|
|
+
|
|
|
|
|
+| 项 | 值 |
|
|
|
|
|
+|---|---|
|
|
|
|
|
+| 完整地址 | `{settings.DOMAIN}/api/upload`(域名同上,非 dev:`https://dev2.valimart.net`) |
|
|
|
|
|
+| 请求方式 | `POST` |
|
|
|
|
|
+| Content-Type | `multipart/form-data`(由 requests 的 `files` 参数自动生成) |
|
|
|
|
|
+| 表单字段 | `file` — 图片文件 / 字节流 |
|
|
|
|
|
+| 鉴权 | 请求头 `Authorization`(两种写法见下) |
|
|
|
|
|
+
|
|
|
|
|
+### 8.2 三种调用方式(项目中实际存在)
|
|
|
|
|
+
|
|
|
|
|
+**方式A:PIL 对象 → 字节流上传**(`image_deal_base_func.py` L8-27、`remove_bg_ali.py` L27-46、`api.py` L2446-2465)
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+def uploadImage(im: Image) -> str:
|
|
|
|
|
+ img_byte_io = BytesIO()
|
|
|
|
|
+ if im.mode == 'RGBA':
|
|
|
|
|
+ im.save(img_byte_io, format='PNG')
|
|
|
|
|
+ else:
|
|
|
|
|
+ im.save(img_byte_io, format='JPEG')
|
|
|
|
|
+ img_byte_io.seek(0)
|
|
|
|
|
+
|
|
|
|
|
+ post_headers = {"Authorization": settings.USER_TOKEN}
|
|
|
|
|
+ url = settings.DOMAIN + "/api/upload"
|
|
|
|
|
+ resultData = requests.post(
|
|
|
|
|
+ url,
|
|
|
|
|
+ files={"file": ("image.jpg", img_byte_io, "image/jpeg")},
|
|
|
|
|
+ headers=post_headers
|
|
|
|
|
+ ).json()
|
|
|
|
|
+ return resultData["data"]["url"]
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**方式B:本地文件路径上传**(`online_request/module_online_data.py` L54-61)
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+post_headers = {"Authorization": self.token} # token 形如 "Bearer xxx"
|
|
|
|
|
+resultData = requests.post(
|
|
|
|
|
+ settings.DOMAIN + "/api/upload",
|
|
|
|
|
+ files={"file": open(local_path, "rb")},
|
|
|
|
|
+ headers=post_headers
|
|
|
|
|
+).json()
|
|
|
|
|
+return resultData["data"]["url"]
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**方式C:BytesIO 直接上传(无文件名三元组)**(`remove_bg_pixian.py` L175-193、`api.py` L1776-1783)
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+resultData = requests.post(url, files={"file": img}, timeout=100).json()
|
|
|
|
|
+return resultData["data"]["url"]
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 8.3 鉴权说明
|
|
|
|
|
+
|
|
|
|
|
+- 项目内两种写法并存:
|
|
|
|
|
+ - `{"Authorization": settings.USER_TOKEN}` — USER_TOKEN 由外部注入(`api.py` L1650/L1917、`sockets/message_handler.py` L690),通常已含 `Bearer ` 前缀;
|
|
|
|
|
+ - `{"Authorization": "Bearer " + token}` — 显式拼接。
|
|
|
|
|
+- 若 `settings.USER_TOKEN` 为空,抠图接口调用直接返回 None。
|
|
|
|
|
+
|
|
|
|
|
+### 8.4 返回结构与取值
|
|
|
|
|
+
|
|
|
|
|
+响应为 JSON:
|
|
|
|
|
+
|
|
|
|
|
+```json
|
|
|
|
|
+{
|
|
|
|
|
+ "code": 0,
|
|
|
|
|
+ "msg": "...",
|
|
|
|
|
+ "data": {
|
|
|
|
|
+ "url": "https://ossimg.valimart.net/xxxx.jpg"
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**取值方式**:`resultData["data"]["url"]` — 上传成功后的 OSS 图片外链,直接作为后续抠图接口的 `image_url` 入参。
|
|
|
|
|
+
|
|
|
|
|
+> 注:`shoe_main_image_compose.py` L192-215 的 `upload_image_by_io` 是该接口的合成场景封装(RGBA 存 PNG / 其余存 JPEG,带 100s 超时,失败返回 None);`api.py` L2482 展示了 URL 后拼接 OSS 处理参数的用法:`uploadImageIM(pil) + "?x-oss-process=image/format,jpg"`。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 九、相关辅助接口(同域名,供参考)
|
|
|
|
|
+
|
|
|
|
|
+`Segment` 类中还有两个基于 `/api/upload` 的辅助接口(鞋类合成阴影场景使用):
|
|
|
|
|
+
|
|
|
|
|
+| 接口 | 方法 | 请求体 | 返回取值 |
|
|
|
|
|
+|---|---|---|---|
|
|
|
|
|
+| `{DOMAIN}/api/ai_image/segment_shadow/...`(阴影生成) | `get_platform_shadow` | `{"oraginal_image": <上传URL>}` | `resultData["data"]["oss_url"]` |
|
|
|
|
|
+| 阴影合成 | `get_ultra_shadow` | `{"oraginal_image": <URL>, "cutout_image": <URL>, "opacity": <float>, "bright_target": <float>}` | `resultData["data"]["matte_image"]` |
|
|
|
|
|
+
|
|
|
|
|
+均为 `POST` + JSON + `Authorization: Bearer {USER_TOKEN}`,`data` 或目标字段为空即视为失败返回 None。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 十、关键方法:`picture_resize_to_original` 尺寸还原
|
|
|
|
|
+
|
|
|
|
|
+定义位置:`remove_bg_ali.py` L693-733(`RemoveUltraBackground`)、`deal_one_image.py` L306-346(`DealOneImage`),两份实现相同。模式2、模式4 在"二次抠图是否缩放=True"时调用。
|
|
|
|
|
+
|
|
|
|
|
+### 10.1 作用
|
|
|
|
|
+
|
|
|
|
|
+第三方抠图是在**缩小后的图**上执行的,返回结果分辨率低。本方法把低分辨率抠图结果**放大回原尺寸**,用其 alpha 通道生成选区 mask,再从**原尺寸原图**中重新抠出主体,保证输出保留原图细节。
|
|
|
|
|
+
|
|
|
|
|
+### 10.2 具体实现
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+def picture_resize_to_original(self, _img, original_im):
|
|
|
|
|
+ """
|
|
|
|
|
+ _img 需要还原的抠图结果(小尺寸 RGBA PIL 对象)
|
|
|
|
|
+ original_im 原尺寸的原图 PIL 对象(抠图扩边后PIL对象)
|
|
|
|
|
+ """
|
|
|
|
|
+ # 1. 将抠图结果放大到原始图大小(resample=1 即 BICUBIC)
|
|
|
|
|
+ _img = _img.resize(original_im.size, resample=1)
|
|
|
|
|
+
|
|
|
|
|
+ # 2. 用抠图结果的 alpha 通道生成黑白 mask
|
|
|
|
|
+ # 黑底图 new_big_mask,把纯白 white 按 alpha 通道粘贴上去
|
|
|
|
|
+ # → 主体处为白色(255),背景处为黑色(0)
|
|
|
|
|
+ new_big_mask = Image.new("RGB", _img.size, (0, 0, 0))
|
|
|
|
|
+ white = Image.new("RGB", _img.size, (255, 255, 255))
|
|
|
|
|
+ new_big_mask.paste(white, mask=_img.split()[3]) # split()[3] = alpha 通道
|
|
|
|
|
+
|
|
|
|
|
+ # 3. 转 OpenCV 灰度图并二值化:非 255 的像素(半透明边缘)全部归 0
|
|
|
|
|
+ mask = cv2.cvtColor(np.asarray(new_big_mask), cv2.COLOR_BGR2GRAY)
|
|
|
|
|
+ mask[mask != 255] = 0
|
|
|
|
|
+
|
|
|
|
|
+ # 4. 形态学腐蚀:椭圆核 (10,10),选区整体向内收缩约 10px
|
|
|
|
|
+ # 目的:去掉放大后边缘的白边/毛边
|
|
|
|
|
+ kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (10, 10))
|
|
|
|
|
+ erode_im = cv2.morphologyEx(mask, cv2.MORPH_ERODE, kernel)
|
|
|
|
|
+
|
|
|
|
|
+ # 5. 转回 PIL,用收缩后的 mask 从"原尺寸原图"重新抠出透明图
|
|
|
|
|
+ mask = Image.fromarray(cv2.cvtColor(erode_im, cv2.COLOR_GRAY2RGBA))
|
|
|
|
|
+ transparent_im = Image.new("RGBA", original_im.size, (0, 0, 0, 0))
|
|
|
|
|
+ transparent_im.paste(original_im, (0, 0), mask.convert("L")) # L=灰度作透明度
|
|
|
|
|
+
|
|
|
|
|
+ # 6. 将原图细节抠出的透明图叠加回放大后的抠图结果上并返回
|
|
|
|
|
+ _img.paste(transparent_im, (0, 0), transparent_im)
|
|
|
|
|
+ return _img
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 10.3 流程图解
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+小尺寸抠图结果(_img) 原尺寸原图(original_im)
|
|
|
|
|
+ │ │
|
|
|
|
|
+ 放大到原尺寸 │
|
|
|
|
|
+ │ │
|
|
|
|
|
+ 取 alpha 通道 → 黑白 mask │
|
|
|
|
|
+ │ │
|
|
|
|
|
+ 二值化(去半透明边) → 腐蚀收缩10px │
|
|
|
|
|
+ │ │
|
|
|
|
|
+ └──── mask 作为选区 ────→ 从原图抠出 transparent_im
|
|
|
|
|
+ │
|
|
|
|
|
+ 叠加合并 ←─────────────┘
|
|
|
|
|
+ │
|
|
|
|
|
+ 返回 RGBA 抠图结果(原图分辨率、原图细节)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> 模式1/3/5 中的"放大回原尺寸 + mask 腐蚀 + 原图重抠"内联代码与本方法逻辑完全一致,只是未抽成函数。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 十一、公共约定汇总
|
|
|
|
|
+
|
|
|
|
|
+1. **输入限制**:原图必须为 RGB 模式(JPG),PNG 模式直接返回 None。
|
|
|
|
|
+2. **EXIF 方向校正**:旗舰/透明模式调用前按 EXIF Orientation 标签(0x0112)旋转/翻转。
|
|
|
|
|
+3. **缩放阈值**:普通/旗舰 2000px,透明物品 4000px,精细模式二次抠图上限 `MAX_PIXIAN_SIZE=12,000,000` 像素。
|
|
|
|
|
+4. **还原策略**:缩放过的小图结果,放大回原尺寸后用 alpha 通道做 mask,OpenCV 椭圆核 `(10,10)` 腐蚀收缩选区,再从原图重抠拼接,保证输出为原图分辨率。
|
|
|
|
|
+5. **输出**:统一保存为 PNG(quality=100、dpi=350),返回 RGBA PIL 对象。
|
|
|
|
|
+6. **超时**:单图 40 秒(`func_timeout`),超时/异常在 `base_deal.py` 捕获并通过钉钉 `sendDingMsg` 告警、记入失败列表。
|