You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenCV自定义逆映射实现图像正切拉伸误差原因分析

问题说明

对图像执行水平正切映射拉伸时,两种重映射实现方案输出存在边缘差异:

  • 方案1:推导正切映射的解析反函数,直接构建逆映射表完成重映射,输出结果符合预期
  • 方案2:先构建正切正向映射表,再对映射表迭代求逆后重映射,仅边缘区域拉伸程度低于预期,其余区域匹配正确结果
    对应实现代码如下:
import cv2
import glob
import numpy as np
import math

A = -1010
B = -3.931
C = 5.258
D = 978.3

M = -193.8
N = 1740

def get_tan_func_value(x):
    return A * math.tan((((x-N)/M)+B)/C) + D

def get_inverse_tan_func_value(x):
    return M * (C*math.atan((x-D)/A) - B) + N

# answer from linked post
def invert_map(F, shape):
    I = np.zeros_like(F)
    I[:,:,1], I[:,:,0] = np.indices(shape)
    P = np.copy(I)
    for i in range(10):
        P += I - cv2.remap(F, P, None, interpolation=cv2.INTER_LINEAR)
    return P

# import image
images = glob.glob('*.jpg')
img = cv2.imread(images[0])
h,  w = img.shape[:2]

map_x_tan = np.zeros((img.shape[0], img.shape[1]), dtype=np.float32)
map_x_inverse_tan = np.zeros((img.shape[0], img.shape[1]), dtype=np.float32)
map_y = np.zeros((img.shape[0], img.shape[1]), dtype=np.float32)

# x tan function map
for i in range(map_x_tan.shape[0]):
    map_x_tan[i,:] = [get_tan_func_value(x) for x in range(map_x_tan.shape[1])]

# x inverse tan function map
for i in range(map_x_inverse_tan.shape[0]):
    map_x_inverse_tan[i,:] = [get_inverse_tan_func_value(x) for x in range(map_x_inverse_tan.shape[1])]

# default y map
for j in range(map_y.shape[1]):              
    map_y[:,j] = [y for y in range(map_y.shape[0])]

# convert x tan map to 2 channel (x,y) map
(xymap_tan, _) = cv2.convertMaps(map1=map_x_tan, map2=map_y, dstmap1type=cv2.CV_32FC2)

# invert the 2 channel x tan map
xymap_inverted = invert_map(xymap_tan, (h,w))

# remap and write the target image (inverse tan function with normal map)
target = cv2.remap(img, map_x_inverse_tan, map_y, cv2.INTER_LINEAR)
cv2.imwrite("target.jpg", target)

# remap and write the attempted image (normal tan function with inverted map) 
attempt = cv2.remap(img, xymap_inverted, None, cv2.INTER_LINEAR)
cv2.imwrite("attempt.jpg", attempt)
误差原因

这个误差不是迭代精度限制直接导致的,核心诱因有两个:

  • 边界采样干扰:正切函数在图像边缘位置的映射值会超出原始图像x坐标的合法范围[0, w-1],迭代求逆调用cv2.remap时默认采用零填充边界模式,超出范围的采样值会返回0,直接给边缘区域的迭代计算引入错误值
  • 固定迭代次数收敛不足:迭代求逆的收敛速度和映射的局部斜率直接相关,图像中间区域正切映射斜率平缓,10次迭代足够收敛到亚像素精度;但边缘区域正切函数接近渐近线,斜率陡增,固定10次迭代远未达到收敛阈值,直接导致边缘位置映射值偏差,表现为拉伸程度不足。
修正方法
  1. 构建正向映射表时,将超出合法坐标范围的映射值裁剪到[0, w-1]区间内,避免出现越界坐标
  2. 迭代求逆时将cv2.remap的边界模式改为BORDER_REPLICATE,用边缘像素值填充越界采样位置,消除零值干扰
  3. 取消固定迭代次数,改为判断每次迭代的坐标更新量,当最大更新量小于0.001像素(亚像素精度)时停止迭代,保证大斜率的边缘区域也能充分收敛

修正后的求逆函数代码:

def invert_map(F, shape):
    I = np.zeros_like(F)
    I[:,:,1], I[:,:,0] = np.indices(shape, dtype=np.float32)
    P = np.copy(I)
    max_update = 1e9
    while max_update > 1e-3:
        # 采用复制边界模式避免越界采样干扰
        remapped = cv2.remap(F, P, None, interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE)
        update = I - remapped
        max_update = np.abs(update).max()
        P += update
    return P

正向映射表构建时增加裁剪逻辑:

for i in range(map_x_tan.shape[0]):
    raw_map = [get_tan_func_value(x) for x in range(map_x_tan.shape[1])]
    map_x_tan[i,:] = np.clip(raw_map, 0, w-1)

修改后两种方案的输出差异会降到亚像素级别,无肉眼可识别的区别。

内容的提问来源于stack exchange,提问作者monopoly_lover

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 01:33:19