图像对差异区域(ImgDiff)
更新时间:2026-09-11
简介
给一对相似图和各自的 caption,找出「两图语义不同」的区域框,用于构造 image-diff 训练数据。整条链路分七步(整对粗筛 → 差异名词抽取 → 分割 → 裁剪 → ITM 判别 → 子图对复筛 → 去重叠),三个模型(CLIP / BLIP-ITM / FastSAM)直接在算子内加载,裁剪子图全程留在内存、不落盘。
功能描述
处理流程(每行独立执行):
- Step1 整对粗筛:CLIP 图图余弦相似度不落在
[min_score_1, max_score_1]内(差异过大或过小)的图对直接丢弃 - Step2 差异名词:
difflib.Differ比较两个 caption(小写、去标点、按空格拆行),取各自独有的词做 WordNet 词形还原,再用 nltk POS 只保留名词(NN/NNS/NNP/NNPS)作为 "valid object";任一侧抽不到名词就返回空列表 - Step3 分割:把 image_b 双线性缩放到 image_a 尺寸,两张图分别过 FastSAM,候选框是两张图分割结果的并集,坐标统一在 image_a 坐标系
- Step4 裁剪:每个候选框在两张图上各裁一块子图,框按中心点 xywh 语义还原成
(left, upper, right, lower)并裁到图像边界内,宽或高不足 1 像素的框跳过 - Step5 ITM 判别:BLIP-ITM 判断子图是否含有对应的 valid object,任一 object 的匹配概率落在
[itm_min_score, itm_max_score]内即算命中;两侧同时命中的框才保留 - Step6 子图对复筛:对保留下来的子图对再算一次 CLIP 相似度,不落在
[min_score_2, max_score_2]内的框剔除 - Step7 去重叠:
iou_filter贪心去重(面积按+1计算),阈值iou_thresh
其他行为:
- 输出是
list<list<f64>>,每个框是 中心点 xywh 四元组,坐标系为 image_a;任何一步筛空、任一输入为None/空 caption、或该行抛异常,都输出空列表并记日志,不中断整列 - 七步流程在单进程内串起三个模型,裁剪子图全程内存内完成、不落盘;无框时输出空列表,不用零框占位
- CLIP / BLIP / FastSAM 权重与 nltk 数据都只从本地加载,缺任何一个在构造期就抛
FileNotFoundError,不联网下载、不做运行时 pip 安装 - 设备为
cuda:(rank % 可见卡数)(use_gpu 且 CUDA 可用)否则 cpu,FastSAM 用同一张卡的编号;CPU 上指定float16会回退 float32 - 构造期校验
image_src_type以及三组阈值的下界不超过上界 - 四列长度不一致时抛
ValueError
算子参数
输入
| 输入 | 含义 |
|---|---|
| image_a | 第一张图,内容形式由 image_src_type 决定(URL/本地或 BOS 路径 / Base64 / 二进制) |
| image_b | 第二张图,编码形式与 image_a 相同;会被缩放到 image_a 尺寸 |
| caption_a | image_a 的描述文本 |
| caption_b | image_b 的描述文本 |
输出
| 输出 | 含义 |
|---|---|
| diff_bbox | list<list |
参数
| 参数名称 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| image_src_type | str | "image_url" | 两列图像的编码形式(同一种):image_url / image_base64 / image_binary |
| model_path | str | "/opt/aihc/model" | 权重根目录(注意是 model 而非 models) |
| clip_model_name | str | "openai/clip-vit-base-patch32" | CLIP 权重子目录 |
| blip_model_name | str | "Salesforce/blip-itm-base-coco" | BLIP-ITM 权重子目录 |
| fastsam_model_name | str | "FastSAM-x.pt" | FastSAM 权重文件名(相对 model_path) |
| nltk_data_path | str | "/opt/aihc/model/nltk_data" | 离线预置的 nltk 数据目录,需含 taggers 与 corpora/wordnet |
| dtype | str | "float32" | BLIP / CLIP 权重精度:float16 / float32 / bfloat16 |
| min_score_1 | float | 0.1 | 整对图相似度下界(Step1) |
| max_score_1 | float | 1.0 | 整对图相似度上界(Step1) |
| min_score_2 | float | 0.1 | 子图对相似度下界(Step6) |
| max_score_2 | float | 1.0 | 子图对相似度上界(Step6) |
| itm_min_score | float | 0.1 | 子图与 valid object 的 ITM 分数下界(Step5) |
| itm_max_score | float | 1.0 | 子图与 valid object 的 ITM 分数上界(Step5) |
| imgsz | int | 1024 | FastSAM 推理分辨率 |
| conf | float | 0.05 | FastSAM 置信度阈值 |
| iou | float | 0.5 | FastSAM NMS IoU 阈值 |
| iou_thresh | float | 0.5 | 最终框去重叠的 IoU 阈值 |
| rank | int | 0 | 多卡场景 worker 序号 |
注意事项
- 输出框是中心点 xywh,不是左上角 + 宽高;直接当
xyxy或ltwh用会画错位置。 - nltk 数据不会自动联网下载(算子内不做网络请求与安装),
nltk_data_path指向的目录缺taggers或corpora/wordnet会在构造期或 POS 阶段失败。 - Step2 的 POS 与词形还原是英文语料,中文 caption 抽不到名词,整行直接返回空列表。
- 两个 caption 必须各有对方没有的名词,caption 完全相同或差异只在虚词上时结果恒为空。
- 三个模型在同一进程同一张卡上常驻,显存占用是三者之和;
imgsz越大 FastSAM 越吃显存(测试用 640)。
调用示例
Python
1from __future__ import annotations
2
3import os
4
5import daft
6from daft import col
7
8from daft.aihc.common.udf import aihc_udf
9from daft.aihc.functions.multimodal.imgdiff_difference_area import ImgdiffDifferenceArea
10
11MODEL_PATH = "/path/to/models"
12
13if __name__ == "__main__":
14 if os.getenv("DAFT_RUNNER", "native") == "ray":
15 import ray
16 ray.init(ignore_reinit_error=True)
17 daft.set_runner_ray()
18 daft.set_execution_config(actor_udf_ready_timeout=6000, min_cpu_per_task=0)
19
20ds = daft.from_pydict(
21 {
22 "image_a": ["/tmp/aihc_imgdiff/pair_a.jpg"],
23 "image_b": ["/tmp/aihc_imgdiff/pair_b.jpg"],
24 "caption_a": ["a photo of a room with a window"],
25 "caption_b": ["a photo of a room with a box"],
26 }
27 )
28 ds = ds.with_column(
29 "diff_bbox",
30 aihc_udf(
31 ImgdiffDifferenceArea,
32 construct_args={
33 "image_src_type": "image_url",
34 "model_path": MODEL_PATH,
35 "nltk_data_path": os.path.join(MODEL_PATH, "nltk_data"),
36 "imgsz": 640,
37 },
38 num_cpus=1,
39 num_gpus=1,
40 concurrency=1,
41 batch_size=1,
42 )(col("image_a"), col("image_b"), col("caption_a"), col("caption_b")),
43 )
44 ds.show()
评价此篇文章
