基于opencv的工业相机标定

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

简介

编辑 播报

在图像测量过程以及机器视觉应用中,为确定空间物体表面某点的三维几何位置与其在图像中对应点之间的相互关系,必须建立相机成像的几何模型,这些几何模型参数就是相机参数。在大多数条件下这些参数必须通过实验与计算才能得到,这个求解参数的过程就称之为相机标定(或摄像机标定)。无论是在图像测量或者机器视觉应用中,相机参数的标定都是非常关键的环节,其标定结果的精度及算法的稳定性直接影响相机工作产生结果的准确性。因此,做好相机标定是做好后续工作的前提,提高标定精度是科研工作的重点所在。

方法

**编辑

相机标定方法有:传统相机标定法、主动视觉相机标定方法、相机自标定法、零失真相机标定法。

传统相机标定法需要使用尺寸已知的标定物,通过建立标定物上坐标已知的点与其图像点之间的对应,利用一定的算法获得相机模型的内外参数。根据标定物的不同可分为三维标定物和平面型标定物。三维标定物可由单幅图像进行标定,标定精度较高,但高精密三维标定物的加工和维护较困难。平面型标定物比三维标定物制作简单,精度易保证,但标定时必须采用两幅或两幅以上的图像。传统相机标定法在标定过程中始终需要标定物,且标定物的制作精度会影响标定结果。同时有些场合不适合放置标定物也限制了传统相机标定法的应用。

目前出现的自标定算法中主要是利用相机运动的约束。相机的运动约束条件太强,因此使得其在实际中并不实用。利用场景约束主要是利用场景中的一些平行或者正交的信息。其中空间平行线在相机图像平面上的交点被称为消失点,它是射影几何中一个非常重要的特征,所以很多学者研究了基于消失点的相机自标定方法。自标定方法灵活性强,可对相机进行在线定标。但由于它是基于绝对二次曲线或曲面的方法,其算法鲁棒性差。

基于主动视觉的相机标定法是指已知相机的某些运动信息对相机进行标定。该方法不需要标定物,但需要控制相机做某些特殊运动,利用这种运动的特殊性可以计算出相机内部参数。基于主动视觉的相机标定法的优点是算法简单,往往能够获得线性解,故鲁棒性较高,缺点是系统的成本高、实验设备昂贵、实验条件要求高,而且不适合于运动参数未知或无法控制的场合。

零失真相机标定法是以LCD显示屏为参考基准,以相移光栅为媒介,建立LCD像素与相机传感器像素之间的映射关系,确定每个相机像素点在LCD上的视点位置。镜头使相机在LCD上的视场为非矩形。在这个有畸变的视场内,可以构造一个内接的虚拟传感器,并保持相同的像素数。这样每个虚拟像素点就一定落在某四个相邻视点构成的任意四边形之内。虚拟像素点的亮度将由这四点的亮度经加权插值确定,而与其它像素点无关。用这四个加权系数的集合对原始图像作重采样(四次乘法、四次加法),就可以得到零畸变的输出图。对彩色相机的RGB三通道分别处理,但选用一个公共的虚拟传感器,则合成的彩色图像将是零畸变、零色差的。每个像素点(物理视点和虚拟点)的位置误差都是零均值,均方差都可小于1/1,000像素点距。

技术

**编辑

基于离线相机标定

基于离线相机标定技术需要准确的相机内参数和外参数作为重构算法的输入和先决条件,目前最为流行的离线相机标定算法是R. Tsai在1987年提出的[Tsai1987],Tsai方法使用一个带有非共面专用标定标识的三维标定物来提供图像点和其对应的三维空间点的对应并计算标定参数。Z. Zhang在1998年提出了另一个实用方法 [Bouguet2007],该方法需要对一个平面标定图案的至少两幅不同视图来进行标定。加州理工学院的相机标定工具对以上两个方法均作了有效实现,并且已经被集成到Intel的视觉算法库OpenCV中[OpenCV2004]。通过标定算法,可以计算相机的投影矩阵,并提供场景的三维测度信息。在不给定真实场景的绝对平移、旋转和放缩参数的情况下,可以达到相似变换级别的测度重构。

零失真相机标定技术将相机看作“成像灰盒”,不需要知道相机内部的任何信息,如镜头的机械和光学构造,传感器在相机内的六自由度位置和方位等等。仅借助于显示一组(6幅)相移光栅的LCD显示屏,就可以得到相机每个像素的外部几何特性和色差特性,所以适用于所有镜头,如常规镜头、远心镜头、平移-倾斜镜头、Scheimpflug镜头等等。该方法向后100%全兼容小孔模型和其它所有模型。该方法由B. Yang在2016年提出并实现,2018年开源,2020年发表于SPIE。

