140 Commits

Author SHA1 Message Date
9db2f96557 更新 .gitea/workflows/prod_build_manual.yaml 2026-04-24 10:20:13 +08:00
zcr
cc2404831d Merge branch 'develop' 2026-04-17 14:15:57 +08:00
zcr
6892361050 修复design印花部分 overall 模式印花平铺起始从印花图片中心开始
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-04-15 17:36:29 +08:00
zcr
f0b73d5fc1 修复design印花部分 mask_inv_print 提取错误
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-04-15 17:23:00 +08:00
zcr
ea7522a45d Merge branch 'develop'
# Conflicts:
#	app/service/design_fast/utils/synthesis_item.py
#	app/service/prompt_generation/chatgpt_for_translation.py
2026-04-14 10:18:04 +08:00
zcr
7543d6b346 feat: 更新flux2 klein 的输出示例 ; fix:
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-04-14 10:16:30 +08:00
zcr
3ca4003e30 feat: 更新flux2 klein 的输出示例 ; fix: 2026-03-30 17:22:14 +08:00
zcr
59e8a88a01 feat: 更新flux2 klein 的输出示例 ; fix: 2026-03-30 17:14:18 +08:00
zcr
ac6f74438d feat: 更新分割模型参数 ; fix: 2026-03-27 15:10:16 +08:00
zcr
e0d519bfb3 feat: 更新分割模型参数 ; fix: 2026-03-27 15:10:16 +08:00
zcr
3414f2c1aa feat: 更新分割模型参数 ; fix: 2026-03-27 14:59:27 +08:00
zcr
160bf1a6b1 feat: 更新分割模型参数 ; fix: 2026-03-27 14:56:32 +08:00
zcr
79eb3fb859 feat: flux2 增加状态码 ; fix: 2026-03-25 23:17:03 +08:00
zcr
4395d67288 feat: flux2 增加状态码 ; fix: 2026-03-25 23:17:02 +08:00
zcr
674514ec11 feat: brand dna logo生成替换flux2klein ; fix: 2026-03-25 23:16:53 +08:00
zcr
e9ca1d301b feat: 新增flux2klein作为moodboard的localbase 模型 ; fix: 2026-03-25 23:16:48 +08:00
zcr
a4d55fdb14 feat: flux2 增加状态码 ; fix: 2026-03-25 10:29:03 +08:00
zcr
7f2f79d029 feat: flux2 增加状态码 ; fix: 2026-03-24 14:35:39 +08:00
zcr
6d9e96305b feat: brand dna logo生成替换flux2klein ; fix: 2026-03-23 11:21:50 +08:00
zcr
d93c50ce2b feat: 新增flux2klein作为moodboard的localbase 模型 ; fix: 2026-03-23 10:46:16 +08:00
zcr
316c2fef67 feat:
fix: 删除计数中间件
2026-03-13 11:22:57 +08:00
zcr
e25f49a776 feat:
fix: 删除计数中间件
2026-03-13 11:22:12 +08:00
zcr
33b4dd4a7f feat:
fix: 翻译 模型ip更换
2026-03-05 15:20:40 +08:00
zcr
ac8ca4dd46 feat:
fix: 翻译 模型ip更换
2026-03-05 15:17:47 +08:00
zcr
db88d9b813 feat:
fix: 翻译 模型ip更换
2026-03-05 15:13:40 +08:00
zcr
6ea9837f83 feat:
fix: sam 模型ip更换
2026-03-05 15:06:27 +08:00
zcr
7e48420ba7 feat:
fix: sam 模型ip更换
2026-03-05 15:06:19 +08:00
zcr
13002eefda feat:
fix:  others 旋转功能修复
2026-03-05 14:02:01 +08:00
zcr
09e25f423e feat:
fix:  others 旋转功能修复
2026-03-05 14:01:29 +08:00
zcr
bcc82ba065 feat:
fix:  替换项目中所有mmcv的依赖
2026-02-27 15:45:28 +08:00
zcr
dcc88adfc0 feat:
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix:  替换项目中所有mmcv的依赖
2026-02-27 15:26:07 +08:00
zcr
e4fd7b2fb9 feat:
fix:  替换项目中所有mmcv的依赖
2026-02-10 13:04:14 +08:00
ba93d33a17 更新 .gitea/workflows/prod_build_scheduled.yaml 2026-02-10 11:44:59 +08:00
292da1de2b 更新 .gitea/workflows/prod_build_manual.yaml 2026-02-10 11:44:48 +08:00
c6ebfae942 更新 .gitea/workflows/ltx_develop_build_manual.yaml 2026-02-10 11:44:36 +08:00
4dd8416911 更新 .gitea/workflows/develop_build_scheduled.yaml 2026-02-10 11:44:27 +08:00
zcr
bafcb68028 feat:
fix:  替换项目中所有mmcv的依赖
2026-02-10 11:34:09 +08:00
zcr
c03b7e263e feat:
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix:  替换项目中所有mmcv的依赖
2026-02-10 11:17:31 +08:00
zcr
200414e5ad feat: 停用flux2 img2product 复用sdxl img2product
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix:
2026-02-09 17:33:07 +08:00
23a6a30cc4 更新 .gitea/workflows/develop_build_manual.yaml 2026-02-09 15:28:47 +08:00
4d0688afd5 删除 .gitea/workflows/develop_build_commit.yaml 2026-02-09 15:28:22 +08:00
zcr
9a00fce0eb feat: 印花逻辑修改 默认不处理除overall以外所有印花类型
fix:
2026-02-03 16:44:01 +08:00
zcr
4656eeee91 feat: 印花逻辑修改 默认不处理除overall以外所有印花类型
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix:
2026-02-03 16:43:33 +08:00
zcr
f017d7e212 feat:
fix: 修复sketch类型为others时 跳过 上印花 导致的尺寸与分割尺寸不一致问题, 修复others分割出后片的问题
2026-02-03 16:23:05 +08:00
zcr
fe25f5878b feat:
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix: 修复sketch类型为others时 跳过 上印花 导致的尺寸与分割尺寸不一致问题, 修复others分割出后片的问题
2026-02-03 16:22:47 +08:00
zcr
c1b80c58f1 feat:
fix: 队列名修复
2026-02-02 15:37:31 +08:00
zcr
2cc17a1210 feat:
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix: 队列名修复
2026-02-02 15:37:01 +08:00
zcr
be92d48abb feat:
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix: 回溯镜像旋转逻辑
2026-01-30 15:45:57 +08:00
zcr
57be559cf2 feat:
fix:  修复类别为other时出现的pipeline item缺失
2026-01-29 16:26:16 +08:00
zcr
f8382f280f feat:
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix:  修复类别为other时出现的pipeline item缺失
2026-01-29 16:25:43 +08:00
zcr
c24862507f feat:
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix:  slogan 服务迁移
2026-01-28 15:37:03 +08:00
zcr
d5452098f3 feat:
fix: 移除打印
2026-01-27 13:53:21 +08:00
zcr
315e298ba8 feat:
fix:
2026-01-27 13:53:17 +08:00
zcr
ec26c8b507 feat:
fix:  印花overall 角度异常
2026-01-27 13:53:04 +08:00
zcr
e02ca351b6 feat:
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix:  印花overall 角度异常
2026-01-27 13:42:34 +08:00
zcr
c987f498bc feat:
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix:
2026-01-27 11:28:36 +08:00
zcr
3aa8dfa0f4 feat:
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix: 移除打印
2026-01-27 10:12:23 +08:00
zcr
265f4de50e feat:
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix: 更新端口
2026-01-26 16:32:30 +08:00
zcr
a996a1853d feat:
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix: 更新端口
2026-01-26 16:11:10 +08:00
zcr
1cbd019ffd feat: 更新翻译模型
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix:
2026-01-26 15:56:42 +08:00
zcr
e2a49e2f3a feat: 新增to product img flux2 版,停用sdxl版
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix:
2026-01-26 15:26:15 +08:00
zcr
66037c94e6 feat: 新增to product img flux2 版,停用sdxl版
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix:
2026-01-26 15:23:49 +08:00
zcr
754e8d7735 feat: 新增to product img flux2 版,停用sdxl版
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix:
2026-01-26 15:21:51 +08:00
zcr
cdaeb6daac feat: 新增to product img flux2 版,停用sdxl版
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
fix:
2026-01-26 15:19:28 +08:00
zcr
863d9287dc fix: 参数对齐
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
(cherry picked from commit ddef6af1cf)
2026-01-26 14:56:49 +08:00
zcr
ddef6af1cf fix: 参数对齐 2026-01-26 14:49:57 +08:00
zcr
fdffb1e724 Merge branch 'develop' 2026-01-24 22:05:57 +08:00
zcr
ecf10611c2 fix: merge 模式下 镜像和旋转功能与前端对其
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-24 14:43:10 +08:00
zcr
f78809b22a fix: merge 模式下 镜像和旋转功能与前端对其
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-24 14:03:35 +08:00
63a2f5e007 更新 .gitea/workflows/prod_build_scheduled.yaml 2026-01-24 03:20:19 +08:00
aeb67f366a 更新 .gitea/workflows/prod_build_manual.yaml 2026-01-24 03:20:03 +08:00
zcr
c244e313ae Merge branch 'develop' 2026-01-24 03:14:24 +08:00
zcr
15934085e0 fix: 修复design merge 模式 ,旋转sketch位置计算错误
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-24 03:03:26 +08:00
zcr
40b41d02a4 fix: 修复design merge 模式 ,旋转sketch位置计算错误
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-24 03:01:34 +08:00
1a1fd46f81 添加 .gitea/workflows/prod_build_manual.yaml 2026-01-24 02:55:56 +08:00
zcr
dcd8e26f0f fix: 修复design merge 模式 ,旋转sketch位置计算错误 2026-01-24 02:46:52 +08:00
zcr
fd94a3b4f0 Merge branch 'develop' 2026-01-24 02:44:51 +08:00
zcr
682c589238 fix: 修复design merge 模式 ,旋转sketch位置计算错误
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-24 02:44:13 +08:00
3f9309235a 2026.01.23 生产部署
All checks were successful
定时 AiDA python prod 分支构建部署 / scheduled_deploy (push) Successful in 2m20s
2026-01-23 21:06:19 +08:00
zcr
a578aa4fc5 暂时移除design 缓存
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-23 18:09:21 +08:00
zcr
ebd665b241 Merge branch 'develop'
# Conflicts:
#	app/core/config.py
2026-01-23 17:37:49 +08:00
zcr
ec649152e3 移除keypoint 缓存
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-23 17:34:51 +08:00
833e1bc924 暂时停用定时部署 2026-01-23 10:41:43 +08:00
zcr
7ed5911336 服务迁移测试
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-22 13:41:47 +08:00
zcr
b09538e294 feat: 新增design模式 merge,回参增加mask
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-15 14:13:56 +08:00
zcr
313863a6a7 fix: design 预处理 读取四通道图片背景变黑问题
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-13 15:36:28 +08:00
zcr
9ca1a2ba1f fix: design 单品未传design_type
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-13 14:58:31 +08:00
litianxiang
fb46a9521d Merge remote-tracking branch 'origin/develop' into dev-ltx
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-13 13:57:28 +08:00
litianxiang
b90688f835 更改增量更新日志级别 2026-01-13 13:57:15 +08:00
zcr
7e30779aec feat: seg any thing 新增box模式
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-13 12:43:30 +08:00
zcr
f7294f5966 feat: seg any thing 新增box模式
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-13 12:32:18 +08:00
zcr
0ac5a4e0a8 Merge remote-tracking branch 'origin/develop' into develop
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-12 16:18:15 +08:00
zcr
40b57b749c feat: 新增design模式 merge,前端CV python 合成 2026-01-12 16:18:04 +08:00
litianxiang
b8a538a8a1 fix:增量更新向量问题修改
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-12 13:59:06 +08:00
litianxiang
29b4f43a27 debug:推荐接口
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-12 13:34:56 +08:00
litianxiang
69dc20207d debug:推荐接口
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-12 13:03:58 +08:00
litianxiang
18979af604 debug:推荐接口返回redis值
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-12 13:01:26 +08:00
litianxiang
74406f9be4 推荐接口更新向量接口注册
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-12 11:59:01 +08:00
litianxiang
df99e3ac76 新增查看redis内容接口
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-12 11:51:37 +08:00
litianxiang
19346c2eb7 Merge remote-tracking branch 'origin/develop' into dev-ltx
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-12 09:51:52 +08:00
litianxiang
2af9cbfe78 fix:推荐接口 2026-01-12 09:49:07 +08:00
zcr
fe12b5697d fix: design 镜像默认值修改,旋转方向和前端保持一致
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-09 17:40:49 +08:00
zcr
c04d4877b0 fix: design 回参新增镜像旋转参数
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-09 17:12:53 +08:00
zcr
91016e6cae fix: design 回参新增镜像旋转参数
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-09 17:08:16 +08:00
zcr
0f4bb260ad fix: design 回参新增镜像旋转参数
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-09 17:06:39 +08:00
zcr
c792106f02 fix: design 回参新增镜像旋转参数
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-09 15:42:42 +08:00
zcr
deac5a4cab fix: design item sketch旋转参数为none
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-09 12:31:34 +08:00
zcr
15682036b3 feat : 新增seg anything 接口 ,接口文档补充
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-08 17:39:27 +08:00
zcr
9ba3a0ca49 feat : 新增seg anything 接口
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-08 17:33:54 +08:00
zcr
f6963070fb feat : 支持上下左右同时镜像
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-08 13:47:44 +08:00
zcr
12f5ca3ca3 feat : design 示例说明
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-08 10:44:02 +08:00
zcr
19110f51bf feat : design 示例说明
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-08 10:29:31 +08:00
2b7e4013ee 更新 .gitea/workflows/develop_build_manual.yaml
All checks were successful
定时 AiDA python develop 分支构建部署 / scheduled_deploy (push) Successful in 2m14s
2026-01-08 10:23:45 +08:00
zcr
e04636ce21 feat : design overall print 新增平铺间距和旋转角度
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-07 17:03:02 +08:00
zcr
2a50e7040e feat : design overall print 新增平铺间距和旋转角度
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-07 16:22:19 +08:00
zcr
a6f3bda9f7 feat : design 单品新增 镜像旋转功能
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-06 12:21:10 +08:00
zcr
c18f45e549 feat : design 单品新增 镜像旋转功能
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2026-01-06 12:00:58 +08:00
zcr
4951fab71a 代码整理
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2025-12-30 17:49:22 +08:00
zcr
aa57478852 新推荐接口first commit
All checks were successful
git commit AiDA python develop 分支构建部署 / scheduled_deploy (push) Has been skipped
2025-12-30 17:35:32 +08:00
5b2bb3ce7c 添加 .gitea/workflows/ltx_develop_build_manual.yaml
All checks were successful
定时 AiDA python develop 分支构建部署 / scheduled_deploy (push) Successful in 2m15s
2025-12-29 11:01:49 +08:00
6739a92d28 2025.12.19 生产部署
All checks were successful
定时 AiDA python prod 分支构建部署 / scheduled_deploy (push) Successful in 22s
定时 AiDA python develop 分支构建部署 / scheduled_deploy (push) Successful in 28s
2025-12-19 17:47:54 +08:00
f23b99d326 更新 .gitea/workflows/prod_build_scheduled.yaml 2025-12-19 17:46:35 +08:00
10d41cd32f 新增生产部署actions文件 2025-12-19 17:46:03 +08:00
zcr
bb7b85bfb8 Merge branch 'develop'
All checks were successful
定时 AiDA python develop 分支构建部署 / scheduled_deploy (push) Successful in 15s
# Conflicts:
#	.gitea/workflows/develop_build_scheduled.yaml
#	app/service/design_fast/design_generate.py
2025-12-16 14:37:05 +08:00
6ecb6be59c 更新 .gitea/workflows/develop_build_scheduled.yaml
All checks were successful
定时 AiDA python develop 分支构建部署 / scheduled_deploy (push) Successful in 17s
2025-11-28 17:42:22 +08:00
64285cd5f3 更新 .gitea/workflows/develop_build_scheduled.yaml 2025-11-28 17:41:22 +08:00
fe6a5fb029 更新 .gitea/workflows/develop_build_scheduled.yaml
All checks were successful
定时 AiDA python develop 分支构建部署 / scheduled_deploy (push) Successful in 13s
2025-11-28 17:31:29 +08:00
5217847d49 上传文件至「.gitea/workflows」
All checks were successful
定时 AiDA python develop 分支构建部署 / scheduled_deploy (push) Successful in 14s
2025-11-28 17:23:07 +08:00
0a9fc51310 更新 .gitea/workflows/develop_build_commit.yaml 2025-11-28 17:19:47 +08:00
cf052f9632 上传文件至「.gitea/workflows」 2025-11-28 17:18:52 +08:00
19a8ea9a93 更新 .gitea/workflows/develop_build_commit.yaml 2025-11-28 17:11:35 +08:00
09ff2f1ab7 更新 .gitea/workflows/develop_build_commit.yaml 2025-11-28 17:10:04 +08:00
109a23197a 上传文件至「.gitea/workflows」 2025-11-28 17:02:38 +08:00
zhh
2135a180be feat(新功能):
fix(修复bug):  删除java端debug callback api url
docs(文档变更):
refactor(重构):
test(增加测试):
2025-11-21 23:17:53 +08:00
zhh
09032c0564 Merge branch 'develop'
# Conflicts:
#	app/service/design_fast/design_generate.py
2025-11-21 23:01:34 +08:00
zhh
167faa10c8 feat(新功能): fix(修复bug): 取消design-v2 java端测试接口(重构): test(增加测试): 2025-09-27 00:02:23 +08:00
zhh
0a048bf37f Merge branch 'develop'
# Conflicts:
#	app/service/design_fast/design_generate.py
2025-09-26 23:31:42 +08:00
zhh
05045dda76 feat(新功能): fix(修复bug): : refactor(重构): test(增加测试): 徐佩design测试 2025-09-23 11:38:33 +08:00
zhh
30f9a99df2 Merge branch 'develop'
# Conflicts:
#	app/service/design_fast/pipeline/split.py
2025-09-22 17:56:18 +08:00
zhh
3932b8359a feat(新功能):
fix(修复bug):
docs(文档变更):
refactor(重构):
test(增加测试):  mask 使用原尺寸测试
2025-09-17 16:43:26 +08:00
54 changed files with 1551 additions and 1152 deletions

View File

@@ -1,2 +1,6 @@
seg_cache seg_cache
test test
.venv
__pycache__/
*.pyc
.git/

View File

@@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
REMOTE_DEPLOY_PATH: /workspace/Trinity/Fastapi_AiDA_Trinity_Dev REMOTE_DEPLOY_PATH: /workspace/AiDA_Workspace/Python_Server_Workspace/Dev
steps: steps:
- name: 1.检出代码 - name: 1.检出代码
@@ -35,6 +35,4 @@ jobs:
cd ${{ env.REMOTE_DEPLOY_PATH }} cd ${{ env.REMOTE_DEPLOY_PATH }}
docker-compose down 2>&1 docker-compose down 2>&1
docker-compose up -d --build --remove-orphans 2>&1 docker-compose up -d 2>&1
docker image prune -f 2>&1

View File

@@ -1,15 +1,15 @@
name: 定时 AiDA python develop 分支构建部署 name: 定时 AiDA python develop 分支构建部署
on: on:
# 使用 schedule 触发器,遵循标准的 Cron 格式 (分钟 小时-8 日期 月份 星期) # 使用 schedule 触发器,遵循标准的 Cron 格式 (分钟 小时-8 日期 月份 星期)
schedule: # schedule:
- cron: '30 9 * * *' # - cron: '30 9 * * *'
jobs: jobs:
scheduled_deploy: scheduled_deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
REMOTE_DEPLOY_PATH: /workspace/Trinity/Fastapi_AiDA_Trinity_Dev REMOTE_DEPLOY_PATH: /workspace/AiDA_Workspace/Python_Server_Workspace/Dev
steps: steps:
- name: 1.检出代码 - name: 1.检出代码

View File

@@ -1,23 +1,19 @@
name: git commit AiDA python develop 分支构建部署 name: 手动 AiDA python develop 分支构建部署
on: on:
workflow_dispatch: workflow_dispatch:
push:
branches:
- develop
jobs: jobs:
scheduled_deploy: scheduled_deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: "contains(github.event.head_commit.message, '[run build]')"
env: env:
REMOTE_DEPLOY_PATH: /workspace/Trinity/Fastapi_AiDA_Trinity_Dev REMOTE_DEPLOY_PATH: /workspace/AiDA_Workspace/Python_Server_Workspace/Dev
steps: steps:
- name: 1.检出代码 - name: 1.检出代码
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
ref: 'develop' ref: 'dev-ltx'
- name: 2.复制文件到服务器 - name: 2.复制文件到服务器
uses: appleboy/scp-action@v0.1.7 uses: appleboy/scp-action@v0.1.7
@@ -28,7 +24,7 @@ jobs:
source: "." source: "."
target: ${{ env.REMOTE_DEPLOY_PATH }} target: ${{ env.REMOTE_DEPLOY_PATH }}
- name: Restart Docker containers - name: 3.重启docker-compose
uses: appleboy/ssh-action@v0.1.10 uses: appleboy/ssh-action@v0.1.10
with: with:
host: ${{ secrets.SERVER_HOST }} host: ${{ secrets.SERVER_HOST }}

View File

@@ -0,0 +1,40 @@
name: 定时 AiDA python prod 分支构建部署
on:
workflow_dispatch:
jobs:
scheduled_deploy:
runs-on: ubuntu-latest
env:
REMOTE_DEPLOY_PATH: /workspace/AiDA_Workspace/Python_Server_Workspace/AiDA_Prod
steps:
- name: 1.检出代码
uses: actions/checkout@v4
with:
ref: 'master'
- name: 2.复制文件到服务器
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
password: ${{ secrets.SERVER_PASSWORD }}
source: "."
target: ${{ env.REMOTE_DEPLOY_PATH }}
- name: Restart Docker containers
uses: appleboy/ssh-action@v0.1.10
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
password: ${{ secrets.SERVER_PASSWORD }}
script: |
# 进入项目目录
cd ${{ env.REMOTE_DEPLOY_PATH }}
docker-compose down 2>&1
docker-compose up -d 2>&1
docker image prune -f 2>&1

View File

@@ -0,0 +1,42 @@
name: 定时 AiDA python prod 分支构建部署
on:
# 使用 schedule 触发器,遵循标准的 Cron 格式 (分钟 小时-8 日期 月份 星期)
schedule:
- cron: '07 13 23 1 *'
jobs:
scheduled_deploy:
runs-on: ubuntu-latest
env:
REMOTE_DEPLOY_PATH: /workspace/AiDA_Workspace/Python_Server_Workspace/Prod
steps:
- name: 1.检出代码
uses: actions/checkout@v4
with:
ref: 'master'
- name: 2.复制文件到服务器
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
password: ${{ secrets.SERVER_PASSWORD }}
source: "."
target: ${{ env.REMOTE_DEPLOY_PATH }}
- name: Restart Docker containers
uses: appleboy/ssh-action@v0.1.10
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
password: ${{ secrets.SERVER_PASSWORD }}
script: |
# 进入项目目录
cd ${{ env.REMOTE_DEPLOY_PATH }}
docker-compose down 2>&1
docker-compose up -d 2>&1
docker image prune -f 2>&1

View File

@@ -20,7 +20,6 @@
$ conda activate trinity_client_aida $ conda activate trinity_client_aida
$ pip install -r requirements.txt $ pip install -r requirements.txt
$ conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia -y $ conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia -y
$ pip install mmcv==1.4.2 -f https://download.openmmlab.com/mmcv/dist/cu117/torch1.13/index.html
1. 启动服务器 1. 启动服务器

View File

