当前位置: 首页 > news >正文

棋盘格角点检测顺序问题

文章目录

  • 前言
  • 一、OpenCV函数测试
  • 二、原因分析
  • 三、libcbdetect修改
  • 总结


前言

棋盘格角点检测在相机拼接、机械臂手眼标定中等应用很广泛,通常也要求尽量各种角度摆放从而保证标定精度。然后就自然想到了这个问题:如果棋盘格任意角度摆放怎么能对应上角点的顺序?如图所示(原来是8x6的棋盘格,我划掉了一列,变成了7x6):在这里插入图片描述
在这里插入图片描述


一、OpenCV函数测试

为了解决这个困惑,先用OpenCV自带的棋盘格检测试试,打印一下角点序号:

void onChange(int angle, void* userdata)
{Mat img = *(Mat*)userdata;Mat img_rotate;cv::Point2f center(img.cols / 2.0, img.rows / 2.0);cv::Mat rot_mat = cv::getRotationMatrix2D(center, angle, 1.0);cv::warpAffine(img, img_rotate, rot_mat, img.size());vector<Point2f> points;findChessboardCorners(img_rotate, Size(7, 6), points);for(int i=0;i<points.size();i++){//circle(img_rotate, points[i], 5, Scalar(0, 0, 255), 2, 8, 0);putText(img_rotate, to_string(i), points[i], FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 255), 1, 8, false);}imshow("img_rotate", img_rotate);waitKey(1);
}int main(int argc, char* argv[])
{Mat img;if(argc < 2)img = imread("20250305155453261.bmp");elseimg = imread(argv[1]);int angle = 180;namedWindow("img_rotate", WINDOW_AUTOSIZE);createTrackbar("angle", "img_rotate", &angle, 360,onChange,(void*)&img);   waitKey(0);return 0;
}

然后测试了7x6的棋盘格,发现随便怎么转序号都不变,就跟上面2张图一样。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、原因分析

原来8x6的棋盘格就会混淆序号,其实也好理解,行数和列数均为偶数时,棋盘格是对称的,旋转180度无法区分上下。当行数和列数一奇一偶时,有一边就是不对称的,起始点的格子颜色也不一样,例如7x6中,起始点格子为黑色,水平放置时判断一下左上角和右下角格子颜色,黑色就是起始块,当然也可以约定白色是起始块。

三、libcbdetect修改

libcbdetect是一个github大神根据论文开源的棋盘格角点检测算法,有时opencv检测不出来的它能检测,但是貌似是不支持这种角点顺序不变的功能。因此想着根据起始块颜色来区分一下,暂且就定义黑色为起始块,算法思路也比较简单:

  1. 根据检测到的角点将图片旋转至水平
  2. 角点也相应的旋转至水平,旋转前的角点记录下来
  3. 旋转后的角点按照从左到右,从上到下排序
  4. 抠出左上角和右下角4个角点组成的块,比较一下谁是黑块
  5. 若黑块在左上角,按排序后的角点顺序输出原图对应的角点;若黑块在右下角,将排序后的角点倒序输出原图对应的角点即可。
#include "libcbdetect/boards_from_corners.h"
#include "libcbdetect/config.h"
#include "libcbdetect/find_corners.h"
#include "libcbdetect/plot_boards.h"
#include "libcbdetect/plot_corners.h"
#include <opencv2/opencv.hpp>using namespace cv;
using namespace std;int get_cell_mean_value(Mat img, cv::Point2f p1, cv::Point2f p2, cv::Point2f p3, cv::Point2f p4)
{    vector<Point2f> pts;pts.push_back(p1);pts.push_back(p2);pts.push_back(p3);pts.push_back(p4);Rect rect = boundingRect(pts);rect.x += 2;rect.y += 2;rect.width -= 4;rect.height -= 4;return mean(img(rect)).val[0];
}bool findchessboard_libdetect(Mat img, std::vector<Point2f>& points, const int w, const int h)
{points.clear();cbdetect::Corner corners;std::vector<cbdetect::Board> boards;cbdetect::Params params;params.corner_type = cbdetect::SaddlePoint;//params.score_thr = 0.01;cbdetect::find_corners(img, corners, params);if (corners.p.size() == 0){//LOG_ERROR( "detect chessboard fail, empty corners");printf("detect chessboard fail, empty corners\n");return false;}cbdetect::boards_from_corners(img, corners, boards, params);//cbdetect::plot_boards(img, corners,boards, params);if (boards.size() == 0){//LOG_ERROR( "detect chessboard fail");printf("detect chessboard fail\n");return false;}if (boards.size() > 1){//LOG_ERROR( "detect too many chessboards");printf("detect too many chessboards\n");return false;}int bh = boards[0].idx.size() - 2;int bw = boards[0].idx[0].size() - 2;if ((h != bh || w != bw) && (h != bw || w != bh)){printf("board size faild: %d,%d\n", bw, bh);return false;}points.resize(bh * bw);int count = 0;auto& board = boards[0];for (int i = 1; i < board.idx.size() - 1; ++i) {if (board.idx[i].size() != bw + 2){//LOG_ERROR( "each line has different nums of corners");printf("each line has different nums of corners\n");return false;}for (int j = 1; j < board.idx[i].size() - 1; ++j) {if (board.idx[i][j] < 0) {continue;}if (h == bh){points[count] = (corners.p[board.idx[i][j]]);}else{points[(count % bw) * w + count / bw] = (corners.p[board.idx[i][j]]);}++count;}}//下面代码固定棋盘格角点输出顺序if(points.size()!= w *h) return false;    Mat gray_img;Mat rot_mat;Mat img_rotate;if(img.channels() == 3) cvtColor(img, gray_img, COLOR_BGR2GRAY);else gray_img = img;//将棋盘格旋转至水平float angle;if(points[0].x == points[bw-1].x){angle = 90;}else{angle = atan((points[bw-1].y - points[0].y) / (points[bw-1].x - points[0].x)) * 180 / M_PI;}cv::Point2f center(gray_img.cols / 2.0, gray_img.rows / 2.0);rot_mat = getRotationMatrix2D(center, angle, 1.0);warpAffine(gray_img, img_rotate,rot_mat, gray_img.size());cv::Rect bbox = cv::RotatedRect(center, gray_img.size(), angle).boundingRect();rot_mat.at<double>(0, 2) += bbox.width / 2.0 - center.x;  //x方向的偏移量Txrot_mat.at<double>(1, 2) += bbox.height / 2.0 - center.y; //y方向的偏移量Tyfloat m_dx = bbox.width / 2 - center.x;float m_dy = bbox.height / 2 - center.y;struct _point{Point2f pt;int index;};vector<_point> points_rotate;for (int i=0;i<points.size();++i){const Point2f& pt = points[i];Mat p = Mat::ones(3, 1, CV_64F);p.at<double>(0) = pt.x;p.at<double>(1) = pt.y;Mat pp = rot_mat * p;p.at<double>(0) = pp.at<double>(0) - m_dx;p.at<double>(1) = pp.at<double>(1) - m_dy;_point pt_rotate;pt_rotate.pt = Point2f(p.at<double>(0), p.at<double>(1));pt_rotate.index = i;points_rotate.push_back(pt_rotate);}sort(points_rotate.begin(), points_rotate.end(), [](_point a, _point b) {return a.pt.y < b.pt.y;});for(int i=0;i<h;++i){sort(points_rotate.begin() + i*w, points_rotate.begin() + (i+1)*w, [](_point a, _point b) {return a.pt.x < b.pt.x;});}//判断一下黑色格子在左上角还是右下角int tl_val = get_cell_mean_value(img_rotate, points_rotate[0].pt, points_rotate[1].pt, points_rotate[w].pt, points_rotate[w+1].pt);int br_val = get_cell_mean_value(img_rotate, points_rotate[w * (h -1) - 1].pt, points_rotate[w * (h -1) - 2].pt, points_rotate[w * h - 2].pt, points_rotate[w * h - 1].pt);cout<<tl_val<<" "<<br_val<<endl;for(int i=0;i<points_rotate.size();++i){putText(img_rotate, to_string(i), points_rotate[i].pt, FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 255), 1, 8, false);circle(img_rotate, points_rotate[i].pt, 5, Scalar(255), 2, 8, 0);}vector<Point2f> points_sort;for(int i=0;i<points_rotate.size();++i){if(tl_val > br_val){points_sort.push_back(points[points_rotate[w * h - 1 -i].index]);}else{points_sort.push_back(points[points_rotate[i].index]);}}points = points_sort;return true;}

总结

  1. 做实验确认opencv棋盘格检测对于7x6这种一奇一偶的棋盘格检测出的角点顺序不会发生变化,8x6这种不行。
  2. 根据棋盘格起始格子颜色为libcbdetect打补丁,使其也能够固定角点检测顺序。

相关文章:

  • 几种查看PyTorch、cuda 和 Python 版本方法
  • 如何在 Unity 中导入 gltf /glb 文件
  • Vision-Language Dual-Pattern Matching for Out-of-Distribution Detection
  • 【国产化之路】VPX-3U :基于D2000 /FT2000的硬件架构到操作系统兼容
  • 鸿蒙-状态管理V1和V2在ForEach循环渲染的表现
  • Linux命令-perf
  • 企业为何要求禁用缺省口令?安全风险及应对措施分析
  • 论文笔记(七十九)STOMP: Stochastic Trajectory Optimization for Motion Planning
  • 如何创建极狐GitLab 议题?
  • 论文阅读笔记——π0.5: a Vision-Language-Action Model with Open-World Generalization
  • SpringBoot 封装统一API返回格式对象 标准化开发 请求封装 统一格式处理
  • 【Yolo精读+实践+魔改系列】Yolov1论文超详细精讲(翻译+笔记)
  • 字典与集合——测试界的黑话宝典与BUG追捕术
  • 系统思考:技术与产品协同
  • SLAM常用地图对比示例
  • nextjs国际化
  • Vue3 + TypeScript,使用provide提供只读的响应式数据的详细分析与解决方法
  • #define STEUER_A_H {PWM_A_ON}
  • C#中用 OxyPlot 在 WinForms 实现波形图可视化(附源码教程)
  • 深度理解spring——BeanFactory的实现
  • 政企研合力,科学监测分析服务消费
  • 漫画阅读APP刊载1200余部侵权作品:20人获刑,案件罚金超千万元
  • 百年前的亚裔艺术家与巴黎
  • 中国驻日本大使馆发言人就日方涉靖国神社消极动向答记者问
  • 广州一男子早高峰爬上猎德大桥顶部疑似要跳桥,路段一度拥堵
  • 上海与丰田汽车签署战略合作协议,雷克萨斯纯电动汽车项目落子金山