基于在线相机标定

在很多场合下,如缺失标定设备或相机内参数持续改变的情况下,没有足够数据来支持离线相机标定,对这类场景的多视三维重构就要用到在线相机标定的技术。在线标定和离线标定框架的主要区别在于标定相机或估计相机参数的方法上。在大多数文献中在线标定技术被称为自标定。自标定方法可以大致分为两类:基于场景约束的自标定和基于几何约束的自标定。

①基于场景约束的自标定

合适的场景约束往往能够在很大程度上简化自标定的难度。比如说,广泛存在于建筑或人造场景中的平行线能够帮助提供三个主正交方向的消视点和消视线信息,并能够据此给出相机内参数的代数解或数值解 [Caprile1990]。消视点的求解可以通过投票并搜索最大值的方法进行。Barnard采用高斯球构造求解空间 [Barnard1983]。Quan、Lutton和Rother等给出了进一步的优化策略[Quan1989, Lutton1994, Rother2000]。文献[Quan1989]中给出了搜索解空间的直接算法,Heuvel给出的改进算法加入了强制性的正交条件 [Heuvel1998]。Caprile给出了基于三个主正交方向消视点的几何参数估计法,Hartley使用标定曲线计算焦距 [Hartley2003]。Liebowitz等进一步从消视点位置构造绝对二次曲线的约束并用考克斯分解求解标定矩阵 [Liebowitz1999]。

② 基于几何约束的自标定

基于几何约束的自标定不需要外在场景约束,仅仅依靠多视图自身彼此间的内在几何限制来完成标定任务。利用绝对二次曲面作自标定的理论和算法最先由Triggs提出 [Triggs1997]。基于Kruppa方程求解相机参数则始于 Faugeras, Maybank等的工作 [Faugeras1992, Maybank1992]。Hartley给予基本矩阵推导出了Kruppa方程的另一个推导 [Hartley1997]。文献[Sturm2000]则给出了Kruppa方程的不确定性的理论探讨。层进式自标定技术被用于从射影重构升级到度量重构 [Faugeras1992]。自标定技术的一个主要困难在于它不是无限制地用于任意图像或视频序列,事实上,存在着特定运动序列或空间特征分布导致自标定求解框架的退化和奇异解。文献[Sturm1997]给出了关于退化情形的详细讨论和分类。对一些特殊可解情况存在性和求解方法的讨论可以参考文献[Wilesde1996]等。

标定模板

**编辑

标定模板(标定板 Calibration Target) 在机器视觉、图像测量、摄影测量、三维重建等应用中,为校正镜头畸变;确定物理尺寸和像素间的换算关系;以及确定空间物体表面某点的三维几何位置与其在图像中对应点之间的相互关系,需要建立相机成像的几何模型。通过相机拍摄带有固定间距图案阵列平板、经过标定算法的计算,可以得出相机的几何模型,从而得到高精度的测量和重建结果。而带有固定间距图案阵列的平板就是标定模板(标定板 Calibration Target)。

在零失真相机标定中,一般用LCD显示屏作为标定板。在远距离、大视场应用中,可以将大型电影院的屏幕作为标定板,由经过标定的、零失真的数字投影仪投射相移光栅。在毫米级视场应用中,可以用无镜头的有源微型投影成像器件作为标定板。

模板种类

**编辑

① 等间距实心圆阵列图案 Ti-times CG-100-D

② 国际象棋盘图案 Ti-times CG-076-T

③ 相移光栅(3纵、3横,120°相位移)

附录:基于opencv的工业相机标定:

