600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > 棋盘格角点检测

棋盘格角点检测

时间:2020-10-06 15:00:10

相关推荐

棋盘格角点检测

棋盘格角点检测

文章目录

棋盘格角点检测1、cv::findChessboardCorners 棋盘格角点检测2、Opencv源码实现3、基于生长的棋盘格角点检测

背景:

最近开发一个光学检测的项目,检测方式是通过一个成像亮度计(光学相机)拍摄一个显示屏,显示屏上显示的是另一个工业相机实时拍摄棋盘格的图片。听起来有点绕😂,但不管咋样,最后在成像亮度计中获取到了有棋盘格样式图片,然后经图像算法检测棋盘格角点坐标,将得到的角点坐标按给定的方式进行计算。下图是测试过程中拍摄的一张图片,可通过opencv自带的cv::findChessboardCorners()接口实现。

1、cv::findChessboardCorners 棋盘格角点检测

参考官网文档:/4.1.0/index.html

在此注意:我上面给出来的棋盘格与正规的棋盘格有所不同,它周边的背景是黑色的,而正规的棋盘格其周边是白色的。如下图所示:

因此,对于黑色背景的棋盘格,在数角点时要内缩两个角点进行计数,而对于白色背景的棋盘格,只需要在数角点时内缩一个角点进行计数即可。如上图,白色背景水平方向角点数为16,竖直方向角点数为10,而黑色背景(我最上面给出的图片)水平方向角点数为27,竖直方向角点数为17,若不理解的可以自己数一下。实例代码如下:

//Opencv头文件#include <opencv2/core/core.hpp>#include <opencv2/highgui/highgui.hpp>#include <opencv2/imgproc/imgproc.hpp>#include <opencv2/calib3d/calib3d.hpp>//导入Opencv库#ifdef _DEBUG#pragma comment(lib,"opencv_world342d.lib")#else#pragma comment(lib,"opencv_world342.lib")#endifint main(){cv::Mat srcMat;srcMat = cv::imread("1.png", 1);if (srcMat.empty()){return -1;}std::vector<cv::Point2f> corners;cv::Size boardSize(27, 17);bool found = findChessboardCorners(srcMat, boardSize, corners);if (!found){return -1;}cv::drawChessboardCorners(srcMat, boardSize, corners, found);return 0;}

但如果你不想让黑色背景的棋盘格内缩两格,想和正规棋盘格的图像一样内缩一格对角点进行获取,可参考如下代码:

//Opencv头文件#include <time.h>#include <iostream>#include <opencv2/core/core.hpp>#include <opencv2/highgui/highgui.hpp>#include <opencv2/imgproc/imgproc.hpp>#include <opencv2/calib3d/calib3d.hpp>//导入Opencv库#ifdef _DEBUG#pragma comment(lib,"opencv_world342d.lib")#else#pragma comment(lib,"opencv_world342.lib")#endifdouble imgScale = 1;cv::Size patSizes = cv::Size(12, 11);void cornerDetect(cv::Mat m, std::vector<cv::Point2f> &pts){cv::Mat mGray, mask, simg;cv::Size camSize;if (m.empty()){std::cout << "Matrix is empty!" << std::endl;return;}if (m.type() == CV_8UC3){cv::cvtColor(m, mGray, cv::COLOR_BGR2GRAY);}else{mGray = m.clone();}camSize = mGray.size();clock_t flag1 = clock();resize(mGray, mask, cv::Size(200, 200));GaussianBlur(mask, mask, cv::Size(13, 13), 11, 11);clock_t flag2 = clock();resize(mask, mask, camSize);medianBlur(mask, mask, 9);clock_t flag3 = clock();for (int v = 0; v < camSize.height; v++){for (int u = 0; u < camSize.width; u++){int x = (((int)mGray.at<uchar>(v, u) - (int)mask.at<uchar>(v, u)) << 1) + 128;mGray.at<uchar>(v, u) = std::max(std::min(x, 255), 0);}}resize(mGray, simg, cv::Size(), imgScale, imgScale);clock_t flag4 = clock();bool found = findChessboardCorners(simg, patSizes, pts);clock_t flag5 = clock();for (unsigned int i = 0; i < pts.size(); ++i){pts[i] *= 1. / imgScale;}clock_t flag6 = clock();if (found){cornerSubPix(mGray, pts, cv::Size(21, 21), cv::Size(-1, -1),cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 5000, 0.0001));}clock_t flag7 = clock();drawChessboardCorners(m, patSizes, cv::Mat(pts), found);clock_t flag8 = clock();std::cout << cv::saturate_cast<double>(flag2 - flag1) / CLOCKS_PER_SEC << '\t'<< cv::saturate_cast<double>(flag3 - flag2) / CLOCKS_PER_SEC << '\t'<< cv::saturate_cast<double>(flag4 - flag3) / CLOCKS_PER_SEC << '\t'<< cv::saturate_cast<double>(flag5 - flag4) / CLOCKS_PER_SEC << '\t'<< cv::saturate_cast<double>(flag7 - flag6) / CLOCKS_PER_SEC << '\t'<< cv::saturate_cast<double>(flag8 - flag7) / CLOCKS_PER_SEC << '\t'<< cv::saturate_cast<double>(flag8 - flag1) / CLOCKS_PER_SEC << '\t' << found << '\t';return;}int main(){cv::Mat srcMat;srcMat = cv::imread("2.png", 1);if (srcMat.empty()){return -1;}std::vector<cv::Point2f> corners;cornerDetect(srcMat, corners);return 0;}

测试图片:

未经处理直接进行角点检测:

经以上处理后的图像:

处理后的图像角点检测:

那为什么这样处理就可以达到和正规棋盘格一样进行相同行为的检测呢?

2、Opencv源码实现

Opencv中棋盘格角点检测的源码是开源的,可以在calibinit.cpp文件中找到。这边我将我自己整理的代码贴出来(基于Opencv4.5.5最新版)。如果你需要其最原始版的,可基于下图所示的位置获取。

棋盘格检测的头文件:ChessBoardDetector.h

#pragma once#include <map>#include <stack>#include <iostream>#include <opencv2/core/core.hpp>#include <opencv2/highgui/highgui.hpp>#include <opencv2/imgproc/imgproc.hpp>#define CALIB_CB_ADAPTIVE_THRESH 1#define CALIB_CB_NORMALIZE_IMAGE 2#define CALIB_CB_FILTER_QUADS 4#define CALIB_CB_FAST_CHECK8//用于存储四边形角点信息 + 其父轮廓的IDstruct QuadCountour{//四个角点信息(顺时针存放)cv::Point pt[4];//四边形的父轮廓IDint parent_contour;//构造函数QuadCountour(const cv::Point pt_[4], int parent_contour_) :parent_contour(parent_contour_){pt[0] = pt_[0]; pt[1] = pt_[1]; pt[2] = pt_[2]; pt[3] = pt_[3];}};//用于存储角点信息struct ChessBoardCorner{//角点坐标cv::Point2f pt;//邻近的角点个数int row;//角点所在的行索引/列索引int count;//其邻近的角点信息struct ChessBoardCorner *neighbors[4];//构造函数ChessBoardCorner(const cv::Point2f &pt_ = cv::Point2f()) :pt(pt_), row(0), count(0){neighbors[0] = neighbors[1] = neighbors[2] = neighbors[3] = NULL;}//计算邻近角点信息的距离(欧氏距离)和float sumDist(int &n_) const{float sum = 0;int n = 0;for (int i = 0; i < 4; ++i){if (neighbors[i]){sum += sqrt(cv::normL2Sqr<float>(neighbors[i]->pt - pt));n++;}}//输出计算距离的次数n_ = n;return sum;}};//用于存储四边形信息struct ChessBoardQuad{//最近邻四边形个数int count;//四边形组IDint group_idx;//四边形位于棋盘格的行列坐标int row, col;//按顺时针方向存储最近邻四边形 默认值:false//当为true时,该四边形为内四边形,有四个最近邻的四边形bool ordered;//四边形最小边长(平方)float edge_len;//四边形四个角点信息ChessBoardCorner *corners[4];//最近邻四边形信息struct ChessBoardQuad *neighbors[4];//构造函数ChessBoardQuad(int group_idx_ = -1) :count(0),group_idx(group_idx_),row(0), col(0),ordered(0),edge_len(0){corners[0] = corners[1] = corners[2] = corners[3] = NULL;neighbors[0] = neighbors[1] = neighbors[2] = neighbors[3] = NULL;}};class ChessBoardDetector{public://二值图cv::Mat binarized_image;//实际棋盘格角点个数cv::Size pattern_size;//用于存储所有四边形的信息数组cv::AutoBuffer<ChessBoardQuad> all_quads;//用于存储所有四边形角点信息数组cv::AutoBuffer<ChessBoardCorner> all_corners;//存储四边形的总个数int all_quads_count;//构造函数ChessBoardDetector(const cv::Size &pattern_size_) :pattern_size(pattern_size_),all_quads_count(0){}//复位void reset(){all_quads.deallocate();all_corners.deallocate();all_quads_count = 0;}//提取四边形信息void generateQuads(const cv::Mat &image, int flags);//获取四边形的最近邻四边形void findQuadNeighbors();//按顺时针方向连接四边形void findConnectedQuads(std::vector<ChessBoardQuad*> &out_group, int group_idx);//将四边形按正确的顺序排序void orderQuad(ChessBoardQuad &quad, ChessBoardCorner &corner, int common);//填补内四边形缺失的外四边形int addOuterQuad(ChessBoardQuad &quad, std::vector<ChessBoardQuad*> &quads);//排除非本组四边形或未连接内四边形的外四边形void removeQuadFromGroup(std::vector<ChessBoardQuad*> &quads, ChessBoardQuad &q0);//将四边形数组按指定顺序排序int orderFoundConnectedQuads(std::vector<ChessBoardQuad*> &quads);//清理多出来的四边形int cleanFoundConnectedQuads(std::vector<ChessBoardQuad*> &quad_group);//检测角点数组int checkQuadGroup(std::vector<ChessBoardQuad*> &quad_group, std::vector<ChessBoardCorner*> &out_corners);//检查输出角点数组的单调性bool checkBoardMonotony(const std::vector<cv::Point2f> &corners);//总流程(提取棋盘格角点信息,并返回)bool processQuads(std::vector<cv::Point2f> &out_corners, int &prev_sqr_size);};

头文件对应的cpp文件: ChessBoardDetector.cpp