@@ -1,9 +1,11 @@
import json import json
import logging import logging
import requests
from fastapi import APIRouter, HTTPException, BackgroundTasks from fastapi import APIRouter, HTTPException, BackgroundTasks
from app.schemas.design import DesignModel, ModelProgressModel, DesignStreamModel from app.core.config import settings
from app.schemas.design import DesignModel, ModelProgressModel, DesignStreamModel, SAMRequestModel
from app.schemas.response_template import ResponseModel from app.schemas.response_template import ResponseModel
from app.service.design_fast.design_generate import design_generate, design_generate_v2 from app.service.design_fast.design_generate import design_generate, design_generate_v2
from app.service.design_fast.model_process_service import model_transpose from app.service.design_fast.model_process_service import model_transpose
@@ -15,177 +17,141 @@ logger = logging.getLogger()
@router.post("/design") @router.post("/design")
def design(request_data: DesignModel): def design(request_data: DesignModel):
""" """
objects.items.transparent: - **objects.items.transparent**:
"transparent":{ ```json
"mask_url":"test/transparent_test/transparent_mask.png", "transparent":{
"scale":0.1 "mask_url":"test/transparent_test/transparent_mask.png",
}, "scale":0.1
mask_url 为空"" -> 单件衣服透明 },
mask_url 非空"mask_url" -> 区域透明 ```
- **mask_url** 为空"" -> 单件衣服透明
- **mask_url** 非空"mask_url" -> 区域透明
- **transpose** 镜像模式 ,:"top_bottom""left_right"
- **rotate** 45,
创建一个具有以下参数的请求体: - ** design 参数变更:
示例参数: design detail 请求参数中 basic -> preview_submit 替换为design_type 可选参数 default ,merge (移除preview和submit)
{ design_type 参数说明:
"objects": [ defuault模式下 请求参数不变
{ merge模式下 items -> 每个item需要新增 merge_image_path , merge_image_path为前端处理 print color等操作后的单件结果图
"basic": {
"body_point_test": { **
"waistband_right": [
203, - 创建一个具有以下参数的请求体:
249 示例参数:
], ```json
"hand_point_right": [ {
229, "objects": [
343 {
], "basic": {
"waistband_left": [ "body_point_test": {
119, "waistband_right": [
248 203,
], 249
"hand_point_left": [ ],
97, "hand_point_right": [
343 229,
], 343
"shoulder_left": [ ],
108, "waistband_left": [
107 119,
], 248
"shoulder_right": [ ],
212, "hand_point_left": [
107 97,
] 343
}, ],
"layer_order": true, "shoulder_left": [
"preview_submit": "submit", 108,
"scale_bag": 0.7, 107
"scale_earrings": 0.16, ],
"self_template": true, "shoulder_right": [
"single_overall": "overall", 212,
"switch_category": "" 107
]
}, },
"items": [ "layer_order": true,
{ "design_type": "preview",
"businessId": 2377945, "scale_bag": 0.7,
"color": "209 196 171", "scale_earrings": 0.16,
"image_id": 189410, "self_template": true,
"offset": [ "single_overall": "overall",
0, "switch_category": ""
0 },
], "items": [
"path": "aida-collection-element/89/Sketchboard/53d38bd5-f77b-4034-ada2-45f1e2ebe00c.png", {
"print": { "businessId": 2115382,
"element": { "color": "",
"element_angle_list": [], "image_id": 61686,
"element_path_list": [], "offset": [
"element_scale_list": [], 0,
"location": [] 0
}, ],
"overall": { "path": "aida-sys-image/images/female/dress/0628000564.jpg",
"location": [], "transpose": [
"print_angle_list": [], 1,
"print_path_list": [], 1
"print_scale_list": [] ],
}, "rotate": 45,
"single": { "print": {
"location": [], "element": {
"print_angle_list": [], "element_angle_list": [],
"print_path_list": [], "element_path_list": [],
"print_scale_list": [] "element_scale_list": [],
} "location": []
}, },
"priority": 12, "overall": {
"resize_scale": [ "location": [
1.0, [
1.0 53.0,
], 118.5
"seg_mask_url": "aida-clothing/mask/mask_8e96ddb0-e466-11f0-8de2-0242ac130002.png", ]
"type": "Outwear" ],
}, "print_angle_list": [
{ 0.0
"businessId": 2377946, ],
"color": "122 152 139", "print_path_list": [
"image_id": 81868, "aida-users/89/print/02d57aa8-f342-4e1d-b02c-b278f94dcfe6-3-89.png"
"offset": [ ],
0, "print_scale_list": [
0 [
], 0.5,
"path": "aida-sys-image/images/female/blouse/0825001443.jpg", 0.5
"print": { ]
"element": { ],
"element_angle_list": [], "gap": [
"element_path_list": [], [
"element_scale_list": [], 10,
"location": [] 10
}, ]
"overall": { ]
"location": [],
"print_angle_list": [],
"print_path_list": [],
"print_scale_list": []
},
"single": {
"location": [],
"print_angle_list": [],
"print_path_list": [],
"print_scale_list": []
}
}, },
"priority": 11, "single": {
"resize_scale": [ "location": [],
1.0, "print_angle_list": [],
1.0 "print_path_list": [],
], "print_scale_list": []
"seg_mask_url": "aida-clothing/mask/mask_8f0fab78-e466-11f0-8de2-0242ac130002.png", }
"type": "Blouse"
}, },
{ "priority": 10,
"businessId": 2377947, "resize_scale": [
"color": "111 78 63", 1.0,
"gradient": "aida-gradient/517c3a4d-aed7-4423-aa99-7b60d3577df1.png", 1.0
"image_id": 116494, ],
"offset": [ "seg_mask_url": "aida-clothing/mask/mask_9698b428-eb93-11f0-9327-0242c0a80003.png",
0, "type": "Dress"
0 },
], {
"path": "aida-sys-image/images/female/skirt/0825000219.jpg", "body_path": "aida-sys-image/models/female/2e4815b9-1191-419d-94ed-5771239ca4a5.png",
"print": { "image_id": 67277,
"element": { "type": "Body"
"element_angle_list": [], }
"element_path_list": [], ]
"element_scale_list": [], }
"location": [] ],
}, "process_id": "89"
"overall": { }
"location": [], ```
"print_angle_list": [],
"print_path_list": [],
"print_scale_list": []
},
"single": {
"location": [],
"print_angle_list": [],
"print_path_list": [],
"print_scale_list": []
}
},
"priority": 10,
"resize_scale": [
1.0,
1.0
],
"seg_mask_url": "aida-clothing/mask/mask_8f6191fe-e466-11f0-8de2-0242ac130002.png",
"type": "Skirt"
},
{
"body_path": "aida-sys-image/models/female/2e4815b9-1191-419d-94ed-5771239ca4a5.png",
"image_id": 67277,
"type": "Body"
}
]
}
],
"process_id": "89"
}
""" """
# logger.info(f"design request item is : @@@@@@:{json.dumps(request_data.dict(),indent=4)}") # logger.info(f"design request item is : @@@@@@:{json.dumps(request_data.dict(),indent=4)}")
# data = generate(request_data=request_data) # data = generate(request_data=request_data)
@@ -421,6 +387,55 @@ async def design_v2(request_data: DesignStreamModel, background_tasks: Backgroun
return ResponseModel() return ResponseModel()
@router.post("/seg_anything")
async def seg_anything(request_data: SAMRequestModel):
"""
**Segment Anything 交互式分割接口**
通过传入图片路径和点击的点坐标,返回分割后的掩码数据。
### 参数说明:
- **bucket**: minio bucket name
- **object_name**: minio object name
- **image_path**: 图片在服务器或云端的相对路径。
- **type**: 推理类型
- **box**: 框选矩形点位信息
- **points**: 交互点的坐标列表。每个点为 [x, y] 像素格式。
- **labels**: 坐标点的属性标签,必须与 points 长度一致:
- 1: **前景点** (代表想要分割出的区域)
- 0: **背景点** (代表想要排除的区域)
### 请求体示例:
```json
point
{
"bucket": "test",
"object_name": "7068-400a-ac94-c01647fa5f6f.png",
"image_path": "aida-users/89/sketch/4e8fe37d-7068-400a-ac94-c01647fa5f6f.png",
"type":"point",
"points": [[310, 403], [493, 375], [261, 266], [404, 484]],
"labels": [1, 1, 0, 1]
}
box
{
"bucket": "test",
"object_name": "7068-400a-ac94-c01647fa5f6f.png",
"image_path": "aida-users/89/sketch/4e8fe37d-7068-400a-ac94-c01647fa5f6f.png",
"type":"box",
"box": [350, 286, 544, 520]
}
```
"""
try:
logger.info(f"seg_anything request item is : @@@@@@:{json.dumps(request_data.dict(), indent=4)}")
data = requests.post(f"http://{settings.B_4_X_4090_SERVICE_HOST}:10075/predict", json=request_data.dict())
logger.info(f"seg_anything response @@@@@@:{json.dumps(json.loads(data.content), indent=4)}")
return ResponseModel(data=json.loads(data.content))
except Exception as e:
logger.warning(f"seg_anything Run Exception @@@@@@:{e}")
# @router.post('/get_progress') # @router.post('/get_progress')
# def get_progress(request_data: DesignProgressModel): # def get_progress(request_data: DesignProgressModel):
# """ # """

View File

@@ -1,9 +1,12 @@
import json import json
import logging import logging
import httpx
import requests
from fastapi import APIRouter, BackgroundTasks, HTTPException from fastapi import APIRouter, BackgroundTasks, HTTPException
from app.schemas.generate_image import GenerateImageModel, GenerateProductImageModel, GenerateSingleLogoImageModel, GenerateRelightImageModel, GenerateMultiViewModel, BatchGenerateProductImageModel, BatchGenerateRelightImageModel, AgentTollGenerateImageModel from app.core.config import settings
from app.schemas.generate_image import GenerateImageModel, GenerateProductImageModel, GenerateSingleLogoImageModel, GenerateRelightImageModel, GenerateMultiViewModel, BatchGenerateProductImageModel, BatchGenerateRelightImageModel, AgentTollGenerateImageModel, Flux2ToProductImgModel, GenerateSloganImageModel, GenerateImageFlux2KleinModel
from app.schemas.pose_transform import BatchPoseTransformModel from app.schemas.pose_transform import BatchPoseTransformModel
from app.schemas.response_template import ResponseModel from app.schemas.response_template import ResponseModel
from app.service.generate_batch_image.service import start_product_batch_generate, start_relight_batch_generate, start_pose_transform_batch_generate from app.service.generate_batch_image.service import start_product_batch_generate, start_relight_batch_generate, start_pose_transform_batch_generate
@@ -20,6 +23,61 @@ logger = logging.getLogger()
'''generate image''' '''generate image'''
# flux2 klein
@router.post("/generate_image_flux2_klein")
async def generate_image_flux2_klein(request_item: GenerateImageFlux2KleinModel):
"""
创建一个具有以下参数的请求体:
- **bucket_name**: OSS桶名 (必填)
- **object_name**: OSS对象名文件路径(必填)
- **width**: 图片宽度默认1024像素 (非必填,1024)
- **height**: 图片高度默认1024像素 (非必填,默认1024)
- **prompt**: 文本提示词,用于模型推理等场景 (非必填,默认"")
- **steps**: 推理步数,控制模型生成过程的迭代次数 (非必填,默认4)
- **guidance**: 引导系数,调节提示词对生成结果的影响程度 (非必填,默认 4.0 )
### 示例参数:
```
{
"bucket_name": "aida-users",
"object_name": "89/moodboard/5fdc698c-cb9b-4b36-afa9ce4-1-89.png",
"prompt": "a single item of sketch of dress, 4k, white background"
}
```
### 输出示例:
```
{
"code": 200,
"msg": "OK!",
"data": {
"output_path": "aida-users/89/moodboard/5fdc698c-cb9b-4b36-afa9ce4-1-89.png"
}
}
```
"""
try:
logger.info(f"generate_image_flux2_gen_img request: {json.dumps(request_item.model_dump(), indent=4)}")
async with httpx.AsyncClient(timeout=120) as client:
resp = await client.post(
f"http://{settings.FLUX2_GEN_IMG_MODEL_URL}/predict",
json=request_item.model_dump(),
)
if resp.status_code == 200:
result = resp.json()
logger.info(f"flux2_gen_img response: {json.dumps(result, indent=4)}")
return ResponseModel(data=result)
else:
error = resp.json()
logger.info(f"flux2_gen_img response: {json.dumps(error, indent=4)}")
return ResponseModel(data=error, msg="ERROR!", code=500)
except Exception as e:
logger.warning(f"generate_image_flux2_gen_img Run Exception @@@@@@:{e}")
raise HTTPException(status_code=404, detail=str(e))
# sdxl
@router.post("/generate_image") @router.post("/generate_image")
def generate_image(request_item: GenerateImageModel, background_tasks: BackgroundTasks): def generate_image(request_item: GenerateImageModel, background_tasks: BackgroundTasks):
""" """
@@ -154,6 +212,62 @@ def generate_single_logo_image(tasks_id: str):
return ResponseModel(data=data['data']) return ResponseModel(data=data['data'])
"""slogan """
@router.post("/generate_slogan")
async def generate_slogan(request_data: GenerateSloganImageModel):
"""
### 请求体示例:
```json
{
"num_point": 16,
"image_url": "aida-slogan/6886785f-0aac-4052-b6fd-7ae20a841d8d.png",
"prompt": "123",
"tasks_id": "string-89"
}
```
"""
try:
logger.info(f"generate_slogan request item is : @@@@@@:{json.dumps(request_data.dict(), indent=4)}")
data = requests.post(f"http://{settings.A6000_SERVICE_HOST}:10020/api/slogan", json=request_data.dict())
logger.info(f"generate_slogan response @@@@@@:{json.dumps(json.loads(data.content), indent=4)}")
return ResponseModel(data=json.loads(data.content))
except Exception as e:
logger.warning(f"generate_slogan Run Exception @@@@@@:{e}")
"""product image flux2.0"""
# @router.post("/img_to_product")
# async def img_to_product(request_data: Flux2ToProductImgModel):
# """
# 创建一个具有以下参数的请求体:
# - **tasks_id**: 任务id 用于取消生成任务和获取生成结果
# - **prompt**: 想要生成图片的描述词
# - **image_path**: 被生成图片的S3或minio url地址
# - **infer_step**: 推理步数
#
# ### 请求体示例:
# ```json
# point
# {
# "prompt": "Create realistic studio photo with real people model standing and wearing this garment, in white studio, Keep original model if present, or generate appropriate model, Standing pose, facing camera.",
# "image_path":"aida-results/result_38151e0a-f83b-11f0-89f6-0242ac130002.png",
# "infer_step":4,
# "tasks_id":"123456-123"
# }
# ```
# """
# try:
# logger.info(f"img_to_product request item is : @@@@@@:{json.dumps(request_data.dict(), indent=4)}")
# data = requests.post(f"http://{settings.A6000_SERVICE_HOST}:10090/api/v1/to_product", json=request_data.dict())
# logger.info(f"img_to_product response @@@@@@:{json.dumps(json.loads(data.content), indent=4)}")
# return ResponseModel(data=json.loads(data.content))
# except Exception as e:
# logger.warning(f"img_to_product Run Exception @@@@@@:{e}")
'''product image''' '''product image'''
@@ -178,7 +292,7 @@ def generate_product_image(request_item: GenerateProductImageModel, background_t
} }
""" """
try: try:
logger.info(f"generate_product_image request item is : @@@@@@:{json.dumps(request_item.dict(),indent=4)}") logger.info(f"generate_product_image request item is : @@@@@@:{json.dumps(request_item.dict(), indent=4)}")
service = GenerateProductImage(request_item) service = GenerateProductImage(request_item)
background_tasks.add_task(service.get_result) background_tasks.add_task(service.get_result)
except Exception as e: except Exception as e:

View File

@@ -137,10 +137,13 @@ router = APIRouter()
# logger.error(f"推荐失败: {str(e)}", exc_info=True) # logger.error(f"推荐失败: {str(e)}", exc_info=True)
# raise HTTPException(status_code=500, detail=str(e)) # raise HTTPException(status_code=500, detail=str(e))
# @router.on_event("startup") @router.on_event("startup")
async def startup_event(): async def startup_event():
"""启动时初始化增量监听任务""" """启动时初始化增量监听任务"""
try: try:
# 屏蔽 apscheduler 的 INFO 日志
logging.getLogger("apscheduler").setLevel(logging.WARNING)
# 确保 Milvus 集合已创建(若已存在则直接返回) # 确保 Milvus 集合已创建(若已存在则直接返回)
try: try:
create_collection() create_collection()
@@ -172,4 +175,32 @@ async def recommend(
return [path] return [path]
except Exception as e: except Exception as e:
logger.error("新版推荐接口失败 [user=%s, category=%s]: %s", user_id, category, e, exc_info=True) logger.error("新版推荐接口失败 [user=%s, category=%s]: %s", user_id, category, e, exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.get("/redis/user_pref")
async def get_all_user_preferences():
"""
获取所有以 user_pref 为前缀的 Redis key 信息
"""
try:
from app.service.utils.redis_utils import Redis
from app.service.recommendation_system.config import REDIS_KEY_USER_PREF_PREFIX
# 扫描所有匹配 user_pref:* 的 key
pattern = f"{REDIS_KEY_USER_PREF_PREFIX}:*"
keys = Redis.scan_keys(pattern)
# 直接返回所有 key 和原始 value
result = {}
for key in keys:
# 读取对应的值
value = Redis.read(key)
if value:
result[key] = value
return result
except Exception as e:
logger.error("获取用户偏好数据失败: %s", e, exc_info=True)
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))

View File

@@ -7,6 +7,7 @@ from app.api import api_design_pre_processing
from app.api import api_generate_image from app.api import api_generate_image
from app.api import api_mannequins_edit from app.api import api_mannequins_edit
from app.api import api_pose_transform from app.api import api_pose_transform
from app.api import api_precompute
from app.api import api_prompt_generation from app.api import api_prompt_generation
from app.api import api_recommendation from app.api import api_recommendation
from app.api import api_test from app.api import api_test
@@ -21,6 +22,7 @@ router.include_router(api_prompt_generation.router, tags=['prompt_generation'],
router.include_router(api_design_pre_processing.router, tags=['design_pre_processing'], prefix="/api") router.include_router(api_design_pre_processing.router, tags=['design_pre_processing'], prefix="/api")
router.include_router(api_brand_dna.router, tags=['api_brand_dna'], prefix="/api") router.include_router(api_brand_dna.router, tags=['api_brand_dna'], prefix="/api")
router.include_router(api_recommendation.router, tags=['api_recommendation'], prefix="/api") router.include_router(api_recommendation.router, tags=['api_recommendation'], prefix="/api")
router.include_router(api_precompute.router, tags=['api_precompute'], prefix="/api")
router.include_router(api_mannequins_edit.router, tags=['api_mannequins_edit'], prefix="/api") router.include_router(api_mannequins_edit.router, tags=['api_mannequins_edit'], prefix="/api")
router.include_router(api_pose_transform.router, tags=['api_pose_transform'], prefix="/api") router.include_router(api_pose_transform.router, tags=['api_pose_transform'], prefix="/api")
router.include_router(api_clothing_seg.router, tags=['api_clothing_seg'], prefix="/api") router.include_router(api_clothing_seg.router, tags=['api_clothing_seg'], prefix="/api")

View File

@@ -1,235 +0,0 @@
import os
import pika
from dotenv import load_dotenv
from pydantic import BaseSettings
BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))
load_dotenv(os.path.join(BASE_DIR, '.env'))
class Settings(BaseSettings):
PROJECT_NAME: str = 'FASTAPI BASE'
SECRET_KEY: str = ''
API_PREFIX: str = ''
BACKEND_CORS_ORIGINS: list[str] = ['*']
DATABASE_URL: str = ''
ACCESS_TOKEN_EXPIRE_SECONDS: int = 60 * 60 * 24 * 7 # Token expired after 7 days
SECURITY_ALGORITHM: str = 'HS256'
LOGGING_CONFIG_FILE: str = os.path.join(BASE_DIR, 'logging_env.py')
OSS = "minio"
DEBUG = False
if DEBUG:
LOGS_PATH = "logs/"
CATEGORY_PATH = "service/attribute/config/descriptor/category/category_dis.csv"
SEG_CACHE_PATH = "../seg_cache/"
POSE_TRANSFORM_VIDEO_PATH = "../pose_transform_video/"
RECOMMEND_PATH_PREFIX = "service/recommend/"
CHROMADB_PATH = "./chromadb/"
else:
LOGS_PATH = "app/logs/"
CATEGORY_PATH = "app/service/attribute/config/descriptor/category/category_dis.csv"
SEG_CACHE_PATH = "/seg_cache/"
POSE_TRANSFORM_VIDEO_PATH = "/pose_transform_video/"
RECOMMEND_PATH_PREFIX = "app/service/recommend/"
CHROMADB_PATH = "/chromadb/"
# RABBITMQ_ENV = "" # 生产环境
RABBITMQ_ENV = os.getenv("RABBITMQ_ENV", "-dev")
# RABBITMQ_ENV = "-local" # 本地测试环境
if RABBITMQ_ENV == "-dev":
JAVA_STREAM_API_URL = f"https://develop.api.aida.com.hk/api/third/party/receiveDesignResults"
elif RABBITMQ_ENV == "-prod":
JAVA_STREAM_API_URL = f"https://api.aida.com.hk/api/third/party/receiveDesignResults"
settings = Settings()
# minio 配置
MINIO_URL = "www.minio-api.aida.com.hk"
MINIO_ACCESS = 'vXKFLSJkYeEq2DrSZvkB'
MINIO_SECRET = 'uKTZT3x7C43WvPN9QTc99DiRkwddWZrG9Uh3JVlR'
MINIO_SECURE = True
# S3 配置
S3_ACCESS_KEY = "AKIAVD3OJIMF6UJFLSHZ"
S3_AWS_SECRET_ACCESS_KEY = "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"
S3_REGION_NAME = "ap-east-1"
# redis 配置
REDIS_HOST = "10.1.1.240"
REDIS_PORT = "6379"
REDIS_DB = "2"
# rabbitmq config
RABBITMQ_PARAMS = {
"host": "18.167.251.121",
"port": 5672,
"credentials": pika.credentials.PlainCredentials(username='rabbit', password='123456'),
"virtual_host": "/"
}
# milvus 配置
MILVUS_URL = "http://10.1.1.240:19530"
MILVUS_TOKEN = "root:Milvus"
MILVUS_ALIAS = "default"
MILVUS_TABLE_KEYPOINT = "keypoint_cache_2"
MILVUS_TABLE_SEG = "seg_cache"
# Mysql 配置
DB_HOST = '18.167.251.121' # 数据库主机地址
# DB_PORT = int( 33006)
DB_PORT = 33008 # 数据库端口
DB_USERNAME = 'aida_con_python' # 数据库用户名
DB_PASSWORD = '123456' # 数据库密码
DB_NAME = 'aida' # 数据库库名
# openai
os.environ['SERPAPI_API_KEY'] = "a793513017b0718db7966207c31703d280d12435c982f1e67bbcbffa52e7632c"
OPENAI_STREAM = True
BUFFER_THRESHOLD = 6 # must be even number
SINGLE_TOKEN_THRESHOLD = 200
TOKEN_THRESHOLD = 600
OPENAI_TEMPERATURE = 0
# OPENAI_API_KEY = "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"
OPENAI_API_KEY = "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"
OPENAI_MODEL = "gpt-3.5-turbo-0613"
OPENAI_MODEL_LIST = {"gpt-3.5-turbo-0613",
"gpt-3.5-turbo-16k-0613",
"gpt-4-0314",
"gpt-4-32k-0314",
"gpt-4-0613",
"gpt-4-32k-0613", }
# SR service config
SR_MODEL_NAME = "super_resolution"
SR_TRITON_URL = "10.1.1.240:10031"
SR_MINIO_BUCKET = "aida-users"
SR_RABBITMQ_QUEUES = f"SuperResolution{RABBITMQ_ENV}"
# GenerateImage service config
FAST_GI_MODEL_URL = '10.1.1.243:10011'
FAST_GI_MODEL_NAME = 'stable_diffusion_xl'
GI_MODEL_URL = '10.1.1.240:10061'
GI_MODEL_NAME = 'flux'
GMV_MODEL_URL = '10.1.1.243:10081'
GMV_MODEL_NAME = 'multi_view'
GMV_RABBITMQ_QUEUES = f"GenerateMultiView{RABBITMQ_ENV}"
GI_MINIO_BUCKET = "aida-users"
GI_RABBITMQ_QUEUES = f"GenerateImage{RABBITMQ_ENV}"
GI_SYS_IMAGE_URL = "aida-sys-image/generate_image/white_image.jpg"
# SLOGAN service config
SLOGAN_RABBITMQ_QUEUES = f"Slogan{RABBITMQ_ENV}"
# Generate Single Logo service config
GSL_MODEL_URL = '10.1.1.243:10041'
GSL_MINIO_BUCKET = "aida-users"
GSL_MODEL_NAME = 'stable_diffusion_xl_transparent'
GEN_SINGLE_LOGO_RABBITMQ_QUEUES = f"GenSingleLogo{RABBITMQ_ENV}"
# Generate Product service config
# GPI_RABBITMQ_QUEUES = os.getenv("GEN_PRODUCT_IMAGE_RABBITMQ_QUEUES", f"ToProductImage{RABBITMQ_ENV}")
# GPI_MODEL_NAME_OVERALL = 'sdxl_ensemble_all'
# GPI_MODEL_URL = '10.1.1.243:10051'
# Generate Product service config 旧版product img 模型
GPI_RABBITMQ_QUEUES = f"ToProductImage{RABBITMQ_ENV}"
BATCH_GPI_RABBITMQ_QUEUES = f"BatchToProductImage{RABBITMQ_ENV}"
GPI_MODEL_NAME_OVERALL = 'diffusion_ensemble_all'
GPI_MODEL_NAME_SINGLE = 'stable_diffusion_1_5_cnet'
GPI_MODEL_URL = '10.1.1.243:10051'
# Generate Single Logo service config
GRI_RABBITMQ_QUEUES = f"Relight{RABBITMQ_ENV}"
BATCH_GRI_RABBITMQ_QUEUES = f"BatchRelight{RABBITMQ_ENV}"
GRI_MODEL_NAME_OVERALL = 'diffusion_relight_ensemble'
GRI_MODEL_NAME_SINGLE = 'stable_diffusion_1_5_relight'
GRI_MODEL_URL = '10.1.1.240:10051'
# Pose Transform service config
PS_RABBITMQ_QUEUES = f"PoseTransform{RABBITMQ_ENV}"
BATCH_PS_RABBITMQ_QUEUES = f"BatchPoseTransform{RABBITMQ_ENV}"
PT_MODEL_URL = '10.1.1.243:10061'
# SEG service config
SEGMENTATION = {
"new_model_name": "seg_knet",
"name": "seg_ocrnet_hr18",
"input": "seg_input__0",
"output": "seg_output__0",
}
# ollama config
OLLAMA_URL = "http://10.1.1.240:11434/api/embeddings"
# design batch
BATCH_DESIGN_RABBITMQ_QUEUES = f"DesignBatch{RABBITMQ_ENV}"
# DESIGN config
DESIGN_MODEL_URL = '10.1.1.240:10000'
AIDA_CLOTHING = "aida-clothing"
KEYPOINT_RESULT_TABLE_FIELD_SET = ('neckline_left', 'neckline_right', 'shoulder_left', 'shoulder_right', 'armpit_left', 'armpit_right',
'cuff_left_in', 'cuff_left_out', 'cuff_right_in', 'cuff_right_out', 'waistband_left', 'waistband_right')
# DESIGN 预处理
IF_DEBUG_SHOW = False
# 优先级
PRIORITY_DICT = {
'earring_front': 99,
'bag_front': 98,
'hairstyle_front': 97,
'outwear_front': 20,
'tops_front': 19,
'dress_front': 18,
'blouse_front': 17,
'skirt_front': 16,
'trousers_front': 15,
'bottoms_front': 14,
'shoes_right': 1,
'shoes_left': 1,
'body': 0,
'bottoms_back': -14,
'trousers_back': -15,
'skirt_back': -16,
'blouse_back': -17,
'dress_back': -18,
'tops_back': -19,
'outwear_back': -20,
'hairstyle_back': -97,
'bag_back': -98,
'earring_back': -99,
}
QWEN_API_KEY = "sk-f31c29e61ac2498ba5e307aaa6dc10e0"
DB_CONFIG = {
"host": "18.167.251.121",
"port": 3306,
"user": "root",
"password": "QWa998345",
"database": "aida",
"charset": "utf8mb4"
}
TABLE_CATEGORIES = {
"female_dress": "female/dress",
"female_outwear": "female/outwear",
"female_trousers": "female/trousers",
"female_skirt": "female/skirt",
"female_blouse": "female/blouse",
"male_tops": "male/tops",
"male_bottoms": "male/bottoms",
"male_outwear": "male/outwear"
}
# --- ComfyUI 配置信息 ---
COMFYUI_SERVER_ADDRESS = "10.1.2.227:8080" # 替换为您的 ComfyUI 服务器地址

View File

@@ -36,7 +36,7 @@ class Settings(BaseSettings):
# --- mysql 配置信息 --- # --- mysql 配置信息 ---
MYSQL_HOST: str = Field(default='', description="") MYSQL_HOST: str = Field(default='', description="")
MYSQL_PORT: str = Field(default='', description="") MYSQL_PORT: int = Field(default=3306, description="")
MYSQL_USER: str = Field(default='', description="") MYSQL_USER: str = Field(default='', description="")
MYSQL_PASSWORD: str = Field(default='', description="") MYSQL_PASSWORD: str = Field(default='', description="")
MYSQL_DB: str = Field(default='', description="") MYSQL_DB: str = Field(default='', description="")
@@ -64,11 +64,19 @@ class Settings(BaseSettings):
# --- Design Callback Java 接口 --- # --- Design Callback Java 接口 ---
JAVA_STREAM_API_URL: str = Field(default='', description="") JAVA_STREAM_API_URL: str = Field(default='', description="")
# --- flux2 klein model url ---
FLUX2_GEN_IMG_MODEL_URL: str = Field(default='', description="")
# --- 服务器IP ---
A6000_SERVICE_HOST: str = Field(default='', description="")
B_4_X_4090_SERVICE_HOST: str = Field(default='', description="")
# --- 其他配置信息 以下均为Docker容器内配置--- # --- 其他配置信息 以下均为Docker容器内配置---
LOGS_PATH: str = Field(default="/logs/", description="") LOGS_PATH: str = Field(default="/logs/", description="")
CATEGORY_PATH: str = Field(default="/app/service/attribute/config/descriptor/category/category_dis.csv", description="") CATEGORY_PATH: str = Field(default="/app/service/attribute/config/descriptor/category/category_dis.csv", description="")
SEG_CACHE_PATH: str = Field(default="/seg_cache/", description="") SEG_CACHE_PATH: str = Field(default="/seg_cache/", description="")
RECOMMEND_PATH_PREFIX: str = Field(default="/app/service/recommend/", description="") RECOMMEND_PATH_PREFIX: str = Field(default="/app/service/recommend/", description="")
SERVE_PORT: int = Field(default=2010, description="")
settings = Settings() settings = Settings()
@@ -117,39 +125,41 @@ KEYPOINT_RESULT_TABLE_FIELD_SET = ('neckline_left', 'neckline_right', 'shoulder_
MILVUS_TABLE_KEYPOINT = "keypoint_cache_2" MILVUS_TABLE_KEYPOINT = "keypoint_cache_2"
# ollama 地址 # ollama 地址
OLLAMA_URL = "http://10.1.1.240:11434/api/embeddings" OLLAMA_URL = f"http://{settings.A6000_SERVICE_HOST}:11434/api/embeddings"
"""Triton Server Config""" """Triton Server Config"""
# Design # Design
DESIGN_MODEL_URL = '10.1.1.240:10000' DESIGN_MODEL_URL = f'{settings.A6000_SERVICE_HOST}:10000'
DESIGN_MODEL_NAME = 'seg_knet' DESIGN_MODEL_NAME = 'seg_knet'
# Seg Product
SEG_PRODUCT_MODEL_URL = f'{settings.B_4_X_4090_SERVICE_HOST}:30000'
# Generate Image # Generate Image
GI_MODEL_URL = '10.1.1.240:10061' GI_MODEL_URL = f'{settings.A6000_SERVICE_HOST}:10061'
GI_MODEL_NAME = 'flux' GI_MODEL_NAME = 'flux'
# Generate Single Logo # Generate Single Logo
GSL_MODEL_URL = '10.1.1.243:10041' GSL_MODEL_URL = f'{settings.B_4_X_4090_SERVICE_HOST}:10041'
GSL_MODEL_NAME = 'stable_diffusion_xl_transparent' GSL_MODEL_NAME = 'stable_diffusion_xl_transparent'
# Generate Product (整套和单品) # Generate Product (整套和单品)
GPI_MODEL_URL = '10.1.1.243:10051' GPI_MODEL_URL = f'{settings.B_4_X_4090_SERVICE_HOST}:10051'
GPI_MODEL_NAME_OVERALL = 'diffusion_ensemble_all' GPI_MODEL_NAME_OVERALL = 'diffusion_ensemble_all'
GPI_MODEL_NAME_SINGLE = 'stable_diffusion_1_5_cnet' GPI_MODEL_NAME_SINGLE = 'stable_diffusion_1_5_cnet'
# 以下停用中...************* # 以下停用中...*************
# 多视角生成 # 多视角生成
GMV_MODEL_URL = '10.1.1.243:10081' GMV_MODEL_URL = f'{settings.B_4_X_4090_SERVICE_HOST}:10081'
GMV_MODEL_NAME = 'multi_view' GMV_MODEL_NAME = 'multi_view'
# 超分 # 超分
SR_MODEL_NAME = "super_resolution" SR_MODEL_NAME = "super_resolution"
SR_TRITON_URL = "10.1.1.240:10031" SR_TRITON_URL = f"{settings.A6000_SERVICE_HOST}:10031"
# 打光 # 打光
GRI_MODEL_URL = '10.1.1.240:10051' GRI_MODEL_URL = f'{settings.A6000_SERVICE_HOST}:10051'
GRI_MODEL_NAME_OVERALL = 'diffusion_relight_ensemble' GRI_MODEL_NAME_OVERALL = 'diffusion_relight_ensemble'
GRI_MODEL_NAME_SINGLE = 'stable_diffusion_1_5_relight' GRI_MODEL_NAME_SINGLE = 'stable_diffusion_1_5_relight'
# agent 图片生成 # agent 图片生成
FAST_GI_MODEL_URL = '10.1.1.243:10011' FAST_GI_MODEL_URL = f'{settings.B_4_X_4090_SERVICE_HOST}:10011'
FAST_GI_MODEL_NAME = 'stable_diffusion_xl' FAST_GI_MODEL_NAME = 'stable_diffusion_xl'
# 图转视频 triton版 # 图转视频 triton版
PT_MODEL_URL = '10.1.1.243:10061' PT_MODEL_URL = f'{settings.B_4_X_4090_SERVICE_HOST}:10061'
# ************* # *************

View File

@@ -16,7 +16,7 @@ from fastapi.responses import JSONResponse
from app.api.api_route import router from app.api.api_route import router
from app.core.config import settings from app.core.config import settings
from app.core.record_api_count import count_api_calls # from app.core.record_api_count import count_api_calls
from app.schemas.response_template import ResponseModel from app.schemas.response_template import ResponseModel
from logging_env import LOGGER_CONFIG_DICT from logging_env import LOGGER_CONFIG_DICT
from dotenv import load_dotenv from dotenv import load_dotenv
@@ -48,7 +48,7 @@ def get_application() -> FastAPI:
allow_methods=["*"], allow_methods=["*"],
allow_headers=["*"], allow_headers=["*"],
) )
application.middleware("http")(count_api_calls) # application.middleware("http")(count_api_calls)
application.include_router(router=router) application.include_router(router=router)
return application return application

