单、双目相机标定及畸变校正、立体矫正的python实现(含拍照程序)

  由于本人水平有限,以下单双目代码是我自己花时间搜集、整理并加以修改的单双目标定的python代码,希望能帮到和我一样半路出家的只会python的小白。
  文中标定过程中用到的相关函数在我另外一篇博客里都有介绍。

1、双目拍照

#coding:utf-8
import cv2
import time
import time

left_camera = cv2.VideoCapture(0)
left_camera.set(cv2.CAP_PROP_FRAME_WIDTH,640)
left_camera.set(cv2.CAP_PROP_FRAME_HEIGHT,480)

right_camera = cv2.VideoCapture(1)
right_camera.set(cv2.CAP_PROP_FRAME_WIDTH,640)
right_camera.set(cv2.CAP_PROP_FRAME_HEIGHT,480)

path="/home/song/pic/" #图片存储路径

AUTO =False   # True自动拍照,False则手动按s键拍照
INTERVAL = 0.0000005 # 调整自动拍照间隔

cv2.namedWindow("left")
cv2.namedWindow("right")
cv2.moveWindow("left", 0, 0)

counter = 0
utc = time.time()
folder = "/home/song/pic/" # 照片存储路径

def shot(pos, frame):
    global counter
    timestr = datetime.datetime.now()
    path = folder + pos + "_" + str(counter) +".jpg"
    cv2.imwrite(path, frame)
    print("snapshot saved into: " + path)

while True:
    ret, left_frame = left_camera.read()
    ret, right_frame = right_camera.read()

    cv2.imshow("left", left_frame)
    cv2.imshow("right", right_frame)

    now = time.time()
    if AUTO and now - utc >= INTERVAL:
        shot("left", left_frame)
        shot("right", right_frame)
        counter += 1
        utc = now

    key = cv2.waitKey(1)
    if key == ord("q"):
        break
    elif key == ord("s"):
        shot("left", left_frame)
        shot("right", right_frame)
        counter += 1
        
left_camera.release()
right_camera.release()
cv2.destroyWindow("left")
cv2.destroyWindow("right")

照片拍摄后如下:
在这里插入图片描述

2、单目标定

  刚入坑比较菜,单目标定改了老半天结果发现opencv就有官方的python例程],在它的基础上进行修改,我用的是opencv 3。

#-*- coding:utf-8 -*-
import numpy as np
import cv2
import glob

# 设置迭代终止条件
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)

# 设置 object points, 形式为 (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6*7,3), np.float32) #我用的是6×7的棋盘格,可根据自己棋盘格自行修改相关参数
objp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2)

# 用arrays存储所有图片的object points 和 image points
objpoints = [] # 3d point in real world space
imgpoints = [] # 2d points in image plane.

#用glob匹配文件夹/home/song/pic_1/right/下所有文件名含有“.jpg"的图片
images = glob.glob(r"/home/song/pic/right/*.jpg")

for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 查找棋盘格角点
    ret, corners = cv2.findChessboardCorners(gray, (7,6), None)
    # 如果找到了就添加 object points, image points
    if ret == True:
        objpoints.append(objp)
        corners2=cv2.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria)
        imgpoints.append(corners)
        # 对角点连接画线加以展示
        cv2.drawChessboardCorners(img, (7,6), corners2, ret)
        cv2.imshow('img', img)
        cv2.waitKey(500)
cv2.destroyAllWindows()

# 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
print(mtx, dist)

#对所有图片进行去畸变,有两种方法实现分别为: undistort()和remap()
images = glob.glob(r"/home/song/pic/right/*.jpg")
for fname in images:
    prefix=fname.split('/')[5]
    img = cv2.imread(fname)
    h,  w = img.shape[:2]
    newcameramtx, roi=cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))

    # # 使用 cv.undistort()进行畸变校正
    # dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
    # # 对图片有效区域进行剪裁
    # # x, y, w, h = roi
    # # dst = dst[y:y+h, x:x+w]
    # cv2.imwrite('/home/song/pic_1/undistort/'+prefix, dst)

    #  使用 remap() 函数进行校正
    mapx, mapy = cv2.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w, h), 5)
    dst = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)
    # 对图片有效区域进行剪裁
    x, y, w, h = roi
    dst = dst[y:y + h, x:x + w]
    cv2.imwrite('/home/song/pic/undistort/'+prefix, dst)

#重投影误差计算
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))

标定过程中的图片,及标定结果如下:
在这里插入图片描述
在这里插入图片描述

3、双目标定及其立体校正

  双目的python代码比较难搞定,改了一下凑合能用,因为我最后要实现手动点击图片并获取相应像素的坐标,最终的极线对齐显示的效果就自己用plt画了一个。

#coding:utf-8
import numpy as np
import cv2
import matplotlib.pyplot as plt
from PIL import Image

# 设置迭代终止条件
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
criteria_stereo = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)

# 设置 object points, 形式为 (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6 * 7, 3), np.float32)  #我用的是6×7的棋盘格,可根据自己棋盘格自行修改相关参数
objp[:, :2] = np.mgrid[0:7, 0:6].T.reshape(-1, 2)

