GrabBag/Module/ChessboardDetector/Src/ChessboardDetector.cpp

215 lines
6.7 KiB
C++
Raw Permalink Normal View History

2026-02-18 15:11:41 +08:00
#include "ChessboardDetector.h"
#include <opencv2/opencv.hpp>
#include <cmath>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
ChessboardDetector::ChessboardDetector()
: m_adaptiveThresh(true)
, m_normalizeImage(true)
, m_filterQuads(false)
{
}
ChessboardDetector::~ChessboardDetector()
{
}
int ChessboardDetector::DetectChessboard(
const unsigned char* imageData,
int width,
int height,
int channels,
int patternWidth,
int patternHeight,
ChessboardDetectResult& result)
{
if (!imageData || width <= 0 || height <= 0) {
return -1;
}
result.detected = false;
result.corners.clear();
result.patternWidth = patternWidth;
result.patternHeight = patternHeight;
result.hasPose = false;
try {
// 创建 OpenCV Mat
cv::Mat image;
if (channels == 1) {
image = cv::Mat(height, width, CV_8UC1, (void*)imageData).clone();
} else if (channels == 3) {
image = cv::Mat(height, width, CV_8UC3, (void*)imageData).clone();
cv::cvtColor(image, image, cv::COLOR_BGR2GRAY);
} else {
return -2;
}
// 检测标定板角点
cv::Size patternSize(patternWidth, patternHeight);
std::vector<cv::Point2f> corners;
int flags = 0;
if (m_adaptiveThresh) flags |= cv::CALIB_CB_ADAPTIVE_THRESH;
if (m_normalizeImage) flags |= cv::CALIB_CB_NORMALIZE_IMAGE;
if (m_filterQuads) flags |= cv::CALIB_CB_FILTER_QUADS;
bool found = cv::findChessboardCorners(image, patternSize, corners, flags);
if (found && corners.size() == patternWidth * patternHeight) {
// 亚像素精细化
cv::cornerSubPix(image, corners, cv::Size(11, 11), cv::Size(-1, -1),
cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30, 0.1));
result.detected = true;
result.corners.reserve(corners.size());
for (const auto& pt : corners) {
result.corners.push_back(Point2D(pt.x, pt.y));
}
}
return 0;
}
catch (const cv::Exception& e) {
return -3;
}
}
int ChessboardDetector::DetectChessboardWithPose(
const unsigned char* imageData,
int width,
int height,
int channels,
int patternWidth,
int patternHeight,
double squareSize,
const CameraIntrinsics& intrinsics,
ChessboardDetectResult& result)
{
// 首先检测角点
int ret = DetectChessboard(imageData, width, height, channels,
patternWidth, patternHeight, result);
if (ret != 0 || !result.detected) {
return ret;
}
try {
// 构建3D物体点标定板坐标系
std::vector<cv::Point3f> objectPoints;
for (int i = 0; i < patternHeight; i++) {
for (int j = 0; j < patternWidth; j++) {
objectPoints.push_back(cv::Point3f(j * squareSize, i * squareSize, 0));
}
}
// 构建2D图像点
std::vector<cv::Point2f> imagePoints;
for (const auto& pt : result.corners) {
imagePoints.push_back(cv::Point2f(pt.x, pt.y));
}
// 相机内参矩阵
cv::Mat cameraMatrix = (cv::Mat_<double>(3, 3) <<
intrinsics.fx, 0, intrinsics.cx,
0, intrinsics.fy, intrinsics.cy,
0, 0, 1);
// 畸变系数
cv::Mat distCoeffs = (cv::Mat_<double>(5, 1) <<
intrinsics.k1, intrinsics.k2, intrinsics.p1, intrinsics.p2, intrinsics.k3);
// 求解PnP
cv::Mat rvec, tvec;
bool success = cv::solvePnP(objectPoints, imagePoints, cameraMatrix, distCoeffs,
rvec, tvec, false, cv::SOLVEPNP_ITERATIVE);
if (success) {
result.hasPose = true;
// 保存旋转向量和平移向量
result.rvec[0] = rvec.at<double>(0);
result.rvec[1] = rvec.at<double>(1);
result.rvec[2] = rvec.at<double>(2);
result.tvec[0] = tvec.at<double>(0);
result.tvec[1] = tvec.at<double>(1);
result.tvec[2] = tvec.at<double>(2);
// 标定板中心位置(平移向量就是标定板原点在相机坐标系下的位置)
result.center.x = tvec.at<double>(0);
result.center.y = tvec.at<double>(1);
result.center.z = tvec.at<double>(2);
// 将旋转向量转换为旋转矩阵
cv::Mat rotMat;
cv::Rodrigues(rvec, rotMat);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
result.rotationMatrix[i * 3 + j] = rotMat.at<double>(i, j);
}
}
// 标定板法向量标定板坐标系的Z轴在相机坐标系下的方向
// 旋转矩阵的第三列就是标定板Z轴方向
result.normal.x = rotMat.at<double>(0, 2);
result.normal.y = rotMat.at<double>(1, 2);
result.normal.z = rotMat.at<double>(2, 2);
// 计算欧拉角 (ZYX顺序)
double sy = sqrt(rotMat.at<double>(0, 0) * rotMat.at<double>(0, 0) +
rotMat.at<double>(1, 0) * rotMat.at<double>(1, 0));
bool singular = sy < 1e-6;
double roll, pitch, yaw;
if (!singular) {
roll = atan2(rotMat.at<double>(2, 1), rotMat.at<double>(2, 2));
pitch = atan2(-rotMat.at<double>(2, 0), sy);
yaw = atan2(rotMat.at<double>(1, 0), rotMat.at<double>(0, 0));
} else {
roll = atan2(-rotMat.at<double>(1, 2), rotMat.at<double>(1, 1));
pitch = atan2(-rotMat.at<double>(2, 0), sy);
yaw = 0;
}
// 转换为度
result.eulerAngles[0] = roll * 180.0 / M_PI;
result.eulerAngles[1] = pitch * 180.0 / M_PI;
result.eulerAngles[2] = yaw * 180.0 / M_PI;
}
return 0;
}
catch (const cv::Exception& e) {
return -4;
}
}
int ChessboardDetector::SetDetectionFlags(
bool adaptiveThresh,
bool normalizeImage,
bool filterQuads)
{
m_adaptiveThresh = adaptiveThresh;
m_normalizeImage = normalizeImage;
m_filterQuads = filterQuads;
return 0;
}
// 工厂函数实现
IChessboardDetector* CreateChessboardDetectorInstance()
{
return new ChessboardDetector();
}
void DestroyChessboardDetectorInstance(IChessboardDetector* instance)
{
if (instance) {
delete instance;
}
}