View File

@@ -1,4 +1,16 @@
from pydantic import BaseModel from typing import List, Optional
from pydantic import BaseModel, Field
class SAMRequestModel(BaseModel):
bucket: str = Field(..., description="minio bucket name ")
object_name: str = Field(..., description="minio object name ")
image_path: str = Field(..., description="图片路径,必填字段")
type: str = Field(..., description="推理类型,必填字段")
points: Optional[List[List[float]]] | None = None
labels: Optional[List[int]] | None = None
box: Optional[List[int]] | None = None
class DesignModel(BaseModel): class DesignModel(BaseModel):

View File

@@ -1,6 +1,6 @@
from typing import List from typing import List, Optional
from pydantic import BaseModel from pydantic import BaseModel, Field
class GenerateMultiViewModel(BaseModel): class GenerateMultiViewModel(BaseModel):
@@ -8,6 +8,17 @@ class GenerateMultiViewModel(BaseModel):
image_url: str image_url: str
class GenerateImageFlux2KleinModel(BaseModel):
bucket_name: str = Field(..., description="OSS桶名不传则为None")
object_name: str = Field(..., description="OSS对象名文件路径不传则为None")
# input_image_paths: Optional[List[str]] = Field(default=[], description="输入图片路径列表")
width: Optional[int] = Field(default=1024, description="图片宽度默认512像素")
height: Optional[int] = Field(default=1024, description="图片高度默认512像素")
prompt: Optional[str] = Field(default="", description="文本提示词,用于模型推理等场景")
steps: Optional[int] = Field(default=4, description="推理步数,控制模型生成过程的迭代次数")
guidance: Optional[float] = Field(default=4.0, description="引导系数,调节提示词对生成结果的影响程度")
class GenerateImageModel(BaseModel): class GenerateImageModel(BaseModel):
tasks_id: str tasks_id: str
prompt: str prompt: str
@@ -24,6 +35,13 @@ class GenerateSingleLogoImageModel(BaseModel):
seed: str seed: str
class GenerateSloganImageModel(BaseModel):
num_point: int
tasks_id: str
prompt: str
image_url: str
class GenerateProductImageModel(BaseModel): class GenerateProductImageModel(BaseModel):
tasks_id: str tasks_id: str
prompt: str prompt: str
@@ -32,6 +50,13 @@ class GenerateProductImageModel(BaseModel):
product_type: str product_type: str
class Flux2ToProductImgModel(BaseModel):
tasks_id: str
prompt: str
image_path: str
infer_step: int | None = None
class GenerateRelightImageModel(BaseModel): class GenerateRelightImageModel(BaseModel):
tasks_id: str tasks_id: str
prompt: str prompt: str

View File

@@ -3,7 +3,6 @@
from pprint import pprint from pprint import pprint
import cv2 import cv2
import mmcv
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import torch import torch
@@ -12,6 +11,7 @@ from minio import Minio
from app.core.config import settings, DESIGN_MODEL_URL from app.core.config import settings, DESIGN_MODEL_URL
from app.schemas.attribute_retrieve import AttributeRecognitionModel from app.schemas.attribute_retrieve import AttributeRecognitionModel
from app.service.utils.image_normalize import my_imnormalize
from app.service.utils.new_oss_client import oss_get_image from app.service.utils.new_oss_client import oss_get_image
minio_client = Minio(settings.MINIO_URL, access_key=settings.MINIO_ACCESS, secret_key=settings.MINIO_SECRET, secure=settings.MINIO_SECURE) minio_client = Minio(settings.MINIO_URL, access_key=settings.MINIO_ACCESS, secret_key=settings.MINIO_SECRET, secure=settings.MINIO_SECURE)
@@ -109,10 +109,9 @@ class AttributeRecognition:
@staticmethod @staticmethod
def preprocess(img): def preprocess(img):
img = mmcv.imread(img)
img_scale = (224, 224) img_scale = (224, 224)
img = cv2.resize(img, img_scale) img = cv2.resize(img, img_scale)
img = mmcv.imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True) img = my_imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True)
preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0) preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0)
return preprocessed_img return preprocessed_img

View File

@@ -10,7 +10,6 @@
from minio import Minio from minio import Minio
from skimage import transform from skimage import transform
import cv2 import cv2
import mmcv
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import tritonclient.http as httpclient import tritonclient.http as httpclient
@@ -18,6 +17,7 @@ import torch
from app.core.config import settings, DESIGN_MODEL_URL from app.core.config import settings, DESIGN_MODEL_URL
from app.schemas.attribute_retrieve import CategoryRecognitionModel from app.schemas.attribute_retrieve import CategoryRecognitionModel
from app.service.utils.image_normalize import my_imnormalize
from app.service.utils.new_oss_client import oss_get_image from app.service.utils.new_oss_client import oss_get_image
minio_client = Minio(settings.MINIO_URL, access_key=settings.MINIO_ACCESS, secret_key=settings.MINIO_SECRET, secure=settings.MINIO_SECURE) minio_client = Minio(settings.MINIO_URL, access_key=settings.MINIO_ACCESS, secret_key=settings.MINIO_SECRET, secure=settings.MINIO_SECURE)
@@ -39,11 +39,10 @@ class CategoryRecognition:
@staticmethod @staticmethod
def preprocess(img): def preprocess(img):
img = mmcv.imread(img)
# ori_shape = img.shape[:2] # ori_shape = img.shape[:2]
img_scale = (224, 224) img_scale = (224, 224)
img = cv2.resize(img, img_scale) img = cv2.resize(img, img_scale)
img = mmcv.imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True) img = my_imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True)
preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0) preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0)
return preprocessed_img return preprocessed_img

View File

@@ -1,7 +1,6 @@
import logging import logging
import cv2 import cv2
import mmcv
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import torch import torch
@@ -9,11 +8,12 @@ import torch.nn.functional as F
import tritonclient.http as httpclient import tritonclient.http as httpclient
from minio import Minio from minio import Minio
from app.core.config import DESIGN_MODEL_URL from app.core.config import DESIGN_MODEL_URL, SEG_PRODUCT_MODEL_URL
from app.core.config import settings from app.core.config import settings
from app.schemas.brand_dna import BrandDnaModel from app.schemas.brand_dna import BrandDnaModel
from app.service.attribute.config import const from app.service.attribute.config import const
from app.service.utils.generate_uuid import generate_uuid from app.service.utils.generate_uuid import generate_uuid
from app.service.utils.image_normalize import my_imnormalize
from app.service.utils.new_oss_client import oss_upload_image, oss_get_image from app.service.utils.new_oss_client import oss_upload_image, oss_get_image
minio_client = Minio(settings.MINIO_URL, access_key=settings.MINIO_ACCESS, secret_key=settings.MINIO_SECRET, secure=settings.MINIO_SECURE) minio_client = Minio(settings.MINIO_URL, access_key=settings.MINIO_ACCESS, secret_key=settings.MINIO_SECRET, secure=settings.MINIO_SECURE)
@@ -29,7 +29,7 @@ class BrandDna:
self.attr_type = pd.read_csv(settings.CATEGORY_PATH) self.attr_type = pd.read_csv(settings.CATEGORY_PATH)
# self.attr_type = pd.read_csv(r"E:\workspace\trinity_client_aida\app\service\attribute\config\descriptor\category\category_dis.csv") # self.attr_type = pd.read_csv(r"E:\workspace\trinity_client_aida\app\service\attribute\config\descriptor\category\category_dis.csv")
self.att_client = httpclient.InferenceServerClient(url=DESIGN_MODEL_URL) self.att_client = httpclient.InferenceServerClient(url=DESIGN_MODEL_URL)
self.seg_client = httpclient.InferenceServerClient(url='10.1.1.243:30000') self.seg_client = httpclient.InferenceServerClient(url=SEG_PRODUCT_MODEL_URL)
self.const = const self.const = const
# self.const = local_debug_const # self.const = local_debug_const
@@ -202,7 +202,7 @@ class BrandDna:
# 服装分割预处理 # 服装分割预处理
@staticmethod @staticmethod
def seg_product_preprocess(image): def seg_product_preprocess(image):
img = mmcv.imread(image) img = image
ori_shape = img.shape[:2] ori_shape = img.shape[:2]
img_scale_w, img_scale_h = ori_shape img_scale_w, img_scale_h = ori_shape
if ori_shape[0] > 1024: if ori_shape[0] > 1024:
@@ -211,9 +211,9 @@ class BrandDna:
img_scale_h = 1024 img_scale_h = 1024
# 如果图片size任意一边 大于 1024 则会resize 成1024 # 如果图片size任意一边 大于 1024 则会resize 成1024
if ori_shape != (img_scale_w, img_scale_h): if ori_shape != (img_scale_w, img_scale_h):
# mmcv.imresize(img, img_scale_h, img_scale_w) # 老代码 引以为戒!哈哈哈~ h和w写反了 # my_imnormalize(img, img_scale_h, img_scale_w) # 老代码 引以为戒!哈哈哈~ h和w写反了
img = cv2.resize(img, (img_scale_h, img_scale_w)) img = cv2.resize(img, (img_scale_h, img_scale_w))
img = mmcv.imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True) img = my_imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True)
preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0) preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0)
return preprocessed_img, ori_shape return preprocessed_img, ori_shape
@@ -227,11 +227,10 @@ class BrandDna:
# 类别检测模型预处理 # 类别检测模型预处理
@staticmethod @staticmethod
def category_preprocess(img): def category_preprocess(img):
img = mmcv.imread(img)
# ori_shape = img.shape[:2] # ori_shape = img.shape[:2]
img_scale = (224, 224) img_scale = (224, 224)
img = cv2.resize(img, img_scale) img = cv2.resize(img, img_scale)
img = mmcv.imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True) img = my_imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True)
preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0) preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0)
return preprocessed_img return preprocessed_img

View File

@@ -1,19 +1,10 @@
import logging import uuid
import httpx
import cv2
import numpy as np
import tritonclient.grpc as grpcclient
from langchain_classic.output_parsers import ResponseSchema, StructuredOutputParser from langchain_classic.output_parsers import ResponseSchema, StructuredOutputParser
from langchain_community.chat_models import ChatTongyi from langchain_community.chat_models import ChatTongyi
from langchain_core.prompts import PromptTemplate from langchain_core.prompts import PromptTemplate
from minio import Minio from minio import Minio
from tritonclient.utils import np_to_triton_dtype
from app.core.config import GI_MODEL_URL, GI_MODEL_NAME
from app.schemas.brand_dna import GenerateBrandModel from app.schemas.brand_dna import GenerateBrandModel
from app.service.utils.generate_uuid import generate_uuid
from app.service.utils.new_oss_client import oss_upload_image
from app.core.config import settings from app.core.config import settings
@@ -26,14 +17,9 @@ class GenerateBrandInfo:
# user info init # user info init
self.user_id = request_data.user_id self.user_id = request_data.user_id
self.category = "brand_logo" self.category = "brand_logo"
# generate logo init
self.grpc_client = grpcclient.InferenceServerClient(url=GI_MODEL_URL)
self.image = np.random.randint(0, 256, (1024, 1024, 3), dtype=np.uint8)
self.batch_size = 1
self.mode = 'txt2img'
# llm generate brand info init # llm generate brand info init
self.model = ChatTongyi(model="qwen2.5-14b-instruct", api_key="sk-7658298c6b99443c98184a5e634fe6ab") self.model = ChatTongyi(model="qwen2.5-14b-instruct", api_key=settings.QWEN_API_KEY)
self.response_schemas = [ self.response_schemas = [
ResponseSchema(name="brand_name", description="Brand name."), ResponseSchema(name="brand_name", description="Brand name."),
@@ -63,38 +49,20 @@ class GenerateBrandInfo:
self.generate_logo_prompt = brand_data['brand_logo_prompt'] self.generate_logo_prompt = brand_data['brand_logo_prompt']
def generate_brand_logo(self): def generate_brand_logo(self):
prompts = [self.generate_logo_prompt] * self.batch_size request_item = {
modes = [self.mode] * self.batch_size "bucket_name": "aida-users",
images = [self.image.astype(np.float16)] * self.batch_size "object_name": f'{self.user_id}/{self.category}/{uuid.uuid4().hex}.png',
"prompt": self.generate_logo_prompt,
text_obj = np.array(prompts, dtype="object").reshape((-1, 1)) "height": 1024,
mode_obj = np.array(modes, dtype="object").reshape((-1, 1)) "width": 1024
image_obj = np.array(images, dtype=np.float16).reshape((-1, 1024, 1024, 3)) }
with httpx.Client(timeout=120) as client:
input_text = grpcclient.InferInput("prompt", text_obj.shape, np_to_triton_dtype(text_obj.dtype)) resp = client.post(
input_image = grpcclient.InferInput("input_image", image_obj.shape, np_to_triton_dtype(image_obj.dtype)) f"http://{settings.FLUX2_GEN_IMG_MODEL_URL}/predict",
input_mode = grpcclient.InferInput("mode", mode_obj.shape, np_to_triton_dtype(mode_obj.dtype)) json=request_item,
)
input_text.set_data_from_numpy(text_obj) result = resp.json()
input_image.set_data_from_numpy(image_obj) self.result_data['brand_logo'] = result.get("output_path", "")
input_mode.set_data_from_numpy(mode_obj)
inputs = [input_text, input_image, input_mode]
result = self.grpc_client.infer(model_name=GI_MODEL_NAME, inputs=inputs)
image = result.as_numpy("generated_image")
image_result = cv2.cvtColor(np.squeeze(image.astype(np.uint8)), cv2.COLOR_RGB2BGR)
logo_url = self.upload_logo_image(image_result, generate_uuid())
self.result_data['brand_logo'] = logo_url
def upload_logo_image(self, image, object_name):
try:
_, img_byte_array = cv2.imencode('.jpg', image)
object_name = f'{self.user_id}/{self.category}/{object_name}.jpg'
oss_upload_image(oss_client=self.minio_client, bucket="aida-users", object_name=object_name, image_bytes=img_byte_array)
image_url = f"aida-users/{object_name}"
return image_url
except Exception as e:
logging.warning(f"upload_png_mask runtime exception : {e}")
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -23,7 +23,7 @@ class ClothingSeg:
def __init__(self, request_data): def __init__(self, request_data):
self.image_data = request_data.image_data self.image_data = request_data.image_data
self.user_id = request_data.user_id self.user_id = request_data.user_id
self.triton_client = grpcclient.InferenceServerClient(url="10.1.1.243:10071") self.triton_client = grpcclient.InferenceServerClient(url=f"{settings.B_4_X_4090_SERVICE_HOST}:10071")
@RunTime @RunTime
def get_result(self): def get_result(self):
@@ -139,7 +139,7 @@ def get_bounding_box(mask):
if __name__ == "__main__": if __name__ == "__main__":
test_data = ClothingSegModel( test_data = ClothingSegModel(
user_id=89, user_id="89",
image_data=[ image_data=[
# { # {
# "image_url": "test/clothing_seg/dress.jpg", # "image_url": "test/clothing_seg/dress.jpg",

View File

@@ -13,7 +13,7 @@ from PIL import Image
from minio import Minio, S3Error from minio import Minio, S3Error
from moviepy.video.io.VideoFileClip import VideoFileClip from moviepy.video.io.VideoFileClip import VideoFileClip
from app.core.config import settings from app.core.config import settings, PS_RABBITMQ_QUEUES
from app.schemas.comfyui_i2v import ComfyuiPose2VModel from app.schemas.comfyui_i2v import ComfyuiPose2VModel
from app.service.generate_image.utils.mq import publish_status from app.service.generate_image.utils.mq import publish_status
@@ -622,9 +622,9 @@ class ComfyUIServerPose2V:
# 推送消息 # 推送消息
if not settings.DEBUG: if not settings.DEBUG:
publish_status(json.dumps(self.pose_transform_data), settings.COMFYUI_SERVER_ADDRESS) publish_status(json.dumps(self.pose_transform_data), PS_RABBITMQ_QUEUES)
logger.info( logger.info(
f" [x] Sent to {settings.COMFYUI_SERVER_ADDRESS} data@@@@ {json.dumps(self.pose_transform_data, indent=4)}") f" [x] Sent to {PS_RABBITMQ_QUEUES} data@@@@ {json.dumps(self.pose_transform_data, indent=4)}")
return "\n🎉 所有任务完成!" return "\n🎉 所有任务完成!"

View File

@@ -10,13 +10,13 @@
import logging import logging
import cv2 import cv2
import mmcv
import numpy as np import numpy as np
import torch import torch
import torch.nn.functional as F import torch.nn.functional as F
import tritonclient.http as httpclient import tritonclient.http as httpclient
from app.core.config import DESIGN_MODEL_URL, DESIGN_MODEL_NAME from app.core.config import DESIGN_MODEL_URL, DESIGN_MODEL_NAME
from app.service.utils.image_normalize import my_imnormalize
""" """
keypoint keypoint
@@ -25,13 +25,13 @@ from app.core.config import DESIGN_MODEL_URL, DESIGN_MODEL_NAME
def keypoint_preprocess(img_path): def keypoint_preprocess(img_path):
img = mmcv.imread(img_path) img = img_path
img_scale = (256, 256) img_scale = (256, 256)
h, w = img.shape[:2] h, w = img.shape[:2]
img = cv2.resize(img, img_scale) img = cv2.resize(img, img_scale)
w_scale = img_scale[0] / w w_scale = img_scale[0] / w
h_scale = img_scale[1] / h h_scale = img_scale[1] / h
img = mmcv.imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True) img = my_imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True)
preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0) preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0)
return preprocessed_img, (w_scale, h_scale) return preprocessed_img, (w_scale, h_scale)
@@ -74,7 +74,7 @@ def keypoint_postprocess(output, scale_factor):
# KNet # KNet
def seg_preprocess(img_path): def seg_preprocess(img_path):
img = mmcv.imread(img_path) img = img_path
ori_shape = img.shape[:2] ori_shape = img.shape[:2]
img_scale_w, img_scale_h = ori_shape img_scale_w, img_scale_h = ori_shape
if ori_shape[0] > 1024: if ori_shape[0] > 1024:
@@ -83,9 +83,9 @@ def seg_preprocess(img_path):
img_scale_h = 1024 img_scale_h = 1024
# 如果图片size任意一边 大于 1024 则会resize 成1024 # 如果图片size任意一边 大于 1024 则会resize 成1024
if ori_shape != (img_scale_w, img_scale_h): if ori_shape != (img_scale_w, img_scale_h):
# mmcv.imresize(img, img_scale_h, img_scale_w) # 老代码 引以为戒!哈哈哈~ h和w写反了 # my_imnormalize(img, img_scale_h, img_scale_w) # 老代码 引以为戒!哈哈哈~ h和w写反了
img = cv2.resize(img, (img_scale_h, img_scale_w)) img = cv2.resize(img, (img_scale_h, img_scale_w))
img = mmcv.imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True) img = my_imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True)
preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0) preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0)
return preprocessed_img, ori_shape return preprocessed_img, ori_shape

View File

