分享

生成黑白棋盘标定图和单目相机标定(python+opencv实现)

 bit54song 2020-04-05

学习记录。

事实上很早就接触过视觉定位这东西,但是到现在才返回头学习一下相机的标定,真是可耻啊!我把想法和过程记录一下。

相机成像

相机的成像原理——小孔成像

然而,在实际由于设计工艺问题、相机安装环境或物体摆放位置等影响,会照成成像与实际图像不一样的现象。

由于设计工艺照成的影响是无法改变的事实,所以这将是相机的内参;

由环境或安装方式照成的影响是可以改变的,这就是相机的外参。

https://blog.csdn.net/aoulun/article/details/78768570中详细介绍了相机成像原理,相机内、外参数是什么。这里为了保证记录的完整型,把成像平面的像素座标与实际物体的世界座标公式写下来。

1.红框就是相机外参,R为旋转矩阵,T为平移向量;如果相机镜头和物体平面平行(室内定位中,有一种基于视觉的室内定位,定位方式就是在移动的小车上安装单目相机,在屋顶安装各种可识别的标签,相机的光轴一直与屋顶是垂直的),在这种情况下,旋转矩阵可以看作是单位向量及R=E,而平移向量T=0。

2.蓝框就是相机的内参,相机的内参从出厂后就被固定了。

f:相机的焦距

(u0,v0):像平面的投影中心点

dx,dy:就是单位像素对应实际距离

Zc:可以认为相机镜头到成像物体的垂直距离

对这部分我就不赘述了,在麻呱智能 的文章中介绍的特别详细,我就不班门弄斧了。

生成棋盘标定图

创建自定义的棋盘标定图,这个没啥要说的,就是调用了opencv的画矩形框的函数,代码如下:

#生成想要的标定图,大小自定义
import cv2
import sys

#读入一张空白图片,该图片最好和你想要标定的相机分辨率一致
image = cv2.imread('C:\\Users\\wlx\\Documents\\py_study\\camera calibration\\white.jpg')
#设置图片上黑白方格
dpi = 96                                                #dpi自己电脑上一英寸显示的像素个数
cm_to_inch = 0.3937                                     #1cm = 0.3937inch
square_length = 1.5                                     #黑白方格边长1.5cm
x_nums = 10                                             #x方向画10个方格
y_nums = 8                                              #y方向画8个方格
square_pixel = int(square_length * cm_to_inch * dpi)    #方格边长的像素
#为了把方格图像放在纸张的中间,设定起始座标
x0 = 40
y0 = 16

#画方格
def DrawSquare():
    flag = -1                                            #颜色转变标志
    #一行一行的画
    for i in range(y_nums):
        #每画一行先换一次颜色
        flag = 0 - flag
        for j in range(x_nums):
            if flag > 0:
                color = [0,0,0]                          #黑色方格
            else:
                color = [255,255,255]
            #调用opencv中的画方框函数
            cv2.rectangle(image,(x0 + j*square_pixel,y0 + i*square_pixel),
                          (x0 + j*square_pixel+square_pixel,y0 + i*square_pixel+square_pixel),color,-1)
            flag = 0 - flag
    #保存图像
    cv2.imwrite('chess_map.jpg',image)

#判断本程序是独立运行还是被调用
if __name__ == '__main__':
    DrawSquare()

上面的代码可以生成自己想要的棋盘标定图,修改x_nums和y_nums参数的值,就能获得任意数量的黑白格子。实现原理就是在图像的某一点开始,先画一个黑色方格(或者白色),画完后将起点座标和终点座标都向右移动方格边长的距离,然后改变颜色再画一个方格,依次类推,画完一行后,就转战到第二行,直到全部完成。

注意:在图像上座标是这样的

cv2.rectangle(img,pt1,pt2,color,thickness=None,line_type=None,shift=None)

img:图像,要画图的图像

pt1:方格的起点座标(x0,y0)

pt2:方格的终点座标(x1,y1)

color:方框的颜色

thickness:方框线的宽度(像素),当值为负数时,填充方框

line_type:方框线的样式

采集标定图

我采用离线标定,先把不同角度的标定图采集保存下来,然后再开始标定。下面是采集的代码

import cv2
import os

#标定图像保存路径
photo_path = "C:\\Users\\wlx\\Documents\\py_study\\camera calibration\\image"
#创建路径
def CreateFolder(path):
    #去除首位空格
    del_path_space = path.strip()
    #去除尾部'\'
    del_path_tail = del_path_space.rstrip('\\')
    #判读输入路径是否已存在
    isexists = os.path.exists(del_path_tail)
    if not isexists:
        os.makedirs(del_path_tail)
        return True
    else:
        return False
#获取不同角度的标定图像
def gain_photo(photo_path):
    # 检查输入路径是否存在——不存在就创建
    CreateFolder(photo_path)
    #开启摄像头
    video = cv2.VideoCapture(0)
    #显示窗口名称
    photo_window = 'calibration'
    #保存的标定图像名称以数量命名
    photo_num = 0
    while video.isOpened():
        ok,frame = video.read()                                 #读一帧的图像
        if not ok:
            break
        else:
            cv2.imshow(photo_window,frame)
            key = cv2.waitKey(10)
            #按键盘‘A’保存图像
            if key & 0xFF == ord('a'):
                photo_num += 1
                photo_name = photo_path + '\\' + str(photo_num) + '.jpg'
                cv2.imwrite(photo_name,frame)

                print('create photo is :',photo_name)
            #按键盘‘Q’中断采集
            if key & 0xFF == ord('q'):
                break

