打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

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

学习记录。

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

相机成像

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

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

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

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

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

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

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

f:相机的焦距

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

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

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

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

生成棋盘标定图

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

#生成想要的标定图,大小自定义import cv2import sys#读入一张空白图片,该图片最好和你想要标定的相机分辨率一致image = cv2.imread('C:\\Users\\wlx\\Documents\\py_study\\camera calibration\\white.jpg')#设置图片上黑白方格dpi = 96                                                #dpi自己电脑上一英寸显示的像素个数cm_to_inch = 0.3937                                     #1cm = 0.3937inchsquare_length = 1.5                                     #黑白方格边长1.5cmx_nums = 10                                             #x方向画10个方格y_nums = 8                                              #y方向画8个方格square_pixel = int(square_length * cm_to_inch * dpi)    #方格边长的像素#为了把方格图像放在纸张的中间,设定起始座标x0 = 40y0 = 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 cv2import 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'):                breakif __name__ == '__main__':    gain_photo(photo_path)
video = cv2.VideoCapture(0)中的0代表的是我的USB相机在我电脑上的驱动位置
if __name__ == '__main__':就是判断这些代码是不是要当作单独的代码执行,如果是就执行if中的内容。

标定

我先上代码吧

import cv2import sysimport numpy as npimport 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

 

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
基于双目视觉的三维重建实战C++
闪光字
PS+IR制作闪光字效
PS做发光字
(学习笔记)摄像机模型与标定——标定函数
2013年1月7日小学六年级数学奥数难题《计数题》天天练答案名师辅导
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服