python opencv最小化重投影误差求解PnP(高斯牛顿法)

15 篇文章 2 订阅
12 篇文章 2 订阅

使用jupyter (参考视觉SLAM十四讲第二版第七章源码)

 1.ORB特征点提取和配准 得到3D坐标-2D坐标

import cv2 as cv
import numpy as np
from  matplotlib import pyplot as plt

# 读取图片 以及深度
im1 = cv.imread('./data/1.png')
im2 = cv.imread('./data/2.png')
im1_d = cv.imread('./data/1_depth.png',-1)

# ORB特征提取
orb = cv.ORB_create()
kp1 = orb.detect(im1)  
kp2 = orb.detect(im2)
kp1, des1 = orb.compute(im1, kp1) # 求特征
kp2, des2 = orb.compute(im2, kp2) # 求特征
bf = cv.DescriptorMatcher_create('BruteForce-Hamming')
matches = bf.match(des1, des2)    # 配准
# 进行初步筛选
min_distance = 10000
max_distance = 0
for x in matches:
    if x.distance < min_distance: min_distance = x.distance
    if x.distance > max_distance: max_distance = x.distance
print('最小距离:%f' % min_distance)
print('最大距离:%f' % max_distance)
good_match = []
for x in matches:
    if x.distance <= max(2 * min_distance, 30):
        good_match.append(x)
print('匹配数:%d' % len(good_match))

# 提取配准点
points1 = []
points2 = []
for i in good_match:
    points1.append(list(kp1[i.queryIdx].pt));
    points2.append(list(kp2[i.trainIdx].pt));
points1 = np.array(points1).astype(int)
points2 = np.array(points2).astype(int)
#print(im1_d.shape)

## 提取深度 再次筛选
K = np.array([520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1]).reshape([3,-1]) # 相机参数
point1_3D = []
point2_2D = []
for i in range(points1.shape[0]):
    p = points1[i]
    d = im1_d[p[1],p[0]]/5000.0 # 深度距离  以5000为一个单位
    if d==0 :continue          # 无深度信息 跳过
    p = (p - (K[0][2],K[1][2]))/(K[0][0],K[1][1])*d  # 归一化坐标 根据深度单位转为实际坐标
    point1_3D.append([p[0],p[1],d])
    point2_2D.append(points2[i])
    #print([p[0],p[1],d])
    #print(d)
print('最终匹配数:%d' % len(point1_3D))
point1_3D = np.array(point1_3D).astype(np.float64)
point2_2D = np.array(point2_2D).astype(np.float64)
print(point1_3D[0:5])
#print(point2_2D.shape)
# 最小化重投影误差求解PnP 使用高斯牛顿法迭代求解 
pose = np.array([0,0,0,0,0,0], dtype=np.float64)    # 初始位姿 
K = np.array([520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1]).reshape([3,-1]) # 相机参数
last_cost = 0.0   # 损失
fx = K[0,0]
fy = K[1,1]
cx = K[0,2]
cy = K[1,2]
for iter in range(10):
    cost = 0
    H = 0
    b = 0
    for i in range(point1_3D.shape[0]):
        R,j = cv.Rodrigues(pose[3:6])   # 旋转矩阵
        t = pose[0:3]                   # 平移矩阵
        p_3D = np.dot(R,point1_3D[i])+t # 3D坐标点
        X = p_3D[0]                     #
        Y = p_3D[1]                     #
        inv_z = 1.0/p_3D[2]             # 深度距离
        inv_z2 = inv_z*inv_z            # 深度距离的平方
        p_2D = np.dot(K,p_3D)[0:2]*inv_z# 重投影   一开始忘记归一化深度了
        e = point2_2D[i] - p_2D         # 误差向量 一开始写反了  cost一直增大
        cost += np.linalg.norm(e)       # 向量的模 累加
        J = [[-fx * inv_z,           0, fx * X * inv_z2,      fx * X * Y * inv_z2, -fx - fx * X * X * inv_z2,  fx * Y * inv_z],
             [          0, -fy * inv_z, fy * Y * inv_z2, fy + fy * Y * Y * inv_z2,      -fy * X * Y * inv_z2, -fy * X * inv_z]]
        J = np.array(J)          # 以上建立雅克比矩阵
        H += np.dot(J.T,J)       # 得到H
        b += np.dot(-J.T,e)      # 得到b         

    if iter>0 and cost>=last_cost:# 误差变大则退出
        print('cost = ', cost,' last_cost = ',last_cost)
        break
    last_cost = cost
    if np.linalg.det(H) == 0:     # 奇异矩阵 
        print('Singular matrix')
        break
    x = np.linalg.solve(H,b)     # 求解方程组 得到增量
    pose += x                    # 更新位姿
    #print(np.linalg.norm(x))
    if np.linalg.norm(x) < 1e-6: # 增量很小 
        print(x)
        break
    print('%d: %f'%(iter,cost))

R,j = cv.Rodrigues(pose[3:6])
print('R = ', R)
print('t = ', pose[0:3])

  • 3
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
要可视化投影误差,可以按照以下步骤进行操作: 1. 读取标定板图像和相机参数 ``` python import cv2 import numpy as np # 读取标定板图像 img = cv2.imread('calibration.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 读取相机参数 ret = np.load('ret.npy') mtx = np.load('mtx.npy') dist = np.load('dist.npy') rvecs = np.load('rvecs.npy') tvecs = np.load('tvecs.npy') ``` 2. 定义标定板的世界坐标和图像坐标 ``` python # 定义标定板的世界坐标和图像坐标 objp = np.zeros((6*9, 3), np.float32) objp[:,:2] = np.mgrid[0:9,0:6].T.reshape(-1,2) # 存储图像的标定板角点 imgpoints = [] # 存储标定板的世界坐标系的三维点 objpoints = [objp] # 寻找标定板角点 ret, corners = cv2.findChessboardCorners(gray, (9,6), None) if ret == True: imgpoints.append(corners) # 计算旋转和平移向量 retval, rvecs, tvecs = cv2.solvePnP(objpoints, imgpoints, mtx, dist) ``` 3. 计算投影误差 ``` python # 计算投影误差 mean_error = 0 for i in range(len(objpoints)): imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist) error = cv2.norm(imgpoints[i],imgpoints2, cv2.NORM_L2)/len(imgpoints2) mean_error += error print("total error: ", mean_error/len(objpoints)) ``` 4. 可视化投影误差 ``` python # 可视化投影误差 h, w = img.shape[:2] newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h)) # 根据新的相机矩阵将图像去畸变 dst = cv2.undistort(img, mtx, dist, None, newcameramtx) # 计算投影点 imgpoints2, _ = cv2.projectPoints(objpoints[0], rvecs[0], tvecs[0], mtx, dist) # 绘制标定板角点和投影点 for i in range(len(imgpoints[0])): img = cv2.circle(img, tuple(imgpoints[0][i]), 5, (0,0,255), -1) img = cv2.circle(img, tuple(imgpoints2[i][0]), 5, (0,255,0), -1) # 显示结果 cv2.imshow('img', img) cv2.waitKey(0) cv2.destroyAllWindows() ``` 运行以上代码,就可以得到可视化的投影误差结果。注意,这里只绘制了第一个标定板的角点和投影点,如果需要可视化多个标定板的投影误差,需要进行相应的修改。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大米粥哥哥

感谢认可!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值