@@ -6,10 +6,10 @@ import requests
from minio import Minio from minio import Minio
from app.core.config import settings from app.core.config import settings
from app.service.design_fast.item import BodyItem, TopItem, BottomItem, OthersItem from app.service.design_fast.item import BodyItem, TopItem, BottomItem, OthersItem, TopMergeItem, BottomMergeItem, OthersMergeItem
from app.service.design_fast.utils.organize import organize_body, organize_clothing, organize_others from app.service.design_fast.utils.organize import organize_body, organize_clothing, organize_others
from app.service.design_fast.utils.progress import final_progress, update_progress from app.service.design_fast.utils.progress import final_progress, update_progress
from app.service.design_fast.utils.synthesis_item import synthesis, synthesis_single, update_base_size_priority from app.service.design_fast.utils.synthesis_item import synthesis, synthesis_single, update_base_size_priority, merge
from app.service.utils.decorator import RunTime from app.service.utils.decorator import RunTime
id_lock = threading.Lock() id_lock = threading.Lock()
@@ -19,22 +19,46 @@ logger = logging.getLogger()
minio_client = Minio(settings.MINIO_URL, access_key=settings.MINIO_ACCESS, secret_key=settings.MINIO_SECRET, secure=settings.MINIO_SECURE) minio_client = Minio(settings.MINIO_URL, access_key=settings.MINIO_ACCESS, secret_key=settings.MINIO_SECRET, secure=settings.MINIO_SECURE)
def process_item(item, basic): def process_item(item, basic, design_type):
# 处理project中单个item # 1. 定义映射配置
if item['type'] == "Body": # key 为 item_type 的小写value 为对应的处理类
body_server = BodyItem(data=item, basic=basic, minio_client=minio_client) DESIGN_MAP = {
item_data = body_server.process() 'body': BodyItem,
elif item['type'].lower() in ['blouse', 'outwear', 'dress', 'tops']: 'blouse': TopItem, 'outwear': TopItem,
top_server = TopItem(data=item, basic=basic, minio_client=minio_client) 'dress': TopItem, 'tops': TopItem,
item_data = top_server.process() 'skirt': BottomItem, 'trousers': BottomItem,
elif item['type'].lower() in ['skirt', 'trousers', 'bottoms']: 'bottoms': BottomItem,
bottom_server = BottomItem(data=item, basic=basic, minio_client=minio_client) 'others': OthersItem
item_data = bottom_server.process() }
elif item['type'].lower() in ['others']:
bottom_server = OthersItem(data=item, basic=basic, minio_client=minio_client) MERGE_MAP = {
item_data = bottom_server.process() 'body_merge': BodyItem,
'blouse_merge': TopMergeItem, 'outwear_merge': TopMergeItem,
'dress_merge': TopMergeItem, 'tops_merge': TopMergeItem,
'skirt_merge': BottomMergeItem, 'trousers_merge': BottomMergeItem,
'bottoms_merge': BottomMergeItem,
'others_merge': OthersMergeItem
}
# 2. 根据 design_type 选择映射表
mapping = MERGE_MAP if design_type == 'merge' else DESIGN_MAP
if design_type == 'merge':
item_type_key = f"{item['type'].lower()}_merge"
elif design_type == 'default':
item_type_key = item['type'].lower()
else: else:
raise NotImplementedError(f"Item type {item['type']} not implemented") item_type_key = item['type'].lower()
handler_class = mapping.get(item_type_key)
if not handler_class:
raise NotImplementedError(f"Item type {item['type']} not implemented for design_type={design_type}")
# 4. 统一实例化并执行
# 注意:这里假设所有 Item 类构造函数签名一致
server = handler_class(data=item, basic=basic, minio_client=minio_client)
item_data = server.process()
return item_data return item_data
@@ -44,7 +68,7 @@ def process_layer(item, layers):
body_layer = organize_body(item) body_layer = organize_body(item)
layers.append(body_layer) layers.append(body_layer)
return item['body_image'].size return item['body_image'].size
elif item['name'] == 'others': elif item['name'] in ['others', 'others_merge']:
front_layer, back_layer = organize_others(item) front_layer, back_layer = organize_others(item)
layers.append(front_layer) layers.append(front_layer)
layers.append(back_layer) layers.append(back_layer)
@@ -70,10 +94,11 @@ def design_generate(request_data):
nonlocal active_threads nonlocal active_threads
basic = object['basic'] basic = object['basic']
items_response = {'layers': [], 'objectSign': object['objectSign'] if 'objectSign' in object.keys() else ""} items_response = {'layers': [], 'objectSign': object['objectSign'] if 'objectSign' in object.keys() else ""}
design_type = basic.get('design_type', "default")
if basic['single_overall'] == "overall": if basic['single_overall'] == "overall":
item_results = [] item_results = []
for item in object['items']: for item in object['items']:
item_results.append(process_item(item, basic)) item_results.append(process_item(item, basic, design_type))
layers = [] layers = []
for item in item_results: for item in item_results:
process_layer(item, layers) process_layer(item, layers)
@@ -93,12 +118,19 @@ def design_generate(request_data):
'image_url': lay['image_url'] if 'image_url' in lay.keys() else None, 'image_url': lay['image_url'] if 'image_url' in lay.keys() else None,
'pattern_overall_image_url': lay['pattern_overall_image_url'] if 'pattern_overall_image_url' in lay.keys() else None, 'pattern_overall_image_url': lay['pattern_overall_image_url'] if 'pattern_overall_image_url' in lay.keys() else None,
'pattern_print_image_url': lay['pattern_print_image_url'] if 'pattern_print_image_url' in lay.keys() else None, 'pattern_print_image_url': lay['pattern_print_image_url'] if 'pattern_print_image_url' in lay.keys() else None,
'transpose': lay.get('transpose', None),
'rotate': lay.get('rotate', None),
# 'back_perspective_url': lay['back_perspective_url'] if 'back_perspective_url' in lay.keys() else None, # 'back_perspective_url': lay['back_perspective_url'] if 'back_perspective_url' in lay.keys() else None,
}) })
items_response['synthesis_url'] = synthesis(layers, new_size, basic) if basic.get('design_type') == 'default':
items_response['synthesis_url'] = synthesis(layers, new_size, basic)
elif basic.get('design_type') == 'merge':
items_response['synthesis_url'] = merge(layers, new_size, basic)
else:
items_response['synthesis_url'] = synthesis(layers, new_size, basic)
else: else:
item_result = process_item(object['items'][0], basic) item_result = process_item(object['items'][0], basic, design_type)
items_response['layers'].append({ items_response['layers'].append({
'image_category': f"{item_result['name']}_front", 'image_category': f"{item_result['name']}_front",
'image_size': item_result['back_image'].size if item_result['back_image'] else None, 'image_size': item_result['back_image'].size if item_result['back_image'] else None,
@@ -152,6 +184,7 @@ def design_generate_v2(request_data):
def process_object(object, callback_url): def process_object(object, callback_url):
basic = object['basic'] basic = object['basic']
design_type = basic.get('design_type', "default")
items_response = { items_response = {
'layers': [], 'layers': [],
'objectSign': object['objectSign'] if 'objectSign' in object.keys() else "", 'objectSign': object['objectSign'] if 'objectSign' in object.keys() else "",
@@ -160,7 +193,7 @@ def design_generate_v2(request_data):
if basic['single_overall'] == "overall": if basic['single_overall'] == "overall":
item_results = [] item_results = []
for item in object['items']: for item in object['items']:
item_results.append(process_item(item, basic)) item_results.append(process_item(item, basic, design_type))
layers = [] layers = []
for item in item_results: for item in item_results:
process_layer(item, layers) process_layer(item, layers)
@@ -185,7 +218,7 @@ def design_generate_v2(request_data):
}) })
items_response['synthesis_url'] = synthesis(layers, new_size, basic) items_response['synthesis_url'] = synthesis(layers, new_size, basic)
else: else:
item_result = process_item(object['items'][0], basic) item_result = process_item(object['items'][0], basic, design_type)
items_response['layers'].append({ items_response['layers'].append({
'image_category': f"{item_result['name']}_front", 'image_category': f"{item_result['name']}_front",
'image_size': item_result['back_image'].size if item_result['back_image'] else None, 'image_size': item_result['back_image'].size if item_result['back_image'] else None,

View File

@@ -7,6 +7,7 @@ class BaseItem:
self.result['name'] = data['type'].lower() self.result['name'] = data['type'].lower()
self.result.pop("type") self.result.pop("type")
self.result.update(basic) self.result.update(basic)
self.result['design_type'] = basic.get('design_type', None)
class OthersItem(BaseItem): class OthersItem(BaseItem):
@@ -14,10 +15,7 @@ class OthersItem(BaseItem):
super().__init__(data, basic) super().__init__(data, basic)
self.Others_pipeline = [ self.Others_pipeline = [
LoadImage(minio_client), LoadImage(minio_client),
# KeyPoint(),
# ContourDetection(),
Segmentation(minio_client), Segmentation(minio_client),
# BackPerspective(minio_client),
Color(minio_client), Color(minio_client),
NoSegPrintPainting(minio_client), NoSegPrintPainting(minio_client),
PrintPainting(minio_client), PrintPainting(minio_client),
@@ -74,6 +72,65 @@ class BottomItem(BaseItem):
return self.result return self.result
"""merge"""
class OthersMergeItem(BaseItem):
def __init__(self, data, basic, minio_client):
super().__init__(data, basic)
self.Others_pipeline = [
LoadImage(minio_client),
# KeyPoint(),
# ContourDetection(),
Segmentation(minio_client),
# BackPerspective(minio_client),
Color(minio_client),
# NoSegPrintPainting(minio_client),
# PrintPainting(minio_client),
Scaling(),
Split(minio_client)
]
def process(self):
for item in self.Others_pipeline:
self.result = item(self.result)
return self.result
class TopMergeItem(BaseItem):
def __init__(self, data, basic, minio_client):
super().__init__(data, basic)
self.top_pipeline = [
LoadImage(minio_client),
KeyPoint(),
Segmentation(minio_client),
Scaling(),
Split(minio_client)
]
def process(self):
for item in self.top_pipeline:
self.result = item(self.result)
return self.result
class BottomMergeItem(BaseItem):
def __init__(self, data, basic, minio_client):
super().__init__(data, basic)
self.bottom_pipeline = [
LoadImage(minio_client),
KeyPoint(),
Segmentation(minio_client),
Scaling(),
Split(minio_client)
]
def process(self):
for item in self.bottom_pipeline:
self.result = item(self.result)
return self.result
class BodyItem(BaseItem): class BodyItem(BaseItem):
def __init__(self, data, basic, minio_client): def __init__(self, data, basic, minio_client):
super().__init__(data, basic) super().__init__(data, basic)

View File

@@ -1,7 +1,7 @@
import logging import logging
import numpy as np import numpy as np
from pymilvus import MilvusClient # from pymilvus import MilvusClient
from app.core.config import KEYPOINT_RESULT_TABLE_FIELD_SET, MILVUS_TABLE_KEYPOINT, settings from app.core.config import KEYPOINT_RESULT_TABLE_FIELD_SET, MILVUS_TABLE_KEYPOINT, settings
from app.service.design_fast.utils.design_ensemble import get_keypoint_result from app.service.design_fast.utils.design_ensemble import get_keypoint_result
@@ -54,63 +54,64 @@ class KeyPoint:
"keypoint_vector": result.tolist() "keypoint_vector": result.tolist()
} }
] ]
try: return dict(zip(KEYPOINT_RESULT_TABLE_FIELD_SET, result.reshape(12, 2).astype(int).tolist()))
client = MilvusClient(uri=settings.MILVUS_URL, token=settings.MILVUS_TOKEN, db_name=settings.MILVUS_ALIAS)
client.upsert(collection_name=MILVUS_TABLE_KEYPOINT, data=data)
client.close()
return dict(zip(KEYPOINT_RESULT_TABLE_FIELD_SET, result.reshape(12, 2).astype(int).tolist()))
except Exception as e:
logger.info(f"save keypoint cache milvus error : {e}")
return dict(zip(KEYPOINT_RESULT_TABLE_FIELD_SET, result.reshape(12, 2).astype(int).tolist()))
@staticmethod # try:
def update_keypoint_cache(keypoint_id, infer_result, search_result, site): # client = MilvusClient(uri=settings.MILVUS_URL, token=settings.MILVUS_TOKEN, db_name=settings.MILVUS_ALIAS)
if site == "up": # client.upsert(collection_name=MILVUS_TABLE_KEYPOINT, data=data)
# 需要的是up 即推理出来的是up 那么查询的就是down # client.close()
result = np.concatenate([infer_result.flatten(), search_result[-4:]]) # except Exception as e:
else: # logger.info(f"save keypoint cache milvus error : {e}")
# 需要的是down 即推理出来的是down 那么查询的就是up # return dict(zip(KEYPOINT_RESULT_TABLE_FIELD_SET, result.reshape(12, 2).astype(int).tolist()))
result = np.concatenate([search_result[:20], infer_result.flatten()])
data = [
{"keypoint_id": keypoint_id,
"keypoint_site": "all",
"keypoint_vector": result.tolist()
}
]
try: # @staticmethod
client = MilvusClient(uri=settings.MILVUS_URL, token=settings.MILVUS_TOKEN, db_name=settings.MILVUS_ALIAS) # def update_keypoint_cache(keypoint_id, infer_result, search_result, site):
client.upsert( # if site == "up":
collection_name=MILVUS_TABLE_KEYPOINT, # # 需要的是up 即推理出来的是up 那么查询的就是down
data=data # result = np.concatenate([infer_result.flatten(), search_result[-4:]])
) # else:
return dict(zip(KEYPOINT_RESULT_TABLE_FIELD_SET, result.reshape(12, 2).astype(int).tolist())) # # 需要的是down 即推理出来的是down 那么查询的就是up
except Exception as e: # result = np.concatenate([search_result[:20], infer_result.flatten()])
logger.info(f"save keypoint cache milvus error : {e}") # data = [
return dict(zip(KEYPOINT_RESULT_TABLE_FIELD_SET, result.reshape(12, 2).astype(int).tolist())) # {"keypoint_id": keypoint_id,
# "keypoint_site": "all",
# "keypoint_vector": result.tolist()
# }
# ]
#
# try:
# client = MilvusClient(uri=settings.MILVUS_URL, token=settings.MILVUS_TOKEN, db_name=settings.MILVUS_ALIAS)
# client.upsert(
# collection_name=MILVUS_TABLE_KEYPOINT,
# data=data
# )
# return dict(zip(KEYPOINT_RESULT_TABLE_FIELD_SET, result.reshape(12, 2).astype(int).tolist()))
# except Exception as e:
# logger.info(f"save keypoint cache milvus error : {e}")
# return dict(zip(KEYPOINT_RESULT_TABLE_FIELD_SET, result.reshape(12, 2).astype(int).tolist()))
@RunTime # @RunTime
def keypoint_cache(self, result, site): # def keypoint_cache(self, result, site):
try: # try:
client = MilvusClient(uri=settings.MILVUS_URL, token=settings.MILVUS_TOKEN, db_name=settings.MILVUS_ALIAS) # client = MilvusClient(uri=settings.MILVUS_URL, token=settings.MILVUS_TOKEN, db_name=settings.MILVUS_ALIAS)
keypoint_id = result['image_id'] # keypoint_id = result['image_id']
res = client.query( # res = client.query(
collection_name=MILVUS_TABLE_KEYPOINT, # collection_name=MILVUS_TABLE_KEYPOINT,
# ids=[keypoint_id], # # ids=[keypoint_id],
filter=f"keypoint_id == {keypoint_id}", # filter=f"keypoint_id == {keypoint_id}",
output_fields=['keypoint_vector', 'keypoint_site'] # output_fields=['keypoint_vector', 'keypoint_site']
) # )
if len(res) == 0: # if len(res) == 0:
# 没有结果 直接推理拿结果 并保存 # # 没有结果 直接推理拿结果 并保存
keypoint_infer_result, site = self.infer_keypoint_result(result) # keypoint_infer_result, site = self.infer_keypoint_result(result)
return self.save_keypoint_cache(result['image_id'], keypoint_infer_result, site) # return self.save_keypoint_cache(result['image_id'], keypoint_infer_result, site)
elif res[0]["keypoint_site"] == "all" or res[0]["keypoint_site"] == site: # elif res[0]["keypoint_site"] == "all" or res[0]["keypoint_site"] == site:
# 需要的类型和查询的类型一致或者查询的类型为all 则直接返回查询的结果 # # 需要的类型和查询的类型一致或者查询的类型为all 则直接返回查询的结果
return dict(zip(KEYPOINT_RESULT_TABLE_FIELD_SET, np.array(res[0]['keypoint_vector']).astype(int).reshape(12, 2).tolist())) # return dict(zip(KEYPOINT_RESULT_TABLE_FIELD_SET, np.array(res[0]['keypoint_vector']).astype(int).reshape(12, 2).tolist()))
elif res[0]["keypoint_site"] != site: # elif res[0]["keypoint_site"] != site:
# 需要的类型和查询到的不一致则更新类型为all # # 需要的类型和查询到的不一致则更新类型为all
keypoint_infer_result, site = self.infer_keypoint_result(result) # keypoint_infer_result, site = self.infer_keypoint_result(result)
return self.update_keypoint_cache(result["image_id"], keypoint_infer_result, res[0]['keypoint_vector'], site) # return self.update_keypoint_cache(result["image_id"], keypoint_infer_result, res[0]['keypoint_vector'], site)
except Exception as e: # except Exception as e:
logger.info(f"search keypoint cache milvus error {e}") # logger.info(f"search keypoint cache milvus error {e}")
return False # return False

View File

@@ -35,15 +35,9 @@ class LoadImage:
return cls.name return cls.name
def __call__(self, result): def __call__(self, result):
if result.get("merge_image_path"):
result['merge_image'], _ = self.read_image(result['merge_image_path'])
result['image'], result['pre_mask'] = self.read_image(result['path']) result['image'], result['pre_mask'] = self.read_image(result['path'])
# if 'extract_lines' in result.keys():
# if result['extract_lines']:
# result['gray'] = self.get_lines(cv2.cvtColor(result['image'], cv2.COLOR_BGR2GRAY), result['path'])
# else:
# result['gray'] = cv2.cvtColor(result['image'], cv2.COLOR_BGR2GRAY)
# else:
# result['gray'] = cv2.cvtColor(result['image'], cv2.COLOR_BGR2GRAY)
result['gray'] = self.get_lines(cv2.cvtColor(result['image'], cv2.COLOR_BGR2GRAY)) result['gray'] = self.get_lines(cv2.cvtColor(result['image'], cv2.COLOR_BGR2GRAY))
result['keypoint'] = self.get_keypoint(result['name']) result['keypoint'] = self.get_keypoint(result['name'])
result['img_shape'] = result['image'].shape result['img_shape'] = result['image'].shape
@@ -61,21 +55,6 @@ class LoadImage:
mask = skeleton mask = skeleton
result = np.ones_like(img) * 255 result = np.ones_like(img) * 255
result[mask] = img[mask] result[mask] = img[mask]
# 步骤2细化边缘可选让线条更干净
# kernel = np.ones((1, 1), np.uint8)
# clean = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
# thinned = cv2.ximgproc.thinning(binary, thinningType=cv2.ximgproc.THINNING_ZHANGSUEN) # thinning算法细化线条
# mask = thinned > 0
# result = np.ones_like(img) * 255
# result[mask] = img[mask]
# 步骤3反转回 白底黑线
# lines = cv2.bitwise_not(thinned)
# cv2.imwrite(os.path.join('/home/user/PycharmProjects/trinity_client_aida/test/lines_original_result_5', f"Original_{path.replace('/', '-')}.png"), img)
# cv2.imwrite(os.path.join('/home/user/PycharmProjects/trinity_client_aida/test/lines_original_result_5', f"Line_{path.replace('/', '-')}.png"), result)
return result return result
def read_image(self, image_path): def read_image(self, image_path):
@@ -96,19 +75,19 @@ class LoadImage:
@staticmethod @staticmethod
def get_keypoint(name): def get_keypoint(name):
if name == 'blouse' or name == 'outwear' or name == 'dress' or name == 'tops': if name in ['blouse', 'outwear', 'dress', 'tops', 'blouse_merge', 'outwear_merge', 'dress_merge', 'tops_merge']:
keypoint = 'shoulder' keypoint = 'shoulder'
elif name == 'trousers' or name == 'skirt' or name == 'bottoms': elif name in ['trousers', 'skirt', 'bottoms', 'trousers_merge', 'skirt_merge', 'bottoms_merge']:
keypoint = 'waistband' keypoint = 'waistband'
elif name == 'bag': elif name in ['bag', 'bag_merge']:
keypoint = 'hand_point' keypoint = 'hand_point'
elif name == 'shoes': elif name in ['shoes', 'shoes_merge']:
keypoint = 'toe' keypoint = 'toe'
elif name == 'hairstyle': elif name in ['hairstyle', 'hairstyle_merge']:
keypoint = 'head_point' keypoint = 'head_point'
elif name == 'earring': elif name in ['earring', 'earring_merge']:
keypoint = 'ear_point' keypoint = 'ear_point'
elif name == 'others': elif name in ['others', 'others_merge']:
keypoint = "others" keypoint = "others"
else: else:
raise KeyError(f"{name} does not belong to item category list: blouse, outwear, dress, trousers, skirt, " raise KeyError(f"{name} does not belong to item category list: blouse, outwear, dress, trousers, skirt, "

View File

@@ -9,32 +9,27 @@ from app.service.utils.new_oss_client import oss_get_image
class NoSegPrintPainting: class NoSegPrintPainting:
def __init__(self, minio_client): def __init__(self, minio_client):
self.random_seed = random.randint(0, 1000)
self.minio_client = minio_client self.minio_client = minio_client
def __call__(self, result): def __call__(self, result):
single_print = result['print']['single'] # single_print = [result['print']['single']]
overall_print = result['print']['overall'] overall_print = result['print']['overall']
element_print = result['print']['element'] # element_print = result['print']['element'
single_print = None
element_print = None
result['single_image'] = None result['single_image'] = None
result['print_image'] = None result['print_image'] = None
if overall_print['print_path_list']: if overall_print['print_path_list']:
painting_dict = {'dim_image_h': result['pattern_image'].shape[0], 'dim_image_w': result['pattern_image'].shape[1]} painting_dict = {'dim_image_h': result['pattern_image'].shape[0], 'dim_image_w': result['pattern_image'].shape[1]}
if "print_angle_list" in overall_print.keys() and overall_print['print_angle_list'][0] != 0: # 获取平铺 + 旋转 的overall print
painting_dict = self.painting_collection(painting_dict, overall_print, print_trigger=True) painting_dict = self.painting_collection(painting_dict, overall_print)
painting_dict['tile_print'] = self.rotate_crop_image(img=painting_dict['tile_print'], angle=-overall_print['print_angle_list'][0], crop=True)
painting_dict['mask_inv_print'] = self.rotate_crop_image(img=painting_dict['mask_inv_print'], angle=-overall_print['print_angle_list'][0], crop=True)
# resize 到sketch大小
painting_dict['tile_print'] = self.resize_and_crop(img=painting_dict['tile_print'], target_width=painting_dict['dim_image_w'], target_height=painting_dict['dim_image_h'])
painting_dict['mask_inv_print'] = self.resize_and_crop(img=painting_dict['mask_inv_print'], target_width=painting_dict['dim_image_w'], target_height=painting_dict['dim_image_h'])
else:
painting_dict = self.painting_collection(painting_dict, overall_print, print_trigger=True, is_single=False)
result['no_seg_sketch_overall'] = result['no_seg_sketch_print'] = self.printpaint(result, painting_dict, print_=True) result['no_seg_sketch_overall'] = result['no_seg_sketch_print'] = self.printpaint(result, painting_dict, print_=True)
result['pattern_image'] = result['no_seg_sketch_overall'] # result['pattern_image'] = result['no_seg_sketch_overall']
if single_print['print_path_list']: if single_print:
print_background = np.zeros((result['pattern_image'].shape[0], result['pattern_image'].shape[1], 3), dtype=np.uint8) print_background = np.zeros((result['pattern_image'].shape[0], result['pattern_image'].shape[1], 3), dtype=np.uint8)
mask_background = np.zeros((result['pattern_image'].shape[0], result['pattern_image'].shape[1], 3), dtype=np.uint8) mask_background = np.zeros((result['pattern_image'].shape[0], result['pattern_image'].shape[1], 3), dtype=np.uint8)
for i in range(len(single_print['print_path_list'])): for i in range(len(single_print['print_path_list'])):
@@ -74,7 +69,7 @@ class NoSegPrintPainting:
single_image = cv2.add(tmp1, tmp2) single_image = cv2.add(tmp1, tmp2)
result['no_seg_sketch_print'] = single_image result['no_seg_sketch_print'] = single_image
if element_print['element_path_list']: if element_print:
print_background = np.zeros((result['final_image'].shape[0], result['final_image'].shape[1], 3), dtype=np.uint8) print_background = np.zeros((result['final_image'].shape[0], result['final_image'].shape[1], 3), dtype=np.uint8)
mask_background = np.zeros((result['final_image'].shape[0], result['final_image'].shape[1], 3), dtype=np.uint8) mask_background = np.zeros((result['final_image'].shape[0], result['final_image'].shape[1], 3), dtype=np.uint8)
for i in range(len(element_print['element_path_list'])): for i in range(len(element_print['element_path_list'])):
@@ -151,7 +146,6 @@ class NoSegPrintPainting:
temp_fg = np.expand_dims(result['mask'], axis=2).repeat(3, axis=2) temp_fg = np.expand_dims(result['mask'], axis=2).repeat(3, axis=2)
tmp2 = (result['final_image'] * (temp_fg / 255)).astype(np.uint8) tmp2 = (result['final_image'] * (temp_fg / 255)).astype(np.uint8)
result['no_seg_sketch_print'] = cv2.add(tmp1, tmp2) result['no_seg_sketch_print'] = cv2.add(tmp1, tmp2)
return result return result
@staticmethod @staticmethod
@@ -166,26 +160,23 @@ class NoSegPrintPainting:
print_background = img1_bg + img2_fg print_background = img1_bg + img2_fg
return print_background return print_background
def painting_collection(self, painting_dict, print_dict, print_trigger=False, is_single=False): def painting_collection(self, painting_dict, print_dict):
if print_trigger: print_ = self.get_print(print_dict)
print_ = self.get_print(print_dict) painting_dict['location'] = print_['location']
painting_dict['Trigger'] = not is_single dim_max = max(painting_dict['dim_image_h'], painting_dict['dim_image_w'])
painting_dict['location'] = print_['location'] dim_pattern = (int(dim_max * print_['scale'] / 5), int(dim_max * print_['scale'] / 5))
single_mask_inv_print = self.get_mask_inv(print_['image']) gap = print_dict.get('gap', [[0, 0]])[0]
dim_max = max(painting_dict['dim_image_h'], painting_dict['dim_image_w']) painting_dict['tile_print'], painting_dict['mask_inv_print'] = tile_image(pattern=print_['image'],
dim_pattern = (int(dim_max * print_['scale'] / 5), int(dim_max * print_['scale'] / 5)) mask=print_['mask'],
if not is_single: dim=dim_pattern,
# 如果print 模式为overall 且 有角度的话 组合的print为正方形方便裁剪 gap_x=gap[0],
if "print_angle_list" in print_dict.keys() and print_dict['print_angle_list'][0] != 0: gap_y=gap[1],
painting_dict['mask_inv_print'] = self.tile_image(single_mask_inv_print, dim_pattern, print_['scale'], dim_max, dim_max, painting_dict['location'], trigger=True) canvas_h=painting_dict['dim_image_h'],
painting_dict['tile_print'] = self.tile_image(print_['image'], dim_pattern, print_['scale'], dim_max, dim_max, painting_dict['location'], trigger=True) canvas_w=painting_dict['dim_image_w'],
else: location=painting_dict['location'],
painting_dict['mask_inv_print'] = self.tile_image(single_mask_inv_print, dim_pattern, print_['scale'], painting_dict['dim_image_h'], painting_dict['dim_image_w'], painting_dict['location'], trigger=True) angle=int(print_.get('print_angle_list', [0])[0]))
painting_dict['tile_print'] = self.tile_image(print_['image'], dim_pattern, print_['scale'], painting_dict['dim_image_h'], painting_dict['dim_image_w'], painting_dict['location'], trigger=True) # painting_dict['mask_inv_print'] = np.zeros(painting_dict['tile_print'].shape[:2], dtype=np.uint8)
else: # painting_dict['mask_inv_print'] = self.get_mask_inv(painting_dict['tile_print'])
painting_dict['mask_inv_print'] = self.tile_image(single_mask_inv_print, dim_pattern, print_['scale'], painting_dict['dim_image_h'], painting_dict['dim_image_w'], painting_dict['location'])
painting_dict['tile_print'] = self.tile_image(print_['image'], dim_pattern, print_['scale'], painting_dict['dim_image_h'], painting_dict['dim_image_w'], painting_dict['location'])
painting_dict['dim_print_h'], painting_dict['dim_print_w'] = dim_pattern
return painting_dict return painting_dict
def tile_image(self, pattern, dim, scale, dim_image_h, dim_image_w, location, trigger=False): def tile_image(self, pattern, dim, scale, dim_image_h, dim_image_w, location, trigger=False):
@@ -219,33 +210,32 @@ class NoSegPrintPainting:
@staticmethod @staticmethod
def printpaint(result, painting_dict, print_=False): def printpaint(result, painting_dict, print_=False):
if print_:
if print_ and painting_dict['Trigger']:
print_mask = cv2.bitwise_and(result['mask'], cv2.bitwise_not(painting_dict['mask_inv_print'])) print_mask = cv2.bitwise_and(result['mask'], cv2.bitwise_not(painting_dict['mask_inv_print']))
img_fg = cv2.bitwise_and(painting_dict['tile_print'], painting_dict['tile_print'], mask=print_mask) img_fg = cv2.bitwise_and(painting_dict['tile_print'], painting_dict['tile_print'], mask=print_mask)
else: else:
print_mask = result['mask'] print_mask = result['mask']
img_fg = result['final_image'] img_fg = result['final_image']
if print_ and not painting_dict['Trigger']: # if print_ and not painting_dict['Trigger']:
index_ = None # index_ = None
try: # try:
index_ = len(painting_dict['location']) # index_ = len(painting_dict['location'])
except: # except:
assert f'there must be parameter of location if choose IfSingle' # assert f'there must be parameter of location if choose IfSingle'
#
for i in range(index_): # for i in range(index_):
start_h, start_w = int(painting_dict['location'][i][1]), int(painting_dict['location'][i][0]) # start_h, start_w = int(painting_dict['location'][i][1]), int(painting_dict['location'][i][0])
#
length_h = min(start_h + painting_dict['dim_print_h'], img_fg.shape[0]) # length_h = min(start_h + painting_dict['dim_print_h'], img_fg.shape[0])
length_w = min(start_w + painting_dict['dim_print_w'], img_fg.shape[1]) # length_w = min(start_w + painting_dict['dim_print_w'], img_fg.shape[1])
#
change_region = img_fg[start_h: length_h, start_w: length_w, :] # change_region = img_fg[start_h: length_h, start_w: length_w, :]
# problem in change_mask # # problem in change_mask
change_mask = print_mask[start_h: length_h, start_w: length_w] # change_mask = print_mask[start_h: length_h, start_w: length_w]
# get real part into change mask # # get real part into change mask
_, change_mask = cv2.threshold(change_mask, 220, 255, cv2.THRESH_BINARY) # _, change_mask = cv2.threshold(change_mask, 220, 255, cv2.THRESH_BINARY)
cv2.bitwise_not(painting_dict['mask_inv_print']) # cv2.bitwise_not(painting_dict['mask_inv_print'])
img_fg[start_h:start_h + painting_dict['dim_print_h'], start_w:start_w + painting_dict['dim_print_w'], :] = change_region # img_fg[start_h:start_h + painting_dict['dim_print_h'], start_w:start_w + painting_dict['dim_print_w'], :] = change_region
clothes_mask_print = cv2.bitwise_not(print_mask) clothes_mask_print = cv2.bitwise_not(print_mask)
@@ -267,18 +257,21 @@ class NoSegPrintPainting:
image = oss_get_image(oss_client=self.minio_client, bucket=bucket_name, object_name=object_name, data_type="PIL") image = oss_get_image(oss_client=self.minio_client, bucket=bucket_name, object_name=object_name, data_type="PIL")
# 判断图片格式如果是RGBA 则贴在一张纯白图片上 防止透明转黑 # 判断图片格式如果是RGBA 则贴在一张纯白图片上 防止透明转黑
if image.mode == "RGBA": if image.mode == "RGBA":
mask_pil = image.split()[3]
new_background = Image.new('RGB', image.size, (255, 255, 255)) new_background = Image.new('RGB', image.size, (255, 255, 255))
new_background.paste(image, mask=image.split()[3]) new_background.paste(image, mask=image.split()[3])
image = new_background image = new_background
else:
mask_pil = Image.new('L', image.size, 255) # L=灰度图255=纯白
print_dict['image'] = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR) print_dict['image'] = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)
print_dict['mask'] = cv2.threshold(np.array(mask_pil), 127, 255, cv2.THRESH_BINARY)[1]
return print_dict return print_dict
def crop_image(self, image, image_size_h, image_size_w, location, print_shape): def crop_image(self, image, image_size_h, image_size_w, location, print_shape):
print_w = print_shape[1] print_w = print_shape[1]
print_h = print_shape[0] print_h = print_shape[0]
random.seed(self.random_seed)
# 1.拿到偏移量后和resize后的print宽高取余 得到真正偏移量 # 1.拿到偏移量后和resize后的print宽高取余 得到真正偏移量
# 偏移量增加2分之print.w 使坐标位于图中间 如果要位于左上角删除+ print_w // 2 即可 # 偏移量增加2分之print.w 使坐标位于图中间 如果要位于左上角删除+ print_w // 2 即可
x_offset = print_w - int(location[0][1] % print_w) + print_w // 2 x_offset = print_w - int(location[0][1] % print_w) + print_w // 2
@@ -420,3 +413,118 @@ class NoSegPrintPainting:
cropped_img = resized_img[start_y:start_y + target_height, :] cropped_img = resized_img[start_y:start_y + target_height, :]
return cropped_img return cropped_img
def tile_image(pattern, mask, dim, gap_x, gap_y, canvas_h, canvas_w, location, angle=0):
"""
按照指定的 X/Y 间距平铺印花,并支持旋转
【修改版】以被平铺图案的【中心】作为平铺基准点
:param location: [[center_y, center_x]] → 第一个图案中心的坐标
:param angle: 旋转角度 (度数, 逆时针)
"""
# 1. 确保输入是 RGBA
if pattern.shape[2] == 3:
pattern = cv2.cvtColor(pattern, cv2.COLOR_BGR2BGRA)
# 2. 缩放与旋转印花
resized_p = cv2.resize(pattern, dim, interpolation=cv2.INTER_AREA)
rotated_p = rotate_image(resized_p, angle)
p_h, p_w = rotated_p.shape[:2]
# 3. 创建透明单元格(图案放在单元格中心)
cell_h = p_h + gap_y
cell_w = p_w + gap_x
unit_cell = np.zeros((cell_h, cell_w, 4), dtype=np.uint8)
# 计算图案在单元格中的左上角位置(让图案居中)
start_y = (cell_h - p_h) // 2
start_x = (cell_w - p_w) // 2
unit_cell[start_y:start_y + p_h, start_x:start_x + p_w, :] = rotated_p
# 4. 执行平铺
tiles_y = (canvas_h // cell_h) + 3 # 多加一点余量更安全
tiles_x = (canvas_w // cell_w) + 3
full_tiled = np.tile(unit_cell, (tiles_y, tiles_x, 1))
# 5. 计算偏移(关键修改:以中心为基准)
center_y, center_x = location[0][0], location[0][1] # 第一个图案的中心位置
# 计算从哪个位置开始裁剪,才能让中心落在指定坐标
offset_y = int((center_y - (p_h // 2)) % cell_h)
offset_x = int((center_x - (p_w // 2)) % cell_w)
tiled_layer = full_tiled[offset_y: offset_y + canvas_h,
offset_x: offset_x + canvas_w]
# 6. 创建纯白色背景并合成(保持你原来的风格)
white_background = np.full((canvas_h, canvas_w, 3), 255, dtype=np.uint8)
tiled_bgr = tiled_layer[:, :, :3]
alpha_mask = tiled_layer[:, :, 3] / 255.0
alpha_mask = cv2.merge([alpha_mask, alpha_mask, alpha_mask])
tiled_print = (tiled_bgr * alpha_mask + white_background * (1 - alpha_mask)).astype(np.uint8)
# ====================== 处理 Mask ======================
# Mask 也同样居中处理
resized_mask = cv2.resize(mask, dim, interpolation=cv2.INTER_NEAREST)
rotated_mask = rotate_image(resized_mask, angle) # 注意mask也需要旋转
unit_mask = np.zeros((cell_h, cell_w), dtype=np.uint8)
unit_mask[start_y:start_y + p_h, start_x:start_x + p_w] = rotated_mask
full_mask_tiled = np.tile(unit_mask, (tiles_y, tiles_x))
tiled_mask = full_mask_tiled[offset_y: offset_y + canvas_h,
offset_x: offset_x + canvas_w]
return tiled_print, cv2.bitwise_not(tiled_mask)
def rotate_image(image, angle):
"""
旋转图片并保持完整内容(自动扩大画布)
"""
if angle == 0:
return image
(h, w) = image.shape[:2]
(cX, cY) = (w // 2, h // 2)
# 获取旋转矩阵
M = cv2.getRotationMatrix2D((cX, cY), angle, 1.0)
# 计算旋转后新边界的 sine 和 cosine
cos = np.abs(M[0, 0])
sin = np.abs(M[0, 1])
# 计算新的画布尺寸
nW = int((h * sin) + (w * cos))
nH = int((h * cos) + (w * sin))
# 调整旋转矩阵以考虑平移
M[0, 2] += (nW / 2) - cX
M[1, 2] += (nH / 2) - cY
# 执行旋转
return cv2.warpAffine(image, M, (nW, nH))
def crop_image(image, image_size_h, image_size_w, location, print_shape):
print_w = print_shape[1]
print_h = print_shape[0]
# 1.拿到偏移量后和resize后的print宽高取余 得到真正偏移量
# 偏移量增加2分之print.w 使坐标位于图中间 如果要位于左上角删除+ print_w // 2 即可
x_offset = print_w - int(location[0][1] % print_w) + print_w // 2
y_offset = print_h - int(location[0][0] % print_h) + print_h // 2
# y_offset = int(location[0][0])
# x_offset = int(location[0][1])
if len(image.shape) == 2:
image = image[x_offset: x_offset + image_size_h, y_offset: y_offset + image_size_w]
elif len(image.shape) == 3:
image = image[x_offset: x_offset + image_size_h, y_offset: y_offset + image_size_w, :]
return image

View File

@@ -9,14 +9,17 @@ from app.service.utils.new_oss_client import oss_get_image
class PrintPainting: class PrintPainting:
def __init__(self, minio_client): def __init__(self, minio_client):
self.random_seed = None
self.minio_client = minio_client self.minio_client = minio_client
def __call__(self, result): def __call__(self, result):
single_print = result['print']['single'] # single_print = result['print']['single']
overall_print = result['print']['overall'] overall_print = result['print']['overall']
element_print = result['print']['element'] # element_print = result['print']['element']
partial_path = result['print']['partial'] if 'partial' in result['print'] else None # partial_path = result['print']['partial'] if 'partial' in result['print'] else None
single_print = None
element_print = None
partial_path = None
result['single_image'] = None result['single_image'] = None
result['print_image'] = None result['print_image'] = None
# TODO 给result['pattern_image'] resize 到resize_scale的大小 # TODO 给result['pattern_image'] resize 到resize_scale的大小
@@ -38,24 +41,15 @@ class PrintPainting:
if overall_print['print_path_list']: if overall_print['print_path_list']:
overall_print['location'][0] = [x * y for x, y in zip(overall_print['location'][0], result['resize_scale'])] overall_print['location'][0] = [x * y for x, y in zip(overall_print['location'][0], result['resize_scale'])]
painting_dict = {'dim_image_h': result['pattern_image'].shape[0], 'dim_image_w': result['pattern_image'].shape[1]} painting_dict = {'dim_image_h': result['pattern_image'].shape[0], 'dim_image_w': result['pattern_image'].shape[1]}
result['print_image'] = result['pattern_image'] result['print_image'] = result['pattern_image'].copy()
if "print_angle_list" in overall_print.keys() and overall_print['print_angle_list'][0] != 0: # 获取平铺 + 旋转 的overall print
painting_dict = self.painting_collection(painting_dict, overall_print, print_trigger=True) painting_dict = self.painting_collection(painting_dict, overall_print)
painting_dict['tile_print'] = self.rotate_crop_image(img=painting_dict['tile_print'], angle=-overall_print['print_angle_list'][0], crop=True)
painting_dict['mask_inv_print'] = self.rotate_crop_image(img=painting_dict['mask_inv_print'], angle=-overall_print['print_angle_list'][0], crop=True)
# resize 到sketch大小
painting_dict['tile_print'] = self.resize_and_crop(img=painting_dict['tile_print'], target_width=painting_dict['dim_image_w'], target_height=painting_dict['dim_image_h'])
painting_dict['mask_inv_print'] = self.resize_and_crop(img=painting_dict['mask_inv_print'], target_width=painting_dict['dim_image_w'], target_height=painting_dict['dim_image_h'])
else:
painting_dict = self.painting_collection(painting_dict, overall_print, print_trigger=True, is_single=False)
result['print_image'] = self.printpaint(result, painting_dict, print_=True) result['print_image'] = self.printpaint(result, painting_dict, print_=True)
result['single_image'] = result['final_image'] = result['pattern_image'] = result['print_image'] result['single_image'] = result['final_image'] = result['pattern_image'] = result['print_image']
if single_print['print_path_list']: if single_print:
# 2025-9-19 印花调整 印花坐标按照sketch的缩放比调整 # 2025-9-19 印花调整 印花坐标按照sketch的缩放比调整
sketch_resize_scale = result['resize_scale'] sketch_resize_scale = result['resize_scale']
print_background = np.zeros((result['pattern_image'].shape[0], result['pattern_image'].shape[1], 3), dtype=np.uint8) print_background = np.zeros((result['pattern_image'].shape[0], result['pattern_image'].shape[1], 3), dtype=np.uint8)
mask_background = np.zeros((result['pattern_image'].shape[0], result['pattern_image'].shape[1], 3), dtype=np.uint8) mask_background = np.zeros((result['pattern_image'].shape[0], result['pattern_image'].shape[1], 3), dtype=np.uint8)
for i in range(len(single_print['print_path_list'])): for i in range(len(single_print['print_path_list'])):
@@ -78,75 +72,6 @@ class PrintPainting:
print_background = cv2.cvtColor(np.array(source_image_pil), cv2.COLOR_RGBA2BGR) print_background = cv2.cvtColor(np.array(source_image_pil), cv2.COLOR_RGBA2BGR)
mask_background = cv2.cvtColor(np.array(source_image_pil_mask), cv2.COLOR_RGBA2BGR) mask_background = cv2.cvtColor(np.array(source_image_pil_mask), cv2.COLOR_RGBA2BGR)
ret, mask_background = cv2.threshold(mask_background, 124, 255, cv2.THRESH_BINARY) ret, mask_background = cv2.threshold(mask_background, 124, 255, cv2.THRESH_BINARY)
# else:
# mask = self.get_mask_inv(image)
# mask = np.expand_dims(mask, axis=2)
# mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
# mask = cv2.bitwise_not(mask)
#
# mask = cv2.resize(mask, (int(result['final_image'].shape[1] * single_print['print_scale_list'][i][0]), int(result['final_image'].shape[0] * single_print['print_scale_list'][i][1])))
# image = cv2.resize(image, (int(result['final_image'].shape[1] * single_print['print_scale_list'][i][0]), int(result['final_image'].shape[0] * single_print['print_scale_list'][i][1])))
# # 旋转后的坐标需要重新算
# rotate_mask, _ = self.img_rotate(mask, single_print['print_angle_list'][i])
# rotate_image, rotated_new_size = self.img_rotate(image, single_print['print_angle_list'][i])
# # x, y = int(result['print']['location'][i][0] - rotated_new_size[0] - (rotate_mask.shape[0] - image.shape[0]) / 2), int(result['print']['location'][i][1] - rotated_new_size[1] - (rotate_mask.shape[1] - image.shape[1]) / 2)
# x, y = int(single_print['location'][i][0] - rotated_new_size[0]), int(single_print['location'][i][1] - rotated_new_size[1])
#
# image_x = print_background.shape[1] # 底图宽
# image_y = print_background.shape[0] # 底图高
# print_x = rotate_image.shape[1] #印花宽
# print_y = rotate_image.shape[0] #印花高
#
# # 有bug
# # if x + print_x > image_x:
# # rotate_image = rotate_image[:, :x + print_x - image_x]
# # rotate_mask = rotate_mask[:, :x + print_x - image_x]
# # #
# # if y + print_y > image_y:
# # rotate_image = rotate_image[:y + print_y - image_y]
# # rotate_mask = rotate_mask[:y + print_y - image_y]
#
# # 不能是并行
# # 当前第一轮的if 108以及115是判断有没有过下界和右界。第二轮的是判断左上有没有超出。 如果这个样子的话先裁了右边再左移region就会有问题
# # 先挪 再判断 最后裁剪
#
# # 如果print旋转了 或者 print贴边了 则需要判断 判断左界和上界是否小于0
# if x <= 0: # 如果X轴偏移量小于0说明印花需要被裁剪至合适大小 或当X轴偏移量大于印花宽度时裁剪后的印花宽度为0
# rotate_image = rotate_image[:, abs(x):]
# rotate_mask = rotate_mask[:, abs(x):]
# start_x = x = 0
# else:
# start_x = x
#
# if y <= 0: # 如果X轴偏移量大于0说明印花需要被裁剪至合适大小 或当Y轴偏移量大于印花宽度时裁剪后的印花宽度为0
# rotate_image = rotate_image[abs(y):, :]
# rotate_mask = rotate_mask[abs(y):, :]
# start_y = y = 0
# else:
# start_y = y
#
# # ------------------
# # 如果print-size大于image-size 则需要裁剪print
#
# if x + print_x > image_x:
# rotate_image = rotate_image[:, :image_x - x]
# rotate_mask = rotate_mask[:, :image_x - x]
#
# if y + print_y > image_y:
# rotate_image = rotate_image[:image_y - y, :]
# rotate_mask = rotate_mask[:image_y - y, :]
#
# # mask_background[start_y:y + rotate_mask.shape[0], start_x:x + rotate_mask.shape[1]] = cv2.bitwise_xor(mask_background[start_y:y + rotate_mask.shape[0], start_x:x + rotate_mask.shape[1]], rotate_mask)
# # print_background[start_y:y + rotate_image.shape[0], start_x:x + rotate_image.shape[1]] = cv2.add(print_background[start_y:y + rotate_image.shape[0], start_x:x + rotate_image.shape[1]], rotate_image)
#
# # mask_background[start_y:y + rotate_mask.shape[0], start_x:x + rotate_mask.shape[1]] = rotate_mask
# # print_background[start_y:y + rotate_image.shape[0], start_x:x + rotate_image.shape[1]] = rotate_image
# mask_background = self.stack_prin(mask_background, result['pattern_image'], rotate_mask, start_y, y, start_x, x)
# print_background = self.stack_prin(print_background, result['pattern_image'], rotate_image, start_y, y, start_x, x)
# gray_image = cv2.cvtColor(mask_background, cv2.COLOR_BGR2GRAY)
# print_background = cv2.bitwise_and(print_background, print_background, mask=gray_image)
print_mask = cv2.bitwise_and(result['mask'], cv2.cvtColor(mask_background, cv2.COLOR_BGR2GRAY)) print_mask = cv2.bitwise_and(result['mask'], cv2.cvtColor(mask_background, cv2.COLOR_BGR2GRAY))
img_fg = cv2.bitwise_or(print_background, print_background, mask=print_mask) img_fg = cv2.bitwise_or(print_background, print_background, mask=print_mask)
img_bg = cv2.bitwise_and(result['pattern_image'], result['pattern_image'], mask=cv2.bitwise_not(print_mask)) img_bg = cv2.bitwise_and(result['pattern_image'], result['pattern_image'], mask=cv2.bitwise_not(print_mask))
@@ -163,10 +88,9 @@ class PrintPainting:
tmp2 = (result['final_image'] * (temp_fg / 255)).astype(np.uint8) tmp2 = (result['final_image'] * (temp_fg / 255)).astype(np.uint8)
result['single_image'] = cv2.add(tmp1, tmp2) result['single_image'] = cv2.add(tmp1, tmp2)
if element_print['element_path_list']: if element_print:
# 2025-9-19 印花调整 印花坐标按照sketch的缩放比调整 # 2025-9-19 印花调整 印花坐标按照sketch的缩放比调整
sketch_resize_scale = result['resize_scale'] sketch_resize_scale = result['resize_scale']
print_background = np.zeros((result['final_image'].shape[0], result['final_image'].shape[1], 3), dtype=np.uint8) print_background = np.zeros((result['final_image'].shape[0], result['final_image'].shape[1], 3), dtype=np.uint8)
mask_background = np.zeros((result['final_image'].shape[0], result['final_image'].shape[1], 3), dtype=np.uint8) mask_background = np.zeros((result['final_image'].shape[0], result['final_image'].shape[1], 3), dtype=np.uint8)
for i in range(len(element_print['element_path_list'])): for i in range(len(element_print['element_path_list'])):
@@ -207,20 +131,6 @@ class PrintPainting:
print_x = rotate_image.shape[1] print_x = rotate_image.shape[1]
print_y = rotate_image.shape[0] print_y = rotate_image.shape[0]
# 有bug
# if x + print_x > image_x:
# rotate_image = rotate_image[:, :x + print_x - image_x]
# rotate_mask = rotate_mask[:, :x + print_x - image_x]
# #
# if y + print_y > image_y:
# rotate_image = rotate_image[:y + print_y - image_y]
# rotate_mask = rotate_mask[:y + print_y - image_y]
# 不能是并行
# 当前第一轮的if 108以及115是判断有没有过下界和右界。第二轮的是判断左上有没有超出。 如果这个样子的话先裁了右边再左移region就会有问题
# 先挪 再判断 最后裁剪
# 如果print旋转了 或者 print贴边了 则需要判断 判断左界和上界是否小于0
if x <= 0: if x <= 0:
rotate_image = rotate_image[:, -x:] rotate_image = rotate_image[:, -x:]
rotate_mask = rotate_mask[:, -x:] rotate_mask = rotate_mask[:, -x:]
@@ -235,9 +145,6 @@ class PrintPainting:
else: else:
start_y = y start_y = y
# ------------------
# 如果print-size大于image-size 则需要裁剪print
if x + print_x > image_x: if x + print_x > image_x:
rotate_image = rotate_image[:, :image_x - x] rotate_image = rotate_image[:, :image_x - x]
rotate_mask = rotate_mask[:, :image_x - x] rotate_mask = rotate_mask[:, :image_x - x]
@@ -246,11 +153,6 @@ class PrintPainting:
rotate_image = rotate_image[:image_y - y, :] rotate_image = rotate_image[:image_y - y, :]
rotate_mask = rotate_mask[:image_y - y, :] rotate_mask = rotate_mask[:image_y - y, :]
# mask_background[start_y:y + rotate_mask.shape[0], start_x:x + rotate_mask.shape[1]] = cv2.bitwise_xor(mask_background[start_y:y + rotate_mask.shape[0], start_x:x + rotate_mask.shape[1]], rotate_mask)
# print_background[start_y:y + rotate_image.shape[0], start_x:x + rotate_image.shape[1]] = cv2.add(print_background[start_y:y + rotate_image.shape[0], start_x:x + rotate_image.shape[1]], rotate_image)
# mask_background[start_y:y + rotate_mask.shape[0], start_x:x + rotate_mask.shape[1]] = rotate_mask
# print_background[start_y:y + rotate_image.shape[0], start_x:x + rotate_image.shape[1]] = rotate_image
mask_background = self.stack_prin(mask_background, result['pattern_image'], rotate_mask, start_y, y, start_x, x) mask_background = self.stack_prin(mask_background, result['pattern_image'], rotate_mask, start_y, y, start_x, x)
print_background = self.stack_prin(print_background, result['pattern_image'], rotate_image, start_y, y, start_x, x) print_background = self.stack_prin(print_background, result['pattern_image'], rotate_image, start_y, y, start_x, x)
@@ -298,12 +200,8 @@ class PrintPainting:
ret, mask_background = cv2.threshold(mask_background, 124, 255, cv2.THRESH_BINARY) ret, mask_background = cv2.threshold(mask_background, 124, 255, cv2.THRESH_BINARY)
print_mask = cv2.bitwise_and(result['mask'], cv2.cvtColor(mask_background, cv2.COLOR_BGR2GRAY)) print_mask = cv2.bitwise_and(result['mask'], cv2.cvtColor(mask_background, cv2.COLOR_BGR2GRAY))
img_fg = cv2.bitwise_or(print_background, print_background, mask=print_mask) img_fg = cv2.bitwise_or(print_background, print_background, mask=print_mask)
# TODO element 丢失信息
three_channel_image = cv2.merge([cv2.bitwise_not(print_mask), cv2.bitwise_not(print_mask), cv2.bitwise_not(print_mask)]) three_channel_image = cv2.merge([cv2.bitwise_not(print_mask), cv2.bitwise_not(print_mask), cv2.bitwise_not(print_mask)])
img_bg = cv2.bitwise_and(result['final_image'], three_channel_image) img_bg = cv2.bitwise_and(result['final_image'], three_channel_image)
# mask_mo = np.expand_dims(print_mask, axis=2).repeat(3, axis=2)
# gray_mo = np.expand_dims(result['gray'], axis=2).repeat(3, axis=2)
# img_fg = (img_fg * (mask_mo / 255) * (gray_mo / 255)).astype(np.uint8)
result['final_image'] = cv2.add(img_bg, img_fg) result['final_image'] = cv2.add(img_bg, img_fg)
canvas = np.full_like(result['final_image'], 255) canvas = np.full_like(result['final_image'], 255)
temp_bg = np.expand_dims(cv2.bitwise_not(result['mask']), axis=2).repeat(3, axis=2) temp_bg = np.expand_dims(cv2.bitwise_not(result['mask']), axis=2).repeat(3, axis=2)
@@ -325,27 +223,21 @@ class PrintPainting:
print_background = img1_bg + img2_fg print_background = img1_bg + img2_fg
return print_background return print_background
def painting_collection(self, painting_dict, print_dict, print_trigger=False, is_single=False): def painting_collection(self, painting_dict, print_dict):
if print_trigger: print_ = self.get_print(print_dict)
print_ = self.get_print(print_dict) painting_dict['location'] = print_['location']
painting_dict['Trigger'] = not is_single dim_max = max(painting_dict['dim_image_h'], painting_dict['dim_image_w'])
painting_dict['location'] = print_['location'] dim_pattern = (int(dim_max * print_['scale'] / 5), int(dim_max * print_['scale'] / 5))
single_mask_inv_print = self.get_mask_inv(print_['image']) gap = print_dict.get('gap', [[0, 0]])[0]
dim_max = max(painting_dict['dim_image_h'], painting_dict['dim_image_w']) painting_dict['tile_print'], painting_dict['mask_inv_print'] = tile_image(pattern=print_['image'],
dim_pattern = (int(dim_max * print_['scale'] / 5), int(dim_max * print_['scale'] / 5)) mask=print_['mask'],
if not is_single: dim=dim_pattern,
self.random_seed = random.randint(0, 1000) gap_x=gap[0],
# 如果print 模式为overall 且 有角度的话 组合的print为正方形方便裁剪 gap_y=gap[1],
if "print_angle_list" in print_dict.keys() and print_dict['print_angle_list'][0] != 0: canvas_h=painting_dict['dim_image_h'],
painting_dict['mask_inv_print'] = self.tile_image(single_mask_inv_print, dim_pattern, print_['scale'], dim_max, dim_max, painting_dict['location'], trigger=True) canvas_w=painting_dict['dim_image_w'],
painting_dict['tile_print'] = self.tile_image(print_['image'], dim_pattern, print_['scale'], dim_max, dim_max, painting_dict['location'], trigger=True) location=painting_dict['location'],
else: angle=int(print_.get('print_angle_list', [0])[0]))
painting_dict['mask_inv_print'] = self.tile_image(single_mask_inv_print, dim_pattern, print_['scale'], painting_dict['dim_image_h'], painting_dict['dim_image_w'], painting_dict['location'], trigger=True)
painting_dict['tile_print'] = self.tile_image(print_['image'], dim_pattern, print_['scale'], painting_dict['dim_image_h'], painting_dict['dim_image_w'], painting_dict['location'], trigger=True)
else:
painting_dict['mask_inv_print'] = self.tile_image(single_mask_inv_print, dim_pattern, print_['scale'], painting_dict['dim_image_h'], painting_dict['dim_image_w'], painting_dict['location'])
painting_dict['tile_print'] = self.tile_image(print_['image'], dim_pattern, print_['scale'], painting_dict['dim_image_h'], painting_dict['dim_image_w'], painting_dict['location'])
painting_dict['dim_print_h'], painting_dict['dim_print_w'] = dim_pattern
return painting_dict return painting_dict
def tile_image(self, pattern, dim, scale, dim_image_h, dim_image_w, location, trigger=False): def tile_image(self, pattern, dim, scale, dim_image_h, dim_image_w, location, trigger=False):
@@ -374,51 +266,37 @@ class PrintPainting:
mask_inv = cv2.inRange(print_tile, lower, upper) mask_inv = cv2.inRange(print_tile, lower, upper)
return mask_inv return mask_inv
else: else:
# bg_color = cv2.cvtColor(print_, cv2.COLOR_BGR2LAB)[0][0]
# print_tile = cv2.cvtColor(print_, cv2.COLOR_BGR2LAB)
# bg_l, bg_a, bg_b = bg_color[0], bg_color[1], bg_color[2]
# bg_L_high, bg_L_low = self.get_low_high_lab(bg_l, L=True)
# bg_a_high, bg_a_low = self.get_low_high_lab(bg_a)
# bg_b_high, bg_b_low = self.get_low_high_lab(bg_b)
# lower = np.array([bg_L_low, bg_a_low, bg_b_low])
# upper = np.array([bg_L_high, bg_a_high, bg_b_high])
# print_tile = cv2.cvtColor(print_, cv2.COLOR_BGR2LAB)
# mask_inv = cv2.cvtColor(print_tile, cv2.COLOR_BGR2GRAY)
# mask_inv = cv2.cvtColor(print_, cv2.COLOR_BGR2GRAY)
mask_inv = np.zeros(print_.shape[:2], dtype=np.uint8) mask_inv = np.zeros(print_.shape[:2], dtype=np.uint8)
return mask_inv return mask_inv
@staticmethod @staticmethod
def printpaint(result, painting_dict, print_=False): def printpaint(result, painting_dict, print_=False):
if print_:
if print_ and painting_dict['Trigger']:
print_mask = cv2.bitwise_and(result['mask'], cv2.bitwise_not(painting_dict['mask_inv_print'])) print_mask = cv2.bitwise_and(result['mask'], cv2.bitwise_not(painting_dict['mask_inv_print']))
img_fg = cv2.bitwise_and(painting_dict['tile_print'], painting_dict['tile_print'], mask=print_mask) img_fg = cv2.bitwise_and(painting_dict['tile_print'], painting_dict['tile_print'], mask=print_mask)
else: else:
print_mask = result['mask'] print_mask = result['mask']
img_fg = result['final_image'] img_fg = result['final_image']
if print_ and not painting_dict['Trigger']: # if print_ and not painting_dict['Trigger']:
index_ = None # index_ = None
try: # try:
index_ = len(painting_dict['location']) # index_ = len(painting_dict['location'])
except: # except:
assert f'there must be parameter of location if choose IfSingle' # assert f'there must be parameter of location if choose IfSingle'
#
for i in range(index_): # for i in range(index_):
start_h, start_w = int(painting_dict['location'][i][1]), int(painting_dict['location'][i][0]) # start_h, start_w = int(painting_dict['location'][i][1]), int(painting_dict['location'][i][0])
#
length_h = min(start_h + painting_dict['dim_print_h'], img_fg.shape[0]) # length_h = min(start_h + painting_dict['dim_print_h'], img_fg.shape[0])
length_w = min(start_w + painting_dict['dim_print_w'], img_fg.shape[1]) # length_w = min(start_w + painting_dict['dim_print_w'], img_fg.shape[1])
#
change_region = img_fg[start_h: length_h, start_w: length_w, :] # change_region = img_fg[start_h: length_h, start_w: length_w, :]
# problem in change_mask # # problem in change_mask
change_mask = print_mask[start_h: length_h, start_w: length_w] # change_mask = print_mask[start_h: length_h, start_w: length_w]
# get real part into change mask # # get real part into change mask
_, change_mask = cv2.threshold(change_mask, 220, 255, cv2.THRESH_BINARY) # _, change_mask = cv2.threshold(change_mask, 220, 255, cv2.THRESH_BINARY)
cv2.bitwise_not(painting_dict['mask_inv_print']) # cv2.bitwise_not(painting_dict['mask_inv_print'])
img_fg[start_h:start_h + painting_dict['dim_print_h'], start_w:start_w + painting_dict['dim_print_w'], :] = change_region # img_fg[start_h:start_h + painting_dict['dim_print_h'], start_w:start_w + painting_dict['dim_print_w'], :] = change_region
clothes_mask_print = cv2.bitwise_not(print_mask) clothes_mask_print = cv2.bitwise_not(print_mask)
@@ -440,21 +318,21 @@ class PrintPainting:
image = oss_get_image(oss_client=self.minio_client, bucket=bucket_name, object_name=object_name, data_type="PIL") image = oss_get_image(oss_client=self.minio_client, bucket=bucket_name, object_name=object_name, data_type="PIL")
# 判断图片格式如果是RGBA 则贴在一张纯白图片上 防止透明转黑 # 判断图片格式如果是RGBA 则贴在一张纯白图片上 防止透明转黑
if image.mode == "RGBA": if image.mode == "RGBA":
mask_pil = image.split()[3]
new_background = Image.new('RGB', image.size, (255, 255, 255)) new_background = Image.new('RGB', image.size, (255, 255, 255))
new_background.paste(image, mask=image.split()[3]) new_background.paste(image, mask=image.split()[3])
image = new_background image = new_background
else:
mask_pil = Image.new('L', image.size, 255) # L=灰度图255=纯白
print_dict['image'] = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR) print_dict['image'] = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)
print_dict['mask'] = cv2.threshold(np.array(mask_pil), 127, 255, cv2.THRESH_BINARY)[1]
return print_dict return print_dict
def crop_image(self, image, image_size_h, image_size_w, location, print_shape): def crop_image(self, image, image_size_h, image_size_w, location, print_shape):
print_w = print_shape[1] print_w = print_shape[1]
print_h = print_shape[0] print_h = print_shape[0]
random.seed(self.random_seed)
# logging.info(f'overall print location : {location}')
# x_offset = random.randint(0, image.shape[0] - image_size_h)
# y_offset = random.randint(0, image.shape[1] - image_size_w)
# 1.拿到偏移量后和resize后的print宽高取余 得到真正偏移量 # 1.拿到偏移量后和resize后的print宽高取余 得到真正偏移量
# 偏移量增加2分之print.w 使坐标位于图中间 如果要位于左上角删除+ print_w // 2 即可 # 偏移量增加2分之print.w 使坐标位于图中间 如果要位于左上角删除+ print_w // 2 即可
x_offset = print_w - int(location[0][1] % print_w) + print_w // 2 x_offset = print_w - int(location[0][1] % print_w) + print_w // 2
@@ -596,3 +474,118 @@ class PrintPainting:
cropped_img = resized_img[start_y:start_y + target_height, :] cropped_img = resized_img[start_y:start_y + target_height, :]
return cropped_img return cropped_img
def tile_image(pattern, mask, dim, gap_x, gap_y, canvas_h, canvas_w, location, angle=0):
"""
按照指定的 X/Y 间距平铺印花,并支持旋转
【修改版】以被平铺图案的【中心】作为平铺基准点
:param location: [[center_y, center_x]] → 第一个图案中心的坐标
:param angle: 旋转角度 (度数, 逆时针)
"""
# 1. 确保输入是 RGBA
if pattern.shape[2] == 3:
pattern = cv2.cvtColor(pattern, cv2.COLOR_BGR2BGRA)
# 2. 缩放与旋转印花
resized_p = cv2.resize(pattern, dim, interpolation=cv2.INTER_AREA)
rotated_p = rotate_image(resized_p, angle)
p_h, p_w = rotated_p.shape[:2]
# 3. 创建透明单元格(图案放在单元格中心)
cell_h = p_h + gap_y
cell_w = p_w + gap_x
unit_cell = np.zeros((cell_h, cell_w, 4), dtype=np.uint8)
# 计算图案在单元格中的左上角位置(让图案居中)
start_y = (cell_h - p_h) // 2
start_x = (cell_w - p_w) // 2
unit_cell[start_y:start_y + p_h, start_x:start_x + p_w, :] = rotated_p
# 4. 执行平铺
tiles_y = (canvas_h // cell_h) + 3 # 多加一点余量更安全
tiles_x = (canvas_w // cell_w) + 3
full_tiled = np.tile(unit_cell, (tiles_y, tiles_x, 1))
# 5. 计算偏移(关键修改:以中心为基准)
center_y, center_x = location[0][0], location[0][1] # 第一个图案的中心位置
# 计算从哪个位置开始裁剪,才能让中心落在指定坐标
offset_y = int((center_y - (p_h // 2)) % cell_h)
offset_x = int((center_x - (p_w // 2)) % cell_w)
tiled_layer = full_tiled[offset_y: offset_y + canvas_h,
offset_x: offset_x + canvas_w]
# 6. 创建纯白色背景并合成(保持你原来的风格)
white_background = np.full((canvas_h, canvas_w, 3), 255, dtype=np.uint8)
tiled_bgr = tiled_layer[:, :, :3]
alpha_mask = tiled_layer[:, :, 3] / 255.0
alpha_mask = cv2.merge([alpha_mask, alpha_mask, alpha_mask])
tiled_print = (tiled_bgr * alpha_mask + white_background * (1 - alpha_mask)).astype(np.uint8)
# ====================== 处理 Mask ======================
# Mask 也同样居中处理
resized_mask = cv2.resize(mask, dim, interpolation=cv2.INTER_NEAREST)
rotated_mask = rotate_image(resized_mask, angle) # 注意mask也需要旋转
unit_mask = np.zeros((cell_h, cell_w), dtype=np.uint8)
unit_mask[start_y:start_y + p_h, start_x:start_x + p_w] = rotated_mask
full_mask_tiled = np.tile(unit_mask, (tiles_y, tiles_x))
tiled_mask = full_mask_tiled[offset_y: offset_y + canvas_h,
offset_x: offset_x + canvas_w]
return tiled_print, cv2.bitwise_not(tiled_mask)
def rotate_image(image, angle):
"""
旋转图片并保持完整内容(自动扩大画布)
"""
if angle == 0:
return image
(h, w) = image.shape[:2]
(cX, cY) = (w // 2, h // 2)
# 获取旋转矩阵
M = cv2.getRotationMatrix2D((cX, cY), angle, 1.0)
# 计算旋转后新边界的 sine 和 cosine
cos = np.abs(M[0, 0])
sin = np.abs(M[0, 1])
# 计算新的画布尺寸
nW = int((h * sin) + (w * cos))
nH = int((h * cos) + (w * sin))
# 调整旋转矩阵以考虑平移
M[0, 2] += (nW / 2) - cX
M[1, 2] += (nH / 2) - cY
# 执行旋转
return cv2.warpAffine(image, M, (nW, nH))
def crop_image(image, image_size_h, image_size_w, location, print_shape):
print_w = print_shape[1]
print_h = print_shape[0]
# 1.拿到偏移量后和resize后的print宽高取余 得到真正偏移量
# 偏移量增加2分之print.w 使坐标位于图中间 如果要位于左上角删除+ print_w // 2 即可
x_offset = print_w - int(location[0][1] % print_w) + print_w // 2
y_offset = print_h - int(location[0][0] % print_h) + print_h // 2
# y_offset = int(location[0][0])
# x_offset = int(location[0][1])
if len(image.shape) == 2:
image = image[x_offset: x_offset + image_size_h, y_offset: y_offset + image_size_w]
elif len(image.shape) == 3:
image = image[x_offset: x_offset + image_size_h, y_offset: y_offset + image_size_w, :]
return image

View File

@@ -34,22 +34,25 @@ class Segmentation:
result['mask'] = result['front_mask'] + result['back_mask'] result['mask'] = result['front_mask'] + result['back_mask']
else: else:
# preview 过模型 不缓存 # preview 过模型 不缓存
if "preview_submit" in result.keys() and result['preview_submit'] == "preview": if result.get("design_type", None) == "merge":
# 推理获得seg 结果
seg_result = get_seg_result(result['image']) seg_result = get_seg_result(result['image'])
# submit 过模型 缓存 # 默认design 模式 - 过模型 缓存
elif "preview_submit" in result.keys() and result['preview_submit'] == "submit": # elif result.get("design_type", None) == "submit":
# 推理获得seg 结果 # 推理获得seg 结果
seg_result = get_seg_result(result['image']) # seg_result = get_seg_result(result['image'])
self.save_seg_result(seg_result, result['image_id']) # self.save_seg_result(seg_result, result['image_id'])
# null 正常流程 加载本地缓存 无缓存则过模型
# 默认模式- 加载模型,找不到则过模型推理,推理后保存到本地
else: else:
# 本地查询seg 缓存是否存在 # 本地查询seg 缓存是否存在
_, seg_result = self.load_seg_result(result["image_id"]) _, seg_result = self.load_seg_result(result["image_id"])
# 判断缓存和实际图片size是否相同 # 判断缓存和实际图片size是否相同
_ = False
if not _ or result["image"].shape[:2] != seg_result.shape: if not _ or result["image"].shape[:2] != seg_result.shape:
# 推理获得seg 结果 # 推理获得seg 结果
seg_result = get_seg_result(result['image']) seg_result = get_seg_result(result['image'])
if result['name'] == 'others':
seg_result = seg_result.clip(max=1)
self.save_seg_result(seg_result, result['image_id']) self.save_seg_result(seg_result, result['image_id'])
result['seg_result'] = seg_result result['seg_result'] = seg_result

View File

@@ -4,6 +4,7 @@ import logging
import cv2 import cv2
import numpy as np import numpy as np
from PIL import Image from PIL import Image
from celery.bin.result import result
from app.service.design_fast.utils.conversion_image import rgb_to_rgba from app.service.design_fast.utils.conversion_image import rgb_to_rgba
from app.service.design_fast.utils.transparent import sketch_to_transparent from app.service.design_fast.utils.transparent import sketch_to_transparent
@@ -19,105 +20,122 @@ class Split(object):
def __call__(self, result): def __call__(self, result):
try: try:
if result['name'] in ('outwear', 'dress', 'blouse', 'skirt', 'trousers', 'tops', 'bottoms', 'others'): if result['name'] in ('outwear', 'dress', 'blouse', 'skirt', 'trousers', 'tops', 'bottoms', 'others'):
ori_front_mask = result['front_mask'].copy() if result.get('design_type', None) == 'merge':
ori_back_mask = result['back_mask'].copy() ori_front_mask = result['front_mask'].copy()
ori_back_mask = result['back_mask'].copy()
if result['resize_scale'][0] == 1.0 and result['resize_scale'][1] == 1.0: if result['resize_scale'][0] == 1.0 and result['resize_scale'][1] == 1.0:
front_mask = result['front_mask'] front_mask = result['front_mask']
back_mask = result['back_mask'] back_mask = result['back_mask']
else:
height, width = result['front_mask'].shape[:2]
new_width = int(width * result['resize_scale'][0])
new_height = int(height * result['resize_scale'][1])
front_mask = cv2.resize(result['front_mask'], (new_width, new_height), interpolation=cv2.INTER_AREA)
back_mask = cv2.resize(result['back_mask'], (new_width, new_height), interpolation=cv2.INTER_AREA)
rgba_image = rgb_to_rgba(result['final_image'], front_mask + back_mask)
new_size = (int(rgba_image.shape[1] * result["scale"]), int(rgba_image.shape[0] * result["scale"]))
rgba_image = cv2.resize(rgba_image, new_size, interpolation=cv2.INTER_AREA)
result_front_image = np.zeros_like(rgba_image)
front_mask = cv2.resize(front_mask, new_size, interpolation=cv2.INTER_AREA)
result_front_image[front_mask != 0] = rgba_image[front_mask != 0]
result_front_image_pil = Image.fromarray(cv2.cvtColor(result_front_image, cv2.COLOR_BGR2RGBA))
if 'transparent' in result.keys():
# 用户自选区域transparent
transparent = result['transparent']
if transparent['mask_url'] is not None and transparent['mask_url'] != "":
# 预处理用户自选区mask
seg_mask = oss_get_image(oss_client=self.minio_client, bucket=transparent['mask_url'].split('/')[0], object_name=transparent['mask_url'][transparent['mask_url'].find('/') + 1:], data_type="cv2")
seg_mask = cv2.resize(seg_mask, new_size, interpolation=cv2.INTER_AREA)
# 转换颜色空间为 RGBOpenCV 默认是 BGR
image_rgb = cv2.cvtColor(seg_mask, cv2.COLOR_BGR2RGB)
r, g, b = cv2.split(image_rgb)
blue_mask = b > r
# 创建红色和绿色掩码
transparent_mask = np.array(blue_mask, dtype=np.uint8) * 255
result_front_image_pil = sketch_to_transparent(result_front_image_pil, transparent_mask, transparent["scale"])
else: else:
result_front_image_pil = sketch_to_transparent(result_front_image_pil, front_mask, transparent["scale"]) height, width = result['front_mask'].shape[:2]
result['front_image'], result["front_image_url"], _ = upload_png_mask(self.minio_client, result_front_image_pil, f'{generate_uuid()}', mask=None) new_width = int(width * result['resize_scale'][0])
new_height = int(height * result['resize_scale'][1])
# 前片部分 (红图部分) front_mask = cv2.resize(result['front_mask'], (new_width, new_height), interpolation=cv2.INTER_AREA)
# height, width = front_mask.shape back_mask = cv2.resize(result['back_mask'], (new_width, new_height), interpolation=cv2.INTER_AREA)
# mask_image = np.zeros((height, width, 3)) result['merge_image'] = cv2.resize(result['merge_image'], (new_width, new_height), interpolation=cv2.INTER_AREA)
# mask_image[front_mask != 0] = [0, 0, 255]
# 切换为原始图片尺寸------------------------------- rgba_image = rgb_to_rgba(result['merge_image'], front_mask + back_mask)
height, width = ori_front_mask.shape new_size = (int(rgba_image.shape[1] * result["scale"]), int(rgba_image.shape[0] * result["scale"]))
mask_image = np.zeros((height, width, 3)) rgba_image = cv2.resize(rgba_image, new_size, interpolation=cv2.INTER_AREA)
mask_image[ori_front_mask != 0] = [0, 0, 255] result_front_image = np.zeros_like(rgba_image)
# ----------------------------------------------- front_mask = cv2.resize(front_mask, new_size, interpolation=cv2.INTER_AREA)
result_front_image[front_mask != 0] = rgba_image[front_mask != 0]
result_front_image_pil = Image.fromarray(cv2.cvtColor(result_front_image, cv2.COLOR_BGR2RGBA))
result['front_image'], result["front_image_url"], _ = upload_png_mask(self.minio_client, result_front_image_pil, f'{generate_uuid()}', mask=None)
# if result["name"] in ('blouse', 'dress', 'outwear', 'tops'): height, width = ori_front_mask.shape
# result_back_image = np.zeros_like(rgba_image) mask_image = np.zeros((height, width, 3))
# back_mask = cv2.resize(back_mask, new_size, interpolation=cv2.INTER_AREA) mask_image[ori_front_mask != 0] = [0, 0, 255]
# result_back_image[back_mask != 0] = rgba_image[back_mask != 0] mask_image[ori_back_mask != 0] = [0, 255, 0]
# result_back_image_pil = Image.fromarray(cvtColor(result_back_image, COLOR_BGR2RGBA)) rbga_mask = rgb_to_rgba(mask_image, ori_front_mask + ori_back_mask)
# result['back_image'], result["back_image_url"], _ = upload_png_mask(self.minio_client, result_back_image_pil, f'{generate_uuid()}', mask=None) mask_pil = Image.fromarray(cv2.cvtColor(rbga_mask.astype(np.uint8), cv2.COLOR_BGR2RGBA))
# mask_image[back_mask != 0] = [0, 255, 0] image_data = io.BytesIO()
# mask_pil.save(image_data, format='PNG')
# rbga_mask = rgb_to_rgba(mask_image, front_mask + back_mask) image_data.seek(0)
# mask_pil = Image.fromarray(cvtColor(rbga_mask.astype(np.uint8), COLOR_BGR2RGBA)) image_bytes = image_data.read()
# image_data = io.BytesIO() req = oss_upload_image(oss_client=self.minio_client, bucket="aida-clothing", object_name=f"mask/mask_{generate_uuid()}.png", image_bytes=image_bytes)
# mask_pil.save(image_data, format='PNG') result['mask_url'] = req.bucket_name + "/" + req.object_name
# image_data.seek(0)
# image_bytes = image_data.read()
# req = oss_upload_image(oss_client=self.minio_client, bucket=AIDA_CLOTHING, object_name=f"mask/mask_{generate_uuid()}.png", image_bytes=image_bytes)
# result['mask_url'] = req.bucket_name + "/" + req.object_name
# else:
# rbga_mask = rgb_to_rgba(mask_image, front_mask)
# mask_pil = Image.fromarray(cvtColor(rbga_mask.astype(np.uint8), COLOR_BGR2RGBA))
# image_data = io.BytesIO()
# mask_pil.save(image_data, format='PNG')
# image_data.seek(0)
# image_bytes = image_data.read()
# req = oss_upload_image(oss_client=self.minio_client, bucket=AIDA_CLOTHING, object_name=f"mask/mask_{generate_uuid()}.png", image_bytes=image_bytes)
# result['mask_url'] = req.bucket_name + "/" + req.object_name
# result['back_image'] = None
# result["back_image_url"] = None
# # result["back_mask_url"] = None
# # result['back_mask_image'] = None
result_back_image = np.zeros_like(rgba_image)
back_mask = cv2.resize(back_mask, new_size, interpolation=cv2.INTER_AREA)
result_back_image[back_mask != 0] = rgba_image[back_mask != 0]
result_back_image_pil = Image.fromarray(cv2.cvtColor(result_back_image, cv2.COLOR_BGR2RGBA))
result['back_image'], result["back_image_url"], _ = upload_png_mask(self.minio_client, result_back_image_pil, f'{generate_uuid()}', mask=None)
# mask_image[back_mask != 0] = [0, 255, 0] result_back_image = np.zeros_like(rgba_image)
mask_image[ori_back_mask != 0] = [0, 255, 0] back_mask = cv2.resize(back_mask, new_size, interpolation=cv2.INTER_AREA)
result_back_image[back_mask != 0] = rgba_image[back_mask != 0]
result_back_image_pil = Image.fromarray(cv2.cvtColor(result_back_image, cv2.COLOR_BGR2RGBA))
result['back_image'], result["back_image_url"], _ = upload_png_mask(self.minio_client, result_back_image_pil, f'{generate_uuid()}', mask=None)
return result
else:
ori_front_mask = result['front_mask'].copy()
ori_back_mask = result['back_mask'].copy()
rbga_mask = rgb_to_rgba(mask_image, ori_front_mask + ori_back_mask) if result['resize_scale'][0] == 1.0 and result['resize_scale'][1] == 1.0:
mask_pil = Image.fromarray(cv2.cvtColor(rbga_mask.astype(np.uint8), cv2.COLOR_BGR2RGBA)) front_mask = result['front_mask']
image_data = io.BytesIO() back_mask = result['back_mask']
mask_pil.save(image_data, format='PNG') else:
image_data.seek(0) height, width = result['front_mask'].shape[:2]
image_bytes = image_data.read() new_width = int(width * result['resize_scale'][0])
req = oss_upload_image(oss_client=self.minio_client, bucket="aida-clothing", object_name=f"mask/mask_{generate_uuid()}.png", image_bytes=image_bytes) new_height = int(height * result['resize_scale'][1])
result['mask_url'] = req.bucket_name + "/" + req.object_name
front_mask = cv2.resize(result['front_mask'], (new_width, new_height), interpolation=cv2.INTER_AREA)
back_mask = cv2.resize(result['back_mask'], (new_width, new_height), interpolation=cv2.INTER_AREA)
rgba_image = rgb_to_rgba(result['final_image'], front_mask + back_mask)
new_size = (int(rgba_image.shape[1] * result["scale"]), int(rgba_image.shape[0] * result["scale"]))
rgba_image = cv2.resize(rgba_image, new_size, interpolation=cv2.INTER_AREA)
result_front_image = np.zeros_like(rgba_image)
front_mask = cv2.resize(front_mask, new_size, interpolation=cv2.INTER_AREA)
result_front_image[front_mask != 0] = rgba_image[front_mask != 0]
result_front_image_pil = Image.fromarray(cv2.cvtColor(result_front_image, cv2.COLOR_BGR2RGBA))
if 'transparent' in result.keys():
# 用户自选区域transparent
transparent = result['transparent']
if transparent['mask_url'] is not None and transparent['mask_url'] != "":
# 预处理用户自选区mask
seg_mask = oss_get_image(oss_client=self.minio_client, bucket=transparent['mask_url'].split('/')[0], object_name=transparent['mask_url'][transparent['mask_url'].find('/') + 1:], data_type="cv2")
seg_mask = cv2.resize(seg_mask, new_size, interpolation=cv2.INTER_AREA)
# 转换颜色空间为 RGBOpenCV 默认是 BGR
image_rgb = cv2.cvtColor(seg_mask, cv2.COLOR_BGR2RGB)
r, g, b = cv2.split(image_rgb)
blue_mask = b > r
# 创建红色和绿色掩码
transparent_mask = np.array(blue_mask, dtype=np.uint8) * 255
result_front_image_pil = sketch_to_transparent(result_front_image_pil, transparent_mask, transparent["scale"])
else:
result_front_image_pil = sketch_to_transparent(result_front_image_pil, front_mask, transparent["scale"])
result['front_image'], result["front_image_url"], _ = upload_png_mask(self.minio_client, result_front_image_pil, f'{generate_uuid()}', mask=None)
height, width = ori_front_mask.shape
mask_image = np.zeros((height, width, 3))
mask_image[ori_front_mask != 0] = [0, 0, 255]
result_back_image = np.zeros_like(rgba_image)
back_mask = cv2.resize(back_mask, new_size, interpolation=cv2.INTER_AREA)
result_back_image[back_mask != 0] = rgba_image[back_mask != 0]
result_back_image_pil = Image.fromarray(cv2.cvtColor(result_back_image, cv2.COLOR_BGR2RGBA))
result['back_image'], result["back_image_url"], _ = upload_png_mask(self.minio_client, result_back_image_pil, f'{generate_uuid()}', mask=None)
# mask_image[back_mask != 0] = [0, 255, 0]
mask_image[ori_back_mask != 0] = [0, 255, 0]
rbga_mask = rgb_to_rgba(mask_image, ori_front_mask + ori_back_mask)
mask_pil = Image.fromarray(cv2.cvtColor(rbga_mask.astype(np.uint8), cv2.COLOR_BGR2RGBA))
image_data = io.BytesIO()
mask_pil.save(image_data, format='PNG')
image_data.seek(0)
image_bytes = image_data.read()
req = oss_upload_image(oss_client=self.minio_client, bucket="aida-clothing", object_name=f"mask/mask_{generate_uuid()}.png", image_bytes=image_bytes)
result['mask_url'] = req.bucket_name + "/" + req.object_name
# 创建中间图层(未分割图层) 1.color + overall_print 2.color + overall_print + print
result_pattern_overall_image_pil = Image.fromarray(cv2.cvtColor(rgb_to_rgba(result['no_seg_sketch_overall'], ori_front_mask + ori_back_mask), cv2.COLOR_BGR2RGBA))
result['pattern_overall_image'], result['pattern_overall_image_url'], _ = upload_png_mask(self.minio_client, result_pattern_overall_image_pil, f'{generate_uuid()}')
result_pattern_print_image_pil = Image.fromarray(cv2.cvtColor(rgb_to_rgba(result['no_seg_sketch_print'], ori_front_mask + ori_back_mask), cv2.COLOR_BGR2RGBA))
result['pattern_print_image'], result['pattern_print_image_url'], _ = upload_png_mask(self.minio_client, result_pattern_print_image_pil, f'{generate_uuid()}')
return result
else: else:
ori_front_mask, ori_back_mask = None, None ori_front_mask, ori_back_mask = None, None
# 创建中间图层(未分割图层) 1.color + overall_print 2.color + overall_print + print # 创建中间图层(未分割图层) 1.color + overall_print 2.color + overall_print + print
@@ -127,5 +145,6 @@ class Split(object):
result_pattern_print_image_pil = Image.fromarray(cv2.cvtColor(rgb_to_rgba(result['no_seg_sketch_print'], ori_front_mask + ori_back_mask), cv2.COLOR_BGR2RGBA)) result_pattern_print_image_pil = Image.fromarray(cv2.cvtColor(rgb_to_rgba(result['no_seg_sketch_print'], ori_front_mask + ori_back_mask), cv2.COLOR_BGR2RGBA))
result['pattern_print_image'], result['pattern_print_image_url'], _ = upload_png_mask(self.minio_client, result_pattern_print_image_pil, f'{generate_uuid()}') result['pattern_print_image'], result['pattern_print_image_url'], _ = upload_png_mask(self.minio_client, result_pattern_print_image_pil, f'{generate_uuid()}')
return result return result
except Exception as e: except Exception as e:
logging.warning(f"split runtime exception : {e} image_id : {result['image_id']}") logging.warning(f"split runtime exception : {e} image_id : {result['image_id']}")

View File

@@ -10,12 +10,12 @@
import logging import logging
import cv2 import cv2
import mmcv
import numpy as np import numpy as np
import torch import torch
import tritonclient.http as httpclient import tritonclient.http as httpclient
from app.core.config import DESIGN_MODEL_URL, DESIGN_MODEL_NAME from app.core.config import DESIGN_MODEL_URL, DESIGN_MODEL_NAME
from app.service.utils.image_normalize import my_imnormalize
""" """
keypoint keypoint
@@ -24,14 +24,14 @@ from app.core.config import DESIGN_MODEL_URL, DESIGN_MODEL_NAME
def keypoint_preprocess(img_path): def keypoint_preprocess(img_path):
img = mmcv.imread(img_path) img = img_path
img = cv2.copyMakeBorder(img, 25, 25, 25, 25, cv2.BORDER_CONSTANT, value=[255, 255, 255]) img = cv2.copyMakeBorder(img, 25, 25, 25, 25, cv2.BORDER_CONSTANT, value=[255, 255, 255])
img_scale = (256, 256) img_scale = (256, 256)
h, w = img.shape[:2] h, w = img.shape[:2]
img = cv2.resize(img, img_scale) img = cv2.resize(img, img_scale)
w_scale = img_scale[0] / w w_scale = img_scale[0] / w
h_scale = img_scale[1] / h h_scale = img_scale[1] / h
img = mmcv.imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True) img = my_imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True)
preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0) preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0)
return preprocessed_img, (w_scale, h_scale) return preprocessed_img, (w_scale, h_scale)
@@ -78,7 +78,7 @@ def keypoint_postprocess(output, scale_factor):
# KNet # KNet
def seg_preprocess(img_path): def seg_preprocess(img_path):
img = mmcv.imread(img_path) img = img_path
ori_shape = img.shape[:2] ori_shape = img.shape[:2]
img_scale_w, img_scale_h = ori_shape img_scale_w, img_scale_h = ori_shape
if ori_shape[0] > 1024: if ori_shape[0] > 1024:
@@ -87,12 +87,12 @@ def seg_preprocess(img_path):
img_scale_h = 1024 img_scale_h = 1024
# 如果图片size任意一边 大于 1024 则会resize 成1024 # 如果图片size任意一边 大于 1024 则会resize 成1024
if ori_shape != (img_scale_w, img_scale_h): if ori_shape != (img_scale_w, img_scale_h):
# mmcv.imresize(img, img_scale_h, img_scale_w) # 老代码 引以为戒!哈哈哈~ h和w写反了 # my_imnormalize(img, img_scale_h, img_scale_w) # 老代码 引以为戒!哈哈哈~ h和w写反了
img = cv2.resize(img, (img_scale_h, img_scale_w)) img = cv2.resize(img, (img_scale_h, img_scale_w))
# 扩充25的白边 # 扩充25的白边
img = cv2.copyMakeBorder(img, 25, 25, 25, 25, cv2.BORDER_CONSTANT, value=[255, 255, 255]) img = cv2.copyMakeBorder(img, 25, 25, 25, 25, cv2.BORDER_CONSTANT, value=[255, 255, 255])
img = mmcv.imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True) img = my_imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True)
preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0) preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0)
return preprocessed_img, ori_shape return preprocessed_img, ori_shape

View File

@@ -23,20 +23,23 @@ def organize_clothing(layer):
front_layer = dict(priority=layer['priority'] if layer.get("layer_order", False) else PRIORITY_DICT.get(f'{layer["name"].lower()}_front', None), front_layer = dict(priority=layer['priority'] if layer.get("layer_order", False) else PRIORITY_DICT.get(f'{layer["name"].lower()}_front', None),
name=f'{layer["name"].lower()}_front', name=f'{layer["name"].lower()}_front',
image=layer["front_image"], image=layer["front_image"],
merge_image=layer["front_image"],
# mask_image=layer['front_mask_image'], # mask_image=layer['front_mask_image'],
image_url=layer['front_image_url'], image_url=layer['front_image_url'],
mask_url=layer['mask_url'], mask_url=layer.get("mask_url", None),
sacle=layer['scale'], sacle=layer['scale'],
clothes_keypoint=layer['clothes_keypoint'], clothes_keypoint=layer['clothes_keypoint'],
position=start_point, position=start_point,
resize_scale=layer["resize_scale"], resize_scale=layer["resize_scale"],
mask=cv2.resize(layer['mask'], layer["front_image"].size), mask=cv2.resize(layer['mask'], layer["front_image"].size),
gradient_string=layer['gradient_string'] if 'gradient_string' in layer.keys() else "", gradient_string=layer['gradient_string'] if 'gradient_string' in layer.keys() else "",
pattern_overall_image_url=layer['pattern_overall_image_url'], pattern_overall_image_url=layer.get('pattern_overall_image_url', None),
pattern_print_image_url=layer['pattern_print_image_url'], pattern_print_image_url=layer.get('pattern_print_image_url', None),
pattern_image=layer['pattern_image'], pattern_image=layer.get('pattern_image', None),
# back_perspective_url=layer['back_perspective_url'] if 'back_perspective_url' in layer.keys() else "" # back_perspective_url=layer['back_perspective_url'] if 'back_perspective_url' in layer.keys() else ""
transpose=layer.get("transpose", [1, 1]), # 默认为1, 1代表不镜像
rotate=layer.get('rotate', 0),
) )
# 后片数据 # 后片数据
back_layer = dict(priority=-layer.get("priority", 0) if layer.get("layer_order", False) else PRIORITY_DICT.get(f'{layer["name"].lower()}_back', None), back_layer = dict(priority=-layer.get("priority", 0) if layer.get("layer_order", False) else PRIORITY_DICT.get(f'{layer["name"].lower()}_back', None),
@@ -44,16 +47,18 @@ def organize_clothing(layer):
image=layer["back_image"], image=layer["back_image"],
# mask_image=layer['back_mask_image'], # mask_image=layer['back_mask_image'],
image_url=layer['back_image_url'], image_url=layer['back_image_url'],
mask_url=layer['mask_url'], mask_url=layer.get('mask_url', None),
sacle=layer['scale'], sacle=layer['scale'],
clothes_keypoint=layer['clothes_keypoint'], clothes_keypoint=layer['clothes_keypoint'],
position=start_point, position=start_point,
resize_scale=layer["resize_scale"], resize_scale=layer["resize_scale"],
mask=cv2.resize(layer['mask'], layer["front_image"].size), mask=cv2.resize(layer['mask'], layer["front_image"].size),
gradient_string=layer['gradient_string'] if 'gradient_string' in layer.keys() else "", gradient_string=layer['gradient_string'] if 'gradient_string' in layer.keys() else "",
pattern_overall_image_url=layer['pattern_overall_image_url'], pattern_overall_image_url=layer.get('pattern_overall_image_url', None),
pattern_print_image_url=layer['pattern_print_image_url'], pattern_print_image_url=layer.get('pattern_print_image_url', None),
# back_perspective_url=layer['back_perspective_url'] if 'back_perspective_url' in layer.keys() else "" # back_perspective_url=layer['back_perspective_url'] if 'back_perspective_url' in layer.keys() else ""
transpose=layer.get("transpose", [1, 1]), # 默认为1, 1代表不镜像
rotate=layer.get('rotate', 0),
) )
return front_layer, back_layer return front_layer, back_layer
@@ -76,17 +81,19 @@ def organize_others(layer):
image=layer["front_image"], image=layer["front_image"],
# mask_image=layer['front_mask_image'], # mask_image=layer['front_mask_image'],
image_url=layer['front_image_url'], image_url=layer['front_image_url'],
mask_url=layer['mask_url'], mask_url=layer.get('mask_url', None),
sacle=layer['scale'], sacle=layer['scale'],
clothes_keypoint=(0, 0), clothes_keypoint=(0, 0),
position=start_point, position=start_point,
resize_scale=layer["resize_scale"], resize_scale=layer["resize_scale"],
mask=cv2.resize(layer['mask'], layer["front_image"].size), mask=cv2.resize(layer['mask'], layer["front_image"].size),
gradient_string=layer['gradient_string'] if 'gradient_string' in layer.keys() else "", gradient_string=layer['gradient_string'] if 'gradient_string' in layer.keys() else "",
pattern_overall_image_url=layer['pattern_overall_image_url'], pattern_overall_image_url=layer.get('pattern_overall_image_url', None),
pattern_print_image_url=layer['pattern_print_image_url'], pattern_print_image_url=layer.get('pattern_print_image_url', None),
pattern_image=layer['pattern_image'], pattern_image=layer.get('pattern_image', None),
# back_perspective_url=layer['back_perspective_url'] if 'back_perspective_url' in layer.keys() else "" # back_perspective_url=layer['back_perspective_url'] if 'back_perspective_url' in layer.keys() else ""
transpose=layer.get("transpose", [1, 1]), # 默认为1, 1代表不镜像
rotate=layer.get('rotate', 0),
) )
# 后片数据 # 后片数据
back_layer = dict(priority=-layer.get("priority", 0) if layer.get("layer_order", False) else PRIORITY_DICT.get(f'{layer["name"].lower()}_back', None), back_layer = dict(priority=-layer.get("priority", 0) if layer.get("layer_order", False) else PRIORITY_DICT.get(f'{layer["name"].lower()}_back', None),
@@ -94,16 +101,18 @@ def organize_others(layer):
image=layer["back_image"], image=layer["back_image"],
# mask_image=layer['back_mask_image'], # mask_image=layer['back_mask_image'],
image_url=layer['back_image_url'], image_url=layer['back_image_url'],
mask_url=layer['mask_url'], mask_url=layer.get('mask_url', None),
sacle=layer['scale'], sacle=layer['scale'],
clothes_keypoint=(0, 0), clothes_keypoint=(0, 0),
position=start_point, position=start_point,
resize_scale=layer["resize_scale"], resize_scale=layer["resize_scale"],
mask=cv2.resize(layer['mask'], layer["front_image"].size), mask=cv2.resize(layer['mask'], layer["front_image"].size),
gradient_string=layer['gradient_string'] if 'gradient_string' in layer.keys() else "", gradient_string=layer['gradient_string'] if 'gradient_string' in layer.keys() else "",
pattern_overall_image_url=layer['pattern_overall_image_url'], pattern_overall_image_url=layer.get('pattern_overall_image_url', None),
pattern_print_image_url=layer['pattern_print_image_url'], pattern_print_image_url=layer.get('pattern_print_image_url', None),
# back_perspective_url=layer['back_perspective_url'] if 'back_perspective_url' in layer.keys() else "" # back_perspective_url=layer['back_perspective_url'] if 'back_perspective_url' in layer.keys() else ""
transpose=layer.get("transpose", [1, 1]), # 默认为1, 1代表不镜像
rotate=layer.get('rotate', 0),
) )
return front_layer, back_layer return front_layer, back_layer

View File

@@ -151,9 +151,11 @@ def synthesis(data, size, basic_info):
if layer['image'] is not None: if layer['image'] is not None:
if layer['name'] != "body": if layer['name'] != "body":
test_image = Image.new('RGBA', size, (0, 0, 0, 0)) test_image = Image.new('RGBA', size, (0, 0, 0, 0))
test_image.paste(layer['image'], (layer['adaptive_position'][1], layer['adaptive_position'][0]), layer['image']) paste_img, position = transpose_rotate(layer, layer['image'])
test_image.paste(paste_img, position, paste_img)
mask_data = np.where(all_mask > 0, 255, 0).astype(np.uint8) mask_data = np.where(all_mask > 0, 255, 0).astype(np.uint8)
mask_alpha = Image.fromarray(mask_data) mask_alpha = Image.fromarray(mask_data)
mask_alpha.paste(paste_img.getchannel('A'), position, paste_img.getchannel('A'))
cropped_image = Image.composite(test_image, Image.new("RGBA", test_image.size, (255, 255, 255, 0)), mask_alpha) cropped_image = Image.composite(test_image, Image.new("RGBA", test_image.size, (255, 255, 255, 0)), mask_alpha)
base_image.paste(test_image, (0, 0), cropped_image) # test_image 已经按照坐标贴到最大宽值的图片上 坐着这里坐标为00 base_image.paste(test_image, (0, 0), cropped_image) # test_image 已经按照坐标贴到最大宽值的图片上 坐着这里坐标为00
else: else:
@@ -185,6 +187,111 @@ def synthesis(data, size, basic_info):
logging.warning(f"synthesis runtime exception : {e}") logging.warning(f"synthesis runtime exception : {e}")
def merge(data, size, basic_info):
# out_of_bounds_control: 是否允许服装越界 True 允许 False 不允许 默认情况允许
out_of_bounds_control = basic_info.get('out_of_bounds_control', True)
# 创建底图
base_image = Image.new('RGBA', size, (0, 0, 0, 0))
try:
all_mask_shape = (size[1], size[0])
body_mask = None
for d in data:
if d['name'] == 'body' or d['name'] == 'mannequin':
# 创建一个新的宽高透明图像, 把模特贴上去获取mask
transparent_image = Image.new("RGBA", size, (0, 0, 0, 0))
transparent_image.paste(d['image'], (d['adaptive_position'][1], d['adaptive_position'][0]), d['image']) # 此处可变数组会被paste篡改值所以使用下标获取position
body_mask = np.array(transparent_image.split()[3])
# 根据新的坐标获取新的肩点
left_shoulder = [x + y for x, y in zip(basic_info['body_point_test']['shoulder_left'], [d['adaptive_position'][1], d['adaptive_position'][0]])]
right_shoulder = [x + y for x, y in zip(basic_info['body_point_test']['shoulder_right'], [d['adaptive_position'][1], d['adaptive_position'][0]])]
body_mask[:min(left_shoulder[1], right_shoulder[1]), left_shoulder[0]:right_shoulder[0]] = 255
_, binary_body_mask = cv2.threshold(body_mask, 127, 255, cv2.THRESH_BINARY)
top_outer_mask = np.array(binary_body_mask)
bottom_outer_mask = np.array(binary_body_mask)
others_outer_mask = np.array(binary_body_mask)
top = True
bottom = True
others = True
i = len(data)
while i:
i -= 1
if top and data[i]['name'] in ["blouse_front", "outwear_front", "dress_front", "tops_front"]:
if out_of_bounds_control:
top = True
else:
top = False
mask_shape = data[i]['mask'].shape
y_offset, x_offset = data[i]['adaptive_position']
# 初始化叠加区域的起始和结束位置
all_y_start, all_y_end, mask_y_start, mask_y_end = positioning(all_mask_shape=all_mask_shape[0], mask_shape=mask_shape[0], offset=y_offset)
all_x_start, all_x_end, mask_x_start, mask_x_end = positioning(all_mask_shape=all_mask_shape[1], mask_shape=mask_shape[1], offset=x_offset)
# 将叠加区域赋值为相应的像素值
_, sketch_mask = cv2.threshold(data[i]['mask'], 127, 255, cv2.THRESH_BINARY)
background = np.zeros_like(top_outer_mask)
background[all_y_start:all_y_end, all_x_start:all_x_end] = sketch_mask[mask_y_start:mask_y_end, mask_x_start:mask_x_end]
top_outer_mask = background + top_outer_mask
elif bottom and data[i]['name'] in ["trousers_front", "skirt_front", "bottoms_front", "dress_front"]:
# bottom = False
mask_shape = data[i]['mask'].shape
y_offset, x_offset = data[i]['adaptive_position']
# 初始化叠加区域的起始和结束位置
all_y_start, all_y_end, mask_y_start, mask_y_end = positioning(all_mask_shape=all_mask_shape[0], mask_shape=mask_shape[0], offset=y_offset)
all_x_start, all_x_end, mask_x_start, mask_x_end = positioning(all_mask_shape=all_mask_shape[1], mask_shape=mask_shape[1], offset=x_offset)
# 将叠加区域赋值为相应的像素值
_, sketch_mask = cv2.threshold(data[i]['mask'], 127, 255, cv2.THRESH_BINARY)
background = np.zeros_like(top_outer_mask)
background[all_y_start:all_y_end, all_x_start:all_x_end] = sketch_mask[mask_y_start:mask_y_end, mask_x_start:mask_x_end]
bottom_outer_mask = background + bottom_outer_mask
elif others and data[i]['name'] in ['others_front']:
mask_shape = data[i]['mask'].shape
y_offset, x_offset = data[i]['adaptive_position']
# 初始化叠加区域的起始和结束位置
all_y_start, all_y_end, mask_y_start, mask_y_end = positioning(all_mask_shape=all_mask_shape[0], mask_shape=mask_shape[0], offset=y_offset)
all_x_start, all_x_end, mask_x_start, mask_x_end = positioning(all_mask_shape=all_mask_shape[1], mask_shape=mask_shape[1], offset=x_offset)
# 将叠加区域赋值为相应的像素值
_, sketch_mask = cv2.threshold(data[i]['mask'], 127, 255, cv2.THRESH_BINARY)
background = np.zeros_like(top_outer_mask)
background[all_y_start:all_y_end, all_x_start:all_x_end] = sketch_mask[mask_y_start:mask_y_end, mask_x_start:mask_x_end]
others_outer_mask = background + others_outer_mask
pass
elif bottom is False and top is False:
break
all_mask = cv2.bitwise_or(top_outer_mask, bottom_outer_mask)
all_mask = cv2.bitwise_or(all_mask, others_outer_mask)
for layer in data:
if layer['image'] is not None:
if layer['name'] != "body":
test_image = Image.new('RGBA', size, (0, 0, 0, 0))
paste_img, position = transpose_rotate(layer, layer['image'])
test_image.paste(paste_img, position, paste_img)
mask_data = np.where(all_mask > 0, 255, 0).astype(np.uint8)
mask_alpha = Image.fromarray(mask_data)
mask_alpha.paste(paste_img.getchannel('A'), position, paste_img.getchannel('A'))
cropped_image = Image.composite(test_image, Image.new("RGBA", test_image.size, (255, 255, 255, 0)), mask_alpha)
base_image.paste(test_image, (0, 0), cropped_image) # test_image 已经按照坐标贴到最大宽值的图片上 坐着这里坐标为00
else:
base_image.paste(layer['merge_image'], (layer['adaptive_position'][1], layer['adaptive_position'][0]), layer['merge_image'])
result_image = base_image
image_data = io.BytesIO()
result_image.save(image_data, format='PNG')
image_data.seek(0)
# oss upload
image_bytes = image_data.read()
bucket_name = "aida-results"
object_name = f'result_{generate_uuid()}.png'
oss_upload_image(oss_client=minio_client, bucket=bucket_name, object_name=object_name, image_bytes=image_bytes)
return f"{bucket_name}/{object_name}"
except Exception as e:
logging.warning(f"synthesis runtime exception : {e}")
def synthesis_single(front_image, back_image): def synthesis_single(front_image, back_image):
result_image = None result_image = None
if front_image: if front_image:
@@ -232,3 +339,36 @@ def update_base_size_priority(layers):
for info in layers: for info in layers:
info['adaptive_position'] = (info['position'][0], info['position'][1] - min_x) info['adaptive_position'] = (info['position'][0], info['position'][1] - min_x)
return layers, (new_width, new_height) return layers, (new_width, new_height)
def transpose_rotate(layer, image):
# transpose[0]是左右 transpose[1]是上下
transpose = layer.get('transpose', [1, 1]) # 默认为1, 1代表不镜像
rotate = layer.get('rotate', 0)
paste_x, paste_y = layer['adaptive_position'][1], layer['adaptive_position'][0]
original_w = image.width
original_h = image.height
# transpose左右是1 上下是-1
if transpose[0] != 1:
# 左右
image = image.transpose(0)
if transpose[1] != 1:
# 上下
image = image.transpose(1)
if rotate:
image = image.rotate(-rotate, expand=True)
# 4. 计算粘贴位置以保持视觉中心一致
# 原本 (15, 36) 是 288*288 的左上角,我们计算其中心点
target_center_x = paste_x + original_w // 2
target_center_y = paste_y + original_h // 2
# 获取旋转后图像的新尺寸
new_w, new_h = image.size
# 计算新的左上角坐标,使得旋转后的图像中心依然在原定的中心位置
paste_x = target_center_x - new_w // 2
paste_y = target_center_y - new_h // 2
return image, (paste_x, paste_y)

View File

@@ -7,7 +7,7 @@ import numpy as np
import torch import torch
import tritonclient.grpc as grpcclient import tritonclient.grpc as grpcclient
from minio import Minio from minio import Minio
from pymilvus import MilvusClient # from pymilvus import MilvusClient
from urllib3.exceptions import ResponseError from urllib3.exceptions import ResponseError
from app.core.config import settings, SR_MODEL_NAME, SR_TRITON_URL, MILVUS_TABLE_KEYPOINT, KEYPOINT_RESULT_TABLE_FIELD_SET from app.core.config import settings, SR_MODEL_NAME, SR_TRITON_URL, MILVUS_TABLE_KEYPOINT, KEYPOINT_RESULT_TABLE_FIELD_SET
@@ -58,7 +58,21 @@ class DesignPreprocessing:
if len(image.shape) == 2: if len(image.shape) == 2:
image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)
elif image.shape[2] == 4: # 如果是四通道 mask elif image.shape[2] == 4: # 如果是四通道 mask
image = image[:, :, :3] # 分离RGB和Alpha通道
bgr = image[:, :, :3]
alpha = image[:, :, 3]
# 创建白色背景(也可改为其他颜色,如(255,255,255)就是白色)
background_color = (255, 255, 255)
background = np.full_like(bgr, background_color)
# 将Alpha通道转换为掩码0=透明255=不透明)
alpha_mask = alpha / 255.0 # 归一化到0-1
alpha_mask = np.expand_dims(alpha_mask, axis=-1) # 扩展维度,方便广播计算
# 混合背景和原图:透明区域显示背景色,不透明区域显示原图
image = (bgr * alpha_mask + background * (1 - alpha_mask)).astype(np.uint8)
# 此时image已经是3通道RGB无需再执行image = image[:, :, :3]
obj["image_obj"] = image obj["image_obj"] = image
return image_list return image_list
@@ -174,8 +188,9 @@ class DesignPreprocessing:
scale = 0.4 scale = 0.4
if waist_width / scale >= image_width: if waist_width / scale >= image_width:
add_width = int((waist_width / scale - image_width) / 2) add_width = int((waist_width / scale - image_width) / 2)
ret = cv2.copyMakeBorder(image['obj'], 0, 0, add_width, add_width, cv2.BORDER_CONSTANT, value=(256, 256, 256)) ret = cv2.copyMakeBorder(image['obj'], 0, 0, add_width, add_width, cv2.BORDER_CONSTANT, value=(255, 255, 255))
image_bytes = cv2.imencode(".jpg", ret)[1].tobytes() img_rgba = cv2.cvtColor(ret, cv2.COLOR_RGB2RGBA)
image_bytes = cv2.imencode(".png", img_rgba)[1].tobytes()
# image['show_image_url'] = f"{image['image_url'].split('/', 1)[0]}/{self.minio_client.put_object(image['image_url'].split('/', 1)[0], image['image_url'].split('/', 1)[1].replace('.', '-show.'), io.BytesIO(image_bytes), len(image_bytes), content_type='image/jpeg').object_name}" # image['show_image_url'] = f"{image['image_url'].split('/', 1)[0]}/{self.minio_client.put_object(image['image_url'].split('/', 1)[0], image['image_url'].split('/', 1)[1].replace('.', '-show.'), io.BytesIO(image_bytes), len(image_bytes), content_type='image/jpeg').object_name}"
bucket_name = image['image_url'].split('/', 1)[0] bucket_name = image['image_url'].split('/', 1)[0]
object_name = image['image_url'].split('/', 1)[1].replace('.', '-show.') object_name = image['image_url'].split('/', 1)[1].replace('.', '-show.')
@@ -261,14 +276,15 @@ class DesignPreprocessing:
def keypoint_cache(self, sketch): def keypoint_cache(self, sketch):
try: try:
client = MilvusClient(uri=settings.MILVUS_URL, token=settings.MILVUS_TOKEN, db_name=settings.MILVUS_ALIAS) # client = MilvusClient(uri=settings.MILVUS_URL, token=settings.MILVUS_TOKEN, db_name=settings.MILVUS_ALIAS)
keypoint_id = sketch['image_id'] keypoint_id = sketch['image_id']
res = client.query( # res = client.query(
collection_name=MILVUS_TABLE_KEYPOINT, # collection_name=MILVUS_TABLE_KEYPOINT,
# ids=[keypoint_id], # # ids=[keypoint_id],
filter=f"keypoint_id == {keypoint_id}", # filter=f"keypoint_id == {keypoint_id}",
output_fields=['keypoint_vector', 'keypoint_site'] # output_fields=['keypoint_vector', 'keypoint_site']
) # )
res = []
if len(res) == 0: if len(res) == 0:
# 没有结果 直接推理拿结果 并保存 # 没有结果 直接推理拿结果 并保存
keypoint_infer_result = self.infer_keypoint_result(sketch) keypoint_infer_result = self.infer_keypoint_result(sketch)

View File

@@ -11,7 +11,6 @@ import logging
import uuid import uuid
import cv2 import cv2
import mmcv
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import torch import torch
@@ -21,6 +20,7 @@ from minio import Minio
from tritonclient.utils import np_to_triton_dtype from tritonclient.utils import np_to_triton_dtype
from app.core.config import settings, FAST_GI_MODEL_URL, GI_MODEL_URL, DESIGN_MODEL_URL, FAST_GI_MODEL_NAME, GI_MODEL_NAME from app.core.config import settings, FAST_GI_MODEL_URL, GI_MODEL_URL, DESIGN_MODEL_URL, FAST_GI_MODEL_NAME, GI_MODEL_NAME
from app.service.utils.image_normalize import my_imnormalize
from app.service.utils.new_oss_client import oss_upload_image from app.service.utils.new_oss_client import oss_upload_image
logger = logging.getLogger() logger = logging.getLogger()
@@ -86,10 +86,9 @@ class AgentToolGenerateImage:
@staticmethod @staticmethod
def preprocess(img): def preprocess(img):
img = mmcv.imread(img)
img_scale = (224, 224) img_scale = (224, 224)
img = cv2.resize(img, img_scale) img = cv2.resize(img, img_scale)
img = mmcv.imnormalize( img = my_imnormalize(
img, img,
mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]),
to_rgb=True) to_rgb=True)

View File

@@ -189,10 +189,10 @@ if __name__ == '__main__':
tasks_id="123-89", tasks_id="123-89",
prompt="a single item of sketch of dress, 4k, white background", prompt="a single item of sketch of dress, 4k, white background",
image_url="aida-collection-element/89/Sketchboard/95f20cdc-e059-435c-b8b1-d04cc9e80c3d.png", image_url="aida-collection-element/89/Sketchboard/95f20cdc-e059-435c-b8b1-d04cc9e80c3d.png",
mode='img2img', mode='txt2img',
category="sketch", category="sketch",
gender="Female", gender="Female",
version="fast" version="hight"
) )
server = GenerateImage(rd) server = GenerateImage(rd)
print(server.get_result()) print(server.get_result())

View File

@@ -2,23 +2,23 @@ import logging
import time import time
import cv2 import cv2
import mmcv
import numpy as np import numpy as np
import torch import torch
import tritonclient.http as httpclient import tritonclient.http as httpclient
from app.core.config import settings, DESIGN_MODEL_URL, DESIGN_MODEL_NAME from app.core.config import settings, DESIGN_MODEL_URL, DESIGN_MODEL_NAME
from app.service.generate_image.utils.upload_sd_image import upload_stain_png_sd, upload_face_png_sd from app.service.generate_image.utils.upload_sd_image import upload_stain_png_sd, upload_face_png_sd
from app.service.utils.image_normalize import my_imnormalize
logger = logging.getLogger() logger = logging.getLogger()
def seg_preprocess(img_path): def seg_preprocess(img_path):
img = mmcv.imread(img_path) img = img_path
ori_shape = img.shape[:2] ori_shape = img.shape[:2]
img_scale = ori_shape img_scale = ori_shape
img = cv2.resize(img, img_scale) img = cv2.resize(img, img_scale)
img = mmcv.imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True) img = my_imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True)
preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0) preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0)
return preprocessed_img, ori_shape return preprocessed_img, ori_shape
@@ -242,10 +242,9 @@ def stain_detection(image, user_id, category, tasks_id, spot_size=100):
def generate_category_recognition(image, gender): def generate_category_recognition(image, gender):
def preprocess(img): def preprocess(img):
img = mmcv.imread(img)
img_scale = (224, 224) img_scale = (224, 224)
img = cv2.resize(img, img_scale) img = cv2.resize(img, img_scale)
img = mmcv.imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True) img = my_imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True)
preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0) preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0)
return preprocessed_img return preprocessed_img