if __name__ == '__main__':
    gain_photo(photo_path)
video = cv2.VideoCapture(0)中的0代表的是我的USB相机在我电脑上的驱动位置
if __name__ == '__main__':就是判断这些代码是不是要当作单独的代码执行,如果是就执行if中的内容。

标定

我先上代码吧

import cv2
import sys
import numpy as np
import glob

#标定图像保存路径
photo_path = "C:\\Users\\wlx\\Documents\\py_study\\camera calibration\\image"
#标定图像
def calibration_photo(photo_path):
    #设置要标定的角点个数
    x_nums = 9                                                          #x方向上的角点个数
    y_nums = 7
    # 设置(生成)标定图在世界座标中的座标
    world_point = np.zeros((x_nums * y_nums,3),np.float32)            #生成x_nums*y_nums个座标,每个座标包含x,y,z三个元素
    world_point[:,:2] = np.mgrid[:x_nums,:y_nums].T.reshape(-1, 2)    #mgrid[]生成包含两个二维矩阵的矩阵,每个矩阵都有x_nums列,y_nums行
                                                                        #.T矩阵的转置
                                                                        #reshape()重新规划矩阵,但不改变矩阵元素
    #保存角点座标
    world_position = []
    image_position = []
    #设置角点查找限制
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER,30,0.001)
    #获取所有标定图
    images = glob.glob(photo_path+'\\*.jpg')
    #print(images)
    for image_path in images:
        image = cv2.imread(image_path)
        gray = cv2.cvtColor(image,cv2.COLOR_RGB2GRAY)
        #查找角点
        ok,corners = cv2.findChessboardCorners(gray,(y_nums,x_nums),None)

        if ok:
            #把每一幅图像的世界座标放到world_position中
            world_position.append(world_point)
            #获取更精确的角点位置
            exact_corners = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
            #把获取的角点座标放到image_position中
            image_position.append(exact_corners)
            #可视化角点
            # image = cv2.drawChessboardCorners(image,(y_nums,x_nums),exact_corners,ok)
            # cv2.imshow('image_corner',image)
            # cv2.waitKey(5000)
    #计算内外参数
    ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(world_position, image_position, gray.shape[::-1], None,None)
    print(mtx, dist)
    #计算偏差
    mean_error = 0
    for i in range(len(world_position)):
        image_position2, _ = cv2.projectPoints(world_position[i], rvecs[i], tvecs[i], mtx, dist)
        error = cv2.norm(image_position[i], image_position2, cv2.NORM_L2) / len(image_position2)
        mean_error += error
    print("total error: ", mean_error / len(image_position))
    
if __name__ == '__main__':
    calibration_photo(photo_path)

使用opencv标定这些图像,步骤大致就是:

1、设置想标定角点的个数

2、创建对应角点个数的世界座标

3、将采集到的标定图读入缓存

4、灰度处理

5、使用findChessboardCorners(img,patternSize,corners,flags=None)函数,查找图像中的内点

  • image:输入的棋盘图像,必须是8位的灰度或者彩色图像
  • patternSize:棋盘中每行每列的角点个数
  • corners:检测到的角点
  • flags:各种操作标准

6、使用cornerSubPix(image,corners,winSize,zeroZone,criteria)函数,精确查找图像上的角点

  • image:输入图像,,必须是8位的灰度或者彩色图像

  • corners:输入角点的初始化座标,也存储精确的输出角点座标

  • winSize:搜索窗口的一半尺度,如winSize=(5,5),则使用(2x5+1, 2x5+1)=(11,11)的搜索窗

  • zeroZone:死区的一半尺寸,死区为不对搜索区做求和运算的区域,当值为(-1,-1)时,表示没有死区

  • criteria:搜索角点停止的标志

criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER,30,0.001)就是一个标志,TERM_CRITERIA_EPS 代表误差也就是精度,TERM_CRITERIA_MAX_ITER代表迭代次数,它两之和就是指两个因素同时作用,这里当迭代次数超过30或误差大于0.001都会停止运算。

7、将图像的世界座标保存到数组world_position中,将找到的角点座标保存到数组image_position中

8、使用cv2.calibrateCamera(world_position, image_position, gray.shape[::-1], None,None)计算内外参数

9、计算一下准确度,也就是通过你算出来的内外参数,逆运算出角点座标,然后将这个座标和识别出来的角点座标进行误差运算,得到偏差值。

最后,可以将得到的内外参数保存到一个文件中,以后在用相机采集图像时,就可以用内外参数去矫正图像了,这里我没做图像的矫正,过几天做了再也上来。

另外,用来标定的图像不要太少,我做了实验随着标定图像的增加,最后计算出的偏差会减小;而且拍摄标定图像时,角度要合理,相机固定位置不要发生变化。

下面是我实验,就是为了验证代码是否能运行,所以没有打印标定图纸,而是直接用摄像头拍摄电脑显示器上的图像,摄像头也没固定牢靠,所以结果不是很好。拍摄了20张图像

相机内参:

[[1.24956824e+03 0.00000000e+00 2.16230694e+02]
 [0.00000000e+00 2.92154313e+03 3.87901459e+02]
 [0.00000000e+00 0.00000000e+00 1.00000000e+00]] 

相机外参:

[[-2.80875093e+02  8.92572104e+03 -2.80196846e+00  8.90336346e+00 -1.46134126e+05]]

偏差值:

error:  6.968280883027417

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多