参考官网文档:https://docs.opencv.org/4.1.0/index.html
在此注意:我上面给出来的棋盘格与正规的棋盘格有所不同,它周边的背景是黑色的,而正规的棋盘格其周边是白色的。如下图所示:
因此,对于黑色背景的棋盘格,在数角点时要内缩两个角点进行计数,而对于白色背景的棋盘格,只需要在数角点时内缩一个角点进行计数即可。如上图,白色背景水平方向角点数为 16 ,竖直方向角点数为 10,而黑色背景(我最上面给出的图片)水平方向角点数为 27,竖直方向角点数为 17,若不理解的可以自己数一下。实例代码如下:
//Opencv头文件
#include
#include
#include
#include
//导入Opencv库
#ifdef _DEBUG
#pragma comment(lib,"opencv_world342d.lib")
#else
#pragma comment(lib,"opencv_world342.lib")
#endif
int 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
#include
#include
#include
#include
#include
//导入Opencv库
#ifdef _DEBUG
#pragma comment(lib,"opencv_world342d.lib")
#else
#pragma comment(lib,"opencv_world342.lib")
#endif
double 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;
}
测试图片:
未经处理直接进行角点检测:
经以上处理后的图像:
处理后的图像角点检测:
那为什么这样处理就可以达到和正规棋盘格一样进行相同行为的检测呢?
Opencv中棋盘格角点检测的源码是开源的,可以在calibinit.cpp文件中找到。这边我将我自己整理的代码贴出来(基于Opencv4.5.5最新版)。如果你需要其最原始版的,可基于下图所示的位置获取。
棋盘格检测的头文件:ChessBoardDetector.h
#pragma once
#include
#include
#include
#include
#include
#include
#define CALIB_CB_ADAPTIVE_THRESH 1
#define CALIB_CB_NORMALIZE_IMAGE 2
#define CALIB_CB_FILTER_QUADS 4
#define CALIB_CB_FAST_CHECK 8
//用于存储四边形角点信息 + 其父轮廓的ID
struct QuadCountour
{
//四个角点信息(顺时针存放)
cv::Point pt[4];
//四边形的父轮廓ID
int 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;
//四边形组ID
int 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)
{
//寻找对应角点ID
int 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 = 1
int 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 = 0
int 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 = 2
int 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 = 1
orderQuad(*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_size
for (; 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, ¢ers[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 = 4
int next_j = (j + 1) & 3;
ChessBoardCorner *a = q->corners[j], *b = q->corners[next_j];
//标记所属的内部角:
//有一个邻居的四边形为 ROW1 = 1000000
//有两个邻居的四边形为 ROW2 = 2000000
//有三个或四个邻居的四边形为 ROW3 = 3000000
int 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;
else
right = 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;
else
goto 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 == 假设为27
int 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 + 1
template<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的像素转化为0
img = (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;
}
//图案的宽度和高度都应该大于2
if (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")
#endif
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);
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
//导入Opencv库
#ifdef _DEBUG
#pragma comment(lib,"opencv_world342d.lib")
#else
#pragma comment(lib,"opencv_world342.lib")
#endif
double 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 图片:
可以看出,原先周边黑色方块区域是连接在一起的,经处理后,被分割成不同的周边黑色小方格区域,使其在进行第一步操作时可以将这些黑色方块区域捕获到,在后续的处理过程中,便依据这些捕获的方块进行角点获取。若还不理解的,看下图:
最终效果图如下:
以上程序都需要已知棋盘格角点后再进行角点检测,那有没有不需要输入,自适应的获取棋盘格角点位置信息呢?
代码来源于:http://www.cvlibs.net/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 * sin
float 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(i);
// float* dataDu = imgDu.ptr(i);
// float* dataAngle = imgAngle.ptr(i);
// float* dataWeight = imgWeight.ptr(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;
else
break;
}
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;
//鲁棒定向细化1
float 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);
}
}
//鲁棒定向细化2
float 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;
//计算转角统计@半径1
std::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;
else
flipflag = 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;
}
//假设棋盘格的大小为3x3
chessboard = -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..4
cv::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
#include
#include
#include
#include
//导入OpenCV - lib库
#ifdef _DEBUG
#pragma comment(lib,"opencv_world342d.lib")
#else
#pragma comment(lib,"opencv_world342.lib")
#endif
typedef 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中棋盘格角点检测中的源码,使其更适应于我自己项目的图片,且可以去掉一部分不需要的程序加快代码的执行速度。若你们也有此项目的开发,可以自己尝试修改程序使其更适用于你们自己的棋盘格图片哦。在此,预祝你们修改成功。