跳转至

Comparison of MATLAB CV toolbox & OpenCV

Computer Vision - Camera Calibrator

找角点 detectCheckerboard

源码在 MATLAB\R2022a\toolbox\vision\vision下。主要函数是这两个:

function [points, boardSize] = detectCheckerboard(I, sigma, peakThreshold, highDistortion, usePartial)

function [cxy, c45, Ix, Iy, Ixy, I_45_45] = secondDerivCornerMetric(I, sigma, highDistortion)
思路概述: - 高度失真和普通图像的处理方式有所差异,体现在关键参数不同、某些步骤在高度失真的图像上会重复进行

  • 先做一个高斯低通滤波(效果直观上看是模糊)

  • 用二阶微分器滤波,作用是把明暗交界处提取出来。对低失真图片先做x轴、再做y轴的连续微分,从而把拐点(即既是x方向的明暗交界也是y方向的明暗交界)提取出来;对高失真图片做0、45、90、-45度的二阶微分,并做一些叠加操作,从而提取出角点。两者最后都需要进行腐蚀操作把不是corner的亮点去除。

  • 使用阈值法选出最亮的点作为角点的坐标。

1)低通滤波

用fspecial函数生成了一个大小为[sigma]*[sigma],标准差为sigma的高斯低通滤波器,并将其视作卷积核对图像进行卷积,这一步的作用是低通滤波

此处的sigma,如果勾选了highDistortion(高失真),则设置为1.5;反之设置为2。在高失真的情况下,更低的标准差(sigma)可以减少平滑,这是为了减少特征的损失。如果第一次检测没有测到角点,则把sigma设置为4。

实际上所谓低通滤波就是“模糊”。

原图

Sigma=1.5

Sigma=2

Sigma=4

2)二阶微分滤波 derivFilter

接着是二阶微分过滤器。微分滤波器对图像亮度急剧变化的边缘有提取效果,可以获得邻接像素的差值。

可以通过卷积核[-1 0 1]与图像卷积实现。

y方向微分 x方向微分 对x微分后对y微分(二阶微分),可以看到把拐点,也就是交叉点显现出来了。 如果是高失真图片,还需要对45、0、90、-45度分别进行二阶卷积并以某种方式合成最后的图片

45度卷积 -45度卷积 运算后不同的图片

3)腐蚀

最后通过不同卷积后的图片的叠加和相减形成新的图像,最后通过腐蚀的方式获取corner(也就是最亮的几个点)。最后通过阈值的方式消除其他非corner的亮点。

4)找到确定的像素

最后在经过一个findsubpixel的步骤,得到所有corner和棋盘大小。

标定算法

MATLAB

  1. 求H(单应性矩阵,即内参矩阵和外参矩阵的乘积) 假设失真系数 skew为0,则cx、cy为图像中点,也就是相机感光板中心在像素坐标系下的坐标,初始假设 cx = (imageSize(2)-1)/2 ; cy = (imageSize(1)-1)/2;

  2. 求焦距fx,fy,原理:https://imkaywu.github.io/blog/2017/10/focal-from-homography/

  3. 使用cx,cy,fx,fy和skew构建内参矩阵 [fx, skew, cx; 0, fy, cy; 0, 0, 1];

  4. 接着计算V矩阵、B矩阵和内参矩阵(A): V = computeV(H)、B = computeB(V)、 A = computeIntrinsics(B);

  5. 然后再计算外参 [rvecs, tvecs] = computeExtrinsics(A, H);

  6. 返回参数结构体

OpenCV

REF:https://www.douban.com/note/541379633/?from=tag&_i=9676166HvBL1B1

找角点 findChessboardCorners

思路概述

  • 用自适应阈值的方法对图像进行二值化,使得图像的直方图比较均衡,从而减轻亮度不均匀的影响

  • 对白色部分进行膨胀,从而切断黑色部分的连接

  • 检测四边形,计算每个轮廓的凸包,多边形检测,以及判断是否只有四个顶点,若是则为四边形,再用长宽比、周长和面积等约束去除一些干扰四边形

  • 将每个四边形作为一个单元,它分别有邻近的四边形,无邻近四边形的为干扰四边形,两个邻近四边形为边界处四边形,四个邻近四边形为内部四边形。每个四边形的序号可按邻近关系排序,然后按对角两个四边形相对的两个点,取其连线的中间点作为角点

bool findChessboardCorners(InputArray image_, Size pattern_size,OutputArray corners_, int flags) @https://github.com/opencv/opencv/blob/4.x/modules/calib3d/src/calibinit.cpp
int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
CvPoint2D32f* out_corners, int* out_corner_count, int flags )
@https://github.com/opencv/opencv/blob/4.x/modules/calib3d/src/calibinit.cpp

1)二值化

先对图片进行直方图均衡化,增强对比度 使用函数:icvBinarizationHistogramBased @https://github.com/opencv/opencv/blob/8d0fbc6a1e9f20c822921e8076551a01e58cd632/modules/calib3d/src/calibinit.cpp

再进行二值化,设定一个阈值(一般是128),在阈值上的变成255,在阈值下的变0。

int checkChessboardBinary(const cv::Mat & img, const cv::Size & size)
@https://github.com/opencv/opencv/blob/17234f82d025e3bbfbf611089637e5aa2038e7b8/modules/calib3d/src/checkchessboard.cpp