#include "ChessBoardDetector.h"extern std::string storagePath;//按顺时针顺序返回角点//对于不同的四边形,角点不一定从相同的位置开始存储(如第一个四边形可能先从左上角开始存储,第二个四边形可能先从右上角开始存储)void ChessBoardDetector::generateQuads(const cv::Mat &image, int flags){//浅拷贝binarized_image = image;cv::Mat drawImg;cv::cvtColor(image, drawImg, cv::COLOR_GRAY2BGR);//记录检测方块的个数int quad_count = 0;//清空all_quads.deallocate();all_corners.deallocate();//四边形允许的最小面积(经验值)int min_size = 25;bool filterQuads = (flags & CALIB_CB_FILTER_QUADS) != 0;std::vector<std::vector<cv::Point>> contours;std::vector<cv::Vec4i> hierarchy;cv::findContours(image, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);if (contours.empty()){std::cout << "ChessBoardDetector::generateQuads: cv::findContours() returns no contours" << std::endl;return;}std::vector<int> contour_child_counter(contours.size(), 0);int boardIdx = -1; //获取四边形最多个数的父类ID -- 若勾选cv::CALIB_CB_FILTER_QUADS,只考虑该父类轮廓的子类轮廓为想要的获取的四边形std::vector<QuadCountour> contour_quads;for (int idx = (int)(contours.size() - 1); idx >= 0; --idx){int parentIdx = hierarchy[idx][3];//将无父轮廓与有子轮廓的集合删除if (hierarchy[idx][2] != -1 || parentIdx == -1)continue;const std::vector<cv::Point> &contour = contours[idx];cv::Rect contour_rect = boundingRect(contour);if (contour_rect.area() < min_size)continue;std::vector<cv::Point> approx_contour;const int min_approx_level = 1, max_approx_level = 7;for (int approx_level = min_approx_level; approx_level <= max_approx_level; approx_level++){//将连续光滑的曲线点折线化cv::approxPolyDP(contour, approx_contour, (float)approx_level, true);if (approx_contour.size() == 4)break;//在第一次的基础上执行第二次折线化std::vector<cv::Point> approx_contour_tmp;std::swap(approx_contour, approx_contour_tmp);cv::approxPolyDP(approx_contour_tmp, approx_contour, (float)approx_level, true);if (approx_contour.size() == 4)break;}//丢弃点个数非四个的轮廓if (approx_contour.size() != 4)continue;//丢弃点是非凸点的轮廓if (!cv::isContourConvex(approx_contour))continue;cv::Point pt[4];for (int i = 0; i < 4; ++i)pt[i] = approx_contour[i];if (filterQuads){//轮廓的周长double p = cv::arcLength(approx_contour, true);//轮廓的面积double area = cv::contourArea(approx_contour, false);//轮廓的对角线边长double d1 = sqrt(cv::normL2Sqr<double>(pt[0] - pt[2]));double d2 = sqrt(cv::normL2Sqr<double>(pt[1] - pt[3]));//轮廓的边长double d3 = sqrt(cv::normL2Sqr<double>(pt[0] - pt[1]));double d4 = sqrt(cv::normL2Sqr<double>(pt[1] - pt[2]));//只接受更正的四边形if (!(d3 * 4 > d4 && d4 * 4 > d3 && d3 * d4 < area * 1.5 && area > min_size &&d4 >= 0.15 * p && d2 >= 0.15 * p))continue;}cv::drawContours(drawImg, contours, idx, cv::Scalar(0, 0, 255), 3, 8);contour_child_counter[parentIdx]++;if (boardIdx != parentIdx && (boardIdx < 0 || contour_child_counter[boardIdx] < contour_child_counter[parentIdx]))boardIdx = parentIdx;contour_quads.emplace_back(pt, parentIdx);}size_t total = contour_quads.size();size_t max_quad_buf_size = MAX((size_t)2, total * 3);//动态分配内存all_quads.allocate(max_quad_buf_size);all_corners.allocate(max_quad_buf_size * 4);//创建四边形角点的数组for (size_t idx = 0; idx < total; ++idx){QuadCountour &qc = contour_quads[idx];if (filterQuads && qc.parent_contour != boardIdx){continue;}//后++,先赋值再++int quad_idx = quad_count++;ChessBoardQuad &q = all_quads[quad_idx];q = ChessBoardQuad();for (int i = 0; i < 4; ++i){cv::Point2f pt(qc.pt[i]);ChessBoardCorner &corner = all_corners[quad_idx * 4 + i];corner = ChessBoardCorner(pt);q.corners[i] = &corner;}//四边形的边长 -- > 只获取最小边的边长的平方q.edge_len = FLT_MAX;for (int i = 0; i < 4; ++i){float d = cv::normL2Sqr<float>(q.corners[i]->pt - q.corners[(i + 1) & 3]->pt);q.edge_len = MIN(q.edge_len, d);}}std::string strImg = storagePath + "/step1.bmp";cv::imwrite(strImg, drawImg);//获取到的四边形个数all_quads_count = quad_count;}//获取四边形的最近邻四边形void ChessBoardDetector::findQuadNeighbors(){const float thresh_scale = 1.f;//寻找邻四边形for (int idx = 0; idx < all_quads_count; idx++){ChessBoardQuad &cur_quad = (ChessBoardQuad &)all_quads[idx];//遍历当前四边形的四个角点(不同位置的角对应不同近邻的四边形 一一对应)for (int i = 0; i < 4; i++){if (cur_quad.neighbors[i])continue;//初始化float min_dist = FLT_MAX;int closest_corner_idx = -1;ChessBoardQuad *closest_quad = 0;cv::Point2f pt = cur_quad.corners[i]->pt;//在其他所有的四边形中找与该角点最近距离的四边形for (int k = 0; k < all_quads_count; k++){if (k == idx)continue;ChessBoardQuad &q_k = all_quads[k];//遍历四边形的四个角点for (int j = 0; j < 4; j++){if (q_k.neighbors[j])continue;//计算相应两个角点的距离(平方)float dist = cv::normL2Sqr<float>(pt - q_k.corners[j]->pt);//两个角点的距离要求//1、要小于自身四边形的最小边长//2、要小于判定四边形的最小边长//3、自身与判定四边形角点的距离最近(欧式距离->但未开方)if (dist < min_dist &&dist <= cur_quad.edge_len * thresh_scale &&dist <= q_k.edge_len * thresh_scale){//检查边长,确保它们是兼容的(即二者相差不要太大)float ediff = cur_quad.edge_len - q_k.edge_len;if (ediff > 32 * cur_quad.edge_len ||ediff > 32 * q_k.edge_len){continue;}closest_corner_idx = j;closest_quad = &q_k;min_dist = dist;}}}//判断是否获取到最近邻的角点if (closest_corner_idx >= 0 && min_dist < FLT_MAX){CV_Assert(closest_quad);if (cur_quad.count >= 4 || closest_quad->count >= 4)continue;ChessBoardCorner &closest_corner = *closest_quad->corners[closest_corner_idx];//如果当前四边形的最近邻已经存在(现在查询到的最近邻)四边形,则排除//如果当前四边形的其他角与(现在查询到的最近邻)四边形的最近邻角的距离,小于计算出来的最小值(当前四边形的角与(现在查询到的最近邻)四边形最近邻的角的距离),也排除int j = 0;for (; j < 4; j++){if (cur_quad.neighbors[j] == closest_quad)break;if (cv::normL2Sqr<float>(closest_corner.pt - cur_quad.corners[j]->pt) < min_dist)break;}if (j < 4)continue;//判断(现在查询到的最近邻)四边形的最近邻数组中是否存在当前四边形,若存在,则排除for (j = 0; j < closest_quad->count; j++){if (closest_quad->neighbors[j] == &cur_quad)break;}if (j < closest_quad->count)continue;//除当前四边形与(现在查询到的最近邻)四边形,查询其他四边形的四个角点与(现在查询到的最近邻)四边形的最近邻角的距离,//小于计算出来的最小值(当前四边形的角与(现在查询到的最近邻)四边形最近邻的角的距离),若存在,则排除for (j = 0; j < all_quads_count; j++){ChessBoardQuad *q = &const_cast<ChessBoardQuad &>(all_quads[j]);if (j == idx || q == closest_quad)continue;int k = 0;for (; k < 4; k++){if (!q->neighbors[k]){if (cv::normL2Sqr<float>(closest_corner.pt - q->corners[k]->pt) < min_dist)break;}}if (k < 4)break;}if (j < all_quads_count)continue;//对(现在查询到的最近邻)四边形的最近邻的角重新赋值,计算两角间(当前四边形的对应角与(现在查询到的最近邻)四边形的最近邻角)的中心坐标closest_corner.pt = (pt + closest_corner.pt) * 0.5f;//找到的最近邻四边形的个数cur_quad.count++;cur_quad.neighbors[i] = closest_quad;cur_quad.corners[i] = &closest_corner;//对(现在查询到的最近邻)四边形同样赋值(二者是相互的,你是我的最近邻,我也是你的最近邻,而且不同角的坐标对应其最近邻的四边形)closest_quad->count++;closest_quad->neighbors[closest_corner_idx] = &cur_quad;}}}}//按顺时针方向连接四边形void ChessBoardDetector::findConnectedQuads(std::vector<ChessBoardQuad*> &out_group, int group_idx){//清空out_group.clear();//栈,先进后出//pop, 出栈,删除栈顶元素//top, 查询栈顶元素//push, 进栈,出入栈顶元素std::stack<ChessBoardQuad*> stack;cv::Mat drawImg;cv::cvtColor(binarized_image, drawImg, cv::COLOR_GRAY2BGR);int i = 0;for (; i < all_quads_count; i++){ChessBoardQuad *q = (ChessBoardQuad *)&all_quads[i];//扫描四边形数组,将没有最近邻四边形的四边形排除,或将已经标记的四边形排除if (q->count <= 0 || q->group_idx >= 0)continue;//从种子all_quads[i]开始,递归地找到一组连接的四边形stack.push(q);out_group.push_back(q);q->group_idx = group_idx;q->ordered = false;while (!stack.empty()){//进栈q = stack.top();//出栈stack.pop();//其自身(红色)for (int k = 0; k < 4; ++k){cv::line(drawImg, q->corners[k]->pt, q->corners[(k + 1) & 3]->pt, cv::Scalar(0, 0, 255), 3, cv::LINE_8);}for (int k = 0; k < 4; k++){ChessBoardQuad *neighbor = q->neighbors[k];if (neighbor && neighbor->count > 0 && neighbor->group_idx < 0){stack.push(neighbor);out_group.push_back(neighbor);neighbor->group_idx = group_idx;neighbor->ordered = false;}}}//获取到的四边形数组for (int j = 0; j < out_group.size(); j++){cv::putText(drawImg, std::to_string(j + 1), cv::Point((MIN(MIN(MIN(out_group[j]->corners[0]->pt.x, out_group[j]->corners[1]->pt.x), out_group[j]->corners[2]->pt.x), out_group[j]->corners[3]->pt.x) +MAX(MAX(MAX(out_group[j]->corners[0]->pt.x, out_group[j]->corners[1]->pt.x), out_group[j]->corners[2]->pt.x), out_group[j]->corners[3]->pt.x)) / 2,(MIN(MIN(MIN(out_group[j]->corners[0]->pt.y, out_group[j]->corners[1]->pt.y), out_group[j]->corners[2]->pt.y), out_group[j]->corners[3]->pt.y) +MAX(MAX(MAX(out_group[j]->corners[0]->pt.y, out_group[j]->corners[1]->pt.y), out_group[j]->corners[2]->pt.y), out_group[j]->corners[3]->pt.y)) / 2),cv::FONT_HERSHEY_COMPLEX, 1.0, cv::Scalar(0, 255, 0));}break;}std::string strImg = storagePath + "/step2.bmp";cv::imwrite(strImg, drawImg);}//将四边形按正确的顺序排序void ChessBoardDetector::orderQuad(ChessBoardQuad &quad, ChessBoardCorner &corner, int common){//寻找对应角点IDint tc = 0;;for (; tc < 4; ++tc)if (quad.corners[tc]->pt == corner.pt)break;//组角秩序,平移最近邻方块的角点与相应的最邻近方块,使其对角一一对应while (tc != common){ChessBoardCorner *tempc = quad.corners[3];ChessBoardQuad *tempq = quad.neighbors[3];for (int i = 3; i > 0; --i){quad.corners[i] = quad.corners[i - 1];quad.neighbors[i] = quad.neighbors[i - 1];}quad.corners[0] = tempc;quad.neighbors[0] = tempq;tc = (tc + 1) & 3;}}//填补内四边形缺失的外四边形int ChessBoardDetector::addOuterQuad(ChessBoardQuad &quad, std::vector<ChessBoardQuad*> &quads){int added = 0;int max_quad_buf_size = (int)all_quads.size();for (int i = 0; i < 4 && all_quads_count < max_quad_buf_size; i++){//创建外四边形为其内四边形的邻居if (!quad.neighbors[i]){// 对角坐标// i = 0 | (0 + 2) & 3 = 2// i = 1 | (1 + 2) & 3 = 3// i = 2 | (2 + 2) & 3 = 0// i = 3 | (3 + 2) & 3 = 1int j = (i + 2) & 3;int q_index = all_quads_count++;ChessBoardQuad &q = all_quads[q_index];q = ChessBoardQuad(0);added++;quads.push_back(&q);//初始化其外四边形quad.neighbors[i] = &q;quad.count += 1;q.neighbors[j] = &quad;q.group_idx = quad.group_idx;q.count = 1;q.ordered = false;q.edge_len = quad.edge_len;//重新制作新的四角//和相邻的四边形一样,但有偏移const cv::Point2f pt_offset = quad.corners[i]->pt - quad.corners[j]->pt;for (int k = 0; k < 4; k++){ChessBoardCorner &corner = (ChessBoardCorner&)all_corners[q_index * 4 + k];const cv::Point2f &pt = quad.corners[k]->pt;corner = ChessBoardCorner(pt);q.corners[k] = &corner;corner.pt += pt_offset;}//设置确切的角(因为四边形角的起点不一定是)q.corners[j] = quad.corners[i];//现在,如果可能的话,找到其他邻居并添加它// i = 0 | (0 + 1) & 3 = 1// i = 1 | (1 + 1) & 3 = 2// i = 2 | (2 + 1) & 3 = 3// i = 3 | (3 + 1) & 3 = 0int next_i = (i + 1) & 3;// i = 0 | (0 + 3) & 3 = 3// i = 1 | (1 + 3) & 3 = 0// i = 2 | (2 + 3) & 3 = 1// i = 3 | (3 + 3) & 3 = 2int prev_i = (i + 3) & 3;ChessBoardQuad* quad_prev = quad.neighbors[prev_i];if (quad_prev &&quad_prev->ordered &&quad_prev->neighbors[i] &&quad_prev->neighbors[i]->ordered){ChessBoardQuad* qn = quad_prev->neighbors[i];q.count = 2;q.neighbors[prev_i] = qn;qn->neighbors[next_i] = &q;qn->count += 1;//设置确切的角q.corners[prev_i] = qn->corners[next_i];}}}return added;}//排除非本组四边形或未连接内四边形的外四边形void ChessBoardDetector::removeQuadFromGroup(std::vector<ChessBoardQuad*> &quads, ChessBoardQuad &q0){const int count = (int)quads.size();int self_idx = -1;//移除任何对这个四边形的引用作为邻居for (int i = 0; i < count; ++i){ChessBoardQuad *q = quads[i];if (q == &q0)self_idx = i;for (int j = 0; j < 4; j++){if (q->neighbors[j] == &q0){q->neighbors[j] = NULL;q->count--;for (int k = 0; k < 4; ++k){if (q0.neighbors[k] == q){q0.neighbors[k] = 0;q0.count--;break;}}break;}}}//删除该数组内的四边形if (self_idx != count - 1)quads[self_idx] = quads[count - 1];quads.resize(count - 1);}//将四边形数组按指定顺序排序int ChessBoardDetector::orderFoundConnectedQuads(std::vector<ChessBoardQuad*> &quads){const int max_quad_buf_size = (int)all_quads.size();int quad_count = (int)quads.size();cv::Mat drawImg;cv::cvtColor(binarized_image, drawImg, cv::COLOR_GRAY2BGR);std::stack<ChessBoardQuad*> stack;//找到数组第一个有四个最近邻四边形的内四边形ChessBoardQuad *start = NULL;for (int i = 0; i < quad_count; i++){if (quads[i]->count == 4){start = quads[i];break;}}//若没有一个是内四边形则返回if (start == NULL)return 0;//从第一个内四边形开始,分配行列坐标int row_min = 0, col_min = 0, row_max = 0, col_max = 0;//字典集合std::map<int, int> col_hist;std::map<int, int> row_hist;stack.push(start);start->row = 0;start->col = 0;start->ordered = true;//递归的排列四边形while (!stack.empty()){//查询栈顶元素ChessBoardQuad* q = stack.top();//出栈stack.pop();//其自身内四边形(红色)for (int k = 0; k < 4; ++k){cv::line(drawImg, q->corners[k]->pt, q->corners[(k + 1) & 3]->pt, cv::Scalar(0, 0, 255), 3, cv::LINE_8);}int col = q->col;int row = q->row;col_hist[col]++;row_hist[row]++;// 检查最大最小值if (row > row_max)row_max = row;if (row < row_min)row_min = row;if (col > col_max)col_max = col;if (col < col_min)col_min = col;for (int i = 0; i < 4; i++){ChessBoardQuad *neighbor = q->neighbors[i];//调整行与列//从左上角开始,顺时针方向switch (i){case 0:row--; col--; break;case 1:col += 2; break;case 2:row += 2; break;case 3:col -= 2; break;}// 仅针对内四边形if (neighbor && neighbor->ordered == false && neighbor->count == 4){//其最近邻内四边形(红色)for (int k = 0; k < 4; ++k){cv::line(drawImg, neighbor->corners[k]->pt, neighbor->corners[(k + 1) & 3]->pt, cv::Scalar(0, 255, 0), 3, cv::LINE_8);}//(对角坐标) == i为自身内四边形角点的索引// i = 0 | (0 + 2) & 3 = 2// i = 1 | (1 + 2) & 3 = 3// i = 2 | (2 + 2) & 3 = 0// i = 3 | (3 + 2) & 3 = 1orderQuad(*neighbor, *(q->corners[i]), (i + 2) & 3);neighbor->ordered = true;neighbor->row = row;neighbor->col = col;stack.push(neighbor);}}}//分析内四边形结构int w = pattern_size.width - 1;int h = pattern_size.height - 1;int drow = row_max - row_min + 1;int dcol = col_max - col_min + 1;//找到对应的指标(行列指标)if ((w > h && dcol < drow) ||(w < h && drow < dcol)){h = pattern_size.width - 1;w = pattern_size.height - 1;}//检查是否存在足够的内四边形if (dcol < w || drow < h){return 0;}//检查内四边形的边缘//如果内四边形有一个外部四边形缺失,将其改为内部四边形,并按指定方式排序int found = 0;for (int i = 0; i < quad_count; ++i){ChessBoardQuad &q = *quads[i];//只考虑内四边形if (q.count != 4)continue;{//看其最近邻四边形int col = q.col;int row = q.row;for (int j = 0; j < 4; j++){//为其四边形调整行列//从左上角开始,顺时针排列switch (j){case 0:row--; col--; break;case 1:col += 2; break;case 2:row += 2; break;case 3:col -= 2; break;}ChessBoardQuad *neighbor = q.neighbors[j];//判断是否属于内四边形if (neighbor && !neighbor->ordered &&col <= col_max && col >= col_min &&row <= row_max && row >= row_min){//如果是内四边形,按顺序设置found++;CV_Assert(q.corners[j]);orderQuad(*neighbor, *q.corners[j], (j + 2) & 3);neighbor->ordered = true;neighbor->row = row;neighbor->col = col;}}}}//如果我们找到缺失的内四边形,请填补其缺失的外四边形if (found > 0){for (int i = 0; i < quad_count && all_quads_count < max_quad_buf_size; i++){ChessBoardQuad &q = *quads[i];if (q.count < 4 && q.ordered){int added = addOuterQuad(q, quads);quad_count += added;}}if (all_quads_count >= max_quad_buf_size)return 0;}//外四边形最后的修整//找到正确的内四边形if (dcol == w && drow == h){//消除任何未连接到有序四边形的四边形for (int i = quad_count - 1; i >= 0; i--){ChessBoardQuad& q = *quads[i];if (q.ordered == false){bool outer = false;for (int j = 0; j < 4; j++){//确保外四边形与内四边形有连接if (q.neighbors[j] && q.neighbors[j]->ordered)outer = true;}//排除非本组或位于内四边形有连接的外四边形if (!outer){removeQuadFromGroup(quads, q);}}}std::string strImg = storagePath + "/step3.bmp";cv::imwrite(strImg, drawImg);return (int)quads.size();}return 0;}//清理多出来的四边形int ChessBoardDetector::cleanFoundConnectedQuads(std::vector<ChessBoardQuad*> &quad_group){//该模式应该包含的四边形数量(已知的四边形数量)int count = ((pattern_size.width + 1) * (pattern_size.height + 1) + 1) / 2;//假如有过多的四边形,删除那些重复的或不规正的四边形int quad_count = (int)quad_group.size();if (quad_count <= count)return quad_count;//创建一个四边形中心数组cv::AutoBuffer<cv::Point2f> centers(quad_count);cv::Point2f center;for (int i = 0; i < quad_count; ++i){ChessBoardQuad* q = quad_group[i];const cv::Point2f ci = (q->corners[0]->pt +q->corners[1]->pt +q->corners[2]->pt +q->corners[3]->pt) * 0.25f;centers[i] = ci;center += ci;}center.x *= (1.0f / quad_count);//如果还有更多的四边形,//我们试图通过最小化边界来消除不好的四边形。//我们会迭代地移除那些最大程度地减少斑点包围盒大小的点(因为我们希望矩形尽可能小)//最大的blobs的包围盒//删除导致最大缩减的四边形//知道其数量等于pattern_sizefor (; quad_count > count; quad_count--){double min_box_area = DBL_MAX;int min_box_area_index = -1;// 对于每个点,计算不含该点的框面积for (int skip = 0; skip < quad_count; ++skip){//得到边界矩形cv::Point2f temp = centers[skip];//暂时使索引'skip'与图案中心相同(因此它不被计入凸包)centers[skip] = center;std::vector<cv::Point2f> hull;cv::Mat points(1, quad_count, CV_32FC2, &centers[0]);//计算凸包cv::convexHull(points, hull, true);centers[skip] = temp;double hull_area = contourArea(hull, true);//记住最小的盒子面积if (hull_area < min_box_area){min_box_area = hull_area;min_box_area_index = skip;}}ChessBoardQuad *q0 = quad_group[min_box_area_index];//移除任何对这个四边形的引用作为邻居for (int i = 0; i < quad_count; ++i){ChessBoardQuad *q = quad_group[i];CV_DbgAssert(q);for (int j = 0; j < 4; ++j){if (q->neighbors[j] == q0){q->neighbors[j] = 0;q->count--;for (int k = 0; k < 4; ++k){if (q0->neighbors[k] == q){q0->neighbors[k] = 0;q0->count--;break;}}break;}}}//移除方块quad_count--;quad_group[min_box_area_index] = quad_group[quad_count];centers[min_box_area_index] = centers[quad_count];}return quad_count;}//检测角点数组int ChessBoardDetector::checkQuadGroup(std::vector<ChessBoardQuad*> &quad_group, std::vector<ChessBoardCorner*> &out_corners){const int ROW1 = 1000000;const int ROW2 = 2000000;const int ROW_ = 3000000;cv::Mat drawImg;cv::cvtColor(binarized_image, drawImg, cv::COLOR_GRAY2BGR);int quad_count = (int)quad_group.size();std::vector<ChessBoardCorner *> corners(quad_count * 4);int corner_count = 0;int result = 0;int width = 0, height = 0;int hist[5] = {0,0,0,0,0 };//ChessBoardCorner* first = 0, *first2 = 0, *right, *cur, *below, *c;//构建对偶图,其中顶点为内部四角//如果两个顶点在同一条四边形边上,它们就是连通的for (int i = 0; i < quad_count; ++i){ChessBoardQuad* q = quad_group[i];for (int j = 0; j < 4; ++j){cv::line(drawImg, q->corners[j]->pt, q->corners[(j + 1) & 3]->pt, cv::Scalar(0, 0, 255), 3, cv::LINE_8);if (q->neighbors[j]){// j = 0 | next_j = 1// j = 1 | next_j = 2// j = 2 | next_j = 3// j = 3 | next_j = 4int next_j = (j + 1) & 3;ChessBoardCorner *a = q->corners[j], *b = q->corners[next_j];//标记所属的内部角://有一个邻居的四边形为 ROW1 = 1000000//有两个邻居的四边形为 ROW2 = 2000000//有三个或四个邻居的四边形为 ROW3 = 3000000int row_flag = q->count == 1 ? ROW1 : q->count == 2 ? ROW2 : ROW_;if (a->row == 0){corners[corner_count++] = a;a->row = row_flag;}else if (a->row > row_flag){a->row = row_flag;}if (q->neighbors[next_j]){if (a->count >= 4 || b->count >= 4)goto finalize;for (int k = 0; k < 4; ++k){if (a->neighbors[k] == b)goto finalize;if (b->neighbors[k] == a)goto finalize;}a->neighbors[a->count++] = b;b->neighbors[b->count++] = a;}}}}//判断内部角的个数是否等于给定的棋盘格角点数if (corner_count != pattern_size.width * pattern_size.height)goto finalize;{ChessBoardCorner *first = NULL, *first2 = NULL;for (int i = 0; i < corner_count; ++i){int n = corners[i]->count;hist[n]++;if (!first && n == 2){if (corners[i]->row == ROW1)first = corners[i];else if (!first2 && corners[i]->row == ROW2)first2 = corners[i];}}//从只有一个邻居的四边形开始//如果没有那样的,那就从右两个邻居的四边形开始if (!first)first = first2;if (!first || hist[0] != 0 || hist[1] != 0 || hist[2] != 4 ||hist[3] != (pattern_size.width + pattern_size.height) * 2 - 8)goto finalize;ChessBoardCorner* cur = first;ChessBoardCorner* right = NULL;ChessBoardCorner* below = NULL;out_corners.push_back(cur);for (int k = 0; k < 4; ++k){ChessBoardCorner* c = cur->neighbors[k];if (c){if (!right)right = c;else if (!below)below = c;}}if (!right || (right->count != 2 && right->count != 3) ||!below || (below->count != 2 && below->count != 3))goto finalize;cur->row = 0;cv::circle(drawImg, cur->pt, 5, cv::Scalar(0, 255, 0), -1, 8, 0);cv::circle(drawImg, right->pt, 5, cv::Scalar(0, 255, 255), -1, 8, 0);cv::circle(drawImg, below->pt, 5, cv::Scalar(255, 0, 255), -1, 8, 0);//记住下一排的第一个角first = below;//查找并存储第一行(或列)for (int j = 1; ; ++j){right->row = 0;out_corners.push_back(right);cv::circle(drawImg, right->pt, 5, cvScalar(0, 255, 0), -1, 8, 0);if (right->count == 2)break;if (right->count != 3 || (int)out_corners.size() >= MAX(pattern_size.width, pattern_size.height))goto finalize;cur = right;for (int k = 0; k < 4; ++k){ChessBoardCorner* c = cur->neighbors[k];if (c && c->row > 0){int kk = 0;for (; kk < 4; ++kk){if (c->neighbors[kk] == below)break;}if (kk < 4)below = c;elseright = c;}}}width = (int)out_corners.size();if (width == pattern_size.width)height = pattern_size.height;else if (width == pattern_size.height)height = pattern_size.width;elsegoto finalize;//查找并存储所有其他行for (int i = 1; ; ++i){if (!first)break;cur = first;first = 0;int j = 0;for (; ; ++j){cur->row = i;out_corners.push_back(cur);cv::circle(drawImg, cur->pt, 5, cvScalar(0, 255, 0), -1, 8, 0);if (cur->count == 2 + (i < height - 1) && j > 0)break;right = 0;//找到一个尚未处理的邻居//它有一个来自前一行的邻居for (int k = 0; k < 4; ++k){ChessBoardCorner* c = cur->neighbors[k];if (c && c->row > i){int kk = 0;for (; kk < 4; ++kk){if (c->neighbors[kk] && c->neighbors[kk]->row == i - 1)break;}if (kk < 4){right = c;if (j > 0)break;}else if (j == 0)first = c;}}if (!right)goto finalize;cur = right;}if (j != width - 1)goto finalize;}if ((int)out_corners.size() != corner_count)goto finalize;//检查我们是否需要调换棋盘if (width != pattern_size.width){std::swap(width, height);std::vector<ChessBoardCorner*> tmp(out_corners);for (int i = 0; i < height; ++i)for (int j = 0; j < width; ++j)out_corners[i * width + j] = tmp[j * height + i];}//检查是否需要恢复每一行的顺序{cv::Point2f p0 = out_corners[0]->pt,p1 = out_corners[pattern_size.width - 1]->pt,p2 = out_corners[pattern_size.width]->pt;if ((p1.x - p0.x)*(p2.y - p1.y) - (p1.y - p0.y)*(p2.x - p1.x) < 0){if (width % 2 == 0){for (int i = 0; i < height; ++i)for (int j = 0; j < width / 2; ++j)std::swap(out_corners[i * width + j], out_corners[i * width + width - j - 1]);}else{for (int j = 0; j < width; ++j)for (int i = 0; i < height / 2; ++i)std::swap(out_corners[i * width + j], out_corners[(height - i - 1) * width + j]);}}}result = corner_count;}finalize:if (result <= 0){corner_count = MIN(corner_count, pattern_size.area());out_corners.resize(corner_count);for (int i = 0; i < corner_count; i++)out_corners[i] = corners[i];result = -corner_count;if (result == -pattern_size.area())result = -result;}std::string strImg = storagePath + "/step4.bmp";cv::imwrite(strImg, drawImg);return result;}//检查输出角点数组的单调性//检查每一行和每一列都是非常单调的曲线//它分析棋盘格每一行每一列如下://对于位于同一行/列端点之间的每个角c,它检查到线段(a, b)的点投影位于同一行/列中相邻角的投影之间bool ChessBoardDetector::checkBoardMonotony(const std::vector<cv::Point2f> &corners){for (int k = 0; k < 2; ++k){//只分析 k == 0 时// max_i = pattern_size.height == 假设为16// max_j = pattern_size.width == 假设为27int max_i = (k == 0 ? pattern_size.height : pattern_size.width);int max_j = (k == 0 ? pattern_size.width : pattern_size.height) - 1;for (int i = 0; i < max_i; ++i){// i = 0 时 a = corners[0]// i = 0 时 b = corners[26]// i = 1 时 a = corners[27]// i = 1 时 b = corners[53]// 即 a 和 b 分别为水平方向不同行的端点角cv::Point2f a = k == 0 ? corners[i * pattern_size.width] : corners[i];cv::Point2f b = k == 0 ? corners[(i + 1) * pattern_size.width - 1] : corners[(pattern_size.height - 1) * pattern_size.width + i];//计算两个端点角距离曼哈顿距离float dx0 = b.x - a.x, dy0 = b.y - a.y;if (fabs(dx0) + fabs(dy0) < FLT_EPSILON)return false;float prevt = 0;for (int j = 1; j < max_j; ++j){// i = 0, j = 1 时 c = corners[1]// i = 0, j = 2 时 c = corners[2]// i = 1, j = 1 时 c = corners[28]// i = 1, j = 2 时 c = corners[29]// 即 c 为对应行内的角点信息cv::Point2f c = k == 0 ? corners[i * pattern_size.width + j] : corners[j * pattern_size.width + i];//判定依据float t = ((c.x - a.x) * dx0 + (c.y - a.y) * dy0) / (dx0 * dx0 + dy0 * dy0);if (t < prevt || t > 1)return false;prevt = t;}}}return true;}//总流程(提取棋盘格角点信息,并返回)bool ChessBoardDetector::processQuads(std::vector<cv::Point2f> &out_corners, int &prev_sqr_size){out_corners.resize(0);if (all_quads_count <= 0)return false;//四边形数组大小size_t max_quad_buf_size = all_quads.size();//获取四边形的邻四边形findQuadNeighbors();//分配额外的四边形数组std::vector<ChessBoardQuad*> quad_group;std::vector<ChessBoardCorner*> corner_group;corner_group.reserve(max_quad_buf_size * 4);for (int group_idx = 0; ; group_idx++){//按顺时针方向连接四边形findConnectedQuads(quad_group, group_idx);if (quad_group.empty())break;int count = (int)quad_group.size();//将四边形数组按指定顺序排序count = orderFoundConnectedQuads(quad_group);//没有找到内四边形,获取剩余四边形关联数组if (count == 0)continue;//清理多出来的四边形count = cleanFoundConnectedQuads(quad_group);//检测角点数组count = checkQuadGroup(quad_group, corner_group);int n = count > 0 ? pattern_size.width * pattern_size.height : -count;n = MIN(n, pattern_size.width * pattern_size.height);float sum_dist = 0;int total = 0;//计算每个格子的平均距离for (int i = 0; i < n; i++){int ni = 0;float sum = corner_group[i]->sumDist(ni);sum_dist += sum;total += ni;}prev_sqr_size = cvRound(sum_dist / MAX(total, 1));if (count > 0 || (-count > (int)out_corners.size())){//复制边角到输出阵列out_corners.reserve(n);for (int i = 0; i < n; ++i)out_corners.push_back(corner_group[i]->pt);if (count == pattern_size.width * pattern_size.height&& checkBoardMonotony(out_corners)){return true;}}}return false;}