View File

@@ -1,7 +1,6 @@
import logging import logging
import cv2 import cv2
import mmcv
import numpy as np import numpy as np
import torch import torch
import torch.nn.functional as F import torch.nn.functional as F
@@ -10,6 +9,7 @@ from minio import Minio
from app.core.config import settings from app.core.config import settings
from app.core.config import DESIGN_MODEL_URL from app.core.config import DESIGN_MODEL_URL
from app.schemas.image2sketch import Image2SketchModel from app.schemas.image2sketch import Image2SketchModel
from app.service.utils.image_normalize import my_imnormalize
from app.service.utils.new_oss_client import oss_get_image, oss_upload_image from app.service.utils.new_oss_client import oss_get_image, oss_upload_image
logger = logging.getLogger() logger = logging.getLogger()
@@ -67,7 +67,7 @@ class LineArtService:
@staticmethod @staticmethod
def line_art_preprocess(image): def line_art_preprocess(image):
img = mmcv.imread(image) img = image
ori_shape = img.shape[:2] ori_shape = img.shape[:2]
img_scale_w, img_scale_h = ori_shape img_scale_w, img_scale_h = ori_shape
if ori_shape[0] > 1024: if ori_shape[0] > 1024:
@@ -76,9 +76,9 @@ class LineArtService:
img_scale_h = 1024 img_scale_h = 1024
# 如果图片size任意一边 大于 1024 则会resize 成1024 # 如果图片size任意一边 大于 1024 则会resize 成1024
if ori_shape != (img_scale_w, img_scale_h): if ori_shape != (img_scale_w, img_scale_h):
# mmcv.imresize(img, img_scale_h, img_scale_w) # 老代码 引以为戒!哈哈哈~ h和w写反了 # my_imnormalize(img, img_scale_h, img_scale_w) # 老代码 引以为戒!哈哈哈~ h和w写反了
img = cv2.resize(img, (img_scale_h, img_scale_w)) img = cv2.resize(img, (img_scale_h, img_scale_w))
img = mmcv.imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True) img = my_imnormalize(img, mean=np.array([123.675, 116.28, 103.53]), std=np.array([58.395, 57.12, 57.375]), to_rgb=True)
preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0) preprocessed_img = np.expand_dims(img.transpose(2, 0, 1), axis=0)
return preprocessed_img, ori_shape return preprocessed_img, ori_shape

