照相机标定——棋盘格

照相机标记步骤:

1.制作棋盘格(每个格子的大小可测量),最好是打印出来,贴在平面上(实在不行就用我刚才演示的黑白棋盘格方法)

2.根据棋盘格,采集10-20张图片,提取角点

3.解算出内外参数,内参截图放在博客中,外部参数最好能可视化

一、棋盘格选定:

规格:10cm×10cm(5×5)  每个方格2cm×2cm

手机型号:vivoy67  相机分辨率:1300×1600 

二、棋盘格的Harris角点检测

     首先Harris 角点检测是基于图像像素灰度值变化梯度的,灰度值图像的角点附近,是其像素灰度值变化非常大的区域,其梯度也非常大。换句话说,在非角点位置邻域里,各点的像素值变化不大,甚至几乎相等,其梯度相对也比较小。如果邻域内点的灰度值与中心点Image (i,j) 的灰度值之差 的绝对值 在一个阈值t 范围 内,那就认为这个点与中心点是相似的。与此同时,属于该 Image(i,j) 点的相似点计数器nlike(i,j) 也随之加1。在 Image (i,j) 点的n 邻域 全部被遍历一边之后,就能得到在这个邻域范围内与中心点相似的 点个数的统计值nlike(i,j) 。根据nlike(i,j) 的大小,就可以判断这个中心点是否可能为角点。由于选择3*3的检测窗口,所以,对于中心像素点 , 在下面的讨论中只考虑其8 邻域内像素点的相似度。算该范围的像素点与中心像素点的灰度值之差的绝对值 (记为 Δ ) , 如果该值小于等于设定的阈值 ( 记为 t) , 则认为该像素点与目标像素点相似。

nlike(i,j)=sum(R(i+x,j+y))(-1≤x≤1,-1≤y≤1,且 x≠0 , y≠0) ,其中 : R(i+x, j+y)=1 , 当Δ(i+x , j+y)≤t

        R(i+x,j+y)=0 , 当Δ(i+x , j+y)>t

 从定义中可以看出 : 0≤nlike (i,j)≤8。现在讨论nlike( i , j) 值的含义 。

   (1) nlike (i , j) = 8 ,表示当前中心像素点的 8邻域范围内都是与之相似的像素点 , 所以该像素点邻域范围内的梯度不会很大 , 因此角点检测时 , 应该排除此类像素点,不将其作为角点的候选点 。

   (2) nlike (i , j) = 0 ,表示当前中心像素点的 8邻域范围内没有与之相似的像素点 , 所以该像素点为孤立像素点或者是噪声点 , 因此角点检测时 , 也应该排除此类像素点 。

   (3) nlike (i , j) = 7 ,可以归结为以下的两者情况 ,其他情形都可以通过旋转来得到 ( 图中黑色区域仅表示与中心像素相似 , 而两个黑色区域像素可能是相似的 , 也可能不相似 ) 

   (4) nlike (i , j) = 1 , 中心像素点也不可能为角点 。  

   (5) 2≤ nlike ( i , j) ≤ 6 , 情况比较复杂 , 无法确认像素点准确的性质。我采取的方法是先将其列入候选角点之列,对其进行计算角点响应CRF等后续操作。

   然后计算各候选角点的CBF,进行非极大抑制,再次由于Harris算子不具有尺度不变性,为确保角点检测的准确度和完整性,我们采用Mikolajczyk和Schmid提出的Harris - Laplace检测方法[8],将Harris算子与高斯尺度空间相结合,从而克服Harris 算子只能在单一尺度下检测角点的缺点

代码:

#include <iostream>
#include <opencv2/opencv.hpp>
int main(int argc, char *argv[])
{
    int blockSize = 2;
    int kSize = 3;
    double k = 0.04;
    double thread_value = 85;  // R的阈值
    
    cv::Mat  orignal_image = cv::imread (argv[1]);     
    cv::Mat gray_image , after_harris_image;
    cv::Mat norm_image; //归一化后的图
    cv::Mat scaled_image; //线性变换后的八位无符号整形的图
    cv::cvtColor (orignal_image,gray_image,CV_BGR2GRAY);     // 灰度变换
    cv::cornerHarris(gray_image, after_harris_image, blockSize, kSize, k, cv::BORDER_DEFAULT); //harris corner detect
    cv::normalize(after_harris_image, norm_image, 0, 255, cv::NORM_MINMAX, CV_32FC1, cv::Mat()); //归一化处理
    convertScaleAbs(norm_image, scaled_image);
    for (int i = 0; i < norm_image.rows; i++)
    {
        for (int j = 0; j < norm_image.cols; j++)
        {
            if ((int)norm_image.at<float>(i, j) > thread_value)
            {
                cv::circle(orignal_image, cv::Point(j, i), 10, cv::Scalar(10, 10, 255), 1, 8, 0);  //tag the corner
            }
        }
    }
    cv::imshow("after_harris_corner", orignal_image);
    cv::imwrite("after_harris_corner.png",orignal_image);
    cv::waitKey(0);

    return 0;
}

检测结果:

 

小结:

    基于图像灰度的角点检测和图像边缘提取的角点检测,其中后一种依赖于图像边缘提取的好坏,实用性不是很强。其中Harris的定位性能和鲁棒性较好,加之Harris算法原理简单,不受摄像机姿态和光照的影响,现已成为了使用最为广泛的角点提取算法之一。因此,针对Harris算法现在也已提出了许多改进算法:亚像素级的角点检测算法与尺度空间相结合的算法,自适应角点提取算法根据棋盘格的几何特性提出的检测算法等,但大部分都是以增加计算量或者低效率为代价,同时,用 Harris 算法进行检测,有三点不足:(1 )该算法不具有尺度不变性;(2 )该算法提取的角点是像素级的;(3 )该算法检测时间不是很令人满意。

