Gemma4 遥感识别与霍城县全县切图操作复盘

创建时间:2026-04-22
记录人:Codex 协作整理
主题:SAM -> 单对象切图 -> Gemma4 批量识别 -> 双卡 Open WebUI/Ollama -> 霍城县全县切图

1. 这次到底做了什么

这轮主要不是单点改一个脚本,而是围绕“遥感目标识别批处理”连续做了几次方案迭代:

  1. 先梳理了原始单对象切图和单图识别脚本。
  2. 尝试过“多图一批返回多个结果”的方案。
  3. 尝试过“多对象拼一张图再一起识别”的方案。
  4. 最后又回到“单对象 chips 保持简单,但一次送 Gemma 多张图”的思路。
  5. 又进一步补了伊犁场景扩展类别版提示词。
  6. 同时搭建了双卡 Ollama / 双 Open WebUI。
  7. 最后为霍城县全县数据新建了专用切图脚本和并行切图脚本。

这篇记录重点总结:

  • 哪些方案试过
  • 遇到了什么具体问题
  • 最终用了哪些脚本
  • 代码和命令应该怎么组织

2. 原始参考脚本

2.1 单对象切图参考脚本

参考脚本:

  • /root/sam21file/cut_feature_chips_512.py

逻辑很直接:

  1. 读取 tif + shp
  2. 对每个要素取外包框中心点
  3. 以中心点为中心裁一张 512 x 512
  4. 把当前要素红色边界画在图上
  5. 左上角写 fid
  6. 保存 png + chips_index.csv

这个逻辑的优点是简单稳定,后面所有批量识别脚本基本都是围绕这套单对象 chips 展开的。

2.2 单图 Gemma 识别脚本

原始参考脚本:

  • /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_single/classify_chips_with_gemma4_v2.py

它的问题也很明确:

  • 一张图发一次请求
  • 总图数接近 2000 时,HTTP 往返太多
  • 整体速度明显偏慢

3. 方案演进与踩坑记录

3.1 第一阶段:尝试“多张图一批”

为了解决单图太慢的问题,新建过一个批量版思路:

  • 一次请求送多张图片
  • 让模型一次返回多个结果

这个思路本身是可行的,而且比逐张请求快很多。

但早期版本出现过两个问题:

  1. chips_index.csv 里的 chip_path 仍然指向旧目录。
  2. 模型虽然返回了 JSON 数组,但有些批次会出现:
    • fid 重复
    • fid 丢失
    • reason 混入垃圾文本,比如 reasons:en

3.2 第二阶段:尝试“多对象一张图”

后来又试了一条更激进的思路:

  • 一张复合图里放多个目标对象
  • 给每个对象编号
  • 让模型一次看一张图,返回多个对象结果

新建过相关脚本与目录,例如:

  • cut_multi_object_chips_20260421.py
  • classify_multi_object_with_gemma4_20260421.py

这个思路理论上更省请求数,但最终没有作为主线继续用下去,原因是:

  1. 多目标图在密集区编号容易互相遮挡。
  2. 对模型来说,目标边界和标签混在一起时更容易串号。
  3. 如果遥感对象很小,多对象拼图反而会让单个目标更不清晰。

后来又试过:

  • 固定网格切块
  • 512 + overlap 64
  • 每块把所有要素 fid 都标出来

结果也不理想:

  • 稀疏区还能看
  • 密集区严重拥挤
  • 并不适合直接送模型

3.3 第三阶段:回到“单对象 chips + 批量送图”

最终回到一个更稳的主线:

  • 切图仍然保持“一对象一图”
  • 但请求改成“一次送 10 张”
  • 程序端严控返回结果质量

这一阶段形成了真正可用的主脚本:

  • /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/classify_chips_with_gemma4_batch10_20260421.py

这是后面所有扩展版的底座。


4. 批量识别脚本里真正踩到的关键问题

4.1 旧 chips_index.csv 路径失效

问题:

  • chips_index.csv 在新目录里
  • 但里面保存的 chip_path 还是旧路径

解决:

  • 在批量识别脚本里增加 resolve_chip_path
  • 自动尝试当前目录下的 chips_512
  • 不要求手工改 CSV

