3. 角点检测原理:OpenCV中的findChessboardCorners与cornerSubPix详解

做镜头畸变校正,第一步就是找到标定板上的角点。

说白了,你得先告诉算法:「喏,这些点是棋盘格的交叉点,你按这个来算畸变参数。」

OpenCV 给了我们两个主力函数:findChessboardCornerscornerSubPix。一个负责粗定位,一个负责精调。我刚开始接触标定时,以为第一个函数就够用了,结果校正出来的图像边缘总是有细微扭曲……后来才发现,少了亚像素精度的这一步,精度根本不够。

3.1 findChessboardCorners:粗定位是怎么工作的?

这个函数的核心思路,其实挺直观的。它会在图像里搜索棋盘格的黑白交叉点。

具体流程大致是这样的:

  1. 二值化与阈值处理:先把图像转成灰度图,然后用自适应阈值把它变成黑白分明的二值图。
  2. 四边形检测:在二值图里找轮廓,筛选出那些像棋盘格小方块的四边形区域。
  3. 角点候选生成:找到这些四边形的顶点,作为候选角点。
  4. 模式匹配与排序:根据你指定的棋盘格尺寸(比如 9x6),把这些候选点按行列顺序排好。

嗯,这里要注意:这个函数对光照和对比度比较敏感。我在项目中遇到过,如果标定板表面有反光,或者光线太暗,它经常找不到角点,或者找到的角点顺序是乱的。

核心参数说明:

  • patternSize:棋盘格的内角点数,比如 (9,6) 表示每行9个内角点,每列6个。注意不是格子数,是交叉点数。
  • flags:可以传 CALIB_CB_ADAPTIVE_THRESH 让函数自己调阈值,或者 CALIB_CB_NORMALIZE_IMAGE 先做归一化再处理。

我的小技巧: 如果发现角点检测失败,试试把 flags 设为 CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE。这两个组合能解决大部分光照不均的问题。

3.2 cornerSubPix:为什么要做亚像素精调?

你想想看,findChessboardCorners 给出的角点坐标是整数像素级别的。但真实世界中的角点,往往落在像素之间的某个位置。

举个例子,一个角点可能在像素 (100.3, 200.7) 的位置。如果只用整数坐标去算畸变参数,误差会累积,最终校正出来的图像边缘就会「发虚」或者「扭曲」。

这就是 cornerSubPix 的用武之地。它能把整数坐标精调到亚像素级别,精度可以达到 0.1 像素甚至更高。

它的原理其实也不复杂:

  • 在粗定位的角点周围,取一个小窗口。
  • 在这个窗口里,计算图像的梯度方向。
  • 利用梯度方向的信息,通过迭代优化,找到那个「梯度变化最剧烈」的精确位置。

我记得有一次做高精度工业检测,用整数坐标校正后,测量出来的物体尺寸总是差那么 0.02 毫米。后来加上 cornerSubPix,问题就解决了。说白了,精度就是抠出来的。

注意: cornerSubPix 的迭代次数和精度阈值要设好。我见过有人把迭代次数设成 100,结果跑起来慢得不行。一般 30 次以内就够了。

3.3 两个函数的配合使用流程

在实际代码里,这两个函数是成对出现的。流程大概是:

import cv2
import numpy as np

# 读取图像
img = cv2.imread('chessboard.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 1. 粗定位角点
ret, corners = cv2.findChessboardCorners(gray, (9, 6), None)

if ret:
    # 2. 亚像素精调
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
    corners_sub = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
    
    # 3. 绘制角点(可选)
    cv2.drawChessboardCorners(img, (9, 6), corners_sub, ret)

你看,代码其实很短。但每一步都有讲究。

3.4 参数调优经验

我整理了一个表格,方便你对照调整:

参数 推荐值 说明
winSize (cornerSubPix) (11, 11) 或 (5, 5) 搜索窗口大小。窗口越大,精度越高,但速度越慢。我一般用 (11, 11)。
zeroZone (-1, -1) 一般设 -1,表示不排除任何区域。
criteria (EPS + MAX_ITER, 30, 0.001) 迭代 30 次,精度到 0.001 像素。够用了。
flags (findChessboard) ADAPTIVE_THRESH + NORMALIZE 应对光照不均的「黄金组合」。

避坑指南: 我曾经在标定板上贴了透明胶带,结果反光太强,findChessboardCorners 死活找不到角点。后来把胶带撕掉,用柔光灯从侧面打光,一次就成功了。所以,物理条件也很重要。

3.5 核心逻辑流程图

下面这张 SVG 图,把整个角点检测的流程串起来了。你可以对照着看,心里会更清楚:

角点检测核心流程 输入图像 灰度图 / 彩色图 findChessboardCorners 粗定位(整数像素) cornerSubPix 亚像素精调 检测成功? 输出亚像素角点 用于畸变参数计算 调整参数 或改善光照 重新尝试 关键参数:patternSize, winSize, criteria, flags 💡 经验:先用 findChessboardCorners 粗定位,再用 cornerSubPix 精调,缺一不可 光照均匀、标定板平整,能省掉你 80% 的调试时间

3.6 常见问题与排查思路

做角点检测时,你可能会遇到这些问题。我按经验排了个序:

  1. 找不到角点:先检查棋盘格尺寸参数对不对。然后看光照是否均匀。最后试试加 CALIB_CB_ADAPTIVE_THRESH 标志。
  2. 角点顺序错乱:通常是棋盘格图案不对称,或者标定板有遮挡。确保棋盘格完整出现在画面里。
  3. 精调后角点偏移:检查 winSize 是不是太小了。如果窗口太小,亚像素算法找不到足够的梯度信息。
  4. 程序跑得慢:把 criteria 里的迭代次数降到 20,或者缩小 winSize

重要提醒: 不要在一张图像上反复跑 cornerSubPix。它每次都会重新计算,但结果不会变。跑一次就够了。

好了,角点检测的原理和实战细节,基本就这些。你只要把这两个函数用熟,标定工作的地基就算打牢了。下一章我们会聊怎么用这些角点去算畸变系数,到时候你会发现,前面的精调工作,每一分努力都值。


公众号:蓝海资料掘金营,微信deep3321