保存时间:2026/4/3 18:53:52
findChessboardCorners 是 OpenCV 中用于检测棋盘格角点的核心函数,其源代码位于 OpenCV 的 calib3d 模块中。以下是该函数相关的核心实现逻辑(基于 OpenCV 4.x 版本,简化并标注关键部分):opencv/modules/calib3d/src/calibinit.cppbool findChessboardCorners( InputArray image, Size patternSize, OutputArray corners, int flags )
{
// 1. 输入参数检查与初始化
Mat gray;
if (image.channels() == 3)
cvtColor(image, gray, COLOR_BGR2GRAY); // 转为灰度图
else
image.copyTo(gray);
corners.release();
bool patternFound = false;
// 2. 根据不同模式选择检测算法(默认使用快速角点检测)
if (flags & CALIB_CB_ADAPTIVE_THRESH)
{
// 自适应阈值处理,增强对比度
Mat thresh;
adaptiveThreshold(gray, thresh, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 11, 2);
gray = thresh;
}
// 3. 核心角点检测:基于棋盘格的网格结构特征
// 先检测边缘,再寻找交叉点(棋盘格角点是两条正交边缘的交点)
std::vector<Point2f> _corners;
patternFound = findChessboardCornersSimple(gray, patternSize, _corners, flags);
// 4. 亚像素级优化(提高角点精度)
if (patternFound && !(flags & CALIB_CB_DOUBLE_CHECK))
{
cornerSubPix(gray, _corners, Size(11, 11), Size(-1, -1),
TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 30, 0.1));
corners.assign(_corners);
}
return patternFound;
}
// 内部辅助函数:简化版角点检测逻辑
bool findChessboardCornersSimple( const Mat& gray, Size patternSize, std::vector<Point2f>& corners, int flags )
{
int w = patternSize.width, h = patternSize.height;
int total = w * h;
corners.reserve(total);
// 1. 边缘检测(使用 Sobel 算子)
Mat dx, dy;
Sobel(gray, dx, CV_16S, 1, 0, 3); // x 方向边缘
Sobel(gray, dy, CV_16S, 0, 1, 3); // y 方向边缘
// 2. 寻找棋盘格交叉点(核心逻辑)
// 遍历图像,寻找满足“正交边缘交叉”特征的点(网格交点)
// 利用棋盘格的周期性(黑白交替)过滤非角点
for (int y = 1; y < gray.rows - 1; y++)
{
for (int x = 1; x < gray.cols - 1; x++)
{
// 简化的角点判定条件:x和y方向边缘强度均较高,且周围像素存在黑白交替
if (isCornerCandidate(dx, dy, x, y) && checkChessboardPattern(gray, x, y))
{
corners.emplace_back((float)x, (float)y);
if (corners.size() == total) break;
}
}
if (corners.size() == total) break;
}
// 3. 验证角点是否构成网格结构(排序并检查连续性)
return checkCornersOrder(corners, patternSize);
}
// 辅助函数:检查是否为候选角点(基于边缘强度)
bool isCornerCandidate( const Mat& dx, const Mat& dy, int x, int y )
{
int dx_val = abs(dx.at<short>(y, x));
int dy_val = abs(dy.at<short>(y, x));
// 边缘强度阈值(经验值)
return dx_val > 30 && dy_val > 30;
}
// 辅助函数:检查是否符合棋盘格黑白交替模式
bool checkChessboardPattern( const Mat& gray, int x, int y )
{
// 检查周围4个方向的像素是否黑白交替(棋盘格特征)
uchar c = gray.at<uchar>(y, x);
uchar c1 = gray.at<uchar>(y-1, x);
uchar c2 = gray.at<uchar>(y+1, x);
uchar c3 = gray.at<uchar>(y, x-1);
uchar c4 = gray.at<uchar>(y, x+1);
// 简单判定:对角像素颜色差异显著(黑白交替)
return (abs(c1 - c2) > 50) && (abs(c3 - c4) > 50);
}
patternSize 匹配)。cornerSubPix 函数进一步提高角点坐标精度(亚像素级别)。modules/calib3d/src/ 目录,找到 calibinit.cpp 即可查看完整实现。(Gx, Gy) 表示,Gx 是水平方向(x轴)的灰度变化率,Gy 是垂直方向(y轴)的灰度变化率。G = sqrt(Gx² + Gy²)(实际中常用近似值 |Gx| + |Gy| 简化计算)。Gx 和 Gy。[-1, 0, 1]
[-2, 0, 2]
[-1, 0, 1]
[-1, -2, -1]
[0, 0, 0]
[1, 2, 1]
Gx 和 Gy。Gx 和 Gy 计算梯度强度,强度超过阈值的像素被判定为边缘点。Sobel 函数为例)Sobel 函数的定义为:void Sobel(InputArray src, OutputArray dst, int ddepth, int dx, int dy, int ksize=3, double scale=1, double delta=0, int borderType=BORDER_DEFAULT);
src:输入图像(通常为灰度图)。dst:输出图像(存储梯度计算结果)。ddepth:输出图像的深度(数据类型)。由于梯度可能为负值,通常设为 CV_16S(16位有符号整数)避免溢出。dx:x方向的导数阶数(0/1/2),dx=1 表示计算水平方向梯度。dy:y方向的导数阶数(0/1/2),dy=1 表示计算垂直方向梯度。ksize:卷积核大小(必须为奇数,如 3、5、7)。ksize=3 是最常用的 3×3 核;若 ksize=1,则使用 1×3 或 3×1 的简化核。scale:缩放因子(可选),对计算结果进行缩放(如 scale=0.5 表示缩小一半)。delta:偏移量(可选),计算结果加上该值后再存入 dst。findChessboardCorners 等函数中用于定位棋盘格的边缘交叉点(角点)。在实际应用中,常结合阈值处理或非极大值抑制(NMS)进一步优化边缘检测结果。dx=1, dy=0 和 dx=0, dy=1),然后求梯度强度(G = |Gx| + |Gy|)。adaptiveThreshold),让棋盘区域和背景的灰度差异更明显,Sobel提取的边缘会更干净。cornerHarris(角点检测)打分,保留分数高的点,过滤杂线交点。灰度化 → 高斯模糊 → Sobel边缘检测 → 霍夫直线变换 → 求交点 → 角点筛选如果中间某一步效果差(比如Sobel边缘太乱),就针对性调参数(比如加大高斯核尺寸,或收紧阈值)。#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
// 1. 读取图像(替换为你的棋盘图路径)
Mat src = imread("chessboard.jpg");
if (src.empty()) {
cout << "无法读取图像!" << endl;
return -1;
}
imshow("原图", src);
// 2. 预处理:增强对比度,减少光照影响
Mat gray, blur;
cvtColor(src, gray, COLOR_BGR2GRAY); // 转灰度图
GaussianBlur(gray, blur, Size(5, 5), 1.5); // 高斯模糊(核越大,抗模糊越强)
// 3. Sobel 边缘检测:提取边缘骨架
Mat grad_x, grad_y, edge;
// 计算 x 方向梯度(检测垂直边缘)
Sobel(blur, grad_x, CV_16S, 1, 0, 3); // 3x3 核,x 方向一阶导数
// 计算 y 方向梯度(检测水平边缘)
Sobel(blur, grad_y, CV_16S, 0, 1, 3);
// 转换为 uint8 类型,合并 x/y 方向边缘
convertScaleAbs(grad_x, grad_x);
convertScaleAbs(grad_y, grad_y);
addWeighted(grad_x, 0.5, grad_y, 0.5, 0, edge); // 边缘融合(0.5+0.5权重)
// 4. 阈值处理:过滤弱边缘,保留强边缘(关键参数,根据实际调整)
Mat edge_binary;
threshold(edge, edge_binary, 30, 255, THRESH_BINARY); // 阈值30可根据图像明暗调整
imshow("Sobel边缘图", edge_binary);
// 5. 霍夫变换:从边缘中检测直线
vector<Vec2f> lines; // 存储检测到的直线(rho, theta)
HoughLines(edge_binary, lines, 1, CV_PI/180, 150); // 最后一个参数是直线阈值(越高越严格)
// 6. 筛选直线:保留棋盘格的横线和竖线(假设棋盘线近似水平/垂直)
vector<Vec2f> horizontal_lines, vertical_lines;
for (size_t i = 0; i < lines.size(); i++) {
float rho = lines[i][0], theta = lines[i][1];
// 角度接近 0 或 PI(水平直线)
if (theta < CV_PI/18 || theta > CV_PI - CV_PI/18) {
horizontal_lines.push_back(lines[i]);
}
// 角度接近 PI/2(垂直直线)
else if (theta > CV_PI/2 - CV_PI/18 && theta < CV_PI/2 + CV_PI/18) {
vertical_lines.push_back(lines[i]);
}
}
// 7. 计算直线交点(棋盘角点候选)
vector<Point2f> corners;
for (auto& h_line : horizontal_lines) {
float rho_h = h_line[0], theta_h = h_line[1];
for (auto& v_line : vertical_lines) {
float rho_v = v_line[0], theta_v = v_line[1];
// 解方程组求交点(直线方程:x*cosθ + y*sinθ = rho)
double a1 = cos(theta_h), b1 = sin(theta_h), c1 = rho_h;
double a2 = cos(theta_v), b2 = sin(theta_v), c2 = rho_v;
double det = a1*b2 - a2*b1;
if (fabs(det) < 1e-6) continue; // 直线平行,跳过
double x = (b2*c1 - b1*c2) / det;
double y = (a1*c2 - a2*c1) / det;
corners.emplace_back(x, y);
}
}
// 8. 筛选角点:保留在图像范围内的点(简单过滤)
vector<Point2f> valid_corners;
for (auto& p : corners) {
if (p.x > 0 && p.x < src.cols && p.y > 0 && p.y < src.rows) {
valid_corners.push_back(p);
}
}
// 9. 可视化结果
for (auto& p : valid_corners) {
circle(src, p, 5, Scalar(0, 0, 255), -1); // 用红色圆点标记角点
}
imshow("检测结果", src);
waitKey(0);
return 0;
}
GaussianBlur 平滑图像,减少光照不均导致的噪声;Sobel 输出的边缘图通过 threshold 过滤弱边缘,阈值可根据图像明暗调整(暗图调低,亮图调高)。HoughLines 的检测范围(比如先手动框选棋盘大致区域)。Size(5,5))可增大(如 7x7),增强对模糊边缘的平滑效果;HoughLines 的最后一个参数(直线阈值)调低,允许检测到部分断裂的边缘(模糊导致)。GaussianBlur 的核大小:模糊严重时调大(如 9x9);threshold 的阈值:边缘弱时调低(如 20),背景杂多时调高(如 50);HoughLines 的直线阈值:值越大,检测到的直线越少(越严格),可从 100 到 200 尝试。kmeans 聚类,保留密集的点集,因为棋盘角点是规则网格)。试试这个流程,有具体问题可以再细化~#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>
#include <cmath>
using namespace cv;
using namespace std;
// 封装检测函数:输入图像和参数,返回是否成功检测到有效角点及角点坐标
bool detectChessboardCorners(
const Mat& src,
int gaussian_ksize, // 高斯核大小(必须为奇数,如3,5,7...)
int sobel_threshold, // Sobel边缘阈值
int hough_threshold, // 霍夫直线阈值
vector<Point2f>& out_corners // 输出检测到的角点
) {
out_corners.clear();
Mat gray, blur;
cvtColor(src, gray, COLOR_BGR2GRAY);
// 高斯模糊(核大小必须为奇数)
if (gaussian_ksize % 2 == 0) gaussian_ksize++; // 确保奇数
GaussianBlur(gray, blur, Size(gaussian_ksize, gaussian_ksize), 1.5);
// Sobel边缘检测
Mat grad_x, grad_y, edge;
Sobel(blur, grad_x, CV_16S, 1, 0, 3);
Sobel(blur, grad_y, CV_16S, 0, 1, 3);
convertScaleAbs(grad_x, grad_x);
convertScaleAbs(grad_y, grad_y);
addWeighted(grad_x, 0.5, grad_y, 0.5, 0, edge);
// 边缘阈值处理
Mat edge_binary;
threshold(edge, edge_binary, sobel_threshold, 255, THRESH_BINARY);
// 霍夫直线检测
vector<Vec2f> lines;
HoughLines(edge_binary, lines, 1, CV_PI/180, hough_threshold);
if (lines.empty()) return false; // 无直线,直接失败
// 筛选水平/垂直直线
vector<Vec2f> horizontal, vertical;
for (auto& line : lines) {
float theta = line[1];
if (theta < CV_PI/18 || theta > CV_PI - CV_PI/18) { // 水平
horizontal.push_back(line);
} else if (theta > CV_PI/2 - CV_PI/18 && theta < CV_PI/2 + CV_PI/18) { // 垂直
vertical.push_back(line);
}
}
if (horizontal.size() < 2 || vertical.size() < 2) return false; // 直线太少,失败
// 计算交点(角点候选)
vector<Point2f> corners;
for (auto& h : horizontal) {
double a1 = cos(h[1]), b1 = sin(h[1]), c1 = h[0];
for (auto& v : vertical) {
double a2 = cos(v[1]), b2 = sin(v[1]), c2 = v[0];
double det = a1*b2 - a2*b1;
if (fabs(det) < 1e-6) continue;
double x = (b2*c1 - b1*c2) / det;
double y = (a1*c2 - a2*c1) / det;
if (x > 0 && x < src.cols && y > 0 && y < src.rows) {
corners.emplace_back(x, y);
}
}
}
if (corners.size() < 4) return false; // 角点太少,失败
// 筛选:保留最可能的4个角点(假设为矩形的四个顶点)
// 思路:计算所有点的最小外接矩形,取其四个顶点
RotatedRect rrect = minAreaRect(corners);
Point2f vertices[4];
rrect.points(vertices);
for (int i = 0; i < 4; i++) {
out_corners.push_back(vertices[i]);
}
return true; // 成功检测到4个角点
}
// 参数遍历函数:尝试所有可能的参数组合,返回第一个成功的结果
bool findChessboardWithParamSearch(
const Mat& src,
vector<Point2f>& final_corners,
// 参数遍历范围(可根据实际场景调整)
const vector<int>& gaussian_ksizes = {3,5,7,9}, // 高斯核大小范围
const vector<int>& sobel_thresholds = {20,30,40,50}, // Sobel阈值范围
const vector<int>& hough_thresholds = {100,120,150} // 霍夫直线阈值范围
) {
// 遍历所有参数组合
for (int gk : gaussian_ksizes) {
for (int st : sobel_thresholds) {
for (int ht : hough_thresholds) {
cout << "尝试参数:高斯核=" << gk << ", Sobel阈值=" << st << ", 霍夫阈值=" << ht << endl;
vector<Point2f> corners;
if (detectChessboardCorners(src, gk, st, ht, corners)) {
final_corners = corners;
cout << "参数组合成功!" << endl;
return true;
}
}
}
}
cout << "所有参数组合均失败" << endl;
return false;
}
int main() {
// 读取图像(替换为你的图像路径)
Mat src = imread("chessboard.jpg");
if (src.empty()) {
cout << "无法读取图像!" << endl;
return -1;
}
// 调用参数遍历检测函数
vector<Point2f> corners;
bool success = findChessboardWithParamSearch(src, corners);
// 可视化结果
if (success) {
for (auto& p : corners) {
circle(src, p, 5, Scalar(0,255,0), -1); // 绿色标记成功检测的角点
}
putText(src, "Found 4 corners", Point(20,30), FONT_HERSHEY_SIMPLEX, 1, Scalar(0,255,0), 2);
} else {
putText(src, "Detection failed", Point(20,30), FONT_HERSHEY_SIMPLEX, 1, Scalar(0,0,255), 2);
}
imshow("结果", src);
waitKey(0);
return 0;
}
detectChessboardCorners 函数中,输入三个关键参数(高斯核大小、Sobel 阈值、霍夫阈值),输出是否成功及角点坐标。findChessboardWithParamSearch 函数遍历所有参数组合,按顺序尝试,一旦找到成功的组合就返回(避免无效计算)。gaussian_ksizes、sobel_thresholds、hough_thresholds),比如:11,13);10,15);160,180)。cornerSubPix 提高精度),根据你的需求再调整即可~#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>
#include <cmath>
using namespace cv;
using namespace std;
// 计算两点间距离
double distanceBetweenPoints(const Point2f& p1, const Point2f& p2) {
double dx = p1.x - p2.x;
double dy = p1.y - p2.y;
return sqrt(dx*dx + dy*dy);
}
// 检验四个点是否构成正方形(允许一定误差)
bool isSquare(const vector<Point2f>& corners, double eps = 0.15) {
if (corners.size() != 4) return false; // 必须是4个点
// 计算四条边的长度
double d1 = distanceBetweenPoints(corners[0], corners[1]);
double d2 = distanceBetweenPoints(corners[1], corners[2]);
double d3 = distanceBetweenPoints(corners[2], corners[3]);
double d4 = distanceBetweenPoints(corners[3], corners[0]);
// 计算两条对角线的长度
double diag1 = distanceBetweenPoints(corners[0], corners[2]);
double diag2 = distanceBetweenPoints(corners[1], corners[3]);
// 正方形条件1:四边长度接近(误差在eps范围内)
double avg_side = (d1 + d2 + d3 + d4) / 4;
if (fabs(d1 - avg_side) > avg_side * eps ||
fabs(d2 - avg_side) > avg_side * eps ||
fabs(d3 - avg_side) > avg_side * eps ||
fabs(d4 - avg_side) > avg_side * eps) {
return false;
}
// 正方形条件2:两条对角线长度接近(误差在eps范围内)
if (fabs(diag1 - diag2) > diag1 * eps) {
return false;
}
// 正方形条件3:对角线长度约为边长的√2倍(1.414倍,允许误差)
double expected_diag = avg_side * 1.414;
if (fabs(diag1 - expected_diag) > expected_diag * eps) {
return false;
}
return true;
}
// 封装检测函数(包含正方形检验)
bool detectChessboardCorners(
const Mat& src,
int gaussian_ksize,
int sobel_threshold,
int hough_threshold,
vector<Point2f>& out_corners
) {
out_corners.clear();
Mat gray, blur;
cvtColor(src, gray, COLOR_BGR2GRAY);
// 高斯模糊(确保核为奇数)
if (gaussian_ksize % 2 == 0) gaussian_ksize++;
GaussianBlur(gray, blur, Size(gaussian_ksize, gaussian_ksize), 1.5);
// Sobel边缘检测
Mat grad_x, grad_y, edge;
Sobel(blur, grad_x, CV_16S, 1, 0, 3);
Sobel(blur, grad_y, CV_16S, 0, 1, 3);
convertScaleAbs(grad_x, grad_x);
convertScaleAbs(grad_y, grad_y);
addWeighted(grad_x, 0.5, grad_y, 0.5, 0, edge);
// 边缘阈值处理
Mat edge_binary;
threshold(edge, edge_binary, sobel_threshold, 255, THRESH_BINARY);
// 霍夫直线检测
vector<Vec2f> lines;
HoughLines(edge_binary, lines, 1, CV_PI/180, hough_threshold);
if (lines.empty()) return false;
// 筛选水平/垂直直线
vector<Vec2f> horizontal, vertical;
for (auto& line : lines) {
float theta = line[1];
if (theta < CV_PI/18 || theta > CV_PI - CV_PI/18) { // 水平
horizontal.push_back(line);
} else if (theta > CV_PI/2 - CV_PI/18 && theta < CV_PI/2 + CV_PI/18) { // 垂直
vertical.push_back(line);
}
}
if (horizontal.size() < 2 || vertical.size() < 2) return false;
// 计算交点(角点候选)
vector<Point2f> corners;
for (auto& h : horizontal) {
double a1 = cos(h[1]), b1 = sin(h[1]), c1 = h[0];
for (auto& v : vertical) {
double a2 = cos(v[1]), b2 = sin(v[1]), c2 = v[0];
double det = a1*b2 - a2*b1;
if (fabs(det) < 1e-6) continue;
double x = (b2*c1 - b1*c2) / det;
double y = (a1*c2 - a2*c1) / det;
if (x > 0 && x < src.cols && y > 0 && y < src.rows) {
corners.emplace_back(x, y);
}
}
}
if (corners.size() < 4) return false;
// 提取最小外接矩形的四个顶点(作为候选角点)
RotatedRect rrect = minAreaRect(corners);
Point2f vertices[4];
rrect.points(vertices);
out_corners = vector<Point2f>(vertices, vertices + 4);
// 关键:检验这四个点是否构成正方形
return isSquare(out_corners, 0.2); // 允许20%的误差(可调整)
}
// 参数遍历函数(不变)
bool findChessboardWithParamSearch(
const Mat& src,
vector<Point2f>& final_corners,
const vector<int>& gaussian_ksizes = {3,5,7,9},
const vector<int>& sobel_thresholds = {20,30,40,50},
const vector<int>& hough_thresholds = {100,120,150}
) {
for (int gk : gaussian_ksizes) {
for (int st : sobel_thresholds) {
for (int ht : hough_thresholds) {
cout << "尝试参数:高斯核=" << gk << ", Sobel阈值=" << st << ", 霍夫阈值=" << ht << endl;
vector<Point2f> corners;
if (detectChessboardCorners(src, gk, st, ht, corners)) {
final_corners = corners;
cout << "参数组合成功!" << endl;
return true;
}
}
}
}
cout << "所有参数组合均失败" << endl;
return false;
}
int main() {
Mat src = imread("chessboard.jpg");
if (src.empty()) {
cout << "无法读取图像!" << endl;
return -1;
}
vector<Point2f> corners;
bool success = findChessboardWithParamSearch(src, corners);
// 可视化结果
if (success) {
// 连接四个点,显示正方形
for (int i = 0; i < 4; i++) {
line(src, corners[i], corners[(i+1)%4], Scalar(0,255,0), 2);
circle(src, corners[i], 5, Scalar(0,0,255), -1);
}
putText(src, "正方形角点检测成功", Point(20,30), FONT_HERSHEY_SIMPLEX, 1, Scalar(0,255,0), 2);
} else {
putText(src, "未检测到有效正方形", Point(20,30), FONT_HERSHEY_SIMPLEX, 1, Scalar(0,0,255), 2);
}
imshow("结果", src);
waitKey(0);
return 0;
}
isSquare 函数: eps,默认20%),应对图像畸变或检测误差。detectChessboardCorners 中,提取四个候选角点后,必须通过 isSquare 检验才返回成功,避免误判非正方形的四个点。eps 可根据实际情况调整:棋盘畸变小时调小(如0.15),畸变大时调大(如0.25)。 isSquare 为检验“矩形”(只需前两个条件:对边相等、对角线相等)。cv::adaptiveThreshold(gray, thresh, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY_INV, 11, 2);
Size(5, 5),可能会更有效地消除噪声。cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
cv::morphologyEx(thresh, thresh, cv::MORPH_CLOSE, kernel, cv::Point(-1, -1), 1);
cv::medianBlur(gray, gray, 5);
minLineLength设为30或更大。std::vector<cv::Vec4i> lines;
cv::HoughLinesP(thresh, lines, 1, CV_PI / 180, 50, 30, 10);
#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>
int main() {
cv::Mat image = cv::imread("your_image_path.jpg");
if (image.empty()) {
std::cout << "Could not open or find the image!" << std::endl;
return -1;
}
cv::Mat gray;
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
// 中值滤波
cv::medianBlur(gray, gray, 5);
// 自适应阈值处理
cv::Mat thresh;
cv::adaptiveThreshold(gray, thresh, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY_INV, 11, 2);
// 形态学闭运算
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
cv::morphologyEx(thresh, thresh, cv::MORPH_CLOSE, kernel, cv::Point(-1, -1), 1);
// 霍夫变换检测直线
std::vector<cv::Vec4i> lines;
cv::HoughLinesP(thresh, lines, 1, CV_PI / 180, 50, 30, 10);
// 线段聚类与筛选
std::vector<cv::Vec4i> horizontalLines, verticalLines;
for (const auto& line : lines) {
float angle = std::atan2(line[3] - line[1], line[2] - line[0]) * 180 / CV_PI;
if (std::abs(angle) < 10 || std::abs(angle - 180) < 10) {
horizontalLines.push_back(line);
} else if (std::abs(angle - 90) < 10 || std::abs(angle - 270) < 10) {
verticalLines.push_back(line);
}
}
// 显示结果
cv::Mat result = image.clone();
for (const auto& line : horizontalLines) {
cv::line(result, cv::Point(line[0], line[1]), cv::Point(line[2], line[3]), cv::Scalar(0, 0, 255), 2);
}
for (const auto& line : verticalLines) {
cv::line(result, cv::Point(line[0], line[1]), cv::Point(line[2], line[3]), cv::Scalar(0, 255, 0), 2);
}
cv::imshow("Result", result);
cv::waitKey(0);
return 0;
}
HoughLinesP函数用于执行概率霍夫线变换,它是标准霍夫线变换的改进版本,主要用于在图像中检测直线。该函数的定义如下:void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength = 0, double maxLineGap = 0);
image (InputArray类型):edge_image,就可以将edge_image作为该参数传入。lines (OutputArray类型):Vec4i类型的向量形式存储。对于每条检测到的直线,Vec4i包含四个元素(x1, y1, x2, y2),分别表示直线的起点坐标(x1, y1)和终点坐标(x2, y2) 。std::vector<cv::Vec4i> detected_lines;,然后将detected_lines作为该参数传入,函数执行后,detected_lines中就会存储检测到的直线信息。rho (double类型):1 ,如果想更精细地搜索距离,可以设置为0.5等更小的值,但会增加计算量。theta (double类型):CV_PI/180,表示每次在角度方向上增加1度(转换为弧度)进行投票。cv::HoughLinesP(..., 1, CV_PI/180,...),表示距离分辨率为1像素,角度分辨率为1度。threshold (int类型):50,那么只有在累加器中投票数达到50及以上的直线才会被输出。minLineLength (double类型,默认值为0):minLineLength设置为30。maxLineGap (double类型,默认值为0):10,那么只要两条线段之间的距离小于10像素,就会被合并成一条直线。HoughLinesP函数的这些参数,可以在不同的图像中更准确、有效地检测出所需的直线。 #include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>
#include <cmath>
using namespace cv;
using namespace std;
// 计算两点间距离
double distance(const Point2f& p1, const Point2f& p2) {
return sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2));
}
// 寻找棋盘交叉点(网格检测核心函数)
vector<Point2f> detectGridIntersections(const Mat& src, int patternSize = 19) {
// 1. 预处理:转为灰度图并增强对比度
Mat gray, blur;
cvtColor(src, gray, COLOR_BGR2GRAY);
GaussianBlur(gray, blur, Size(5, 5), 1.5);
// 2. 使用自适应阈值处理,增强棋盘线
Mat thresh;
adaptiveThreshold(blur, thresh, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 11, 2);
// 3. 使用HoughLinesP检测直线
vector<Vec4i> lines;
HoughLinesP(thresh, lines, 1, CV_PI/180, 50, 30, 10);
// 4. 将直线分类为横线和竖线(基于角度)
vector<Vec4i> horizontalLines, verticalLines;
for (const auto& line : lines) {
float dx = line[2] - line[0];
float dy = line[3] - line[1];
float angle = atan2(dy, dx) * 180 / CV_PI;
if (abs(angle) < 10 || abs(angle - 180) < 10) {
horizontalLines.push_back(line);
} else if (abs(angle - 90) < 10 || abs(angle - 270) < 10) {
verticalLines.push_back(line);
}
}
// 5. 计算横线和竖线的交点(候选交叉点)
vector<Point2f> intersections;
for (const auto& hLine : horizontalLines) {
for (const auto& vLine : verticalLines) {
// 计算两条线的交点
Point2f intersection;
float denominator = (vLine[2] - vLine[0]) * (hLine[3] - hLine[1]) -
(vLine[3] - vLine[1]) * (hLine[2] - hLine[0]);
if (denominator != 0) {
float ua = ((hLine[0] - vLine[0]) * (hLine[3] - hLine[1]) -
(hLine[1] - vLine[1]) * (hLine[2] - hLine[0])) / denominator;
intersection.x = vLine[0] + ua * (vLine[2] - vLine[0]);
intersection.y = vLine[1] + ua * (vLine[3] - vLine[1]);
// 检查交点是否在图像范围内
if (intersection.x > 0 && intersection.x < src.cols &&
intersection.y > 0 && intersection.y < src.rows) {
intersections.push_back(intersection);
}
}
}
}
// 6. 验证每个交叉点周围是否有网格特征
vector<Point2f> validIntersections;
for (const auto& intersection : intersections) {
if (verifyGridPattern(thresh, intersection)) {
validIntersections.push_back(intersection);
}
}
// 7. 基于规则网格结构筛选最佳交叉点集合
// (此处省略复杂的聚类和筛选逻辑,实际应用中需要实现)
return validIntersections;
}
// 验证交叉点周围是否有网格模式(核心函数)
bool verifyGridPattern(const Mat& binaryImage, const Point2f& center, int windowSize = 21) {
// 窗口大小必须是奇数
if (windowSize % 2 == 0) windowSize++;
// 确保窗口在图像范围内
int halfSize = windowSize / 2;
if (center.x - halfSize < 0 || center.x + halfSize >= binaryImage.cols ||
center.y - halfSize < 0 || center.y + halfSize >= binaryImage.rows) {
return false;
}
// 提取以center为中心的窗口
Rect window(center.x - halfSize, center.y - halfSize, windowSize, windowSize);
Mat roi = binaryImage(window);
// 计算水平和垂直方向的投影直方图
vector<int> horizontalProjection(windowSize, 0);
vector<int> verticalProjection(windowSize, 0);
for (int y = 0; y < windowSize; y++) {
for (int x = 0; x < windowSize; x++) {
if (roi.at<uchar>(y, x) > 128) { // 白色像素(线条)
horizontalProjection[y]++;
verticalProjection[x]++;
}
}
}
// 验证水平方向是否有两条明显的线(横线)
bool hasHorizontalLines = countPeaks(horizontalProjection) >= 2;
// 验证垂直方向是否有两条明显的线(竖线)
bool hasVerticalLines = countPeaks(verticalProjection) >= 2;
return hasHorizontalLines && hasVerticalLines;
}
// 计算投影直方图中的峰值数量(用于判断是否有线条)
int countPeaks(const vector<int>& projection, int thresholdRatio = 0.5) {
int maxVal = *max_element(projection.begin(), projection.end());
int threshold = maxVal * thresholdRatio;
int peakCount = 0;
bool inPeak = false;
for (int val : projection) {
if (val >= threshold) {
if (!inPeak) {
peakCount++;
inPeak = true;
}
} else {
inPeak = false;
}
}
return peakCount;
}
verifyGridPattern 函数通过在交叉点周围提取一个小窗口,分析窗口内的黑白像素分布。windowSize:验证窗口的大小,应根据棋盘格子大小调整。thresholdRatio:判断峰值的阈值比例,影响线条检测的灵敏度。cornerSubPix 函数进行亚像素级优化,提高定位精度。cv::polylines函数用于在图像上绘制多个多边形或折线,其函数定义如下:void polylines(InputOutputArray img, InputArrayOfArrays pts, bool isClosed, const Scalar& color, int thickness = 1, int lineType = LINE_8, int shift = 0);
img (InputOutputArray 类型)CV_8UC3,表示 8 位无符号 3 通道彩色图像 ),也可以是灰度图像(如CV_8UC1,表示 8 位无符号单通道灰度图像)。函数执行后,会在该图像上直接绘制线条,修改图像内容。Mat image = imread("test.jpg");,就可以将image作为该参数传入,如cv::polylines(image,...) ,后续绘制的多边形或折线就会显示在这张图像上。pts (InputArrayOfArrays 类型)std::vector<cv::Point> trianglePoints = { cv::Point(100, 100), cv::Point(200, 100), cv::Point(150, 200) };,如果有多个多边形,就需要将多个这样的子数组存储在一个更大的数组中,如std::vector<std::vector<cv::Point>> allPolygons = {trianglePoints, anotherTrianglePoints,...}; ,然后将allPolygons作为pts参数传入。std::vector<cv::Point> rectanglePoints = { cv::Point(50, 50), cv::Point(150, 50), cv::Point(150, 150), cv::Point(50, 150) };
std::vector<std::vector<cv::Point>> polygons = {rectanglePoints};
cv::polylines(image, polygons,...);
isClosed (bool 类型)isClosed为true,则绘制的多边形会自动连接第一个顶点和最后一个顶点,形成一个封闭的图形;如果为false,则绘制的是不封闭的折线。isClosed可以设置为true ,即cv::polylines(image, polygons, true,...); ;如果绘制一条折线,比如从一个点到另一个点的路径,就可以设置为false,如cv::polylines(image, anotherPolylinePoints, false,...); 。color (const Scalar & 类型)cv::Scalar来表示颜色,格式为cv::Scalar(B, G, R) ,分别对应蓝色、绿色、红色通道的值,取值范围是 0 - 255。例如,cv::Scalar(0, 0, 255)表示红色;对于灰度图像,cv::Scalar表示灰度值,取值范围同样是 0 - 255。cv::polylines(image, polygons, true, cv::Scalar(255, 0, 0),...); 。thickness (int 类型,默认值为 1)thickness设置为一个较大的值,如cv::polylines(image, polygons, true, cv::Scalar(0, 255, 0), 3,...); ,此时线条的粗细为 3 像素。lineType (int 类型,默认值为 LINE_8)cv::LINE_4:表示 4 连通线,线条由 4 个方向的像素连接而成,速度较快,但可能会有锯齿。cv::LINE_8:表示 8 连通线,线条由 8 个方向的像素连接而成,是默认值,比LINE_4更平滑。cv::LINE_AA:表示抗锯齿线,通过插值算法生成更平滑的线条,视觉效果最好,但计算量相对较大。cv::polylines(image, polygons, true, cv::Scalar(0, 0, 255), 1, cv::LINE_AA,...); 。shift (int 类型,默认值为 0)shift = 1表示顶点坐标值需要除以 2(因为 2 的 1 次方是 2)来得到实际的坐标值;通常情况下,顶点坐标是整数形式,此时shift设置为 0 即可。0 ,即cv::polylines(image, polygons, true, cv::Scalar(255, 255, 0), 2,...); 。cv::polylines函数的这些参数,就可以在图像上绘制出符合需求的多边形或折线图形。