当前位置:   article > 正文

相机标定实战之双目标定_双目相机标定

双目相机标定

相机标定原理



前言

相机标定可以说是计算机视觉/机器视觉的基础,也是面试过程中经常出现的问题。相机标定涉及的知识面很广,成像几何、镜头畸变、单应矩阵、非线性优化等。在双目测距系统中,相机标定能消除畸变,进行立体校正,从而提高视差计算的准确性,这样才能得到精确的深度图。

首先需要准备一张棋盘,如下图所示。对于标定不同测距范围相机所用的棋盘方格宽度会有所不同。对于短焦双目相机(测距范围在20m以内),棋盘中方格的宽度达到20mm即可;对于长焦双目相机(测距范围在40m左右),棋盘中方格的宽度需要尽量大,否则会影响标定的精度,一般至少达到60mm。
在这里插入图片描述

尽量拍摄多组照片,这样可以提高标定效果,标定效果的好坏直接影响到测距的精度。对于短焦相机通常拍摄40组照片即可;长焦相机通常会需要更多组照片,标定长焦相机时拍摄了60组。

一、采集图像

如果使用的是左右图像拼接1个USB接口输出的双目摄像头,在采集图像时需要注意其图像拼接的顺序,例如我使用的双目摄像头拼接顺序从左至右为右目——左目(不知道是不是都是这么拼接的,还是厂家搞错了),这个问题处理不好可能会导致后面的处理结果都不对,所以这里特别说明一下。
在这里插入图片描述

import os
import argparse
import cv2

