This commit is contained in:
2026-04-22 09:26:39 +08:00
parent c3bd750a66
commit ccc059edc0
11 changed files with 4371 additions and 3 deletions

2217
StripStitch.py Normal file

File diff suppressed because it is too large Load Diff

153
StripStitch.spec Normal file
View File

@ -0,0 +1,153 @@
# -*- mode: python ; coding: utf-8 -*-
import os
from PyInstaller.utils.hooks import (
collect_data_files,
collect_dynamic_libs,
collect_submodules,
)
def _safe_collect_submodules(name: str):
try:
return collect_submodules(name)
except Exception:
return []
def _safe_collect_data_files(name: str):
try:
return collect_data_files(name)
except Exception:
return []
def _safe_collect_dynamic_libs(name: str):
try:
return collect_dynamic_libs(name)
except Exception:
return []
project_root = r"e:\code\vismatch-main\vismatch-main"
test_dir = os.path.join(project_root, "test")
script_path = os.path.join(test_dir, "StripStitch.py")
# Find the actual vismatch installation location (usually in site-packages)
import vismatch as _vismatch_pkg
vismatch_sitepkg_root = os.path.dirname(_vismatch_pkg.__file__)
hiddenimports = []
hiddenimports += _safe_collect_submodules("vismatch")
hiddenimports += _safe_collect_submodules("rasterio")
hiddenimports += _safe_collect_submodules("rasterio._base")
hiddenimports += _safe_collect_submodules("rasterio._io")
hiddenimports += _safe_collect_submodules("affine")
hiddenimports += _safe_collect_submodules("cv2")
hiddenimports += ["tif_caijain"]
hiddenimports += _safe_collect_submodules("pyproj")
hiddenimports += _safe_collect_submodules("scipy")
hiddenimports += _safe_collect_submodules("skimage")
hiddenimports += _safe_collect_submodules("SimpleITK")
hiddenimports += _safe_collect_submodules("pirt")
hiddenimports += _safe_collect_submodules("loguru")
# MatchAnything's src module - need to collect from third_party directory
# Add the src module path to pathex so PyInstaller can find it during analysis
matchanything_src_dir = os.path.join(vismatch_sitepkg_root, "third_party", "MatchAnything", "imcui", "third_party", "MatchAnything")
if os.path.isdir(matchanything_src_dir):
# Add to pathex for analysis
pass # Will be added to pathex in Analysis
# Also try to collect src submodules if they exist
try:
hiddenimports += _safe_collect_submodules("src")
except Exception:
pass
datas = []
datas += _safe_collect_data_files("vismatch")
datas += _safe_collect_data_files("rasterio")
datas += _safe_collect_data_files("pyproj")
# vismatch 的 third_party 下包含大量运行时动态 add_to_path 的源码(例如 matchanything 依赖的 src.*)。
# 这些 .py 文件不会被 collect_data_files 收集,需作为 datas 复制到 dist 里,并保持相对路径结构,
# 使得运行时 THIRD_PARTY_DIR = Path(vismatch.__file__).parent/"third_party" 可找到它们.
# Use the actual vismatch installation location (site-packages) instead of project root
# NOTE: Analysis() expects hook-style 2-tuples (source_dir, dest_dir), not Tree() TOC 3-tuples.
third_party_dir = os.path.join(vismatch_sitepkg_root, "third_party")
if os.path.isdir(third_party_dir):
datas.append((third_party_dir, os.path.join("vismatch", "third_party")))
# Include HuggingFace model weights for offline use (~/.cache/huggingface/hub/)
hf_cache_dir = os.path.join(os.path.expanduser("~"), ".cache", "huggingface", "hub")
if os.path.isdir(hf_cache_dir):
for model_dir in os.listdir(hf_cache_dir):
if "vismatch" in model_dir.lower():
full_model_path = os.path.join(hf_cache_dir, model_dir)
if os.path.isdir(full_model_path):
datas.append((full_model_path, os.path.join("hub", model_dir)))
binaries = []
binaries += _safe_collect_dynamic_libs("rasterio")
binaries += _safe_collect_dynamic_libs("pyproj")
binaries += _safe_collect_dynamic_libs("cv2")
# Build pathex - include MatchAnything src directory for proper import analysis
pathex = [project_root, test_dir]
matchanything_src_dir = os.path.join(vismatch_sitepkg_root, "third_party", "MatchAnything", "imcui", "third_party", "MatchAnything")
if os.path.isdir(matchanything_src_dir):
pathex.append(matchanything_src_dir)
# Also add ROMA if it exists
roma_dir = os.path.join(vismatch_sitepkg_root, "third_party", "MatchAnything", "imcui", "third_party", "MatchAnything", "third_party", "ROMA")
if os.path.isdir(roma_dir):
pathex.append(roma_dir)
a = Analysis(
[script_path],
pathex=pathex,
binaries=binaries,
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name="StripStitch",
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=False,
upx_exclude=[],
name="StripStitch",
)