View File

@@ -90,7 +90,7 @@ def get_response(messages):
def get_translation_from_llama3(text): def get_translation_from_llama3(text):
start_time = time.time() start_time = time.time()
url = "http://10.1.1.240:11434/api/generate" url = f"http://{settings.A6000_SERVICE_HOST}:12434/api/generate"
# url = "http://10.1.1.240:1143/api/generate" # url = "http://10.1.1.240:1143/api/generate"
# prompt = f"System: {prefix_for_llama}\nUser:[{text}]" # prompt = f"System: {prefix_for_llama}\nUser:[{text}]"
@@ -103,8 +103,8 @@ def get_translation_from_llama3(text):
# 创建请求的负载 translator是自定义的翻译模型 # 创建请求的负载 translator是自定义的翻译模型
payload = { payload = {
"model": "translator", "model": "AiDA-translator:latest",
"prompt": f"[{text}]", "prompt": text,
"stream": False "stream": False
} }
# 将负载转换为 JSON 格式 # 将负载转换为 JSON 格式
@@ -148,7 +148,7 @@ def get_translation_from_llama3(text):
def get_prompt_from_image(image_path, text): def get_prompt_from_image(image_path, text):
start_time = time.time() start_time = time.time()
# url = "http://localhost:11434/api/generate" # url = "http://localhost:11434/api/generate"
url = "http://10.1.1.243:11434/api/generate" url = f"http://{settings.B_4_X_4090_SERVICE_HOST}:11434/api/generate"
image_base64 = minio_util.minio_url_to_base64(image_path.img) image_base64 = minio_util.minio_url_to_base64(image_path.img)
# image_base64 = minio_url_to_base64(image_path) # image_base64 = minio_url_to_base64(image_path)
@@ -180,7 +180,7 @@ def get_prompt_from_image(image_path, text):
def main(): def main():
"""Main function""" """Main function"""
text = get_translation_from_llama3("[火焰]") text = get_translation_from_llama3("火焰")
print(text) print(text)

