【opencv】squares.cpp 检测图像中的方块形状

4ab55123362033eadf4fa1d0f002f75d.png

4ca8e5175fff50b680849eab186facfb.png

5d5d19d06c2005162ebdc4462d1eedb3.png

adf1ebbd316fba5f85351a494fadb261.png

e42ac9b809b801b06b4891c63a136adb.png

08c2b74601a297b3f0cddb769e6f6dde.png

b11d3bd310eefa34b1e2e336401ec831.png

// 这是一个名为“方块检测”的程序。
// 它连续加载多个图像并尝试在每个图像中找到方块


#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边缘检测和轮廓查找,通过轮廓近似和几何属性分析来识别形状为方块的轮廓。在识别出的方块轮廓上画线,并显示结果图像。如果用户提供命令行参数,程序也会尝试读取指定的图像文件进行方块检测。

上一篇:企业计算机服务器中了helper勒索病毒怎么办?Helper勒索病毒解密流程步骤


下一篇:归一化平面和像素坐标系