如何通过OpenCV实现畸变还原(从Reality Capture生成的无畸变图像恢复原始带畸变图像)
如何通过OpenCV实现畸变还原(从Reality Capture生成的无畸变图像恢复原始带畸变图像)
嘿,我之前刚好折腾过类似的需求,知道OpenCV没有直接的“逆向畸变”函数,不过咱们可以自己实现反向映射来搞定!核心思路就是:正向去畸变是把带畸变点映射到无畸变点,逆向就是反过来——给每个无畸变图像的像素,找到它在原始带畸变图像里对应的位置,然后用remap来采样还原。
下面一步步来,结合你提到的Reality Capture参数和畸变模型:
第一步:先把Reality Capture的35mm格式参数转成OpenCV能用的像素坐标系参数
Reality Capture给的是35mm胶片格式的参数,咱们得先转成像素级的相机内参,不然没法和图像对应上:
- 像素焦距计算:35mm全幅传感器的尺寸是36mm(宽)×24mm(高),假设你的原始图像分辨率是
W×H像素,那么:fx = (f_35mm / 36.0) * W(f_35mm是Reality Capture给的焦距值)fy = (f_35mm / 24.0) * H
- 光心坐标转换:Reality Capture的
dcx/dcy是35mm格式下相对于全幅光学中心(18mm,12mm,也就是36/2和24/2的位置)的偏移,所以先算出35mm下的实际光心,再转成像素:- 35mm光心:
cx_35 = 18 + dcx,cy_35 = 12 + dcy - 像素光心:
cx = (cx_35 / 36.0) * W,cy = (cy_35 / 24.0) * H
- 35mm光心:
- 畸变系数对应:Reality Capture给的
k1,k2,p1,p2,k3,k4...直接对应OpenCV的畸变系数数组,顺序就是[k1, k2, p1, p2, k3, k4, ...](和OpenCV的distCoeffs顺序完全一致)
第二步:针对不同畸变模型实现逆向映射表
OpenCV的initUndistortRectifyMap是生成正向去畸变的映射,逆向的话咱们得自己遍历每个无畸变像素,计算它对应的原始带畸变像素位置,生成映射表。
情况1:Division模型(单径向参数k1)
这个模型有解析解,不用迭代,直接解方程就行:
- 对无畸变图像的每个像素
(u_dst, v_dst),先转成归一化无畸变坐标:x_dst = (u_dst - cx) / fx y_dst = (v_dst - cy) / fy r_dst_sq = x_dst**2 + y_dst**2 - 解逆向方程:已知正向是
x_dst = x_src/(1 + k1*r_src_sq),y_dst = y_src/(1 + k1*r_src_sq),代入后整理成关于t = r_src_sq的二次方程:k1²*r_dst_sq*t² + (2*k1*r_dst_sq - 1)*t + r_dst_sq = 0 - 解这个二次方程,取正实根
t(因为r²肯定是非负的),然后计算带畸变的归一化坐标:x_src = x_dst * (1 + k1 * t) y_src = y_dst * (1 + k1 * t) - 再转成像素坐标
(u_src, v_src) = (x_src*fx + cx, y_src*fy + cy),把这个坐标存到映射表里。
情况2:Brown3/4模型(3/4个径向参数)
这个模型的逆向没有解析解,咱们用牛顿迭代法来近似求解,收敛速度很快,几次迭代就够了:
- 同样先把无畸变像素转成归一化坐标
(x_dst, y_dst) - 初始化迭代的初始值:用无畸变坐标当第一次猜测,
x_src = x_dst,y_src = y_dst - 迭代更新(一般3-5次就收敛到足够精度):
for _ in range(5): r_sq = x_src**2 + y_src**2 # 计算径向畸变因子,Brown3用k1,k2,k3,Brown4加k4*r_sq**2 radial = 1 + k1*r_sq + k2*r_sq**2 + k3*r_sq**3 # 计算切向畸变部分 x_dist = x_src*radial + 2*p1*x_src*y_src + p2*(r_sq + 2*x_src**2) y_dist = y_src*radial + p1*(r_sq + 2*y_src**2) + 2*p2*x_src*y_src # 计算误差 dx = x_dist - x_dst dy = y_dist - y_dst # 计算简化版雅可比逆(针对小畸变足够用,不用精确计算) inv_jacobian = 1.0 / (radial**2 + 2*(k1 + 2*k2*r_sq + 3*k3*r_sq**2)*r_sq) # 更新x_src和y_src x_src -= dx * inv_jacobian y_src -= dy * inv_jacobian - 迭代结束后把
x_src,y_src转成像素坐标,存到映射表。
第三步:用OpenCV的remap函数生成原始带畸变图像
把咱们生成的逆向映射表(map1和map2,格式要符合OpenCV要求,比如用CV_32FC2类型)传入remap函数,选择和正向去畸变时一致的插值方式(比如INTER_CUBIC),就能得到还原的原始带畸变图像了:
import cv2 import numpy as np # 假设已经生成了逆向映射表map_x, map_y(每个元素是对应原始图像的x,y坐标) undistorted_img = cv2.imread("undistorted_image.jpg") original_img = cv2.remap(undistorted_img, map_x, map_y, interpolation=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE) cv2.imwrite("restored_distorted_image.jpg", original_img)
一些要注意的坑
- 35mm转像素参数时,一定要用原始图像的分辨率,不能用无畸变图像的(不过你说正向还原的无畸变图和原始误差小,应该尺寸是一样的)
- 畸变系数的顺序千万不能搞混,一定要对应OpenCV的
[k1,k2,p1,p2,k3,k4,...]顺序 - 迭代法的边界处理:如果计算出来的原始像素坐标超出图像范围,可以用
BORDER_REPLICATE或者BORDER_CONSTANT来填充,避免黑边 - 正向去畸变时你已经验证过参数正确,所以逆向的参数转换只要和正向一致,结果就不会差
备注:内容来源于stack exchange,提问作者derekboase
相关产品推荐
相关产品推荐