棋盘格角点检测总流程文件: FindChessBoardCorners.cpp

#include "ChessBoardDetector.h"//计算输入图像的直方图template<typename ArrayContainer>static void icvGetIntensityHistogram256(const cv::Mat &img, ArrayContainer &piHist){//初始化数组直方图for (int i = 0; i < 256; i++){piHist[i] = 0;}//求对应值像素个数之和for (int j = 0; j < img.rows; j++){const uchar* row = img.ptr<uchar>(j);for (int i = 0; i < img.cols; i++){piHist[row[i]]++;}}}//平滑直方图使用的窗口大小为 2 * iWidth + 1template<int iWidth_, typename ArrayContainer>static void icvSmoothHistogram256(const ArrayContainer &piHist, ArrayContainer &piHistSmooth, int iWidth = 0){iWidth = (iWidth_ != 0) ? iWidth_ : iWidth;CV_Assert(iWidth > 0);for (int i = 0; i < 256; ++i){int iIdx_min = MAX(0, i - iWidth);int iIdx_max = MIN(255, i + iWidth);int iSmooth = 0;for (int iIdx = iIdx_min; iIdx <= iIdx_max; ++iIdx){iSmooth += piHist[iIdx];}//缺陷:对边缘直方图数组不友好(但Opencv未做处理,可能是该问题对整体平滑影响不大)piHistSmooth[i] = iSmooth / (2 * iWidth + 1);}}//计算直方图的梯度template<typename ArrayContainer>static void icvGradientOfHistogram256(const ArrayContainer &piHist, ArrayContainer &piHistGrad){piHistGrad[0] = 0;int prev_grad = 0;for (int i = 1; i < 255; i++){int grad = piHist[i - 1] - piHist[i + 1];if (std::abs(grad) < 100){if (prev_grad == 0){grad = -100;}else{grad = prev_grad;}}piHistGrad[i] = grad;prev_grad = grad;}piHistGrad[255] = 0;}//基于灰度直方图分析进行智能图像阈值分割static void icvBinarizationHistogramBased(cv::Mat &img){int iCols = img.cols;int iRows = img.rows;int iMaxPix = iCols * iRows;int iMaxPix1 = iMaxPix / 1000;const int iNumBins = 256;const int iMaxPos = 20;//动态创建用于存放0~256数据的数组cv::AutoBuffer<int, 256> piHistIntensity(iNumBins);cv::AutoBuffer<int, 256> piHistSmooth(iNumBins);cv::AutoBuffer<int, 256> piHistGrad(iNumBins);cv::AutoBuffer<int> piMaxPos(iMaxPos);//计算输入图像的直方图icvGetIntensityHistogram256(img, piHistIntensity);//平滑图像的直方图(iWidth_ = 1)icvSmoothHistogram256<1>(piHistIntensity, piHistSmooth);//计算梯度icvGradientOfHistogram256(piHistSmooth, piHistGrad);//检查零项(获取直方图顶点对应的像素区域)unsigned iCntMaxima = 0;for (int i = iNumBins - 2; (i > 2) && (iCntMaxima < iMaxPos); --i){if ((piHistGrad[i - 1] < 0) && (piHistGrad[i] > 0)){int iSumAroundMax = piHistSmooth[i - 1] + piHistSmooth[i] + piHistSmooth[i + 1];if (!(iSumAroundMax < iMaxPix1 && i < 64)){piMaxPos[iCntMaxima++] = i;}}}int iThresh = 0;if (iCntMaxima == 0){//内部没有任何最大值(图像只有0和255未被作为计算)//图像是否已经二值化(不管是否二值化,选择平均强度作为阈值分割线)const int iMaxPix2 = iMaxPix / 2;for (int sum = 0, i = 0; i < 256; ++i){sum += piHistIntensity[i];if (sum > iMaxPix2){sum += piHistIntensity[i];if (sum > iMaxPix2){iThresh = i;break;}}}}else if (iCntMaxima == 1){iThresh = piMaxPos[0] / 2;}else if (iCntMaxima == 2){iThresh = (piMaxPos[0] + piMaxPos[1]) / 2;}else //iCntMaxima >= 3{//检查白色阈值int iIdxAccSum = 0, iAccum = 0;for (int i = iNumBins - 1; i > 0; --i){iAccum += piHistIntensity[i];//iMaxPix/18大约是5,5%,棋盘白色部分所需的最小像素数(经验值)if (iAccum > iMaxPix / 18){iIdxAccSum = i;break;}}unsigned iIdxBGMax = 0;int iBrightMax = piMaxPos[0];for (unsigned n = 0; n < iCntMaxima - 1; ++n){iIdxBGMax = n + 1;if (piMaxPos[n] < iIdxAccSum){break;}iBrightMax = piMaxPos[n];}//检查黑色阈值int iMaxVal = piHistIntensity[piMaxPos[iIdxBGMax]];//若阈值太过接近255,则跳转到下一个最大值if (piMaxPos[iIdxBGMax] >= 250 && iIdxBGMax + 1 < iCntMaxima){iIdxBGMax++;iMaxVal = piHistIntensity[piMaxPos[iIdxBGMax]];}for (unsigned n = iIdxBGMax + 1; n < iCntMaxima; n++){if (piHistIntensity[piMaxPos[n]] >= iMaxVal){iMaxVal = piHistIntensity[piMaxPos[n]];iIdxBGMax = n;}}//计算二值化阈值int iDist2 = (iBrightMax - piMaxPos[iIdxBGMax]) / 2;iThresh = iBrightMax - iDist2;}if (iThresh > 0){//运算符重载,实现图像二值化,将大于或等于iThresh的像素转换为255,将小于iThresh的像素转化为0img = (img >= iThresh);}}static void icvGetQuadrangleHypotheses(std::vector<std::vector<cv::Point>> &contours, const std::vector<cv::Vec4i> &hierarchy, std::vector<std::pair<float, int>> &quads, int class_id){//最小方体比例const float min_aspect_ratio = 0.3f;//最大方体比例const float max_aspect_ratio = 3.0f;//最小盒子大小const float min_box_size = 10.0f;typedef std::vector< std::vector<cv::Point>>::const_iterator iter_t;iter_t i;for (i = contours.begin(); i != contours.end(); ++i){const iter_t::difference_type idx = i - contours.begin();if (hierarchy.at(idx)[3] != -1)continue;const std::vector<cv::Point> &c = *i;cv::RotatedRect box = cv::minAreaRect(c);float box_size = MAX(box.size.width, box.size.height);if (box_size < min_box_size){continue;}float aspect_ratio = box.size.width / MAX(box.size.height, 1);if (aspect_ratio < min_aspect_ratio || aspect_ratio > max_aspect_ratio){continue;}quads.emplace_back(box_size, class_id);}}static void fillQuads(cv::Mat &white, cv::Mat &black, double white_thresh, double black_thresh, std::vector<std::pair<float, int> > &quads){cv::Mat thresh;{std::vector<std::vector<cv::Point>> contours;std::vector<cv::Vec4i> hierarchy;cv::threshold(white, thresh, white_thresh, 255, cv::THRESH_BINARY);//cv::RETR_CCOMP 检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的轮廓还包含了其他的轮廓信息,则内围外的所有轮廓均归属于顶层//cv::CHAIN_APPROX_SIMPLE 仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留cv::findContours(thresh, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);icvGetQuadrangleHypotheses(contours, hierarchy, quads, 1);}{std::vector<std::vector<cv::Point>> contours;std::vector<cv::Vec4i> hierarchy;threshold(black, thresh, black_thresh, 255, cv::THRESH_BINARY_INV);cv::findContours(thresh, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);icvGetQuadrangleHypotheses(contours, hierarchy, quads, 0);}}inline bool less_pred(const std::pair<float, int> &p1, const std::pair<float, int> &p2){return p1.first < p2.first;}static void countClasses(const std::vector<std::pair<float, int>> &pairs, size_t idx1, size_t idx2, std::vector<int> &counts){//将两个为零的元素赋值到当前counts容器中counts.assign(2, 0);for (size_t i = idx1; i != idx2; i++){counts[pairs[i].second]++;}}static bool checkQuads(std::vector<std::pair<float, int>> &quads, const cv::Size &size){const size_t min_quads_count = size.width * size.height / 2;std::sort(quads.begin(), quads.end(), less_pred);//检查是否有很多大小相同的方块const float size_rel_dev = 0.4f;//floodfill-style算法for (size_t i = 0; i < quads.size(); i++){size_t j = i + 1;for (; j < quads.size(); j++){if (quads[j].first / quads[i].first > 1.0f + size_rel_dev){break;}}if (j + 1 > min_quads_count + i){//检查黑色和白色块的数量std::vector<int> counts;countClasses(quads, i, j, counts);const int black_count = cvRound(ceil(size.width / 2.0) * ceil(size.height / 2.0));const int white_count = cvRound(floor(size.width / 2.0)*floor(size.height / 2.0));if (counts[0] < black_count*0.75 ||counts[1] < white_count*0.75){continue;}return true;}}return false;}//快速检查输入图像中是否有棋盘。int checkChessboardBinary(const cv::Mat &img, const cv::Size &size){CV_Assert(img.channels() == 1 && img.depth() == CV_8U);cv::Mat white = img.clone();cv::Mat black = img.clone();int result = 0;for (int erosion_count = 0; erosion_count <= 3; erosion_count++){if (1 == result)break;//第一次迭代保留原始图像if (0 != erosion_count){cv::erode(white, white, cv::Mat(), cv::Point(-1, -1), 1);cv::dilate(black, black, cv::Mat(), cv::Point(-1, -1), 1);}std::vector<std::pair<float, int>> quads;fillQuads(white, black, 128, 128, quads);if (checkQuads(quads, size))result = 1;}return result;}bool checkChessboard(cv::Mat img, cv::Size size){CV_Assert(img.channels() == 1 && img.depth() == CV_8U);const int erosion_count = 1;const float black_level = 20.f;const float white_level = 130.f;const float black_white_gap = 70.f;cv::Mat white;cv::Mat black;erode(img, white, cv::Mat(), cv::Point(-1, -1), erosion_count);dilate(img, black, cv::Mat(), cv::Point(-1, -1), erosion_count);bool result = false;for (float thresh_level = black_level; thresh_level < white_level && !result; thresh_level += 20.0f){std::vector<std::pair<float, int>> quads;fillQuads(white, black, thresh_level + black_white_gap, thresh_level, quads);if (checkQuads(quads, size))result = true;}return result;}//CALIB_CB_ADAPTIVE_THRESH == 1 使用自适应阈值将灰度图转化为二值图像,而不是固定的由图像的平均亮度计算出来的阈值//CALIB_CB_NORMALIZE_IMAGE == 2 在利用固定阈值或自适应的阈值进行二值化之前, 先使用equalizeHist来均衡化图像Gamma值//CALIB_CB_FILTER_QUADS == 4 使用其他准则(如轮廓面积、周长、类似方形的形状)来去除轮廓检测阶段检测到的错误方块//CALIB_CB_FAST_CHECK== 8 在图像上快速检测一下棋盘格角点,如果没有棋盘格角点被发现,绕过其他函数的调用,直接运行结束bool findChessBoardCorner(cv::Mat image, cv::Size patternSize, std::vector<cv::Point2f> &corners, int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE){//CV_INSTRUMENT_REGION(); --Opencv接口监控bool found = false;const int min_dilations = 0;const int max_dilations = 7;int type = image.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type);cv::Mat img = image.clone();//只支持8位灰度或彩色图像if (!(type, depth == CV_8U && (cn == 1 || cn == 3 || cn == 4))){return false;}//图案的宽度和高度都应该大于2if (patternSize.width <= 2 || patternSize.height <= 2){return false;}//角容量为空if (!corners.empty()){return false;}//将多通道转换为单通道if (img.channels() != 1){cvtColor(img, img, cv::COLOR_BGR2GRAY);}std::vector<cv::Point2f> out_corners;int prev_sqr_size = 0;//clone()是深拷贝cv::Mat threshImgNew = img.clone();//自适应二值化icvBinarizationHistogramBased(threshImgNew);if (flags & CALIB_CB_FAST_CHECK){//执行新的方法检测棋盘//使用依赖于图像直方图的阈值对图像进行二值化//使用旧方法if (checkChessboardBinary(threshImgNew, patternSize) <= 0){//再次筛选if (!checkChessboard(img, patternSize)){corners.clear();return false;}}}ChessBoardDetector detector(patternSize);//对图像进行膨胀,如果未找全棋盘格,就在原来膨胀的基础上继续膨胀,直到找全棋盘格,或达到迭代次数后终止。for (int dilations = min_dilations; dilations <= max_dilations; dilations++){cv::dilate(threshImgNew, threshImgNew, cv::getStructuringElement(cv::MORPH_RECT,cv::Size(3, 3), cv::Point(-1, -1)), cv::Point(-1, -1), 1);//我们可以找到边缘的矩形,我们在图像边缘周围画一条白线。cv::rectangle(threshImgNew, cv::Point(0, 0), cv::Point(threshImgNew.cols - 1, threshImgNew.rows - 1), cv::Scalar(255), 3, cv::LINE_8);//复位(清空)detector.reset();//浅拷贝cv::Mat binarizedImg = threshImgNew;//提取四边形信息detector.generateQuads(binarizedImg, flags);//总流程(提取棋盘格角点信息,并返回)if (detector.processQuads(out_corners, prev_sqr_size)){found = true;break;}}//如果检测失败,恢复到旧的、更慢的方法if (!found){if (flags & CALIB_CB_NORMALIZE_IMAGE){img = img.clone();equalizeHist(img, img);}cv::Mat thresh_img;prev_sqr_size = 0;const bool useAdaptive = flags & CALIB_CB_ADAPTIVE_THRESH;if (!useAdaptive){//经验值(图像均值 - 10 -- 作为分割阈值)double mean = cv::mean(img).val[0];int thresh_level = MAX(cvRound(mean - 10), 10);threshold(img, thresh_img, thresh_level, 255, cv::THRESH_BINARY);}//如果没有设置CALIB_CB_ADAPTIVE_THRESH标志,则遍历k没有意义int max_k = useAdaptive ? 6 : 1;for (int k = 0; k < max_k && !found; k++){for (int dilations = min_dilations; dilations <= max_dilations; dilations++){if (useAdaptive){//计算块大小(经验值)int block_size = cvRound(prev_sqr_size == 0? MAX(img.cols, img.rows) * (k % 2 == 0 ? 0.2 : 0.1): prev_sqr_size * 2);//保证计算出来的块大小是奇数block_size = block_size | 1;//自适应二值化//参数1 输入图像//参数2 输出图像//参数3 向上最大值//参数4 自适应方法cv::ADAPTIVE_THRESH_MEAN_C(平均)像素块每个像素加权值相同 cv::ADAPTIVE_THRESH_GAUSSIAN_C(高斯)像素块每个像素加权值不同//参数5 阈值话类型//参数6 块大小 计算每个像素周围块大小的像素块加权均值并减去常量C得到,即每个像素的阈值分割不一致,且块大小必须是奇数//参数7 常量cv::adaptiveThreshold(img, thresh_img, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, block_size, (k / 2) * 5);if (dilations > 0)dilate(thresh_img, thresh_img, cv::Mat(), cv::Point(-1, -1), dilations - 1);}else{dilate(thresh_img, thresh_img, cv::Mat(), cv::Point(-1, -1), 1);}//往后的方法与上述一致rectangle(thresh_img, cv::Point(0, 0), cv::Point(thresh_img.cols - 1, thresh_img.rows - 1), cv::Scalar(255), 1, cv::LINE_8);detector.reset();cv::Mat binarized_img = thresh_img;detector.generateQuads(binarized_img, flags);if (detector.processQuads(out_corners, prev_sqr_size)){found = 1;break;}}}}//不建议运行,但源码存在该程序(重复运行)if (found)found = detector.checkBoardMonotony(out_corners);//要求每个角点不能太过接近图像边缘(可执行可不执行)if (found){const int BORDER = 8;for (int k = 0; k < patternSize.width * patternSize.height; ++k){if (out_corners[k].x <= BORDER || out_corners[k].x > img.cols - BORDER ||out_corners[k].y <= BORDER || out_corners[k].y > img.rows - BORDER){found = false;break;}}}if (found){//假如水平角点个数或竖直角点个数中有一个或两个都为偶数时,查看角点排列是否倒序,如果倒序进行回正if ((patternSize.height & 1) == 0 && (patternSize.width & 1) == 0){int last_row = (patternSize.height - 1) * patternSize.width;double dy0 = out_corners[last_row].y - out_corners[0].y;if (dy0 < 0){int n = patternSize.width * patternSize.height;for (int i = 0; i < n / 2; i++){std::swap(out_corners[i], out_corners[n - i - 1]);}}}//亚像素角点检测//参数1 输入图像//参数2 角点集合(即是输入,又是输出)//参数3 计算亚像素角点时考虑的区域范围//参数4 类似参数3,但是总是具有较小的范围,通常忽略//参数5 计算亚像素迭代停止标准 cv::TermCriteria::MAX_ITER(达到最大迭代次数终止 15) cv::TermCriteria::EPS(角点位置变化的最小值达到指定大小终止 0.1)cv::cornerSubPix(img, out_corners, cv::Size(2, 2), cv::Size(-1, -1),cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 15, 0.1));}corners.resize(out_corners.size());std::copy(out_corners.begin(), out_corners.end(), corners.begin());return found;}