# 用arrays存储所有图片的object points 和 image points
objpoints = []  # 3d points in real world space
imgpointsR = []  # 2d points in image plane
imgpointsL = []

# 本次实验采集里共计30组待标定图片依次读入进行以下操作
for i in range(0,30):  
    t = str(i)
    ChessImaR = cv2.imread('/home/song/pic/right_' + t + '.jpg', 0)  # 右视图
    ChessImaL = cv2.imread('/home/song/pic/left_' + t + '.jpg', 0)  # 左视图
    retR, cornersR = cv2.findChessboardCorners(ChessImaR,(7, 6), None)  # 提取右图每一张图片的角点
    retL, cornersL = cv2.findChessboardCorners(ChessImaL,(7, 6), None)  # # 提取左图每一张图片的角点
    if (True == retR) & (True == retL):
        objpoints.append(objp)
        cv2.cornerSubPix(ChessImaR, cornersR, (11, 11), (-1, -1), criteria)  # 亚像素精确化,对粗提取的角点进行精确化
        cv2.cornerSubPix(ChessImaL, cornersL, (11, 11), (-1, -1), criteria)  # 亚像素精确化,对粗提取的角点进行精确化
        imgpointsR.append(cornersR)
        imgpointsL.append(cornersL)

# 相机的单双目标定、及校正
#   右侧相机单独标定
retR, mtxR, distR, rvecsR, tvecsR = cv2.calibrateCamera(objpoints,imgpointsR,ChessImaR.shape[::-1], None, None)

#   获取新的相机矩阵后续传递给initUndistortRectifyMap,以用remap生成映射关系
hR, wR = ChessImaR.shape[:2]
OmtxR, roiR = cv2.getOptimalNewCameraMatrix(mtxR, distR,(wR, hR), 1, (wR, hR))

#   左侧相机单独标定
retL, mtxL, distL, rvecsL, tvecsL = cv2.calibrateCamera(objpoints,imgpointsL,ChessImaL.shape[::-1], None, None)

#   获取新的相机矩阵后续传递给initUndistortRectifyMap,以用remap生成映射关系
hL, wL = ChessImaL.shape[:2]
OmtxL, roiL = cv2.getOptimalNewCameraMatrix(mtxL, distL, (wL, hL), 1, (wL, hL))

# 双目相机的标定
# 设置标志位为cv2.CALIB_FIX_INTRINSIC,这样就会固定输入的cameraMatrix和distCoeffs不变,只求解𝑅,𝑇,𝐸,𝐹
flags = 0
flags |= cv2.CALIB_FIX_INTRINSIC

retS, MLS, dLS, MRS, dRS, R, T, E, F = cv2.stereoCalibrate(objpoints,imgpointsL,imgpointsR,OmtxL,distL,OmtxR,distR,
                                                           ChessImaR.shape[::-1], criteria_stereo,flags)


# 利用stereoRectify()计算立体校正的映射矩阵
rectify_scale= 1 # 设置为0的话,对图片进行剪裁,设置为1则保留所有原图像像素
RL, RR, PL, PR, Q, roiL, roiR= cv2.stereoRectify(MLS, dLS, MRS, dRS,
                                                 ChessImaR.shape[::-1], R, T,
                                                 rectify_scale,(0,0))  
# 利用initUndistortRectifyMap函数计算畸变矫正和立体校正的映射变换,实现极线对齐。
Left_Stereo_Map= cv2.initUndistortRectifyMap(MLS, dLS, RL, PL,
                                             ChessImaR.shape[::-1], cv2.CV_16SC2)   

Right_Stereo_Map= cv2.initUndistortRectifyMap(MRS, dRS, RR, PR,
                                              ChessImaR.shape[::-1], cv2.CV_16SC2)

#立体校正效果显示
for i in range(0,1):  # 以第一对图片为例
    t = str(i)
    frameR = cv2.imread('/home/song/pic/right_' + t + '.jpg', 0)  
    frameL = cv2.imread('/home/song/pic/left_' + t + '.jpg', 0) 
    
    Left_rectified= cv2.remap(frameL,Left_Stereo_Map[0],Left_Stereo_Map[1], cv2.INTER_LANCZOS4, cv2.BORDER_CONSTANT, 0)  # 使用remap函数完成映射
    im_L=Image.fromarray(Left_rectified) # numpy 转 image类
   
    Right_rectified= cv2.remap(frameR,Right_Stereo_Map[0],Right_Stereo_Map[1], cv2.INTER_LANCZOS4, cv2.BORDER_CONSTANT, 0)
    im_R=Image.fromarray(Right_rectified) # numpy 转 image 类

	#创建一个能同时并排放下两张图片的区域,后把两张图片依次粘贴进去
    width = im_L.size[0]*2
    height = im_L.size[1]

    img_compare = Image.new('RGBA',(width, height))
    img_compare.paste(im_L,box=(0,0))
    img_compare.paste(im_R,box=(640,0))
    
    #在已经极线对齐的图片上均匀画线
    for i in range(1,20):
        len=480/20
        plt.axhline(y=i*len, color='r', linestyle='-')
    plt.imshow(img_compare)
    plt.show()

  立体校正最终效果如下:
