perf(图片): 接口2/3/5 返回 JPG(体积~9×↓),接口1 标注图仍 PNG(透明)
- app.py: 接口2(预览+生发)/3(生发)/5(发际线叠图) 编码改 JPG(质量90,env JPG_QUALITY); 接口1 annotated_image 含透明仍 PNG。_png_to_jpg_b64 把 ComfyUI 的 PNG 重编码为 JPG(无法解码则透传) - gateway/forward.py: 落盘按内容嗅探扩展名(PNG头→.png 否则.jpg),原先硬编码 .png - 测试/文档同步;实测接口5 一张 59KB(JPG) vs 548KB(PNG)。pytest 44 全绿 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -226,6 +226,24 @@ async def resolve_image_bytes(image_file, image_url, image_base64):
|
||||
return None, err(1008, "base64 解码失败")
|
||||
|
||||
|
||||
# 接口2/3/5 返回不透明照片,用 JPG 显著减小体积(接口1 标注图含透明,仍用 PNG)
|
||||
_JPG_QUALITY = int(os.getenv("JPG_QUALITY", "90"))
|
||||
|
||||
|
||||
def _jpg_b64(bgr) -> str:
|
||||
"""BGR 图 → JPG base64。"""
|
||||
_ok, buf = cv2.imencode(".jpg", bgr, [cv2.IMWRITE_JPEG_QUALITY, _JPG_QUALITY])
|
||||
return base64.b64encode(buf.tobytes()).decode()
|
||||
|
||||
|
||||
def _png_to_jpg_b64(png_bytes) -> str:
|
||||
"""ComfyUI 返回的 PNG 字节 → 重编码为 JPG base64;无法解码则原样透传。"""
|
||||
img = cv2.imdecode(np.frombuffer(png_bytes, np.uint8), cv2.IMREAD_COLOR)
|
||||
if img is None:
|
||||
return base64.b64encode(png_bytes).decode()
|
||||
return _jpg_b64(img)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 通用图片请求 Body(JSON 方式,用于 url / base64)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -514,12 +532,10 @@ async def hair_grow(
|
||||
|
||||
results = []
|
||||
for p in items:
|
||||
_ok, png = cv2.imencode(".png", p["image_bgr"])
|
||||
grown_b64 = (base64.b64encode(p["grown_png"]).decode()
|
||||
if p["grown_png"] else None)
|
||||
results.append({
|
||||
"image_base64": base64.b64encode(png.tobytes()).decode(),
|
||||
"grown_image_base64": grown_b64,
|
||||
"image_base64": _jpg_b64(p["image_bgr"]), # 预览图 JPG
|
||||
"grown_image_base64": (_png_to_jpg_b64(p["grown_png"]) # 生发图 JPG
|
||||
if p["grown_png"] else None),
|
||||
"hairline_type": p["hairline_type"],
|
||||
"order": p["order"],
|
||||
})
|
||||
@@ -602,7 +618,7 @@ async def hair_grow_b(
|
||||
if res["status"] == "no_line":
|
||||
return err(1001, "未检测到发际线划线,请确认划线图额头有清晰的手绘发际线")
|
||||
|
||||
grown_b64 = base64.b64encode(res["grown_png"]).decode() if res["grown_png"] else None
|
||||
grown_b64 = _png_to_jpg_b64(res["grown_png"]) if res["grown_png"] else None # 生发图 JPG
|
||||
data = {
|
||||
"hair_growth_image_base64": grown_b64,
|
||||
"hairline_type": "custom",
|
||||
@@ -783,9 +799,8 @@ async def hairline_generate(
|
||||
|
||||
hairline_images = []
|
||||
for it in res["images"]:
|
||||
_ok, png = cv2.imencode(".png", it["image_bgr"])
|
||||
hairline_images.append({
|
||||
"image_base64": base64.b64encode(png.tobytes()).decode(),
|
||||
"image_base64": _jpg_b64(it["image_bgr"]), # 发际线叠图 JPG
|
||||
"order": it["order"],
|
||||
})
|
||||
c = res["best_center"]
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
| 4 | (网关本机产出,无图片字段,`features` 为 JSON 字符串) | — |
|
||||
|
||||
> 实现建议:递归遍历 data,凡 key 以 `_base64` 结尾就落盘改 `_url`,自动覆盖嵌套/新增字段。
|
||||
> **图片格式**:接口1 标注图含透明用 **PNG**;接口2/3/5 是不透明照片用 **JPG**(小很多,~9×)。
|
||||
> 网关落盘按内容嗅探扩展名(PNG 头→`.png`,否则 `.jpg`)。
|
||||
|
||||
### 错误码
|
||||
|
||||
|
||||
+3
-2
@@ -102,8 +102,9 @@ def rewrite_base64_to_url(
|
||||
if key.endswith("_base64") and isinstance(value, str):
|
||||
img_bytes = _decode_base64_value(value)
|
||||
if img_bytes is not None:
|
||||
# 生成文件名并落盘
|
||||
filename = f"{uuid.uuid4().hex}.png"
|
||||
# 按内容嗅探扩展名:PNG(接口1标注图,含透明) / JPEG(接口2/3/5 照片)
|
||||
ext = "png" if img_bytes[:8] == b"\x89PNG\r\n\x1a\n" else "jpg"
|
||||
filename = f"{uuid.uuid4().hex}.{ext}"
|
||||
filepath = Path(static_dir) / filename
|
||||
filepath.write_bytes(img_bytes)
|
||||
|
||||
|
||||
+8
-7
@@ -82,9 +82,10 @@ def test_grow_missing_gender_1004(client):
|
||||
assert r.json()["code"] == 1004
|
||||
|
||||
|
||||
_PNG_1x1 = base64.b64decode(
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
|
||||
)
|
||||
# mock ComfyUI 输出:一张合法 PNG(worker 会把它重编码成 JPG)
|
||||
import cv2 as _cv2
|
||||
import numpy as _np
|
||||
_PNG_1x1 = _cv2.imencode(".png", _np.full((8, 8, 3), 200, _np.uint8))[1].tobytes()
|
||||
|
||||
|
||||
def test_grow_female_returns_5(client, monkeypatch):
|
||||
@@ -99,8 +100,8 @@ def test_grow_female_returns_5(client, monkeypatch):
|
||||
results = body["data"]["results"]
|
||||
assert [x["hairline_type"] for x in results] == ["ellipse", "flower", "heart", "straight", "wave"]
|
||||
assert [x["order"] for x in results] == [1, 2, 3, 4, 5]
|
||||
assert base64.b64decode(results[0]["image_base64"])[:8] == b"\x89PNG\r\n\x1a\n"
|
||||
assert base64.b64decode(results[0]["grown_image_base64"])[:8] == b"\x89PNG\r\n\x1a\n"
|
||||
assert base64.b64decode(results[0]["image_base64"])[:3] == b"\xff\xd8\xff" # JPEG
|
||||
assert base64.b64decode(results[0]["grown_image_base64"])[:3] == b"\xff\xd8\xff" # JPEG
|
||||
assert "image_url" not in results[0]
|
||||
|
||||
|
||||
@@ -126,7 +127,7 @@ def test_growb_success(client, monkeypatch):
|
||||
assert body["code"] == 0, body
|
||||
d = body["data"]
|
||||
assert d["hairline_type"] == "custom"
|
||||
assert base64.b64decode(d["hair_growth_image_base64"])[:8] == b"\x89PNG\r\n\x1a\n"
|
||||
assert base64.b64decode(d["hair_growth_image_base64"])[:3] == b"\xff\xd8\xff" # JPEG
|
||||
assert "best_hairline_image_base64" not in d # 已去掉该字段
|
||||
assert "best_hairline_image_url" not in d
|
||||
|
||||
@@ -145,7 +146,7 @@ def test_hairline_gen_female(client):
|
||||
assert body["code"] == 0, body
|
||||
d = body["data"]
|
||||
assert [x["order"] for x in d["hairline_images"]] == [1, 2, 3, 4, 5]
|
||||
assert base64.b64decode(d["hairline_images"][0]["image_base64"])[:8] == b"\x89PNG\r\n\x1a\n"
|
||||
assert base64.b64decode(d["hairline_images"][0]["image_base64"])[:3] == b"\xff\xd8\xff" # JPEG
|
||||
c = d["best_hairline_center_point"]
|
||||
assert 0 <= c["x"] <= 682 and 0 <= c["y"] <= 811 # 落在原图范围内
|
||||
assert "image_url" not in d["hairline_images"][0]
|
||||
|
||||
Reference in New Issue
Block a user