主程序:main.cpp

//Opencv头文件#include "ChessBoardDetector.h"//导入Opencv库#ifdef _DEBUG#pragma comment(lib,"opencv_world342d.lib")#else#pragma comment(lib,"opencv_world342.lib")#endifstd::string storagePath;bool findChessBoardCorner(cv::Mat image, cv::Size patternSize, std::vector<cv::Point2f> &corners, int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE);int main(){cv::Mat srcMat;srcMat = cv::imread("2.png", 1);if (srcMat.empty()){return -1;}std::vector<cv::Point2f> corners;cv::Size boardSize(10, 9);storagePath = ".";bool found = findChessBoardCorner(srcMat, boardSize, corners);if (!found){return -1;}for (int i = 0; i < corners.size(); i++){cv::circle(srcMat, corners[i], 5, cv::Scalar(0, 255, 0), -1, 8, 0);}return 0;}

上面的程序我在不同处理阶段打印了效果图,便于查看不同阶段代码的实现效果。

step1.png - > 检测棋盘格图像中黑色区域四边形(这是上面黑色背景和白色背景不同处理角点的主要原因)

step2.png - > 按顺指针方向连接找到的黑色四边形区域信息

step3.png - > 找黑色四边形的内四边形区域(内四边形区域及时其周边有四个相邻的黑色四边形区域)

