Skip to content

整体配准一:feature-based(reg、ecc)

来自本人笔记: https://github.com/masterAllen/LearnOpenCV/blob/main/docs/整体配准一:feature-based(reg、ecc)

整体配准就是指:求出前后两张图片变换矩阵,然后去做一个矩阵变换就行。而整体配准又可以分为两个部分:

  • pixel-based: 按照像素来比较两张图片,计算用什么矩阵变换后,让这两张图片各个位置像素差距最小。
  • feature-based: 找两张图片上一些对应点,即找出一些点在前后两张图片的各自位置。然后用它们反算出矩阵。

推荐看这篇文章,写的很好: https://helios2.mi.parisdescartes.fr/~lomn/Cours/CV/SeqVideo/registration_tutorial.pdf


从第一段可以看出,pixel-based 尽量让两张图片各个位置像素差距最小,所以关键在差距是指什么差距、如何最小。OpenCV 有两种:

1. 专门的 reg 模块,它用的差距是 gradient;计算两张图片的 graident 和 different 后,可以直接根据公式一下子算出矩阵,具体可查阅源码的 mappergrad 开头的 cpp 文件中的 calculate 方法(如 mappergradaffine 或 mappergradeuclid)。
2. main module 的 ecc 方法。它用的差距是专门叫 ECC Criteria;这个方法不是直接算出矩阵,而是迭代。给出一个初始矩阵,然后计算矩阵变换后两张图片之间的 ECC 差异指标。根据这个差异对矩阵进行修改,直到最后的差异满足要求,就退出这样的迭代。

reg 模块(梯度)

Opencv 专门的 reg 模块,这个模块下都是用梯度来求矩阵的。

使用逻辑是:

1. 创建 Mapper 具体类,类名为 MapperGradXXX(XXX=[Shift, Eculid, Similar, Affine, Proj])
2. 使用 MapperPyramid 类包装这个 mapper(可选)
3. 传入图片,调用 calculate 计算得到一个 Map 基类
4. 用 MapperTypeCaster 将 Map 基类转为具体的 Map 类(要根据第一步中的 mapper 用对应方法,对应关系看下面代码)
5. 使用 Map 类的 inverseWarp 或 warp 转换图片(Map 类还有 compose 等方法,这个自行查文档)

其中第二步的用 MapperPyramid 类是可选方法,从名字也看出这是相当于加了多层金字塔下的处理。按照 OpenCV 文档的话来说:如果差异很小那就不需要,但如果差异大最好要用。

看这下面的代码就够了,真的很简单:

# reg 模块
mappers = [
    ('Shift',   cv2.reg.MapperGradShift(),   cv2.reg.MapTypeCaster.toShift),
    ('Euclid',  cv2.reg.MapperGradEuclid(),  cv2.reg.MapTypeCaster.toAffine),
    ('Similar', cv2.reg.MapperGradSimilar(), cv2.reg.MapTypeCaster.toAffine),
    ('Affine',  cv2.reg.MapperGradAffine(),  cv2.reg.MapTypeCaster.toAffine),
    ('Projec',  cv2.reg.MapperGradProj(),    cv2.reg.MapTypeCaster.toProjec)
]

# 直接使用
for (mapper_name, mapper, mapper_cast) in mappers:
    now_img = np.copy(imgs).astype(float)
    for i in range(1, 3):
        # ! 重要:必须传入 float 类型!!
        map_ptr = mapper.calculate(imgs[0].astype(float), imgs[i].astype(float))
        real_map = mapper_cast(map_ptr)
        now_img[..., i] = real_map.inverseWarp(imgs[i].astype(float))

    now_img = now_img.clip(0, 255).astype(np.uint8)
    results.append((mapper_name, now_img))

# 配合 Pyramid 使用
for (mapper_name, mapper, mapper_cast) in mappers:
    now_img = np.copy(imgs).astype(float)
    # 其实就加了这一句话
    mapper_pyr = cv2.reg.MapperPyramid(mapper)
    for i in range(1, 3):
        map_ptr = mapper_pyr.calculate(imgs[0].astype(float), imgs[i].astype(float))
        real_map = mapper_cast(map_ptr)
        # 可以打印矩阵,当然如果是 Affine,需要用 MapAffine 具体方法
        # if mapper_name == 'Projec':
        #     print(real_map.getProjTr())
        now_img[..., i] = real_map.inverseWarp(imgs[i])
    now_img = now_img.clip(0, 255).astype(np.uint8)
    results.append((f'{mapper_name} + Pyramid', now_img))

show_images(results, colnum=3, scale=10)

ECC(迭代求差异)

Main Module 中的 Image Processing 的 Geometric Image Transformations。

如开头所述,指标是自定义的叫做 ECC 差异(Enhanced Correlation Coefficient value)。用的话主要是 findTransformECC 这个函数来直接进行上面的全部迭代操作,最后得出一个矩阵;还有一个 computeECC 是计算两张图片的 ECC 差异值,比较少用。

# 默认是 50, 0.001;实测这种指标不太够
iter_ = 800
eps_ = 1e-5
criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, iter_, eps_)

def getECCTransform(img1, img2):
    t1 = time.time()

    warp_matrix = np.eye(3, 3, dtype=np.float32)
    try:
        # 可以选择透视和仿射等不同矩阵,这里就直接用透视矩阵了
        result, warp_matrix = cv2.findTransformECC(img1, img2, warp_matrix, cv2.MOTION_HOMOGRAPHY, criteria, None, 1)
    except Exception as e:
        print('Not Threshold')
        pass

    t2 = time.time()
    print(f'消耗时间: {t2-t1}s')
    return warp_matrix

img1 = cv2.imread('../img/img-4-1.png', -1)
img2 = cv2.imread('../img/img-4-2.png', -1)
ecc_matrix = getECCTransform(img1, img2, radius=800)
newimg = cv2.warpPerspective(img2, warp_matrix, (img2.shape[1], img2.shape[0]), flags=cv2.INTER_LINEAR + cv2.WARP_INVERSE_MAP)

Comments