View File

@@ -14,7 +14,7 @@ REDIS_KEY_USER_PREF_PREFIX = "user_pref"
RECOMMENDATION_CONFIG = { RECOMMENDATION_CONFIG = {
# 时间衰减半衰期(用于计算时间衰减权重) # 时间衰减半衰期(用于计算时间衰减权重)
# 值越小,最近的行为权重越大 # 值越小,最近的行为权重越大
"K_half": 20, "K_half": 10,
# 探索与利用的比例 (0.0-1.0) # 探索与利用的比例 (0.0-1.0)
# - 值越大,使用探索分支(随机推荐)的几率越大,结果更随机 # - 值越大,使用探索分支(随机推荐)的几率越大,结果更随机
@@ -25,7 +25,7 @@ RECOMMENDATION_CONFIG = {
# 向量检索返回的候选数量 # 向量检索返回的候选数量
# 值越大,候选池越大,但计算成本也越高 # 值越大,候选池越大,但计算成本也越高
# 建议范围: 100-1000 # 建议范围: 100-1000
"topk": 1000, "topk": 200,
# Style 加分系数(同 style 的候选进行加分) # Style 加分系数(同 style 的候选进行加分)
# 值越大,匹配 style 的候选被选中的概率越大 # 值越大,匹配 style 的候选被选中的概率越大
@@ -53,7 +53,7 @@ RECOMMENDATION_CONFIG = {
} }
# 数据库表名 # 数据库表名
TABLE_USER_PREFERENCE_LOG = "user_preference_log_test" TABLE_USER_PREFERENCE_LOG = "user_preference"
TABLE_SYS_FILE = "t_sys_file" TABLE_SYS_FILE = "t_sys_file"
# MySQL 连接配置(用于推荐系统) # MySQL 连接配置(用于推荐系统)

View File

@@ -1,6 +1,6 @@
""" """
增量监听模块 增量监听模块
实时监听 user_preference_log_test 表的新增记录,更新用户偏好向量 实时监听 user_preference 表的新增记录,更新用户偏好向量
""" """
import logging import logging
import math import math
@@ -48,7 +48,7 @@ class IncrementalListener:
if self.last_process_time is None: if self.last_process_time is None:
# 第一次运行查询最近30分钟的数据 # 第一次运行查询最近30分钟的数据
cursor.execute(f""" cursor.execute(f"""
SELECT id, account_id, path, category, style, data_time, is_system_sketch, sys_file_id SELECT id, account_id, path, category, style, data_time
FROM {TABLE_USER_PREFERENCE_LOG} FROM {TABLE_USER_PREFERENCE_LOG}
WHERE data_time > DATE_SUB(NOW(), INTERVAL 30 MINUTE) WHERE data_time > DATE_SUB(NOW(), INTERVAL 30 MINUTE)
ORDER BY data_time ORDER BY data_time
@@ -56,7 +56,7 @@ class IncrementalListener:
else: else:
# 基于上次处理时间查询 # 基于上次处理时间查询
cursor.execute(f""" cursor.execute(f"""
SELECT id, account_id, path, category, style, data_time, is_system_sketch, sys_file_id SELECT id, account_id, path, category, style, data_time
FROM {TABLE_USER_PREFERENCE_LOG} FROM {TABLE_USER_PREFERENCE_LOG}
WHERE data_time > %s WHERE data_time > %s
ORDER BY data_time ORDER BY data_time
@@ -258,7 +258,7 @@ class IncrementalListener:
} }
else: else:
# 用户图 # 用户图
# 从 user_preference_log_test 获取 category如果有 # 从 user_preference 获取 category如果有
cursor.execute(f""" cursor.execute(f"""
SELECT category SELECT category
FROM {TABLE_USER_PREFERENCE_LOG} FROM {TABLE_USER_PREFERENCE_LOG}
@@ -308,6 +308,10 @@ class IncrementalListener:
def start_background_listener(scheduler: BackgroundScheduler): def start_background_listener(scheduler: BackgroundScheduler):
"""将增量监听任务注册到后台调度器""" """将增量监听任务注册到后台调度器"""
# 降低 apscheduler 的日志级别,避免大量刷屏
logging.getLogger('apscheduler.executors.default').setLevel(logging.WARNING)
logging.getLogger('apscheduler.scheduler').setLevel(logging.WARNING)
listener = IncrementalListener() listener = IncrementalListener()
scheduler.add_job( scheduler.add_job(
listener.process_once, listener.process_once,

View File

@@ -23,7 +23,7 @@ def get_milvus_client() -> MilvusClient:
_milvus_client = MilvusClient( _milvus_client = MilvusClient(
uri=settings.MILVUS_URL, uri=settings.MILVUS_URL,
token=settings.MILVUS_TOKEN, token=settings.MILVUS_TOKEN,
db_name=settings.MILVUS_DB, db_name="",
) )
logger.info("Milvus 客户端连接成功") logger.info("Milvus 客户端连接成功")
except Exception as e: except Exception as e:
@@ -203,39 +203,74 @@ def search_similar_vectors(
query_vector: np.ndarray, query_vector: np.ndarray,
category: str, category: str,
topk: int = 500, topk: int = 500,
style: Optional[str] = None style: Optional[str] = None,
style_boost_ratio: float = 0.2
) -> List[Dict]: ) -> List[Dict]:
""" """
向量相似度检索 向量相似度检索
Args: Args:
query_vector: 查询向量2048维 query_vector: 查询向量2048维
category: 类别过滤 category: 类别过滤
topk: 返回数量 topk: 返回数量
style: 风格过滤(可选) style: 风格过滤(可选)- 当提供时会给对应style的结果加分
style_boost_ratio: 风格加分比例默认0.1即10%
Returns: Returns:
检索结果列表,每个元素包含 path, score, style, category 等字段 检索结果列表,每个元素包含 path, score, style, category 等字段
""" """
client = get_milvus_client() client = get_milvus_client()
try: try:
# 构建过滤表达式 # 如果没有指定style使用原始逻辑
# 使用 filter 参数而不是 expr根据 pymilvus MilvusClient API if not style:
filter_expr = f"category == '{category}' && deprecated == 0" filter_expr = f"category == '{category}' && deprecated == 0"
if style: results = client.search(
filter_expr += f" && style == '{style}'" collection_name=MILVUS_COLLECTION_SKETCH_VECTORS,
data=[query_vector.tolist()],
anns_field="feature_vector",
search_params={"metric_type": "IP", "params": {"nprobe": 10}},
limit=topk,
filter=filter_expr,
output_fields=["path", "style", "category", "sys_file_id"]
)
else:
# 有style参数时使用两阶段搜索策略
# 搜索 # 第一阶段搜索匹配style的向量使用boosted query vector
results = client.search( filter_expr_style = f"category == '{category}' && deprecated == 0 && style == '{style}'"
collection_name=MILVUS_COLLECTION_SKETCH_VECTORS, boosted_query = query_vector * (1 + style_boost_ratio)
data=[query_vector.tolist()], results_style = client.search(
anns_field="feature_vector", collection_name=MILVUS_COLLECTION_SKETCH_VECTORS,
search_params={"metric_type": "IP", "params": {"nprobe": 10}}, data=[boosted_query.tolist()],
limit=topk, anns_field="feature_vector",
filter=filter_expr, search_params={"metric_type": "IP", "params": {"nprobe": 10}},
output_fields=["path", "style", "category", "sys_file_id"] limit=topk,
) filter=filter_expr_style,
output_fields=["path", "style", "category", "sys_file_id"]
)
# 第二阶段搜索其他style的向量
filter_expr_others = f"category == '{category}' && deprecated == 0 && style != '{style}'"
results_others = client.search(
collection_name=MILVUS_COLLECTION_SKETCH_VECTORS,
data=[query_vector.tolist()],
anns_field="feature_vector",
search_params={"metric_type": "IP", "params": {"nprobe": 10}},
limit=topk,
filter=filter_expr_others,
output_fields=["path", "style", "category", "sys_file_id"]
)
# 合并结果
results = []
if results_style and len(results_style) > 0:
results.extend(results_style[0])
if results_others and len(results_others) > 0:
results.extend(results_others[0])
# 转换为单个结果列表格式
results = [results] if results else []
# 格式化结果 # 格式化结果
formatted_results = [] formatted_results = []
@@ -249,7 +284,10 @@ def search_similar_vectors(
"sys_file_id": hit.get("entity", {}).get("sys_file_id") "sys_file_id": hit.get("entity", {}).get("sys_file_id")
}) })
return formatted_results # 按分数排序并返回topk
formatted_results.sort(key=lambda x: x["score"], reverse=True)
return formatted_results[:topk]
except Exception as e: except Exception as e:
logger.error(f"向量检索失败: {e}", exc_info=True) logger.error(f"向量检索失败: {e}", exc_info=True)
return [] return []
@@ -280,7 +318,7 @@ def query_random_candidates(category: str, style: Optional[str] = None, limit: i
collection_name=MILVUS_COLLECTION_SKETCH_VECTORS, collection_name=MILVUS_COLLECTION_SKETCH_VECTORS,
filter=filter_expr, filter=filter_expr,
output_fields=["path", "style", "category"], output_fields=["path", "style", "category"],
limit=10000 # 先查询大量数据,然后随机选择 limit=10000
) )
# 随机选择 # 随机选择