2)轮廓检测

接着对图像的两个克隆分别做腐蚀和膨胀的操作,相当于分别把黑色/白色部分扩大。并且腐蚀/膨胀的核是不断扩大的,直到可以测到轮廓为止。

对腐蚀/膨胀后的图像外围画一白色/黑色的矩形框(fillQuads),方便轮廓提取,然后进行轮廓提取findContours。经过膨胀后的二值图像,每个黑色的方格已经被分开,轮廓提取后可以得到每个方格的轮廓。

static void fillQuads(Mat & white, Mat & black, double white_thresh, double black_thresh, vector<pair<float, int> > & quads)
@https://github.com/opencv/opencv/blob/17234f82d025e3bbfbf611089637e5aa2038e7b8/modules/calib3d/src/checkchessboard.cpp

3)角点定位

寻找每个方格的相邻方格,并记相邻方格的个数,连同相邻方格的信息存在相应CvCBQuad结构体中。二值图像在膨胀后原本相邻的方格,分开了,原来相连部分有一个公共点,现在分开变成了两个点。找到相邻的方格之后,计算出原来的公共点,用公共点替代膨胀后分开的点。这主要由icvFindQuadNeighborhors函数完成。

根据已知所求的角点个数(用户输入),判别每个类中方格是否为所求的棋盘方格,并对棋盘方格排序,即该方格位于哪行那列。在这个过程中,可以添加每类方格总缺少的方格,也可以删除每类方格中多余的方格。icvOrderFoundConnetedQuads函数完成该过程。

icvCleanFoundConnectedQuads函数、icvCheckQuadGroup函数根据已知棋盘的方格个数(由棋盘的角点数计算出来)确认方格位置及个数是否正确,并确定粗略强角点的位置(两个方格的相连位置)。icvCheckBoardMonotony再次检验棋盘方格是否提取正确。

以上如果有一步所有方格都不符合要求,则进入一个新的循环。若循环结束,还尚未找到符合要求的方格,则棋盘定位失败,退出函数。

最后,cvFindCornerSubpix()根据上步的强角点位置,确定强角点的精确位置。

标定算法

参考:https://max.book118.com/html/2021/0704/7105004042003141.shtm

@https://github.com/opencv/opencv/blob/8b4fa2605e1155bbef0d906bb1f272ec06d7796e/modules/calib3d/src/calibration.cpp

  1. 计算出每一张图像得到的单应性矩阵;将图像的中心像素坐标作为初始的u0和v0,求出含有fx,fy的向量B。
  2. 利用最小二乘原理,采用求SVD奇异值分解和伪逆矩阵的方法求解fx,fy,并得到包含 4个参数的摄像机内部参数矩阵M
  3. 求出旋转向量和平移向量的初始值(利用正交的性质进行推导)
  4. 行全局 Levenberg-Marquardt 优化算法以最小化重投影误差。通过高斯牛顿迭代法( 20次)对旋转向量和平移向量进行精炼。每次迭代的时候,先通过投影函数将所有的物理世界坐标在考虑畸变、内部参数、外部参数的情况下进行再投影,同时求出图像上关于旋转向量和平移向量部分的雅可比导数矩阵,再通过 SVD 分解和伪逆矩阵的求解计算出偏差值,与初始值相加以后作为下一次迭代的初始值。
  5. 对所有的参数进行全局的优化,同样采用选代法对所有的参数进行精炼(30次),每次迭代的时候,先将所有的物理世界坐标在考虑畸变、内部参数、外部参数的情況下进行再投影,同时求出图像上关于旋转向量、平移向量、fx、fy、u0、v0和所有畸变系数K1、K2、K3、K4的雅可比导数矩阵,再通过 SVD 分解和伪逆矩阵的求解计算出偏差值,与初始值相加以后作为下一次迭代的初始值。

Aruco定位算法

参考:https://www.cnblogs.com/yilangUAV/p/14436171.html

https://blog.csdn.net/Zhaoxi_Li/article/details/106879489

结果对比

找角点

下面是一张有6*9个角点的图片经过MATLAB和OpenCV的算法得到的角点坐标中的其中两行。橙色的是MATLAB得到的,蓝色的是Opencv得到的。

可以看出两者的差距大概在1个像素左右。

综上可以发现,两者寻找角点的实现方式很不一样。

OpenCV优劣势:面向用户的可选择性更多,自由度更高,可以选择更多的参数调整;但同时也带来了不够自动化,如果用户不熟练,并不太容易得到好的标定结果;对环境干扰的鲁棒性不太好,因为直方图均衡并不能完全解决环境光强干扰的问题。

MATLAB CV toolbox优劣势:更加自动化,而且基本上都能得到比较好的结果

而通过分析两者的定位算法,我们可以得到Opencv和MATLAB的主要区别:MATLAB并没有使用重投影迭代法;Opencv中提供了接口让用户选择迭代的次数和精度。所以迭代这一步是造成结果主要区别的原因。

附加资料

  1. 直方图均衡

https://blog.csdn.net/weixin_40163266/article/details/113802909

  1. 单应性(homography)变换

https://zhuanlan.zhihu.com/p/511839985

https://blog.csdn.net/qq_33175713/article/details/105520515