在张正友标定法中,标定板角点检测是第一步,也是保证标定精度的关键之一。通常使用棋盘格(checkerboard)图案作为标定板,其角点具有高对比度、规则排列,便于检测和亚像素精细化。下面详细介绍标定板角点检测原理与算法流程。
1、角点定义
在棋盘格图案中,角点是白格与黑格交界的交叉点,即四个颜色区域交汇处的内角点(不包括外边框角点)。如图所示:
若棋盘格为 8x6 格,则角点数量为 7×5=35 个。
2、 OpenCV中提供的角点检测算法接口
OpenCV 提供稳定的函数:
bool findChessboardCorners(InputArray image,
Size patternSize,
OutputArray corners,
int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE);
参数说明:
image
:输入灰度图像patternSize
:角点行列数(列数-1, 行数-1)corners
:输出二维图像坐标点列表flags
:检测模式控制标志位,如自适应阈值、图像归一化、快速检查等
3、OpenCV角点检测核心算法流程
在OpenCV中棋盘格角点检测大致包括以下几个步骤:
1. 图像预处理
- 转为灰度图
- 可选:直方图均衡化、滤波平滑、CLAHE 增强等
2. 边缘与角点候选检测
-
使用 图像梯度(Sobel)计算边缘响应
-
应用 Harris 或 Shi-Tomasi 算法提取角点候选:
R = det ( M ) − k ⋅ ( trace ( M ) ) 2 R = \det(M) - k \cdot (\text{trace}(M))^2 R=det(M)−k⋅(trace(M))2
- M M M 是像素邻域的梯度矩阵
- R R R 是角点响应函数
3. 多边形轮廓提取
- 对二值化图像使用
findContours()
获取轮廓 - 分析轮廓结构并提取出类似正方形交错排列的结构
4. 匹配几何模式(匹配棋盘格)
- 使用凸包排列和网格几何结构,判断角点是否匹配棋盘格排列
- 结合 连通性判断 和 斜率相似性 对候选角点排序
5. 亚像素精细化(提高检测精度)
一旦粗略角点位置获得,使用如下函数提高精度:
cornerSubPix(image, corners, winSize, zeroZone, criteria);
- 使用图像灰度插值拟合方法,在小窗口内优化角点位置
- 通常将精度提升到亚像素级别(如 0.01 像素)
4、现代改进算法(可选替代)
1. ChArUco 标定板
- 棋盘格 + ArUco 标记组合(OpenCV 支持)
- 支持部分遮挡和旋转识别
- 用于增强鲁棒性和自动识别性
2. Deep Learning 检测角点(如 D2-Net)
- 用于不规则纹理图案、工业标定板
5、典型完整调用流程(OpenCV)
cv::Mat image = cv::imread("chessboard.jpg", cv::IMREAD_GRAYSCALE);
cv::Size patternSize(9, 6); // 棋盘格内角点数(9列×6行)
std::vector<cv::Point2f> corners;
bool found = cv::findChessboardCorners(image, patternSize, corners,
cv::CALIB_CB_ADAPTIVE_THRESH + cv::CALIB_CB_NORMALIZE_IMAGE);
if (found) {
cv::cornerSubPix(image, corners, cv::Size(11,11), cv::Size(-1,-1),
cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 30, 0.01));
cv::drawChessboardCorners(image, patternSize, corners, found);
cv::imshow("Corners", image);
cv::waitKey();
}
6、角点检测质量判断
- OpenCV 会根据检测匹配质量自动返回 true/false
- 可用
cv::drawChessboardCorners()
可视化确认 - 标定前建议去除检测失败或畸变严重图像
7 、Harris 角点检测的原理
Harris 角点检测算法是图像处理中广泛使用的特征检测算法,特别适用于检测棋盘格、角落、边缘交汇等区域。下面我们将详细讲解 Harris 算法的原理、数学推导过程以及 C++ 实现代码。
7.1、Harris 角点检测的原理
Harris 算法基于图像灰度变化的二阶矩阵来判断某点周围是否是角点、边缘或平坦区域。
➤ 基本思想:
若一个图像窗口向任意方向移动,且图像灰度变化显著,说明该区域是角点。
➤ 数学形式化:
对图像 I ( x , y ) I(x, y) I(x,y),考虑一个窗口移动 ( u , v ) (u, v) (u,v) 后图像灰度差异:
E ( u , v ) = ∑ x , y w ( x , y ) [ I ( x + u , y + v ) − I ( x , y ) ] 2 E(u,v) = \sum_{x,y} w(x,y) [I(x+u, y+v) - I(x, y)]^2 E(u,v)=x,y∑w(x,y)[I(x+u,y+v)−I(x,y)]2
其中:
- w ( x , y ) w(x, y) w(x,y):窗口函数(通常为高斯核)
- ( x , y ) (x, y) (x,y):窗口中的像素
- ( u , v ) (u, v) (u,v):窗口偏移量
7.2、Harris 响应函数推导
步骤 1:泰勒展开近似
I ( x + u , y + v ) ≈ I ( x , y ) + I x u + I y v I(x+u, y+v) \approx I(x, y) + I_x u + I_y v I(x+u,y+v)≈I(x,y)+Ixu+Iyv
代入差值平方:
E ( u , v ) ≈ ∑ x , y w ( x , y ) [ I x u + I y v ] 2 = [ u v ] ∑ w ( x , y ) [ I x 2 I x I y I x I y I y 2 ] ⏟ M [ u v ] = d T M d E(u,v) \approx \sum_{x,y} w(x,y) [I_x u + I_y v]^2 = \begin{bmatrix} u & v \end{bmatrix} \underbrace{ \sum w(x,y) \begin{bmatrix} I_x^2 & I_x I_y \\ I_x I_y & I_y^2 \end{bmatrix} }_{M} \begin{bmatrix} u \\ v \end{bmatrix} = \mathbf{d}^T M \mathbf{d} E(u,v)≈x,y∑w(x,y)[Ixu+Iyv]2=[uv]M ∑w(x,y)[Ix2IxIyIxIyIy2][uv]=dTMd
步骤 2:角点响应矩阵 M M M
M = ∑ w ( x , y ) [ I x 2 I x I y I x I y I y 2 ] M = \sum w(x,y) \begin{bmatrix} I_x^2 & I_x I_y \\ I_x I_y & I_y^2 \end{bmatrix} M=∑w(x,y)[Ix2IxIyIxIyIy2]
这里的 M M M 是图像的结构张量(Structure Tensor),描述局部灰度变化。
步骤 3:特征值分析判断角点类型
- 若 λ 1 , λ 2 \lambda_1, \lambda_2 λ1,λ2 都很大 → 角点
- 若 λ 1 ≫ λ 2 \lambda_1 \gg \lambda_2 λ1≫λ2 或相反 → 边缘
- 若 λ 1 , λ 2 \lambda_1, \lambda_2 λ1,λ2 都小 → 平坦区域
步骤 4:定义角点响应函数(Harris 响应)
R = det ( M ) − k ⋅ ( trace ( M ) ) 2 = λ 1 λ 2 − k ( λ 1 + λ 2 ) 2 R = \det(M) - k \cdot (\operatorname{trace}(M))^2 = \lambda_1 \lambda_2 - k(\lambda_1 + \lambda_2)^2 R=det(M)−k⋅(trace(M))2=λ1λ2−k(λ1+λ2)2
- k ∈ [ 0.04 , 0.06 ] k \in [0.04, 0.06] k∈[0.04,0.06]:经验常数
- 若 R > threshold R > \text{threshold} R>threshold,该点为角点
7.3、C++ 实现示例
下面是一个 原始 Harris 算法 的 C++ 实现(依赖 OpenCV 基础功能):
#include <opencv2/opencv.hpp>
using namespace cv;
int main() {
Mat src = imread("chessboard.jpg", IMREAD_GRAYSCALE);
if (src.empty()) return -1;
Mat src_float;
src.convertTo(src_float, CV_32FC1, 1.0 / 255.0); // 转为 float
// 计算梯度
Mat Ix, Iy;
Sobel(src_float, Ix, CV_32FC1, 1, 0, 3);
Sobel(src_float, Iy, CV_32FC1, 0, 1, 3);
// 计算结构张量的每个元素
Mat Ixx = Ix.mul(Ix);
Mat Iyy = Iy.mul(Iy);
Mat Ixy = Ix.mul(Iy);
// 高斯模糊平滑张量
GaussianBlur(Ixx, Ixx, Size(3, 3), 1.0);
GaussianBlur(Iyy, Iyy, Size(3, 3), 1.0);
GaussianBlur(Ixy, Ixy, Size(3, 3), 1.0);
// 计算角点响应 R
Mat R = Mat::zeros(src.size(), CV_32FC1);
float k = 0.04f;
for (int y = 0; y < src.rows; ++y) {
for (int x = 0; x < src.cols; ++x) {
float a = Ixx.at<float>(y, x);
float b = Ixy.at<float>(y, x);
float c = Iyy.at<float>(y, x);
float det = a * c - b * b;
float trace = a + c;
R.at<float>(y, x) = det - k * trace * trace;
}
}
// 标记大于阈值的角点
Mat R_norm;
normalize(R, R_norm, 0, 255, NORM_MINMAX, CV_32FC1);
Mat R_uint8;
convertScaleAbs(R_norm, R_uint8);
Mat result;
cvtColor(src, result, COLOR_GRAY2BGR);
for (int y = 0; y < R_norm.rows; ++y) {
for (int x = 0; x < R_norm.cols; ++x) {
if (R_norm.at<float>(y, x) > 100.0) {
circle(result, Point(x, y), 3, Scalar(0, 0, 255), 1);
}
}
}
imshow("Harris Corners", result);
waitKey(0);
return 0;
}
7.4、OpenCV 中快速调用方式
Mat dst, dst_norm;
cornerHarris(src, dst, 2, 3, 0.04);
normalize(dst, dst_norm, 0, 255, NORM_MINMAX);
for (int y = 0; y < dst_norm.rows; y++) {
for (int x = 0; x < dst_norm.cols; x++) {
if ((int)dst_norm.at<float>(y, x) > 150)
circle(src_color, Point(x, y), 5, Scalar(0, 0, 255));
}
}
8 、Shi-Tomasi角点检测的原理
Shi-Tomasi 角点检测(也称为 Good Features to Track, GFTT)是 Harris 算法的改进版本,它对特征点的判断更为稳定,并能更好地适应后续的光流跟踪等任务。
8.1、Shi-Tomasi 算法原理
Shi-Tomasi 基于 Harris 的结构张量 M M M,但在角点判断标准上采用了更加直观且稳定的特征值最小值判断:
回顾 Harris 中结构张量:
对于某一点的图像窗口,计算其图像梯度后得到:
M = [ ∑ I x 2 ∑ I x I y ∑ I x I y ∑ I y 2 ] M = \begin{bmatrix} \sum I_x^2 & \sum I_x I_y \\ \sum I_x I_y & \sum I_y^2 \end{bmatrix} M=[∑Ix2∑IxIy∑IxIy∑Iy2]
该矩阵的两个特征值 λ 1 \lambda_1 λ1, λ 2 \lambda_2 λ2 描述窗口内梯度变化的方向性。
Harris 与 Harris 的对比
方法 | 响应函数 R |
---|---|
Harris | R = det ( M ) − k ⋅ ( trace ( M ) ) 2 R = \det(M) - k \cdot (\text{trace}(M))^2 R=det(M)−k⋅(trace(M))2 |
Shi-Tomasi | R = min ( λ 1 , λ 2 ) R = \min(\lambda_1, \lambda_2) R=min(λ1,λ2) |
Shi-Tomasi 判断角点的条件:
- 若两个特征值都大,说明窗口在两个正交方向上都有显著变化(角点);
- 使用较小的特征值做响应,确保两个方向的梯度都强。
8.2、特征值推导与计算
对于矩阵:
M = [ a b b c ] M = \begin{bmatrix} a & b \\ b & c \end{bmatrix} M=[abbc]
特征值为:
λ 1 , 2 = a + c 2 ± ( a − c 2 ) 2 + b 2 \lambda_{1,2} = \frac{a + c}{2} \pm \sqrt{\left( \frac{a - c}{2} \right)^2 + b^2} λ1,2=2a+c±(2a−c)2+b2
因此最小特征值为:
R = min ( λ 1 , λ 2 ) = a + c 2 − ( a − c 2 ) 2 + b 2 R = \min(\lambda_1, \lambda_2) = \frac{a + c}{2} - \sqrt{\left( \frac{a - c}{2} \right)^2 + b^2} R=min(λ1,λ2)=2a+c−(2a−c)2+b2
无需显式求解特征值矩阵,可直接用上式快速计算。
8.3、C++ 实现示例
#include <opencv2/opencv.hpp>
#include <vector>
using namespace cv;
int main() {
Mat src_gray = imread("chessboard.jpg", IMREAD_GRAYSCALE);
if (src_gray.empty()) return -1;
Mat src_color;
cvtColor(src_gray, src_color, COLOR_GRAY2BGR);
// 计算图像梯度
Mat Ix, Iy;
Sobel(src_gray, Ix, CV_32FC1, 1, 0, 3);
Sobel(src_gray, Iy, CV_32FC1, 0, 1, 3);
// 构造结构张量分量
Mat Ixx = Ix.mul(Ix);
Mat Iyy = Iy.mul(Iy);
Mat Ixy = Ix.mul(Iy);
GaussianBlur(Ixx, Ixx, Size(3,3), 1.0);
GaussianBlur(Iyy, Iyy, Size(3,3), 1.0);
GaussianBlur(Ixy, Ixy, Size(3,3), 1.0);
Mat R = Mat::zeros(src_gray.size(), CV_32FC1);
for (int y = 0; y < src_gray.rows; ++y) {
for (int x = 0; x < src_gray.cols; ++x) {
float a = Ixx.at<float>(y, x);
float b = Ixy.at<float>(y, x);
float c = Iyy.at<float>(y, x);
float trace = a + c;
float det = a * c - b * b;
// 最小特征值近似计算
float temp = sqrtf(0.25f * (a - c) * (a - c) + b * b);
float lambda_min = 0.5f * (trace - 2.0f * temp);
R.at<float>(y, x) = lambda_min;
}
}
// 归一化并阈值处理
Mat R_norm;
normalize(R, R_norm, 0, 255, NORM_MINMAX, CV_32FC1);
// 绘制角点
for (int y = 0; y < R_norm.rows; ++y) {
for (int x = 0; x < R_norm.cols; ++x) {
if (R_norm.at<float>(y, x) > 100.0f) {
circle(src_color, Point(x, y), 3, Scalar(0, 255, 0), 1); // 绿色 Shi-Tomasi
}
}
}
imshow("Shi-Tomasi Corners", src_color);
waitKey(0);
return 0;
}
8.4、OpenCV 推荐调用效率更高
使用 OpenCV 提供的封装函数 goodFeaturesToTrack
:
std::vector<Point2f> corners;
goodFeaturesToTrack(src_gray, corners, 100, 0.01, 10);
for (const auto& pt : corners) {
circle(src_color, pt, 3, Scalar(255, 0, 0), 1); // 蓝色角点
}
-
参数说明:
100
:最多提取的角点数量0.01
:质量等级(特征值最小值相对于最大值的比)10
:最小间距
9、Harris 与 Shi-Tomasi 区别
特性 | Harris | Shi-Tomasi |
---|---|---|
响应函数 | 组合特征值 | 最小特征值 |
对噪声鲁棒性 | 一般 | 更好 |
特征稳定性 | 受 k 参数影响 | 无需额外参数 |
光流跟踪表现 | 中 | 更优 |
OpenCV API | cornerHarris | goodFeaturesToTrack |