4.2 模型返回 fid 重复或缺失

这是最关键的问题。

表现:

  • 模型返回数组长度看起来对
  • 但某些批次出现重复 fid
  • 后面的 fid 丢失

例如:

  • 期望:181..200
  • 实际:其中某个 fid 出现两次
  • 另一个 fid 完全没了

这个问题如果不拦住,最危险的后果是:

  • 程序把结果按顺序错位写进 CSV
  • 表面看起来“有结果”
  • 其实 fid 和类别已经对不上了

解决:

  • 程序端严格校验:
    • fid 必须全部属于当前批次
    • fid 不能重复
    • fid 不能缺失
  • 只要不满足,整批判错

4.3 过强的 JSON Schema 直接把 Ollama 打成 500

后面为了进一步约束模型返回,又尝试过一个“更强”的 schema:

  • 不是只要求 fid 是整数
  • 而是要求数组中每一项的 fid 必须严格等于当前批次指定值

结果:

  • Ollama 直接返回 HTTP 500
  • 而且即使拆到单张仍然 500

结论:

  • 这个约束思路虽然理论上更严
  • 但当前 Ollama / 模型组合下不稳定
  • 最终退回更简单的 schema
  • 通过 prompt 模板 + 程序端校验来兜底

4.4 reason 污染

问题表现:

  • 返回 reasons:
  • 返回 en
  • 返回一些英文片段、符号片段

解决:

  • reason 做清洗,只保留中文
  • 其余一律截断或落成 待复核

4.5 批次失败时整批变成 -1

原始策略是:

  • 一批失败
  • 这一批全记成 -1

问题:

  • 如果批量是 20
  • 一次失败就会有 20-1

改进:

  • 批次失败后自动拆分
  • 20 -> 10 -> 5 -> 1
  • 直到单张

这样不会再因为一个坏批次拖垮整批结果。

4.6 错误结果不应该被当成“已完成”

这个也是一个实战里很容易忽略的问题。

如果 -1 结果也被当作已完成,下次重跑时会被跳过。

后来改成:

  • 只有 class_id != -1 的结果才算完成
  • -1 会在 rerun 时继续进入待处理列表

5. 最终形成的核心脚本

5.1 通用单对象 batch10 识别脚本

路径:

  • /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/classify_chips_with_gemma4_batch10_20260421.py

主要职责:

  • 读取 chips_index.csv
  • 自动修复 chip_path
  • 每批送 10 张图到 Ollama
  • 校验返回数组中的 fid
  • 失败自动拆批重试
  • 写 CSV
  • 回写新的 shp

5.2 伊犁扩展类别包装脚本

路径:

  • /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/classify_chips_with_gemma4_batch10_yili8cls_20260421.py

这是在稳定底座脚本上做的“场景版包装”。

扩展类别为:

CLASS_NAME_MAP = {
    -1: "ERROR",
    0: "无法判断",
    1: "建筑",
    2: "耕地",
    3: "道路",
    4: "水体",
    5: "林地/防护林",
    6: "草地",
    7: "裸地",
    8: "果园/园地",
}

改动重点:

  • 类别集合扩展为更适合新疆伊犁农区
  • 提示词重写,更强调类别边界
  • 仍然保留自由 reason
  • 底层批量逻辑不重写,只复用稳定版

5.3 结果合并脚本

路径:

  • /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/merge_gemma_batch_results_20260421.py

用途:

  • 双卡并行后,两路结果写进两个不同 CSV
  • 最后用这个脚本合并
  • 同时回写最终 shp

6. Open WebUI / Ollama 双卡问题复盘

为了让两张显卡都利用起来,搭建过双 ollama serve + 双 Open WebUI

6.1 一开始的问题

看起来都启动了,但实际配置是错的:

  • 两个 Open WebUI 都指向同一个 Ollama 端口
  • 两个 ollama serve 实际都绑在同一张卡上

也就是说:

  • 页面看起来有两个
  • 实际还是同一个后端

6.2 正确的双卡结构

