|
|
@@ -0,0 +1,121 @@
|
|
|
+import cv2
|
|
|
+import numpy as np
|
|
|
+import py360convert
|
|
|
+import requests
|
|
|
+import json
|
|
|
+import os
|
|
|
+import argparse
|
|
|
+from pathlib import Path
|
|
|
+
|
|
|
+
|
|
|
+def imread_url(url):
|
|
|
+ """从 URL 读取图片并转换为 OpenCV 格式"""
|
|
|
+ try:
|
|
|
+ response = requests.get(url, timeout=10)
|
|
|
+ response.raise_for_status()
|
|
|
+ image_array = np.asarray(bytearray(response.content), dtype="uint8")
|
|
|
+ img = cv2.imdecode(image_array, cv2.IMREAD_COLOR)
|
|
|
+ return img
|
|
|
+ except Exception as e:
|
|
|
+ print(f"❌ 无法从 URL 下载图片: {url}, 错误: {e}")
|
|
|
+ return None
|
|
|
+
|
|
|
+
|
|
|
+def generate_pinhole_from_array(img, points, output_size=(600, 800)):
|
|
|
+ """处理图像数组,根据给定的四个点生成 Pinhole 图像"""
|
|
|
+ h, w = img.shape[:2]
|
|
|
+ pts = np.array(points, dtype=np.float32)
|
|
|
+
|
|
|
+ # 处理全景图跨界逻辑
|
|
|
+ if pts[1][0] < pts[0][0]:
|
|
|
+ pts[1][0] += w
|
|
|
+ if pts[2][0] < pts[3][0]:
|
|
|
+ pts[2][0] += w
|
|
|
+
|
|
|
+ center_x = np.mean(pts[:, 0]) % w
|
|
|
+ center_y = np.mean(pts[:, 1])
|
|
|
+
|
|
|
+ u_deg = (center_x / w) * 360 - 180
|
|
|
+ v_deg = 90 - (center_y / h) * 180
|
|
|
+
|
|
|
+ span_x = np.max(pts[:, 0]) - np.min(pts[:, 0])
|
|
|
+ span_y = np.max(pts[:, 1]) - np.min(pts[:, 1])
|
|
|
+
|
|
|
+ fov_h = (span_x / w) * 360 * 1.2
|
|
|
+ fov_v = (span_y / h) * 180 * 1.2
|
|
|
+
|
|
|
+ fov_h = min(fov_h, 110)
|
|
|
+ fov_v = min(fov_v, 110)
|
|
|
+
|
|
|
+ pinhole_img = py360convert.e2p(
|
|
|
+ img,
|
|
|
+ fov_deg=(fov_h, fov_v),
|
|
|
+ u_deg=u_deg,
|
|
|
+ v_deg=v_deg,
|
|
|
+ out_hw=output_size,
|
|
|
+ )
|
|
|
+ return pinhole_img
|
|
|
+
|
|
|
+
|
|
|
+def main():
|
|
|
+ # 1. 定义参数解析器
|
|
|
+ parser = argparse.ArgumentParser(description="全景图区域批量切图工具")
|
|
|
+ parser.add_argument("--json", type=str, required=True, help="输入的 JSON 文件路径")
|
|
|
+ parser.add_argument("--output", type=str, default="cropped_results", help="输出根目录 (默认: cropped_results)")
|
|
|
+ parser.add_argument("--width", type=int, default=5671, help="输出图片的宽度 (默认: 800)")
|
|
|
+ parser.add_argument("--height", type=int, default=3186, help="输出图片的高度 (默认: 600)")
|
|
|
+
|
|
|
+ args = parser.parse_args()
|
|
|
+
|
|
|
+ # 2. 读取 JSON
|
|
|
+ if not os.path.exists(args.json):
|
|
|
+ print(f"❌ 找不到文件: {args.json}")
|
|
|
+ return
|
|
|
+
|
|
|
+ with open(args.json, 'r', encoding='utf-8') as f:
|
|
|
+ data = json.load(f)
|
|
|
+
|
|
|
+ scene_code = data.get("sceneCode", "unknown_scene")
|
|
|
+ image_cache = {}
|
|
|
+ output_size = (args.height, args.width) # 注意 opencv 是 (h, w)
|
|
|
+
|
|
|
+ # 3. 循环处理
|
|
|
+ for item in data.get("list", []):
|
|
|
+ sid = item.get("tag", {}).get("sid", "unknown_sid")
|
|
|
+ snaps = item.get("snap", [])
|
|
|
+
|
|
|
+ for idx, snap in enumerate(snaps):
|
|
|
+ img_url = snap.get("imageUrl")
|
|
|
+ pano_id = snap.get("panoId", "0")
|
|
|
+ corners = snap.get("corners", [])
|
|
|
+
|
|
|
+ if not img_url or len(corners) < 4:
|
|
|
+ continue
|
|
|
+
|
|
|
+ if img_url not in image_cache:
|
|
|
+ print(f"🌐 正在下载全景图: {img_url}")
|
|
|
+ image_cache[img_url] = imread_url(img_url)
|
|
|
+
|
|
|
+ origin_img = image_cache[img_url]
|
|
|
+ if origin_img is None: continue
|
|
|
+
|
|
|
+ pts_list = [(c['x'], c['y']) for c in corners]
|
|
|
+
|
|
|
+ try:
|
|
|
+ print(f"🎨 处理 [SID: {sid}] [Pano: {pano_id}] - 区域 {idx}")
|
|
|
+ result_img = generate_pinhole_from_array(origin_img, pts_list, output_size=output_size)
|
|
|
+
|
|
|
+ # 创建保存目录
|
|
|
+ save_dir = Path(args.output) / scene_code / sid
|
|
|
+ save_dir.mkdir(parents=True, exist_ok=True)
|
|
|
+
|
|
|
+ save_path = save_dir / f"pano_{pano_id}_snap_{idx}.jpg"
|
|
|
+ cv2.imwrite(str(save_path), result_img)
|
|
|
+ except Exception as e:
|
|
|
+ print(f"⚠️ 处理失败: {e}")
|
|
|
+
|
|
|
+ print("\n✨ 处理完成!")
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ main()
|