Panno-AI-API / scripts /processor.py
GitHub Actions Bot
Sync: Thu Feb 12 07:00:42 UTC 2026
dce7eca
import cv2
import numpy as np
import base64
import requests
import sys
import os
import json
class PanoEngine:
def __init__(self, api_key):
self.api_key = api_key
# 创建拼接器,全景模式
self.stitcher = cv2.Stitcher_create(cv2.Stitcher_PANORAMA)
def stitch_and_generate_mask(self, image_paths):
"""1. 使用 OpenCV 拼接并生成掩码"""
imgs = []
for p in image_paths:
img = cv2.imread(p)
if img is not None:
imgs.append(img)
if len(imgs) < 1:
raise Exception("No valid images found for stitching.")
if len(imgs) == 1:
# 只有一张图时,直接作为基础图
pano = imgs[0]
else:
# 拼接
status, pano = self.stitcher.stitch(imgs)
if status != cv2.Stitcher_OK:
# 如果拼接失败,回退到第一张图
pano = imgs[0]
# Prepare canvas with strictly 2:1 ratio
h, w = pano.shape[:2]
canvas_w = w
canvas_h = int(w / 2)
canvas = np.zeros((canvas_h, canvas_w, 3), dtype=np.uint8)
if h > canvas_h:
# Prevent vertical stretching: Crop center height
start_y = (h - canvas_h) // 2
stitched_crop = pano[start_y:start_y+canvas_h, :]
canvas = stitched_crop
else:
# Fill gaps: Center vertically
y_offset = (canvas_h - h) // 2
canvas[y_offset:y_offset+h, 0:w] = pano
# Create specialized mask for ceiling/floor inpainting
mask = np.ones((canvas_h, canvas_w), dtype=np.uint8) * 255
actual_h = min(h, canvas_h)
actual_y = (canvas_h - actual_h) // 2
mask[actual_y:actual_y+actual_h, 0:w] = 0
return canvas, mask
def stability_inpaint(self, image, mask, prompt):
"""2. Stability AI v2beta Inpaint API"""
url = "https://api.stability.ai/v2beta/stable-image/edit/inpaint"
_, img_encoded = cv2.imencode(".png", image)
_, mask_encoded = cv2.imencode(".png", mask)
headers = {
"Authorization": f"Bearer {self.api_key}",
"Accept": "image/*"
}
files = {
"image": ("image.png", img_encoded.tobytes(), "image/png"),
"mask": ("mask.png", mask_encoded.tobytes(), "image/png"),
}
data = {
"prompt": f"{prompt}, photorealistic 360 panorama, wide angle, immersive view, seamless texture",
"output_format": "png",
}
response = requests.post(url, headers=headers, files=files, data=data)
if response.status_code != 200:
raise Exception(f"Stability API Error ({response.status_code}): {response.text}")
result_base64 = base64.b64encode(response.content).decode('utf-8')
return result_base64
if __name__ == "__main__":
if len(sys.argv) < 3:
print(json.dumps({"success": False, "error": "Insufficient arguments"}))
sys.exit(1)
api_key = sys.argv[1]
prompt = sys.argv[2]
img_paths = sys.argv[3:]
try:
engine = PanoEngine(api_key)
# 某些环境可能没有安装 OpenCV 拼接插件,做个兼容
pano, mask = engine.stitch_and_generate_mask(img_paths)
# 如果没有配置 API key,仅返回拼接结果(开发者调试用)
if not api_key or api_key == "undefined":
_, final_encoded = cv2.imencode(".png", pano)
res_base64 = base64.b64encode(final_encoded).decode('utf-8')
print(json.dumps({"success": True, "image": f"data:image/png;base64,{res_base64}", "note": "Stitched only, no AI"}))
else:
res_base64 = engine.stability_inpaint(pano, mask, prompt)
print(json.dumps({"success": True, "image": f"data:image/png;base64,{res_base64}"}))
except Exception as e:
print(json.dumps({"success": False, "error": str(e)}))