目标结构应该是:

  • GPU0 -> ollama serve on 11434
  • GPU1 -> ollama serve on 11435
  • WebUI 3000 -> 11434
  • WebUI 3001 -> 11435

注意一个关键点:

如果 Docker 容器中的 Open WebUI 要访问宿主机 Ollama,
OLLAMA_HOST 不能只绑 127.0.0.1,最好绑 0.0.0.0

6.3 重要结论

一个 Python 识别脚本只会访问一个 --ollama-url

所以“同时用两张卡”的正确方式不是:

  • 一个脚本自动吃两张卡

而是:

  • 开两个识别进程
  • 每个进程连不同的 Ollama 端口
  • 各自写自己的 CSV
  • 最后合并

7. 双卡并行识别的实际组织方式

以伊犁扩展类别版为例,双卡并行建议拆成前后两半。

7.1 第一张卡跑前半段

python /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/classify_chips_with_gemma4_batch10_yili8cls_20260421.py \
  --chips-csv /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/chips_512/chips_index.csv \
  --src-shp /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/shp_result/sam21_remote_sensing_v2style_union.shp \
  --ollama-url http://127.0.0.1:11434 \
  --model gemma4:31b \
  --batch-size 10 \
  --limit 984 \
  --results-csv /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/gemma4_chip_results_yili8cls_gpu0_part1.csv \
  --log-jsonl /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/gemma4_chip_results_yili8cls_gpu0_part1.jsonl \
  --out-shp /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/shp_result/tmp_gpu0_part1.shp \
  --overwrite

7.2 第二张卡跑后半段

python /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/classify_chips_with_gemma4_batch10_yili8cls_20260421.py \
  --chips-csv /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/chips_512/chips_index.csv \
  --src-shp /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/shp_result/sam21_remote_sensing_v2style_union.shp \
  --ollama-url http://127.0.0.1:11435 \
  --model gemma4:31b \
  --batch-size 10 \
  --start-fid 985 \
  --results-csv /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/gemma4_chip_results_yili8cls_gpu1_part2.csv \
  --log-jsonl /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/gemma4_chip_results_yili8cls_gpu1_part2.jsonl \
  --out-shp /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/shp_result/tmp_gpu1_part2.shp \
  --overwrite

7.3 两路结果合并

python /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/merge_gemma_batch_results_20260421.py \
  --input-csv /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/gemma4_chip_results_yili8cls_gpu0_part1.csv \
  --input-csv /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/gemma4_chip_results_yili8cls_gpu1_part2.csv \
  --src-shp /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/shp_result/sam21_remote_sensing_v2style_union.shp \
  --out-csv /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/gemma4_chip_results_batch10_yili8cls_20260421_merged.csv \
  --out-shp /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/shp_result/sam21_remote_sensing_v2style_union_gemma4_batch10_yili8cls_20260421_merged.shp

这个流程才是真正的“双卡都用上”。


8. 霍城县全县切图:为什么又改回单对象切图

后来开始切霍城县全县数据。

输入:

  • /root/sam21file/sam21_huocheng_v2style_union_out/huocheng_opt.tif
  • /root/sam21file/sam21_huocheng_v2style_union_out/sam_result/sam21_huocheng_v2style_union_1.shp

其中:

  • shp 要素数:192965
  • 栅格尺寸:77164 x 87634

一开始延续参考脚本思路,写了霍城县专用单线程版:

  • /root/sam21file/sam21_huocheng_v2style_union_out/gemma4result/cut_feature_chips_512_huocheng_20260421.py

这个脚本的切法很简单:

  1. 对每个要素取 bbox
  2. bbox 中心点
  3. 以中心点为中心裁一张 512 x 512
  4. 画红框
  5. 左上角写 fid
  6. 保存图片并写 chips_index.csv

优点:

  • 与前面伊犁数据主线完全一致
  • 后面给 Gemma 很方便
  • 一对象一图,最容易和 fid 对齐

问题:

  • 全县 19 万多个要素,顺序切太慢

9. 霍城县并行切图脚本

为了解决霍城县切图太慢的问题,又新建了并行版:

  • /root/sam21file/sam21_huocheng_v2style_union_out/gemma4result/cut_feature_chips_512_huocheng_parallel_20260421.py