class StereoCamera(object):
    """采集双目标定图片,按键盘【c】或【s】保存图片"""
 
    def __init__(self, chess_width, chess_height, detect=False):
        """
        :param chess_width: chessboard width size,即棋盘格宽方向黑白格子相交点个数,
        :param chess_height: chessboard height size,即棋盘格长方向黑白格子相交点个数
        :param detect: 是否实时检测棋盘格,方便采集数据
        """
        self.chess_width = chess_width
        self.chess_height = chess_height
        self.detect = detect
        self.criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
 
    def detect_chessboard(self, image):
        """检测棋盘格并显示"""
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        ret, corners = cv2.findChessboardCorners(gray, (self.chess_width, self.chess_height), None)
        if ret:
            # 角点精检测
            corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), self.criteria)
            # Draw and display the corners
            image = cv2.drawChessboardCorners(image, (self.chess_width, self.chess_height), corners2, ret)
        return image
 
    def capture2(self, left_video, right_video, save_dir):
        """
        用于采集双USB连接线的双目摄像头
        :param left_video:int or str,左路视频路径或者摄像头ID
        :param right_video:int or str,右视频路径或者摄像头ID
        :param save_dir: str,保存左右图片的路径
        :return:
        """
        self.create_file(save_dir)
        capL = cv2.VideoCapture(left_video)
        capR = cv2.VideoCapture(right_video)
        widthL, heightL, numFramesL, fpsL = self.get_video_info(capL)
        widthR, heightR, numFramesR, fpsR = self.get_video_info(capR)
        print("capL:\n", widthL, heightL, numFramesL, fpsL)
        print("capR:\n", widthR, heightR, numFramesR, fpsR)
        save_videoL = self.create_file(save_dir, "video", "left_video.avi")
        save_videoR = self.create_file(save_dir, "video", "right_video.avi")
        writerL = self.get_video_writer(save_videoL, widthL, heightL, fpsL)
        writerR = self.get_video_writer(save_videoR, widthR, heightR, fpsR)
        i = 0
        while True:
            isuccessL, frameL = capL.read()
            isuccessR, frameR = capR.read()
            if not (isuccessL and isuccessR):
                print("No more frames")
                break
            if self.detect:
                l = self.detect_chessboard(frameL.copy())
                r = self.detect_chessboard(frameR.copy())
            else:
                l = frameL.copy()
                r = frameR.copy()
            cv2.imshow('left', l)
            cv2.imshow('right', r)
            key = cv2.waitKey(10)
            if key == ord('q'):
                break
            elif key == ord('c') or key == ord('s'):
                print("save image:{:0=3d}".format(i))
                cv2.imwrite(os.path.join(save_dir+"/left", "left_{:0=3d}.png".format(i)), frameL)
                cv2.imwrite(os.path.join(save_dir+"/right", "right_{:0=3d}.png".format(i)), frameR)
                i += 1
            writerL.write(frameL)
            writerR.write(frameR)
        capL.release()
        capR.release()
        cv2.destroyAllWindows()
 
    def capture1(self, video, save_dir):
        """
        用于采集单USB连接线的双目摄像头(左右摄像头被拼接在同一个视频中显示)
        :param video:int or str,视频路径或者摄像头ID
        :param save_dir: str,保存左右图片的路径
        """
        self.create_file(save_dir)
        cap = cv2.VideoCapture(video)


        width, height, numFrames, fps = self.get_video_info(cap)
        print("capL:\n", width, height, numFrames, fps)
        save_videoL = self.create_file(save_dir, "video", "left_video.avi")
        save_videoR = self.create_file(save_dir, "video", "right_video.avi")
        writerL = self.get_video_writer(save_videoL, int(width / 2), height, fps)
        writerR = self.get_video_writer(save_videoR, int(width / 2), height, fps)
        i = 0
        while True:
            isuccess, frame = cap.read()
            if not isuccess:
                print("No more frames")
                break
            # 分离左右摄像头
            # 注意:需要根据摄像头画面分割左右图像
            frameL = frame[:, int(width / 2):, :]
            frameR = frame[:, :int(width / 2), :]


            if self.detect:
                l = self.detect_chessboard(frameL.copy())
                r = self.detect_chessboard(frameR.copy())
            else:
                l = frameL.copy()
                r = frameR.copy()
            cv2.imshow('left', l)
            cv2.imshow('right', r)
            key = cv2.waitKey(10)
            if key == ord('q'):
                break
            elif key == ord('c') or key == ord('s'):
                print("save image:{:0=3d}".format(i))
                cv2.imwrite(os.path.join(save_dir+"/left", "left_{:0=3d}.png".format(i)), frameL)
                cv2.imwrite(os.path.join(save_dir+"/right", "right_{:0=3d}.png".format(i)), frameR)
                i += 1
            writerL.write(frameL)
            writerR.write(frameR)
        cap.release()
        cv2.destroyAllWindows()
 
    @staticmethod
    def get_video_info(video_cap):
        width = int(video_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        height = int(video_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        numFrames = int(video_cap.get(cv2.CAP_PROP_FRAME_COUNT))
        fps = int(video_cap.get(cv2.CAP_PROP_FPS))
        return width, height, numFrames, fps
 
    @staticmethod
    def get_video_writer(save_path, width, height, fps):
        if not os.path.exists(os.path.dirname(save_path)):
            os.makedirs(os.path.dirname(save_path))
        fourcc = cv2.VideoWriter_fourcc(*'XVID')
        frameSize = (int(width), int(height))
        video_writer = cv2.VideoWriter(save_path, fourcc, fps, frameSize)
        print("video:width:{},height:{},fps:{}".format(width, height, fps))
        return video_writer
 
    @staticmethod
    def create_file(parent_dir, dir1=None, filename=None):
        out_path = parent_dir
        if dir1:
            out_path = os.path.join(parent_dir, dir1)
        if not os.path.exists(out_path):
            os.makedirs(out_path)
        if filename:
            out_path = os.path.join(out_path, filename)
        return out_path
 
 
def get_parser():
    width = 12
    height = 8
    left_video = 0
    right_video = -1
    save_dir = "data/"
    parser = argparse.ArgumentParser(description='Camera calibration')
    parser.add_argument('--detect', type=bool, default=True, help='detect corner')
    parser.add_argument('--width', type=int, default=width, help='chessboard width size')
    parser.add_argument('--height', type=int, default=height, help='chessboard height size')
    parser.add_argument('--left_video', type=int, default=left_video, help='left video file or camera ID')
    parser.add_argument('--right_video', type=int, default=right_video, help='right video file or camera ID')
    parser.add_argument('--save_dir', type=str, default=save_dir, help='YML file to save calibrate matrices')
    return parser
 
 
if __name__ == '__main__':
    args = get_parser().parse_args()
    stereo = StereoCamera(args.width, args.height, detect=args.detect)
    if args.left_video > -1 and args.right_video > -1:
        # 双USB连接线的双目摄像头
        stereo.capture2(left_video=args.left_video, right_video=args.right_video, save_dir=args.save_dir)
    elif args.left_video > -1:
        # 单USB连接线的双目摄像头(左右摄像头被拼接在同一个视频中显示)
        stereo.capture1(video=args.left_video, save_dir=args.save_dir)
    else:
        raise Exception("Error: Check your camera{}".format(args.left_video, args.right_video))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183

二、基于Matlab单双目标定流程

采集棋盘图

尽量让棋盘占据照片中最多的画面(不小于1\3)
尽量让棋盘格出现在图像中的各个位置(尤其图像的四个角)
拍摄的棋盘图是具有多个角度的(前倾,后倾,左倾,右倾,斜倾等)
拍摄的棋盘图要清晰可辩,最好是在最清晰的范围内拍摄
拍摄照片数量要多一点,推荐20张以上

https://blog.csdn.net/leonardohaig/article/details/81254179

三、基于OpenCV-Python双目标定流程

双目标定的目的是获取左右目相机的内参矩阵、畸变向量、旋转矩阵和平移矩阵。除了Matlab的标定工具箱之外,OpenCV同样也实现了张友正标定法,而我们只需要调用相关的函数即可对相机进行标定。
双目相机标定步骤:

检测棋盘格角点

retL, cornersL = cv2.findChessboardCorners(ChessImaL,(self.width, self.height), cv2.CALIB_CB_ADAPTIVE_THRESH | cv2.CALIB_CB_FILTER_QUADS)  # 提取左图每一张图片的角点
retR, cornersR = cv2.findChessboardCorners(ChessImaR,(self.width, self.height), cv2.CALIB_CB_ADAPTIVE_THRESH | cv2.CALIB_CB_FILTER_QUADS)  # 提取右图每一张图片的角点
  • 1
  • 2

对角点进行亚像素精细化

criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
cv2.cornerSubPix(ChessImaL, cornersL, (11, 11), (-1, -1), criteria)  # 亚像素精确化,对粗提取的角点进行精确化
cv2.cornerSubPix(ChessImaR, cornersR, (11, 11), (-1, -1), criteria)  # 亚像素精确化,对粗提取的角点进行精确化
  • 1
  • 2
  • 3

单目标定

#   左侧相机单独标定
retL, K1, D1, rvecsL, tvecsL = cv2.calibrateCamera(objpoints,imgpointsL,ChessImaL.shape[::-1], None, None)
#   右侧相机单独标定
retR, K2, D2, rvecsR, tvecsR = cv2.calibrateCamera(objpoints,imgpointsR,ChessImaR.shape[::-1], None, None)
  • 1
  • 2
  • 3
  • 4

双目标定

criteria_stereo = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-5)

flags = 0
flags |= cv2.CALIB_FIX_INTRINSIC

# 内参、畸变系数、平移向量、旋转矩阵
retS, K1, D1, K2, D2,  R, T, E, F = cv2.stereoCalibrate(objpoints,imgpointsL,imgpointsR,K1,D1,K2,D2,ChessImaR.shape[::-1], criteria_stereo,flags)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

我们要注意函数中的flags

CV_CALIB_FIX_INTRINSIC:固定K和D矩阵。这是默认标志。如果你校准好你的相机,那就只求解
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/599266
推荐阅读
相关标签