Spaces:
Running
Running
TheEeeeLin
commited on
Commit
•
23cd1cf
1
Parent(s):
3519dcc
update
Browse files- Dockerfile +0 -35
- README.md +1 -325
- app.py +59 -11
- app.spec +0 -44
- assets/demoImage.png +0 -3
- assets/gradio-image.jpeg +0 -0
- assets/hivisionai.ico +0 -0
- deploy_api.py +0 -157
- docker-compose.yml +0 -11
- docs/api_CN.md +0 -554
- docs/api_EN.md +0 -553
- hivision/creator/__init__.py +4 -2
- hivision/creator/face_detector.py +87 -5
- hivision/creator/photo_adjuster.py +1 -1
- hivision/error.py +12 -0
- hivision/utils.py +29 -7
- inference.py +0 -107
- requests_api.py +0 -119
- requirements-app.txt +0 -2
- requirements.txt +2 -1
- scripts/build_pypi.py +0 -9
- test/create_id_photo.py +0 -22
- test/temp/.gitkeep +0 -1
Dockerfile
DELETED
@@ -1,35 +0,0 @@
|
|
1 |
-
FROM ubuntu:22.04
|
2 |
-
|
3 |
-
# apt换源,安装pip
|
4 |
-
RUN echo "==> 换成清华源,并更新..." && \
|
5 |
-
sed -i s@/archive.ubuntu.com/@/mirrors.tuna.tsinghua.edu.cn/@g /etc/apt/sources.list && \
|
6 |
-
sed -i s@/security.ubuntu.com/@/mirrors.tuna.tsinghua.edu.cn/@g /etc/apt/sources.list && \
|
7 |
-
apt-get clean && \
|
8 |
-
apt-get update
|
9 |
-
|
10 |
-
# 安装python3.10
|
11 |
-
RUN apt-get install -y python3 curl && \
|
12 |
-
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && \
|
13 |
-
python3 get-pip.py && \
|
14 |
-
pip3 install -U pip && \
|
15 |
-
pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
|
16 |
-
|
17 |
-
# 安装ffmpeg等库
|
18 |
-
RUN apt-get install libpython3.10-dev ffmpeg libgl1-mesa-glx libglib2.0-0 cmake -y && \
|
19 |
-
pip3 install --no-cache-dir cmake
|
20 |
-
|
21 |
-
WORKDIR /app
|
22 |
-
|
23 |
-
COPY . .
|
24 |
-
|
25 |
-
RUN pip3 install -r requirements.txt && \
|
26 |
-
pip3 install -r requirements-app.txt
|
27 |
-
|
28 |
-
RUN echo "==> Clean up..." && \
|
29 |
-
rm -rf ~/.cache/pip
|
30 |
-
|
31 |
-
# 指定工作目录
|
32 |
-
|
33 |
-
EXPOSE 7860
|
34 |
-
|
35 |
-
CMD [ "python3", "app.py", "--host", "0.0.0.0", "--port", "7860"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
@@ -7,328 +7,4 @@ sdk: gradio
|
|
7 |
sdk_version: 4.43.0
|
8 |
app_file: app.py
|
9 |
pinned: true
|
10 |
-
---
|
11 |
-
|
12 |
-
|
13 |
-
<div align="center">
|
14 |
-
<h1>HivisionIDPhoto</h1>
|
15 |
-
|
16 |
-
[English](README_EN.md) / 中文 / [日本語](README_JP.md) / [한국어](README_KO.md)
|
17 |
-
|
18 |
-
[![GitHub](https://img.shields.io/static/v1?label=GitHub&message=GitHub&color=black)](https://github.com/xiaolin199912/HivisionIDPhotos)
|
19 |
-
[![SwanHub Demo](https://swanhub.co/git/repo/SwanHub%2FAuto-README/file/preview?ref=main&path=swanhub.svg)](https://swanhub.co/ZeYiLin/HivisionIDPhotos/demo)
|
20 |
-
[![zhihu](https://img.shields.io/static/v1?label=知乎&message=知乎&color=blue)](https://zhuanlan.zhihu.com/p/638254028)
|
21 |
-
[![Spaces](https://img.shields.io/badge/🤗-Open%20in%20Spaces-blue)](https://huggingface.co/spaces/TheEeeeLin/HivisionIDPhotos)
|
22 |
-
<a href="https://docs.qq.com/doc/DUkpBdk90eWZFS2JW" target="_blank">
|
23 |
-
<img alt="Static Badge" src="https://img.shields.io/badge/WeChat-微信-4cb55e"></a>
|
24 |
-
|
25 |
-
<a href="https://trendshift.io/repositories/11622" target="_blank"><img src="https://trendshift.io/api/badge/repositories/11622" alt="Zeyi-Lin%2FHivisionIDPhotos | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
26 |
-
|
27 |
-
<img src="assets/demoImage.png" width=900>
|
28 |
-
|
29 |
-
</div>
|
30 |
-
|
31 |
-
<br>
|
32 |
-
|
33 |
-
> **相关项目**:
|
34 |
-
>
|
35 |
-
> - [SwanLab](https://github.com/SwanHubX/SwanLab):训练人像抠图模型全程用它来分析和监控,以及和实验室同学协作交流,大幅提升了训练效率。
|
36 |
-
|
37 |
-
<br>
|
38 |
-
|
39 |
-
# 🤩 项目更新
|
40 |
-
|
41 |
-
- 在线体验: [![SwanHub Demo](https://img.shields.io/static/v1?label=Demo&message=SwanHub%20Demo&color=blue)](https://swanhub.co/ZeYiLin/HivisionIDPhotos/demo)、[![Spaces](https://img.shields.io/badge/🤗-Open%20in%20Spaces-blue)](https://huggingface.co/spaces/TheEeeeLin/HivisionIDPhotos)
|
42 |
-
|
43 |
-
- 2024.9.5: 更新 [Restful API 文档](docs/api_CN.md)
|
44 |
-
- 2024.9.2: 更新**调整照片 KB 大小**,[DockerHub](https://hub.docker.com/r/linzeyi/hivision_idphotos/tags)
|
45 |
-
- 2023.12.1: 更新**API 部署(基于 fastapi)**
|
46 |
-
- 2023.6.20: 更新**预设尺寸菜单**
|
47 |
-
- 2023.6.19: 更新**排版照**
|
48 |
-
|
49 |
-
# Overview
|
50 |
-
|
51 |
-
> 🚀 谢谢你对我们的工作感兴趣。您可能还想查看我们在图像领域的其他成果,欢迎来信:zeyi.lin@swanhub.co.
|
52 |
-
|
53 |
-
HivisionIDPhoto 旨在开发一种实用的证件照智能制作算法。
|
54 |
-
|
55 |
-
它利用一套完善的模型工作流程,实现对多种用户拍照场景的识别、抠图与证件照生成。
|
56 |
-
|
57 |
-
**HivisionIDPhoto 可以做到:**
|
58 |
-
|
59 |
-
1. 轻量级抠图(仅需 **CPU** 即可快速推理)
|
60 |
-
2. 根据不同尺寸规格生成不同的标准证件照、六寸排版照
|
61 |
-
3. 美颜(waiting)
|
62 |
-
4. 智能换正装(waiting)
|
63 |
-
|
64 |
-
<div align="center">
|
65 |
-
<img src="assets/gradio-image.jpeg" width=900>
|
66 |
-
</div>
|
67 |
-
|
68 |
-
---
|
69 |
-
|
70 |
-
如果 HivisionIDPhoto 对你有帮助,请 star 这个 repo 或推荐给你的朋友,解决证件照应急制作问题!
|
71 |
-
|
72 |
-
<br>
|
73 |
-
|
74 |
-
# 🔧 环境安装与依赖
|
75 |
-
|
76 |
-
- Python >= 3.7(项目主要测试在 python 3.10)
|
77 |
-
- onnxruntime
|
78 |
-
- OpenCV
|
79 |
-
- Option: Linux, Windows, MacOS
|
80 |
-
|
81 |
-
**1. 克隆项目**
|
82 |
-
|
83 |
-
```bash
|
84 |
-
git clone https://github.com/Zeyi-Lin/HivisionIDPhotos.git
|
85 |
-
cd HivisionIDPhotos
|
86 |
-
```
|
87 |
-
|
88 |
-
**2. (重要)安装依赖环境**
|
89 |
-
|
90 |
-
> 建议 conda 创建一个 python3.10 虚拟环境后,执行以下命令
|
91 |
-
|
92 |
-
```bash
|
93 |
-
pip install -r requirements.txt
|
94 |
-
pip install -r requirements-app.txt
|
95 |
-
```
|
96 |
-
|
97 |
-
**3. 下载权重文件**
|
98 |
-
|
99 |
-
在我们的[Release](https://github.com/Zeyi-Lin/HivisionIDPhotos/releases/tag/pretrained-model)下载权重文件`hivision_modnet.onnx` (24.7MB),存到项目的`hivision/creator/weights`目录下。
|
100 |
-
|
101 |
-
<br>
|
102 |
-
|
103 |
-
# 🚀 运行 Gradio Demo
|
104 |
-
|
105 |
-
```bash
|
106 |
-
python app.py
|
107 |
-
```
|
108 |
-
|
109 |
-
运行程序将生成一个本地 Web 页面,在页面中可完成证件照的操作与交互。
|
110 |
-
|
111 |
-
<br>
|
112 |
-
|
113 |
-
# 🚀 Python 推理
|
114 |
-
|
115 |
-
## 1. 证件照制作
|
116 |
-
|
117 |
-
输入 1 张照片,获得 1 张标准证件照和 1 张高清证件照的 4 通道透明 png
|
118 |
-
|
119 |
-
```python
|
120 |
-
python inference.py -i demo/images/test.jpg -o ./idphoto.png --height 413 --width 295
|
121 |
-
```
|
122 |
-
|
123 |
-
## 2. 增加底色
|
124 |
-
|
125 |
-
输入 1 张 4 通道透明 png,获得 1 张增加了底色的图像)
|
126 |
-
|
127 |
-
```python
|
128 |
-
python inference.py -t add_background -i ./idphoto.png -o ./idhoto_ab.jpg -c 000000 -k 30
|
129 |
-
```
|
130 |
-
|
131 |
-
## 3. 得到六寸排版照
|
132 |
-
|
133 |
-
输入 1 张 3 通道照片,获得 1 张六寸排版照
|
134 |
-
|
135 |
-
```python
|
136 |
-
python inference.py -t generate_layout_photos -i ./idhoto_ab.jpg -o ./idhoto_layout.jpg --height 413 --width 295 -k 200
|
137 |
-
```
|
138 |
-
|
139 |
-
<br>
|
140 |
-
|
141 |
-
# ⚡️ 部署 API 服务
|
142 |
-
|
143 |
-
## 启动后端
|
144 |
-
|
145 |
-
```
|
146 |
-
python deploy_api.py
|
147 |
-
```
|
148 |
-
|
149 |
-
|
150 |
-
## 请求 API 服务 - Python Request
|
151 |
-
|
152 |
-
> 请求方式请参考 [API 文档](docs/api_CN.md),含 [cURL](docs/api_CN.md#curl-请求示例)、[Python](docs/api_CN.md#python-请求示例)、[Java](docs/api_CN.md#java-请求示例)、[Javascript](docs/api_CN.md#javascript-请求示例) 请求示例。
|
153 |
-
|
154 |
-
### 1. 证件照制作
|
155 |
-
|
156 |
-
输入 1 张照片,获得 1 张标准证件照和 1 张高清证件照的 4 通道透明 png
|
157 |
-
|
158 |
-
```python
|
159 |
-
import requests
|
160 |
-
|
161 |
-
url = "http://127.0.0.1:8080/idphoto"
|
162 |
-
input_image_path = "demo/images/test.jpg"
|
163 |
-
|
164 |
-
files = {"input_image": open(input_image_path, "rb")}
|
165 |
-
data = {"height": 413, "width": 295}
|
166 |
-
|
167 |
-
response = requests.post(url, files=files, data=data).json()
|
168 |
-
|
169 |
-
# response为一个json格式字典,包含status、image_base64_standard和image_base64_hd三项
|
170 |
-
print(response)
|
171 |
-
|
172 |
-
```
|
173 |
-
|
174 |
-
### 2. 增加底色
|
175 |
-
|
176 |
-
输入 1 张 4 通道透明 png,获得 1 张增加了底色的图像
|
177 |
-
|
178 |
-
```python
|
179 |
-
import requests
|
180 |
-
|
181 |
-
url = "http://127.0.0.1:8080/add_background"
|
182 |
-
input_image_path = "test.png"
|
183 |
-
|
184 |
-
files = {"input_image": open(input_image_path, "rb")}
|
185 |
-
data = {"color": '638cce', 'kb': None}
|
186 |
-
|
187 |
-
response = requests.post(url, files=files, data=data).json()
|
188 |
-
|
189 |
-
# response为一个json格式字典,包含status和image_base64
|
190 |
-
print(response)
|
191 |
-
```
|
192 |
-
|
193 |
-
### 3. 得到六寸排版照
|
194 |
-
|
195 |
-
输入 1 张 3 通道照片,获得 1 张六寸排版照
|
196 |
-
|
197 |
-
```python
|
198 |
-
import requests
|
199 |
-
|
200 |
-
url = "http://127.0.0.1:8080/generate_layout_photos"
|
201 |
-
input_image_path = "test.jpg"
|
202 |
-
|
203 |
-
files = {"input_image": open(input_image_path, "rb")}
|
204 |
-
data = {"height": 413, "width": 295, "kb": 200}
|
205 |
-
|
206 |
-
response = requests.post(url, files=files, data=data).json()
|
207 |
-
|
208 |
-
# response为一个json格式字典,包含status和image_base64
|
209 |
-
print(response)
|
210 |
-
```
|
211 |
-
|
212 |
-
<br>
|
213 |
-
|
214 |
-
# 🐳 Docker 部署
|
215 |
-
|
216 |
-
## 1. 拉取或构建镜像
|
217 |
-
|
218 |
-
> 以下方式三选一
|
219 |
-
|
220 |
-
**方式一:拉取镜像:**
|
221 |
-
|
222 |
-
```bash
|
223 |
-
docker pull linzeyi/hivision_idphotos:v1
|
224 |
-
docker tag linzeyi/hivision_idphotos:v1 hivision_idphotos
|
225 |
-
```
|
226 |
-
|
227 |
-
国内拉取加速:
|
228 |
-
|
229 |
-
```bash
|
230 |
-
docker pull registry.cn-hangzhou.aliyuncs.com/swanhub/hivision_idphotos:v1
|
231 |
-
docker tag registry.cn-hangzhou.aliyuncs.com/swanhub/hivision_idphotos:v1 hivision_idphotos
|
232 |
-
```
|
233 |
-
|
234 |
-
**方式二:Dockrfile 直接构建镜像:**
|
235 |
-
|
236 |
-
在确保将模型权重文件[hivision_modnet.onnx](https://github.com/Zeyi-Lin/HivisionIDPhotos/releases/tag/pretrained-model)放到`hivision/creator/weights`下后,在项目根目录执行:
|
237 |
-
|
238 |
-
```bash
|
239 |
-
docker build -t hivision_idphotos .
|
240 |
-
```
|
241 |
-
|
242 |
-
**方式三:Docker compose 构建:**
|
243 |
-
|
244 |
-
确保将模型权重文件 [hivision_modnet.onnx](https://github.com/Zeyi-Lin/HivisionIDPhotos/releases/tag/pretrained-model) 放在`hivision/creator/weights`下后,在项目根目录下执行:
|
245 |
-
|
246 |
-
```bash
|
247 |
-
docker compose build
|
248 |
-
```
|
249 |
-
|
250 |
-
镜像打包完成后,运行以下命令启动 Gradio 服务:
|
251 |
-
|
252 |
-
```bash
|
253 |
-
docker compose up -d
|
254 |
-
```
|
255 |
-
|
256 |
-
## 2. 运行 Gradio Demo
|
257 |
-
|
258 |
-
等待镜像封装完毕后,运行以下指令,即可开启 Gradio Demo 服务:
|
259 |
-
|
260 |
-
```bash
|
261 |
-
docker run -p 7860:7860 hivision_idphotos
|
262 |
-
```
|
263 |
-
|
264 |
-
在你的本地访问 [http://127.0.0.1:7860](http://127.0.0.1:7860/) 即可使用。
|
265 |
-
|
266 |
-
## 3. 运行 API 后端服务
|
267 |
-
|
268 |
-
```bash
|
269 |
-
docker run -p 8080:8080 hivision_idphotos python3 deploy_api.py
|
270 |
-
```
|
271 |
-
|
272 |
-
<br>
|
273 |
-
|
274 |
-
# 🌲 友情链接
|
275 |
-
|
276 |
-
- [HivisionIDPhotos-windows-GUI](https://github.com/zhaoyun0071/HivisionIDPhotos-windows-GUI)
|
277 |
-
|
278 |
-
<br>
|
279 |
-
|
280 |
-
# 📖 引用项目
|
281 |
-
|
282 |
-
1. MTCNN:
|
283 |
-
|
284 |
-
```bibtex
|
285 |
-
@software{ipazc_mtcnn_2021,
|
286 |
-
author = {ipazc},
|
287 |
-
title = {{MTCNN}},
|
288 |
-
url = {https://github.com/ipazc/mtcnn},
|
289 |
-
year = {2021},
|
290 |
-
publisher = {GitHub}
|
291 |
-
}
|
292 |
-
```
|
293 |
-
|
294 |
-
2. ModNet:
|
295 |
-
|
296 |
-
```bibtex
|
297 |
-
@software{zhkkke_modnet_2021,
|
298 |
-
author = {ZHKKKe},
|
299 |
-
title = {{ModNet}},
|
300 |
-
url = {https://github.com/ZHKKKe/MODNet},
|
301 |
-
year = {2021},
|
302 |
-
publisher = {GitHub}
|
303 |
-
}
|
304 |
-
```
|
305 |
-
|
306 |
-
<br>
|
307 |
-
|
308 |
-
# 💻 开发小贴士
|
309 |
-
|
310 |
-
**1. 如何修改预设尺寸?**
|
311 |
-
|
312 |
-
修改[size_list_CN.csv](demo/size_list_CN.csv)后再次运行 app.py 即可,其中第一列为尺寸名,第二列为高度,第三列为宽度。
|
313 |
-
|
314 |
-
<br>
|
315 |
-
|
316 |
-
# 📧 联系我们
|
317 |
-
|
318 |
-
如果您有任何问题,请发邮件至 zeyi.lin@swanhub.co
|
319 |
-
|
320 |
-
<br>
|
321 |
-
|
322 |
-
# 贡献者
|
323 |
-
|
324 |
-
<a href="https://github.com/Zeyi-Lin/HivisionIDPhotos/graphs/contributors">
|
325 |
-
<img src="https://contrib.rocks/image?repo=Zeyi-Lin/HivisionIDPhotos" />
|
326 |
-
</a>
|
327 |
-
|
328 |
-
[Zeyi-Lin](https://github.com/Zeyi-Lin)、[SAKURA-CAT](https://github.com/SAKURA-CAT)、[Feudalman](https://github.com/Feudalman)、[swpfY](https://github.com/swpfY)、[Kaikaikaifang](https://github.com/Kaikaikaifang)、[ShaohonChen](https://github.com/ShaohonChen)、[KashiwaByte](https://github.com/KashiwaByte)
|
329 |
-
|
330 |
-
<br>
|
331 |
-
|
332 |
-
# StarHistory
|
333 |
-
|
334 |
-
[![Star History Chart](https://api.star-history.com/svg?repos=Zeyi-Lin/HivisionIDPhotos&type=Date)](https://star-history.com/#Zeyi-Lin/HivisionIDPhotos&Date)
|
|
|
7 |
sdk_version: 4.43.0
|
8 |
app_file: app.py
|
9 |
pinned: true
|
10 |
+
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
CHANGED
@@ -1,16 +1,14 @@
|
|
1 |
import os
|
2 |
import gradio as gr
|
3 |
from hivision import IDCreator
|
4 |
-
from hivision.error import FaceError
|
5 |
from hivision.utils import add_background, resize_image_to_kb
|
6 |
from hivision.creator.layout_calculator import (
|
7 |
generate_layout_photo,
|
8 |
generate_layout_image,
|
9 |
)
|
10 |
-
from hivision.creator.human_matting import
|
11 |
-
|
12 |
-
extract_human,
|
13 |
-
)
|
14 |
import pathlib
|
15 |
import numpy as np
|
16 |
from demo.utils import csv_to_size_list
|
@@ -59,6 +57,7 @@ def idphoto_inference(
|
|
59 |
custom_image_kb,
|
60 |
language,
|
61 |
matting_model_option,
|
|
|
62 |
head_measure_ratio=0.2,
|
63 |
head_height_ratio=0.45,
|
64 |
top_distance_max=0.12,
|
@@ -78,7 +77,7 @@ def idphoto_inference(
|
|
78 |
"The width should not be greater than the length; the length and width should not be less than 100, and no more than 1800.": "宽度应不大于长度;长宽不应小于 100,大于 1800",
|
79 |
"Custom Color": "自定义底色",
|
80 |
"Custom": "自定义",
|
81 |
-
"The number of faces is not equal to 1": "人脸数量不等于 1",
|
82 |
"Solid Color": "纯色",
|
83 |
"Up-Down Gradient (White)": "上下渐变 (白)",
|
84 |
"Center Gradient (White)": "中心渐变 (白)",
|
@@ -92,7 +91,7 @@ def idphoto_inference(
|
|
92 |
"The width should not be greater than the length; the length and width should not be less than 100, and no more than 1800.": "The width should not be greater than the length; the length and width should not be less than 100, and no more than 1800.",
|
93 |
"Custom Color": "Custom Color",
|
94 |
"Custom": "Custom",
|
95 |
-
"The number of faces is not equal to 1": "The number of faces is not equal to 1",
|
96 |
"Solid Color": "Solid Color",
|
97 |
"Up-Down Gradient (White)": "Up-Down Gradient (White)",
|
98 |
"Center Gradient (White)": "Center Gradient (White)",
|
@@ -156,6 +155,11 @@ def idphoto_inference(
|
|
156 |
else:
|
157 |
creator.matting_handler = extract_human
|
158 |
|
|
|
|
|
|
|
|
|
|
|
159 |
change_bg_only = idphoto_json["size_mode"] in ["只换底", "Only Change Background"]
|
160 |
# 生成证件照
|
161 |
try:
|
@@ -170,8 +174,21 @@ def idphoto_inference(
|
|
170 |
result_message = {
|
171 |
img_output_standard: gr.update(value=None),
|
172 |
img_output_standard_hd: gr.update(value=None),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
173 |
notification: gr.update(
|
174 |
-
value=
|
175 |
visible=True,
|
176 |
),
|
177 |
}
|
@@ -284,6 +301,12 @@ if __name__ == "__main__":
|
|
284 |
# argparser.add_argument(
|
285 |
# "--host", type=str, default="127.0.0.1", help="The host of the server"
|
286 |
# )
|
|
|
|
|
|
|
|
|
|
|
|
|
287 |
|
288 |
# args = argparser.parse_args()
|
289 |
|
@@ -294,6 +317,12 @@ if __name__ == "__main__":
|
|
294 |
for file in os.listdir(os.path.join(root_dir, "hivision/creator/weights"))
|
295 |
if file.endswith(".onnx")
|
296 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
297 |
|
298 |
size_mode_CN = ["尺寸列表", "只换底", "自定义尺寸"]
|
299 |
size_mode_EN = ["Size List", "Only Change Background", "Custom Size"]
|
@@ -353,10 +382,16 @@ if __name__ == "__main__":
|
|
353 |
value="中文",
|
354 |
elem_id="language",
|
355 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
356 |
matting_model_options = gr.Dropdown(
|
357 |
choices=matting_model_list,
|
358 |
-
label="
|
359 |
-
value=
|
360 |
elem_id="matting_model",
|
361 |
)
|
362 |
|
@@ -482,6 +517,8 @@ if __name__ == "__main__":
|
|
482 |
choices=image_kb_CN,
|
483 |
value="不设置",
|
484 |
),
|
|
|
|
|
485 |
custom_image_kb_size: gr.update(label="KB 大小"),
|
486 |
notification: gr.update(label="状态"),
|
487 |
img_output_standard: gr.update(label="标准照"),
|
@@ -518,6 +555,8 @@ if __name__ == "__main__":
|
|
518 |
choices=image_kb_EN,
|
519 |
value="Not Set",
|
520 |
),
|
|
|
|
|
521 |
custom_image_kb_size: gr.update(label="KB size"),
|
522 |
notification: gr.update(label="Status"),
|
523 |
img_output_standard: gr.update(label="Standard photo"),
|
@@ -574,6 +613,8 @@ if __name__ == "__main__":
|
|
574 |
img_but,
|
575 |
render_options,
|
576 |
image_kb_options,
|
|
|
|
|
577 |
custom_image_kb_size,
|
578 |
notification,
|
579 |
img_output_standard,
|
@@ -614,6 +655,7 @@ if __name__ == "__main__":
|
|
614 |
custom_image_kb_size,
|
615 |
language_options,
|
616 |
matting_model_options,
|
|
|
617 |
],
|
618 |
outputs=[
|
619 |
img_output_standard,
|
@@ -624,4 +666,10 @@ if __name__ == "__main__":
|
|
624 |
],
|
625 |
)
|
626 |
|
627 |
-
demo.launch(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import os
|
2 |
import gradio as gr
|
3 |
from hivision import IDCreator
|
4 |
+
from hivision.error import FaceError, APIError
|
5 |
from hivision.utils import add_background, resize_image_to_kb
|
6 |
from hivision.creator.layout_calculator import (
|
7 |
generate_layout_photo,
|
8 |
generate_layout_image,
|
9 |
)
|
10 |
+
from hivision.creator.human_matting import *
|
11 |
+
from hivision.creator.face_detector import *
|
|
|
|
|
12 |
import pathlib
|
13 |
import numpy as np
|
14 |
from demo.utils import csv_to_size_list
|
|
|
57 |
custom_image_kb,
|
58 |
language,
|
59 |
matting_model_option,
|
60 |
+
face_detect_option,
|
61 |
head_measure_ratio=0.2,
|
62 |
head_height_ratio=0.45,
|
63 |
top_distance_max=0.12,
|
|
|
77 |
"The width should not be greater than the length; the length and width should not be less than 100, and no more than 1800.": "宽度应不大于长度;长宽不应小于 100,大于 1800",
|
78 |
"Custom Color": "自定义底色",
|
79 |
"Custom": "自定义",
|
80 |
+
"The number of faces is not equal to 1, please upload an image with a single face. If the actual number of faces is 1, it may be an issue with the accuracy of the detection model. Please switch to a different face detection model on the left or raise a Github Issue to notify the author.": "人脸数量不等于 1,请上传单张人脸的图像。如果实际人脸数量为 1,可能是检测模型精度的问题,请在左边更换人脸检测模型或给作者提Github Issue。",
|
81 |
"Solid Color": "纯色",
|
82 |
"Up-Down Gradient (White)": "上下渐变 (白)",
|
83 |
"Center Gradient (White)": "中心渐变 (白)",
|
|
|
91 |
"The width should not be greater than the length; the length and width should not be less than 100, and no more than 1800.": "The width should not be greater than the length; the length and width should not be less than 100, and no more than 1800.",
|
92 |
"Custom Color": "Custom Color",
|
93 |
"Custom": "Custom",
|
94 |
+
"The number of faces is not equal to 1, please upload an image with a single face. If the actual number of faces is 1, it may be an issue with the accuracy of the detection model. Please switch to a different face detection model on the left or raise a Github Issue to notify the author.": "The number of faces is not equal to 1, please upload an image with a single face. If the actual number of faces is 1, it may be an issue with the accuracy of the detection model. Please switch to a different face detection model on the left or raise a Github Issue to notify the author.",
|
95 |
"Solid Color": "Solid Color",
|
96 |
"Up-Down Gradient (White)": "Up-Down Gradient (White)",
|
97 |
"Center Gradient (White)": "Center Gradient (White)",
|
|
|
155 |
else:
|
156 |
creator.matting_handler = extract_human
|
157 |
|
158 |
+
if face_detect_option == "mtcnn":
|
159 |
+
creator.detection_handler = detect_face_mtcnn
|
160 |
+
else:
|
161 |
+
creator.detection_handler = detect_face_face_plusplus
|
162 |
+
|
163 |
change_bg_only = idphoto_json["size_mode"] in ["只换底", "Only Change Background"]
|
164 |
# 生成证件照
|
165 |
try:
|
|
|
174 |
result_message = {
|
175 |
img_output_standard: gr.update(value=None),
|
176 |
img_output_standard_hd: gr.update(value=None),
|
177 |
+
img_output_layout: gr.update(visible=False),
|
178 |
+
notification: gr.update(
|
179 |
+
value=text_lang_map[language][
|
180 |
+
"The number of faces is not equal to 1, please upload an image with a single face. If the actual number of faces is 1, it may be an issue with the accuracy of the detection model. Please switch to a different face detection model on the left or raise a Github Issue to notify the author."
|
181 |
+
],
|
182 |
+
visible=True,
|
183 |
+
),
|
184 |
+
}
|
185 |
+
except APIError as e:
|
186 |
+
result_message = {
|
187 |
+
img_output_standard: gr.update(value=None),
|
188 |
+
img_output_standard_hd: gr.update(value=None),
|
189 |
+
img_output_layout: gr.update(visible=False),
|
190 |
notification: gr.update(
|
191 |
+
value=f"Please make sure you have correctly set up the Face++ API Key and Secret.\nAPI Error\nStatus Code is {e.status_code}\nPossible errors are: {e}\n",
|
192 |
visible=True,
|
193 |
),
|
194 |
}
|
|
|
301 |
# argparser.add_argument(
|
302 |
# "--host", type=str, default="127.0.0.1", help="The host of the server"
|
303 |
# )
|
304 |
+
# argparser.add_argument(
|
305 |
+
# "--root_path",
|
306 |
+
# type=str,
|
307 |
+
# default=None,
|
308 |
+
# help="The root path of the server, default is None (='/'), e.g. '/myapp'",
|
309 |
+
# )
|
310 |
|
311 |
# args = argparser.parse_args()
|
312 |
|
|
|
317 |
for file in os.listdir(os.path.join(root_dir, "hivision/creator/weights"))
|
318 |
if file.endswith(".onnx")
|
319 |
]
|
320 |
+
DEFAULT_MATTING_MODEL = "modnet_photographic_portrait_matting"
|
321 |
+
if DEFAULT_MATTING_MODEL in matting_model_list:
|
322 |
+
matting_model_list.remove(DEFAULT_MATTING_MODEL)
|
323 |
+
matting_model_list.insert(0, DEFAULT_MATTING_MODEL)
|
324 |
+
|
325 |
+
face_detect_model_list = ["mtcnn", "face++ (联网API)"]
|
326 |
|
327 |
size_mode_CN = ["尺寸列表", "只换底", "自定义尺寸"]
|
328 |
size_mode_EN = ["Size List", "Only Change Background", "Custom Size"]
|
|
|
382 |
value="中文",
|
383 |
elem_id="language",
|
384 |
)
|
385 |
+
face_detect_model_options = gr.Dropdown(
|
386 |
+
choices=face_detect_model_list,
|
387 |
+
label="人脸检测模型",
|
388 |
+
value=face_detect_model_list[0],
|
389 |
+
elem_id="matting_model",
|
390 |
+
)
|
391 |
matting_model_options = gr.Dropdown(
|
392 |
choices=matting_model_list,
|
393 |
+
label="抠图模型",
|
394 |
+
value=matting_model_list[0],
|
395 |
elem_id="matting_model",
|
396 |
)
|
397 |
|
|
|
517 |
choices=image_kb_CN,
|
518 |
value="不设置",
|
519 |
),
|
520 |
+
matting_model_options: gr.update(label="抠图模型"),
|
521 |
+
face_detect_model_options: gr.update(label="人脸检测模型"),
|
522 |
custom_image_kb_size: gr.update(label="KB 大小"),
|
523 |
notification: gr.update(label="状态"),
|
524 |
img_output_standard: gr.update(label="标准照"),
|
|
|
555 |
choices=image_kb_EN,
|
556 |
value="Not Set",
|
557 |
),
|
558 |
+
matting_model_options: gr.update(label="Matting model"),
|
559 |
+
face_detect_model_options: gr.update(label="Face detect model"),
|
560 |
custom_image_kb_size: gr.update(label="KB size"),
|
561 |
notification: gr.update(label="Status"),
|
562 |
img_output_standard: gr.update(label="Standard photo"),
|
|
|
613 |
img_but,
|
614 |
render_options,
|
615 |
image_kb_options,
|
616 |
+
matting_model_options,
|
617 |
+
face_detect_model_options,
|
618 |
custom_image_kb_size,
|
619 |
notification,
|
620 |
img_output_standard,
|
|
|
655 |
custom_image_kb_size,
|
656 |
language_options,
|
657 |
matting_model_options,
|
658 |
+
face_detect_model_options,
|
659 |
],
|
660 |
outputs=[
|
661 |
img_output_standard,
|
|
|
666 |
],
|
667 |
)
|
668 |
|
669 |
+
demo.launch(
|
670 |
+
# server_name=args.host,
|
671 |
+
# server_port=args.port,
|
672 |
+
# show_api=False,
|
673 |
+
# favicon_path=os.path.join(root_dir, "assets/hivision_logo.png"),
|
674 |
+
# root_path=args.root_path,
|
675 |
+
)
|
app.spec
DELETED
@@ -1,44 +0,0 @@
|
|
1 |
-
# -*- mode: python ; coding: utf-8 -*-
|
2 |
-
from PyInstaller.utils.hooks import collect_data_files
|
3 |
-
|
4 |
-
datas = [('hivisionai', 'hivisionai'), ('hivision_modnet.onnx', '.'), ('size_list_CN.csv', '.')]
|
5 |
-
datas += collect_data_files('gradio_client')
|
6 |
-
datas += collect_data_files('gradio')
|
7 |
-
|
8 |
-
|
9 |
-
a = Analysis(
|
10 |
-
['app/web.py'],
|
11 |
-
pathex=[],
|
12 |
-
binaries=[],
|
13 |
-
datas=datas,
|
14 |
-
hiddenimports=[],
|
15 |
-
hookspath=[],
|
16 |
-
hooksconfig={},
|
17 |
-
runtime_hooks=[],
|
18 |
-
excludes=[],
|
19 |
-
noarchive=False,
|
20 |
-
optimize=0,
|
21 |
-
)
|
22 |
-
pyz = PYZ(a.pure)
|
23 |
-
|
24 |
-
exe = EXE(
|
25 |
-
pyz,
|
26 |
-
a.scripts,
|
27 |
-
a.binaries,
|
28 |
-
a.datas,
|
29 |
-
[],
|
30 |
-
name='HivisionIDPhotos',
|
31 |
-
debug=False,
|
32 |
-
bootloader_ignore_signals=False,
|
33 |
-
strip=False,
|
34 |
-
upx=True,
|
35 |
-
upx_exclude=[],
|
36 |
-
runtime_tmpdir=None,
|
37 |
-
console=True,
|
38 |
-
disable_windowed_traceback=False,
|
39 |
-
argv_emulation=False,
|
40 |
-
target_arch=None,
|
41 |
-
codesign_identity=None,
|
42 |
-
entitlements_file=None,
|
43 |
-
icon=['assets\hivisionai.ico'],
|
44 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assets/demoImage.png
DELETED
Git LFS Details
|
assets/gradio-image.jpeg
DELETED
Binary file (395 kB)
|
|
assets/hivisionai.ico
DELETED
Binary file (21.7 kB)
|
|
deploy_api.py
DELETED
@@ -1,157 +0,0 @@
|
|
1 |
-
from fastapi import FastAPI, UploadFile, Form
|
2 |
-
import onnxruntime
|
3 |
-
from hivision import IDCreator
|
4 |
-
from hivision.error import FaceError
|
5 |
-
from hivision.creator.layout_calculator import (
|
6 |
-
generate_layout_photo,
|
7 |
-
generate_layout_image,
|
8 |
-
)
|
9 |
-
from hivision.utils import add_background, resize_image_to_kb_base64, hex_to_rgb
|
10 |
-
import base64
|
11 |
-
import numpy as np
|
12 |
-
import cv2
|
13 |
-
import os
|
14 |
-
|
15 |
-
app = FastAPI()
|
16 |
-
creator = IDCreator()
|
17 |
-
|
18 |
-
|
19 |
-
# 将图像转换为Base64编码
|
20 |
-
def numpy_2_base64(img: np.ndarray):
|
21 |
-
retval, buffer = cv2.imencode(".png", img)
|
22 |
-
base64_image = base64.b64encode(buffer).decode("utf-8")
|
23 |
-
|
24 |
-
return base64_image
|
25 |
-
|
26 |
-
|
27 |
-
# 证件照智能制作接口
|
28 |
-
@app.post("/idphoto")
|
29 |
-
async def idphoto_inference(
|
30 |
-
input_image: UploadFile,
|
31 |
-
height: str = Form(...),
|
32 |
-
width: str = Form(...),
|
33 |
-
head_measure_ratio=0.2,
|
34 |
-
head_height_ratio=0.45,
|
35 |
-
top_distance_max=0.12,
|
36 |
-
top_distance_min=0.10,
|
37 |
-
):
|
38 |
-
|
39 |
-
image_bytes = await input_image.read()
|
40 |
-
nparr = np.frombuffer(image_bytes, np.uint8)
|
41 |
-
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
|
42 |
-
|
43 |
-
# 将字符串转为元组
|
44 |
-
size = (int(height), int(width))
|
45 |
-
try:
|
46 |
-
result = creator(
|
47 |
-
img,
|
48 |
-
size=size,
|
49 |
-
head_measure_ratio=head_measure_ratio,
|
50 |
-
head_height_ratio=head_height_ratio,
|
51 |
-
)
|
52 |
-
except FaceError:
|
53 |
-
result_message = {"status": False}
|
54 |
-
# 如果检测到人脸数量等于1, 则返回标准证和高清照结果(png 4通道图像)
|
55 |
-
else:
|
56 |
-
result_message = {
|
57 |
-
"status": True,
|
58 |
-
"image_base64_standard": numpy_2_base64(result.standard),
|
59 |
-
"image_base64_hd": numpy_2_base64(result.hd),
|
60 |
-
}
|
61 |
-
|
62 |
-
return result_message
|
63 |
-
|
64 |
-
|
65 |
-
# 透明图像添加纯色背景接口
|
66 |
-
@app.post("/add_background")
|
67 |
-
async def photo_add_background(
|
68 |
-
input_image: UploadFile, color: str = Form(...), kb: str = Form(None)
|
69 |
-
):
|
70 |
-
image_bytes = await input_image.read()
|
71 |
-
nparr = np.frombuffer(image_bytes, np.uint8)
|
72 |
-
img = cv2.imdecode(nparr, cv2.IMREAD_UNCHANGED)
|
73 |
-
|
74 |
-
color = hex_to_rgb(color)
|
75 |
-
color = (color[2], color[1], color[0])
|
76 |
-
|
77 |
-
result_image = add_background(img, bgr=color).astype(np.uint8)
|
78 |
-
|
79 |
-
if kb:
|
80 |
-
result_image = cv2.cvtColor(result_image, cv2.COLOR_RGB2BGR)
|
81 |
-
result_image_base64 = resize_image_to_kb_base64(result_image, int(kb))
|
82 |
-
else:
|
83 |
-
result_image_base64 = numpy_2_base64(result_image)
|
84 |
-
|
85 |
-
# try:
|
86 |
-
|
87 |
-
result_messgae = {
|
88 |
-
"status": True,
|
89 |
-
"image_base64": result_image_base64,
|
90 |
-
}
|
91 |
-
|
92 |
-
# except Exception as e:
|
93 |
-
# print(e)
|
94 |
-
# result_messgae = {
|
95 |
-
# "status": False,
|
96 |
-
# "error": e
|
97 |
-
# }
|
98 |
-
|
99 |
-
return result_messgae
|
100 |
-
|
101 |
-
|
102 |
-
# 六寸排版照生成接口
|
103 |
-
@app.post("/generate_layout_photos")
|
104 |
-
async def generate_layout_photos(
|
105 |
-
input_image: UploadFile,
|
106 |
-
height: str = Form(...),
|
107 |
-
width: str = Form(...),
|
108 |
-
kb: str = Form(None),
|
109 |
-
):
|
110 |
-
# try:
|
111 |
-
image_bytes = await input_image.read()
|
112 |
-
nparr = np.frombuffer(image_bytes, np.uint8)
|
113 |
-
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
|
114 |
-
|
115 |
-
size = (int(height), int(width))
|
116 |
-
|
117 |
-
typography_arr, typography_rotate = generate_layout_photo(
|
118 |
-
input_height=size[0], input_width=size[1]
|
119 |
-
)
|
120 |
-
|
121 |
-
result_layout_image = generate_layout_image(
|
122 |
-
img, typography_arr, typography_rotate, height=size[0], width=size[1]
|
123 |
-
).astype(np.uint8)
|
124 |
-
|
125 |
-
if kb:
|
126 |
-
result_layout_image = cv2.cvtColor(result_layout_image, cv2.COLOR_RGB2BGR)
|
127 |
-
result_layout_image_base64 = resize_image_to_kb_base64(
|
128 |
-
result_layout_image, int(kb)
|
129 |
-
)
|
130 |
-
else:
|
131 |
-
result_layout_image_base64 = numpy_2_base64(result_layout_image)
|
132 |
-
|
133 |
-
result_messgae = {
|
134 |
-
"status": True,
|
135 |
-
"image_base64": result_layout_image_base64,
|
136 |
-
}
|
137 |
-
|
138 |
-
# except Exception as e:
|
139 |
-
# result_messgae = {
|
140 |
-
# "status": False,
|
141 |
-
# }
|
142 |
-
|
143 |
-
return result_messgae
|
144 |
-
|
145 |
-
|
146 |
-
if __name__ == "__main__":
|
147 |
-
import uvicorn
|
148 |
-
|
149 |
-
# 加载权重文件
|
150 |
-
root_dir = os.path.dirname(os.path.abspath(__file__))
|
151 |
-
HY_HUMAN_MATTING_WEIGHTS_PATH = os.path.join(
|
152 |
-
root_dir, "hivision/creator/weights/hivision_modnet.onnx"
|
153 |
-
)
|
154 |
-
sess = onnxruntime.InferenceSession(HY_HUMAN_MATTING_WEIGHTS_PATH)
|
155 |
-
|
156 |
-
# 在8080端口运行推理服务
|
157 |
-
uvicorn.run(app, host="0.0.0.0", port=8080)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docker-compose.yml
DELETED
@@ -1,11 +0,0 @@
|
|
1 |
-
version: "3.8"
|
2 |
-
|
3 |
-
services:
|
4 |
-
hivision_idphotos:
|
5 |
-
build:
|
6 |
-
context: .
|
7 |
-
dockerfile: Dockerfile
|
8 |
-
image: hivision_idphotos2
|
9 |
-
command: python3 app.py --host 0.0.0.0 --port 7860
|
10 |
-
ports:
|
11 |
-
- "7860:7860"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docs/api_CN.md
DELETED
@@ -1,554 +0,0 @@
|
|
1 |
-
# API Docs
|
2 |
-
|
3 |
-
中文 | [English](api_EN.md)
|
4 |
-
|
5 |
-
## 目录
|
6 |
-
|
7 |
-
- [开始之前:开启后端服务](#开始之前开启后端服务)
|
8 |
-
- [接口功能说明](#接口功能说明)
|
9 |
-
- [cURL 请求示例](#curl-请求示例)
|
10 |
-
- [Python 请求示例](#python-请求示例)
|
11 |
-
- [Python Requests 请求方法](#1️⃣-python-requests-请求方法)
|
12 |
-
- [Python 脚本请求方法](#2️⃣-python-脚本请求方法)
|
13 |
-
- [Java 请求示例](#java-请求示例)
|
14 |
-
- [Javascript 请求示例](#javascript-请求示例)
|
15 |
-
|
16 |
-
## 开始之前:开启后端服务
|
17 |
-
|
18 |
-
在请求 API 之前,请先运行后端服务
|
19 |
-
|
20 |
-
```bash
|
21 |
-
python delopy_api.py
|
22 |
-
```
|
23 |
-
|
24 |
-
<br>
|
25 |
-
|
26 |
-
## 接口功能说明
|
27 |
-
|
28 |
-
### 1.生成证件照(底透明)
|
29 |
-
|
30 |
-
接口名:`idphoto`
|
31 |
-
|
32 |
-
`生成证件照`接口的逻辑是发送一张 RGB 图像,输出一张标准证件照和一张高清证件照:
|
33 |
-
|
34 |
-
- **高清证件照**:根据`size`的宽高比例制作的证件照,文件名为`output_image_dir`增加`_hd`后缀
|
35 |
-
- **标准证件照**:尺寸等于`size`,由高清证件照缩放而来,文件名为`output_image_dir`
|
36 |
-
|
37 |
-
需要注意的是,生成的两张照片都是透明的(RGBA 四通道图像),要生成完整的证件照,还需要下面的`添加背景色`接口。
|
38 |
-
|
39 |
-
> 问:为什么这么设计?
|
40 |
-
> 答:因为在实际产品中,经常用户会频繁切换底色预览效果,直接给透明底图像,由前端 js 代码合成颜色是更好体验的做法。
|
41 |
-
|
42 |
-
### 2.添加背景色
|
43 |
-
|
44 |
-
接口名:`add_background`
|
45 |
-
|
46 |
-
`添加背景色`接口的逻辑是发送一张 RGBA 图像,根据`color`添加背景色,合成一张 JPG 图像。
|
47 |
-
|
48 |
-
### 3.生成六寸排版照
|
49 |
-
|
50 |
-
接口名:`generate_layout_photos`
|
51 |
-
|
52 |
-
`生成六寸排版照`接口的逻辑是发送一张 RGB 图像(一般为添加背景色之后的证件照),根据`size`进行照片排布,然后生成一张六寸排版照。
|
53 |
-
|
54 |
-
<br>
|
55 |
-
|
56 |
-
|
57 |
-
## cURL 请求示例
|
58 |
-
|
59 |
-
cURL 是一个命令行工具,用于使用各种网络协议传输数据。以下是使用 cURL 调用这些 API 的示例。
|
60 |
-
|
61 |
-
### 1. 生成证件照(底透明)
|
62 |
-
|
63 |
-
```bash
|
64 |
-
curl -X POST "http://127.0.0.1:8080/idphoto" \
|
65 |
-
-F "input_image=@demo/images/test.jpg" \
|
66 |
-
-F "height=413" \
|
67 |
-
-F "width=295"
|
68 |
-
```
|
69 |
-
|
70 |
-
### 2. 添加背景色
|
71 |
-
|
72 |
-
```bash
|
73 |
-
curl -X POST "http://127.0.0.1:8080/add_background" \
|
74 |
-
-F "input_image=@test.png" \
|
75 |
-
-F "color=638cce" \
|
76 |
-
-F "kb=200"
|
77 |
-
```
|
78 |
-
|
79 |
-
### 3. 生成六寸排版照
|
80 |
-
|
81 |
-
```bash
|
82 |
-
curl -X POST "http://127.0.0.1:8080/generate_layout_photos" \
|
83 |
-
-F "input_image=@test.jpg" \
|
84 |
-
-F "height=413" \
|
85 |
-
-F "width=295" \
|
86 |
-
-F "kb=200"
|
87 |
-
```
|
88 |
-
|
89 |
-
|
90 |
-
## Python 请求示例
|
91 |
-
|
92 |
-
### 1️⃣ Python Requests 请求方法
|
93 |
-
|
94 |
-
#### 1.生成证件照(底透明)
|
95 |
-
|
96 |
-
```python
|
97 |
-
import requests
|
98 |
-
|
99 |
-
url = "http://127.0.0.1:8080/idphoto"
|
100 |
-
input_image_path = "images/test.jpg"
|
101 |
-
|
102 |
-
files = {"input_image": open(input_image_path, "rb")}
|
103 |
-
data = {"height": 413, "width": 295}
|
104 |
-
|
105 |
-
response = requests.post(url, files=files, data=data).json()
|
106 |
-
|
107 |
-
# response为一个json格式字典,包含status、image_base64_standard和image_base64_hd三项
|
108 |
-
print(response)
|
109 |
-
|
110 |
-
```
|
111 |
-
|
112 |
-
#### 2.添加背景色
|
113 |
-
|
114 |
-
```python
|
115 |
-
import requests
|
116 |
-
|
117 |
-
url = "http://127.0.0.1:8080/add_background"
|
118 |
-
input_image_path = "test.png"
|
119 |
-
|
120 |
-
files = {"input_image": open(input_image_path, "rb")}
|
121 |
-
data = {"color": '638cce', 'kb': None}
|
122 |
-
|
123 |
-
response = requests.post(url, files=files, data=data).json()
|
124 |
-
|
125 |
-
# response为一个json格式字典,包含status和image_base64
|
126 |
-
print(response)
|
127 |
-
```
|
128 |
-
|
129 |
-
#### 3.生成六寸排版照
|
130 |
-
|
131 |
-
```python
|
132 |
-
import requests
|
133 |
-
|
134 |
-
url = "http://127.0.0.1:8080/generate_layout_photos"
|
135 |
-
input_image_path = "test.jpg"
|
136 |
-
|
137 |
-
files = {"input_image": open(input_image_path, "rb")}
|
138 |
-
data = {"height": 413, "width": 295, "kb": 200}
|
139 |
-
|
140 |
-
response = requests.post(url, files=files, data=data).json()
|
141 |
-
|
142 |
-
# response为一个json格式字典,包含status和image_base64
|
143 |
-
print(response)
|
144 |
-
```
|
145 |
-
|
146 |
-
<br>
|
147 |
-
|
148 |
-
### 2️⃣ Python 脚本请求方法
|
149 |
-
|
150 |
-
```bash
|
151 |
-
python requests_api.py -u <URL> -t <TYPE> -i <INPUT_IMAGE_DIR> -o <OUTPUT_IMAGE_DIR> [--height <HEIGHT>] [--width <WIDTH>] [-c <COLOR>] [-k <KB>]
|
152 |
-
```
|
153 |
-
|
154 |
-
#### 参数说明
|
155 |
-
|
156 |
-
##### 基本参数
|
157 |
-
|
158 |
-
- `-u`, `--url`
|
159 |
-
|
160 |
-
- **描述**: API 服务的 URL。
|
161 |
-
- **默认值**: `http://127.0.0.1:8080`
|
162 |
-
|
163 |
-
- `-t`, `--type`
|
164 |
-
|
165 |
-
- **描述**: 请求 API 的种类,可选值有 `idphoto`、`add_background` 和 `generate_layout_photos`。分别代表证件照制作、透明图加背景和排版照生成。
|
166 |
-
- **默认值**: `idphoto`
|
167 |
-
|
168 |
-
- `-i`, `--input_image_dir`
|
169 |
-
|
170 |
-
- **描述**: 输入图像路径。
|
171 |
-
- **必需**: 是
|
172 |
-
- **示例**: `./input_images/photo.jpg`
|
173 |
-
|
174 |
-
- `-o`, `--output_image_dir`
|
175 |
-
- **描述**: 保存图像路径。
|
176 |
-
- **必需**: 是
|
177 |
-
- **示例**: `./output_images/processed_photo.jpg`
|
178 |
-
|
179 |
-
##### 可选参数
|
180 |
-
|
181 |
-
- `--height`,
|
182 |
-
- **描述**: 标准证件照的输出尺寸的高度。
|
183 |
-
- **默认值**: 413
|
184 |
-
- `--width`,
|
185 |
-
|
186 |
-
- **描述**: 标准证件照的输出尺寸的宽度。
|
187 |
-
- **默认值**: 295
|
188 |
-
|
189 |
-
- `-c`, `--color`
|
190 |
-
|
191 |
-
- **描述**: 给透明图增加背景色,格式为 Hex(如#638cce),仅在 type 为`add_background`时生效
|
192 |
-
- **默认值**: `638cce`
|
193 |
-
|
194 |
-
- `-k`, `--kb`
|
195 |
-
- **描述**: 输出照片的 KB 值,仅在 type 为`add_background`和`generate_layout_photos`时生效,值为 None 时不做设置。
|
196 |
-
- **默认值**: `None`
|
197 |
-
- **示例**: `50`
|
198 |
-
|
199 |
-
### 1.生成证件照(底透明)
|
200 |
-
|
201 |
-
```bash
|
202 |
-
python requests_api.py \
|
203 |
-
-u http://127.0.0.1:8080 \
|
204 |
-
-t idphoto \
|
205 |
-
-i ./photo.jpg \
|
206 |
-
-o ./idphoto.png \
|
207 |
-
--height 413 \
|
208 |
-
--width 295
|
209 |
-
```
|
210 |
-
|
211 |
-
### 2.添加背景色
|
212 |
-
|
213 |
-
```bash
|
214 |
-
python requests_api.py \
|
215 |
-
-u http://127.0.0.1:8080 \
|
216 |
-
-t add_background \
|
217 |
-
-i ./idphoto.png \
|
218 |
-
-o ./idphoto_with_background.jpg \
|
219 |
-
-c 638cce \
|
220 |
-
-k 50
|
221 |
-
```
|
222 |
-
|
223 |
-
### 3.生成六寸排版照
|
224 |
-
|
225 |
-
```bash
|
226 |
-
python requests_api.py \
|
227 |
-
-u http://127.0.0.1:8080 \
|
228 |
-
-t generate_layout_photos \
|
229 |
-
-i ./idphoto_with_background.jpg \
|
230 |
-
-o ./layout_photo.jpg \
|
231 |
-
--height 413 \
|
232 |
-
--width 295 \
|
233 |
-
-k 200
|
234 |
-
```
|
235 |
-
|
236 |
-
### 请求失败的情况
|
237 |
-
|
238 |
-
- 照片中检测到的人脸大于 1,则失败
|
239 |
-
|
240 |
-
## Java 请求示例
|
241 |
-
|
242 |
-
### 添加 maven 依赖
|
243 |
-
|
244 |
-
```java
|
245 |
-
<dependency>
|
246 |
-
<groupId>cn.hutool</groupId>
|
247 |
-
<artifactId>hutool-all</artifactId>
|
248 |
-
<version>5.8.16</version>
|
249 |
-
</dependency>
|
250 |
-
|
251 |
-
<dependency>
|
252 |
-
<groupId>commons-io</groupId>
|
253 |
-
<artifactId>commons-io</artifactId>
|
254 |
-
<version>2.6</version>
|
255 |
-
</dependency>
|
256 |
-
```
|
257 |
-
|
258 |
-
### 运行代码
|
259 |
-
|
260 |
-
#### 1.生成证件照(底透明)
|
261 |
-
|
262 |
-
```java
|
263 |
-
/**
|
264 |
-
* 生成证件照(底透明) /idphoto 接口
|
265 |
-
* @param inputImageDir 文件地址
|
266 |
-
* @return
|
267 |
-
* @throws IOException
|
268 |
-
*/
|
269 |
-
public static String requestIdPhoto(String inputImageDir) throws IOException {
|
270 |
-
String url = BASE_URL+"/idphoto";
|
271 |
-
// 创建文件对象
|
272 |
-
File inputFile = new File(inputImageDir);
|
273 |
-
Map<String, Object> paramMap=new HashMap<>();
|
274 |
-
paramMap.put("input_image",inputFile);
|
275 |
-
paramMap.put("height","413");
|
276 |
-
paramMap.put("width","295");
|
277 |
-
//包含status、image_base64_standard和image_base64_hd三项
|
278 |
-
return HttpUtil.post(url, paramMap);
|
279 |
-
}
|
280 |
-
```
|
281 |
-
|
282 |
-
#### 2.添加背景色
|
283 |
-
|
284 |
-
```java
|
285 |
-
/**
|
286 |
-
* 添加背景色 /add_background 接口
|
287 |
-
* @param inputImageDir 文件地址
|
288 |
-
* @return
|
289 |
-
* @throws IOException
|
290 |
-
*/
|
291 |
-
public static String requestAddBackground(String inputImageDir) throws IOException {
|
292 |
-
String url = BASE_URL+"/add_background";
|
293 |
-
// 创建文件对象
|
294 |
-
File inputFile = new File(inputImageDir);
|
295 |
-
Map<String, Object> paramMap=new HashMap<>();
|
296 |
-
paramMap.put("input_image",inputFile);
|
297 |
-
paramMap.put("color","638cce");
|
298 |
-
paramMap.put("kb","200");
|
299 |
-
// response为一个json格式字典,包含status和image_base64
|
300 |
-
return HttpUtil.post(url, paramMap);
|
301 |
-
}
|
302 |
-
```
|
303 |
-
|
304 |
-
#### 3.生成六寸排版照
|
305 |
-
|
306 |
-
```java
|
307 |
-
/**
|
308 |
-
* 生成六寸排版照 /generate_layout_photos 接口
|
309 |
-
* @param inputImageDir 文件地址
|
310 |
-
* @return
|
311 |
-
* @throws IOException
|
312 |
-
*/
|
313 |
-
public static String requestGenerateLayoutPhotos(String inputImageDir) throws IOException {
|
314 |
-
String url = BASE_URL+"/generate_layout_photos";
|
315 |
-
// 创建文件对象
|
316 |
-
File inputFile = new File(inputImageDir);
|
317 |
-
Map<String, Object> paramMap=new HashMap<>();
|
318 |
-
paramMap.put("input_image",inputFile);
|
319 |
-
paramMap.put("height","413");
|
320 |
-
paramMap.put("width","295");
|
321 |
-
paramMap.put("kb","200");
|
322 |
-
//response为一个json格式字典,包含status和image_base64
|
323 |
-
return HttpUtil.post(url, paramMap);
|
324 |
-
}
|
325 |
-
```
|
326 |
-
|
327 |
-
#### 4.汇总
|
328 |
-
|
329 |
-
```java
|
330 |
-
|
331 |
-
import cn.hutool.http.HttpUtil;
|
332 |
-
import cn.hutool.json.JSONObject;
|
333 |
-
import cn.hutool.json.JSONUtil;
|
334 |
-
import org.apache.commons.io.FileUtils;
|
335 |
-
import org.springframework.util.StringUtils;
|
336 |
-
import java.io.File;
|
337 |
-
import java.io.IOException;
|
338 |
-
import java.util.Base64;
|
339 |
-
import java.util.HashMap;
|
340 |
-
import java.util.Map;
|
341 |
-
|
342 |
-
/**
|
343 |
-
* @author: qingshuang
|
344 |
-
* @createDate: 2024/09/05
|
345 |
-
* @description: java生成证件照,测试用例
|
346 |
-
*/
|
347 |
-
public class Test {
|
348 |
-
/**
|
349 |
-
* 接口地址
|
350 |
-
*/
|
351 |
-
private final static String BASE_URL = "http://127.0.0.1:8080";
|
352 |
-
|
353 |
-
/**
|
354 |
-
* 生成证件照(底透明) /idphoto 接口
|
355 |
-
* @param inputImageDir 文件地址
|
356 |
-
* @return
|
357 |
-
* @throws IOException
|
358 |
-
*/
|
359 |
-
public static String requestIdPhoto(String inputImageDir) throws IOException {
|
360 |
-
String url = BASE_URL+"/idphoto";
|
361 |
-
// 创建文件对象
|
362 |
-
File inputFile = new File(inputImageDir);
|
363 |
-
Map<String, Object> paramMap=new HashMap<>();
|
364 |
-
paramMap.put("input_image",inputFile);
|
365 |
-
paramMap.put("height","413");
|
366 |
-
paramMap.put("width","295");
|
367 |
-
return HttpUtil.post(url, paramMap);
|
368 |
-
}
|
369 |
-
/**
|
370 |
-
* 添加背景色 /add_background 接口
|
371 |
-
* @param inputImageDir 文件地址
|
372 |
-
* @return
|
373 |
-
* @throws IOException
|
374 |
-
*/
|
375 |
-
public static String requestAddBackground(String inputImageDir) throws IOException {
|
376 |
-
String url = BASE_URL+"/add_background";
|
377 |
-
// 创建文件对象
|
378 |
-
File inputFile = new File(inputImageDir);
|
379 |
-
Map<String, Object> paramMap=new HashMap<>();
|
380 |
-
paramMap.put("input_image",inputFile);
|
381 |
-
paramMap.put("color","638cce");
|
382 |
-
paramMap.put("kb","200");
|
383 |
-
return HttpUtil.post(url, paramMap);
|
384 |
-
}
|
385 |
-
/**
|
386 |
-
* 生成六寸排版照 /generate_layout_photos 接口
|
387 |
-
* @param inputImageDir 文件地址
|
388 |
-
* @return
|
389 |
-
* @throws IOException
|
390 |
-
*/
|
391 |
-
public static String requestGenerateLayoutPhotos(String inputImageDir) throws IOException {
|
392 |
-
String url = BASE_URL+"/generate_layout_photos";
|
393 |
-
// 创建文件对象
|
394 |
-
File inputFile = new File(inputImageDir);
|
395 |
-
Map<String, Object> paramMap=new HashMap<>();
|
396 |
-
paramMap.put("input_image",inputFile);
|
397 |
-
paramMap.put("height","413");
|
398 |
-
paramMap.put("width","295");
|
399 |
-
paramMap.put("kb","200");
|
400 |
-
return HttpUtil.post(url, paramMap);
|
401 |
-
}
|
402 |
-
/**
|
403 |
-
* 生成证件照(底透明)
|
404 |
-
* @param inputImageDir 源文件地址
|
405 |
-
* @param outputImageDir 输出文件地址
|
406 |
-
* @throws IOException
|
407 |
-
*/
|
408 |
-
private static void requestIdPhotoToImage(String inputImageDir, String outputImageDir) throws IOException {
|
409 |
-
String res =requestIdPhoto(inputImageDir);
|
410 |
-
//转成json
|
411 |
-
JSONObject response= JSONUtil.parseObj(res);
|
412 |
-
if(response.getBool("status")){//请求接口成功
|
413 |
-
String image_base64_standard= response.getStr("image_base64_standard");
|
414 |
-
String image_base64_hd =response.getStr("image_base64_hd");
|
415 |
-
String[] outputImageDirArr= StringUtils.split(outputImageDir,".");
|
416 |
-
// Base64 保存为图片
|
417 |
-
FileUtils.writeByteArrayToFile(new File(outputImageDirArr[0]+"_standard."+outputImageDirArr[1]), Base64.getDecoder().decode(image_base64_standard));
|
418 |
-
FileUtils.writeByteArrayToFile(new File(outputImageDirArr[0]+"_hd."+outputImageDirArr[1]), Base64.getDecoder().decode(image_base64_hd));
|
419 |
-
}
|
420 |
-
}
|
421 |
-
/**
|
422 |
-
* 添加背景色
|
423 |
-
* @param inputImageDir 源文件地址
|
424 |
-
* @param outputImageDir 输出文件地址
|
425 |
-
* @throws IOException
|
426 |
-
*/
|
427 |
-
private static void requestAddBackgroundToImage(String inputImageDir, String outputImageDir) throws IOException {
|
428 |
-
String res =requestAddBackground(inputImageDir);
|
429 |
-
//转成json
|
430 |
-
JSONObject response= JSONUtil.parseObj(res);
|
431 |
-
if(response.getBool("status")){//请求接口成功
|
432 |
-
String image_base64= response.getStr("image_base64");
|
433 |
-
String[] outputImageDirArr= StringUtils.split(outputImageDir,".");
|
434 |
-
// Base64 保存为图片
|
435 |
-
FileUtils.writeByteArrayToFile(new File(outputImageDirArr[0]+"_background."+outputImageDirArr[1]), Base64.getDecoder().decode(image_base64));
|
436 |
-
}
|
437 |
-
}
|
438 |
-
/**
|
439 |
-
* 生成六寸排版照
|
440 |
-
* @param inputImageDir 源文件地址
|
441 |
-
* @param outputImageDir 输出文件地址
|
442 |
-
* @throws IOException
|
443 |
-
*/
|
444 |
-
private static void requestGenerateLayoutPhotosToImage(String inputImageDir, String outputImageDir) throws IOException {
|
445 |
-
String res =requestGenerateLayoutPhotos(inputImageDir);
|
446 |
-
//转成json
|
447 |
-
JSONObject response= JSONUtil.parseObj(res);
|
448 |
-
if(response.getBool("status")){//请求接口成功
|
449 |
-
String image_base64= response.getStr("image_base64");
|
450 |
-
String[] outputImageDirArr= StringUtils.split(outputImageDir,".");
|
451 |
-
// Base64 保存为图片
|
452 |
-
FileUtils.writeByteArrayToFile(new File(outputImageDirArr[0]+"_layout."+outputImageDirArr[1]), Base64.getDecoder().decode(image_base64));
|
453 |
-
}
|
454 |
-
}
|
455 |
-
|
456 |
-
public static void main(String[] args) {
|
457 |
-
try {
|
458 |
-
//生成证件照(底透明)
|
459 |
-
requestIdPhotoToImage("C:\\Users\\Administrator\\Desktop\\1111.jpg","C:\\Users\\Administrator\\Desktop\\2222.png");
|
460 |
-
//添加背景色
|
461 |
-
requestAddBackgroundToImage("C:\\Users\\Administrator\\Desktop\\2222_hd.png","C:\\Users\\Administrator\\Desktop\\idphoto_with_background.jpg");
|
462 |
-
//生成六寸排版照
|
463 |
-
requestGenerateLayoutPhotosToImage("C:\\Users\\Administrator\\Desktop\\1111.jpg","C:\\Users\\Administrator\\Desktop\\2222.png");
|
464 |
-
|
465 |
-
} catch (IOException e) {
|
466 |
-
e.printStackTrace();
|
467 |
-
}
|
468 |
-
}
|
469 |
-
}
|
470 |
-
|
471 |
-
```
|
472 |
-
|
473 |
-
## JavaScript 请求示例
|
474 |
-
|
475 |
-
在JavaScript中,我们可以使用`fetch` API来发送HTTP请求。以下是如何使用JavaScript调用这些API的示例。
|
476 |
-
|
477 |
-
### 1. 生成证件照(底透明)
|
478 |
-
|
479 |
-
```javascript
|
480 |
-
async function generateIdPhoto(inputImagePath, height, width) {
|
481 |
-
const url = "http://127.0.0.1:8080/idphoto";
|
482 |
-
const formData = new FormData();
|
483 |
-
formData.append("input_image", new File([await fetch(inputImagePath).then(res => res.blob())], "test.jpg"));
|
484 |
-
formData.append("height", height);
|
485 |
-
formData.append("width", width);
|
486 |
-
|
487 |
-
const response = await fetch(url, {
|
488 |
-
method: 'POST',
|
489 |
-
body: formData
|
490 |
-
});
|
491 |
-
|
492 |
-
const result = await response.json();
|
493 |
-
console.log(result);
|
494 |
-
return result;
|
495 |
-
}
|
496 |
-
|
497 |
-
// 示例调用
|
498 |
-
generateIdPhoto("images/test.jpg", 413, 295).then(response => {
|
499 |
-
console.log(response);
|
500 |
-
});
|
501 |
-
```
|
502 |
-
|
503 |
-
### 2. 添加背景色
|
504 |
-
|
505 |
-
```javascript
|
506 |
-
async function addBackground(inputImagePath, color, kb) {
|
507 |
-
const url = "http://127.0.0.1:8080/add_background";
|
508 |
-
const formData = new FormData();
|
509 |
-
formData.append("input_image", new File([await fetch(inputImagePath).then(res => res.blob())], "test.png"));
|
510 |
-
formData.append("color", color);
|
511 |
-
formData.append("kb", kb);
|
512 |
-
|
513 |
-
const response = await fetch(url, {
|
514 |
-
method: 'POST',
|
515 |
-
body: formData
|
516 |
-
});
|
517 |
-
|
518 |
-
const result = await response.json();
|
519 |
-
console.log(result);
|
520 |
-
return result;
|
521 |
-
}
|
522 |
-
|
523 |
-
// 示例调用
|
524 |
-
addBackground("test.png", "638cce", 200).then(response => {
|
525 |
-
console.log(response);
|
526 |
-
});
|
527 |
-
```
|
528 |
-
|
529 |
-
### 3. 生成六寸排版照
|
530 |
-
|
531 |
-
```javascript
|
532 |
-
async function generateLayoutPhotos(inputImagePath, height, width, kb) {
|
533 |
-
const url = "http://127.0.0.1:8080/generate_layout_photos";
|
534 |
-
const formData = new FormData();
|
535 |
-
formData.append("input_image", new File([await fetch(inputImagePath).then(res => res.blob())], "test.jpg"));
|
536 |
-
formData.append("height", height);
|
537 |
-
formData.append("width", width);
|
538 |
-
formData.append("kb", kb);
|
539 |
-
|
540 |
-
const response = await fetch(url, {
|
541 |
-
method: 'POST',
|
542 |
-
body: formData
|
543 |
-
});
|
544 |
-
|
545 |
-
const result = await response.json();
|
546 |
-
console.log(result);
|
547 |
-
return result;
|
548 |
-
}
|
549 |
-
|
550 |
-
// 示例调用
|
551 |
-
generateLayoutPhotos("test.jpg", 413, 295, 200).then(response => {
|
552 |
-
console.log(response);
|
553 |
-
});
|
554 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docs/api_EN.md
DELETED
@@ -1,553 +0,0 @@
|
|
1 |
-
# API Documentation
|
2 |
-
|
3 |
-
[中文](api_CN.md) | English
|
4 |
-
|
5 |
-
## TOC
|
6 |
-
|
7 |
-
- [Before You Start: Launch the Backend Service](#before-you-start-launch-the-backend-service)
|
8 |
-
- [Interface Function Descriptions](#interface-function-descriptions)
|
9 |
-
- [cURL Request Example](#curl-request-examples)
|
10 |
-
- [Python Request Example](#python-request-example)
|
11 |
-
- [Python Requests Method](#1️⃣-python-requests-method)
|
12 |
-
- [Python Script Method](#2️⃣-python-script-request-method)
|
13 |
-
- [Java Request Example](#java-request-example)
|
14 |
-
- [Javascript Request Example](#javascript-request-examples)
|
15 |
-
|
16 |
-
## Before You Start: Launch the Backend Service
|
17 |
-
|
18 |
-
Before making API requests, please run the backend service:
|
19 |
-
|
20 |
-
```bash
|
21 |
-
python deploy_api.py
|
22 |
-
```
|
23 |
-
|
24 |
-
<br>
|
25 |
-
|
26 |
-
## Interface Function Descriptions
|
27 |
-
|
28 |
-
### 1. Generate ID Photo (Transparent Background)
|
29 |
-
|
30 |
-
Interface Name: `idphoto`
|
31 |
-
|
32 |
-
The `Generate ID Photo` interface logic involves sending an RGB image and receiving a standard ID photo and a high-definition ID photo:
|
33 |
-
|
34 |
-
- **High-Definition ID Photo**: An ID photo made according to the aspect ratio of `size`, with the filename being `output_image_dir` appended with `_hd` suffix.
|
35 |
-
- **Standard ID Photo**: A photo with dimensions equal to `size`, scaled from the high-definition ID photo, with the filename being `output_image_dir`.
|
36 |
-
|
37 |
-
It should be noted that both generated photos are transparent (RGBA four-channel images). To produce a complete ID photo, the following `Add Background Color` interface is also required.
|
38 |
-
|
39 |
-
> Q: Why is this design used?
|
40 |
-
> A: In actual products, users often need to frequently switch background colors to preview effects. Providing a transparent background image and allowing the front-end JavaScript code to synthesize the color offers a better user experience.
|
41 |
-
|
42 |
-
### 2. Add Background Color
|
43 |
-
|
44 |
-
Interface Name: `add_background`
|
45 |
-
|
46 |
-
The `Add Background Color` interface logic involves sending an RGBA image, adding a background color based on `color`, and synthesizing a JPG image.
|
47 |
-
|
48 |
-
### 3. Generate 6-inch Layout Photo
|
49 |
-
|
50 |
-
Interface Name: `generate_layout_photos`
|
51 |
-
|
52 |
-
The `Generate 6-inch Layout Photo` interface logic involves sending an RGB image (usually an ID photo after adding a background color), arranging the photos according to `size`, and then generating a 6-inch layout photo.
|
53 |
-
|
54 |
-
<br>
|
55 |
-
|
56 |
-
|
57 |
-
## cURL Request Examples
|
58 |
-
|
59 |
-
cURL is a command-line tool used to transfer data using various network protocols. Below are examples of how to use cURL to call these APIs.
|
60 |
-
|
61 |
-
### 1. Generate ID Photo (Transparent Background)
|
62 |
-
|
63 |
-
```bash
|
64 |
-
curl -X POST "http://127.0.0.1:8080/idphoto" \
|
65 |
-
-F "input_image=@demo/images/test.jpg" \
|
66 |
-
-F "height=413" \
|
67 |
-
-F "width=295"
|
68 |
-
```
|
69 |
-
|
70 |
-
### 2. Add Background Color
|
71 |
-
|
72 |
-
```bash
|
73 |
-
curl -X POST "http://127.0.0.1:8080/add_background" \
|
74 |
-
-F "input_image=@test.png" \
|
75 |
-
-F "color=638cce" \
|
76 |
-
-F "kb=200"
|
77 |
-
```
|
78 |
-
|
79 |
-
### 3. Generate Six-Inch Layout Photo
|
80 |
-
|
81 |
-
```bash
|
82 |
-
curl -X POST "http://127.0.0.1:8080/generate_layout_photos" \
|
83 |
-
-F "input_image=@test.jpg" \
|
84 |
-
-F "height=413" \
|
85 |
-
-F "width=295" \
|
86 |
-
-F "kb=200"
|
87 |
-
```
|
88 |
-
|
89 |
-
## Python Request Example
|
90 |
-
|
91 |
-
### 1️⃣ Python Requests Method
|
92 |
-
|
93 |
-
#### 1. Generate ID Photo (Transparent Background)
|
94 |
-
|
95 |
-
```python
|
96 |
-
import requests
|
97 |
-
|
98 |
-
url = "http://127.0.0.1:8080/idphoto"
|
99 |
-
input_image_path = "images/test.jpg"
|
100 |
-
|
101 |
-
files = {"input_image": open(input_image_path, "rb")}
|
102 |
-
data = {"height": 413, "width": 295}
|
103 |
-
|
104 |
-
response = requests.post(url, files=files, data=data).json()
|
105 |
-
|
106 |
-
# response is a JSON dictionary containing status, image_base64_standard, and image_base64_hd
|
107 |
-
print(response)
|
108 |
-
```
|
109 |
-
|
110 |
-
#### 2. Add Background Color
|
111 |
-
|
112 |
-
```python
|
113 |
-
import requests
|
114 |
-
|
115 |
-
url = "http://127.0.0.1:8080/add_background"
|
116 |
-
input_image_path = "test.png"
|
117 |
-
|
118 |
-
files = {"input_image": open(input_image_path, "rb")}
|
119 |
-
data = {"color": '638cce', 'kb': None}
|
120 |
-
|
121 |
-
response = requests.post(url, files=files, data=data).json()
|
122 |
-
|
123 |
-
# response is a JSON dictionary containing status and image_base64
|
124 |
-
print(response)
|
125 |
-
```
|
126 |
-
|
127 |
-
#### 3. Generate 6-inch Layout Photo
|
128 |
-
|
129 |
-
```python
|
130 |
-
import requests
|
131 |
-
|
132 |
-
url = "http://127.0.0.1:8080/generate_layout_photos"
|
133 |
-
input_image_path = "test.jpg"
|
134 |
-
|
135 |
-
files = {"input_image": open(input_image_path, "rb")}
|
136 |
-
data = {"height": 413, "width": 295, "kb": 200}
|
137 |
-
|
138 |
-
response = requests.post(url, files=files, data=data).json()
|
139 |
-
|
140 |
-
# response is a JSON dictionary containing status and image_base64
|
141 |
-
print(response)
|
142 |
-
```
|
143 |
-
|
144 |
-
<br>
|
145 |
-
|
146 |
-
### 2️⃣ Python Script Request Method
|
147 |
-
|
148 |
-
```bash
|
149 |
-
python requests_api.py -u <URL> -t <TYPE> -i <INPUT_IMAGE_DIR> -o <OUTPUT_IMAGE_DIR> [--height <HEIGHT>] [--width <WIDTH>] [-c <COLOR>] [-k <KB>]
|
150 |
-
```
|
151 |
-
|
152 |
-
#### Parameter Descriptions
|
153 |
-
|
154 |
-
##### Basic Parameters
|
155 |
-
|
156 |
-
- `-u`, `--url`
|
157 |
-
|
158 |
-
- **Description**: The URL of the API service.
|
159 |
-
- **Default Value**: `http://127.0.0.1:8080`
|
160 |
-
|
161 |
-
- `-t`, `--type`
|
162 |
-
|
163 |
-
- **Description**: The type of API request, with optional values being `idphoto`, `add_background`, and `generate_layout_photos`. They represent ID photo creation, transparent image background addition, and layout photo generation, respectively.
|
164 |
-
- **Default Value**: `idphoto`
|
165 |
-
|
166 |
-
- `-i`, `--input_image_dir`
|
167 |
-
|
168 |
-
- **Description**: The path of the input image.
|
169 |
-
- **Required**: Yes
|
170 |
-
- **Example**: `./input_images/photo.jpg`
|
171 |
-
|
172 |
-
- `-o`, `--output_image_dir`
|
173 |
-
- **Description**: The path to save the image.
|
174 |
-
- **Required**: Yes
|
175 |
-
- **Example**: `./output_images/processed_photo.jpg`
|
176 |
-
|
177 |
-
##### Optional Parameters
|
178 |
-
|
179 |
-
- `--height`
|
180 |
-
|
181 |
-
- **Description**: The height of the output size for the standard ID photo.
|
182 |
-
- **Default Value**: 413
|
183 |
-
|
184 |
-
- `--width`
|
185 |
-
|
186 |
-
- **Description**: The width of the output size for the standard ID photo.
|
187 |
-
- **Default Value**: 295
|
188 |
-
|
189 |
-
- `-c`, `--color`
|
190 |
-
|
191 |
-
- **Description**: Adds a background color to the transparent image, in Hex format (e.g., #638cce), only effective when the type is `add_background`.
|
192 |
-
- **Default Value**: `638cce`
|
193 |
-
|
194 |
-
- `-k`, `--kb`
|
195 |
-
- **Description**: The KB value of the output photo, only effective when the type is `add_background` or `generate_layout_photos`, and no setting is made when the value is None.
|
196 |
-
- **Default Value**: `None`
|
197 |
-
- **Example**: `50`
|
198 |
-
|
199 |
-
#### 1. Generate ID Photo (Transparent Background)
|
200 |
-
|
201 |
-
```bash
|
202 |
-
python requests_api.py \
|
203 |
-
-u http://127.0.0.1:8080 \
|
204 |
-
-t idphoto \
|
205 |
-
-i ./photo.jpg \
|
206 |
-
-o ./idphoto.png \
|
207 |
-
--height 413 \
|
208 |
-
--width 295
|
209 |
-
```
|
210 |
-
|
211 |
-
#### 2. Add Background Color
|
212 |
-
|
213 |
-
```bash
|
214 |
-
python requests_api.py \
|
215 |
-
-u http://127.0.0.1:8080 \
|
216 |
-
-t add_background \
|
217 |
-
-i ./idphoto.png \
|
218 |
-
-o ./idphoto_with_background.jpg \
|
219 |
-
-c 638cce \
|
220 |
-
-k 50
|
221 |
-
```
|
222 |
-
|
223 |
-
#### 3. Generate 6-inch Layout Photo
|
224 |
-
|
225 |
-
```bash
|
226 |
-
python requests_api.py \
|
227 |
-
-u http://127.0.0.1:8080 \
|
228 |
-
-t generate_layout_photos \
|
229 |
-
-i ./idphoto_with_background.jpg \
|
230 |
-
-o ./layout_photo.jpg \
|
231 |
-
--height 413 \
|
232 |
-
--width 295 \
|
233 |
-
-k 200
|
234 |
-
```
|
235 |
-
|
236 |
-
#### Request Failure Scenarios
|
237 |
-
|
238 |
-
- The request fails if more than one face is detected in the photo.
|
239 |
-
|
240 |
-
## Java Request Example
|
241 |
-
|
242 |
-
### Add Maven Dependency
|
243 |
-
|
244 |
-
```java
|
245 |
-
<dependency>
|
246 |
-
<groupId>cn.hutool</groupId>
|
247 |
-
<artifactId>hutool-all</artifactId>
|
248 |
-
<version>5.8.16</version>
|
249 |
-
</dependency>
|
250 |
-
|
251 |
-
<dependency>
|
252 |
-
<groupId>commons-io</groupId>
|
253 |
-
<artifactId>commons-io</artifactId>
|
254 |
-
<version>2.6</version>
|
255 |
-
</dependency>
|
256 |
-
```
|
257 |
-
|
258 |
-
### Running the Code
|
259 |
-
|
260 |
-
#### 1. Generate ID Photo (Transparent Background)
|
261 |
-
|
262 |
-
```java
|
263 |
-
/**
|
264 |
-
* Generate ID Photo (Transparent Background) /idphoto interface
|
265 |
-
* @param inputImageDir File address
|
266 |
-
* @return
|
267 |
-
* @throws IOException
|
268 |
-
*/
|
269 |
-
public static String requestIdPhoto(String inputImageDir) throws IOException {
|
270 |
-
String url = BASE_URL+"/idphoto";
|
271 |
-
// Create file object
|
272 |
-
File inputFile = new File(inputImageDir);
|
273 |
-
Map<String, Object> paramMap=new HashMap<>();
|
274 |
-
paramMap.put("input_image",inputFile);
|
275 |
-
paramMap.put("height","413");
|
276 |
-
paramMap.put("width","295");
|
277 |
-
// Contains status, image_base64_standard, and image_base64_hd
|
278 |
-
return HttpUtil.post(url, paramMap);
|
279 |
-
}
|
280 |
-
```
|
281 |
-
|
282 |
-
#### 2. Add Background Color
|
283 |
-
|
284 |
-
```java
|
285 |
-
/**
|
286 |
-
* Add Background Color /add_background interface
|
287 |
-
* @param inputImageDir File address
|
288 |
-
* @return
|
289 |
-
* @throws IOException
|
290 |
-
*/
|
291 |
-
public static String requestAddBackground(String inputImageDir) throws IOException {
|
292 |
-
String url = BASE_URL+"/add_background";
|
293 |
-
// Create file object
|
294 |
-
File inputFile = new File(inputImageDir);
|
295 |
-
Map<String, Object> paramMap=new HashMap<>();
|
296 |
-
paramMap.put("input_image",inputFile);
|
297 |
-
paramMap.put("color","638cce");
|
298 |
-
paramMap.put("kb","200");
|
299 |
-
// Response is a JSON dictionary containing status and image_base64
|
300 |
-
return HttpUtil.post(url, paramMap);
|
301 |
-
}
|
302 |
-
```
|
303 |
-
|
304 |
-
#### 3. Generate 6-inch Layout Photo
|
305 |
-
|
306 |
-
```java
|
307 |
-
/**
|
308 |
-
* Generate 6-inch Layout Photo /generate_layout_photos interface
|
309 |
-
* @param inputImageDir File address
|
310 |
-
* @return
|
311 |
-
* @throws IOException
|
312 |
-
*/
|
313 |
-
public static String requestGenerateLayoutPhotos(String inputImageDir) throws IOException {
|
314 |
-
String url = BASE_URL+"/generate_layout_photos";
|
315 |
-
// Create file object
|
316 |
-
File inputFile = new File(inputImageDir);
|
317 |
-
Map<String, Object> paramMap=new HashMap<>();
|
318 |
-
paramMap.put("input_image",inputFile);
|
319 |
-
paramMap.put("height","413");
|
320 |
-
paramMap.put("width","295");
|
321 |
-
paramMap.put("kb","200");
|
322 |
-
// Response is a JSON dictionary containing status and image_base64
|
323 |
-
return HttpUtil.post(url, paramMap);
|
324 |
-
}
|
325 |
-
```
|
326 |
-
|
327 |
-
#### 4. Summary
|
328 |
-
|
329 |
-
```java
|
330 |
-
|
331 |
-
import cn.hutool.http.HttpUtil;
|
332 |
-
import cn.hutool.json.JSONObject;
|
333 |
-
import cn.hutool.json.JSONUtil;
|
334 |
-
import org.apache.commons.io.FileUtils;
|
335 |
-
import org.springframework.util.StringUtils;
|
336 |
-
import java.io.File;
|
337 |
-
import java.io.IOException;
|
338 |
-
import java.util.Base64;
|
339 |
-
import java.util.HashMap;
|
340 |
-
import java.util.Map;
|
341 |
-
|
342 |
-
/**
|
343 |
-
* @author: qingshuang
|
344 |
-
* @createDate: 2024/09/05
|
345 |
-
* @description: Java generate ID photo, test case
|
346 |
-
*/
|
347 |
-
public class Test {
|
348 |
-
/**
|
349 |
-
* Interface address
|
350 |
-
*/
|
351 |
-
private final static String BASE_URL = "http://127.0.0.1:8080";
|
352 |
-
|
353 |
-
/**
|
354 |
-
* Generate ID Photo (Transparent Background) /idphoto interface
|
355 |
-
* @param inputImageDir File address
|
356 |
-
* @return
|
357 |
-
* @throws IOException
|
358 |
-
*/
|
359 |
-
public static String requestIdPhoto(String inputImageDir) throws IOException {
|
360 |
-
String url = BASE_URL+"/idphoto";
|
361 |
-
// Create file object
|
362 |
-
File inputFile = new File(inputImageDir);
|
363 |
-
Map<String, Object> paramMap=new HashMap<>();
|
364 |
-
paramMap.put("input_image",inputFile);
|
365 |
-
paramMap.put("height","413");
|
366 |
-
paramMap.put("width","295");
|
367 |
-
return HttpUtil.post(url, paramMap);
|
368 |
-
}
|
369 |
-
/**
|
370 |
-
* Add Background Color /add_background interface
|
371 |
-
* @param inputImageDir File address
|
372 |
-
* @return
|
373 |
-
* @throws IOException
|
374 |
-
*/
|
375 |
-
public static String requestAddBackground(String inputImageDir) throws IOException {
|
376 |
-
String url = BASE_URL+"/add_background";
|
377 |
-
// Create file object
|
378 |
-
File inputFile = new File(inputImageDir);
|
379 |
-
Map<String, Object> paramMap=new HashMap<>();
|
380 |
-
paramMap.put("input_image",inputFile);
|
381 |
-
paramMap.put("color","638cce");
|
382 |
-
paramMap.put("kb","200");
|
383 |
-
return HttpUtil.post(url, paramMap);
|
384 |
-
}
|
385 |
-
/**
|
386 |
-
* Generate 6-inch Layout Photo /generate_layout_photos interface
|
387 |
-
* @param inputImageDir File address
|
388 |
-
* @return
|
389 |
-
* @throws IOException
|
390 |
-
*/
|
391 |
-
public static String requestGenerateLayoutPhotos(String inputImageDir) throws IOException {
|
392 |
-
String url = BASE_URL+"/generate_layout_photos";
|
393 |
-
// Create file object
|
394 |
-
File inputFile = new File(inputImageDir);
|
395 |
-
Map<String, Object> paramMap=new HashMap<>();
|
396 |
-
paramMap.put("input_image",inputFile);
|
397 |
-
paramMap.put("height","413");
|
398 |
-
paramMap.put("width","295");
|
399 |
-
paramMap.put("kb","200");
|
400 |
-
return HttpUtil.post(url, paramMap);
|
401 |
-
}
|
402 |
-
/**
|
403 |
-
* Generate ID Photo (Transparent Background)
|
404 |
-
* @param inputImageDir Source file address
|
405 |
-
* @param outputImageDir Output file address
|
406 |
-
* @throws IOException
|
407 |
-
*/
|
408 |
-
private static void requestIdPhotoToImage(String inputImageDir, String outputImageDir) throws IOException {
|
409 |
-
String res =requestIdPhoto(inputImageDir);
|
410 |
-
// Convert to JSON
|
411 |
-
JSONObject response= JSONUtil.parseObj(res);
|
412 |
-
if(response.getBool("status")){// Request interface success
|
413 |
-
String image_base64_standard= response.getStr("image_base64_standard");
|
414 |
-
String image_base64_hd =response.getStr("image_base64_hd");
|
415 |
-
String[] outputImageDirArr= StringUtils.split(outputImageDir,".");
|
416 |
-
// Base64 save as image
|
417 |
-
FileUtils.writeByteArrayToFile(new File(outputImageDirArr[0]+"_standard."+outputImageDirArr[1]), Base64.getDecoder().decode(image_base64_standard));
|
418 |
-
FileUtils.writeByteArrayToFile(new File(outputImageDirArr[0]+"_hd."+outputImageDirArr[1]), Base64.getDecoder().decode(image_base64_hd));
|
419 |
-
}
|
420 |
-
}
|
421 |
-
/**
|
422 |
-
* Add Background Color
|
423 |
-
* @param inputImageDir Source file address
|
424 |
-
* @param outputImageDir Output file address
|
425 |
-
* @throws IOException
|
426 |
-
*/
|
427 |
-
private static void requestAddBackgroundToImage(String inputImageDir, String outputImageDir) throws IOException {
|
428 |
-
String res =requestAddBackground(inputImageDir);
|
429 |
-
// Convert to JSON
|
430 |
-
JSONObject response= JSONUtil.parseObj(res);
|
431 |
-
if(response.getBool("status")){// Request interface success
|
432 |
-
String image_base64= response.getStr("image_base64");
|
433 |
-
String[] outputImageDirArr= StringUtils.split(outputImageDir,".");
|
434 |
-
// Base64 save as image
|
435 |
-
FileUtils.writeByteArrayToFile(new File(outputImageDirArr[0]+"_background."+outputImageDirArr[1]), Base64.getDecoder().decode(image_base64));
|
436 |
-
}
|
437 |
-
}
|
438 |
-
/**
|
439 |
-
* Generate 6-inch Layout Photo
|
440 |
-
* @param inputImageDir Source file address
|
441 |
-
* @param outputImageDir Output file address
|
442 |
-
* @throws IOException
|
443 |
-
*/
|
444 |
-
private static void requestGenerateLayoutPhotosToImage(String inputImageDir, String outputImageDir) throws IOException {
|
445 |
-
String res =requestGenerateLayoutPhotos(inputImageDir);
|
446 |
-
// Convert to JSON
|
447 |
-
JSONObject response= JSONUtil.parseObj(res);
|
448 |
-
if(response.getBool("status")){// Request interface success
|
449 |
-
String image_base64= response.getStr("image_base64");
|
450 |
-
String[] outputImageDirArr= StringUtils.split(outputImageDir,".");
|
451 |
-
// Base64 save as image
|
452 |
-
FileUtils.writeByteArrayToFile(new File(outputImageDirArr[0]+"_layout."+outputImageDirArr[1]), Base64.getDecoder().decode(image_base64));
|
453 |
-
}
|
454 |
-
}
|
455 |
-
|
456 |
-
public static void main(String[] args) {
|
457 |
-
try {
|
458 |
-
// Generate ID Photo (Transparent Background)
|
459 |
-
requestIdPhotoToImage("C:\\Users\\Administrator\\Desktop\\1111.jpg","C:\\Users\\Administrator\\Desktop\\2222.png");
|
460 |
-
// Add Background Color
|
461 |
-
requestAddBackgroundToImage("C:\\Users\\Administrator\\Desktop\\2222_hd.png","C:\\Users\\Administrator\\Desktop\\idphoto_with_background.jpg");
|
462 |
-
// Generate 6-inch Layout Photo
|
463 |
-
requestGenerateLayoutPhotosToImage("C:\\Users\\Administrator\\Desktop\\1111.jpg","C:\\Users\\Administrator\\Desktop\\2222.png");
|
464 |
-
|
465 |
-
} catch (IOException e) {
|
466 |
-
e.printStackTrace();
|
467 |
-
}
|
468 |
-
}
|
469 |
-
}
|
470 |
-
```
|
471 |
-
|
472 |
-
## JavaScript Request Examples
|
473 |
-
|
474 |
-
In JavaScript, we can use the `fetch` API to send HTTP requests. Below are examples of how to call these APIs using JavaScript.
|
475 |
-
|
476 |
-
### 1. Generate ID Photo (Transparent Background)
|
477 |
-
|
478 |
-
```javascript
|
479 |
-
async function generateIdPhoto(inputImagePath, height, width) {
|
480 |
-
const url = "http://127.0.0.1:8080/idphoto";
|
481 |
-
const formData = new FormData();
|
482 |
-
formData.append("input_image", new File([await fetch(inputImagePath).then(res => res.blob())], "test.jpg"));
|
483 |
-
formData.append("height", height);
|
484 |
-
formData.append("width", width);
|
485 |
-
|
486 |
-
const response = await fetch(url, {
|
487 |
-
method: 'POST',
|
488 |
-
body: formData
|
489 |
-
});
|
490 |
-
|
491 |
-
const result = await response.json();
|
492 |
-
console.log(result);
|
493 |
-
return result;
|
494 |
-
}
|
495 |
-
|
496 |
-
// Example call
|
497 |
-
generateIdPhoto("images/test.jpg", 413, 295).then(response => {
|
498 |
-
console.log(response);
|
499 |
-
});
|
500 |
-
```
|
501 |
-
|
502 |
-
### 2. Add Background Color
|
503 |
-
|
504 |
-
```javascript
|
505 |
-
async function addBackground(inputImagePath, color, kb) {
|
506 |
-
const url = "http://127.0.0.1:8080/add_background";
|
507 |
-
const formData = new FormData();
|
508 |
-
formData.append("input_image", new File([await fetch(inputImagePath).then(res => res.blob())], "test.png"));
|
509 |
-
formData.append("color", color);
|
510 |
-
formData.append("kb", kb);
|
511 |
-
|
512 |
-
const response = await fetch(url, {
|
513 |
-
method: 'POST',
|
514 |
-
body: formData
|
515 |
-
});
|
516 |
-
|
517 |
-
const result = await response.json();
|
518 |
-
console.log(result);
|
519 |
-
return result;
|
520 |
-
}
|
521 |
-
|
522 |
-
// Example call
|
523 |
-
addBackground("test.png", "638cce", 200).then(response => {
|
524 |
-
console.log(response);
|
525 |
-
});
|
526 |
-
```
|
527 |
-
|
528 |
-
### 3. Generate Six-Inch Layout Photo
|
529 |
-
|
530 |
-
```javascript
|
531 |
-
async function generateLayoutPhotos(inputImagePath, height, width, kb) {
|
532 |
-
const url = "http://127.0.0.1:8080/generate_layout_photos";
|
533 |
-
const formData = new FormData();
|
534 |
-
formData.append("input_image", new File([await fetch(inputImagePath).then(res => res.blob())], "test.jpg"));
|
535 |
-
formData.append("height", height);
|
536 |
-
formData.append("width", width);
|
537 |
-
formData.append("kb", kb);
|
538 |
-
|
539 |
-
const response = await fetch(url, {
|
540 |
-
method: 'POST',
|
541 |
-
body: formData
|
542 |
-
});
|
543 |
-
|
544 |
-
const result = await response.json();
|
545 |
-
console.log(result);
|
546 |
-
return result;
|
547 |
-
}
|
548 |
-
|
549 |
-
// Example call
|
550 |
-
generateLayoutPhotos("test.jpg", 413, 295, 200).then(response => {
|
551 |
-
console.log(response);
|
552 |
-
});
|
553 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hivision/creator/__init__.py
CHANGED
@@ -12,7 +12,7 @@ from typing import Tuple
|
|
12 |
import hivision.creator.utils as U
|
13 |
from .context import Context, ContextHandler, Params, Result
|
14 |
from .human_matting import extract_human
|
15 |
-
from .face_detector import
|
16 |
from .photo_adjuster import adjust_photo
|
17 |
|
18 |
|
@@ -41,7 +41,9 @@ class IDCreator:
|
|
41 |
"""
|
42 |
# 处理者
|
43 |
self.matting_handler: ContextHandler = extract_human
|
44 |
-
self.detection_handler: ContextHandler =
|
|
|
|
|
45 |
# 上下文
|
46 |
self.ctx = None
|
47 |
|
|
|
12 |
import hivision.creator.utils as U
|
13 |
from .context import Context, ContextHandler, Params, Result
|
14 |
from .human_matting import extract_human
|
15 |
+
from .face_detector import detect_face_mtcnn, detect_face_face_plusplus
|
16 |
from .photo_adjuster import adjust_photo
|
17 |
|
18 |
|
|
|
41 |
"""
|
42 |
# 处理者
|
43 |
self.matting_handler: ContextHandler = extract_human
|
44 |
+
self.detection_handler: ContextHandler = detect_face_face_plusplus
|
45 |
+
# self.detection_handler: ContextHandler = detect_face_mtcnn
|
46 |
+
|
47 |
# 上下文
|
48 |
self.ctx = None
|
49 |
|
hivision/creator/face_detector.py
CHANGED
@@ -9,15 +9,19 @@ r"""
|
|
9 |
"""
|
10 |
from mtcnnruntime import MTCNN
|
11 |
from .context import Context
|
12 |
-
from hivision.error import FaceError
|
|
|
|
|
13 |
import cv2
|
|
|
|
|
14 |
|
15 |
mtcnn = None
|
16 |
|
17 |
|
18 |
-
def
|
19 |
"""
|
20 |
-
|
21 |
:param ctx: 上下文,此时已获取到原始图和抠图结果,但是我们只需要原始图
|
22 |
:param scale: 最大边长缩放比例,原图:缩放图 = 1:scale
|
23 |
:raise FaceError: 人脸检测错误,多个人脸或者没有人脸
|
@@ -30,13 +34,91 @@ def detect_face(ctx: Context, scale: int = 2):
|
|
30 |
(ctx.origin_image.shape[1] // scale, ctx.origin_image.shape[0] // scale),
|
31 |
interpolation=cv2.INTER_AREA,
|
32 |
)
|
33 |
-
faces, _ = mtcnn.detect(image)
|
|
|
34 |
if len(faces) != 1:
|
35 |
# 保险措施,如果检测到多个人脸或者没有人脸,用原图再检测一次
|
36 |
faces, _ = mtcnn.detect(ctx.origin_image)
|
37 |
else:
|
|
|
38 |
for item, param in enumerate(faces[0]):
|
39 |
faces[0][item] = param * 2
|
40 |
if len(faces) != 1:
|
41 |
raise FaceError("Expected 1 face, but got {}".format(len(faces)), len(faces))
|
42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
"""
|
10 |
from mtcnnruntime import MTCNN
|
11 |
from .context import Context
|
12 |
+
from hivision.error import FaceError, APIError
|
13 |
+
from hivision.utils import resize_image_to_kb_base64
|
14 |
+
import requests
|
15 |
import cv2
|
16 |
+
import os
|
17 |
+
|
18 |
|
19 |
mtcnn = None
|
20 |
|
21 |
|
22 |
+
def detect_face_mtcnn(ctx: Context, scale: int = 2):
|
23 |
"""
|
24 |
+
基于MTCNN模型的人脸检测处理器,只进行人脸数量的检测
|
25 |
:param ctx: 上下文,此时已获取到原始图和抠图结果,但是我们只需要原始图
|
26 |
:param scale: 最大边长缩放比例,原图:缩放图 = 1:scale
|
27 |
:raise FaceError: 人脸检测错误,多个人脸或者没有人脸
|
|
|
34 |
(ctx.origin_image.shape[1] // scale, ctx.origin_image.shape[0] // scale),
|
35 |
interpolation=cv2.INTER_AREA,
|
36 |
)
|
37 |
+
faces, _ = mtcnn.detect(image, thresholds=[0.8, 0.8, 0.8])
|
38 |
+
# print(len(faces))
|
39 |
if len(faces) != 1:
|
40 |
# 保险措施,如果检测到多个人脸或者没有人脸,用原图再检测一次
|
41 |
faces, _ = mtcnn.detect(ctx.origin_image)
|
42 |
else:
|
43 |
+
# 如果只有一个人脸,将人脸坐标放大
|
44 |
for item, param in enumerate(faces[0]):
|
45 |
faces[0][item] = param * 2
|
46 |
if len(faces) != 1:
|
47 |
raise FaceError("Expected 1 face, but got {}".format(len(faces)), len(faces))
|
48 |
+
|
49 |
+
left = faces[0][0]
|
50 |
+
top = faces[0][1]
|
51 |
+
width = faces[0][2] - left + 1
|
52 |
+
height = faces[0][3] - top + 1
|
53 |
+
|
54 |
+
ctx.face = (left, top, width, height)
|
55 |
+
|
56 |
+
|
57 |
+
def detect_face_face_plusplus(ctx: Context):
|
58 |
+
"""
|
59 |
+
基于Face++ API接口的人脸检测处理器,只进行人脸数量的检测
|
60 |
+
:param ctx: 上下文,此时已获取到原始图和抠图结果,但是我们只需要原始图
|
61 |
+
:param scale: 最大边长缩放比例,原图:缩放图 = 1:scale
|
62 |
+
:raise FaceError: 人脸检测错误,多个人脸或者没有人脸
|
63 |
+
"""
|
64 |
+
url = "https://api-cn.faceplusplus.com/facepp/v3/detect"
|
65 |
+
api_key = os.getenv("FACE_PLUS_API_KEY")
|
66 |
+
api_secret = os.getenv("FACE_PLUS_API_SECRET")
|
67 |
+
|
68 |
+
image = ctx.origin_image
|
69 |
+
# 将图片转为 base64, 且不大于2MB(Face++ API接口限制)
|
70 |
+
image_base64 = resize_image_to_kb_base64(image, 2000, mode="max")
|
71 |
+
|
72 |
+
files = {
|
73 |
+
"api_key": (None, api_key),
|
74 |
+
"api_secret": (None, api_secret),
|
75 |
+
"image_base64": (None, image_base64),
|
76 |
+
}
|
77 |
+
|
78 |
+
# 发送 POST 请求
|
79 |
+
response = requests.post(url, files=files)
|
80 |
+
|
81 |
+
# 获取响应状态码
|
82 |
+
status_code = response.status_code
|
83 |
+
response_json = response.json()
|
84 |
+
|
85 |
+
if status_code == 200:
|
86 |
+
face_num = response_json["face_num"]
|
87 |
+
if face_num == 1:
|
88 |
+
face_rectangle = response_json["faces"][0]["face_rectangle"]
|
89 |
+
ctx.face = (
|
90 |
+
face_rectangle["left"],
|
91 |
+
face_rectangle["top"],
|
92 |
+
face_rectangle["width"],
|
93 |
+
face_rectangle["height"],
|
94 |
+
)
|
95 |
+
else:
|
96 |
+
raise FaceError(
|
97 |
+
"Expected 1 face, but got {}".format(face_num), len(face_num)
|
98 |
+
)
|
99 |
+
|
100 |
+
elif status_code == 401:
|
101 |
+
raise APIError(
|
102 |
+
f"Face++ Status code {status_code} Authentication error: API key and secret do not match.",
|
103 |
+
status_code,
|
104 |
+
)
|
105 |
+
|
106 |
+
elif status_code == 403:
|
107 |
+
reason = response_json.get("error_message", "Unknown authorization error.")
|
108 |
+
raise APIError(
|
109 |
+
f"Authorization error: {reason}",
|
110 |
+
status_code,
|
111 |
+
)
|
112 |
+
|
113 |
+
elif status_code == 400:
|
114 |
+
error_message = response_json.get("error_message", "Bad request.")
|
115 |
+
raise APIError(
|
116 |
+
f"Bad request error: {error_message}",
|
117 |
+
status_code,
|
118 |
+
)
|
119 |
+
|
120 |
+
elif status_code == 413:
|
121 |
+
raise APIError(
|
122 |
+
f"Face++ Status code {status_code} Request entity too large: The image exceeds the 2MB limit.",
|
123 |
+
status_code,
|
124 |
+
)
|
hivision/creator/photo_adjuster.py
CHANGED
@@ -21,7 +21,7 @@ def adjust_photo(ctx: Context):
|
|
21 |
standard_size = ctx.params.size
|
22 |
params = ctx.params
|
23 |
x, y = face_rect[0], face_rect[1]
|
24 |
-
w, h = face_rect[2]
|
25 |
height, width = ctx.processing_image.shape[:2]
|
26 |
width_height_ratio = standard_size[0] / standard_size[1]
|
27 |
# Step2. 计算高级参数
|
|
|
21 |
standard_size = ctx.params.size
|
22 |
params = ctx.params
|
23 |
x, y = face_rect[0], face_rect[1]
|
24 |
+
w, h = face_rect[2], face_rect[3]
|
25 |
height, width = ctx.processing_image.shape[:2]
|
26 |
width_height_ratio = standard_size[0] / standard_size[1]
|
27 |
# Step2. 计算高级参数
|
hivision/error.py
CHANGED
@@ -19,3 +19,15 @@ class FaceError(Exception):
|
|
19 |
"""
|
20 |
super().__init__(err)
|
21 |
self.face_num = face_num
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
"""
|
20 |
super().__init__(err)
|
21 |
self.face_num = face_num
|
22 |
+
|
23 |
+
|
24 |
+
class APIError(Exception):
|
25 |
+
def __init__(self, err, status_code):
|
26 |
+
"""
|
27 |
+
API错误
|
28 |
+
Args:
|
29 |
+
err: 错误描述
|
30 |
+
status_code: 告诉此时的错误状态码
|
31 |
+
"""
|
32 |
+
super().__init__(err)
|
33 |
+
self.status_code = status_code
|
hivision/utils.py
CHANGED
@@ -74,13 +74,20 @@ def resize_image_to_kb(input_image, output_image_path, target_size_kb):
|
|
74 |
quality = 1
|
75 |
|
76 |
|
77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
78 |
"""
|
79 |
Resize an image to a target size in KB and return it as a base64 encoded string.
|
80 |
将图像调整大小至目标文件大小(KB)并返回base64编码的字符串。
|
81 |
|
82 |
:param input_image: Input image as a NumPy array or PIL Image. 输入图像,可以是NumPy数组或PIL图像。
|
83 |
:param target_size_kb: Target size in KB. 目标文件大小(KB)。
|
|
|
84 |
|
85 |
:return: Base64 encoded string of the resized image. 调整大小后的图像的base64编码字符串。
|
86 |
"""
|
@@ -109,19 +116,30 @@ def resize_image_to_kb_base64(input_image, target_size_kb):
|
|
109 |
# Get the size of the image in KB
|
110 |
img_size_kb = len(img_byte_arr.getvalue()) / 1024
|
111 |
|
112 |
-
# Check
|
113 |
-
if
|
|
|
|
|
|
|
|
|
114 |
# If the image is smaller than the target size, add padding
|
115 |
-
|
116 |
padding_size = int(
|
117 |
(target_size_kb * 1024) - len(img_byte_arr.getvalue())
|
118 |
)
|
119 |
padding = b"\x00" * padding_size
|
120 |
img_byte_arr.write(padding)
|
|
|
|
|
|
|
|
|
|
|
|
|
121 |
|
122 |
-
|
123 |
-
|
124 |
-
|
|
|
125 |
|
126 |
# Reduce the quality if the image is still too large
|
127 |
quality -= 5
|
@@ -130,6 +148,10 @@ def resize_image_to_kb_base64(input_image, target_size_kb):
|
|
130 |
if quality < 1:
|
131 |
quality = 1
|
132 |
|
|
|
|
|
|
|
|
|
133 |
|
134 |
def numpy_2_base64(img: np.ndarray):
|
135 |
_, buffer = cv2.imencode(".png", img)
|
|
|
74 |
quality = 1
|
75 |
|
76 |
|
77 |
+
import numpy as np
|
78 |
+
from PIL import Image
|
79 |
+
import io
|
80 |
+
import base64
|
81 |
+
|
82 |
+
|
83 |
+
def resize_image_to_kb_base64(input_image, target_size_kb, mode="exact"):
|
84 |
"""
|
85 |
Resize an image to a target size in KB and return it as a base64 encoded string.
|
86 |
将图像调整大小至目标文件大小(KB)并返回base64编码的字符串。
|
87 |
|
88 |
:param input_image: Input image as a NumPy array or PIL Image. 输入图像,可以是NumPy数组或PIL图像。
|
89 |
:param target_size_kb: Target size in KB. 目标文件大小(KB)。
|
90 |
+
:param mode: Mode of resizing ('exact', 'max', 'min'). 模式:'exact'(精确大小)、'max'(不大于)、'min'(不小于)。
|
91 |
|
92 |
:return: Base64 encoded string of the resized image. 调整大小后的图像的base64编码字符串。
|
93 |
"""
|
|
|
116 |
# Get the size of the image in KB
|
117 |
img_size_kb = len(img_byte_arr.getvalue()) / 1024
|
118 |
|
119 |
+
# Check based on the mode
|
120 |
+
if mode == "exact":
|
121 |
+
# If the image size is equal to the target size, we can return it
|
122 |
+
if img_size_kb == target_size_kb:
|
123 |
+
break
|
124 |
+
|
125 |
# If the image is smaller than the target size, add padding
|
126 |
+
elif img_size_kb < target_size_kb:
|
127 |
padding_size = int(
|
128 |
(target_size_kb * 1024) - len(img_byte_arr.getvalue())
|
129 |
)
|
130 |
padding = b"\x00" * padding_size
|
131 |
img_byte_arr.write(padding)
|
132 |
+
break
|
133 |
+
|
134 |
+
elif mode == "max":
|
135 |
+
# If the image size is within the target size, we can return it
|
136 |
+
if img_size_kb <= target_size_kb or quality == 1:
|
137 |
+
break
|
138 |
|
139 |
+
elif mode == "min":
|
140 |
+
# If the image size is greater than or equal to the target size, we can return it
|
141 |
+
if img_size_kb >= target_size_kb:
|
142 |
+
break
|
143 |
|
144 |
# Reduce the quality if the image is still too large
|
145 |
quality -= 5
|
|
|
148 |
if quality < 1:
|
149 |
quality = 1
|
150 |
|
151 |
+
# Encode the image data to base64
|
152 |
+
img_base64 = base64.b64encode(img_byte_arr.getvalue()).decode("utf-8")
|
153 |
+
return img_base64
|
154 |
+
|
155 |
|
156 |
def numpy_2_base64(img: np.ndarray):
|
157 |
_, buffer = cv2.imencode(".png", img)
|
inference.py
DELETED
@@ -1,107 +0,0 @@
|
|
1 |
-
import os
|
2 |
-
import cv2
|
3 |
-
import argparse
|
4 |
-
import numpy as np
|
5 |
-
import onnxruntime
|
6 |
-
from hivision.error import FaceError
|
7 |
-
from hivision.utils import hex_to_rgb, resize_image_to_kb, add_background
|
8 |
-
from hivision import IDCreator
|
9 |
-
from hivision.creator.layout_calculator import (
|
10 |
-
generate_layout_photo,
|
11 |
-
generate_layout_image,
|
12 |
-
)
|
13 |
-
|
14 |
-
parser = argparse.ArgumentParser(description="HivisionIDPhotos 证件照制作推理程序。")
|
15 |
-
|
16 |
-
creator = IDCreator()
|
17 |
-
|
18 |
-
parser.add_argument(
|
19 |
-
"-t",
|
20 |
-
"--type",
|
21 |
-
help="请求 API 的种类,有 idphoto、add_background 和 generate_layout_photos 可选",
|
22 |
-
default="idphoto",
|
23 |
-
)
|
24 |
-
parser.add_argument("-i", "--input_image_dir", help="输入图像路径", required=True)
|
25 |
-
parser.add_argument("-o", "--output_image_dir", help="保存图像路径", required=True)
|
26 |
-
parser.add_argument("--height", help="证件照尺寸-高", default=413)
|
27 |
-
parser.add_argument("--width", help="证件照尺寸-宽", default=295)
|
28 |
-
parser.add_argument("-c", "--color", help="证件照背景色", default="638cce")
|
29 |
-
parser.add_argument(
|
30 |
-
"-k", "--kb", help="输出照片的 KB 值,仅对换底和制作排版照生效", default=None
|
31 |
-
)
|
32 |
-
|
33 |
-
args = parser.parse_args()
|
34 |
-
|
35 |
-
root_dir = os.path.dirname(os.path.abspath(__file__))
|
36 |
-
|
37 |
-
# 预加载 ONNX 模型
|
38 |
-
print("正在加载抠图模型...")
|
39 |
-
# HY_HUMAN_MATTING_WEIGHTS_PATH = os.path.join(
|
40 |
-
# root_dir, "hivision/creator/weights/hivision_modnet.onnx"
|
41 |
-
# )
|
42 |
-
# sess = onnxruntime.InferenceSession(HY_HUMAN_MATTING_WEIGHTS_PATH)
|
43 |
-
|
44 |
-
input_image = cv2.imread(args.input_image_dir, cv2.IMREAD_UNCHANGED)
|
45 |
-
|
46 |
-
|
47 |
-
# 如果模式是生成证件照
|
48 |
-
if args.type == "idphoto":
|
49 |
-
|
50 |
-
# 将字符串转为元组
|
51 |
-
size = (int(args.height), int(args.width))
|
52 |
-
try:
|
53 |
-
result = creator(input_image, size=size)
|
54 |
-
except FaceError:
|
55 |
-
print("人脸数量不等于 1,请上传单张人脸的图像。")
|
56 |
-
else:
|
57 |
-
# 保存标准照
|
58 |
-
cv2.imwrite(args.output_image_dir, result.standard)
|
59 |
-
|
60 |
-
# 保存高清照
|
61 |
-
file_name, file_extension = os.path.splitext(args.output_image_dir)
|
62 |
-
new_file_name = file_name + "_hd" + file_extension
|
63 |
-
cv2.imwrite(new_file_name, result.hd)
|
64 |
-
|
65 |
-
# 如果模式是添加背景
|
66 |
-
elif args.type == "add_background":
|
67 |
-
|
68 |
-
# 将字符串转为元组
|
69 |
-
color = hex_to_rgb(args.color)
|
70 |
-
# 将元祖的 0 和 2 号数字交换
|
71 |
-
color = (color[2], color[1], color[0])
|
72 |
-
|
73 |
-
result_image = add_background(input_image, bgr=color)
|
74 |
-
result_image = result_image.astype(np.uint8)
|
75 |
-
|
76 |
-
if args.kb:
|
77 |
-
result_image = cv2.cvtColor(result_image, cv2.COLOR_RGB2BGR)
|
78 |
-
result_image = resize_image_to_kb(
|
79 |
-
result_image, args.output_image_dir, int(args.kb)
|
80 |
-
)
|
81 |
-
else:
|
82 |
-
cv2.imwrite(args.output_image_dir, result_image)
|
83 |
-
|
84 |
-
# 如果模式是生成排版照
|
85 |
-
elif args.type == "generate_layout_photos":
|
86 |
-
|
87 |
-
size = (int(args.height), int(args.width))
|
88 |
-
|
89 |
-
typography_arr, typography_rotate = generate_layout_photo(
|
90 |
-
input_height=size[0], input_width=size[1]
|
91 |
-
)
|
92 |
-
|
93 |
-
result_layout_image = generate_layout_image(
|
94 |
-
input_image,
|
95 |
-
typography_arr,
|
96 |
-
typography_rotate,
|
97 |
-
height=size[0],
|
98 |
-
width=size[1],
|
99 |
-
)
|
100 |
-
|
101 |
-
if args.kb:
|
102 |
-
result_layout_image = cv2.cvtColor(result_layout_image, cv2.COLOR_RGB2BGR)
|
103 |
-
result_layout_image = resize_image_to_kb(
|
104 |
-
result_layout_image, args.output_image_dir, int(args.kb)
|
105 |
-
)
|
106 |
-
else:
|
107 |
-
cv2.imwrite(args.output_image_dir, result_layout_image)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requests_api.py
DELETED
@@ -1,119 +0,0 @@
|
|
1 |
-
import requests
|
2 |
-
import base64
|
3 |
-
import argparse
|
4 |
-
import os
|
5 |
-
|
6 |
-
|
7 |
-
def base64_save(_base64_image_data, save_path):
|
8 |
-
# 解码 Base64 数据并保存为 PNG 文件
|
9 |
-
img_data = base64.b64decode(_base64_image_data)
|
10 |
-
with open(save_path, "wb") as file:
|
11 |
-
file.write(img_data)
|
12 |
-
|
13 |
-
|
14 |
-
# 读取本地图像文件并转换为Base64编码
|
15 |
-
def file_2_base64(file_path):
|
16 |
-
with open(file_path, "rb") as file:
|
17 |
-
encoded_string = base64.b64encode(file.read()).decode("utf-8")
|
18 |
-
return encoded_string
|
19 |
-
|
20 |
-
|
21 |
-
# 发送请求到 /idphoto 接口
|
22 |
-
def request_idphoto(file_path, height, width):
|
23 |
-
files = {"input_image": open(file_path, "rb")}
|
24 |
-
data = {"height": int(height), "width": int(width)}
|
25 |
-
response = requests.post(url, files=files, data=data)
|
26 |
-
return response.json()
|
27 |
-
|
28 |
-
|
29 |
-
# 发送请求到 /add_background 接口
|
30 |
-
def request_add_background(file_path, color, kb=None):
|
31 |
-
files = {"input_image": open(file_path, "rb")}
|
32 |
-
data = {"color": str(color), "kb": kb}
|
33 |
-
response = requests.post(url, files=files, data=data)
|
34 |
-
return response.json()
|
35 |
-
|
36 |
-
|
37 |
-
# 发送请求到 /generate_layout_photos 接口
|
38 |
-
def request_generate_layout_photos(file_path, height, width, kb=None):
|
39 |
-
files = {"input_image": open(file_path, "rb")}
|
40 |
-
data = {"height": height, "width": width, "kb": kb}
|
41 |
-
response = requests.post(url, files=files, data=data)
|
42 |
-
return response.json()
|
43 |
-
|
44 |
-
|
45 |
-
# 示例调用
|
46 |
-
if __name__ == "__main__":
|
47 |
-
|
48 |
-
parser = argparse.ArgumentParser(
|
49 |
-
description="HivisionIDPhotos 证件照制作推理程序。"
|
50 |
-
)
|
51 |
-
parser.add_argument(
|
52 |
-
"-u", "--url", help="API 服务的 URL", default="http://localhost:8080"
|
53 |
-
)
|
54 |
-
|
55 |
-
parser.add_argument(
|
56 |
-
"-t",
|
57 |
-
"--type",
|
58 |
-
help="请求 API 的种类,有 idphoto、add_background 和 generate_layout_photos 可选",
|
59 |
-
default="idphoto",
|
60 |
-
)
|
61 |
-
parser.add_argument("-i", "--input_image_dir", help="输入图像路径", required=True)
|
62 |
-
parser.add_argument("-o", "--output_image_dir", help="保存图像路径", required=True)
|
63 |
-
parser.add_argument("--height", help="证件照尺寸-高", default=413)
|
64 |
-
parser.add_argument("--width", help="证件照尺寸-宽", default=295)
|
65 |
-
parser.add_argument("-c", "--color", help="证件照背景色", default="638cce")
|
66 |
-
parser.add_argument(
|
67 |
-
"-k", "--kb", help="输出照片的 KB 值,仅对换底和制作排版照生效", default=None
|
68 |
-
)
|
69 |
-
args = parser.parse_args()
|
70 |
-
|
71 |
-
url = f"{args.url}/{args.type}" # 替换为实际的接口 URL
|
72 |
-
# color = hex_to_rgb(args.color)
|
73 |
-
# color = (color[2], color[1], color[0])
|
74 |
-
|
75 |
-
if args.type == "idphoto":
|
76 |
-
# 调用 /idphoto 接口
|
77 |
-
idphoto_response = request_idphoto(
|
78 |
-
args.input_image_dir, int(args.height), int(args.width)
|
79 |
-
)
|
80 |
-
|
81 |
-
if idphoto_response["status"]:
|
82 |
-
# 解码 Base64 数据并保存为 PNG 文件
|
83 |
-
base64_image_data_standard = idphoto_response["image_base64_standard"]
|
84 |
-
base64_image_data_standard_hd = idphoto_response["image_base64_hd"]
|
85 |
-
|
86 |
-
file_name, file_extension = os.path.splitext(args.output_image_dir)
|
87 |
-
# 定义新的文件路径(在原有的文件名后添加"_hd")
|
88 |
-
new_file_name = file_name + "_hd" + file_extension
|
89 |
-
|
90 |
-
# 解码 Base64 数据并保存为 PNG 文件
|
91 |
-
base64_save(base64_image_data_standard, args.output_image_dir)
|
92 |
-
base64_save(base64_image_data_standard_hd, new_file_name)
|
93 |
-
|
94 |
-
print(f"请求{args.type}接口成功,已保存图像。")
|
95 |
-
else:
|
96 |
-
print("人脸数量不等于 1,请上传单张人脸的图像。")
|
97 |
-
|
98 |
-
elif args.type == "add_background":
|
99 |
-
# 调用 /add_background 接口
|
100 |
-
add_background_response = request_add_background(
|
101 |
-
args.input_image_dir, args.color, kb=args.kb
|
102 |
-
)
|
103 |
-
base64_image_data = add_background_response["image_base64"]
|
104 |
-
base64_save(base64_image_data, args.output_image_dir)
|
105 |
-
|
106 |
-
print(f"请求{args.type}接口成功,已保存图像。")
|
107 |
-
|
108 |
-
elif args.type == "generate_layout_photos":
|
109 |
-
# 调用 /generate_layout_photos 接口
|
110 |
-
generate_layout_response = request_generate_layout_photos(
|
111 |
-
args.input_image_dir, int(args.height), int(args.width), args.kb
|
112 |
-
)
|
113 |
-
base64_image_data = generate_layout_response["image_base64"]
|
114 |
-
base64_save(base64_image_data, args.output_image_dir)
|
115 |
-
|
116 |
-
print(f"请求{args.type}接口成功,已保存图像。")
|
117 |
-
|
118 |
-
else:
|
119 |
-
print("不支持的 API 类型,请检查输入参数。")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requirements-app.txt
DELETED
@@ -1,2 +0,0 @@
|
|
1 |
-
gradio>=4.43.0
|
2 |
-
fastapi
|
|
|
|
|
|
requirements.txt
CHANGED
@@ -3,4 +3,5 @@ onnxruntime>=1.15.0
|
|
3 |
numpy<=1.26.4
|
4 |
mtcnn-runtime
|
5 |
gradio>=4.43.0
|
6 |
-
fastapi
|
|
|
|
3 |
numpy<=1.26.4
|
4 |
mtcnn-runtime
|
5 |
gradio>=4.43.0
|
6 |
+
fastapi
|
7 |
+
requests
|
scripts/build_pypi.py
DELETED
@@ -1,9 +0,0 @@
|
|
1 |
-
#!/usr/bin/env python
|
2 |
-
# -*- coding: utf-8 -*-
|
3 |
-
r"""
|
4 |
-
@DATE: 2024/9/5 16:56
|
5 |
-
@File: build_pypi.py
|
6 |
-
@IDE: pycharm
|
7 |
-
@Description:
|
8 |
-
构建pypi包
|
9 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test/create_id_photo.py
DELETED
@@ -1,22 +0,0 @@
|
|
1 |
-
#!/usr/bin/env python
|
2 |
-
# -*- coding: utf-8 -*-
|
3 |
-
r"""
|
4 |
-
@DATE: 2024/9/5 21:39
|
5 |
-
@File: create_id_photo.py
|
6 |
-
@IDE: pycharm
|
7 |
-
@Description:
|
8 |
-
用于测试创建证件照
|
9 |
-
"""
|
10 |
-
from hivision.creator import IDCreator
|
11 |
-
import cv2
|
12 |
-
import os
|
13 |
-
|
14 |
-
now_dir = os.path.dirname(__file__)
|
15 |
-
image_path = os.path.join(os.path.dirname(now_dir), "app", "images", "test.jpg")
|
16 |
-
output_dir = os.path.join(now_dir, "temp")
|
17 |
-
|
18 |
-
image = cv2.imread(image_path)
|
19 |
-
creator = IDCreator()
|
20 |
-
result = creator(image)
|
21 |
-
cv2.imwrite(os.path.join(output_dir, "result.png"), result.standard)
|
22 |
-
cv2.imwrite(os.path.join(output_dir, "result_hd.png"), result.hd)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test/temp/.gitkeep
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
存放一些测试临时文件
|
|
|
|