9.1 并行逻辑

这版不是让多个进程一起写同一个 CSV,而是:

  1. 主进程顺序读取 shp
  2. 把每个要素转成一个切图任务
  3. 多个 worker 进程各自打开大图 tif
  4. worker 并行裁图并保存 PNG
  5. 主进程统一把结果写进 chips_index.csv

这样做的好处:

  • 不容易把 csv 写坏
  • 仍然保留断点续跑能力
  • 对 19 万级数据更稳

9.2 为什么不能简单把 worker 开到 64 或 128

这类任务虽然吃 CPU,但更容易先被下面两个瓶颈限制:

  • 大图随机读取
  • PNG 写盘

所以不是 worker 越多越快。

机器硬件信息:

  • CPU:Intel Xeon Gold 6530
  • 物理核心:64
  • 线程:128
  • 内存:125 GiB

但实际建议不要一上来就开很大,而是试:

  • workers 8
  • workers 12
  • workers 16

通常比较稳的建议是:

--workers 12 --max-inflight 48

如果磁盘也很强,再试:

--workers 16 --max-inflight 64

9.3 并行切图示例命令

source /root/miniconda3/etc/profile.d/conda.sh
conda activate sam21

python /root/sam21file/sam21_huocheng_v2style_union_out/gemma4result/cut_feature_chips_512_huocheng_parallel_20260421.py \
  --output-dir /root/sam21file/sam21_huocheng_v2style_union_out/gemma4result/chips_512_parallel \
  --workers 12 \
  --max-inflight 48 \
  --progress-every 500 \
  --overwrite

如果只想先测速:

python /root/sam21file/sam21_huocheng_v2style_union_out/gemma4result/cut_feature_chips_512_huocheng_parallel_20260421.py \
  --output-dir /root/sam21file/sam21_huocheng_v2style_union_out/gemma4result/chips_512_parallel_test \
  --workers 12 \
  --max-inflight 48 \
  --limit 5000 \
  --overwrite

10. 这轮代码清单

10.1 识别相关

  • /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/classify_chips_with_gemma4_batch10_20260421.py
  • /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/classify_chips_with_gemma4_batch10_yili8cls_20260421.py
  • /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/merge_gemma_batch_results_20260421.py
  • /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_multi_object/start_inference_gemma

10.2 霍城县切图相关

  • /root/sam21file/sam21_huocheng_v2style_union_out/gemma4result/cut_feature_chips_512_huocheng_20260421.py
  • /root/sam21file/sam21_huocheng_v2style_union_out/gemma4result/cut_feature_chips_512_huocheng_parallel_20260421.py

10.3 参考代码

  • /root/sam21file/cut_feature_chips_512.py
  • /root/sam21file/sam21_remote_sensing_v2style_union_out/gemma4_single/classify_chips_with_gemma4_v2.py

11. 最后总结

这轮最重要的经验,不是“把代码写出来”,而是把工程边界摸清楚了:

  1. 单对象 chips 是主线
    对模型最稳,最容易做 fid 对齐。

  2. 多对象一张图虽然省请求,但容易串号
    在密集目标遥感场景里,编号和对象边界容易互相干扰。

  3. 批量送图是有效提速点,但程序端必须强校验返回
    不能只相信模型返回 JSON 看起来像对的。

  4. Ollama 的结构化输出不能一味加码 schema
    约束太强,可能直接把服务打成 500

  5. 双卡并行的正确姿势是双进程 + 双端口 + 双结果文件 + 最后合并
    不是一个进程自动“吃满两张卡”。

  6. 全县切图必须考虑并行和断点续跑
    19 万级要素如果没有并行和断点能力,基本不可用。

  7. CPU 强不代表 worker 可以无限加
    大图随机读取和写盘会先成为瓶颈。

这轮真正落地后的体系可以概括成一句话:

用最简单、最稳定的对象级 chips 组织识别任务;
用批量请求、双卡分流和结果合并提升吞吐;
用程序端严格校验和断点续跑保证工程可控。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