287
StripStitch_说明.md Normal file
View File

@ -0,0 +1,287 @@
## `StripStitch.py` 说明文档(遥感航带批量配准)
`StripStitch.py` 用于 **批量将文件夹内的 `.bip` 航带影像配准到一张参考 GeoTIFF`.tif`)底图**。脚本默认启动 **GUITkinter**,也支持通过 `--cli` 走“无界面批处理”(但当前版本 **没有 argparse 参数**CLI 模式仍使用脚本内的默认配置/或你手动改常量)。
---
## 1. 功能概览
- **批量处理**:遍历 `BIP_DIR` 下的 `*.bip`,逐个配准到参考底图。
- **自动裁剪参考 ROI**:根据源图有效像素掩膜推算与底图的重叠范围,并在底图上取 ROI可额外 padding
- **特征匹配**:通过 `vismatch.get_matcher()` 调用多种 matcher可选 GPU/CPU
- **质量控制**
- 掩膜羽化(降低硬边界导致的假匹配)
- 掩膜边缘带剔除(减少“掩膜边界”上的伪匹配点)
- 纹理过滤(低梯度/低纹理区域的匹配点剔除)
- 最少内点数/最少内点比例阈值
- **多模型变换尝试并自动选最优**:按你设置的优先级尝试 `similarity/affine/homography/piecewise_affine/polynomial/tps...`以重投影误差p95选择最优。
- **输出**
- 配准后的 `.bip`ENVI driver
- 匹配可视化图(匹配线、关键点)
- 统计 CSV每个航带一行内点数、内点比例、选用模型、误差等
- **可选底图掩膜**:先用另一个 mask GeoTIFF 对参考底图做掩膜生成新底图,再用新底图进行配准(依赖 `tif_caijain.py`)。
---
## 2. 运行方式
### 2.1 GUI默认
在脚本所在目录执行:
```bash
python StripStitch.py
```
GUI 会打开“遥感影像批量配准工具”,在界面里选择:
- 参考底图GeoTIFF
- BIP 文件夹
- 输出文件夹
- matcher、设备CUDA/CPU
- 变换模型优先级
- 一系列阈值/过滤参数
点击“开始”后批处理运行,日志会实时显示;可点击“停止”中断。
### 2.2 CLI无界面批处理
```bash
python StripStitch.py --cli
```
注意:
- 当前脚本仅通过 `if "--cli" in sys.argv` 切换模式,**没有命令行参数解析**。
- CLI 模式使用脚本顶部的默认常量(如 `REF_TIF / BIP_DIR / OUT_DIR / MATCHER_NAME ...`)构造配置。
- 如果你希望 CLI 可配置参数,需要额外改造(例如用 `argparse`)。
---
## 3. 输入数据要求
- **参考底图**GeoTIFF`.tif`),必须有 **有效 CRS/transform**(脚本会报错或无法正确投影/裁剪)。
- **待配准航带**ENVI BIP扩展名 `.bip`,内部 profile 由 `rasterio` 读取)。
- **坐标系**
-`.bip` 若缺少 CRS脚本会尝试使用参考底图 CRS 继续(可能导致错误配准,建议为源数据补齐 CRS
- ROI 的推算使用 `rasterio.warp.transform_bounds` 从源 CRS 转到参考 CRS。
---
## 4. 输出结构与文件命名
假设输出目录为 `OUT_DIR`,运行一次批处理会生成:
- **配准结果**`OUT_DIR/<bip_stem>_registered.bip`
- 写入使用 `rasterio.open(..., driver="ENVI", interleave="bip")`
- 旁边通常会伴随 ENVI 的头文件(如 `.hdr` 等,取决于 `rasterio/GDAL` 行为)
- **可视化**`OUT_DIR/visualizations/`
- `<bip_stem>_matches.png`:匹配线可视化
- `<bip_stem>_keypoints_src.png`:源图关键点
- `<bip_stem>_keypoints_ref.png`:参考 ROI 关键点
- **统计**`OUT_DIR/stats/registration_stats_<YYYYMMDD_HHMMSS>.csv`
- 列:`timestamp, filename, num_inliers, num_matches, inlier_ratio, selected_method, median_error, p95_error, success`
- **(可选)掩膜后的参考底图**`OUT_DIR/masked_refs/<ref_stem>_masked_<ts>.tif`
- 仅当 GUI 勾选“启用底图掩膜”且 `tif_caijain.py` 可用时生成
---
## 5. 参数说明GUI 与脚本配置一致)
### 5.1 基础路径
- **参考 TIF 文件**:配准目标底图(匹配在底图 ROI 上进行)。
- **BIP 文件夹**:批量遍历 `*.bip`
- **输出文件夹**:结果、可视化、统计都写入这里。
### 5.2 底图掩膜(可选)
GUI 勾选“启用底图掩膜”后:
- 需要提供 **掩膜 TIF**,并要求与底图 **严格对齐**(同 CRS、分辨率、范围、尺寸
- 掩膜值为 `remove_value` 的区域会被设置为 NoData`tif_caijain.mask_data_by_binary_mask` 实现)。
- 脚本会先生成“掩膜后的底图”,后续配准基于该底图进行。
### 5.3 matcher 与设备
- **匹配算法matcher_name**:由 `vismatch.get_matcher(name, device=...)` 创建。
- GUI 下拉框内内置了一长串候选(如 `matchanything-roma`, `loftr`, `sift-lightglue`, `roma`, `xfeat-star` 等)。
- 不同 matcher 在速度/鲁棒性/显存占用方面差异很大。
- **设备device**
- `cuda`:更快,但需要 GPU + CUDA 环境
- `cpu`:更通用,但会慢
### 5.4 变换模型(按优先级)
可多选并排序;脚本会按优先级逐一估计并评估误差,最终选 p95 重投影误差最小者。
常用含义(对应 GUI 列表):
- **`similarity`**:相似变换(平移 + 旋转 + 等比缩放)
- **`affine`**:仿射变换(含非等比缩放/切变)
- **`homography`**:单应(透视)变换,最灵活但更易受离群点影响
- **`piecewise_affine` / `polynomial` / `polynomial_order3` / `tps`**:非刚性/高阶模型(依赖可选库,见“依赖”)
### 5.5 匹配与 ROI 参数
- **匹配最大边长match_max_side**:匹配前会等比缩小到最大边不超过该值。越大越慢、细节更多。
- **ROI 填充像素roi_pad_px**:从源有效区域推算出的底图 ROI会再向外扩展该像素数底图像素尺度
- **掩膜膨胀像素mask_pad_px**:仅用于匹配阶段,对源有效掩膜重投影到参考 ROI 后进行膨胀,扩大可匹配区域。
### 5.6 质量控制(建议从默认值起调)
- **最少内点数min_inliers**:过滤后 RANSAC 内点数低于该值直接判失败。
- **最少内点比例min_inlier_ratio**\(\text{inliers} / \text{matches}\) 低于阈值判失败。
- **掩膜羽化宽度feather_px**:在掩膜边界做平滑过渡,减少硬边界假匹配。
- **边缘带剔除宽度edge_band_px**:剔除距离掩膜边界过近的匹配点(小图尺度会按缩放比例换算)。
- **纹理过滤分位阈值min_grad_quantile**:在匹配尺寸上计算梯度幅值分位数,低纹理区域的点会被剔除。值越大,保留的区域越“高纹理”。
---
## 6. 内部处理流程(高层)
单个 `.bip` 的核心流程(简化版):
- 读取源图与源有效掩膜(`read_masks`),在源 CRS 下取有效像素包围盒
- 包围盒 bounds 投影到参考 CRS在参考底图上构建 ROI window并额外 `roi_pad_px`
- 读取源图全图、参考底图 ROI
- 将源有效掩膜重投影到参考 ROI并按 `mask_pad_px` 膨胀
- 对源/参考做掩膜羽化(`feather_px`)后进入匹配
- 下采样到 `match_max_side`,运行 matcher 得到匹配点/内点
- 用“边缘带剔除 + 纹理过滤”对匹配点/内点二次过滤,得到最终内点与质量指标
- 在你选定的多个变换模型中估计并评估,选 p95 误差最小者
- 根据模型类型执行重采样并写出 ENVI BIP
- 写统计 CSV保存可视化图片
---
## 7. 依赖与可选依赖(缺失时的行为)
### 7.1 必需依赖(脚本直接 import
- `numpy`
- `opencv-python``cv2`
- `rasterio`
- `affine`
- `tkinter`Windows 自带 Python 通常包含)
- `vismatch`(脚本依赖它来创建 matcher并用 `vismatch.viz` 保存可视化图)
### 7.2 可选依赖(缺失会降级/跳过)
- **`tif_caijain.py`**:用于“底图掩膜”功能;缺失则 GUI 勾选会报错。
- **`scikit-image`**:用于 `piecewise_affine` / `polynomial` 等变换;缺失会跳过这些方法或走回退逻辑。
- **`matplotlib` + `scipy`ConvexHull**:用于点集凸包的“内点判定”,缺失时会退化为矩形内判断(更可能外推导致异常区域)。
- **`SimpleITK` / `pirt` / `scipy.interpolate.Rbf`**:用于 TPS 等更复杂的非线性变换与回退路径;缺失时可能回退到更简单的仿射。
---
## 8. 常见问题排查
- **报错:参考文件缺少 CRS 信息**
- 参考 GeoTIFF 必须有 CRS。用 GIS 软件或 `gdalinfo` 检查并修复投影信息。
- **源 `.bip` 缺少 CRS**
- 脚本会“尝试用参考 CRS”但这通常不可靠建议为源数据补齐正确 CRS。
- **内点很少 / 内点比例很低**
- 优先检查:
- 参考底图与航带是否确实有重叠区域
- ROI padding 是否过小(`roi_pad_px`
- 边缘带剔除是否过强(`edge_band_px`
- 纹理过滤是否过强(`min_grad_quantile`
- 其次尝试更鲁棒的 matcher或改用 GPU
- **输出范围不对 / 被裁得太小**
- 输出范围由匹配点映射到参考像素后的外接矩形决定;匹配点集中在局部会导致 bbox 偏小。
- 可尝试增大 `roi_pad_px`、降低过滤强度、或换更稳定的 matcher。
- **非线性方法piecewise/polynomial/tps经常失败**
- 先确保 `scikit-image``scipy` 等可用。
- 非线性方法更依赖匹配点覆盖范围与质量;当点分布很局部时更容易外推/数值不稳。
- 生产环境通常建议把 `affine` 作为兜底并放在较高优先级。
---
## 9. 推荐使用习惯(实操)
- **先用少量样本跑通**:把 `BIP_DIR` 里先放 13 条航带验证匹配与输出,再批量跑全量。
- **先看 `visualizations/`**:匹配线与关键点能最快判断“是没重叠、还是过滤过强、还是 matcher 不适配”。
- **保留 `stats/`**:后续筛选失败样本、做参数回归/对比很有用。
flowchart TD
A[开始处理单个BIP文件] --> B[读取源文件和参考文件]
B --> C{检查CRS坐标系统}
C -->|源文件无CRS| D[使用参考文件CRS]
C -->|源文件有CRS| E[使用源文件CRS]
D --> F[计算有效区域ROI]
E --> F
F --> F1[基于有效掩膜计算包围盒]
F1 --> F2[将包围盒转换到参考坐标系]
F2 --> F3[扩展窗口ROI_PAD_PX]
F3 --> G[读取图像数据]
G --> H[将源图掩膜重投影到参考空间]
H --> H1[可选: 膨胀掩膜MASK_PAD_PX]
H1 --> I[图像预处理]
I --> I1[转3通道_float01格式]
I1 --> I2[百分位数拉伸归一化]
I2 --> J[生成软掩膜]
J --> J1[距离变换生成羽化边缘]
J1 --> J2[应用掩膜到图像]
J2 --> K[降采样用于匹配]
K --> K1[等比缩放到MATCH_MAX_SIDE]
K1 --> L[特征匹配]
L --> L1[调用Matcher获取匹配点]
L1 --> M[匹配点过滤]
M --> M1[边缘带剔除 EDGE_BAND_PX]
M --> M2[纹理过滤 MIN_GRAD_QUANTILE]
M1 --> M3[组合掩膜过滤]
M2 --> M3
M3 --> N{质量控制检查}
N -->|内点数<MIN_INLIERS| O[配准失败]
N -->|内点比例<MIN_RATIO| O
N -->|通过检查| P[计算全分辨率坐标]
P --> Q[估计多种变换模型]
Q --> Q1[similarity]
Q --> Q2[affine]
Q --> Q3[homography]
Q --> Q4[piecewise_affine]
Q --> Q5[polynomial]
Q1 --> R[评估变换质量]
Q2 --> R
Q3 --> R
Q4 --> R
Q5 --> R
R --> S[选择最优变换模型]
S --> T{变换类型判断}
T -->|Affine| U[仿射变换处理]
T -->|Homography| V[单应变换处理]
T -->|Piecewise/Polynomial| W[非线性变换处理]
U --> X[计算最小外接矩形]
V --> X
W --> X
X --> Y[创建输出文件]
Y --> Z[逐波段几何重采样]
Z --> AA[保存配准结果]
AA --> AB[记录统计信息]
AB --> AC[结束]
O --> AD[记录失败信息]
AD --> AC
T -->|所有变换失败| AE[仿射回退处理]
AE -->|回退成功| AA
AE -->|回退失败| O

Binary file not shown.

1336
test V10.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -62,7 +62,7 @@ logger = logging.getLogger(__name__)
# ---------- 配置 ----------
# 请根据实际情况修改这些路径
REF_TIF = r"E:\is2\guidingsahn\mask_water.tif" # 参考 tif 文件路径
BIP_DIR = Path(r"E:\is2\guidingsahn") # .bip 文件所在文件夹
BIP_DIR = Path(r"D:\BaiduNetdiskDownload\20250902\_3_52_52\Geoout\Geoout") # .bip 文件所在文件夹
OUT_DIR = Path(r"E:\is2\guidingsahn\output") # 输出文件夹
# 匹配算法选择

View File

@ -1345,7 +1345,10 @@ def _run_batch(cfg: RegistrationConfig, stop_event: threading.Event, progress_cb
out_dir.mkdir(parents=True, exist_ok=True)
stats_dir = out_dir / "stats"
stats_dir.mkdir(parents=True, exist_ok=True)
stats_csv = stats_dir / "registration_stats.csv"
ts = datetime.now().strftime('%Y%m%d_%H%M%S')
stats_csv = stats_dir / f"registration_stats_{ts}.csv"
logger.info(f"统计信息将保存到: {stats_csv}")
init_stats_csv(stats_csv)

186
tif_caijain.py Normal file
View File

@ -0,0 +1,186 @@
"""
使用二值掩膜 TIF 文件值为1的区域需要去除对数据 TIF 文件进行掩膜。
输入:
data_tif: 要掩膜的数据文件路径
mask_tif: 二值掩膜文件路径值为1表示需要去除的区域
输出:
掩膜后的数据 TIF 文件,仅将掩膜对应位置设为 NoData
要求:
两个 TIF 文件具有相同的投影、分辨率、范围和尺寸(精确对齐),
否则程序将报错或行为未定义。
"""
import argparse
import numpy as np
import rasterio
from rasterio.windows import Window
import logging
import sys
from pathlib import Path
from tqdm import tqdm
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class _NullWriter:
def write(self, _s):
return 0
def flush(self):
return None
def _tqdm_output():
for fp in (getattr(sys, "stderr", None), getattr(sys, "stdout", None)):
if fp is not None and hasattr(fp, "write"):
return fp
return _NullWriter()
def _tqdm_disable(fp) -> bool:
if isinstance(fp, _NullWriter):
return True
try:
return not bool(fp.isatty())
except Exception:
return True
def mask_data_by_binary_mask(
data_path,
mask_path,
output_path=None,
remove_value=1,
nodata_value=None,
tile_size=4096,
):
"""使用二值掩膜 TIF 对数据 TIF 进行掩膜。
将数据 TIF 中对应掩膜值等于 remove_value 的像素设为 NoData其余保留。
性能建议:
- 若数据源是 tiled GeoTIFF可将 tile_size 设为 0 以按源文件块窗口遍历(通常更快)。
"""
data_path = Path(data_path)
mask_path = Path(mask_path)
if output_path is None:
output_path = data_path.parent / f"{data_path.stem}_masked{data_path.suffix}"
else:
output_path = Path(output_path)
logger.info(f"数据文件: {data_path.name}")
logger.info(f"掩膜文件: {mask_path.name}")
logger.info(f"去除掩膜值: {remove_value}")
with rasterio.Env(GDAL_NUM_THREADS="ALL_CPUS"):
with rasterio.open(data_path) as src_data, rasterio.open(mask_path) as src_mask:
if src_data.crs != src_mask.crs:
raise ValueError("数据与掩膜的 CRS 不一致,请先统一投影。")
if src_data.transform != src_mask.transform:
logger.warning(
"数据与掩膜的地理变换不一致,可能未精确对齐,继续处理可能存在风险。"
)
if (src_data.width, src_data.height) != (src_mask.width, src_mask.height):
raise ValueError("数据与掩膜的尺寸不一致,无法直接按像素对应掩膜。")
# 确定输出 NoData 值(并尽量匹配数据 dtype避免隐式类型转换带来的开销
if nodata_value is None:
nodata_value = src_data.nodata if src_data.nodata is not None else 0
try:
nodata_value_cast = np.array(
nodata_value, dtype=src_data.dtypes[0]
).item()
except Exception:
nodata_value_cast = nodata_value
# 创建输出元数据:基于数据源的元数据,更新 nodata 和压缩选项
out_meta = src_data.meta.copy()
out_meta.update(
{
"nodata": nodata_value,
"compress": (
src_data.compression.value if src_data.compression else "lzw"
),
"tiled": src_data.is_tiled,
}
)
if src_data.is_tiled:
out_meta.update(
{
"blockxsize": src_data.block_shapes[0][0],
"blockysize": src_data.block_shapes[0][1],
}
)
# 创建输出文件
with rasterio.open(output_path, "w", **out_meta) as dst:
width, height = src_data.width, src_data.height
if tile_size is None or tile_size <= 0:
windows = [w for _, w in src_data.block_windows(1)]
else:
stride = int(tile_size)
windows = [
Window(i, j, min(stride, width - i), min(stride, height - j))
for i in range(0, width, stride)
for j in range(0, height, stride)
]
tqdm_fp = _tqdm_output()
with tqdm(
total=len(windows),
desc="处理瓦片",
unit="",
file=tqdm_fp,
disable=_tqdm_disable(tqdm_fp),
) as pbar:
for window in windows:
# 读取相同位置的掩膜瓦片(假设完全对齐)
mask = src_mask.read(1, window=window)
remove_mask = mask == remove_value
# 读取数据瓦片
data = src_data.read(window=window) # shape: (bands, h, w)
if remove_mask.any():
for band_idx in range(data.shape[0]):
np.putmask(
data[band_idx], remove_mask, nodata_value_cast
)
dst.write(data, window=window)
pbar.update(1)
logger.info(f"处理完成,输出文件:{output_path}")
return str(output_path)
def main():
parser = argparse.ArgumentParser(
description="使用二值掩膜 TIF值为1的区域对数据 TIF 进行掩膜,将对应位置设为 NoData。"
)
parser.add_argument("data_tif", help="要掩膜的数据 TIF 文件路径")
parser.add_argument("mask_tif", help="二值掩膜 TIF 文件路径值为1表示需要去除的区域")
parser.add_argument("-o", "--output", help="输出文件路径 (可选)")
parser.add_argument("-r", "--remove_value", type=int, default=1,
help="掩膜中要去除的值默认为1")
parser.add_argument("-n", "--nodata", type=float,
help="输出 NoData 值 (可选,默认使用数据 TIF 的 NoData 或 0)")
parser.add_argument(
"-t",
"--tile_size",
type=int,
default=4096,
help="分块大小像素默认4096设为0则按源文件块窗口遍历tiled 文件通常更快)",
)
args = parser.parse_args()
mask_data_by_binary_mask(
args.data_tif, args.mask_tif, args.output,
args.remove_value, args.nodata, args.tile_size
)
if __name__ == "__main__":
exit(main())

BIN
思维导图.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

186
说明文档 Normal file
View File

@ -0,0 +1,186 @@
# StripStitch航带批量配准工具使用说明
## 1. 工具简介
`StripStitch.py` 用于将一个文件夹中的多幅 `.bip` 航带影像批量配准到一幅参考底图 `.tif`GeoTIFF输出配准后的 BIP 文件,并生成配准统计 CSV、日志与可视化结果若启用/代码包含)。
支持的能力(按当前脚本实现为准):
- GUI 图形界面:选择输入/输出、配置 matcher、选择变换模型、设置质量阈值与过滤参数
- 参考底图可选“掩膜”:先用 `tif_caijain.py` 对底图做 NoData 掩膜,再用掩膜后的底图执行配准
- 统计 CSV每次运行生成独立文件文件名包含时间戳
- 出错弹窗:处理线程异常会弹窗显示完整 traceback便于定位问题
---
## 2. 目录结构(建议)
建议将脚本与相关文件保持在同一目录(当前工程已是):
- `e:\code\vismatch-main\vismatch-main\test\StripStitch.py`
- `e:\code\vismatch-main\vismatch-main\test\tif_caijain.py`
- `e:\code\vismatch-main\vismatch-main\vismatch\`matcher 依赖)
---
## 3. 环境依赖
### 3.1 必需依赖(运行 GUI/配准)
- Python 3.9+(建议与现有环境一致)
- `numpy`
- `opencv-python`cv2
- `rasterio`
- `affine`
- `vismatch`(本仓库内模块)
### 3.2 可选依赖(对应功能可用/不可用)
- `scikit-image`piecewise/polynomial 变换相关
- `matplotlib` + `scipy`:凸包内点判定等
- `SimpleITK` / `pirt` / `scipy.interpolate`TPS 相关
- `tqdm`:掩膜处理进度条(已做无控制台环境兼容)
---
## 4. 输入数据要求
### 4.1 参考底图GeoTIFF
- 文件格式:`.tif` / `.tiff`
- 应包含正确 CRS 与 transform地理参考信息
### 4.2 待配准航带ENVI BIP
- 文件格式:`.bip`
- 建议包含合理的波段与 NoData若有
### 4.3 可选底图掩膜GeoTIFF
启用“底图掩膜”时:
- 掩膜必须是与底图 **严格对齐** 的 GeoTIFF同 CRS、同 transform、同 width/height
- `tif_caijain.py` 默认逻辑:掩膜中值等于 `1` 的像素会被置为 NoData可在后续扩展为 GUI 可配)
---
## 5. GUI 使用方法(推荐)
### 5.1 启动 GUI
在 Windows PowerShell 或 CMD 中运行:
```powershell
cd "e:\code\vismatch-main\vismatch-main\test"
python "StripStitch.py"
```
### 5.2 GUI 字段说明
- 参考TIF文件要配准到的底图
- 启用底图掩膜:勾选后需要选择掩膜 TIF并会先生成“掩膜后的底图”
- BIP文件夹包含待配准 `.bip` 的文件夹(程序会遍历 `*.bip`
- 输出文件夹:所有输出写入此目录
- 匹配算法:选择 matcher例如 `matchanything-roma`
- 设备:`cuda`(更快)或 `cpu`
- 变换方法:可多选,按优先级尝试(可上移/下移顺序)
- 参数设置匹配缩放、ROI 扩展、质量阈值、边缘/纹理过滤等
### 5.3 开始/停止
- 开始处理:开始批量配准
- 停止处理:设置停止标记,当前文件处理结束后停止
---
## 6. 命令行模式CLI
脚本保留了 CLI 模式入口(若实现支持 `--cli`
```powershell
python "StripStitch.py" --cli
```
说明:
- CLI 模式下通常使用脚本顶部的默认配置REF_TIF / BIP_DIR / OUT_DIR 等)。
- 若需要完整参数化 CLI可后续再扩展 argparse。
---
## 7. 输出说明
假设输出目录为 `OUT_DIR`
### 7.1 统计 CSV
每次运行会生成一个新 CSV
- `OUT_DIR\stats\registration_stats_YYYYMMDD_HHMMSS.csv`
内容包含(示例字段):
- 时间戳、文件名、匹配点/内点数、内点比例、所选方法、误差统计、成功与否等
### 7.2 掩膜后的底图(如果启用底图掩膜)
- `OUT_DIR\masked_refs\<底图名>_masked_<时间戳>.tif`
配准时会使用这个掩膜后的底图作为参考。
### 7.3 配准输出
- 脚本会将每个 `.bip` 配准后输出到 `OUT_DIR`(具体命名以代码为准,例如 `_registered.bip`)。
---
## 8. 常见问题与排查
### 8.1 处理过程中发生错误: No module named 'src'
原因:
- `matchanything-*` 依赖第三方源码目录(`vismatch/third_party/MatchAnything/.../src`),打包或运行时没有被正确加入 `sys.path`。
解决:
- 确保 PyInstaller spec 中把 `vismatch/third_party` 打进包里Tree 方式)
- 运行时脚本会尝试从 `_MEIPASS` 和 exe 目录自动寻找 third_party 并加入 `sys.path`
- 如果仍失败,检查 dist 目录下是否存在:
- `dist\StripStitch\_internal\vismatch\third_party\MatchAnything\imcui\third_party\MatchAnything\src\`
### 8.2 No module named 'loguru'
原因:
- MatchAnything 的第三方代码中引用了 `loguru`
解决:
- 打包时将 `loguru` 加入 hiddenimports或安装 loguru
- 脚本也提供了缺失时的兼容 stub以避免直接崩溃
### 8.3 底图掩膜时报错:'NoneType' object has no attribute 'write'
原因:
- PyInstaller `console=False` 时 tqdm 可能没有可写的输出流
解决:
- `tif_caijain.py` 已处理:无控制台环境会自动禁用 tqdm 或使用安全输出,不应再崩溃
- 重新打包并运行最新代码
---
## 9. 打包PyInstaller文件夹模式 onedir
### 9.1 使用 spec 打包
在 `test` 目录执行:
```powershell
pyinstaller --clean -y "e:\code\vismatch-main\vismatch-main\test\StripStitch.spec"
```
输出:
- `dist\StripStitch\StripStitch.exe`(以及同目录依赖文件)
### 9.2 打包注意事项
- 若你改动了 `.spec` 或 `.py`,务必 `--clean` 重新打包
- 深度学习 matcher 依赖多,出现 “No module named xxx” 时通常需要:
- spec 增加 hiddenimports
- 或把第三方源码目录作为 datas 打进去
- 或做运行时兼容 stub仅当该依赖不影响核心逻辑/可替代)
---
## 10. 建议的使用流程(从零到一)
1) 准备底图 `result.tif` 与待配准 `.bip` 文件夹
2) (可选)准备与底图对齐的掩膜 `result_mask.tif`(值=1 为去除区域)
3) 启动 GUI选择底图、BIP 文件夹、输出目录
4) 选择 matcher建议从 `matchanything-roma` 或你已验证可用的 matcher 开始)
5) 选择变换方法(建议先 `affine + homography`
6) 点“开始处理”,观察日志与进度
7) 处理结束后查看:
- 输出 BIP
- `stats\registration_stats_*.csv`
- (可选)`masked_refs\*_masked_*.tif`
---