cpp
// 这是一个名为"方块检测"的程序。
// 它连续加载多个图像并尝试在每个图像中找到方块
#include "opencv2/core.hpp" // 包含OpenCV核心头文件
#include "opencv2/imgproc.hpp" // 包含OpenCV图像处理头文件
#include "opencv2/imgcodecs.hpp" // 包含OpenCV图像编解码头文件
#include "opencv2/highgui.hpp" // 包含OpenCV高级图形界面头文件
#include <iostream> // 包含输入输出流头文件
using namespace cv; // 使用OpenCV命名空间
using namespace std; // 使用标准命名空间
// 帮助函数:输出程序的用法说明
static void help(const char* programName)
{
cout <<
"\nA program using pyramid scaling, Canny, contours and contour simplification\n"
"to find squares in a list of images (pic1-6.png)\n"
"Returns sequence of squares detected on the image.\n"
"Call:\n"
"./" << programName << " [file_name (optional)]\n"
"Using OpenCV version " << CV_VERSION << "\n" << endl;
}
// 全局变量定义
int thresh = 50, N = 11; // 阈值和控制次数
const char* wndname = "Square Detection Demo"; // 窗口名称
// 辅助函数:寻找角度的余弦值
// 通过pt0->pt1和pt0->pt2两向量
static double angle( Point pt1, Point pt2, Point pt0 )
{
double dx1 = pt1.x - pt0.x; // 计算向量pt0->pt1的x分量
double dy1 = pt1.y - pt0.y; // 计算向量pt0->pt1的y分量
double dx2 = pt2.x - pt0.x; // 计算向量pt0->pt2的x分量
double dy2 = pt2.y - pt0.y; // 计算向量pt0->pt2的y分量
// 返回两向量的余弦值
return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}
// 函数返回在图像上检测到的方块序列
static void findSquares( const Mat& image, vector<vector<Point> >& squares )
{
squares.clear(); // 清空方块序列
Mat pyr, timg, gray0(image.size(), CV_8U), gray;
// 通过降采样和上采样图像来过滤噪声
pyrDown(image, pyr, Size(image.cols/2, image.rows/2));
pyrUp(pyr, timg, image.size());
vector<vector<Point> > contours;
// 在图像的每个颜色平面中寻找方块
for( int c = 0; c < 3; c++ )
{
int ch[] = {c, 0};
mixChannels(&timg, 1, &gray0, 1, ch, 1);
// 尝试多个阈值等级
for( int l = 0; l < N; l++ )
{
// 使用Canny算子代替阈值为零的情况
// Canny有助于捕捉到有渐变阴影的方块
if( l == 0 )
{
// 应用Canny算子。上阈值由滑块决定
// 下阈值设为0(这强制合并边缘)
Canny(gray0, gray, 0, thresh, 5);
// 扩张Canny算子的输出结果,以移除潜在的
// 边缘片段之间的空洞
dilate(gray, gray, Mat(), Point(-1,-1));
}
else
{
// 如果l不为0,则应用阈值处理
// tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
gray = gray0 >= (l+1)*255/N;
}
// 查找轮廓并将它们全部存储为一个列表
findContours(gray, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
vector<Point> approx;
// 检测每个轮廓
for( size_t i = 0; i < contours.size(); i++ )
{
// 使用与轮廓周长成比例的精度值来近似轮廓
approxPolyDP(contours[i], approx, arcLength(contours[i], true)*0.02, true);
// 方块轮廓在近似后应该具有4个顶点
// 并且要具有相对较大的面积(以过滤噪声轮廓)
// 还要求轮廓是凸的。
// 注意:使用面积绝对值是因为面积可能是正或负。
// 这与轮廓方向一致
if( approx.size() == 4 &&
fabs(contourArea(approx)) > 1000 &&
isContourConvex(approx) )
{
double maxCosine = 0;
for( int j = 2; j < 5; j++ )
{
// 查找连接边之间角度的最大余弦值
double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
maxCosine = MAX(maxCosine, cosine);
}
// 如果所有角的余弦值都很小
// (所有角接近90度),则将四边形的顶点
// 写入结果序列
if( maxCosine < 0.3 )
squares.push_back(approx);
}
}
}
}
}
// 主函数
int main(int argc, char** argv)
{
const char* names[] = { "pic1.png", "pic2.png", "pic3.png",
"pic4.png", "pic5.png", "pic6.png", 0 }; // 图片文件名列表
help(argv[0]); // 输出帮助信息
if( argc > 1) // 如果有命令行参数传入
{
names[0] = argv[1]; // 使用传入的文件名
names[1] = 0; // 结束符
}
// 对列表中的每个文件名进行操作
for( int i = 0; names[i] != 0; i++ )
{
string filename = samples::findFile(names[i]); // 查找文件
Mat image = imread(filename, IMREAD_COLOR); // 读取图像
if( image.empty() ) // 如果读取失败
{
cout << "Couldn't load " << filename << endl; // 输出错误信息
continue; // 继续下一个文件名
}
vector<vector<Point> > squares; // 存放检测到的方块
findSquares(image, squares); // 执行方块检测
// 绘制检测到的方块
polylines(image, squares, true, Scalar(0, 255, 0), 3, LINE_AA);
imshow(wndname, image); // 显示图像
int c = waitKey(); // 等待按键
if( c == 27 ) // 如果按键为ESC
break; // 退出循环
}
return 0; // 程序结束
}
该代码的功能是使用OpenCV库对一系列图像进行处理,以检测图像中的方块形状。程序首先通过降采样和上采样去噪声,然后对每个颜色通道应用Canny边缘检测和轮廓查找,通过轮廓近似和几何属性分析来识别形状为方块的轮廓。在识别出的方块轮廓上画线,并显示结果图像。如果用户提供命令行参数,程序也会尝试读取指定的图像文件进行方块检测。