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次迭代远未达到收敛阈值,直接导致边缘位置映射值偏差,表现为拉伸程度不足。
修正方法
- 构建正向映射表时,将超出合法坐标范围的映射值裁剪到[0, w-1]区间内,避免出现越界坐标
- 迭代求逆时将
cv2.remap的边界模式改为BORDER_REPLICATE,用边缘像素值填充越界采样位置,消除零值干扰 - 取消固定迭代次数,改为判断每次迭代的坐标更新量,当最大更新量小于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
相关产品推荐
相关产品推荐