step4.png - > 增删黑色棋盘格多余或缺失的四边形区域后,按一定规则排序并绘制角点

最终效果如下:

现将处理后的棋盘格带入此接口,测试代码如下:观察 step1.png 图片:

//Opencv头文件#include "ChessBoardDetector.h"#include <time.h>//导入Opencv库#ifdef _DEBUG#pragma comment(lib,"opencv_world342d.lib")#else#pragma comment(lib,"opencv_world342.lib")#endifdouble imgScale = 1;cv::Size patSizes = cv::Size(12, 11);std::string storagePath;bool findChessBoardCorner(cv::Mat image, cv::Size patternSize, std::vector<cv::Point2f> &corners, int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE);void cornerDetect(cv::Mat m, std::vector<cv::Point2f> &pts){cv::Mat mGray, mask, simg;cv::Size camSize;if (m.empty()){std::cout << "Matrix is empty!" << std::endl;return;}if (m.type() == CV_8UC3){cv::cvtColor(m, mGray, cv::COLOR_BGR2GRAY);}else{mGray = m.clone();}camSize = mGray.size();clock_t flag1 = clock();resize(mGray, mask, cv::Size(200, 200));GaussianBlur(mask, mask, cv::Size(13, 13), 11, 11);clock_t flag2 = clock();resize(mask, mask, camSize);medianBlur(mask, mask, 9);clock_t flag3 = clock();for (int v = 0; v < camSize.height; v++){for (int u = 0; u < camSize.width; u++){int x = (((int)mGray.at<uchar>(v, u) - (int)mask.at<uchar>(v, u)) << 1) + 128;mGray.at<uchar>(v, u) = std::max(std::min(x, 255), 0);}}resize(mGray, simg, cv::Size(), imgScale, imgScale);clock_t flag4 = clock();storagePath = ".";bool found = findChessBoardCorner(simg, patSizes, pts);clock_t flag5 = clock();for (unsigned int i = 0; i < pts.size(); ++i){pts[i] *= 1. / imgScale;}clock_t flag6 = clock();if (found){cornerSubPix(mGray, pts, cv::Size(21, 21), cv::Size(-1, -1),cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 5000, 0.0001));}clock_t flag7 = clock();for (int i = 0; i < pts.size(); i++){cv::circle(m, pts[i], 5, cv::Scalar(0, 255, 0), -1, 8, 0);}clock_t flag8 = clock();std::cout << cv::saturate_cast<double>(flag2 - flag1) / CLOCKS_PER_SEC << '\t'<< cv::saturate_cast<double>(flag3 - flag2) / CLOCKS_PER_SEC << '\t'<< cv::saturate_cast<double>(flag4 - flag3) / CLOCKS_PER_SEC << '\t'<< cv::saturate_cast<double>(flag5 - flag4) / CLOCKS_PER_SEC << '\t'<< cv::saturate_cast<double>(flag7 - flag6) / CLOCKS_PER_SEC << '\t'<< cv::saturate_cast<double>(flag8 - flag7) / CLOCKS_PER_SEC << '\t'<< cv::saturate_cast<double>(flag8 - flag1) / CLOCKS_PER_SEC << '\t' << found << '\t';return;}int main(){cv::Mat srcMat;srcMat = cv::imread("2.png", 1);if (srcMat.empty()){return -1;}std::vector<cv::Point2f> corners;cornerDetect(srcMat, corners);return 0;}

观察 step1.png 图片:

可以看出,原先周边黑色方块区域是连接在一起的,经处理后,被分割成不同的周边黑色小方格区域,使其在进行第一步操作时可以将这些黑色方块区域捕获到,在后续的处理过程中,便依据这些捕获的方块进行角点获取。若还不理解的,看下图:

最终效果图如下:

以上程序都需要已知棋盘格角点后再进行角点检测,那有没有不需要输入,自适应的获取棋盘格角点位置信息呢?

3、基于生长的棋盘格角点检测

代码来源于:/software/libcbdetect/

现贴出我整理后的部分,若需要纯源码,可进入以上给出的路径进行获取。

获取角点的头文件:FindCorners.h

#ifndef FINDCORNERS_H#define FINDCORNERS_H#include "HeadStruct.h"class FindCorners{public:FindCorners();FindCorners(cv::Mat img);~FindCorners();public:void detectCorners(cv::Mat &Src, Corners &resultCornors, float scoreThreshold, bool isDrawSrc = false);private://正态分布float normpdf(float dist, float mu, float sigma);//获取最小值void getMin(cv::Mat src1, cv::Mat src2, cv::Mat &dst);//获取最大值void getMax(cv::Mat src1, cv::Mat src2, cv::Mat &dst);//计算图像导数,用于主轴估计void getImageAngleAndWeight(cv::Mat img, cv::Mat &imgDu, cv::Mat &imgDv, cv::Mat &imgAngle, cv::Mat &imgWeight);//估计边缘方向void edgeOrientations(cv::Mat imgAngle, cv::Mat imgWeight, int index);//找到平滑直方图的模式void findModesMeanShift(std::vector<float> hist, std::vector<float> &hist_smoothed, std::vector<std::pair<float, int>> &modes, float sigma);//分数 角落void scoreCorners(cv::Mat img, cv::Mat imgAngle, cv::Mat imgWeight, std::vector<cv::Point2f> &cornors, std::vector<int> radius, std::vector<float> &score);//统计计算角落void cornerCorrelationScore(cv::Mat img, cv::Mat imgWeight, std::vector<cv::Point2f> cornersEdge, float &score);//亚像素细分void refineCorners(std::vector<cv::Point2f> &cornors, cv::Mat imgDu, cv::Mat imgDv, cv::Mat imgAngle, cv::Mat imgWeight, float radius);//生成核void createkernel(float angle1, float angle2, int kernelSize, cv::Mat &kernelA, cv::Mat &kernelB, cv::Mat &kernelC, cv::Mat &kernelD);//非极大值抑制void nonMaximumSuppression(cv::Mat& inputCorners, std::vector<cv::Point2f>& outputCorners, float threshold, int margin, int patchSize);private:std::vector<cv::Point2f> cornerPoints;std::vector<cv::Point2f> templateProps;std::vector<int> radius;std::vector<std::vector<float> > cornersEdge1;std::vector<std::vector<float> > cornersEdge2;};#endif

获取角点对应的cpp文件:FindCorners.cpp