View File

@@ -6,6 +6,7 @@ import logging
import math import math
import pymysql import pymysql
import numpy as np import numpy as np
from datetime import datetime
from typing import List, Dict, Tuple, Optional from typing import List, Dict, Tuple, Optional
from collections import defaultdict from collections import defaultdict
@@ -25,7 +26,7 @@ logger = logging.getLogger(__name__)
def optimize_database_table(): def optimize_database_table():
""" """
优化 user_preference_log_test 表结构 优化 user_preference 表结构
添加冗余字段和索引 添加冗余字段和索引
""" """
conn = None conn = None
@@ -317,8 +318,8 @@ def precompute_system_sketch_vectors(batch_size: int = 1000, retry_times: int =
def compute_user_preference_vector( def compute_user_preference_vector(
account_id: int, account_id: int,
category: str, category: str,
conn: Optional[pymysql.connections.Connection] = None conn: Optional[pymysql.connections.Connection] = None,
# max_date: Optional[datetime] = None max_date: Optional[datetime] = None
) -> Optional[np.ndarray]: ) -> Optional[np.ndarray]:
""" """
计算用户偏好向量 计算用户偏好向量
@@ -419,8 +420,8 @@ def compute_user_preference_vector(
p_i = 1 + math.log(1 + like_count) p_i = 1 + math.log(1 + like_count)
# 综合权重 # 综合权重
# w_i = d_k * p_i w_i = d_k * p_i
w_i = p_i # w_i = p_i
vectors.append(feature_vector) vectors.append(feature_vector)
weights.append(w_i) weights.append(w_i)
@@ -518,16 +519,16 @@ def run_precompute():
logger.info("=" * 50) logger.info("=" * 50)
# 1. 优化数据库表结构 # 1. 优化数据库表结构
logger.info("\n[1/5] 优化数据库表结构...") # logger.info("\n[1/5] 优化数据库表结构...")
optimize_database_table() # optimize_database_table()
# # 2. 创建 Milvus 集合 # # 2. 创建 Milvus 集合
# logger.info("\n[2/5] 创建 Milvus 集合...") # logger.info("\n[2/5] 创建 Milvus 集合...")
# create_collection() # create_collection()
# 3. 历史数据迁移 # 3. 历史数据迁移
logger.info("\n[3/5] 历史数据迁移...") # logger.info("\n[3/5] 历史数据迁移...")
migrate_historical_data() # migrate_historical_data()
# # 4. 系统图向量预计算 # # 4. 系统图向量预计算
# logger.info("\n[4/5] 系统图向量预计算...") # logger.info("\n[4/5] 系统图向量预计算...")
@@ -543,13 +544,13 @@ def run_precompute():
if __name__ == "__main__": if __name__ == "__main__":
# 1. 优化数据库表结构 # # 1. 优化数据库表结构
logger.info("\n[1/5] 优化数据库表结构...") # logger.info("\n[1/5] 优化数据库表结构...")
optimize_database_table() # optimize_database_table()
#
# 3. 历史数据迁移 # # 3. 历史数据迁移
logger.info("\n[3/5] 历史数据迁移...") # logger.info("\n[3/5] 历史数据迁移...")
migrate_historical_data() # migrate_historical_data()
# 5. 初始用户偏好向量生成 # 5. 初始用户偏好向量生成
logger.info("\n[5/5] 初始用户偏好向量生成...") logger.info("\n[5/5] 初始用户偏好向量生成...")

View File

@@ -10,7 +10,7 @@ from torchvision import models, transforms
from PIL import Image from PIL import Image
from minio import Minio from minio import Minio
from app.core.config import MINIO_URL, MINIO_ACCESS, MINIO_SECRET, MINIO_SECURE from app.core.config import settings
from app.service.recommendation_system.config import RECOMMENDATION_CONFIG from app.service.recommendation_system.config import RECOMMENDATION_CONFIG
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -48,10 +48,10 @@ def get_minio_client():
global _minio_client global _minio_client
if _minio_client is None: if _minio_client is None:
_minio_client = Minio( _minio_client = Minio(
MINIO_URL, settings.MINIO_URL,
access_key=MINIO_ACCESS, access_key=settings.MINIO_ACCESS,
secret_key=MINIO_SECRET, secret_key=settings.MINIO_SECRET,
secure=MINIO_SECURE secure=settings.MINIO_SECURE
) )
return _minio_client return _minio_client

View File

@@ -0,0 +1,27 @@
import cv2
import numpy as np
def my_imnormalize(img, mean, std, to_rgb=True):
"""Inplace normalize an image with mean and std.
Args:
img (ndarray): Image to be normalized.
mean (ndarray): The mean to be used for normalize.
std (ndarray): The std to be used for normalize.
to_rgb (bool): Whether to convert to rgb.
Returns:
ndarray: The normalized image.
"""
# cv2 inplace normalization does not accept uint8
img = img.copy().astype(np.float32)
assert img.dtype != np.uint8
mean = np.float64(mean.reshape(1, -1))
stdinv = 1 / np.float64(std.reshape(1, -1))
if to_rgb:
cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img) # inplace
cv2.subtract(img, mean, img) # inplace
cv2.multiply(img, stdinv, img) # inplace
return img

View File

@@ -81,7 +81,7 @@ if __name__ == '__main__':
# url = "aida-users/89/sketchboard/female/Dress/e6724ab7-8d3f-4677-abe0-c3e42ab7af85.jpeg" # url = "aida-users/89/sketchboard/female/Dress/e6724ab7-8d3f-4677-abe0-c3e42ab7af85.jpeg"
# url = "aida-users/87/print/956614a2-7e75-4fbe-9ed0-c1831e37a2c9-4-87.png" # url = "aida-users/87/print/956614a2-7e75-4fbe-9ed0-c1831e37a2c9-4-87.png"
# url = "aida-users/89/single_logo/123-89.png" # url = "aida-users/89/single_logo/123-89.png"
url = "lanecarford/lc_stylist_agent_outfit_items/141/ee25ec85-d504-4b42-9a18-db6682fe9e3b-6.jpg" url = "aida-results/result_a7adcbd8-ef8d-11f0-8c92-0966ede33ab5.png"
# url = "aida-collection-element/12148/Sketchboard/95ea577b-305b-4a62-b30a-39c0dd3ddb3f.png" # url = "aida-collection-element/12148/Sketchboard/95ea577b-305b-4a62-b30a-39c0dd3ddb3f.png"
read_type = "2" read_type = "2"

View File

@@ -91,6 +91,21 @@ class Redis(object):
r = cls._get_r() r = cls._get_r()
r.expire(name, expire_in_seconds) r.expire(name, expire_in_seconds)
@classmethod
def scan_keys(cls, pattern="*"):
"""
扫描匹配模式的key
"""
r = cls._get_r()
keys = []
cursor = 0
while True:
cursor, partial_keys = r.scan(cursor, match=pattern, count=1000)
keys.extend(partial_keys)
if cursor == 0:
break
return [key.decode('utf-8') if isinstance(key, bytes) else key for key in keys]
if __name__ == '__main__': if __name__ == '__main__':
redis_client = Redis() redis_client = Redis()

View File

@@ -1,13 +1,20 @@
services: services:
aida_server: aida_server:
container_name: "AiDA_${SERVE_ENV}_Server"
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
working_dir: /app working_dir: /app
volumes: volumes:
- ./app:/app/app - ./app:/app/app
- ./.env_prod:/app/.env - ./.env:/app/.env
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
- ./seg_cache:/seg_cache - ./seg_cache:/seg_cache
ports: ports:
- "10200:80" - "${SERVE_PORT}:80"
networks:
- aida_app_net
networks:
aida_app_net:
external: true
name: aida_app_net

View File

@@ -1,10 +1,15 @@
import os
from app.core.config import settings from app.core.config import settings
LOGGER_CONFIG_DICT = { LOGGER_CONFIG_DICT = {
'version': 1, 'version': 1,
'disable_existing_loggers': False, 'disable_existing_loggers': False,
'formatters': { 'formatters': {
'simple': {'format': '%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s %(message)s'} 'simple': {
'format': '%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S' # 补充日期格式,日志更易读
}
}, },
'handlers': { 'handlers': {
'console': { 'console': {
@@ -17,7 +22,7 @@ LOGGER_CONFIG_DICT = {
'class': 'logging.handlers.RotatingFileHandler', 'class': 'logging.handlers.RotatingFileHandler',
'level': 'INFO', 'level': 'INFO',
'formatter': 'simple', 'formatter': 'simple',
'filename': f'{settings.LOGS_PATH}info.log', 'filename': os.path.join(settings.LOGS_PATH, 'info.log'),
'maxBytes': 10485760, 'maxBytes': 10485760,
'backupCount': 50, 'backupCount': 50,
'encoding': 'utf8', 'encoding': 'utf8',
@@ -26,7 +31,7 @@ LOGGER_CONFIG_DICT = {
'class': 'logging.handlers.RotatingFileHandler', 'class': 'logging.handlers.RotatingFileHandler',
'level': 'ERROR', 'level': 'ERROR',
'formatter': 'simple', 'formatter': 'simple',
'filename': f'{settings.LOGS_PATH}error.log', 'filename': os.path.join(settings.LOGS_PATH, 'error.log'),
'maxBytes': 10485760, 'maxBytes': 10485760,
'backupCount': 20, 'backupCount': 20,
'encoding': 'utf8', 'encoding': 'utf8',
@@ -35,7 +40,7 @@ LOGGER_CONFIG_DICT = {
'class': 'logging.handlers.RotatingFileHandler', 'class': 'logging.handlers.RotatingFileHandler',
'level': 'DEBUG', 'level': 'DEBUG',
'formatter': 'simple', 'formatter': 'simple',
'filename': f'{settings.LOGS_PATH}debug.log', 'filename': os.path.join(settings.LOGS_PATH, 'debug.log'),
'maxBytes': 10485760, 'maxBytes': 10485760,
'backupCount': 50, 'backupCount': 50,
'encoding': 'utf8', 'encoding': 'utf8',
@@ -45,7 +50,7 @@ LOGGER_CONFIG_DICT = {
'my_module': {'level': 'INFO', 'handlers': ['console'], 'propagate': 'no'} 'my_module': {'level': 'INFO', 'handlers': ['console'], 'propagate': 'no'}
}, },
'root': { 'root': {
'level': 'INFO', 'level': 'DEBUG',
'handlers': ['error_file_handler', 'info_file_handler', 'debug_file_handler', 'console'], 'handlers': ['error_file_handler', 'info_file_handler', 'debug_file_handler', 'console'],
}, },
} }

View File

@@ -23,8 +23,8 @@ dependencies = [
"load-dotenv>=0.1.0", "load-dotenv>=0.1.0",
"loguru>=0.7.3", "loguru>=0.7.3",
"minio>=7.2.20", "minio>=7.2.20",
"mmcv>=2.2.0",
"moviepy==1.0.3", "moviepy==1.0.3",
"np>=1.0.2",
"numpy<2", "numpy<2",
"ollama>=0.6.1", "ollama>=0.6.1",
"opencv-python>=4.11.0.86", "opencv-python>=4.11.0.86",

Binary file not shown.

Binary file not shown.

88
uv.lock generated
View File

@@ -8,15 +8,6 @@ resolution-markers = [
"(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')", "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')",
] ]
[[package]]
name = "addict"
version = "2.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/85/ef/fd7649da8af11d93979831e8f1f8097e85e82d5bfeabc8c68b39175d8e75/addict-2.4.0.tar.gz", hash = "sha256:b3b2210e0e067a281f5646c8c5db92e99b7231ea8b0eb5f74dbdf9e259d4e494", size = 9186, upload-time = "2020-11-21T16:21:31.416Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6a/00/b08f23b7d7e1e14ce01419a467b583edbb93c6cdb8654e54a9cc579cd61f/addict-2.4.0-py3-none-any.whl", hash = "sha256:249bb56bbfd3cdc2a004ea0ff4c2b6ddc84d53bc2194761636eb314d5cfa5dfc", size = 3832, upload-time = "2020-11-21T16:21:29.588Z" },
]
[[package]] [[package]]
name = "agentaction" name = "agentaction"
version = "0.1.7" version = "0.1.7"
@@ -1671,43 +1662,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3e/9a/b697530a882588a84db616580f2ba5d1d515c815e11c30d219145afeec87/minio-7.2.20-py3-none-any.whl", hash = "sha256:eb33dd2fb80e04c3726a76b13241c6be3c4c46f8d81e1d58e757786f6501897e", size = 93751, upload-time = "2025-11-27T00:37:13.993Z" }, { url = "https://files.pythonhosted.org/packages/3e/9a/b697530a882588a84db616580f2ba5d1d515c815e11c30d219145afeec87/minio-7.2.20-py3-none-any.whl", hash = "sha256:eb33dd2fb80e04c3726a76b13241c6be3c4c46f8d81e1d58e757786f6501897e", size = 93751, upload-time = "2025-11-27T00:37:13.993Z" },
] ]
[[package]]
name = "mmcv"
version = "2.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "addict" },
{ name = "mmengine" },
{ name = "numpy" },
{ name = "opencv-python" },
{ name = "packaging" },
{ name = "pillow" },
{ name = "pyyaml" },
{ name = "regex", marker = "sys_platform == 'win32'" },
{ name = "yapf" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e9/a2/57a733e7e84985a8a0e3101dfb8170fc9db92435c16afad253069ae3f9df/mmcv-2.2.0.tar.gz", hash = "sha256:ac479247e808d8802f89eadf04d4118de86bdfe81361ec5aed0cc1bf731c67c9", size = 479121, upload-time = "2024-04-24T14:24:28.064Z" }
[[package]]
name = "mmengine"
version = "0.10.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "addict" },
{ name = "matplotlib" },
{ name = "numpy" },
{ name = "opencv-python" },
{ name = "pyyaml" },
{ name = "regex", marker = "sys_platform == 'win32'" },
{ name = "rich" },
{ name = "termcolor" },
{ name = "yapf" },
]
sdist = { url = "https://files.pythonhosted.org/packages/17/14/959360bbd8374e23fc1b720906999add16a3ac071a501636db12c5861ff5/mmengine-0.10.7.tar.gz", hash = "sha256:d20ffcc31127567e53dceff132612a87f0081de06cbb7ab2bdb7439125a69225", size = 378090, upload-time = "2025-03-04T12:23:09.568Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/98/8e/f98332248aad102511bea4ae19c0ddacd2f0a994f3ca4c82b7a369e0af8b/mmengine-0.10.7-py3-none-any.whl", hash = "sha256:262ac976a925562f78cd5fd14dd1bc9b680ed0aa81f0d85b723ef782f99c54ee", size = 452720, upload-time = "2025-03-04T12:23:06.339Z" },
]
[[package]] [[package]]
name = "mmh3" name = "mmh3"
version = "5.2.0" version = "5.2.0"
@@ -1801,6 +1755,12 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" },
] ]
[[package]]
name = "np"
version = "1.0.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/40/7d/749666e5a9976dcbc4d16d487bbe571efc6bbf4cdf3f4620c0ccc52b57ef/np-1.0.2.tar.gz", hash = "sha256:781265283f3823663ad8fb48741aae62abcf4c78bc19f908f8aa7c1d3eb132f8", size = 7419, upload-time = "2017-10-05T11:26:00.956Z" }
[[package]] [[package]]
name = "numpy" name = "numpy"
version = "1.26.4" version = "1.26.4"
@@ -2269,15 +2229,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012, upload-time = "2025-10-15T18:22:23.621Z" }, { url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012, upload-time = "2025-10-15T18:22:23.621Z" },
] ]
[[package]]
name = "platformdirs"
version = "4.5.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" },
]
[[package]] [[package]]
name = "posthog" name = "posthog"
version = "5.4.0" version = "5.4.0"
@@ -2746,17 +2697,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" },
] ]
[[package]]
name = "regex"
version = "2025.11.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cc/a9/546676f25e573a4cf00fe8e119b78a37b6a8fe2dc95cda877b30889c9c45/regex-2025.11.3.tar.gz", hash = "sha256:1fedc720f9bb2494ce31a58a1631f9c82df6a09b49c19517ea5cc280b4541e01", size = 414669, upload-time = "2025-11-03T21:34:22.089Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/59/9b/7c29be7903c318488983e7d97abcf8ebd3830e4c956c4c540005fcfb0462/regex-2025.11.3-cp312-cp312-win32.whl", hash = "sha256:3839967cf4dc4b985e1570fd8d91078f0c519f30491c60f9ac42a8db039be204", size = 266194, upload-time = "2025-11-03T21:31:51.53Z" },
{ url = "https://files.pythonhosted.org/packages/1a/67/3b92df89f179d7c367be654ab5626ae311cb28f7d5c237b6bb976cd5fbbb/regex-2025.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:e721d1b46e25c481dc5ded6f4b3f66c897c58d2e8cfdf77bbced84339108b0b9", size = 277069, upload-time = "2025-11-03T21:31:53.151Z" },
{ url = "https://files.pythonhosted.org/packages/d7/55/85ba4c066fe5094d35b249c3ce8df0ba623cfd35afb22d6764f23a52a1c5/regex-2025.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:64350685ff08b1d3a6fff33f45a9ca183dc1d58bbfe4981604e70ec9801bbc26", size = 270330, upload-time = "2025-11-03T21:31:54.514Z" },
]
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.32.5" version = "2.32.5"
@@ -3224,8 +3164,8 @@ dependencies = [
{ name = "load-dotenv" }, { name = "load-dotenv" },
{ name = "loguru" }, { name = "loguru" },
{ name = "minio" }, { name = "minio" },
{ name = "mmcv" },
{ name = "moviepy" }, { name = "moviepy" },
{ name = "np" },
{ name = "numpy" }, { name = "numpy" },
{ name = "ollama" }, { name = "ollama" },
{ name = "opencv-python" }, { name = "opencv-python" },
@@ -3275,8 +3215,8 @@ requires-dist = [
{ name = "load-dotenv", specifier = ">=0.1.0" }, { name = "load-dotenv", specifier = ">=0.1.0" },
{ name = "loguru", specifier = ">=0.7.3" }, { name = "loguru", specifier = ">=0.7.3" },
{ name = "minio", specifier = ">=7.2.20" }, { name = "minio", specifier = ">=7.2.20" },
{ name = "mmcv", specifier = ">=2.2.0" },
{ name = "moviepy", specifier = "==1.0.3" }, { name = "moviepy", specifier = "==1.0.3" },
{ name = "np", specifier = ">=1.0.2" },
{ name = "numpy", specifier = "<2" }, { name = "numpy", specifier = "<2" },
{ name = "ollama", specifier = ">=0.6.1" }, { name = "ollama", specifier = ">=0.6.1" },
{ name = "opencv-python", specifier = ">=4.11.0.86" }, { name = "opencv-python", specifier = ">=4.11.0.86" },
@@ -3605,18 +3545,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/54/85/6ec269b0952ec7e36ba019125982cf11d91256a778c7c3f98a4c5043d283/xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829", size = 27876, upload-time = "2025-10-02T14:34:54.371Z" }, { url = "https://files.pythonhosted.org/packages/54/85/6ec269b0952ec7e36ba019125982cf11d91256a778c7c3f98a4c5043d283/xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829", size = 27876, upload-time = "2025-10-02T14:34:54.371Z" },
] ]
[[package]]
name = "yapf"
version = "0.43.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "platformdirs" },
]
sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25c7604178b48532fa5901f721bcf1b8d8148b13e5588/yapf-0.43.0.tar.gz", hash = "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", size = 254907, upload-time = "2024-11-14T00:11:41.584Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/37/81/6acd6601f61e31cfb8729d3da6d5df966f80f374b78eff83760714487338/yapf-0.43.0-py3-none-any.whl", hash = "sha256:224faffbc39c428cb095818cf6ef5511fdab6f7430a10783fdfb292ccf2852ca", size = 256158, upload-time = "2024-11-14T00:11:39.37Z" },
]
[[package]] [[package]]
name = "yarl" name = "yarl"
version = "1.22.0" version = "1.22.0"