guqing 14 годин тому
батько
коміт
b888f897fa

+ 165 - 0
pano_ori_2pinehole/README.md

@@ -0,0 +1,165 @@
+# 全景图区域批量切图工具
+
+## 项目简介
+
+这是一个用于处理全景图(360° 全景图片)的命令行工具,可以根据 JSON 配置文件中定义的区域坐标,批量裁剪出多个 Pinhole(普通视角)图片。
+
+## 核心功能
+
+- 从 URL 下载全景图片
+- 根据给定的四个角点坐标,将全景图裁剪为普通视角图片
+- 支持全景图跨边界处理(当坐标跨越 360° 边界时自动校正)
+- 批量处理多个场景和多个区域
+- 自动创建分层输出目录
+
+## 代码运行流程
+
+### 1. 启动流程
+
+```
+命令行执行 → argparse 解析参数 → main() 函数
+```
+
+执行命令:
+```bash
+基础用法: python process_pano.py --json <输入 JSON 文件> 
+可调参数:python process_pano.py --json <输入 JSON 文件> [--output 输出目录] [--width 宽度] [--height 高度]
+```
+
+### 2. 主要处理步骤
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│  步骤 1: 读取 JSON 配置文件                                  │
+└─────────────────────────────────────────────────────────────┘
+                              ↓
+┌─────────────────────────────────────────────────────────────┐
+│  步骤 2: 解析 sceneCode,初始化图片缓存                       │
+└─────────────────────────────────────────────────────────────┘
+                              ↓
+┌─────────────────────────────────────────────────────────────┐
+│  步骤 3: 遍历每个场景项 (list 数组)                           │
+│     - 获取 tag.sid (场景 ID)                                 │
+│     - 获取 snap[] 数组 (快照列表)                            │
+└─────────────────────────────────────────────────────────────┘
+                              ↓
+┌─────────────────────────────────────────────────────────────┐
+│  步骤 4: 遍历每个快照 (snap)                                  │
+│     - 获取 imageUrl, panoId, corners                         │
+│     - 跳过无效数据 (无 URL 或少于 4 个角点)                     │
+└─────────────────────────────────────────────────────────────┘
+                              ↓
+┌─────────────────────────────────────────────────────────────┐
+│  步骤 5: 下载全景图 (使用缓存避免重复下载)                     │
+│     imread_url() → OpenCV 图片格式                           │
+└─────────────────────────────────────────────────────────────┘
+                              ↓
+┌─────────────────────────────────────────────────────────────┐
+│  步骤 6: 生成 Pinhole 图片                                   │
+│     generate_pinhole_from_array():                          │
+│     - 处理全景图跨边界坐标                                   │
+│     - 计算中心点 (u_deg, v_deg)                             │
+│     - 计算视场角 (fov_h, fov_v)                             │
+│     - 调用 py360convert.e2p() 转换                          │
+└─────────────────────────────────────────────────────────────┘
+                              ↓
+┌─────────────────────────────────────────────────────────────┐
+│  步骤 7: 保存结果图片                                         │
+│     路径:{output}/{sceneCode}/{sid}/pano_{panoId}_snap_{idx}.jpg │
+└─────────────────────────────────────────────────────────────┘
+```
+
+## 输入格式
+
+JSON 文件格式示例:
+
+```json
+{
+  "sceneCode": "场景编码",
+  "list": [
+    {
+      "tag": {
+        "sid": "场景 ID"
+      },
+      "snap": [
+        {
+          "imageUrl": "https://example.com/panorama.jpg",
+          "panoId": "0",
+          "corners": [
+            {"x": 100, "y": 200},
+            {"x": 500, "y": 200},
+            {"x": 500, "y": 600},
+            {"x": 100, "y": 600}
+          ]
+        }
+      ]
+    }
+  ]
+}
+```
+
+## 输出结果
+
+### 保存位置
+
+默认输出目录结构:
+
+```
+cropped_results/
+└── {sceneCode}/
+    └── {sid}/
+        ├── pano_0_snap_0.jpg
+        ├── pano_0_snap_1.jpg
+        └── pano_1_snap_0.jpg
+```
+
+- **根目录**: 可通过 `--output` 参数自定义 (默认:`cropped_results`)
+- **场景目录**: 以 `sceneCode` 命名
+- **子目录**: 以 `sid` (场景 ID) 命名
+- **文件命名**: `pano_{panoId}_snap_{索引}.jpg`
+
+### 输出图片规格
+
+- 默认尺寸:5671 × 3186 像素
+- 可通过 `--width` 和 `--height` 参数调整
+
+## 依赖安装
+
+```bash
+pip install -r requirements.txt
+```
+
+需要的主要依赖:
+- `opencv-python`: 图片读取和保存
+- `numpy`: 数组运算
+- `py360convert`: 全景图转普通视角转换
+- `requests`: HTTP 下载图片
+
+## 使用示例
+
+```bash
+# 基本用法
+python process_pano.py --json config.json
+
+# 自定义输出目录和尺寸
+python process_pano.py --json config.json --output my_results --width 1920 --height 1080
+```
+
+## 关键算法说明
+
+### 全景图跨边界处理
+
+当全景图坐标跨越 0°/360°边界时,代码自动进行坐标校正:
+
+```python
+if pts[1][0] < pts[0][0]:
+    pts[1][0] += w  # 右边界点向左补偿
+if pts[2][0] < pts[3][0]:
+    pts[2][0] += w  # 下边界点向左补偿
+```
+
+### 视场角计算
+
+- 水平视场角:`fov_h = (span_x / w) * 360 * 1.2` (最大 110°)
+- 垂直视场角:`fov_v = (span_y / h) * 180 * 1.2` (最大 110°)
+- 乘以 1.2 是为了留 20% 的边距

+ 121 - 0
pano_ori_2pinehole/process_pano.py

@@ -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()

+ 3 - 0
pano_ori_2pinehole/requirements.txt

@@ -0,0 +1,3 @@
+opencv-python
+numpy
+py360convert requests