#include "FindCorners.h"FindCorners::FindCorners(){}FindCorners::~FindCorners(){}//构造函数(初始化模板参数)FindCorners::FindCorners(cv::Mat img){//初始化核半径radius.push_back(4);radius.push_back(8);radius.push_back(12);//初始化核旋转角度templateProps.push_back(cv::Point2f((float)0, (float)CV_PI / 2));templateProps.push_back(cv::Point2f((float)CV_PI / 4, (float)-CV_PI / 4));templateProps.push_back(cv::Point2f((float)0, (float)CV_PI / 2));templateProps.push_back(cv::Point2f((float)CV_PI / 4, (float)-CV_PI / 4));templateProps.push_back(cv::Point2f((float)0, (float)CV_PI / 2));templateProps.push_back(cv::Point2f((float)CV_PI / 4, (float)-CV_PI / 4));}//正态概率密度函数float FindCorners::normpdf(float dist, float mu, float sigma) {return exp(-0.5 * (dist - mu) * (dist - mu) / (sigma * sigma)) / (std::sqrt(2 * CV_PI) * sigma);}//**************************生成核*****************************////angle代表核类型:45度核和90度核//kernelSize代表核大小(最终生成的核的大小为kernelSize * 2 + 1)//kernelA...kernelD是生成的核//*************************************************************************//void FindCorners::createkernel(float angle1, float angle2, int kernelSize, cv::Mat &kernelA, cv::Mat &kernelB, cv::Mat &kernelC, cv::Mat &kernelD) {int width = (int)kernelSize * 2 + 1;int height = (int)kernelSize * 2 + 1;kernelA = cv::Mat::zeros(height, width, CV_32F);kernelB = cv::Mat::zeros(height, width, CV_32F);kernelC = cv::Mat::zeros(height, width, CV_32F);kernelD = cv::Mat::zeros(height, width, CV_32F);for (int u = 0; u < width; ++u) {for (int v = 0; v < height; ++v) {//相当于将坐标原点移动到核中心float vec[] = {u - kernelSize, v - kernelSize };//相当于计算到中心的距离float dis = std::sqrt(vec[0] * vec[0] + vec[1] * vec[1]);//相当于将坐标原点移动后的核进行旋转,以此产生四种核//X= X0 * cos + Y0 * sin;//Y= Y0 * cos - X0 * sinfloat side1 = vec[0] * (-sin(angle1)) + vec[1] * cos(angle1);float side2 = vec[0] * (-sin(angle2)) + vec[1] * cos(angle2);if (side1 <= -0.1 && side2 <= -0.1) {kernelA.ptr<float>(v)[u] = normpdf(dis, 0, kernelSize / 2);}if (side1 >= 0.1 && side2 >= 0.1) {kernelB.ptr<float>(v)[u] = normpdf(dis, 0, kernelSize / 2);}if (side1 <= -0.1 && side2 >= 0.1) {kernelC.ptr<float>(v)[u] = normpdf(dis, 0, kernelSize / 2);}if (side1 >= 0.1 && side2 <= -0.1) {kernelD.ptr<float>(v)[u] = normpdf(dis, 0, kernelSize / 2);}}}//归一化kernelA = kernelA / cv::sum(kernelA)[0];kernelB = kernelB / cv::sum(kernelB)[0];kernelC = kernelC / cv::sum(kernelC)[0];kernelD = kernelD / cv::sum(kernelD)[0];}//**************************//获取最小值*****************************////*************************************************************************//void FindCorners::getMin(cv::Mat src1, cv::Mat src2, cv::Mat &dst) {int rowsLeft = src1.rows;int colsLeft = src1.cols;int rowsRight = src2.rows;int colsRight = src2.cols;if (rowsLeft != rowsRight || colsLeft != colsRight)return;//查看图像数据是否一行行连接,可不执行该操作int nr = rowsLeft;int nc = colsLeft;if (src1.isContinuous()) {nc = nc * nr;nr = 1;}for (int i = 0; i < nr; i++) {const float* dataLeft = src1.ptr<float>(i);const float* dataRight = src2.ptr<float>(i);float* dataResult = dst.ptr<float>(i);for (int j = 0; j < nc; ++j) {dataResult[j] = (dataLeft[j] < dataRight[j]) ? dataLeft[j] : dataRight[j];}}}//**************************//获取最大值*****************************////*************************************************************************//void FindCorners::getMax(cv::Mat src1, cv::Mat src2, cv::Mat &dst) {int rowsLeft = src1.rows;int colsLeft = src1.cols;int rowsRight = src2.rows;int colsRight = src2.cols;if (rowsLeft != rowsRight || colsLeft != colsRight)return;int nr = rowsLeft;int nc = colsLeft;if (src1.isContinuous()) {nc = nc * nr;nr = 1;}for (int i = 0; i < nr; i++){const float* dataLeft = src1.ptr<float>(i);const float* dataRight = src2.ptr<float>(i);float* dataResult = dst.ptr<float>(i);for (int j = 0; j < nc; ++j) {dataResult[j] = (dataLeft[j] >= dataRight[j]) ? dataLeft[j] : dataRight[j];}}}//计算图像导数,用于主轴估计void FindCorners::getImageAngleAndWeight(cv::Mat img, cv::Mat &imgDu, cv::Mat &imgDv, cv::Mat &imgAngle, cv::Mat &imgWeight) {cv::Mat sobelKernel(3, 3, CV_32F);cv::Mat sobelKernelTrs(3, 3, CV_32F);//soble滤波器算子核sobelKernel.col(0).setTo(cv::Scalar(-1));sobelKernel.col(1).setTo(cv::Scalar(0));sobelKernel.col(2).setTo(cv::Scalar(1));sobelKernelTrs = sobelKernel.t();filter2D(img, imgDu, CV_32F, sobelKernel);filter2D(img, imgDv, CV_32F, sobelKernelTrs);if (imgDu.size() != imgDv.size())return;//笛卡尔坐标转化为极坐标cv::cartToPolar(imgDu, imgDv, imgWeight, imgAngle, false);for (int i = 0; i < imgDu.rows; i++){for (int j = 0; j < imgDu.cols; j++){float* dataAngle = imgAngle.ptr<float>(i);if (dataAngle[j] < 0)dataAngle[j] = dataAngle[j] + CV_PI;else if (dataAngle[j] > CV_PI)dataAngle[j] = dataAngle[j] - CV_PI;}}//for (int i = 0; i < imgDu.rows; i++)//{//float* dataDv = imgDv.ptr<float>(i);//float* dataDu = imgDu.ptr<float>(i);//float* dataAngle = imgAngle.ptr<float>(i);//float* dataWeight = imgWeight.ptr<float>(i);//for (int j = 0; j < imgDu.cols; j++)//{//if (dataDu[j] > 0.000001)//{//dataAngle[j] = atan2((float)dataDv[j], (float)dataDu[j]);//if (dataAngle[j] < 0)//dataAngle[j] = dataAngle[j] + CV_PI;//else if (dataAngle[j] > CV_PI)//dataAngle[j] = dataAngle[j] - CV_PI;//}//dataWeight[j] = std::sqrt((float)dataDv[j] * (float)dataDv[j] + (float)dataDu[j] * (float)dataDu[j]);//}//}}//**************************非极大值抑制*****************************////inputCorners是输入角点,outputCorners是非极大值抑制后的角点//threshold是设定的阈值//margin是进行非极大值抑制时检查方块与输入矩阵边界的距离,patchSize是该方块的大小//*************************************************************************//void FindCorners::nonMaximumSuppression(cv::Mat& inputCorners, std::vector<cv::Point2f>& outputCorners, float threshold, int margin, int patchSize){if (inputCorners.size <= 0){std::cout << "The imput mat is empty!" << std::endl; return;}//移动检查方块,每次移动一个方块的大小for (int i = margin + patchSize; i < inputCorners.cols - (margin + patchSize); i = i + patchSize + 1){for (int j = margin + patchSize; j < inputCorners.rows - (margin + patchSize); j = j + patchSize + 1){float maxVal = inputCorners.ptr<float>(j)[i];int maxX = i; int maxY = j;//找出该检查方块中的局部最大值for (int m = i; m < i + patchSize + 1; m++){for (int n = j; n < j + patchSize + 1; n++){float temp = inputCorners.ptr<float>(n)[m];if (temp > maxVal){maxVal = temp; maxX = m; maxY = n;}}}//若该局部最大值小于阈值则不满足要求if (maxVal < threshold)continue;//二次检查int flag = 0;for (int m = maxX - patchSize; m < std::min(maxX + patchSize, inputCorners.cols - margin); m++){for (int n = maxY - patchSize; n < std::min(maxY + patchSize, inputCorners.rows - margin); n++){if (inputCorners.ptr<float>(n)[m] > maxVal && (m<i || m>i + patchSize || n<j || n>j + patchSize)){flag = 1; break;}}if (flag)break;}if (flag)continue;outputCorners.push_back(cv::Point(maxX, maxY));std::vector<float> e1(2, 0.0);std::vector<float> e2(2, 0.0);cornersEdge1.push_back(e1);cornersEdge2.push_back(e2);}}}int cmp(const std::pair<float, int> &a, const std::pair<float, int> &b){return a.first > b.first;}//寻找平滑直方图模式void FindCorners::findModesMeanShift(std::vector<float> hist, std::vector<float> &hist_smoothed, std::vector<std::pair<float, int>> &modes, float sigma) {//通过直方图平滑的有效均值漂移近似//计算平滑直方图bool allZeros = true;for (int i = 0; i < hist.size(); i++){float sum = 0;for (int j = -(int)round(2 * sigma); j <= (int)round(2 * sigma); j++){int idx = 0;idx = (i + j) % hist.size();sum = sum + hist[idx] * normpdf(j, 0, sigma);}hist_smoothed[i] = sum;//检查是否至少有一个条目是非零的//(否则寻模可能无穷无尽)if (abs(hist_smoothed[i] - hist_smoothed[0]) > 0.0001)allZeros = false;}if (allZeros)return;//追寻模式for (int i = 0; i < hist.size(); i++){int j = i;while (true){float h0 = hist_smoothed[j];int j1 = (j + 1) % hist.size();int j2 = (j - 1) % hist.size();float h1 = hist_smoothed[j1];float h2 = hist_smoothed[j2];if (h1 >= h0 && h1 >= h2)j = j1;else if (h2 > h0 && h2 > h1)j = j2;elsebreak;}bool ys = true;if (modes.size() == 0){ys = true;}else{for (int k = 0; k < modes.size(); k++){if (modes[k].second == j){ys = false;break;}}}if (ys == true){modes.push_back(std::make_pair(hist_smoothed[j], j));}}std::sort(modes.begin(), modes.end(), cmp);}//估计边缘方向void FindCorners::edgeOrientations(cv::Mat imgAngle, cv::Mat imgWeight, int index) {//箱数(直方图参数)int binNum = 32;//将图像转换为向量if (imgAngle.size() != imgWeight.size())return;std::vector<float> vec_angle, vec_weight;for (int i = 0; i < imgAngle.cols; i++){for (int j = 0; j < imgAngle.rows; j++){//将角度从法线转换为方向float angle = imgAngle.ptr<float>(j)[i] + CV_PI / 2;angle = angle > CV_PI ? (angle - CV_PI) : angle;vec_angle.push_back(angle);vec_weight.push_back(imgWeight.ptr<float>(j)[i]);}}//创建柱状图std::vector<float> angleHist(binNum, 0);for (int i = 0; i < vec_angle.size(); i++){int bin = std::max(std::min((int)floor(vec_angle[i] / (CV_PI / binNum)), binNum - 1), 0);angleHist[bin] = angleHist[bin] + vec_weight[i];}//寻找平滑直方图模式std::vector<float> hist_smoothed(angleHist);std::vector<std::pair<float, int> > modes;findModesMeanShift(angleHist, hist_smoothed, modes, 1);// 如果只有一个或没有模式=>返回无效角if (modes.size() <= 1)return;//计算模态方向并按角排序float pin = (CV_PI / binNum);float fo[2];fo[0] = modes[0].second * pin;fo[1] = modes[1].second * pin;float deltaAngle = 0;if (fo[0] > fo[1]){float t = fo[0];fo[0] = fo[1];fo[1] = t;}deltaAngle = MIN(fo[1] - fo[0], fo[0] - fo[1] + CV_PI);//如果角太小=>返回无效角if (deltaAngle <= 0.3)return;//组数据:方向cornersEdge1[index][0] = cos(fo[0]);cornersEdge1[index][1] = sin(fo[0]);cornersEdge2[index][0] = cos(fo[1]);cornersEdge2[index][1] = sin(fo[1]);}float norm2d(cv::Point2f o){return sqrt(o.x*o.x + o.y*o.y);}//亚像素细分void FindCorners::refineCorners(std::vector<cv::Point2f> &cornors, cv::Mat imgDu, cv::Mat imgDv, cv::Mat imgAngle, cv::Mat imgWeight, float radius) {//图像大小int width = imgDu.cols;int height = imgDu.rows;for (int i = 0; i < cornors.size(); i++){//提取当前角点位置int cu = cornors[i].x;int cv = cornors[i].y;//估计边缘方向int startX, startY, ROIwidth, ROIheight;startX = std::max(cu - radius, (float)0);startY = std::max(cv - radius, (float)0);ROIwidth = std::min(cu + radius, (float)width - 1) - startX + 1;ROIheight = std::min(cv + radius, (float)height - 1) - startY + 1;cv::Mat roiAngle, roiWeight;roiAngle = imgAngle(cv::Rect(startX, startY, ROIwidth, ROIheight));roiWeight = imgWeight(cv::Rect(startX, startY, ROIwidth, ROIheight));edgeOrientations(roiAngle, roiWeight, i);//继续,如果无效的边缘方向if (cornersEdge1[i][0] == 0 && cornersEdge1[i][1] == 0 || cornersEdge2[i][0] == 0 && cornersEdge2[i][1] == 0)continue;//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%//% 角落位置优化 %//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%cv::Mat A1 = cv::Mat::zeros(cv::Size(2, 2), CV_32F);cv::Mat A2 = cv::Mat::zeros(cv::Size(2, 2), CV_32F);for (int u = startX; u < startX + ROIwidth; u++){for (int v = startY; v < startY + ROIheight; v++){//像素的方向向量cv::Point2f o(imgDu.at<float>(v, u), imgDv.at<float>(v, u));float no = norm2d(o);if (no < 0.1)continue;o = o / no;//鲁棒定向细化1float t0 = abs(o.x * cornersEdge1[i][0] + o.y * cornersEdge1[i][1]);//内围层if (t0 < 0.25){cv::Mat addtion(1, 2, CV_32F);addtion.col(0).setTo(imgDu.at<float>(v, u));addtion.col(1).setTo(imgDv.at<float>(v, u));cv::Mat addtionu = imgDu.at<float>(v, u) * addtion;cv::Mat addtionv = imgDv.at<float>(v, u) * addtion;for (int j = 0; j < A1.cols; j++){A1.at<float>(0, j) = A1.at<float>(0, j) + addtionu.at<float>(0, j);A1.at<float>(1, j) = A1.at<float>(1, j) + addtionv.at<float>(0, j);}}//鲁棒定向细化2float t1 = abs(o.x * cornersEdge2[i][0] + o.y * cornersEdge2[i][1]);if (t1 < 0.25) // inlier ?{cv::Mat addtion(1, 2, CV_32F);addtion.col(0).setTo(imgDu.at<float>(v, u));addtion.col(1).setTo(imgDv.at<float>(v, u));cv::Mat addtionu = imgDu.at<float>(v, u) * addtion;cv::Mat addtionv = imgDv.at<float>(v, u) * addtion;for (int j = 0; j < A2.cols; j++){A2.at<float>(0, j) = A2.at<float>(0, j) + addtionu.at<float>(0, j);A2.at<float>(1, j) = A2.at<float>(1, j) + addtionv.at<float>(0, j);}}}}//设置新的角方向cv::Mat v1, foo1;cv::Mat v2, foo2;cv::eigen(A1, v1, foo1);cv::eigen(A2, v2, foo2);cornersEdge1[i][0] = -foo1.at<float>(1, 0);cornersEdge1[i][1] = -foo1.at<float>(1, 1);cornersEdge2[i][0] = -foo2.at<float>(1, 0);cornersEdge2[i][1] = -foo2.at<float>(1, 1);//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%//% 角落位置优化 %//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%cv::Mat G = cv::Mat::zeros(cv::Size(2, 2), CV_32F);cv::Mat b = cv::Mat::zeros(cv::Size(1, 2), CV_32F);for (int u = startX; u < startX + ROIwidth; u++){for (int v = startY; v < startY + ROIheight; v++){//像素的方向向量cv::Point2f o(imgDu.at<float>(v, u), imgDv.at<float>(v, u));float no = norm2d(o);if (no < 0.1)continue;o = o / no;//鲁棒的亚像素角估计//不考虑中心像素if (u != cu || v != cv) {//计算雷尔。像素的位置和距离的向量cv::Point2f w(u - cu, v - cv);float wvv1 = w.x * cornersEdge1[i][0] + w.y * cornersEdge1[i][1];float wvv2 = w.x * cornersEdge2[i][0] + w.y * cornersEdge2[i][1];cv::Point2f wv1(wvv1 * cornersEdge1[i][0], wvv1 * cornersEdge1[i][1]);cv::Point2f wv2(wvv2 * cornersEdge2[i][0], wvv2 * cornersEdge2[i][1]);cv::Point2f vd1(w.x - wv1.x, w.y - wv1.y);cv::Point2f vd2(w.x - wv2.x, w.y - wv2.y);float d1 = norm2d(vd1), d2 = norm2d(vd2);//如果像素对应于任意一个向量/方向if ((d1 < 3) && abs(o.x*cornersEdge1[i][0] + o.y*cornersEdge1[i][1]) < 0.25 \|| (d2 < 3) && abs(o.x*cornersEdge2[i][0] + o.y*cornersEdge2[i][1]) < 0.25){float du = imgDu.at<float>(v, u), dv = imgDv.at<float>(v, u);cv::Mat uvt = (cv::Mat_<float>(2, 1) << u, v);cv::Mat H = (cv::Mat_<float>(2, 2) << du * du, du * dv, dv * du, dv * dv);G = G + H;cv::Mat t = H * (uvt);b = b + t;}}}}//设置新的角位置,如果G有满秩cv::Mat s, u, v;cv::SVD::compute(G, s, u, v);int rank = 0;for (int k = 0; k < s.rows; k++){//不等于零if (s.at<float>(k, 0) > 0.0001 || s.at<float>(k, 0) < -0.0001){rank++;}}if (rank == 2){cv::Mat mp = G.inv()*b;cv::Point2f corner_pos_new(mp.at<float>(0, 0), mp.at<float>(1, 0));//设置角为无效,如果位置更新很大if (norm2d(cv::Point2f(corner_pos_new.x - cu, corner_pos_new.y - cv)) >= 4){cornersEdge1[i][0] = 0;cornersEdge1[i][1] = 0;cornersEdge2[i][0] = 0;cornersEdge2[i][1] = 0;}else{cornors[i].x = mp.at<float>(0, 0);cornors[i].y = mp.at<float>(1, 0);}}//否则:将corner设置为无效else{cornersEdge1[i][0] = 0;cornersEdge1[i][1] = 0;cornersEdge2[i][0] = 0;cornersEdge2[i][1] = 0;}}}//统计计算角落void FindCorners::cornerCorrelationScore(cv::Mat img, cv::Mat imgWeight, std::vector<cv::Point2f> cornersEdge, float &score) {//图像中心int c[] = {imgWeight.cols / 2, imgWeight.cols / 2 };//计算梯度滤波核(带宽= 3 px)cv::Mat img_filter = cv::Mat::ones(imgWeight.size(), imgWeight.type());img_filter = img_filter * -1;for (int i = 0; i < imgWeight.cols; i++){for (int j = 0; j < imgWeight.rows; j++){cv::Point2f p1 = cv::Point2f(i - c[0], j - c[1]);cv::Point2f p2 = cv::Point2f(p1.x*cornersEdge[0].x*cornersEdge[0].x + p1.y*cornersEdge[0].x*cornersEdge[0].y,p1.x*cornersEdge[0].x*cornersEdge[0].y + p1.y*cornersEdge[0].y*cornersEdge[0].y);cv::Point2f p3 = cv::Point2f(p1.x*cornersEdge[1].x*cornersEdge[1].x + p1.y*cornersEdge[1].x*cornersEdge[1].y,p1.x*cornersEdge[1].x*cornersEdge[1].y + p1.y*cornersEdge[1].y*cornersEdge[1].y);float norm1 = sqrt((p1.x - p2.x)*(p1.x - p2.x) + (p1.y - p2.y)*(p1.y - p2.y));float norm2 = sqrt((p1.x - p3.x)*(p1.x - p3.x) + (p1.y - p3.y)*(p1.y - p3.y));if (norm1 <= 1.5 || norm2 <= 1.5){img_filter.ptr<float>(j)[i] = 1;}}}//归一化cv::Mat mean, std, mean1, std1;cv::meanStdDev(imgWeight, mean, std);cv::meanStdDev(img_filter, mean1, std1);for (int i = 0; i < imgWeight.cols; i++){for (int j = 0; j < imgWeight.rows; j++){imgWeight.ptr<float>(j)[i] = (float)(imgWeight.ptr<float>(j)[i] - mean.ptr<double>(0)[0]) / (float)std.ptr<double>(0)[0];img_filter.ptr<float>(j)[i] = (float)(img_filter.ptr<float>(j)[i] - mean1.ptr<double>(0)[0]) / (float)std1.ptr<double>(0)[0];}}//转换成向量std::vector<float> vec_filter, vec_weight;for (int i = 0; i < imgWeight.cols; i++){for (int j = 0; j < imgWeight.rows; j++){vec_filter.push_back(img_filter.ptr<float>(j)[i]);vec_weight.push_back(imgWeight.ptr<float>(j)[i]);}}//计算梯度评分float sum = 0;for (int i = 0; i < vec_weight.size(); i++){sum += vec_weight[i] * vec_filter[i];}sum = (float)sum / (float)(vec_weight.size() - 1);float score_gradient = sum >= 0 ? sum : 0;//创建强度滤波器核cv::Mat kernelA, kernelB, kernelC, kernelD;createkernel(atan2(cornersEdge[0].y, cornersEdge[0].x), atan2(cornersEdge[1].y, cornersEdge[1].x), c[0], kernelA, kernelB, kernelC, kernelD);//1.1 产生四种核//棋盘的反应float a1, a2, b1, b2;a1 = kernelA.dot(img);a2 = kernelB.dot(img);b1 = kernelC.dot(img);b2 = kernelD.dot(img);float mu = (a1 + a2 + b1 + b2) / 4;float score_a = (a1 - mu) >= (a2 - mu) ? (a2 - mu) : (a1 - mu);float score_b = (mu - b1) >= (mu - b2) ? (mu - b2) : (mu - b1);float score_1 = score_a >= score_b ? score_b : score_a;score_b = (b1 - mu) >= (b2 - mu) ? (b2 - mu) : (b1 - mu);score_a = (mu - a1) >= (mu - a2) ? (mu - a2) : (mu - a1);float score_2 = score_a >= score_b ? score_b : score_a;float score_intensity = score_1 >= score_2 ? score_1 : score_2;score_intensity = score_intensity > 0.0 ? score_intensity : 0.0;score = score_gradient * score_intensity;}//分数角落void FindCorners::scoreCorners(cv::Mat img, cv::Mat imgAngle, cv::Mat imgWeight, std::vector<cv::Point2f> &cornors, std::vector<int> radius, std::vector<float> &score){//为了所有的角落for (int i = 0; i < cornors.size(); i++){//角点定位int u = cornors[i].x + 0.5;int v = cornors[i].y + 0.5;//计算转角统计@半径1std::vector<float> scores;for (int j = 0; j < radius.size(); j++){scores.push_back(0);int r = radius[j];if (u > r && u <= (img.cols - r - 1) && v > r && v <= (img.rows - r - 1)){int startX, startY, ROIwidth, ROIheight;startX = u - r;startY = v - r;ROIwidth = 2 * r + 1;ROIheight = 2 * r + 1;cv::Mat sub_img = img(cv::Rect(startX, startY, ROIwidth, ROIheight)).clone();cv::Mat sub_imgWeight = imgWeight(cv::Rect(startX, startY, ROIwidth, ROIheight)).clone();std::vector<cv::Point2f> cornersEdge;cornersEdge.push_back(cv::Point2f((float)cornersEdge1[i][0], (float)cornersEdge1[i][1]));cornersEdge.push_back(cv::Point2f((float)cornersEdge2[i][0], (float)cornersEdge2[i][1]));cornerCorrelationScore(sub_img, sub_imgWeight, cornersEdge, scores[j]);}}//取最高分score.push_back(*max_element(begin(scores), end(scores)));}}//角点检测void FindCorners::detectCorners(cv::Mat &Src, Corners &corners, float scoreThreshold, bool isDrawSrc){cv::Mat gray, imageNorm;gray = cv::Mat(Src.size(), CV_8U);if (Src.channels() == 3){//变为灰度图cvtColor(Src, gray, cv::COLOR_BGR2GRAY);}else gray = Src.clone();//对灰度图进行归一化normalize(gray, imageNorm, 0, 1, cv::NORM_MINMAX, CV_32F);//初始化初始角点cv::Mat imgCorners = cv::Mat::zeros(imageNorm.size(), CV_32F);//第一步:寻找可能是棋盘格角点的点位for (int i = 0; i < 6; i++){cv::Mat kernelA1, kernelB1, kernelC1, kernelD1;//获取4种滤波核createkernel(templateProps[i].x, templateProps[i].y, radius[i / 2], kernelA1, kernelB1, kernelC1, kernelD1);cv::Mat imgCornerA1(imageNorm.size(), CV_32F);cv::Mat imgCornerB1(imageNorm.size(), CV_32F);cv::Mat imgCornerC1(imageNorm.size(), CV_32F);cv::Mat imgCornerD1(imageNorm.size(), CV_32F);//用所产生的核对图像做卷积cv::filter2D(imageNorm, imgCornerA1, CV_32F, kernelA1);cv::filter2D(imageNorm, imgCornerB1, CV_32F, kernelB1);cv::filter2D(imageNorm, imgCornerC1, CV_32F, kernelC1);cv::filter2D(imageNorm, imgCornerD1, CV_32F, kernelD1);cv::Mat imgCornerMean(imageNorm.size(), CV_32F);imgCornerMean = (imgCornerA1 + imgCornerB1 + imgCornerC1 + imgCornerD1) / 4;cv::Mat imgCornerA(imageNorm.size(), CV_32F);cv::Mat imgCornerB(imageNorm.size(), CV_32F);cv::Mat imgCorner1(imageNorm.size(), CV_32F);cv::Mat imgCorner2(imageNorm.size(), CV_32F);getMin(imgCornerA1 - imgCornerMean, imgCornerB1 - imgCornerMean, imgCornerA);getMin(imgCornerMean - imgCornerC1, imgCornerMean - imgCornerD1, imgCornerB);getMin(imgCornerA, imgCornerB, imgCorner1);getMin(imgCornerMean - imgCornerA1, imgCornerMean - imgCornerB1, imgCornerA);getMin(imgCornerC1 - imgCornerMean, imgCornerD1 - imgCornerMean, imgCornerB);getMin(imgCornerA, imgCornerB, imgCorner2);getMax(imgCorners, imgCorner1, imgCorners);getMax(imgCorners, imgCorner2, imgCorners);//getMin(imgCornerA1, imgCornerB1, imgCornerA); getMin(imgCornerC1, imgCornerD1, imgCornerB);//getMin(imgCornerA - imgCornerMean, imgCornerMean - imgCornerB, imgCorner1);//getMin(imgCornerMean - imgCornerA, imgCornerB - imgCornerMean, imgCorner2);//getMax(imgCorners, imgCorner2, imgCorners);//获取每个像素点的得分//getMax(imgCorners, imgCorner1, imgCorners);//获取每个像素点的得分}//非极大值抑制算法进行过滤,获取棋盘格角点初步结果nonMaximumSuppression(imgCorners, cornerPoints, 0.025, 5, 3);//第二步:亚像素细分cv::Mat imageDu(gray.size(), CV_32F);cv::Mat imageDv(gray.size(), CV_32F);cv::Mat img_angle(gray.size(), CV_32F);cv::Mat img_weight(gray.size(), CV_32F);//计算图像导数,用于主轴估计getImageAngleAndWeight(gray, imageDu, imageDv, img_angle, img_weight);//亚像素细分refineCorners(cornerPoints, imageDu, imageDv, img_angle, img_weight, 10);//去掉棱角if (cornerPoints.size() > 0){for (int i = 0; i < cornerPoints.size(); i++){if (cornersEdge1[i][0] == 0 && cornersEdge2[i][0] == 0){cornerPoints[i].x = 0; cornerPoints[i].y = 0;}}}//角落 分数std::vector<float> score;scoreCorners(imageNorm, img_angle, img_weight, cornerPoints, radius, score);for (int i = 0; i < cornerPoints.size(); i++){if (score[i] > scoreThreshold){if (isDrawSrc){cv::circle(Src, cornerPoints[i], 5, CV_RGB(255, 0, 0), 2, 8);}corners.p.push_back(cornerPoints[i]);corners.v1.push_back(cv::Vec2f(cornersEdge1[i][0], cornersEdge1[i][1]));corners.v2.push_back(cv::Vec2f(cornersEdge2[i][0], cornersEdge2[i][1]));corners.score.push_back(score[i]);}}std::vector<cv::Vec2f> corners_n1(corners.p.size());for (int i = 0; i < corners_n1.size(); i++){if (corners.v1[i][0] + corners.v1[i][1] < 0.0){corners.v1[i] = -corners.v1[i];}corners_n1[i] = corners.v1[i];float flipflag = corners_n1[i][0] * corners.v2[i][0] + corners_n1[0][1] * corners.v2[i][1];if (flipflag > 0)flipflag = -1;elseflipflag = 1;corners.v2[i] = flipflag * corners.v2[i];}//写XML文件cv::Point maxLoc;cv::FileStorage fs2("test.xml", cv::FileStorage::WRITE);fs2 << "img_corners_a1" << cornerPoints;}