三、求解内参数矩阵:

单应性:在计算机视觉中被定义为一个平面到另一个平面的投影映射。首先确定,图像平面与标定物棋盘格平面的单应性。设三维世界坐标的点为二维相机平面像素坐标为,所以标定用的棋盘格平面到图像平面的单应性关系为:(其中,K为相机的内参矩阵,R为外部参数矩阵(旋转矩阵),T为平移向量。令,设棋盘格位于Z=0的平面,定义旋转矩阵R的第i列为 ri, 则有:

于是空间到图像的映射可改为:H=λK[r1 r2 t]

其中H 是描述Homographic矩阵,可通过最小二乘,从角点世界坐标到图像坐标的关系求解

根据步骤1中的式子,令H为H =[h1 h2 h3],则[h1 h2 h3]=λK[r1 r2 t],再根据正交和归一化的约束可以得到等式:

即每个单应性矩阵能提供两个方程,而内参数矩阵包含5个参数,要求解,至少需要3个单应性矩阵。为了得到三个不同的单应性矩阵,我们使用至少三幅棋盘格平面的图片进行标定。通过改变相机与标定板之间的相对位置来得到三个不同的图片。为了方便计算,我们定义:

 

B中的未知量可表示为6D向量b,设H中的第列为h;根据b的定义,可以推导出公式

最后推到出通过上式,我们可知当观测平面n≥3时,即至少3幅棋盘格图像,可以得到b的唯一解,求得相机内参数矩阵K。

 3、计算外参数矩阵:

外部参数可通过Homography求解,由 H = [h1 h2 h3] = λA[r1 r2 t],可推出

代码:

# coding=utf-8
import numpy as np
import cv2
import glob
# 终止标准
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
#w = 6
#h = 6
#准备对象点,如(0,0,0),(1,0,0),(2,0,0)......,(6,5,0)
#objp = np.zeros((w*h,3), np.float32)
objp = np.zeros((4*4,3), np.float32)
objp[:,:2] = np.mgrid[0:4,0:4].T.reshape(-1,2)

# 用于存储所有图像中的对象点和图像点的数组。
objpoints = [] # 在现实世界空间的3d点
imgpoints = [] # 图像平面中的2d点。
#glob是个文件名管理工具
images = glob.glob('C:/B1/*.jpg')
print('...loading')
for fname in images:
    #对每张图片,识别出角点,记录世界物体坐标和图像坐标
    print('processing img:{fname}')
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#转灰度
    print('grayed')
    #寻找角点,存入corners,ret是找到角点的flag
    #ret, corners = cv2.findChessboardCorners(gray, (w, h),None)
    ret, corners = cv2.findChessboardCorners(gray, (4, 4),None)

    # 如果找到,添加对象点,图像点(精炼后)
    if ret == True:
        print('chessboard detected')
        objpoints.append(objp)
        #执行亚像素级角点检测
        corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
        imgpoints.append(corners2)

        # 绘制并显示角点
        #img = cv2.drawChessboardCorners(img, (w,h), corners2,ret)
        img = cv2.drawChessboardCorners(img, (4,4), corners2,ret)
        cv2.namedWindow('img',0)
        cv2.resizeWindow('img', 500, 500)
        cv2.imshow('img',img)
        cv2.waitKey(500)
        cv2.destroyAllWindows()
'''
传入所有图片各自角点的三维、二维坐标,相机标定。
每张图片都有自己的旋转和平移矩阵,但是相机内参和畸变系数只有一组。
mtx,相机内参;dist,畸变系数;revcs,旋转矩阵;tvecs,平移矩阵。
'''

img2 = cv2.imread("C:/B1/13.jpg")
print("type objpoints:{objpoints[0].shape}")
print("type imgpoints:{imgpoints[0].shape}")

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1],None,None)
h,  w = img2.shape[:2]

'''
优化相机内参(camera matrix),这一步可选。
参数1表示保留所有像素点,同时可能引入黑色像素,
设为0表示尽可能裁剪不想要的像素,这是个scale,0-1都可以取。
'''
newcameramtx, roi=cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h))
#纠正畸变
dst = cv2.undistort(img2, mtx, dist, None, newcameramtx)

# 裁剪图像,输出纠正畸变以后的图片
x,y,w,h = roi
dst = dst[y:y+h, x:x+w]
cv2.imwrite('calibresult.png',dst)

#打印我们要求的两个矩阵参数
print ("newcameramtx外参:\n",newcameramtx)
print ("dist畸变值:\n",dist)
print ("newcameramtx旋转(向量)外参:\n",rvecs)
print ("dist平移(向量)外参:\n",tvecs)
#计算误差
tot_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)
    tot_error += error

print ("total error: ", tot_error/len(objpoints))

运行结果:

实验总结:

   实验时要保证视场大小与标定板大小相当,或者稍微大一些,以保证在标定板平移时仍然在视场内。先把光圈调到最大进行对焦,对焦时要考虑到全场的清晰程度,达到一种全场的均衡。对焦完成后,把光圈减小,增加曝光,同时补光增加亮度,这样做是为了增加拍摄时的景深。关于标定板亮度,看灰度直方图,平均亮度在150可以,在180~200之间为宜,不能出现过曝。  其次是相机水平和对称问题,根据实验结果分析。要减小误差需要让双摄像机尽量与水平面平行,且两相机的y坐标尽量相同,左右摄像机关于中轴线对称。 

posted @ 2020-04-07 18:27  cenyan  阅读(9196)  评论(0编辑  收藏  举报