```C++ GenCaltab.cpp : 定义控制台应用程序的入口点。

//

include "stdafx.h"

include

include

include

using namespace cv;

using namespace std;

typedef unsigned int uint;

/const string &/void GenCaltab(const string &pathName, Mat &caltab/string &outPathName/, int width = 600, int height = 600, int cols = 20, int rows = 20)  // 黑白的,

{

    Scalar color(0);                                //  背景色(全黑)  

    caltab = Mat(height, width, CV_8UC1, color);   // calibration table标定板

    //Mat caltab(height, width, CV_8UC1, color);

    int nWidthOfROI = int(width / float(cols));

    int imgHeight = caltab.rows;

    int imgWidth = caltab.cols;

    for (int j = 0; j < imgHeight; j++)

    {

        uchar* data = caltab.ptr (j);

        for (int i = 0; i < imgWidth; i++)

        {

            if ((i / nWidthOfROI + j / nWidthOfROI) % 2)

            {

                data[i] = 255;  // 沒有三通道,

            }

        }

    }

    Mat caltab1 = caltab.clone();

    imwrite(pathName, caltab);

    /*imshow("標定板", caltab);

    waitKey();*/

    //outPathName = pathName;

    //return outPathName;

}

/bool/void CalibateCamera(Mat &image, Mat &cameraMatirx, Mat &distortionMat, vector &rvecs, vector &tvecs,

    int rows = 19, int cols = 19, float distance = 30, int flags = 0)

{

    // int cols = 10; int rows = 7;             // 行列应该相等(正方形)    

    // float distance = 30;                      //标定板黑点距离

    Size patternSize(cols, rows);              // 标定板内每行每列

    vector corners;                     vector > cornersVect;       // 角度矢量

    vector worldPoints;               // 世界坐标点

    vector > worldPointsVect;  // 世界坐标矢量

    for (int i = 0; i < cols; i++)

    {

        for (int j = 0; j < rows; j++)

        {

            // 此處是不是應該改一下?? ((i + 1) * distance, (j + 1) * distance, 0)

            worldPoints.push_back(Point3f(i * distance, j * distance, 0));// z = 0;

            //worldPoints.push_back(Point3f((i + 1) * distance, (j + 1) * distance, 0));// z = 0;

        }

    }

    // int *corner = NULL;

    bool find = findChessboardCorners(image, patternSize, corners);       

    drawChessboardCorners(image, patternSize, corners, find);              // 绘制棋盘点

    // Mat cameraMatirx, distortionMat;

    // vector rvecs, tvecs, rvecs2, tvecs2;                          // r:rotate(旋轉);t:transplant(平移)  

    if (find)

    {

        cornersVect.push_back(corners);        

        worldPointsVect.push_back(worldPoints);

        calibrateCamera(worldPointsVect, cornersVect, image.size(), cameraMatirx, distortionMat, rvecs, tvecs);

    }

    /*imshow("", image);

    waitKey();*/

}

int _tmain(int argc, _TCHAR* argv[])

{

    //string pathName = {'\0'};

    Mat caltab;

    GenCaltab("C:\Users\liyabin_sumeida\Desktop\1.jpg", caltab/pathName//, 891, 630, 10, 7/);

    //waitKey(0);                                                         // 按任意鍵

    caltab = imread("C:\Users\liyabin_sumeida\Desktop\1.jpg");  

    Mat cameraMatirx, distortionMat;

    vector rvecs,tvecs;

    CalibateCamera(caltab, cameraMatirx, distortionMat, rvecs, tvecs /, 7, 10, 90, 0/);

    printf("相机内参数:\n");

    for (int i = 0; i < cameraMatirx.size().height; i++)

    {

        uchar *data = cameraMatirx.ptr (i);

        for (int j = 0; j < cameraMatirx.size().width; j++)

        {

            printf("\t  %d\n", data[j]);

        }

    }

    printf("畸变矩阵陣:\n");

    for (int i = 0; i < distortionMat.size().height; i++)

    {

        uchar *data = distortionMat.ptr (i);

        for (int j = 0; j < distortionMat.size().width; j++)

        {

            printf("\t%d\n", data[j]);

        }

    }

    printf("相机外参之旋转矩阵:\n");

    for (uint i = 0; i < rvecs.size(); i++)             // vector是三維的???

    {

        printf("第【%d】个矩阵的值:\n", i);

        Mat rMat = rvecs[i];                          // 某一個矩陣

        for (int j = 0; j < rMat.size().height; j++)

        {

            uchar *data = rMat.ptr (j);        // 指向某一行

            for (int k = 0; k < rMat.size().width; k++)

            {

                printf("                 %d\n", data[k]);;

            }

        }

    }

    printf("相机外参数之平移矩阵:\n");

    for (uint i = 0; i < tvecs.size(); i++)             // vector是三維的???

    {

        printf("第【%d】个矩阵的值:\n", i);

        Mat rMat = tvecs[i];                          // 某一个矩阵

        for (int j = 0; j < rMat.size().height; j++)

        {

            uchar *data = rMat.ptr (j);        // 指向某一行

            for (int k = 0; k < rMat.size().width; k++)

            {

                printf("                 %d\n",data[k]);

            }

        }

    }

    imshow("标定图像", caltab);

    waitKey();

    return 0;

} ```

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宋小童

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值