获取棋盘格头文件:Chessboards.h

#ifndef CHESSBOARDS_H#define CHESSBOARDS_H#include "HeadStruct.h"class Chessboards{public:Chessboards();~Chessboards();//通过角点获取棋盘方格void chessboardsFromCorners(Corners &corners, std::vector<cv::Mat> &chessboards, float lamda = 0.5);void drawchessboard(cv::Mat &img, Corners &corners, std::vector<cv::Mat> &chessboards, std::vector<std::vector<cv::Point>> &findContours, std::vector<cv::Size> &boardSizes, const char *title = "chessboard", cv::Rect rect = cv::Rect(0, 0, 0, 0));private://初始化棋盘格cv::Mat initChessboard(Corners &corners, int idx);//寻找上下左右的邻居int directionalNeighbor(int idx, cv::Vec2f v, cv::Mat chessboard, Corners &corners, int &neighbor_idx, float &min_dist);//查看棋盘格是否是一个有用的初步猜想float chessboardEnergy(cv::Mat chessboard, Corners &corners);//副本预测void predictCorners(std::vector<cv::Vec2f> &p1, std::vector<cv::Vec2f> &p2, std::vector<cv::Vec2f> &p3, std::vector<cv::Vec2f> &pred);//生长棋盘cv::Mat growChessboard(cv::Mat chessboard, Corners &corners, int border_type);//分配最近角点void assignClosestCorners(std::vector<cv::Vec2f> &cand, std::vector<cv::Vec2f> &pred, std::vector<int> &idx);private:cv::Mat chessboard;float m_lamda;};#endif

获取棋盘对应的cpp文件:Chessboards.cpp

