lidar_camera_cablition/api_alg_pnp.py

333 lines
12 KiB
Python
Raw Permalink Normal View History

2024-12-16 12:30:52 +08:00
#Import modules
import os
from unicodedata import mirrored
import cv2
import numpy as np
import math
import time
import json
import sys
# import transforms3d as tfs
# 1、src 放标准图还是待检测图
# 2、问题一两张图的尺寸要一致
# 3、问题二相机的z轴 是手臂的y轴
# 4、所有参数应该是 第二个参数到第一个参数的差值::标准图必须做目标图像
# 5、if m.distance < 0.72*n.distance: 这个阈值是个关键参数,越小定位约精准
# 6、assert(isRotationMatrix(R)) 报错是因为截图或者拉伸的原因,使得角度旋转无解
# 7、截图比例会影响旋转矩阵甚至会使得旋转矩阵无解。一定要4:3
# 8、原图中目标图像的像素数量与目标图像的像素数量【缩放比】越接近M的值越准确
# 9、缩放比需要打印
# 10、会改变M的参数变量包括
# img1 = cv2.resize
# interpolation=cv2.INTER_AREA
# cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 1.0) 1.0
# if m.distance < 0.85 * n.distance: 0.85
# 11、编写自动化选择参数脚本自动找到最优解
# 12、可优化参数
# cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
# cv2.resize
########################################################################
#Import pictures
homo = "./image/2_origin_centor.jpg"
homo_train = "./image/10_bigorigin_rz_x.jpg"
homo = "./scp_file//o.jpg"
homo_train = "./scp_file/6_-0.59803_0.264184_0.588617_-1.556_0.343_1.072.jpg"
homo = "./error1009/grayscale1.jpg"
homo_train = "./error1009/9983823d/GRAY_SCALE/WIDE/1/IMG_20221009_105934.jpg"
# homo = "./error0926/c24.jpg"
# homo = "./error0928/1g.jpg"
# homo_train = "./error0926/IMG_20220926_165931.jpg"
homo = "./error1010/sfrplus.jpg"
homo_train = "./error1010/IMG_20221010_162855.jpg"
# homo_train = "./060701/IMG_20230604_130953.jpg"
# homo = "./error06042/C24.jpg"
# homo_train = "./060701/IMG_20230603_185437.jpg"
# homo_train = "./060701/IMG_20230603_185512.jpg"
# homo_train = "./060701/IMG_20230603_185518.jpg"
# homo_train = "./error0604/IMG_20230604_175620.jpg"
# homo_train = "./error0604/IMG_20230604_175620.jpg"
# homo_train = "./error06042/IMG_20230604_182406.jpg"
homo = "./error0605/grayscale.jpg"
homo_train = "./error0605/IMG_20230605_121151.jpg"
# homo = "./error0605/sfrplus.jpg"
# homo_train = "./error0605/IMG_20230605_132952.jpg"
# homo = "./error0605 near/sfrplus.jpg"
# homo_train = "./error0605 near/IMG_20230605_174504.jpg"
homo = "./error0605gray/grayscale.jpg"
homo_train = "./error0605gray/IMG_20230605_194543.jpg"
def Pnp(objpos, imgpos, M, cameraMatrix, coff_dis):
dst_imgpos = cv2.perspectiveTransform(imgpos, M)
# imgp.append(dst_imgpos)
# pnp 求解
_, rvec, tvec = cv2.solvePnP(objpos, dst_imgpos, cameraMatrix, None, flags=cv2.SOLVEPNP_UPNP)
# print(cv2.Rodrigues(rvec))
rotM = cv2.Rodrigues(rvec)[0]
# camera_postion = -np.mat(rotM).T * np.mat(tvec)
# Ca = camera_postion.T
# 计算相机坐标系的三轴旋转欧拉角旋转后可以转出世界坐标系。旋转顺序z,y,x
thetaZ = math.atan2(rotM[1, 0], rotM[0, 0]) * 180.0 / math.pi
thetaY = math.atan2(-1.0 * rotM[2, 0], math.sqrt(rotM[2, 1] ** 2 + rotM[2, 2] ** 2)) * 180.0 / math.pi
thetaX = math.atan2(rotM[2, 1], rotM[2, 2]) * 180.0 / math.pi
# 相机坐标系下值
x = tvec[0]
y = tvec[1]
z = tvec[2]
# 进行三次旋转
def RotateByZ(Cx, Cy, thetaZ):
rz = thetaZ * math.pi / 180.0
outX = math.cos(rz) * Cx - math.sin(rz) * Cy
outY = math.sin(rz) * Cx + math.cos(rz) * Cy
return outX, outY
def RotateByY(Cx, Cz, thetaY):
ry = thetaY * math.pi / 180.0
outZ = math.cos(ry) * Cz - math.sin(ry) * Cx
outX = math.sin(ry) * Cz + math.cos(ry) * Cx
return outX, outZ
def RotateByX(Cy, Cz, thetaX):
rx = thetaX * math.pi / 180.0
outY = math.cos(rx) * Cy - math.sin(rx) * Cz
outZ = math.sin(rx) * Cy + math.cos(rx) * Cz
return outY, outZ
(x, y) = RotateByZ(x, y, -1.0 * thetaZ)
(x, z) = RotateByY(x, z, -1.0 * thetaY)
(y, z) = RotateByX(y, z, -1.0 * thetaX)
Cx = x * -1
Cy = y * -1
Cz = z * -1
# 输出相机位置
camera_Location = [Cx, Cy, Cz]
# print("相机位姿",camera_Location)
# 输出相机旋转角
# print("相机旋转角度:",'[', thetaX, thetaY, -thetaZ, ']')
angles = [thetaX, thetaY, thetaZ]
#result = [camera_Location, "旋转角度", angles]
return angles, camera_Location
# 欧拉角->旋转矩阵
def eulerAnglesToRotationMatrix(theta) :
R_x = np.array([[1, 0, 0 ],
[0, math.cos(theta[0]), -math.sin(theta[0]) ],
[0, math.sin(theta[0]), math.cos(theta[0]) ]
])
R_y = np.array([[math.cos(theta[1]), 0, math.sin(theta[1]) ],
[0, 1, 0 ],
[-math.sin(theta[1]), 0, math.cos(theta[1]) ]
])
R_z = np.array([[math.cos(theta[2]), -math.sin(theta[2]), 0],
[math.sin(theta[2]), math.cos(theta[2]), 0],
[0, 0, 1]
])
R = np.dot(R_z, np.dot( R_y, R_x ))
return R
def _angel_zoom(rx, ry, rz, angel_zoom=1):
ax, angle = tfs.euler.euler2axangle(rx, ry, rz, "sxyz")
eulers = tfs.euler.axangle2euler(ax * angel_zoom, angle * angel_zoom, "sxyz")
return eulers[0], eulers[1], eulers[2]
# 固定 resolution = 1 sigma = 1.9
def cal_homo(homo_train, homo, mirror = False, resolution = 1, angle_zoom=1, sigma = 1.6):
# 默认初始化参数
resolution = 1
angle_zoom = 1
# SIFT:该值越大被选中的特征点越多计算时间越长推荐10 同时适用于广角和后置
edgeThreshold = 10
# SIFT:该值越大,精度越高,计算时间越长
sigma = 4.5
# if os.path.basename(homo) == "grayscale.jpg": sigma = 1.9
ransacReprojThreshold = 3.0 # Homography: 该值越小,精度越高,对时间没有影像
# imgWidth、imgHeight 越大,精度越高,计算时间越长
imgWidth = 1000
imgHeight = 750
# if os.path.basename(homo) == "grayscale.jpg":
# imgWidth = 1600
# imgHeight = 1200
# 算法开始执行计时
start = time.time()
# 读取图像与标准图像进行对比
img1 = cv2.imread(homo_train, cv2.IMREAD_GRAYSCALE)
if mirror: img1 = cv2.flip(img1, 1)
img1 = cv2.resize(img1, (imgWidth, imgHeight), interpolation=cv2.INTER_AREA) # 使用INTER_AREA可以提升特征点识别率
img2 = cv2.imread(homo, cv2.IMREAD_GRAYSCALE)
img2 = cv2.resize(img2, (img1.shape[1], img1.shape[0]), interpolation=cv2.INTER_AREA) # 使用INTER_AREA可以提升特征点识别率两张照片必须同样尺寸
# 1、使用 SIFT 进行特征提取
MIN_MATCH_COUNT = 20
MAX_MATCH_COUNT = 100
# 从优选择特征点限定800个
if resolution == 1: sift = cv2.xfeatures2d.SIFT_create(nfeatures=1500, nOctaveLayers=10, contrastThreshold=0.02, edgeThreshold=edgeThreshold, sigma=sigma)
else: sift = cv2.xfeatures2d.SIFT_create(nfeatures=1500)
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# 2、使用 flann 进行特征点匹配
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks = 50)
flann = cv2.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)
# 挑选匹配情况较好的特征点
good = []
for m,n in matches:
if m.distance < 0.85 * n.distance:
good.append(m)
# 从优选择特征点限定MAX个特征
# good = sorted(good, key=lambda x: x.distance)
# if len(good) >= MAX_MATCH_COUNT: good = good[:100]
# 如果特征匹配点的数量不低于 MIN_MATCH_COUNT 设置的数量
if len(good) > MIN_MATCH_COUNT:
src_pts = np.float32([ kp1[m.queryIdx].pt for m in good ]).reshape(-1,1,2)
dst_pts = np.float32([ kp2[m.trainIdx].pt for m in good ]).reshape(-1,1,2)
# 3、查找单应矩阵
# 修改第三个参数 1.0 -> 5.0
# 利用基于RANSAC的鲁棒算法选择最优的四组配对点再计算转换矩阵H(3*3)并返回,以便于反向投影错误率达到最小
# ransacReprojThreshold 设置为 1.0,精度最高
M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, ransacReprojThreshold, maxIters=1500, confidence=0.998) ## 修改 1.0 -> 5.0
# print("api_alg 单应性矩阵 M:\n", M)
matchesMask = mask.ravel().tolist()
# 用于测试显示,使用时注释
import matplotlib.pyplot as plt
plt.ion()
draw_params = dict(matchColor = (0,255,0),
singlePointColor = None,
matchesMask = matchesMask,
flags = 2)
img3 = cv2.drawMatches(img1, kp1, img2, kp2, good, None, **draw_params)
plt.imshow(img3, 'gray'),plt.show()
# 4、采用PNP进行相机的姿态估计
h, w = img1.shape
# 模拟实际物体世界坐标参数
if resolution == 1:
objpos = np.float32([[0.001, 0.001, 0], [0.001, 1.200, 0], [1.600, 1.200, 0], [1.600, 0.001, 0]]).reshape(-1, 1, 3)
else:
objpos = np.float32([[0.0008, 0.0008, 0], [0.0008, 0.5994, 0], [0.7992, 0.5994, 0], [0.7992, 0.0008, 0]]).reshape(-1, 1, 3)
imgpos = np.float32([[0,0], [0,h-1], [w-1,h-1], [w-1,0]]).reshape(-1, 1, 2)
# 模拟相机参数
focal_length = img1.shape[1] # 使用摄像头的宽度(像素)代表焦距
center = (img1.shape[1] / 2, img1.shape[0] / 2) # 使用图像的中心代替光学中心
K = np.array([[focal_length, 0, center[0]],
[0, focal_length, center[1]],
[0, 0, 1]])
angles, T = Pnp(objpos, imgpos, M, K, None)
rand = [angles[0] / 180 * math.pi, angles[1] / 180 * math.pi, angles[2] / 180 * math.pi]
R = eulerAnglesToRotationMatrix(angles)
# print("api_alg 图像计算旋转矩阵 R: \n", R)
# print("api_alg 图像计算角度偏移 a: ", angles)
# print("api_alg 图像计算弧度偏移 r: ", rand)
# print("api_alg 图像计算平移偏移 t: ", [[T[0][0]], [T[1][0]], [T[2][0]]])
# print("mmatches count: ", len(matches), " good count: ", len(good), " matchesMask count: ", len(matchesMask))
# print(rand[0], rand[1], rand[2], M[0][0], M[1][1], M[0][2], M[1][2])
print("mmatches count: ", len(matches), " good count: ", len(good), " matchesMask count: ", len(matchesMask))
print(rand[0], rand[1], rand[2], M[0][0], M[1][1], M[0][2], M[1][2])
end = time.time()
print("api_alg 程序的运行时间为:{}".format(end-start))
# print(rand[0], rand[1], rand[2])
if angle_zoom != 1:
rand[0], rand[1], rand[2] = _angel_zoom(rand[0], rand[1], rand[2], angle_zoom)
return rand[0], rand[1], rand[2], M[0][0], M[1][1], M[0][2], M[1][2], R, T
# img2 = cv2.polylines(img2, [np.int32(dst)], True, 255, 3, cv2.LINE_AA)
else:
# print ("api_alg : Not enough matches are found - %d/%d") % (len(good), MIN_MATCH_COUNT)
matchesMask = None
# print("good features is too few!", "mmatches count: ", len(matches), " good count: ", len(good))
if __name__ == '__main__':
cal_homo(homo_train, homo)
# resp = {'code': 0, 'message': 'success'}
# if len(sys.argv) < 3:
# resp['code'] = -1
# resp['message'] = '参数错误!'
# else:
# img1 = sys.argv[1]
# img2 = sys.argv[2]
# mirror = False
# resolution = 0
# angle_zoom = 1
# sigma = 1.3
# if len(sys.argv) >= 4:
# mirror = sys.argv[3].lower() == 'true'
# if len(sys.argv) >= 5:
# resolution = int(sys.argv[4])
# if len(sys.argv) >= 6:
# angle_zoom = float(sys.argv[5])
# if len(sys.argv) >= 7:
# sigma = float(sys.argv[6])
# result = cal_homo(img1, img2, mirror, resolution, angle_zoom, sigma)
# if result is not None:
# resp['result'] = {
# 'drx': result[0],
# 'dry': result[1],
# 'drz': result[2],
# 'hs': result[3],
# 'ws': result[4],
# 'xf': result[5],
# 'yf': result[6],
# 'dx': result[8][0][0],
# 'dy': result[8][1][0],
# 'dz': result[8][2][0],
# }
# else:
# resp['code'] = -1
# resp['message'] = '解析错误!'
# print(json.dumps(resp, indent = 2, ensure_ascii = False))