在这里插入图片描述

要进行双目相机标定并获取点云图像,需要先准备好相机标定板,它是一张黑白相间的方格纸,可以在网上下载并打印。然后按照以下步骤进行操作: 1. 拍摄相机标定板的多张照片,保证相机位置和角度不变,只改变拍照时的标定板位置和角度。照片越多越好,最好超过10张。 2. 使用 OpenCV 库中的 stereoCalibrate 函数,对双目相机进行标定。这个函数会输出相机内部参数、旋转矩阵和平移向量等参数。 3. 使用 OpenCV 库中的 stereoRectify 函数,对左右相机进行校正,使它们的光轴平行。这个函数会输出左右相机校正变换矩阵。 4. 使用 OpenCV 库中的 undistort 函数,对左右相机的照片进行畸变矫正。 5. 使用 OpenCV 库中的 stereoMatch 函数,对左右相机的照片进行立体匹配,得到每个像素点的视差(disparity)。 6. 使用 OpenCV 库中的 reprojectImageTo3D 函数,将视差图像转换为三维坐标。 7. 使用点云库(如 PCL)将三维坐标转换为点云图像。 8. 可以使用可视化工具(如 CloudCompare)查看点云图像。 需要注意的是,双目相机标定和点云图像获取的过程比较复杂,需要一定的图像处理和计算机视觉基础。建议在进行这些操作前先学习相关知识。 以下是一个简Python 代码示例,展示如何进行双目相机标定并获取点云图像: ``` import cv2 import numpy as np import open3d as o3d # 准备相机标定板 pattern_size = (9, 6) # 标定板上的内角点数量 square_size = 0.02 # 标定板上每个方格的大小,位为米 objp = np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32) objp[:, :2] = np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) * square_size # 拍摄标定板的多张照片并进行标定 image_paths_left = ['left1.jpg', 'left2.jpg', 'left3.jpg', ...] image_paths_right = ['right1.jpg', 'right2.jpg', 'right3.jpg', ...] objpoints = [] # 存储标定板上的三维坐标 imgpoints_left = [] # 存储左相机照片中的二维像素坐标 imgpoints_right = [] # 存储右相机照片中的二维像素坐标 for image_path_left, image_path_right in zip(image_paths_left, image_paths_right): img_left = cv2.imread(image_path_left) img_right = cv2.imread(image_path_right) gray_left = cv2.cvtColor(img_left, cv2.COLOR_BGR2GRAY) gray_right = cv2.cvtColor(img_right, cv2.COLOR_BGR2GRAY) ret_left, corners_left = cv2.findChessboardCorners(gray_left, pattern_size, None) ret_right, corners_right = cv2.findChessboardCorners(gray_right, pattern_size, None) if ret_left and ret_right: objpoints.append(objp) imgpoints_left.append(corners_left) imgpoints_right.append(corners_right) ret, mtx_left, dist_left, mtx_right, dist_right, R, T, E, F = cv2.stereoCalibrate(objpoints, imgpoints_left, imgpoints_right, gray_left.shape[::-1]) # 校正矫正 R_left, R_right, P_left, P_right, Q, roi_left, roi_right = cv2.stereoRectify(mtx_left, dist_left, mtx_right, dist_right, gray_left.shape[::-1], R, T, alpha=0) mapx_left, mapy_left = cv2.initUndistortRectifyMap(mtx_left, dist_left, R_left, P_left, gray_left.shape[::-1], cv2.CV_32FC1) mapx_right, mapy_right = cv2.initUndistortRectifyMap(mtx_right, dist_right, R_right, P_right, gray_right.shape[::-1], cv2.CV_32FC1) img_left = cv2.imread('left.jpg') img_right = cv2.imread('right.jpg') dst_left = cv2.remap(img_left, mapx_left, mapy_left, cv2.INTER_LINEAR) dst_right = cv2.remap(img_right, mapx_right, mapy_right, cv2.INTER_LINEAR) # 立体匹配 stereoMatcher = cv2.StereoSGBM_create( minDisparity=0, numDisparities=16*6, # 要为16的倍数 blockSize=5, speckleWindowSize=100, speckleRange=2, disp12MaxDiff=1, uniquenessRatio=15, P1=8 * 3**2, P2=32 * 3**2 ) gray_left = cv2.cvtColor(dst_left, cv2.COLOR_BGR2GRAY) gray_right = cv2.cvtColor(dst_right, cv2.COLOR_BGR2GRAY) disparity = stereoMatcher.compute(gray_left, gray_right).astype(np.float32) / 16.0 # 转换为三维坐标 points3d = cv2.reprojectImageTo3D(disparity, Q) points3d = points3d.reshape(-1, 3) mask = disparity > disparity.min() colors = dst_left.reshape(-1, 3)[mask] pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(points3d[mask]) pcd.colors = o3d.utility.Vector3dVector(colors) # 可视化 o3d.visualization.draw_geometries([pcd]) ``` 需要注意的是,这只是一个简的示例代码,实际操作中可能会涉及到更多的细节和问题,需要根据具体情况进行调整和修改。
评论 47
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值