#include "Chessboards.h"Chessboards::Chessboards(){}Chessboards::~Chessboards(){}inline float distv(cv::Vec2f a, cv::Vec2f b){return std::sqrt((a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]));}inline float mean_l(std::vector<float> &resultSet){double sum = std::accumulate(std::begin(resultSet), std::end(resultSet), 0.0);double mean = sum / resultSet.size(); return mean;}inline float stdev_l(std::vector<float> &resultSet, float &mean){double accum = 0.0;mean = mean_l(resultSet);std::for_each(std::begin(resultSet), std::end(resultSet), [&](const double d) {accum += (d - mean) * (d - mean);});double stdev = sqrt(accum / (resultSet.size() - 1));return stdev;}inline float stdevmean(std::vector<float> &resultSet){float stdvalue, meanvalue;stdvalue = stdev_l(resultSet, meanvalue);return stdvalue / meanvalue;}//寻找上下左右的邻居int Chessboards::directionalNeighbor(int idx, cv::Vec2f v, cv::Mat chessboard, Corners &corners, int &neighbor_idx, float &min_dist){//当前未使用的相邻元素列表std::vector<int> unused(corners.p.size());for (int i = 0; i < unused.size(); i++){unused[i] = i;}for (int i = 0; i < chessboard.rows; i++){for (int j = 0; j < chessboard.cols; j++){int xy = chessboard.at<int>(i, j);if (xy >= 0){unused[xy] = -1;}}}int nsize = unused.size();for (int i = 0; i < nsize;){if (unused[i] < 0){std::vector<int>::iterator iter = unused.begin() + i;unused.erase(iter);i = 0;nsize = unused.size();continue;}i++;}std::vector<float> dist_edge;std::vector<float> dist_point;cv::Vec2f idxp = cv::Vec2f(corners.p[idx].x, corners.p[idx].y);//到未使用角落的方向和距离for (int i = 0; i < unused.size(); i++){int ind = unused[i];cv::Vec2f diri = cv::Vec2f(corners.p[ind].x, corners.p[ind].y) - idxp;float disti = diri[0] * v[0] + diri[1] * v[1];cv::Vec2f de = diri - disti * v;//存储未使用角落点到该点梯度向量v方向的垂直距离dist_edge.push_back(distv(de, cv::Vec2f(0, 0)));//存储未使用角落点到点的欧式距离dist_point.push_back(disti);}//找到最好的邻居int min_idx = 0;min_dist = std::numeric_limits<float>::max();//min_dist = dist_point[0] + 5 * dist_edge[0];for (int i = 0; i < dist_point.size(); i++){if (dist_point[i] > 0){float m = dist_point[i] + 5 * dist_edge[i];if (m < min_dist){min_dist = m;min_idx = i;}}}neighbor_idx = unused[min_idx];return 1;}//初始化棋盘格cv::Mat Chessboards::initChessboard(Corners &corners, int idx){//如果没有足够的角点则返回if (corners.p.size() < 9){std::cout << "没有足够的角点" << std::endl;chessboard.release();return chessboard;}//假设棋盘格的大小为3x3chessboard = -1 * cv::Mat::ones(3, 3, CV_32S);//提取特征指标和方向(中心元素)cv::Vec2f v1 = corners.v1[idx];cv::Vec2f v2 = corners.v2[idx];chessboard.at<int>(1, 1) = idx;//发现左 / 右 / 上 / 下的邻居std::vector<float> dist1(2), dist2(6);directionalNeighbor(idx, +1 * v1, chessboard, corners, chessboard.at<int>(1, 2), dist1[0]);directionalNeighbor(idx, -1 * v1, chessboard, corners, chessboard.at<int>(1, 0), dist1[1]);directionalNeighbor(idx, +1 * v2, chessboard, corners, chessboard.at<int>(2, 1), dist2[0]);directionalNeighbor(idx, -1 * v2, chessboard, corners, chessboard.at<int>(0, 1), dist2[1]);//发现左上 /右上 /左下 /右下的邻居directionalNeighbor(chessboard.at<int>(1, 0), -1 * v2, chessboard, corners, chessboard.at<int>(0, 0), dist2[2]);directionalNeighbor(chessboard.at<int>(1, 0), +1 * v2, chessboard, corners, chessboard.at<int>(2, 0), dist2[3]);directionalNeighbor(chessboard.at<int>(1, 2), -1 * v2, chessboard, corners, chessboard.at<int>(0, 2), dist2[4]);directionalNeighbor(chessboard.at<int>(1, 2), +1 * v2, chessboard, corners, chessboard.at<int>(2, 2), dist2[5]);//初始化必须均匀分布bool sigood = false;sigood = sigood || (dist1[0] < 0) || (dist1[1] < 0);sigood = sigood || (dist2[0] < 0) || (dist2[1] < 0) || (dist2[2] < 0) || (dist2[3] < 0) || (dist2[4] < 0) || (dist2[5] < 0);sigood = sigood || (stdevmean(dist1) > 0.3) || (stdevmean(dist2) > 0.3);if (sigood == true){chessboard.release();return chessboard;}return chessboard;}//查看棋盘格是否是一个有用的初步猜想float Chessboards::chessboardEnergy(cv::Mat chessboard, Corners &corners){float lamda = m_lamda;//能量:角的数量float E_corners = -1 * chessboard.size().area();//能量结构float E_structure = 0;//遍历行for (int i = 0; i < chessboard.rows; i++){for (int j = 0; j < chessboard.cols - 2; j++){std::vector<cv::Vec2f> x;float E_structure0 = 0;for (int k = j; k <= j + 2; k++){int n = chessboard.at<int>(i, k);x.push_back(corners.p[n]);}E_structure0 = distv(x[0] + x[2] - 2 * x[1], cv::Vec2f(0, 0));float tv = distv(x[0] - x[2], cv::Vec2f(0, 0));E_structure0 = E_structure0 / tv;if (E_structure < E_structure0)E_structure = E_structure0;}}//遍历列for (int i = 0; i < chessboard.cols; i++){for (int j = 0; j < chessboard.rows - 2; j++){std::vector<cv::Vec2f> x;float E_structure0 = 0;for (int k = j; k <= j + 2; k++){int n = chessboard.at<int>(k, i);x.push_back(corners.p[n]);}E_structure0 = distv(x[0] + x[2] - 2 * x[1], cv::Vec2f(0, 0));float tv = distv(x[0] - x[2], cv::Vec2f(0, 0));E_structure0 = E_structure0 / tv;if (E_structure < E_structure0)E_structure = E_structure0;}}//最终能量float E = E_corners + lamda * chessboard.size().area() * E_structure;return E;}//副本预测(新)void Chessboards::predictCorners(std::vector<cv::Vec2f> &p1, std::vector<cv::Vec2f> &p2,std::vector<cv::Vec2f> &p3, std::vector<cv::Vec2f> &pred){cv::Vec2f v1, v2;float a1, a2, a3;float s1, s2, s3;pred.resize(p1.size());for (int i = 0; i < p1.size(); i++){//计算向量v1 = p2[i] - p1[i];v2 = p3[i] - p2[i];//预测角度a1 = atan2(v1[1], v1[0]);a2 = atan2(v1[1], v1[0]);a3 = 2.0 * a2 - a1;//预测角度s1 = distv(v1, cv::Vec2f(0, 0));s2 = distv(v2, cv::Vec2f(0, 0));s3 = 2 * s2 - s1;//预测p3(在极端情况下,因子0.75保证了这一点)//选择更接近预测的失真(omnicam))pred[i] = p3[i] + 0.75 * s3 * cv::Vec2f(cos(a3), sin(a3));}}//分配最近的角点void Chessboards::assignClosestCorners(std::vector<cv::Vec2f> &cand, std::vector<cv::Vec2f> &pred, std::vector<int> &idx){//如果没有足够的候选项,则返回错误if (cand.size() < pred.size()){idx.resize(1);idx[0] = -1;return;}idx.resize(pred.size());//建立距离矩阵cv::Mat D = cv::Mat::zeros(cand.size(), pred.size(), CV_32FC1);float mind = FLT_MAX;for (int i = 0; i < D.cols; i++){cv::Vec2f delta;for (int j = 0; j < D.rows; j++){delta = cand[j] - pred[i];float s = distv(delta, cv::Vec2f(0, 0));D.at<float>(j, i) = s;if (s < mind){mind = s;}}}//贪婪法寻找最近的角落for (int k = 0; k < pred.size(); k++){bool isbreak = false;for (int i = 0; i < D.rows; i++){for (int j = 0; j < D.cols; j++){if (fabs(D.at<float>(i, j) - mind) < 10e-10){idx[j] = i;for (int m = 0; m < D.cols; m++){D.at<float>(i, m) = FLT_MAX;}for (int m = 0; m < D.rows; m++){D.at<float>(m, j) = FLT_MAX;}isbreak = true;break;}}if (isbreak == true)break;}mind = FLT_MAX;for (int i = 0; i < D.rows; i++){for (int j = 0; j < D.cols; j++){if (D.at<float>(i, j) < mind){mind = D.at<float>(i, j);}}}}}//生长棋盘cv::Mat Chessboards::growChessboard(cv::Mat chessboard, Corners& corners, int border_type){//如果没有任何棋盘,请立即返回if (chessboard.empty() == true){return chessboard;}//提取特征的位置std::vector<cv::Point2f> p = corners.p;//未使用的特性元素列表std::vector<int> unused(p.size());for (int i = 0; i < unused.size(); i++){unused[i] = i;}for (int i = 0; i < chessboard.rows; i++){for (int j = 0; j < chessboard.cols; j++){int xy = chessboard.at<int>(i, j);if (xy >= 0){unused[xy] = -1;}}}int nsize = unused.size();for (int i = 0; i < nsize; ){if (unused[i] < 0){std::vector<int>::iterator iter = unused.begin() + i;unused.erase(iter);i = 0;nsize = unused.size();continue;}i++;}//来自闲置角落的候选人std::vector<cv::Vec2f> cand;for (int i = 0; i < unused.size(); i++){cand.push_back(corners.p[unused[i]]);}//切换类型1..4cv::Mat chesstemp;switch (border_type){case 0:{std::vector<cv::Vec2f> p1, p2, p3, pred;for (int row = 0; row < chessboard.rows; row++){for (int col = 0; col < chessboard.cols; col++){if (col == chessboard.cols - 3){int ij = chessboard.at<int>(row, col);p1.push_back(cv::Vec2f(p[ij]));}if (col == chessboard.cols - 2){int ij = chessboard.at<int>(row, col);p2.push_back(cv::Vec2f(p[ij]));}if (col == chessboard.cols - 1){int ij = chessboard.at<int>(row, col);p3.push_back(cv::Vec2f(p[ij]));}}}std::vector<int> idx;predictCorners(p1, p2, p3, pred);assignClosestCorners(cand, pred, idx);if (idx[0] < 0){return chessboard;}cv::copyMakeBorder(chessboard, chesstemp, 0, 0, 0, 1, 0, 0);for (int i = 0; i < chesstemp.rows; i++){chesstemp.at<int>(i, chesstemp.cols - 1) = unused[idx[i]];}chessboard = chesstemp.clone();break;}case 2:{std::vector<cv::Vec2f> p1, p2, p3, pred;for (int row = 0; row < chessboard.rows; row++){for (int col = 0; col < chessboard.cols; col++){if (col == 2){int ij = chessboard.at<int>(row, col);p1.push_back(cv::Vec2f(p[ij]));}if (col == 1){int ij = chessboard.at<int>(row, col);p2.push_back(cv::Vec2f(p[ij]));}if (col == 0){int ij = chessboard.at<int>(row, col);p3.push_back(cv::Vec2f(p[ij]));}}}std::vector<int> idx;predictCorners(p1, p2, p3, pred);assignClosestCorners(cand, pred, idx);if (idx[0] < 0){return chessboard;}cv::copyMakeBorder(chessboard, chesstemp, 0, 0, 1, 0, 0, 0);for (int i = 0; i < chesstemp.rows; i++){chesstemp.at<int>(i, 0) = unused[idx[i]];}chessboard = chesstemp.clone();break;}case 3:{std::vector<cv::Vec2f> p1, p2, p3, pred;for (int row = 0; row < chessboard.rows; row++){for (int col = 0; col < chessboard.cols; col++){if (row == 2){int ij = chessboard.at<int>(row, col);p1.push_back(cv::Vec2f(p[ij]));}if (row == 1){int ij = chessboard.at<int>(row, col);p2.push_back(cv::Vec2f(p[ij]));}if (row == 0){int ij = chessboard.at<int>(row, col);p3.push_back(cv::Vec2f(p[ij]));}}}std::vector<int> idx;predictCorners(p1, p2, p3, pred);assignClosestCorners(cand, pred, idx);if (idx[0] < 0){return chessboard;}cv::copyMakeBorder(chessboard, chesstemp, 1, 0, 0, 0, 0, 0);for (int i = 0; i < chesstemp.cols; i++){chesstemp.at<int>(0, i) = unused[idx[i]];}chessboard = chesstemp.clone();break;}default:break;}return chessboard;}//通过角点获取棋盘方格void Chessboards::chessboardsFromCorners(Corners &corners, std::vector<cv::Mat> &chessboards, float lamda){m_lamda = lamda;//遍历所有角点for (int i = 0; i < corners.p.size(); i++){//从种子i开始创建3x3个棋盘cv::Mat csbd = initChessboard(corners, i);//检查一下这是否是一个有用的初步猜测if (csbd.empty() == true || chessboardEnergy(csbd, corners) > 0){continue;}int s = 0;//尝试生长棋盘while (true){s++;//计算当前能量float energy = chessboardEnergy(chessboard, corners);//计算建议和能量std::vector<cv::Mat> proposal(4);std::vector<float> p_energy(4);for (int j = 0; j < 4; j++){proposal[j] = growChessboard(chessboard, corners, j);p_energy[j] = chessboardEnergy(proposal[j], corners);}//寻找最好的方案float min_value = p_energy[0];int min_idx = 0;for (int i0 = 1; i0 < p_energy.size(); i0++){if (min_value > p_energy[i0]){min_value = p_energy[i0];min_idx = i0;}}//如果能量减少,就接受最好的建议cv::Mat chessboardt;if (p_energy[min_idx] < energy){chessboardt = proposal[min_idx];chessboard = chessboardt.clone();}else{break;}}//如果棋盘有低能量(对应高质量)if (chessboardEnergy(chessboard, corners) < -10){//检查新的棋盘方案是否与现有的棋盘重叠cv::Mat overlap = cv::Mat::zeros(cv::Size(2, chessboards.size()), CV_32FC1);for (int j = 0; j < chessboards.size(); j++){bool isbreak = false;for (int k = 0; k < chessboards[j].size().area(); k++){int refv = chessboards[j].at<int>(k / chessboards[j].cols, k % chessboards[j].cols);for (int l = 0; l < chessboard.size().area(); l++){int isv = chessboard.at<int>(l / chessboard.cols, l % chessboard.cols);if (refv == isv){overlap.at<float>(j, 0) = 1.0;float s = chessboardEnergy(chessboards[j], corners);overlap.at<float>(j, 1) = s;isbreak = true;break;}}}}//添加棋盘(并在必要时替换重叠部分)bool isoverlap = false;for (int i0 = 0; i0 < overlap.rows; i0++){if (overlap.empty() == false){if (fabs(overlap.at<float>(i0, 0)) > 0.000001)// ==1{isoverlap = true;break;}}}if (isoverlap == false){chessboards.push_back(chessboard);}else{bool flagpush = true;std::vector<bool> flagerase(overlap.rows);for (int m = 0; m < flagerase.size(); m++){flagerase[m] = false;}float ce = chessboardEnergy(chessboard, corners);for (int i1 = 0; i1 < overlap.rows; i1++){if (fabs(overlap.at<float>(i1, 0)) > 0.0001){bool isb1 = overlap.at<float>(i1, 1) > ce;int a = int(overlap.at<float>(i1, 1) * 1000);int b = int(ce * 1000);bool isb2 = a > b;if (isb1 != isb2)std::cout << "寻找bug" << std::endl;if (isb2){flagerase[i1] = true;}else{flagpush = false;}}}if (flagpush == true){for (int i1 = 0; i1 < chessboards.size();){std::vector<cv::Mat>::iterator it = chessboards.begin() + i1;std::vector<bool>::iterator it1 = flagerase.begin() + i1;if (*it1 == true){chessboards.erase(it);flagerase.erase(it1);i1 = 0;}i1++;}chessboards.push_back(chessboard);}}}}}//绘制棋盘格void Chessboards::drawchessboard(cv::Mat &img, Corners &corners, std::vector<cv::Mat> &chessboards, std::vector<std::vector<cv::Point>> &findContours, std::vector<cv::Size> &boardSizes, const char *title, cv::Rect rect){cv::RNG rng(0xFFFFFFFF);if (img.channels() < 3)cv::cvtColor(img, img, CV_GRAY2BGR);int n = 8;if (img.rows < 2000 || img.cols < 2000){n = 2;}std::vector<cv::Point> findContour;cv::Size boardSize;for (int k = 0; k < chessboards.size(); k++){cv::Scalar s(rng.uniform(0.0, 1.0), rng.uniform(0.0, 1.0), rng.uniform(0.0, 1.0));s = s * 255;for (int i = 0; i < chessboards[k].rows; i++){for (int j = 0; j < chessboards[k].cols; j++){int d = chessboards[k].at<int>(i, j);cv::circle(img, cv::Point2f(corners.p[d].x + rect.x, corners.p[d].y + rect.y), n, s, n);findContour.push_back(cv::Point2f(corners.p[d].x + rect.x, corners.p[d].y + rect.y));}}findContours.push_back(findContour);boardSize.width = chessboards[k].cols;boardSize.height = chessboards[k].rows;boardSizes.push_back(boardSize);findContour.clear();}}

获取要导入的头文件信息:HeadStruct.h

#pragma once#include "opencv2/opencv.hpp"#include <iostream>#include <algorithm>#include <limits>#include <numeric>#include <time.h>//导入OpenCV - lib库#ifdef _DEBUG#pragma comment(lib,"opencv_world342d.lib")#else#pragma comment(lib,"opencv_world342.lib")#endiftypedef struct Corners{std::vector<cv::Point2f> p;std::vector<cv::Vec2f> v1;std::vector<cv::Vec2f> v2;std::vector<cv::Vec2f> score;};

主程序:main.cpp

#include "FindCorners.h "#include "Chessboards.h"int main(int argc, char* argv[]){//读XML文件/*cv::Mat kernels;cv::FileStorage fs2("templateA1.xml", cv::FileStorage::READ);fs2["templateA1"] >> kernels;std::cout << "kernels: " << kernels << std::endl;*///读入原始图像cv::Mat src;std::cout << "This is a demo for Parking slot detection." << std::endl;std::cout << "开始读入图像..." << std::endl;//加载图像路径std::string filename = ".\\data\\00.png";src = cv::imread(filename, 1);if (src.empty()){printf("Cannot read image file: %s\n", filename.c_str());return -1;}std::cout << "读入图像成功" << std::endl;std::cout << "开始寻找可能为棋盘格的角点" << std::endl;//存储找到可能为棋盘格的角点Corners corners;FindCorners detectCorner(src);detectCorner.detectCorners(src, corners, 0.01, false);std::cout << "开始获取棋盘格并绘制角点信息" << std::endl;//获取棋盘格并绘制其角点信息Chessboards chessboard;std::vector<cv::Size> boardSizes;std::vector<std::vector<cv::Point>> findContours;std::vector<cv::Mat> chessboards;chessboard.chessboardsFromCorners(corners, chessboards, 1);chessboard.drawchessboard(src, corners, chessboards, findContours, boardSizes, "cd");//打印棋盘格角点信息std::cout << "打印棋盘格角点个数:" << std::endl;for (int i = 0; i < boardSizes.size(); i++){std::cout << "(" << boardSizes[i].width << ", " << boardSizes[i].height << ")" << std::endl;}cv::imwrite("棋盘格效果图.png", src);return 1;}

效果展示:

原图:

效果图:

基于生长的棋盘格角点检测法相关原理可参考此博客或上述论文。在此提醒,该算法计算时间很长,建议在release模式下运行。当然,如果你可以提升算法执行速度,建议读透后实现嘿嘿😂。

我在自己的项目上稍作修改了Opencv中棋盘格角点检测中的源码,使其更适应于我自己项目的图片,且可以去掉一部分不需要的程序加快代码的执行速度。若你们也有此项目的开发,可以自己尝试修改程序使其更适用于你们自己的棋盘格图片哦。在此,预祝你们修改成功。🎈🎈🎈

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。